本例主要针对kaggle上的Titanic数据集进行分析预测,文章主体分为以下两个部分:
- 机器学习流程的回顾
- Titanic数据集的分析和处理
PS:流程回顾来源于Udacity的机器学习入门课程,Titanic数据的处理参考了kaggle上众位大佬的分享。
——————流程回顾——————
在开始进行分析之前,让我们仔细回顾下一个机器学习项目的流程。
数据集/问题——>特征处理——>算法建模——>评估
第一步,数据集/问题
机器学习是用来解决实际问题的,收集与实际问题有关的数据,能够有助于后续步骤的继续进行。在这个阶段需要知道
- 数据量足够大?
- 我提出了什么问题?
- 要回答这个问题,有足够的正确的特征么?
第二步,特征处理
探索型分析(EDA)
分析特征与变量的相关性(pearsonr相关系数)
删除一些离群值
清理特征与数据生成新的特征
一般来说,基于对现实业务的了解,生成新的特征。-
特征选择
- 单变量特征选择工具:
SelectPercentile :最强大的 X% 特征(X 是参数)
SelectKBest :选择 K 个最强大的特征(K 是参数) - 迭代特征(增/减)
- lasso回归:
在最小误差与特征数量之间寻找一个平衡,实际应用中,该数值越接近0,说明该特征带来的影响也就越小。 - 删除不必要的特征
- 单变量特征选择工具:
特征的缩放(适用于维度变化的算法):
减去平均值:x - mean
minmax scaler:(x-min)/(max-min)
standard scaler:(x-mean)/sigma-
转换特征:
-
PCA(主成分分析):数据中使方差最大化的方向,在对这些成分压缩或投影时,将信息丢失的可能性降至最低。
【只对符合高斯分布的样本点有效】
一般用于转换成新的特征,也可以用来划分等级,方差最大为第一等级,以此类推,但第一主成分绝对不会与第二主成分叠加。主成分等级的数量是有上限的,受制于输入特征的数量。用处:
- 寻找隐藏特征
- 降维:可视化高维数据,减噪
ICA(独立成分分析)
FA(因子分析)
-
第三步,算法
根据是否需要labels,分为监督学习和非监督学习
- 监督学习:
- 回归:线性回归、lasso回归、决策树回归、sv回归
- 分类:决策树、朴素贝叶斯、SVM、随机森林、Adaboost、knn、LDA、logistic回归
- 非监督学习:
- 聚类:K均值、DBSCN、EM算法、离群值检测
- 运行算法:调参、可视化检验、在测试集上运行、寻找最佳参数(GridsearchCV)
第四步,评估
- 验证:
分隔测试集和训练集、k-flods法、可视化 - 评估指标
SSE/R^2、准确率、召回率、F1 分数、特征曲线
PS:
以上步骤的顺序不是绝对的,为了获得拥有强大泛化能力的模型,我们需要不断地重复某些步骤。
———————Titanic数据集的分析与预测——————
n久之前,初生不畏牛犊的我,进入了kaggel,第一个练手的数据集——Titanic,死得剧惨。重新整理了思路了之后,预测效果也好了很多,具体思路见下文。
主要思路
- 加载数据集并进行简要探索性分析
- 特征工程
- 建模与模型评估
以前分析思路:
加载数据集——数据清理——探索性分析——特征处理——建模评估
1. 加载数据集&探索性分析
(1)数据概览:
train:
从简要的概览中,可以看出训练集中存在这些情况:
- Age,Cabin,Embarked存在空值
- Age,SibSp,Parch,Fare分布似乎呈偏右分布,具体还需要验证
测试集的加载和训练集类似,可以自己去试下。
这里为方便处理,合并训练集和测试集,生成新的DataFrame.
#合并测试集和训练集
df = pd.concat([train, test], axis=0).reset_index()
(2)从分布来看:
a. 整体的生存状况
b. SeX
c. Pclass
d. Embarked
e. Age
f. Fare
g. Parch
h. SibSp
发现:
- 女性的存活率高于男性
- Pclass1,2,3的生存率依次降低,这有可能与不同层的乘船人的社会地位,富裕程度有关
- Pclass 1,2女性的生存率远大于Pclass 3的女性,Pclass 1 的男性的生存率大于Pclass 2,3层的男性
- S口岸登船的人数最多,生存率也最低,C、Q口岸登船的人数和生存率正好相反。那个时代,远没有现在网络时代的发达,所以我们可以假定认为一个口岸登船的同一层的很容易坐在一起的,这也很可能是一个影响逃生率的因素。
- 年龄上来说,似乎很统一,小孩先走,存活率较高
- Fare分布差异很大,可能存在幼童免票,团体票,家庭票等情况
- Parch 代表同船的父母或子女,SipSp代表同船的兄弟姐妹,这都是两个表现亲人的关系,后面一起会做特征处理。
- 单人的存活几率低于有1个以上的或3个以下的亲人同船的存活几率,但过于一些过于庞大的家庭成员的生存几率。家庭成员过多也不好,过少也不好。
2. 特征工程
(1)Embarked
#缺失值填充
df['Embarked'].fillna("S",inplace=True)
#数值化S,C,Q
le = LabelEncoder()
le.fit(df['Embarked'])
df['Embarked'] = le.transform(df['Embarked'])
(2)Fare
#分配到个人票价
df['Fare'] = df['Fare'] / df.groupby('Ticket')['Fare'].transform('count')
df['Fare'].fillna(df['Fare'].median(),inplace=True)
sns.distplot(df["Fare"])
plt.title("Distribution of Fare");
#定义一个票价分级函数
def fare_level(s):
if s <= 5 :
m = 0
elif s>5 and s<=20:
m = 1
elif s>20 and s<=40:
m = 2
else:
m = 3
return m
df['Fare_level'] = df['Fare'].apply(fare_level)
(3)Parch and SibSp
#组合Parch,SibSp
df['Family_memebers'] = df['Parch'] + df['SibSp'] + 1
(4)Sex
#数值化性别
le.fit(df['Sex'])
df['Sex'] = le.transform(df['Sex'])
(5)Age
年龄拥有大量的缺失值,处理方法有很多中,这里采用建立一个回归模型预测年龄缺失值。
#利用线性回归和随机森林回归模型预测Age的值
age_nan = pd.DataFrame(df[['Age', 'Sex','Family_memebers', 'Fare', 'Pclass', 'Embarked']])
age_train = age_nan[age_nan.Age.notnull()]
age_test = age_nan[age_nan.Age.isnull()]
#线性回归
lr = LinearRegression()
lr_grid_pattern = {'fit_intercept': [True], 'normalize': [True]}
lr_grid = GridSearchCV(lr, lr_grid_pattern, cv=10, n_jobs=25, verbose=1, scoring='neg_mean_squared_error')
lr_grid.fit(age_train.drop("Age",axis=1), age_train["Age"])
print('Age feature Best LR Params:' + str(lr_grid.best_params_))
print('Age feature Best LR Score:' + str(lr_grid.best_score_))
lr = lr_grid.predict(age_test.drop("Age",axis=1))
#随机森林回归
rfr = RandomForestRegressor()
rfr_grid_pattern = {'max_depth': [3], 'max_features': [3]}
rfr_grid = GridSearchCV(rfr, rfr_grid_pattern, cv=10, n_jobs=25, verbose=1, scoring='neg_mean_squared_error')
rfr_grid.fit(age_train.drop("Age",axis=1), age_train["Age"])
print('Age feature Best LR Params:' + str(rfr_grid.best_params_))
print('Age feature Best LR Score:' + str(rfr_grid.best_score_))
rfr = rfr_grid.predict(age_test.drop("Age",axis=1))
#取二者均值
age_test["Age"] = (lr+rfr)/2
#定义年龄分级的函数
def age_level(s):
if s <= 15 : #儿童
m = 0
elif s>15 and s<=30: #青年及少年
m = 1
elif s>30 and s<=60: #壮年
m = 2
else: #老年
m = 3
return m
df["Age_level"] = df["Age"].apply(age_level)
3. 建模与模型评估
X = df[:len(train)][['Age_level', 'Sex','Family_memebers', 'Fare_level', 'Pclass', 'Embarked']]
y = df[:len(train)]["Survived"]
(1)评估方法
- 混淆矩阵
有准确率、召回率、F1值等指标,本文采用准确率。 - 交叉检验
将数据集划分成n份,选择一份作为测试集,其余n-1份作为训练集,重复n次(每次的测试集都不同)。
(2)建模
利用交叉检验得到结果,cv = 10,指标为准确率,可以看到SVC具有出色的表现。
(3)模型优化
主要利用GridSearchCV寻找最佳拟合的结果。
SVMC = SVC(probability=True)
svc_param_grid = {'kernel': ['rbf'],
'gamma': [ 0.001, 0.01, 0.1, 1],
'C': [1, 10, 50, 100,200,300, 1000]}
gsSVMC = GridSearchCV(SVMC,param_grid = svc_param_grid, cv=10, scoring="accuracy", n_jobs= 4, verbose = 1)
gsSVMC.fit(X,y)
SVMC_best = gsSVMC.best_estimator_
(4)预测
features = ['Age_level', 'Sex','Family_memebers', 'Fare_level', 'Pclass', 'Embarked']
SVMC_best.fit(X,y)
out_text = SVMC_best.predict(df[len(train):][features])
text = pd.DataFrame(out_text.astype(int),index=df[len(train):]['PassengerId'].values).reset_index()
text.rename(columns={"index":"PassengerId",0:"Survived"}).to_csv('predict_02.csv',index=False)
最后的结果:
笔者终于从倒数爬到了正数,不容易。
总体上来看,
- Titanic的特征数量比较少,很容易入手
- 后来我又用VotingClassifier进行集成,但结果居然没有单独的SVC表现更好,这可能与其强大随机性有关,具体的原因还可以再探究一下。
- 就特征方面来说,name和ticket可能包含了同一家人的信息,笔者没有做具体分析,这块可以再继续深入。
原文代码:在这里