本文通过PyTorch框架来构建、训练以及评估一个简单的全连接神经网络,以便理解神经网络的基本结构,并通过实际操作获得第一手的经验。选择的任务是在经典的MNIST手写数字数据集上进行数字识别,这是学习深度学习不可或缺的一个实验。
一、PyTorch概览
PyTorch是一个开源的机器学习库,广泛用于计算机视觉和自然语言处理等领域。它由Facebook的人工智能研究团队开发,以其灵活性和速度受到研究人员和工业界的青睐。
二、网络构建
网络结构相对基础:包括两个有128个神经元的隐藏层,以及一个适用于10类分类任务的输出层。每个隐藏层后面都跟有一个ReLU激活函数,以增加网络的非线性能力,使其能够学习复杂的数据模式。
import torch
import torch.nn as nn
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 128)
self.output = nn.Linear(128, 10)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.output(x)
return x
这段代码创建了一个简单的全连接神经网络,适用于输入特征维度为784(例如,处理后的28x28图片),并通过两个隐藏层向10个输出节点(10个类别)映射的分类任务。每个隐藏层后使用ReLU函数引入非线性,以增强模型的表达能力。这段代码详解如下:
(1)导入必要的库:
import torch
import torch.nn as nn
这两行代码导入了PyTorch (torch) 和其神经网络模块 (torch.nn)。torch.nn 包含建立神经网络所需的所有构建块,如各种类型的层和激活函数。
(2)定义神经网络类:
class SimpleNN(nn.Module):
这里定义了一个名为 SimpleNN 的新类,它继承自 nn.Module。在PyTorch中,所有的神经网络模型都应继承自 nn.Module,这样可以利用到该基类中的很多功能,如参数管理和模块化。
(3)初始化方法:
def __init__(self):
super(SimpleNN, self).__init__()
在类的初始化方法中,首先调用 super() 函数初始化基类 nn.Module。这是固定写法,保证了神经网络基础功能的正确初始化。
(4)定义神经网络层:
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 128)
self.output = nn.Linear(128, 10)
这部分代码定义了三个全连接层(也称为线性层):
- self.fc1 是第一个全连接层,输入特征为784(通常是处理过的28x28像素的MNIST图像),输出特征为128。
- self.fc2 是第二个全连接层,它接收前一层128个特征的输出,并输出128个特征。
- self.output 是输出层,接收来自上一层的128个特征的输出,并将其映射到10个输出,通常表示分类任务中的类别数量。
Linear运算方法:
Linear
参数量的计算方法:参数量为:w * h + h, 如果 bias = False, 则 为 w * h, 所以第一层数量为:784*128 + 128 = 100480,打印出模型参数也是一致:
================================================================= Layer (type:depth-idx) Param # ================================================================= SimpleNN -- ├─Linear: 1-1 100,480 ├─Linear: 1-2 16,512 ├─Linear: 1-3 1,290 ================================================================= Total params: 118,282 Trainable params: 118,282 Non-trainable params: 0 =================================================================
(5)定义前向传播方法:
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.output(x)
return x
这个 forward 方法定义了数据通过网络的方式,即神经网络的前向传播。
首先,数据 x 被传入第一个全连接层 fc1,然后通过ReLU激活函数进行非线性变换。
处理后的输出再次通过第二个全连接层 fc2 和另一个ReLU激活函数。
最后,通过输出层 output 得到最终的输出。
三、数据加载与处理
对于数据加载与处理,我们使用torchvision库来加载MNIST数据集,并利用transforms进行归一化处理,使神经网络的训练过程更为高效。
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_set = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
test_set = datasets.MNIST(root='./data', train=False, transform=transform)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
这段代码展示了如何使用torchvision和torch.utils.data包中的工具来加载和预处理MNIST数据集,以便进行机器学习或深度学习任务。通过这种方式,可以方便地在训练和测试过程中按批获取数据,同时进行必要的数据预处理。这段代码详解如下:
(1)导入必要的库:
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
- torchvision 是处理图像数据的库,它提供了常用的数据集和图像变换功能。
- transforms 用于数据预处理和增强。
- datasets 用于加载数据集。
- DataLoader 是PyTorch中一个非常重要的类,它将一个可迭代的数据集封装成一个迭代器,方便批处理和数据洗牌等操作。
(2)定义数据预处理操作:
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
- 这部分代码定义了将要对数据执行的变换操作,Compose 创建了一个变换序列。
- ToTensor() 将PIL图像或NumPy ndarray 转换为FloatTensor,并在[0., 1.]范围内缩放图像的像素强度值。
- Normalize((0.5,), (0.5,)) 对张量图像进行标准化,给出的均值(mean)和标准差(std)应用于所有三个通道。这里均值和标准差设置为0.5,意味着[0, 1]的输入将被标准化到[-1, 1]。对于灰度图(如MNIST),只需要给出一个通道的均值和标准差。
(3)加载训练集:
train_set = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
使用datasets.MNIST加载MNIST训练数据集。root参数指定数据存储的路径,train=True表明是加载训练集(train=False加载测试集),download=True指在数据不在指定路径时自动下载,transform=transform应用之前定义的预处理操作。
(4)创建训练数据加载器:
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
DataLoader封装了train_set,batch_size=64指定了每个批次的大小,shuffle=True表示在每个epoch开始时,数据将被打乱,这有助于模型泛化。
(5)加载测试集:
test_set = datasets.MNIST(root='./data', train=False, transform=transform)
类似于训练集加载方式,train=False表明此处加载的是测试集。
(6)创建测试数据加载器:
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
为测试数据集创建一个DataLoader,shuffle=False通常用于测试数据,因为在测试阶段不需要打乱数据。
四、训练过程
在定义了模型和数据加载器之后,我们通过编写一个训练循环来训练模型。在此过程中,我们采用CrossEntropyLoss作为损失函数,并选择Adam作为优化器。
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
for epoch in range(5): # 训练5个Epoch
for images, labels in train_loader:
images = images.view(-1, 28*28)
optimizer.zero_grad()
output = model(images)
loss = criterion(output, labels)
loss.backward()
optimizer.step()
这段代码通过设定训练周期,不断对数据进行前向传播、损失计算、反向传播和参数更新,以此来训练神经网络模型,优化其在给定数据上的性能。这段代码详解如下:
(1)设置优化器:
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
- 这里使用的是Adam优化器,是一种自适应学习率的算法,常用于训练深度学习模型。
- model.parameters() 返回模型中所有需要被训练的参数。
- lr=0.001 设置了学习率为0.001。学习率是一个超参数,用来控制在优化过程中参数更新的步长大小。
(2)定义损失函数:
criterion = nn.CrossEntropyLoss()
使用交叉熵损失函数,它是分类任务中常用的损失函数之一。它同时考虑了正确分类的概率并进行了对数变换,以胜避数值不稳定。
(3)训练循环:
for epoch in range(5): # 训练5个Epoch
for images, labels in train_loader:
外层循环变量epoch用于迭代整个数据集,循环次数表示为5个训练周期(或“epoch”),每个epoch包括对整个训练集的一次遍历。
内层循环通过train_loader迭代器遍历训练数据。train_loader将数据集分成多个批次,其中每个批次包含了图像和相应的标签。
(4)数据预处理:
images = images.view(-1, 28*28)
将图像张量从二维图像(每个图像28x28像素)转换为一维向量,因为我们的模型是全连接的(SimpleNN)。-1在这里的意思是自动计算这一维的大小,确保整个批次的数据能被正确reshape。
(5)梯度归零:
optimizer.zero_grad()
在每次的梯度计算之前需要将梯度归零(重置),否则梯度会累加到已有的梯度上,这是因为PyTorch在调用.backward()时默认会累积梯度。
(6)前向传播:
output = model(images)
执行模型的前向传播步骤,输出每个类别的预测结果。
(7)计算损失:
loss = criterion(output, labels)
根据模型输出和真实标签计算损失值。
(8)反向传播:
loss.backward()
执行反向传播,计算关于损失函数的所有模型参数的梯度。
(9)参数更新:
optimizer.step()
根据计算得到的梯度更新模型参数,以最小化损失函数。
五、评估与分析
在模型被训练后,我们对其性能进行评估,以了解模型在未见过的数据上的表现。此外,我们也可以分析模型在哪些类别上表现良好,哪些类别上还有改进的空间。
def evaluate_model(model, data_loader):
model.eval()
total_correct = 0
total = 0
with torch.no_grad():
for images, labels in data_loader:
images = images.view(-1, 28*28)
output = model(images)
_, predicted = torch.max(output.data, 1)
total += labels.size(0)
total_correct += (predicted == labels).sum().item()
print(f'Accuracy: {100 * total_correct / total:.2f}%')
evaluate_model(model, test_loader)
这段代码主要用于模型训练后的性能验证,帮助了解模型在未知数据上的泛化能力。通过这种方式,可以很直观地看到模型准确率的具体数值。这段代码详解如下:
(1)函数定义:
def evaluate_model(model, data_loader):
定义名为 evaluate_model 的函数,接受两个参数:model —— 训练好的模型,data_loader —— 用于评估该模型的数据加载器。
(2)模型评估模式:
model.eval()
调用 .eval() 将模型设置到评估模式。这是必要的步骤,因为某些模型层(如:Dropout、BatchNorm等)在训练和评估时的行为是不同的。
(3)初始化计数器:
total_correct = 0
total = 0
初始化 total_correct 变量来记录预测正确的样本数量,初始化 total 变量来记录总样本数量。
(4)禁用梯度计算:
with torch.no_grad():
使用 torch.no_grad() 上下文管理器在评估模型时禁用梯度计算。这是为了提高计算效率,及节省内存,因为在评估过程中不需要进行反向传播。
(5)数据加载并处理:
for images, labels in data_loader:
images = images.view(-1, 28*28)
迭代数据加载器中的数据。数据加载器返回一批图像和对应的标签。
将每批图像数据通过 view 方法调整为二维张量,以匹配模型的输入格式(模型期望输入为向量形式)。
(6)模型预测:
output = model(images)
_, predicted = torch.max(output.data, 1)
使用模型对输入图像进行前向传播,得到预测结果。
torch.max(output.data, 1) 返回每一行的最大值的索引,即预测的类别标签。
(7)统计正确的预测数量和总数量:
total += labels.size(0)
total_correct += (predicted == labels).sum().item()
更新总样本数。labels.size(0) 给出当前批次的样本数量。
更新正确预测的总数。比较 predicted 和 labels 得到一个布尔张量,然后使用 .sum().item() 得到正确预测的数量。
(8)打印准确率:
print(f'Accuracy: {100 * total_correct / total:.2f}%')
最后输出准确率,计算方式是正确预测的数量除以总样本数量,转换为百分比格式输出。
(9)调用函数评估模型:
evaluate_model(model, test_loader)
使用上面定义的 evaluate_model 函数和参数 model(训练好的模型)及 test_loader(测试数据加载器)来评估模型性能。
六、完整代码和运行结果
(1)调用GPU计算:
import torch
import torch.nn as nn
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import time
train_start = time.time() # 训练开始时间
# 设定是否使用CUDA(如果可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
# 网络构建
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义第一层全连接层:输入特征784,输出特征128
self.fc1 = nn.Linear(784, 128)
# 定义第二层全连接层:输入特征128,输出特征128
self.fc2 = nn.Linear(128, 128)
# 定义输出层:输入特征128,输出特征10(对应10个数字类别)
self.output = nn.Linear(128, 10)
def forward(self, x):
# 对第一层的输出应用ReLU激活函数
x = torch.relu(self.fc1(x))
# 对第二层的输出也应用ReLU激活函数
x = torch.relu(self.fc2(x))
# 通过输出层得到最终的分类结果
x = self.output(x)
return x
# 数据加载与处理
transform = transforms.Compose([
transforms.ToTensor(), # 将图片数据转换为Tensor
transforms.Normalize((0.5,), (0.5,)), # 对数据进行归一化处理
])
# 加载训练数据集
train_set = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
# 加载测试数据集
test_set = datasets.MNIST(root='./data', train=False, transform=transform)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
# 将模型实例化并转移到定义的设备(CPU或GPU)
model = SimpleNN().to(device)
# 选择优化器和损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
# 训练过程
for epoch in range(5): # 训练5个循环周期
for images, labels in train_loader:
# 调整图片形状并转移到相同的设备
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
optimizer.zero_grad() # 清除历史梯度
output = model(images) # 前向传播计算模型输出
loss = criterion(output, labels) # 计算损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 评估函数
def evaluate_model(model, data_loader):
model.eval() # 将模型设置为评估模式
total_correct = 0
total = 0
with torch.no_grad(): # 禁止梯度计算
for images, labels in data_loader:
# 将图片和标签数据转移到相同的设备
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
output = model(images) # 前向传播得到预测结果
_, predicted = torch.max(output.data, 1) # 得到预测的类别
total += labels.size(0)
total_correct += (predicted == labels).sum().item() # 统计正确预测的数量
# 打印准确率
print(f'Accuracy: {100 * total_correct / total:.2f}%')
# 使用测试数据集评估模型性能
evaluate_model(model, test_loader)
train_end = time.time() # 训练结束时间
print("训练时间: {:.2f}秒".format(train_end - train_start))
运行结果:
(2)调用CPU计算:
import torch
import torch.nn as nn
from torchvision import transforms, datasets
from torch.utils.data import DataLoader
import time
train_start = time.time() # 训练开始时间
# 设定是否使用CUDA(如果可用)
device = torch.device("cpu")
print(device)
# 网络构建
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
# 定义第一层全连接层:输入特征784,输出特征128
self.fc1 = nn.Linear(784, 128)
# 定义第二层全连接层:输入特征128,输出特征128
self.fc2 = nn.Linear(128, 128)
# 定义输出层:输入特征128,输出特征10(对应10个数字类别)
self.output = nn.Linear(128, 10)
def forward(self, x):
# 对第一层的输出应用ReLU激活函数
x = torch.relu(self.fc1(x))
# 对第二层的输出也应用ReLU激活函数
x = torch.relu(self.fc2(x))
# 通过输出层得到最终的分类结果
x = self.output(x)
return x
# 数据加载与处理
transform = transforms.Compose([
transforms.ToTensor(), # 将图片数据转换为Tensor
transforms.Normalize((0.5,), (0.5,)), # 对数据进行归一化处理
])
# 加载训练数据集
train_set = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_set, batch_size=64, shuffle=True)
# 加载测试数据集
test_set = datasets.MNIST(root='./data', train=False, transform=transform)
test_loader = DataLoader(test_set, batch_size=64, shuffle=False)
# 将模型实例化并转移到定义的设备(CPU或GPU)
model = SimpleNN().to(device)
# 选择优化器和损失函数
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()
# 训练过程
for epoch in range(5): # 训练5个循环周期
for images, labels in train_loader:
# 调整图片形状并转移到相同的设备
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
optimizer.zero_grad() # 清除历史梯度
output = model(images) # 前向传播计算模型输出
loss = criterion(output, labels) # 计算损失
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新模型参数
# 评估函数
def evaluate_model(model, data_loader):
model.eval() # 将模型设置为评估模式
total_correct = 0
total = 0
with torch.no_grad(): # 禁止梯度计算
for images, labels in data_loader:
# 将图片和标签数据转移到相同的设备
images, labels = images.view(-1, 28*28).to(device), labels.to(device)
output = model(images) # 前向传播得到预测结果
_, predicted = torch.max(output.data, 1) # 得到预测的类别
total += labels.size(0)
total_correct += (predicted == labels).sum().item() # 统计正确预测的数量
# 打印准确率
print(f'Accuracy: {100 * total_correct / total:.2f}%')
# 使用测试数据集评估模型性能
evaluate_model(model, test_loader)
train_end = time.time() # 训练结束时间
print("训练时间: {:.2f}秒".format(train_end - train_start))
转载:https://zhuanlan.zhihu.com/p/696017829
标签:nn,self,torch,神经网络,pytorch,train,images,model,MNIST From: https://www.cnblogs.com/grasp/p/18540403