一、前言

泰坦尼克号作为Kaggle经典项目,被拿来做考核或者实验非常多,也是机器学习入门的一个很好的项目。我们机器学习的老师也是直接用这个项目来做考核。

他分工分成了数据处理、模型搭建、PPT制作和文档制作,但是鄙人认为,数据处理与模型搭建密不可分的,比如说在决策树模型中,是不在乎数据实际差值的,但是SVM逻辑回归等算法,如果不用较好的数据变换,基本是没准确度的。

基本上数据处理用了2个小时基本就搞定了,也参考了很多别人的题解,不过我都是自己再写了一遍,代码量不大很好处理。模型搭建其实也就是调用一下sklearn库,我自己认为对于这种比较难以估计的真实情况,而且数据特征、样本量都比较少。把注意力花在调参上,没有什么大的突破。所以我只是为了学习操作流程,把各个模型拿出来,意思意思过一下罢了。菜不行,再怎么烹饪,也煮不出东西来。

本质上这个项目,就是一个入门项目,学习机器学习/数据挖掘的一个过程。差不多得了hhhh。

二、环境搭建

本次实验使用的是

  • window11(操作系统)
  • python 3.9(python语言)
  • jupyter notebook(编辑器很方便)
  • copy(用来复制内容)
  • numpy(数组处理)
  • pandas(数据处理和数据分析)
  • matplotlib(数据可视化)
  • seaborn(热力图)
  • sklearn(机器学习库)
  • xgboost(机器学习的其中一个模型)
  • lightgbm(机器学习的其中一个模型)
1
2
3
4
5
6
7
8
9
10
11
12
13
#具体导包内容
import os
import sys
import pandas as pd
import numpy as np
import seaborn as sns
import copy
from matplotlib import pyplot as plt
from sklearn.impute import SimpleImputer
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn import metrics

详细可参考:

Anaconda安装

jupyter notebook安装

按着这些教程来就可以了,如果能会用虚拟环境更好,在base环境下跑代码也行,因为用的库对版本要求不大。各种库只需要pip install xxx 就可以了。

三、项目介绍

比赛平台:Titanic - Machine Learning from Disaster | Kaggle

泰坦尼克号的沉没是历史上最臭名昭著的沉船事件之一。

1912年4月15日,在她的处女航中,被广泛认为是 "不沉的 "泰坦尼克号在与冰山相撞后沉没。不幸的是,没有足够的救生艇供船上所有人使用,导致2224名乘客和船员中的1502人死亡。

虽然幸存下来有一定的运气成分,但似乎有些人群比其他人群更有可能幸存下来。

在这项挑战中,我们要求你建立一个预测模型来回答这个问题。"什么样的人更有可能生存?"使用乘客数据(即姓名、年龄、性别、社会经济阶层等)。

主要是介绍了具体项目背景,以及所给train 、 test 数据集中,各个特征的具体含义,我们将在下文进行分析、总结。

四、数据分析

可以使用SPSSPRO和python的可视化工具进行观察。

我这里强烈建议使用SPSSPRO,当然也会介绍一下python的用法。

由于数据处理前后数据特征会发生变化,我们将使用python数据处理前的数据观察,使用SPSSPRO进行数据处理后的观察。

4.1 数据解释(官方)

  • survival:是否生存
    • 0 = No :未存活
    • 1 = Yes:存活
  • pclass:船票等级。(社会经济地位(SES)的代表)
    • 1 = 1st:最高级的船票
    • 2 = 2nd:第二级的船票
    • 3 = 3rd:第三级的船票
  • Sex:性别
    • female:女人
    • male :男人
  • Age:年龄(若年龄为0.5,则其为预测的值,不影响数据分布)
  • sibsp:在泰坦尼克号上的兄弟姐妹/配偶的数量。
  • parch:在泰坦尼克号上的父母/子女的数量
  • ticket: 船票号(如果船票号一样,可以说明是同行的人)
  • fare: 票价。
  • cabin:机舱号
  • embarked : 上岸的港口(可以表现社会地位??)
    • C = Cherbourg
    • Q = Queenstown
    • S = Southampton

数据解释网址kaggle

4.2 python 数据观察

4.2.1 导入数据处理库和数据集

