Pytorch 卷积神经网络-手写数字识别

卷积神经网络是深度学习中的一个里程碑式的技术,有了这个技术,才会让计算机有能力理解图片和视频信息,才会有计算机视觉的众多应用。 本文讨论卷积神经网络模型(CNN)的Hello World。前面讨论的是一个二分类问题,本文讨论多分类问题。每张图片是一个28*28的灰度图片,所以本文的任务是给出一张图片,能识别这个图片是0-9数字中的哪一个。不过在此之前,还得学习一下卷积神经网络的基础知识。

1 卷积神经网络基础

之前我们学习的案例,对于模型的输入都是向量,但是当输入是一个图片的时候该怎么做呢?最直接的方式是把图片的像素点按照行列拉成一个长长的向量。这样就可以采用之前的方式来训练模型。但是如上图所示,一个“猫”的照片是1000_1000,由于是彩色的,具有R,G,B三个通道,那么输入的数据大小就是1000_1000_3,如果一个神经网络第一层有1000个神经元,那么总的参数量为1000_1000_3_1000 = 3 * 109个参数,这对硬件资源提出了太高的要求。

传统的方式,进行可以先对上面的图片进行模糊化处理,这个是怎么做到的呢?

RGB可以看出3个2维矩阵,在模糊化的过程中,需要用上面的这个3_3矩阵,称之为核-kernel,核的大小为3_3,被称为kernel-size,这个核和原来矩阵作用的过程称为卷积。这个kernel就类似全连接层中的Weights一样,所以卷积核里的数值,也是通过反向传播的方法学习到的。

1.1 卷积层

卷积的运算规则:卷积核在输入矩阵中上下滑动,然后和对应的元素相乘求和。

如上图,卷积核大小是3x3的,也就是说其卷积核每次覆盖原图像的9个像素,行和列都滑动了3次,一共滑动3x3=9次,得到了一个3*3的二维矩阵。卷积核在矩阵横向或者纵向一次移动的大小叫做步长(stride),步长可以为1,2,3,4。我们直接看一下代码。

import numpy as np
import torch
from torch import nn
from torch.autograd import Variable
import torch.nn.functional as F
from PIL import Image
import matplotlib.pyplot as plt

if __name__ == "__main__":
   # 使用convert('L')读入一个灰度图片
   image = Image.open('../digital_recognition/digital_data/cat.png').convert('L')
   # 将图片转成矩阵
   image = np.array(image, dtype='float32')
   # 将图片显示出来
   plt.imshow(image.astype('uint8'), cmap='gray')
   print("finish")

torch.nn.Conv2d(
   in_channels, #输入特征矩阵的深度即channel,比如输入一张RGB彩色图像,那in_channels = 3;
   out_channels, #代表卷积核的个数,使用n个卷积核输出的特征矩阵深度即channel就是n;
   kernel_size, #卷积核大小
   stride=1, # 卷积的步长
   padding=0, #卷积核对图像四周的填充边界
   dilation=1, 
   groups=1, 
   bias=True, 
   padding_mode='zeros', 
   device=None, 
   dtype=None
)

# 将图片矩阵转换成pytorch tensor,并适配卷积的输入的要求
image = torch.from_numpy(image.reshape(1,1,image.shape[0],image.shape[1]))

# 构建一个卷积,输入和输出通道都是1(因为是黑白的),卷积核大小是3,
conv = nn.Conv2d(1,1,3,bias=False)
# 构建卷积核
sobel_kernel = np.array([[-1,-1,-1],[-1,-8,-1],[-1,-1,-1]],dtype='float32')
sobel_kernel = sobel_kernel.reshape((1, 1, 3, 3))
# 给卷积的kernel赋值
conv.weight.data = torch.from_numpy(sobel_kernel)
edge = conv(Variable(image))
# 将输出转成图片的格式
edge = edge.data.squeeze().numpy()
plt.imshow(edge,cmap='gray')
print("finish")

卷积操作图片结果如下:

下面表格列举了其他卷积核的效果

卷积核里面的数字现在是固定的,只能表现图像的某些特性,那么我们可以不固定卷积核里面的数值,然后通过监督学习的方式去自动学习他,“这个可以学习的卷积操作”就是构成卷积神经网络里面最重要的概念。

1.2 池化层

池化是一个对输入进行下采样的操作,能快速减少输入大小,从而减少神经网络后面的参数量,便于训练模型。相对于卷积的下采样,有不需要参数的优点(没有卷积核参数)。一般有两种池化方式:
● 最大值池化层(max pooling)
● 平均值池化层(average pooling)

