
- 领 域
- 数据挖掘 疾病诊断 经济预测
- 定 义
- 线性回归模型
- 分 类
- 计算机 数学
- 用 途
- 预测 判别
- 中文名
- logistic回归
- 外文名
- logistic regressive
-
机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾
2015-11-12 12:07:12手把手机器学习之逻辑回归应用——Kaggle泰坦尼克之灾1.引言先说一句,年末双十一什么的一来,真是非(mang)常(cheng)欢(gou)乐(le)!然后push自己抽出时间来写这篇blog的原因也非常简单: 写完前两篇逻辑回归的介绍...作者: 寒小阳
时间:2015年11月。
出处:http://blog.csdn.net/han_xiaoyang/article/details/49797143
声明:版权所有,转载请注明出处,谢谢。1.引言
先说一句,年末双十一什么的一来,真是非(mang)常(cheng)欢(gou)乐(le)!然后push自己抽出时间来写这篇blog的原因也非常简单:
- 写完前两篇逻辑回归的介绍和各个角度理解之后,我们讨论群(戳我入群)的小伙伴们纷纷表示『好像很高级的样纸,but 然并卵 啊!你们倒是拿点实际数据来给我们看看,这玩意儿 有!什!么!用!啊!』
- talk is cheap, show me the code!
- no example say a jb!
OK,OK,这就来了咯,同学们别着急,我们先找个简单的实际例子,来看看,所谓的数据挖掘或者机器学习实际应用到底是怎么样一个过程。
『喂,那几个说要看大数据上机器学习应用的,对,就是说你们!别着急好么,我们之后拉点大一点实际数据用liblinear或者spark,MLlib跑给你们看,行不行?咱们先拿个实例入入门嘛』
好了,我是一个严肃的技术研究和分享者,咳咳,不能废话了,各位同学继续往下看吧!
2.背景
2.1 关于Kaggle
- 我是Kaggle地址,翻我牌子
- 亲,逼格这么高的地方,你一定听过对不对?是!这就是那个无数『数据挖掘先驱』们,在回答"枪我有了,哪能找到靶子练练手啊?"时候的答案!
- 这是一个要数据有数据,要实际应用场景有场景,要一起在数据挖掘领域high得不要不要的小伙伴就有小伙伴的地方啊!!!
艾玛,逗逼模式开太猛了。恩,不闹,不闹,说正事,Kaggle是一个数据分析建模的应用竞赛平台,有点类似KDD-CUP(国际知识发现和数据挖掘竞赛),企业或者研究者可以将问题背景、数据、期望指标等发布到Kaggle上,以竞赛的形式向广大的数据科学家征集解决方案。而热爱数(dong)据(shou)挖(zhe)掘(teng)的小伙伴们可以下载/分析数据,使用统计/机器学习/数据挖掘等知识,建立算法模型,得出结果并提交,排名top的可能会有奖金哦!
2.2 关于泰坦尼克号之灾
-
带大家去该问题页面溜达一圈吧
- 下面是问题背景页
- 下面是可下载Data的页面
- 下面是小伙伴们最爱的forum页面,你会看到各种神级人物厉(qi)害(pa)的数据处理/建模想法,你会直视『世界真奇妙』。
- 下面是问题背景页
-
泰坦尼克号问题之背景
-
就是那个大家都熟悉的『Jack and Rose』的故事,豪华游艇倒了,大家都惊恐逃生,可是救生艇的数量有限,无法人人都有,副船长发话了『lady and kid first!』,所以是否获救其实并非随机,而是基于一些背景有rank先后的。
-
训练和测试数据是一些乘客的个人信息以及存活状况,要尝试根据它生成合适的模型并预测其他人的存活状况。
-
对,这是一个二分类问题,是我们之前讨论的logistic regression所能处理的范畴。
-
3.说明
接触过Kaggle的同学们可能知道这个问题,也可能知道RandomForest和SVM等等算法,甚至还对多个模型做过融合,取得过非常好的结果,那maybe这篇文章并不是针对你的,你可以自行略过。
我们因为之前只介绍了Logistic Regression这一种分类算法。所以本次的问题解决过程和优化思路,都集中在这种算法上。其余的方法可能我们之后的文章里会提到。
说点个人的观点。不一定正确。
『解决一个问题的方法和思路不止一种』
『没有所谓的机器学习算法优劣,也没有绝对高性能的机器学习算法,只有在特定的场景、数据和特征下更合适的机器学习算法。』4.怎么做?
手把手教程马上就来,先来两条我看到的,觉得很重要的经验。
-
印象中Andrew Ng老师似乎在coursera上说过,应用机器学习,千万不要一上来就试图做到完美,先撸一个baseline的model出来,再进行后续的分析步骤,一步步提高,所谓后续步骤可能包括『分析model现在的状态(欠/过拟合),分析我们使用的feature的作用大小,进行feature selection,以及我们模型下的bad case和产生的原因』等等。
-
Kaggle上的大神们,也分享过一些experience,说几条我记得的哈:
- 『对数据的认识太重要了!』
- 『数据中的特殊点/离群点的分析和处理太重要了!』
- 『特征工程(feature engineering)太重要了!在很多Kaggle的场景下,甚至比model本身还要重要』
- 『要做模型融合(model ensemble)啊啊啊!』
更多的经验分享请加讨论群,具体方式请联系作者,或者参见《“ML学分计划”说明书》
5.初探数据
先看看我们的数据,长什么样吧。在Data下我们train.csv和test.csv两个文件,分别存着官方给的训练和测试数据。
import pandas as pd #数据分析 import numpy as np #科学计算 from pandas import Series,DataFrame data_train = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/Train.csv") data_train
pandas是常用的python数据处理包,把csv文件读入成dataframe各式,我们在ipython notebook中,看到data_train如下所示:
这就是典型的dataframe格式,如果你没接触过这种格式,完全没有关系,你就把它想象成Excel里面的列好了。
我们看到,总共有12列,其中Survived字段表示的是该乘客是否获救,其余都是乘客的个人信息,包括:- PassengerId => 乘客ID
- Pclass => 乘客等级(1/2/3等舱位)
- Name => 乘客姓名
- Sex => 性别
- Age => 年龄
- SibSp => 堂兄弟/妹个数
- Parch => 父母与小孩个数
- Ticket => 船票信息
- Fare => 票价
- Cabin => 客舱
- Embarked => 登船港口
逐条往下看,要看完这么多条,眼睛都有一种要瞎的赶脚。好吧,我们让dataframe自己告诉我们一些信息,如下所示:
data_train.info()
看到了如下的信息:
上面的数据说啥了?它告诉我们,训练数据中总共有891名乘客,但是很不幸,我们有些属性的数据不全,比如说:
- Age(年龄)属性只有714名乘客有记录
- Cabin(客舱)更是只有204名乘客是已知的
似乎信息略少啊,想再瞄一眼具体数据数值情况呢?恩,我们用下列的方法,得到数值型数据的一些分布(因为有些属性,比如姓名,是文本型;而另外一些属性,比如登船港口,是类目型。这些我们用下面的函数是看不到的):
我们从上面看到更进一步的什么信息呢?
mean字段告诉我们,大概0.383838的人最后获救了,2/3等舱的人数比1等舱要多,平均乘客年龄大概是29.7岁(计算这个时候会略掉无记录的)等等…6.数据初步分析
每个乘客都这么多属性,那我们咋知道哪些属性更有用,而又应该怎么用它们啊?说实话这会儿我也不知道,但我们记得前面提到过
- 『对数据的认识太重要了!』
- 『对数据的认识太重要了!』
- 『对数据的认识太重要了!』
重要的事情说三遍,恩,说完了。仅仅最上面的对数据了解,依旧无法给我们提供想法和思路。我们再深入一点来看看我们的数据,看看每个/多个 属性和最后的Survived之间有着什么样的关系呢。
6.1 乘客各属性分布
脑容量太有限了…数值看花眼了。我们还是统计统计,画些图来看看属性和结果之间的关系好了,代码如下:
import matplotlib.pyplot as plt fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 plt.subplot2grid((2,3),(0,0)) # 在一张大图里分列几个小图 data_train.Survived.value_counts().plot(kind='bar')# 柱状图 plt.title(u"获救情况 (1为获救)") # 标题 plt.ylabel(u"人数") plt.subplot2grid((2,3),(0,1)) data_train.Pclass.value_counts().plot(kind="bar") plt.ylabel(u"人数") plt.title(u"乘客等级分布") plt.subplot2grid((2,3),(0,2)) plt.scatter(data_train.Survived, data_train.Age) plt.ylabel(u"年龄") # 设定纵坐标名称 plt.grid(b=True, which='major', axis='y') plt.title(u"按年龄看获救分布 (1为获救)") plt.subplot2grid((2,3),(1,0), colspan=2) data_train.Age[data_train.Pclass == 1].plot(kind='kde') data_train.Age[data_train.Pclass == 2].plot(kind='kde') data_train.Age[data_train.Pclass == 3].plot(kind='kde') plt.xlabel(u"年龄")# plots an axis lable plt.ylabel(u"密度") plt.title(u"各等级的乘客年龄分布") plt.legend((u'头等舱', u'2等舱',u'3等舱'),loc='best') # sets our legend for our graph. plt.subplot2grid((2,3),(1,2)) data_train.Embarked.value_counts().plot(kind='bar') plt.title(u"各登船口岸上船人数") plt.ylabel(u"人数") plt.show()
bingo,图还是比数字好看多了。所以我们在图上可以看出来,被救的人300多点,不到半数;3等舱乘客灰常多;遇难和获救的人年龄似乎跨度都很广;3个不同的舱年龄总体趋势似乎也一致,2/3等舱乘客20岁多点的人最多,1等舱40岁左右的最多(→_→似乎符合财富和年龄的分配哈,咳咳,别理我,我瞎扯的);登船港口人数按照S、C、Q递减,而且S远多于另外俩港口。
这个时候我们可能会有一些想法了:
- 不同舱位/乘客等级可能和财富/地位有关系,最后获救概率可能会不一样
- 年龄对获救概率也一定是有影响的,毕竟前面说了,副船长还说『小孩和女士先走』呢
- 和登船港口是不是有关系呢?也许登船港口不同,人的出身地位不同?
口说无凭,空想无益。老老实实再来统计统计,看看这些属性值的统计分布吧。
6.2 属性与获救结果的关联统计
#看看各乘客等级的获救情况 fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 Survived_0 = data_train.Pclass[data_train.Survived == 0].value_counts() Survived_1 = data_train.Pclass[data_train.Survived == 1].value_counts() df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0}) df.plot(kind='bar', stacked=True) plt.title(u"各乘客等级的获救情况") plt.xlabel(u"乘客等级") plt.ylabel(u"人数") plt.show()
啧啧,果然,钱和地位对舱位有影响,进而对获救的可能性也有影响啊←_←
咳咳,跑题了,我想说的是,明显等级为1的乘客,获救的概率高很多。恩,这个一定是影响最后获救结果的一个特征。#看看各性别的获救情况 fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 Survived_m = data_train.Survived[data_train.Sex == 'male'].value_counts() Survived_f = data_train.Survived[data_train.Sex == 'female'].value_counts() df=pd.DataFrame({u'男性':Survived_m, u'女性':Survived_f}) df.plot(kind='bar', stacked=True) plt.title(u"按性别看获救情况") plt.xlabel(u"性别") plt.ylabel(u"人数") plt.show()
歪果盆友果然很尊重lady,lady first践行得不错。性别无疑也要作为重要特征加入最后的模型之中。
再来个详细版的好了。
#然后我们再来看看各种舱级别情况下各性别的获救情况 fig=plt.figure() fig.set(alpha=0.65) # 设置图像透明度,无所谓 plt.title(u"根据舱等级和性别的获救情况") ax1=fig.add_subplot(141) data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3].value_counts().plot(kind='bar', label="female highclass", color='#FA2479') ax1.set_xticklabels([u"获救", u"未获救"], rotation=0) ax1.legend([u"女性/高级舱"], loc='best') ax2=fig.add_subplot(142, sharey=ax1) data_train.Survived[data_train.Sex == 'female'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='female, low class', color='pink') ax2.set_xticklabels([u"未获救", u"获救"], rotation=0) plt.legend([u"女性/低级舱"], loc='best') ax3=fig.add_subplot(143, sharey=ax1) data_train.Survived[data_train.Sex == 'male'][data_train.Pclass != 3].value_counts().plot(kind='bar', label='male, high class',color='lightblue') ax3.set_xticklabels([u"未获救", u"获救"], rotation=0) plt.legend([u"男性/高级舱"], loc='best') ax4=fig.add_subplot(144, sharey=ax1) data_train.Survived[data_train.Sex == 'male'][data_train.Pclass == 3].value_counts().plot(kind='bar', label='male low class', color='steelblue') ax4.set_xticklabels([u"未获救", u"获救"], rotation=0) plt.legend([u"男性/低级舱"], loc='best') plt.show()
恩,坚定了之前的判断。
我们看看各登船港口的获救情况。
fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 Survived_0 = data_train.Embarked[data_train.Survived == 0].value_counts() Survived_1 = data_train.Embarked[data_train.Survived == 1].value_counts() df=pd.DataFrame({u'获救':Survived_1, u'未获救':Survived_0}) df.plot(kind='bar', stacked=True) plt.title(u"各登录港口乘客的获救情况") plt.xlabel(u"登录港口") plt.ylabel(u"人数") plt.show()
下面我们来看看 堂兄弟/妹,孩子/父母有几人,对是否获救的影响。
g = data_train.groupby(['SibSp','Survived']) df = pd.DataFrame(g.count()['PassengerId']) print df g = data_train.groupby(['SibSp','Survived']) df = pd.DataFrame(g.count()['PassengerId']) print df
好吧,没看出特别特别明显的规律(为自己的智商感到捉急…),先作为备选特征,放一放。
#ticket是船票编号,应该是unique的,和最后的结果没有太大的关系,先不纳入考虑的特征范畴把 #cabin只有204个乘客有值,我们先看看它的一个分布 data_train.Cabin.value_counts()
部分结果如下:
这三三两两的…如此不集中…我们猜一下,也许,前面的ABCDE是指的甲板位置、然后编号是房间号?…好吧,我瞎说的,别当真…
关键是Cabin这鬼属性,应该算作类目型的,本来缺失值就多,还如此不集中,注定是个棘手货…第一感觉,这玩意儿如果直接按照类目特征处理的话,太散了,估计每个因子化后的特征都拿不到什么权重。加上有那么多缺失值,要不我们先把Cabin缺失与否作为条件(虽然这部分信息缺失可能并非未登记,maybe只是丢失了而已,所以这样做未必妥当),先在有无Cabin信息这个粗粒度上看看Survived的情况好了。
fig = plt.figure() fig.set(alpha=0.2) # 设定图表颜色alpha参数 Survived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts() Survived_nocabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts() df=pd.DataFrame({u'有':Survived_cabin, u'无':Survived_nocabin}).transpose() df.plot(kind='bar', stacked=True) plt.title(u"按Cabin有无看获救情况") plt.xlabel(u"Cabin有无") plt.ylabel(u"人数") plt.show()
咳咳,有Cabin记录的似乎获救概率稍高一些,先这么着放一放吧。
7.简单数据预处理
大体数据的情况看了一遍,对感兴趣的属性也有个大概的了解了。
下一步干啥?咱们该处理处理这些数据,为机器学习建模做点准备了。对了,我这里说的数据预处理,其实就包括了很多Kaggler津津乐道的feature engineering过程,灰常灰常有必要!
『特征工程(feature engineering)太重要了!』
『特征工程(feature engineering)太重要了!』
『特征工程(feature engineering)太重要了!』恩,重要的事情说三遍。
先从最突出的数据属性开始吧,对,Cabin和Age,有丢失数据实在是对下一步工作影响太大。
先说Cabin,暂时我们就按照刚才说的,按Cabin有无数据,将这个属性处理成Yes和No两种类型吧。
再说Age:
通常遇到缺值的情况,我们会有几种常见的处理方式
- 如果缺值的样本占总数比例极高,我们可能就直接舍弃了,作为特征加入的话,可能反倒带入noise,影响最后的结果了
- 如果缺值的样本适中,而该属性非连续值特征属性(比如说类目属性),那就把NaN作为一个新类别,加到类别特征中
- 如果缺值的样本适中,而该属性为连续值特征属性,有时候我们会考虑给定一个step(比如这里的age,我们可以考虑每隔2/3岁为一个步长),然后把它离散化,之后把NaN作为一个type加到属性类目中。
- 有些情况下,缺失的值个数并不是特别多,那我们也可以试着根据已有的值,拟合一下数据,补充上。
本例中,后两种处理方式应该都是可行的,我们先试试拟合补全吧(虽然说没有特别多的背景可供我们拟合,这不一定是一个多么好的选择)
我们这里用scikit-learn中的RandomForest来拟合一下缺失的年龄数据(注:RandomForest是一个用在原始数据中做不同采样,建立多颗DecisionTree,再进行average等等来降低过拟合现象,提高结果的机器学习算法,我们之后会介绍到)
from sklearn.ensemble import RandomForestRegressor ### 使用 RandomForestClassifier 填补缺失的年龄属性 def set_missing_ages(df): # 把已有的数值型特征取出来丢进Random Forest Regressor中 age_df = df[['Age','Fare', 'Parch', 'SibSp', 'Pclass']] # 乘客分成已知年龄和未知年龄两部分 known_age = age_df[age_df.Age.notnull()].as_matrix() unknown_age = age_df[age_df.Age.isnull()].as_matrix() # y即目标年龄 y = known_age[:, 0] # X即特征属性值 X = known_age[:, 1:] # fit到RandomForestRegressor之中 rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1) rfr.fit(X, y) # 用得到的模型进行未知年龄结果预测 predictedAges = rfr.predict(unknown_age[:, 1::]) # 用得到的预测结果填补原缺失数据 df.loc[ (df.Age.isnull()), 'Age' ] = predictedAges return df, rfr def set_Cabin_type(df): df.loc[ (df.Cabin.notnull()), 'Cabin' ] = "Yes" df.loc[ (df.Cabin.isnull()), 'Cabin' ] = "No" return df data_train, rfr = set_missing_ages(data_train) data_train = set_Cabin_type(data_train)
恩。目的达到,OK了。
因为逻辑回归建模时,需要输入的特征都是数值型特征,我们通常会先对类目型的特征因子化。
什么叫做因子化呢?举个例子:以Cabin为例,原本一个属性维度,因为其取值可以是[‘yes’,‘no’],而将其平展开为’Cabin_yes’,'Cabin_no’两个属性
- 原本Cabin取值为yes的,在此处的"Cabin_yes"下取值为1,在"Cabin_no"下取值为0
- 原本Cabin取值为no的,在此处的"Cabin_yes"下取值为0,在"Cabin_no"下取值为1
我们使用pandas的"get_dummies"来完成这个工作,并拼接在原来的"data_train"之上,如下所示。
dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix= 'Cabin') dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix= 'Embarked') dummies_Sex = pd.get_dummies(data_train['Sex'], prefix= 'Sex') dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix= 'Pclass') df = pd.concat([data_train, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1) df.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True) df
bingo,我们很成功地把这些类目属性全都转成0,1的数值属性了。
这样,看起来,是不是我们需要的属性值都有了,且它们都是数值型属性呢。
有一种临近结果的宠宠欲动感吧,莫急莫急,我们还得做一些处理,仔细看看Age和Fare两个属性,乘客的数值幅度变化,也忒大了吧!!如果大家了解逻辑回归与梯度下降的话,会知道,各属性值之间scale差距太大,将对收敛速度造成几万点伤害值!甚至不收敛! (╬▔皿▔)…所以我们先用scikit-learn里面的preprocessing模块对这俩货做一个scaling,所谓scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内。
import sklearn.preprocessing as preprocessing scaler = preprocessing.StandardScaler() age_scale_param = scaler.fit(df['Age']) df['Age_scaled'] = scaler.fit_transform(df['Age'], age_scale_param) fare_scale_param = scaler.fit(df['Fare']) df['Fare_scaled'] = scaler.fit_transform(df['Fare'], fare_scale_param) df
恩,好看多了,万事俱备,只欠建模。马上就要看到成效了,哈哈。我们把需要的属性值抽出来,转成scikit-learn里面LogisticRegression可以处理的格式。
8.逻辑回归建模
我们把需要的feature字段取出来,转成numpy格式,使用scikit-learn中的LogisticRegression建模。
from sklearn import linear_model # 用正则取出我们要的属性值 train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*') train_np = train_df.as_matrix() # y即Survival结果 y = train_np[:, 0] # X即特征属性值 X = train_np[:, 1:] # fit到RandomForestRegressor之中 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) clf.fit(X, y) clf
good,很顺利,我们得到了一个model,如下:
先淡定!淡定!你以为把test.csv直接丢进model里就能拿到结果啊…骚年,图样图森破啊!我们的"test_data"也要做和"train_data"一样的预处理啊!!
data_test = pd.read_csv("/Users/Hanxiaoyang/Titanic_data/test.csv") data_test.loc[ (data_test.Fare.isnull()), 'Fare' ] = 0 # 接着我们对test_data做和train_data中一致的特征变换 # 首先用同样的RandomForestRegressor模型填上丢失的年龄 tmp_df = data_test[['Age','Fare', 'Parch', 'SibSp', 'Pclass']] null_age = tmp_df[data_test.Age.isnull()].as_matrix() # 根据特征属性X预测年龄并补上 X = null_age[:, 1:] predictedAges = rfr.predict(X) data_test.loc[ (data_test.Age.isnull()), 'Age' ] = predictedAges data_test = set_Cabin_type(data_test) dummies_Cabin = pd.get_dummies(data_test['Cabin'], prefix= 'Cabin') dummies_Embarked = pd.get_dummies(data_test['Embarked'], prefix= 'Embarked') dummies_Sex = pd.get_dummies(data_test['Sex'], prefix= 'Sex') dummies_Pclass = pd.get_dummies(data_test['Pclass'], prefix= 'Pclass') df_test = pd.concat([data_test, dummies_Cabin, dummies_Embarked, dummies_Sex, dummies_Pclass], axis=1) df_test.drop(['Pclass', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked'], axis=1, inplace=True) df_test['Age_scaled'] = scaler.fit_transform(df_test['Age'], age_scale_param) df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'], fare_scale_param) df_test
不错不错,数据很OK,差最后一步了。
下面就做预测取结果吧!!test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*') predictions = clf.predict(test) result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)}) result.to_csv("/Users/Hanxiaoyang/Titanic_data/logistic_regression_predictions.csv", index=False)
啧啧,挺好,格式正确,去make a submission啦啦啦!
在Kaggle的Make a submission页面,提交上结果。如下:
0.76555,恩,结果还不错。毕竟,这只是我们简单分析处理过后出的一个baseline模型嘛。
9.逻辑回归系统优化
9.1 模型系数关联分析
亲,你以为结果提交上了,就完事了?
我不会告诉你,这只是万里长征第一步啊(泪牛满面)!!!这才刚撸完baseline model啊!!!还得优化啊!!!看过Andrew Ng老师的machine Learning课程的同学们,知道,我们应该分析分析模型现在的状态了,是过/欠拟合?,以确定我们需要更多的特征还是更多数据,或者其他操作。我们有一条很著名的learning curves对吧。
不过在现在的场景下,先不着急做这个事情,我们这个baseline系统还有些粗糙,先再挖掘挖掘。
-
首先,Name和Ticket两个属性被我们完整舍弃了(好吧,其实是因为这俩属性,几乎每一条记录都是一个完全不同的值,我们并没有找到很直接的处理方式)。
-
然后,我们想想,年龄的拟合本身也未必是一件非常靠谱的事情,我们依据其余属性,其实并不能很好地拟合预测出未知的年龄。再一个,以我们的日常经验,小盆友和老人可能得到的照顾会多一些,这样看的话,年龄作为一个连续值,给一个固定的系数,应该和年龄是一个正相关或者负相关,似乎体现不出两头受照顾的实际情况,所以,说不定我们把年龄离散化,按区段分作类别属性会更合适一些。
上面只是我瞎想的,who knows是不是这么回事呢,老老实实先把得到的model系数和feature关联起来看看。
pd.DataFrame({"columns":list(train_df.columns)[1:], "coef":list(clf.coef_.T)})
首先,大家回去前两篇文章里瞄一眼公式就知道,这些系数为正的特征,和最后结果是一个正相关,反之为负相关。
我们先看看那些权重绝对值非常大的feature,在我们的模型上:
- Sex属性,如果是female会极大提高最后获救的概率,而male会很大程度拉低这个概率。
- Pclass属性,1等舱乘客最后获救的概率会上升,而乘客等级为3会极大地拉低这个概率。
- 有Cabin值会很大程度拉升最后获救概率(这里似乎能看到了一点端倪,事实上从最上面的有无Cabin记录的Survived分布图上看出,即使有Cabin记录的乘客也有一部分遇难了,估计这个属性上我们挖掘还不够)
- Age是一个负相关,意味着在我们的模型里,年龄越小,越有获救的优先权(还得回原数据看看这个是否合理)
- 有一个登船港口S会很大程度拉低获救的概率,另外俩港口压根就没啥作用(这个实际上非常奇怪,因为我们从之前的统计图上并没有看到S港口的获救率非常低,所以也许可以考虑把登船港口这个feature去掉试试)。
- 船票Fare有小幅度的正相关(并不意味着这个feature作用不大,有可能是我们细化的程度还不够,举个例子,说不定我们得对它离散化,再分至各个乘客等级上?)
噢啦,观察完了,我们现在有一些想法了,但是怎么样才知道,哪些优化的方法是promising的呢?
因为test.csv里面并没有Survived这个字段(好吧,这是废话,这明明就是我们要预测的结果),我们无法在这份数据上评定我们算法在该场景下的效果…
而『每做一次调整就make a submission,然后根据结果来判定这次调整的好坏』其实是行不通的…
9.2 交叉验证
重点又来了:
『要做交叉验证(cross validation)!』
『要做交叉验证(cross validation)!』
『要做交叉验证(cross validation)!』恩,重要的事情说三遍。我们通常情况下,这么做cross validation:把train.csv分成两部分,一部分用于训练我们需要的模型,另外一部分数据上看我们预测算法的效果。
我们用scikit-learn的cross_validation来帮我们完成小数据集上的这个工作。
先简单看看cross validation情况下的打分
from sklearn import cross_validation #简单看看打分情况 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) all_data = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*') X = all_data.as_matrix()[:,1:] y = all_data.as_matrix()[:,0] print cross_validation.cross_val_score(clf, X, y, cv=5)
结果是下面酱紫的:
[0.81564246 0.81005587 0.78651685 0.78651685 0.81355932]似乎比Kaggle上的结果略高哈,毕竟用的是不是同一份数据集评估的。
等等,既然我们要做交叉验证,那我们干脆先把交叉验证里面的bad case拿出来看看,看看人眼审核,是否能发现什么蛛丝马迹,是我们忽略了哪些信息,使得这些乘客被判定错了。再把bad case上得到的想法和前头系数分析的合在一起,然后逐个试试。
下面我们做数据分割,并且在原始数据集上瞄一眼bad case:
# 分割数据,按照 训练数据:cv数据 = 7:3的比例 split_train, split_cv = cross_validation.train_test_split(df, test_size=0.3, random_state=0) train_df = split_train.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*') # 生成模型 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) clf.fit(train_df.as_matrix()[:,1:], train_df.as_matrix()[:,0]) # 对cross validation数据进行预测 cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*') predictions = clf.predict(cv_df.as_matrix()[:,1:]) origin_data_train = pd.read_csv("/Users/HanXiaoyang/Titanic_data/Train.csv") bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)] bad_cases
我们判定错误的 bad case 中部分数据如下:
大家可以自己跑一遍试试,拿到bad cases之后,仔细看看。也会有一些猜测和想法。其中会有一部分可能会印证在系数分析部分的猜测,那这些优化的想法优先级可以放高一些。
现在有了"train_df" 和 “vc_df” 两个数据部分,前者用于训练model,后者用于评定和选择模型。可以开始可劲折腾了。
我们随便列一些可能可以做的优化操作:
- Age属性不使用现在的拟合方式,而是根据名称中的『Mr』『Mrs』『Miss』等的平均值进行填充。
- Age不做成一个连续值属性,而是使用一个步长进行离散化,变成离散的类目feature。
- Cabin再细化一些,对于有记录的Cabin属性,我们将其分为前面的字母部分(我猜是位置和船层之类的信息) 和 后面的数字部分(应该是房间号,有意思的事情是,如果你仔细看看原始数据,你会发现,这个值大的情况下,似乎获救的可能性高一些)。
- Pclass和Sex俩太重要了,我们试着用它们去组出一个组合属性来试试,这也是另外一种程度的细化。
- 单加一个Child字段,Age<=12的,设为1,其余为0(你去看看数据,确实小盆友优先程度很高啊)
- 如果名字里面有『Mrs』,而Parch>1的,我们猜测她可能是一个母亲,应该获救的概率也会提高,因此可以多加一个Mother字段,此种情况下设为1,其余情况下设为0
- 登船港口可以考虑先去掉试试(Q和C本来就没权重,S有点诡异)
- 把堂兄弟/兄妹 和 Parch 还有自己 个数加在一起组一个Family_size字段(考虑到大家族可能对最后的结果有影响)
- Name是一个我们一直没有触碰的属性,我们可以做一些简单的处理,比如说男性中带某些字眼的(‘Capt’, ‘Don’, ‘Major’, ‘Sir’)可以统一到一个Title,女性也一样。
大家接着往下挖掘,可能还可以想到更多可以细挖的部分。我这里先列这些了,然后我们可以使用手头上的"train_df"和"cv_df"开始试验这些feature engineering的tricks是否有效了。
试验的过程比较漫长,也需要有耐心,而且我们经常会面临很尴尬的状况,就是我们灵光一闪,想到一个feature,然后坚信它一定有效,结果试验下来,效果还不如试验之前的结果。恩,需要坚持和耐心,以及不断的挖掘。
我最好的结果是在『Survived~C(Pclass)+C(Title)+C(Sex)+C(Age_bucket)+C(Cabin_num_bucket)Mother+Fare+Family_Size』下取得的,结果如下(抱歉,博主君commit的时候手抖把页面关了,于是没截着图,下面这张图是在我得到最高分之后,用这次的结果重新make commission的,截了个图,得分是0.79426,不是目前我的最高分哈,因此排名木有变…):
9.3 learning curves
有一个很可能发生的问题是,我们不断地做feature engineering,产生的特征越来越多,用这些特征去训练模型,会对我们的训练集拟合得越来越好,同时也可能在逐步丧失泛化能力,从而在待预测的数据上,表现不佳,也就是发生过拟合问题。
从另一个角度上说,如果模型在待预测的数据上表现不佳,除掉上面说的过拟合问题,也有可能是欠拟合问题,也就是说在训练集上,其实拟合的也不是那么好。
额,这个欠拟合和过拟合怎么解释呢。这么说吧:
- 过拟合就像是你班那个学数学比较刻板的同学,老师讲过的题目,一字不漏全记下来了,于是老师再出一样的题目,分分钟精确出结果。but数学考试,因为总是碰到新题目,所以成绩不咋地。
- 欠拟合就像是,咳咳,和博主level差不多的差生。连老师讲的练习题也记不住,于是连老师出一样题目复习的周测都做不好,考试更是可想而知了。
而在机器学习的问题上,对于过拟合和欠拟合两种情形。我们优化的方式是不同的。
对过拟合而言,通常以下策略对结果优化是有用的:
- 做一下feature selection,挑出较好的feature的subset来做training
- 提供更多的数据,从而弥补原始数据的bias问题,学习到的model也会更准确
而对于欠拟合而言,我们通常需要更多的feature,更复杂的模型来提高准确度。
著名的learning curve可以帮我们判定我们的模型现在所处的状态。我们以样本数为横坐标,训练和交叉验证集上的错误率作为纵坐标,两种状态分别如下两张图所示:过拟合(overfitting/high variace),欠拟合(underfitting/high bias)
我们也可以把错误率替换成准确率(得分),得到另一种形式的learning curve(sklearn 里面是这么做的)。
回到我们的问题,我们用scikit-learn里面的learning_curve来帮我们分辨我们模型的状态。举个例子,这里我们一起画一下我们最先得到的baseline model的learning curve。
import numpy as np import matplotlib.pyplot as plt from sklearn.learning_curve import learning_curve # 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib画出learning curve def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1, train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True): """ 画出data在某模型上的learning curve. 参数解释 ---------- estimator : 你用的分类器。 title : 表格的标题。 X : 输入的feature,numpy类型 y : 输入的target vector ylim : tuple格式的(ymin, ymax), 设定图像中纵坐标的最低点和最高点 cv : 做cross-validation的时候,数据分成的份数,其中一份作为cv集,其余n-1份作为training(默认为3份) n_jobs : 并行的的任务数(默认1) """ train_sizes, train_scores, test_scores = learning_curve( estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose) train_scores_mean = np.mean(train_scores, axis=1) train_scores_std = np.std(train_scores, axis=1) test_scores_mean = np.mean(test_scores, axis=1) test_scores_std = np.std(test_scores, axis=1) if plot: plt.figure() plt.title(title) if ylim is not None: plt.ylim(*ylim) plt.xlabel(u"训练样本数") plt.ylabel(u"得分") plt.gca().invert_yaxis() plt.grid() plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std, alpha=0.1, color="b") plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std, alpha=0.1, color="r") plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分") plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分") plt.legend(loc="best") plt.draw() plt.show() plt.gca().invert_yaxis() midpoint = ((train_scores_mean[-1] + train_scores_std[-1]) + (test_scores_mean[-1] - test_scores_std[-1])) / 2 diff = (train_scores_mean[-1] + train_scores_std[-1]) - (test_scores_mean[-1] - test_scores_std[-1]) return midpoint, diff plot_learning_curve(clf, u"学习曲线", X, y)
在实际数据上看,我们得到的learning curve没有理论推导的那么光滑哈,但是可以大致看出来,训练集和交叉验证集上的得分曲线走势还是符合预期的。
目前的曲线看来,我们的model并不处于overfitting的状态(overfitting的表现一般是训练集上得分高,而交叉验证集上要低很多,中间的gap比较大)。因此我们可以再做些feature engineering的工作,添加一些新产出的特征或者组合特征到模型中。
10.模型融合(model ensemble)
好了,终于到这一步了,我们要祭出机器学习/数据挖掘上通常最后会用到的大杀器了。恩,模型融合。
『强迫症患者』打算继续喊喊口号…
『模型融合(model ensemble)很重要!』
『模型融合(model ensemble)很重要!』
『模型融合(model ensemble)很重要!』
重要的事情说三遍,恩,噢啦。先解释解释,一会儿再回到我们的问题上哈。
啥叫模型融合呢,我们还是举几个例子直观理解一下好了。大家都看过知识问答的综艺节目中,求助现场观众时候,让观众投票,最高的答案作为自己的答案的形式吧,每个人都有一个判定结果,最后我们相信答案在大多数人手里。
再通俗一点举个例子。你和你班某数学大神关系好,每次作业都『模仿』他的,于是绝大多数情况下,他做对了,你也对了。突然某一天大神脑子犯糊涂,手一抖,写错了一个数,于是…恩,你也只能跟着错了。
我们再来看看另外一个场景,你和你班5个数学大神关系都很好,每次都把他们作业拿过来,对比一下,再『自己做』,那你想想,如果哪天某大神犯糊涂了,写错了,but另外四个写对了啊,那你肯定相信另外4人的是正确答案吧?最简单的模型融合大概就是这么个意思,比如分类问题,当我们手头上有一堆在同一份数据集上训练得到的分类器(比如logistic regression,SVM,KNN,random forest,神经网络),那我们让他们都分别去做判定,然后对结果做投票统计,取票数最多的结果为最后结果。
bingo,问题就这么完美的解决了。
模型融合可以比较好地缓解,训练过程中产生的过拟合问题,从而对于结果的准确度提升有一定的帮助。
话说回来,回到我们现在的问题。你看,我们现在只讲了logistic regression,如果我们还想用这个融合思想去提高我们的结果,我们该怎么做呢?
既然这个时候模型没得选,那咱们就在数据上动动手脚咯。大家想想,如果模型出现过拟合现在,一定是在我们的训练上出现拟合过度造成的对吧。
那我们干脆就不要用全部的训练集,每次取训练集的一个subset,做训练,这样,我们虽然用的是同一个机器学习算法,但是得到的模型却是不一样的;同时,因为我们没有任何一份子数据集是全的,因此即使出现过拟合,也是在子训练集上出现过拟合,而不是全体数据上,这样做一个融合,可能对最后的结果有一定的帮助。对,这就是常用的Bagging。
我们用scikit-learn里面的Bagging来完成上面的思路,过程非常简单。代码如下:
from sklearn.ensemble import BaggingRegressor train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title') train_np = train_df.as_matrix() # y即Survival结果 y = train_np[:, 0] # X即特征属性值 X = train_np[:, 1:] # fit到BaggingRegressor之中 clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1) bagging_clf.fit(X, y) test = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title') predictions = bagging_clf.predict(test) result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)}) result.to_csv("/Users/HanXiaoyang/Titanic_data/logistic_regression_bagging_predictions.csv", index=False)
然后你再Make a submission,恩,发现对结果还是有帮助的。
11.总结
文章稍微有点长,非常感谢各位耐心看到这里。
总结的部分,我就简短写几段,出现的话,很多在文中有对应的场景,大家有兴趣再回头看看。对于任何的机器学习问题,不要一上来就追求尽善尽美,先用自己会的算法撸一个baseline的model出来,再进行后续的分析步骤,一步步提高。
在问题的结果过程中:
- 『对数据的认识太重要了!』
- 『数据中的特殊点/离群点的分析和处理太重要了!』
- 『特征工程(feature engineering)太重要了!』
- 『模型融合(model ensemble)太重要了!』
本文中用机器学习解决问题的过程大概如下图所示:
12.关于数据和代码
本文中的数据和代码已经上传至github中,欢迎大家下载和自己尝试。
-
逻辑回归
2019-02-28 16:28:15Q1:逻辑回归的原理 Q2:逻辑回归为什么又叫对率回归 Q3:逻辑回归为什么使用sigmoid Q4:信息熵的定义及推导过程 Q5:逻辑回归损失函数的推导过程 Q6:逻辑回归为什么要用交叉熵作为损失函数,为什么不用平方...目录
Q6:逻辑回归为什么要用交叉熵作为损失函数,为什么不用平方损失函数
Q1:逻辑回归的原理
logistic回归是一种广义线性回归(generalized linear model),假设待预测数据服从伯努利分布,通过极大化似然函数的方法,运用梯度下降来求解参数,来达到将数据分类的目的。
Q2:逻辑回归为什么又叫对率回归
将逻辑回归的公式进行整理,我们可以得到
如果把一个事件的几率(odds)定义为该事件发生的概率与该事件不发生的概率的比值
p / (1 - p) ,那么逻辑回归可以看作是对于“y=1|x”这一事件的对数几率的线性回归,于是“逻辑回归”这一称谓也就延续了下来。
Q3:逻辑回归为什么使用sigmoid
先来看一下sigmoid函数和它的图像:
(1)sigmoid函数的输出范围是(0,1),符合概率的定义;
(2)sigmoid函数是一个单调递增函数;
(3)对于二分类问题,其满足伯努利分布。
上述的回答过于笼统,下面我将详细的描述为什么会是这个函数。事实上并不是因为sigmoid函数有很多优秀的性质才使得logstic回归选择了它,而是冥冥之中自有定数。
首先贴一个描述指数簇分布和广义线性模型的图。
明白这两个性质之后,我们就可以开始推导了。
Q4:信息熵的定义及推导过程
信息熵是考虑该随机变量(可以理解为label,只能是分类,不能用于回归)的所有可能取值(即所有可能发生事件)所带来的信息量的期望。表示随机变量的不确定性,显然均匀分布的不确定性最大,当随机变量为一个定值的时候,信息熵是最小的。
假设现在有两个互相独立的事件
和事件
,这两件事的信息量之和,显然应该是它们两相加
;
同时由于事件
和事件
互相独立,所以有
;
一眼看过去这两个公式,好像有那么点
联系!
也就是说,我们似乎可以用概率去度量信息量。
于是我们得到了信息量的度量公式,符号很明显是为了把负数变为正数。
当一件事情是确定一定发生的,也就是当
时,该事件的信息量最小,这听起来很符合常理——比如说我们知道父亲一定是男性,众所周知,这句话也就没什么信息量(一个好无聊的例子...)。
那么信息熵则是一个随机变量所有可能取值所带来的信息量的期望,于是就有:
(待补充,均匀分布的信息熵最大)
Q5:逻辑回归损失函数的推导过程
我们从伯努利分布的质量函数说起,对于二分类问题,我们假设 y 是服从伯努利分布的,当样本和参数确定时,属于类别1和类别2的概率也就确定了。
这显然又是一个似然函数,可以通过极大似然估计+梯度下降(牛顿法也行)求解。还是常规操作,先取一个
。
注意:最后一个式子右边少了一个log 于是我们就得到了逻辑回归的损失函数,我们又称其为交叉熵:
Q6:逻辑回归为什么要用交叉熵作为损失函数,为什么不用平方损失函数
当逻辑回归使用平方损失函数时,损失函数是非凸的,有很多局部最小值;而交叉熵则是凸函数,图像如下:
如果使用均方误差,有梯度下降法可知
有绝对值项,很麻烦,而且从上面sigmoid函数的导数图像可知,当自变量 x 很大或很小时,导数接近0,梯度下降的速度将会极其缓慢,用交叉熵则不然,这也要得益于sigmoid函数的优良性质。我们会发现当真实值和预测值的误差较大时,梯度下降会更快;反之则会更慢,这完全符合我们的预期。
注意:这里第二个式子下面应该是b不是w Q7:逻辑回归为什么可以表示概率,表示的是真实的概率吗?
当待预测变量 y 确实服从伯努利分布且满足广义线性模型的第三个条件时,可以认为逻辑回归输出的就是真实的概率,训练模型的过程,就确实是在对概率进行建模。但一般都不满足这个条件,所以呢,很多情况下,我们得出的逻辑回归输出值,无法当作真实的概率,只能作为置信度来使用。
这里再简单说明一下怎么判断一个随机变量是否符合二项分布。
二项分布(Binomial Distribution),即重复n次的伯努里试验(Bernoulli Experiment),如果
(1)在每次试验中只有两种可能的结果,而且是互相对立的;
(2)每次实验是独立的,与其它各次试验结果无关;
(3)结果事件发生的概率在整个系列试验中保持不变,则这一系列试验称为伯努力试验.
简单来说就是要满足二分类+对立、每次互相独立、概率不变。
Q8:如何求解逻辑回归的损失函数
这里用批梯度下降法来推导。
几种梯度下降法的优劣:
(1)批梯度下降通常会获得全局最优解,缺点是在更新每个参数的时候需要遍历所有的数据,计算量会很大,并且会有很多的冗余计算,导致的结果是当数据量大的时候,每个参数的更新都会很慢;
(2)随机梯度下降是以高方差频繁更新,优点是使得SGD会跳到新的和潜在更好的局部最优解,速度较快,缺点是使得收敛到局部最优解的过程更加杂乱,且有可能收敛到局部最优解;
(3)小批量梯度下降结合了SGD和BGD的优点,每次更新的时候使用一小部分样本。减少了参数更新的次数,可以达到更加稳定收敛结果,一般在深度学习当中我们采用这种方法。
这里其实还有几个问题,我会专门写一个梯度下降和牛顿法的博客来解释。
(1)如何对模型选择合适的学习率;
(2)如何对参数选择合适的学习率。
Q9:逻辑回归如何防止过拟合
算法层面-正则化:
- L1正则,通过增大正则项导致更多参数为0,参数系数化降低模型复杂度,从而抵抗过拟合。
- L2正则,通过使得参数都趋于0,变得很小,降低模型的抖动,从而抵抗过拟合。
- 特征离散化
数据层面:
- 加大样本量。
- 通过特征选择减少特征量。
业务层面:
- EDA-探索有区分性的特征。
- 特征派生-不断派生更多强组合的特征。
来源:https://www.zhihu.com/question/269763205/answer/599663035
Q10:逻辑回归如何做多分类
方法一:多分类问题中的拆分策略
有两种简单的拆分策略:
(1)"一对一" (OvO)
(2)"一对其余"(OvR,rest)
OvO将这N个类别两两配对,从而产生N(N-1)/2个二分类任务,例如OvO将为区分类别Ci和Cj训练一个分类器,该分类器把D中的Ci类样例作为正例,Cj类样例作为反例。在测试阶段,新样本将同时提交给所有分类器,于是我们将得到N(N-1)/2个分类结果,最终结果可通过投票产生:即把被预测得最多的类别作为最终分类结果。
OvR则是每次将一个类的样例作为正例、所有其他类的样例作为反例来训练N个分类器。在测试时若仅有一个分类器预测为正类,则对应的类别标记作为最终分类结果。若有多个分类器预测为正类,则通常考虑各分类器的预测置信度,选择置信度最大的类别标记作为分类结果。
方法二:多项逻辑回归(softmax regression)
Q11:逻辑回归和线性回归的异同点
Q12:逻辑回归的特征为什么要做离散化,这样做有什么好处
工业界CTR预估一般都是用LR,而且特征都是离散的。这样做有这么几个好处:
(1)离散特征的增加和减少都很容易,易于模型的快速迭代;
(2)稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
(3)散化后的特征对异常数据有很强的鲁棒性,更加稳定;
(4)特征离散化后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
(5)离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
(6)特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
当使用连续特征时,一个特征对应于一个权重,那么,如果这个特征权重较大,模型就会很依赖于这个特征,这个特征的一个微小变化可能会导致最终结果产生很大的变化,这样子的模型很危险,当遇到新样本的时候很可能因为对这个特征过分敏感而得到错误的分类结果,也就是泛化能力差,容易过拟合。而使用离散特征的时候,一个特征变成了多个,权重也变为多个,那么之前连续特征对模型的影响力就被分散弱化了,从而降低了过拟合的风险。
总结一下就是:
(1)计算快,易存储;
(2)方便调整特征,更加稳定,更易迭代;相当于简化了模型,降低过拟合的风险。
(3)表达能力提升,更强的鲁棒性;
参考:https://blog.csdn.net/zhangbaoanhadoop/article/details/82657791?utm_source=blogxgwz5
Q13:逻辑回归的优缺点
优点
- 形式简单,模型的可解释性非常好。从特征的权重可以看到不同的特征对最后结果的影响,某个特征的权重值比较高,那么这个特征最后对结果的影响会比较大。
- 模型效果不错。在工程上是可以接受的(作为baseline),如果特征工程做的好,效果不会太差,并且特征工程可以大家并行开发,大大加快开发的速度。
- 训练速度较快。分类的时候,计算量仅仅只和特征的数目相关。并且逻辑回归的分布式优化sgd发展比较成熟,训练的速度可以通过堆机器进一步提高,这样我们可以在短时间内迭代好几个版本的模型。
- 资源占用小,尤其是内存。因为只需要存储各个维度的特征值,。
- 方便输出结果调整。逻辑回归可以很方便的得到最后的分类结果,因为输出的是每个样本的概率分数,我们可以很容易的对这些概率分数进行cutoff,也就是划分阈值(大于某个阈值的是一类,小于某个阈值的是一类)。
缺点
- 准确率并不是很高。因为形式非常的简单(非常类似线性模型),很难去拟合数据的真实分布。
- 很难处理数据不平衡的问题。举个例子:如果我们对于一个正负样本非常不平衡的问题比如正负样本比 10000:1.我们把所有样本都预测为正也能使损失函数的值比较小。但是作为一个分类器,它对正负样本的区分能力不会很好。
- 处理非线性数据较麻烦。逻辑回归在不引入其他方法的情况下,只能处理线性可分的数据,或者进一步说,处理二分类的问题 。
- 逻辑回归本身无法筛选特征。有时候,我们会用GBDT来筛选特征,然后再上逻辑回归。
参考博客
https://blog.csdn.net/qq_19645269/article/details/79551576
https://blog.csdn.net/huwenxing0801/article/details/82791879
https://www.zhihu.com/question/269763205
https://blog.csdn.net/zhangbaoanhadoop/article/details/82657791?utm_source=blogxgwz5
-
逻辑回归(logistics regression)
2018-11-10 22:39:17逻辑回归(logistics regression) 前几章分别讲了多元线性回归的推理思路和求解过程(解析解求解和梯度下降求解),文章并不以代码和公式推导过程为重点,目的是跟大家一起理解算法.前两章的内容是学习算法的基础,所以...逻辑回归(logistics regression)
前几章分别讲了多元线性回归的推理思路和求解过程(解析解求解和梯度下降求解),文章并不以代码和公式推导过程为重点,目的是跟大家一起理解算法.前两章的内容是学习算法的基础,所以本章会在前两章的基础上讨论逻辑回归(logistics regression).逻辑回归也属于有监督机器学习.
之前我们了解到了多元线性回归是用线性的关系来拟合一个事情的发生规律,找到这个规律的表达公式,将得到的数据带入公式以用来实现预测的目的,我们习惯将这类预测未来的问题称作回归问题.机器学习中按照目的不同可以分为两大类:回归和分类.今天我们一起讨论的逻辑回归就可以用来完成分类任务.
本文将通过以下几部分来讲解逻辑回归:
一.分类和回归任务的区别
二.逻辑回归不是回归
三.如果是你,你要怎么做
四.把回归函数掰弯
五.选定阈值
六.最大似然估计
七.求解交叉熵损失函数
一.分类和回归任务的区别
我们可以按照任务的种类,将任务分为回归任务和分类任务.那这两者的区别是什么呢?按照较官方些的说法,输入变量与输出变量均为连续变量的预测问题是回归问题,输出变量为有限个离散变量的预测问题成为分类问题.
通俗一点讲,我们要预测的结果是一个数,比如要通过一个人的饮食预测一个人的体重,体重的值可以有无限多个,有的人50kg,有的人51kg,在50和51之间也有无限多个数.这种预测结果是某一个确定数,而具体是哪个数有无限多种可能的问题,我们会训练出一个模型,传入参数后得到这个确定的数,这类问题我们称为回归问题.预测的这个变量(体重)因为有无限多种可能,在数轴上是连续的,所以我们称这种变量为连续变量.
我们要预测一个人身体健康或者不健康,预测会得癌症或者不会得癌症,预测他是水瓶座,天蝎座还是射手座,这种结果只有几个值或者多个值的问题,我们可以把每个值都当做一类,预测对象到底属于哪一类.这样的问题称为分类问题.如果一个分类问题的结果只有两个,比如"是"和"不是"两个结果,我们把结果为"是"的样例数据称为"正例",讲结果为"不是"的样例数据称为"负例",对应的,这种结果的变量称为离散型变量.
二.逻辑回归不是回归
从名字来理解逻辑回归.在逻辑回归中,逻辑一词是logistics [lə'dʒɪstɪks]的音译字,并不是因为这个算法是突出逻辑的特性.
至于回归,我们前一段讲到回归任务是结果为连续型变量的任务,logistics regression是用来做分类任务的,为什么叫回归呢?那我们是不是可以假设,逻辑回归就是用回归的办法来做分类的呢.跟上思路.
三.如果是你,你要怎么做
假设刚刚的思路是正确的,逻辑回归就是在用回归的办法做分类任务,那有什么办法可以做到呢,此时我们就先考虑最简单的二分类,结果是正例或者负例的任务.
按照多元线性回归的思路,我们可以先对这个任务进行线性回归,学习出这个事情结果的规律,比如根据人的饮食,作息,工作和生存环境等条件预测一个人"有"或者"没有"得恶性肿瘤,可以先通过回归任务来预测人体内肿瘤的大小,取一个平均值作为阈值,假如平均值为y,肿瘤大小超过y为恶心肿瘤,无肿瘤或大小小于y的,为非恶性.这样通过线性回归加设定阈值的办法,就可以完成一个简单的二分类任务.如下图:
上图中,红色的x轴为肿瘤大小,粉色的线为回归出的函数
的图像,绿色的线为阈值.
预测肿瘤大小还是一个回归问题,得到的结果(肿瘤的大小)也是一个连续型变量.通过设定阈值,就成功将回归问题转化为了分类问题.但是,这样做还存在一个问题.
我们上面的假设,依赖于所有的肿瘤大小都不会特别离谱,如果有一个超大的肿瘤在我们的例子中,阈值就很难设定.加入还是取平均大小为阈值,则会出现下图的情况:
从上边的例子可以看出,使用线性的函数来拟合规律后取阈值的办法是行不通的,行不通的原因在于拟合的函数太直,离群值(也叫异常值)对结果的影响过大,但是我们的整体思路是没有错的,错的是用了太"直"的拟合函数,如果我们用来拟合的函数是非线性的,不这么直,是不是就好一些呢?
所以我们下面来做两件事:
1-找到一个办法解决掉回归的函数严重受离群值影响的办法.
2-选定一个阈值.
四:把回归函数掰弯
没错,本小节用来解决上边说的第一个问题.开玩笑了,无论如何我也不可能掰弯这个函数.我们能做的呢,就是换一个.原来的判别函数我们用线性的y =
,逻辑回归的函数呢,我们目前就用sigmod函数,函数如下:
公式中,e为欧拉常数(是常数,如果不知道,自行百度),Z就是我们熟悉的多元线性回归中的
,建议现阶段大家先记住逻辑回归的判别函数用它就好了.如果你不服,请参考:朱先生1994的博客(博客讲的很好).
就像我们说多元线性回归的判别函数为
一样.追究为什么是他花费的经历会比算法本身更多.
sigmod函数的图像如下:
该函数具有很强的鲁棒性(鲁棒是Robust的音译,也就是健壮和强壮的意思),并且将函数的输入范围(∞,-∞)映射到了输出的(0,1)之间且具有概率意义.具有概率意义是怎么理解呢:将一个样本输入到我们学习到的函数中,输出0.7,意思就是这个样本有70%的概率是正例,1-70%就是30%的概率为负例.
再次强调一下,如果你的数学功底很好,可以看一下我上边分享的为什么是sigmod函数的连接,如果数学一般,我们这个时候没有必要纠结为什么是sigmod,函数那么多为什么选他.学习到后边你自然就理解了.
总结一下上边所讲:我们利用线性回归的办法来拟合然后设置阈值的办法容易受到离群值的影响,sigmod函数可以有效的帮助我们解决这一个问题,所以我们只要在拟合的时候把
即y =
换成
即可,其中
z=
,也就是说g(z) =
. 同时,因为g(z)函数的特性,它输出的结果也不再是预测结果,而是一个值预测为正例的概率,预测为负例的概率就是1-g(z).
函数形式表达:
P(y=0|w,x) = 1 – g(z)
P(y=1|w,x) = g(z)
P(正确) =
*
为某一条样本的预测值,取值范围为0或者1.
到这里,我们得到一个回归函数,它不再像y=wT * x一样受离群值影响,他的输出结果是样本预测为正例的概率(0到1之间的小数).我们接下来解决第二个问题:选定一个阈值.
五:选定阈值
选定阈值的意思就是,当我选阈值为0.5,那么小于0.5的一定是负例,哪怕他是0.49.此时我们判断一个样本为负例一定是准确的吗?其实不一定,因为它还是有49%的概率为正利的.但是即便他是正例的概率为0.1,我们随机选择1w个样本来做预测,还是会有接近100个预测它是负例结果它实际是正例的误差.无论怎么选,误差都是存在的.所以我们选定阈值的时候就是在选择可以接受误差的程度.
我们现在知道了sigmod函数预测结果为一个0到1之间的小数,选定阈值的第一反应,大多都是选0.5,其实实际工作中并不一定是0.5,阈值的设定往往是根据实际情况来判断的.本小节我们只举例让大家理解为什么不完全是0.5,并不会有一个万能的答案,都是根据实际工作情况来定的.
0到1之间的数阈值选作0.5当然是看着最舒服的,可是假设此时我们的业务是像前边的例子一样,做一个肿瘤的良性恶性判断.选定阈值为0.5就意味着,如果一个患者得恶性肿瘤的概率为0.49,模型依旧认为他没有患恶性肿瘤,结果就是造成了严重的医疗事故.此类情况我们应该将阈值设置的小一些.阈值设置的小,加入0.3,一个人患恶性肿瘤的概率超过0.3我们的算法就会报警,造成的结果就是这个人做一个全面检查,比起医疗事故来讲,显然这个更容易接受.
第二种情况,加入我们用来识别验证码,输出的概率为这个验证码识别正确的概率.此时我们大可以将概率设置的高一些.因为即便识别错了又能如何,造成的结果就是在一个session时间段内重试一次.机器识别验证码就是一个不断尝试的过程,错误率本身就很高.
以上两个例子可能不大准确,只做意会,你懂了就好. [此时我的表情无法描述]
到这里,逻辑回归的由来我们就基本理清楚了,现在我们知道了逻辑回归的判别函数就是
,z=
.休息两分钟,我们下面看如何求解逻辑回归,也就是如何找到一组可以让
全都预测正确的概率最大的W.
六.最大似然估计
此时我们想要找到一组w,使函数
正确的概率最大.而我们在上面的推理过程中已经得到每个单条样本预测正确概率的公式:
P(正确) =
*
若想让预测出的结果全部正确的概率最大,根据最大似然估计(多元线性回归推理中有讲过,此处不再赘述),就是所有样本预测正确的概率相乘得到的P(总体正确)最大,此时我们让
,数学表达形式如下:
上述公式最大时公式中W的值就是我们要的最好的W.下面对公式进行求解.
我们知道,一个连乘的函数是不好计算的,我们可以通过两边同事取log的形式让其变成连加.
得到的这个函数越大,证明我们得到的W就越好.因为在函数最优化的时候习惯让一个函数越小越好,所以我们在前边加一个负号.得到公式如下:
这个函数就是我们逻辑回归(logistics regression)的损失函数,我们叫它交叉熵损失函数.
七.求解交叉熵损失函数
求解损失函数的办法我们还是使用梯度下降,同样在批量梯度下降与随机梯度下降一节有详细写到,此处我们只做简要概括.
求解步骤如下:
1-随机一组W.
2-将W带入交叉熵损失函数,让得到的点沿着负梯度的方向移动.
3-循环第二步.
求解梯度部分同样是对损失函数求偏导,过程如下:
交叉熵损失函数的梯度和最小二乘的梯度形式上完全相同,区别在于,此时的
,而最小二乘的
.
PS:加一个总结:逻辑回归为什么对切斜的数据特别敏感(正负例数据比例相差悬殊时预测效果不好)
首先从文章开头部分举例的两个图可以看到,使用线性模型进行分类第一个要面对的问题就是如何降低离群值的影响,而第二大问题就是,在正负例数据比例相差悬殊时预测效果不好.为什么会出现这种情况呢?原因来自于逻辑回归交叉熵损失函数是通过最大似然估计来推导出的.
使用最大似然估计来推导损失函数,那无疑,我们得到的结果就是所有样本被预测正确的最大概率.注意重点是我们得到的结果是预测正确率最大的结果,100个样本预测正确90个和预测正确91个的两组w,我们会选正确91个的这一组.那么,当我们的业务场景是来预测垃圾邮件,预测黄色图片时,我们数据中99%的都是负例(不是垃圾邮件不是黄色图片),如果有两组w,第一组为所有的负例都预测正确,而正利预测错误,正确率为99%,第二组是正利预测正确了,但是负例只预测出了97个,正确率为98%.此时我们算法会认为第一组w是比较好的.但实际我们业务需要的是第二组,因为正例检测结果才是业务的根本.
此时我们需要对数据进行欠采样/重采样来让正负例保持一个差不多的平衡,或者使用树型算法来做分类.一般树型分类的算法对数据倾斜并不是很敏感,但我们在使用的时候还是要对数据进行欠采样/重采样来观察结果是不是有变好.
到这里,逻辑回归就讲解完毕了.请大家帮忙勘误,共同学习.感谢您的耐心阅读.
下一篇:一文读懂L-BFGS算法
-
逻辑回归(logistic regression)的本质——极大似然估计
2017-08-14 19:36:24逻辑回归是分类当中极为常用的手段,因此,掌握其内在原理是非常必要的。我会争取在本文中尽可能简明地展现逻辑回归(logistic regression)的整个推导过程。1 前言
逻辑回归是分类当中极为常用的手段,因此,掌握其内在原理是非常必要的。我会争取在本文中尽可能简明地展现逻辑回归(logistic regression)的整个推导过程。
2 什么是逻辑回归
逻辑回归在某些书中也被称为对数几率回归,明明被叫做回归,却用在了分类问题上,我个人认为这是因为逻辑回归用了和回归类似的方法来解决了分类问题。
假设有一个二分类问题,输出为,而线性回归模型产生的预测值为是实数值,我们希望有一个理想的阶跃函数来帮我们实现值到值的转化。
然而该函数不连续,我们希望有一个单调可微的函数来供我们使用,于是便找到了来替代。
两者的图像如下图所示(图片出自文献2)
图1:sigmoid & step function 有了之后,由于其取值在,我们就可以将其视为类的后验概率估计。说白了,就是如果有了一个测试点,那么就可以用算出来的结果来当做该点属于类别的概率大小。
于是,非常自然地,我们把计算得到的值大于等于的归为类别,小于的归为类别。
同时逻辑回归与自适应线性网络非常相似,两者的区别在于逻辑回归的激活函数是而自适应线性网络的激活函数是,两者的网络结构如下图所示(图片出自文献1)。
图2:自适应线性网络 图3:逻辑回归网络 3 逻辑回归的代价函数
好了,所要用的几个函数我们都有了,接下来要做的就是根据给定的训练集,把参数给求出来了。要找参数,首先就是得把代价函数(cost function)给定义出来,也就是目标函数。
我们第一个想到的自然是模仿线性回归的做法,利用误差平方和来当代价函数。
其中,,表示第个样本点,表示第个样本的真实值,表示第个样本的预测值。
这时,如果我们将代入的话,会发现这是一个非凸函数,这就意味着代价函数有着许多的局部最小值,这不利于我们的求解。
图4:凸函数和非凸函数 那么我们不妨来换一个思路解决这个问题。前面,我们提到了可以视为类的后验估计,所以我们有
其中,表示给定,那么点的概率大小。
上面两式可以写成一般形式
接下来我们就要用极大似然估计来根据给定的训练集估计出参数。
为了简化运算,我们对上面这个等式的两边都取一个对数
我们现在要求的是使得最大的。没错,我们的代价函数出现了,我们在前面加个负号不就变成就最小了吗?不就变成我们代价函数了吗?
为了更好地理解这个代价函数,我们不妨拿一个例子的来看看
也就是说
我们来看看这是一个怎么样的函数
图5:代价函数 从图中不难看出,如果样本的值是的话,估计值越接近付出的代价就越小,反之越大;同理,如果样本的值是的话,估计值越接近付出的代价就越小,反之越大。
4 利用梯度下降法求参数
在开始梯度下降之前,要这里插一句,有一个很好的性质就是
下面会用到这个性质。
还有,我们要明确一点,梯度的负方向就是代价函数下降最快的方向。什么?为什么?好,我来说明一下。借助于泰特展开,我们有
其中,和为向量,那么这两者的内积就等于
当时,也就是在的负方向上时,取得最小值,也就是下降的最快的方向了~
okay?好,坐稳了,我们要开始下降了。
没错,就是这么下降。没反应过来?那我再写详细一些
其中,表示第个特征的权重;为学习率,用来控制步长。
重点来了。
所以,在使用梯度下降法更新权重时,只要根据下式即可
此式与线性回归时更新权重用的式子极为相似,也许这也是逻辑回归要在后面加上回归两个字的原因吧。
当然,在样本量极大的时候,每次更新权重会非常耗费时间,这时可以采用随机梯度下降法,这时每次迭代时需要将样本重新打乱,然后用下式不断更新权重。
也就是去掉了求和,而是针对每个样本点都进行更新。
5 结束语
以上就是我参考了基本书中的说法之后对逻辑回归整个推到过程的梳理,也不知道讲清楚没有。
如有不足,还请指正~6 参考文献
[1] Raschka S. Python Machine Learning[M]. Packt Publishing, 2015.
[2] 周志华. 机器学习 : = Machine learning[M]. 清华大学出版社, 2016. -
逻辑回归 - 理论篇
2014-07-16 15:42:14什么是逻辑回归? Logistic回归与多重线性回归实际上有很多相同之处,最大的区别就在于它们的因变量不同,其他的基本都差不多。正是因为如此,这两种回归可以归于同一个家族,即广义线性模型(generalizedli... -
逻辑回归画图_逻辑回归
2020-08-11 21:53:47逻辑回归画图翻译自: https://medium.com/@er.amansingh2019/logistic-regression-401b3a7723de逻辑回归画图 -
R语言多元Logistic逻辑回归 应用案例
2019-06-14 15:00:55可以使用阶梯函数通过逐步过程确定多重逻辑回归。此函数选择模型以最小化AIC,而不是像手册中的SAS示例那样根据p值。另请注意,在此示例中,步骤函数找到了与“ 手册”中的过程不同的模型。 通常建议不要盲目地遵循... -
机器学习之逻辑回归(logistics regression)
2020-06-27 19:42:11逻辑回归(Logistic Regression)与线性回归(Linear Regression)都是一种广义线性模型(generalized linear model)。逻辑回归假设因变量 y 服从伯努利分布,而线性回归假设因变量 y 服从高斯分布。 因此逻辑回归... -
第七章 逻辑回归 - 多元逻辑回归
2020-03-10 21:41:43第七章 逻辑回归 - 多元逻辑回归、一对多分类 -
逻辑回归详谈
2013-09-01 22:03:56本文从数学上对逻辑回归做了一个详尽的分析,其中包括回归分析、最小二乘法、sigmoid 函数以及梯度下降等知识点的介绍。 -
逻辑回归三部曲——逻辑回归(logistics regression)原理-让你彻底读懂逻辑回归
2020-05-05 21:25:52逻辑回归原理详解 -
逻辑回归详解
2018-08-15 22:08:19什么是逻辑回归、逻辑回归的损失函数、梯度下降算法推导与逻辑回归的 Python 实现 -
逻辑回归 总结
2021-03-02 09:31:57逻辑回归 总结概述Sigmoid 函数逻辑回归实现案例一 概述 逻辑回归 (Logic Regression) 本质上就是线性回归. 虽然逻辑回归被称为回归, 但实际上是一个分类模型, 用作二分类问题. 逻辑回归的决策边界可以是非线性的. ... -
逻辑回归之为什么叫逻辑回归
2019-01-31 11:18:12为什么叫逻辑回归? 我们先欣赏一条S型曲线: 再来欣赏一条长得比较像、但是比较直的“曲线”: 图一叫做 logistic function,又叫做 sigmod function。图二叫做单位阶跃函数。逻辑函数取值范围是(0, 1),单位阶跃... -
逻辑回归算法总结
2020-04-15 15:23:47逻辑回归 -
Sklearn-LogisticRegression逻辑回归
2017-02-06 11:19:53逻辑回归: 可以做概率预测,也可用于分类,仅能用于线性问题。通过计算真实值与预测值的概率,然后变换成损失函数,求损失函数最小值来计算模型参数,从而得出模型。 sklearn.linear_model.LogisticRegression... -
sklearn——线性逻辑回归和非线性逻辑回归
2020-06-07 19:58:02线性逻辑回归 本文用代码实现怎么利用sklearn来进行线性逻辑回归的计算,下面先来看看用到的数据。 这是有两行特征的数据,然后第三行是数据的标签。 python代码 首先导入包和载入数据 写一个画图的函数,把这些... -
逻辑回归的通俗解释 逻辑回归的定位
2018-02-07 16:34:321 逻辑回归的定位 首先,逻辑回归是一种分类(Classification)算法。比如说: 给定一封邮件,判断是不是垃圾邮件给出一个交易明细数据,判断这个交易是否是欺诈交易给出一个肿瘤检查的结果数据,判断这个肿瘤... -
逻辑回归——一文带你搞懂逻辑回归原理
2020-02-26 16:03:39目录 1、绘制X轴、Y轴平行线 2、绘制折线图 1)问题一:如何将等式左右连续化 2)问题二:使用... 本人是统计学专业,这里将自己学习中所理解的逻辑回归,给你们做一个详细的说明,希望能帮助到你们...