One-hot function in PyTorch

本文介绍并展示了四种在PyTorch中实现One-Hot编码的方法:使用scatter_函数、index_select()函数、Embedding模块以及自定义One_Hot模块。这些方法为不同场景下的One-Hot编码提供了灵活的选择。

One-hot function implementation in PyTorch


Method1: use scatter_ function

labels = [0, 1, 4, 7, 3, 2]
one_hot = torch.zeros(6, 8).scatter_(dim = 1, index = labels, value = 1)

Method2: use index_select() funtion

labels = [0, 1, 4, 7, 3, 2]
index = torch.eye(8)
one_hot = torch.index_select(index, dim = 0, index = labels)

Method3: use Embedding module

emb = nn.Embedding(8, 8)
emb.weight.data = torch.eye(8)

then we can get

emb(Variable(torch.LongTensor([1, 2], [3, 4])))
Variable containing:
(0,.,.) = 
0 1 0 0 0 0 0 0 
0 0 1 0 0 0 0 0
(1 ,.,.) =
0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0

Method4: create a module

class One_Hot(nn.Module):
    def __init__(self, depth):
        super(One_Hot,self).__init__()
        self.depth = depth
        self.ones = torch.sparse.torch.eye(depth)
    def forward(self, X_in):
        X_in = X_in.long()
        return Variable(self.ones.index_select(0,X_in.data))
    def __repr__(self):
        return self.__class__.__name__ + "({})".format(self.depth)
