使用PyTorch构建神经网络笔记
专有名词
Batch Size
在深度学习中,批大小(Batch Size) 是指每次前向传播和反向传播时使用的样本数量。它是训练神经网络时的一个关键超参数,直接影响训练速度、内存占用和模型性能。
(1) 计算梯度
-
在训练时,模型通过损失函数计算预测值与真实值的误差。
-
梯度(Gradient) 表示损失函数对模型参数的偏导数,用于更新权重。
-
批大小决定了一次计算梯度时使用的样本数量:
-
Batch Size = 1(随机梯度下降,SGD):每次用 1 个样本 计算梯度,更新参数。
-
Batch Size = N(小批量梯度下降,Mini-batch SGD):每次用 N 个样本 的平均梯度更新参数。
-
Batch Size = 全部训练数据(批量梯度下降,Batch GD):每次用 所有数据 计算梯度(计算量大,内存要求高)。
-
(2) 影响训练稳定性
-
较大的 Batch Size:
-
梯度计算更稳定(噪声小)。
-
训练速度更快(GPU 并行计算)。
-
但可能陷入局部最优,泛化能力较差。
-
-
较小的 Batch Size:
-
梯度噪声大,训练不稳定。
-
可能跳出局部最优,泛化能力更好。
-
但训练速度较慢(GPU 利用率低)。
-
构建神经网络
1. 数据准备
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]
X = torch.tensor(x).float() # 转换为浮点型张量
Y = torch.tensor(y).float()# 将数据移动到GPU(如果可用)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)
-
数据:
-
x
是输入数据,形状为[4, 2]
(4个样本,每个样本2个特征)。 -
y
是目标数据,形状为[4, 1]
(4个样本,每个样本1个输出值)。
-
-
设备切换:通过
.to(device)
将数据移动到 GPU(如果可用)。
2. 神经网络定义
from torch import nnclass MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2, 8) # 输入层→隐藏层(2→8)self.hidden_layer_activation = nn.ReLU() # 激活函数self.hidden_to_output_layer = nn.Linear(8, 1) # 隐藏层→输出层(8→1)def forward(self, x):x = self.input_to_hidden_layer(x) # 线性变换x = self.hidden_layer_activation(x) # ReLU激活x = self.hidden_to_output_layer(x) # 输出层return x
-
y = x @ W.T + b
-
网络结构:
-
输入层→隐藏层:
nn.Linear(2, 8)
-
输入特征数:2,输出特征数:8(隐藏层神经元数)。
-
权重矩阵形状:
[8, 2]
,偏置形状:[8]
。
-
-
激活函数:
nn.ReLU()
(非线性变换)。 -
隐藏层→输出层:
nn.Linear(8, 1)
-
输入特征数:8,输出特征数:1。
-
权重矩阵形状:
[1, 8]
,偏置形状:[1]
。
-
-
3. 打印 nn.Linear
信息
print(nn.Linear(2, 7))
# 输出:Linear(in_features=2, out_features=7, bias=True)
-
解释:
-
nn.Linear(2, 7)
是一个未初始化的线性层,输入特征数为2,输出特征数为7,默认启用偏置(bias=True
)。 -
了解一下,这跟上面的没有关系。
-
4. 创建模型并打印权重
mynet = MyNeuralNet().to(device) # 实例化模型并移动到设备
print(mynet.input_to_hidden_layer.weight)
tensor([[ 0.0618, -0.1801],[-0.0899, 0.4533],[-0.0178, -0.2600],[ 0.1930, -0.1421],[-0.7004, 0.5656],[ 0.6977, 0.4310],[-0.4469, -0.0127],[-0.4786, -0.3897]], device='cuda:0', requires_grad=True)
-
解释:
-
input_to_hidden_layer.weight
是隐藏层的权重矩阵,形状为[8, 2]
。 -
每个权重是随机初始化的(PyTorch 默认使用均匀分布初始化)。
-
requires_grad=True
表示这些权重会在训练时自动计算梯度。
-
5.打印神经网络所有参数
# 获取神经网络模型的所有可学习参数(权重和偏置)
# mynet.parameters() 返回一个生成器,包含模型中所有需要训练的参数(即定义了 requires_grad=True 的张量)
params = mynet.parameters()# 遍历并打印每一个参数张量
for param in params:print(param) # 打印当前参数张量的值和属性print('-' * 50) # 分隔线,便于观察
Parameter containing:
tensor([[ 0.0618, -0.1801],[-0.0899, 0.4533],[-0.0178, -0.2600],[ 0.1930, -0.1421],[-0.7004, 0.5656],[ 0.6977, 0.4310],[-0.4469, -0.0127],[-0.4786, -0.3897]], device='cuda:0', requires_grad=True)
--------------------------------------------------
Parameter containing:
tensor([ 0.1349, 0.5562, 0.6507, -0.2334, -0.0498, 0.1597, 0.0484, -0.5478],device='cuda:0', requires_grad=True)
--------------------------------------------------
Parameter containing:
tensor([[ 0.1448, -0.3510, -0.2759, -0.1556, -0.1209, 0.1024, 0.1095, 0.1628]],device='cuda:0', requires_grad=True)
--------------------------------------------------
Parameter containing:
tensor([-0.3037], device='cuda:0', requires_grad=True)
--------------------------------------------------
-
参数的顺序与模型定义的顺序一致
-
通常先打印权重矩阵,然后是偏置向量
-
对于你的
MyNeuralNet
示例,会依次打印:-
input_to_hidden_layer.weight (形状 [8,2])
-
input_to_hidden_layer.bias (形状 [8])
-
hidden_to_output_layer.weight (形状 [1,8])
-
hidden_to_output_layer.bias (形状 [1])
-
6.画epoch变化
# 定义均方误差(MSE)损失函数
loss_func = nn.MSELoss()# 前向传播:用网络(mynet)计算输入X的预测输出(_Y)
_Y = mynet(X)# 计算预测输出(_Y)和真实标签(Y)之间的损失
loss_value = loss_func(_Y,Y)
print(loss_value)
# 输出显示初始损失值(127.4498),在CUDA设备上,带有梯度函数
# tensor(127.4498, device='cuda:0', grad_fn=<MseLossBackward>)# 导入并初始化随机梯度下降(SGD)优化器
# 参数:网络参数和学习率0.001
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)# 清除之前步骤累积的梯度
opt.zero_grad()# 重新计算损失(这里重复计算mynet(X),效率稍低)
loss_value = loss_func(mynet(X),Y)# 反向传播:计算损失对所有可训练参数的梯度
loss_value.backward()# 用计算出的梯度更新网络参数
opt.step()# 初始化列表用于存储训练过程中的损失值
loss_history = []# 训练循环,50次迭代
for _ in range(50):# 在每一步前清除梯度opt.zero_grad()# 前向传播:计算损失loss_value = loss_func(mynet(X),Y)# 反向传播:计算梯度loss_value.backward()# 更新参数opt.step()# 记录当前损失值(转换为Python标量)loss_history.append(loss_value.item())
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.title('Loss variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')
plt.show()
操作 | 作用 | 是否必须 |
---|---|---|
optimizer.zero_grad() | 清除之前计算的梯度,防止梯度累积 | ✅ 必须(除非做梯度累积) |
loss.backward() | 计算当前 batch 的梯度 | ✅ 必须 |
optimizer.step() | 用梯度更新参数 | ✅ 必须 |
7.总体代码
import torch
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]
X = torch.tensor(x).float()
Y = torch.tensor(y).float()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)
from torch import nn
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2,8)self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.Linear(8,1)def forward(self, x):x = self.input_to_hidden_layer(x)x = self.hidden_layer_activation(x)x = self.hidden_to_output_layer(x)return x
print(nn.Linear(2, 7))
# Linear(in_features=2, out_features=7, bias=True)
mynet = MyNeuralNet().to(device)
print(mynet.input_to_hidden_layer.weight)
mynet.parameters()
for param in mynet.parameters():print(param)print('-' * 50)
loss_func = nn.MSELoss()
_Y = mynet(X)
loss_value = loss_func(_Y,Y)
print(loss_value)
# tensor(127.4498, device='cuda:0', grad_fn=<MseLossBackward>)
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)
opt.zero_grad()
loss_value = loss_func(mynet(X),Y)
loss_value.backward()
opt.step()
loss_history = []
for _ in range(50):opt.zero_grad()loss_value = loss_func(mynet(X),Y)loss_value.backward()opt.step()loss_history.append(loss_value.item())
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.title('Loss variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')
plt.show()
神经网络数据加载
1. 数据准备
(1) 定义输入 x
和输出 y
x = [[1,2],[3,4],[5,6],[7,8]] # 输入数据(4 个样本,每个样本 2 个特征)
y = [[3],[7],[11],[15]] # 输出数据(4 个样本,每个样本 1 个目标值)
-
这是一个简单的线性关系:
y = 2 * x1 + 1 * x2
(例如2*1 + 1*2 = 4
,但给定的y
是3
,可能是模拟带噪声的数据)。
(2) 转换为 PyTorch Tensor 并移至 GPU(如果可用)
X = torch.tensor(x).float() # 转换为 float32 Tensor
Y = torch.tensor(y).float() # 同上
device = 'cuda' if torch.cuda.is_available() else 'cpu' # 检查 GPU
X = X.to(device) # 数据移至 GPU/CPU
Y = Y.to(device)
2. 构建数据集(Dataset)和数据加载器(DataLoader)
(1) 自定义 MyDataset
类
class MyDataset(Dataset):def __init__(self, x, y):self.x = x.clone().detach() # 避免修改原数据self.y = y.clone().detach()def __len__(self):return len(self.x) # 返回数据集大小def __getitem__(self, idx):return self.x[idx], self.y[idx] # 返回第 idx 个样本
-
.clone().detach()
:创建数据的独立副本,确保对数据集的操作不会影响原始数据。 -
如果省略这一步,直接赋值
self.x = x
,外部对x
的修改会直接影响数据集。 -
如果
x
的形状是[100, 5]
(100个样本,每个样本5个特征),__len__
会返回100
。 -
根据索引
idx
返回对应的样本和标签。
(2) 创建 DataLoader
ds = MyDataset(X, Y) # 实例化 Dataset
dl = DataLoader(ds, batch_size=2, shuffle=True) # 按 batch_size=2 加载,并打乱数据
-
batch_size=2
:每次训练用 2 个样本 计算梯度。训练的每个样本梯度之和除以训练样本数。 -
shuffle=True
:每个epoch
数据顺序随机打乱,防止模型学习到顺序偏差。
3. 定义神经网络模型
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden = nn.Linear(2, 8) # 输入层→隐藏层(2→8)self.activation = nn.ReLU() # 激活函数self.hidden_to_output = nn.Linear(8, 1) # 隐藏层→输出层(8→1)def forward(self, x):x = self.input_to_hidden(x) # 线性变换x = self.activation(x) # ReLU 激活x = self.hidden_to_output(x) # 输出预测return x
-
网络结构:
-
输入层:2 个神经元(对应
x
的 2 个特征)。 -
隐藏层:8 个神经元(使用
ReLU
激活函数)。 -
输出层:1 个神经元(回归任务,无激活函数)。
-
-
前向传播:
-
x → Linear(2,8) → ReLU → Linear(8,1) → 输出
。
-
4. 训练流程
(1) 初始化模型、损失函数、优化器
mynet = MyNeuralNet().to(device) # 实例化模型并移至 GPU/CPU
loss_func = nn.MSELoss() # 均方误差损失(回归任务常用)
opt = SGD(mynet.parameters(), lr=0.001) # 随机梯度下降优化器
(2) 训练循环
loss_history = [] # 记录损失值
start = time.time()for _ in range(50): # 训练 50 个 epochfor x, y in dl: # 遍历每个 batchopt.zero_grad() # 清除梯度pred = mynet(x) # 前向传播loss = loss_func(pred, y) # 计算损失loss.backward() # 反向传播opt.step() # 更新参数loss_history.append(loss.item()) # 记录损失end = time.time()
print(f"训练时间: {end - start:.4f} 秒") # 输出耗时
-
关键步骤:
-
opt.zero_grad()
:清除上一轮的梯度。 -
pred = mynet(x)
:前向计算预测值。 -
loss = loss_func(pred, y)
:计算预测值与真实值的误差。 -
loss.backward()
:反向传播计算梯度。 -
opt.step()
:用梯度更新模型参数。
-
5. 输出分析
(1) 训练时间
print(end - start) # 输出: 0.0854 秒
-
在 GPU 上训练 50 个 epoch(共 4 个样本,batch_size=2,每个 epoch 2 次迭代),速度非常快。
(1) Epoch(训练轮次)
-
1 Epoch = 完整遍历一次所有训练数据。
-
你的数据
x
有 4 个样本,所以 1 个 Epoch 会处理全部 4 个样本。
(2) Batch Size(批大小)
-
Batch Size = 2,表示每次训练用 2 个样本 计算梯度并更新模型。
-
因为总样本数是 4,所以:
-
每个 Epoch 的迭代次数(Steps) = 总样本数 / Batch Size = 4 / 2 = 2 次迭代。
-
(3) 50 Epochs
-
你设置了
for _ in range(50)
,表示训练 50 轮。 -
因此:
-
总迭代次数 = 50 Epochs × 2 Steps/Epoch = 100 次梯度更新。
-
(2) 损失变化
import matplotlib.pyplot as plt
plt.plot(loss_history)
plt.xlabel("Iteration")
plt.ylabel("Loss")
plt.title("Training Loss")
plt.show()
模型测试
val_x = [[10,11]]
val_x = torch.tensor(val_x).float().to(device)
print(mynet(val_x))
# tensor([[20.0105]], device='cuda:0', grad_fn=<AddmmBackward>)
获取中间层的值
假设网络结构如下:
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2, 8) # 输入层→隐藏层self.hidden_layer_activation = nn.ReLU() # 激活函数self.hidden_to_output_layer = nn.Linear(8, 1) # 隐藏层→输出层def forward(self, x):x = self.input_to_hidden_layer(x)x = self.hidden_layer_activation(x)x = self.hidden_to_output_layer(x)return x
直接获取隐藏层输出的方式:
print(mynet.hidden_layer_activation(mynet.input_to_hidden_layer(X)))
或者修改隐藏层
class MyNeuralNet(nn.Module):def __init__(self):super().__init__()self.input_to_hidden_layer = nn.Linear(2,8)self.hidden_layer_activation = nn.ReLU()self.hidden_to_output_layer = nn.Linear(8,1)def forward(self, x):hidden1 = self.input_to_hidden_layer(x)hidden2 = self.hidden_layer_activation(hidden1)x = self.hidden_to_output_layer(hidden2)return x, hidden2
print(mynet(X)[1])
1输出隐藏层激活后的值
0输出预测结果
使用Sequential构建神经网络
import torch
import torch.nn as nn
import numpy as np
from torch.utils.data import Dataset, DataLoader
device = 'cuda' if torch.cuda.is_available() else 'cpu'
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]class MyDataset(Dataset):def __init__(self, x, y):self.x = torch.tensor(x).float().to(device)self.y = torch.tensor(y).float().to(device)def __getitem__(self, ix):return self.x[ix], self.y[ix]def __len__(self): return len(self.x)
ds = MyDataset(x, y)
dl = DataLoader(ds, batch_size=2, shuffle=True)
model = nn.Sequential(nn.Linear(2, 8),nn.ReLU(),nn.Linear(8, 1)
).to(device)
from torchsummary import summary
print(summary(model, (2,)))
loss_func = nn.MSELoss()
from torch.optim import SGD
opt = SGD(model.parameters(), lr = 0.001)
import time
loss_history = []
start = time.time()
for _ in range(50):for ix, iy in dl:opt.zero_grad()loss_value = loss_func(model(ix),iy)loss_value.backward()opt.step()loss_history.append(loss_value.item())
end = time.time()
print(end - start)
val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float()
print(model(val.to(device)))
"""
tensor([[16.7774],[20.6186],[ 4.2415]], device='cuda:0', grad_fn=<AddmmBackward>)
"""
pytorch模型保存和加载
1.模型状态
print(model.state_dict())
"""
OrderedDict([('0.weight', tensor([[-0.4732, 0.1934],[ 0.1475, -0.2335],[-0.2586, 0.0823],[-0.2979, -0.5979],[ 0.2605, 0.2293],[ 0.0566, 0.6848],[-0.1116, -0.3301],[ 0.0324, 0.2609]], device='cuda:0')), ('0.bias', tensor([ 0.6835, 0.2860, 0.1953, -0.2162, 0.5106, 0.3625, 0.1360, 0.2495],device='cuda:0')), ('2.weight', tensor([[ 0.0475, 0.0664, -0.0167, -0.1608, -0.2412, -0.3332, -0.1607, -0.1857]],device='cuda:0')), ('2.bias', tensor([0.2595], device='cuda:0'))])
"""
2.模型保存
save_path = 'mymodel.pth'
torch.save(model.state_dict(), save_path)
3.模型加载
model = nn.Sequential(nn.Linear(2, 8),nn.ReLU(),nn.Linear(8, 1)
).to(device)state_dict = torch.load('mymodel.pth')model.load_state_dict(state_dict)
model.to(device)val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float()
model(val.to(device))