在已经有了训练集和测试集的前提下使用python3.7版本进行的编码分析
主要分析内容是根据泰坦尼克号上乘客的各种已知的属性信息和获救情况的关系分析,相当于一个二分类分析,特征如下
训练集和测试集数据地址可以从kaggle下载也可以从我的网盘直接拿:
链接:https://pan.baidu.com/s/1unIMw5t_m2h2uFylPL0Amw
提取码:4nox
一 数据简单的绘图分析
简单的借助pythony的 matplotlib.pyplot以及seaborn对数据进行简单地分析,也可根据经验觉得那些属性之间存在可能的关系,然后予以验证
python数据分析代码如下
import inline as inline
import pandas as pd
import seaborn as sns
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
data_train = pd.read_csv("Train.csv")
# #兄弟姐妹数量与获救情况分析(兄弟姐妹越多不被获救几率稍微偏低【也许是有人可替代】,不一定最符合正相关)
AF = data_train[['SibSp',"Survived"]]#只要部分列的数据
sns.regplot(x="SibSp",y="Survived",data=AF,y_jitter=0.1,x_jitter=0.3,marker='.',logistic=True)#x_jitter,y_jitter指定在x轴或y轴上的浮动防止点位覆盖
plt.show()
# #父母孩子数量与获救情况分析(父母孩子数量越多获救几率稍微偏高【也许是跟需要人照顾】,不一定最符合正相关)
sns.regplot(x="Parch",y="Survived",data=data_train,y_jitter=0.1,x_jitter=0.3,marker='.',logistic=True)
plt.show()
# #年龄与获救情况分析(年龄越小获救几率越高,不一定最符合正相关)
# AF = data_train[['Age',"Survived"]]#只要部分列的数据
# sns.regplot(x="Age",y="Survived",data=AF,y_jitter=0.1,x_jitter=0.3)#x_jitter,y_jitter指定在x轴或y轴上的浮动防止点位覆盖
# plt.show()
#性别与获救情况分析(年龄越小获救几率越高,不一定最符合正相关)
# AF = data_train[['Sex',"Survived"]]#只要部分列的数据
# print(AF['Sex'].map(lambda x: x=='male'?'0':'1'))
# AF['Sex'].e
# AF['Sex']=(AF['Sex']=='female').astype('int')#将性别用01表示方便绘制离散图
# 当有好多种类是非数字表示时候,为了用regplot加x_jitter,y_jitter来看其分布
# data_train['SexNum'] = data_train['Sex'].replace(['male','female'],[0,1])
# 将数据集中Ticket里纯数字和非纯数字的区分成两类 0纯数字 1非纯数字
# data_train['Ticket_'] = data_train['Ticket'].replace([r'^[0-9]*$',r'[^\d]'],[0,1],regex=True)
# print(data_train[['Ticket','Ticket_','Survived']])
# print(AF)
# print(type(AF))
# print(AF.dtypes)
# print(AF)
# sns.regplot(x="Sex",y="Survived",data=AF,x_jitter=.2,y_jitter=.2)#x_jitter,y_jitter指定在x轴或y轴上的浮动防止点位覆盖
# plt.show()
#分类图,x,y至少有一个数值(个人感觉此图效果最佳),女性的获救情况比男性高很多
# sns.swarmplot(x='Sex',y="Age",data=data_train,hue="Survived")
# plt.show()
# sns.stripplot(x='Sex',y="Age",data=data_train,hue="Survived")
# plt.show()
# 小提琴图split=True会根据hue指定的属性分隔在两边
# sns.violinplot(x='Sex',y="Age",data=data_train,hue="Survived",split=True)
# plt.show()
# # 通过观察船舱号码的有无发现有船舱号的获救概率大
# data_train['IsCabin']=data_train['Cabin'].isnull()
# sns.regplot(x='IsCabin',y="Survived",data=data_train,y_jitter=.2,x_jitter=.2)
# plt.show()
# import statsmodels
# # 通过观察票价发现票价越高获救概率大
# print(data_train[data_train.Fare<300])
# sns.regplot(x='Fare',y="Survived",data=data_train,marker="+",y_jitter=.1,logistic=True)
# sns.regplot(x='Fare',y="Survived",data=data_train[data_train.Fare<300],marker="+",y_jitter=.1,logistic=True)
# sns.regplot(x='Fare',y="Survived",data=data_train,y_jitter=.4,x_jitter=40,marker="+",logistic=True)
# plt.show()
# 不同的仓位等级以及性别对获救情况影响 因为Survived字段是0-1(1是获救) sns.barplot图是按照y属性求均值,在此处正好是获救占比
# sns.barplot(x='Sex',y='Survived',hue='Pclass',data=data_train)
# plt.show()
# 不同的仓位等级以及性别对获救情况影响 因为Survived字段是0-1(1是获救) sns.barplot图是按照y属性求均值,在此处正好是获救占比
# sns.pointplot(x='Sex',y='Survived',hue='Pclass',data=data_train)
# plt.show()
# sns.pointplot(x='Pclass',y='Survived',hue='Sex',data=data_train)
# plt.show()
# 登船港口不同获救概率明显不一样C>Q>S
# sns.pointplot(x='Embarked',y='Survived',data=data_train)
# sns.pointplot(x='Embarked',y='Survived',hue='Sex',data=data_train)
# plt.show()
# Ticket票码是否是纯数字的对于获救并无明显影响
# data_train['Ticket_'] = data_train['Ticket'].replace([r'^[0-9]*$',r'[^\d]'],[0,1],regex=True)
# print(data_train[['Ticket','Ticket_','Survived']])
# # sns.pointplot(x='Ticket_',y='Survived',data=data_train)
# sns.pointplot(x='Ticket_',y='Survived',hue='Sex',data=data_train)
# plt.show()
# factorplot可以指定kind 来做到上边的所有图类型默认kind='point'
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train)
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='point')
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='box')
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='strip')
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='violin')
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='swarm')
# sns.factorplot(x='Pclass',y='Survived',hue='Sex',data=data_train,kind='bar')
# plt.show()
# data_train['SexNum'] = data_train['Sex'].replace(['male','female'],[0,1])
# data_train['Ticket_']=data_train[data_train.Ticket>='A']=1
# data_train['Ticket_']=data_train['A'>data_train.Ticket]=0
# print(data_train[['Ticket','Ticket_','Survived']])
特征分析结果汇总:
# a.女性比男性获救几率大很多
# b.小孩比大人获救几率大很多,但经验猜测不是与age成线性关系
# c.有Cabin记录的似乎获救概率稍高一些
# d.Pclass的仓位级别越高获救几率越大,也许与当时的人的社会地位和拥有财富相关
# e.兄弟姐妹越多不被获救几率稍微偏低【也许是有人可替代传宗接代】
# e.父母孩子数量越多获救几率稍微偏高【也许是跟需要人照顾】
# f.票价发现票价越高获救概率大
# g.登船港口不同获救概率明显不一样C>Q>S
# h.Ticket票码是否是纯数字的对于获救并无明显影响
二.数据预处理以及初步建模
import pandas as pd #数据分析
import numpy as np #科学计算
data_train = pd.read_csv("Train.csv")
# print(data_train.info())
# 1.先整体观察一下数据,发现有不少列存在空值:Age Cabin Embarked
# data_train.info()
# 2.在 titanic数据分析.py 中了解到了数据中属性间对能否获救的关系
# a.女性比男性获救几率大很多
# b.小孩比大人获救几率大很多,但经验猜测不是与age成线性关系
# c.有Cabin记录的似乎获救概率稍高一些
# d.Pclass的仓位级别越高获救几率越大,也许与当时的人的社会地位和拥有财富相关
# e.兄弟姐妹越多不被获救几率稍微偏低【也许是有人可替代传宗接代】
# e.父母孩子数量越多获救几率稍微偏高【也许是跟需要人照顾】
# f.票价发现票价越高获救概率大
# g.登船港口不同获救概率明显不一样C>Q>S
# h.Ticket票码是否是纯数字的对于获救并无明显影响
# 读取训练集并将数据进行预处理
# ---------------------部分属性进行有必要的数值填充,部分属性数值化或行离散因子化-----------------------------------
# 处理年龄缺失值问题
from sklearn.ensemble import RandomForestRegressor
# 使用 RandomForestClassifier 填补缺失的年龄属性
def set_missing_ages(df):
# 将男女用01代替0男 1女
df['Sex_'] = df['Sex'].replace(['male', 'female'], [0, 1])
# 将是否有船舱记录用01代替 0有 1没有
df['Cabin_'] = df['Cabin'].isnull().astype('int');
# print(df[['Sex','Cabin','Sex_','Cabin_']])
# 认为称呼中Mr. Mrs. Miss. 不同称呼人群的平均年龄不一样 为了让称呼也作为补充缺失年龄所参考特征值 所以追加如下
df['Name_'] = 0
df.loc[df.Name.str.contains('Mr\.'), 'Name_'] = 1
df.loc[df.Name.str.contains('Mrs\.'), 'Name_'] = 2
df.loc[df.Name.str.contains('Miss\.'), 'Name_'] = 3
# 此处是因为测试集也用这个方法将数据预处理,但是测试集中存在Fare属性为空的情况所以此处的if主要是为了填充Fare属性为空的人员
if len(df[df.Fare.isnull()])>0:
# -------------------------------------------------------
# 把已有的数值型特征取出来丢进Random Forest Regressor中
Fare_df = df[['Fare', 'Name_', 'Parch', 'SibSp', 'Pclass']]
# 乘客分成已知年龄和未知年龄两部分
known_Fare = Fare_df[Fare_df.Fare.notnull()].as_matrix()
unknown_Fare = Fare_df[Fare_df.Fare.isnull()].as_matrix()
# y即目标年龄
y = known_Fare[:, 0]
# X即特征属性值
X = known_Fare[:, 1:]
# fit到RandomForestRegressor之中
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X, y)
# 用得到的模型进行未知年龄结果预测
predictedFares = rfr.predict(unknown_Fare[:, 1::])
# 用得到的预测结果填补原缺失数据
df.loc[(df.Fare.isnull()), 'Fare'] = predictedFares
# -----------------------------------------
# 把已有的数值型特征取出来丢进Random Forest Regressor中
age_df = df[['Age','Name_', 'Fare', 'Parch', 'SibSp', 'Pclass']]
# 乘客分成已知年龄和未知年龄两部分
known_age = age_df[age_df.Age.notnull()].as_matrix()
unknown_age = age_df[age_df.Age.isnull()].as_matrix()
# y即目标年龄
y = known_age[:, 0]
# X即特征属性值
X = known_age[:, 1:]
# fit到RandomForestRegressor之中
rfr = RandomForestRegressor(random_state=0, n_estimators=2000, n_jobs=-1)
rfr.fit(X, y)
# 用得到的模型进行未知年龄结果预测
predictedAges = rfr.predict(unknown_age[:, 1::])
# 用得到的预测结果填补原缺失数据
df.loc[(df.Age.isnull()), 'Age'] = predictedAges
# 因为逻辑回归建模时,需要输入的特征都是数值型特征,我们通常会先对类目型的特征因子化。
# 暂时只认为另外的非数值列Embarked 会对预测结果有影响,所以将其进行离散因子化
dummies_Embarked = pd.get_dummies(df['Embarked'], prefix='Embarked')
df = pd.concat([df, dummies_Embarked], axis=1)
# 去除非数值型的列以及已经无用的'Name_',他只是用来修正缺失Age用的
df.drop(['PassengerId', 'Name', 'Sex', 'Ticket', 'Cabin', 'Embarked', 'Name_'], axis=1, inplace=True)
# ---------------特征缩放-----------------------------
# 打印df发现属性中的发现Age和Fare数值幅度变化太大
# 逻辑回归与梯度下降中如果各属性值之间scale差距太大,将对收敛速度造成几万点伤害值!甚至不收敛!
# 显示所有列
pd.set_option('display.max_columns', None)
# 显示所有行
# pd.set_option('display.max_rows', None)
# 设置value的显示长度为100,默认为50
pd.set_option('max_colwidth', 100)
# print(df)
# 用scikit-learn里面的preprocessing模块对Age和Fare这俩货做一个scaling,所谓scaling,其实就是将一些变化幅度较大的特征化到[-1,1]之内
import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
age_scale_param = scaler.fit(df[['Age']])
df['Age_scaled'] = scaler.fit_transform(df[['Age']], age_scale_param)
fare_scale_param = scaler.fit(df[['Fare']])
df['Fare_scaled'] = scaler.fit_transform(df[['Fare']], fare_scale_param)
return df, rfr
# def set_Cabin_type(df):
# df.loc[(df.Cabin.notnull()), 'Cabin'] = "Yes"
# df.loc[(df.Cabin.isnull()), 'Cabin'] = "No"
# return df
# df = set_Cabin_type(df)
df, rfr = set_missing_ages(data_train)
# 用正则取出我们要的属性值
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_|Sex_|Pclass')
train_np = train_df.as_matrix()
# print(train_np)
# --------------------逻辑回归建模-----------------------
from sklearn import linear_model
# y即Survival结果
y = train_np[:, 0]
# X即特征属性值
X = train_np[:, 1:]
# fit到RandomForestRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(X, y)
# 输出模型
# print(clf)
# -----------------------test.csv直接丢进model里---------------------------
data_test = pd.read_csv("test.csv")
# print(data_test.info())
df, rfr_test = set_missing_ages(data_test)
# 用正则取出我们要的属性值
test = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_|Sex_|Pclass')
predictions = clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
# 将预测结果存入predictions_result.csv
result.to_csv("predictions_result.csv", index=False)
将模型初步的初步预测结果提交kaggle,得分0.76555,还可以,不过这才刚刚开始,模型的优化
三.模型优化
# -------------------------交叉验证--------------------------------------------
# from sklearn import cross_validation
# 改为下面的从model_selection直接import cross_val_score 和 train_test_split
from sklearn.model_selection import cross_val_score, train_test_split
#简单看看打分情况
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
all_data = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass_.*')
X = all_data.as_matrix()[:,1:]
y = all_data.as_matrix()[:,0]
# print(cross_validation.cross_val_score(clf, X, y, cv=5))
print(cross_val_score(clf, X, y, cv=5))
# ---------------------------------------------------把交叉验证里面的bad case拿出来看看,看看人眼审核,是否能发现什么蛛丝马迹,
# ---------------------------------------------------是我们忽略了哪些信息,使得这些乘客被判定错了。再把bad case上得到的想法和前头系数分析的合在一起,然后逐个试试
# 分割数据,按照 训练数据:cv数据 = 7:3的比例
# split_train, split_cv = cross_validation.train_test_split(df, test_size=0.3, random_state=0)
split_train, split_cv = train_test_split(df, test_size=0.3, random_state=42)
train_df = split_train.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_|Sex_|Pclass_.*|Embarked_.*')
# 生成模型
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
clf.fit(train_df.as_matrix()[:,1:], train_df.as_matrix()[:,0])
# 对cross validation数据进行预测
cv_df = split_cv.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_|Sex_|Pclass_.*|Embarked_.*')
predictions = clf.predict(cv_df.as_matrix()[:,1:])
print(predictions)
origin_data_train = pd.read_csv("train.csv")
bad_cases = origin_data_train.loc[origin_data_train['PassengerId'].isin(split_cv[predictions != cv_df.as_matrix()[:,0]]['PassengerId'].values)]
bad_cases.to_csv("bad_case_result.csv", index=False)
通过观察bad case尝试了一些新的组合特性和其他的新增特性效果有一点点提高,大家可以根据自己的才行新增一些特性试,比如在称呼上找到些新特性,为空年龄的确定方式,在兄弟姐们和父母孩子这两属性上看一看,比图如果有孩子还是女性我们认为他是一个母亲,也许获救几率会更大些,等等。。。
四.学习曲线以及模型融合
学习曲线主要是观察训练样本数量对模型质量的影响可以简单的看出到底是欠拟合还是过拟合,
模型融合有点类似于选举多个模型同事对一个样本投票票数高的决定他是否能获救,多模型可以是一组训练集+多种类的模型(比如使用逻辑回归模型和KNN还有神经网络等多种类的模型)出来的多模型,也可以是多组训练集+一中类型的模型,因为我们只提到了逻辑回归模型,下边就是使用BaggingRegressor实现的多组训练集+逻辑回归模型实现的模型融合
# -------------------------------------学习曲线图-------------------------------------------
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams['font.sans-serif'] = ['SimHei']#防止绘图中的中文乱码
# from sklearn.learning_curve import learning_curve 修改以fix learning_curve DeprecationWarning
from sklearn.model_selection import learning_curve
# 用sklearn的learning_curve得到training_score和cv_score,使用matplotlib画出learning curve
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None, n_jobs=1,
train_sizes=np.linspace(.05, 1., 20), verbose=0, plot=True):
"""
画出data在某模型上的learning curve.
参数解释
----------
estimator : 你用的分类器。
title : 表格的标题。
X : 输入的feature,numpy类型
y : 输入的target vector
ylim : tuple格式的(ymin, ymax), 设定图像中纵坐标的最低点和最高点
cv : 做cross-validation的时候,数据分成的份数,其中一份作为cv集,其余n-1份作为training(默认为3份)
n_jobs : 并行的的任务数(默认1)
"""
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
if plot:
plt.figure()
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel(u"训练样本数")
plt.ylabel(u"得分")
plt.gca().invert_yaxis()
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std, train_scores_mean + train_scores_std,
alpha=0.1, color="b")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std, test_scores_mean + test_scores_std,
alpha=0.1, color="r")
plt.plot(train_sizes, train_scores_mean, 'o-', color="b", label=u"训练集上得分")
plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"交叉验证集上得分")
plt.legend(loc="best")
plt.draw()
plt.gca().invert_yaxis()
plt.show()
midpoint = ((train_scores_mean[-1] + train_scores_std[-1]) + (test_scores_mean[-1] - test_scores_std[-1])) / 2
diff = (train_scores_mean[-1] + train_scores_std[-1]) - (test_scores_mean[-1] - test_scores_std[-1])
return midpoint, diff
plot_learning_curve(clf, u"学习曲线", X, y)
# ----------------------------模型融合------------------------
# 因为暂时只学了logistic regression逻辑回归模型,所以使用BaggingRegressor来做单种类模型,多训练集,来做单种类多的多个模型来实现模型融合
from sklearn.ensemble import BaggingRegressor
train_df = df.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_.*|Embarked_.*|Sex_.*|Pclass.*|Mother|Child|Family|Title')
train_np = train_df.as_matrix()
# y即Survival结果
y = train_np[:, 0]
# X即特征属性值
X = train_np[:, 1:]
# fit到BaggingRegressor之中
clf = linear_model.LogisticRegression(C=1.0, penalty='l1', tol=1e-6)
bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8, max_features=1.0, bootstrap=True, bootstrap_features=False, n_jobs=-1)
bagging_clf.fit(X, y)
test = df_test.filter(regex='Survived|Age_.*|SibSp|Parch|Fare_.*|Cabin_|Sex_|Pclass_.*|Embarked_.*')
predictions = bagging_clf.predict(test)
result = pd.DataFrame({'PassengerId':data_test['PassengerId'].as_matrix(), 'Survived':predictions.astype(np.int32)})
result.to_csv("logistic_regression_bagging_predictions.csv", index=False)
五.总结
对于任何的机器学习问题,不要一上来就追求尽善尽美,先用自己会的算法撸一个最基本的model出来,再进行后续的分析步骤,一步步提高。
在后续的问题分析的过程中,谨记以下四点:
- 『对数据的认识很重要了!』
- 『数据中的特殊点/离群点的分析和处理很重要了!』
- 『特征工程(feature engineering)很重要了!』
- 『模型融合(model ensemble)很重要了!』
本文中用机器学习解决问题的过程大概如下图所示: