飞桨PaddlePaddle深度学习集训营CV学习心得

之前学习过吴恩达老师的深度学习的视频,但是比较少代码的实践,而且很多理论知识理解不到位。通过这次集训营的学习,增强了实战能力,对很多知识也理解更深刻,也学会多了一个深度学习框架飞桨的基本使用。

一、主要学习内容及学习到的技术:

1、不用框架只用python来实现一个房价预测的案例

这个案例对一些概念如:前向计算、反向计算、损失函数、梯度下降的理解特别有用,以下是一部分关键代码,
主要包括前向计算、计算损失、梯度计算和梯度更新

def forward(self, x):
    self.z1 = np.dot(x, self.w0) + self.b0 # N * 13
    self.z2 = np.dot(self.z1, self.w1) + self.b1 # N * 13
    self.z3 = np.dot(self.z2, self.w2) + self.b2 # N * 1
    return self.z3

def loss(self, z, y):
    error = z - y
    num_samples = error.shape[0]
    cost = error * error
    cost = np.sum(cost) / num_samples
    return cost

def gradient(self, x, y):
    z3 = self.forward(x)
    # print("*"*50)
    gradient_w2 = (z3-y) * self.z2 
    gradient_w2 = np.mean(gradient_w2, axis=0)
    gradient_w2 = gradient_w2[:, np.newaxis]  
    gradient_b2 = (z3 - y)
    gradient_b2 = np.mean(gradient_b2)
    
    gradient_w1 = (z3-y) * self.w2.T * self.z1 
    gradient_w1 = np.mean(gradient_w1, axis=0)
    gradient_w1 = gradient_w1[:, np.newaxis]  
    gradient_b1 = (z3 - y) * self.w2.T
    gradient_b1 = np.mean(gradient_b1)
    
    w1 = self.w1.T
    gradient_b0 = (z3-y) * self.w2.T * w1[0:1]
    gradient_b0 = np.mean(gradient_b0)
    gradient_b0 = gradient_b0.reshape(-1,1)
    gradient_w0 = (z3-y) * self.w2.T * w1[0:1] * x
    gradient_w0 = np.mean(gradient_w0, axis=0)
    gradient_w0 = gradient_w0[:, np.newaxis]
    
    for k in range(1,w1.shape[0],1):
        t = w1[k:k+1] 
        gw0 = (z3-y) * self.w2.T * t * x
        gw0 = np.mean(gw0, axis=0)
        gw0 = gw0[:, np.newaxis]
        gradient_w0 = np.hstack((gradient_w0,gw0))
        b0 = (z3-y) * self.w2.T * t
        b0 = np.mean(b0)
        b0 = b0.reshape(-1,1)
        gradient_b0 = np.hstack((gradient_b0,b0))
        
    return gradient_w0, gradient_b0,gradient_w1, gradient_b1,gradient_w2, gradient_b2

def update(self, gradient_w0, gradient_b0, gradient_w1, gradient_b1,gradient_w2, gradient_b2, eta = 0.01):
    self.w0 = self.w0 - eta * gradient_w0
    self.b0 = self.b0 - eta * gradient_b0
    self.w1 = self.w1 - eta * gradient_w1
    self.b1 = self.b1 - eta * gradient_b1
    self.w2 = self.w2 - eta * gradient_w2
    self.b2 = self.b2 - eta * gradient_b2

2、使用paddle重写房价预测案例

在这一部分学会了paddle的一些基本使用,还有训练一个模型的基本步骤:
1. 数据处理:读取数据 和 预处理操作
2. 模型设计:网络结构(假设)
3. 训练配置:优化器(寻解算法)
4. 训练过程:循环调用训练过程,包括前向计算 + 计算损失(优化目标) + 后向传播
5. 保存模型并测试:将训练好的模型保存

3、通过一个手写识别的案例来深入学习

主要包括:数据处理、网络结构、损失函数、优化算法、多GPU训练、训练调试及优化、恢复训练这及部分内容