1
2
3
4
5
6
7
8
9
10
# 导入数据处理库
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt

# 导入测试集和训练集
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')
df = train_data

4.2.2 数据初步观察

1
2
3
4
5
6
7
# 数据初步观察
train_data.info()
print()
test_data.info()
print()
# 查看训练集的描述性信息
df.describe(include="all")

train_data.info()结果:

train_data,info

test_data.info()结果:

test_data.info

观察发现,其实test数据集只是少了’Survived’这一特征,我们后续对数据处理以train为主要对象,再对test处理,避免代码冗杂。

同时能得到所有特征的数据类型,object是字符串的意思,对于字符串来说其实对机器学习模型不大友好,基本都是要转定量标准,我们还发现’Age’和‘'Cabin’有一定程度的数据丢失,我们考虑在后续进行处理。

训练集样本量为891,这对于机器学习中,算是比较小的样本量了。对于需要大规模进行数据训练的模型需要谨慎使用。

describe结果:

展示训练集的前五行:

1
2
# 展示训练集的前五行
train_data.head(5)

4.2.3 python数据可视化

我们先根据启发式算法,也就是生活尝试,判断一下哪些特征可能会跟生存率有影响,我的理解是

[性别、年龄、票价、船舱等级]这几类容易影响,总之画画图啥的,试试看。

统计生还人数:

1
2
3
# 统计生还人数
Survived=df.groupby('Survived').count()
Survived

1
2
3
4
# 展示生还人数与性别的关系
sns.countplot(data=df, x ='Survived', hue = 'Sex')
plt.xlabel('Survived (0 = Dead,1 = Survived)' )

1
2
3
4
5
# 展示生还人数与船仓等级的关系
sns.countplot(x="Survived",
hue="Pclass",
data=df)
plt.xlabel('Survived (0 = Dead,1 = Survived)' )

1
2
3
# 相关性分析展示
sns.heatmap(df.corr(), annot=True)
plt.show()

  • 通过上面的热图,我们很容易看到正相关最强的特征(橙色)和负相关最强的特征(黑色)
  • SibSp(堂兄弟)和Parch之间有很强的正相关关系——孩子通常有兄弟姐妹,父母和孩子经常一起旅行
  • Pclass(船舱等级)和票价之间存在很强的负相关关系——头等舱的机票比下等舱的机票更贵,这意味着当票价越低(接近1),票价就越高
  • 年龄和Pclass(船舱等级)呈中度负相关——富人通常年龄较大

五、数据处理

5.1 异常值处理

1
2
3
4
5
6
7
8
9
10
11
12
# 异常值处理
# 舍弃Cabin
train_data = train_data.drop(['Cabin'],axis=1)

# 将两行Embarked缺失的值删去
train_data.dropna(inplace=True,axis=0,subset=['Embarked'])

# 年龄补充平均数
train_data.Age.fillna(train_data.Age.mean(),inplace=True)

# 观察总体特征概况
train_data.info()

5.2 定性数据处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定性数据处理
# 为了提高模型的能力,我们将Embarked 进行独热编码,Sex 进行定值替换即0为female,1为male。

# Sex 替换
train_data['Sex'] = train_data['Sex'].replace({'female':0,'male':1})

# Embarked 独热编码
a = ['Embarked']
c = pd.get_dummies(train_data['Embarked'])
del(train_data['Embarked'])
train_data = pd.concat([train_data,c],axis=1)
train_data.rename(columns={'C':'Embarked_C','Q':'Embarked_Q','S':'Embarked_S'},inplace=True)

# 其余非数值标签转为 数值标签
le = LabelEncoder()
object_columns = train_data.select_dtypes(include=["object"]).columns
train_data[object_columns] = train_data[object_columns].apply(le.fit_transform)

