深度学习
二、感知机
1.运行原理
多个输入——>一个输出(0或1) 当信号*权重之和大于某个阈值θ时,输出1 表达式为:
b为偏置参数. 使用感知机可以表示与门、与非门、或门等逻辑电路. 与门、与非门、或门只有参数的值即权重和阈值不同。
2.局限性和优势 无法实现异或门。 局限性在于感知机只能表示由一条直线分割的线性空间,而异或门只能用曲线分割为非线性空间, 优势在于可以“叠加层”,通过叠加层来表示异或门。
3.多层感知机 利用与门、或门和与非门可以形成异或门 (1)第0层的两个神经元接受输入信号,并将信号发送至第一层的神经元; (2)第1层的神经元将信号发送至第2层的神经元,第2层的神经元输出y.
感知机通过叠加层能够进行非线性的表示,理论上可以表示计算机进行的处理。
三、神经网络
神经网络的一个重要的性质就是它可以自动地从数据中学习到合适的权重参数。
1.神经网络的基本结构
输入层–中间层(隐藏层)–输出层
2.激活函数
引入h(x),其作用在于决定如何来激活输入信号的总和: y=h(b+w1x1+w2x2) a=b+w1x1+w2x2 y=h(a) 节点a即为信号的加权总合,或者称为神经元.
(1)sigmoid函数
def sigmoid(x): return 1 / (1 + np.exp(-x))
绘图
def main(): x = np.arange(-5.0, 5.0, 0.1) y = sigmoid(x) plt.plot(x,y) plt.ylim(-0.1, 1.1) plt.show()
(2)阶跃函数
Python实现比较简单,if条件分支语句即可。
3)非线性函数
阶跃函数和sigmoid函数都是非线性函数,为了发挥叠加层带来的优势,激活函数必须使用非线性函数。
(4)ReLU函数
def relu(x): return np.maximum(0, x)
3.多维数组的运算
与matlab中矩阵相乘类似 神经网络的内积等于X矩阵和W矩阵的点积
Y = np.dot(X, W)
4.三层神经网络的实现
输入层有2个神经元,第1个隐藏层有3个神经元,第2个隐藏层有2个神经元,输出层有2个神经元。 W12(1): 12下标代表前一层的第2个神经元到后一层的第1个神经元的权重; (1)上标代表权重和神经元的层号为1。
第一层传递用矩阵表达如下
import numpy as np from sigmoid_function import sigmoid X = np.array([1.0, 0.5]) W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) B1 = np.array([0.1, 0.2, 0.3]) print(W1.shape) print(X.shape) print(B1.shape) A1 = np.dot(X, W1) + B1 Z1 = sigmoid(A1) print(A1) # [0.3 0.7 1.1] print(Z1) # [0.57444252 0.66818777 0.75026011]
第二层、第三层与第一层几乎完全相同
定义了identity_function()函数,将其作为输出层的激活函数,恒等函数会将输入按原样输出。 总共三层神经元
import numpy as np from sigmoid_function import sigmoid def init_network(): network = {} network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]]) network['b1'] = np.array([0.1, 0.3, 0.5]) network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]]) network['b2'] = np.array([0.1, 0.2]) network['W3'] = np.array([[0.1, 0.3], [0.2, 0.4]]) network['b3'] = np.array([0.1, 0.2]) return network def identity_function(x): return x def forward(network, x): W1, W2, W3 = network['W1'], network['W2'], network['W3'] b1, b2, b3 = network['b1'], network['b2'], network['b3'] a1 = np.dot(x, W1) + b1 z1 = sigmoid(a1) a2 = np.dot(z1, W2) + b2 z2 = sigmoid(a2) a3 = np.dot(z2, W3) + b3 y = identity_function(a3) return y if __name__ == '__main__': network = init_network() x = np.array([1.0, 0.5]) y = forward(network, x) print(y)
5.输出层设计
softmax函数——恒等函数即σ,输入信号原封不动地被输出。做分类问题时(判断数据属于哪一类),会用到softmax函数:
n即为输出层的神经元,用人话说:最终输出等于输出层求指数后在输出总和中的占比。相当于用概率的方法处理问题。 但softmax不适合做太大数字的运算,会产生溢出现象,可以将表达式进行改进,分子分母同乘相同的常数,一般使得logC等于输入信号中的最大值。
import numpy as np def softmax(a): exp_a = np.exp(a) sum_exp_a = np.sum(exp_a) y = exp_a / sum_exp_a return y
6.手写数字识别 (1)MINIST数据集 非常常用的手写数字图像集,由0-9的数字图像构成,一般使用方法为,先用训练图像进行学习,再用学习到的模型度量能在多大程度上对测试图像进行正确的分类。 load_mnist()函数以(训练图像,训练标签),(测试图像,测试标签)的形式返回读入的MNIST数据。 选择参数FLATTEN等于True的话,读入的MNIST图像数据会转化为一维数组,需要用reshape函数进行调整后才能绘图,同时也需要使用PIL库展示图像。
(2)神经网络的推理 输入层有784个神经元即28*28=784个像素,输出层有10个神经元,数字来源于10类别分类(即数字0-9),两个隐藏层,第一个隐藏层有50个神经元,第二个隐藏层有100个神经元。 主函数如下:
x, t = get_data() network = init_network() accuracy_cnt = 0 for i in range(len(x)): y = predict(network, x[i]) p= np.argmax(y) # 获取概率最高的元素的索引 if p == t[i]: accuracy_cnt += 1
获取数据——初始化神经网络(读入参数)——循环推导神经网络——将回答正确的概率作为识别精度。 np.argmax(array, axis)函数用于返回一个numpy数组中最大值的索引值。当一组中同时出现几个最大值时,返回第一个最大值的索引值。 运行函数得到Accuracy:0.9352,表示有93.52%的数据正确分类了。 另外,可以使用normalize将图像像素值除以255进行正规化,使得数据都在0.0-1.0的范围内,这种预处理方式可以提高识别性能和学习效率。
(3)批处理 前面的神经网络的推导是基于单张图像形成的,如果我们考虑打包输入多张图像的情形,比如100张,则需要使得输入图像X形状变为100 784,输出Y也相应地变成了100 10. 这样打包输入的数据称为批,可以大幅度缩短每张图像的处理时间。 基于批的代码实现:
x, t = get_data() network = init_network() batch_size = 100 accuracy_cnt = 0 for i in range(0, len(x), batch_size): x_batch = x[i:i + batch_size] y_batch = predict(network, x_batch) p= np.argmax(y_batch, axis=1) # 获取概率最高的元素的索引 accuracy_cnt += np.sum(p == t[i:i + batch_size]) print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
range(start,end,step)可以用来生成列表, axis=1代表沿着一维方向寻找值最大的元素索引。axis对应的是剥掉的中括号的位置
四、神经网络的学习
1.从数据中学习
对于线性可分问题,根据“感知机收敛定理”,通过有限次数的学习,线性可分问题是可解的。但是非线性可分问题无法通过学习解决。 解决问题有以下三种方法:
通过算法解决,通过对特征量的机器学习解决,通过神经网络对图像进行深度学习。特征量仍然是人所想到的,设计的,而在神经网络中,图像中包含的重要特征量也都是由机器来学习。深度学习也被称为端对端学习,从原始数据中获得目标结果,其优点就是对所有问题都可以用同样的流程来解决。 获得泛化能力是机器学习的最终目标。泛化能力指的是处理未被观察过的数据的能力。一般将数据分为训练数据和测试数据两部分来进行学习和实验。首先,使用训练数据进行学习,寻找最优的参数,然后,使用测试参数评价训练得到的模型的实际能力。
2.损失函数 神经网络的学习中所用的指标称为损失函数,表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合。这个损失函数可以使用任意函数,但是一般用均方误差和交叉熵误差等。
(1)均方误差
yk代表神经网路的输出,tk表示监督数据,k表示数据的维数。 代码与测试:
import numpy as np def mean_squared_error(y, t): return 0.5 * np.sum((y - t) ** 2) if __name__ == '__main__': t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0] y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] print(mean_squared_error(np.array(y), np.array(t)))
结果:0.510825457099338 加上微小值delta=1e-7可以防止负无穷大的产生。
mini-batch学习
求所有训练数据的损失函数的总和如下:
2.梯度
计算各个参数在某点处的偏导数,得到一个形状和参数相同的梯度数组。
def _numerical_gradient_no_batch(f, x): h = 1e-4 # 0.0001 grad = np.zeros_like(x)
for idx in range(x.size): tmp_val = x[idx] x[idx] = float(tmp_val) + h fxh1 = f(x) # f(x+h) x[idx] = tmp_val - h fxh2 = f(x) # f(x-h) grad[idx] = (fxh1 - fxh2) / (2*h) x[idx] = tmp_val # 还原值 return grad
np.zeros_like(x)会生成一个形状和x相同、所有元素都为0的数组。 梯度表示的是各点处的函数值减小最多的方向,函数的极小值、最小值以及被称为鞍点的地方,梯度为0.寻找最小值的梯度法称为梯度下降法,对应的有梯度上升法。
η表示更新量,或者称为学习率,代表更新参数的程度
学习率太大或者太小都可能会得到不好的结果,学习率太大时会发散成一个很大的值,学习率过小,需要的步数会很多。 神经网络的梯度是指损失函数关于权重参数的梯度,梯度的表达式与权重矩阵形状相同:
第五章:误差反向传播法
计算图求解的优势: 优势一:计算图可以集中精力于局部计算。无论全局的计算有多么复杂,各个步骤所要做的就是对象节点的局部计算。通过局部计算,最后求出最优解。 优势二:利用计算图可以将中间的计算结果全部保存起来,通过反向传播高效计算导数。
正向传播(forward propagation是从计算图出发点到结束点的传播。
而反向传播即从结果出发,反推过程。
1链式法则的计算
加法节点的反向传播
加法节点的反向传播只是将输入信号直接输出到下一个节点。
乘法节点的传播
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。(就是分别对x,y求偏导)
2激活函数层的实现
1ReLU层:
2 Sigmoid层:
3 Affine层
这里,X、W、B分别是形状为(2.)、(2,3)、(3.)的多维数组。这样一来,神经元的加权和可以用Y=np.dot(X,W)+B计算出来。然后,Y经过激活函数转换后,传递给下一层。这就是神经网络正向传播的流程。此外我们来复习一下,矩阵的乘积运算的要点是使对应维度的元素个数一致,X和W的乘积必须使对应维度的元素个数一致 另外,这里矩阵的形状用(2,3)这样的括号表示(为了和NumPy的shape属性的输出一致)
4 softmax层
Softmax层将输入值正规化(将输出值的和调整为1)之后再输出。 Softmax层中的误差反向传播也包含作为损失函数的交叉熵误差(cross entropy error),所以称为“Softmax-with-Loss层”。其简易计算图如下:
3学习算法的实现
原理很简单: 选出mini-batch——计算梯度——更新参数——重复步骤
import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np import matplotlib.pyplot as plt from dataset.mnist import load_mnist from two_layer_net import TwoLayerNet # 读入数据 (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True) network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) iters_num = 10000 # 适当设定循环的次数 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] train_acc_list = [] test_acc_list = [] iter_per_epoch = max(train_size / batch_size, 1) for i in range(iters_num): batch_mask = np.random.choice(train_size, batch_size) x_batch = x_train[batch_mask] t_batch = t_train[batch_mask] # 计算梯度 #grad = network.numerical_gradient(x_batch, t_batch) grad = network.gradient(x_batch, t_batch) # 更新参数 for key in ('W1', 'b1', 'W2', 'b2'): network.params[key] -= learning_rate * grad[key] loss = network.loss(x_batch, t_batch) train_loss_list.append(loss) if i % iter_per_epoch == 0: train_acc = network.accuracy(x_train, t_train) test_acc = network.accuracy(x_test, t_test) train_acc_list.append(train_acc) test_acc_list.append(test_acc) print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc)) # 绘制图形 markers = {'train': 'o', 'test': 's'} x = np.arange(len(train_acc_list)) plt.plot(x, train_acc_list, label='train acc') plt.plot(x, test_acc_list, label='test acc', linestyle='--') plt.xlabel("epochs") plt.ylabel("accuracy") plt.ylim(0, 1.0) plt.legend(loc='lower right') plt.show()
第六章:与学习相关的技巧
权重参数的更新 训练神经网络的目的是找到使损失函数的值尽可能小的参数。寻找最优参数的过程称为最优化(optimization)。因为参数空间非常复杂,所以无法通过数学式一下子求出最小值。在前几章中,我们曾将参数的梯度(导数)作为寻找最优参数的线索。下面将介绍SGD、Monmentum、AdaGrad和Adam四种常见的最优化方法。
1 SGD
使用参数的梯度,沿着梯度方向更新参数,重复该步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称SGD。
计算公式
所存在的问题:
在解决某些问题中缺乏效率(特别是梯度变化不明显的问题)
学习率的选取要求较高
2 Momentum:
Momentum是“动量”的意思,和物理相关。 这里新出现的变量v,对应物理上的速度。式子表示物体在梯度方向上受力,在力的作用下速度增加,类似于小球在地面上滚动。式中的αv,在物体不受任何力时,承担使物体逐渐减速的任务(α设定小于1大于0的值),对应物理上的摩擦力或空气阻力。
3AdaGrad
在神经网络的训练中,过小的学习率会导致训练时间长,过大的学习率会导致网络不能收敛、在最优值附近徘徊。实际上,一开始“多” 学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。AdaGrad会为参数的每个元素适当调整学习率,同时进行训练。
这里出现的新变量h,保存了以前的所有梯度值的平方和。在更新W时学习率η除以h的开根号,意味着参数的元素中变动较大的元素的学习率将变小。(这里是相对变化,该方法的学习率是始终减小的。) 由于y 轴方向上的梯度较大,因此刚开始变动较大,但是后面会根据这个较大的变动按比例进行调整,减小更新的步伐。因此,y 轴方向上的更新程度被减弱,“之”字形的变动程度有所衰减。
4Adam
Adam方法的基本思路更像是融合Momentum和AdaGrad二者的优点,此外还进行了超参数的“偏置校正”
5权重参数的初始值
权重初始化的目的是防止神经网络的正向传播过程中层激活函数的输出损失梯度出现爆炸或消失。如果发生任何一种情况,损失梯度太大或者太小,都无法有效向后反传,即便可以反向传播,神经网络也需要花更长的时间来达到收敛。 为了提高模型的泛化能力,我们采用一个技巧是权值衰减(weightdecay),即以减小权重参数的值为目的进行学习的方法。 但初始值不可以设为0,因为那样由于参数相同以及输出值都一样,不同的节点根本无法学习到不同的特征,这样就失去了网络学习特征的意义了。 严格地说,初始值设成一样的值的时候都会出现这种问题,所以必须随机生成初始值。
Xavier初始值的特点是:与前一层有n个节点连接时,初始值使用标准差为(1/n)的开根号的分布。使用Xavier初始值后的结果如上图所示。从这个结果可知,越是后面的层,图像变得越歪斜(tanh函数作为激活函数的时候能改善),但是呈现了比之前更有广度的分布。
sigmoid当数值趋近于0,1其会出现梯度消失,所以建议使用Relu
当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,也就是KaimingHe等人推荐的初始值,也称为“He初始值”。He初始值的特点是:与前一层有n个节点连接时,初始值使用标准差为(2/n)的开根号的分布。
6Batch Normalization
因为在传统神经网络中权值参数的初始值对模型训练的最终结果影响非常大·,并且确定参数的合适值也比较麻烦,便提出Batch Normalization,
Batch Normalization:
可以使学习快速进行(可以增大学习率)。
•
不那么依赖初始值(对于初始值不用那么神经质)。
•
抑制过拟合(降低Dropout等的必要性)。
Batch Norm的思路是调整各层的激活值分布使其拥有适当
的广度。为此,要向神经网络中插入对数据分布进行正规化的层
Batch Norm,顾名思义,以进行学习时的mini-batch为单位,按mini
batch进行正规化。具体而言,就是进行使数据分布的均值为0、方差为1的
正规化。用数学式表示的话,如下所示。
式1是对单个数据进行正规化。
式2是通过对一系列数据进行缩放,从而使数据的均值为0,方差为1.
特别注意由于Batch Normalization是对数据的正规化调整,要注意其使用频率,否则会降低学习效率。
7正则化
过拟合指的是只能拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。机器学习的目标是提高泛化能力,即便是没有包含在训练数据里的未观测数据,也希望模型可以进行正确的识别。
发生原因:
模型拥有大量参数、表现力强。
训练数据少。
L1正则化
L1正则化,又称Lasso Regression,是指权值向量w中各个元素的绝对值之和。
当我们在原始损失函数后添加L1正则化项时,相当于对损失函数做了一个约束。
L2正则化
L2正则化是指权值向量中各个元素的平方和然后再求平方根,对参数进行二次约束,参数w变小,但不为零,不会形成稀疏解 。它会使优化求解稳定快速,使权重平滑。所以L2适用于特征之间没有关联的情况。
Dropout
如果网络的模型变得很复杂,只用权值衰减就难以应对了在这种情况下,我们经常会使用Dropout 方法。
Dropout是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递,
def __init__(self, dropout_ratio=0.5): self.dropout_ratio = dropout_ratio self.mask = None def forward(self, x, train_flg=True): if train_flg: self.mask = np.random.rand(*x.shape) > self.dropout_ratio return x * self.mask else: return x * (1.0 - self.dropout_ratio) def backward(self, dout): return dout * self.mask
超参数
超参数是指,比如各层的神经元数量、batch大小、参数更新时的学习率或权值衰减等。如果这些超参数没有设置合适的值,模型的性能就会很差。
调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据(validation data)。
超参数的最优化:
步骤0
设定超参数的范围。
步骤1
从设定的超参数范围中随机采样。
步骤2
使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精
度(但是要将epoch设置得很小)。
步骤3
重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参
数的范围。
第七章:卷积神经网络
卷积神经网络(Convolutional Neural Network,CNN)被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以CNN为基础。
卷积层
CNN 中,有时将卷积层的输入输出数据称为特征图(feature map)。其中,卷积层的输入数据称为输入特征图(input feature map),输出数据称为输出特征(output feature map)。
为了减少数据的丢失通常还要设置偏置,这个值会被加到滤波器的·所有元素上
填充
由于卷积层的运动方式导致中间元素的使用频率要高于边缘的元素,为了使每个元素的使用频率相同,并且不改变最后的特征值,通常会人为的在输入数据外围填充0.
步幅
应用滤波器的位置间隔称为步幅。
特征图大小的计算
这里,假设输入大小为(H, W),滤波器大小为(FH, FW),输出大小为(OH, OW),填充为P,步幅为S。
3维数据的卷积运算
图像是3维数据,除了高、长方向之外,还需要处理通道方向。(例如图片的三色,其通道在三维中也可考虑成立方体的厚度。)
在3维数据的卷积运算中,输入数据和滤波器的通道数要设为相同的值。
批处理
神经网络的处理中进行的将输入数据打包。
通过批处理,能够实现处理的高效化和学习时对mini-batch的对应
处理改成对N个数据进行批处理,数据作为4维的形状在各层间传递,对这N个数据进行了卷积运算。
池化层
池化是缩小高、长方向上的空间的运算。
因为最大数据对特征图的影响最大,所以可以进行Max池化操作。
(除了Max池化之外,还有Average池化,相对于Max池化是从目标区域中取出最大值,Average池化则是计算目标区域的平均值。)
池化层的特征
没有要学习的参数
池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
通道数不发生变化
经过池化运算,输入数据和输出数据的通道数不会发生变化。如图7-15所示,计算是按通道独立进行的。
卷积层和池化层的实现
im2col会把输入数据展开以适合滤波器.汇总成一个大的矩阵进行计算,对计算机的计算颇有益处。
im2col (input_data, filter_h, filter_w, stride=1, pad=0)
• input_data―由(数据量,通道,高,长)的4维数组构成的输入数据
• filter_h―滤波器的高
• filter_w―滤波器的长
• stride―步幅
• pad―填充
卷积层的实现
import sys, os sys.path.append(os.pardir) from common.util import im2col x1 = np.random.rand(1, 3, 7, 7) col1 = im2col(x1, 5, 5, stride=1, pad=0) print(col1.shape) # (9, 75) x2 = np.random.rand(10, 3, 7, 7) # 10个数据 col2 = im2col(x2, 5, 5, stride=1, pad=0) print(col2.shape) # (90, 75)
class Convolution: def __init__(self, W, b, stride=1, pad=0): self.W = W self.b = b self.stride = stride self.pad = pad def forward(self, x): FN, C, FH, FW = self.W.shape N, C, H, W = x.shape out_h = int(1 + (H + 2*self.pad - FH) / self.stride) out_w = int(1 + (W + 2*self.pad - FW) / self.stride) col = im2col(x, FH, FW, self.stride, self.pad) col_W = self.W.reshape(FN, -1).T # 滤波器的展开 out = np.dot(col, col_W) + self.b out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2) return out
池化层的实现
class Pooling: def __init__(self, pool_h, pool_w, stride=1, pad=0): self.pool_h = pool_h self.pool_w = pool_w self.stride = stride self.pad = pad def forward(self, x): N, C, H, W = x.shape out_h = int(1 + (H - self.pool_h) / self.stride) out_w = int(1 + (W - self.pool_w) / self.stride) # 展开(1) col = im2col(x, self.pool_h, self.pool_w,self.stride,self.pad) col = col.reshape(-1, self.pool_h*self.pool_w) # 最大值(2) out = np.max(col, axis=1) # 转换(3) out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2) return out
CNN的实现
第八章:深度学习
加深网络
加深层的好处:
1 是可以减少网络的参数数量。
图1所需参数个数 5x5
图2所需参数个数 3x3x2
2 使学习更加高效。
通过加深层,可以分层次地传递信息
深度学习小历史
VGG是由卷积层和池化层构成的基础的CNN。
GoogLeNet的特征是,网络不仅在纵向上有深度,在横向上也有深度(广度)。在GoogLeNet中,很多地方都使用了大小为1 × 1的滤波器的卷积层。这个1 × 1的卷积运算通过在通道方向上减小大小,有助于减少参数和实现高速化处理
ResNet导入“快捷结构”,
快捷结构横跨(跳过)了输入数据的卷积层,将输入x合计到输出。通过快捷结构,反向传播时信号可以无衰减地传递,使模型可以高效学习。
ResNet通过以2个卷积层为间隔跳跃式地连接来加深层。
深度学习高速化
基于GPU的高速化
与使用单个CPU相比,使用GPU进行深度学习的运算可以达到惊人的高速化。
分布式学习
为了进一步提高深度学习所需的计算的速度,可以考虑在多个GPU或者多台机器上进行分布式计算。
运算精度的位数缩减
如今出现了16位的半精度浮点数,可以在顺利学习的基础上缩短学习时间。
(无监督学习:没有监督数据,只给出大量图像)
Deep Q-Network(强化学习)
让计算机也在摸索试验的过程中自主学习,这称为强化学习(reinforcement learning)
强化学习的基本框架是,代理(Agent)根据环境选择行动,然后通过这个行动改变环境。根据环境的变化,代理获得某种报酬。强化学习的目的是决定代理的行动方针,以获得更好的报酬
Q学习的细节,不过在Q学习中,为了确定最合适的行动,需要确定一个被称为最优行动价值函数的函数。为了近似这个函数,DQN使用了深度学习(CNN)
(AlphaGo技术的内部也用了深度学习和强化学习。)
深度学习2.0
Pytorch导入模块:
#@save import numpy as np import torch import torchvision from PIL import Image from torch import nn from torch.nn import functional as F from torch.utils import data from torchvision import transforms
第一章:引言
机器学习的关键组件
-
可以用来学习的数据(data);
-
如何转换数据的模型(model);
-
一个目标函数(objective function),用来量化模型的有效性;
-
调整模型参数以优化目标函数的算法(algorithm)。
Data
数据由样本组成,每一个样本通常有一系列特征,机器学习模型会根据这些属性进行预测。
当每个样本的特征类别数量都是相同的时候,其特征向量是固定长度的,这个长度被称为数据的维数(dimensionality)。机器便可以同时处理这些样本。但在实际过程中获得这样样本的难度很大。与传统机器学习方法相比,深度学习的一个主要优势是可以处理不同长度的数据。
modle
大多数机器学习会涉及到数据的转换,深度学习与经典方法的区别主要在于:前者关注的功能强大的模型,这些模型由神经网络错综复杂的交织在一起,包含层层数据转换,因此被称为深度学习
目标函数
在机器学习中,我们需要定义模型的优劣程度的度量,这个度量在大多数情况是“可优化”的,这被称之为目标函数。
可用数据集通常可以分成两部分:训练数据集用于拟合模型参数,测试数据集用于评估拟合的模型。
当一个模型在训练集上表现良好,但不能推广到测试集时,这个模型被称为过拟合(overfitting)的。
优化算法
深度学习中,大多流行的优化算法通常基于一种基本方法–梯度下降(gradient descent)。
各种机器学习问题
监督学习
监督学习常用作在给定标准的情况下预测标签。
监督学习的学习过程一般可以分为三大步骤:
-
从已知大量数据样本中随机选取一个子集,为每个样本获取真实标签。有时,这些样本已有标签(例如,患者是否在下一年内康复?);有时,这些样本可能需要被人工标记(例如,图像分类)。这些输入和相应的标签一起构成了训练数据集;
-
选择有监督的学习算法,它将训练数据集作为输入,并输出一个“已完成学习的模型”;
-
将之前没有见过的样本特征放到这个“已完成学习的模型”中,使用模型的输出作为相应标签的预测。
回归
当标签取任意数值时,我们称之为回归问题,此时的目标是生成一个模型,使它的预测非常接近实际标签值。
判断回归问题的一个很好的经验法则是,任何有关“有多少”的问题很可能就是回归问题。
分类
分类问题希望模型能够预测样本属于哪个类别
当有两个以上的类别时,我们把这个问题称为多项分类(multiclass classification)问题。与解决回归问题不同,分类问题的常见损失函数被称为交叉熵(cross-entropy)。
层次分类,宁可信其有不可信其无,就是一种不是所有标签都是均等的层次分类。
搜索
在信息检索领域,我们希望对一组项目进行排序。 搜索结果的排序是非常重要的。
推荐系统
另一类与搜索和排名相关的问题是推荐系统(recommender system),它的目标是向特定用户进行“个性化”推荐。
序列学习
序列学习需要摄取输入序列或预测输出序列,或两者兼而有之。具体来说,输入和输出都是可变长度的序列,例如机器翻译和从语音中转录文本。
无监督学习
数据中不含有“目标”的机器学习问题通常被为无监督学习(unsupervised learning)。
聚类学习
主成分分析问题
因果关系和概率图模型问题
生成对抗性网络
与环境互动
到目前为止,不管是监督学习还是无监督学习,我们都会预先获取大量数据,然后启动模型,不再与环境交互。这里所有学习都是在算法与环境断开后进行的,被称为离线学习(offline learning)。
好处:
可以孤立的进行识别,减少外部因素的影响。
缺点:
能解决的问题相当有限。
强化学习
在强化学习问题中,智能体(agent)在一系列的时间步骤上与环境交互。 在每个特定时间点,智能体从环境接收一些观察(observation),并且必须选择一个动作(action),然后通过某种机制(有时称为执行器)将其传输回环境,最后智能体从环境中获得奖励(reward)。
强化学习的困难点:
强化学习者必须处理学分分配(credit assignment)问题:决定哪些行为是值得奖励的,哪些行为是需要惩罚的。强化学习可能还必须处理部分可观测性问题。还可能要判断是利用当前测略,还是探索更好的策略,
当环境可被完全观察到时,强化学习问题被称为马尔可夫决策过程(markov decision process)。当状态不依赖于之前的操作时,我们称该问题为上下文赌博机(contextual bandit problem)。 当没有状态,只有一组最初未知回报的可用动作时,这个问题就是经典的多臂赌博机(multi-armed bandit problem)。
第二章:预备知识
X=torch.arange(12,dtype=torch.float32).reshape((3,4)) Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)#张量连结,0代表竖向链接,列不变;1为横向链接
(tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 2., 1., 4., 3.], [ 1., 2., 3., 4.], [ 4., 3., 2., 1.]]), tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.], [ 4., 5., 6., 7., 1., 2., 3., 4.], [ 8., 9., 10., 11., 4., 3., 2., 1.]]))
就像在任何其他Python数组中一样,张量中的元素可以通过索引访问。 与任何Python数组一样:第一个元素的索引是0,最后一个元素索引是-1; 可以指定范围以包含第一个元素和最后一个之前的元素。如下所示,我们可以用[-1]
选择最后一个元素,可以用[1:3]
选择第二个和第三个元素:
X[-1], X[1:3]
(tensor([ 8., 9., 10., 11.]), tensor([[ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]))
X[1, 2] = 9 X
tensor([[ 0., 1., 2., 3.], [ 4., 5., 9., 7.], [ 8., 9., 10., 11.]])
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。 例如,[0:2, :]
访问第1行和第2行,其中“:”代表沿轴1(列)的所有元素。 虽然我们讨论的是矩阵的索引,但这也适用于向量和超过2个维度的张量。
X[0:2, :] = 12 X
tensor([[12., 12., 12., 12.], [12., 12., 12., 12.], [ 8., 9., 10., 11.]])
广播机制规则:
-
如果遵守以下规则,则两个tensor是“可广播的”:
-
每个tensor至少有一个维度;
-
遍历tensor所有维度时,从末尾随开始遍历,两个tensor存在下列情况:
-
tensor维度相等。
-
tensor维度不等且其中一个维度为1。
-
tensor维度不等且其中一个维度不存在。
-
-
-
如果两个tensor是“可广播的”,则计算过程遵循下列规则:
-
如果两个tensor的维度不同,则在维度较小的tensor的前面增加维度,使它们维度相等。
-
对于每个维度,计算结果的维度值取两个tensor中较大的那个值。
-
两个tensor扩展维度的过程是将数值进行复制。
-
节省内存
before = id(Y) Y = Y + X id(Y) == before
False
显而易见Y被重新分配了内存空间
这可能是不可取的,原因有两个:
-
首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
-
如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
幸运的是,执行原地操作非常简单。 我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如Y[:] = <expression>
。 为了说明这一点,我们首先创建一个新的矩阵Z
,其形状与另一个Y
相同, 使用zeros_like
来分配一个全0的块。
Z = torch.zeros_like(Y) print('id(Z):', id(Z)) Z[:] = X + Y print('id(Z):', id(Z))
id(Z): 140327634811696 id(Z): 140327634811696
转化为其他python对象
将深度学习框架定义的张量转换为NumPy张量(ndarray
)很容易,反之也同样容易。 torch张量和numpy数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
A = X.numpy() B = torch.tensor(A) type(A), type(B)
(numpy.ndarray, torch.Tensor)
要将大小为1的张量转换为Python标量,我们可以调用item
函数或Python的内置函数。
a = torch.tensor([3.5]) a, a.item(), float(a), int(a)
(tensor([3.5000]), 3.5, 3.5, 3)
数据预处理
在Python中常用的数据分析工具中,我们通常使用pandas
软件包。
import os os.makedirs(os.path.join('..', 'data'), exist_ok=True) data_file = os.path.join('..', 'data', 'house_tiny.csv') with open(data_file, 'w') as f: f.write('NumRooms,Alley,Price\n') # 列名 f.write('NA,Pave,127500\n') # 每行表示一个数据样本 f.write('2,NA,106000\n') f.write('4,NA,178100\n') f.write('NA,NA,140000\n')
# 如果没有安装pandas,只需取消对以下行的注释来安装pandas # !pip install pandas import pandas as pd data = pd.read_csv(data_file) print(data)
NumRooms Alley Price 0 NaN Pave 127500 1 2.0 NaN 106000 2 4.0 NaN 178100 3 NaN NaN 140000
我们首先创建一个人工数据集,并存储在CSV(逗号分隔值)文件 ../data/house_tiny.csv
中。 以其他格式存储的数据也可以通过类似的方式进行处理。 下面我们将数据集按行写入CSV文件中。要从创建的CSV文件中加载原始数据集,我们导入pandas
包并调用read_csv
函数。该数据集有四行三列。其中每行描述了房间数量(“NumRooms”)、巷子类型(“Alley”)和房屋价格(“Price”)。(csv是一个以逗号为分隔符的纯文本文件)
“NaN”项代表缺失值。 为了处理缺失的数据,典型的方法包括插值法和删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] inputs = inputs.fillna(inputs.mean()) print(inputs)
iloc
,我们将data
分成inputs
和outputs
, 其中前者为data
的前两列,而后者为data
的最后一列。并用同一列均值代替NaN。
NumRooms Alley 0 3.0 Pave 1 2.0 NaN 2 4.0 NaN 3 3.0 NaN
inputs = pd.get_dummies(inputs, dummy_na=True) print(inputs)
NumRooms Alley_Pave Alley_nan 0 3.0 1 0 1 2.0 0 1 2 4.0 0 1 3 3.0 0 1
Alley列只接受两种类型的类别值“Pave”和“NaN”,pandas
可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。
转换张量格式
当数据采用张量格式后,可以通过在 2.1节中引入的那些张量函数来进一步操作。
import torch X = torch.tensor(inputs.to_numpy(dtype=float)) y = torch.tensor(outputs.to_numpy(dtype=float)) X, y
(tensor([[3., 1., 0.], [2., 0., 1.], [4., 0., 1.], [3., 0., 1.]], dtype=torch.float64), tensor([127500., 106000., 178100., 140000.], dtype=torch.float64))
张量算法的基本性质
A=torch.arange(20,dtype=torch.float32).reshape(5, 4) B = A.clone() # 通过分配新内存,将A的一个副本分配给B A, A + B
(tensor([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.], [16., 17., 18., 19.]]), tensor([[ 0., 2., 4., 6.], [ 8., 10., 12., 14.], [16., 18., 20., 22.], [24., 26., 28., 30.], [32., 34., 36., 38.]]))
A * B
tensor([[ 0., 1., 4., 9.], [ 16., 25., 36., 49.], [ 64., 81., 100., 121.], [144., 169., 196., 225.], [256., 289., 324., 361.]])
降维
x = torch.arange(4, dtype=torch.float32) x, x.sum()
(tensor([0., 1., 2., 3.]), tensor(6.))
调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。
求和所有行,用函数时指定axis=0
。 由于输入矩阵沿0轴降维以生成输出向量
A_sum_axis0 = A.sum(axis=0) A_sum_axis0, A_sum_axis0.shape
(tensor([40., 45., 50., 55.]), torch.Size([4]))
A_sum_axis1 = A.sum(axis=1) A_sum_axis1, A_sum_axis1.shape
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))
指定axis=1
将通过汇总所有列的元素降维(轴1)。因此,输入轴1的维数在输出形状中消失
沿着行和列对矩阵求和,等价于对矩阵的所有元素进行求和。
A.sum(axis=[0, 1]) # 结果和A.sum()相同
A.mean(axis=0), A.sum(axis=0) / A.shape[0]
(tensor([ 8., 9., 10., 11.]), tensor([ 8., 9., 10., 11.]))
sum_A = A.sum(axis=1, keepdims=True) sum_A
tensor([[ 6.], [22.], [38.], [54.], [70.]])
维度不变
A / sum_A
tensor([[0.0000, 0.1667, 0.3333, 0.5000], [0.1818, 0.2273, 0.2727, 0.3182], [0.2105, 0.2368, 0.2632, 0.2895], [0.2222, 0.2407, 0.2593, 0.2778], [0.2286, 0.2429, 0.2571, 0.2714]])
如果我们想沿某个轴计算A
元素的累积总和, 比如axis=0
(按行计算),可以调用cumsum
函数。 此函数不会沿任何轴降低输入张量的维度。
A.cumsum(axis=0)
tensor([[ 0., 1., 2., 3.], [ 4., 6., 8., 10.], [12., 15., 18., 21.], [24., 28., 32., 36.], [40., 45., 50., 55.]])
点积
y = torch.ones(4, dtype = torch.float32) x, y, torch.dot(x, y)
(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
torch.sum(x * y)
tensor(6.)
矩阵-向量积
在代码中使用张量表示矩阵-向量积,我们使用mv
函数。 当我们为矩阵A
和向量x
调用torch.mv(A, x)
时,会执行矩阵-向量积。 注意,A
的列维数(沿轴1的长度)必须与x
的维数(其长度)相同。
A.shape, x.shape, torch.mv(A, x)
(torch.Size([5, 4]), torch.Size([4]), tensor([ 14., 38., 62., 86., 110.]))
B = torch.ones(4, 3) torch.mm(A, B)
tensor([[ 6., 6., 6.], [22., 22., 22.], [38., 38., 38.], [54., 54., 54.], [70., 70., 70.]])
范数
u = torch.tensor([3.0, -4.0]) torch.norm(u)
tensor(5.)
torch.abs(u).sum()
tensor(7.)
torch.norm(torch.ones((4, 9)))
tensor(6.)
自动微分
x.requires_grad_(True) # 等价x=torch.arange(4.0,requires_grad=True)
参数为true ,表示追踪x的操作,.backward后自动存储x的梯度,但随着操作的增加梯度也会累积。
第三章
优化器(补充)
RMSprop
这是 种非常有效的自适应学习率的改进方法,它的公式是:
a这是一个衰减率,RMSprop 不再会将前面所有的梯度平方
求和,而是通过 个衰减率将其变小,使用了 种滑动平均的方式,越 前面的梯度对自适应的学习率影响越小,这样就能更加有效地避免 Adagra 学习率一直递减太多问题,能够更快地收敛
Adam
Adam 算法会使用一个动量变量 v 和一个 RMSProp 中的梯度元素平方的移动指数加权平均 s,首先将他们全部初始化为 0,然后在每次迭代中,计算他们的移动加权平均进行更新
def adam(parameters, vs, sqrs, lr, t, beta1=0.9, beta2=0.999): eps = 1e-8 for param, v, sqr in zip(parameters, vs, sqrs): v[:] = beta1 * v + (1 - beta1) * param.grad.data sqr[:] = beta2 * sqr + (1 - beta2) * param.grad.data ** 2 v_hat = v / (1 - beta1 ** t) s_hat = sqr / (1 - beta2 ** t) param.data = param.data - lr * v_hat / torch.sqrt(s_hat + eps)
为了减轻 v 和 s 被初始化为 0 的初期对计算指数加权移动平均的影响,每次 v 和 s 都做下面的修正
算法作者建议B1=0.9,B2=0.999
3.7 处理数据和训练模型的技巧
1中心化
数据预处理中 个最常见的处理办法就是每个特征维度都减去相应的均值实现中心化 ,这样可以使得数据变成 均值
-
标准化
在使得数据都变成 均值之后,还需要使用标准化的做法让数据不同的特征维度都有着相同的规模 有两种常用的方法 是除以标准 差,这样可以使得新数据的分布接近标准高斯分布;还有 种做法就是让每个特征维度的最大值和最小值按比例放到一 之间
权重初始化
随机初始化
并不是越小的随机化产生的结果越好,会导致其梯度很小,这会极大地减弱梯度流的信号,成为神 网络训练中的一个隐患
稀疏初始化
将权重全部初始0 ,然后为了打破对称性在里面随机挑选 些参数附上一些随机值,但是实际中使用较少。
初始化偏置
对于偏置(bias ),通常是初始化为 ,因为权重已经打破了对称性,所以使用初始化是最简单的
批标准化( Batch Normalization )
实际中批标准化已经变成了神经网络中的 个标准技术,特别是在卷积神经网络中,它对于很坏的初始化有很强的鲁棒性,同时还可以加快网络的收敛速度
第四章卷积神经网络
卷积神经网络的原理和结构
1局限性
一张需要检测的图片特征通常由图片的一片区域决定。
2相同性
不同图片若具有相同特征,这些特征会出现在图片不同的位置,但是特征检测所做的操作几乎一样
3不变性
卷积神经网络中的主要层结构有 个:卷积层、池化层和全连接层
卷积层
步长的限制
输入尺寸
10 的时候,如果不使用零填充,即 = ,滤波器尺寸 = ,这样步长 8=2 就行不通,因为旦孚土豆+ 1=4.5 ,结果不是一个整数,
参数共享
在卷积层使用参数共享可以有效地减少参数的个数,这样之所以能够行得通,是。因为之前介绍的特征的相同性
最后总结 下卷积层的 些性质
( 1 )输入数据体的尺寸是 W x H x D
( 2) 4个超参数 滤波器数量 K,滤波器空间尺寸 F,滑动步长 S,零填充的数量P
( )输出数据体的尺寸为 W2 x H2 x D2 ,W2=(W1-F+2P)/S+1
D2 = K
(4 )由于参数共享,每个滤波器包含的权重数目为 F X F XD1,
卷积层共有F X F X D1 X K 个权重和 个偏置
( 5 )在输出体数据中,第 D个深度切片(空间尺寸是 W2 H2 ),用第 D个滤波器和输入数据进行有效卷积运算的结果,再加上第 D个偏置
对于卷积神经网络的一些超参数,常见的设置是 F=3,S=l,P=l
池化层
通常会在卷积层之间周期性插入一个池 层,其作用是逐渐降低数据体的空间尺寸,这样就能够减少 络中参数的数量,减少计算资源耗费,同时也能够有效地控制过拟合
全连接层
在这个过程中为了防止过拟合会引人 Dropout 最近的研究表明,在进入全连接层之前,使用全局平均池化能够有效地降低过拟合
卷积神经网络的基本形式
小滤波器的有效性
1减少参数
2更好提取特征
网络的尺寸
对于卷积神经网络的尺寸设计,没有严格的数学证明,这是根据经验制定出来的规则
( 1 )输入层:一般而言,输入层的大小应该能够被 整除很多次,常用的数字包括32, 64, 96 , 224
(2)卷积层:卷积层应该尽可能使用小尺寸的滤波器,比如3x3 或者5x5 ,滑动步长取1 还有 点就是需要对输入数据体进行零填充,这样可以有效地保证卷积层不会改变输入数据体的空间尺寸
( 3 )池化层 池化层负责对输入的数据空间维度进行下采样,常用的设置使用2x2的感受野做最大值池化,滑动步长取 2