开始了解深度学习有一小段时间了,期间我看了几个up主讲的LeNet实现,从最开始的每个函数都要百度到现在基本知道每部分的作用,还能比较熟练的搭建网络模型,我想记录一下一个菜鸟的成长经历。
代码是由b站up【霹雳吧啦Wz】的源码进行注解和稍微改动过的,初学者可以去看up的视频,讲的非常透彻
首先我们开看一下LeNet的网络结构:
卷积+池化(下采样)+卷积+池化+三个全连接层
计算卷积输出大小的公式:N = (W − F + 2P )/S+1
N:图片输出的大小 W:原宽度 F:卷积核大小 P:padding S:stride
每层的参数:大家对照上面的图和下面的表格 就会发现网络的结构非常清晰
但是也有好多人把第一个全连接层也用卷积层来写,还是使用5*5的卷积核,正好展成一行,我们使用的激活函数是ReLU,我看的第一个视频是b站的炮哥的lenet实现手写数字识别,他就是用了三个卷积层,而且因为第三层卷积是把它变成线性的了就没有再加激活函数,包括全连接层也没有加激活函数(全连接层本来就是线性的,还需要激活函数吗?俺现在也不太明白)但是由于我使用那个网络结构训练的结果并不理想,这里我采用标准的网络模型,三层全连接层来实现,并在前两个全连接层上使用了激活函数
要做的解释:
【看up的视频学到了:搭好网络模型后,可以采用打断点的方式debug,步进看每一层的参数大小和深度如何变化】
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5)
self.pool1 = nn.MaxPool2d(kernel_size=2,stride=2)
self.conv2 = nn.Conv2d(16, 32, 5)
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
x = self.pool1(x) # output(16, 14, 14)
x = F.relu(self.conv2(x)) # output(32, 10, 10)
x = self.pool2(x) # output(32, 5, 5)
# x.view() 对tensor进行reshape -1表示规定另一个参数 这个参数自己计算 我们规定列数为32*5*5(展平 行由view函数自己算(参数的总数除规定的列数 这里也就是一行
x = x.view(-1, 32*5*5) # output(32*5*5)
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10)
return x
先贴源码,后面解释
import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=False, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
shuffle=True, num_workers=0)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=5000,
shuffle=False, num_workers=0)
test_data_iter = iter(test_loader)
test_image, test_label = test_data_iter.next()
test_image, test_label = test_image.to(device), test_label.to(device)
# 迭代器中的测试集图片和label也要to(device)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# def imshow(img):
# img = img /2 +0.5 #反标准化处理(为了图像能正常展示
# npimg = img.numpy() #转成图像格式
# plt.imshow(np.transpose(npimg,(1, 2, 0))) #tensor格式[b,c,h,w]的纬度转换成正常纬度[h,w,c]:
# plt.show()
#
# print(' '.join("%5s" % classes[test_label[j]] for j in range(4))) #打印标签
# imshow(torchvision.utils.make_grid(test_image)) #展示图片
net = LeNet().to(device)
loss_function = nn.CrossEntropyLoss() #内置了softmax函数
optimizer = optim.Adam(net.parameters(), lr=0.001)
save_path = './Lenet.pth'
maxAcc = 0.0
for epoch in range(5): # loop over the dataset multiple times
running_loss = 0.0
for step, data in enumerate(train_loader, start=0):
# get the inputs; data is a list of [inputs, labels]
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device)
# 训练时 反向传播用的
optimizer.zero_grad()
outputs = net(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if step % 500 == 499: # print every 500 mini-batches 训练五百次测试一次
with torch.no_grad(): #测试过程中不计算梯度
outputs = net(test_image) # [batch, 10]
predict_y = torch.max(outputs, dim=1)[1] #dim(轴 0表示列 1表示行
accuracy = torch.eq(predict_y, test_label).sum().item() / test_label.size(0) #size(0)行数 size(1)列数 item():tensor转成数值
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
if accuracy>maxAcc:
maxAcc = accuracy
torch.save(net.state_dict(), save_path)
print('save best model')
running_loss = 0.0
print('Finished Training')
if __name__ == '__main__':
main()
对其中的一些代码进行说明:
up本来使用cpu训练的,我拿着源码修改的时候以为还是跟之前一样只需要加这两行就行了,结果一直报错如:Input type (torch.FloatTensor) and weight type (torch.cuda.FloatTensor) should be the same
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = LeNet().to(device)
查了一下,是因为有些数据没放到gpu里面,这次的代码使用了iter迭代(之前没用过),迭代器中的数据(测试集的)没有to(device)所以报错了,一定要注意,所有放到网络里处理的数据都要放到gpu中【语言表达的不准确,只是说了我理解的意思】
如果使用cpu训练,只需要查找 .to(device) ,然后全部删除就好了
使用的device和数据预处理
ToTensor()和Normalize()作为经典的两个数据预处理函数,解释参考俺贴的这个博客
预处理看这个
ToTensor简单的说就是把图片变成tensor格式,(注意里面的维度顺序发生了编号)而Normalize就是数据归一化,0均值,1方差
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
# 设置使用cpu还是gpu 并打印显示 如果使用的gpu会打印cuda
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 数据进行预处理,因为使用的是CIFAR数据集中的数据,大小本来就是32了,而lenet网络的输入也是32,所以不需要再修改
数据集加载和处理
CIFAR数据集包括10个种类,50000张训练集和10000张测试集,第一次使用先下载,后面再运行就可把download设置成false了
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=False, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36,
shuffle=True, num_workers=0)
# 测试集打不打乱没有影响 直接用了5000为一个批次(本来是使用10000的 但是好像效果没有5000好)
test_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=5000,
shuffle=False, num_workers=0)
# iter:迭代器,使用next可以调用下一批数据 我们设置为迭代器之后只用了一个next,
#也就是调用了第一批数据5000张作为测试集的输入【我自己的理解,不知道对不对】
test_data_iter = iter(test_loader)
test_image, test_label = test_data_iter.next()
#to(device)是为了后面的测试做准备 把测试集的图片和标签都传到gpu中 不然就会报我上面提到那个错误
test_image, test_label = test_image.to(device), test_label.to(device)
net = LeNet().to(device) # 网络模型扔到gpu
loss_function = nn.CrossEntropyLoss() #损失函数使用交叉熵损失函数 内置了softmax函数
optimizer = optim.Adam(net.parameters(), lr=0.001) #优化器使用Adam,第一个参数是网络参数,第二个是学习率
save_path = './Lenet.pth' #训练好的模型要保存到本目录下的这个路径
maxAcc = 0.0 #设置一个最优精确度 初始化0
for epoch in range(5): # epoch设置成5 也就是训练的数据集50000张跑5次 每跑一次是一个epoch
running_loss = 0.0 #设置一个损失率 初值为0
# 对一个batch的数据训练的过程称为 一个 iteration 或 step 训练集中用step 测试用迭代器iter 所以下面开始迭代训练集中的数据,step是迭代的次数 (多少个batch) data里面是图片和标签
for step, data in enumerate(train_loader, start=0):
inputs, labels = data
inputs, labels = inputs.to(device), labels.to(device) #训练集放到gpu中
optimizer.zero_grad() #历史损失梯度清零 选择在哪用可以变相增大batch??还是不太理解
outputs = net(inputs) #前向传播
loss = loss_function(outputs, labels) # 计算损失函数
loss.backward() #反向传播
optimizer.step() # 设置权重 梯度更新
# 训练集有50000个样本 每批训练batch_size=32张照片 完整的训练完50000 step为1562次 所以使用32时会输出三次 但是这里使用的36 50000/36=1388 所以只print两次
running_loss += loss.item() # 计算每次的损失函数累计,是本来是tensor形式的,要求数值就用item()
if step % 500 == 499: # 训练五百次测试一次并打印精确率(一次只使用5000张照片)
with torch.no_grad(): #测试过程中不计算梯度(测试过程不需要反向传播,浪费算力
outputs = net(test_image) # [batch, 10]大小的矩阵 36行是一批照片,每张一行 10列是每个类的对应特征值
predict_y = torch.max(outputs, dim=1)[1] #dim(轴 0表示列 1表示行 所以就是求每行最大的 [1] 本来的输出是一个二元组 我们只有后面的label 前面是真实值
accuracy = torch.eq(predict_y, test_label).sum().item() / test_label.size(0) #size(0)行数 size(1)列数 item():tensor转成数值
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
if accuracy>maxAcc:
maxAcc = accuracy
torch.save(net.state_dict(), save_path)
# torch.save()用来保存网络模型,如果只保存网络参数,两个参数是(net.state_dict(),PATH)
# 如果是历史最优的就保存路径并打印这句话
print('save best model')
running_loss = 0.0 # 每一次测试重新计算损失率 清零
先贴代码
import torch
import torchvision.transforms as transforms
from PIL import Image
import matplotlib.pyplot as plt
from model import LeNet
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
transform = transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# net = LeNet()
net = LeNet().to(device)
net.load_state_dict(torch.load('Lenet.pth'))
im = Image.open('1.jpg')
plt.imshow(im) #接受一张图像 但是不展示出来 后续还可以进行draw等操作等用plt.show才会展示出来 比如本代码中后来给图片加了title
im = transform(im) #转化成img或者numpy格式 [c h w]
im = torch.unsqueeze(im, dim=0) #转化成tensor[b,c,h,w] 在最前面(在dim=0时)增加一个纬度b
with torch.no_grad():
outputs = net(im.to(device))
# numpy格式是cpu-only的 所以使用之前要先恢复到cpu 不然会报错TypeError: can’t convert CUDA tensor to numpy
predict = torch.max(outputs, dim=1)[1].cpu().numpy() #是一个数组类型[6] 所以要转成int才能用 后面这个[1]是因为max函数输出两个值[val,index] 我们只需要index
# max函数是输出一个data 一个index maxavg函数只输出一个index
predict_1 = torch.softmax(outputs, dim=1) #softmax本来输出是tensor格式 但是是高维
predict_1 = torch.squeeze(predict_1).cpu().numpy() #在tensor的形式下去掉b纬度 然后转化成numpy数组 同样需要转回cpu
plt.title(classes[int(predict)]) #给图片加上title 他的对应类名
plt.show() #展示图片
# print(predict_1)
print(classes[int(predict)])
print('%.3f' %predict_1[int(predict)])
if __name__ == '__main__':
main()
测试集要说明的几个地方:
# 保存网络中的参数, 速度快,占空间少 在train中
torch.save(net.state_dict(),PATH)
# 加载保存的部分参数 在test中
model_dict=model.load_state_dict(torch.load(PATH))
完结撒花~~写这个好累,因为中途发现好多我自己的错误点,报了好多错,差点没运行起来,纪念一下,虽然我语言描述不准确,因为好多地方我也是按自己的想法来写的,并不是一定正确,如果有大佬看到了这篇请指正。