6

运用Python实现机器学习算法:神经网络

 2 years ago
source link: https://www.51cto.com/article/701556.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
运用Python实现机器学习算法:神经网络-51CTO.COM
运用Python实现机器学习算法:神经网络
作者:Python林路 2022-02-15 23:38:22

今天将在感知机的基础上继续介绍神经网络模型。我们都知道,感知机是一种线性模型,对于非线性问题很难给出解决方案。

今天将在感知机的基础上继续介绍神经网络模型。我们都知道,感知机是一种线性模型,对于非线性问题很难给出解决方案。

比如咱们熟知的这种异或问题(XOR),就是一种典型的线性不可分问题,普通的感知机很难处理:

11f4ab500145f75fa7b734a00f61286a9c876b.png

因此,在普通的感知机基础上,我们对感知机结构进行了延伸,通过添加隐藏层的方式来使得感知机能够拟合非线性问题。

这种包含隐藏层结构的感知机模型就是神经网络,也叫多层感知机(Multilayer Perceptron)。

86ab90584dcf4f9b86e4261b7a21f6db50dc5d.png

关于神经网络的众多概念和知识:包括输入层、隐藏层、输出层、激活函数、前向传播、反向传播、梯度下降、权值更新等概念小编不再赘述。

本节以一个两层网络,即单隐层网络为例,来看看如何利用numpy实现一个神经网络模型。正式搭建神经网络之前我们先来准备一下数据。定义一个数据生成函数:

def create_dataset():
    np.random.seed(1)
    m = 400 # 数据量
    N = int(m/2) # 每个标签的实例数
    D = 2 # 数据维度
    X = np.zeros((m,D)) # 数据矩阵
    Y = np.zeros((m,1), dtype='uint8') # 标签维度
    a = 4 
    
    for j in range(2):
        ix = range(N*j,N*(j+1))
        t = np.linspace(j*3.12,(j+1)*3.12,N) + np.random.randn(N)*0.2 # theta
        r = a*np.sin(4*t) + np.random.randn(N)*0.2 # radius
        X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
        Y[ix] = j
        
    X = X.T
    Y = Y.T


    return X, Y

数据可视化展示如下:

66603ab61ffde68c2cd566e014ae00b0a0bc0d.png

继续回顾一下搭建一个神经网络的基本思路和步骤:

  • 定义网络结构(指定输出层、隐藏层、输出层的大小)
  • 初始化模型参数
  • 循环操作:执行前向传播/计算损失/执行后向传播/权值更新

定义网络结构

假设X为神经网络的输入特征矩阵,y为标签向量。则含单层的神经网络的结构如下所示:

a172ced89955fb0d2d17347789fdb89db38c50.png

网络结构的函数定义如下:

def layer_sizes(X, Y):
    n_x = X.shape[0] # 输入层大小
    n_h = 4 # 隐藏层大小
    n_y = Y.shape[0] # 输出层大小
    return (n_x, n_h, n_y)

其中输入层和输出层的大小分别与X和 y的shape有关。而隐层的大小可由我们手动指定。这里我们指定隐层的大小为4。

初始化模型参数

假设W1为输入层到隐层的权重数组、b1为输入层到隐层的偏置数组;W2为隐层到输出层的权重数组,b2为隐层到输出层的偏置数组。于是我们定义参数初始化函数如下:

def initialize_parameters(n_x, n_h, n_y):
    W1 = np.random.randn(n_h, n_x)*0.01
    b1 = np.zeros((n_h, 1))
    W2 = np.random.randn(n_y, n_h)*0.01
    b2 = np.zeros((n_y, 1)) 
   
    assert (W1.shape == (n_h, n_x))    
    assert (b1.shape == (n_h, 1))    
    assert (W2.shape == (n_y, n_h))    
    assert (b2.shape == (n_y, 1))


    parameters = {"W1": W1, 
                  "b1": b1,                 
                  "W2": W2,                  
                  "b2": b2}   
                   
    return parameters

其中对权值的初始化我们利用了numpy中的生成随机数的模块np.random.randn,偏置的初始化则使用了 np.zeros模块。通过设置一个字典进行封装并返回包含初始化参数之后的结果。

在定义好网络结构并初始化参数完成之后,就要开始执行神经网络的训练过程了。而训练的第一步则是执行前向传播计算。

假设隐层的激活函数为tanh函数, 输出层的激活函数为sigmoid函数。则前向传播计算表示为:

361ad6061997f69ed2b0528e0362724cd964fa.png

定义前向传播计算函数为:

def forward_propagation(X, parameters):
    # 获取各参数初始值
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']    
    # 执行前向计算
    Z1 = np.dot(W1, X) + b1
    A1 = np.tanh(Z1)
    Z2 = np.dot(W2, A1) + b2
    A2 = sigmoid(Z2)    
    assert(A2.shape == (1, X.shape[1]))


    cache = {"Z1": Z1,                   
             "A1": A1,                   
             "Z2": Z2,                  
             "A2": A2}    


    return A2, cache

从参数初始化结果字典里取到各自的参数,然后执行一次前向传播计算,将前向传播计算的结果保存到cache这个字典中, 其中A2为经过sigmoid激活函数激活后的输出层的结果。

计算当前训练损失

