深度学习基础技术分析2:神经网络(含代码分析)

1. 模型图示

感知机仅能解决线性的问题,这个局限性使得其无法适应多数的实际应用。因此人们提出了神经网络。如图2.1所示。
在这里插入图片描述图2.1 神经网络

2. 相关技术

技术2.1 隐藏层

从结构上看,神经网络有多层,比感知机复杂。除了输入层、输出层,还增加了1个或多个隐藏层。输入层与输出层节点的个数由具体应用所确定,这个和感知机没有区别。隐藏层的层数、每层节点个数(这两个可称为神经网络的参数),则对神经网络的效果起到很大作用。
对于神经网络的新手玩家而言,针对具体应用,调整这些参数既可能是有意思的活儿,也可能是枯燥的活儿。
仅仅增加隐藏层能否增强网络的能力?很遗憾,答案是否定的。用简单的推导可以证明,增加隐藏层(可以是多层)并未改变线性的本质,和单层结构的作用完全相同。

技术2.2 激活函数

激函数才是使得神经网络变得无所不能的技术。如图2.1所示,第1层的输出是 z = ( z 1 , z 2 , z 3 ) \mathbf{z} = (z_1, z_2, z_3) z=(z1,z2,z3),它们经过变换,成为 a = ( a 1 , a 2 , a 3 ) \mathbf{a} = (a_1, a_2, a_3) a=(a1,a2,a3)。干这个事儿的就是激活函数。

sigmoid

f ( x ) = 1 1 + e − x . (1) f(x) = \frac{1}{1 + e^{-x}}. \tag{1} f(x)=1+ex1.(1)
它有良好的数学性质:

  1. 把数据从 ( − ∞ , + ∞ ) (-\infty, +\infty) (,+)映射到 ( 0 , + 1 ) (0, +1) (0,+1)
  2. 从0往两边,变化速度很快,然后平稳;
  3. f ′ ( x ) = f ( x ) ( 1 − f ( x ) ) f'(x) = f(x) (1 - f(x)) f(x)=f(x)(1f(x))
    显然,经过激活函数后,神经网络所表达的变换肯定不是线性的了。
    在这里插入图片描述图2.2 sigmoid函数

tanh

f ( x ) = e x − e − x e x + e − x . (2) f(x) = \frac{e^x - e^{-x} }{e^x + e^{-x}}. \tag{2} f(x)=ex+exexex.(2)
它也有良好的数学性质:

  1. 把数据从 ( − ∞ , + ∞ ) (-\infty, +\infty) (,+) 映射到 ( − 1 , + 1 ) (-1, +1) (1,+1)
  2. 从0往两边,变化速度很快,然后平稳;
  3. f ′ ( x ) = 1 − f 2 ( x ) f'(x) = 1 - f^2(x) f(x)=1f2(x)
    图2.3 tanh
    图2.3 tanh函数

技术2.3 多层反馈

一般将神经网络称为BP神经网络,BP就是Backpropagation。从输出层可以向输入层反馈,逐步调整每层的权重。越靠近输入层,梯度越小,权重改变越小,因此一般5层就够了。
权值更新的具体步骤如下:
Step 1. 根据激活函数调整loss. 使用 L 1 L_1 L1 损失时, 令输出层的节点值为 y ^ i \hat{y}_i y^i, 标准的输出为 y i y_i yi之间, 激活函数为 Sigmoid. 则输出节点的误差为:
f ′ ( y ^ i ) ( y ^ i − y i ) = y ^ i ( 1 − y ^ i ) ( y ^ i − y i ) . (3) f'(\hat{y}_i)(\hat{y}_i - y_i) = \hat{y}_i (1 - \hat{y}_i) (\hat{y}_i - y_i). \tag{3} f(y^i)(y^iyi)=y^i(1y^i)(y^iyi).(3)
Step 2. 将这些 loss 反向传递, 由前一层的各条边承担. 令 n i n_i ni 表示第 i i i层的节点数, d l i d_{li} dli表示第 l l l 层的第 i i i 个节点的当前数据, e l i e_{li} eli 表示第 l l l 层的第 i i i 个节点的当前误差, w l i j w_{lij} wlij 表示第 l l l 层第 i i i 个节点与第 l + 1 l + 1 l+1 层第 j j j 个节点之间边的权重. 则边的权重更新为
w l i j = w l i j + δ e l + 1 , j d l i , (4) w_{lij} = w_{lij} + \delta e_{l + 1, j} d_{li}\tag{4}, wlij=wlij+δel+1,jdli,(4)
其中 δ \delta δ 为学习速度. 如果还要考虑动量调整, 式(4) 可以加一个分量.
节点的误差更新为
e l i = d l i ( 1 − d l i ) ∑ k = 1 n j + 1 e l + 1 , k w l i k , (5) e_{li} = d_{li} (1 - d_{li}) \sum_{k = 1}^{n_{j + 1}} e_{l + 1, k} w_{lik}\tag{5}, eli=dli(1dli)k=1nj+1el+1,kwlik,(5)
其中,前半部分与激活函数求导有关, 与式(3)同理; 后半部分则与下层各节点上的损失, 以及边的权重有关.

技术2.4 输出层表示

神经网络的输出层是多样的,既可是一个节点,也可以是多个。这使得它可以应对不同的任务。例如,回归时只需要一个节点;分类( c c c 个类别)需要 c c c 个节点,每个节点表示一个 [ 0 , 1 ] [0, 1] [0,1] 区间的实数值,哪个节点的值大,就预测为相应类别;多标签学习( l l l 个标签)需要 l l l 个节点,分别表示属于各个类别的可能性;围棋程序,输出为361个节点,分别表示下一步放在相应位置的胜率。

