原理就是这么简单 用Python搭建神经网络
Date: 11/22/2018
不做调包侠!
之前我在网易云课堂与稀牛学院的深度学习公开课上讲述了“不调包,仅用Python如何四步搭建神经网络”。
我发现各位小伙伴们对原理的渴求非常强烈!说明大家在“不做调包侠”上非常重视,我也非常开心。
这篇文章也是“原理就是这么简单”的系列文章之一,希望通过文章的方式将原理描述的更加丰满!帮助更多想学习深度学习的朋友!
对比Sklearn中的线性回归
学过机器学习的朋友都知道Sklearn是非常重要的工具包,很多复杂的机器学习模型也许用sklearn几行代码就能搞定,
举个例子比如线性回归,我们可以构建如下的线性回归对象训练和预测一气呵成:
linreg = LinearRegression()
linreg.fit(train_features,train_targets)
y_pred = linreg.predict(val_features)
发现使用非常简单,仅仅三行代码就能得到线性回归的结果(y_pred)
如果使用的案例是波士顿房价的数据集,那相应可以得到如下的预测结果:
预测结果还不错。
那如何构建一个类似Skleran.LinearRegression 的神经网络工具包呢?
接下来我就要将数学原理转换为代码。
Python搭建神经网络完成回归工作
终于到了本篇文章的重点,我会用数学原理和python代码详细讲解如何构建如下结构的神经网络:
nn.png
介绍网络
大家可以看到这个神经网络分别有
- Input Layer
- Hidden Layer
- Output Layer
这个是这个网络的基本结构,在进行下面的讲解之前需要在这里声明一下,
既然是Layer 层 , 都会有这个层本身输入和输出,就好比一个净水过滤器,传进去的是脏水,传出来的是净水,那么神经网络的各个层也有这个性质。
1, Input Layer 值得注意的地方是Input Layer 只有输出,传入的就是数据特征,如果使用的是预测房价的案例传入的就是(面积,朝向,地段,etc..)。
公式中会表示成X
2, Hidden Layer 有输入和输出两个概念了,就好比净水器。那么针对Hidden Layer净水器的过滤网是什么呢? 其实就是激活函数(这个后面我会详细讲解)。
公式中会表示成h_input/h_out
3, Output Layer 也有输入和输出两个概念,只不过这次我搭建的是处理回归模型的网络,所以其输入和输出是一样的值。
公式中会表示成O_input/O_out
4, 链接层与层之间的是权重矩阵。
公式中会表示成W_i_h/ W_h_o
BP算法数学原理和代码实现
其实 BP 算法的全名为"Error BackPropagation 误差反向传播"
但是其实BP算法的流程分为三个部分:
一为前向传播求误差
二为反向传播求梯度
三为通过梯度更新权重
可能大家会对这三个部分晦涩难懂,其实我举个不恰当的例子你就能明白。
比如小明想去学习自由泳,他没有教练,不过好在有一个泳池他可以无限次的在里面尝试。
然后小明开始自学:
- 步骤一,他脑子里想了A,B,C 三套动作准备在水中尝试,然后直接跳入水中瞎扑腾,看看到底是落水还是前进。
- 步骤二,在水中他认真体会各种动作对于游泳的影响,发现使用C动作他还能扑腾一会,但是使用A,B动作,瞬间落水。
- 步骤三,他上了岸,然后仔细总结刚才的C动作并加以改进,并且尝试去除自己的A,B动作。
然后再次跳入水中。
经过n 次的刻苦训练,小明练成了自由泳。
其实这个流程就好比BP算法的流程。
步骤一就像前向传播,带着一些不靠谱的动作相当于初始权重矩阵
步骤二就像于反向传播,仔细体验哪个动作更加有效这相当于求权重的梯度
步骤三就像于梯度更新,更新自己的动作相当于更新权重矩阵
下面我就要仔细用数学推导BP算法:
首先来学习一下数学基础:
base.JPG
Loss function 不用过多解释,了解机器学习的都知道这是MSE。
Sigmoid 就是刚才提到的过滤器也就是激活函数。
前向传播:
代码如下:
代码中final_outputs 就是 O_out
hidden_inputs = np.dot(X,self.input_hidden_weight)
hidden_outputs = self.sigmoid(hidden_inputs)
final_inputs = np.dot(hidden_outputs,self.hidden_output_weight)
final_outputs = final_inputs
反向传播:
基于链式求导法则如下:
反向传播.JPG
关注O_input_error_term, h_input_error_term
因为这两项是关于权重矩阵W_i_h和W_h_o的函数。
代码如下:
final_output_error = y-final_outputs
final_input_error_term = final_output_error*1
hidden_output_error = np.dot(self.hidden_output_weight, final_input_error_term)
hidden_input_error_term = hidden_output_error*hidden_outputs*(1-hidden_outputs)
梯度更新:
代码如下:
代码中update_x_x 就是权重的梯度
update_i_h += np.dot(X_item,hidden_error_term.T)
update_h_o += np.dot(hidden_outputs,output_error_term.T)
到了这里你已经学会了BP算法的精华了,但是如果想构造一个可以使用的神经网络还差一点点,是什么呢?
下面我会根据代码给你讲解。
Python 四步构造NeuralNetwork
想要构造一个类似Sklearn的方便使用的神经网络,我们需要四步
Step1:
初始化:
- 构造sigmoid 激活函数
- 构造mse 用于求损失
- 设置学习率,以及网络节点
- 初始化两个权重矩阵
Step2
- 实现前向传播
- 实现predect函数基于前向传播
Step3
- 实现反向传播
整个代码如下:
···
class NeuralNetwork(object):
def init(self, input_units, hidden_units = 4, output_units = 1, learning_rate = 0.01):
self.sigmoid = lambda x : 1/(1+np.exp(-x))
self.mean_squared_error = lambda y_true, y_pred: np.mean((y_true-y_pred)**2)
self.input_units = input_units
self.hidden_units = hidden_units
self.output_units = output_units
self.learning_rate = learning_rate
np.random.seed(1119)
self.input_hidden_weight = np.random.randn(self.input_units,self.hidden_units)
self.hidden_output_weight = np.random.randn(self.hidden_units,self.output_units)
def __forward__(self, X):
''' forward pass through the neural network with X
Arguments
---------
X: 2D array
features
Returns
-------
hidden_outputs: 1D array
final_outputs: 1D array
'''
hidden_inputs = np.dot(X,self.input_hidden_weight)
hidden_outputs = self.sigmoid(hidden_inputs)
final_inputs = np.dot(hidden_outputs,self.hidden_output_weight)
final_outputs = final_inputs
return hidden_outputs,final_outputs
def __backward__(self, y, hidden_outputs, final_outputs):
''' backward pass through the neural network with X
Arguments
---------
X: 2D array
features
Returns
-------
hidden_input_error_term: 1D array
final_input_error_term: 1D array
'''
final_output_error = y-final_outputs
final_input_error_term = final_output_error*1
hidden_output_error = np.dot(self.hidden_output_weight, final_input_error_term)
hidden_input_error_term = hidden_output_error*hidden_outputs*(1-hidden_outputs)
return hidden_input_error_term,final_input_error_term
def fit(self, X, y):
''' fit(X, y) method of NeuralNetwork
Parameters
----------
X : 2D array
Training data
y : 1D array
Target values
Returns
-------
void
'''
n_records = X.shape[0]
for X_item, y_item in zip(X, y):
hidden_outputs, final_outputs = self.__forward__(X_item)
hidden_error_term, output_error_term = self.__backward__(y_item,hidden_outputs,final_outputs)
update_i_h += np.dot(X_item,hidden_error_term.T)
update_h_o += np.dot(hidden_outputs,output_error_term.T)
self.hidden_output_weight += self.learning_rate * (update_h_o / n_records)
self.input_hidden_weight += self.learning_rate * (update_i_h / n_records)
def predict(self, X):
''' predict(X) method of NeuralNetwork
Arguments
---------
X: 2D array
features
Returns
-------
outputs: 1D array
predicted values
'''
hidden_outputs, final_outputs = self.__forward__(X)
return final_outputs
总结
对比了一下自己构造的神经网络和线性回归,预测波士顿房价的案例可以得到如下结果:
线性回归VS神经网络.PNG
可以明显看出神经网络的性能更好一些。
并且神经网络可以做回归和分类两种任务,只要将Output layer 加一个Sigmoid 即可实现二分类,加一个Softmax 又可以实现多分类(可参考另一篇 原理就是这么简单 Softmax 分类)
理论上来说神经网络,有足够都的数据有足够多的层数和节点数,可以拟合任何函数,这也是神经网络的强大之处。
也是如今的AI 时代深度学习作为爆发技术的主要原因。
你也可以自己尝试实现,如果有问题,欢迎给我留言