1、数据处理:

这里主要学到如何去编写适合当前任务的数据处理程序
自己编写数据处理程序时,一般会涉及以下四个部分:
1、数据读取与数据集划分
训练模型时一半需要将数据分为训练数据、验证数据、测试数据三部分,分别用于训练、模型调整优化、模型最终效果测试
2、定义数据读取器,以下为主要代码

# 定义数据生成器
		def data_generator():
			if mode == 'train':
				# 训练模式下打乱数据
				random.shuffle(index_list)
			imgs_list = []
			labels_list = []
			for i in index_list:
				# 将数据处理成希望的格式,比如类型为float32,shape为[1, 28, 28]
				img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
				label = np.reshape(labels[i], [1]).astype('float32')
				imgs_list.append(img) 
				labels_list.append(label)
				if len(imgs_list) == BATCHSIZE:
					# 获得一个batchsize的数据,并返回
					yield np.array(imgs_list), np.array(labels_list)
					# 清空数据读取列表
					imgs_list = []
					labels_list = []

			# 如果剩余数据的数目小于BATCHSIZE,
			# 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
			if len(imgs_list) > 0:
				yield np.array(imgs_list), np.array(labels_list)

3、校验数据的有效性,分为机器校验和人工校验
机器校验,简单的如下所示:

imgs_length = len(imgs)
			assert len(imgs) == len(labels), \
				  "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(labels))

人工校验分两步,首先打印数据输出结果,观察是否是设置的格式。再从训练的结果验证数据处理和读取的有效性。
4、异步数据读取
同步数据读取方式,针对于样本量较大、数据读取较慢的场景,需要采用异步数据读取方式,可以让数据读取和模型训练并行化,加快数据读取速度,牺牲一小部分内存换取数据读取效率的提升。
同步数据读取:每当模型需要数据的时候,运行数据读取函数获得当前批次的数据。在读取数据期间,模型一直在等待数据读取结束,获得数据后才会进行计算。
异步数据读取:数据读取和模型训练过程异步进行,读取到的数据先放入缓存区。模型训练完一个批次后,不用等待数据读取过程,直接从缓存区获得下一批次数据进行训练。
飞桨PaddlePaddle深度学习集训营CV学习心得_第1张图片

2、网络结构

经典神经网络的组成:输入层、隐含层和输出层

输入层:准备数据,输入给神经网络。
隐含层:增加网络深度和复杂度,隐含层的节点数是可以调整的。节点数越多,神经网络表示能力越强,但同时参数量也会增加。
输出层:输出网络计算结果

激活函数
隐含层引入非线性激活函数sigmoid是为了增加神经网络的非线性能力,举例来说,一个神经网络有四个输入x1~x4,一个输出y,采用线性变换。假设第一层的变换是z1=x1-x2和z2=x3+x4,第二层的变换是y=z1+z2。将两层的变换展开后得到 y=x1-x2+x3+x4。原始输入和最终输出之间依然是线性关系,无论中间累积了多少层线性变换均是如此。

3、损失函数

损失函数是模型优化的目标,用于衡量在无数的参数取值中,哪一个是最理想的。损失函数的计算在训练过程的代码中,每一轮的训练代码均是一致的过程:先根据输入数据正向计算预测输出,再根据预测值和真实值计算损失,最后根据损失反向传播梯度并更新参数。

这一块主要学习了Softmax函数及交叉熵损失函数的概念和公式。

之前在做房价预测时使用了均方误差函数,但是房价预测是回归任务,
手写识别是分类任务,分类任务这种损失函数是不适用的,而且使用均方误差作为损失存在逻辑和效果上的缺欠,
比如房价可以是0-9之间的任何浮点数,手写数字识别的数字只可能是0-9之间的10个实数值(标签)

4、优化算法

