神经网络的参数主要有两大块,一是各神经元之间连接的权重参数,而是表示各功能神经元阈值的偏置参数。通过对损失函数使用梯度下降法,可以找到最优的权重和偏置参数,使得损失函数达到极小。
神经网络原理介绍(以二层神经网络为例)
如上图所示,一个简单二层神经网络包含输入层、隐层和输出层。输入的数据乘以第一层权重参数矩阵 W ( 1 ) W^{(1)} W(1)后,到达隐层,经隐层的激活函数 h ( x ) h(x) h(x)作用后,乘以第二层权重参数矩阵 W ( 2 ) W^{(2)} W(2)后到达输出层,经输出层的激活函数处理后输出。其中,隐层的激活函数一般为Sigmoid函数或是Relu函数,而输出层的激活函数一般为恒等函数或是Softmax函数。
假如输入数据为
x
=
(
x
1
,
x
2
)
\pmb{x} = (x_1, x_2)
xxx=(x1,x2),隐层有三个神经元,记
x
1
x_1
x1到第一个隐层神经元的权重参数为
w
11
(
1
)
w^{(1)}_{11}
w11(1),
x
2
x_2
x2到第一个隐层神经元的权重参数为
w
12
(
1
)
w^{(1)}_{12}
w12(1),
x
1
x_1
x1到第二个隐层神经元的权重参数为
w
21
(
1
)
w^{(1)}_{21}
w21(1),那么权重参数矩阵可以表示为
W
(
1
)
=
[
w
11
(
1
)
w
21
(
1
)
w
31
(
1
)
w
12
(
1
)
w
22
(
1
)
w
32
(
1
)
]
W^{(1)} = \begin{bmatrix} w^{(1)}_{11}\quad w^{(1)}_{21} \quad w^{(1)}_{31}\\ w^{(1)}_{12}\quad w^{(1)}_{22} \quad w^{(1)}_{32} \end{bmatrix}
W(1)=[w11(1)w21(1)w31(1)w12(1)w22(1)w32(1)]
从输入层到隐层的变换可以表示为
(
x
1
x
2
)
[
w
11
(
1
)
w
21
(
1
)
w
31
(
1
)
w
12
(
1
)
w
22
(
1
)
w
32
(
1
)
]
=
(
a
1
a
2
a
3
)
(x_1\quad x_2)\begin{bmatrix} w^{(1)}_{11}\quad w^{(1)}_{21} \quad w^{(1)}_{31}\\ w^{(1)}_{12}\quad w^{(1)}_{22} \quad w^{(1)}_{32} \end{bmatrix}=(a_1\quad a_2\quad a_3)
(x1x2)[w11(1)w21(1)w31(1)w12(1)w22(1)w32(1)]=(a1a2a3)
但是一般情况下,我们会设定一个阈值
b
\pmb{b}
bbb,用它来控制神经元被激活的容易程度,即(这里先以最简单的阶跃函数
h
(
x
)
h(x)
h(x)来作为激活函数)
(
x
1
x
2
)
[
w
11
(
1
)
w
21
(
1
)
w
31
(
1
)
w
12
(
1
)
w
22
(
1
)
w
32
(
1
)
]
+
(
b
1
(
1
)
b
2
(
1
)
b
3
(
1
)
)
=
(
a
1
a
2
a
3
)
(x_1\quad x_2)\begin{bmatrix} w^{(1)}_{11}\quad w^{(1)}_{21} \quad w^{(1)}_{31}\\ w^{(1)}_{12}\quad w^{(1)}_{22} \quad w^{(1)}_{32} \end{bmatrix}+(b^{(1)}_1\quad b^{(1)}_2\quad b^{(1)}_3)=(a_1\quad a_2\quad a_3)
(x1x2)[w11(1)w21(1)w31(1)w12(1)w22(1)w32(1)]+(b1(1)b2(1)b3(1))=(a1a2a3)
h
(
x
)
=
{
0
,
w
11
(
1
)
x
1
+
w
21
(
1
)
x
2
+
b
<
=
0
1
,
w
11
(
1
)
x
1
+
w
21
(
1
)
x
2
+
b
>
0
h(x)=\begin{cases} 0,\qquad w^{(1)}_{11}x_1+w^{(1)}_{21}x_2+b<=0\\ 1,\qquad w^{(1)}_{11}x_1+w^{(1)}_{21}x_2+b>0 \end{cases}
h(x)={0,w11(1)x1+w21(1)x2+b<=01,w11(1)x1+w21(1)x2+b>0
输入到隐层的信息经过激活函数作用后,变为向量
z
\pmb{z}
zzz,向量
z
\pmb{z}
zzz再经第二层权重矩阵
W
(
2
)
W^{(2)}
W(2)作用后到达输出层,第一个隐层神经元与第一个输出神经元之间的权重参数记作
w
11
(
2
)
w^{(2)}_{11}
w11(2),显然,由隐层到输出层的过程可以表示为
(
z
1
z
2
z
3
)
[
w
11
(
2
)
w
21
(
2
)
w
12
(
2
)
w
22
(
2
)
w
13
(
2
)
w
23
(
2
)
]
+
(
b
1
(
2
)
b
2
(
2
)
)
=
(
y
1
y
2
)
(z_1\quad z_2\quad z_3)\begin{bmatrix} w^{(2)}_{11}\quad w^{(2)}_{21} \\ w^{(2)}_{12}\quad w^{(2)}_{22} \\ w^{(2)}_{13}\quad w^{(2)}_{23} \\ \end{bmatrix}+(b^{(2)}_1\quad b^{(2)}_2)=(y_1\quad y_2)
(z1z2z3)⎣⎢⎡w11(2)w21(2)w12(2)w22(2)w13(2)w23(2)⎦⎥⎤+(b1(2)b2(2))=(y1y2)
神经网络的学习步骤
SGD(stochastic gradient descent)随机梯度下降法
- 从训练集中随机选出一部分数据(这部分数据成为mini-batch)。训练的目标是减小mini-batch的损失函数的值。
- 求出各参数的梯度,梯度指向了损失函数减小最多的方向。
- 将损失函数向着梯度方向进行更新
- 将上述步骤进行迭代
从零创建一个两层神经网络
首先准备好要用到的几个函数
其中,定义求梯度函数
g
r
e
d
i
e
n
t
(
f
,
x
)
gredient(f, x)
gredient(f,x),在上一篇博客中有提到提速下降的原理和实现,详见这篇博客
#交叉熵误差
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)
#sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
#softmax函数
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # 溢出对策
return np.exp(x) / np.sum(np.exp(x))
#定义求梯度的函数,在上一篇博客中有提到梯度下降的实现
#详见 https://blog.csdn.net/ISMedal/article/details/87893200 这里
def gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
准备好了这些函数以后,我们定义一个类,来实现上面说的最简单的二层神经网络
import numpy as np
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
#初始化权重
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
#np.random.randn(...)是为了创建初始化的第一层权重矩阵,其行数为输入层神经元的个数,列数为隐层神经元的个数
self.params['b1'] = np.zeros(hidden_size) #初始化的第一层偏置项,其长度为隐层神经元的个数
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) #初始化的第二层权重矩阵
self.params['b2'] = np.zeros(output_size) #初始化的第二层偏置项
def predict(self, x):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
a1 = np.dot(x, W1) + b1 #隐层接收到的信息
z1 = sigmoid(a1) #使用sigmoid函数作为隐层的激活函数
a2 = np.dot(z1, W2) + b2 #输出层接收到的信息
y = softmax(a2) #使用softmax函数作为输出层的激活函数
return y
def loss(self, x, t): #定义损失函数,其中x为输入的待预测数据,t为真实数据
y = self.predict(x)
return cross_entropy_error(y, t) #使用交叉熵误差作为损失函数
#真实结果都是经过One-hot编码后的
#如果预测结果y_1=[0.1, 0.3, 0.5, 0.1],真实结果为[0, 0, 1, 0]
#那么表示该条数据属于第三类的可能性最大,而真实结果说明它就是属于第三类,则该条数据分类正确
def accuracy(self, x, t):
y = self.predict(x)
y = np.argmax(y, axis = 1) #对每条样本求其预测的最可能的类别
t = np.argmax(t, axis = 1) #每条样本的真实类别
accuracy = np.sum(y == t)/float(x.shape[0]) #要将int格式转化为可以参与计算的float格式
return accuracy
def numerical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
#将损失函数定义为关于W的函数(而不是关于x,更不是关于t的),是以W为自变量,后面对W求偏导
grads = {}
grads['W1'] = gradient(loss_W, self.params['W1']) #求权重参数矩阵W1的梯度(导数)
grads['b1'] = gradient(loss_W, self.params['b1'])
grads['W2'] = gradient(loss_W, self.params['W2'])
grads['b2'] = gradient(loss_W, self.params['b2'])
return grads
以手写字体数据集为例,实现mini-batch学习
mini-batch学习,指的是从训练数据中,随机选择一部分数据,再以这些数据为对象,使用梯度法(在这篇博客有解释)更新参数的过程。
注:导入需要的数据集mnist,其中需要用到的py文件dataset.mnist.py在这里取(把它放在python或ipynb文件所在的目录就可以用了)
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(normalize = True, one_hot_label = True)
train_loss_list = []
#人为设定的超参数
iters_num = 10000 #迭代10000次
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1
network = TwoLayerNet(input_size = 784, hidden_size = 50, output_size = 10)
#因为输入的图像大小是784(28*28),因此设定input_size = 784
for i in range(iters_num):
#获取每一次训练的那个batch
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
#计算梯度
grad = network.numerical_gradient(x_batch, t_batch)
#更新参数
for key in ('W1', 'b1', 'W2', 'b2'):
network.params[key] -= learning_rate * grad[key]
#记录学习过程
loss = network.loss(x_batch, t_batch)
train_loss_list.append(loss)