Deep Learning-深度学习(三)

深度学习入门

1、NumPy案例运用

1.1 计算激活函数

  激活函数:就像之前所学习的那样,所有的处理都还是基于线性的,然是在实际的运用当中是具有很多非线性的运算,并不是简单的加权和,而是各种数据直接还会相互影响,互相产生新的数据,从而得到新的数据。对于这些过程就需要激活函数来达到这种复杂非线性的计算。激活函数有几个性质,①可微性,因为是以梯度进行计算学学习,所以必须是要可微的。②单调性,这能保证单层网络是凸函数。③输出值的范围,当激活函数输出值是有限的时候,基于梯度的优化方法会更加稳定。

  Sigmoid激活函数:使用范围最广的一类激活函数,为指数形状,十分接近物理意义的生物神经元,即S型生长曲线。

  

  利用NumPy进行实现:

  

1 # x是1维数组,数组大小是从-10. 到10.的实数,每隔0.1取一个点
2 x = np.arange(-10, 10, 0.1)
3 # 计算 Sigmoid函数
4 s = 1.0 / (1 + np.exp(- x))

 

  ReLU函数:即修正线性单元(Rectified linear unit,ReLU),通常以斜坡或者变种为代表的非线性函数。

  

 

  利用NumPy进行实现:

  

1 # 计算ReLU函数
2 y = np.clip(x, a_min = 0., a_max = None)

  

  通过画图来直观的进行感受:

  

 1 # 设置两个子图窗口,将Sigmoid的函数图像画在左边
 2 f = plt.subplot(121)
 3 # 画出函数曲线
 4 plt.plot(x, s, color='r')
 5 # 添加文字说明
 6 plt.text(-5., 0.9, r'$y=\sigma(x)$', fontsize=13)
 7 # 设置坐标轴格式
 8 currentAxis=plt.gca()
 9 currentAxis.xaxis.set_label_text('x', fontsize=15)
10 currentAxis.yaxis.set_label_text('y', fontsize=15)
11 
12 # 将ReLU的函数图像画在右边
13 f = plt.subplot(122)
14 # 画出函数曲线
15 plt.plot(x, y, color='g')
16 # 添加文字说明
17 plt.text(-3.0, 9, r'$y=ReLU(x)$', fontsize=13)
18 # 设置坐标轴格式
19 currentAxis=plt.gca()
20 currentAxis.xaxis.set_label_text('x', fontsize=15)
21 currentAxis.yaxis.set_label_text('y', fontsize=15)
22 
23 plt.show()

  

  可得到:

  

 

  这里需要知道的是其中的绘画函数plt.subplot的用法,借鉴一下其他的例子就是:

  

1 plt.subplot(221)
2 # plt.subplot(222)表示将整个图像窗口分为2行2列, 当前位置为2.
3 plt.subplot(222) # 第一行的右图
4 # plt.subplot(223)表示将整个图像窗口分为2行2列, 当前位置为3.
5 plt.subplot(223)
6 # plt.subplot(224)表示将整个图像窗口分为2行2列, 当前位置为4.
7 plt.subplot(224)

 

  可以发现的是,这里的运算是可以通过np.arange的方式进行计算,可以少掉很多的麻烦,大大增加了简便性。

 

1.2 图像处理

  图像在计算机中进行存储是以数字的方式进行的,就是对应于不同位置的不同像素点构成一个矩阵,可以通过矩阵的特征值等进行对图片的处理。这里进行对图片的翻转和剪切,最主要的就是其数值可以用ndarray来表示。首先准备一张图片,如下:

  

 

  现在对其进行利用代码来进行数据的提取,与数值的转化:

  

1 # 读入图片
2 image = Image.open('./work/images/000000001584.jpg')
3 image = np.array(image)
4 # 查看数据形状,其形状是[H, W, 3],
5 # 其中H代表高度, W是宽度,3代表RGB三个通道
6 print(image.shape)

 

  该图片的RGB值为:

  

 

 

   可以利用函数plt.imshow函数来查看原始的图片:

  

1 plt.imshow(image)
2 plt.show()

  

 

 

  然后就是对图片进行翻转操作,这里的意思是很明显的,因为我们知道,现在的图片展示在计算机当中就是一个n阶的矩阵,进行翻转在计算机当中的意思就是进行矩阵行变换,最后的变为最前的,一 一进行变换。

  

