机器学习之step by step实战及知识积累笔记
数据工作者工作时间划分
据crowdflower数据科学研究报告,数据科学工作者的时间分配主要在以下几个领域:
首先是数据收集要占20%左右的时间和精力,接着就是数据清洗和再组织需要占用60%的时间。也就是说数据科学家80%的精力都花在了数据收集和预处理,从而生成能够用于训练模型的训练集。真正的算法优化和训练只占4%左右,另外10%左右用于特征提取,数据再造。
正确的特征集及足够的数据量决定了机器学习效果的上限,算法的优化可以无限逼近这个上限
机器学习的一般流程
获取kaggle titanic数据集
以前通过requests以及session就能够先登录kaggle,然后直接get到相应的dataset,但是似乎kaggle也在做相应的转型,现在只能通过kaggle的命令行操作:
kaggle.exe datasets download -d rashigoel/titanic-machine-learning-from-disaster #Downloading titanic-machine-learning-from-disaster.zip to C:\Users\zhenghuz\.kaggle\datasets\rashigoel\titanic-machine-learning-from-disaster 100%|▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒| 33.9k/33.9k [00:00<00:00, 105kB/s]
cleaning and organizing
这个过程具体有哪些工作要做:
经过数据收集这个过程拿到raw data后,我们首先需要通过常规的统计分析及数据可视化工具手段来熟悉和学习这些数据,该过程我们称之为EDA(exploratory Data Analysis),比如这些数据中有没有缺失的字段,有没有明显背离常识范围的数据?这个阶段我们发现数据问题,随后我们要经过一个所谓"数据清洗"的过程(data munging)来解决这些问题。比如对于缺失部分字段的数据可能需要使用某种平均值的算法来做填充,或者彻底清除。对于明显超范的数据做抛弃处理等。。到这里,数据基本上是可以用的并且“干净”可靠的了,但是在喂给学习算法之前,往往我们要做一个名为“特征工程”(feature engineering)的工程过程,主要是从已有的数据attribute属性中,我们找出那些真正对结果有重要影响的属性作为"feature特征",或者我们可能会组合创造出新的feature(特征)用于算法学习。同时我们可能要通过相关性探究发现那些重复的特征,剔除冗余信息,只保留独立的特征数据。
在这个过程中,我们可能会用到一些交叉数据特征探究的高级数据可视方法(advanced visulization),来辅助对数据的认识和特征构造及提取。
numpy and pandas
numpy是python数据科学的基础库,其提供了非常强大高效的n维数组。pandas基于numpy,提供了dataframe,series等数据结构,非常方便地以类似表格的形式来做数据检索和处理。pandas也提供基于matplotlib的图形库,方便数据学习和特征提取。pandas的dataframe每一行都可以看作是一个observation,每一列都可以看作是一个feature
Exploratory Data Analysis(EDA)
EDA阶段我们主要探索数据集的基础结构(basic structure), 综合统计(summary statistics),数据分布特性(distribution),分组特征(grouping),交叉特征和pivot
基础结构(basic structure)
- 有多少行数据(observation)?
- 每个observation有多少个feature?
- feature对应的数据类型
- 每一行大概长什么样子?(可能要通过tail,head命令)
首先读取数据
import pandas as pd import numpy as np import os raw_data_path = os.path.join(os.path.curdir,'data','raw') train_file_path = os.path.join(raw_data_path,'train.csv') test_file_path = os.path.join(raw_data_path,'test.csv') # read the data with pandas into datafram # train_df,test_df: pandas.core.frame.DataFrame train_df = pd.read_csv(train_file_path,index_col='PassengerId') test_df = pd.read_csv(test_file_path,index_col='PassengerId')
随后使用基础pandas数据检查方法
train_df.info() <class 'pandas.core.frame.DataFrame'> Int64Index: 891 entries, 1 to 891 Data columns (total 11 columns): Survived 891 non-null int64 Pclass 891 non-null int64 Name 891 non-null object Sex 891 non-null object Age 714 non-null float64 SibSp 891 non-null int64 Parch 891 non-null int64 Ticket 891 non-null object Fare 891 non-null float64 Cabin 204 non-null object Embarked 889 non-null object dtypes: float64(2), int64(4), object(5) memory usage: 83.5+ KB
综合统计(summary statistics)
每个observation行数据的列可以分为两类:数值类和类别类,我们可以分别从以下几个方面来关注:
数值类别
中值度量:(mean, meadian)
mean:简单对所有value的算术平均,可以说是数据的中点。
median是针对mean均值的改进,如果一个数据集中有几个非常巨大的异常数据,这时我们简单使用均值则不能反映数据的真实特征,但是如果我们先把这个数据集做一下排序,随后取这个排序序列的中间的值,则能有效剔除那些巨大或者巨小的数据带来的均值问题。也就是说如果变量的分布是skiew偏离值比较大,则非常适合使用median值来描述该variable
mean和median指标非常适合描述quantitative型变量
离散度量(dispersion):(range, percentile, variance, standard deviation)
离散(分散)的度量指标描述数据相对中心点的分散情况。
类似的range本身也会受到巨大或者巨小异常数据的干扰,我们可以使用percentile这个来度量。
percentitle: x percentile is y意味着:x%的值都小于y这个数值。比如 50 percentile是10可以这样解读:50%的值都小于10这个数据, 75 percentile 是12则解读为75%的值都小于12
反映percentitle指标常用Box-Whisker Plot来形象表达:
方差(variance)度量每个数值对均值的偏离程度,方差越小意味着数据分散性越小
方差这个指标同样会受巨大或者巨小数值的影响,同时由于方差是偏差的平方,其单位将失去比较的意义,因此更多时候我们使用标准偏差(standard deviation)这个指标,因为该指标可以清晰的看到数据的分散情况
# dispersion measures print('Min fare : {0}'.format(df.Fare.min())) # minimum print('Max fare : {0}'.format(df.Fare.max())) # maximum print('Fare range : {0}'.format(df.Fare.max() - df.Fare.min())) # range print('25 percentile : {0}'.format(df.Fare.quantile(.25))) # 25 percentile print('50 percentile : {0}'.format(df.Fare.quantile(.5))) # 50 percentile print('75 percentile : {0}'.format(df.Fare.quantile(.75))) # 75 percentile print('Variance fare : {0}'.format(df.Fare.var())) # variance print('Standard deviation fare : {0}'.format(df.Fare.std())) # standard deviation # 注意:如果数据集中有空值,则pandas对应的统计数据就会出现Nan值,这时我们就必须对这些数据处理 df.Fare.plot(kind='box') df.describe(include='all')
类别数据
总类别数(total count),唯一类别数(unique count),类别总数及其占比(category counts and proportion),每类别统计(per category stastics)。
mode: 发生频率最高的那个value,非常适合categorical特征的度量
数据分布特性(distribution)
单变量的分布图(univariate)可以使用直方图(Histogram), Kernel Density Estimation(KDE) plot来完美呈现
直方图histogram概念模型解释:
kde实际上将直方图中的频率换算成了对应的概率。相应地,后面我们也会引入概率分布,比如正态分布,skewness等概念
对于标准正态分布,没有任何skewness,其mean均值和median的值相等,大于均值和小于均值的数值均等分布。
如果median<mean则称为左偏分布,说明一定有巨大的值参杂其中或者较大的值占比比较高;
如果median>mean则称为右偏分布,说明一定有巨小的值参杂其中或者较小的值占比比较高
两个变量的(bivariate)分布图可以使用散点图Scatter plot, scatter plot非常适合描述两个feture之间的关联关系.例如你可能设想Age这个特征和Height这个特征应该具有一定的关系(pattern),我们就可以非常方便地通过这个散点图一眼看出他们的关系。
http://seaborn.pydata.org/tutorial/categorical.html#categorical-tutorial
变量之间的相关性:pearson's R
在特征工程中,如果两个特征之间具有线性相关性,那么不应该都应用到机器学习中,而应该剔除其中一个。研究特征之间线性相关性的指标为pearson's R.该指标可以指示是正相关,还是负相关以及相关的程度。其值在-1到1之间,0为不相关,1为最大正相关,-1为最大负相关。其计算方法:
其中
SDx和SDy是x和y两个feature的标准差
pearson R计算示意图:
数据的grouping
数据的grouping是指根据选择的feature,对row进行分组聚合,分类组合后,我们可以对每个分组应用不同的数字统计计算,比如mean,median,count等
交叉表格(crosstabs)
crosstab非常适合于探究category类型的数据,其按照crosstab的feature罗列出每个交叉类别对应的observation次数
Pivot
pivot table是对crosstable的自然延伸,和crosstable不同的是,他使用某个feature的值来代替crosstable中表示发生次数的数值。
df.pivot_table(index='Sex',columns='Pclass',values='Age',aggfunc='mean')
Data Munging
正如前面所述,在EDA阶段,我们只是对我们的数据集做了初步的探索,发现可能存在的问题,而在这个data munging阶段,我们则需要解决EDA阶段发现的数据问题。
- 缺失的数据项处理(value missing);
- 对巨大值巨小值的处理(extreme value/outliers);
- 错误数据处理(erroneous values)
value missing
产生的原因:数字确实未知,数据录入错误,设备错误(特别是物联网传感器数据产生了错误)
解决方案:
- 删除
- 数据补偿
- 使用mean均值(适合于数值型feature)
- 使用median均值(适合于数值型feature)
- 使用最高频的category值来填充(适合于category类型feature)
- 使用左邻或者右邻数据来填充(适合于category类型feature,并且数据是有序排列的)
- 使用某种预测模型来填充(比如线性模型)
处理过程:先使用df.info()获取missing的是哪些字段,使用df[df.xxx.isnull()]过滤列出那些observation,结合缺失数据已有的其他信息字段,来推断我们应该用什么统计信息来填写
df[df.Fare.isnull()] medianFare_in_embarkedS_pclass3 = df[(df.Pclass==3)&(df.Embarked=='S')].Fare.median() df.Fare.fillna(medianFare_in_embarkedS_pclass3, inplace=True)
df[df.Age.notnull()].boxplot('Age',['Pclass','Sex']) # 通过boxplot罗列出Age和Pclass, Sex组合下各种分类的Age分布情况,如果发现分布差异巨大,则 # 非常适合选择这些特征作为辅助项目。相反,如果发现groupby分布差异不大,则最好不要使用该groupby的median来做填充!!
从上面的图中,我们可以看到Age的分布针对Sex和Pclass来groupby的话,其值具有明显的差异性,因此非常适合我们就使用Pclass+Sex对应的Age median值来做缺值的填充
outlier值的处理
outlier是指明显远远大于或者远远小于正常均值的值,这些outlier在EDA阶段会产生Biased analysis,比如mean, ,range, deviation都受到巨大值的巨大影响。而如果这些outlier值用于训练模型并产生预测,则会产生Biased model。
但是outlier也往往会携带非常有意义的信息,给我们以重要的启示,因此不能一概而论,outlier存在于数据集就是不好的。
outlier值的检测:
单变量的hitogram
单变量的Boxplot
两个变量组合时可以使用scatter plot
outlier值处理:
- 删除
- 变换transformation,比如log
- 分段binning
- imputation: 替换为更有意义的值(需要小心,因为outlier值可能携带重要信息,你一旦替换可能丢失)
Feature Engineering(Domain knowledge + Technical Expertise):
特征工程(feature engineering)是而对原始数据进行变换以便更能表征特征pattern(better representative)从而能够创建更好预测模型的流程
- 变换(transformation)
- 再生新feature(依赖于领域专家知识和经验)
- 类别型feature的编码:
类别型feature的编码
由于机器学习只能使用数值型变量,因此对于category类型的特征feature我们必须编码成对应的数字型数据,否则无法进入机器学习。一般地,常见的编码方法有:
binary encoding
非常适合于只有两个类别值的category特征编码,比如性别:男和女,中国人和外国人。。这种直接用is_male, is_chinese其值编码为1,0
label encoding
非常适合于有多个类别值,同时其类别有序列意义的category feature编码,比如收入:底,中,高,成绩:不及格,及格,良好,优秀,满分。这种类型的数据本身直接用0,1,2,3,来编码其类别值是有意义的。
one-hot encoding
如果待编码的类别数据本身没有序列意义,则最安全的编码方式就是这种one-hot编码方式了,其根据cate类别值分别创建is_catea,is_cateb,is_catec等。
advanced data visulization
matplotlib相比于pandas可视化有更强大的功能,比如支持subplot,一个visulization同时展示多张图片
机器学习模型训练
有了数据后,我们就可以根据问题类型预先设定一个模型,比如logistic regression, SVM,神经网络,将这些数据应用到学习算法中,找到能够较好拟合这些数据的模型。
这时我们就有了一个trained model,再将test data输入到这个已训练模型中,根据预测值和实际值的差异得出评价指标(比如逻辑回归的正确率),如果不满意,则可以通过调整对应模型的超参数继续重新训练得到一个较好结果的模型。或者我们重新选择模型训练得到一个完全重构的已训练模型,直到对模型预测指标满意为止。
一般的,我们在应用数据创建训练模型之前我们可以先设定一个基础模型,该模型的输出对于分类器来说,我们就选择那个最高出现频率的类别,比如对于泰坦尼克号存活率如果大部分都是不能幸存,比如60%,那么我们就将基础模型设定输出为不能幸存。那么如果我们新训练出来的模型准确率小于 60%,那么就表明我们的模型是完全失败的,预测模型是没有任何意义的!!!
还有一点需要注意的是对于逻辑回归,我们要检视一下0和1输出的占比,如果严重失衡的话,我们有必要处理(im-balanced classifications)
https://www.analyticsvidhya.com/blog/2016/03/practical-guide-deal-imbalanced-classification-problems/
import pandas as pd import numpy as np import os processed_data_path = os.path.join(os.path.curdir,'data','processed') train_file_path = os.path.join(processed_data_path,'train.csv') test_file_path = os.path.join(processed_data_path,'test.csv') train_df = pd.read_csv(train_file_path,index_col='PassengerId') test_df = pd.read_csv(test_file_path,index_col='PassengerId') X = train_df.loc[:, 'Age':].as_matrix().astype('float') y = train_df['Survived'].ravel() from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2,random_state=0)
下面我们创建一个dummy model用作baseline model:
import sklearn from sklearn.dummy import DummyClassifier #create dummy model model_dummy = DummyClassifier(strategy='most_frequent',random_state=0) #train dummy model model_dummy.fit(X_train,y_train) model_dummy.get_params() print('score for dummy classifier is: {0:.2f}'.format(model_dummy.score(X_test,y_test))) # 0.61
logistic model
逻辑回归模型可以简单看作是线性回归模型应用在sigmoid函数后实现非线性后形成的model,输出值为概率值0到1
我们来看看Logistic model对应的代码
from sklearn.linear_model import LogisticRegression # create model model_lr_1 = LogisticRegression(random_state=0) # train the model model_lr_1.fit(X_train,y_train) # evaluate model print('score for logistic regression -version 1: {0:.2f}'.format(model_lr_1.score(X_test,y_test))) # 0.83 model_lr_1.intercept_ # array([1.10324257]) 截距 model_lr_1.coef_ # 对应每个特征的系数 ''' array([[-0.02840734, 0.00455631, -0.50017007, 0.61922839, -0.81414743, 0.12823264, -0.17253859, -0.39355489, 0.52215008, 1.09939125, 0.40346551, -0.18369316, -0.30021028, 0.37253571, 0.73070686, 0.16297325, 0.2474635 , 0.27998253, 0.41282329, 0.28258585, 1.21850069, 0.56334182, -1.44612508, 1.07146232, -0.11345497, -0.47306807, 0.49202885, 0.46214499, 0.14906873, 0.96558544, 0.48281793, -0.3451608 ]]) '''
分类器效果评估(precision, recall,accuracy)
Precision:查准率(预测为1中,真值为1的比例(概率)) $Precision = \frac{TP}{TP+FP} = P(y_i=1|\widehat{y_i}=1)$
Recall:查全率(真值为1中,预测值也为1的比例(概率))$Recall = \frac{TP}{TP+FN} = P(\widehat{y_i}=1|y_i=1)$
Precision和Recall都是针对Positive事件的,往往是我们感兴趣的事件,相对地,我们也可以取Negative事件的指标。
总准确率: (所有预测正确的比例(含1,0))$accuracy = \frac{TP+TN}{TP+TN+FP+FN} = P(\widehat{y_i}==y_i)$
模型调优(model tuning)
在通过sklearn logistic regression model训练后我们得到第一版trained model,效果还不错达到83%的准确率。下面我们看看还有哪些可以继续优化的地方来不断优化模型,有一个更好的呈现效果。
underfitting vs overfitting
underfitting(欠拟合):模型对训练集的拟合不足,也就是说即使是训练集,模型本身都不能准确预测
overfitting(过拟合):模型过于复杂,完全是基于training data的拟合,无法泛化
regularization
我们知道模型如果过于复杂容易产生过拟合,如果太简单容易产生欠拟合,针对逻辑回归模型我们有一些参数来调整模型的复杂度等参数。
比如C参数表征复杂度,数字越大越复杂,penalty是正规惩罚项。除了逻辑回归Model,越复杂的模型类别,其超参数越多。数据科学家很重要的一个
工作是寻求一组最优化的超参数组合,使得model预测效果最佳。
hyperparameter tuning
超参数优化的基本思路是将参数组合做成表格,分别针对这些不同的组合来训练模型给出性能指标,后面我们选择一个最佳的指标来。
cross validation
样例代码:
model_lr_base = LogisticRegression(random_state=0) from sklearn.model_selection import GridSearchCV hparameters = {'C':[1.0,10.0,50.0,100.0,1000.0],'penalty':['l1','l2']} clf = GridSearchCV(model_lr_base,param_grid=hparameters,cv=3) # cv表示3 K FOLD CROSS VALIDATION clf.fit(X_train,y_train) clf.best_params_ # {'C': 1.0, 'penalty': 'l1'}
Feature normalization and Feature Standadization(特征正规化)
如果输入特征数据的range变化很大,很有可能对训练出来的模型产生偏离效果,我们最好在训练模型之前将输入数据正规化处理,使得所有feature都在0到1的范围内,并且具有正态分布的特征(期望为0,方差为1)
更换其他类型的逻辑回归
几个概念:centered, standardized,normalized:
model_lr_base = LogisticRegression(random_state=0) hparameters = {'C':[1.0,10.0,50.0,100.0,1000.0],'penalty':['l1','l2']} clf = GridSearchCV(model_lr_base,param_grid=hparameters,cv=3) # cv表示3 K FOLD CROSS VALIDATION clf.fit(X_train_scaled,y_train) clf.best_params_
实验结果表明正规化参数后并未对预测结果产生明显的改进,反而有点下降
模型持久化和API
model api要實現的功能是:接收raw data,并且处理他,做出预测,并且返回json
Feature normalization
机器学习实战中遇到的问题解决方案
Traceback (most recent call last): File "C:/Users/Administrator/devenvironment/Code/intro_ds/ch04-linear/simple_example/linear_stat.py", line 14, in <module> import statsmodels.api as sm File "C:\Users\Administrator\Anaconda3\lib\site-packages\statsmodels\api.py", line 5, in <module> from . import regression File "C:\Users\Administrator\Anaconda3\lib\site-packages\statsmodels\regression\__init__.py", line 1, in <module> from .linear_model import yule_walker File "C:\Users\Administrator\Anaconda3\lib\site-packages\statsmodels\regression\linear_model.py", line 43, in <module> from scipy.stats.stats import ss
ImportError: cannot import name 'ss'
ImportError: No module named 'tensorflow'
解決方案:
conda install tensorflow
SKLearn算法库选择指南
https://scikit-learn.org/stable/tutorial/machine_learning_map/index.html
conda upgrade statsmodels/pip install statsmodels --upgrade