文章目录
前言
大致总结一下深度学习的流程:
- 配置变量:批次,学习率,迭代次数设备等。
- 导入数据:数据预处理(标准化,清洗,去噪,划分数据集),弹性形变等。
- 搭建网络:卷积、池化、激活函数等。
- 训练模型:选择损失函数,选择优化方法,迭代循环嵌套批次循环。(训练外层可以套k折交叉验证)
-
- 内层循环执行过程:x输入网络得到输出y->y与标签比对得到损失->梯度清零->计算梯度->反馈->记录损失以及正确率方便每次迭代后展示。
LeNet
第一次将卷积神经网络推上舞台,为世人所知。
import time
import torch
from torch import nn, optim
import d2l as d2l
# 设计网络
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
nn.Sigmoid(),
nn.MaxPool2d(2, 2), # kernel_size, stride
nn.Conv2d(6, 16, 5),
nn.Sigmoid(),
nn.MaxPool2d(2, 2)
)
self.fc = nn.Sequential(
nn.Linear(16*4*4, 120),
nn.Sigmoid(),
nn.Linear(120, 84),
nn.Sigmoid(),
nn.Linear(84, 10)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
def train_ch5(net, train_iter, test_iter, batch_size, loss, optimizer, device, num_epochs):
net = net.to(device)
print("training on", device)
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
# 计算损失,呈现数据
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
n += y.shape[0]
batch_count += 1
# 一个周期,计算一次test,其实也非必要
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
def evaluate_accuracy(data_iter, net, device=None):
if device is None and isinstance(net, torch.nn.Module):
# 如果没指定device就使用net的device
device = list(net.parameters())[0].device
acc_sum, n = 0.0, 0
with torch.no_grad():
for X, y in data_iter:
net.eval() # 评估模式, 这会关闭dropout
acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
net.train() # 改回训练模式
n += y.shape[0]
return acc_sum / n
if __name__ == "__main__":
# 配置
lr, num_epochs = 0.001, 5
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
batch_size = 256
# 导入数据
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
# 网络、损失、优化方法
net = LeNet()
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, loss, optimizer, device, num_epochs)
AlexNet
LeNet被其他机器学习方法超越了,因为在小数据集上取得好成绩,但是在大数据集上不行;而且计算开销大。想要设计一个多通道、多层、多参数的无法完成。从上面也可以看到层数少,通道少。
当时认为机器学习只做分类即可。但是有些科学家认为神经网络可以自己提取特征并且分类。在以前,特征是基于各种手工设计的函数从数据中提取出来的。机器学习只负责分类就好。例如,分类癌细胞与非癌细胞,机器学习要计算出每个细胞的大小,形状、细胞间的距离等等。这些都是设计函数计算得到的特征,然后把特征给机器学习分类。而且,计算机视觉的人认为数据和特征比选择一个机器学习的模型更重要。
- AlexNet首次证明了学习到的特征可以超越手工设计的特征,从而一举打破计算机视觉研究的前状。
- AlexNet提出丢弃法。
- AlexNet引入旋转和颜色变换等数据增强。
下面的代码与论文网络结构相比有改动,因为之前显存有限设置双数据流,现在不需要了。
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),# in_channels, out_channels, kernel_size, stride, padding
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
VGGNet
import time
import torch
from torch import nn, optim
import d2l as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 添加多少卷积层,设计多层vgg更加灵活
def vgg_block(num_convs, in_channels, out_channels):
blk = []
for i in range(num_convs):
if i == 0:
blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
else:
blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
blk.append(nn.ReLU())
blk.append(nn.MaxPool2d(kernel_size=2, stride=2))
return nn.Sequential(*blk)
def vgg(conv_arch, fc_features, fc_hidden_units=4096):
net = nn.Sequential()
# 卷积层部分
for i, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
# 每经过一个vgg_block都会使宽高减半
net.add_module("vgg_block_" + str(i+1), vgg_block(num_convs, in_channels, out_channels))
# 全连接层部分
net.add_module("fc", nn.Sequential(d2l.FlattenLayer(), # 全连接层,一张图片要拉成一维数组
nn.Linear(fc_features, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, 10)
))
return net