不离不弃
芳龄永继
概述
第二章假设我们将亲自实践一个端到端的机器学习项目,体会真正的机器学习工程项目的过程。
实验流程
实验数据
从 StatLib 库中选择了加州住房价格的数据集,该数据集基于1990年加州人口普查的数据。数据集下载地址:Dataset
下载并预览数据
下载并解压 housing.tgz
预览数据
每一行代表一个区域,每个区域有10个相关属性: longitude, latitude, housing_median_age, total_rooms, total_bed rooms, population, households, median_income, median_house_value, ocean_proximity
读取并初步分析数据
- 读取数据
- 查看数据结构和描述
可知大部分属性是float64类型的,ocean_proximity则是object,该数据集中包含20640个实例,total_bedrooms只有20433个实例,即207个区域缺失了这个特征 - 查看数据基本情况
由上一步可知,ocean_proximity则是object,预览数据时可知,其是文本类型数据,查看其存在多少种分类
可知其存在5种分类
绘制各属性的频数分布直方图观察数据分布情况
发现房价、房龄两个属性被设置了上限,大量超过限制的实例被压缩到末尾区间内,以及部分属性重尾(右侧延伸大于其左侧延伸),之后需要注意这些问题
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%作为测试集,其分布如下
思索一下,发现问题似乎没有这么简单,随机抽样固然排除了人为因素,但是容易造成抽样偏差,假设一个地区有70%的男生,30%的女生,完全随机抽样有可能全部抽出男生,那么此时测试集就不能代表整体了。因而我们为保证测试集具有代表性,会选择分层抽样。此例中我们按照地区收入中位数进行分层抽样:
- 首先观察收入中位数的分布,发现大部分人处于2~3w左右
- 收入数据为连续值,我们四舍五入对其取整,将其离散化
- 为减少离散值类数目,将大于5w的部分归于5w类目中
- 按此比例对原数据进行分层抽样
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:测试集的构建很重要,却往往被忽视,因此机器学习项目中需要注意
数据探索和可视化
首先将测试集放在一边,我们只能够在训练集上进行数据探索(如果数据量比较大,可以先提取出一个较小的探索集),这样才不会损坏训练集。
- 将地理数据可视化
可见与加州地图轮廓类似,但也看不出什么了,因而改变alpha参数,观察实例分布密度
可见有明显的高密度区域,就是湾区、洛杉矶等地。 - 将人口、房价信息可视化
图中每个圆的半径代表人口数量,颜色代表房价高低(蓝色为低,红色为高),可见人口与房价的关系。 - 寻找特征之间的相关性
查看各属性与房价之间的皮尔森相关性系数
相关性从-1~1。越接近1,就有越强的正相关;越接近-1,就有越强的负相关。从图中可知,一个地区收入中位数越高,房价越高;而越往北,房价越低。而与房价相关性最高的特征是收入。 - 绘制收入、房价散点图
二者相关性的确很强,但是50w以上的部分是一条直线,因为我们将50w以上的实例全部归类于50w这一类了。仔细观察,发现45w、35w、23w附近貌似也有这样的直线,值得注意。 -
尝试不同属性的组合
比如目前知道每个地区的房间数,但并没有什么用,如果知道了每个家庭的房间数,可能就大大不同了,因此可以对属性进行组合。
发现每个家庭的房间数、每个房间的卧室数与房价也很有相关性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)
数据准备
- 准备干净的数据集,将预测器与标签分开
- 数据清洗
- 放弃这些相应的地区
- 放弃这个属性
- 将缺失的值设为某个值(0、平均数或中位数等)
我选择用中位数填补缺失值
填补之后,可见数据集已经没有缺失部分了
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)
-
处理文本与分类属性
之前ocean_proximity为文本属性,无法进行数值计算,因此我们需要将其转化为数字,但是此时算法会将其理解为纯数值进行运算,这是不对的,我们的原始数据是分类数据,是类别,不能用于加减乘除等运算,因此我们需要将其转换成独热编码。
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)
-
自定义转换器
之前讨论过组合属性形成新的属性,我们也希望可以引入新的属性,但是又希望这个过程可以与Scikit-Learn的流水线无缝衔接,因此可以自定义转换器。所需要的只是新创建一个类,应用fit()、transform()、fit_transform()就可以了。如果添加TransformerMixin作为基类,可以直接获得最后一个方法;添加BaseEstimator作为基类,则可以获得两个自动调整超参数的方法get_params()和set_params()。
增加前:
增加后:
新增加了每间房房间数量和每户家庭人口数两个组合属性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]
-
转换流水线
许多数据转换需要正确的步骤来运行,因此需要设置流水线工作。我仿照书上写这段代码时,发现由于版本更新,这里失效了:
经检查是LabelBinarizer更新后只接收两个参数,因此重写了一个自己的MyLabelBinarizerclass 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)
准备数据成功
选择和训练模型
-
培训和评估训练集
使用线性回归模型,看看预测情况如何#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)
部分预测结果,效果不尽如人意,利用RMSE进行评估
效果确实不行!使用决策树模型,看看情况如何
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)
情况喜人!!但是转念一想,100%正确,这不是过拟合了吧?进行模型验证评估!! -
验证模型 选择交叉验证方法验证模型
效果真的烂,保险起见,检查一下线性回归模型
也很差劲,换模型!!#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)
- 使用随机森林
情况烂到家了,理论上来说,不应该,选其他模型或者调参数 -
调参
利用网格搜索使用不同参数组合,找出最好参数结果
查看整体情况
此时随机森林的最佳参数是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)
-
分析最佳模型及其错误
查看每个特征的重要性
可见收入是最重要的特征#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)
-
通过测试集评估系统
最终误差可以接受#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)
- 启动、监控和维护系统
后记
性能指标
回归问题的典型性能衡量指标是均方根误差(RMSE),它测量的是预测过程中预测错误的标准偏差。
当数据离群点很多时,可以考虑使用平均绝对误差(MAE)
特征缩放
如果输入的数据有较大的比例差异,可以考虑对特征数值进行缩放,通常有两种方法:最大-最小缩放、标准化
最大-最小缩放:归一化,将数值缩放到0~1之间,实现方法是将值减去最小值并除以最大值和最小值的差
标准化:首先减去平均值,再除以方差