下面图是一个最大值池化层,每种颜色的矩阵取一个最大值构成右边的图。

我们从代码来看一下:

#池化核大小是2,移动步长是2
max_pool = nn.MaxPool2d(2, 2)
print('before max pool, image shape:{} * {}'.format(image.shape[2], image.shape[3]))

image = max_pool(Variable(image))

image = image.data.squeeze().numpy()
plt.imshow(image,cmap='gray')
print('before max pool, image shape:{} * {}'.format(image.shape[0], image.shape[1]))
print("max_pool finish")

image shape= (886, 878)
before max pool, image shape:886 * 878
before max pool, image shape:443 * 439

和原图对比,内容没有变化但是尺寸发生了变化。说明池化只能改变图片的大小,不会影响图片的内容。

1.3 总结

理解了卷积层和池化层,那么卷积神经网络就是卷积层+池化层作用神经网络的隐藏层反复出现的多层神经网络结构,如下图所示。

我们分析一下它的层级结构:
● 原始的输入是一张图片,可以是彩色的,也可以是灰度的或黑白的。这里假设是只有一个通道的图片,目的是识别0~9的手写体数字;
● 第一层卷积,我们使用了4个卷积核,得到了4张feature map;激活函数层没有单独画出来,这里我们紧接着卷积操作使用了Relu激活函数;
● 第二层是池化,使用了Max Pooling方式,把图片的高宽各缩小一倍,但仍然是4个feature map;
● 第三层卷积,我们使用了4x6个卷积核,其中4对应着输入通道,6对应着输出通道,从而得到了6张feature map,当然也使用了Relu激活函数;
● 第四层再次做一次池化,现在得到的图片尺寸只是原始尺寸的四分之一左右;
● 第五层把第四层的6个图片展平成一维,成为一个全连接层;
● 第六层再接一个小一些的全连接层;
● 最后接一个softmax函数,判别10个分类,这个后面实战会在介绍一下。
所以,在一个典型的卷积神经网络中,会至少包含以下几个层:
● 卷积层
● 激活函数层
● 池化层
● 全连接分类层

2 手写数字识别实战

2.1 ReLU激活函数

上面了解了卷积神经网络的基础知识,这里在介绍一个新的激活函数,ReLU函数(Rectified Linear Unit)。

ReLU和Sigmoid函数是常用的激活函数,它们在神经网络中起到非线性映射的作用,下面是它们的优缺点对比:
ReLU函数优点:
● 计算简单,只需要判断输入是否大于零。
● 解决了sigmoid函数的梯度消失问题,能更好地应对梯度下降算法。
● 可以使一部分神经元的输出为零,从而实现稀疏性,减少模型的复杂度。
ReLU函数缺点:
● ReLU函数在输入小于零时,梯度为零,导致神经元无法更新权重,称为“神经元死亡”问题。
● 对于输入小于零的情况,ReLU函数不是严格的非线性函数,可能导致模型的表达能力受限。

Sigmoid函数优点:
● Sigmoid函数的输出范围在(0,1)之间,可以将输出解释为概率。
● Sigmoid函数是严格的非线性函数,具有较强的表达能力。
● 可以将Sigmoid函数的输出直接作为分类器的输出,适用于二分类问题。
Sigmoid函数缺点:
● Sigmoid函数存在梯度饱和问题,当输入的绝对值很大时,梯度接近于零,导致梯度下降算法收敛缓慢。
● Sigmoid函数的计算量较大,使用指数运算,计算时间较长。

2.2 交叉熵损失函数

2.3 读取数据集
def load_dataset():
   train_data = mnist.MNIST('.data', train=True, download=True)
   test_data = mnist.MNIST('.data', train=False, download=True)
   return train_data, test_data


if __name__ == '__main__':
   train_data, test_data = load_dataset()
   data, label = train_data[0]
   print("data", data)
   print("label", label)

   image = np.array(data, dtype='float32')
   print("image shape =", image.shape)
   print("image =", image)
   # 将图片显示出来
   plt.imshow(image.astype('uint8'), cmap='gray')
   print("finish")


首次执行的时候,需要下载。

输出:

