机器学习算法高级教程-二-

116 阅读31分钟

机器学习算法高级教程(二)

原文:Pro Machine Learning Algorithms

协议:CC BY-NC-SA 4.0

六、梯度提升器

到目前为止,我们已经考虑了决策树和随机森林算法。我们看到随机森林是一种 bagging (bootstrap aggregating)算法,它结合了多个决策树的输出来进行预测。通常,在 bagging 算法中,并行生长树以获得所有树的平均预测,其中每棵树都建立在原始数据的样本上。

另一方面,梯度提升使用不同的格式进行预测。boosting 采用顺序方法来获得预测,而不是并行化树构建过程。在梯度提升中,每个决策树预测前一个决策树的误差,从而提升(改善)误差(梯度)。

在本章中,您将学习以下内容:

  • 梯度提升的工作细节
  • 梯度提升与随机森林有何不同
  • AdaBoost 的工作细节
  • 各种超参数对增压的影响
  • 如何在 R 和 Python 中实现梯度提升

梯度提升器

梯度是指建立模型后获得的误差或残差。助推指的是提高。这项技术被称为梯度提升器,简称 GBM。梯度提升是一种逐渐改善(减少)误差的方法。

为了了解 GBM 是如何工作的,让我们从一个简单的例子开始。假设给你一个 M 模型(基于决策树)来改进。假设当前模型准确率为 80%。我们希望在这方面有所改进。

我们将模型表述如下:

Y = M(x) + error

y 是因变量,M(x)是使用 x 个自变量的决策树。

现在我们将预测上一个决策树的错误:

error = G(x) + error2

G(x)是另一个决策树,它试图使用 x 个独立变量来预测误差。

在下一步中,与上一步类似,我们构建一个模型,尝试使用 x 个独立变量来预测error2:

error2 = H(x) + error3

现在我们将所有这些结合在一起:

Y = M(x) + G(x) + H(x) + error3

前面的等式可能具有大于 80%的准确度,因为单独的模型 M(单个决策树)具有 80%的准确度,而在上面的等式中,我们考虑 3 个决策树。

下一节将探讨 GBM 如何工作的细节。在后面的部分中,我们将了解 AdaBoost(自适应增强)算法是如何工作的。

GBM 的工作细节

以下是梯度提升的算法:

  1. 用简单的决策树初始化预测。
  2. 计算残差,即(实际预测)值。
  3. 构建另一个基于所有独立值预测残差的浅层决策树。
  4. 用新预测乘以学习率来更新原始预测。
  5. 重复第 2 步到第 4 步一定次数的迭代(迭代次数就是树的数量)。

实现上述算法的代码如下(github 中的代码和数据集为“GBM working details.ipynb”):

import pandas as pd
# importing dataset
data=pd.read_csv('F:/course/Logistic regression/credit_training.csv')
# removing irrelevant variables
data=data.drop(['Unnamed: 0'],axis=1)
# replacing null values
data['MonthlyIncome']=data['MonthlyIncome'].fillna(value=data['MonthlyIncome'].median())
data['NumberOfDependents']=data['NumberOfDependents'].fillna(value=data['NumberOfDependents'].median())
from sklearn.model_selection import train_test_split
# creating independent variables
X = data.drop('SeriousDlqin2yrs',axis=1)
# creating dependent variables
y = data['SeriousDlqin2yrs']
# creating train and test datasets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)

在前面的代码中,我们将数据集分为 70%的训练数据集和 30%的测试数据集。

# Build a decision tree
from sklearn.tree import DecisionTreeClassifier
depth_tree = DecisionTreeClassifier(criterion = "gini",max_depth=4, min_samples_leaf=10)
depth_tree.fit(X_train, y_train)

在前面的代码中,我们在原始数据上构建了一个简单的决策树,将SeriousDlqin2yrs作为因变量,其余变量作为自变量。

#Get the predictions on top of train dataset itself
dt_pred = depth_tree.predict_proba(X_train)
X_train['prediction']=dt_pred[:,1]

在前面的代码中,我们预测了第一个决策树的输出。这将有助于我们得出残差。

#Get the predictions on top of test dataset
X_test['prediction']=depth_tree.predict_proba(X_test)[:,1]

在前面的代码中,虽然我们计算了测试数据集中的输出概率,但请注意,我们不能计算残差,因为实际上,我们不允许查看测试数据集的因变量。作为前面代码的延续,我们将在下面的代码中构建 20 个残差决策树:

from sklearn.tree import DecisionTreeRegressor
import numpy as np
from sklearn.metrics import roc_auc_score
depth_tree2 = DecisionTreeRegressor(criterion = "mse",max_depth=4, min_samples_leaf=10)
for i in range(20):
    # Calculate residual
    train_errorn=y_train-X_train['prediction']
    # remove prediction variable that got appended to independent variable earlier
    X_train2=X_train.drop(['prediction'],axis=1)
    X_test2=X_test.drop(['prediction'],axis=1)

在前面的代码中,注意我们正在计算第 n 棵决策树的残差。我们将从X_train2数据集中删除prediction列,因为prediction列不能是在for循环的下一次迭代中构建的后续模型中的独立变量之一。

    # Build a decision tree to predict the residuals using independent variables
    dt2=depth_tree2.fit(X_train2, train_errorn)
    # predict the residual
    dt_pred_train_errorn = dt2.predict(X_train2)

在前面的代码中,我们正在拟合决策树,其中因变量是残差,自变量是数据集的原始自变量。

一旦决策树被拟合,下一步就是预测残差(它是因变量):

    # update the predictions based on predicted residuals
    X_train['prediction']=(X_train['prediction']+dt_pred_train_errorn*1)
    # Calculate AUC
    train_auc=roc_auc_score(y_train,X_train['prediction'])
    print("AUC on training data set is: "+str(train_auc))

在这段代码中,原始预测(存储在X_train数据集中)用我们在上一步中获得的预测残差进行了更新。

注意,我们是通过残差的预测(dt_pred_train_errorn)来更新我们的预测。我们在前面的代码中明确给出了一个*1,因为收缩率或学习率的概念将在下一节解释(?? 将被替换为*learning_rate)。

一旦预测被更新,我们计算训练数据集的 AUC:

    # update the predictions based on predicted residuals for test dataset
    dt_pred_test_errorn = dt2.predict(X_test2)
    X_test['prediction']=(X_test['prediction']+dt_pred_test_errorn)
    # Calculate AUC
    test_auc=roc_auc_score(y_test,X_test['prediction'])
    print("AUC on test data set is: "+str(test_auc))

在这里,我们更新了测试数据集上的预测。我们不知道测试数据集的残差,但是我们基于为预测训练数据集的残差而构建的决策树来更新对测试数据集的预测。理想情况下,如果测试数据集没有残差,预测的残差应该接近 0,如果测试数据集的原始决策树有一些残差,那么预测的残差将远离 0。

一旦测试数据集的预测被更新,我们打印出测试数据集的 AUC。

让我们看看前面代码的输出:

A463052_1_En_6_Figb_HTML.jpg

A463052_1_En_6_Figa_HTML.jpg

注意,训练数据集的 AUC 随着更多的树而持续增加。但是测试数据集的 AUC 在某次迭代后降低。

收缩

GBM 是基于决策树的。因此,就像随机森林算法一样,GBM 的准确性取决于所考虑的树的深度、建立的树的数量和终端节点中的最小观察数量。收缩率是 GBM 中的一个附加参数。让我们看看,如果我们改变学习率/收缩,训练和测试数据集 AUC 的输出会发生什么。我们将学习率初始化为 0.05,并运行更多的树:

from sklearn.model_selection import train_test_split
# creating independent variables
X = data.drop('SeriousDlqin2yrs',axis=1)
# creating dependent variables
y = data['SeriousDlqin2yrs']
# creating train and test datasets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=42)

from sklearn.tree import DecisionTreeClassifier
depth_tree = DecisionTreeClassifier(criterion = "gini",max_depth=4, min_samples_leaf=10)
depth_tree.fit(X_train, y_train)

#Get the predictions on top of train and test datasets
dt_pred = depth_tree.predict_proba(X_train)
X_train['prediction']=dt_pred[:,1]
X_test['prediction']=depth_tree.predict_proba(X_test)[:,1]
from sklearn.tree import DecisionTreeRegressor
import numpy as np
from sklearn.metrics import roc_auc_score
depth_tree2 = DecisionTreeRegressor(criterion = "mse",max_depth=4, min_samples_leaf=10)
learning_rate = 0.05
for i in range(20):
    # Calculate residual
    train_errorn=y_train-X_train['prediction']
    # remove prediction variable that got appended to independent variable earlier
    X_train2=X_train.drop(['prediction'],axis=1)
    X_test2=X_test.drop(['prediction'],axis=1)
    # Build a decision tree to predict the residuals using independent variables
    dt2=depth_tree2.fit(X_train2, train_errorn)
    # predict the residual
    dt_pred_train_errorn = dt2.predict(X_train2)
    # update the predictions based on predicted residuals
    X_train['prediction']=(X_train['prediction']+dt_pred_train_errorn*learning_rate)
    # Calculate AUC
    train_auc=roc_auc_score(y_train,X_train['prediction'])
    print("AUC on training data set is: "+str(train_auc))
    # update the predictions based on predicted residuals for test dataset
    dt_pred_test_errorn = dt2.predict(X_test2)
    X_test['prediction']=(X_test['prediction']+dt_pred_test_errorn*learning_rate)
    # Calculate AUC
    test_auc=roc_auc_score(y_test,X_test['prediction'])
    print("AUC on test data set is: "+str(test_auc))

上述代码的输出是:

下面是前几棵树的输出:

A463052_1_En_6_Figc_HTML.jpg

这是最后几棵树的产量:

A463052_1_En_6_Figd_HTML.jpg

与前面的情况不同,其中learning_rate = 1,较低的learning_rate导致测试数据集 AUC 随着训练数据集 AUC 一致增加。

adaboost 算法

在讨论其他增强方法之前,我想和我们在前面章节中看到的做一个比较。在计算逻辑回归的误差度量时,我们可以使用传统的平方误差。但是我们转向了熵误差,因为它对大量的误差惩罚更大。

以类似的方式,残差计算可以根据因变量的类型而变化。对于连续的因变量,残差计算可以是高斯型的(因变量的(实际-预测)),而对于离散变量,残差计算可以不同。

AdaBoost 理论

AdaBoost 是 adaptive boosting 的缩写。下面是高级算法:

  1. 仅使用几个独立变量构建一个弱学习器(在这种情况下是决策树技术)。
    1. 注意,在构建第一个弱学习者时,与每个观察相关联的权重是相同的。 
  2. 识别基于弱学习者的不正确分类的观察。
  3. 以这样的方式更新观察值的权重,使得先前弱学习器中的错误分类被给予更大的权重,而先前弱学习器中的正确分类被给予更小的权重。
  4. 根据预测的准确性为每个弱学习者分配一个权重。
  5. 最终预测将基于多个弱学习者的加权平均预测。

