学校的机器学习课程期末作业竟然是分析Kaggle中的经典题目,泰坦尼克数据集。在此将整个分析流程记录一下。
Kaggle地址:https://www.kaggle.com/c/titanic/overview
本文使用了支持向量机、随机森林算法和BP神经网络三种方法对乘客的生还情况进行预测。
由于数据分析需要读取csv文件,以及对数据矩阵进行处理,我们引入pandas和numpy库。
# 数据分析
import pandas as pd
import numpy as np
由于在数据分析过程中需要绘制图案,以直观的分析数据,我们需要引入matplotlib和seaborn库。
# 绘图
import matplotlib.pyplot as plt
import seaborn as sns
在train.csv中,数据共有891行和12列,则可知训练数据共有891条,每条数据有12个属性,各个属性如下:
在test.csv中,数据共有418行和11列,则可知训练数据共有418条,每条数据有11个属性,即除了Survived以外的其他属性。我们要做的就是通过其他属性来预测test.csv中的Survived项。
我们需要做的就是对各个属性进行分析,看看这些属性中哪些对获救情况Survived有影响。所以首先要读取数据。
df_train = pd.read_csv('./期末数据/train.csv')
df_test = pd.read_csv('./期末数据/test.csv')
可以通过info()
和describe()
方法查看数据的内容和信息:
print(df_train.info())
# 查看训练集详细信息
print(df_train.describe())
print(df_test.info())
# 查看测试集详细信息
print(df_test.describe())
根据以上信息可得:
# 填充数据值
def fillna_data(df_train, df_test):
# 对训练集和测试集中的"Age"数据进行平均值填充
df_train['Age'] = df_train['Age'].fillna(df_train['Age'].mean())
df_test['Age'] = df_test['Age'].fillna(df_test['Age'].mean())
# 添加一个新的类别"Missing"来填充"Cabin"
df_train['Cabin'] = df_train['Cabin'].fillna('Missing')
df_test['Cabin'] = df_test['Cabin'].fillna('Missing')
# 用出现频率最多的类别填充训练集中的"Embarked"属性
df_train['Embarked'] = df_train['Embarked'].fillna(
df_train['Embarked'].mode()[0])
# 用出现频率最多的类别填充测试集中的"Fare"属性
df_test['Fare'] = df_test['Fare'].fillna(
df_test['Fare'].mode()[0])
return df_train, df_test
# 得到填充后的数据集 df_train, df_test
df_train, df_test = fillna_data(df_train, df_test)
从常识来说,等级越高的船舱越靠近甲板,在发生沉船事故时越容易逃生,对此我们进行具体分析:
sns.barplot(x='Pclass', y='Survived', data=df_train,
palette="Set1",
errwidth=1.2,
errcolor="0.1",
capsize=0.05,
alpha=0.6)
plt.show()
根据柱状图我们可以看出船舱等级与生还率的关系是很大的。1等船舱的生还率远远大于其他船舱,达到了60%以上,几乎是3等船舱的3倍。
从主观上来说,我认为男性与女性的身体素质存在差异,生存概率应该与性别有关,对此我们进行具体分析:
sns.barplot(x='Sex', y='Survived', data=df_train,
capsize=0.05,
errwidth=1.2,
errcolor='0.1',
alpha=0.6)
plt.show()
可以看出女性的生还率远远大于男性。从我个人的角度以及我曾经观看《泰坦尼克号》电影的感受来看,由于逃生艇承载人数有限,会让妇女、儿童和老人先逃生,所以我们可以将性别和年龄同时与存活率进行分析,以验证我们的推断。
# 绘制不同年龄与性别的存活率
women = df_train[df_train["Sex"] == "female"]
men = df_train[df_train["Sex"] == "male"]
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
ax = sns.distplot(men[men['Survived'] == 1].Age,
label='Survived', ax=axes[0], kde=False)
ax.set_title('Male')
_ax = sns.distplot(women[women['Survived'] == 1].Age,
label='Survived', ax=axes[1], kde=False)
_ax.set_title('Female')
plt.show()
根据该图我们得出,不论是男性还是女性,儿童和青年人的生存率较高,这也基本符合我们的推断。由此,我们又可以推测一下,乘客在船上的兄弟姐妹和父母小孩的数量可能会影响到该乘客的存活率。
# 创建1行2列,大小为10*4的画布
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))
# 乘客在船上的兄弟姐妹和父母小孩的数量与存活率的关系
ax = sns.lineplot(x='SibSp', y='Survived', data=df_train,
ax=axes[0], label='SibSp')
ax = sns.lineplot(x='Parch', y='Survived', data=df_train,
ax=axes[0], label='Parch')
# 将两个属性相加,更加方便分析
relatives = df_train['SibSp'] + df_train['Parch']
# 乘客在船上的亲属的数量与存活率的关系
_ax = sns.lineplot(x=relatives, y=df_train['Survived'],
ax=axes[1], label='Relatives')
plt.show()
通过左边的折线图来看,当乘客在船上的兄弟姐妹数量为3时存活率最大,当乘客在船上的父母孩子数量为1时存活率最大;通过右边的折线图来看,将两个属性合并,即乘客在船上的亲属数量为3是存活率最大。故乘客在船上的兄弟/姐妹个数“SibSp”与乘客在船上的父母与小孩个数“Parch”与存活率有关。
由于登船港口为明显的分类值,其所能取的值仅有S、C和Q三种,所以我们可以进行分析,以探寻登船港口与存活率之间是否有联系。
首先我们统计下各个从港口登船的人数:
# 统计登船港口的分类值
print(df_train['Embarked'].value_counts())
可知,从S港登船的人数为646;从C港登船的人数为168;从Q港登船的人数为77。
我们绘制出从各个港口登船的乘客的生还率:
# 绘制各个港口登船的乘客的生存率
sns.pointplot(x='Embarked', y='Survived', data=df_train, order=None)
plt.show()
由此可以看出,登船港口与生存率有明显的关系,从C港登船的乘客的生存率大大高于其他两个港口的乘客。对此我的推断是C港口登船的乘客多数居住在1等船舱。
查看train.csv发现确实如此,168位C港登船乘客中,有85位在1等舱,占比达到50.5%。
g = sns.FacetGrid(df_train, col='Embarked')
g.map(sns.barplot, 'Sex', 'Survived', data=df_train,
palette="Set1",
capsize=0.05,
errwidth=1.2,
errcolor='0.1',
alpha=0.6,
order=None)
# 添加图例
g.add_legend()
plt.show()
由此还可以看出,从C港登船的男性的存活率比其他港口的要高很多,从S港和Q港登船的女性存活率较高。
根据船舱等级与存活率的分析我们可知,船舱等级越高,存活率越大。由于等级越高的船舱票价越贵,那么我们可以推断票价越高,存活率也越大。
在主观上我认为乘客姓名“Name”、票号“Ticket”和舱位“Cabin”对存活率无影响,故需要去除。
首先保存一下乘客ID信息,以便之后数据输出。
id_test = df_test.loc[:, 'PassengerId']
接着删除不需要的属性。这里我进行了三次测试,每次测试所保留的属性不同。
第一次:
对船舱等级”Pclass”、性别”Sex”、年龄”Age”、兄弟/姐妹数量”SibSp”、父母孩子数量”Parch”和登船港口”Embarked”进行分析。
# 第一次处理
# 去掉了以下特征
# 即对 Pclass Sex Age SibSp Parch Embarked 分析
df_train = df_train.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin', 'Fare'])
df_test = df_test.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin', 'Fare'])
第二次:
对船舱等级”Pclass”、性别”Sex”、年龄”Age”、兄弟/姐妹数量”SibSp”、父母孩子数量”Parch”、票价”Fare”和登船港口”Embarked”进行分析。
# 第二次处理
# 在第一次的基础上,添加了归一化处理的特征 Fare
# 即对 Pclass Sex Age SibSp Parch Fare Embarked 分析
df_train = df_train.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin'])
df_test = df_test.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin'])
第三次:
对船舱等级”Pclass”、性别”Sex”、年龄”Age”、票价”Fare”和登船港口”Embarked”进行分析。
# 第三次处理
# 在第二次的基础上,去掉了特征 SibSp Parch
df_train = df_train.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp', 'Parch'])
df_test = df_test.drop(columns=['PassengerId', 'Name', 'Ticket', 'Cabin', 'SibSp', 'Parch'])
对数据集进行查看分析后,得出需要处理的几个点:
前两点我们在上述步骤中已经处理,下面我们只需要进行编码转换以及数据归一化处理。
使用ont-hot编码处理”Embarked”属性;将”Age”和”Fare”进行归一化处理;把”Sex”属性中的”male”和”female”分别映射为1和0。
# 对数据集中的字符串数据进行编码处理
def preprocess_data(train, test):
# 使用one-hot编码将登船港口"Embarked"进行转换
# 训练集
Embarked = pd.get_dummies(train['Embarked'], prefix='Embarked')
tmp_train = pd.concat([train, Embarked], axis=1)
tmp_train.drop(columns=['Embarked'], inplace=True)
# 测试集
Embarked = pd.get_dummies(test['Embarked'], prefix='Embarked')
tmp_test = pd.concat([test, Embarked], axis=1)
tmp_test.drop(columns=['Embarked'], inplace=True)
# 将年龄归一化
tmp_train['Age'] = (tmp_train['Age'] - tmp_train['Age'].min()) / (tmp_train['Age'].max() - tmp_train['Age'].min())
tmp_test['Age'] = (tmp_test['Age'] - tmp_test['Age'].min()) / (tmp_test['Age'].max() - tmp_test['Age'].min())
# 将船票价格归一化
if 'Fare' in tmp_train.columns:
tmp_train['Fare'] = (tmp_train['Fare'] - tmp_train['Fare'].min()) / (tmp_train['Fare'].max() - tmp_train['Fare'].min())
if 'Fare' in tmp_test.columns:
tmp_test['Fare'] = (tmp_test['Fare'] - tmp_test['Fare'].min()) / (tmp_test['Fare'].max() - tmp_test['Fare'].min())
# 将性别"Sex"这一特征从字符串映射至数值
# 0代表female,1代表male
gender_class = {'female': 0, 'male': 1}
tmp_train['Sex'] = tmp_train['Sex'].map(gender_class)
tmp_test['Sex'] = tmp_test['Sex'].map(gender_class)
return tmp_train, tmp_test
获取处理后的数据data_train和data_test。
data_train, data_test = preprocess_data(df_train, df_test)
查看处理后的训练集数据:
print(data_train.head())
print(data_test.head())
输出相似性矩阵热力图,看出效果不是很好…只有票价与C登船口与生还率相似度较高,可能是归一化的原因。
sns.heatmap(data_train.corr(), annot=True)
plt.show()
提取训练集中”Survived”列数据,因为其不能作为训练参数。
label_train = data_train.loc[:, 'Survived']
data_train = data_train.drop(columns=['Survived'])
从原始数据集(source)中拆分出训练数据集(用于模型训练train),测试数据集(用于模型评估test)。
from sklearn.model_selection import train_test_split
'''
从原始数据集(source)中拆分出训练数据集(用于模型训练train),测试数据集(用于模型评估test)
train_test_split是交叉验证中常用的函数,功能是从样本中随机的按比例选取train data和test data
train_data:所要划分的样本特征集
train_target:所要划分的样本结果
test_size:样本占比,如果是整数的话就是样本的数量
'''
# 建立模型用的训练数据集和测试数据集
train_X, test_X, train_y, test_y = train_test_split(data_train,
label_train,
train_size=.8)
对于分类问题,我们分别选择支持向量机算法、随机森林算法和BP神经网络算法进行训练和预测,并对每个模型使用多次交叉验证进行评估。
from sklearn import svm
'''
SVM函数参数解析:
C:float, default=1.0
正则化参数。正则化的强度与C成反比,必须是严格的正数。惩罚是一个平方的l2惩罚。
gamma:{‘scale’, ‘auto’} or float, default=’scale’
rbf'、'poly'和'sigmoid'的内核系数。
如果gamma='scale'(默认)被执行,那么它使用1/(n_features * X.var())作为gamma的值。
如果是'auto',则使用1/n_features。
decision_function_shape:{‘ovo’, ‘ovr’}, default=’ovr’
多分类问题选择'ovo'
'''
clf_SVM = svm.SVC(C=2, gamma=0.4, kernel='rbf')
# 训练SVM模型
clf_SVM.fit(train_X, train_y)
from sklearn.metrics import confusion_matrix, classification_report
pred_SVM = clf_SVM.predict(test_X)
# 混淆矩阵
print(confusion_matrix(test_y, pred_SVM))
'''
classification_report函数用于显示主要分类指标的文本报告
显示每个类的精确度,召回率,F1值等信息
混淆矩阵 TP FP
FN TN
'''
print(classification_report(test_y, pred_SVM))
from sklearn.model_selection import cross_val_score
# 在训练集和测试集上的准确性
train_acc_SVM = cross_val_score(clf_SVM, train_X, train_y, cv=10, scoring='accuracy')
test_acc_SVM = cross_val_score(clf_SVM, test_X, test_y, cv=10, scoring='accuracy')
print('SVM Model on Train Data Accuracy: %f' %(train_acc_SVM.mean()))
print('SVM Model on Test Data Accuracy: %f' %(test_acc_SVM.mean()))
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
# 初始化模型实例
svc = SVC()
# 定义多种参数
parameters = {
'C': range(0, 21, 1),
'gamma': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
'kernel': ['rbf', 'linear']
}
# 暴力搜索调优
# 通过GridSearchCV函数可以根据给定的模型自动进行交叉验证,通过调节每一个参数来跟踪评分结果。
better_clf_SVM = GridSearchCV(svc, parameters, cv=5, n_jobs=8)
# 训练模型
better_clf_SVM.fit(train_X, train_y)
输出最优参数:
# 输出最优的模型的参数
print(better_clf_SVM.best_params_)
使用优化模型进行预测:
# 获取到最优的分类器
best_clf_SVM = better_clf_SVM.best_estimator_
print(better_clf_SVM.best_estimator_)
# 进行预测
best_pred_SVM = best_clf_SVM.predict(test_X)
对优化模型进行评估:
from sklearn.metrics import confusion_matrix, classification_report
print("Optimal SVM Model's Confusion Matrix:\n", confusion_matrix(test_y, best_pred_SVM))
print("Optimal SVM Model's Classification Report:\n", classification_report(test_y, best_pred_SVM))
进行对比分析:
from sklearn.model_selection import cross_val_score
# 在测试集上的准确性
train_acc_best_SVM = cross_val_score(best_clf_SVM, train_X, train_y, cv=10, scoring='accuracy')
test_acc_best_SVM = cross_val_score(best_clf_SVM, test_X, test_y, cv=10, scoring='accuracy')
print('Optimal SVM Model on Train Data Accuracy: %f' %(train_acc_best_SVM.mean()))
print('Optimal SVM Model on Test Data Accuracy: %f' %(test_acc_best_SVM.mean()))
print('------------------------------------------------------')
print('Original SVM Model on Train Data Accuracy: %f' %(train_acc_SVM.mean()))
print('Original SVM Model on Test Data Accuracy: %f' %(test_acc_SVM.mean()))
可以看出,在参数调优后,模型更差劲了…可能出现了过拟合现象。这也说明原来的参数就挺合理的。
from sklearn.ensemble import RandomForestClassifier
clf_RFC = RandomForestClassifier()# 未填参数,需调优
# 训练随机森林分类器模型
clf_RFC.fit(train_X, train_y)
4.2.2模型预测
from sklearn.metrics import confusion_matrix, classification_report
pred_RFC = clf_RFC.predict(test_X)
# 混淆矩阵
print(confusion_matrix(test_y, pred_RFC))
# 分类报告
print(classification_report(test_y, pred_RFC))
from sklearn.model_selection import cross_val_score
# 在训练集和测试集上的准确性
train_acc_RFC = cross_val_score(clf_RFC, train_X, train_y, cv=10, scoring='accuracy')
test_acc_RFC = cross_val_score(clf_RFC, test_X, test_y, cv=10, scoring='accuracy')
print('Random Forest Classifier Model on Train Data Accuracy: %f' %(train_acc_RFC.mean()))
print('Random Forest Classifier Model on Test Data Accuracy: %f' %(test_acc_RFC.mean()))
以及各个特征的重要性(采用第三次数据处理方式):
keys = ['Pclass', 'Sex', 'Age', 'Embarked_C', 'Embarked_Q', 'Embarked_S']
values = clf_RFC.feature_importances_
feature_importances_dict = dict(zip(keys, values))
print('=====随机森林算法中,各个特征的重要性=====')
for k, v in feature_importances_dict.items():
print(f'{k}:{v*100:.2f}%')
由于上述模型是粗略地构建的,我们可以对参数进行调整,使模型的拟合度更高,预测精度更准确。
对于随机森林算法,影响其预测精度的主要参数为:max_features、n_estimators和min_sample_leaf
即最大特征数量、子树数量和最小样本叶片大小。
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
# 初始化模型实例
rfc = RandomForestClassifier()
# 定义多种参数
parameters = {
'n_estimators': range(10,71,10)
}
# 暴力搜索调优
# 通过GridSearchCV函数可以根据给定的模型自动进行交叉验证,通过调节每一个参数来跟踪评分结果。
better_clf_RFC = GridSearchCV(rfc, parameters, cv=5, n_jobs=8)
# 训练模型
better_clf_RFC.fit(train_X, train_y)
输出最优模型参数:
# 输出最优的模型的参数
print(better_clf_RFC.best_params_)
# 获取到最优的分类器
best_clf_RFC = better_clf_RFC.best_estimator_
print(better_clf_RFC.best_estimator_)
# 进行预测
best_pred_RFC = best_clf_RFC.predict(test_X)
对优化模型进行评估:
from sklearn.metrics import confusion_matrix, classification_report
print("Optimal RFC Model's Confusion Matrix:\n", confusion_matrix(test_y, best_pred_RFC))
print("Optimal RFC Model's Classification Report:\n", classification_report(test_y, best_pred_RFC))
对比分析:
from sklearn.model_selection import cross_val_score
# 在训练集和测试集上的准确性
train_acc_best_RFC = cross_val_score(best_clf_RFC, train_X, train_y, cv=10, scoring='accuracy')
test_acc_best_RFC = cross_val_score(best_clf_RFC, test_X, test_y, cv=10, scoring='accuracy')
print('Optimal RFC Model on Train Data Accuracy: %f' %(train_acc_best_RFC.mean()))
print('Optimal RFC Model on Test Data Accuracy: %f' %(test_acc_best_RFC.mean()))
print('------------------------------------------------------')
print('Original RFC Model on Train Data Accuracy: %f' %(train_acc_RFC.mean()))
print('Original RFC Model on Test Data Accuracy: %f' %(test_acc_RFC.mean()))
这里采用了两层隐藏层的神经网络,第一层大小为64,第二层大小为32。
from sklearn.neural_network import MLPClassifier
# 两个隐藏层,第一层为64个神经元,第二层为32个神经元
mlp = MLPClassifier(hidden_layer_sizes=(64, 32), activation='relu',
solver='adam',
max_iter=800)
# 训练神经网络
mlp.fit(train_X, train_y)
from sklearn.metrics import confusion_matrix, classification_report
pred_BP = mlp.predict(test_X)
# 混淆矩阵
print(confusion_matrix(test_y, pred_BP))
输出分类报告:
# 分类报告
print(classification_report(test_y, pred_BP))
train_acc_BP = mlp.score(train_X, train_y)
test_acc_BP = mlp.score(test_X, test_y)
print('MLP Classifier Model on Train Data Accuracy: %f' %(train_acc_BP))
print('MLP Classifier Model on Test Data Accuracy: %f' %(test_acc_BP))
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import GridSearchCV
# 两个隐藏层,第一层为128个神经元,第二层为64个神经元
mlp_0 = MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=800)
parameters = {
'activation': ['relu', 'tanh', 'logistic'],
'solver': ['lbfgs', 'sgd', 'adam']
}
better_mlp = GridSearchCV(mlp_0, parameters, cv=5, n_jobs=8)
better_mlp.fit(train_X, train_y)
输出最优参数:
# 输出最优的模型的参数
print(better_mlp.best_params_)
# 获取到最优的分类器
best_mlp = better_mlp.best_estimator_
print(better_mlp.best_estimator_)
# 进行预测
best_pred_mlp = best_mlp.predict(test_X)
输出混淆矩阵:
from sklearn.metrics import confusion_matrix, classification_report
print("Optimal BP Model's Confusion Matrix:\n", confusion_matrix(test_y, best_pred_mlp))
输出分类报告:
print("Optimal BP Model's Classification Report:\n", classification_report(test_y, best_pred_mlp))
综合来看,BP神经网络的训练效果最好。
将预测结果输出到submission_<模型名>.csv文件中。
支持向量机模型:
final_pred_SVM = best_clf_SVM.predict(data_test)
output_SVM = pd.DataFrame({'PassengerId': id_test, 'Survived': final_pred_SVM})
output_SVM.to_csv('./output/submission_SVM.csv', index=False)
print('submission_SVM.csv生成完毕!')
随机森林模型:
final_pred_RFC = best_clf_RFC.predict(data_test)
output_RFC = pd.DataFrame({'PassengerId': id_test, 'Survived': final_pred_RFC})
output_RFC.to_csv('./output/submission_RFC.csv', index=False)
print('submission_RFC.csv生成完毕!')
BP神经网络模型:
final_pred_BP = best_mlp.predict(data_test)
output_BP = pd.DataFrame({'PassengerId': id_test, 'Survived': final_pred_BP})
output_BP.to_csv('./output/submission_BP.csv', index=False)
print('submission_BP.csv生成完毕!')
该数据集是Kaggle上的经典数据集——Titanic数据集。这也是我第一次从头到尾对一个数据集进行数据清洗、数据分析、预处理以及训练模型并输出预测结果。在这个过程中我学习了很多Pandas库的操作技巧,并且更加深入的了解了支持向量机、随机森林算法和BP神经网络。过程虽然麻烦,但也充满了探索的乐趣。我也将预测的结果提交至了Kaggle,经过多次对模型的调整,也得到了0.78708的最好成绩。