本讲小结
- 经过了第九讲的全连接神经网络,我们终于步入神秘的卷积网络。深度卷积层又是深度学习得以广泛应用的又一个里程碑。
0深度全连接网络
- 之所以把线性层称为全连接层,是因为相邻两层线性层的任意两个节点之间都有权重。我们可以粗略计算一下上一小节做手写数据集分类设计的网络模型的参数量,
784
∗
512
+
512
∗
256
+
256
∗
128
+
128
∗
64
+
64
∗
10
=
574080
784*512+512*256+256*128+128*64+64*10=574080
784∗512+512∗256+256∗128+128∗64+64∗10=574080的权重需要计算,对一个这样简单的任务,在计算机视觉领域一个渺小的数据量来说,都需要这么多参数量,按照这种设计,对于大数据集来说,网络学习的参数将会无比恐怖。用计算器算一下,如下图:
1一般的卷积网络
- 一般的卷积网络有如上几个步骤:输入图像–>图像卷积–>下采样–>图像卷积–>下采样–>特征图展平成一维向量–>线性层做分类器输出。一般我们把线性层之前的操作称为特征提取。
2图像卷积
- 关于图像卷积,文字是描述不清楚的。我推荐看视频,b站有很多图像卷积可视化的视频。推荐一个:图解,卷积神经网络CNN可视化
- 关于图像卷积,要理解几个概念:
-
- 1,卷积运算是怎么算?本质上还是一种线性计算方式。
-
- 2,彩色图像和灰度图,多输入输出通道,通道数的概念。
-
- 3,经过卷积后如何得到多通道的特征图,特征图的概念以及拼接的概念。
-
- 4,填充、步距、空洞
-
- 5,理解pytorch官网给出的公式
- 5,理解pytorch官网给出的公式
卷积层小实验
import torch
in_channels, out_channels= 5, 10 #上面的n,m
width, height = 100, 100 #输入图像的大小
kernel_size = 3 #卷积核大小为3*3
batch_size = 1
#torch.randn()函数指生成服从正态分布的随机数
input = torch.randn(batch_size,
in_channels,
width,
height)
conv_layer = torch.nn.Conv2d(in_channels,
out_channels,
kernel_size=kernel_size) # 其实kernel_size可以是长方形,写成kernel_size=(a,b)
output = conv_layer(input) #输入传入卷积层,得到输出
print(input.shape) # -->torch.Size([1,5,100,100])
print(output.shape) # -->torch.Size([1,10,98,98])
print(conv_layer.weight.shape) # -->卷积层权重的形状torch.Size([10,5,3,3])
#10是输出通道的数量,5是输入通道的数量,3,3是卷积核的大小
3池化层
- 池化层有最大池化,有平均池化。是一种运算方法,没有权重。
- 最大池化如下图:
- 平均池化如下:
池化层小实验
import torch
input = [3,4,6,5,
2,4,6,8,
1,6,7,8,
9,7,4,6,
]
input = torch.Tensor(input).view(1, 1, 4, 4)
print(input.shape) # torch.Size([1, 1, 4, 4])
#当kernel_size=2时,maxpooling默认的步长也是2,特征图尺寸经过该池化后变成原尺寸的一半
maxpooling_layer = torch.nn.MaxPool2d(kernel_size=2)
output = maxpooling_layer(input)
print(output.shape) # torch.Size([1, 1, 2, 2])
4设计卷积模型
- 说明,为了让网络层结构和刘老师画的模型结构相同。做了一点修改,实际上因为池化层是一种运算操作,只需要实例化一个池化对象即可。但这样打印出来的网络模型就像是只做了一个池化层一样。
import torch
import torch.nn.functional as F
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
self.pooling1 = torch.nn.MaxPool2d(2)
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
self.pooling2 = torch.nn.MaxPool2d(2)
self.fc = torch.nn.Linear(320, 10)
def forward(self, x):
# Flatten data from (n, 1, 28, 28) to (n, 784)
batch_size = x.size(0)
x = F.relu(self.pooling1(self.conv1(x))) # 先池化再激活区别不大
x = F.relu(self.pooling2(self.conv2(x)))
x = x.view(batch_size, -1) # flatten
x = self.fc(x)
return x
model = Net()
print(model)
实验结果
5使用GPU训练和测试
- 1,模型;2,数据。只需要满足这两个条件都放在GPU就行。
- 使用GPU要先设置设备,用以下语句:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#把整个模型的参数,缓存,所有的模块都放到cuda里面,转成cuda tensor
model.to(device)
- 然后训练数据的地方,把数据放到GPU
inputs, target = data
#加入下面这行,把每一步的inputs和targets迁移到GPU
inputs, target = inputs.to(device), target.to(device)
- 这里要注意,由于测试的时候,我们并不需要做损失计算。因此只需要把图像放到GPU即可。而且由于要计算正确率,因此还要把输出转到cpu上。所以如果完全安装刘老师的代码跑,是跑步起来的。测试部分我做了如下修改:
outputs = model(images.to(device)).cpu()
实验结果
- 结果分析:老师说,相比SoftMax分类器中全连接网络97%的准确率,卷积神经网络的准确率为98%,看似只上升了一个百分点,但是错误率由3%降到了2%,错误率降低了三分之一,这句话我愿称为写论文之圣经。
完整代码
import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import torch.nn.functional as F # 用Relu函数
import torch.optim as optim # 优化器优化
batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
# transform:把图像转化成图像张量
train_dataset = datasets.MNIST(root='./datasets/data',
train=True,
download=True,
transform=transform) # 训练数据集
train_loader = DataLoader(train_dataset,
shuffle=True,
batch_size=batch_size)
test_dataset = datasets.MNIST(root='./datasets/data',
train=False,
download=True,
transform=transform)
test_loader = DataLoader(test_dataset,
shuffle=False,
batch_size=batch_size)
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
self.pooling = torch.nn.MaxPool2d(2)
self.fc = torch.nn.Linear(320, 10)
def forward(self, x):
# Flatten data from (n,1,28,28) to (n,784)
batch_size = x.size(0)
x = F.relu(self.pooling(self.conv1(x))) # 先把输入做卷积,然后做池化,然后做relu
x = F.relu(self.pooling(self.conv2(x)))
x = x.view(batch_size, -1) # Flatten,变成想要的全连接网络需要的输入
x = self.fc(x) # 用全连接层做变换
return x # 因为要做交叉熵损失,最后一层不做激活
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = Net()
model.to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5)
def train(epoch):
running_loss = 0.0
for batch_idx, data in enumerate(train_loader, 0):
inputs, target = data
# 加入下面这行,把每一步的inputs和targets迁移到GPU
inputs, target = inputs.to(device), target.to(device)
optimizer.zero_grad() # 优化器,输入之前清零
# forward + backward + updat
outputs = model(inputs)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 300 == 299: # 每300轮输出一次
print('[%d,%5d] loss:%.3f' % (epoch + 1, batch_idx + 1, running_loss / 300))
running_loss = 0.0
def test():
correct = 0 # 正确多少
total = 0 # 总数多少
with torch.no_grad(): # 测试不用算梯度
for data in test_loader: # 从test_loader拿数据
images, labels = data # 测试时,不用计算损失。因此只需要把数据放到GPU即可
outputs = model(images.to(device)).cpu() # 输出也是在GPU上,但是我们是在cpu上计算正确率的,所以要把输出放在cpu上
_, predicted = torch.max(outputs.data, dim=1) # 沿着第一个维度找最大值的下标,返回值有两个,因为是10列嘛,返回值
# 返回值一个是每一行的最大值,另一个是最大值的下标(每一个样本就是一行,每一行有10个量)(行是第0个维度,列是第1个维度)
total += labels.size(0) # 取size元组的第0个元素(N,1)
# 推测出来的分类与label是否相等,真就是1,假就是0,求完和之后把标量拿出来
correct += (predicted == labels).sum().item()
print('Accuracy on test set:%d %%' % (100 * correct / total))
# 训练
if __name__ == '__main__':
for epoch in range(10):
train(epoch)
test() # 训练一轮,测试一轮
实验结果
6作业
尝试更复杂的模型
- 卷积层*3
- 非线性激活层*3
- 池化层*3
- 线性层*3
之后有空再写吧,很简单的,需要注意下通道数和尺寸就行。