新手往往将Titanic作为Kaggle入门,关于题目的详情可以去Kaggle查看,题目的基本意思的是根据一些乘客信息判断是否能存活。
机器学习的问题第一步总是先写一个baseline出来跑通,然后再做优化。
传统机器学习方法第一步需要观察数据,先人工提取有用的feature。
数据观察
说起观察想起高中数学老师总说做数学需要强大的观察能力,现在看来做数据分析也是非常需要的。
数据是csv格式,首先是python pandas库的使用,pandas库是python数据处理十分强大的库,在加载csv格式文件时你往往能看见它。如果数据列比较多,可能print显示时中间可以...代替,这样就无法完整查看数据是什么格式的了,小的文件你还能直接拿Excel打开,大文件你必须用程序查看前几行,知道都是些什么数据。
import pandas as pd
import matplotlib as mpl
pd.set_option('display.max_columns', 500, 'display.width', 1000) # 设置显示宽度
data_train = pd.read_csv("train.csv")
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
mpl.rcParams['font.family'] = 'STSong'
mpl.rcParams['font.size'] = 10
data_train.info()可以查看数据的基本信息如图1.
可以看到数据有哪些列,这些列也是基础的feature,共891行,但Age,Cabin等信息存在缺失,这在后续步骤中需要填充等处理。
data_train.describe()可以对一些数值信息有基本的认识。如图2所示。
可以看到总数,平均值等信息。
如果光看数据无法看出要预测的数据与哪个feature关系比较大,可通过画图直观的显示,往往先选取自己认为关系比较大的,比如这里年龄,性别,等级跟存活率关系会大一点。
fig = plt.figure()
fig.set(alpha=0.2) # 透明度设置
plt.subplot2grid((2, 3), (0, 0)) # 2*3格,第一张图
data_train.Survived.value_counts().plot(kind='bar') # 获救人数柱状图
plt.title("获救情况(1为获救)")
plt.ylabel("人数")
plt.subplot2grid((2, 3), (0, 1))
data_train.Pclass.value_counts().plot(kind='bar') # 乘客等级柱状图
plt.title("乘客等级")
plt.ylabel("人数")
plt.subplot2grid((2, 3), (0, 2))
plt.scatter(data_train.Survived, data_train.Age) # 散点图,横坐标是否存活,纵坐标年龄
plt.ylabel("年龄")
plt.grid(b=True, which='major', axis='y') # b设置网线,which默认'major',绘制y方向的网线
plt.title("按年龄看获救情况")
plt.subplot2grid((2, 3), (1, 0), colspan=2) # 横向两格
# kernel density estimate(核密度估计)
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("年龄")
plt.ylabel("密度")
plt.title("各等级的年龄分布")
plt.legend(("1等仓", "2等仓", "3等仓"), loc='best') # loc显示位置,best自适应
plt.subplot2grid((2, 3), (1, 2))
data_train.Embarked.value_counts().plot(kind='bar')
plt.title("各登船口岸上船人数")
plt.ylabel("人数")
图形如图3:
同样可以画出各个feature与存活率之间的关系。
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({"获救": survived_1, "未获救": survived_0})
df.plot(kind='bar', stacked=True)
plt.title("各乘客等级的获救情况")
plt.xlabel("乘客等级")
plt.ylabel("人数")
survived_00 = data_train.Sex[data_train.Survived == 0].value_counts()
survived_11 = data_train.Sex[data_train.Survived == 1].value_counts()
df1 = pd.DataFrame({"获救": survived_11, "未获救": survived_00})
df1.plot(kind='bar', stacked=True)
plt.title("男女各获救情况")
plt.xlabel("性别")
plt.ylabel("人数")
fig = plt.figure()
fig.set(alpha=0.65) # 设置图像透明度,无所谓
plt.title(u"根据舱等级和性别的获救情况")
plt.axis('off') # 原坐标轴不显示
ax1 = fig.add_subplot(141)
data_train.Survived[data_train.Sex == 'female'][data_train.Pclass != 3]\
.value_counts().plot(kind='bar', label="female high_class", color='#FA2479')
ax1.set_xticklabels(["获救", "未获救"], rotation=0)
ax1.legend(["女性 高级舱"], 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(["未获救", "获救"], rotation=0)
plt.legend(["女性 低级舱"], 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(["未获救", "获救"], rotation=0)
plt.legend(["男性 高级舱"], 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(["未获救", "获救"], rotation=0)
plt.legend(["男性 低级舱"], loc='best')
Survived_e0 = data_train.Embarked[data_train.Survived == 0].value_counts()
Survived_e1 = data_train.Embarked[data_train.Survived == 1].value_counts()
df = pd.DataFrame({'获救': Survived_e1, '未获救': Survived_e0})
df.plot(kind='bar', stacked=True)
plt.title("各登录港口乘客的获救情况")
plt.xlabel("登录港口")
plt.ylabel("人数")
g_SibSp = data_train.groupby(['SibSp', 'Survived'])
df_SibSp = pd.DataFrame(g_SibSp.count()['PassengerId'])
print(df_SibSp)
g_Parch = data_train.groupby(['Parch', 'Survived'])
df_Parch = pd.DataFrame(g_Parch.count()['PassengerId'])
print(df_Parch)
Survived_cabin = data_train.Survived[pd.notnull(data_train.Cabin)].value_counts()
Survived_no_cabin = data_train.Survived[pd.isnull(data_train.Cabin)].value_counts()
df = pd.DataFrame({'有': Survived_cabin, '无': Survived_no_cabin}).transpose()
df.plot(kind='bar', stacked=True)
plt.title("按Cabin有无看获救情况")
plt.xlabel("Cabin有无")
plt.ylabel("人数")
图表数据分析先到此为止了,因为这个问题非常现实,往往不怎么分析我们也知道一些信息与要预测的存在相关性,需要图表分析整理的往往是一眼看不出来存在关系的,或者某几个串在一起会出现关系,或者转换一下存在关系。
下一步是对数据进行初步的处理,先处理Age与Cabin缺失的数据,这两个feature是明显与存活率有关的,所以必须对这两项数据进行处理。
# 使用RandomForestClassifier填充缺失的年龄属性
def set_missing_ages(df):
age_df = df[['Age', 'Fare', 'Parch', 'SibSp', 'Pclass']] # 把已有的数值特征取出来放进Random Forest Regressor中
# 乘客分成已知年龄与未知年龄两部分
know_age = age_df[age_df.Age.notnull()].values # convert the frame to its Numpy-array representation
unknow_age = age_df[age_df.Age.isnull()].values
# y目标年龄
y = know_age[:, 0]
# X 特征属性
X = know_age[:, 1:]
# fit到RandomForestRegressor中
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1) # 单独说明
rfr.fit(X, y)
# 用得到的模型进行未知年龄结果预测
predicted_ages = rfr.predict(unknow_age[:, 1::])
# 用的到的预测结果填充原缺失数据
df.loc[(df.Age.isnull()), 'Age'] = predicted_ages
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)
利用随机深林分类将年龄数据补全。因Cabin数据比较杂,缺的也挺多,就简单将它分为有无数据两类。
将Cabin,Embarked等数据变为one-hot编码,这是与使用逻辑回归函数有关。
# 特征因子化,相当于one-hot编码
dummies_Cabin = pd.get_dummies(data_train['Cabin'], prefix='Cabin')
dummies_Embarked = pd.get_dummies(data_train['Embarked'], prefix='Embarked')
dummies_Pclass = pd.get_dummies(data_train['Pclass'], prefix='Pclass')
dummies_Sex = pd.get_dummies(data_train['Sex'], prefix='Sex')
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) # 去除原来的列
因Age与Fare的数据幅度相对较大,对于利用逻辑回归算法非常不利,需要将其统一分布在[-1, 1]之间。
# Age与Fare数值幅度太大,不利于逻辑回归与梯度下降
# scaling处理,将一些变化幅度较大的特征化到[-1,1]之间,这样可以加速logistic regression回归
scaler = preprocessing.StandardScaler()
age_scale_param = scaler.fit(df['Age'].values.reshape(-1, 1)) # 输入时2D的,如果是单一feature要reshape(-1,1),values是转化为array
df['Age_scaled'] = scaler.fit_transform(df['Age'].values.reshape(-1, 1), age_scale_param)
fare_scale_param = scaler.fit(df['Fare'].values.reshape(-1, 1))
df['Fare_scaled'] = scaler.fit_transform(df['Fare'].values.reshape(-1, 1), fare_scale_param)
将需要的特征提取出来建立线性回归模型
# 将需要的feature字段取出来,装成numpy格式,使用scikit-learn中的LogisticRegression建模
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
train_np = train_df.values
# y即Survival结果
y = train_np[:, 0]
# x即特征属性
X = train_np[:, 1:]
# fit到LogisticRegression中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6) # 单独分析
clf.fit(X, y)
对测试集同样处理数据
# 同样对test数据集处理
data_test = pd.read_csv("test.csv")
# print(data_test.info())
# Fare有一个缺失
data_test.loc[(data_test.Fare.isnull()), 'Fare'] = 0
# 利用train数据集训练的模型填充test中的Age
tmp_df = data_test[['Age', 'Fare', 'Parch', 'SibSp', "Pclass"]]
test_no_age = tmp_df[data_test.Age.isnull()].values
test_predicted_age = rfr.predict(test_no_age[:, 1:])
data_test.loc[(data_test.Age.isnull()), 'Age'] = test_predicted_age
data_test = set_Cabin_type(data_test)
# 特征因子化,相当于one-hot编码
dummies_Cabin_test = pd.get_dummies(data_test['Cabin'], prefix='Cabin')
dummies_Embarked_test = pd.get_dummies(data_test['Embarked'], prefix='Embarked')
dummies_Pclass_test = pd.get_dummies(data_test['Pclass'], prefix='Pclass')
dummies_Sex_test = pd.get_dummies(data_test['Sex'], prefix='Sex')
df_test = pd.concat([data_test, dummies_Cabin_test, dummies_Embarked_test, dummies_Sex_test, dummies_Pclass_test], 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'].values.reshape(-1, 1), age_scale_param) # age_scale_param依然使用train的范围
df_test['Fare_scaled'] = scaler.fit_transform(df_test['Fare'].values.reshape(-1, 1), fare_scale_param)
# print(df_test)
test_df = df_test.filter(regex='Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
最后利用线性回归模型预测并保存最后结果
predictions = clf.predict(test_df)
result = pd.DataFrame({'PassengerId': data_test['PassengerId'].values, 'Survived': predictions.astype(np.int32)})
result.to_csv("logistic_regression_predictions.csv", index=False) # 保存csv
至此一个基础的baseline完成的,可以上传测试,可以发现结果不是很乐观,毕竟只是利用了最简单的线性回归模型,更多地时间花在了数据初步分析与处理上了,接下来要做的就是继续优化。
参考文档
https://blog.csdn.net/han_xiaoyang/article/details/49797143