上一讲PyTorch深度学习实践概论笔记9-SoftMax分类器介绍了多分类问题如何解决,这一讲来介绍卷积神经网络。
回顾一下之前的内容,先看全连接神经网络吧。
之前的网络里面用的都是线性层,如果一个网络全都由线性层串行连接起来,就叫做全连接网络。在线性层里面输入和每一个输出值之间都存在权重,即每一个输入节点都要参与到下一层输出节点的计算上,这样的线性层也叫全连接层(Fully Connected)。
在做图像处理时,卷积神经网络把图像按照原始的空间结构保存,能保留原始的空间信息。输入的三维图像经过Convolution层之后依旧是三维张量。接下来做一个2x2的subsampling,下采样之后通道数不变、宽度和高度变小(减少数据量,降低运算量)。最终的输出是一个10维的向量,需要经过Fully Connected(全连接层)。
前面的卷积层和下采样层叫做特征提取(Feature Extraction),后面的全连接层做的操作最后输出10维向量叫做分类器(Classification)。
如上图,我们看到的彩色图像一般都是RGB的图像,有三通道。 (老师举了物理的光敏电阻的例子,刘老师真的是全能!)(栅格图像和矢量图像)
接下来看卷积的运算过程,看单一通道如何卷积。
Input是1x5x5,卷积核是3*3,将上面红色方框和卷积核对应元素相乘再求和。其他类似。
接下来看3通道如何卷积。
图示如下:
上图的卷积核也叫过滤器。上图也可以画成:
输入一个RGB的三通道,经过一系列卷积后,输出1通道。
对于N通道的卷积操作。
这是使用一个过滤器的情况。如果想要输出是m个通道怎么办呢?
这是使用m个过滤器的情况,输入N通道,输出M通道。
准备m个卷积核,每一个卷积核对原始输入卷积完之后通道数都是1,然后把这些通道拼接(cat)起来。每一个卷积核通道数量要求和输入通道一样,这里都为n。卷积核的数量和输出通道数是一样的,这里为m。卷积核的大小自己定,与图像大小无关,这里是3*3。
共享权重机制:对每一个图像块操作用的是相同的卷积核。
可以把m个卷积核拼成4维张量:m(卷积核个数)*n(通道数)*width*height,我们构建卷积层,它的权重就是这样的一个维度。
代码如下:
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)
#输入传入卷积层,得到输出
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是卷积核的大小
卷积层并不在于输入的张量的宽度和高度,因为我们是复用的权重,只是把图像遍历一遍,图像大输出就大,图像小,输出就小。所以卷积层对输入图像的宽度和高度没有要求,只对输入通道数有要求。定义一个卷积层,需要确定输入通道,输出通道,卷积核的大小。
接下来介绍卷积层中常见的参数。
如何保证卷积之后图像宽高不变?
padding=1
这是有规律可循的:
代码如下:
import torch
input = [3,4,6,5,7,
2,4,6,8,2,
1,6,7,8,4,
9,7,4,6,2,
3,7,5,4,1]
#把输入变成(batch_size,channel,width,height)
input = torch.Tensor(input).view(1, 1, 5, 5)
#构建卷积层,输入和输出都是1个通道,卷积核3*3
#如果bias=True,卷积完之后会给每一个通道加上一个偏置量,不需要加偏置量就bias=False
#padding=1,当卷积核为3*3时,保证输出还是5*5
conv_layer = torch.nn.Conv2d(1, 1, kernel_size=3, padding=1, bias=False)
#构建卷积核(输出通道数,输入通道数,宽度,高度)
kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
#把做出来的张量赋值给卷积层的权重.data,把卷积层的权重初始化
conv_layer.weight.data = kernel.data
output = conv_layer(input)
print(output)
输出结果为:
介绍一个新概念,stride(步长),索引时每次移动两格。
代码如下:
import torch
input = [3,4,6,5,7,
2,4,6,8,2,
1,6,7,8,4,
9,7,4,6,2,
3,7,5,4,1]
input = torch.Tensor(input).view(1, 1, 5, 5)
conv_layer = torch.nn.Conv2d(1, 1, kernel_size=3, stride=2, bias=False)
kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
conv_layer.weight.data = kernel.data
output = conv_layer(input)
print(output)
下采样用的比较多的是MaxPooling(最大池化层)。最大池化层是没有权重的,2*2的maxpooling默认stride=2,就是把图像分成2*2的一个组,在每个组里面找最大值。所以做maxpooling的时候只能把一个通道拿出来做maxpooling,通道之间不会去找最大值,所以通道数量不会发生改变,但是如果用2*2的maxpooling图像大小会缩成原来的一半。
代码如下:
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)
#当kernel_size=2时,maxpooling默认的步长也是2
maxpooling_layer = torch.nn.MaxPool2d(kernel_size=2)
output = maxpooling_layer(input)
print(output)
第一个最大池化做一个就行,因为它没有权重,跟sigmoid和relu一样没有权重的,但是有权重的必须每一个层单独做一个实例。
代码如下:
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))) #先池化再激活区别不大
x = F.relu(self.pooling(self.conv2(x)))
x = x.view(batch_size, -1) # flatten
x = self.fc(x)
return x
model = Net()
print(model)
输出结果如下:
Net(
(conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
(pooling): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(fc): Linear(in_features=320, out_features=10, bias=True)
)
步骤1.把模型迁移到GPU
代码如下:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#把整个模型的参数,缓存,所有的模块都放到cuda里面,转成cuda tensor
model.to(device)
如果装的是支持cuda版本的pyTorch,available就是True,默认是cuda 0;只有cpu就是false。如果有多个显卡,不同的任务可以使用不同的显卡。model.to(device)把整个模型的参数,缓存,所有的模块都放到cuda里面,转成cuda tensor。
步骤2.把计算张量迁移到GPU
计算的时候要把用来计算的张量也迁移到GPU,主要是输入和对应的输出,把inputs和target都迁移到device上,注意迁移数据的device和模型的device要在同一块显卡上,比如把模型放在第0块显卡,数据放在第一块显卡,是没法工作的。
训练代码如下:
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 + update
outputs = model(inputs)
loss = criterion(outputs, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
if batch_idx % 300 == 299:
print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))
running_loss = 0.0
测试代码如下:
def test():
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
inputs, target = data
#加入下面这行,把每一步的inputs和targets迁移到GPU
inputs, target = inputs.to(device), target.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs.data, dim=1)
total += target.size(0)
correct += (predicted == target).sum().item()
print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))
老师结果如下:
不难看出,相比PyTorch深度学习实践概论笔记9-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='../data',
train=True,
download=True,
transform=transform) # 训练数据集
train_loader = DataLoader(train_dataset,
shuffle=True,
batch_size=batch_size)
test_dataset = datasets.MNIST(root='../data',
train=False,
download=True,
transform=transform)
test_loader = DataLoader(test_dataset,
shuffle=False,
batch_size=batch_size)
#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.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#因为要做交叉熵损失,最后一层不做激活
model=Net()
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
#加入下面这行,把每一步的inputs和targets迁移到GPU
inputs, target = inputs.to(device), target.to(device)
outputs = model(images) # 拿完数据做预测
_, predicted = torch.max(outputs.data, dim=1) # 沿着第一个维度找最大值的下标,返回值有两个,因为是10列嘛,返回值
# 返回值一个是每一行的最大值,另一个是最大值的下标(每一个样本就是一行,每一行有10个量)(行是第0个维度,列是第1个维度)
total += labels.size(0) # 取size元组的第0个元素(N,1),
correct += (predicted == labels).sum().item() # 推测出来的分类与label是否相等,真就是1,假就是0,求完和之后把标量拿出来
print('Accuracy on test set:%d %%' % (100 * correct / total))
# 训练
if __name__ == '__main__':
for epoch in range(10):
train(epoch)
test() #训练一轮,测试一轮
我的输出结果如下:
效果不是很理想,卷积网络设计的有点简单。
看一下练习题。
• Try a more complex CNN:(尝试更复杂的CNN)
• Conv2d Layer *3
• ReLU Layer * 3
• MaxPooling Layer * 3
• Linear Layer * 3
• Try different configuration of this CNN:(尝试不同的CNN配置)
• Compare their performance.
(练习的解答之后会完善,请看评论区。)
说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。