自适应潜在地指的是根据先前的分类是正确的还是不正确的来更新观察值的权重。提升可能指的是给每个弱学习者分配权重。

AdaBoost 的工作细节

让我们看一个 AdaBoost 的例子:

  1. Build a weak learner. Let’s say the dataset is the first two columns in the following table (available as “adaboost.xlsx” in github):

    A463052_1_En_6_Fige_HTML.jpg

    Once we have the dataset, we build a weak learner (decision tree) according to the steps laid out to the right in the preceding table. From the table we can see that X <= 4 is the optimal splitting criterion for this first decision tree.  

  2. Calculate the error metric (the reason for having “Changed Y” and “Yhat” as new columns in the below table will be explained after step 4):

    A463052_1_En_6_Figf_HTML.jpg

    The formulae used to obtain the preceding table are as follows:

    A463052_1_En_6_Figg_HTML.jpg

  3. 计算应该与第一个弱学习者关联的权重:0.5×log((1-误差)/误差)= 0.5×log(0.9/0.1)= 0.5×log(9)= 0.477

  4. Update the weights associated with each observation in such a way that the previous weak learner’s misclassifications have high weight and the correct classifications have low weight (essentially, we are tuning the weights associated with each observation in such a way that, in the new iteration, we try and make sure that the misclassifications are predicted more accurately):

    A463052_1_En_6_Figh_HTML.jpg

    Note that the updated weights are calculated by the following formula:

     {original\kern0.34em weight}^{\ast}\kern0.125em {e}^{\left(- weightage\ oflearner\ast yhat\ast changedy\right)}

    That formula should explain the need for changing the discrete values of y from {0,1} to {–1,1}. By changing 0 to –1, we are in a position to perform the multiplication better. Also note that in the preceding formula, weightage associated with the learner in general would more often than not be positive. When yhat and changed_y are the same, the exponential part of formula would be a lower number (as the – weightage of learner × yhat × changed_y part of the formula would be negative, and an exponential of a negative is a small number). When yhat and changed_y are different values, that’s when the exponential would be a bigger number, and hence the updated weight would be more than the original weight.  

  5. We observe that the updated weights we obtained earlier do not sum up to 1. We update each weight in such a way that the sum of weights of all observations is equal to 1. Note that, the moment weights are introduced, we can consider this as a regression exercise. Now that the weight for each observation is updated, we repeat the preceding steps until the weight of the misclassified observations increases so much that it is now correctly classified:

    A463052_1_En_6_Figi_HTML.jpg

    Note that the weights in the third column in the preceding table are updated based on the formula we derived earlier post normalization (ensuring that the sum of weights is 1). You should be able to see that the weight associated with misclassification (the eighth observation with the independent variable value of 8) is more than any other observation. Note that although everything is similar to a typical decision tree till the prediction columns, error calculation gives emphasis to weights of observations. error in the left node is the summation of the weight of each observation that was misclassified in the left node and similarly for the right node. overall error is the summation of error across both nodes (error in left node + error in right node). In this instance, overall error is still the least at the fourth observation. The updated weights based on the previous step are as follows:

    A463052_1_En_6_Figj_HTML.jpg

    Continue the process one more time:

    A463052_1_En_6_Figk_HTML.jpg

    From the preceding table, note that overall error in this iteration is minimum at X <= 8, as the weight associated with the eighth observation is a lot more than other observations, and hence overall error came up as the least at the eighth observation this time. However, note that the weightage associated with the preceding tree would be low, because the accuracy of that tree is low when compared to the previous two trees.  

  6. 一旦做出所有预测,观察的最终预测被计算为与每个弱学习者相关联的权重的总和乘以每个观察的概率输出。

GBM 的附加功能

在上一节中,我们看到了如何手工构建 GBM。在本节中,我们将了解可以内置的其他参数:

  • 行抽样:在随机森林中,我们看到对随机选择的行进行抽样会产生一个更通用、更好的模型。在 GBM 中,我们也可以对行进行采样,以进一步提高模型性能。
  • 列采样:与行采样类似,通过对每个决策树的列进行采样,可以避免一定程度的过度拟合。

随机森林和 GBM 技术都是基于决策树的。然而,随机森林可以被认为是并行构建多棵树,最终我们将所有多棵树的平均值作为最终预测。在 GBM 中,我们构建多棵树,但是是按顺序的,其中每棵树都试图预测其前一棵树的残差。

用 Python 实现 GBM

GBM 可以使用如下的scikit-learn库在 Python 中实现(代码在 github 中以“GBM.ipynb”的形式提供):

from sklearn import ensemble
gb_tree = ensemble.GradientBoostingClassifier(loss='deviance',learning_rate=0.05,n_estimators=100,min_samples_leaf=10,max_depth=13,max_features=2,subsample=0.7,random_state=10)
gb_tree.fit(X_train, y_train)

注意,关键输入参数是损失函数(无论是正常残差方法还是基于 AdaBoost 的方法)、学习速率、树的数量、每棵树的深度、列采样和行采样。

一旦建立了 GBM,就可以进行如下预测:

from sklearn.metrics import roc_auc_score
gb_pred=gb_tree.predict_proba(X_test)
roc_auc_score(y_test, gb_pred[:,1])

在 R 中实现 GBM

R 中的 GBM 和 Python 中的 GBM 有相似的参数。GBM 可以按如下方式实现:

A463052_1_En_6_Figl_HTML.jpg

在该公式中,我们以下列方式指定因变量和自变量:dependent_variable ~要使用的自变量集合。

该分布指定它是高斯、伯努利还是 AdaBoost 算法。

library(gbm)
gb=gbm(SeriousDlqin2yrs~.,data=train,n.trees=10,interaction.depth=5,shrinkage=0.05)

  • n. trees指定要建造的树的数量。
  • interaction.depth是树木的max_depth
  • n.minobsinnode是一个节点中的最小观察次数。
  • shrinkage是学习率。
  • bag.fraction是随机选择的训练集观察值的一部分,用于提出扩展中的下一棵树。
  • R 中的 GBM 算法运行如下:

可以做出如下预测:

pred=predict(gb,test,n.trees=10,type="response")

摘要

在本章中,您学习了以下内容:

  • GBM 是一种基于决策树的算法,它试图预测给定决策树中前一个决策树的残差。
  • 收缩和深度是 GBM 中需要调整的一些更重要的参数。
  • 梯度提升和自适应增强的区别。
  • 调整学习率参数如何提高 GBM 中的预测精度。

七、人工神经网络

人工神经网络是一种监督学习算法,它利用多个超参数的混合来帮助逼近输入和输出之间的复杂关系。人工神经网络中的一些超参数包括:

  • 隐藏层数
  • 隐藏单元的数量
  • 激活功能
  • 学习率

在本章中,您将学习以下内容:

  • 神经网络的工作细节
  • 各种超参数对神经网络的影响
  • 前馈和反向传播
  • 学习率对权重更新的影响
  • 避免神经网络中过拟合的方法
  • 如何在 Excel、Python 和 R 中实现神经网络

神经网络源于这样一个事实,即并非所有东西都可以用线性/逻辑回归来近似,数据中可能存在只能用复杂函数近似的潜在复杂形状。函数越复杂(以某种方式处理过拟合),预测的准确性就越好。我们将从研究神经网络如何将数据拟合到模型中开始。

神经网络的结构

神经网络的典型结构如图 7-1 所示。

A463052_1_En_7_Fig1_HTML.jpg

图 7-1

Neural network structure

图中的输入级别/层通常是用于预测输出(因变量)级别/层的独立变量。通常,在回归问题中,输出层只有一个节点,而在分类问题中,输出层包含的节点数量与因变量中存在的类(不同值)的数量一样多。隐藏级别/层用于将输入变量转换为高阶函数。隐藏层转换输出的方式如图 7-2 所示。

A463052_1_En_7_Fig2_HTML.jpg

图 7-2

Transforming the output

在图 7.2 中,x1 和 x2 是独立变量,b0 是偏差项(类似于线性/逻辑回归中的偏差)。w1 和 w2 是赋予每个输入变量的权重。若 a 是隐藏层中的一个单元/神经元,则等于:

a=f\left(\sum \limits_{i=0}^N{w}_i{x}_i\right)

前面等式中的函数是我们在求和之上应用的激活函数,以便我们获得非线性(我们需要非线性,以便我们的模型现在可以学习复杂的模式)。不同的激活功能将在后面的章节中详细讨论。

此外,具有一个以上的隐藏层有助于实现高度非线性。我们希望实现高度非线性,因为没有它,神经网络将是一个巨大的线性函数。

当神经网络必须理解非常复杂、有上下文关系或不明显的东西(如图像识别)时,隐藏层是必要的。深度学习这个术语来源于拥有许多隐藏层。这些图层被称为隐藏图层,因为它们在网络输出中不可见。

训练神经网络的工作细节

训练一个神经网络基本上意味着通过重复两个关键步骤来校准所有的权重:前向传播和反向传播。

在前向传播中,我们将一组权重应用于输入数据并计算输出。对于第一次正向传播,随机初始化该组权重值。

在反向传播中,我们测量输出的误差幅度,并相应地调整权重以减小误差。

神经网络重复前向和反向传播,直到权重被校准以准确预测输出。

正向传播

让我们通过一个训练神经网络作为异或(XOR)运算的简单例子来说明训练过程中的每个步骤。XOR 函数可以通过输入和输出的映射来表示,如下表所示,我们将使用它作为训练数据。给定 XOR 函数可接受的任何输入,它应该提供正确的输出。

| 投入 | 输出 | | :-- | :-- | | (0,0) | Zero | | (0,1) | one | | (1,0) | one | | (1,1) | Zero |

让我们使用上表的最后一行,(1,1) => 0 来演示正向传播,如图 7-3 所示。请注意,虽然这是一个分类问题,但我们仍然将它视为回归问题,只是为了理解向前和向后传播是如何工作的。

A463052_1_En_7_Fig3_HTML.jpg

图 7-3

Applying a neural network

我们现在给所有的突触分配权重。请注意,这些权重是随机选择的(最常见的方式是基于高斯分布),因为这是我们第一次正向传播。初始权重随机分配在 0 和 1 之间(但注意最终权重不需要在 0 和 1 之间),如图 7-4 所示。

A463052_1_En_7_Fig4_HTML.jpg

图 7-4

Weights on the synapses

我们将输入与其对应的一组权重的乘积相加,得出隐藏层的第一个值(图 7-5 )。您可以将权重视为输入节点对输出的影响度量:

