在BP手写识别MINIST中,存在很多杂乱的知识点,很多知识点大家已经了解,就不会赘述它,我所认为的难理解或者不熟悉的知识点在每个部分都会注释出来,如果大家的基础知识比较牢固,可以直接跳过去看完整代码及运算结果。
简介
MINIST数据集即手写数字数据集,共有70000张图像,其中训练集60000张,测试集10000张。
所有图像都是28×28的灰度图像,每张图像包含一个手写数字。
共10个类别,每个类别代表0~9之间的一个数字,每张图像只有一个类别。
分部分描述代码
导入库
import torch.nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import matplotlib
#设置字体为楷体
matplotlib.rcParams['font.sans-serif'] = ['KaiTi'
其中,torchvision是计算机的视觉库
torchvision.transforms是数据转换模块
构建BP网络模型
class BPnetwork(torch.nn.Module):
def __init__(self):
super(BPnetwork, self).__init__() # 调用父类初始化方法
self.linear1 = torch.nn.Linear(28 * 28, 128)
self.ReLU1 = torch.nn.ReLU()
self.linear2 = torch.nn.Linear(128, 64)
self.ReLU2 = torch.nn.ReLU()
self.linear3 = torch.nn.Linear(64, 10)
self.softmax = torch.nn.LogSoftmax(dim=1)
def forward(self, x):
x=x.reshape(x.shape[0],-1)
x = self.linear1(x)
x = self.ReLU1(x)
x = self.linear2(x)
x = self.ReLU2(x)
x = self.linear3(x)
x = self.softmax(x)
return x
其中,输入层为图片的大小,即28*28
self.linear为隐藏层的定义,linear1-linear2分别表示两个隐藏层,而linear3为输出层
self.ReLu为使用ReLu激活函数来将其激活
最后,使用LogSoftmax来进行归一化处理
我们先来复习一下Softmax
而logsoftmax与softmax有什么不同呢,这里又为什么要用logsoftmax呢
1.Softmax函数将输入向量(通常表示某个实体的特征向量)转换为概率分布,其中每个元素的值介于0和1之间,并且所有元素的和为1。而LogSoftmax则是Softmax函数的一个变种,它在Softmax的基础上对结果进行了对数运算。
2.在下面会应用的NLLLoss(负对数似然损失)期望的是对数概率作为输入。LogSoftmax函数将对网络的原始输出(logits)应用softmax函数,并返回对数概率。
在前向传播函数(forward函数)中,刚开始的x=x.reshape(x.shape[0],-1)是什么意思呢
总而言之,就是重塑这个数组
最后别忘了返回值,返回x
准备数据集
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,))])
trainset = torchvision.datasets.MNIST('./data', train=True, download=True, transform=transform)
testset = torchvision.datasets.MNIST('./data', train=False, download=True, transform=transform)
trainloader=torch.utils.data.DataLoader(trainset,batch_size=2, shuffle=True)
testloader=torch.utils.data.DataLoader(testset,batch_size=2, shuffle=True)
让我们来一块一块的分析他们:transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5))])
这个功能函数可以看作一个功能函数容器,它里面可以放多个功能函数(多个的话应该把这些功能函数放在一个列表内),当该功能函数定义好后,其内部的其他功能函数也会随之按我们给定的要求定义好,当我们调用compose的实例的时候,就会按照我们在容器内部摆放的顺序,从左至右的依次调用功能函数
然后是里面的两个功能函数,
第一个ToTensor()是PIL.Image或numpy.ndarray转换成tensor
第二个Normalize((0.5),(0.5))是指两个参数分别表示均值和标准差,意味着数据会被减去0.5后再除以0.5,结果会落在[-1, 1]范围内
接下来是trainset和testset加载MNIST测试集和训练集
'./data'表示数据集的存储路径
train=True表示加载的是训练集,为False则为测试集
download=True表示如果数据集不存在,则自动从网上下载 transform=transform表示应用前面定义的预处理流程
最后是trainloader和testloader 创建数据加载器
使用torch.utils.data.DataLoader来加载数据
batch_size=2表示每个批次包含2个样本
shuffle=True表示在每个训练周期开始时打乱数据
训练模型
model=BPnetwork()
criterion = torch.nn.NLLLoss()
optimizer= torch.optim.SGD(model.parameters(), lr=0.003, momentum=0.9)
eopchs=20
for i in range(eopchs):
sumloss=0
for images, lables in trainloader:
ypre=model(images)
loss=criterion(ypre,lables)
loss.backward()
optimizer.step()
optimizer.zero_grad()
sumloss+=loss.item()
print("Epoch {}, Loss: {}".format(i+1, sumloss/len(trainloader)))
这里损失函数critierion用的是NLLLoss负对数似然损失函数,巧妙的是,它能与LogSoftmax函数对应起来,那么他是怎么对应的呢?
在神经网络的最后一层,通常使用softmax函数将输出转换为概率分布。softmax函数与NLLLoss配合使用是非常常见的做法,因为softmax函数可以将网络的原始输出转换为概率分布,而NLLLoss则可以直接计算这些概率与真实标签之间的差异。
而优化器使用了SGD随机梯度下降优化器来更新参数
这里我们选择进行20批次的循环,而每批次内存在两个数据
内层循环for images, lables in trainloader:
从trainloader
中按批次读取训练数据图像和标签。
ypre=model(images)
将图像数据传递给模型进行前向传播,得到预测结果。
loss=criterion(ypre,lables)
计算预测结果和真实标签之间的损失。
loss.backward()
反向传播计算梯度。
optimizer.step()
使用优化器更新模型参数。
optimizer.zero_grad()
清空之前的梯度信息。
sumloss+=loss.item()
累加每一个批次的损失值。
测试模型
examples=enumerate(testloader)
batch,(images,lables)=next(examples)
fig=plt.figure()
for i in range(2):
logps=model(images[i])
probab=list(logps.detach().numpy()[0])
pred_label=probab.index(max(probab ))
img=torch.squeeze(images[i])
img=img.numpy()
plt.subplot(8,8,i+1)
plt.tight_layout()
plt.imshow(img[i],cmap='gray',interpolation='none')
plt.title(f"预测值:{pred_label}")
plt.xticks([])
plt.yticks([])
plt.show()
examples=enumerate(testloader)
初始化一个枚举器来遍历测试数据加载器。
next(examples)
调用会获取 examples
迭代器的下一个元素,即下一个批次的索引和内容
probab = list(logps.detach().numpy()[0])
pred_lable = probab.index(max(probab))
这个过程用于将模型的输出(对数概率)转换为模型预测的类别索引。由于我们直接比较对数概率来找到最大值,这意味着具有最高对数概率的类别将被认为是模型预测的最可能的类别。
全部代码
import torch.nn
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import matplotlib
#设置字体为楷体
matplotlib.rcParams['font.sans-serif'] = ['KaiTi']
# 构建BP网络模型
class BPnetwork(torch.nn.Module):
def __init__(self):
super(BPnetwork, self).__init__()
self.linear1 = torch.nn.Linear(28 * 28, 128)
self.ReLU1 = torch.nn.ReLU()
self.linear2 = torch.nn.Linear(128, 64)
self.ReLU2 = torch.nn.ReLU()
self.linear3 = torch.nn.Linear(64, 10)
self.softmax = torch.nn.LogSoftmax(dim=1)
def forward(self, x):
x=x.reshape(x.shape[0],-1)
x = self.linear1(x)
x = self.ReLU1(x)
x = self.linear2(x)
x = self.ReLU2(x)
x = self.linear3(x)
x = self.softmax(x)
return x
transform=transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,), (0.5,))])
trainset = torchvision.datasets.MNIST('./data', train=True, download=True, transform=transform)
testset = torchvision.datasets.MNIST('./data', train=False, download=True, transform=transform)
trainloader=torch.utils.data.DataLoader(trainset,batch_size=2, shuffle=True)
testloader=torch.utils.data.DataLoader(testset,batch_size=2, shuffle=True)
model=BPnetwork()
criterion = torch.nn.NLLLoss()
optimizer= torch.optim.SGD(model.parameters(), lr=0.003, momentum=0.9)
eopchs=20
for i in range(eopchs):
sumloss=0
for images, lables in trainloader:
ypre=model(images)
loss=criterion(ypre,lables)
loss.backward()
optimizer.step()
optimizer.zero_grad()
sumloss+=loss.item()
print("Epoch {}, Loss: {}".format(i+1, sumloss/len(trainloader)))
# 测试模型
examples=enumerate(testloader)
batch,(images,lables)=next(examples)
fig=plt.figure()
for i in range(2):
logps=model(images[i])
probab=list(logps.detach().numpy()[0])
pred_label=probab.index(max(probab))
img=torch.squeeze(images[i])
img=img.numpy()
plt.subplot(2,2,i+1)
plt.tight_layout()
plt.imshow(img,cmap='gray',interpolation='none')
plt.title(f"预测值:{pred_label}")
plt.xticks([])
plt.yticks([])
plt.show()
代码结果
Epoch 1, Loss: 0.4721189867448605
Epoch 2, Loss: 0.3229958871696436
Epoch 3, Loss: 0.27505046313140197
Epoch 4, Loss: 0.2502201998357109
Epoch 5, Loss: 0.2433886310391548
Epoch 6, Loss: 0.2204606224730118
Epoch 7, Loss: 0.21268426436964044
Epoch 8, Loss: 0.2311711722210322
Epoch 9, Loss: 0.21294552893158514
Epoch 10, Loss: 0.22649696154286839
Epoch 11, Loss: 0.21067813366969465
Epoch 12, Loss: 0.21920832267444063
Epoch 13, Loss: 0.20680391188663053
Epoch 14, Loss: 0.2363903035555209
Epoch 15, Loss: 0.2506306987980695
Epoch 16, Loss: 0.22015327460341025
Epoch 17, Loss: 0.21821466266413683
Epoch 18, Loss: 0.20693239517582385
Epoch 19, Loss: 0.2428908858018597
Epoch 20, Loss: 0.20830152642913438
在这里我们可以看到随着轮数的增加,损失值逐渐减小
这里我们可以看到,预测值能正确识别到手写数字,也就代表成功进行了手写字体识别。