原理就是这么简单 用Python搭建神经网络

2018-11-22  本文已影响0人  云雾中的墨先生

Date: 11/22/2018

不做调包侠!

之前我在网易云课堂与稀牛学院的深度学习公开课上讲述了“不调包,仅用Python如何四步搭建神经网络”。
我发现各位小伙伴们对原理的渴求非常强烈!说明大家在“不做调包侠”上非常重视,我也非常开心。
这篇文章也是“原理就是这么简单”的系列文章之一,希望通过文章的方式将原理描述的更加丰满!帮助更多想学习深度学习的朋友!

对比Sklearn中的线性回归

学过机器学习的朋友都知道Sklearn是非常重要的工具包,很多复杂的机器学习模型也许用sklearn几行代码就能搞定,
举个例子比如线性回归,我们可以构建如下的线性回归对象训练和预测一气呵成:

linreg = LinearRegression()
linreg.fit(train_features,train_targets) 
y_pred = linreg.predict(val_features)

发现使用非常简单,仅仅三行代码就能得到线性回归的结果(y_pred
如果使用的案例是波士顿房价的数据集,那相应可以得到如下的预测结果:

Boston_Housing_predict.PNG

预测结果还不错。

那如何构建一个类似Skleran.LinearRegression 的神经网络工具包呢?
接下来我就要将数学原理转换为代码。

Python搭建神经网络完成回归工作

终于到了本篇文章的重点,我会用数学原理和python代码详细讲解如何构建如下结构的神经网络:


nn.png

介绍网络

大家可以看到这个神经网络分别有

这个是这个网络的基本结构,在进行下面的讲解之前需要在这里声明一下,

既然是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算法的流程分为三个部分:
一为前向传播求误差
二为反向传播求梯度
三为通过梯度更新权重

可能大家会对这三个部分晦涩难懂,其实我举个不恰当的例子你就能明白。
比如小明想去学习自由泳,他没有教练,不过好在有一个泳池他可以无限次的在里面尝试。
然后小明开始自学:

然后再次跳入水中。

经过n 次的刻苦训练,小明练成了自由泳。

其实这个流程就好比BP算法的流程。
步骤一就像前向传播,带着一些不靠谱的动作相当于初始权重矩阵
步骤二就像于反向传播,仔细体验哪个动作更加有效这相当于求权重的梯度
步骤三就像于梯度更新,更新自己的动作相当于更新权重矩阵

下面我就要仔细用数学推导BP算法:
首先来学习一下数学基础:


base.JPG

Loss function 不用过多解释,了解机器学习的都知道这是MSE。
Sigmoid 就是刚才提到的过滤器也就是激活函数。

前向传播:

前向传播.JPG
代码如下:
代码中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)

梯度更新:

梯度更新.JPG
代码如下:
代码中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:
初始化:

Step2

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 时代深度学习作为爆发技术的主要原因。

你也可以自己尝试实现,如果有问题,欢迎给我留言

上一篇下一篇

猜你喜欢

热点阅读