前面两篇文章已经把论文中大部分的东西都说完了,前篇直达:CNN基础论文复现----AlexNet(一),网络也搭建起来了,到论文中的第5章了,就是 Details of learning。
上次留了个问题,就是有个均值,标准差和全连接隐层神经元偏置初始化的问题,不是很明白,然后查了一下网上大佬的代码。。。。。
文章中说到 2,4,5卷积层和全连接层初始化偏置为1,其余为0.
可以直接使用(self.C1.bias.data = nn.torch.zeros(self.C1.bias.data.size()),
后面加上
for m in self.modules(): #权重以及线性层偏置初始化
if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
m.weight.data = torch.normal(torch.zeros(m.weight.data.size()), torch.ones(m.weight.data.size()) * 0.01)
if isinstance(m, nn.Linear):
m.bias.data = torch.ones(m.bias.data.size())
但是这一段还有一块没弄明白,所以暂且搁置。
就先不使用文中说的初始化权重和偏置了。
由于ImageNet 数据集太大,所以我直接使用CIFAR-10进行代替,刚好电脑上之前做过CIFAR10的训练,数据包没删 ,可以继续用了。。
如果使用ImageNet 数据集,则均值和标准差像下面这样设置:
transforms.Normalize((0.229, 0.224, 0.225,), (0.485, 0.456, 0.40)),
如果像我一样改用CIFAR-10 就设置成下面这样:
transforms.Normalize((0.4915, 0.4823, 0.4468,), (1.0, 1.0, 1.0)),
文中提到不用标准差进行处理,所以这里设置的都是1,0。
这些参数直接谷歌都有现成的,都是大佬们不断优化之后的结果,拿来用就行了。
然后就写个循环开始训练就行了,我这直接拿上一次复现LeNet的代码。
def train(epoch):
runing_loss = 0.0
i = 1
for i, data in enumerate(train_loader):
x, y = data
x, y = x.cuda(), y.cuda()
i +=1
if i % 10 == 0:
print("运行中,当前运行次数:",i)
# 清零 正向传播 损失函数 反向传播 更新
optimizer.zero_grad()
y_pre = model(x)
loss = criterion(y_pre, y)
loss.backward()
optimizer.step()
runing_loss += loss.item()
print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 391))
return runing_loss / 391
这里是391 因为CIFAR10有 5W个训练样本,50000/128 约等于 391。
其余的地方和LeNet一样, 测试集是一模一样的,我就不贴在这了,最后我会贴一个完整的代码。
程序我已经拿到谷歌云上去跑了,但是跑的无比的慢,我猜是加入了Relu和LRN层的原因,运行速度大概10-20分钟训练一次,等他跑完,我把结果放上来。
我设置了20轮训练,跑了两个小时才跑了9轮,实在是等不下去了,想去睡觉了,
来看一下9轮的结果把,正确率基本已经到了70%,这个网络确实厉害,之前我训练CIFAR10的时候(文章在这),30轮才接近64%的正确率。
文章中在谈数据增强的时候说到了PCA叠加,由于我用的CIFAR10并没有用作者的那个数据集,所以这里就没有深究,不过我查了一下在sklearn库里有现成的API可以用。
前面作者提到使用Relu比sigmoid或者tanh的速度提高了6倍,额 提高6倍之后还这么慢嘛,之前做CIFAR10训练的时候并没有每一层都加激活函数,所以并不知道他这个提高6倍速度是个什么概念,?但是通过公式可以看出来,Relu函数要么为0要么为本身,计算量倒是比sig和tan要少的多。
之前也提到了 如果参数设置不合理,使用Relu函数会导致训练时间大幅增加,现在我这个模型的训练时间我觉得就很不正常,因为我并没有完全性复现网络,包括LRN层都是自己找的API,里面的size参数设置不一定正确,所以我后面会尝试撤掉LRN层再跑一遍。
文章中的SGD加入了动量,就是momentum
这个参数,这个参数相当于给参数加入了 ‘惯性’ 且会保留历史信息。通俗的理解为:假如当前进入局部最优点,加入惯性之后,有可能冲出局部最优点进而达到全局最优点,但并不是所有模型都适于加入动量。
权重衰减,也是文中提到的一个东西,就是这个参数 weight_decay
用来防止过拟合的,看名字也能理解嘛,权重衰减,将权重减小了,拟合度降低喽~ 数学层面理解 可以看一下这篇文章你们想看就看吧,反正我是不想看。。。。。
LRN层,这也是作者提到的,起 ‘抑制’ 作用,增加泛化能力,查阅资料得知,后续的人员研究后发现LRN对于模型的泛化能力并没有显著的作用,并且还有可能会降低模型准确率,还要引入更多的超参数。
这篇论文到这也就差不多了,其实也不算很好的复现了论文,毕竟有几个点都没有用上,比如将多路GPU改为单路(单纯觉得没必要用多路)。还有那个偏置和初始权重的设置问题并没有加进去(源码不熟 ,放假期间~比较懒散,不想深究。)
代码 以及AlexNet的中英文对照版 和 原版 都放在了GitHub上:
https://github.com/shitbro6/paper
import torch
from torch import optim
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn as nn
import matplotlib.pyplot as plt
batch_size = 128
transform = transforms.Compose([
transforms.Resize(224),
transforms.ToTensor (),
transforms.Normalize((0.4915, 0.4823, 0.4468,), (1.0, 1.0, 1.0)),
])
train_dataset = datasets.CIFAR10(root='../data/', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, shuffle=True, batch_size=batch_size)
test_dataset = datasets.CIFAR10(root='../data/', train=False, download=True, transform=transform)
test_loader = DataLoader(test_dataset, shuffle=False, batch_size=batch_size)
print("训练集长度",len(train_dataset))
print("测试集长度",len(test_dataset))
# 模型类设计
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.mode1 = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=96, kernel_size=11, stride=4,padding=2),
nn.ReLU(True),
nn.LocalResponseNorm(size=96,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(kernel_size=3,stride=2),
nn.Conv2d(in_channels=96, out_channels=256, kernel_size=5, stride=1,padding=2),
nn.ReLU(True),
nn.LocalResponseNorm(size=256,alpha=0.0001,beta=0.75,k=2),
nn.MaxPool2d(kernel_size=3,stride=2),
nn.Conv2d(in_channels=256, out_channels=384, kernel_size=3, stride=1,padding=1),
nn.ReLU(True),
nn.Conv2d(in_channels=384, out_channels=384, kernel_size=3, stride=1,padding=1),
nn.ReLU(True),
nn.Conv2d(in_channels=384, out_channels=256, kernel_size=3, stride=1,padding=1),
nn.ReLU(True),
nn.MaxPool2d(kernel_size=3,stride=2),
nn.Flatten(),
nn.Linear(in_features=6*6*256, out_features=4096),
nn.ReLU(True),
nn.Dropout2d(p=0.5),
nn.Linear(in_features=4096, out_features=4096),
nn.ReLU(True),
nn.Dropout2d(p=0.5),
nn.Linear(in_features=4096, out_features=1000),
)
def forward(self, input):
x = self.mode1(input)
return x
model = AlexNet().cuda()
# 损失函数
criterion = torch.nn.CrossEntropyLoss().cuda()
# 优化器
optimizer = optim.SGD(model.parameters(),lr=0.01,weight_decay=0.0005,momentum=0.9)
def train(epoch):
runing_loss = 0.0
i = 1
for i, data in enumerate(train_loader):
x, y = data
x, y = x.cuda(), y.cuda()
i +=1
if i % 10 == 0:
print("运行中,当前运行次数:",i)
# 清零 正向传播 损失函数 反向传播 更新
optimizer.zero_grad()
y_pre = model(x)
loss = criterion(y_pre, y)
loss.backward()
optimizer.step()
runing_loss += loss.item()
# 每轮训练一共训练1W个样本,这里的runing_loss是1W个样本的总损失值,要看每一个样本的平均损失值, 记得除10000
print("这是第 %d轮训练,当前损失值 %.5f" % (epoch + 1, runing_loss / 391))
return runing_loss / 391
def test(epoch):
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
x, y = data
x, y = x.cuda(), y.cuda()
pre_y = model(x)
# 这里拿到的预测值 每一行都对应10个分类,这10个分类都有对应的概率,
# 我们要拿到最大的那个概率和其对应的下标。
j, pre_y = torch.max(pre_y.data, dim=1) # dim = 1 列是第0个维度,行是第1个维度
total += y.size(0) # 统计方向0上的元素个数 即样本个数
correct += (pre_y == y).sum().item() # 张量之间的比较运算
print("第%d轮测试结束,当前正确率:%d %%" % (epoch + 1, correct / total * 100))
return correct / total * 100
if __name__ == '__main__':
plt_epoch = []
loss_ll = []
corr = []
for epoch in range(20):
plt_epoch.append(epoch+1) # 方便绘图
loss_ll.append(train(epoch)) # 记录每一次的训练损失值 方便绘图
corr.append(test(epoch)) # 记录每一次的正确率
plt.rcParams['font.sans-serif'] = ['KaiTi']
plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.title("训练模型")
plt.plot(plt_epoch,loss_ll)
plt.xlabel("循环次数")
plt.ylabel("损失值loss")
plt.subplot(1,2,2)
plt.title("测试模型")
plt.plot(plt_epoch,corr)
plt.xlabel("循环次数")
plt.ylabel("正确率")
plt.show()