1 image2 = image[::-1, :, :]
2 plt.imshow(image2)
3 plt.show()

  这里使用数组反向切片的方式进行完成,即把图片的最后一行换到第一行,倒数第二行到正数第二行……,其中的::1,表示对于行指标的切片

  可以看到结果为:

  

 

 

   现在进行左右翻转:

  

1 image3 = image[:, ::-1, :]
2 plt.imshow(image3)
3 plt.show()

  结果为:

  

 

 

  这里需要知道的是,img函数的三个参数依次为行指标、列指标、RGB指标,当符号为“:”时,代表不变。

 

  然后是进行裁剪,其原理就是抹去一部分矩阵的数据,如对于高度的裁剪,可以发现定义输出图片的高度(行数)和宽度(列数)。

  下面的高度裁剪,是截取图片的1/3到原始最底端的一部分:

  

1 #  高度方向裁剪
2 H, W = image.shape[0], image.shape[1]
3 # 注意此处用整除,H_start必须为整数
4 H1 = H // 3 
5 H2 = H
6 image4 = image[H1:H2, :, :]
7 plt.imshow(image4)
8 plt.show()

  查看结果:

  

 

 

  宽度的裁剪:

  

1 #  宽度方向裁剪
2 H, W = image.shape[0], image.shape[1]
3 W1 = W//6
4 W2 = W//3 * 2
5 image5 = image[:, W1:W2, :]
6 plt.imshow(image5)
7 plt.show()

  查看结果:

  

 

 

  当然也可以同时进行裁剪,只需要再进行设定即可。

 

 

  调节亮度:即对RGB值进行处理:

  

1 # 调整亮度
2 image7 = image * 2.0
3 # 由于图片的RGB像素值必须在0-255之间,
4 # 此处使用np.clip进行数值裁剪
5 image7 = np.clip(image7, \
6         a_min=None, a_max=255.)
7 plt.imshow(image7.astype('uint8'))
8 plt.show()

 

  以下是通过不同值进行处理的图片对比:

  依次分别是:

  

1 image7 = image * 2.0
2 image7 = image * 5.0
3 image7 = image * 0.5

  

 

 

  拉伸与压缩:即对像素点的选取方式的处理:

  高度上每间隔1个取一次像素点:

  

1 #高度方向每隔一行取像素点
2 image8 = image[::2, :, :]
3 plt.imshow(image8)
4 plt.show()

  查看可看到高度的值减少为原来的1/2。

  

 

  宽度也是同理,只需要改变列指标即可。

  

  最后可以通过函数

1 # 保存图片
2 im3 = Image.fromarray(image)
3 im3.save('im3.jpg')

  来将处理后的图片进行保存。

 

2、Paddle.Tensor

   paddle.tensor,一个类型,在我理解而言,可以把他当作更多维度的array,维度更多,即shape[0],shape[1],shape[2]……同时还有更多的数据类型,能够更加灵活的处理一些数据。而且在反向梯度等数据处理上有着很突出的作用。它在计算中,除了可以与Numpy类似的做相乘的操作之外,还可以直接获取到每个变量的导数值。其中要知道的是下面函数:

  

paddle.to_tensor(data, dtype=None, place=None, stop_gradient=True)

  其中:

  • data (scalar|tuple|list|ndarray|Tensor) - 初始化tensor的数据,可以是 scalar,list,tuple,numpy.ndarray,paddle.Tensor类型。

  • dtype (str, optional) - 创建tensor的数据类型,可以是 'bool' ,'float16','float32', 'float64' ,'int8','int16','int32','int64','uint8','complex64','complex128'。 默认值为None,如果 data 为python浮点类型,则从get_default_dtype 获取类型,如果 data 为其他类型, 则会自动推导类型。

  • place (CPUPlace|CUDAPinnedPlace|CUDAPlace, optional) - 创建tensor的设备位置,可以是 CPUPlace, CUDAPinnedPlace, CUDAPlace。默认值为None,使用全局的place。

  • stop_gradient (bool, optional) - 是否阻断Autograd的梯度传导。默认值为True,此时不进行梯度传导。

 

  Tensor还可以与Numppy的数组方便的互转,具体方法为:

  

 1 import paddle
 2 import numpy as np
 3 
 4 tensor_to_convert = paddle.to_tensor([1.,2.])
 5 
 6 #通过 Tensor.numpy() 方法,将 Tensor 转化为 Numpy数组
 7 tensor_to_convert.numpy()
 8 
 9 #通过paddle.to_tensor() 方法,将 Numpy数组 转化为 Tensor