5.3 数据扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 姓名处理 提取出形式和称号,Title 和 Surname
# 家庭人员处理 处理带了多少人,生成新的指标Family_Size.
# 家庭规模由Parch与sibsp组成,因为家庭规模自己也算是其中之一,所以要+1
def variables(df):
df['Title'] = df['Name'].apply(lambda x : x.split(' ')[1].strip('123,./!?'))
df['Surname'] = df['Name'].apply(lambda x : x.split(',')[0])
df.drop('Name', axis = 1 , inplace = True)
df['Title'] = df['Title'].apply(lambda x : x if x in ['Mr','Miss','Mrs','Master'] else 'NoTitle')
#df['Is_Married'] = 0
#df['Is_Married'].loc[df['Title'] == 'Mrs'] = 1
df['Family_Size'] = df['SibSp'] + df['Parch'] + 1
df['Title'] = df['Title'].map({'NoTitle' : 0 , 'Miss': 1, 'Mrs' : 2,'Mr' :3 ,'Master' : 4})
variables(train_data)

# 删去'SibSq'和'Parch'特征
del(train_data['SibSp'])
del(train_data['Parch'])

5.4 最终数据集展示和导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# test数据处理模块
train_data_true = train_data.copy()
train_data = test_data
train_data_ID = train_data['PassengerId']
train_data = train_data.drop(['PassengerId'],axis=1)
train_data = train_data.drop(['Cabin'],axis=1)
train_data = train_data.fillna(train_data.mean())
train_data['Sex'] = train_data['Sex'].replace({'female':0,'male':1})
train_data['Sex'] = train_data['Sex'].replace({'female':0,'male':1})
a = ['Embarked']
c = pd.get_dummies(train_data['Embarked'])
del(train_data['Embarked'])
train_data = pd.concat([train_data,c],axis=1)
train_data.rename(columns={'C':'Embarked_C','Q':'Embarked_Q','S':'Embarked_S'},inplace=True)
def variables(df):
df['Title'] = df['Name'].apply(lambda x : x.split(' ')[1].strip('123,./!?'))
df['Surname'] = df['Name'].apply(lambda x : x.split(',')[0])
df.drop('Name', axis = 1 , inplace = True)
df['Title'] = df['Title'].apply(lambda x : x if x in ['Mr','Miss','Mrs','Master'] else 'NoTitle')
#df['Is_Married'] = 0
#df['Is_Married'].loc[df['Title'] == 'Mrs'] = 1
df['Family_Size'] = df['SibSp'] + df['Parch'] + 1
df['Title'] = df['Title'].map({'NoTitle' : 0 , 'Miss': 1, 'Mrs' : 2,'Mr' :3 ,'Master' : 4})
variables(train_data)
le = LabelEncoder()
object_columns = train_data.select_dtypes(include=["object"]).columns
train_data[object_columns] = train_data[object_columns].apply(le.fit_transform)
test_data = train_data
train_data =train_data_true

del(test_data['SibSp'])
del(test_data['Parch'])
test_data.to_csv('test_new.csv',index = None)
#最终数据
train_data.to_csv('train_new.csv',index = None)

六、模型搭建

表面模型搭建,其实就是套一套用一用,如果想了解具体算法的原理和效果,请参考我的其他博客,在机器学习或数据挖掘标签下基本都有。

6.1 模型数据切割

1
2
3
4
5
6
7
8
# ID没用的 train与同步删了
train_data =train_data.drop(['PassengerId'],axis=1)
## 为了正确评估模型性能,将数据划分为训练集和测试集,并在训练集上训练模型,在测试集上验证模型性能。
data_target_part = train_data['Survived']
data_features_part = train_data[[x for x in train_data.columns if x != 'Survived']]

## 测试集大小为20%, 80%/20%分
x_train, x_test, y_train, y_test = train_test_split(data_features_part, data_target_part, test_size = 0.2, random_state = 0)

6.2 导入XGBoost库(未调参)

1
2
3
4
5
6
7
## 导入XGBoost模型
from xgboost.sklearn import XGBClassifier

## 定义 XGBoost模型
clf = XGBClassifier()
# 在训练集上训练XGBoost模型
clf.fit(x_train, y_train)

6.3 训练模型、评价模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 在训练集和测试集上分布利用训练好的模型进行预测
train_predict = clf.predict(x_train)
test_predict = clf.predict(x_test)
from sklearn import metrics

## 利用accuracy(准确度)【预测正确的样本数目占总预测样本数目的比例】评估模型效果
print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict))
print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict))

## 查看混淆矩阵 (预测值和真实值的各类情况统计矩阵)
confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test)
print('The confusion matrix result:\n',confusion_matrix_result)

