拆解上篇文章的代码,每行通过注释解释,有不清晰的,告诉我,我更新上去。
#torch是一个开源的机器学习库,主要用于深度学习应用,它提供了广泛的工具和功能,
#支持张量操作、自动微分以及各种神经网络构建模块
import torch
# PyTorch 库中的一个模块,主要用于构建和训练神经网络。它提供了许多用于构建深度学习模型的工具,
#包括层、损失函数、优化器等。用户可以利用这个模块方便地定义和训练神经网络模型,进行前向传播和反向
#传播等操作。具体来说,"torch.nn" 包括线性层、卷积层、循环神经网络层、激活函数等多种神经网络组件
import torch.nn as nn
# PyTorch 库中的一个模块,提供了一组优化算法,用于训练神经网络,常见的优化器包括随机梯度
#下降(SGD)、Adam、RMSprop 等
import torch.optim as optim
# PyTorch 中的一个模块,主要用于数据加载和处理
from torch.utils.data import DataLoader
# PyTorch 的 torchvision 库中导入 datasets 和 transforms 模块。
#datasets 模块提供了一些公共数据集的接口,例如 MNIST、CIFAR-10 等,方便加载和使用。
#transforms 模块则提供了一些数据预处理和增强的功能,可以对图像进行转换
#例如调整大小、归一化、翻转等
from torchvision import datasets, transforms
##SummaryWriter 一个用于记录训练过程中的各种信息(如损失、准确率、图像等)到 TensorBoard
#的工具,帮助你可视化模型的训练进度和性能
from torch.utils.tensorboard import SummaryWriter
#Matplotlib 是一个用于绘制图表和数据可视化的强大工具
#pyplot 模块则提供了类似于 MATLAB 的绘图接口,使得绘制各种图形变得简单和直观
import matplotlib.pyplot as plt
# 数据加载与预处理
# transforms.Compose 用于构建图像转换操作序列的函数
transform = transforms.Compose([
#将图像转换为张量(下面附录了解何为张量),并归一化至[0, 1]
transforms.ToTensor(),
#标准化图像数据,将每个通道的像素值按照指定的均值和标准差进行归一化操作
#这里参数(0.5,)表示一个元组,且只有一个元素,表示只有一个通道均值为0.5,标准差为0.5
transforms.Normalize((0.5,), (0.5,))
])
# 加载 MNIST 数据集,并应用指定的变换(transform)
# datasets.MNIST 是PyTorch 中的一个数据集类,用于加载 MNIST 数据集
# root 参数:指定数据存储位置,这里表示存储在当前目录下的 data 文件夹中
# train 参数:如果设置为 True,则加载训练集;如果设置为 False,则加载测试集
# download 参数:如果设置为 True,则如果数据集不存在,会自动下载;如果设置为 False,则不会下载(跑成功过,就可以改为False)
# train_dataset 变量为下载、变换后的训练集数据
train_dataset = datasets.MNIST(root='./data', train=True, download=False, transform=transform)
#test_dataset 变量为下载、变换后的测试集数据
test_dataset = datasets.MNIST(root='./data', train=False, download=False, transform=transform)
print("download success")
#DataLoader 批量加载训练集数据,每个批次包含的样本数64,Shuffle 表示每次训练,随机打乱
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
#DataLoader 批量加载测试集数据,每个批次样本数64,思考测试数据集,为什么不随机打乱?
#训练时,随机打乱,有助于更好训练模型,测试时打乱是为了验证模型结果,打不打乱不影响模型
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
# 模型定义
#"SimpleCNN" 是一个自定义的模型类名,而 "nn.Module" 是所有神经网络模块的基类。
# 在这个类中,通常会定义神经网络的结构(如卷积层、激活函数、池化层等)以及前向传播的方法。
# 通过继承 "nn.Module",可以利用 PyTorch 提供的神经网络功能,如参数管理、向前传播和反向传播的计算等
class SimpleCNN(nn.Module):
#是 Python 中类的构造函数(initializer)。创建一个类的实例时,__init__ 方法会被自动调用。
#它通常用于初始化类的属性,将参数传递给类的实例,并设置初始状态。
#在这个方法中,self 参数代表实例本身,它允许你访问类的属性和方法。
#通过在 __init__ 方法中定义参数,可以在创建对象时传递特定的值来初始化对象的状态。
def __init__(self):
#继承
super(SimpleCNN, self).__init__()
# 段代码用PyTorch的torch.nn模块定义了一个卷积层,具体如下:
#self.conv1 是 Conv2d 类的一个实例,表示一个2D卷积层。
#参数解释如下:1 表示输入通道的数量(例如,灰度图像只有1个通道)。
#32 表示该层将生成输出通道的数量(或滤波器的数量)。
#kernel_size=3 设置卷积核的大小为3x3。
#stride=1 表示步为1
#padding=1 表示图像素填充1格空
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1)
#这里32 是因为self.conv1的输出为32,这里的输入层为刚conv1的输出层
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1)
#nn.MaxPool2d类创建一个二维最大池化层。具体来说:
#nn.MaxPool2d(2, 2) 表示创建一个大小为2x2的池化窗口,步幅也是2。
#也就是说,它会对输入图像的每个2x2区域取最大值,并在每次滑动时跳过两个像素。
#这种池化操作通常用于减少特征图的空间尺寸,从而降低计算量和参数数量,同时保留重要的特征信息
self.pool = nn.MaxPool2d(2, 2)
#定义一个全连接层(fully connected layer),通常在构建神经网络时使用。
#self.fc1 是该层的名称,可以用来在类的其他方法中引用。
#nn.Linear 是 PyTorch 中的一个函数,用于创建一个全连接层。
#64 * 7 * 7 是该层的输入特征维度,表示输入数据的大小。在这里,输入是一个形状为 (64, 7, 7) 的张量,
#其中 64 是通道数(或特征图数量),7 和 7 是特征图的高度和宽度。
#即数据第一维是特征图的数量,第二维和第三维是特征图的尺寸。
#128 是该层的输出特征维度,表示输出将会有 128 个神经元。
self.fc1 = nn.Linear(64 * 7 * 7, 128)
#再做一次全连接,输出为10个特征
self.fc2 = nn.Linear(128, 10)
#这段代码是一个神经网络模型的前向传播(forward)方法,通常出现在深度学习框架PyTorch的模型定义中。下面是代码的逐行解释
def forward(self, x):
#这行代码首先将输入x传递给卷积层self.conv1,该层对输入执行卷积操作。然后通过nn.ReLU()应用ReLU激活函数,最后将结果输入到池化层self.pool,通常用于减少特征图的尺寸。
x = self.pool(nn.ReLU()(self.conv1(x)))
#类似于第一行,这里将经过卷积层self.conv2和ReLU激活函数的输出再次传递给池化层self.pool,进一步处理特征图
x = self.pool(nn.ReLU()(self.conv2(x)))
#这行代码将池化后的张量x重新调整形状。-1表示自动计算该维度的大小,64 * 7 * 7是特征图展平后的维度,通常用于连接到全连接层之前
x = x.view(-1, 64 * 7 * 7)
#最后一行返回模型的输出,通常是类别概率、回归值或者其他预测结果。
x = nn.ReLU()(self.fc1(x))
#在这一行,隐藏层的输出x被传递到第二个全连接层self.fc2,通常作为输出层,用于生成模型的最终预测。
x = self.fc2(x)
#最后一行返回模型的输出,通常是类别概率、回归值或者其他预测结果。
return x
# 训练设置
#实例化神经网络模型
model = SimpleCNN()
#定义一个损失函数,这个函数是多分类问题的一种损失函数,它结合了Softmax函数和交叉熵损失的操作
criterion = nn.CrossEntropyLoss()
#PyTorch 库中的 Adam 优化器来优化一个模型
#optimizer:这是我们定义的变量,用于存储优化器的实例。
#optim.Adam:这是调用了 Adam 优化算法,Adam 是一种常用的优化算法,它结合了动量和自适应学习率的优点。
#model.parameters():用于获取模型中所有可学习参数的列表,这些参数将在优化过程中进行更新。
#lr=0.001:学习率的设置,表示每一次更新参数时,步长的大小为 0.001。学习率控制模型学习的速度,值过大会导致训练不稳定,值过小则可能导致训练速度过慢。
optimizer = optim.Adam(model.parameters(), lr=0.001)
#分10批训练
num_epochs = 10
# TensorBoard 初始化
writer = SummaryWriter()
# 记录损失和准确率
loss_values = []
accuracy_values = []
# 训练循环
for epoch in range(num_epochs):
running_loss = 0.0
correct = 0
total = 0
for images, labels in train_loader:
#用于在每次优化步骤之前清零模型参数的梯度。在使用反向传播计算梯度时,会在每次迭代中累加梯度。
#如果不清零,梯度将会不断累积,从而导致更新不准确。
optimizer.zero_grad()
#输入的图像数据 images 传递给模型 model,以生成对应的输出 outputs
outputs = model(images)
#这句代码是在计算模型输出和真实标签之间的损失值。
#outputs 是模型的预测结果,labels 是实际的类别标签,
#criterion 通常是一个损失函数(如交叉熵损失或均方误差)。
#损失值用于衡量模型的预测与真实标签之间的差距,损失越小,模型的预测性能越好
loss = criterion(outputs, labels)
#计算损失函数关于模型参数的梯度,每个可训练参数的梯度,并将这些梯度存储在相应参数的 .grad 属性中
loss.backward()
#作用是使用已计算出的梯度来更新模型的参数,使模型向更低的损失值方向优化
optimizer.step()
#统计总的loss
running_loss += loss.item()
#找到最概率最大的预测值
#_, predicted:下划线 _ 表示我们不关心第一个返回值(即最大值)
#predicted 则会存储每个样本中得分最高类别的索引
_, predicted = torch.max(outputs.data, 1)
#计算总数
total += labels.size(0)
#计算预测值与实际值正确的次数
correct += (predicted == labels).sum().item()
#计算平均差异(损失)
avg_loss = running_loss / len(train_loader)
#计算争取率
accuracy = 100 * correct / total
# 记录到 TensorBoard 画图,见下图,y轴为平均损失,x 轴为批次好
writer.add_scalar('Loss/train', avg_loss, epoch)
writer.add_scalar('Accuracy/train', accuracy, epoch)
# 记录到列表
loss_values.append(avg_loss)
accuracy_values.append(accuracy)
#打印每个批次,损失情况,准确率情况
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {avg_loss:.4f}, Accuracy: {accuracy:.2f}%')
writer.close()
# 测试模型
model.eval()
test_loss = 0.0
correct = 0
total = 0
i=0
#用于在代码块内部禁用梯度计算。这意味着在这个代码块内,模型的参数不会计算梯度
with torch.no_grad():
#开始测试,每行代码含义,参考训练代码中的描述
for images, labels in test_loader:
outputs = model(images)
loss = criterion(outputs, labels)
test_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
if (predicted[i] != labels[i] ):
print("我猜是",predicted[i],"实际是",labels[i])
plt.imshow(images[i].reshape(28,28),cmap='Greys', interpolation='nearest')
plt.show()
test_loss /= len(test_loader)
test_accuracy = 100 * correct / total
print(f'Test Loss: {test_loss:.4f}, Test Accuracy: {test_accuracy:.2f}%')
# 使用 Matplotlib 绘制损失和准确率
plt.figure(figsize=(12, 4))
# 绘制损失图
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs + 1), loss_values, label='Loss')
plt.title('Training Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
# 绘制准确率图
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs + 1), accuracy_values, label='Accuracy', color='orange')
plt.title('Training Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.tight_layout()
plt.show()
附录:
这篇学点什么
一、理解什么是张量/矩阵/向量/标量?
1、什么是标量【Scalar】一个单独的数字,比如 50,3.1425926.. 【零维张量,无方向】
2、什么是向量【Vector】多个标量的集合【一维张量】,如[年龄、性别、身高], 描述一个人 [50,1,178]
3、什么是矩阵【Matrix可以】理解为多维的向量【二维张量】,比如一群人,2*3矩阵 [[50,1,178],[20,0,160]]
4、什么是张量【Tensor】可以理解为多维的数组,多维矩阵男人、女人维度2*2*3 [[[50,1,178],[20,1,180]],[[50,0,178],[20,0,160]]]
二、神经网络的实现大致步骤:
有样本,有标注的数据,经过模型训练后,如何进行对测试样本进行预测。整个过程大致是:
① 下载数据样本数据,并进行预处理,比如图片进行transform为张量,样本数据可以分为训练数据与测试数据,训练数据用于训练模型,测试数据为了验证模型。
②设计神经网络,根据样本图片,抽象为N个通道(比如R/G/B 三个通道的)张量。
- 选择网络类型:根据任务选择合适的网络结构(如全连接网络、卷积神经网络(CNN)、递归神经网络(RNN)等)。
- 确定层数和每层神经元数:根据任务复杂性决定深度和宽度。
- 选择激活函数:常用的有ReLU、Sigmoid、Tanh等。
- 设计输出层:根据问题选择输出层的激活函数(如Softmax用于分类,线性用于回归)。
③ 训练配置
- 选择损失函数:根据任务类型选择适当的损失函数(如交叉熵、均方误差)。
- 选择优化算法:如SGD、Adam等。
- 设置超参数:如学习率、批次大小、训练轮次等。
④ 模型评估
- 评估标准:选择合适的评估指标(如准确率、召回率、F1-score等)。
- 在验证集和测试集上评估:确定模型的实际性能。
下篇,再尝试找个官方体系化的教程,细化下整个案例的过程中,隐藏的AI知识或概念。