前向传播计算完成后我们需要确定以当前参数执行计算后的的输出与标签值之间的损失大小。与笔记1一样,损失函数同样选择为交叉熵损失:

e4b4d8397b517c0bfde23403571eea9062ff77.png

定义计算损失函数为:

def compute_cost(A2, Y, parameters):
    # 训练样本量
    m = Y.shape[1] 
    # 计算交叉熵损失
    logprobs = np.multiply(np.log(A2),Y) + np.multiply(np.log(1-A2), 1-Y)
    cost = -1/m * np.sum(logprobs)
    # 维度压缩
    cost = np.squeeze(cost)     


    assert(isinstance(cost, float))    
    return cost

执行反向传播

当前向传播和当前损失确定之后,就需要继续执行反向传播过程来调整权值了。中间涉及到各个参数的梯度计算,具体如下图所示:

a38daed2102d562ea4a297884f25e49ae047db.png

根据上述梯度计算公式定义反向传播函数:

def backward_propagation(parameters, cache, X, Y):
    m = X.shape[1]    
    # 获取W1和W2
    W1 = parameters['W1']
    W2 = parameters['W2']    
    # 获取A1和A2
    A1 = cache['A1']
    A2 = cache['A2']    
    # 执行反向传播
    dZ2 = A2-Y
    dW2 = 1/m * np.dot(dZ2, A1.T)
    db2 = 1/m * np.sum(dZ2, axis=1, keepdims=True)
    dZ1 = np.dot(W2.T, dZ2)*(1-np.power(A1, 2))
    dW1 = 1/m * np.dot(dZ1, X.T)
    db1 = 1/m * np.sum(dZ1, axis=1, keepdims=True)


    grads = {"dW1": dW1,
             "db1": db1,                      
             "dW2": dW2,             
             "db2": db2}   
    return grads

将各参数的求导计算结果放入字典grad进行返回。

这里需要提一下的是涉及到的关于数值优化方面的知识。在机器学习中,当所学问题有了具体的形式之后,机器学习就会形式化为一个求优化的问题。

不论是梯度下降法、随机梯度下降、牛顿法、拟牛顿法,抑或是 Adam 之类的高级的优化算法,这些都需要花时间去掌握其数学原理。

52094dc39c3d6a34f61865d8152ea3009de4d1.gif

迭代计算的最后一步就是根据反向传播的结果来更新权值了,更新公式如下:

094853288c3434936a12256a2d380c2f5e232a.png

由该公式可以定义权值更新函数为:

def update_parameters(parameters, grads, learning_rate=1.2):
    # 获取参数
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']    
    # 获取梯度
    dW1 = grads['dW1']
    db1 = grads['db1']
    dW2 = grads['dW2']
    db2 = grads['db2']    
    # 参数更新
    W1 -= dW1 * learning_rate
    b1 -= db1 * learning_rate
    W2 -= dW2 * learning_rate
    b2 -= db2 * learning_rate


    parameters = {"W1": W1, 
                  "b1": b1,            
                  "W2": W2,   
                  "b2": b2}    
    return parameters

这样,前向传播-计算损失-反向传播-权值更新的神经网络训练过程就算部署完成了。

当前了,跟之前几讲一样,为了更加pythonic一点,我们也将各个模块组合起来,定义一个神经网络模型:

def nn_model(X, Y, n_h, num_iterations=10000, print_cost=False):
    np.random.seed(3)
    n_x = layer_sizes(X, Y)[0]
    n_y = layer_sizes(X, Y)[2]    
    # 初始化模型参数
    parameters = initialize_parameters(n_x, n_h, n_y)
    W1 = parameters['W1']
    b1 = parameters['b1']
    W2 = parameters['W2']
    b2 = parameters['b2']    
    # 梯度下降和参数更新循环
    for i in range(0, num_iterations):        
    # 前向传播计算
        A2, cache = forward_propagation(X, parameters)        
        # 计算当前损失
        cost = compute_cost(A2, Y, parameters)        
        # 反向传播
        grads = backward_propagation(parameters, cache, X, Y)        
        # 参数更新
        parameters = update_parameters(parameters, grads, learning_rate=1.2)        
        # 打印损失
        if print_cost and i % 1000 == 0:            
            print ("Cost after iteration %i: %f" %(i, cost))    
            
    return parameters

模型主体完成之后也可以再定义一个基于训练结果的预测函数:

def predict(parameters, X): 
    A2, cache = forward_propagation(X, parameters)
    predictions = (A2>0.5)
    return predictions

下面我们便基于之前生成的数据来测试一下模型:

parameters = nn_model(X, Y, n_h = 4, 
                      num_iterations=10000, 
                      print_cost=True)

198e681405d3cace9b5403b8464c0530402495.png

经过9000次迭代后损失下降到了0.21。我们再来看一下测试的准确率:

# 预测准确率
predictions = predict(parameters, X)
print ('Accuracy: %d' % float((np.dot(Y,predictions.T) + 
      np.dot(1-Y,1-predictions.T))/float(Y.size)*100) + '%')

c5e9c0190fdf145ab7012588b44fd028e7f334.png

测试准确率达到0.9。

绘制神经网络的决策边界效果如下:

c4ee939151ee493714438048b2a6b8adcd981e.png

以上便是本节的主要内容,利用numpy手动搭建一个单层的神经网路。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK