文章目录
- 一、基本概念
- 1、epoch
- 2、遍历DataLoader
- 二、神经网络训练过程代码详解
- 步骤一:选择并初始化优化器
- 步骤二:计算损失
- 步骤三:反向传播
- 步骤四:更新模型参数
- 步骤五:清空梯度
- 组合到训练循环中
- 步骤六:保存模型
- 三、神经网络评估过程代码详解
- 步骤一:加载模型
- 步骤二:切换至评估模式
- 步骤三:进行评估(这里计算分类问题的准确率)
- 四、经典数据集——鸢尾花数据集
一、基本概念
for epoch in range(total_epoch):
for label_x,label_y in dataloader:
pass
1、epoch
epoch
指的是整个数据集在训练过程中被完整地遍历一次。如果数据集被分成多个批次输入模型,则一个 epoch
完成后意味着所有的批次已被模型处理一次。epoch
的数目通常根据训练数据的大小、模型复杂度和任务需求来决定。每个 epoch
结束后,模型学到的知识会更加深入,但也存在过度学习(过拟合)的风险,特别是当 epoch
数目过多时。
即每一个epoch
会处理所有的batch
,epoch
也被称为训练周期。
2、遍历DataLoader
遍历DataLoader
,实际上就是每次取出一个batch
的数据。
二、神经网络训练过程代码详解
建议先理解:Module模块
步骤一:选择并初始化优化器
首先,根据模型的需求选择一个合适的优化器。不同的优化器可能适合不同类型的数据和网络架构。一旦选择了优化器,需要将模型的参数传递给它,并设置一些特定的参数,如学习率、权重衰减等。
import torch.optim as optim
# 假设 model 是你的网络模型
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
在这个例子中,选择了随机梯度下降(SGD)作为优化器,并设置了学习率和动量。
步骤二:计算损失
在训练循环中,每次迭代都会处理一批数据,模型会根据这些数据进行预测,并计算损失。
criterion = torch.nn.CrossEntropyLoss() # 选择合适的损失函数
outputs = model(inputs) # 前向传播
loss = criterion(outputs, labels) # 计算损失
步骤三:反向传播
一旦有了损失,就可以使用 .backward()
方法来自动计算模型中所有可训练参数的梯度。
loss.backward()
这一步将计算损失函数相对于每个参数的梯度,并将它们存储在各个参数的 .grad
属性中。
步骤四:更新模型参数
使用优化器的 .step()
方法来根据计算得到的梯度更新参数。
optimizer.step()
这个调用会更新模型的参数,具体的更新方式取决于你选择的优化算法。
步骤五:清空梯度
在每次迭代后,需要手动清空梯度,以便下一次迭代。如果不清空梯度,梯度会累积,导致不正确的参数更新。
optimizer.zero_grad()
组合到训练循环中
将上述步骤组合到一个训练循环中,我们得到了完整的训练过程:
model = MyModel() #实例化神经网络层,调用继承自Module类的MyModel类的构造函数
criterion = torch.nn.CrossEntropyLoss() # 选择合适的损失函数,这里是交叉熵损失函数
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)# 定义优化器,传入模型参数
model.train()#切换至训练模式
for epoch in range(total_epochs):
for inputs, labels in dataloader: # 从数据加载器获取数据
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad() # 清空之前的梯度
loss.backward() # 反向传播
# 优化参数
optimizer.step() # 更新参数
print(f'Epoch [{epoch+1}/{total_epochs}], Loss: {loss.item()}')
-
loss.item()
- 在 PyTorch 中,
loss
是一个torch.Tensor
对象。当计算模型的损失时,这个对象通常只包含一个元素(一个标量值),它代表了当前批次数据的损失值。loss.item()
方法是从包含单个元素的张量中提取出那个标量值作为 Python 数值。这是很有用的,因为它允许你将损失值脱离张量的形式进行进一步的处理或输出,比如打印、记录或做条件判断。
- 在 PyTorch 中,
-
print(f'Epoch [{epoch+1}/{total_epochs}], Loss: {loss.item()}')
- 这行代码是用来在训练过程中输出当前
epoch
的编号和该epoch
的损失值。这对于监控训练进程和调试模型非常有帮助。具体来说:epoch+1
:由于计数通常从 0 开始,所以+1
是为了更自然地显示(从 1 开始而不是从 0 开始)。{total_epochs}
:这是训练过程中总的epoch
数。{loss.item()}
:如前所述,这表示当前批次的损失值,作为一个标量数值输出。
- 这行代码是用来在训练过程中输出当前
步骤六:保存模型
torch.save(model.state_dict(),"model.pth")
三、神经网络评估过程代码详解
步骤一:加载模型
实例化对应模型,使用该模型对象的.load_state_dict()
方法导入之前存储的模型参数。
model=MyModel()
model.to(device)
model.load_state_dict(torch.load("model.pth"))
步骤二:切换至评估模式
model.eval()
步骤三:进行评估(这里计算分类问题的准确率)
- 定义评估时需要的变量
- 使用
torch.no_grad()
指定上下文Pytorch不追踪梯度信息
total_correct=0 #正确的样本数
total_samples=0 #样本数
with torch.no_grad():# 该局部化区域内的张量不再计算梯度
for batch_x,batch_y in dataloader:
batch_x.to(device)
batch_y.to(device)
batch_x = batch_x.to(torch.float) #转换成浮点
output = model(batch_x) # 前向传播,得到分类结果 形状为[batch_size,num_classes]
_,predicted = torch.max(output,dim=1) # 不考虑dim=0的batch_size,从第一维开始考虑,沿着dim=1的方向寻找最大值,实际上就是找分类得分最高的分类,predicted接收的是max的索引,因此predicted的形状是[batch_size,1] 就是每一个样本的分类
total_correct += (predicted == batch_y).sum().item()
total_samples += predicted.size(dim=0)
accuracy=total_correct / total_samples
print(f"accuracy:{accuracy}")
_,predicted=tensor.max(output,dim=1)
:
output
形状是[batch_size,classes_num]
,沿着列求最大值, 得到列中的最大值索引,相当于得到的predicted
的形状是[batch_size,1]
,这里的1 的数值 是一行中 最大值的索引 也就是预测的类别。然后batch_y
的形状是[batch_size,1]
和predicted
进行对比,就是对比类别是否相同。所以,我们在考虑问题的时候,由于batch_size
的存在,第0维忽略掉考虑也行,然后就好理解了
四、经典数据集——鸢尾花数据集
代码来源
import torch
import torch.nn as nn
from sklearn.datasets import load_iris
from torch.utils.data import Dataset, DataLoader
# 此函数用于加载鸢尾花数据集
def load_data(shuffle=True):
x = torch.tensor(load_iris().data)
y = torch.tensor(load_iris().target)
# 数据归一化
x_min = torch.min(x, dim=0).values
x_max = torch.max(x, dim=0).values
x = (x - x_min) / (x_max - x_min)
if shuffle:
idx = torch.randperm(x.shape[0])
x = x[idx]
y = y[idx]
return x, y
# 自定义鸢尾花数据类
class IrisDataset(Dataset):
def __init__(self, mode='train', num_train=120, num_dev=15):
super(IrisDataset, self).__init__()
x, y = load_data(shuffle=True) # 将x转换为浮点型数据
y = y.long() # 将y转换为长整型数据
# x, y = load_data(shuffle=True)
if mode == 'train':
self.x, self.y = x[:num_train], y[:num_train]
elif mode == 'dev':
self.x, self.y = x[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
else:
self.x, self.y = x[num_train + num_dev:], y[num_train + num_dev:]
def __getitem__(self, idx):
return self.x[idx], self.y[idx]
def __len__(self):
return len(self.x)
# 创建一个模型类来定义神经网络模型
class IrisModel(nn.Module):
def __init__(self):
super(IrisModel, self).__init__()
self.fc = nn.Linear(4, 3)
def forward(self, x):
return self.fc(x)
# 加载数据
batch_size = 16
train_dataset = IrisDataset(mode='train')
dev_dataset = IrisDataset(mode='dev')
test_dataset = IrisDataset(mode='test')
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = DataLoader(dev_dataset, batch_size=batch_size)
test_loader = DataLoader(test_dataset, batch_size=1, shuffle=True)
# 实例化神经网络模型
model = IrisModel()
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# 训练模型
num_epochs = 20
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
for epoch in range(num_epochs):
model.train()
total_loss = 0
for batch_x, batch_y in train_loader:
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
batch_x = batch_x.to(torch.float) # 使用float32数据类型
# RuntimeError: mat1 and mat2 must have the same dtype, but got Double and Float
optimizer.zero_grad()
output = model(batch_x)
loss = criterion(output, batch_y)
loss.backward()
optimizer.step()
total_loss += loss.item()
avg_loss = total_loss / len(train_loader)
print(f"Epoch [{epoch + 1}/{num_epochs}], Train Loss: {avg_loss}")
# 保存模型
torch.save(model.state_dict(), 'iris_model.pth')
#%%
# 加载模型
model = IrisModel() # 先实例化一个模型
model.to(device)
model.load_state_dict(torch.load('iris_model.pth'))
# 评估模型
model.eval()
total_correct = 0
total_samples = 0
with torch.no_grad():
for batch_x, batch_y in test_loader:
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
batch_x = batch_x.to(torch.float) # 使用float32数据类型
output = model(batch_x)
_, predicted = torch.max(output, dim=1)
total_correct += (predicted == batch_y).sum().item()
total_samples += batch_y.size(0)
accuracy = total_correct / total_samples
print(f"Test Accuracy: {accuracy}")