# 利用热力图对于结果进行可视化
plt.figure(figsize=(8, 6))
sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')
plt.xlabel('Predicted labels')
plt.ylabel('True labels')
plt.show()

结果如下:

6.3 预测结果

1
2
3
4
5
6
7
8
#预测
test_predict = clf.predict(test_data)

# 答案生成
test_predict = {'PassengerId' : pd.Series(train_data_ID),
'Survived' : pd.Series(test_predict)}
test_predict = pd.DataFrame(test_predict)
test_predict.to_csv("Submission_xgboost.csv",index=None)

七、SPSSPRO数据可视化

7.1 重要特征图表

7.1.1 Pclass 环图

7.1.2 Sex 帕累托图

7.1.3 Family_Size 柱状图

大部分都是single all the way 嘻嘻嘻。

7.2 相关热力图

相关性较强的,Sex,Fare ,Pclass,Title(Title的原因是因为性别相关)。

八、其余算法和答案生成

8.1 全0全1的生成

1
2
3
4
5
6
7
8
9
10
11
# 全部都是1,survived
allone = {'PassengerId' : pd.Series(train_data_ID),
'Survived' : pd.Series(np.ones((418),dtype=int))}
allone = pd.DataFrame(allone)
allone.to_csv("allone.csv",index=None)

# 全部都是0。zeros
allzeros = {'PassengerId' : pd.Series(train_data_ID),
'Survived' : pd.Series(np.zeros((418),dtype=int))}
allzeros = pd.DataFrame(allzeros)
allzeros.to_csv("allzeros.csv",index=None)

8.2 性别区分

1
2
3
4
5
6
7
8
9
10
11
12
13
# 性别判断,男的全死了,女的都活了
sex_predict = []
for i in range(0,len(test_data)):
if test_data['Sex'][i] == 1:
sex_predict.append(0)
else :
sex_predict.append(1)

test_predict = {'PassengerId' : pd.Series(train_data_ID),
'Survived' : pd.Series(np.array(sex_predict))}
test_predict = pd.DataFrame(test_predict)
test_predict.to_csv("Submission_sex.csv",index=None)

8.3 一大堆算法的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#AdaBoostClassifier
from sklearn.ensemble import AdaBoostClassifier
clf_Ada = AdaBoostClassifier(random_state=0)

# 决策树
from sklearn.tree import DecisionTreeClassifier
clf_Tree = DecisionTreeClassifier(random_state=0)

# KNN
from sklearn.neighbors import KNeighborsClassifier
clf_KNN = KNeighborsClassifier()

# SVM
from sklearn.svm import SVC
clf_svm = SVC(random_state=0)

# Logistic
from sklearn.linear_model import LogisticRegression
clf_log = LogisticRegression(random_state=0)

# 随机森林
from sklearn.ensemble import RandomForestClassifier
clf_forest = RandomForestClassifier(random_state=0)

# GBDT
from sklearn.ensemble import GradientBoostingClassifier
clf_gbdt = GradientBoostingClassifier(random_state=0)

# Lightgbm
from lightgbm.sklearn import LGBMClassifier
clf_lightgbm = LGBMClassifier()

一个个写代码很麻烦,我弄了一个函数用来自定义跑,不过缺点就是没得调参咯。所以准确率差强人意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#模型训练函数 
def fuckyou(clf,name,train_data,test_data):
## 为了正确评估模型性能,将数据划分为训练集和测试集,并在训练集上训练模型,在测试集上验证模型性能。
data_target_part = train_data['Survived']
data_features_part = train_data[[x for x in train_data.columns if x != 'Survived']]
x_train, x_test, y_train, y_test = train_test_split(data_features_part, data_target_part, test_size = 0.2, random_state = 500)
clf.fit(x_train, y_train)
train_predict = clf.predict(x_train)
test_predict = clf.predict(x_test)
print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_train,train_predict))
print('The accuracy of the Logistic Regression is:',metrics.accuracy_score(y_test,test_predict))
confusion_matrix_result = metrics.confusion_matrix(test_predict,y_test)
print('The confusion matrix result:\n',confusion_matrix_result)
plt.figure(figsize=(8, 6))
sns.heatmap(confusion_matrix_result, annot=True, cmap='Blues')
plt.xlabel('Predicted labels')
plt.ylabel('True labels')
plt.title(name)
plt.show()
# 答案生成
test_predict = clf.predict(test_data)
test_predict = {'PassengerId' : pd.Series(train_data_ID),
'Survived' : pd.Series(test_predict)}
test_predict = pd.DataFrame(test_predict)
test_predict.to_csv("Submission_{}.csv".format(name),index=None)