神经网络所拟合的函数是高度非凸函数,理想的训练目标是,优化这类函数,达到函数最小值点或接近最小值的极小值点。选择合适的优化器和学习率是实现训练目标的关键因素。
主要学习到了怎么设置合理的优化器和学习率参数,保证训练结果达到理想目标。
优化器有很多种:如SGDOptimizer、MomentumOptimizer、AdagradOptimizer、AdamOptimizer等,大多的目的都是为了使模型训练的更加稳定、动态调整动量和学习率、加快收敛和避免陷入局部最小值、鞍点这些情况。学习率过高,则会导致梯度更新数值过大、会越过最优值等;学习率过低,则会导致训练很慢收敛或者无法前进,所以学习率的选取和调整很重要,学习率的调整可以通过使用类似AdamOptimizer这种动态调整学习率的优化器,也可以自己调整学习率调,如飞桨的提供的一些API:
飞桨PaddlePaddle深度学习集训营CV学习心得_第2张图片

5、多GPU训练

这一部分,由于资源限制,暂时还没怎么实践。
分布式训练有两种实现模式:模型并行和数据并行。

  1. 模型并行
    模型并行是将一个网络模型拆分为多份,拆分后的模型分到多个设备上(GPU)训练,每个设备的训练数据是相同的。 模型并行的方式一般适用于:
    模型架构过大,完整的模型无法放入单个GPU。2012年ImageNet大赛的冠军模型AlexNet是模型并行的典型案例。由于当时GPU内存较小,单个GPU不足以承担AlexNet。研究者将AlexNet拆分为两部分放到两个GPU上并行训练。
    网络模型的设计结构可以并行化时,采用模型并行的方式。例如在计算机视觉目标检测任务中,一些模型(YOLO9000)的边界框回归和类别预测是独立的,可以将独立的部分分在不同的设备节点上完成分布式训练。
    说明:当前GPU硬件技术快速发展,深度学习使用的主流GPU的内存已经足以满足大多数的网络模型需求,所以大多数情况下使用数据并行的方式。

  2. 数据并行
    数据并行与模型并行不同,数据并行每次读取多份数据,读取到的数据输入给多个设备(GPU)上的模型,每个设备上的模型是完全相同的。数据并行的方式与众人拾柴火焰高的道理类似,如果把训练数据比喻为砖头,把一个设备(GPU)比喻为一个人,那单GPU训练就是一个人在搬砖,多GPU训练就是多个人同时搬砖,每次搬砖的数量倍数增加,效率呈倍数提升。但是注意到,每个设备的模型是完全相同的,但是输入数据不同,每个设备的模型计算出的梯度是不同的,如果每个设备的梯度更新当前设备的模型就会导致下次训练时,每个模型的参数都不同了,所以我们还需要一个梯度同步机制,保证每个设备的梯度是完全相同的。

数据并行中有一个参数管理服务器(parameter server)收集来自每个设备的梯度更新信息,并计算出一个全局的梯度更新。当参数管理服务器收到来自训练设备的梯度更新请求时,统一更新模型的梯度。
飞桨有便利的数据并行训练方式,仅改动几行代码即可实现多GPU训练,如果想了解飞桨数据并行的基本思想,可以参考官网文档-https://www.paddlepaddle.org.cn/documentation/docs/zh/user_guides/howto/training/cluster_howto.html。

6、训练调试及优化

1、计算分类准确率,观测模型训练效果。
交叉熵损失函数只能作为优化目标,无法直接准确衡量模型的训练效果。准确率可以直接衡量训练效果,但由于其离散性质,不适合做为损失函数优化神经网络。
2、检查模型训练过程,识别潜在问题。
如果模型的损失或者评估指标表现异常,我们通常需要打印模型每一层的输入和输出来定位问题,分析每一层的内容来获取错误的原因。
3、加入校验或测试,更好评价模型效果。
理想的模型训练结果是在训练集和验证集上均有较高的准确率。
如果发生欠拟合,则网络训练程度不够;如果发生了过拟合现象,即在训练集上的损失小,在校验集或测试集上的损失较大,
为了避免模型过拟合,在没有扩充样本量的可能下,只能降低模型的复杂度。降低模型的复杂度,
可以通过限制参数的数量或可能取值(参数值尽量小)实现。
具体来说,在模型的优化目标(损失)中人为加入对参数规模的惩罚项。当参数越多或取值越大时,该惩罚项就越大。
4、可视化分析。观察损失、模型评价指标等

