神经网络(深度学习)算法

我想接触过机器学习的人应该都听过一个高大上,但是又非常陌生的算法,就是“神经网络”。尤其是最近两年,这类被称为神经网络的算法以“深度学习”的名字再度流行。虽然深度学习在许多机器学习应用中都有非常大的潜力,但深度学习算法往往经过精确调整,只适用于特定的使用场景。接下来,我们只讨论一些相对简单的方法,即用于分类和回归的多层感知机(MLP),它可以作为研究更复杂的深度学习方法的起点。MLP也被称为(普通)前馈神经网络,有时也简称为神经网络。

算法简介

神经网络-MLP

PS:接下来要说理论的东西了,很枯燥,已经了解的小伙伴可以直接跳过

MLP可以被视为广义的线性模型,执行多层处理后得到结论。还记得我之前说过的线性回归的预测公式

y=w[0]x[0]+w[1]x[1]+w[2]x[2]+…+w[p]x[p]+b

简单来说,y是输入特征x[0]到x[p]的加权求和,权重为学到的系数w[0]到w[p]。我们可以将这个公式可视化,如下图所示:

在这里插入图片描述

图中,左边的每个点代表一个输入特征,连线代表学到的系数,右边的结点代表输出,是输入的加权求和。

在MLP中,多次重复这个计算加权求和的过程,首先计算代表中间过程的隐单元,然后再计算这些隐单元的加权求和并得到最终结果,如下图所示:

在这里插入图片描述

毫无疑问,在中间加了一层隐单元之后,模型变得更加精细了。但是这也意味着,这个模型在学习过程中需要考虑更多的系数:在每个输入与每个隐单元(隐单元组成了隐层)之间有一个系数,在每个隐单元与输出之间也有一个系数。

实际上,从数学的角度看,计算一系列加权求和与只计算一个加权求和是完全相同的,因此,为了让这个模型真正比线性模型更为强大,我们还需要一个技巧。在计算完每个隐单元的加权求和之后,对结果再应用一个非线性函数-通常是校正非线性(也叫校正线性单元或relu)正切双曲线(也叫tanh)。然后将这个函数的结果用于加权求和,计算得到输出y。有了这两种非线性函数,神经网络可以学习比线性模型复杂得多的函数。具体的公式如下所示:

h[0]=tanh(w[0,0]*x[0]+w[1,0]*x[1]+w[2,0]*x[2]+w[3,0]*x[3]+b[0])
h[1]=tanh(w[0,0]*x[0]+w[1,0]*x[1]+w[2,0]*x[2]+w[3,0]*x[3]+b[1])
h[2]=tanh(w[0,0]*x[0]+w[1,0]*x[1]+w[2,0]*x[2]+w[3,0]*x[3]+b[2])
y=v[0]*h[0]+v[1]*h[1]+v[2]*h[2]+b

其中,w是输入x与隐层h之间的权重,v是隐层h与输出y之间的权重。权重w和v要从数据中学习得到,x是输入特征,y是计算得到的输出,h是计算的中间结果。需要用户设置的一个重要参数是隐层中的结点个数。对于非常小或非常简单的数据集,这个值可以小到10;对于非常复杂的数据,这个值可以大到10000。当然,模型中可以添加多个隐层,如下图所示:

在这里插入图片描述

这些由许多计算层组成的大型神经网络,正是术语“深度学习”的灵感来源。

数据来源

信用卡客户分类:https://www.kaggle.com/sakshigoyal7/credit-card-customers

在这里插入图片描述

该数据包括信用卡客户的性别,年龄,收入,消费金额,学历等维度的数据,而我们所需要做的就是通过这些特征值来区分有哪些客户还在继续使用信用卡,哪些已经放弃使用,

在这里插入图片描述

数据集包含23个特征维度,10127条特征记录,然后客户分为2类:Existing Customer-还在使用的客户,Attrited Customer-不使用的客户。

数据挖掘

1.导入第三方库并读取文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd
import numpy as np
import winreg
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier#多层感知机-MLP
from sklearn.metrics import accuracy_score
###################
real_address = winreg.OpenKey(winreg.HKEY_CURRENT_USER,r'Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders',)
file_address=winreg.QueryValueEx(real_address, "Desktop")[0]
file_address+='\\'
file_origin=file_address+"\\源数据-分析\\BankChurners.csv"###https://www.kaggle.com/sakshigoyal7/credit-card-customers
Credit_Card=pd.read_csv(file_origin)
#设立桌面绝对路径,读取源数据文件,这样将数据直接下载到桌面上就可以了,省得还要去找
###################

