想要一直做一个完整的机器学习工程已经好久了,这篇博客呢,我会用到决策树,随机森林,svm,xgboost,投票法等方法。对波士顿的房价进行预测。本篇博客不会对相关原理进行解释,如果想要了解各个算法的原理,请阅读李航的统计机器学习,周志华的机器学习,以及陈天奇的XGBoost: A Scalable Tree Boosting System 和相关博客
部分代码参考利用python进行数据分析
其实在研一上数据挖掘课程的时候,老师就强烈推荐了sklearn这个包,当时数理知识还不是很强,学的算法也不是很明白,面对着1000多页的sklearn文档实在是令人望而生畏。这几天重新捡起来看了一点,发现sklearn的设计者们实在是太牛了!!不过还是期待市面上早日出现一本关于sklearn的书,如果能像利用python进行数据那本书写的一样好就可以了。
建议如果大家学了传统的机器学习算法之后,强烈推荐看看sklearn的文档,还有一些相关的重要参数。即使调包我们也要调的漂亮。嘿嘿。。。
在个人对 sklearn 的api的学习过程中,我觉得很有意义也是最漂亮的就是sklearn中的就是他的estimator,predictor,transform这三个接口了,结合pipeline 还有featureUnion 简直无敌。进行数据预处理的时候,管道设计实在是太人性化了。
sklearn 关于pipeline的应用的官方实例是给了一个20新闻集团语料库的例子,这个源代码对于初学者肯定特别不友好,所以我另外找了一个关于波士顿房价数据集的作为说明。我这也相当于又复习了一遍。(声明:这个完整机器学习项目并非我的原创,是我从github上下载的一个英文实例项目,但它做的比较浅,所以数据预处理部分的转换器我又重新设计了一下,而且没有进行调参和用集成学习的方法去做,我准备在后面加一些关于自己的esemble的东西,并顺变做一些ROC,F1-score,调参方法的测试等内容)
做这个的时候不由想到之前京东金融的面试,问我会选取哪些特征变量作为双十一预测客户是否会购买很多的特征变量。当时是在是太紧张了,就答了一个消费等级、消费金额。也有一部分原因是特征工程做的太少的原因,不管怎样,要一直练习下去呀。
突然悟到:
在找特征的过程中我们应该从时间特征(时间段、时间点)、地域特征(是否市区,人口密度)、身份(是否学生,消费能力等级,年龄层次)等等。
首先二话不说先送上下载数据的代码:
import os
import tarfile
from six.moves import urllib
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = os.path.join("datasets", "housing")
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/housing.tgz"
def fetch_data(housing_url = HOUSING_URL,housing_path = HOUSING_PATH):
if not os.path.isdir(housing_path):
os.makedirs(housing_path)
tgz_path = os.path.join(housing_path,"housing_tgz")
urllib.request.urlretrieve(housing_url,tgz_path)
housing_tgz = tarfile.open(tgz_path)
housing_tgz.extractall(path=housing_path)
housing_tgz.close()
os,tarfile的含义很容易理解。urllib其实也是做爬虫的时候经常用到的一个库。含义很容易理解在此就不做额外说明了。
Step2 读取数据
fetch_housing_data()
import pandas as pd
def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)
housing = load_housing_data()
housing.head()
我们由此可以得到一些关于数据集前6行的信息,有一些经度纬度,人口,房屋总数的数据,收入中位数的数据,我们要预测的是房价中位数。
这里提供了俩种方法,一种是用numpy的permutation方法
import numpy as np
# For illustration only. Sklearn has train_test_split()
def split_train_test(data, test_ratio):
shuffled_indices = np.random.permutation(len(data))
test_set_size = int(len(data) * test_ratio)
test_indices = shuffled_indices[:test_set_size]
train_indices = shuffled_indices[test_set_size:]
return data.iloc[train_indices], data.iloc[test_indices]
通常我们用来做batch 批量训练的时候也用到这个方法,如下
def batch_data(X,y,batchsize):
rnd_idx = np.random.permutation(len(X))
n_batches = len(X)//batchsize
for batch_idx in np.array_split(rnd_idx,n_batches):
yield X[batch_idx],y[batch_idx]
另一个直接调用sklearn的包
#注意! 请体会下面俩个train_test_split 切分结果的不同
from sklearn.model_selection import train_test_split
X_train, X_test = train_test_split(housing, test_size=0.2, random_state=42)
X_train,y_train,X_text,y_test = train_test_split(data,target,test_size= 0.2,random_state=44)
当然我们有时候用这样的采样方式是不合理的,比如对于1000个样本中 1 类的数据有500个占了50%,2类 3类的数据各有250个占据25%那么我们这个时候不能随便选20%个数据,应该采用分层采样的方式,去选数据sklearn就提供了这样的一个方法
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in split.split(housing, housing["income_cat"]):
strat_train_set = housing.loc[train_index]
strat_test_set = housing.loc[test_index]
这里的n_splits非常重要!n_split =1表示只划分出一个train 和一个test
.1其产生指定数量的独立的train/test数据集划分数据集划分成n组。
2.首先将样本随机打乱,然后根据设置参数划分出train/test对。
3.其创建的每一组划分将保证每组类比比例相同。即第一组训练数据类别比例为2:1,则后面每组类别都满足这个比例
光靠api 调用肯定不行!我自己也写了一个,如果是dataframe 类型 大概就用series的value_count方法吧,写的好丑。。。
import pandas as pd
x_train = np.array([])
x_test = np.array([])
counts = housing['income'].value_counts()
nums = counts.values
for i in counts.columns:
x_init = housing[housing['income'].isin([i])].values
x1,x2 = split_train_test(x_init,test_size = 0.2)
x_train = np.vstack(x_train,x1)
x_test = np.vstack(x_test,x2)
columnlist = housing.columns.values
X_train= pd.DataFrame(x_train,columns=columnlist)
我们直接调用housing.hist()方法可以看到各个变量的直方图,用Series 的value_count 来进行统计分析
现在呢,假设有专家告诉你房屋的价格和房主的收入有很大关系,那么你就不妨调用housing["median_income"].hist()的方法
看一些收入的分布情况
如图大多数房主的收入中位数的值聚集在 2-5(万美元),但是一些收入中位数会超过 6。数据集中的每个分层都要有足够的实例位于你的数据中,这点很重要。否则,对分层重要性的评估就会有偏差。这意味着,你不能有过多的分层,且每个分层都要足够大。后面的代码通过将收入中位数除以 1.5(以限制收入分类的数量),创建了一个收入类别属性,用ceil
对值舍入(以产生离散的分类),然后将所有大于 5的分类归入到分类 5:代码如下
housing["incat"] = np.ceil(housing['median_income']/1.5)
housing['incat'].where(housing['incat']>5,5,inplace =True)
现在,就可以根据收入分类,进行分层采样。你可以使用之前我提到的 Scikit-Learn 的StratifiedShuffleSplit
类:
#进行分层采样
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1,test_size=0.2,random_state=42)
for trainidx ,testidx in split.split(housing,housing['income_cat']):
strat_train_set = housing.loc[trainidx]
strat_test_set = housing.loc[testidx]
strat_train_set['income_cat'].value_counts()/len(strat_train_set)
左图是总体类别,右图是分层抽样的结果可以看出,分层抽样还是有效的
我查了一下官方文档,大概有交叉验证等好几种分离器,这些东西,一时半会也记不住,得等用到的时候现查,或者自己写函数也可以了。
在我们做特征工程的过程中,数据的地理信息、时间信息往往都特别重要。那么怎样来绘制合适图表来发现数据的内在联系呢?
首先根据经度纬度 画一画散点图
housing = strat_train_set.copy()
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
啊哈!好像看不出来啥尴尬,那我们再结合人口看一看?
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.4,
s=housing["population"]/100, label="population",
c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,
)
plt.legend()
plt.show()
如上图,每个圈的半径表示街区的人口(选项s
),颜色代表价格(选项c
)。我们用预先定义的名为jet
的颜色图(选项cmap
),它的范围是从蓝色(低价)到红色(高价)这张图说明房价和位置(比如,靠海)和人口密度联系密切,这点你可能早就知道。可以使用聚类算法来检测主要的聚集,用一个新的特征值测量聚集中心的距离。
可以直接调用
corr_matrix = housing.corr()
corr_matrix
corr_matrix['median_house_value'].sort_values(ascending=False)
可以看到返回的还是一个dataframe类型如果需要对一个特定变量进行排序,那么我们可以使用sort_values 方法
#根据我们的直觉,可能觉得房价和房屋的平均人口,平均卧室数目,平均房间数目有关
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]
首先查看数据有没有缺失值
sample_incompletedata = housing[housing.isnull().any(axis=1)]
sample_incompletedata.head()
可以发现total_bedroom存在缺失值,我们可以用fillna 方法进行填充,也可以用sklearn 中的Imputer 方法进行填充
方法一:
median_housing_value = housing['total_bedrooms'].median()
sample_incompletedata['total_bedrooms'].fillna(median_housing_value,inplace=True)
方法二:用sklearn中的imputer方法,对数据进行填充这个方法的好处是,可以对所有的数值型数据进行填充,除此之外还可以用到最后的Pipeline方法
from sklearn.preprocessing import Imputer
imputer = Imputer(strategy="median")
housing_num = housing.drop(['ocean_proximity'],axis=1)
imputer.fit(housing_num)
X = imputer.transform(housing_num)
housing_tr = pd.DataFrame(X,columns=housing_num.columns,index=housing_num.index)
更多关于数据预处理的部分,可以通过查询sklearn的preprocessing模块比如如何标准化,如何自定义转换器
链接为http://sklearn.apachecn.org/cn/0.19.0/modules/preprocessing.html#imputation
对于一些特征值是字符串的数据可以用label_encode 的方式,但是我不建议变量X用这种方式,因为X当中有很多变量,这种编码的顺序可能和原先的index不太一样,对于y可以直接这样
from sklearn.preprocessing import LabelEncoder
ord_encode =LabelEncoder()
housing_cat_encoded= ord_encode.fit_transform(housing_cat)
housing_cat_encoded
一般我都是自己写一个字典建立一一映射的关系,一般是在数据比较小的情况下,如果数据量大,占内存较大,可以试试用apply方法,用生成器一个个生成
cat_counts = housing_cat['ocean_proximity'].value_counts()
listcat = list(cat_counts.index)
dictcat ={}
for index,value in enumerate(listcat):
dictcat[value] = index
housing_cat['encode'] = housing_cat['ocean_proximity'].map(dictcat)
#当然这样进行数据预处理,没有fit_transform方法就很难用管道Pipeline了
在此我们可以自定义一个转换器,来进行,这里就是要继承我们刚开始前面所说的fit transformation类了,还是添加3个特征变量
from sklearn.base import BaseEstimator,TransformerMixin
room_ix,bedrooms_ix,populaton_ix,households_ix = 3,4,5,6
class CombineAttr(BaseEstimator,TransformerMixin):
def __init__(self,add_bedroom_per_room):
self.add_bedroom_per_room = add_bedroom_per_room
def fit(self,X,y=None):
return self
def transform(self,X,y=None):
room_per_household = X[:,room_ix]/X[:,households_ix]
populaton_per_household = X[:,populaton_ix]/X[:,households_ix]
if self.add_bedroom_per_room:
bedrooms_per_room = X[:,bedrooms_ix]/X[:,room_ix]
return np.c_[X,room_per_household,populaton_per_household,bedrooms_per_room]
else:
return np.c_[X,room_per_household,populaton_per_household]
attr_addr = CombineAttr(add_bedroom_per_room=False)
housing_extra_attribs = attr_addr.transform(housing.values)
housing_extra_attribs = pd.DataFrame(housing_extra_attribs,columns=list(housing.columns)+["room_per_household","population_per_household"])
housing_extra_attribs.head()
调用Pipeline 结果
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', Imputer(strategy="median")),
('attribs_adder', CombineAttr()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
对类别变量也进行pipeline 然后进行featureunlion
#1.建立选择变量的转换器
from sklearn.base import BaseEstimator, TransformerMixin
class DataFrameSelector(BaseEstimator, TransformerMixin):
def __init__(self, attribute_names):
self.attribute_names = attribute_names
def fit(self, X, y=None):
return self
def transform(self, X):
return X[self.attribute_names].values
from sklearn.pipeline import FeatureUnion
from sklearn.preprocessing import LabelBinarizer
num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]
#建立类别变量的转换器
class Onehot(BaseEstimator, TransformerMixin):
def fit(self,X,y=None):
return self
def transform(self,X,y=None):
X =X.reshape(-1)
catnums = len(set(X))
catlist =list(set(X))
zero_mat = np.zeros((len(X),catnums))
dummies = pd.DataFrame(zero_mat,columns=np.array(catlist))
def get_indice(label):
return catlist.index(label)
for i, label in enumerate(list(X)):
indices =get_indice(label)
dummies.iloc[i,indices] =1
return dummies.values
num_pipeline = Pipeline([
('selector', DataFrameSelector(num_attribs)),
('imputer', Imputer(strategy="median")),
('attribs_adder', CombineAttr()),
('std_scaler', StandardScaler()),
])
cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('cat_encoder', Onehot()),
])
full_pipeline = FeatureUnion(transformer_list=[
("num_pipeline", num_pipeline),
("cat_pipeline", cat_pipeline),
])
# 这样我们的数据预处理部分就全部结束了!
下一篇就是我们正式调用算法的步骤了