PyTorch学习一:PyTorch 基础知识
1. 神经网络的骨架:nn.Module
在 PyTorch 中,所有的神经网络模型都必须继承自 torch.nn.Module 这个基类。
python
import torch
import torch.nn as nn
# 定义一个简单的网络
class MyFirstModel(nn.Module):
def __init__(self):
super(MyFirstModel, self).__init__() # 必须调用的父类初始化
# 在这里定义网络的"组件"
self.layer1 = nn.Linear(in_features=10, out_features=5)
def forward(self, x):
# 在这里定义数据 x 是怎么流动的
out = self.layer1(x)
return out
# 实例化模型
model = MyFirstModel()
print(model)
2. 核心网络层 (Layers)
A. 全连接层 nn.Linear
最基础的层,将输入数据与权重矩阵相乘并加上偏置。
python
# 定义一个全连接层:输入特征数是 10,输出特征数是 5
linear_layer = nn.Linear(10, 5)
# 模拟一个输入数据 (Batch Size 为 2,特征数为 10)
# randn 生成标准正态分布的随机数
input_data = torch.randn(2, 10)
# 将数据传入层中
output = linear_layer(input_data)
# 输出形状将会是 (2, 5)
print("Linear Output Shape:", output.shape)
B. 二维卷积层 nn.Conv2d
用于处理图像数据的核心层。
python
# 定义一个卷积层:处理 RGB 图像(3通道),输出 16 个特征图,卷积核大小 3x3,填充 1 圈
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
# 模拟一张图片输入:维度顺序必须是 (Batch_Size, Channels, Height, Width)
# 假设有 1 张图片,3通道,尺寸为 32x32
image_input = torch.randn(1, 3, 32, 32)
# 通过卷积层
conv_output = conv_layer(image_input)
# 因为 padding=1 且 stride=1,尺寸保持不变,输出形状为 (1, 16, 32, 32)
print("Conv Output Shape:", conv_output.shape)
3. 激活函数 (Activation Functions)
网络层通常只做线性变换,激活函数负责加入非线性因素,让网络能学习复杂的规律。
nn.ReLU
最常用的激活函数,把所有负数变成 0,正数保持不变。
python
relu = nn.ReLU()
# 模拟带负数的数据
data = torch.tensor([-1.0, 0.0, 2.5])
activated_data = relu(data) # 输出将会是 tensor([0.0, 0.0, 2.5])
print("ReLU Output:", activated_data)
4. 损失函数 (Loss Functions)
用来衡量模型预测结果和真实答案之间的差距。
nn.CrossEntropyLoss
多分类任务(比如识别猫、狗、鸟)的标准损失函数。
python
loss_fn = nn.CrossEntropyLoss()
# 假设模型输出的预测结果 (Logits),Batch size 为 2,有 3 个类别
predictions = torch.tensor([[1.5, 0.2, -0.5],
[0.1, 2.8, -0.2]])
# 真实的标签 (类别索引,从 0 开始算)。假设第一条数据是第 0 类,第二条是第 1 类
targets = torch.tensor([0, 1])
# 计算误差
loss = loss_fn(predictions, targets)
print("Calculated Loss:", loss.item()) # .item() 用于提取张量里面唯一的标量数值
5. 优化器 (Optimizers)
根据损失函数计算出的误差,来更新神经网络中的权重。它位于 torch.optim 模块中。
torch.optim.Adam
最省心、最常用的优化器。
python
import torch.optim as optim
# 假设我们有一个简单的模型
model = nn.Linear(10, 2)
# 初始化 Adam 优化器,告诉它要更新 model 里的参数,学习率为 0.001
optimizer = optim.Adam(params=model.parameters(), lr=0.001)
# --- 标准的参数更新三部曲(非常重要,面试必考) ---
# 1. 清空上一步残留的梯度
optimizer.zero_grad()
# (假设这里有一步 loss 的计算,例如:loss = loss_fn(model(input), target))
dummy_loss = torch.tensor(0.5, requires_grad=True) # 此处仅作示例,实际需要计算真实损失
# 2. 反向传播,计算所有参数的当前梯度
dummy_loss.backward() # 实际使用中应是 loss.backward()
# 3. 根据梯度,更新模型的参数
optimizer.step()
这部分内容是 PyTorch 面试的绝对高频考点。面试官非常喜欢通过这些张量操作来考察你对底层内存模型、维度变换的理解深度。为了让你这块知识"无懈可击",我们把这些核心的 torch 张量操作分成四大类,并详细拆解它们的入参、含义以及面试中最常被问到的"坑"。
6. 形状变换与内存管理 (Shape & Memory - 面试重灾区)
这一组函数经常被放在一起考,核心在于它们是否改变了底层的数据内存地址。
A. x.contiguous()
python
import torch
x = torch.randn(2, 3)
# transpose 交换维度,此时 y 的内存变得不连续了
y = x.transpose(0, 1)
# 检查是否连续 (返回 False)
print("Is y contiguous?", y.is_contiguous())
# 强制变为连续
z = y.contiguous()
print("Is z contiguous?", z.is_contiguous())
B. x.view(*shape)
python
x = torch.arange(6) # 创建一维张量 [0, 1, 2, 3, 4, 5]
# 重塑为 2行3列。修改 y 也会改变 x,因为它们共享内存
y = x.view(2, 3)
# 使用 -1 自动推导列数
z = x.view(2, -1)
print("Shape of z:", z.shape) # 输出 torch.Size([2, 3])
C. x.reshape(*shape)
功能类似 view,但能处理非连续内存的情况(内部可能调用 contiguous())。
7. 维度交换与重排 (Dimension Swapping)
在处理图像(通道转换)或文本(Batch前置或后置)时极其常用。
A. x.transpose(dim0, dim1)
python
# 假设这是一批图像:(Batch_size=2, Channels=3, Height=32, Width=32)
images = torch.randn(2, 3, 32, 32)
# 面试题:某些库需要图像格式为 (Batch_size, Height, Width, Channels)
# 我们需要把 Channels(维度1) 和 Width(维度3) 交换
transposed_images = images.transpose(1, 3)
# 输出 torch.Size([2, 32, 32, 3])
print("Transposed Shape:", transposed_images.shape)
B. x.permute(*dims)
python
images = torch.randn(2, 3, 32, 32)
# 将 (B, C, H, W) 彻底重排为 (B, H, W, C)
# 原来的维度索引分别是 0, 1, 2, 3
permuted_images = images.permute(0, 2, 3, 1)
print("Permuted Shape:", permuted_images.shape)
8. 维度的增减 (Dimension Expansion & Reduction)
很多时候我们需要在张量中增加或去掉"大小为 1"的维度,以便进行广播机制(Broadcasting)或对齐操作。
A. x.unsqueeze(dim)
python
x = torch.tensor([1, 2, 3]) # 形状是 (3,)
print("Original shape:", x.shape)
# 在最前面插入一个维度,常用于把单条数据伪装成 Batch_size 为 1 的数据
y = x.unsqueeze(0)
print("Unsqueeze at 0:", y.shape) # 输出 (1, 3)
# 在最后面插入一个维度
z = x.unsqueeze(1)
print("Unsqueeze at 1:", z.shape) # 输出 (3, 1)
B. x.squeeze(dim=None)
python
# 假设有一个形状为 (1, 3, 1, 32) 的张量
x = torch.randn(1, 3, 1, 32)
# 默认去掉所有大小为 1 的维度
y = x.squeeze()
print("Squeeze all:", y.shape) # 可能输出 (3, 32)
# 仅去掉指定维度(如果该维度大小为1)
z = x.squeeze(0) # 去掉第0维
print("Squeeze dim 0:", z.shape) # 输出 (3, 1, 32)
w = x.squeeze(2) # 去掉第2维
print("Squeeze dim 2:", w.shape) # 输出 (1, 3, 32)