
1. 从“手动复制”到“智能创建”为什么我们需要*_like函数如果你用过 PyTorch 或者 NumPy大概率写过这样的代码想创建一个和已有张量tensor_a形状、数据类型、设备都一模一样的新张量但里面的元素全是 1 或者 0。新手的第一反应可能是torch.ones(tensor_a.shape, dtypetensor_a.dtype, devicetensor_a.device)。这行代码当然能工作但每次都要手动传递shape、dtype、device这三个参数不仅啰嗦还容易出错——比如你修改了tensor_a的数据类型却忘了同步更新这行创建代码一个隐蔽的 bug 就埋下了。torch.ones_like和torch.zeros_like这两个函数就是为了解决这个“复制元信息”的痛点而生的。它们的核心价值就体现在函数名后缀的_like上“像谁一样”。你只需要告诉它“像”哪个张量它就能自动继承该张量的所有关键属性然后填充上你指定的数值1或0。这不仅仅是语法糖更是一种保证张量间兼容性的最佳实践。在深度学习模型搭建、数据预处理、损失函数计算等场景中我们经常需要创建与某个中间张量维度完全匹配的掩码Mask、权重初始值或临时缓冲区*_like函数能让这些操作变得既简洁又可靠。从网络热词可以看到无论是sprintf、open这类基础 C 函数还是lambda、softmax、损失函数这些高级概念函数的核心职责都是封装特定操作提升代码的复用性和可读性。torch.ones_like正是这一思想的体现它将“创建一个与参考张量属性一致的新张量”这一高频操作封装成了一个简单直观的接口。理解了它你就能更顺畅地驾驭 PyTorch 的张量操作避免许多因属性不匹配导致的运行时错误。2.torch.ones_like与torch.zeros_like的深度解析不只是填充数字很多教程会把这两个函数简单描述为“生成一个和输入张量形状一样但元素全是1或0的张量”。这个说法没错但只揭示了冰山一角。要真正用好它们必须理解其继承的“属性包”具体包含哪些内容以及如何通过参数进行微调。2.1 核心行为自动继承的“属性包”当你调用torch.ones_like(input)时函数内部会默默从input张量中提取以下关键属性并用它们来构造新张量形状Shape这是最直观的。新张量的维度大小与input完全一致。数据类型Dtype例如torch.float32、torch.int64、torch.bool。新张量的元素类型与input相同。这意味着如果你对一个bool型张量使用ones_like得到的是一个元素为True在布尔语境下等价于1的张量。设备Device即张量存储在 CPU 内存还是 GPU 显存上如‘cuda:0’。这是分布式计算和利用GPU加速的关键。*_like函数能确保新张量自动创建在正确的设备上避免昂贵的设备间数据传输。内存布局Layout主要是torch.strided默认的稠密张量布局。对于绝大多数情况我们无需关心但它也是被继承的属性之一。一个简单的例子胜过千言万语import torch # 假设我们有一个复杂的输入张量 input_tensor torch.randn(2, 3, dtypetorch.float64, device‘cuda’) print(f“原始张量: shape{input_tensor.shape}, dtype{input_tensor.dtype}, device{input_tensor.device}”) # 使用 ones_like 创建 ones_tensor torch.ones_like(input_tensor) print(f“ones_like 结果: shape{ones_tensor.shape}, dtype{ones_tensor.dtype}, device{ones_tensor.device}”) print(ones_tensor) # 使用 zeros_like 创建 zeros_tensor torch.zeros_like(input_tensor) print(f“\nzeros_like 结果: shape{zeros_tensor.shape}, dtype{zeros_tensor.dtype}, device{zeros_tensor.device}”) print(zeros_tensor)输出会显示ones_tensor和zeros_tensor都完美继承了input_tensor的(2, 3)形状、torch.float64类型和cuda设备属性。2.2 关键参数如何覆盖默认继承行为*_like函数并非铁板一块它们提供了关键的参数让你在需要时覆盖自动继承的行为。这是其灵活性的体现。dtype指定新张量的数据类型。当你希望创建一个与输入形状、设备相同但类型不同的张量时使用。input_tensor torch.randn(3, 4) # 默认是 torch.float32 int_ones torch.ones_like(input_tensor, dtypetorch.int32) print(int_ones.dtype) # 输出: torch.int32device指定新张量的存储设备。用于跨设备创建但需谨慎因为可能引发隐式数据拷贝。cpu_tensor torch.randn(5) # 显式指定在CUDA设备上创建全1张量 cuda_ones torch.ones_like(cpu_tensor, device‘cuda:0’)requires_grad这是PyTorch自动求导Autograd的核心标志。它不从输入张量继承而是默认为False。如果你想创建的新张量需要参与梯度计算例如作为可学习的参数必须显式设置为True。x torch.randn(2, 2, requires_gradTrue) y torch.ones_like(x) # y.requires_grad 为 False z torch.ones_like(x, requires_gradTrue) # z.requires_grad 为 Truelayout、memory_format高级参数用于控制内存存储格式如通道优先channels_last在优化特定硬件如GPU上的计算性能时会用到。注意dtype和device参数虽然可以覆盖但在实际编程中除非有明确理由如类型转换、设备迁移否则建议依赖自动继承以最大程度保证与上下游计算图的兼容性。2.3 与基础创建函数的对比何时用谁PyTorch 提供了torch.ones()和torch.zeros()这两个基础函数。它们与*_like系列的核心区别在于基础函数需要你显式地、完整地指定新张量的所有属性。特性torch.ones(sizes)torch.ones_like(input)形状定义必须显式传入sizes如(3,4)自动从input继承数据类型默认为torch.float32或通过dtype指定自动从input继承可通过dtype覆盖设备默认为 CPU或通过device指定自动从input继承可通过device覆盖使用场景从头创建全新张量或参考形状已知已有参考张量需创建与其兼容的新张量选择策略当你手头已经有一个“模板”张量input时毫不犹豫地使用*_like。这是最安全、最简洁的方式。当你只是凭空想创建一个特定形状的张量没有现成的参考对象时使用torch.ones()/torch.zeros()。3. 实战应用场景在模型构建与数据处理中大显身手理解了原理我们来看看这两个函数在真实项目中如何解决具体问题。它们绝不仅仅是创建全1/全0数组那么简单。3.1 场景一初始化模型权重与掩码Mask在构建神经网络层时我们经常需要初始化一些与输入特征图形状相关的权重或掩码。import torch.nn as nn class CustomAttentionLayer(nn.Module): def __init__(self, feature_dim): super().__init__() self.feature_dim feature_dim # 假设注意力权重初始化为全1一种简单的初始化方式 # 但此时我们还没有输入不知道batch_size和序列长度所以无法用 ones_like def forward(self, x): # x 的形状为 [batch_size, seq_len, feature_dim] batch_size, seq_len, _ x.shape # 创建一个与输入x形状相同的全1张量作为可学习的注意力偏置 # 注意这里需要 requires_gradTrue 以便训练 attention_bias torch.ones_like(x, requires_gradTrue) * 0.1 # 初始化为小值 # 创建一个与输入x形状相同的全0掩码用于屏蔽无效位置如padding # 假设我们有一个布尔列表标识哪些位置是有效的 valid_positions [True, True, False, True] # 示例 mask torch.zeros_like(x[:, :, 0]) # 取一个特征维度创建 [batch_size, seq_len] 的掩码 for i, valid in enumerate(valid_positions): if valid: mask[:, i] 1 # 有效位置置1 # 更高效的做法可能是直接基于条件创建但这里演示 zeros_like 作为基础模板 # ... 后续使用 attention_bias 和 mask 进行计算 return x attention_bias * mask.unsqueeze(-1)在这个例子中ones_like确保了attention_bias与输入x在设备GPU/CPU和数据类型上完全匹配这是正确进行张量运算的前提。zeros_like则快速创建了一个结构正确的“画布”方便我们在此基础上绘制具体的掩码图案。3.2 场景二损失函数计算与梯度处理在实现自定义损失函数或进行梯度操作时经常需要创建与预测值、标签同形状的辅助张量。def custom_huber_loss(pred, target, delta1.0): 实现 Huber 损失函数。 当误差绝对值小于 delta 时使用平方损失否则使用线性损失。 error pred - target abs_error error.abs() # 创建一个与 error 形状相同的张量用于条件判断 quadratic_region abs_error delta # 关键步骤创建与 pred/target 同形状的损失张量 loss torch.zeros_like(pred) # 自动继承设备和类型 # 在平方损失区域计算 loss[quadratic_region] 0.5 * (error[quadratic_region] ** 2) # 在线性损失区域计算 loss[~quadratic_region] delta * (abs_error[~quadratic_region] - 0.5 * delta) return loss.mean() # 模拟数据 pred torch.randn(10, 5, requires_gradTrue) target torch.randn(10, 5) loss custom_huber_loss(pred, target) loss.backward()这里torch.zeros_like(pred)一举三得1) 确定了损失张量的形状2) 确保了它在正确的设备上如果pred在GPU上3) 确定了数据类型通常是float32。如果手动用torch.zeros(pred.shape, ...)很容易漏掉device参数导致损失张量被创建在CPU上与GPU上的pred运算时引发错误。3.3 场景三数据预处理与张量扩展在数据增强或准备模型输入时我们可能需要为一批数据添加一个固定的偏置或创建维度对齐的占位符。def add_channel_dimension(images): 假设输入 images 是灰度图 [N, H, W]需要扩展为 [N, 1, H, W] 以适配卷积层 if images.dim() 3: # 在第二维通道维增加一个大小为1的维度 images images.unsqueeze(1) return images def normalize_with_mean_std(tensor, mean, std): 使用与 tensor 同形状的 mean/std 进行归一化支持逐通道归一化 # mean 和 std 可能是标量也可能是与 tensor 通道维形状相同的向量 [C] # 我们需要将其扩展为与 tensor 相同的形状以进行广播计算 # 假设 tensor 形状为 [N, C, H, W] if mean.dim() 1: # mean 形状为 [C] # 创建与 tensor 同形状的全1张量作为扩展模板 shape_for_broadcast torch.ones_like(tensor) # 通过广播机制将 mean 扩展为 [N, C, H, W] 其中每个 H,W 切片都是相同的均值 mean_expanded mean.view(1, -1, 1, 1) * shape_for_broadcast[:, :1, :, :] # 简化示例逻辑 # 实际中更常用mean[:, None, None] 进行广播这里用 ones_like 说明形状匹配的思想 return (tensor - mean) / std # 更常见的例子创建与某张量 batch_size 相同的索引或标签 batch_features torch.randn(16, 256) # [batch_size, feature_dim] # 为每个样本分配一个初始权重例如全1 sample_weights torch.ones_like(batch_features[:, 0]) # 形状变为 [16]在这些场景中ones_like扮演了“形状生成器”或“广播辅助”的角色帮助我们快速构建出维度匹配的中间张量使后续的广播运算能够正确执行。4. 高级技巧与常见“坑点”排查即使是一个简单的函数在深度使用中也会遇到一些需要特别注意的地方。下面分享一些实战中积累的经验和容易踩坑的地方。4.1 性能考量in-place操作与计算图*_like函数总是返回一个新的张量。这意味着它会有内存分配开销。在极度追求性能的循环或内核函数中如果形状不变可以考虑复用已分配的张量。# 低效做法在循环内反复创建 buffer None for data in data_stream: if buffer is None or buffer.shape ! data.shape: buffer torch.zeros_like(data) # 每次形状变化都重新分配 # ... 使用 buffer 进行计算 # 稍好的做法预分配如果知道最大形状 max_shape (100, 256, 256) big_buffer torch.zeros(max_shape, dtypetorch.float32, device‘cuda’) for data in data_stream: current_buffer big_buffer[:data.shape[0], :data.shape[1], :data.shape[2]] # ... 使用 current_buffer 切片视图进行操作另外请注意requires_grad参数。如果你创建了一个requires_gradTrue的张量它将被纳入计算图PyTorch 会跟踪其上的所有操作以计算梯度。这会带来额外的内存和计算开销。仅在需要梯度的地方使用requires_gradTrue。4.2 设备一致性错误与调试这是使用*_like时最常见的运行时错误之一但往往也是最容易避免的。# 错误示例 device ‘cuda:0’ tensor_cpu torch.tensor([1, 2, 3]) tensor_gpu torch.ones_like(tensor_cpu, devicedevice) # 这行没问题但... result tensor_cpu tensor_gpu # RuntimeError: 张量不在同一设备上 # 正确做法确保运算前张量在同一设备 tensor_cpu tensor_cpu.to(device) # 将CPU张量移动到GPU # 或者从一开始就确保模板张量在目标设备上 template_on_gpu torch.tensor([1, 2, 3], devicedevice) tensor_gpu torch.ones_like(template_on_gpu) # 自动继承 devicecuda:0调试技巧当遇到RuntimeError: Expected all tensors to be on the same device时立即检查参与运算的所有张量的.device属性。*_like创建的张量会继承模板的设备这是一个有力的保证但前提是你的“模板”本身在正确的设备上。4.3 与torch.full_like的协同使用PyTorch 还提供了更通用的torch.full_like(input, fill_value)函数。它可以创建与input形状、类型、设备相同的张量并用任意指定的fill_value填充。input torch.randn(2, 3) # 以下三行代码等价 a torch.ones_like(input) b torch.full_like(input, fill_value1.0) c torch.zeros_like(input) 1.0 # 不推荐效率低且可能产生临时张量 # full_like 的威力填充任意值 negative_ones torch.full_like(input, -1.0) pi_tensor torch.full_like(input, 3.14159)所以ones_like和zeros_like可以看作是full_like在fill_value1和fill_value0时的特化版本语法更简洁。当你需要填充的值不是0或1时记住torch.full_like这个工具。4.4 稀疏张量与特殊数据类型对于稀疏张量torch.sparse或一些特殊数据类型如复数torch.complex64*_like函数的行为依然一致。# 稀疏张量 sparse_input torch.sparse_coo_tensor(indices[[0, 1], [2, 3]], values[1., 2.], size(4, 5)) sparse_ones torch.ones_like(sparse_input) print(sparse_ones) # 这会创建一个具有相同稀疏布局但值全为1的稀疏张量吗 # 注意对于稀疏张量ones_like 会创建一个**稠密**的、形状相同的全1张量而不是稀疏的。 # 要创建稀疏的全1张量需要直接操作稀疏索引和值。 # 复数张量 complex_input torch.complex(torch.randn(3), torch.randn(3)) complex_ones torch.ones_like(complex_input) print(complex_ones) # 输出tensor([1.0.j, 1.0.j, 1.0.j])这里有一个重要的细节对于稀疏张量ones_like返回的是稠密张量。这是因为“全1”的稀疏表示效率极低几乎每个位置都是1。如果你需要稀疏格式的全1张量可能需要重新考虑数据结构是否合适。5. 从函数设计看编程哲学一致性、简洁性与安全性回顾torch.ones_like和torch.zeros_like它们体现了优秀的API设计原则一致性Consistency_like后缀在 PyTorch 中形成了一个清晰的语义家族。除了这两个还有torch.rand_like均匀分布随机数、torch.randn_like标准正态分布随机数、torch.empty_like未初始化内存等。这种命名约定让用户能够举一反三大大降低了学习成本。简洁性Simplicity将高频操作复制张量元信息封装成一个函数调用避免了重复、易错的参数传递。这符合“Don‘t Repeat Yourself” (DRY) 原则。安全性Safety通过自动继承设备、数据类型等关键属性从根本上避免了因属性不匹配导致的隐蔽错误。尤其是在混合精度训练float16/float32或多设备CPU/多GPU编程中这种安全性至关重要。在实际编码中养成使用*_like函数的习惯能让你写出更健壮、更易维护的代码。下次当你需要创建一个与现有张量“类似”的新张量时先问问自己是不是用ones_like或zeros_like更合适这个简单的选择往往是区分新手和有经验开发者的一个小标志。最后再分享一个我个人的小习惯在编写需要返回与输入同形状张量的函数时我通常会先用torch.zeros_like(input)创建一个“外壳”然后再填充内容。这就像先打好画框再作画能让我在编码初期就明确输出形状并利用编辑器的自动补全功能避免后续的形状不匹配错误。这种“先定框架后填逻辑”的思路在很多编程场景下都很有用。