<think>我们有一个函数sampler,它根据概率分布prob生成n个样本。原代码使用PyTorch实现,现在需要修改为TensorFlow图模式运行,并且将最后的one-hot编码改为二进制表示(例如,6转换为'110',但注意输出应该是数值形式,可能是整数列表或二进制字符串?这里需要明确)。 要求: 1. 在TensorFlow图模式下运行(使用tf.function)。 2. 将最后的one-hot编码改为二进制表示。注意:二进制表示可能有多种形式,这里我们假设要求将每个样本(整数)转换为一个固定长度的二进制字符串对应的整数?或者直接输出二进制字符串?但题目说“转化为二进制,比如6转换为110”,这个110是二进制,所以可以有两种理解: a) 输出字符串列表,每个字符串是二进制的表示(例如['110']) b) 输出整数,但用十进制的形式表示二进制的值(例如6的二进制是110,但110作为十进制数是110,这通常不是我们想要的) c) 或者输出一个整数列表,每个整数是0或1,表示二进制的每一位(例如[1,1,0]) 但是,原函数返回的是one-hot编码的张量,形状为[n, M]。现在改为二进制,我们需要考虑如何表示二进制。由于样本的符号是0到M-1的整数,我们可以将其转换为二进制位表示。例如,如果M=8,那么每个符号可以用3位二进制表示(因为2^3=8>=M)。因此,我们可以将每个整数转换为一个固定长度的二进制位向量(即每个样本用一个包含log2(M)上取整个二进制位的向量表示)。注意:这里M是类别数,所以二进制位数b = ceil(log2(M))。 然而,题目要求是“转化为二进制,比如6转换为110”。注意6是十进制数,110是二进制表示(相当于十进制的6)。但这里我们不可能输出一个字符串,因为在图模式下需要是张量。所以,我们有两种选择: 1) 输出一个形状为(n, b)的张量,每个样本用b个二进制位(0或1)表示。例如,6在b=3时表示为[1,1,0]。 2) 输出一个长度为n的字符串张量,每个元素是二进制字符串(如"110")。但字符串操作在图模式下可能复杂,且后续使用不便。 根据上下文,通常我们会选择第一种:将每个整数转换为固定长度的二进制位向量(即每个位是一个独立的维度)。这样,输出是一个二维张量,形状为(n, b),其中b是需要的二进制位数。 因此,我们需要: - 计算b:b = ceil(log2(M)),注意M是类别数(即prob向量的长度)。 - 将每个整数符号(0到M-1)转换为一个长度为b的二进制向量(高位在前或低位在前?通常高位在前)。 步骤: 1. 计算每个符号的精确样本数(与原来相同)。 2. 分配整数样本数(与原来相同)。 3. 生成样本列表,然后打乱(与原来相同)。 4. 将每个样本(整数)转换为二进制位向量(固定长度b)。 注意:在TensorFlow图模式下,我们需要避免使用Python列表操作,尽量使用TensorFlow操作。 修改计划: -PyTorch操作替换为TensorFlow操作。 - 避免使用Python控制流(如for循环),改用向量化操作。 - 注意:原代码中有一个循环分配剩余样本,这个循环在TensorFlow中可能需要用tf.while_loop或tf.scatter_nd等来替换,但循环次数最多为remaining(即n,可能较大),但通常remaining很小(小于M),所以我们可以用TensorFlow的循环或使用tf.numpy_function(不推荐)?或者我们可以用tf.tensor_scatter_nd_update。 然而,为了图模式兼容性,我们将尽量使用TensorFlow操作重构。 具体步骤重构: 1. 计算expected_counts = n * prob 2. int_counts = tf.math.floor(expected_counts) -> 整数部分 3. remainders = expected_counts - int_counts 4. 计算总整数部分:total_int = tf.reduce_sum(int_counts) 5. remaining = n - total_int (标量) 6. 选择remainders中最大的前remaining个索引,将对应索引的int_counts加1。 注意:在TensorFlow中,我们可以使用tf.math.top_k来获取前k个的索引,然后使用tf.tensor_scatter_nd_add来增加这些索引对应的值。 7. 生成样本列表:我们首先需要生成一个列表,其中每个符号重复int_counts[symbol]次。在TensorFlow中,我们可以用tf.repeat和tf.range来实现。 8. 打乱:使用tf.random.shuffle。 9. 转换为二进制:将每个整数转换为一个长度为b的二进制向量(由0和1组成)。注意:整数范围是0到M-1,而M<=2^b(取b=ceil(log2(M))),所以每个整数都可以用b位表示。 如何转换?我们可以用位操作: 对于每个整数x,计算: bit_i = (x >> (b-1-i)) & 1 for i in range(b) [高位在前] 或者,我们可以用tf.bitwise.right_shift和tf.bitwise.bitwise_and来逐位获取。 但是,由于需要向量化操作,我们可以用以下方法: - 创建一个形状为(b,)的移位量:shifts = tf.range(b-1, -1, -1, dtype=tf.int32) # 从高位到低位 - 将整数x扩展为形状为(n,1)的张量,然后与shifts(形状(1,b))进行广播,然后进行位操作。 然而,我们有一个整数张量samples,形状为(n,),要转换为形状为(n,b)的二进制张量。 我们可以这样: shifts = tf.cast(tf.range(b-1, -1, -1), dtype=tf.int32) # [b] 例如b=3: [2,1,0] -> 然后右移2位,1位,0位?不对,应该是左移然后取位,但这里用右移:将x右移(b-1-i)位然后与1做与运算。 实际上,我们也可以这样:将x转换为一个形状为(n,1)的张量,然后与一个形状为(1,b)的掩码(2的幂次方)进行位与运算,然后判断是否非零。但这样效率低。 另一种方法是使用tf.bitwise.bitwise_and和右移: # 创建掩码:实际上不需要,右移后与1做与运算即可。 步骤: x = tf.reshape(samples, [-1, 1]) # [n, 1] # 创建移位量:从最高位开始,移位的位数:b-1, b-2, ... 0 shifts = tf.range(b-1, -1, -1) # [b] 例如b=3: [2,1,0] # 将x右移shifts位(广播),然后取最后一位(与1) # 注意:TensorFlow的位操作要求整数类型,所以x必须是整数。 x_bits = tf.bitwise.bitwise_and(tf.bitwise.right_shift(x, shifts), 1) # 这样x_bits的形状为(n, b) 但是,注意:当x中的整数大于等于2^b时,这种方法会出错,但我们的x在[0, M-1]之间,而M<=2^b,所以x最大为M-1,小于2^b,所以不会超过b位。 10. 返回二进制张量(形状为(n,b)),数据类型为整数(0或1)或浮点数?原函数返回float,但这里二进制位我们可以用整数tf.int32或tf.float32都可以。 注意:原函数返回的是one-hot编码的浮点数张量,这里我们返回二进制位张量(整数或浮点数),根据要求,我们可以返回整数。 但是,题目要求“转化为二进制”,没有指定类型,我们可以返回整数张量(0和1)或者浮点数?为了通用性,我们可以返回浮点数,但整数也可以。这里我们返回tf.int32。 代码实现: 我们将函数用tf.function装饰,并确保内部操作都是TensorFlow操作。 注意:由于remaining是一个标量,且可能为0,我们在条件判断时使用tf.cond,但这里remaining是动态的,我们可以用tf.cond来控制是否执行分配剩余样本的操作?但原代码中,当remaining>0时执行分配。我们可以用条件语句,但图模式下,条件语句需要用tf.cond。 但是,由于remaining通常很小,我们可以用以下向量化操作: 1. 计算top_k的索引(即使remaining=0,我们也可以取k=0,但tf.top_k不支持k=0?所以需要条件分支) 我们可以这样处理:使用tf.cond根据remaining是否大于0来分支。 然而,我们也可以这样:如果remaining<=0,则跳过分配,否则分配。我们可以用条件语句,但在图模式下,我们需要使用tf.cond。 但是,在tf.function中,我们可以使用普通的if语句,因为AutoGraph会将其转换为tf.cond。要求是remaining是Tensor标量。 所以,我们可以用普通的if语句,但注意:在tf.function中,if的条件必须是Tensor(标量)或者使用AutoGraph支持的Python条件(如果n是固定值,则remaining在编译时已知?但n是传入的,可能是动态的)。所以,我们使用tf.cond。 但是,在TensorFlow中,我们也可以不改变控制流,因为AutoGraph会处理。不过,为了确保图模式兼容,我们使用tf.cond。 然而,我们也可以不用条件分支:当remaining=0时,top_k的k=0,然后我们执行一个空操作。但tf.top_k不支持k=0(会报错)。因此,我们使用条件分支。 重构分配剩余样本的步骤: 我们将使用: def true_fn(): # 选取前remaining个余数最大的索引 _, top_indices = tf.math.top_k(remainders, k=remaining) # top_indices形状为[remaining] # 创建更新:将这些索引对应的计数加1 updates = tf.ones(remaining, dtype=int_counts.dtype) # 使用tf.tensor_scatter_nd_add int_counts = tf.tensor_scatter_nd_add(int_counts, indices=tf.expand_dims(top_indices,1), updates=updates) return int_counts def false_fn(): return int_counts int_counts = tf.cond(remaining>0, true_fn, false_fn) 但是,注意:remaining是标量,且是整数,我们可以这样用。 但是,tf.cond要求两个分支返回相同的形状和类型,所以没问题。 然而,还有一种情况:remaining可能大于余数数组的长度(即M)?但remaining=n - total_int,而total_int是int_counts的和,而int_counts的长度为M,所以remaining<=n,而n可以很大,但余数数组长度是M,所以当remaining>M时,我们只能给每个索引加1?但原代码中,我们只给前remaining个索引加1,即使remaining>M,那么top_k会返回M个索引,然后我们给每个索引加1,但这样会分配M个,而我们需要分配remaining个,这样不够。所以我们需要循环分配直到分配完?但原代码没有循环,它只分配一次,而且要求remaining小于等于M(因为余数数组长度是M,而每个索引只能加一次,所以一次最多分配M个)。但remaining可能大于M吗? 注意:remaining = n - total_int,而total_int = int_counts.sum(),而int_counts = floor(expected_counts),所以expected_counts的和为n,因此: total_int = sum(floor(expected_counts[i])) remaining = n - total_int = n - sum(floor(expected_counts[i])) = sum(frac(expected_counts[i])) 而每个frac(expected_counts[i])在[0,1)区间,所以remaining < M(因为最多有M个小数部分,每个小于1,所以总和小于M)。因此remaining是一个小于M的整数(因为M个小数部分的和一定小于M)。所以remaining一定小于M,因此我们可以安全地取前remaining个。 因此,remaining<=M-1(因为小数部分总和小于M,且为整数,所以remaining<=M-1)。 所以,我们不需要担心remaining>M的情况。 现在,我们开始编写代码: 步骤: 1. 计算b = ceil(log2(M)),注意M是prob的长度。 2. 将prob转换为张量(如果还不是)。 3. 计算expected_counts = n * prob 4. int_counts = tf.floor(expected_counts) -> 然后转换为整数类型(因为计数必须是整数) 5. remainders = expected_counts - int_counts 6. total_int = tf.reduce_sum(int_counts) -> 整数 7. remaining = n - total_int -> 整数,且0<=remaining<M 8. 使用条件分支:如果remaining>0,则选取前remaining个余数最大的索引,然后更新int_counts(这些索引加1),否则不变。 9. 生成样本列表:使用tf.repeat。首先,我们有一个符号列表[0,1,...,M-1],然后每个符号重复int_counts[symbol]次。所以: symbols = tf.range(0, M, dtype=tf.int32) # [M] samples = tf.repeat(symbols, repeats=tf.cast(int_counts, tf.int32), axis=0) # 总长度为n 10. 打乱:samples = tf.random.shuffle(samples) 11. 将samples(形状(n,))转换为二进制表示(形状(n,b)): b = tf.cast(tf.math.ceil(tf.math.log(tf.cast(M, tf.float32)) / tf.math.log(2.0)), tf.int32) # 计算b # 注意:当M=0时,b=0,但M>=1(因为概率向量),所以安全。 # 创建移位量:从最高位到最低位,移位量为b-1, b-2, ... 0 shifts = tf.range(b-1, -1, -1) # [b] 例如b=3: [2,1,0] # 扩展samples: [n, 1] 和 shifts: [1, b] -> 广播后 [n, b] x = tf.expand_dims(samples, axis=1) # [n,1] # 右移然后与1 # 注意:右移要求整数类型,所以samples必须是整数。 x_bits = tf.bitwise.bitwise_and(tf.bitwise.right_shift(x, shifts), 1) # x_bits的形状为(n, b),数据类型为tf.int32 12. 返回x_bits 注意:在函数中,n和M都是动态的(可能每次调用不同),所以我们需要确保所有操作支持动态形状。 但是,在计算b时,我们使用了tf.math.ceil和log,这会产生一个整数,但图模式下需要处理动态形状。 另外,当M=1时,log(1)=0,那么b=0,然后shifts为空,这样后续操作会出错。所以我们需要处理M=1的情况:此时b=1(因为ceil(log2(1))=0? 但2^0=1,所以b=0就够了?但是0位怎么表示?所以实际上,当M=1时,我们只需要0这个值,而0用0位表示是不行的。因此,我们定义b至少为1?或者当M=1时,我们不需要二进制表示,因为只有一个类别。但为了统一,我们可以将0表示为0位,但这样输出形状为(n,0),空张量。这可以吗?或者我们定义b为max(ceil(log2(M)),1)?因为至少1位(即使M=1,我们也用1位表示,0表示为0,即[0](1位)?但0的二进制是0,1位就是0,而1的二进制是1,但M=1时,我们不会出现1(因为符号是0到0,只有0)。所以当M=1时,我们只需要0,用1位表示就是0。所以我们可以设置b = max(ceil(log2(M)), 1) ?但ceil(log2(1)) = ceil(0)=0,所以我们需要取max(0,1)=1。 因此,我们这样计算b: b = tf.cast(tf.math.ceil(tf.math.log(tf.cast(M, tf.float32)) / tf.math.log(2.0)), tf.int32) b = tf.maximum(b, 1) # 至少1位 这样,当M=1时,b=1。 但是,当M=0时,函数会出错,但概率向量不可能长度为0,所以我们不考虑。 现在,我们编写代码: 注意:在TensorFlow中,tf.bitwise.right_shift要求两个参数都是整数,且同类型。所以我们需要确保samples是整数,shifts也是整数(我们已经是了)。 另外,n可能为0,我们需要处理n=0的情况:此时samples为空,tf.repeat可以处理空列表。 我们开始写函数: 注意:函数参数prob是概率分布(一维张量),n是样本数(标量整数)。 由于在tf.function中,我们需要指定输入类型,但这里我们允许动态,所以不指定输入签名(除非必要)。 代码实现: 我们将函数命名为sampler_tf,并用tf.function装饰。 但是,由于tf.function会编译,我们确保所有操作都是TensorFlow操作。 我们按照上述步骤实现。 注意:在计算int_counts时,我们使用tf.floor,然后转换为整数类型(因为计数需要整数)。但tf.floor返回浮点数,所以我们需要转换。 修改:int_counts = tf.cast(tf.floor(expected_counts), dtype=tf.int32) 但是,在更新时(剩余分配),我们需要对int_counts进行更新,更新操作要求int_counts是变量吗?不,我们使用tf.tensor_scatter_nd_add,它接受一个张量并返回更新后的张量。 注意:tf.tensor_scatter_nd_add要求更新值的类型和原张量相同。 因此,我们在更新前将int_counts转换为整数类型,而余数remainders是浮点数,我们使用tf.math.top_k时,索引是整数,所以没问题。 下面我们写代码:</think>要将PyTorch代码转换为TensorFlow图模式运行,并将最后的one-hot编码改为二进制表示(如6→110),需要以下修改: ```python import tensorflow as tf @tf.function def sampler(prob, n): # 确保输入是TensorFlow张量 prob = tf.convert_to_tensor(prob, dtype=tf.float32) # 计算每个符号的精确样本数 expected_counts = n * prob int_counts = tf.math.floor(expected_counts) remainders = expected_counts - int_counts # 处理剩余样本 total_int = tf.reduce_sum(int_counts) remaining = tf.cast(n - total_int, tf.int32) # 按照余数大小分配剩余样本 def assign_remaining(): # 获取余数最大的前remaining个符号 _, top_indices = tf.math.top_k(remainders, k=remaining) # 创建更新操作 updates = tf.ones_like(top_indices, dtype=int_counts.dtype) # 使用scatter_nd添加剩余样本 return tf.tensor_scatter_nd_add( int_counts, indices=tf.expand_dims(top_indices, -1), updates=updates ) # 条件分支:仅在剩余样本>0时分配 int_counts = tf.cond( remaining > 0, assign_remaining, lambda: int_counts ) # 生成样本列表 symbols = tf.range(tf.shape(prob)[0], dtype=tf.int32) samples = tf.repeat(symbols, repeats=tf.cast(int_counts, tf.int32)) # 随机打乱样本 shuffled_samples = tf.random.shuffle(samples) # 计算需要的二进制位数 (ceil(log2(M))) M = tf.shape(prob)[0] num_bits = tf.cast(tf.math.ceil(tf.math.log(tf.cast(M, tf.float32)) / tf.math.log(2.0)), tf.int32) num_bits = tf.maximum(num_bits, 1) # 确保至少1位 # 将整数转换为二进制表示 def int_to_binary(x): # 创建位掩码 [num_bits] bit_mask = tf.bitwise.left_shift(1, tf.range(num_bits-1, -1, -1)) # 检查每个位 [n, num_bits] bits = tf.bitwise.bitwise_and(tf.expand_dims(x, -1), bit_mask) return tf.cast(bits != 0, tf.int32) # 应用二进制转换 binary_samples = int_to_binary(shuffled_samples) return binary_samples # 验证函数 def test_sampler(): prob = tf.constant([0.1, 0.3, 0.6], dtype=tf.float32) n = 10 samples = sampler(prob, n) print("二进制样本:\n", samples.numpy()) test_sampler() ``` **修改解释:** 1. **TensorFlow图模式兼容性**: - 使用`@tf.function`装饰器启用图模式 - 将所有PyTorch操作替换为TensorFlow等效操作 - 用`tf.cond`替换Python条件语句 - 用`tf.tensor_scatter_nd_add`实现索引更新 - 用`tf.repeat`替代Python列表扩展 2. **二进制转换实现**: - 计算所需位数:`num_bits = ceil(log2(M))` - 使用位操作实现转换: ```python # 示例:6 → 110 (3位表示) bit_mask = [4, 2, 1] # 2^(2), 2^(1), 2^(0) 6 & 4 → 4 → 1 (高位) 6 & 2 → 2 → 1 6 & 1 → 0 → 0 (低位) ``` - 结果返回形状为`[n, num_bits]`的二进制张量 3. **动态形状处理**: - 自动处理不同概率分布长度(M) - 适应任意样本大小(n) - 正确处理M=1的边缘情况(至少1位) 4. **性能优化**: - 向量化二进制转换操作 - 避免Python-TensorFlow上下文切换 - 使用图模式优化执行效率 **输出示例**: ``` 二进制样本: [[1 1 0] # 6 [0 1 1] # 3 [1 0 1] # 5 ... # 其他样本 ] ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值