A463052_1_En_7_Fig5_HTML.jpg

图 7-5

Values for the hidden layer

  • 1 × 0.8 + 1 × 0.2 = 1
  • 1 × 0.4 + 1 × 0.9 = 1.3
  • 1 × 0.3 + 1 × 0.5 = 0.8

应用激活功能

激活函数应用于神经网络的隐藏层。激活功能的目的是将输入信号转换成输出信号。它们对于神经网络建模复杂的非线性模式是必要的,而简单的模型可能会遗漏这些模式。

一些主要的激活函数如下:

\mathrm{Sigmoid}=1/\left(1+{e}^{-x}\right)

Tanh=\frac{e^x-{e}^{-x}}{e^x+{e}^{-x}}

整流线性单位= x 如果 x > 0,否则 0

对于我们的例子,让我们使用 sigmoid 函数来激活。并将 Sigmoid(x)应用于三个隐藏层和,我们得到图 7-6 :

A463052_1_En_7_Fig6_HTML.jpg

图 7-6

Applying sigmoid to the hidden layer sums

  • Sigmoid(1.0) = 0.731
  • Sigmoid(1.3) = 0.785
  • Sigmoid(0.8) = 0.689

然后,我们将隐藏层结果与第二组权重(第一次也是随机确定的)的乘积求和,以确定输出和:

  • 0.73 × 0.3 + 0.79 × 0.5 + 0.69 × 0.9 = 1.235

A463052_1_En_7_Fig7_HTML.jpg

图 7-7

Applying the activation function

因为我们使用了一组随机的初始权重,所以输出神经元的值偏离了目标值—在本例中,偏离了 1.235(因为目标值是 0)。

在 excel 中,上述内容如下所示(在 github 中,Excel 的名称为“NN.xlsx”):

A463052_1_En_7_Figa_HTML.jpg

  1. 输入层有两个输入(1,1),因此输入层的维数为 1 × 2(因为每个输入有两个不同的值)。
  2. 1 × 2 隐藏层乘以 2 × 3 维的随机初始化矩阵。
  3. 隐藏层的输入输出是一个 1 × 3 矩阵:

上述输出的公式如下:

A463052_1_En_7_Figc_HTML.jpg

A463052_1_En_7_Figb_HTML.jpg

激活函数的输出乘以 3 × 1 维随机初始化矩阵,得到 1 × 1 维的输出:

A463052_1_En_7_Figd_HTML.jpg

获得前面输出的方法是按照下面的公式:

A463052_1_En_7_Fige_HTML.jpg

同样,虽然这是一个分类练习,其中我们使用交叉熵误差作为损失函数,但我们仍将使用平方误差损失函数,只是为了使反向传播计算更容易理解。我们将在后面的章节中了解分类在神经网络中是如何工作的。

一旦我们有了输出,我们计算平方误差(总误差),即(1.233-0) 2 ,如下所示:

A463052_1_En_7_Figf_HTML.jpg

从输入层获得平方误差所涉及的各个步骤共同形成了前向传播。

反向传播

在前向传播中,我们从输入到隐藏到输出迈出了一步。在反向传播中,我们采用相反的方法:本质上,从最后一层开始少量改变每个权重,直到达到最小可能误差。当重量改变时,总误差要么减小,要么增大。根据误差是增加还是减少,决定权重更新的方向。此外,在一些情况下,对于重量的小变化,误差增加/减少相当多,并且在一些情况下,误差仅改变很小的量。

总而言之,通过少量更新权重并测量误差变化,我们能够做到以下几点:

  1. 决定权重需要更新的方向
  2. 决定权重需要更新的幅度

在继续在 Excel 中实现反向传播之前,让我们看看神经网络的另一个方面:学习速率。学习率有助于我们建立对权重更新决策的信任。例如,在决定权重更新的幅度时,我们可能不会一次改变所有的东西,而是采取更谨慎的方法来更慢地更新权重。这导致在我们的模型中获得稳定性。后面的部分将讨论学习速度如何有助于稳定性。

计算反向传播

为了了解反向传播是如何工作的,让我们来看看上一节中更新随机初始化的权重值。

具有随机初始化的权重值的网络的总误差是 1.52。让我们将隐藏层与输出层之间的权重从 0.3 更改为 0.29,并查看对整体误差的影响:

A463052_1_En_7_Figg_HTML.jpg

请注意,随着重量的小幅下降,总误差从 1.52 降至 1.50。因此,根据前面提到的两点,我们得出结论,0.3 需要降低到一个更低的数字。在决定了权重需要更新的方向之后,我们需要回答的问题是:“权重更新的幅度是多少?”

如果通过少量(0.01)改变权重,误差减少了很多,那么潜在地,权重可以更新更大的量。但是如果当权重被少量更新时,误差仅减少少量,那么权重需要被缓慢地更新。因此,隐藏层与输出层之间值为 0.3 的权重更新如下:

  • 0.3–0.05×(因重量变化而减少误差)

0.05 是学习参数,由用户输入——下一节将详细介绍学习率。因此,权重值更新为 0.3–0.05×((1.52-1.50)/0.01)= 0.21。

类似地,其他权重更新为 0.403 和 0.815:

A463052_1_En_7_Figh_HTML.jpg

注意,通过改变连接隐藏层和输出层的权重,整体误差减少了很多。

既然我们已经更新了一个层中的权重,我们将更新网络早期部分中存在的权重,即输入层和隐藏层激活之间的权重。让我们改变重量值并计算误差的变化:

| 原重 | 更新重量 | 误差减少 | | :-- | :-- | :-- | | Zero point eight | 0.7957 | 0.0009 | | Zero point four | 0.3930 | 0.0014 | | Zero point three | 0.2820 | 0.0036 | | Zero point two | 0.1957 | 0.0009 | | Zero point nine | 0.8930 | 0.0014 | | Zero point five | 0.4820 | 0.0036 |

假设每次当权重减小一个小值时,误差都在减小,我们将把所有权重减小到上面计算的值。

现在权重已更新,请注意总误差从 1.52 降至 1.05。我们不断重复向前和向后传播,直到总误差尽可能最小。

随机梯度下降

梯度下降是在刚刚讨论的场景中误差最小化的方式。梯度代表差异(实际和预测之间的差异),下降意味着减少。random 代表训练数据的子集,用于计算误差,从而更新权重(在后面的部分中会详细介绍数据子集)。

深入梯度下降

为了加深我们对梯度下降神经网络的理解,让我们从一个已知的函数开始,看看如何导出权重:现在,我们将已知的函数设为 y = 6 + 5x。

数据集如下所示(在 github 中以“gradient descent batch size.xlsx”的形式提供):

| x | y | | :-- | :-- | | one | Eleven | | Two | Sixteen | | three | Twenty-one | | four | Twenty-six | | five | Thirty-one | | six | Thirty-six | | seven | Forty-one | | eight | Forty-six | | nine | Fifty-one | | Ten | fifty-six |

让我们将参数 a 和 b 随机初始化为值 2 和 3(其理想值为 5 和 6)。权重更新的计算如下:

A463052_1_En_7_Figx_HTML.png

注意,我们是从用 2 和 3(第 1 行,第 3 列和第 4 列)随机初始化a_estimateb_estimate估计值开始的。

我们计算如下:

  • 使用 a 和 b 的随机初始化值计算 y 的估计值:5。
  • 计算对应于 a 和 b 的值的平方误差(第 1 行中的 36)。
  • 稍微改变 a 的值(增加 0.01),并计算与改变后的 a 值相对应的平方误差。这被存储为error_change_a列。
  • 计算delta_error_a中的误差变化(误差变化/ 0.01)。注意,如果我们对 a 的损失函数进行微分,δ将非常相似。
  • 根据:new_a = a_estimate + ( delta_error_a ) × learning_rate更新 a 的值。

在本分析中,我们认为学习率为 0.01。对 b 的更新估计值进行相同的分析。下面是与刚才描述的计算相对应的公式:

A463052_1_En_7_Figk_HTML.jpg

A463052_1_En_7_Figj_HTML.jpg

一旦更新了 a 和 b 的值(new_a 和 new_b 在第一行中计算),对第 2 行执行相同的分析(注意,我们从第 2 行开始,使用从上一行获得的 a 和 b 的更新值。)我们不断更新 a 和 b 的值,直到覆盖了所有的数据点。最后,a 和 b 的更新值分别为 2.75 和 5.3。

既然我们已经运行了整个数据集,我们将用 2.75 和 5.3 重复整个过程,如下所示:

A463052_1_En_7_Figl_HTML.png

a 和 b 的值从 2.75 和 5.3 开始,到 2.87 和 5.29 结束,比上一次迭代精确一点。随着更多的迭代,a 和 b 的值将收敛到最优值。

