一、回顾
在上一篇我们介绍了感知机,以及如何从感知机演变到神经网络。最后得出结论,神经网络就是一个矩阵乘法。在上一篇的末尾我们得到下面的公式:
y = σ(W(2)relu(W(1)X+b1)+b2)y = sigma(W^{(2)}relu(W^{(1)}X+b_1)+b_2)
在这一篇文章,我们将用介绍更多神经网络的细节,并使用numpy来实现神经网络。如果还没有阅读上一篇文章,可以先阅读上一篇:juejin.cn/post/723174…
二、神经网络的组成
2.1 神经网络三大部分
神经网络通常由三个部分组成,分别是输入层、隐藏层(中间层)、输出层,其中隐藏层可能有多层。这里以下图为例:
数据进入输入层,经过矩阵乘法和激活函数,得到第一个隐藏层结果h。隐藏层再执行相同的操作,得到输出层结果。
输入层只用来接收输入,不做处理。输入层到隐藏层由一些线连接,每根线就是一个权重(这里省略了b),输入到隐藏层的操作就是将输入代入:h = WX+b,而隐藏层则是一直重复h2 = Wh1+b这一操作,只不过W和b不同了。
三个部分中,输出层是比较特殊的。输出层通常只需要一个激活函数即可,应对不同的问题,需要设计不同的激活函数。接下来要讨论的就是如何设计输出层的激活函数。
2.2 输出层的激活函数
监督学习中存在两类问题,一个是分类,一个是回归。回归问题只需要输出一个数即可,这个数的取值根据具体问题不同可以在很大范围内波动。因此回归问题中输出层不适合用sigmoid、relu等激活函数。通常回归问题是输出层不使用激活函数,或者说是一个恒等函数作为激活函数,恒等函数的实现如下:
def identity_function(x):
return x
分类问题比较特殊,通常需要输出n个值,n表示类别个数。我们希望设计出一个激活函数,让这n个值有类似概率的性质。softmax函数就能满足上述需求,softmax的公式如下:
softmax(x) = exi∑i=1nexisoftmax(x) = frac{e^{x_i}}{sum_{i=1}^ne^{x_i}}
使用numpy可以很容易实现softmax函数:
def softmax(x):
return np.exp(x) / np.sum(np.exp(x))
上面softmax的实现存在一些问题,下面是一种更好的实现:
def softmax(x):
c = np.max(x)
exp_x = np.exp(x - c)
sum_exp_x = np.sum(exp_x)
return exp_x / sum_exp_x
三、评估网络
利用上面的知识,已经可以构建一个网络了。在网络中,我们需要找到最好一组权重(W、b)。但是现在还无法知道权重的好坏,下面讨论一下如何评估一个网络。
3.1 构建网络
在开始评估前,我们来构建一个分类网络,因为是分类网络,所以输出层的激活函数改为softmax函数,那么神经网络可以用下式:
y = softmax(W(2)relu(W(1)X+b1)+b2)y = softmax(W^{(2)}relu(W^{(1)}X+b_1)+b_2)
使用numpy可以很容易实现上面的网络。下面是一种实现:
import numpy as np
# 模拟数据
X = np.random.randn(1, 2)
# 初始化权重
W1 = np.random.randn(2, 2)
b1 = np.zeros([1])
W2 = np.random.randn(2, 2)
b2 = np.zeros([1])
# 前向传播
a1 = np.dot(X, W1) + b1
h1 = relu(a1)
a2 = np.dot(h1, W2) + b2
y = softmax(a2)
为了方便后续使用,这里用一个字典存储权重,并封装成一个类:
class SimpleNetwork:
def __init__(self, input_size, hidden_size, output_size):
"""
初始网络权重
:param input_size: 输入大小
:param hidden_size: 隐藏层大小
:param output_size: 输出大小
"""
self.params = {
"W1": np.random.randn(input_size, hidden_size),
"b1": np.zeros(1),
"W2": np.random.randn(hidden_size, output_size),
"b2": np.zeros(1)
}
def forward(self, inputs):
"""
进行正向传播
:param inputs: 输入数据
:return: 预测结果
"""
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
outputs = relu(np.dot(inputs, W1) + b1)
return softmax(np.dot(outputs, W2) + b2)
如果想使用网络,传入数据,调用forward方法即可。
3.2 损失函数
损失函数的作用是评估一个模型的好坏,通常与真实值和预测值之间距离成正比。当预测偏离真实时,损失函数会得到较大的值,当预测与真实非常接近时,损失函数会得到较小的值。常见的损失函数有均方误差(Mean Square Error)和交叉熵损失(Cross Entropy Error)等,前者用于回归,后者用于分类。
假设有数据(x1,t1)、(x2,t2)、….,将x输入网络,得到y1、y2、….,此时损失函数的计算为:
其中均方误差的计算如下:
E = 12∑k(yk−tk)2E = frac{1}{2}sum_k(y_k-t_k)^2
交叉熵损失的计算如下:
E = −∑k tklogykE = -sum_k t_klog{y_k}
下面是各自的实现:
def mean_square_error(y, t):
return 0.5 * np.sum((y-t) ** 2)
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
在回归中,输出的结果是一个数,如果真实值t=5,那么mean_square_error(3, t)会小于mean_square_error(4, t),下面是测试代码:
print(mean_square_error(3, 5))
print(mean_square_error(4, 5))
输出如下:
2.0
0.5
如果是分类,输出的是一个结果softmax的概率分布,比如三分类可能输出[0.1, 0.2, 0.7],如果真实值为[0, 0, 1],那么交叉熵损失会比较小。而如果输出[0.5, 0.5, 0],则交叉熵损失会比较大。
3.3 批处理
在深度学习中,往往会采用批处理技术,即一次性处理多个数据,并综合计算损失,这样得到的损失会更稳定,异常样本对训练的影响也越小。
批处理版本的交叉熵损失可以写成:
E = −1N∑n∑k tnklogynkE = -frac{1}{N}sum_nsum_k t_{nk}log{y_{nk}}
下面是批处理版本的交叉熵损失的实现:
def cross_entropy_error(y, t):
if y.ndim == 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 + 1e-7)) / batch_size
当t不是one-hot编码形式,而是1、2、3这种形式,那么交叉熵损失可以写成:
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
四、神经网络的构建
现在可以构建整个神经网络了。首先我们有训练数据(X,T),其中X是样本,T是真实值。我们利用前面的SimpleNetwork作为我们的模型,而我们要做的是分类,所以用交叉熵作为损失函数。此时网络的正向传播(预测)可以用下式表示:
y = softmax(W(2)relu(W(1)X+b1)+b2)y = softmax(W^{(2)}relu(W^{(1)}X+b_1)+b_2)
用代码表示可以写成:
network = SimpleNetwork(784, 512, 10)
y = network.forward(X)
如果想评估模型,则可以使用下式表示:
E=−∑ktklogyk=∑ktklog(softmax(W(2)relu(W(1)xk+b1)+b2))E = -sum_k t_klog{y_k}=sum_k t_klog({softmax(W^{(2)}relu(W^{(1)}x_k+b_1)+b_2)})
用代码表示可以写成:
network = SimpleNetwork(784, 512, 10)
y = network.forward(X)
loss = cross_entropy_error(y, T)
有了这些,我们就可以对网络进行评估,使用网络预测了。
但是现在网络的权重还是随机初始化的,要进行学习才能让网络正确预测结果,在下一篇文章中,我们将介绍网络学习的操作。