3. 代码分析

本节给出 java 代码分析,原代码只有70行,点击访问。我加入读数据代码后放到gibhub,点击访问

3.1 模型定义

//层数, 包含输入层与输出层.
int numLayers;
//每层的结点数(本数组长度即为层数), 如 {5, 8, 6, 2} 表示输入层5个节点,
//两个隐藏层分别为8个和6个,输出层为2个(可以做2分类)
int[] layerNumNodes;
//神经网络各层节点(对应的临时值)
//对于前面的例子, layerNodes.length = 4, layerNodes[0].length = 5
public double[][] layerNodes;
// 神经网络各节点误差, 与 layerNodes 维度一致
public double[][] layerNodesErr;
// 网络各边权重. 第 i 层节点 与第 i + 1 层之间有边,所以例中 i = {0,  1,  2}, edgeWeights.length = 3;
// edgeWeights[0].length = 5, edgeWeights[0][0].length = 8
public double[][][] edgeWeights;
// 各边权重变化量
public double[][][] edgeWeightsDelta;
// 动量系数, 模拟惯性
public double mobp;
// 学习系数
public double rate;

从代码看出,网络可以用二维、三维数组表示. 基础的表示使我们能够清晰观察内部机制.

3.2 前馈

public double[] computeOut(double[] paraIn) {
	// 初始化输入层. paraIn为一条输入数据 (一个训练对象)
	for (int i = 0; i < layerNodes[0].length; i++) {
		layerNodes[0][i] = paraIn[i];
	}// Of for i

	// 逐层计算节点值
	for (int l = 1; l < numLayers; l++) {
		for (int j = 0; j < layerNodes[l].length; j++) {
			// 初始化为偏移量, 因为它的输入为 +1
			//  l - 1表示边的层号
			//layerNodes[l - 1].length 表示上一层的节点个数, 由于是偏移量,它在上一层没有节点,是附加的节点, 其取值为1
			// j表示本层当前需要赋值的节点. 
			double z = edgeWeights[l - 1][layerNodes[l - 1].length][j];
			// 计算加权和
			for (int i = 0; i < layerNodes[l - 1].length; i++) {
				// l - 1表示边的层号, i表示上一层节点号, j表示本层节点号
				z += edgeWeights[l - 1][i][j] * layerNodes[l - 1][i];
			}// Of for i

			// Sigmoid激活函数, 代码和公式一样简单.
			layerNodes[l][j] = 1 / (1 + Math.exp(-z));
		}// Of for j
	}// Of for l
	return layerNodes[numLayers - 1];
}// Of computeOut

通过3层循环对 layerNodes 赋值,就完成了1次前馈。

3.3 反向传播

public void updateWeight(double[] paraTarget) {
	// Step 1. 初始化输出层误差
	int l = numLayers - 1;
	for (int j = 0; j < layerNodesErr[l].length; j++) {
		//前面一半是对 sigmoid 求导, 后面一半是误差  (带符号)
		layerNodesErr[l][j] = layerNodes[l][j] * (1 - layerNodes[l][j])
				* (paraTarget[j] - layerNodes[l][j]);
	}// Of for j
	// Step 2. 逐层反馈, l == 0时也需要计算
	while (l > 0) {
		l--;
		// 第l层, 逐个节点计算
		for (int j = 0; j < layerNumNodes[l]; j++) {
			double z = 0.0;
			// 针对下一层的每个节点
			for (int i = 0; i < layerNumNodes[l + 1]; i++) {
				if (l > 0) {
					z += layerNodesErr[l + 1][i] * edgeWeights[l][j][i];
				}// Of if
				// 隐含层动量调整. mobp 表示对上一个对象训练时 delta 的怀念
				// layerNodes[l][j] 是由加权和式子求偏导而得到,表示丢锅的程度
				edgeWeightsDelta[l][j][i] = mobp
						* edgeWeightsDelta[l][j][i] + rate
						* layerNodesErr[l + 1][i] * layerNodes[l][j];
				// 隐含层权重调整
				edgeWeights[l][j][i] += edgeWeightsDelta[l][j][i];
				if (j == layerNumNodes[l] - 1) {
					// 截距动量调整, 对平常节点同理, 但需要单独计算
					edgeWeightsDelta[l][j + 1][i] = mobp
							* edgeWeightsDelta[l][j + 1][i] + rate
							* layerNodesErr[l + 1][i];
					// 截距权重调整
					edgeWeights[l][j + 1][i] += edgeWeightsDelta[l][j + 1][i];
				}// Of if
			}// Of for i
			// 记录误差. 又要乘以 sigmoid 函数的导数, 为下一次后向传播作准备.
			layerNodesErr[l][j] = layerNodes[l][j] * (1 - layerNodes[l][j])
					* z;
		}// Of for j
	}// Of while
}// Of updateWeight

反向传播的关键点包括:

  1. sigmoid 函数的求导. 如果激活函数换了, 相应代码进行变化即可.
  2. 加权和函数的求导. 估计一般的网络不需要变. 后面的 CNN 之类再说吧.
  3. 动量系数. 这个已经属于高阶功能了.
    感慨一下:基础代码又简单又有效。

4. 小结

  1. 每层都有针对偏移量的 + 1 +1 +1,参见技术1.2.
  2. 在实际应用中,神经网络的层数、每层的节点个数、每个节点使用的激活函数,都对其性能产生重要影响。所谓的“调参师”,就是干这些活儿。
  3. 在本文所述的几个技术上进行改动,都可以做出新的工作。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值