Tensor数据转换
一、Tensor基础概念回顾
Tensor(张量)是深度学习框架中的核心数据结构,可以看作是多维数组的扩展。在PyTorch、TensorFlow等主流框架中,Tensor是进行所有数学运算和神经网络操作的基本单位。
Tensor的主要特性包括:
-
数据类型(dtype):如float32、int64等
-
形状(shape):表示各维度的大小
-
设备(device):指示Tensor存储在CPU还是GPU上
import torch# 创建一个简单的Tensor
x = torch.tensor([[1, 2], [3, 4]])
print(f"Tensor shape: {x.shape}") # 输出: torch.Size([2, 2])
print(f"Data type: {x.dtype}") # 输出: torch.int64
print(f"Device: {x.device}") # 输出: cpu
二、Tensor数据类型转换
2.1 数据类型概述
PyTorch支持多种数据类型,主要包括:
-
torch.float16 / torch.half:半精度浮点
-
torch.float32 / torch.float:单精度浮点
-
torch.float64 / torch.double:双精度浮点
-
torch.int8:8位整数
-
torch.int16 / torch.short:16位整数
-
torch.int32 / torch.int:32位整数
-
torch.int64 / torch.long:64位整数
-
torch.bool:布尔类型
2.2 数据类型转换方法
方法1:使用tensor.type()
方法
x = torch.randn(2, 2) # 默认是torch.float32# 转换为float64
x_float64 = x.type(torch.float64)
print(x_float64.dtype) # 输出: torch.float64# 转换为int32
x_int32 = x.type(torch.int32)
print(x_int32.dtype) # 输出: torch.int32
方法2:使用tensor.to()
方法(推荐)
x = torch.tensor([1, 2, 3])# 转换为float32
x_float = x.to(torch.float32)
print(x_float.dtype) # 输出: torch.float32# 转换为double
x_double = x.to(torch.double)
print(x_double.dtype) # 输出: torch.float64
方法3:使用便捷方法
x = torch.tensor([1.5, 2.3, 3.7])x_int = x.int() # 转换为int32
x_long = x.long() # 转换为int64
x_float = x.float() # 转换为float32
x_half = x.half() # 转换为float16
2.3 类型转换注意事项
1.精度损失:高精度转低精度可能导致数据丢失
x = torch.tensor([1.8, 2.5, 3.3])
x_int = x.int()
print(x_int) # 输出: tensor([1, 2, 3]),小数部分丢失
2.范围溢出:超出目标类型表示范围会导致不可预期结果
x = torch.tensor([128], dtype=torch.int8) # int8范围是-128到127
x_uint8 = x.to(torch.uint8) # uint8范围是0到255
3.自动类型提升:运算时会自动提升到更高精度的类型
a = torch.tensor([1], dtype=torch.int32)
b = torch.tensor([1.5], dtype=torch.float32)
c = a + b
print(c.dtype) # 输出: torch.float32
三、Tensor形状转换
3.1 改变Tensor形状
view()
方法(浅拷贝)
view()
返回一个具有相同数据但不同形状的新Tensor。
x = torch.arange(6) # tensor([0, 1, 2, 3, 4, 5])
y = x.view(2, 3) # 重塑为2行3列
"""
y的值:
tensor([[0, 1, 2],[3, 4, 5]])
"""# 使用-1自动推断维度大小
z = x.view(3, -1) # 3行,列数自动计算为2
"""
z的值:
tensor([[0, 1],[2, 3],[4, 5]])
"""
注意事项:
-
view()
要求Tensor在内存中是连续的 -
新形状的元素总数必须与原形状相同
reshape()
方法
reshape()
可以处理非连续Tensor,功能比view()
更强大。
x = torch.arange(6)
y = x.reshape(2, 3) # 效果与view相同# 处理非连续Tensor
x = torch.arange(6).view(2, 3).transpose(0, 1) # 转置后不连续
# y = x.view(3, 2) # 这会报错
y = x.reshape(3, 2) # 这样可以工作
3.2 转置操作
t()
方法
适用于2D Tensor(矩阵)的转置:
x = torch.tensor([[1, 2], [3, 4]])
y = x.t()
"""
y的值:
tensor([[1, 3],[2, 4]])
"""
transpose()
方法
更通用的转置方法,可以指定任意两个维度交换:
x = torch.randn(2, 3, 4)
y = x.transpose(0, 1) # 交换第0和第1维度
print(y.shape) # 输出: torch.Size([3, 2, 4])z = x.transpose(1, 2) # 交换第1和第2维度
print(z.shape) # 输出: torch.Size([2, 4, 3])
permute()
方法
可以一次性对多个维度进行重新排列:
x = torch.randn(2, 3, 4)
y = x.permute(2, 0, 1) # 新维度顺序: 原第2维->0, 原0维->1, 原1维->2
print(y.shape) # 输出: torch.Size([4, 2, 3])
3.3 扩展和压缩维度
unsqueeze()
方法 升维
在指定位置插入大小为1的维度:
x = torch.tensor([1, 2, 3])
y = x.unsqueeze(0) # 在第0维插入
print(y.shape) # 输出: torch.Size([1, 3])z = x.unsqueeze(1) # 在第1维插入
print(z.shape) # 输出: torch.Size([3, 1])
"""
z的值:
tensor([[1],[2],[3]])
"""
squeeze()
方法 降维
移除所有大小为1的维度,或指定位置的维度:
x = torch.zeros(2, 1, 3, 1, 4)
y = x.squeeze() # 移除所有大小为1的维度
print(y.shape) # 输出: torch.Size([2, 3, 4])z = x.squeeze(1) # 只移除第1维
print(z.shape) # 输出: torch.Size([2, 3, 1, 4])
四、Tensor设备转换(CPU/GPU)
4.1 检查设备信息
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device) # 输出: cuda 或 cpu
4.2 使用to()
方法转换设备
x = torch.randn(2, 2)# 移动到GPU
if torch.cuda.is_available():x_gpu = x.to('cuda')print(x_gpu.device) # 输出: cuda:0# 同时改变设备和数据类型
x_gpu_float16 = x.to('cuda', torch.float16)
print(x_gpu_float16.dtype) # 输出: torch.float16
print(x_gpu_float16.device) # 输出: cuda:0
4.3 使用cuda()
和cpu()
方法
x = torch.randn(2, 2)# 移动到GPU
if torch.cuda.is_available():x_gpu = x.cuda() # 等价于x.to('cuda')# 移回CPU
x_cpu = x_gpu.cpu()
五、Tensor与其它数据结构的转换
5.1 Tensor与NumPy数组转换
Tensor转NumPy
x = torch.tensor([[1, 2], [3, 4]])
x_np = x.numpy()
print(type(x_np)) # 输出: <class 'numpy.ndarray'>
注意:CPU上的Tensor与NumPy数组共享内存,修改一个会影响另一个。
NumPy转Tensor
import numpy as nparr = np.array([[1, 2], [3, 4]])
x = torch.from_numpy(arr)
print(type(x)) # 输出: <class 'torch.Tensor'>
5.2 Tensor与Python列表转换
Tensor转列表
x = torch.tensor([[1, 2], [3, 4]])
x_list = x.tolist()
print(x_list) # 输出: [[1, 2], [3, 4]]
列表转Tensor
lst = [[1, 2], [3, 4]]
x = torch.tensor(lst)
print(x)
"""
输出:
tensor([[1, 2],[3, 4]])
"""
5.3 Tensor与Pandas DataFrame转换
import pandas as pd# DataFrame转Tensor
df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})
x = torch.tensor(df.values)
print(x)
"""
输出:
tensor([[1, 3],[2, 4]], dtype=torch.int64)
"""# Tensor转DataFrame
x = torch.randn(3, 2)
df = pd.DataFrame(x.numpy(), columns=['feature1', 'feature2'])
print(df)
六、高级Tensor操作
6.1 内存连续性与contiguous()
某些操作(如transpose()
)会改变Tensor的内存布局,使其不连续:
x = torch.arange(6).view(2, 3)
y = x.transpose(0, 1)
print(x.is_contiguous()) # 输出: True
print(y.is_contiguous()) # 输出: False# 使不连续Tensor变为连续
z = y.contiguous()
print(z.is_contiguous()) # 输出: True
6.2 原地操作(in-place)
带有下划线后缀的方法执行原地操作:
x = torch.rand(2, 2)
y = torch.rand(2, 2)x.add_(y) # 等价于 x = x + y,但更高效
x.t_() # 原地转置
x.zero_() # 原地置零
注意:原地操作能节省内存但会丢失原始数据,且在自动求导中可能导致问题。
6.3 自定义数据类型转换
可以通过组合操作实现复杂转换:
def normalize_to_uint8(tensor):"""将浮点Tensor归一化并转换为uint8"""# 1. 归一化到0-1范围tensor_min = tensor.min()tensor_max = tensor.max()normalized = (tensor - tensor_min) / (tensor_max - tensor_min)# 2. 扩展到0-255范围并转换为uint8uint8_tensor = (normalized * 255).to(torch.uint8)return uint8_tensorx = torch.randn(3, 3) * 10
x_uint8 = normalize_to_uint8(x)
print(x_uint8)
七、实际应用案例
7.1 图像数据处理
from PIL import Image
import torchvision.transforms as transforms# 加载图像
img = Image.open('example.jpg')# 定义转换管道
transform = transforms.Compose([transforms.Resize(256), # 调整大小transforms.CenterCrop(224), # 中心裁剪transforms.ToTensor(), # 转为Tensor并归一化到[0,1]transforms.Normalize( # 标准化mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])# 应用转换
img_tensor = transform(img)
print(f"Shape: {img_tensor.shape}") # 输出: torch.Size([3, 224, 224])
print(f"Data type: {img_tensor.dtype}") # 输出: torch.float32
7.2 神经网络输入准备
# 假设有一批文本数据,已经转换为词索引
batch_indices = [[1, 23, 45, 67, 0, 0], # 句子1,用0填充[2, 34, 56, 0, 0, 0], # 句子2[3, 45, 67, 89, 12, 34] # 句子3
]# 转换为Tensor
inputs = torch.tensor(batch_indices, dtype=torch.long)# 创建注意力掩码(mask)
mask = (inputs != 0).float()
print("Input tensor:")
print(inputs)
print("\nAttention mask:")
print(mask)
7.3 模型输出后处理
# 模拟模型输出 (batch_size=2, num_classes=3)
logits = torch.tensor([[1.2, 0.3, -0.5], [0.1, 2.1, -1.0]])# 1. 转换为概率 (softmax)
probs = torch.softmax(logits, dim=1)
print("Probabilities:")
print(probs)# 2. 获取预测类别
pred_classes = torch.argmax(probs, dim=1)
print("\nPredicted classes:")
print(pred_classes)# 3. 转换为可读标签
class_labels = ['cat', 'dog', 'bird']
pred_labels = [class_labels[i] for i in pred_classes]
print("\nPredicted labels:")
print(pred_labels)
八、性能优化建议
1.尽量使用批量操作:避免在循环中对单个Tensor操作
# 不好
for i in range(100):x[i] = x[i] * 2# 好
x = x * 2
2.减少CPU-GPU数据传输:在GPU上完成尽可能多的操作
# 不好: 频繁在CPU和GPU间切换
for data in dataset:data = data.to('cuda')output = model(data)output = output.cpu()# 处理output# 好: 批量处理
data_batch = batch.to('cuda')
outputs = model(data_batch)
outputs = outputs.cpu()
3.使用原地操作节省内存(但要注意副作用)
x = x.mul_(2) # 比 x = x * 2 更节省内存
4.注意数据类型选择:
-
训练时通常使用float32
-
推理时可以考虑float16以提升速度
-
整数运算比浮点运算快
九、常见问题与解决方案
Q1:RuntimeError: shape '[10, 3]' is invalid for input of size 28
A:尝试重塑Tensor时,新形状的元素总数必须与原形状相同。28个元素不能重塑为10×3=30的形状。
Q2:RuntimeError: expected scalar type Float but found Double
A:数据类型不匹配。使用.to(torch.float32)
或.float()
将Tensor转换为浮点类型。
Q3:RuntimeError: CUDA error: out of memory
A:GPU内存不足。尝试:
-
减小批量大小
-
使用更小的模型
-
使用
torch.cuda.empty_cache()
清理缓存 -
使用混合精度训练
Q4:如何判断Tensor是否在GPU上?
A:
x = torch.randn(3,3)
print(x.is_cuda) # 输出: Falsex = x.to('cuda')
print(x.is_cuda) # 输出: True
Q5:为什么修改NumPy数组会影响对应的Tensor?
A:因为它们在CPU上共享内存。如果需要独立副本,使用.clone()
:
x = torch.tensor([1, 2, 3])
arr = x.numpy().copy() # 创建独立副本
十、总结
Tensor数据转换是深度学习编程中的基础但至关重要的操作。本文详细介绍了:
-
Tensor数据类型转换的各种方法及适用场景
-
Tensor形状改变、转置、维度调整等操作
-
CPU与GPU设备间的转换技巧
-
Tensor与NumPy、Python列表等数据结构的互转
-
实际应用中的典型案例和性能优化建议
掌握这些Tensor转换技巧将大大提高你的深度学习开发效率,帮助你更好地准备数据、调试模型和处理结果。记得在实践中多尝试这些操作,观察它们对Tensor属性和值的影响,以加深理解。