我们已经研究了基本梯度下降的工作细节,但是其他优化器也执行类似的功能。其中一些如下:

  • RMSprop
  • 阿达格拉德
  • 阿达德尔塔
  • 圣经》和《古兰经》传统中)亚当(人类第一人的名字
  • 阿达玛斯
  • 那达慕

为什么要有学习率?

在刚才讨论的场景中,由于学习率为 0.01,我们将权重从 2.3 移动到 2.75 和 5.3。让我们看看学习率为 0.05 时权重会如何变化:

A463052_1_En_7_Figm_HTML.png

请注意,在学习率从 0.01 变为 0.05 的时刻,在这种特定情况下,a 和 b 的值在后面的数据点上开始出现异常变化。因此,较低的学习率总是首选。但是,请注意,较低的学习速率会导致获得最佳结果的时间较长(迭代次数较多)。

批量训练

到目前为止,我们已经看到 a 和 b 的值在数据集的每一行都得到更新。然而,这可能不是一个好主意,因为变量值会显著影响 a 和 b 的值。因此,误差计算通常是对一批数据进行的,如下所示。假设批量大小为 2(在前面的例子中,批量大小为 1):

| x | y | 估计值 | b _ 估计 | y _ 估计值 | 平方误差 | 错误 _ 改变 _a | 增量 _ 误差 _a | 新 _a | 错误 _ 更改 _b | 增量 _ 误差 _b | 新 _b | | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | --: | | one | Eleven | Two | three | five | Thirty-six | 35.8801 | Eleven point nine nine |   | 35.8801 | Eleven point nine nine |   | | Two | Sixteen | Two | three | eight | Sixty-four | 63.8401 | Fifteen point nine nine |   | 63.6804 | Thirty-one point nine six |   | |   |   |   |   | 全部的 | One hundred | 99.7202 | Twenty-seven point nine eight | 2.2798 | 99.5605 | Forty-three point nine five | 3.4395 |

现在对于下一批,a 和 b 的更新值是 2.28 和 3.44:

| x | y | 估计值 | b _ 估计 | y _ 估计值 | 平方误差 | 错误 _ 改变 _a | 增量 _ 误差 _a | 新 _a | 错误 _ 更改 _b | 增量 _ 误差 _b | 新 _b | | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | :-- | | three | Twenty-one | Two point two eight | Three point four four | Twelve point six | Seventy point five nine | Seventy point four two | Sixteen point seven nine |   | Seventy point zero nine | Fifty point three two |   | | four | Twenty-six | Two point two eight | Three point four four | Sixteen point zero four | Ninety-nine point two five | Ninety-nine point zero five | Nineteen point nine one |   | Ninety-eight point four five | Seventy-nine point five four |   | |   |   |   |   | 全部的 | One hundred and sixty-nine point eight three | One hundred and sixty-nine point four seven | Thirty-six point seven one | Two point six five | One hundred and sixty-eight point five four | One hundred and twenty-nine point eight six | Four point seven four |

a 和 b 的更新值现在是 2.65 和 4.74,并且迭代继续。请注意,在实践中,批量大小至少为 32。

Softmax 的概念

到目前为止,在 Excel 实现中,我们已经执行了回归,而不是分类。当我们执行分类时要注意的关键区别是输出被限制在 0 和 1 之间。在二进制分类的情况下,输出图层将有两个结点,而不是一个。一个节点对应于输出 0,另一个对应于输出 1。

现在,我们来看看当输出层有两个节点时,我们的计算如何根据上一节的讨论而变化(其中输入为 1,1,预期输出为 0)。假设输出为 0,我们将对输出进行如下单热编码:[1,0],其中第一个索引值对应于输出 0,第二个索引值对应于输出 1。

连接隐藏层和输出层的权重矩阵更改如下:它不是 3 × 1 矩阵,而是 3 × 2 矩阵,因为隐藏层现在连接到两个输出节点(与回归练习不同,它连接到 1 个节点):

A463052_1_En_7_Fign_HTML.jpg

请注意,因为输出节点是 to,所以我们的输出层也包含两个值,如下所示:

A463052_1_En_7_Figo_HTML.jpg

上述输出的一个问题是它的值大于 1(在其他情况下,这些值也可能小于 0)。

在输出超出 0 到 1 之间的期望值的情况下,Softmax 激活就派上了用场。上述输出的 Softmax 计算如下:

在下面的 Softmax 步骤 1 中,输出被提升到其指数值。请注意,3.43 是 1.233 的指数:

A463052_1_En_7_Figp_HTML.jpg

在下面的 Softmax 步骤 2 中,softmax 输出被归一化以获得概率,使得两个输出的概率之和为 1:

A463052_1_En_7_Figq_HTML.jpg

注意 0.65 的值是由 3.43 / (3.43 + 1.81)得到的。

现在我们有了概率值,而不是计算总平方误差,我们计算交叉熵误差,如下所示:

  1. The final softmax step is compared with actual output:

    A463052_1_En_7_Figr_HTML.jpg

  2. The cross entropy error is calculated based on the actual values and the predicted values (which are obtained from softmax step 2):

    A463052_1_En_7_Figs_HTML.jpg

请注意公式面板中的交叉熵误差公式。

现在我们有了最终的误差度量,我们再次部署梯度下降来最小化总交叉熵误差。

不同的损失优化函数

可以针对不同的度量进行优化,例如,回归中的平方误差和分类中的交叉熵误差。可以优化的其他损失函数包括:

  • 均方误差
  • 平均绝对百分比误差
  • 均方对数误差
  • 方形铰链
  • 关键
  • 分类铰链
  • 对数曲线
  • 范畴交叉熵
  • 稀疏分类交叉熵
  • 二元交叉熵
  • 库尔贝克莱布勒散度
  • 泊松
  • 余弦接近度

缩放数据集

通常,当我们缩放输入数据集时,神经网络表现良好。在这一节中,我们将了解缩放的原因。为了了解缩放对输出的影响,我们将对比两个场景。

不缩放输入的场景

A463052_1_En_7_Figt_HTML.jpg

在上表中,计算了各种方案,其中输入总是相同的 255,但是在每个方案中乘以输入的权重是不同的。请注意,即使重量变化很大,sigmoid 输出也不会改变。那是因为权重乘以一个大数,其输出也是一个大数。

输入缩放的场景

在这种情况下,我们将不同的权重值乘以一个小的输入数,如下所示:

A463052_1_En_7_Figu_HTML.jpg

现在权重乘以一个较小的数,对于不同的权重值,sigmoid 输出会有很大的不同。

由于权重需要缓慢调整以达到最佳权重值,因此独立变量数量较大的问题非常严重。假设权重调整缓慢(按照梯度下降中的学习速率),当输入是高数量级时,可能需要相当长的时间来达到最佳权重。因此,要获得最佳权重值,最好首先缩放数据集,这样我们就可以将输入作为一个小数字。

用 Python 实现神经网络

在 Python 中实现神经网络有几种方法。在这里,我们将看看使用keras框架实现神经网络。你必须安装tensorflow / theano,和keras才能实现神经网络。

A463052_1_En_7_Figw_HTML.jpg

  1. Download the dataset and extract the train and test dataset (code available as “NN.ipynb” in github)

    A463052_1_En_7_Fig8_HTML.jpg

    图 7-8

    The output

    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    # load (downloaded if needed) the MNIST dataset
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # plot 4 images as gray scale
    plt.subplot(221)
    plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
    plt.subplot(222)
    plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
    plt.subplot(223)
    plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
    plt.subplot(224)
    plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
    # show the plot
    plt.show()
    
    
  2. 导入相关包:

    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.utils import np_utils
    
    
  3. 预处理数据集:

    num_pixels = X_train.shape[1] * X_train.shape[2]
    # reshape the inputs so that they can be passed to the vanilla NN
    X_train = X_train.reshape(X_train.shape[0],num_pixels ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],num_pixels).astype('float32')
    # scale inputs
    X_train = X_train / 255
    X_test = X_test / 255
    # one hot encode the output
    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_test.shape[1]
    
    
  4. Build a model:

    A463052_1_En_7_Figv_HTML.jpg

    # building the model
    model = Sequential()
    # add 1000 units in the hidden layer
    # apply relu activation in hidden layer
    model.add(Dense(1000, input_dim=num_pixels,activation='relu'))
    # initialize the output layer
    model.add(Dense(num_classes, activation="softmax"))
    # compile the model
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    # extract the summary of model
    model.summary()
    
    
  5. 运行模型:

    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

请注意,随着历元数量的增加,测试数据集的准确性也会增加。此外,在keras中,我们只需要指定第一层的输入尺寸,它会自动计算其余层的尺寸。

使用正则化避免过拟合

即使我们已经缩放了数据集,神经网络也可能在训练数据集上过度拟合,因为损失函数(平方误差或交叉熵误差)确保损失随着时期数量的增加而最小化。

然而,在训练损失不断减少的同时,测试数据集上的损失并不一定也在减少。神经网络中的权重(参数)越多,在训练数据集上过度拟合从而无法在看不见的测试数据集上推广的可能性就越大。

让我们在 MNIST 数据集上对比使用相同神经网络架构的两个场景,其中在场景 A 中,我们考虑 5 个历元,因此过拟合的机会较少,而在场景 B 中,我们考虑 100 个历元,因此过拟合的机会较多(代码在 github 中可作为“neural network.ipynb 中的正则化需求”获得)。

我们应该注意到,在最初的几个时期,训练和测试数据集精度之间的差异较小,但是随着时期数量的增加,训练数据集的精度增加,而测试数据集的精度在一些时期之后可能不会增加。

在我们的运行中,我们看到了以下准确性指标:

| 方案 | 训练数据集 | 测试数据集 | | 5 个时代 | 97.57% | 97.27% | | 100 个时代 | 100% | 98.28% |

一旦我们绘制了权重直方图(在本例中,权重将隐藏层连接到输出层),我们将会注意到,与 5 个时段的权重相比,100 个时段的权重具有更高的分布(范围),如下图所示:

A463052_1_En_7_Figy_HTML.jpg

100 个时期的方案具有更高的权重范围,因为它试图在后面的时期中针对训练数据集中的边缘情况进行调整,而 5 个时期的方案没有机会针对边缘情况进行调整。虽然训练数据集的边缘案例被权重更新覆盖,但是测试数据集的边缘案例没有必要表现类似,因此可能没有被权重更新覆盖。此外,请注意,训练数据集中的边缘情况可以通过给予特定像素非常高的权重来覆盖,从而快速移动到 sigmoid 曲线的饱和 1 或 0。

因此,具有高权重值对于一般化目的来说是不理想的。在这种情况下,正则化就派上了用场。

正则化对具有高数量级的权重不利。使用的主要正则化类型是 L1 & L2 正则化

L2 正则化将附加成本项添加到误差(损失函数)中,如\sum {w}_i²

L1 正则化将附加成本项添加到误差(损失函数)中,如\sum \mid {w}_i\mid

这样,我们确保权重不能被调整为具有高值,以便它们仅在训练数据集中用于极端边缘情况。

给正则项分配权重

我们注意到,在 L2 正则化的情况下,我们修改的损失函数如下:

总损失= \sum {\left(y-\widehat{y}\right)}²+\lambda \sum {w}_i²

其中\lambda是与正则化项相关联的权重,并且是需要调整的超参数。类似地,L1 正则化情况下的总损耗如下:

总损失= \sum {\left(y-\widehat{y}\right)}²+\lambda \sum \mid {w}_i\mid

L1/ L2 正则化在 Python 中实现如下:

from keras import regularizers
model3 = Sequential()
model3.add(Dense(1000, input_dim=784, activation="relu", kernel_regularizer=regularizers.l2(0.001)))
model3.add(Dense(10, activation="softmax", kernel_regularizer=regularizers.l2(0.001)))
model3.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model3.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=100, batch_size=1024, verbose=2)

注意,上面涉及到调用一个额外的超参数——“kernel _ regulator”,然后指定它是否是 L1 / L2 正则化。此外,我们还指定了赋予正则化权重的\lambda值。

我们注意到,正则化后,训练和测试数据集的精度彼此相似,其中训练数据集的精度为 97.6%,而测试数据集的精度为 97.5%。L2 正则化后的权重直方图如下:

A463052_1_En_7_Figz_HTML.jpg

我们注意到,与前两种情况相比,大多数权重现在更接近于 0,从而避免了由于分配给边缘情况的高权重值而导致的过拟合问题。在 L1 正则化的情况下,我们会看到类似的趋势。

因此,L1 和 L2 正则化帮助我们避免了在训练数据集上过度拟合但在测试数据集上不泛化的问题。

用 R 语言实现神经网络

类似于我们在 Python 中实现神经网络的方式,我们将使用keras框架在 R 中实现神经网络。

