1、问题
使用Pytorch及CNN框架实现手写数字识别问题
2、代码
1、导包
导入如下一些包,后面会用到
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.utils.data as Data
import torchvision
import time
from torchvision import transforms
import matplotlib.pyplot as plt
2、设置如下超参数
#超参数
EPOCH = 1
BATCH_SIZE = 1
LR = 0.001
# DOWNLOAD_MNIST = False
if_use_gpu = 0
分别是循环次数、每一批次训练的样本个数(因为要输出,所以设置为1)、学习率、是否使用cuda加速
3、导入训练集并传入到train_loader中
# 获取训练集dataset
training_data = torchvision.datasets.MNIST(
root=path, # dataset存储路径
train=True, # True表示是train训练集,False表示test测试集
transform=torchvision.transforms.ToTensor(), # 将原数据规范化到(0,1)区间
download=False,
)
# 获取测试集dataset
test_data = torchvision.datasets.MNIST(
root=path, # dataset存储路径
train=False, # True表示是train训练集,False表示test测试集
transform=torchvision.transforms.ToTensor(), # 将原数据规范化到(0,1)区间
download=False,
)
train_loader = Data.DataLoader(dataset=training_data, batch_size=BATCH_SIZE,shuffle=True)
使用pytorch提供的MNIST库,当download参数一开始设置为True的时候会自动下载数据集
4、创建模型
# 创建模型
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Sequential( # (1,28,28)
nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5,
stride=1, padding=2), # (16,28,28)
# 想要con2d卷积出来的图片尺寸没有变化, padding=(kernel_size-1)/2
nn.ReLU(),
nn.MaxPool2d(kernel_size=2) # (16,14,14)
#此方法中也有一个stride(步长) 意思?
)
self.conv2 = nn.Sequential( # (16,14,14)
nn.Conv2d(16, 32, 5, 1, 2), # (32,14,14)
nn.ReLU(),
nn.MaxPool2d(2) # (32,7,7)
)
self.out = nn.Linear(32 * 7 * 7, 10)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size(0), -1) # 将(batch,32,7,7)展平为(batch,32*7*7)
output = self.out(x)
return output
模型:经过两次卷积+Pooling
第一次卷积,从1层转变为16层,经过一个55的Filter,步长为1,外加两圈0,经过ReLU()激活函数
第二次卷积,从16层转变为32层,经过一个55的Filter,步长为1,外加两圈0,经过ReLU()激活函数
经过一次线性全连接,输出为10个神经元
注:nn.model中会自动经过softmax函数,所以计算出来会自动归一化转为概率
x = x.view(x.size(0), -1) # 将(batch,32,7,7)展平为(batch,32*7*7)
在torch里面,view函数相当于numpy的reshape
这里-1表示一个不确定的数,就是你如果不确定你想要reshape成几行,但是你很肯定要reshape成4列,那不确定的地方就可以写成-1
将数据reshape成x.size(0)行,因为要进入全连接层,所以要转变成线性进行输入
5、显示图片函数(用于显示训练数据)
#显示图片
def show_img(Tensor_img):
torch.squeeze(Tensor_img,dim=0)
unloader = transforms.ToPILImage()
image = Tensor_img.cpu().clone() # clone the tensor
image = image.squeeze(0) # remove the fake batch dimension
image = unloader(image)
image.save(path)
plt.imshow(image)
plt.show()
6、初始化模型并设置损失函数
# 损失
cnn = CNN()
# if if_use_gpu:
# cnn = cnn.cuda()
loss_function = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
损失函数设置为CrossEntropyLoss,交叉熵损失函数
交叉熵损失函数->最后有讲
7、训练模型
# 训练
for epoch in range(EPOCH):
start = time.time()
for step, (x, y) in enumerate(train_loader):
b_x = Variable(x, requires_grad=False)
print(b_x.shape)
show_img(b_x)
b_y = Variable(y, requires_grad=False)
# if if_use_gpu:
b_x = b_x
b_y = b_y
print(step)
output = cnn(b_x)
loss = loss_function(output, b_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
从训练集中去一批(设置为1)数据,使用反向传播的方式进行不断训练(反向传播具体解释在上一章)
8、保存模型
state = {'net':cnn.state_dict(), 'optimizer':optimizer.state_dict(), 'epoch':epoch}
torch.save(state,filePath)
9、使用测试集进行测试
# 测试
test_output = cnn(test_x)
print('-----', test_output.type)
pred_y = torch.max(test_output, 1)[1].data.squeeze()
print(pred_y, test_y)
accuracy = sum(pred_y == test_y) / test_y.size(0)
print('Test Acc: %.4f' % accuracy)
3、结果
从训练集中取一张图片进入训练查看:
预期结果:
实际输出:0
loss:损失值
4、总结问题
1、交叉熵损失函数CrossEntropyLoss
参考文档
该损失函数结合了nn.LogSoftmax()和nn.NLLLoss()两个函数。它在做分类(具体几类)训练的时候是非常有用的。在训练过程中,对于每个类分配权值,可选的参数权值应该是一个1D张量。当你有一个不平衡的训练集时,这是是非常有用的。
交叉熵:
交叉熵主要是用来判定实际的输出与期望的输出的接近程度,为什么这么说呢,举个例子:在做分类的训练的时候,如果一个样本属于第K类,那么这个类别所对应的的输出节点的输出值应该为1,而其他节点的输出都为0,即[0,0,1,0,….0,0],这个数组也就是样本的Label,是神经网络最期望的输出结果。也就是说用它来衡量网络的输出与标签的差异,利用这种差异经过反向传播去更新网络参数。
CrossEntropyLoss公式
交叉熵主要是用来判定实际的输出与期望的输出的接近程度,也就是交叉熵的值越小,两个概率分布就越接近。假设概率分布p为期望输出,概率分布q为实际输出 H(p,q)为交叉熵,表达式:
Pytorch中CrossEntropyLoss()函数的主要是将softmax-log-NLLLoss合并到一块得到的结果。
1、Softmax后的数值都在0~1之间,所以ln之后值域是负无穷到0。
2、然后将Softmax之后的结果取log,将乘法改成加法减少计算量,同时保障函数的单调性 。
3、NLLLoss的结果就是把上面的输出与Label对应的那个值拿出来(下面例子中就是:将log_output\logsoftmax_output中与y_target对应的值拿出来),去掉负号,再求均值。