神经网络的参数主要有两大块,一是各神经元之间连接的权重参数,而是表示各功能神经元阈值的偏置参数。通过对损失函数使用梯度下降法,可以找到最优的权重和偏置参数,使得损失函数达到极小。
神经网络原理介绍(以二层神经网络为例)
如上图所示,一个简单二层神经网络包含输入层、隐层和输出层。输入的数据乘以第一层权重参数矩阵后,到达隐层,经隐层的**函数作用后,乘以第二层权重参数矩阵后到达输出层,经输出层的**函数处理后输出。其中,隐层的**函数一般为Sigmoid函数或是Relu函数,而输出层的**函数一般为恒等函数或是Softmax函数。
假如输入数据为,隐层有三个神经元,记到第一个隐层神经元的权重参数为, 到第一个隐层神经元的权重参数为, 到第二个隐层神经元的权重参数为,那么权重参数矩阵可以表示为
从输入层到隐层的变换可以表示为
但是一般情况下,我们会设定一个阈值,用它来控制神经元被**的容易程度,即(这里先以最简单的阶跃函数来作为**函数)
输入到隐层的信息经过**函数作用后,变为向量,向量再经第二层权重矩阵作用后到达输出层,第一个隐层神经元与第一个输出神经元之间的权重参数记作,显然,由隐层到输出层的过程可以表示为
神经网络的学习步骤
SGD(stochastic gradient descent)随机梯度下降法
- 从训练集中随机选出一部分数据(这部分数据成为mini-batch)。训练的目标是减小mini-batch的损失函数的值。
- 求出各参数的梯度,梯度指向了损失函数减小最多的方向。
- 将损失函数向着梯度方向进行更新
- 将上述步骤进行迭代
从零创建一个两层神经网络
首先准备好要用到的几个函数
其中,定义求梯度函数,在上一篇博客中有提到提速下降的原理和实现,详见这篇博客
#交叉熵误差
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)