老规矩,上来先依次导入建模需要的各个模块,并读取文件。

2.清洗数据

查找缺失值:

在这里插入图片描述

从上面的结果来看,数据中没有缺失值。但是有三列数据需要注意,特征CLIENTNUM代表客户编号,而最后两列只是源数据作者的贝叶斯分类计算的草稿,这三列属于无用的特征,可以直接删掉,代码如下所示:

1
Credit_Card=Credit_Card.drop(Credit_Card.columns[[0,-1,-2]],axis=1)

由于维度”Gender”,”Education_Level”,”Marital_Status”,”Income_Category”,”Card_Category”中的数值均是以字符文字的形式存在的,如果直接建模的话会报错,所以我们需要将其中的分类数据用数字进行替代,譬如在性别”Gender”中,用1代表F(男性),2代表M(女性),代码如下所示:

1
2
3
4
for i in ["Gender","Education_Level","Marital_Status","Income_Category","Card_Category"]:
m=list(set(Credit_Card[i]))
for x in range(len(m)):
Credit_Card.loc[Credit_Card[i]==m[x],i]=x###loc[行,列]

3.建模

1
2
3
train=Credit_Card.drop(["Attrition_Flag"],axis=1)
X_train,X_test,y_train,y_test=train_test_split(train,Credit_Card["Attrition_Flag"],random_state=1)
###考虑到接下来可能需要进行其他的操作,所以定了一个随机种子,保证接下来的train和test是同一组数

划分列索引为特征值和预测值,并将数据划分成训练集和测试集。

1
2
3
mlp=MLPClassifier(solver="lbfgs",random_state=1).fit(X_train,y_train)
print("mlp训练模型评分:"+str(accuracy_score(y_train,mlp.predict(X_train))))
print("mlp待测模型评分:"+str(accuracy_score(y_test,mlp.predict(X_test))))###警告是增大最大迭代次数,或者对数据进行标准化

引入MLP算法,并将算法中的参数依次设立好,进行建模后,对测试集进行精度评分,得到的结果如下:

在这里插入图片描述

可以看到,该模型的精度为84%左右。
注意:红色警告是算法提示要增大最大迭代次数,或者对数据进行标准化,这些问题我们稍后解决。

4.数据标准化

可以看出MLP的精度还可以,但没有其它模型好。与之前说的svm一样,原因就在于数据需要缩放。神经网络也要求所有输入特征的变化范围相似,最理想的情况是均值为0,方差为1,也就是标准化。我们必须对数据进行缩放以满足这些要求。为了让大家更好地理解,这里我们先人工进行处理(以后可以用StandardScaler来完成),代码如下所示:

1
2
3
4
mean_on_train=X_train.mean(axis=0)##计算每个特征的平均值
std_on_train=X_train.std(axis=0)##计算每个特征的标准差
X_train_scald=(X_train-mean_on_train)/std_on_train##减去平均值,然后乘以标准差的倒数,之后,mean=0,std=1
X_test_scald=(X_test-mean_on_train)/std_on_train##对测试集做同样的处理(使用训练集的平均值和标准差)

之后,我们对缩放完成的数据进行建模,代码及结果如下所示:

在这里插入图片描述

可以看出模型精度较之前有了提高,达到了93%。不仅如此,我还设定了最大迭代次数参数max_iter。实际上增加迭代次数仅提高了训练集性能,而不会提高泛化性能。不过由于数据缩放,所以模型的表现较之前有了提升。由于训练性能和测试性能之间仍有一定差距,甚至是出现了过拟合的情况,所以我们可以通过调整其它参数,来提高模型的泛化性能。

5.参数

通常来说,对于模型MLP的定义主要需要4个参数:隐层数,每层的结点,正则化和非线性函数的选择。当然还有一个问题是,如何学习模型或用来学习参数的算法,这一点由solver参数设定。接下来我们依次讲解这些参数。

1.solver

solver有两个好用的选项。默认选项是“adam”,在大多数情况下效果都很好,但对数据的缩放相当敏感(因此,始终将数据缩放为均值为0,方差为1是很重要的)。另一个选项是“lbfgs”,其鲁棒性相当好,但在大型模型或者大型数据集上的时间会比较长。还有更高级的“sgd”选项,许多深度学习研究人员都会用到。“sgd”选项还有许多其它参数需要调节,以便获得最佳结果。不过一般情况下,“adma”和“lbfgs”已经足够满足我们的日常使用。