10 tensor_temp = paddle.to_tensor(np.array([1.0, 2.0]))

 

 3、手写数字识别任务

   对于我们自然人手写在纸上的数字,机器能够进行识别。即:

  • 任务输入:一系列手写数字图片,其中每张图片都是相同的像素矩阵。

  • 任务输出:经过了大小归一化和居中处理,输出对应的数字标签。

 

3.1 前提条件

  在进行数据的处理前,要引用相关的库:

  

1 #加载飞桨和相关类库
2 import paddle
3 from paddle.nn import Linear
4 import paddle.nn.functional as F
5 import os
6 import numpy as np
7 import matplotlib.pyplot as plt

 

 3.2 数据处理

  首先寻找到相关的数据集,然后用封装好的数据集API(飞桨API支持如下常见的学术数据集:mnist、cifar、Conll05、imdb、imikolov、movielens、sentiment、uci_housing、wmt14、wmt16)设置数据读取器,如通过paddle.vision.datasets.MNIST API设置数据读取器。

  以读取其中一个数字为例子,注意的是mnist 数据集包含60000个训练集和10000测试数据集,分为图片和标签,图片是 28 * 28 的像素矩阵,标签为0~9共10个数字:

  

 1 train_dataset = paddle.vision.datasets.MNIST(mode='train')
 2 
 3 train_data0 = np.array(train_dataset[0][0])
 4 train_label_0 = np.array(train_dataset[0][1])
 5 
 6 # 显示第一batch的第一个图像
 7 import matplotlib.pyplot as plt
 8 plt.figure("Image") # 图像窗口名称
 9 plt.figure(figsize=(2,2))
10 plt.imshow(train_data0, cmap=plt.cm.binary)
11 plt.axis('on') # 关掉坐标轴为 off
12 plt.title('image') # 图像题目
13 plt.show()
14 
15 print("图像数据形状和对应数据为:", train_data0.shape)
16 print("图像标签形状和对应数据为:", train_label_0.shape, train_label_0)
17 print("\n打印第一个batch的第一个图像,对应标签数字为{}".format(train_label_0))

  这里应当注意的点在于,train_dataset = paddle.vision.datasets.MNIST(mode='train'),这句代码,当你目录C:\Users\用户名\.cache\paddle\dataset\mnist下没有对应的数据集时,它会主动下载,但是在python中进行下载,几乎时很费时费力的,所以我建议直接下载数据集在目录下较为方便。

  得到的结果为:

  

  

 

  

 

 3.3 模型设计

   因为图像的像素是按照28✖28的,并且是严格按照这个标准来及进行的,对其处理与之前的房价预测一样,还是采用单层网络结构。如下图所示:

  

 

  首先建立网络结构(单层网络结构),这与之前的房价预测有些许相似:

  

 1 # 定义mnist数据识别网络结构,同房价预测网络
 2 class MNIST(paddle.nn.Layer):
 3     def __init__(self):
 4         super(MNIST, self).__init__()
 5         
 6         # 定义一层全连接层,输出维度是1
 7         self.fc = paddle.nn.Linear(in_features=784, out_features=1)
 8         
 9     # 定义网络结构的前向计算过程
10     def forward(self, inputs):
11         outputs = self.fc(inputs)
12         return outputs

  