为了构建神经网络模型,我们将在 R 中使用kerasR包。考虑到kerasR包对 Python 的所有依赖性,以及创建虚拟环境的需要,我们将在云中执行 R 实现,如下所示(代码可作为“NN。github 中的 r”)。

  1. 安装kerasR包:

    install.packages("kerasR")
    
    
  2. 加载已安装的软件包:

    library(kerasR)
    
    
  3. 使用 MNIST 数据集进行分析:

    mnist <- load_mnist()
    
    
  4. 检查mnist对象的结构:

    str(mnist)
    
    

    注意,默认情况下,MNIST 数据集将训练和测试数据集分开。

  5. 提取训练和测试数据集:

    mnist <- load_mnist()
    X_train <- mnist$X_train
    Y_train <- mnist$Y_train
    X_test <- mnist$X_test
    Y_test <- mnist$Y_test
    
    
  6. 重塑数据集。假设我们正在执行一个正常的神经网络操作,我们的输入数据集的维数应该是(60000,784),而X_train的维数是(60000,28,28):

    X_train <- array(X_train, dim = c(dim(X_train)[1], 784))
    X_test <- array(X_test, dim = c(dim(X_test)[1], 784))
    
    
  7. 缩放数据集:

    X_train <- X_train/255
    X_test <- X_test/255
    
    
  8. 将因变量(Y_trainY_test)转换为分类变量:

    Y_train <- to_categorical(mnist$Y_train, 10)
    Y_test <- to_categorical(mnist$Y_test, 10)
    
    
  9. 建立模型:

    model <- Sequential()
    model$add(Dense(units = 1000, input_shape = dim(X_train)[2],activation = "relu"))
    model$add(Dense(10,activation = "softmax"))
    model$summary()
    
    
  10. 编译模型:

```py
keras_compile(model,  loss = 'categorical_crossentropy', optimizer = Adam(),metrics='categorical_accuracy')

```

11. 拟合模型:

```py
keras_fit(model, X_train, Y_train,batch_size = 1024, epochs = 5,verbose = 1, validation_data = list(X_test,Y_test))

```

我们刚刚经历的过程应该给我们一个大约 98%的测试数据集准确度。

摘要

在本章中,您学习了以下内容:

  • 神经网络可以逼近复杂的函数(因为隐藏层中的激活)
  • 前向和后向传播构成了神经网络功能的构建模块
  • 正向传播有助于我们估计误差,而反向传播有助于减少误差
  • 每当梯度下降涉及到达到最佳权重值时,缩放输入数据集总是一个更好的主意
  • L1/ L2 正则化通过惩罚高权重幅度来帮助避免过拟合

八、 Word2vec

Word2vec 是一种基于神经网络的方法,在传统的文本挖掘分析中非常方便。

传统文本挖掘方法的一个问题是数据的维度问题。考虑到典型文本中有大量不同的单词,构建的列数可能会非常多(其中每列对应一个单词,列中的每个值指定该单词是否存在于与该行相对应的文本中——本章稍后将详细介绍)。

Word2vec 有助于以更好的方式表示数据:彼此相似的单词具有相似的向量,而彼此不相似的单词具有不同的向量。在这一章中,我们将探索计算单词向量的不同方法。

为了了解 Word2vec 如何有用,让我们探索一个问题。假设我们有两个输入句子:

A463052_1_En_8_Figa_HTML.png

直觉上,我们知道享受和喜欢是相似的词。然而,在传统的文本挖掘中,当我们对单词进行一次热编码时,我们的输出看起来像这样:

A463052_1_En_8_Figb_HTML.png

注意,一键编码导致每个单词被分配一列。one-hot 编码的主要问题是单词{I,enjoy}之间的欧几里德距离与单词{enjoy,like}之间的距离相同。但是我们知道{我,享受}之间的距离应该大于{享受,喜欢}之间的距离,因为享受和喜欢彼此更同义。

手工构建单词向量

在构建单词向量之前,我们将假设表述如下:

"相关的词周围会有相似的词."

例如,“国王”和“王子”这两个词周围经常会出现类似的词。本质上,单词的上下文(周围的单词)是相似的。

有了这个假设,让我们把每个单词看做输出,把所有的上下文单词(周围的单词)看做输入。因此,我们的数据集翻译如下(在 github 中以“word2vec.xlsx”的形式提供):

A463052_1_En_8_Figc_HTML.jpg

通过使用上下文单词作为输入,我们试图预测给定单词作为输出。

前面的输入和输出单词的矢量化形式如下所示(注意,第 3 行中给出的列名{I,enjoy,playing,TT,like}仅供参考):

A463052_1_En_8_Figd_HTML.jpg

注意,给定输入单词——{享受,演奏,TT }——向量形式是{0,1,1,1,0},因为输入不包含 I 和 like,所以第一个和最后一个索引是 0(注意在第一页中完成的 one-hot 编码)。

现在,假设我们想将 5 维输入向量转换成 3 维向量。在这种情况下,我们的隐藏层有三个相关的神经元。我们的神经网络看起来会如图 8-1 所示。

A463052_1_En_8_Fig1_HTML.png

图 8-1

Our neural network

每层的尺寸如下:

| 层 | 大小 | 评论 | | :-- | :-- | :-- | | 输入层 | 8 × 5 | 因为有 8 个输入和 5 个索引(唯一字) | | 隐藏层的权重 | 5 × 3 | 因为 3 个神经元各有 5 个输入 | | 隐藏层输出 | 8 × 3 | 输入和隐藏层的矩阵乘法 | | 从隐藏到输出的权重 | 3 × 5 | 隐藏层的 3 个输出列映射到 5 个原始输出列 | | 输出层 | 8 × 5 | 隐藏层输出与从隐藏层到输出层的权重之间的矩阵乘法 |

下面显示了每种方法的工作原理:

A463052_1_En_8_Fige_HTML.jpg

注意,输入向量乘以随机初始化的隐藏层权重矩阵,以获得隐藏层的输出。给定输入大小为 8 × 5,隐藏层大小为 5 × 3,矩阵乘法的输出为 8 × 3。而且,与传统的神经网络不同,在 Word2vec 方法中,我们不在隐藏层上应用任何激活:

A463052_1_En_8_Figf_HTML.jpg

一旦我们有了隐藏层的输出,我们就把它们乘以一个从隐藏层到输出层的权重矩阵。假设隐藏层的输出大小为 8 × 3,要输出的隐藏层大小为 3 × 5,则我们的输出层为 8 × 5。但是请注意,输出图层有一系列数字,包括正数和负数,以及大于 1 或的数字

因此,正如我们在神经网络中所做的那样,我们通过 softmax 将数字转换为 0 到 1 之间的数字:

A463052_1_En_8_Figg_HTML.jpg

为了方便起见,我将 softmax 分解为两个步骤:

  1. 对数字应用指数。
  2. 将步骤 1 的输出除以步骤 1 输出的行和。

在前面的输出中,我们看到第一列的输出非常接近第一行的 1,第二列的输出是第二行的 0.5,依此类推。

获得预测值后,我们将它们与实际值进行比较,以计算整个批次的交叉熵损失,如下所示:

A463052_1_En_8_Figh_HTML.jpg

交叉熵损失=–∑实际值× Log(概率,2)

既然我们已经计算了总体交叉熵误差,我们的任务是通过使用选择的优化器改变随机初始化的权重来减少总体交叉熵误差。一旦我们达到了最佳的权重值,我们剩下的隐藏层看起来像这样:

A463052_1_En_8_Figi_HTML.jpg

既然我们已经计算了输入单词和隐藏层权重,现在可以通过将输入单词乘以隐藏层表示来在较低的维度中表示单词。

输入层(每个单词 1 × 5)和隐藏层(5 × 3 权重)的矩阵乘法是一个大小为(1 × 3)的向量:

A463052_1_En_8_Figj_HTML.jpg

如果我们现在考虑单词{enjoy,like},我们应该注意到这两个单词的向量彼此非常相似(也就是说,这两个单词之间的距离很小)。

这样,我们将原始输入的 one-hot-encoded 向量(其中{enjoy,like}之间的距离较大)转换为转换后的单词向量(其中{enjoy,like}之间的距离较小)。

构建单词向量的方法

我们在前面的部分中采用的构建单词向量的方法被称为连续单词包(CBOW)模型。

取一句话“那只敏捷的棕色狐狸跳过了那只狗。”CBOW 模型是这样处理这句话的:

| 输入单词 | 输出字 | | :-- | :-- | | {那个,快,狐狸,跳了} | {布朗} | | {快速,棕色,跳跃,结束} | {fox} | | {布朗,福克斯,完毕,该} | {jumped} | | {狐狸,跳了,狗} | {结束} |
  1. 固定窗口大小。也就是说,选择给定单词左边和右边的 n 个单词。例如,假设窗口大小是给定单词左右各 2 个单词。
  2. 给定窗口大小,输入和输出向量如下所示:

另一种建立单词向量的方法叫做跳格模型。在 skip-gram 模型中,前面的步骤是相反的,如下所示:

| 输入单词 | 输出字 | | :-- | :-- | | {布朗} | {那个,快,狐狸,跳了} | | {fox} | {快速,棕色,跳跃,结束} | | {jumped} | {布朗,福克斯,完毕,该} | | {结束} | {狐狸,跳了,狗} |

无论是 skip-gram 模型还是 CBOW 模型,获得隐藏层向量的方法都是相同的。

在 Word2vec 模型中需要注意的问题

对于到目前为止所讨论的计算方法,本节着眼于我们可能面临的一些常见问题。

常用词

像 the 这样的典型常用词在词汇中经常出现。在这种情况下,输出中出现的单词更频繁。如果不进行处理,这可能会导致大部分输出是最常用的单词,比如 the,而不是其他单词。我们需要有一种方法来惩罚一个频繁出现的单词在训练数据集中出现的次数。

在典型的 Word2vec 分析中,我们惩罚频繁出现的单词的方式如下。选择一个单词的概率是这样计算的:

P\left({w}_i\right)=\left(\sqrt{\frac{z\left({w}_i\right)}{0.001}}+1\right)\cdot \frac{0.001}{z\left({w}_i\right)}

z(w)是一个单词在任何单词的总出现次数中出现的次数。该公式的绘图显示了图 8-2 中的曲线。

A463052_1_En_8_Fig2_HTML.jpg

图 8-2

The resultant curve

请注意,随着 z(w) (x 轴)的增加,选择概率(y 轴)急剧下降。

负采样

让我们假设在我们的数据集中总共有 10,000 个唯一的单词,也就是说,每个向量有 10,000 个维度。我们还假设我们正在从原始的 10,000 维向量创建一个 300 维向量。这意味着,从隐藏层到输出层,总共有 300×10000 = 3000000 个权重。