2.activation

该参数表示的是之前我们提到过的非线性校正函数(relu/tanh),默认参数是relu。调参后的结果如下所示:

在这里插入图片描述

可以看到结果较之前反而有所下降,既然如此,我们依旧选择relu参数。

3.hidden_layer_sizes

该参数代表我们之前提到过的隐结点的个数和层数。默认情况下,MLP使用100个隐结点(hidden_layer_sizes=[100])。这对于一个小型数据集来说已经相当多了,我们可以通过减少隐结点数量,增加隐层数,来降低模型复杂度,提高模型的泛化能力,代码及结果如下所示:

1
2
3
mlp=MLPClassifier(solver="lbfgs",activation="relu",random_state=1,hidden_layer_sizes=[10,5],max_iter=100000).fit(X_train_scald,y_train)
print("mlp训练模型评分:"+str(accuracy_score(y_train,mlp.predict(X_train_scald))))
print("mlp待测模型评分:"+str(accuracy_score(y_test,mlp.predict(X_test_scald))))

在这里插入图片描述

hidden_layer_sizes=[10,5]的意思是该模型有两个隐层数,第一层有10个隐结点,第二层有5个隐结点,最后所得结果较之前也有了些提高。当然,感兴趣的小伙伴还可以试试设立三个隐层,以及其它数量的隐结点(如hidden_layer_sizes=[10,10,5]),看看结果较之前有没有变化。

4.alpha

正如我们在岭回归线性分类器中所做的那样,MLPClassifier中正则化调节L2惩罚系数的参数是alpha(与线性回归模型中的相同),它的默认值很小(弱正则化),下面我们通过遍历来选取一个合适的alpha参数:

1
2
3
4
5
6
result=pd.DataFrame(columns=["alpha","mlp训练模型评分","mlp待测模型评分"])
for i in range(1,20):
m=i/10
mlp=MLPClassifier(solver="lbfgs",activation="tanh",random_state=1,hidden_layer_sizes=[10,5],alpha=m,max_iter=100000).fit(X_train_scald,y_train)
result=result.append([{"alpha":m,"mlp训练模型评分":accuracy_score(y_train,mlp.predict(X_train_scald)),"mlp待测模型评分":accuracy_score(y_test,mlp.predict(X_test_scald))}])
result[result["mlp待测模型评分"]==result["mlp待测模型评分"].max()]

在这里插入图片描述

结果如上所示,当我们将alpha参数设立为1.9时,可以得到mlp的待测模型精度96%,较之前又有了些许提升。

现在可能大家已经意识到了,控制神经网络复杂度的方法有很多种:隐层的个数,每个隐层中的单元个数与正则化(alpha)。实际上还有很多,但这里就不在过多介绍了。

除了上述的参数之外,神经网络还有一个重要参数是random_state。在开始学习之前,神经网络的权重是随机设置的。也就是说,即使使用完全相同的参数,如果随机种子不同的话,我们也可能得到非常不一样的模型。如果网络很大,并且复杂度选择合理的话,那么这应该不会对精度有太大影响,但应该记住这一点(特别是对于较小的网络)。

小结

在机器学习的许多应用中,神经网络再次成为最先进的模型。它的主要优点之一是能够获取大量数据中包含的信息,并且构建无比复杂的模型。给定足够的计算时间和数据,并且仔细调节参数,神经网络通常可以打败其它机器学习算法(无论是分类任务还是回归任务)。

这就引出了下面要说的缺点。神经网络——特别是功能强大的大型神经网络——通常需要很长的训练时间。它还需要仔细地预处理数据,正如我们这里所看到的。与SVM类似,神经网络在“均匀”数据上的性能最好,其中“均匀”是指所有特征都具有相似的含义。如果数据包含不同种类的特征,那么基于树的模型(随机森林/梯度提升回归树)可能表现的更好。不过神经网络调参本身也是一门艺术。

神经网络调参的常用方法是,首先创建一个大到足以过拟合的网络,确保这个网络可以对任务进行学习。在知道训练数据可以被学习之后,要么缩小网络,要么增大alpha来增强正则化,这可以提高泛化性能。

有很多地方做的不是很好,欢迎网友来提出建议,也希望可以遇到些朋友来一起交流讨论。