label 5
image shape = (28, 28)
image = [[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   3.  18.
  18.  18. 126. 136. 175.  26. 166. 255. 247. 127.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  30.  36.  94. 154. 170. 253.
 253. 253. 253. 253. 225. 172. 253. 242. 195.  64.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.  49. 238. 253. 253. 253. 253. 253.
 253. 253. 253. 251.  93.  82.  82.  56.  39.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.  18. 219. 253. 253. 253. 253. 253.
 198. 182. 247. 241.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  80. 156. 107. 253. 253. 205.
  11.   0.  43. 154.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.  14.   1. 154. 253.  90.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 139. 253. 190.
   2.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  11. 190. 253.
  70.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  35. 241.
 225. 160. 108.   1.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  81.
 240. 253. 253. 119.  25.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
  45. 186. 253. 253. 150.  27.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.  16.  93. 252. 253. 187.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0. 249. 253. 249.  64.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
  46. 130. 183. 253. 253. 207.   2.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  39. 148.
 229. 253. 253. 253. 250. 182.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.  24. 114. 221. 253.
 253. 253. 253. 201.  78.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.  23.  66. 213. 253. 253. 253.
 253. 198.  81.   2.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.  18. 171. 219. 253. 253. 253. 253. 195.
  80.   9.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.  55. 172. 226. 253. 253. 253. 253. 244. 133.  11.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0. 136. 253. 253. 253. 212. 135. 132.  16.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
[  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]]

2.4 定义模型

class CNN(nn.Module):  # 我们建立的CNN继承nn.Module这个模块
   def __init__(self):
       super(CNN, self).__init__()
       # 建立第一个卷积(Conv2d)-> 激活函数(ReLU)->池化(MaxPooling)
       self.conv1 = nn.Sequential(
           # 第一个卷积con2d
           nn.Conv2d(  # 输入图像大小(1,28,28)
               in_channels=1,  # 输入图片的高度,因为minist数据集是灰度图像只有一个通道
               out_channels=16,  # n_filters 卷积核的高度
               kernel_size=5,  # filter size 卷积核的大小 也就是长x宽=5x5
               stride=1,  # 步长
               padding=2,  # 想要con2d输出的图片长宽不变,就进行补零操作 padding = (kernel_size-1)/2
           ),  # 输出图像大小(16,28,28)
           # 激活函数
           nn.ReLU(),
           # 池化,下采样
           nn.MaxPool2d(kernel_size=2),  # 在2x2空间下采样
           # 输出图像大小(16,14,14)
       )
       # 建立第二个卷积(Conv2d)-> 激励函数(ReLU)->池化(MaxPooling)
       self.conv2 = nn.Sequential(
           # 输入图像大小(16,14,14)
           nn.Conv2d(  # 也可以直接简化写成nn.Conv2d(16,32,5,1,2)
               in_channels=16,
               out_channels=32,
               kernel_size=5,
               stride=1,
               padding=2
           ),
           # 输出图像大小 (32,14,14)
           nn.ReLU(),
           nn.MaxPool2d(2),
           # 输出图像大小(32,7,7)
       )
       # 建立全卷积连接层
       self.out = nn.Linear(32 * 7 * 7, 10)  # 输出是10个类

   # 下面定义x的传播路线
   def forward(self, x):
       x = self.conv1(x)  # x先通过conv1
       x = self.conv2(x)  # 再通过conv2
       # 把每一个批次的每一个输入都拉成一个维度,即(batch_size,32*7*7)
       # 因为pytorch里特征的形式是[bs,channel,h,w],所以x.size(0)就是batchsize
       x = x.view(x.size(0), -1)  # view就是把x弄成batchsize行个tensor
       output = self.out(x)
       return output

2.5 训练模型
# 超参数
EPOCH = 10 
BATCH_SIZE = 50
LR = 0.001  # 学习率
DOWNLOAD_MNIST = True  # 表示还没有下载数据集,如果数据集下载好了就写False

if __name__ == '__main__':
   # 训练集
   train_data = torchvision.datasets.MNIST(
       root='./data/',  # 保存或提取的位置  会放在当前文件夹中
       train=True,  # true说明是用于训练的数据,false说明是用于测试的数据
       transform=torchvision.transforms.ToTensor(),  # 转换PIL.Image or numpy.ndarray
       download=DOWNLOAD_MNIST,  # 已经下载了就不需要下载了
   )

   # 测试集
   test_data = torchvision.datasets.MNIST(
       root='./data/',
       train=False
   )
   # 加载数据
   train_loader = Data.DataLoader(
       dataset=train_data,
       batch_size=BATCH_SIZE,
       shuffle=True  # 是否打乱数据,一般都打乱
   )

   # 图像的pixel本来是0到255之间,除以255对图像进行归一化使取值范围在(0,1)
   # torch.unsqueeze(a) 是用来对数据维度进行扩充,这样shape就从(x,28,28)->(x,1,28,28)
   test_x = torch.unsqueeze(test_data.train_data, dim=1).type(torch.FloatTensor) / 255

   test_y = test_data.test_labels
   cnn = CNN()
   print(cnn)

   # 优化器选择Adam
   optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)
   # 损失函数
   loss_func = nn.CrossEntropyLoss() 
   # 开始训练
   for epoch in range(EPOCH):
       for step, (b_x, b_y) in enumerate(train_loader):  # 分配batch data
           output = cnn(b_x)  # 先将数据放到cnn中计算output
           loss = loss_func(output, b_y)  # 输出和真实标签的loss,二者位置不可颠倒
           optimizer.zero_grad()  # 清除之前学到的梯度的参数
           loss.backward()  # 反向传播,计算梯度
           optimizer.step()  # 应用梯度
           if step % 100 == 0:
               test_output = cnn(test_x)
               pred_y = torch.max(test_output, 1)[1].data.numpy()
               accuracy = float((pred_y == test_y.data.numpy()).astype(int).sum()) / float(test_y.size(0))
               print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.numpy(), '| test accuracy: %.2f' % accuracy)

   # 保存模型
   torch.save(cnn.state_dict(), 'cnn2.pkl')

