机器学习(二)——端到端的机器学习项目

不离不弃

芳龄永继

概述

第二章假设我们将亲自实践一个端到端的机器学习项目,体会真正的机器学习工程项目的过程。


实验流程

实验数据

StatLib 库中选择了加州住房价格的数据集,该数据集基于1990年加州人口普查的数据。数据集下载地址:Dataset

image

下载并预览数据

下载并解压 housing.tgz

image

预览数据
image

每一行代表一个区域,每个区域有10个相关属性: longitude, latitude, housing_median_age, total_rooms, total_bed rooms, population, households, median_income, median_house_value, ocean_proximity

读取并初步分析数据

  1. 读取数据
  2. 查看数据结构和描述
    image

    可知大部分属性是float64类型的,ocean_proximity则是object,该数据集中包含20640个实例,total_bedrooms只有20433个实例,即207个区域缺失了这个特征
  3. 查看数据基本情况
    由上一步可知,ocean_proximity则是object,预览数据时可知,其是文本类型数据,查看其存在多少种分类
    image

    可知其存在5种分类
    image

    绘制各属性的频数分布直方图观察数据分布情况
    image

    发现房价、房龄两个属性被设置了上限,大量超过限制的实例被压缩到末尾区间内,以及部分属性重尾(右侧延伸大于其左侧延伸),之后需要注意这些问题
import pandas as pd
import matplotlib.pyplot as plt

def load_housing_data(housing_path):
    return pd.read_csv(housing_path)

if __name__ == '__main__':
    housing = load_housing_data('housing.csv')
    housing.head()
    housing.info()
    housing['ocean_proximity'].value_counts()
    housing.describe()

    housing.hist(bins=50, figsize=(20,15))
    plt.savefig('housing_distribution.png')

创建测试集

选取数据集的20%作为测试集,首先我们为排除人为影响,随机选取数据的20%作为测试集,其分布如下

image

思索一下,发现问题似乎没有这么简单,随机抽样固然排除了人为因素,但是容易造成抽样偏差,假设一个地区有70%的男生,30%的女生,完全随机抽样有可能全部抽出男生,那么此时测试集就不能代表整体了。因而我们为保证测试集具有代表性,会选择分层抽样。此例中我们按照地区收入中位数进行分层抽样:

  1. 首先观察收入中位数的分布,发现大部分人处于2~3w左右
  2. 收入数据为连续值,我们四舍五入对其取整,将其离散化
  3. 为减少离散值类数目,将大于5w的部分归于5w类目中
  4. 按此比例对原数据进行分层抽样
    image
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

housing['income_cat'] = np.ceil(housing['median_income'] / 1.5)
housing['income_cat'].where(housing['income_cat'] < 5, 5.0, inplace=True)

spliter = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_index, test_index in spliter.split(housing,housing['income_cat']):
    strat_train_set = housing.loc[train_index]
    strat_test_set = housing.loc[test_index]
for data_set in (strat_train_set, strat_test_set):
    data_set.drop(['income_cat'], axis=1 ,inplace=True)

PS:测试集的构建很重要,却往往被忽视,因此机器学习项目中需要注意

数据探索和可视化