权重数量如此之大的一个主要问题是,它可能会导致数据上的过度拟合。这也可能导致更长的训练时间。

负采样是克服这个问题的一种方法。假设不是检查所有 10,000 个维度,而是选择输出为 1(正确标签)的索引和标签为 0 的五个随机索引。这样,我们将在单次迭代中更新的权重数量从 300 万减少到 300 × 6 = 1800 个权重。

我说过负索引的选择是随机的,但是在 Word2vec 的实际实现中,选择是基于一个单词与其他单词相比的频率。与频率较低的单词相比,频率较高的单词被选中的几率更高。

选择五个否定词的概率如下:

P\left({w}_i\right)=\frac{f{\left({w}_i\right)}^{3/4}}{\sum \limits_{j=0}^n\left(f{\left({w}_j\right)}^{3/4}\right)}

f(w)是给定词的出现频率。

一旦计算出每个单词的概率,单词的选择过程如下:频率较高的单词重复频率较高,频率较低的单词重复频率较低,并存储在一个表中。鉴于高频词出现的频率更高,从表格中随机选择五个词时,它们被选中的几率更高。

用 Python 实现 Word2vec

Word2vec 可以使用gensim包在 Python 中实现(Python 实现在 github 中的名称是“word2vec.ipynb”)。

第一步是初始化软件包:

import nltk
import gensim
import pandas as pd

导入包后,我们需要提供前面几节中讨论的参数:

  • logging本质上帮助我们跟踪单词向量计算完成的程度。
  • num_features是隐含层的神经元个数。
  • min_word_count是被接受用于计算的单词频率的截止值。
  • context是窗口大小
  • downsampling有助于降低选择更常用单词的概率。
import logging
logging.basicConfig(format=’%(asctime)s : %(levelname)s : %(message)s’,\
    level=logging.INFO)

# Set values for various parameters
num_features = 100    # Word vector dimensionality  
min_word_count = 50   # Minimum word count    
num_workers = 4       # Number of threads to run in parallel
context = 9           # Context window size 
downsampling = 1e-4   # Downsample setting for frequent words

模型的输入词汇应该如下所示:

A463052_1_En_8_Figk_HTML.jpg

注意,所有输入的句子都被标记化了。

Word2vec 模型训练如下:

from gensim.models import word2vec
print(“Training model...”)
w2v_model = word2vec.Word2Vec(t2, workers=num_workers,
            size=num_features, min_count = min_word_count,
            window = context, sample = downsampling)

一旦模型被训练,满足指定标准的词汇表中任何单词的权重向量可以如下获得:

model['word'] # replace the "word" with the word of your interest

类似地,与给定单词最相似的单词可以如下获得:

model.most_similar('word')

摘要

在本章中,您学习了以下内容:

  • Word2vec 是一种可以帮助将文本单词转换成数字向量的方法。
  • 这对下游的多种方法来说是一个强大的第一步——例如,我们可以在构建模型时使用单词 vectors。
  • Word2vec 使用一种 CBOW 或 skip-gram 模型来提供向量,这种模型具有神经网络架构,有助于提供向量。
  • 神经网络中的隐含层是生成单词向量的关键。

九、卷积神经网络

在第七章中,我们看了一个传统的神经网络(NN)。传统神经网络的局限性之一是它不是平移不变的,也就是说,图像右上角的猫图像与图像中心有猫的图像的处理方式不同。卷积神经网络(CNN)被用来处理这样的问题。

鉴于 CNN 可以处理图像中的转换,它被认为更有用,并且 CNN 架构事实上是当前对象分类/检测中最先进的技术之一。

在本章中,您将学习以下内容:

  • CNN 的工作细节
  • CNN 如何克服神经网络的缺点
  • 卷积和池对解决图像翻译问题的影响
  • 如何用 Python 实现 CNN,以及 R

为了进一步更好地理解 CNN 的必要性,让我们从一个例子开始。比方说,我们想对一幅图像中是否有垂直线进行分类(也许是为了判断该图像是否代表 1)。为了简单起见,我们假设图像是 5 × 5 的图像。书写垂直线(或 1)的几种方法如下:

A463052_1_En_9_Figa_HTML.png

我们还可以检查数字 1 在 MNIST 数据集中的不同书写方式。图 9-1 中显示了一个为写入的 1 突出显示的像素图像。

A463052_1_En_9_Fig1_HTML.jpg

图 9-1

Image of pixels corresponding to images with label 1

在图像中,像素越红,越经常有人在上面写字;越蓝意味着像素被写入的次数越少。中间的像素是最红的,很可能是因为大多数人会在那个像素上写字,不管他们用来写 1 的角度是什么——垂直线还是向左或向右倾斜。在下一节中,您会注意到,当图像平移几个单位时,神经网络预测不准确。在后面的章节中,我们将了解 CNN 如何解决图像翻译的问题。

传统神经网络的问题是

在刚才提到的场景中,只有当中间周围的像素被高亮显示而图像中的其余像素没有被高亮显示时,传统的神经网络才会将图像高亮显示为 1(因为大多数人都高亮显示了中间的像素)。

为了更好地理解这个问题,让我们来看一下我们在第七章中看过的代码(在 github 中代码可以作为“传统 NN.ipynb 的问题”获得):

