机器学习笔记(3): 神经网络初步

神经网络应该由若干神经元组成。

前面的每一个神经元都会给到一个参数,将传递的所有参数看作一个向量 \(\vec x\),那么此神经元的净输入为:

\z = x \\omega + b \\

其中 \(\omega\) 称为权重向量

这里认为 \(x\) 是 向量,而 \(\omega\) 是向量。

神经元还有一个激活函数 \(f(\cdot)\):

\a = f(z) \\

称为函数的活性值

一般来说,我们使用 Logistic 函数,即 \(\sigma(x) = \frac 1 {1 + exp(-x)}\) 作为激活函数。

激活函数

激活函数有很多很多种,一般来说要满足以下几点:

  1. 连续且可导的非线性函数。
  2. 函数本身和其导数要尽可能简单。
  3. 值域要在一个合适的区间内

这里列举几种常见的函数。

Sigmoid 型

指一类两端饱和的 S 型曲线。
饱和
\(\lim_\limits{x \to -\infty} f'(x) = 0\) 称为左饱和,\(\lim_\limits{x \to \infty} f'(x) = 0\) 称为右饱和。

同时满足则称为两端饱和。

常见的 Sigmoid 型函数有 LogisticTanh

  • Logistic 函数

\\\sigma(x) = \\frac 1 {1 + \\exp(-x)} \\

其导数:

\\\sigma'(x) = \\frac {\\exp(-x)}{(1 + \\exp(-x))\^2} = \\sigma(x) (1 - \\sigma(x)) \\

  • Tanh 函数

\{\\rm tanh}(x) = \\frac {\\exp(x) - \\exp(-x)}{\\exp(x) + \\exp(-x)} \\

其可以看作缩放平移后的 \(\sigma\),因为:

\{\\rm tanh}(x) = 2 \\sigma(2x) - 1 \\

自然其导数:

\{\\rm tanh}'(x) = 4 \\sigma(2x)(1 - \\sigma(2x)) = \\frac {4}{(\\exp(x) + \\exp(-x))\^2} \\

实际上我们可以通过近似的方法去拟合这个函数,毕竟 \(e^x\) 也不是那么好算的。

  • Hard-LogisticHard-Tanh 函数

\{\\rm hard-\\sigma}(x) = \\begin{cases} 1 \& x \> 2 \\\\ \\frac x 4 + \\frac 1 2 \& x \\in \[-2, 2 \\ 0 & x < 2 \end{cases} \]

或者利用 \(\min, \max\) 简化:

\{\\rm hard-\\sigma}(x) = \\max(\\min(\\frac x 4 + \\frac 1 2, 1), 0) \\

类似的:

\{\\rm hard-tanh}(x) = \\max(\\min(x, 1), -1) \\

ReLU

也就是 Rectified Linear Unit,线性修正单元,定义为:

\{\\rm ReLU}(x) = \\begin{cases} x \& x \\ge 0 \\\\ 0 \& x \< 0 \\end{cases} \\

也就是 \({\rm ReLU}(x) = \max(x, 0)\)

当然,因为可能出现 死亡 ReLU 问题,所以一般有如下变形:

\{\\rm PReLU}(x) = \\begin{cases} x \& x \\ge 0 \\\\ \\gamma x \& x \< 0 \\end{cases} \\

如果 \(\gamma = 0\) 则退化为 \(\rm ReLU\) 函数,如果 \(\gamma < 1\),那么也可以写为:

\{\\rm LeakyLU(x)} = \\max(x, \\gamma x) \\

另一个变形是:

\{\\rm ELU}(x) = \\begin{cases} x \& x \\ge 0 \\\\ \\gamma(\\exp(x) - 1) \& x \< 0 \\end{cases} \\

还有一个则是:

\{\\rm Softplus}(x) = \\log(1 + \\exp(x)) \\

Swish 函数

这是一种自控门函数:

\{\\rm swish}(x) = x \\sigma(\\beta x) \\

网络结构

网络结构分三种:

  • 前馈网络
  • 记忆网络
  • 图网络

这里先讲述前馈网络

这是一个前馈网络的示意图,其中第一层为输入层,最后一层为输出层。

而中间的那些层称为隐藏层。隐藏层可以有多个,而这里只画出了一个。

每一层有若干神经元,而两层间的神经元两两相连。

现在我们定义一些符号:

  • \(L\) 表示总层数,注意这里输入层为第 \(0\) 层,不计入其中;输出层为第 \(L\) 层。
  • \(M_l\) 表示第 \(l\) 层的神经元数量。
  • \(f_l(\cdot)\) 表示第 \(l\) 层的激活函数。
  • \(W^{(l)} \in \mathbb{R}^{M_l \times M_{l - 1}}\) 表示第 \(l - 1\) 层到第 \(l\) 层的权重矩阵(若干权重向量组成)。
  • \(b^{(l)} \in \mathbb{R}^{M_l}\) 表示第 \(l\) 层的偏置。
  • \(z^{(l)} \in \mathbb{R}^{M_l}\) 表示净输入。
  • \(a^{(l)} \in \mathbb{R}^{M_l}\) 表示输出。

