【机器学习实战(五)】手写数字识别:sklearn包中神经网络库的使用

目录

    1. 神经网络相关知识点回顾
    • 1.1. 感知机:神经网络的最小单元
    • 1.2. 神经网络的学习:BackPropagation算法
      • 1.2.1. 标准BP算法
      • 1.2.2. 累积BP算法
    1. sklearn中神经网络库的简介
    • 2.1. 分类:MLPClassifier
    1. 编程实现

这是《西瓜书带学训练营·实战任务》系列的第五篇笔记

1. 神经网络相关知识点回顾

1.1. 感知机:神经网络的最小单元

神经网络中最基本的成分是神经元(neuron)模型,最常见的模型是M-P神经元模型,也可以称为感知机(Perceptron)

若对生物学有一点基础的了解,就可以知道上面构造的简化的M-P神经元模型几乎完全吻合生物学上的定义:

当前神经元通过与上游神经元之间的突触结构,接受来自上游的神经元1到n传递过来的信号x1,x2...xn,由于不同的上游信号传递过来它们的影响程度是不一样的,因此对它们进行加权求和,即∑wixi,则得到总的上游信号

另外当前神经元还有一个激活阈值θ,若上游信号的汇总信号强度大于θ,即则∑wixi-θ > 0,则当前神经元被激活,处于兴奋状态;若若上游信号的汇总信号强度小于θ,即则∑wixi-θ < 0,则当前神经元未被激活,处于抑制状态

对于生物学上的神经元来说,它只有两个状态:

  • “1”:对应神经元兴奋;
  • “0”:对应神经元抑制;

则它接受的信号为上游神经元的输出为 xi∈{0,1},它的输出信号为 y∈{0,1}

则它的理想激活函数是阶跃函数,即f(x)=sgn(x)

然而,阶跃函数具有不连续、不光滑等不太好的性质,因此实际上常用sigmoid函数作为激活函数

则此时激活函数为f(x)=sigmoid(∑wixi-θ),很明显若选择sigmod函数作为感知机的激活函数,则此时感知机等价于一个logistic回归分类器

类比logistic回归分类器

  • 对于线性可分的分类任务,感知机完全等价于logistic回归分类器

  • 对于线性不可分问题,logistic回归可以构造额外的高阶多项式:

    本质上是将原始特征空间中线性不可分的样本向高维特征空间进行映射,使得它们在新的高维特征空间中线性可分

    而感知器无法进行高维空间的映射,只能基于原始特征空间寻找线性判别边界,因此感知机在面对简单的非线性可分问题是,往往无法得到理想的判别面

感知机的学习规则:

1.2. 神经网络的学习:BackPropagation算法

神经网络的学习算法本质上是数学上常用的梯度下降(Gradient Descend)算法

依据在一轮神经网络的训练过程中所用到的训练样本的数量的不同,可分为以下两类:

  • 标准BP算法:每次网络训练只针对单个训练样本,一次训练就输入一个训练样本,更新一次网络参数;

  • 累积BP算法:每次网络训练只针对所有训练样本,一次将所有训练样本输入,更新一次网络参数;

1.2.1. 标准BP算法

以wh,j为例进行推导

在标准BP算法中,由于只对输入的一个训练样本进行网络参数的训练,因此它的目标函数为当前样本k的均方误差Ek

此时关键在于怎么推出△whj的表达式?

根据链式求导法则,Whj的影响链条为:

因此△whj可以写成:

中间推导过程省略,最后得到的△whj的表达式为:

1.2.2. 累积BP算法

累积BP算法的目标是最小化训练集的累积误差:

这是累积BP算法与标准BP算法的最大的也是最本质的差别

标准BP算法存在的问题:

每次更新只针对单个训练样本,参数更新得非常频繁,而且不同样本训练的效果可能出现相互“抵消”的现象

因此为了达到与累积误差相同的极小点,标准BP算法往往需要进行更多次的迭代

累积BP算法的优缺点:

  • 优点:直接对累积误差最小化,它在读取整个训练样本集D后才对参数进行一次更新,其参数更新的频率低得多

  • 缺点:累积误差下降到一点程度之后,进一步下降会非常缓慢

    此时标准BP算法往往会更快得到较好的解,因为其解的震荡性给它带来了一个好处——容易跳出局部最优