首先将测试集放在一边,我们只能够在训练集上进行数据探索(如果数据量比较大,可以先提取出一个较小的探索集),这样才不会损坏训练集。

  1. 将地理数据可视化

    image

    可见与加州地图轮廓类似,但也看不出什么了,因而改变alpha参数,观察实例分布密度
    image

    可见有明显的高密度区域,就是湾区、洛杉矶等地。

  2. 将人口、房价信息可视化

    image

    图中每个圆的半径代表人口数量,颜色代表房价高低(蓝色为低,红色为高),可见人口与房价的关系。

  3. 寻找特征之间的相关性
    查看各属性与房价之间的皮尔森相关性系数

    image

    相关性从-1~1。越接近1,就有越强的正相关;越接近-1,就有越强的负相关。从图中可知,一个地区收入中位数越高,房价越高;而越往北,房价越低。而与房价相关性最高的特征是收入。

  4. 绘制收入、房价散点图

    image

    二者相关性的确很强,但是50w以上的部分是一条直线,因为我们将50w以上的实例全部归类于50w这一类了。仔细观察,发现45w、35w、23w附近貌似也有这样的直线,值得注意。

  5. 尝试不同属性的组合
    比如目前知道每个地区的房间数,但并没有什么用,如果知道了每个家庭的房间数,可能就大大不同了,因此可以对属性进行组合。

    image

    发现每个家庭的房间数、每个房间的卧室数与房价也很有相关性

    housing = strat_train_set.copy()
    housing.plot(kind='scatter', x='longitude', y='latitude')
    plt.savefig('gregrophy.png')
    housing.plot(kind='scatter', x='longitude', y='latitude', alpha=0.1)
    plt.savefig('gregrophy_more.png')
    
    fig = plt.scatter(x=housing['longitude'], y=housing['latitude'], alpha=0.4, \
        s=housing['population']/100, label='population', \
        c=housing['median_house_value'], cmap=plt.get_cmap('jet'))
    plt.colorbar(fig)
    plt.legend()
    plt.savefig('gregrophy_population_value.png')
    
    corr_matrix = housing.corr()
    corr_matrix['median_house_value'].sort_values(ascending=False)
    
    housing.plot(kind='scatter', x='median_income', y='median_house_value', alpha=0.1)
    plt.savefig('income_value.png')
    
    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']
    corr_matrix = housing.corr()
    corr_matrix['median_house_value'].sort_values(ascending=False)
    

数据准备

  1. 准备干净的数据集,将预测器与标签分开

  2. 数据清洗

    • 放弃这些相应的地区
    • 放弃这个属性
    • 将缺失的值设为某个值(0、平均数或中位数等)
      我选择用中位数填补缺失值
      image

      填补之后,可见数据集已经没有缺失部分了
    housing = strat_train_set.drop('median_house_value', axis=1)
    housing_labels = strat_train_set['median_house_value'].copy()
    
    imputer = Imputer(strategy='median')
    housing_num = housing.drop('ocean_proximity', axis=1)
    imputer.fit(housing_num)
    imputer.statistics_
    X = imputer.transform(housing_num)
    housing_tr = pd.DataFrame(X, columns=housing_num.columns)
    
  3. 处理文本与分类属性
    之前ocean_proximity为文本属性,无法进行数值计算,因此我们需要将其转化为数字,但是此时算法会将其理解为纯数值进行运算,这是不对的,我们的原始数据是分类数据,是类别,不能用于加减乘除等运算,因此我们需要将其转换成独热编码。

    image

    encoder = LabelEncoder()
    housing_cat = housing['ocean_proximity']
    housing_cat_encoded = encoder.fit_transform(housing_cat)
    
    encoder = OneHotEncoder()
    housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1))
    
    encoder = LabelBinarizer()
    housing_cat_1hot = encoder.fit_transform(housing_cat)
    
  4. 自定义转换器
    之前讨论过组合属性形成新的属性,我们也希望可以引入新的属性,但是又希望这个过程可以与Scikit-Learn的流水线无缝衔接,因此可以自定义转换器。所需要的只是新创建一个类,应用fit()、transform()、fit_transform()就可以了。如果添加TransformerMixin作为基类,可以直接获得最后一个方法;添加BaseEstimator作为基类,则可以获得两个自动调整超参数的方法get_params()和set_params()。
    增加前:

    image

    增加后:
    image

    新增加了每间房房间数量和每户家庭人口数两个组合属性

    class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    """docstring for CombinedAttributesAdder"""
    def __init__(self, add_bedrooms_per_room=True):
        self.add_bedrooms_per_room = add_bedrooms_per_room
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        rooms_per_household = X[:,rooms_ix] / X[:,household_ix]
        population_per_household = X[:,population_ix] / X[:,household_ix]
        if self.add_bedrooms_per_room:
            bedrooms_per_room = X[:,bedrooms_ix] / X[:,rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household, bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]
    
  5. 转换流水线
    许多数据转换需要正确的步骤来运行,因此需要设置流水线工作。我仿照书上写这段代码时,发现由于版本更新,这里失效了:

    image

    经检查是LabelBinarizer更新后只接收两个参数,因此重写了一个自己的MyLabelBinarizer

    class MyLabelBinarizer(BaseEstimator, TransformerMixin):
    """docstring for DataFrameSelector"""
    def __init__(self):
        self.encoder = LabelBinarizer()
    def fit(self, X, y=None):
        return encoder.fit(X)
    def transform(self, X, y=None):
        return encoder.transform(X)
    

    image

    准备数据成功