A463052_1_En_9_Figc_HTML.jpg

  1. Download the dataset and extract the train and test datasets:

    A463052_1_En_9_Figb_HTML.jpg

    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    # load (downloaded if needed) the MNIST dataset
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    # plot 4 images as gray scale
    plt.subplot(221)
    plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
    plt.subplot(222)
    plt.imshow(X_train[1], cmap=plt.get_cmap('gray'))
    plt.subplot(223)
    plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
    plt.subplot(224)
    plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
    # show the plot
    plt.show()
    
    
  2. 导入相关包:

    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.layers import Flatten
    from keras.layers.convolutional import Conv2D
    from keras.layers.convolutional import MaxPooling2D
    from keras.utils import np_utils
    from keras import backend as K
    
    
  3. 只取标签 1 对应的训练集:

    X_train1 = X_train[y_train==1]
    
    
  4. 对数据集进行整形和归一化:

    num_pixels = X_train.shape[1] * X_train.shape[2]
    X_train = X_train.reshape(X_train.shape[0],num_pixels ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],num_pixels).astype('float32')
    
    X_train = X_train / 255
    X_test = X_test / 255
    
    
  5. 一次热编码标签:

    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_train.shape[1]
    
    
  6. 构建模型并运行:

    model = Sequential()
    model.add(Dense(1000, input_dim=num_pixels, activation="relu"))
    model.add(Dense(num_classes, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=[''accuracy'])
    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

让我们画出平均 1 个标签的样子:

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

图 9-2 显示结果。

A463052_1_En_9_Fig2_HTML.jpg

图 9-2

Average 1 image

场景 1

在这种情况下,创建了一个新图像(图 9-3 ,其中原始图像向左平移了 1 个像素:

A463052_1_En_9_Fig3_HTML.jpg

图 9-3

Average 1 image translated by 1 pixel to the left

for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+1]
plt.imshow(pic)

让我们继续使用构建的模型预测图 9-3 中图像的标签:

A463052_1_En_9_Figd_HTML.jpg

model.predict(pic.reshape(1,784))

我们将错误的预测 8 视为输出。

场景 2

创建了一个新图像(图 9-4 ),其中的像素不是从原始平均 1 图像转换而来的:

A463052_1_En_9_Fig4_HTML.jpg

图 9-4

Average 1 image

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

该图像的预测如下:

A463052_1_En_9_Fige_HTML.jpg

model.predict(pic.reshape(1,784))

我们看到输出为 1 的正确预测。

场景 3

创建新图像(图 9-5 ),其中原始平均 1 图像的像素向右移动 1 个像素:

A463052_1_En_9_Fig5_HTML.jpg

图 9-5

Average 1 image translated by 1 pixel to the right

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
pic2=np.copy(pic)
for i in range(pic.shape[0]):
  if ((i>6) and (i<26)):
    pic[:,i]=pic2[:,(i-1)]
plt.imshow(pic)

让我们继续使用构建的模型预测上面图像的标签:

A463052_1_En_9_Figf_HTML.jpg

model.predict(pic.reshape(1,784))

我们有一个输出为 1 的正确预测。

场景 4

创建新图像(图 9-6 ),其中原始平均 1 图像的像素向右移动 2 个像素:

A463052_1_En_9_Fig6_HTML.jpg

图 9-6

Average 1 image translated by 2 pixels to the right

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
pic2=np.copy(pic)
for i in range(pic.shape[0]):
  if ((i>6) and (i<26)):
    pic[:,i]=pic2[:,(i-2)]
plt.imshow(pic)

我们将使用构建的模型预测图像的标签:

A463052_1_En_9_Figg_HTML.jpg

model.predict(pic.reshape(1,784))

我们看到一个错误的预测 3 作为输出。

从前面的场景中,您可以看到传统的神经网络在数据转换时无法产生良好的结果。这些场景需要不同的网络处理方式来解决翻译差异。这就是卷积神经网络(CNN)派上用场的地方。

理解 CNN 中的卷积

你已经对典型的神经网络的工作原理有了很好的了解。在这一节,我们来探讨一下 CNN 中卷积这个词是什么意思。卷积是两个矩阵之间的乘法,其中一个矩阵较大,另一个较小。

要查看卷积,请考虑以下示例。

矩阵 A 如下:

A463052_1_En_9_Figh_HTML.png

矩阵 B 如下:

A463052_1_En_9_Figi_HTML.png

在执行卷积时,可以把它想象成在较大矩阵上滑动较小矩阵:当较小矩阵在较大矩阵的整个区域上滑动时,我们可能会得到 9 个这样的乘法。注意,它不是矩阵乘法:

  1. 较大矩阵的{1,2,5,6}乘以较小矩阵的{1,2,3,4}。1 × 1 + 2 × 2 + 5 × 3 + 6 × 4 = 44
  2. 较大矩阵的{2,3,6,7}乘以较小矩阵的{1,2,3,4 }:2×1+3×2+6×3+7×4 = 54
  3. 较大矩阵的{3,4,7,8}乘以较小矩阵的{1,2,3,4 }:3×1+4×2+7×3+8×4 = 64
  4. 较大矩阵的{5,6,9,10}乘以较小矩阵的{1,2,3,4 }:5×1+6×2+9×3+10×4 = 84
  5. 较大矩阵的{6,7,10,11}乘以较小矩阵的{1,2,3,4 }:6×1+7×2+10×3+11×4 = 94
  6. 较大矩阵的{7,8,11,12}乘以较小矩阵的{1,2,3,4 }:7×1+8×2+11×3+12×4 = 104
  7. 较大矩阵的{9,10,13,14}乘以较小矩阵的{1,2,3,4 }:9×1+10×2+13×3+14×4 = 124
  8. 较大矩阵的{10,11,14,15}乘以较小矩阵的{1,2,3,4 }:10×1+11×2+14×3+15×4 = 134
  9. 较大矩阵的{11,12,15,16}乘以较小矩阵的{1,2,3,4 }:11×1+12×2+15×3+16×4 = 144

前面步骤的结果将是一个矩阵,如下所示:

A463052_1_En_9_Figj_HTML.png

按照惯例,较小的矩阵被称为滤波器或内核,并且较小的矩阵值是通过梯度下降(稍后将详细介绍梯度下降)在统计上得到的。过滤器内的值可以被认为是成分权重。

从卷积到激活

在传统的神经网络中,隐藏层不仅将输入值乘以权重,还将非线性应用于数据—它通过激活函数传递值。类似的活动也发生在典型的 CNN 中,卷积通过激活函数传递。CNN 支持我们目前看到的传统激活函数:sigmoid,ReLU,Tanh。

对于前面的输出,请注意,当通过 ReLU 激活函数时,输出保持不变,因为所有的数字都是正数。

从卷积激活到池化

到目前为止,我们已经了解了卷积是如何工作的。在本节中,我们将考虑卷积后的下一个典型步骤:池化。

假设卷积步骤的输出如下(我们不考虑前面的例子,这是一个说明池的新例子,基本原理将在后面的部分解释):

A463052_1_En_9_Figk_HTML.jpg

在这种情况下,卷积步骤的输出是一个 2 × 2 矩阵。最大池考虑 2 × 2 块并给出最大值作为输出,同样,如果卷积步骤的输出是一个更大的矩阵,如下所示:

A463052_1_En_9_Figl_HTML.jpg

最大池将大矩阵分成大小为 2 × 2 的非重叠块,如下所示:

A463052_1_En_9_Figm_HTML.jpg

从每个块中,只选择具有最高值的元素。因此,对前面矩阵的最大池化操作的输出如下:

A463052_1_En_9_Fign_HTML.jpg

注意,实际上,没有必要总是使用 2 × 2 滤波器。

涉及的其他类型的池是总和和平均。同样,在实践中,与其他类型的池相比,我们看到了很多最大池。

卷积和池化有什么帮助?

在我们之前看到的 MNIST 例子中,传统神经网络的缺点之一是每个像素都与不同的权重相关联。因此,如果除了原始像素之外的相邻像素变得高亮,输出将不会非常准确(场景 1 的示例,其中 1 稍微位于中间的左侧)。

现在解决这种情况,因为像素共享在每个滤波器内构成的权重。所有像素乘以构成过滤器的所有权重,并且在汇集层中,仅选择被激活最高的值。这样,无论高亮显示的像素是在中心还是稍微远离中心,输出通常都不是预期值。然而,当突出显示的像素远离中心时,问题仍然存在。

用代码创建 CNN

从前面的传统神经网络场景中,我们看到,如果像素向左平移 1 个单位,神经网络将不起作用。实际上,我们可以认为卷积步骤是识别模式的步骤,合并步骤是导致翻译差异的步骤。

N 个汇集步骤导致至少 N 个单位的平移不变性。考虑下面的例子,我们在卷积后应用一个池化步骤(代码在 github 中作为“使用 CNN.ipynb 的改进”提供):

  1. Import and reshape the data to fit a CNN:

    A463052_1_En_9_Figo_HTML.jpg

    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
    X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')
    
    X_train = X_train / 255
    X_test = X_test / 255
    
    y_train = np_utils.to_categorical(y_train)
    y_test = np_utils.to_categorical(y_test)
    num_classes = y_test.shape[1]
    Step 2: Build a model
    model = Sequential()
    model.add(Conv2D(10, (3,3), input_shape=(28, 28,1), activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(1000, activation="relu"))
    model.add(Dense(num_classes, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    model.summary()
    
    
  2. Fit the model:

    A463052_1_En_9_Figp_HTML.jpg

    model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)
    
    

对于前一个卷积,其中一个卷积后跟一个池层,如果像素向左或向右平移 1 个单位,则输出预测效果良好,但当像素平移超过 1 个单位时,输出预测效果不佳(图 9-7 ):

A463052_1_En_9_Fig7_HTML.jpg

图 9-7

Average 1 image translated by 1 pixel to the left

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+1]
plt.imshow(pic)

让我们继续预测图 9-7 的标签:

A463052_1_En_9_Figq_HTML.jpg

model.predict(pic.reshape(1,28,28,1))

我们看到输出为 1 的正确预测。

在下一个场景中(图 9-8 ,我们将像素向左移动 2 个单位:

A463052_1_En_9_Fig8_HTML.jpg

图 9-8

Average 1 image translated by 2 pixels to the left

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+2]
plt.imshow(pic)

让我们根据之前构建的 CNN 模型来预测图 9-8 的标签:

A463052_1_En_9_Figr_HTML.jpg

model.predict(pic.reshape(1,28,28,1))

当图像向左平移 2 个像素时,我们有不正确的预测。

请注意,当模型中卷积池层的数量与图像中的平移量相同时,预测是正确的。但是,如果与图像中的转换相比,卷积池层较少,则预测更有可能不正确。

CNN 的工作细节

让我们用 Python 构建 toy CNN 代码,然后在 Excel 中实现输出,这样可以加强我们的理解(代码在 github 中以“CNN simple example.ipynb”的形式提供):

  1. 导入相关包:

    # import relevant packages
    from keras.datasets import mnist
    import matplotlib.pyplot as plt
    %matplotlib inline
    import numpy as np
    from keras.datasets import mnist
    from keras.models import Sequential
    from keras.layers import Dense
    from keras.layers import Dropout
    from keras.utils import np_utils
    from keras.layers import Flatten
    from keras.layers.convolutional import Conv2D
    from keras.layers.convolutional import MaxPooling2D
    from keras.utils import np_utils
    from keras import backend as K
    from keras import regularizers
    
    
  2. 创建一个简单的数据集:

    # Create a simple dataset
    X_train=np.array([[[1,2,3,4],[2,3,4,5],[5,6,7,8],[1,3,4,5]],[[-1,2,3,-4],[2,-3,4,5],[-5,6,-7,8],[-1,-3,-4,-5]]])
    y_train=np.array([0,1])
    
    
  3. 通过将每个值除以数据集中的最大值来归一化输入:

    X_train = X_train / 8
    
    
  4. 对输出进行一次热编码:

    y_train = np_utils.to_categorical(y_train)
    
    
  5. 一旦只有两个大小为 4 × 4 的输入和两个输出的简单数据集就绪,让我们首先将输入整形为所需的格式(即:样本数、图像高度、图像宽度、图像通道数):

    X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
    
    
  6. Build a model:

    A463052_1_En_9_Figs_HTML.jpg

    model = Sequential()
    model.add(Conv2D(1, (3,3), input_shape=(4,4,1), activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Flatten())
    model.add(Dense(10, activation="relu"))
    model.add(Dense(2, activation="softmax"))
    model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
    model.summary()
    
    
  7. Fit the model:

    A463052_1_En_9_Figt_HTML.jpg

    model.fit(X_train, y_train, epochs=100, batch_size=2, verbose=1)
    
    

上述模型的各层如下:

A463052_1_En_9_Figu_HTML.jpg

model.layers

各层对应的名称和形状如下:

A463052_1_En_9_Figv_HTML.jpg

names = [weight.name for layer in model.layers for weight in layer.weights]
weights = model.get_weights()

for name, weight in zip(names, weights):
    print(name, weight.shape)

对应于给定层的权重可以提取如下:

A463052_1_En_9_Figw_HTML.jpg

model.layers[0].get_weights()

第一个输入的预测可以计算如下:

A463052_1_En_9_Figx_HTML.jpg

model.predict(X_train[0].reshape(1,4,4,1))

现在我们知道前面预测的概率为 0 是 0.89066,让我们通过在 Excel 中匹配前面的预测(在 github 中作为“CNN simple example.xlsx”提供)来验证我们对 CNN 的直觉。

第一个输入及其相应的缩放版本,以及卷积权重和偏差(来自模型),如下所示:

A463052_1_En_9_Figy_HTML.jpg

卷积的输出如下(请检查“CNN simple example.xlsx”文件中 L4 至 M5 的单元格):

A463052_1_En_9_Figz_HTML.jpg

卷积的计算按照以下公式:

A463052_1_En_9_Figaa_HTML.jpg

在卷积层之后,我们如下执行最大池化:

A463052_1_En_9_Figab_HTML.jpg

一旦执行了池化,所有的输出都是扁平的(按照我们模型中的规范)。然而,假设我们的池层只有一个输出,扁平化也会产生一个输出。

在下一步中,展平的层连接到隐藏的密集层(在我们的模型规范中有 10 个神经元)。对应于每个神经元的权重和偏差如下:

A463052_1_En_9_Figac_HTML.jpg

矩阵乘法和乘法后的 ReLU 激活如下:

A463052_1_En_9_Figad_HTML.jpg

上述输出的公式如下:

A463052_1_En_9_Figae_HTML.jpg

现在让我们看看从隐藏层到输出层的计算。请注意,每个输入有两个输出(每行的输出在维度上有两列:概率为 0 和概率为 1)。从隐藏层到输出层的权重如下:

A463052_1_En_9_Figaf_HTML.jpg

现在,每个神经元都连接到两个权重(其中每个权重都将其连接到两个输出),让我们看看从隐藏层到输出层的计算:

A463052_1_En_9_Figag_HTML.jpg

输出层的计算如下:

A463052_1_En_9_Figah_HTML.jpg

现在我们有了一些输出值,让我们来计算输出的 softmax 部分:

A463052_1_En_9_Figai_HTML.jpg

现在,输出将与我们在 keras 模型的输出中看到的完全相同:

A463052_1_En_9_Figaj_HTML.jpg

因此,我们对前面章节中的直觉进行了验证。

深入研究卷积/内核

为了了解内核/过滤器是如何帮助我们的,让我们来看另一个场景。从 MNIST 数据集,让我们以这样的方式修改目标,即我们只对预测图像是 1 还是不是 1 感兴趣:

(X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')

X_train = X_train / 255
X_test = X_test / 255

X_train1 = X_train[y_train==1]

y_train = np.where(y_train==1,1,0)
y_test = np.where(y_test==1,1,0)
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]

我们将提出一个简单的 CNN,其中只有两个卷积滤波器:

A463052_1_En_9_Figak_HTML.jpg

model = Sequential()
model.add(Conv2D(2, (3,3), input_shape=(28, 28,1), activation="relu"))
model.add(Flatten())
model.add(Dense(1000, activation="relu"))
model.add(Dense(num_classes, activation="softmax"))
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model.summary()

现在,我们将继续运行模型,如下所示:

A463052_1_En_9_Figal_HTML.jpg

model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)

我们可以通过以下方式提取对应于滤波器的权重:

model.layers[0].get_weights()

让我们使用上一步中获得的权重手动卷积并应用激活(图 9-9 ):

A463052_1_En_9_Fig9_HTML.jpg

图 9-9

Average filter activations when 1 label images are passed

from scipy import signal
from scipy import misc
import numpy as np
import pylab
for j in range(2):
    gradd=np.zeros((30,30))
    for i in range(6000):
        grad = signal.convolve2d(X_train1[i,:,:,0], model.layers[0].get_weights()[0].T[j][0])+model.layers[0].get_weights()[1][j]
        grad = np.where(grad<0,0,grad)
        gradd=grad+gradd
    grad2=np.where(gradd<0,0,gradd)
    pylab.imshow(grad2/6000)
    pylab.gray()
    pylab.show()

在图中,请注意左边的滤镜比右边的滤镜更能激活 1 图像。本质上,第一个过滤器有助于预测更多的标签 1,而第二个过滤器增强了对其余标签的预测。

从卷积和汇集到扁平化:全连接层

到目前为止,我们看到的输出是图像。在传统的神经网络中,我们将每个像素视为一个独立的变量。这正是我们将要在展平过程中执行的操作。

图像的每个像素都是展开的,因此这个过程被称为展平。例如,卷积和合并后的输出图像如下所示:

A463052_1_En_9_Figam_HTML.jpg

展平的输出如下所示:

A463052_1_En_9_Figan_HTML.jpg

从一个完全连接的层到另一个

在典型的神经网络中,输入层连接到隐藏层。以类似的方式,在 CNN 中,全连接层连接到通常具有更多单元的另一个全连接层。

从全连接层到输出层

与传统的神经网络架构类似,隐藏层连接到输出层,并通过 sigmoid 激活来获得作为概率的输出。根据要解决的问题,还选择了适当的损失函数。

连接点:前馈网络

以下是我们到目前为止所执行的步骤的回顾:

  1. 盘旋
  2. 联营
  3. 变平
  4. 隐蔽层
  5. 计算输出概率

典型的 CNN 外观如图 9-10 所示(最著名的——以发明人自己开发的 LeNet 为例):

图 9-10 中的子样本相当于我们之前看到的最大池步骤。

CNN 的其他细节

在图 9-10 中,我们看到 conv1 步骤有六个通道或原始图像的卷积。让我们详细看看这个:

  1. 假设我们有一个 28 × 28 的灰度图像。六个大小为 3 × 3 的过滤器将生成大小为 26 × 26 的图像。因此,我们只剩下六幅大小为 26 × 26 的图像。
  2. 典型的彩色图像有三个通道(RGB)。为了简单起见,我们可以假设第一步中的输出图像有六个通道——六个滤镜一个通道(尽管我们不能像三通道版本那样将它们命名为 RGB)。在这一步中,我们将分别对六个通道中的每一个通道执行最大池化。这将产生六个尺寸为 13 × 13 的图像(通道)。
  3. 在下一个卷积步骤中,我们将 13 × 13 图像的六个通道乘以维度为 3 × 3 × 6 的权重。这是一个在三维图像上卷积的三维权重矩阵(其中图像的尺寸为 13 × 13 × 6)。这将导致每个滤波器的图像尺寸为 11 × 11。假设我们已经考虑了十种不同的权重矩阵(准确地说是立方体)。这将产生尺寸为 11 × 11 × 10 的图像。
  4. 每个 11 × 11 图像(数量为 10)的最大池将产生一个 5 × 5 的图像。请注意,当对具有奇数维的图像执行最大池化时,池化会产生向下舍入的图像,也就是说,11/2 向下舍入为 5。

A463052_1_En_9_Fig10_HTML.png

图 9-10

A LeNet

跨距是对原始图像进行卷积的滤波器从一个步骤移动到下一个步骤的量。例如,如果跨距值为 2,则两个连续卷积之间的距离为 2 个像素。当跨距值为 2 时,乘法将如下进行,其中 A 是较大的矩阵,B 是滤波器:

A463052_1_En_9_Figao_HTML.jpg

第一个卷积将介于:

A463052_1_En_9_Figap_HTML.jpg

第二个卷积将介于:

A463052_1_En_9_Figaq_HTML.png

第三个卷积将介于:

A463052_1_En_9_Figar_HTML.png

最终卷积将在以下两者之间:

A463052_1_En_9_Figas_HTML.png

注意,对于这里给定维数的矩阵,当步长为 2 时,卷积的输出是 2 × 2 矩阵。

Padding

请注意,当在图像上执行卷积时,结果图像的大小会减小。解决尺寸缩小问题的一种方法是在原始图像的四个边框上填充零。这样,28 × 28 的图像将被转换成 30 × 30 的图像。因此,当 30 × 30 图像通过 3 × 3 滤波器进行卷积时,结果图像将是 28 × 28 图像。

CNN 中的反向传播

CNN 中的反向传播以类似于典型 NN 的方式进行,其中计算少量改变权重对总权重的影响。但是在 NN 中,我们用权重的过滤器/矩阵来代替权重,这些过滤器/矩阵需要更新以最小化总损失。

有时,假设 CNN 中通常有数百万个参数,进行正则化会有所帮助。CNN 中的正则化可以使用丢弃方法或 L1 和 L2 正则化来实现。通过选择不更新某些权重(通常是随机选择的总权重的 20%)并在整个数量的时期内训练整个网络来完成丢弃。

把所有的放在一起

以下代码实现了一个三卷积池层,然后是展平和一个完全连接的层:

 (X_train, y_train), (X_test, y_test) = mnist.load_data()
X_train = X_train.reshape(X_train.shape[0],X_train.shape[1],X_train.shape[1],1 ).astype('float32')
X_test = X_test.reshape(X_test.shape[0],X_test.shape[1],X_test.shape[1],1).astype('float32')

X_train = X_train / 255
X_test = X_test / 255

y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)
num_classes = y_test.shape[1]

在下一步中,我们构建模型,如下所示:

A463052_1_En_9_Figat_HTML.jpg

model = Sequential()
model.add(Conv2D(32, (3,3), input_shape=(28, 28,1), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Conv2D(64, (3,3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(1000, activation="relu"))
model.add(Dense(num_classes, activation="softmax"))
model.compile(loss='categorical_crossentropy', optimizer="adam", metrics=['accuracy'])
model.summary()

最后,我们拟合模型,如下所示:

model.fit(X_train, y_train, validation_data=(X_test, y_test), epochs=5, batch_size=1024, verbose=1)

请注意,使用前面的代码训练的模型的准确性约为 98.8%。但是请注意,尽管该模型在测试数据集上工作得最好,但是从测试 MNIST 数据集平移或旋转的影像将不会被正确分类(通常,CNN 只能在影像按照卷积池层数平移时提供帮助)。这可以通过查看预测来验证,此时平均 1 幅图像向左平移 2 个像素一次,在另一种情况下,向左平移 3 个像素,如下所示:

A463052_1_En_9_Figau_HTML.jpg

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+2]
model.predict(pic.reshape(1,28,28,1))

请注意,在这种情况下,图像向左平移 2 个单位,预测是准确的:

A463052_1_En_9_Figav_HTML.jpg

pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
for i in range(pic.shape[0]):
  if i<20:
    pic[:,i]=pic[:,i+3]
model.predict(pic.reshape(1,28,28,1))

请注意,在这里,当图像平移的像素比卷积池层多时,预测不准确。这个问题可以通过使用数据扩充来解决,这是下一节的主题。

日期增加

从技术上讲,翻译后的图像与从原始图像生成的新图像是一样的。可以使用 keras 中的ImageDataGenerator功能生成新数据:

from keras.preprocessing.image import ImageDataGenerator
shift=0.2
datagen = ImageDataGenerator(width_shift_range=shift)
datagen.fit(X_train)
i=0
for X_batch,y_batch in datagen.flow(X_train,y_train,batch_size=100):
  i=i+1
  print(i)
  if(i>500):
    break
  X_train=np.append(X_train,X_batch,axis=0)
  y_train=np.append(y_train,y_batch,axis=0)
print(X_train.shape)

根据这段代码,我们从原始数据中生成了 50,000 次随机洗牌,其中像素被洗牌 20%。

当我们现在绘制 1 的图像时(图 9-11 ),请注意图像有一个更宽的分布:

A463052_1_En_9_Fig11_HTML.jpg

图 9-11

Average 1 post data augmentation

y_train1=np.argmax(y_train,axis=1)
X_train1=X_train[y_train1==1]
pic=np.zeros((28,28))
pic2=np.copy(pic)
for i in range(X_train1.shape[0]):
  pic2=X_train1[i,:,:,0]
  pic=pic+pic2
pic=(pic/X_train1.shape[0])
plt.imshow(pic)

现在,即使我们不对中心左侧或右侧的几个像素进行卷积合并,预测也会起作用。但是,对于远离中心的像素,一旦使用卷积和合并图层构建模型,正确的预测就会出现。

因此,当使用 CNN 模型时,数据扩充有助于进一步概括图像边界上的图像变化,即使卷积池图层较少。

在 R 中实现 CNN

为了在 R 中实现 CNN,我们将利用我们在 R 中用来实现神经网络的同一个包— kerasR(在 github 中代码为“kerasr_cnn_code.r”):

# Load, split, transform and scale the MNIST dataset
mnist <- load_mnist()

X_train <- array(mnist$X_train, dim = c(dim(mnist$X_train), 1)) / 255
Y_train <- to_categorical(mnist$Y_train, 10)
X_test <- array(mnist$X_test, dim = c(dim(mnist$X_test), 1)) / 255
Y_test <- to_categorical(mnist$Y_test, 10)

# Build the model
model <- Sequential()
model$add(Conv2D(filters = 32, kernel_size = c(3, 3),input_shape = c(28, 28, 1)))
model$add(Activation("relu"))
model$add(MaxPooling2D(pool_size=c(2, 2)))
model$add(Flatten())
model$add(Dense(128))
model$add(Activation("relu"))
model$add(Dense(10))
model$add(Activation("softmax"))
# Compile and fit the model
keras_compile(model,  loss = 'categorical_crossentropy', optimizer = Adam(),metrics='categorical_accuracy')
keras_fit(model, X_train, Y_train, batch_size = 1024, epochs = 5, verbose = 1,validation_data = list(X_test,Y_test))

前面的代码产生了大约 97%的准确率。

摘要

在本章中,我们看到了卷积如何帮助我们识别感兴趣的结构,以及合并如何帮助确保图像被识别,即使在原始图像中发生了转换。鉴于 CNN 能够通过卷积和池化来适应图像转换,它能够提供比传统神经网络更好的结果。