2. sklearn中神经网络库的简介

sklearn中神经网络的实现不适用于大规模数据应用。特别是 scikit-learn 不支持 GPU

2.1. MLPClassifier

神经网络又称为多层感知机(Multi-layer Perceptron,MLP)

sklearn中用MLP进行分类的类为MLPClassifier

主要参数说明:

参数 说明
hidden_​​layer_sizes tuple,length = n_layers - 2,默认值(100,)第i个元素表示第i个隐藏层中的神经元数量。
activation {‘identity’,‘logistic’,‘tanh’,‘relu’},默认’relu’ 隐藏层的激活函数:‘identity’,无操作激活,对实现线性瓶颈很有用,返回f(x)= x;‘logistic’,logistic sigmoid函数,返回f(x)= 1 /(1 + exp(-x));‘tanh’,双曲tan函数,返回f(x)= tanh(x);‘relu’,整流后的线性单位函数,返回f(x)= max(0,x)
slover {‘lbfgs’,‘sgd’,‘adam’},默认’adam’。权重优化的求解器:'lbfgs’是准牛顿方法族的优化器;'sgd’指的是随机梯度下降。'adam’是指由Kingma,Diederik和Jimmy Ba提出的基于随机梯度的优化器。注意:默认解算器“adam”在相对较大的数据集(包含数千个训练样本或更多)方面在训练时间和验证分数方面都能很好地工作。但是,对于小型数据集,“lbfgs”可以更快地收敛并且表现更好。
alpha float,可选,默认为0.0001。L2惩罚(正则化项)参数。
batch_size int,optional,默认’auto’。用于随机优化器的minibatch的大小。如果slover是’lbfgs’,则分类器将不使用minibatch。设置为“auto”时,batch_size = min(200,n_samples)
learning_rate {‘常数’,‘invscaling’,‘自适应’},默认’常数"。 用于权重更新。仅在solver ='sgd’时使用。'constant’是’learning_rate_init’给出的恒定学习率;'invscaling’使用’power_t’的逆缩放指数在每个时间步’t’逐渐降低学习速率learning_rate_, effective_learning_rate = learning_rate_init / pow(t,power_t);只要训练损失不断减少,“adaptive”将学习速率保持为“learning_rate_init”。每当两个连续的时期未能将训练损失减少至少tol,或者如果’early_stopping’开启则未能将验证分数增加至少tol,则将当前学习速率除以5。

MLPClassifier的训练使用BP算法,其使用交叉熵损失函数(Cross-Entropy loss function)

MLP的训练需要准备

  • Xm*n:m个训练样本的n维特征向量构成的特征矩阵

  • Ym:m训练样本的目标值组成的向量

from sklearn.neural_network import MLPClassifier

X = [[0., 0.], [1., 1.]]
y = [0, 1]

# 模型训练
clf = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(5, 2), random_state=1)
clf.fit(X, y)

# 模型预测
clf.predict([[2., 2.], [-1., -2.]])

以上训练好的MLP模型保存在clf对象中,该对象有以下两个子变量:

  • clf.coefs_:模型的一系列权重矩阵

    # 用以下命令查看每一层的权重矩阵的维度
    >>[coef.shape for coef in clf.coefs_]
    [(2, 5), (5, 2), (2, 1)]
    

    其中下标为 i 的权重矩阵表示第 i 层和第 i+1 层之间的权重

  • intercepts_:一系列偏置向量

    其中的下标为 i 的向量表示添加到第 i+1 层的偏置值

  • 多分类任务

    MLPClassifier 通过应用 Softmax 作为输出函数来支持多分类

    softmax回归本质上是将原先常见的二元分类任务神经网络的输出层采用的sigmoid激活函数换成softmax回归

    例如要利用神经网络进行k类的分类,则神经网络结构如下:

    那么最后一层即网络的输出层所采用的激活函数——softmax回归到底长什么样呢?

    本人的手写笔记

    因此该网络的输出层又被称为softmax layer

    因此在提供训练集时,若总共有k的类别,当某一个训练样本的类别为第i类时,它的目标值应该为[0, 0, ..., 1, ..., 0],只在向量的第i个位置标为1,其他位置都为0

  • 多标签分类任务

    一个样本可能属于多个类别。 对于每个类,原始输出经过 logistic 函数变换后,大于或等于 0.5 的值将进为 1,否则为 0。 对于样本的预测输出,值为 1 的索引位置表示该样本的分类类别

    >>> X = [[0., 0.], [1., 1.]]
    >>> y = [[0, 1], [1, 1]]
    >>> clf = MLPClassifier(solver='lbfgs', alpha=1e-5,
    ...                     hidden_layer_sizes=(15,), random_state=1)
    ...
    >>> clf.fit(X, y) 
    
    >>> clf.predict([[1., 2.]])
    array([[1, 1]])
    >>> clf.predict([[0., 0.]])
    array([[0, 1]])
    

3. 编程实现

对于神经网络,要说它的Hello world,莫过于识别手写数字了

首先要获取实验数据,下载地址:http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz

数组中的每一行都表示一个灰度图像。每个元素都表示像素所对应的灰度值。

数据集中将数据分成3类:训练集,验证集,测试集。一般情况下会将训练数据分为训练集和测试集

为什么要将训练数据分为训练集和测试集?

将原始数据分开,保证用于测试的数据是训练模型时从未遇到的数据可以使测试更客观。否则就像学习教课书的知识,又只考教课书的知识,就算不理解记下了就能得高分但遇到新问题就傻眼了。

好一点的做法就是用训练集当课本给他上课,先找出把课本知识掌握好的人,再参加由新题组成的月考即测试集,若是还是得分高,那就是真懂不是死记硬背了。

但这样选出来的模型实际是还是用训练集和测试集共同得到的,再进一步,用训练集和验证集反复训练和检测,得到最好的模型,再用测试集来一局定输赢即期末考试,这样选出来的就更好了。

本实验中为了方便将训练集与验证集合并

  1. 先载入数据,并简单查看一下数据

    from sklearn.neural_network import MLPClassifier
    import numpy as np
    import pickle
    import gzip
    import matplotlib.pyplot as plt
    
    # 加载数据
    with gzip.open(r"mnist.pkl.gz") as fp:
        training_data, valid_data, test_data = pickle.load(fp)
    
    X_training_data, y_training_data = training_data
    X_valid_data, y_valid_data = valid_data
    X_test_data, y_test_data = test_data
    
    # 合并训练集,验证集
    X_training = np.vstack((X_training_data, X_valid_data))
    y_training = np.append(y_training_data, y_valid_data)
    
    def show_data_struct():
        print X_training_data.shape, y_training_data.shape
        print X_valid_data.shape, y_valid_data.shape
        print X_test_data.shape, y_test_data.shape
        print X_training_data[0]
        print y_training_data[0]
    
    show_data_struct()
    
  2. 随便看几张训练图片

    def show_image():
        plt.figure(1)
        for i in range(10):
            image = X_training[i]   # 得到包含第i张图的像素向量,为1*768
            pixels = image.reshape((28, 28)) # 将原始像素向量转换为28*28的像素矩阵
            plt.subplot(5,2,i+1)
            plt.imshow(pixels, cmap='gray')
            plt.title(y_training[i])
            plt.axis('off')
        plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.45,
                            wspace=0.85)
        plt.show()
    
    show_image()
    
  3. 训练模型

    为了获得比默认参数更佳的模型,我们采用网格搜索法搜索更优的训练超参数,使用gridSearchCV实现,上文3.2.2.2. 调参方法:网格搜索中有较为详细的说明

    from sklearn.model_selection import GridSearchCV
    
    mlp = MLPClassifier()
    mlp_clf__tuned_parameters = {"hidden_layer_sizes": [(100,), (100, 30)],
                                     "solver": ['adam', 'sgd', 'lbfgs'],
                                     "max_iter": [20],
                                     "verbose": [True]
                                     }
    estimator = GridSearchCV(mlp, mlp_clf__tuned_parameters, n_jobs=6)
    estimator.fit(X_training, y_training)
    

参考资料:

(1) 周志华《机器学习:第5章 神经网络》

(2) 吴恩达《deeplearning.ai:改善深层神经网络》

(3) scikit-learn中文网《神经网络模型(有监督)》

(4) 啊噗不是阿婆主《sklearn 神经网络MLPclassifier参数详解》

(5) 多问Why.《SkLearn之MLP》

你可能感兴趣的:(【机器学习实战(五)】手写数字识别:sklearn包中神经网络库的使用)