赞
踩
卷积神经网络是深度学习中的一个里程碑式的技术,有了这个技术,才会让计算机有能力理解图片和视频信息,才会有计算机视觉的众多应用。 本文讨论卷积神经网络模型(CNN)的Hello World。前面讨论的是一个二分类问题,本文讨论多分类问题。每张图片是一个28*28的灰度图片,所以本文的任务是给出一张图片,能识别这个图片是0-9数字中的哪一个。不过在此之前,还得学习一下卷积神经网络的基础知识。
之前我们学习的案例,对于模型的输入都是向量,但是当输入是一个图片的时候该怎么做呢?最直接的方式是把图片的像素点按照行列拉成一个长长的向量。这样就可以采用之前的方式来训练模型。但是如上图所示,一个“猫”的照片是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一样,所以卷积核里的数值,也是通过反向传播的方法学习到的。
卷积的运算规则:卷积核在输入矩阵中上下滑动,然后和对应的元素相乘求和。
如上图,卷积核大小是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")
卷积操作图片结果如下:
下面表格列举了其他卷积核的效果
卷积核里面的数字现在是固定的,只能表现图像的某些特性,那么我们可以不固定卷积核里面的数值,然后通过监督学习的方式去自动学习他,“这个可以学习的卷积操作”就是构成卷积神经网络里面最重要的概念。
池化是一个对输入进行下采样的操作,能快速减少输入大小,从而减少神经网络后面的参数量,便于训练模型。相对于卷积的下采样,有不需要参数的优点(没有卷积核参数)。一般有两种池化方式:
● 最大值池化层(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
和原图对比,内容没有变化但是尺寸发生了变化。说明池化只能改变图片的大小,不会影响图片的内容。
理解了卷积层和池化层,那么卷积神经网络就是卷积层+池化层作用神经网络的隐藏层反复出现的多层神经网络结构,如下图所示。
我们分析一下它的层级结构:
● 原始的输入是一张图片,可以是彩色的,也可以是灰度的或黑白的。这里假设是只有一个通道的图片,目的是识别0~9的手写体数字;
● 第一层卷积,我们使用了4个卷积核,得到了4张feature map;激活函数层没有单独画出来,这里我们紧接着卷积操作使用了Relu激活函数;
● 第二层是池化,使用了Max Pooling方式,把图片的高宽各缩小一倍,但仍然是4个feature map;
● 第三层卷积,我们使用了4x6个卷积核,其中4对应着输入通道,6对应着输出通道,从而得到了6张feature map,当然也使用了Relu激活函数;
● 第四层再次做一次池化,现在得到的图片尺寸只是原始尺寸的四分之一左右;
● 第五层把第四层的6个图片展平成一维,成为一个全连接层;
● 第六层再接一个小一些的全连接层;
● 最后接一个softmax函数,判别10个分类,这个后面实战会在介绍一下。
所以,在一个典型的卷积神经网络中,会至少包含以下几个层:
● 卷积层
● 激活函数层
● 池化层
● 全连接分类层
上面了解了卷积神经网络的基础知识,这里在介绍一个新的激活函数,ReLU函数(Rectified Linear Unit)。
ReLU和Sigmoid函数是常用的激活函数,它们在神经网络中起到非线性映射的作用,下面是它们的优缺点对比:
ReLU函数优点:
● 计算简单,只需要判断输入是否大于零。
● 解决了sigmoid函数的梯度消失问题,能更好地应对梯度下降算法。
● 可以使一部分神经元的输出为零,从而实现稀疏性,减少模型的复杂度。
ReLU函数缺点:
● ReLU函数在输入小于零时,梯度为零,导致神经元无法更新权重,称为“神经元死亡”问题。
● 对于输入小于零的情况,ReLU函数不是严格的非线性函数,可能导致模型的表达能力受限。
Sigmoid函数优点:
● Sigmoid函数的输出范围在(0,1)之间,可以将输出解释为概率。
● Sigmoid函数是严格的非线性函数,具有较强的表达能力。
● 可以将Sigmoid函数的输出直接作为分类器的输出,适用于二分类问题。
Sigmoid函数缺点:
● Sigmoid函数存在梯度饱和问题,当输入的绝对值很大时,梯度接近于零,导致梯度下降算法收敛缓慢。
● Sigmoid函数的计算量较大,使用指数运算,计算时间较长。
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.]]
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
# 超参数 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
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大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。
作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。