运行操作:

1
2
3
4
5
6
7
8
fuckyou(clf_Ada,'Ada',train_data,test_data)
fuckyou(clf_Tree,'Tree',train_data,test_data)
fuckyou(clf_svm,'svm',train_data,test_data)
fuckyou(clf_log,'log',train_data,test_data)
fuckyou(clf_KNN,'KNN',train_data,test_data)
fuckyou(clf_forest,'forest',train_data,test_data)
fuckyou(clf_gbdt,'gbdt',train_data,test_data)
fuckyou(clf_lightgbm,'lightgbm',train_data,test_data)

8.4 集成学习?的尝试

原理:使用多个算法进行预测,如果不同蒜贩预测结果为1的数量多于预测结果为0的数量,则认为最终结果为1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 集成学习 forest lightgbm gbdt
# 由于这三者原理近似,且表现较好近似,我们使用集成学习,3类
data_forest = pd.read_csv('Submission_forest.csv')
data_lightgbm = pd.read_csv('Submission_lightgbm.csv')
data_gbdt = pd.read_csv('Submission_gbdt.csv')

data = data_forest['Survived'] + data_lightgbm['Survived'] + data_gbdt['Survived']

data.replace(1,0,inplace = True)
data.replace(2,1,inplace = True)
data.replace(3,1,inplace = True)

data_connect3 = pd.concat([data_forest['PassengerId'],data],axis=1)

data_connect3.to_csv("Submission_connect3.csv",index=None)

# 集成学习 forest lightgbm gbdt xgboost Tree 同理5类
data_forest = pd.read_csv('Submission_forest.csv')
data_lightgbm = pd.read_csv('Submission_lightgbm.csv')
data_gbdt = pd.read_csv('Submission_gbdt.csv')
data_xgboost = pd.read_csv('Submission_xgboost.csv')
data_Tree = pd.read_csv('Submission_Tree.csv')

data = data_forest['Survived'] + data_lightgbm['Survived'] + data_gbdt['Survived'] + data_xgboost['Survived'] + data_Tree['Survived']

data.replace(1,0,inplace = True)
data.replace(2,0,inplace = True)
data.replace(3,1,inplace = True)
data.replace(4,1,inplace = True)
data.replace(5,1,inplace = True)

data_connect5 = pd.concat([data_forest['PassengerId'],data],axis=1)

data_connect5.to_csv("Submission_connect5.csv",index=None)

九、最终结果

1
2
3
4
5
6
7
8
9
10
#结果导出代码,得分为具体在Kaggle上面提交的数值。
x = ['男全死,女全活','未调参决策树','未调参KNN','未调参SVM','未调参逻辑回归','未调参随机森林','未调参GBDT','未调参Lightgbm','未调参Xgboost','调参后Xgboost','集成3个算法','集成5个算法','简易BP深度学习']
y = [0.76555,0.70334,0.55741,0.59090,0.77511,0.77033,0.76794,0.75358,0.72009,0.76076,0.78947,0.74641,0.66028 ]

Final_answer = {'算法' : pd.Series(x),
'得分' : pd.Series(y)}
Final_answer = pd.DataFrame(Final_answer)
Final_answer = Final_answer.sort_values(by=['得分'])
Final_answer.to_csv('Final_answer.csv',index = None)
Final_answer

结果如下:

十、优缺点及改良推广

10.1 优点

  • 数据分析的很全面
  • 考虑到很多算法的使用
  • 有较多的可视化分析
  • 备注很多,易于阅读

10.2 缺点

  • 没有进行模型的解释,对模型学习较无力
  • 没有进行调参,有明显的成绩提升空间

10.3 改良推广

  • 根据SPSS的热力图,Title 应该删去,因为与SEX过于相关。
  • 以XGBoost为首的随机森林模型效果优良。可以考虑从这方面进行调参。