3.4 训练配置

  生成模型实例,指设为训练状态,然后设置优化算法和学习率,这里依旧是采用随机梯度下降SGD,学习率为0.001

  

 1 # 声明网络结构
 2 model = MNIST()
 3 
 4 def train(model):
 5     # 启动训练模式
 6     model.train()
 7     # 加载训练集 batch_size 设为 16
 8     train_loader = paddle.io.DataLoader(paddle.vision.datasets.MNIST(mode='train'), 
 9                                         batch_size=16, 
10                                         shuffle=True)
11     # 定义优化器,使用随机梯度下降SGD优化器,学习率设置为0.001
12     opt = paddle.optimizer.SGD(learning_rate=0.001, parameters=model.parameters())

 

  需要注意的是,DataLoader返回一个迭代器,该迭代器根据 batch_sampler 给定的顺序迭代一次给定的 dataset,其参数解释为:

  • dataset (Dataset) - DataLoader从此参数给定数据集中加载数据,此参数必须是 paddle.io.Dataset 或 paddle.io.IterableDataset 的一个子类实例

  • batch_sampler (BatchSampler) - paddle.io.BatchSampler 或其子类的实例,DataLoader通过 batch_sampler 产生的mini-batch索引列表来 dataset 中索引样本并组成mini-batch。默认值为None。

  • batch_size (int|None) - 每mini-batch中样本个数,为 batch_sampler 的替代参数,若 batch_sampler 未设置,会根据 batch_sizeshuffledrop_last 创建一个 paddle.io.BatchSampler 。默认值为1。

  • shuffle (bool) - 生成mini-batch索引列表时是否对索引打乱顺序,为 batch_sampler 的替代参数,若 batch_sampler 未设置,会根据 batch_sizeshuffledrop_last 创建一个 paddle.io.BatchSampler 。默认值为False。

  paddle.optimizer.SGD ( learning_rate=0.001, parameters=None, weight_decay=None, grad_clip=None,name=None )中的参数解释:

  • learning_rate (float|_LRScheduler, 可选) - 学习率,用于参数更新的计算。可以是一个浮点型值或者一个_LRScheduler类,默认值为0.001

  • parameters (list, 可选) - 指定优化器需要优化的参数。在动态图模式下必须提供该参数;在静态图模式下默认值为None,这时所有的参数都将被优化

  • weight_decay (float|Tensor, 可选) - 权重衰减系数,是一个float类型或者shape为[1] ,数据类型为float32的Tensor类型。默认值为0.01

  • grad_clip (GradientClipBase, 可选) – 梯度裁剪的策略,支持三种裁剪策略: cn_api_fluid_clip_GradientClipByGlobalNorm 、 cn_api_fluid_clip_GradientClipByNorm 、 cn_api_fluid_clip_GradientClipByValue 。 默认值为None,此时将不进行梯度裁剪。

  • name (str, 可选)- 该参数供开发人员打印调试信息时使用,默认值为None

 

3.5 训练过程

   同样采用两层循环,①内层循环:负责整个数据集的一次遍历,遍历数据集采用分批次(batch)方式;②外层循环:定义遍历数据集的次数,本次训练中外层循环10次,通过参数EPOCH_NUM设置。

  

 1 import paddle
 2 # 确保从paddle.vision.datasets.MNIST中加载的图像数据是np.ndarray类型
 3 paddle.vision.set_image_backend('cv2')
 4 
 5 # 声明网络结构
 6 model = MNIST()
 7 
 8 def train(model):
 9     # 启动训练模式
10     model.train()
11     # 加载训练集 batch_size 设为 16
12     train_loader = paddle.io.DataLoader(paddle.vision.datasets.MNIST(mode='train'), 
13                                         batch_size=16, 
14                                         shuffle=True)
15     # 定义优化器,使用随机梯度下降SGD优化器,学习率设置为0.001
16     opt = paddle.optimizer.SGD(learning_rate=0.001, parameters=model.parameters())
17     EPOCH_NUM = 10
18     for epoch in range(EPOCH_NUM):
19         for batch_id, data in enumerate(train_loader()):
20             images = norm_img(data[0]).astype('float32')
21             labels = data[1].astype('float32')
22             
23             #前向计算的过程
24             predicts = model(images)
25             
26             # 计算损失
27             loss = F.square_error_cost(predicts, labels)
28             avg_loss = paddle.mean(loss)
29             
30             #每训练了1000批次的数据,打印下当前Loss的情况
31             if batch_id % 1000 == 0:
32                 print("epoch_id: {}, batch_id: {}, loss is: {}".format(epoch, batch_id, avg_loss.numpy()))
33             
34             #后向传播,更新参数的过程
35             avg_loss.backward()
36             opt.step()
37             opt.clear_grad()
38             
39 train(model)
40 paddle.save(model.state_dict(), './mnist.pdparams')

  其中对进行学习训练的数据与label,进行比对,从而计算Loss。不断的更新参数。

  要注意的是这里对图片的数据进行了归一化处理:

  

 1 # 图像归一化函数,将数据范围为[0, 255]的图像归一化到[0, 1]
 2 def norm_img(img):
 3     # 验证传入数据格式是否正确,img的shape为[batch_size, 28, 28]
 4     assert len(img.shape) == 3
 5     batch_size, img_h, img_w = img.shape[0], img.shape[1], img.shape[2]
 6     # 归一化图像数据
 7     img = img / 255
 8     # 将图像形式reshape为[batch_size, 784]
 9     img = paddle.reshape(img, [batch_size, img_h*img_w])
