黑马程序员技术交流社区

标题: 【上海校区】机器学习实战--泰坦尼克号预测案例 [打印本页]

作者: 不二晨    时间: 2018-10-19 09:59
标题: 【上海校区】机器学习实战--泰坦尼克号预测案例
一、关于泰坦尼克号之灾



泰坦尼克号的这个实战案例来自kaggle,上面是它的基本介绍情况。

二、很重要的经验

『对数据的认识太重要了!』
『数据中的特殊点/离群点的分析和处理太重要了!』
『特征工程(feature engineering)太重要了!在很多Kaggle的场景下,甚至比model本身还要重要』
『要做模型融合(model ensemble)!』
(注:这个总结是寒老师的总结,这里我就直接用了)

三、实战项目

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics
from sklearn.linear_model import LogisticRegression
step1:数据理解和探索

train_df = pd.read_csv('train.csv')
train_df.info()
train_df.head(20)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB


从信息中可以看出,年龄的值有缺失值,cabin(客舱)的缺失值更多,Embarked(码头)仅仅只有2个缺失值¶

数据探索

#### 查看Survived与Pclass的关系
Survived_Pclass=train_df['Pclass'].groupby(train_df['Survived'])
Survived_Pclass.value_counts().unstack()

Survived_Pclass.value_counts().unstack().plot(kind='bar',stacked = True)
plt.show()




从图中可以看出,Pclass与获救程度有一定的相关性,获救的部分中接近一半的人数来自1号船舱,而未获救的人数主要来自3号船舱,且 未获救的人中来自1号船舱的非常少

#### 查看Survived与Sex的关系

Survived_Sex=train_df['Sex'].groupby(train_df['Survived'])
Survived_Sex.value_counts().unstack()

Survived_Sex.value_counts().unstack().plot(kind='bar',stacked=True)
plt.show()




说明性别对是否获救有很大的关系,未获救的人中绝大多数是男性。女性很少,获救的人中女性占了大部分的比例

#### 查看Survived与Age的关系
首先要对缺失值处理

train_df['Age']=train_df['Age'].fillna(train_df['Age'].mean())
train_df.info()

train_age = pd.cut(train_df['Age'],bins =[0,5,15,20,35,50,60,100])
train_age

survie_age =pd.DataFrame(train_df['Survived'].values,train_age,columns = ['Survived',])

survie_age.head(10)
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            891 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
0      (20, 35]
1      (35, 50]
2      (20, 35]
3      (20, 35]
4      (20, 35]
5      (20, 35]
6      (50, 60]
7        (0, 5]
8      (20, 35]........
### 利用交叉表crosstab

pd.crosstab(train_age,train_df['Survived'])

pd.crosstab(train_age,train_df['Survived']).plot(kind='bar',stacked=True)
pd.crosstab(train_age,train_df['Survived']).plot(kind='bar')
plt.show()




从图中可以看出,孩子的优先级比较高,未获救的相对来说比较少,其中20-35岁之间未获救的人数特别多

step2:数据准备

train_x = train_df[['Pclass','Sex','Age','SibSp','Parch','Fare']]
train_y = train_df['Survived']
train_x.info()
train_x.head()

train_x['Sex']=train_x['Sex'].replace({'male':1,'female':0})
train_x.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 6 columns):
Pclass    891 non-null int64
Sex       891 non-null int64
Age       891 non-null float64
SibSp     891 non-null int64
Parch     891 non-null int64
Fare      891 non-null float64
dtypes: float64(2), int64(4)
memory usage: 41.8 KB
# 构建第一个分类模型,利用LR模型
lr = LogisticRegression()
lr.fit(train_x,train_y)

print(lr.coef_)
pd.DataFrame(list(zip(np.transpose(lr.coef_),train_x.columns)),columns=['coef_','names'])

lr.score(train_x,train_y)
Out[69]:

LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)


Out[72]:

0.7991021324354658
尝试其他的分类算法

from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier

names = ['LogisticRegression','LinearSVC','KNeighborsClassifier','RandomForestClassifier','DecisionTreeClassifier']
clfs =[LogisticRegression(),LinearSVC(),KNeighborsClassifier(n_neighbors=3),RandomForestClassifier(),DecisionTreeClassifier()]
for name,clf in zip(names,clfs):
    clf.fit(x_train,y_train)
    print(clf)
    print("train_score:",clf.score(x_train,y_train))
    y_pre = clf.predict(x_test)
    test_score = metrics.accuracy_score(y_pre,y_test)
    print("test_score:",test_score)
    print('\n')
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)
train_score: 0.799357945425
test_score: 0.791044776119


LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0)
train_score: 0.735152487961
test_score: 0.776119402985


KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=3, p=2,
           weights='uniform')
train_score: 0.821829855538
test_score: 0.705223880597


RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1,
            oob_score=False, random_state=None, verbose=0,
            warm_start=False)
train_score: 0.963081861958
test_score: 0.798507462687


DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
train_score: 0.980738362761
test_score: 0.753731343284
很显然,决策树和随机森林都发生了严重的过拟合,k近林也有稍许的过拟合,只有逻辑回归在训练集和测试集上两者的差异不是特别明显,因此这里认为逻辑回归取得了较好的效果.

接下来,可以尝试做更多的特征来进行拟合,即我们说的特征工程,再接着,我们不妨尝试下各种模型的融合和参数调优。

特征工程 --准备一个更好的数据集

从1.构建第一个分类模型.ipynb的训练中可以看出,Pclass,Sex,Age对模型应该有很大的关系,其中从图中我们可以看出,经过离散化后,比如age<10(孩子)获救的机会很大,而 age>60则获救的机会不大,因此,年龄我们可以试想经过离散化后,新增成一个特征
还有一个问题,从1.构建第一个分类模型的逻辑回归的系数中,我们发现
[-0.82762531292] Pclass 呈现负相关;  
[-2.51023957603] Sex 呈现负相关;
[-0.0288397831037] Age 呈现负相关,其实我们从最开始的数据分析中可以看出,获救与否与年龄呈现山峰的关系,故这种解释不太正常;
还有一个问题,我们仔细观察name数据,可以发现,名字中没有Mr,Mrs,Miss等,在结合父母孩子的数量,我们大致来猜测age缺失值,于是我们猜想可不可以利用其他的一些的特征来拟合age的缺失值。
Age属性不使用现在的拟合方式,而是根据名称中的『Mr』『Mrs』『Miss』等的平均值进行填充(缺失值的填充)。
Age不做成一个连续值属性,而是使用一个步长进行离散化,变成离散的类目feature(连续变量离散化)。
Pclass和Sex俩太重要了,我们试着用它们去组出一个组合属性来试试,这也是另外一种程度的细化(特征组合)。
单加一个Child字段,Age<=12的,设为1,其余为0(你去看看数据,确实小盆友优先程度很高啊)(特征构造)
如果名字里面有『Mrs』,而Parch>1的,我们猜测她可能是一个母亲,应该获救的概率也会提高,因此可以多加一个Mother字段,此种情况下设为1,其余情况下设为0(特征构造)
Name是一个我们一直没有触碰的属性,我们可以做一些简单的处理,比如说男性中带某些字眼的(‘Capt’, ‘Don’, ‘Major’, ‘Sir’)可以统一到一个Title,女性也一样。
Cabin再细化一些,对于有记录的Cabin属性,我们将其分为前面的字母部分(我猜是位置和船层之类的信息)和后面的数字部分(应该是房间号,有意思的事情是,如果你仔细看看原始数据,你会发现,这个值大的情况下,似乎获救的可能性高一些)。
把堂兄弟/兄妹 和 Parch 还有自己 个数加在一起组一个Family_size字段(考虑到大家族可能对最后的结果有影响)
# 可以组成一个大家族的特征
df['family'] = df['Parch']+df['SibSp']+1
df['family'].value_counts()
Out[43]:

1     790
2     235
3     159
4      43
6      25
5      22
7      16
11     11
8       8
Name: family, dtype: int64
#通过正则表达式来获取称谓
import re
def get_title(name):
    pre_name = re.search(r', ([A-Za-z]+)\.',name)
    if pre_name:
        return pre_name.group(1)
    else:
        return np.nan
df['pre_name'] = df['Name'].apply(get_title)
df.head()


df.info()
df[df['pre_name'].isnull()]


# 通过查看数据,我们替换掉缺失值
df['pre_name'].replace(np.nan,'Mrs',inplace = True)
df.ix[759]
df.info()

df['pre_name'].value_counts()
Out[48]:

Mr          757
Miss        260
Mrs         198
Master       61
Dr            8
Rev           8
Col           4
Ms            2
Major         2
Mlle          2
Don           1
Capt          1
Dona          1
Lady          1
Mme           1
Sir           1
Jonkheer      1
Name: pre_name, dtype: int64
通过仔细的对比数据,Master这里对应的年龄基本在12岁以下,所以我们前面用所有人的均值来替代缺失值是有问题的

df['pre_name'] = df['pre_name'].replace('Mlle', 'Miss')
df['pre_name'] = df['pre_name'].replace('Ms', 'Miss')
df['pre_name'] = df['pre_name'].replace('Mme', 'Mrs')
df['pre_name'] = df['pre_name'].replace('Lady', 'Mrs')
df['pre_name'] = df['pre_name'].replace('Sir', 'Mr')
df['pre_name'] = df['pre_name'].replace(['Dr','Rev','Col','Major','Dona','Don','Capt','Jonkheer'], 'Rare')

df.info()
df.head()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1309 entries, 0 to 417
Data columns (total 15 columns):
Age            1046 non-null float64
Cabin          295 non-null object
Data_type      1309 non-null object
Embarked       1307 non-null object
Fare           1308 non-null float64
Name           1309 non-null object
Parch          1309 non-null int64
PassengerId    1309 non-null int64
Pclass         1309 non-null int64
Sex            1309 non-null object
SibSp          1309 non-null int64
Survived       891 non-null float64
Ticket         1309 non-null object
family         1309 non-null int64
pre_name       1309 non-null object
dtypes: float64(3), int64(5), object(7)
memory usage: 203.6+ KB


对缺失值常用的处理方法有填充平均值,这里我们不用平均值来填充,利用已知数据来拟合缺失值的情况
1 由于Cabin缺失值太多,我们直接舍弃这个特征
2 对age我们利用随机森林来进行拟合。

from sklearn.ensemble import RandomForestRegressor

def fill_miss_age(data):
    age_df = data[['Age','Fare','Parch','SibSp','Pclass','pre_name']]
    # 先填充fare的缺失值,这个直接利用均值
    age_df = pd.get_dummies(age_df)
    age_df['Fare'] = age_df['Fare'].fillna(test_df['Fare'].mean())
    know_age_df = age_df[age_df['Age'].notnull()]
    unknow_age_df = age_df[age_df.Age.isnull()]

    know_y_age = know_age_df.ix[:,0]
    know_x_age = know_age_df.ix[:,1:]
    rfr = RandomForestRegressor(random_state=0, n_estimators=500, n_jobs=-1)
    rfr.fit(know_x_age, know_y_age)
    predictedAges = rfr.predict(unknow_age_df.ix[:,1:])
    data.loc[data.Age.isnull(),'Age']=predictedAges
    return data
fill_df= fill_miss_age(df)
fill_df.info()
fill_df.head()


<class 'pandas.core.frame.DataFrame'>
Int64Index: 1309 entries, 0 to 417
Data columns (total 15 columns):
Age            1309 non-null float64
Cabin          295 non-null object
Data_type      1309 non-null object
Embarked       1307 non-null object
Fare           1308 non-null float64
Name           1309 non-null object
Parch          1309 non-null int64
PassengerId    1309 non-null int64
Pclass         1309 non-null int64
Sex            1309 non-null object
SibSp          1309 non-null int64
Survived       891 non-null float64
Ticket         1309 non-null object
family         1309 non-null int64
pre_name       1309 non-null object
dtypes: float64(3), int64(5), object(7)
memory usage: 203.6+ KB


#### 对pclass进行转换
p_class = pd.get_dummies(fill_df.Pclass,prefix='Pclass_')
p_class.head()

#### 对性别进行转换
Sex=pd.get_dummies(fill_df['Sex'],prefix='Sex')
Sex.head()

#### 对船票的价格进行处理,使用归一化的操作,减少异常值的干扰
import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
fill_df['Fare'] = fill_df['Fare'].fillna(test_df['Fare'].mean())
fare_train = fill_df.loc[fill_df['Data_type']=='train','Fare'].reshape(-1,1)
fare_test = fill_df.loc[fill_df['Data_type']=='test','Fare'].reshape(-1,1)
s = scaler.fit(fare_train)
Standard_train = s.transform(fare_train)
s1 = scaler.fit(fare_test)
Standard_test = s1.transform(fare_test)
Standard_train_df = pd.DataFrame(Standard_train)
Standard_test_df =pd.DataFrame(Standard_test)
fare_df = pd.concat([Standard_train_df,Standard_test_df])
fare_df.columns=['farescal',]
all_df = pd.concat([fill_df,fare_df],axis = 1)
all_df.head()


### 对age进行离散化处理
#### 1 age<12 ;
#### 2 age=[12,20];
#### 3 age=[20,32];
#### 4 age=[32,45];
#### 5 age=[45,60];
#### 6 age>60

bins = [0,12,20,32,45,60,100]
all_df['Age'] = pd.cut(all_df['Age'],bins=bins)
all_df.head()


age_dummies= pd.get_dummies(all_df.Age,prefix = 'age_')
age_dummies.head()


#### 对头衔进行处理
pre_name = pd.get_dummies(all_df.pre_name)
pre_name.head()

#### 判断是否是一个母亲
all_df['is_mother']=0
all_df.loc[(all_df['Sex']=='female') & (all_df['Parch']>0) &(all_df['pre_name']=='Mrs'),'is_mother']=1
all_df.head()

#### sex和plcass的特征值组合
all_df['plcass_sex'] =all_df['Sex']+'_'+all_df['Pclass'].astype(np.str)
plcass_sex = pd.get_dummies(all_df['plcass_sex'])
plcass_sex.head()
所有变量合并

alldf=pd.concat([all_df,pre_name,age_dummies,Sex,p_class,plcass_sex],axis = 1)
alldf.head()


train_feature_x = alldf.loc[alldf['Data_type']=='train',['SibSp','Parch','farescal','family', 'is_mother','Sex_female','Sex_male','age__(0, 12]','age__(12, 20]','age__(20, 32]','age__(32, 45]','age__(45, 60]','age__(60, 100]','Master','Miss','Mr','Mrs','Rare','Pclass__1','Pclass__2','Pclass__3']]
train_feature_y = alldf.loc[alldf['Data_type']=='train','Survived']
test_feature_x = alldf.loc[alldf['Data_type']=='test',['SibSp','Parch','farescal','family','is_mother','Sex_female','Sex_male','age__(0, 12]','age__(12, 20]','age__(20, 32]','age__(32, 45]','age__(45, 60]','age__(60, 100]','Master','Miss','Mr','Mrs','Rare','Pclass__1','Pclass__2','Pclass__3']]
train_feature_x.head()
拆分训练集和验证集

x_train,x_test,y_train,y_test = model_selection.train_test_split(train_feature_x,train_feature_y,test_size=0.3,random_state=33)
#### 模型训练
names = ['LogisticRegression','LinearSVC','KNeighborsClassifier','RandomForestClassifier','DecisionTreeClassifier']
clfs =[LogisticRegression(),LinearSVC(),KNeighborsClassifier(n_neighbors=3),RandomForestClassifier(),DecisionTreeClassifier()]
for name,clf in zip(names,clfs):
    clf.fit(x_train,y_train)
    print(clf)
    print("train_score:",clf.score(x_train,y_train))
    y_pre = clf.predict(x_test)
    test_score = metrics.accuracy_score(y_pre,y_test)
    print("test_score:",test_score)
    print('\n')
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
          verbose=0, warm_start=False)
train_score: 0.817014446228
test_score: 0.850746268657


LinearSVC(C=1.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0)
train_score: 0.817014446228
test_score: 0.85447761194


KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
           metric_params=None, n_jobs=1, n_neighbors=3, p=2,
           weights='uniform')
train_score: 0.866773675762
test_score: 0.817164179104


RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
            max_depth=None, max_features='auto', max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1,
            oob_score=False, random_state=None, verbose=0,
            warm_start=False)
train_score: 0.926163723917
test_score: 0.809701492537


DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=None,
            splitter='best')
train_score: 0.939004815409
test_score: 0.820895522388
从这次的模型可以看出,训练集和测试集的准确率都有了提高,这印证了工业界的一个观点,特征工程的好坏决定的算法的上限,而模型的好坏只是无限逼近这个上限

接下来,我们尝试着做下模型的融合,这个要特别注意,模型的融合很容易造成过拟合

from sklearn.ensemble import VotingClassifier
%run 4.特征工程--准备一个更好的数据集.ipynb

lr = LogisticRegression()
lsvc=LinearSVC()
knn=KNeighborsClassifier(n_neighbors=3)
random_forest=RandomForestClassifier()
dtree=DecisionTreeClassifier()

votingclf = VotingClassifier(estimators=[('lr',lr),('lsvc',lsvc),('knn',knn),('random_forest',random_forest),('dtree',dtree)],voting='hard')
#训练集投票后的结果
votingclf.fit(x_train,y_train)
print("训练融合得分:",votingclf.score(x_train,y_train))
#测试集投票后的结果
y_predict = votingclf.predict(x_test)
print("验证融合得分:",metrics.accuracy_score(y_predict,y_test))
至此,整个项目算是结束了,就差拿模型去测试集上去做测试了
---------------------
【转载】
作者:不曾走远~
原文:https://blog.csdn.net/qq_20412595/article/details/81942279



作者: 不二晨    时间: 2018-10-25 10:45

作者: 魔都黑马少年梦    时间: 2018-11-1 16:20





欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2