在做完数据分析以后,先对测试集有个了解
test_data.describe()
Out[1]:
PassengerId Pclass Age SibSp Parch Fare
count 418.000000 418.000000 332.000000 418.000000 418.000000 417.000000
mean 1100.500000 2.265550 30.272590 0.447368 0.392344 35.627188
std 120.810458 0.841838 14.181209 0.896760 0.981429 55.907576
min 892.000000 1.000000 0.170000 0.000000 0.000000 0.000000
25% 996.250000 1.000000 21.000000 0.000000 0.000000 7.895800
50% 1100.500000 3.000000 27.000000 0.000000 0.000000 14.454200
75% 1204.750000 3.000000 39.000000 1.000000 0.000000 31.500000
max 1309.000000 3.000000 76.000000 8.000000 9.000000 512.329200
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
与训练集不同的是有一行数据的Fare字段为空,查看一下
test_data[test_data['Fare'].isnull()==True]
Out[2]:
PassengerId Pclass Name Sex Age SibSp Parch Ticket \
152 1044 3 Storey, Mr. Thomas male 60.5 0 0 3701
Fare Cabin Embarked
152 NaN NaN S
用同Pclass,同Sex,同Embarked的平均值填充:
test_data.loc[test_data['Fare'].isnull(),'Fare']=test_data[(test_data.Pclass==3)&(test_data.Sex=='male')&(test_data.Embarked=='S')].dropna().Fare.mean()#符合条件的就一行数据,fare是7.65
(注意是&不是and,因为是3个筛选条件并列,写and报错)
因为Fare的值域太大,需要对其进行归一化。
那么为什么值域太大的特征就需要对其进行归一化呢?这个答案很好:
http://www.open-open.com/lib/view/open1429697131932.html
简单来说就是:1)归一化后加快了梯度下降求最优解的速度;2)归一化有可能提高精度”
import sklearn.preprocessing as preprocessing
scaler = preprocessing.StandardScaler()
#Fare
fare_scale_param = scaler.fit(train_data['Fare'].values.reshape(-1, 1))
train_data['Fare'] = fare_scale_param.transform(train_data['Fare'].values.reshape(-1, 1))
test_data['Fare'] = fare_scale_param.transform(test_data['Fare'].values.reshape(-1, 1))
先做一个baseline model,全代码如下:
import pandas as pd
import sklearn.preprocessing as preprocessing
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
train_data=pd.read_csv('../data/train.csv')
test_data=pd.read_csv('../data/test.csv')
#归一化
scaler = preprocessing.StandardScaler()
fare_scale_param = scaler.fit(train_data['Fare'].values.reshape(-1, 1))
#以5岁为一个周期离散,同时10以下,60岁以上的年分别归类
def age_map(x):
if x<10:
return '10-'
if x<60:
return '%d-%d'%(x//5*5, x//5*5+5)
elif x>=60:
return '60+'
else:
return 'Null'
#对训练集的预处理
train_data['Embarked'].fillna('S',inplace=True)# 把训练集Embarked是空的那两行的Embarked填充为S
train_data['Fare'] = fare_scale_param.transform(train_data['Fare'].values.reshape(-1, 1))
#fillna( ) Fill NA/NaN values using the specified method
train_data['Age_map'] = train_data['Age'].apply(lambda x: age_map(x))
#将类别型变量全部One-hot编码,因为逻辑回归只识别数值型,不识别类别型
train_data['Cabin']=train_data['Cabin'].isnull().apply(lambda x:'Null' if x is True else 'Not Null')
train_x = pd.concat([train_data[['SibSp','Parch','Fare']], pd.get_dummies(train_data[['Pclass','Sex','Cabin','Embarked','Age_map']])],axis=1)
train_y = train_data.Survived
#train_data.groupby('Age_map')['Survived'].agg(['count','mean']) 打印出来看看
#对测试集的预处理
test_data.loc[test_data['Fare'].isnull(),'Fare']=test_data[(test_data.Pclass==3)&(test_data.Sex=='male')&(test_data.Embarked=='S')].dropna().Fare.mean()#符合条件的就一行数据,fare是7.65
test_data['Fare'] = fare_scale_param.transform(test_data['Fare'].values.reshape(-1, 1))
#注意写&不能写and
test_data['Age_map'] = test_data['Age'].apply(lambda x: age_map(x))
test_data['Cabin']=test_data['Cabin'].isnull().apply(lambda x:'Null' if x is True else 'Not Null')
test_x = pd.concat([test_data[['SibSp','Parch','Fare']], pd.get_dummies(test_data[['Pclass', 'Sex','Cabin','Embarked', 'Age_map']])],axis=1)
if __name__ == '__main__':
base_line_model = LogisticRegression()
param = {'penalty':['l1','l2'],
'C':[0.1, 0.5, 1.0,5.0]}
grd = GridSearchCV(estimator=base_line_model, param_grid=param, cv=5, n_jobs=3)
grd.fit(train_x, train_y)
gender_submission = pd.DataFrame({'PassengerId': test_data.iloc[:, 0], 'Survived': grd.predict(test_x)})
gender_submission.to_csv('gender_submission.csv', index=None)
# 模型优化:
# 老老实实先把得到的model系数和feature关联起来看看。
print(pd.DataFrame({"columns": list(train_x.columns)[:], "coef": list(grd.best_estimator_.coef_.T)}))
'''
grd.best_estimator_
Out[1]:
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
penalty='l1', random_state=None, solver='liblinear', tol=0.0001,
verbose=0, warm_start=False)
'''
提交结果是6092名,0.77033分
pd.DataFrame({"columns": list(train_x.columns)[:], "coef": list(grd.best_estimator_.coef_.T)})
Out[11]:
coef columns
0 [-0.427371345792] SibSp
1 [-0.2089718658] Parch
2 [0.141110392011] Fare
3 [-0.680177573933] Pclass
4 [2.7955717529] Sex_female
5 [0.0] Sex_male
6 [0.653247015747] Cabin_Not Null
7 [0.0] Cabin_Null
8 [0.183840181375] Embarked_C
9 [0.0100687020896] Embarked_Q
10 [-0.245022175391] Embarked_S
11 [2.19167169207] Age_map_10-
12 [0.0] Age_map_10-15
13 [0.114171699169] Age_map_15-20
14 [0.0] Age_map_20-25
15 [0.289951314871] Age_map_25-30
16 [0.421529404794] Age_map_30-35
17 [0.0374208659749] Age_map_35-40
18 [0.0] Age_map_40-45
19 [-0.185169993649] Age_map_45-50
20 [0.0] Age_map_50-55
21 [-0.749003768057] Age_map_55-60
22 [-0.440792601238] Age_map_60+
23 [0.0] Age_map_Null
这个表coef为正代表与获救结果正相关,就是更容易获救;
coef为负代表与获救结果负相关,即更不容易获救。
可以观察出:
--小于10岁和女性明显更容易获救
--Cabin有值比Cabin无值更容易获救
--年轻力壮的比老年人容易获救
--S港口登录的更不容易获救
模型优化:
from sklearn.model_selection import cross_val_score cross_val_score(grd.best_estimator_,train_x,train_y,cv=5) [ 0.80446927 0.79329609 0.78089888 0.79213483 0.81920904]
- sklearn.cross_validation模块
cross validation大概的意思是:对于原始数据我们要将其一部分分为traindata,一部分分为test data。train data用于训练,test data用于测试准确率。在test data上测试的结果叫做validation error。将一个算法作用于一个原始数据,我们不可能只做出随机的划分一次train和testdata,然后得到一个validation error,就作为衡量这个算法好坏的标准。因为这样存在偶然性。我们必须多次的随机的划分train data和test data,分别在其上面算出各自的validation error。这样就有一组validationerror,根据这一组validationerror,就可以较好的准确的衡量算法的好坏。crossvalidation是在数据量有限的情况下的非常好的一个evaluate performance的方法。而对原始数据划分出train data和testdata的方法有很多种,这也就造成了cross validation的方法有很多种。
- 主要函数
sklearn中的cross validation模块,最主要的函数是如下函数:
sklearn.cross_validation.cross_val_score
调用形式是:sklearn.cross_validation.cross_val_score(estimator, X, y=None, scoring=None, cv=None,n_jobs=1, verbose=0, fit_params=None, pre_dispatch='2*n_jobs')
返回值就是对于每次不同的的划分raw data时,在test data上得到的分类的准确率。
- 参数解释:
estimator:是不同的分类器,可以是任何的分类器。比如支持向量机分类器:estimator = svm.SVC(kernel='linear', C=1)
cv:代表不同的cross validation的方法。如果cv是一个int值,并且如果提供了rawtarget参数,那么就代表使用StratifiedKFold分类方式;如果cv是一个int值,并且没有提供rawtarget参数,那么就代表使用KFold分类方式;也可以给定它一个CV迭代策略生成器,指定不同的CV方法。
scoring:默认Nnoe,准确率的算法,可以通过score_func参数指定;如果不指定的话,是用estimator默认自带的准确率算法。
交叉验证看看是不是过拟合了,画出学习曲线
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(当cv=None时,默认为3份)
n_jobs : 并行的的任务数(默认1)
train_sizes:数组,表示用百分之多少的样本去训练。train_sizes=np.linspace(.05, 1., 20)就是依次用start=5%,,end=100%,分成20份(即20个点)去训练
返回值:
train_sizes:array, shape = (n_unique_ticks,), dtype int
实际用于生成learning curve的训练集的样本数。由于重复的输入将会被删除,所以ticks可能会少于n_ticks.
对于本例就是把总样本数*5%,一直到总样本数*100%,分成20份
train_scores : array, shape= (n_ticks那么多行, n_cv_folds(即cv值)那么多列)
在训练集上的分数
test_scores : array, shape =(n_ticks行, n_cv_folds(即cv值)列)
在交叉验证集上的分数
"""
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes, verbose=verbose,score)
#print("train_sizes are")
#print(train_sizes)
#print("train_scores are")
#print(train_scores)
#print("test_scores are")
#print(test_scores)
#print("train_scores_mean")
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)
#print(train_scores_mean)
#print("type of train_socres_mean is %s",type(train_scores_mean))
#print(train_scores_mean[-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.show()
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
如何读懂学习曲线:http://sklearn.lzjqsdd.com/modules/learning_curve.html#learning-curve
对过拟合而言,通常以下策略对结果优化是有用的:
- 做一下feature selection,挑出较好的feature的subset来做training
- 提供更多的数据,从而弥补原始数据的bias问题,学习到的model也会更准确
而对于欠拟合而言,我们通常需要更多的feature,更复杂的模型来提高准确度。
著名的learning curve可以帮我们判定我们的模型现在所处的状态。我们以样本数为横坐标,训练和交叉验证集上的错误率作为纵坐标,两种状态分别如下两张图所示:过拟合(overfitting/high variace),欠拟合(underfitting/high bias)
再说回本题,根据代码我读懂了:
--线两侧的阴影部分,是mean 加减 std所得,所以阴影上下幅度越宽表明当前训练样本数,参数cv那么多折进行cv那么多次交叉验证,每次得到的分数差异较大
所以当上下幅度的阴影越收敛的时候,表明mean值(即点的值)越稳定越可信
--目前我们画的的曲线看来,我们的model并不处于overfitting的状态(overfitting的表现一般是训练集上得分高,而交叉验证集上要低很多,中间的gap比较大)。因此我们可以再做些feature engineering的工作,添加一些新产出的特征或者组合特征到模型中。