UP:B站-刘二大人
原视频链接:09.多分类问题_哔哩哔哩_bilibili
# Softmax分类器解决多分类问题
# MNIST数据集60000张训练集,10000张测试集
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F # 引入激活函数relu的库
import torch.optim as optim
# prepare dataset
batch_size = 64
# ToTensor()将shape为(H, W, C)的nump.ndarray或img转为shape为(C, H, W)的tensor,其将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。
# Normalize是归一化,括号里面两个数分别是均值和方差,具体操作就是"(x-mean)/std",将每个元素分布到(-1,1)
# 均值和方差一般都是三个数,因为一般来说图像都是三个通道的,所以对应三组数
# 均值和方差的具体求法是对数据集中所有图像的像素计算均值和标准差得到的结果,但是因为MNIST数据集用了很多年,这里就用文献中的经验值
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
# train=True让训练集等于True提取训练集, download=True如果找不到这个数据集就从网上下载, transform=transform直接调用上面定义好的transform对数据集进行处理
train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
# 训练加载器
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
# train=Flase让训练集等于False提取测试集
test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=True, transform=transform)
# 测试加载器
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
# design model using class
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.l1 = torch.nn.Linear(784, 512) # MNIST数据集每个图像是1维,28×28(=784)大小的
self.l2 = torch.nn.Linear(512, 256)
self.l3 = torch.nn.Linear(256, 128)
self.l4 = torch.nn.Linear(128, 64)
self.l5 = torch.nn.Linear(64, 10)
def forward(self, x):
x = x.view(-1, 784) # ,这里将1*28*28改变形状变成1*784,即每幅图像都变成1行784列的输入,第一个数选-1表示根据需要的列数自动计算行数
x = F.relu(self.l1(x))
x = F.relu(self.l2(x))
x = F.relu(self.l3(x))
x = F.relu(self.l4(x))
return self.l5(x) # 最后一层不做激活,不进行非线性变换,后面会接softmax分类器
# 实例化模型
model = Net()
# construct loss and optimizer
criterion = torch.nn.CrossEntropyLoss() # 交叉熵损失
#optim.SGD中的momentum(冲量)参数的意义:
# 在普通的梯度下降法x+=v中,每次x的更新量v为v=−dx∗lr,其中dx为目标函数func(x)对x的一阶导数。
# 当使用冲量时,则把每次x的更新量v考虑为本次的梯度下降量−dx∗lr与上次x的更新量v乘上一个介于[0,1]的因子momentum的和,即:
# v′=−dx*lr+v∗momemtum
# 当本次梯度下降-dx*lr的方向与上次更新量v的方向相同时,上次的更新量能够对本次的搜索起到一个正向加速的作用。
# 当本次梯度下降-dx*lr的方向与上次更新量v的方向相反时,上次的更新量能够对本次的搜索起到一个减速的作用。
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
# training cycle forward, backward, update
# 定义单个epoch
def train(epoch):
running_loss = 0.0
for i, (inputs, target) in enumerate(train_loader, 0):
outputs = model(inputs)
loss = criterion(outputs, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
# 如果迭代次数除以300的余数等于299,因为i从0开始,所以意思是每300轮输出一次
if i % 300 == 299:
# 输出[%d代表是一位整数,%5d代表是5位,位数不够用空格填充],损失用小数点后三位浮点型表示。
# %用于格式化输出的连接符号,总的循环次数+1,batch的循环次数+1,损失除以300轮,求一次的损失
print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 300))
running_loss = 0.0
# 定义测试模块
def test():
correct = 0 # 正确的数量
total = 0 # 总数
with torch.no_grad(): # 用with方式让torch不计算梯度
for (images, labels) in test_loader:
# 用torch中的max函数,沿着输出的数据的维度为1方式,找到最大值和最大值的下标
# _是最大值,predicted是最大值的索引
# outputs的输出其实是总样本数/batch_size个tensor,每个tensor里面是64个1*10的数组(64对应的就是每个batch_size的大小),每个1*10的数组实际是该样本分别被预测成0到9这10类的概率大小
outputs = model(images)
# print(outputs)
# predicted的输出其实是总样本数/batch_size个tensor,每个tensor里面是1*64的数组,64个样本每个样本对应一个数字,等于是从outputs的每个样本的1*10的数组中找到最大值,返回的数字就是这个最大值的下标
_, predicted = torch.max(outputs.data, dim=1) # dim = 1 列是第0个维度,行是第1个维度
# print(predicted)
# labels是N×1的数组,labels.size即为[N, 1],所以第0个元素即为测试集样本总数N
total += labels.size(0) # 总数等于标签的size取第0个元素相加求得总数
for i in range(len(labels)): # 等同于correct += (predicted == labels).sum().item()
if predicted.tolist()[i] == labels.tolist()[i]:
correct += 1
# correct += (predicted == labels).sum().item() # 当预测的与标签相等即为正确的,也就是predicted == labels返回1
# 输出%d代表整数,%%代表%,%格式化输出得连接符号,正确除总数乘100
print('Accuracy on test set: %d %% ' % (100 * correct / total))
if __name__ == '__main__':
for epoch in range(10):
train(epoch)
test()