跑了7个Epoch之后,模型的准确率已经到99%。

Epoch:  0 | train loss: 2.3034 | test accuracy: 0.14
Epoch:  0 | train loss: 0.5552 | test accuracy: 0.89
Epoch:  0 | train loss: 0.1276 | test accuracy: 0.95
Epoch:  0 | train loss: 0.0556 | test accuracy: 0.96
.....
Epoch:  7 | train loss: 0.0666 | test accuracy: 0.99
Epoch:  7 | train loss: 0.0071 | test accuracy: 0.99
Epoch:  7 | train loss: 0.0054 | test accuracy: 0.99

2.6 模型验证
cnn.load_state_dict(torch.load('cnn2.pkl'))
cnn.eval()

test_output = cnn(test_x)
pred_y = torch.max(test_output, 1)[1].data.numpy()
print(pred_y, 'prediction number')
print(test_y.numpy(), 'real number')
# 检查元素是否相等并统计不相等的个数
unequal_count = sum(pred_y[i] != test_y[i] for i in range(len(pred_y)))

# 计算不相等的比例
equal_ratio = 1 - unequal_count / len(pred_y)
# 输出结果
print("总个数:{},不相等的个数{}".format(len(pred_y), unequal_count))
print("准确率:", equal_ratio.item())

[7 2 1 ... 4 5 6] prediction number
[7 2 1 ... 4 5 6] real number
总个数:10000,不相等的个数165
准确率: 0.9835000038146973

那么,我们该如何学习大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、大模型全套的学习路线

学习大型人工智能模型,如GPT-3、BERT或任何其他先进的神经网络模型,需要系统的方法和持续的努力。既然要系统的学习大模型,那么学习路线是必不可少的,下面的这份路线能帮助你快速梳理知识,形成自己的体系。

L1级别:AI大模型时代的华丽登场

L2级别:AI大模型API应用开发工程

L3级别:大模型应用架构进阶实践

L4级别:大模型微调与私有化部署

一般掌握到第四个级别,市场上大多数岗位都是可以胜任,但要还不是天花板,天花板级别要求更加严格,对于算法和实战是非常苛刻的。建议普通人掌握到L4级别即可。

以上的AI大模型学习路线,不知道为什么发出来就有点糊,高清版可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-06-14 12:16:06       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-06-14 12:16:06       101 阅读
  3. 在Django里面运行非项目文件

    2024-06-14 12:16:06       82 阅读
  4. Python语言-面向对象

    2024-06-14 12:16:06       91 阅读

热门阅读

  1. 2024年湘潭大学软件体系结构考试总结

    2024-06-14 12:16:06       30 阅读
  2. 探索未来:前沿科技的突破与挑战

    2024-06-14 12:16:06       25 阅读
  3. 轻兔推荐 —— Syncthing

    2024-06-14 12:16:06       26 阅读
  4. spark mllib 特征学习笔记 (一)

    2024-06-14 12:16:06       25 阅读
  5. 华为网络设备攻击防范

    2024-06-14 12:16:06       29 阅读
  6. 探索Spring Boot的自动配置机制

    2024-06-14 12:16:06       26 阅读
  7. 每天一个数据分析题(三百六十六)- 5WHY分析法

    2024-06-14 12:16:06       28 阅读
  8. docker-compose安装freeradius

    2024-06-14 12:16:06       31 阅读
  9. 【通信协议-RTCM】RTCM信息组

    2024-06-14 12:16:06       33 阅读
  10. 输出数据到excel中

    2024-06-14 12:16:06       24 阅读