7、恢复训练

在日常训练工作中我们会遇到一些突发情况,导致训练过程主动或被动的中断。如果训练一个模型需要花费几天的训练时间,中断后从初始状态重新训练是不可接受的。
这里学习到了如何用飞桨从上一次保存状态开始训练,就不用从初始状态重新训练。

恢复训练有两个要点:
保存模型时同时保存模型参数和优化器参数。
恢复参数时同时恢复模型参数和优化器参数。

最后总结建模的方法如下图:
飞桨PaddlePaddle深度学习集训营CV学习心得_第3张图片

4、计算机视觉(CV)

1、卷积神经网络基础

这里主要学习到一些卷积神经网络的基础模块的原理及使用:
	卷积(Convolution)
	池化(Pooling)
	ReLU激活函数
	批归一化(Batch Normalization)
	丢弃法(Dropout)
个人结合学习内容的一些理解:
	1、卷积:利用图像特征与位置无关的特点,使用了参数共享解决了参数过多的问题;保存了图像的空间信息
	2、池化:减少计算量、一定程序解决平移鲁棒
	3、ReLU激活函数:一定程度解决梯度消失问题
	4、批归一化(Batch Normalization):模型训练更加稳定和能收敛
	5、丢弃法(Dropout): 抑制过拟合

2、图像分类

图像分类是根据图像的语义信息对不同类别图像进行区分,是计算机视觉中重要的基础问题,是物体检测、图像分割、物体跟踪、行为分析、人脸识别等其他高层次视觉任务的基础
这里主要学习到了一些图像分类领域的经典卷积神经网络的原理及使用:LeNet、AlexNet、VGG、GoogLeNet和ResNet,并将它们应用到眼疾筛查数据集上。

3、目标检测

这里主要学习了目标检测的一些基础概念,包括边界框、锚框和交并比等,以及YOLOV3模型的原理及使用

YOLO-V3模型设计思想
	产生候选区域
	生成锚框
	生成预测框
	标注候选区域
	卷积神经网络提取特征
	建立损失函数
	获取样本标签
	建立各项损失函数
	多层级检测
	预测输出
	计算预测框得分和位置
	非极大值抑制

二、飞桨的使用体验和使用计划

这次使用飞桨,总体感觉还不错,不过可能是还在更新,一些API文档不够完善和功能还不太稳定,不过相信会越做越好,我是非常希望能用我们中国人自己研发的框架。
所以将来希望机会将飞桨用于工作中,目前集训营只学了CV,对于目前的工作比较有用的是NLP和推荐的内容,
学完NLP和推荐,就可以分析一些工作的一些数据,提取一些需要的特征,比如情感分析这种,之后尝试将推荐用于产品推荐功能。或者尝试自己构建网络,来做一些风控等特殊的应用。

三、参加AI识虫比赛的总结

1、一定要先做好数据分析和预处理再开始训练!
2、超参数的调整可以从粗到细进行调整,要记录每次调整的结果

下面则是一些自己实践过和别人实践过效果不错的技巧:
1、数据增强,包括mixup
2、anchor box聚类
3、使用focal loss
4、label smooth(类别数较小时不使用)
5、学习率采用warmup算法
6、训练一个分类器对之后的检测结果再次进行分类
7、模型融合
8、NMS算法调整:如去除重复框

四、感谢

非常感谢飞桨的老师和工作人员,感谢百度,提供了这么好的一个学习深度学习的机会!

你可能感兴趣的:(深度学习,深度学习,paddlepaddle,计算机视觉)