选择和训练模型

  1. 培训和评估训练集
    使用线性回归模型,看看预测情况如何

    #train the model
    lin_reg = LinearRegression()
    lin_reg.fit(housing_prepared, housing_labels)
    some_data = housing.iloc[:5]
    some_labels = housing_labels.iloc[:5]
    some_data_prepared = full_pipeline.transform(some_data)
    print("Predictions: ", lin_reg.predict(some_data_prepared))
    print("Labels: ", list(some_labels))
    housing_predictions = lin_reg.predict(housing_prepared)
    lin_mse = mean_squared_error(housing_labels, housing_predictions)
    lin_rmse = np.sqrt(lin_mse)
    

    image

    部分预测结果,效果不尽如人意,利用RMSE进行评估
    image

    效果确实不行!

    使用决策树模型,看看情况如何

tree_reg = DecisionTreeRegressor()
tree_reg.fit(housing_prepared,housing_labels)
housing_predictions = tree_reg.predict(housing_prepared)
tree_mse = mean_squared_error(housing_labels, housing_predictions)
tree_rmse = np.sqrt(tree_mse)
```

image

情况喜人!!但是转念一想,100%正确,这不是过拟合了吧?进行模型验证评估!!

  1. 验证模型
    选择交叉验证方法验证模型

    image

    效果真的烂,保险起见,检查一下线性回归模型
    image

    也很差劲,换模型!!

    #valid models
    tree_scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10)
    tree_rmse_scores = np.sqrt(-tree_scores)
    display_scores(tree_rmse_scores)
    
    lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring='neg_mean_squared_error', cv=10)
    lin_rmse_scores = np.sqrt(-lin_scores)
    display_scores(lin_rmse_scores)
    
  2. 使用随机森林

    image

    情况烂到家了,理论上来说,不应该,选其他模型或者调参数

  3. 调参
    利用网格搜索使用不同参数组合,找出最好参数结果

    image

    查看整体情况
    image

    此时随机森林的最佳参数是max_features为6,n_estimators为30时,RMSE=50019

    #improve the models
    param_grid = [
        {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]},
        {'bootstrap': [False], 'n_estimators': [3, 10], 'max_features': [2, 3, 4]}
    ]
    
    forest_reg = RandomForestRegressor()
    grid_search = GridSearchCV(forest_reg, param_grid, cv=5, scoring='neg_mean_squared_error')
    grid_search.fit(housing_prepared,housing_labels)
    grid_search.best_params_
    cvres = grid_search.cv_results_
    for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
        print(np.sqrt(-mean_score), params)
    
  4. 分析最佳模型及其错误
    查看每个特征的重要性

    image

    image

    可见收入是最重要的特征

    #analyse the model
    feature_importances = grid_search.best_estimator_.feature_importances_
    extra_attribs = ['rooms_per_hhold', 'pop_per_hhold', 'bedrooms_per_room']
    cat_one_hot_attribs = list(encoder.classes_)
    attributes = num_attribs + extra_attribs + cat_one_hot_attribs
    sorted(zip(feature_importances,attributes),reverse=True)
    
  5. 通过测试集评估系统

    image

    最终误差可以接受

    #final model
    final_model = grid_search.best_estimator_
    
    X_test = strat_test_set.drop('median_house_value', axis=1)
    y_teat = strat_test_set['median_house_value'].copy()
    X_test_prepared = full_pipeline.fit_transform(X_test)
    final_predictons = final_model.predict(X_test_prepared)
    final_mse = mean_squared_error(y_teat, final_predictons)
    final_rmse = np.sqrt(final_mse)
    
  6. 启动、监控和维护系统


后记

性能指标

回归问题的典型性能衡量指标是均方根误差(RMSE),它测量的是预测过程中预测错误的标准偏差。

image

当数据离群点很多时,可以考虑使用平均绝对误差(MAE
image

特征缩放

如果输入的数据有较大的比例差异,可以考虑对特征数值进行缩放,通常有两种方法:最大-最小缩放标准化
最大-最小缩放:归一化,将数值缩放到0~1之间,实现方法是将值减去最小值并除以最大值和最小值的差
标准化:首先减去平均值,再除以方差

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容