1. 前言
在上篇文章:【Kaggle】Titanic - Machine Learning from Disaster中简单实现了比较粗暴的做法,直接堆叠了几层神经网络。但是,观察大家在Kaggle 发布的代码来看,仅仅将这个问题看做简单的机器学习模型问题即可。将该问题归纳为回归问题,可以直接使用经典的模型来解决,比如逻辑回归。当然上述问题也仅是其一。最重要的是,大家在分析问题和处理数据的时候,充分考虑了各个因素和目标变量之间的关系,通过简单的统计分析可以排除不相关因素,进而更有利于模型的拟合。在这篇文章中,将对这个题目进行较为正规和形式化的处理。
2. 预备-环境配置
由于通过kaggle 命令行的方式更加方便于提交结果和下载数据,所以在本地需要安装kaggle 。使用pip 安装即可:
pip install kaggle
或者到对应的虚拟环境中进行安装:
(base) C:\Users\w>conda install kaggle
然后到Kaggle 个人主页: 点击Account ,进入个人账户主页。点击Create New API Token : 然后就回自动下载一个kaggle.json 文件: 打开文件可以发现,其实也就是用户登录信息。然后需要将改文件放入用户目录下的.kaggle 隐藏文件夹内: 然后修改一下Kaggle 数据集下载的时候默认下载路径(默认下载路径就是上图路径),通过命令:
C:\Users\w>kaggle config set -n path -v d://Kaggle_Dataset
- path is now set to: d://Kaggle_Dataset
比如下载来测试一下:
kaggle competitions download -c titanic
结果可以得到一个压缩包:
3. 数据集处理
3.1 读取数据集
from zipfile import ZipFile
import pandas as pd
zipfiles = ZipFile("./competitions/titanic/titanic.zip")
zipfiles.extractall("competitions/titanic/")
test_data = pd.read_csv(baseDir+"test.csv")
train_data = pd.read_csv(baseDir+"train.csv")
3.2 查看pandas数据信息
3.2.1 查看总体信息
test_data.info()
结果:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 11 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Name 418 non-null object
Sex 418 non-null object
Age 332 non-null float64
SibSp 418 non-null int64
Parch 418 non-null int64
Ticket 418 non-null object
Fare 417 non-null float64
Cabin 91 non-null object
Embarked 418 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 36.0+ KB
3.2.2 数据集空值统计
test_data.isna().sum()
结果:
PassengerId 0
Pclass 0
Name 0
Sex 0
Age 86
SibSp 0
Parch 0
Ticket 0
Fare 1
Cabin 327
Embarked 0
dtype: int64
由于测试数据集共418条数据,而缺失达到了327,故而对于Cabin可以直接丢弃。
train_data = train_data.drop(['Cabin'], axis=1)
test_data = test_data.drop(['Cabin'], axis=1)
3.3. 相关性分析
由于根据题意需要分析的是最终的测试集的Survived字段,故而从两个角度来分析:
- 异常值检验,可以对Survived字段进行简单统计分析,观察其其大致的分布规律
- 训练集和测试集的单变量分析,也就是对训练集和测试集中的对应特征,可以进行简单的单变量分布规律对比;
- 相关性分析,也就是分析各个字段对Survived字段的相关性;
import seaborn as sns
3.3.1 可以对Survived字段进行简单统计分析,观察其其大致的分布规律
sns.set()
sns.histplot(train_data['Survived'], kde=True)
观察上图可知,绘制该图没有意义,因为可取值只有1和0。只能反映一个问题就是死亡人数超过存活人数。
3.3.2 训练集和测试集的单变量分析
train_count = train_data.shape[0]
test_count = test_data.shape[0]
train_data['Pclass'].value_counts().sort_index()/train_count
(train_data['Pclass'].value_counts().sort_index()/train_count).plot()
更加直接的来说,可以绘制左右的因素在训练集和测集上的分布规律:
import matplotlib.pyplot as plt
features = ['Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Embarked']
for feature in features:
(train_data[feature].value_counts().sort_index()/train_count).plot()
(test_data[feature].value_counts().sort_index()/test_count).plot()
plt.legend(['train', 'test'])
plt.xlabel(feature)
plt.ylabel('ratio')
plt.show()
等等。这里不再完全贴出。很明显,从上述图中可以发现训练集和测试集数据分布不一致的字段有:Name、Ticket,故而可以这里可以直接将这两个字段进行删除。
train_data = train_data.drop(['Name', 'Ticket'], axis=1)
test_data = test_data.drop(['Name', 'Ticket'], axis=1)
train_data.info()
注意到,SibSp, Parch字段的数据分布极为相似,所以这里可以对其进行合并处理,操作如下:
train_data['Family'] = train_data['Parch'] + train_data['SibSp']
train_data['Family'].loc[train_data['Family'] > 0] = 1
train_data['Family'].loc[train_data['Family'] == 0] = 0
train_data = train_data.drop(['Parch', 'SibSp'], axis=1)
test_data['Family'] = test_data['Parch'] + test_data['SibSp']
test_data['Family'].loc[test_data['Family'] > 0] = 1
test_data['Family'].loc[test_data['Family'] == 0] = 0
test_data = test_data.drop(['Parch', 'SibSp'], axis=1)
注意到train_data和test_data中均有PassengerId,故而这里可以简单校验一下数据的PassengerId是否有重复:
train_data['PassengerId'].nunique() == train_count
test_data['PassengerId'].nunique() == test_count
也就是,满足唯一条件。由于PassengerId仅用于表示数据,故而可以在训练数据集中删除,由于测试集最后输出的预测结果需要和PassengerId对应,这里先不删除。
train_data = train_data.drop(['PassengerId'], axis=1)
train_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 7 columns):
Survived 891 non-null int64
Pclass 891 non-null int64
Sex 891 non-null object
Age 714 non-null float64
Fare 891 non-null float64
Embarked 889 non-null object
Family 891 non-null int64
dtypes: float64(2), int64(3), object(2)
memory usage: 48.8+ KB
注意到此时,Age和Embarked字段还没有对缺失值处理,这里需要继续处理。
import numpy as np
age_mean = train_data['Age'].mean()
age_std = train_data['Age'].std()
count_nan_age = train_data["Age"].isnull().sum()
random_ages = np.random.randint(age_mean - age_std, age_mean + age_std, size=count_nan_age)
train_data['Age'].loc[np.isnan(train_data['Age']) == True] = random_ages
test_data的Fare字段缺少部分值,
test_data['Fare'].isna().sum()
结果为1。由于只有一个,直接使用众数填充:
test_data['Fare'].loc[test_data['Fare'].isna() == True] = test_data['Fare'].median()
test_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 7 columns):
PassengerId 418 non-null int64
Pclass 418 non-null int64
Sex 418 non-null object
Age 418 non-null float64
Fare 418 non-null float64
Embarked 418 non-null object
Family 418 non-null int64
dtypes: float64(2), int64(3), object(2)
memory usage: 22.9+ KB
然后可以进一步的来分析一下每个因素和Sruvived的关系。
plt.figure(figsize=(8,6))
sns.countplot(x='Survived', hue='Sex', data=train_data)
可以看出,女性更容易存活。进一步的,统计一下每个年龄段的存活情况:
plt.figure(figsize=(20,6))
train_data.Age = train_data.Age.astype(int)
average_age = train_data[['Age', 'Survived']].groupby(['Age'], as_index=False).mean()
sns.barplot(x='Age', y='Survived', data=average_age)
这里可以看到,年龄小和大均存活较高,可以结合年龄+性别,将用户分为:孩子、老人、男人、女人。
def get_person(person):
age, sex = person
if age<= 16:
return 0
elif age >= 63:
return 1
else:
return 2 if sex == 'male' else 3
train_data['Person'] = train_data[['Age', 'Sex']].apply(get_person, axis=1)
train_data.drop(['Age', 'Sex'], axis=1, inplace=True)
然后将Embarked字段同样处理为数字,查看下类别:
train_data.Embarked.value_counts()
def get_embarked(c):
if c == 'S':
return 0
elif c == 'Q':
return 1
else:
return 2
train_data.Embarked = train_data['Embarked'].apply(get_embarked)
现在数据集:
3.4 相关系数分析
from sklearn.linear_model import LogisticRegression
reg = LogisticRegression()
reg.fit(train_feature.to_numpy(), train_label.to_numpy())
pd.DataFrame(data=reg.coef_, columns=train_feature.columns)
这里可以知道,Fare字段相关性并不大,故而可以删除。
train_feature = train_feature[['Pclass', 'Embarked', 'Family', 'Person']]
test_data = test_data[['Pclass', 'Embarked', 'Family', 'Person']]
3.5 定义模型
import tensorflow as tf
epochs = 500
model = tf.keras.Sequential()
model.add(tf.keras.layers.Dense(64, input_shape=(train_feature.shape[1], ), activation='relu'))
model.add(tf.keras.layers.Dense(1, activation='sigmoid'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
model.summary()
history = model.fit(train_feature.to_numpy(), train_label.to_numpy(), epochs=epochs)
predict = model.predict(test_data)
predict[predict < 0.5] = 0
predict[predict >= 0.5] = 1
predict = predict[:, 0].astype(int)
predict
最后存储到csv文件:
submission = pd.DataFrame({
"PassengerId": test.PassengerId,
"Survived": predict
})
submission.to_csv('titanic.csv', index=False)
提交结果:
提高了一点点。当然,距离大佬的1还是很遥远。在这个过程中了解了数据的分析和处理手段,也挺好的。当然,完整地址:https://gitee.com/weizu_cool/kaggle/blob/master/titanic.ipynb
|