对于一组数据 \((\vec x, y)\),前馈神经网络通过如下算法进行传播:

\\\begin{aligned} z\^{(l)} \&= W\^{(l)} a\^{(l - 1)} + b\^{(l)} \\\\ a\^{(l)} \&= f_l(z\^{(l)}) \\end{aligned} \\

参数学习

参数学习可能略有点复杂,证明过程我懒得写成 \(\LaTeX\),这里就省略了。

我们利用反向传播算法进行学习,其步骤如下:

  • 选取一个数据,计算 \(a^{(l)}\) 和 \(z^{(l)}\)。
  • 反向传播每一层的误差 \(\delta^{(l)}\)
  • 计算每一层的偏导数,更新参数

显然的是 \(\delta^{(L)} = a^{(L)} - y\)

经过一番神秘的推导,我们可以得到:

\\\delta\^{(l)} = f_l'\\left(z\^{(l)}\\right) \\cdot \\left( \\left( W\^{(l + 1)} \\right)\^T \\delta\^{(l + 1)} \\right) \\in \\mathbb{R}\^{M_l} \\

其中 \(\cdot\) 表示元素一一相乘。

而计算偏导数的公式也不难:

\\\begin{aligned} \\frac {\\partial}{\\partial W\^{(l)}} R(W) \&= \\delta\^{(l)} \\left( a\^{(l - 1)} \\right)\^T \\\\ \\frac {\\partial}{\\partial b\^{(l)}} R(W) \&= \\delta\^{(l)} \\end{aligned} \\

也就是参数更新方式为:

\\\begin{aligned} W\^{(l)} \&\\leftarrow W\^{(l)} - \\alpha \\left( \\delta\^{(l)} \\left( a\^{(l - 1)} \\right)\^T + \\lambda W\^{(l)} \\right) \\\\ b\^{(l)} \&\\leftarrow b\^{(l)} - \\alpha \\delta\^{(l)} \\end{aligned} \\

但是值得注意的是,一般我们都会将 \(W^{(l)}\) 的第一列作为 \(b^{(l)}\),也就是不分开,所以在代码实现上要好生注意!

这是吴恩达机器学习 ex4 的部分代码:

octave 复制代码
function [J grad] = nnCostFunction(nn_params, ...
                                   input_layer_size, ...
                                   hidden_layer_size, ...
                                   num_labels, ...
                                   X, y, lambda)

% Theta1 25 x 401
% Theta2 10 x 26

Theta1 = reshape(nn_params(1:hidden_layer_size * (input_layer_size + 1)), ...
                 hidden_layer_size, (input_layer_size + 1));

Theta2 = reshape(nn_params((1 + (hidden_layer_size * (input_layer_size + 1))):end), ...
                 num_labels, (hidden_layer_size + 1));
                 
temp1 = Theta1;
temp2 = Theta2;
temp1(:, 1) = 0;
temp2(:, 1) = 0;

m = size(X, 1);
         
J = 0;
Theta1_grad = zeros(size(Theta1));
Theta2_grad = zeros(size(Theta2));

% forward propagation
A2 = sigmoid([ones(m, 1) X] * Theta1'); % m x 25
A3 = sigmoid([ones(m, 1) A2] * Theta2'); % m x 10

% caculate cost
Y = zeros(m, num_labels);
for i = 1:m
	Y(i, y(i)) = 1;
end
J -= sum(sum( log(A3) .* Y + log(1 - A3) .* (1 - Y) ));
J += lambda / 2 * (sum(sum(temp1 .* temp1)) + sum(sum(temp2 .* temp2)));
J /= m;

% Back Propagation

D1 = zeros(size(Theta1));
D2 = zeros(size(Theta2));

for i = 1:m
	a1 = X(i, :); % 1 x 400
	a2 = A2(i, :); % 1 x 25
	a3 = A3(i, :); % 1 x 10
	y = Y(i, :); % 1 x 10
	d3 = (a3 - y)'; % 10 x 1
	d2 = (Theta2' * d3) .* [1 a2]' .* (1 - [1 a2])'; % 26 x 1
	d2 = d2(2:end) ; % 25 x 1
	
	D1 += d2 * [1 a1];
	D2 += d3 * [1 a2];
end

Theta1_grad = (D1 + lambda * temp1) / m;
Theta2_grad = (D2 + lambda * temp2) / m;

% Unroll gradients
grad = [Theta1_grad(:) ; Theta2_grad(:)];

end