如果对问题更感兴趣,可以跳过引言,直接看第二部分 数据处理, Github地址。
引言
最近这段时间使用机器学习分析了Titanic等几个问题后,发现机器学习的实践过程流程繁多,需要反复调整特征和参数进行迭代,此外,很多流程是基于经验的或者启发式的;机器学习问题处理数据的流程往往是类似的,每个问题处理的结果和方法对其他问题是有参考价值的。类似的流程和发掘模式的过程没有被总结起来。
scikit-learn在流程模块化方面做得不错,将一些重要的方法按模块进行了分类:
- 数据预处理模块
preprocessing
、模型选择model_selection
、模型损失函数metric
、特征提取feature selection and feature extraction
- 流水线管理
pipeline
- 数据集管理
datasets
Aurélien Géron介绍了工程实践中机器学习的重要步骤。
- 熟悉业务类型、业绩解决方法和机器学习模块在工程实践架构中的位置(包括输入与输出等)。
- 数据的第一印象。
- 数据预处理以及特征工程。
- 尝试选用模型,并选出基础模型。
- 调参找出当前合适模型,得到完整模型参数和实验结果。
- 完整表述你的模型,找出核心模式,重复步骤3,直到达到最低的预期结果。
- 发布、监视和调整模型。
我理想的机器学习分析框架是应该包含知识库和工具集,类似于机械公敌中的机器管家。他应该具有以下特点:
- 数据印象报告。由于不同的数据集问题互相是可以提供参考和依据的,所以分析框架应该能够提供其它机器学习的数据印象。
- 特征选择和学习报告。特征的选择和学习是优化数据的重要模块,不同特征选择情况下模型的差异化需要进行分析和整理。
- 流程模块化。类似于scikit-learn的流程模块。
- wiki化。记录以前学习过的例子的印象。
接下来写下我分析Titanic的基本流程,然后搭建机器学习的基础分析框架。
Kaggle Titanic
先使用jupyter结合scikit-learn、pandas、matplotlib进行分析。
初始化环境
%matplotlib inline
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
一、问题类型
1.1 基本分析
Titanic需要根据每个人的年龄、仓位等信息判断生还可能性,属于标准二分类监督学习问题。一般处理流程是先使用决策树、线性逻辑回归这类基础而且具有解释性的模型进行初步分析,得到基础的权重值(Embbed嵌入式特征选择:正则化和决策树);结合Filter过滤(例如相关性分析)进行特征的学习和选择;部分特征值具有二选一的特性时或者相关性较低时可以使用Wrapped特征选择(例如启发性搜索或者随机搜索);最后使用集成学习方法进行训练得到最终模型。
问:是否有类似的问题,是否存在此类问题较好的解决方案?
- 问题经验:妇女、儿童、老人以及富人都可能获得较高的生存可能性,这些字段需要重点考察。
- 应用:倒是想到一些合理的应用:消防方面、交通事故这些领域可以通过综合天气、过往数据等来分配警力和检查频率等。
二、数据印象
2.1 数据构成分析
机器学习的数据是表格化的,很适合使用pandas来进行分析数据。pandas的基本使用方法可以参考pandas。
def get_titanic_data():
train_data =pd.read_csv("../data/titanic/train.csv")
test_data = pd.read_csv("../data/titanic/test.csv")
return train_data, test_data
train_data, test_data = get_titanic_data()
train_data.info()
train_data.head()
根据上面的数据,得到一些结论:
数据规模:采样偏差不大,训练规模较小。总共船员是2224人,死亡1502人,训练数据有891人,训练集死亡人数需要对比一下比例。
数据条目m = 891, 有用特征数目n = 10(除去PassengerId字段), 目标输出k = 1(Survived字段)
。数据特征:类型多样,需要进行筛选和处理。文本:Name;文本+数字:Ticket, Cabin;类目:Sex,Pclass,Embarked; 连续值:Age,Fare,SibSp,Parch。
数据质量:部分数据缺失严重,需要处理(拟合、填充或者参照组忽略)登陆港口少量缺失,年龄缺失1/4,客舱缺失3/4;异常值探测会在下面执行(比如某人的fare特别大等可以排查掉)。
2.2 数据统计学分析
根据数据特征分析,将训练数据进行划分:连续和离散,暂不分析包含文本字段。
target_column = 'Survived'
continuous_column_list = ['Age', 'SibSp', 'Fare',
'Parch']
discrete_column_list = ['Sex', 'Pclass', 'Embarked']
text_column_list = ['Name', 'Ticket', 'Cabin']
continuous_train_data = train_data.filter(continuous_column_list)
discrete_train_data = train_data.filter(discrete_column_list)
2.2.1 统计特征
2.2.1.1 连续值分析
continuous_train_data.describe()
总体情况
- 年龄:主要集中在15~40之间,根据四分区间可知分布较均匀,老人和小孩是少数,即skewed data,很可能被忽略,所以后期可以增加额外的特征字段;根据经验,年龄字段适合作为区间来处理。
- 票价:票价分配不均匀,均值在32,中值在14,方差波动大,少数人的票价较高,同样是skewed data,可以增加高价票字段。
- SibSp(旁系亲属)和Parch(直系亲属):范围取值均在0~10以内,可以考虑作为类目字段处理。75%以上人的Parch都是0,可以考虑换成0/1,考虑结合后期的相关性分析来获得结果。
柱状图
由柱状图可知:Parch、SibSp取值少,分布不均匀,不适合作为连续值来处理;Fare和Age部分区间取值过少,这部分特征容易被掩盖,需要提取额外的特征值。
相关性分析
二分类问题不适合皮尔逊相关系数,使用卡方校验检测Parch和SibSp和Survived相关性。
from sklearn.feature_selection import chi2
print "Parch:", chi2(train_data.filter(["Parch"]), train_data['Survived'])
print "SibSp:", chi2(train_data.filter(["SibSp"]), train_data['Survived'])
Parch: 10.09749911, 0.00148471
SibSp: 2.58186538, 0.10809421
可以看到Parch比SibSp的卡方校验取值大,p-value小,相关性更强。
Parch直系亲属:
feature = 'Parch'
feature_data = train_data.filter([feature, 'Survived'])
survived_data = feature_data[feature][feature_data.Survived == 1].value_counts()
unsurvived_data = feature_data[feature][feature_data.Survived == 0].value_counts()
df = pd.DataFrame({'Survived': survived_data, 'UnSurvivied': unsurvived_data})
df.plot(kind='bar', stacked=True)
plt.title('Survived_' + feature)
plt.xlabel(feature)
plt.ylabel(u'Number of people')
plt.show()
feature_data.groupby(feature).hist()
由上图可知不同取值情况下,Survived和Unsurvived概率不同,Parch/Sib和Survived是具有相关性的。在有3个旁系亲属或者3个直系情况下生还概率会急剧下降,但是由于数据有限,可以使用多亲属来作为新特征。
2.2.1.2 离散值分析
上面在分析Parch和SibSp时,已经进行了离散值分析。同理将Sex、Pclass、Embarked也分析一下。
for column in discrete_train_data.columns:
print discrete_train_data[column].value_counts()
总体情况
不存在skewed data的情况。
相关性分析
from sklearn.feature_selection import chi2
from sklearn.preprocessing import LabelEncoder
sex_label_data = LabelBinarizer().fit_transform(train_data['Sex'])
embarked_label_data = LabelEncoder().fit_transform(train_data['Embarked'].fillna('S'))
print "Embarked", chi2(pd.DataFrame(embarked_label_data), train_data['Survived'])
print "Sex:", chi2(sex_label_data, train_data['Survived'])
print "Pclass:", chi2(train_data.filter(["Pclass"]), train_data['Survived'])
输出
Embarked (array([ 10.20252466]), array([ 0.00140249]))
Sex: (array([ 92.70244698]), array([ 6.07783826e-22]))
Pclass: (array([ 30.87369944]), array([ 2.75378563e-08]))
由此可见:Sex以及Pclass的相关性特别强。Embarked需要进一步分析。
def print_stacked_hist(feature):
feature_data = train_data.filter([feature, 'Survived'])
survived_data = feature_data[feature][feature_data.Survived == 1].value_counts()
unsurvived_data = feature_data[feature][feature_data.Survived == 0].value_counts()
df = pd.DataFrame({'Survived': survived_data, 'UnSurvivied': unsurvived_data})
df.plot(kind='bar', stacked=True)
plt.title('Survived_' + feature)
plt.xlabel(feature)
plt.ylabel(u'Number of people')
plt.show()
print_stacked_hist('Sex')
print_stacked_hist('Pclass')
print_stacked_hist('Embarked')
通过柱状图可以,女性和富人的存活率会更高。不同港口也存在差异,但是不是很明显。可以考虑去除。
三、数据预处理
3.1 基础操作
缺失值
缺失值处理方法:
- 拟合:缺失个数不多时,并且有背景特征可以使用时。
- 填充:缺失数据不多,而且数据可以离散化数据(连续数据考虑step为步长离散化),直接填充NaN新类型或者使用均值、中值。
- 0/1化:当是否有记录本身与否就有意义时,可以考虑将是否有数据作为一个特征。例如可能存在逃票等。
- 丢弃:缺失数据过多时为避免引入噪声,可以直接丢弃。
数据具体处理过程
- Age: 年龄缺失1/4,由于特征和年龄大多没有关系,采取Step(采用5年,由于数据量不大,避免过于分散)离散化+填充NaN新类型的方法。
- 客舱Cabin: 缺失3/4,因为船舱可能和身份有关,所以直接使用0/1化。
- 登录港口Embarked:登录港口缺失少量数据,直接填充为最常见类型。
filled_data = train_data.copy()
# transform Age
filled_data.loc[np.isnan(train_data['Age']), 'Age'] = 0
def transform_category(data, start, step, category):
"""
data是一个array数据
"""
result = ((data - start) / step).astype(int) + category
return result
step = 5
filled_data['Age'] = transform_category(filled_data['Age'], 0, step, 0)
filled_data.loc[filled_data['Cabin'].notnull(), 'Cabin'] = 1
filled_data.loc[filled_data['Cabin'].isnull(), 'Cabin'] = 0
def get_most_common_category(series):
return series.value_counts().axes[0][0]
most_common = get_most_common_category(filled_data['Embarked'])
filled_data.loc[filled_data['Embarked'].isnull(), 'Embarked'] = most_common
通过上面的处理后,所有数据参数完整。
离散化
采样数据和真实数据存在差距。离散化处理好处是可以避免数据分布不均匀的情况,减少误差,坏处是丢失了部分精度。由于Titanic数据量少,所以将年龄离散化处理。
Dummy Coding
部分类目数据,本身是无序的,例如登录港口等,如果作为有序的离散值处理,对后续的距离计算造成误导,例如类别1和类别3距离为2,但是类别1和类别2距离为1。所以使用(1, 0, 0)、(0, 1, 0)和(0,0,1)来存储数据,称作虚拟编码dummy coding。
- Sex\Embarked\Cabin进行dummy coding
- Pclass本身有序的,暂时不进行dummy coding,当然等距离的假设可能不当,后期可以尝试使用dummy coding调整。
dummy_cabin = pd.get_dummies(new_data["Cabin"], prefix="Cabin")
dummy_sex = pd.get_dummies(new_data['Sex'], prefix='Sex')
dummy_embarked = pd.get_dummies(new_data['Embarked'], prefix='Embarked')
dummied_data = pd.concat([new_data, dummy_cabin, dummy_sex, dummy_embarked], axis=1)
dummied_data.drop(['Cabin', 'Sex', 'Embarked'], axis=1, inplace=True)
dummied_data.head()
标准化
根据Ng老师的梯度梯度下降等高线可知,Feature Scaling影响收敛速度。
- 将Fare数据进行归一化
from sklearn.preprocessing import StandardScaler
dummied_data['Fare'] = StandardScaler().fit_transform(dummied_data.filter(['Fare']))
3.2 数据清洗
异常值处理主要用于非监督学习中,空间分布奇异点。
3.3 经验处理
Ticket和Name字段为文本字段,需要经验处理。
搜罗了一下一些博客。
- Name中可以提取到性别信息Mrs、Mr,可以用于校验Sex正确性。
- Name中有‘Capt’、‘Don'等可以作为Title字段
- Ticket可以统计出类别,Titanic中类别过多,暂不使用。
综上,基础模型,暂时去除PassengerId、Ticket和Name字段
unsed_column = ['PassengerId', 'Name', 'Ticket']
target_prepared_y = dummied_data['Survived']
train_prepared_data = dummied_data.drop(unsed_column + ['Survived'], axis=1)
四、模型印象
先构建具有解释性的基础模型,例如LogisticRegression和DecisionTree。
LogisticRegression模型可以提供各特征的线性相关性分析。使用CV得到各特征的相关性如下。
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn import cross_validation, metrics
def modelfit(alg, X, Y, performCV=True, printFeatureImportance=True, cv_folds=5):
#Fit the algorithm on the data
#alg.fit(X, Y)
#Predict training set:
dtrain_predictions = alg.predict(X)
dtrain_predprob = alg.predict_proba(X)[:,1]
#Perform cross-validation:
if performCV:
cv_score = cross_validation.cross_val_score(alg, X, Y, cv=cv_folds, scoring='roc_auc')
#Print model report:
print("\nModel Report")
print("Accuracy : %.4g" % metrics.accuracy_score(Y.values, dtrain_predictions))
print("AUC Score (Train): %f" % metrics.roc_auc_score(Y, dtrain_predprob))
if performCV:
print("CV Score : Mean - %.7g | Std - %.7g | Min - %.7g | Max - %.7g" % (np.mean(cv_score),np.std(cv_score),np.min(cv_score),np.max(cv_score)))
#Print Feature Importance:
if printFeatureImportance:
feat_imp = pd.Series(alg.feature_importances_).sort_values(ascending=False)
feat_imp.plot(kind='bar', title='Feature Importances')
plt.ylabel('Feature Importance Score')
def train_model(model_class, print_coef=False, *args, **kwargs):
kf = KFold(n_splits=10)
best_lr = None
best_score = 0
for train_index, test_index in kf.split(train_prepared_data):
train_sub_data, target_sub_data = train_prepared_data.loc[train_index], target_prepared_y.loc[train_index]
test_sub_data, test_target_sub_data = train_prepared_data.loc[test_index], target_prepared_y.loc[test_index]
lr = model_class(*args, **kwargs)
lr.fit(train_sub_data, target_sub_data)
score = lr.score(test_sub_data, test_target_sub_data)
if score > best_score:
best_lr = lr
best_score = score
print best_score
print best_lr
modelfit(best_lr, train_prepared_data, target_prepared_y, printFeatureImportance=False)
if print_coef:
columns = list(train_prepared_data.columns)
plot_df = pd.DataFrame(best_lr.coef_.ravel(), index=columns)
plot_df.plot(kind='bar')
return best_lr
train_model(LogisticRegression, print_coef=True)
- Pclass: 负相关。即class 1的人生存可能性更高。
- Age:负相关。年龄越小越容易存活,但是特征作用不明显。
- SibSp和Parch均为负相关:作用不明显。
- Fare为正相关:作用不明显。
- Cabin_1为正相关:有舱位的信息明显,不符合常识。
- Female为正相关,Male为负相关。
- C、Q、S港口均为正相关,且高于年龄等字段。
决策树分类边界可以更加复杂,兼顾多区间字段。
逻辑回归中Age字段的分类边界无法同时考虑到儿童和老人。使用决策树的分类边界可能可以兼顾儿童和老人。
决策树在数据不均匀时,容易过拟合。
from sklearn.tree import DecisionTreeClassifier
model = train_model(DecisionTreeClassifier, max_depth=5)
from IPython.display import Image
from sklearn import tree
import pydotplus
dot_data = tree.export_graphviz(model, out_file=None,
feature_names=list(train_prepared_data.columns),
class_names=['UnSurvived', 'Survived'],
filled=True, rounded=True,
special_characters=True)
graph = pydotplus.graph_from_dot_data(dot_data)
Image(graph.create_png())
- 由上图的Survived颜色可知,Female Survived的可能性极大。
- 左分支Male在年龄Age较小财富Fare较少的一个分支上有大量存活。
- 右分支Female在高等级Pclass情况下容易存活。
- 右分支Embarked_S港口登船且财富Fare不少容易存活。
- 有分支Embarked_S港口登船且Age较小容易存活。
由此可知:
- Sex和Pclass均有较大的影响。
- Age、Fare和Embarked_S影响比较大。
比较线性回归和决策树发现:
- Sex和Pclass均是正相关作用。
- Age字段适合在决策树中发挥作用。
- 决策树容易过拟合。忽视大量特征。
提交kaggle结果
线性回归结果:
0.74163
决策树结果:
0.73684
综上:baseline模型完成,数据进行了初步的处理,得到了一些解释现象。下面通过特征工程,并分析错误用例来优化系统。
五、特征选择与特征学习
根据之前的数据分析和baseline模型,关于特征可以有下面的总结。
高质量特征包括Sex和Pclass,可以作为特征空间搜索的起始区间。两个特征在决策树和Logistic中均占有较高的信息熵和权重; 同时两者和Survived的卡方校验值高。
潜力特征Age和Fare:两者在决策树和Logistic中均有不可忽视的影响,但是相对Sex和Pclass影响小;根据之前的数据分析可知,两者分布不均匀,提取新字段:老人、儿童、富人。
Embarked字段:此字段在两个模型中的表现存在较大的差异,同时根据之前的风险,它的相关性一般。可以尝试去除它。
SibSp和Parch字段:表现不明显,根据其分布可知,不均匀。可以考虑将两者结合在一块生成新的字段或者bucket。根据分布数据可知,Parch大部分为0,可以0/1处理。
生成新特征
child_age = 15 / 5
old_man_age = 50 / 5
(train_data['Fare'] > 80).value_counts()
rich_fare = 1
def add_new_features(df):
df['Child'] = df['Age'].apply(lambda x: 1 if 0 < x<= child_age else 0)
df['Senior'] = df['Age'].apply(lambda x: 1 if x >= old_man_age else 0)
df['Rich'] = df['Fare'].apply(lambda x: 1 if x >= rich_fare else 0)
df['FamilySize'] = df['SibSp'] + df['Parch']
df['Single'] = df['Parch'].apply(lambda x: 1 if x == 0 else 0)
return
add_new_features(train_prepared_data)
如上所示,生成了Child、Senior、Rich、FamilySize、Single字段。
生成字段后提交结果:0.76077
特征选择
使用后向搜索,Sex、Pclass属性必需选项不进行筛选。
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
saved_list = ['Sex', 'Pclass']
def choose_feature_list(X, y, saved_list):
left_columns = list(X.columns)
lr = LogisticRegression()
best_score = np.mean(cross_val_score(lr, X, y, cv=5))
final_column_list = [x for x in left_columns]
while len(left_columns) >= 3:
new_item = None
for item in left_columns:
if item in saved_list:
continue
lr = LogisticRegression()
new_column = [x for x in left_columns]
new_column.remove(item)
score = np.mean(cross_val_score(lr, X.filter(new_column), y, cv=5))
if score > best_score:
best_score = score
new_item = item
if new_item is None:
break
final_column_list.remove(item)
print "remove:", new_item, "best_score ever", best_score
left_columns.remove(item)
choose_feature_list(train_prepared_data, target_prepared_y, saved_list)
需要去除Senior, Age
使用完全搜索:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
import re
import itertools
drop_str = 'Age|SibSp|Parch|Cabin_*|Embarked_*|Senior|Rich|Single'
drop_list = [column for column in train_prepared_data.columns if re.match(drop_str, column)]
print drop_list
def choose_feature_list(X, y, drop_list):
lr = LogisticRegression()
best_score = np.mean(cross_val_score(lr, X, y, cv=5))
final_drop_list = []
cnt = 0
for i in xrange(1, len(drop_list) + 1):
drop = None
for combination in itertools.combinations(drop_list, i):
cnt += 1
new_train_X = X.drop(list(combination), axis=1)
lr = LogisticRegression()
score = np.mean(cross_val_score(lr, new_train_X, y, cv=5))
if score > best_score:
best_score = score
final_drop_list = combination
drop = True
if cnt % 100 == 0:
print cnt
if drop:
print "drop_list:", final_drop_list, "best_score ever", best_score
choose_feature_list(train_prepared_data, target_prepared_y, drop_list)
需要去除的参数为
'Age', 'SibSp', 'Parch', 'Cabin_0', 'Senior', 'Rich', 'Single'
结论:两者相差不大,而且在数据集不大的情况下倾向于使用完全搜索得到的数据。
六、分析结果
判断模型的拟合状态,使用准确率来绘制learning curve。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.learning_curve 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"sample n")
plt.ylabel(u"score")
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"train")
plt.plot(train_sizes, test_scores_mean, 'o-', color="r", label=u"cv")
plt.legend(loc="best")
plt.gca().invert_yaxis()
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(lr, u"learning curve", new_prepared, target_prepared_y);
由曲线可知,模型不处于过拟合状态,由于两者相差不大而且准确率不低。
七、迭代问题
Bagging
使用bagging模型直接迭代之前的LR模型。
from sklearn.ensemble import BaggingRegressor
from sklearn.grid_search import GridSearchCV #Perforing grid search
clf = LogisticRegression()
bagging_clf = BaggingRegressor(clf, n_estimators=20, max_samples=0.8)
bagging_clf.fit(new_prepared, target_prepared_y)
print bagging_clf.score(new_prepared, target_prepared_y)
drop_data = dummied_prepard_data.drop(remove_feature_list, axis=1)
result = model.predict(drop_data)
result = model.predict(drop_data)
passage_list = test_data['PassengerId']
# print_result(passage_list, result)
提交后结果0.77512,还是不错的。看下learning curve。
不存在过拟合现象。
Boost
使用ensemble模型,GBDT进行学习。
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import GradientBoostingClassifier
gb_clf = GradientBoostingClassifier()
new_prepared = train_prepared_data.drop(remove_feature_list, axis=1)
model = train_model(new_prepared, target_prepared_y, GradientBoostingClassifier)
drop_data = dummied_prepard_data.drop(remove_feature_list, axis=1)
result = model.predict(drop_data)
passage_list = test_data['PassengerId']
print_result(passage_list, result)
由图可知,得到的结果提交后,发现数据结果并不好。
判断learning curve:
发现存在过拟合现象,进行参数调整。
from sklearn import cross_validation, metrics #Additional scklearn functions
from sklearn.grid_search import GridSearchCV #Perforing grid search
param_test = {
# 'n_estimators': range(20, 10, 40),
'min_samples_split':range(2, 20, 4),
'min_samples_leaf':range(1,10,2),
'learning_rate': (0.05, 0.2, 0.05),
'max_depth': range(6, 10, 2)
}
gv_search = GridSearchCV(estimator = GradientBoostingClassifier(n_estimators=5, max_features='sqrt', subsample=0.8, random_state=10),
param_grid = param_test, scoring='accuracy', iid=False, cv=5)
gv_search.fit(new_prepared, target_prepared_y)
print gv_search.best_score_
# modelfit(gv_search.best_estimator_, new_prepared, target_prepared_y)
得到结果:0.76555
八、表述模型
由于数据集有限,每个LR子学习器都是强学习器的情况下,集成学习最终的表现并不是太好。
当前最佳模型是LR模型,模型去除了Senior, Age得到最佳结果。Sex和Pclass重要性很高。
当前模型由于部分数据不太均匀,挖掘Fare和Age方面的意义不突出。FamilySize参数被接纳。可以考虑继续往Sex以及FamilySize方向继续特征学习和迭代。
通过Titanic熟悉了数据分析的基本流程,过程特别多,希望总结一个分析框架,方便快速分析问题。下面是基本分析框架的重要内容,具体实现下次再具体说明。
基础分析框架
类型划分
- 按label划分:监督学习、非监督学习、半监督学习和强化学习
- 按数据来源划分:online learning和batch learning
- 按输出模型划分: instance-based learning和model-based learning
主要挑战
- 缺乏足够的数据。儿童识别苹果只需要少量的数据,机器识别图片需要上百万的数据。
- 采样数据和真实数据有偏差。
- 数据质量不佳。噪声、异常值。忽略特征或者拟合、补充数据。
- 无用特征。需要筛选特征、构造新特征。
模型评价
- learning curve拟合程度:underfit or overfit
- 交叉验证cv: 检测泛化能力同时避免浪费数据。有些模型最后将模型再跑一遍数据。
- 评价标准和损失函数
框架实现
TODO: 具体框架实现和总结在下一篇说明。
经验总结
- metric选择
- 距离范式越高,越大的维度权重越高。越容易收到异常点的影响。