深度学习
神经网络大致结构:卷积层,池化层,affine(dot,sigmoid) ,softmax,loss
激活函数
众所周知,用作激活函数的函数最好具有关于原点对称的性质。tanh函数是关于原点(0, 0)对称的S型曲曲线。
tanh函数
def tanh(x):
a = 1 - np.exp(-2 * x)
b = 1 + np.exp(-2 * x)
return a / b
Sigmoid函数
连续,sigmoid函数是关于(x, y)=(0, 0.5)对称的S型曲曲线
import numpy as np
def sigmoid(x):
return 1/1+np.exp(-x)
阶跃函数
非连续
def step(x):
if x > 0:
return 1
else:
return 0
ReLU函数
修正函数
def ReLu(x):
if x > 0:
return x
else:
return 0
输出层函数
恒等函数
原样输出,不做改变
def equal(x):
return x
Softmax函数
常见于分类问题
多元分类softmax(由于softmax特性,即softmax的所有输出结果相加为1)
def softmax(x):
c = np.max(x)
exp_x = np.exp(x - c)# 防止溢出
return exp_a / np.sum(exp_x)
损失函数
均方误差
均方根差 = sqrt(均方误差)
RMSE = sqrt(MSE)
def mse(y,t):
return 0.5 * np.sum((y-t)**2)
交叉熵误差
常用
def Cee(y,t):
delta = 1e-7
return - np.sum(t * np.log(y + delta))
Mini-batch 交叉熵
def mini_batch_Cee(y,t):
delta = 1e-7
if y.dim == 1:
t = t.reshape(1,t.size)
y = y.reshape(1,y.size)
batch_size = y.shape[0]
return - np.sum(t * np.log(y + delta)) / batch_size
数值微分
需掌握基础知识
导数(存在误差)
数值微分(推荐使用)
def numercial_diff(f,x):
h = 1e-4
return (f(x+h) - f(x-h))/(2 * h)
梯度(数值微分实现,耗时)
全部变量的偏导数汇总而成的向量
def numerial_gradient(f,x):
h = 1e-4
grad = np.zeros_like(x)
for i in range(x.size):
tmp_val = x[i]
# f(x + h)
x[i] = tmp_val + h
fxh1 = f(x)
# f(x - h)
x[i] = tmp_val - h
fxh2 = f(x)
grad[i] = (fxh1 - fxh2) / (2 * h)
x[i] = tmp_val
return grad
梯度下降
随机梯度下降(SGD):对随机选择的梯度进行梯度下降法
lr : 学习率,step:步数
def gradient_descent(f,init_X,lr = 0.01,step = 100):
x = init_x
for i in range(step):
grad = numerial_gradient(f,x)
x-=lr*grad
return x
误差反向传播
理论依据:链式法则
简单层实现
加法节点
将输入信号输出到下一个节点,不做处理
eg:z = x+y,求x,y的偏导
class AddLayer:
def __init__(self):
pass
def forward(self, x, y):
out = x + y
return out
def backward(self, dout):
dx = dout * 1
dy = dout * 1
return dx, dy
乘法节点
将上游的值乘以正向传播时的输入信号的翻转值后传递给下游
eg:z = xy,求x,y的偏导
class MulLayer:
def __init__(self):
self.x: int = 0
self.y: int = 0
def forward(self, x, y):
self.x = x
self.y = y
out = x * y
return out
def backward(self, val):
dx = val * self.y
dy = val * self.x
return dx, dy
激活函数层实现
ReLu
如果正向传播x>0:
反向传播时将上游的值原封不动传递下去
否则:
传递信号在此处停止
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
Sigmoid
/ 节点
Exp(x)的偏导
得到结果
def Sigmoid:
def __init__(self):
self.out = None
def forward(self,x):
self.out = 1 / (1 + np.exp(-x))
return self.out
def backward(self,dout):
dx = dout * (1.0 - self.out) * self.out
return dx
Affine、Softmax层
Affine
神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿
射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。
反向传播(线性代数)
class Affine:
def __init__(self, W, b):
self.W =W
self.b = b
self.x = None
self.original_x_shape = None
# 权重和偏置参数的导数
self.dW = None
self.db = None
def forward(self, x):
# 对应张量
self.original_x_shape = x.shape
x = x.reshape(x.shape[0], -1)
self.x = x
out = np.dot(self.x, self.W) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
dx = dx.reshape(*self.original_x_shape) # 还原输入数据的形状(对应张量)
return dx
SoftmaxWithLoss
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None # softmax的输出
self.t = None # 监督数据
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况
dx = (self.y - self.t) / batch_size
else:
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx = dx / batch_size
return dx
梯度确认
确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致(严格地讲,是
非常相近)的操作称为梯度确认(gradientcheck)
使用两种不同的求梯度方式,将求解后的结果进行相减求平均值
grad_numerical = network.numerical_gradient(x_batch,t_btach)
grad_backprop = network.gradient(x_batch,t_btach)
# 求平均值
for key in grad+numerical.keys():
diff = np.average(np.abs(grad_backprop[key] - grad_numerucak[key]))
print(key + ":" + str(diff))
技巧
参数更新、梯度下降
SGD
随机梯度下降
class SGD:
"""随机梯度下降法(Stochastic Gradient Descent)"""
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
Momentum
Momentum 意味“动量”
与SGD相比,可以更快地朝x轴方向靠近,减弱“之”字形的变动程度。
α 对应 为地面摩擦、空气阻力
v 对应 为速度
class Momentum:
"""Momentum SGD"""
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items(): # val 什么操作?
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
AdaGrad
AdaGrad会为参数的每个元素适当地调整学习率
h 保存了以前的所有梯度值的平方和
一式中的”⊙“ 表示对应矩阵元素的乘法
class AdaGrad:
"""AdaGrad"""
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
Adam
Momentum参照小球在碗中滚动的物理规则进行移动,AdaGrad为参数的每个元素适当地调整更新步伐。将这两个方法融合即为Adam
权重初始值
Sigmod、tanh
使用Xavier的初始值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BE8qEnbo-1622190502314)(D:\notes\images\image-20210526114850691.png)]
ReLU
使用”He初始值“,速度更快
Batch Norm
优点:
- 可以使学习快速进行(可以增大学习率,加速收敛)。
- 不那么依赖初始值(对于初始值不用那么神经质)。
- 抑制:过拟合(降低Dropout等的必要性)。
这里对mini-batch的m个输人数据的集合B= {x1, x2,… xm}求均值Ub和方差σ^2b,然后,对输人数据进行均值为0、方差为1(合适的分布)的正规化。式中的∈是一个微小值(比如,1e-7等), 它是为了防止出现除以0的情况。
接着,Batch Norm层会对正规化后的数据进行缩放和平移的变换,用数学式可以如下表示。
这里,γ和β是参数。开始γ=1,β=0,然后再通过学习调整到合适的值。
归一化层作用在
● 全连接层和卷积层输出,上,激活函数前
●全连接层和卷积层输入.上
对全连接层,作用在特征维
对于卷积层,作用在通道维
过拟合
过拟合指的是只能拟合训练数据,但不能很好地拟合不包含在训练数据中的其他数据的状态。
权值衰减
一种抑制过拟合的方法。该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。
为损失函数加上权重的平方范数(L2范数)
L1范数是各个元素的绝对值之和
L∞范数,相当于各个元素的绝对值中最大的那一个
def l2_penalty(w):
return torch.sum(w.pow(2)) / 2
Dropout
一种在学习的过程中随机删除神经元的方法
class Dropout:
def __init__(self,dropout_ratio=0.5):
self.dropout_tatio = 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 backword(self,out):
return out * self.mask
pytorch 实现
def dropout(x, dropout):
# 边界判断
assert 0 <= dropout <= 1
# 全丢
if dropout == 1:
return torch.zeros_like(x)
# 不丢
elif dropout == 0:
return x
# 随机生成数字取大于dropout的index
mask = (torch.randn(x.shape) > dropout).float()
return mask * x / (1.0 - dropout)
超参数最优化
训练数据集:训练模型参数
验证数据集:选择模型超参数
数据量不够时:k-折交叉验证
将一个数据集分成几段(常见5,10)将其中的一段作为验证数据集,其余为训练数据集,一共训练5/10次(每一次的训练和验证数据集不相同),将验证精度相加求平均
卷积神经网络(CNN)
CNN被用于图像识别、语音识别等各种场合,在图像识别的比赛中,基于深度学习的方法几乎都以CNN为基础。
CNN中新出现了卷积层(Convolution层)和池化层(Pooling层)。
相邻层的所有神经元之间都有连接,这称为全连接(fully-connected)。
CNN的连接顺序为 卷积层-ReLU-Pooling层(有时被省略)
全连接层(Affine)存在问题
丢失空间信息,将(1,28,28)的三维图片拉为784的一维
卷积层
卷积运算
卷积层的处理称为卷积运算
卷积层的输人输出数据称为特征图
卷积层的输人数据称为输入特征图,输出数据称为输出特征图
其中的滤波器也称为卷积核
已有如上的输入数据和滤波器,如下进行卷积运算
将各个位置的滤波器的元素和输人的对应元素相乘,然后再求和。也称乘积累加运算
滤波器是(FN,C, FH, FW) 的4维形状
填充
在进行卷积层的处理之前,有时要向输人数据的周围填人周定的数据(比如0等),这称为填充(padding),是卷积运算中经常会用到的处理。
使用填充主要是为了调整输出的大小。比如,对大小为(4, 4)的输入数据应用(3, 3)的滤波器时,输出大小变为(2,2),相当于输出大小比输入大小缩小了2个元素,这样避免了在多层卷积神经网络中输出层出现1x1的情况。(将导致无法进行卷积神经网络运算)
Vaild填充
不进行填充
eg:
现有nxn的输入,fxf的滤波器,输出大小为(n-f+1)x(n-f+1)
Same填充
输出特征图和输出特征图大小相等
eg:
现有nxn的输入,fxf的滤波器,输出大小为(n+2p-f+1)x(n+2p-f+1)
则p = (f-1)/2
f通常是奇数
步幅
应用滤波器的位置间隔称为步幅( stride)。
增大步幅后,输出大小会变小。而增大填充后,输出大小会变大。
假设输入大小为(H,W),滤波器大小为(FH, FW),输出大小为(OH, OW),填充为P,步幅为S。此时,输出大小可通过式子进行计算
三维数据的卷积运算
通道方向上有多个特征图时,会按通道进行输人数据和滤波器的卷积运算,并将结果相加,从而得到输出。
需要注意的是,在3维数据的卷积运算中,输人数据和滤波器的通道数要设为相同的值。在这个例子中,输人数据和滤波器的通道数一致,均为3。
书写顺序为(channel, height, width),在这个例子中,数据输出是1张特征图。
池化层
池化是缩小高、长方向上的空间的运算,目标区域中取最大值(或者平均值)
2 x 2的窗口的移动间隔为2个元素。另外,-一般来说,池化的窗口大小会和步幅设定成相同的值。比如,3 x 3的窗口的步幅会设为3, 4x 4的窗口的步幅会设为4等。
池化层的特征
没有要学习的参数
池化层和卷积层不同,没有要学习的参数。池化只是从目标区域中取最大值(或者平均值),所以不存在要学习的参数。
通道数不发生变化
经过池化运算,输入数据和输出数据的通道数不会发生变化。
对微小的位置变化具有鲁棒性(健壮)
输入数据发生微小偏差(位置不一致?)时,池化仍会返回相同的结果。因此,池化对输入数据的微小偏差具有鲁棒性。
卷积层和池化层的实现
im2col展开
im2col是一个函数,将输人数据展开以适合滤波器(权重)。
对于输入数据,将应用滤波器的区域(3维方块)横向展开为1列
输入数据处理
假设滤波器(卷积核)的大小为3x3,输入数据为4x4,步幅为1
过滤器
输入数据
过滤器im2col
输入数据im2col
最终得到结果为1x9的二维矩阵,将结果reshape得到4x4矩阵,最终输出特征图
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
"""
Parameters
----------
input_data : 由(数据量, 通道, 高, 长)的4维数组构成的输入数据
filter_h : 滤波器的高
filter_w : 滤波器的长
stride : 步幅
pad : 填充
Returns
-------
col : 2维数组
"""
N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
卷积层实现
与案例转换的形状相反,是输入 * 卷积核,而非卷积核 * 输入
class Convolution:
def __init__(self, W, b, stride=1, pad=0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
# 中间数据(backward时使用)
self.x = None
self.col = None
self.col_W = None
# 权重和偏置参数的梯度
self.dW = None
self.db = None
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
out_w = 1 + int((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)
self.x = x
self.col = col
self.col_W = col_W
return out
def backward(self, dout):
FN, C, FH, FW = self.W.shape
dout = dout.transpose(0,2,3,1).reshape(-1, FN)
self.db = np.sum(dout, axis=0)
self.dW = np.dot(self.col.T, dout)
self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW) # 交换列
dcol = np.dot(dout, self.col_W.T)
dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
return dx
池化层实现
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
self.x = None
self.arg_max = None
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)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
arg_max = np.argmax(col, axis=1)
out = np.max(col, axis=1)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
self.x = x
self.arg_max = arg_max
return out
def backward(self, dout):
dout = dout.transpose(0, 2, 3, 1)
pool_size = self.pool_h * self.pool_w
dmax = np.zeros((dout.size, pool_size))
dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
dmax = dmax.reshape(dout.shape + (pool_size,))
dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
return dx
更新补充
为何选择3x3,及5x5的卷积核?
- 奇数的卷积核可以防止进行卷积运算时,出现不对称运算结果的现象。
- 3x3,5x5的卷积核有中心像素点,更加方便指出卷积核位置。