第一篇 Pytorch初学简单的线性模型 代码实操
第二篇 Pytorch实现逻辑斯蒂回归模型 代码实操
第三篇 Pytorch实现多特征输入的分类模型 代码实操
第四篇 Pytorch实现Dataset数据集导入 必要性解释及代码实操
第五篇 Pytorch实现多分类问题 样例解释 通俗易懂 新手必看
第六篇 Pytorch使用CNN实现基本的MNIST数据集学习 通俗理解CNN
代码如下(解释已经写在代码中):
import matplotlib.pyplot as plt
import torch
from torch.utils.data import DataLoader
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms
batch_size = 64
mnist_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize([0.1307, ], [0.3081, ])])
train_dataset = datasets.MNIST("../data/mnist", train=True, transform=mnist_transform, download=True)
test_dataset = datasets.MNIST("../data/mnist", train=False, transform=mnist_transform, download=True)
train_dataLoader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataLoader = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.linear1 = torch.nn.Linear(320, 10) # 最后使用线性模型还是要训练回10个维度
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5) # 2d和1d的区别??
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5) # 做卷积
self.pooling = torch.nn.MaxPool2d(2) # 最大池化层
def forward(self, x):
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)
x = self.linear1(x)
return x
model = Net()
# 使用GPU加快计算
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 设置GPU或cpu
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 index, data in enumerate(train_dataLoader, 0):
inputs, target = data
inputs, target = inputs.to(device), target.to(device) # 设置使用device机器
outputs = model(inputs)
loss = criterion(outputs, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
if index % 300 == 299:
print("[%d %5d] loss :%3f " % (epoch + 1, index + 1, running_loss / 300))
def test():
total = 0
correct = 0
with torch.no_grad():
for data in test_dataLoader:
inputs, label = data
inputs, label = inputs.to(device), label.to(device) # 设置使用device机器
outputs = model(inputs)
_, predicted = torch.max(outputs, dim=1)
total += label.size(0)
correct += (predicted == label).sum().item()
print("Accuracy: %d %%" % (100 * correct / total))
return 100 * correct / total
if __name__ == '__main__':
epoch_list = []
acc_list = []
for epoch in range(10):
train(epoch)
acc = test()
epoch_list.append(epoch)
acc_list.append(acc)
plt.plot(epoch_list, acc_list)
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.show()
这里声明,我使用了B站刘二大人的PPT作为讲解。
卷积主要针对图像设计的一种运算,是模型学习的一种方式,但其不局限于图像的应用。它使用卷积核将图片/其他情形上的数值卷积成一个数。如下图:
在图像中,如上图情况为单个通道的卷积,Input是单通道的,Kernel就是卷积核,在Input中,我们在一个和卷积核一样大小的区域内,进行图片下方的计算。当我们在input中对正方形框进行平移移动的时候,可以计算9次,把output中9个区域填满。
而当Input有多通道的时候,则需要多个卷积核对不同的通道计算,再把他们的数值加一起得到结果。
我们将上图矩阵重叠起来,称为filter,中间所做的计算,就是卷积。如下图:
目前我们的输出层outputs还是一维的,要得到多维的卷积结果,我们需要使用多个filter,通过不同的filter和inputs计算,就能在outputs中得到多个维度,如下图:
所以,在我们代码中,我们使用torch.nn.Conv2d(in_channels,out_channels,kernel_size=kernel_size) 代表filter ,我们需要给出三个参数。
要想知道卷积神经网络,首先要明白全连接神经网络是什么,因为全连接神经网络是最基本的。如果你已经了解,可以跳过2.1,直接进入2.2。
全连接的意思就是在神经网络中每一层的每一个节点都与下一层的每一个节点之间有连接。在pytorch中表现为Linear的输入层维度数必须和上一层的输出维度数一样。
下图为全连接网络的图示,输入维度4维,先转为2维,再转1维。我们将配合Pytorch代码讲解。
上图是一个基本的全连接网络模型,他的定义了两个Linear,分别为Linear(4,2),Linear(2,1)。
那么为什么能够从4维变化为2维?
这涉及到Linear线性代数的数学本质,我们可以将线性代数的矩阵视为一个空间变化的工具,它能将高维度映射到低维度中。
我们在计算下一层节点数值的时候,线性模型的计算为Y=Wx+b,W是一个矩阵,但是维度是多少呢?
我们可以在pytorch中输出weight的shape,也就是维度,可知,在Linear[4,2]的模型中,我们W矩阵是一个2*4的矩阵。
上图就是全连接网络的图示图的pytorch内部的矩阵计算,通过一个W权重矩阵,实现空间维度的变化。而权重矩阵是随着随着模型的学习,每一个值都在不断改变的。
减少W权重参数的数量
减少是相对于全连接层而言的,全连接层每一层的每一个节点都会与下一层每一个节点相连(相连代表有权重)。如果一个100100的图像,用全连接输入维度为100100=10000个维度,输出维度为1000的话,那需要,100001000个权重,这还只是一个图像,如果多个图像,那必然造成权重数量巨大。而对于卷积层来说,权重的数量与图片的大小是无关的,与filter有关,例如对于100100的图片,我们使用55100的filter,那么只有2500个参数,大大减少了权重的数量。
保留像素之间的空间关系
在全连接网络中,我们将照片完全展开,相邻的像素在展开后并不在相邻的位置,这就会损失空间上一些关系,而我们使用卷积核进行卷积的时候,我们是对照片某一区域进行卷积,可以得到空间上得关系。
CNN中卷积层的可以理解成提取特征,而全连接层的作用是将这些特征组合起来进行分类
过程:
提取特征,可以理解为每一个卷积核提取一个特征,提取完变成一个特征图,一个卷积层有多个filter,经过一层卷积层后有多个特征图,我们把这些特征图展开成向量的形式,再放入全连接层进行学习。
首先,我们看看卷积后的图像是什么样的。
这里是一张猫咪图片
通过了不同的卷积核,结果如下
下图为第二种卷积结果
我们说一个卷积核对应一个特征,但是我们特征在不同位置,结果是怎么设别出来的呢?如果我们去搜索,大部分答案是:卷积核共享权重。 但是为什么共享权重就可以呢?
所以这里我们从另外一个角度来理解,卷积核其实是不知道什么特征的,对它来说就是一堆数字,两张图片的特征不在同一个地方,我们用同一个卷积核将不同图片卷积出来成不同矩阵(图片也是一个矩阵),展开放入全连接层,那么每一个矩阵的数字,在全连接层都有一个权重与之对应,我们不用管在哪个位置,我们通过调整权重去匹配。
那在图片中可能还会有些杂物的存在,通过卷积,其实已经能够去掉一些,但是为什么模型不会识别错特征呢? 这个我觉得可以这样子理解:不同图片的杂物是不一样的,比如我们在第一个图片中有杂物 “树”,第二张图片没有,那么我们将图片卷积成矩阵后展开后乘上权重,有杂物的数值(图片的在矩阵中的数值)和无杂物的数值对结果影响不大,所以它不是我们要的特征。而比如有无尾巴这个尾巴的像素值,在图片中就会影响结果。所以模型会往尾巴的特征去调整。不过深度学习是一个黑盒,我们无法完全摸清里面的逻辑,我本人主要做一个通俗便于自己的理解。
这里给出一个链接,可以让大家去查看不同卷积核的结果,如下
图像使用不同卷积核结果可视化
对于这个问题,我认为要从数值计算的角度去考量卷积,不能从图片的角度去看。我们图片本质上是一堆数字,而我们看卷积的计算,也容易知道其本质上也是一种线性计算,既然是线性计算,那么在拟合实际的函数曲线的时候,就无法很好的拟合复杂现象,所以才要做非线性变化。而理解的重点就是从数值的角度去考量。
其实我们通过多层的卷积层能解决这个问题,例如在MNIST数据集中,我们原图像是2828,我们通过55的卷积核后,变成了2424,如果再来一层卷积层,变成2020,在2020中,在33的格子中,对应到原图像中,就不止是一个5*5的矩阵,因为我们已经经过了多层卷积。
池化层我们在代码中用的是最大化的池化层,选择22的大小,即在22的框框内选择像素值最大的数作为输出。
self.pooling = torch.nn.MaxPool2d(2)
这样子做的目的是为了降低分辨率,增大感受野。
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)
x = self.linear1(x)
这也就是我们上面说的提取特征+学习
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 设置GPU或cpu
我们创建了一个指定机器的变量,在我们需要使用GPU计算能力的时候,将我们获得的输入核输出标签送入device中,操作如下
inputs, target = data
inputs, target = inputs.to(device), target.to(device) # 设置使用device机器
可以看到我们的结果精确值比97%提高了,甚至到了99%。
[1 300] loss :0.539707
[1 600] loss :0.721674
[1 900] loss :0.857974
Accuracy: 96 %
[2 300] loss :0.102809
[2 600] loss :0.201473
[2 900] loss :0.286052
Accuracy: 97 %
[3 300] loss :0.075775
[3 600] loss :0.149488
[3 900] loss :0.222730
Accuracy: 98 %
[4 300] loss :0.064506
[4 600] loss :0.129991
[4 900] loss :0.186902
Accuracy: 98 %
[5 300] loss :0.055129
[5 600] loss :0.107917
[5 900] loss :0.162026
Accuracy: 98 %
[6 300] loss :0.052135
[6 600] loss :0.101641
[6 900] loss :0.145486
Accuracy: 98 %
[7 300] loss :0.043968
[7 600] loss :0.090816
[7 900] loss :0.133207
Accuracy: 98 %
[8 300] loss :0.040344
[8 600] loss :0.081699
[8 900] loss :0.123566
Accuracy: 98 %
[9 300] loss :0.036629
[9 600] loss :0.074085
[9 900] loss :0.114198
Accuracy: 98 %
[10 300] loss :0.032383
[10 600] loss :0.066107
[10 900] loss :0.105732
Accuracy: 99 %
以上就是我个人对CNN的理解,希望配合上其他文章,能让初学者更容易理解。讲解过程属于个人理解,如表述有误,请谅解。文中有些地方没细讲,因为有的博主,或者网上的资料很多,本文偏向于我个人的理解。如果觉得有用,请大家点赞支持!!!!。