10     
11     return img

 

  最后得到的结果为:

  

 

 

  可以发现直到最后,整个Loss依旧是比较高,可见简单的线性并无法使得该模型有良好的预测和识别功能,接下来对其进行测试。

 

3.6 模型测试

  检测之前的模型是否能够准确数字识别,共有以下几个步骤:

  • 声明实例

  • 加载模型:加载训练过程中保存的模型参数,

  • 灌入数据:将测试样本传入模型,模型的状态设置为校验状态(eval),显式告诉框架我们接下来只会使用前向计算的流程,不会计算梯度和梯度反向传播。

  • 获取预测结果,取整后作为预测标签输出。

 

  读取样例图片,进行归一化操作:

 1 # 导入图像读取第三方库
 2 import matplotlib.pyplot as plt
 3 import numpy as np
 4 from PIL import Image
 5 
 6 img_path = 'example_0.jpg'
 7 # 读取原始图像并显示
 8 im = Image.open('example_0.jpg')
 9 plt.imshow(im)
10 plt.show()
11 # 将原始图像转为灰度图
12 im = im.convert('L')
13 print('原始图像shape: ', np.array(im).shape)
14 # 使用Image.ANTIALIAS方式采样原始图片
15 im = im.resize((28, 28), Image.Resampling.LANCZOS)
16 plt.imshow(im)
17 plt.show()
18 print("采样后图片shape: ", np.array(im).shape)

 

  

  采样前:

  

 

  采样后:

  

 

  

 

 

   对于一张本地的图片进行预测:

  

 1 img_path = 'example_0.jpg'
 2 # 读取一张本地的样例图片,转变成模型输入的格式
 3 def load_image(img_path):
 4     # 从img_path中读取图像,并转为灰度图
 5     im = Image.open(img_path).convert('L')
 6     # print(np.array(im))
 7     im = im.resize((28, 28), Image.Resampling.LANCZOS)
 8     im = np.array(im).reshape(1, -1).astype(np.float32)
 9     # 图像归一化,保持和数据集的数据范围一致
10     im = 1 - im / 255
11     return im
12 
13 # 定义预测过程
14 model = MNIST()
15 params_file_path = 'mnist.pdparams'
16 img_path = 'example_0.jpg'
17 # 加载模型参数
18 param_dict = paddle.load(params_file_path)
19 model.load_dict(param_dict)
20 # 灌入数据
21 model.eval()
22 tensor_img = load_image(img_path)
23 result = model(paddle.to_tensor(tensor_img))
24 print('result',result)
25 #  预测输出取整,即为预测的数字,打印结果
26 print("本次预测的数字是", result.numpy().astype('int32'))

 

  得到的结果为:

  

 

   可以发现手写的数字为0,而体现的数字为1,说明简单的线性加权和并不能很好的进行识别。

 

4、模型优化

  ……

 

5、总结

  对以上部分的学习,可以对很多python的函数等知识有一个更多的认识,也对整个模型建立的过程有了一个知识的巩固。但对于其中的详细步骤,还有好几个地方是无法理解的,就像data[0]和data[1]为什么标识图片数据和lable值等,此外模型到目前为止很明显是有很大问题的,需要进一步的改善。

 

6、参考资料

Paddle.Tensor:https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/to_tensor_cn.html

image错误处理:https://blog.csdn.net/hjxu2016/article/details/70215103

激活函数:https://www.cnblogs.com/missidiot/p/9378079.html

数据集下载:https://aistudio.baidu.com/aistudio/datasetdetail/10595

DataLoader详解:https://lib.yanxishe.com/document/PaddlePaddle/api/paddle.io.DataLoader

paddle.optimizer.SGD函数:https://www.bookstack.cn/read/paddlepaddle-2.0-zh/3aea7f291f9042c8.md

详细教程:https://aistudio.baidu.com/aistudio/projectdetail/4341261

posted @ 2022-07-15 22:59  I'MPGB  阅读(144)  评论(0编辑  收藏  举报