构建一个神经网络:成本函数 和 梯度下降


如何构建一个神经网络

一般的神经网络中,我们都有多层、非线性的激活函数(activation function),而且每个节点都有偏置项。

在这里,我们暂时先只写一层,一个权重参数 w,输出没有激活函数,没有偏置项。

先导入依赖

import numpy
import matplotlib.pyplot as plt
%matplotlib inline
#设置 random 的 seed,使随机变成伪随机
numpy.random.seed(seed=1)

定义目标函数

在这个例子中,目标函数 \mathbf{t}=f(\mathbf{x})+\mathcal{N}(0,0.2)

其中 \mathcal{N}(0,0.2)是我们加进去的高斯噪声(正态分布), f(\mathbf{x})=\mathbf{x}*2 .

# 产生一个一致(0,1)分布的20个数据, x.shape = (20,)
x = numpy.random.uniform(0,1,20)

def f(x):
    return x*2

noise_variance = 0.2

# x.shape[0] = 20, randn 输出一个20(*1)的标准正态分布矩阵
noise = numpy.random.randn(x.shape[0]) * noise_variance

t = f(x) + noise
plt.plot(x, t, 'o', label='t')

plt.plot([0, 1], [f(0), f(1)], 'b-', label='f(x)')
plt.xlabel('$x$', fontsize=15)
plt.ylabel('$t$', fontsize=15)
plt.ylim([0,2])
plt.title('inputs (x) vs targets (t)')
plt.grid()
plt.legend(loc=2)
plt.show()

png

定义成本函数

我们现在的模型是 \mathbf{y} = \mathbf{x}*w

我们优化的目的是使 \xi = \sum_{i=1}^{N} \Vert t_i - y_i \Vert ^2 取到最小值。

神经网络是通过 nn(x,w) 函数来实现的;成本函数是通过 cost(y,t) 来实现的。

def nn(x,w):
    return x * w

def cost(y,t):
    return ((t-y)**2).sum()
ws = numpy.linspace(0, 4, num=100)  # ws 是一个 list, 包含0-4之间的100个均匀分布的数
cost_ws = numpy.vectorize(lambda w: cost(nn(x, w) , t))(ws)  # ws 中每个值对应的 cost

# 制图
plt.plot(ws, cost_ws, 'r-')
plt.xlabel('$w$', fontsize=15)
plt.ylabel('$\\xi$', fontsize=15)
plt.title('cost vs. weight')
plt.grid()
plt.show()

png

从图中我们可以看出来,w=2 的时候 \xi 值最低。w=2 也是我们为 f(x) 选定的斜率。

注意这个方程是一个凸函数,所以只有一个最小值。

优化成本函数

这个例子中最佳权重 w 用肉眼都可以看出来。但是大多数情况下问题没有这么简单,比如

f(\mathbf{x}) = An + \sum_{i=1}^n[x_i^2 - A cos(2\pi x_i)]

其中 A=10x_i \in [-5.12, +5.12]. 在 x=0 取最小值 f(x) = 0 .

所以我们需要优化这个算法。

梯度下降

最常用的优化算法之一就是梯度下降算法。它的原理是在成本函数的某一点(参数)上对 \xi 求导数,然后在负梯度方向更新参数。参数 w 被迭代更新,步数与负梯度成比例:

w(k+1) = w(k) - \Delta w(k)

其中 w(k) 是第 k 次迭代中 w 的值。

\Delta w 的定义为: \Delta w = \mu \frac{\partial \xi}{\partial w}

其中,\mu 是“学习速度”(learning rate),也就是在梯度方向上移动的距离。

对于样本i, 梯度可以根据链式法则进行拆分:

\frac{\partial \xi_i}{\partial w} = \frac{\partial y_i}{\partial w} \frac{\partial \xi_i}{\partial y_i}

其中 \xi_i 是平方差成本,所以 {\partial \xi_i}/{\partial y_i} 可以写成:

\frac{\partial \xi_i}{\partial y_i} = \frac{\partial (t_i - y_i)^2}{\partial y_i} = - 2 (t_i - y_i) = 2 (y_i - t_i)

又因为 y_i = x_i * w,所以我们可以把 {\partial y_i}/{\partial w} 写成:

\frac{\partial y_i}{\partial w} = \frac{\partial (x_i * w)}{\partial w} = x_i

所以样本i\Delta w 最终写成:

\Delta w = \mu * \frac{\partial \xi_i}{\partial w} = \mu * 2 x_i (y_i - t_i)

梯度下降属于在线处理(online processing),与之对应的是批量处理(batch processing)。在批量处理中,我们只是简单地把所有样本的梯度相加:

\Delta w = \mu * 2 * \sum_{i=1}^{N} x_i (y_i - t_i)

使用梯度下降

随机选取一个初始的参数值 w, 然后使用 \Delta w 不断地对 w 进行更新直到收敛。

学习速度 \mu 应该拿出来作为一个超参(hyperparameter),每个神经网络都有不同的学习速度。

梯度 {\partial \xi}/{\partial w} 由函数 gradient(w, x, t) 来实现。\Delta w 是由 delta_w(w_k, x, t, learning_rate) 来计算。

循环执行四次,且打印出当前的 w\xi

def gradient(w, x, t):
    return 2 * x * (nn(x, w) - t)

def delta_w(w_k, x, t, learning_rate):
    return learning_rate * gradient(w_k, x, t).sum()

# 设置初始 w 值
w = 0.1

learning_rate = 0.1


nb_of_iterations = 4
w_cost = [(w, cost(nn(x, w), t))]
for i in range(nb_of_iterations):
    dw = delta_w(w, x, t, learning_rate)
    w = w - dw
    w_cost.append((w, cost(nn(x, w), t)))

for i in range(0, len(w_cost)):
    print('w({}): {:.4f} \t cost: {:.4f}'.format(i, w_cost[i][0], w_cost[i][1]))
w(0): 0.1000 	 cost: 13.6197
w(1): 1.5277 	 cost: 1.1239
w(2): 1.8505 	 cost: 0.4853
w(3): 1.9234 	 cost: 0.4527
w(4): 1.9399 	 cost: 0.4510

可以观察到,第二次迭代就很接近我们的目标了。

我们试着画个图看看,把前两次梯度下降显示出来。

plt.plot(ws, cost_ws, 'r-')

for i in range(0, len(w_cost)-2):
    w1, c1 = w_cost[i]
    w2, c2 = w_cost[i+1]
    plt.plot(w1, c1, 'bo')
    plt.plot([w1, w2],[c1, c2], 'b-')
    plt.text(w1, c1+0.5, '$w({})$'.format(i))

plt.xlabel('$w$', fontsize=15)
plt.ylabel('$\\xi$', fontsize=15)
plt.title('Gradient descent updates plotted on cost function')
plt.grid()
plt.show()

png

我们用十次梯度下降的迭代,与原来的方法相比,画图比较一下。

(两条线都过(0,0)一点,因为我们没有加偏置项(bias term))

w = 0

nb_of_iterations = 10
for i in range(nb_of_iterations):
    dw = delta_w(w, x, t, learning_rate)
    w = w - dw
plt.plot(x, t, 'o', label='t')

plt.plot([0, 1], [f(0), f(1)], 'b-', label='f(x)')

plt.plot([0, 1], [0*w, 1*w], 'r-', label='fitted line')

plt.xlabel('input x')
plt.ylabel('target t')
plt.ylim([0,2])
plt.title('input vs. target')
plt.grid()
plt.legend(loc=2)
plt.show()

png