学习一个东西首先要从大局在掌握,知道整体框架是什么,有哪些部分,然后再逐个击破,便事半功倍。
所以学习最开始,拿一个完整的例子最好不过。
本次项目是通过一系列属性值(专业名词称作:特征值)来对房价进行预测,以找出最适合的模型
一个机器学习案例主要包括八个部分
1.项目概述
2.获取数据
3.发现并可视化数据,发现规律
4.为机器学习算法准备数据
5.选择模型,进行训练
6.微调模型
7.给出解决方案
8.部署、监控、维护系统
1.项目概述
欢迎来到机器学习房地产公司!你的第一个任务是利用加州普查数据,建立一个加州房价模型。
这个数据包含每个街区组的人口、收入中位数、房价中位数等指标。
街区组是美国调查局发布样本数据的最小地理单位(一个街区通常有 600 到 3000 人)。
我们将其简称为“街区”。你的模型要利用这个数据进行学习,然后根据其它指标,预测任何街区的的房价中位数。
划定问题
问老板的第一个问题应该是商业目标是什么?建立模型可能不是最终目标。公司要如何使用、并从模型受益?
这非常重要,因为它决定了如何划定问题,要选择什么算法,评估模型性能的指标是什么,要花多少精力进行微调。
老板告诉你你的模型的输出(一个区的房价中位数)会传给另一个机器学习系统,也有其它信号会传入后面的系统.
这一整套系统可以确定某个区进行投资值不值。确定值不值得投资非常重要,它直接影响利润。
选择性能指标
回归问题的典型指标是均方根误差(RMSE)。均方根误差测量的是系统预测误差的标准差
虽然大多数时候 RMSE 是回归任务可靠的性能指标,在有些情况下,你可能需要另外的函数。
例如,假设存在许多异常的街区。此时,你可能需要使用平均绝对误差(Mean Absolute Error,也称作平均绝对偏差)
核实假设
最后,最好列出并核对迄今(你或其他人)作出的假设,这样可以尽早发现严重的问题。
例如,你的系统输出的街区房价,会传入到下游的机器学习系统,我们假设这些价格确实会被当做街区房价使用。
但是如果下游系统实际上将价格转化成了分类(例如,便宜、中等、昂贵),然后使用这些分类,而不是使用价格。
2.获取数据
import numpy as np import pandas as pd import matplotlib.pyplot as plt filePath = r'housing.csv' f = open(filePath) housing = pd.read_csv(f) housing.head()#查看头文件 housing.info()#产看数据信息 housing['ocean_proximity'].value_counts()#数据类型 a = housing.describe()#数据描述
3.发现并可视化数据,发现规律
#数据可视化展示 housing.hist(bins = 50, figsize = (20,15)) plt.show()
#创建测试集,挑20%的实例放在一边 #首先设置随机种子 np.random.seed(42) 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] train_set, test_set = split_train_test(housing, 0.2)
如果数据集更新,那两个方法都会失效,一个通常的解决方法是使用每个实例的ID来判断
这个实例是否应该放入测试集,(假设每个实例都有唯一并且不变的ID)。例如计算出每个实例
ID的哈希值,只保留其最后一个字节,如果该值小于等于51,就将其放入测试集
import hashlib def test_set_check(identifier, test_ratio, hash = hashlib.md5): return hash(np.int64(identifier)).digest()[-1] < 256 * test_ratio def split_train_test_by_id(data, test_ratio, id_column, hash=hashlib.md5): ids = data[id_column] in_test_set = ids.apply(lambda id_:test_set_check(id_, test_ratio, hash)) return data.loc[~in_test_set], data.loc[in_test_set] housing_with_id = housing.reset_index() #添加索引列,就是index作为新的一列放入矩阵 train_set, train_set = split_train_test_by_id(housing_with_id, 0.2,'index') #本实例没有ID值,行列号可能会因为后来的删减而改变,因此使用地理坐标来作为ID使用 housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"] train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, 'id') test_set.head() #当然使用sklearn的模块也有直接选取测试集的功能 from sklearn.model_selection import train_test_split train_set, test_set = train_test_split(housing, test_size = 0.2, random_state = 42)
#收入中位数是预测房价中位数非常重要的属性,所以首先要创建一个收入类别属性, #再仔细看一下收入中位数的柱状图 housing['median_income'].hist()
#将中位数除以1.5(以限制收入分类的数量),创建了一个收入类别属性 #用ceil对值舍入(以产生离散的分类),然后将所有大于5的分类归入到分类5 housing['income_cat'] = np.ceil(housing['median_income'] / 1.5) housing['income_cat'].where(housing['income_cat'] < 5, 5.0, inplace = True) housing['income_cat'].value_counts() housing['income_cat'].hist()
#通过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] housing['income_cat'].value_counts() / len(housing) strat_test_set['income_cat'].value_counts() / len(strat_test_set)
使用相似的代码,用随机采样和分层采样进行对比,发现分层采样测试集的收入分类比例
与总数据集几乎相同,而随机采样数据集偏差严重
def income_cat_proportions(data): return data['income_cat'].value_counts() / len(data) train_set, test_set = train_test_split(housing, test_size = 0.2, random_state = 42) compare_props = pd.DataFrame({ 'Overall':income_cat_proportions(housing), 'Stratified':income_cat_proportions(strat_test_set), 'Random': income_cat_proportions(test_set) }).sort_index() compare_props['Rand.%error'] = 100 * compare_props['Random'] / compare_props['Overall'] - 100 compare_props['Strat.%error'] = 100 * compare_props['Stratified'] / compare_props['Overall'] - 100 #删除income_cat属性,始数据回到初始状态: for set in (strat_train_set, strat_test_set): set.drop(['income_cat'], axis = 1, inplace = True)
#创建训练集副本,以免损伤训练集 housing = strat_train_set.copy() #散点图 housing.plot(kind = 'scatter', x = 'longitude', y = 'latitude') #散点图设置透明读 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', figsize = (10,7), c = 'median_house_value', cmap = plt.get_cmap('jet'), colorbar = True, sharex = False) plt.legend() #将地图导入散点图 import matplotlib.image as mpimg california_img = mpimg.imread(r'F:\python36_data\01--Sklearn 与 TensorFlow 机器学习实用指南中文版 2018.6.20\handson-ml-master\images\end_to_end_project\california.png') ax = housing.plot(kind = 'scatter', x = 'longitude', y = 'latitude', figsize = (10,7), s = housing['population']/100, label = 'Population', c = 'median_house_value', cmap = plt.get_cmap('jet'), colorbar = False, alpha = 0.4,) plt.imshow(california_img, extent = [-124.55, -113.80, 32.45, 42.05], alpha = 0.5, cmap = plt.get_cmap('jet')) plt.ylabel('Latitude', fontsize = 14) plt.xlabel('Longitude', fontsize = 14) prices = housing['median_house_value'] tick_values = np.linspace(prices.min(), prices.max(), 11) cbar = plt.colorbar() cbar.ax.set_yticklabels(["$%dk"%(round(v/1000)) for v in tick_values], fontsize = 14) cbar.set_label('Median House Value', fontsize = 16) plt.legend(fontsize = 16) plt.show()
查看属性之间的关联度
#查找关联度 corr_matrix = housing.corr() corr_matrix['median_house_value'].sort_values(ascending = False) #画关联度三点矩阵图 from pandas.tools.plotting import scatter_matrix attributes = ['median_house_value', 'median_income','total_rooms', 'housing_median_age'] scatter_matrix(housing[attributes], figsize= (12,8)) #画图 housing.plot(kind = 'scatter', x = 'median_income', y = 'median_house_value', alpha = 0.1) plt.axis([0, 16, 0, 550000])
#计算出新的指标 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) housing.plot(kind = 'scatter', x = 'rooms_per_household', y = 'median_house_value', alpha = 0.2) plt.axis([0, 5, 0, 520000]) plt.show() a = housing.describe()
4.为机器学习算法准备数据
现在来为机器学习算法准备数据。不要手工来做,你需要写一些函数,理由如下:
4.1函数可以让你在任何数据集上(比如,你下一次获取的是一个新的数据集)方便地进行重复数据转换。
4.2你能慢慢建立一个转换函数库,可以在未来的项目中复用。
4.3在将数据传给算法之前,你可以在实时系统中使用这些函数。
4.这可以让你方便地尝试多种数据转换,查看哪些转换方法结合起来效果最好。
#通过再次复制,将预测量和标签分开 housing = strat_train_set.drop("median_house_value", axis = 1) housing_labels = strat_train_set['median_house_value'].copy()
有三种方法处理缺失值
1.去掉对应的街区
housing.dropna(subset = [‘total_bedrooms’])
2.去掉整个属性
housing.drop(‘total_bedrooms’, axis = 1)
3.进行赋值(0、平均值、中位数等等)
median = housing[‘total_bedrooms’].median()
housing[‘total_bedrooms’].fillna(median)
#Scikit-Learn提供了一个方便的类来处理缺失值: Imputer from sklearn.preprocessing import Imputer imputer = Imputer(strategy = 'median') #因为只有数值属性才能算出中位数,创建出一份不包括文本属性ocean_proximity的数据副本 housing_num = housing.drop('ocean_proximity',axis = 1) #用fit()方法将imputer实例拟合到训练数据 imputer.fit(housing_num) #查看数据 imputer.statistics_ imputer.strategy housing_num.median().values #将“训练过”的imputer来对训练集进行转换,将缺失值替换为中位数 X = imputer.transform(housing_num) #将Numpy数组放回到Pandas dataframe中 housing_tr = pd.DataFrame(X, columns=housing_num.columns, index = list(housing.index.values))
skickit-learn设计
一致性:所有对象的接口一致且简单
估计器:任何可以基于数据集对一些参数进行估计的对象都被称为估计器
转换器:一些估计器(比如imputer)也可以转换数据集,这些估计器被称为转换器。
预测器:一些估计器可以根据给出的数据集做预测,这些估计器称为预测器。
可检验:所有估计器的超参数都可以通过实例的public变量直接访问(比如,imputer.strategy),
并且所有估计器学习到的参数也可以通过在实例变量名后加下划线来访问
(比如,imputer.statistics_)。
类不可扩散:数据集被表示成 NumPy 数组或 SciPy 稀疏矩阵,而不是自制的类。
超参数只是普通的 Python 字符串或数字。
可组合;尽可能使用现存的模块。例如,用任意的转换器序列加上一个估计器,
就可以做成一个流水线,后面会看到例子。
合理的默认值;Scikit-Learn 给大多数参数提供了合理的默认值,很容易就能创建一个系统。
#处理文本和类别属性
#把文本标签转换为数字
#Scikit-Learn为这个任务提供了一个转换器LabelEncoder:
from sklearn.preprocessing import LabelEncoder encoder = LabelEncoder()#如果有多个文本特征列的时候,应使用factorize()方法来进行 housing_cat = housing['ocean_proximity'] housing_cat_encoded = encoder.fit_transform(housing_cat) housing_cat_encoded #检查映射表,编码器通过属性classes_来学习的 print(encoder.classes_) #因为上面这个数字编码0和1与0和4相差太大,因此改用独热编码 #Scikit-Learn提供了一个编码器OneHotEncoder,用于将整数分类转变为独热向量 from future_encoders import OneHotEncoder encoder = OneHotEncoder() housing_cat_1hot = encoder.fit_transform(housing_cat_encoded.reshape(-1,1)) housing_cat_1hot.toarray() #使用类LabelBinarizer,我们可以用一步执行这两个转换(从文本分类到整数分类,再从整数分类到独热向量) from sklearn.preprocessing import LabelBinarizer encoder = LabelBinarizer() housing_cat_1hot = encoder.fit_transform(housing_cat) housing_cat_1hot #返回的是一个密集的Numpy数组。向构造器LabelBinarizer 传递sparse_output=True, #就可以得到一个稀疏矩阵
在原书中使用LabelBinarizer的方式也是错误的,该类也应用于标签列的转换。
正确做法是使用sklearn即将提供的CategoricalEncoder类
from sklearn.base import BaseEstimator, TransformerMixin rooms_ix, bedrooms_ix, population_ix, household_ix = 3,4,5,6 class CombinedAttributesAdder(BaseEstimator, TransformerMixin): def __init__(self,add_bedrooms_per_room = True):#no *args or **kargs self.add_bedrooms_per_room = add_bedrooms_per_room def fit(self,X, y=None): return self #nothing else to do def transform(self, X, y=None): rooms_per_household = X[:,rooms_ix] / X[:,household_ix] population_per_household = X[:,bedrooms_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] attr_adder = CombinedAttributesAdder(add_bedrooms_per_room = False) housing_extra_attribs = attr_adder.transform(housing.values) housing_extra_attribs = pd.DataFrame( housing_extra_attribs, columns=list(housing.columns) + ['rooms_per_household', 'population_per_household']) housing_extra_attribs.head()
特征缩放
线性函数归一化(Min-Max scaling):转换器MinMaxScaler,超参feature_range改变范围
标准化(standardization):转换器StandardScaler
#转换流水线。scikit-learn提供了类Pipeline #数值的流水线 from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler num_pipeline = Pipeline([ ('imputer',Imputer(strategy = 'median')), ('attribs_adder',CombinedAttributesAdder()), ('std_scaler',StandardScaler()), ]) housing_num_tr = num_pipeline.fit_transform(housing_num) from future_encoders import ColumnTransformer num_attribs = list(housing_num) cat_attribs = ["ocean_proximity"] full_pipeline = ColumnTransformer([ ("num", num_pipeline, num_attribs), ("cat", OneHotEncoder(), cat_attribs), ]) housing_prepared = full_pipeline.fit_transform(housing)
5.模型训练
5.1线性回归
from sklearn.linear_model import LinearRegression 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:\t", lin_reg.predict(some_data_prepared)) print("Labels:\t\t", list(some_labels)) #使用Scikit-Learn 的mean_squared_error的函数,用全部训练集来计算下这个模型的回归模型 #的RMSE from sklearn.metrics import mean_squared_error housing_predictions = lin_reg.predict(housing_prepared) lin_mse = mean_squared_error(housing_labels, housing_predictions) lin_rmse = np.sqrt(lin_mse) lin_rmse
分析:rmse68234,过大,是一个模型欠拟合的例子
这种情况说明特征没有提供足够多的信息来做出一个好的预测,或者模型不够强大
修复欠拟合的主要方法;
1.选择一个更强大的模型
2.给训练算法提供更好的特征
3.去掉模型上的限制
5.2 决策树
#模型训练 from sklearn.tree import DecisionTreeRegressor 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) tree_rmse ''' rmse:0,过拟合 用train_test_split来分割训练集,得到一个更小的训练集和验证集 用Scikit-learn的交叉验证功能:K折交叉验证(K-fold cross-validation) ''' from sklearn.model_selection import cross_val_score scores = cross_val_score(tree_reg, housing_prepared, housing_labels, scoring = 'neg_mean_squared_error', cv =10) rmse_scores = np.sqrt(-scores) #警告:Scikit-Learn交叉验证功能期望的是效用函数(越大越好),而不是损失函数(越低越好) #因此得分函数实际上与MSE相反(即负值),这就是为什么前面的代码在计算平方根之前先计算-scores def display_scores(scores): print("scores:",scores) print("Mean:",scores.mean()) print("Standard deviation:",scores.std()) display_scores(rmse_scores)
决策树的评分大约是71580,通常波动有±1375
#再用线性回归模型的相同分分数,以做确保
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)
分析:决策树模型过拟合很严重,它的性能比线性回归模型还差
5.3 随机森林
#模型训练 from sklearn.ensemble import RandomForestRegressor forest_reg = RandomForestRegressor() forest_reg.fit(housing_prepared, housing_labels) #评估训练集 housing_predictions = forest_reg.predict(housing_prepared) forest_mse = mean_squared_error(housing_labels, housing_predictions) forest_rmse = np.sqrt(forest_mse) forest_rmse #交叉验证 forest_scores = cross_val_score(forest_reg, housing_prepared, housing_labels, scoring = 'neg_mean_squared_error', cv = 10) forest_rmse_scores = np.sqrt(-forest_scores) display_scores(forest_rmse_scores)
5.4支持向量机
from sklearn.svm import SVR svm_reg = SVR(kernel = 'linear') svm_reg.fit(housing_prepared, housing_labels) housing_predictions = svm_reg.predict(housing_prepared) svm_mse = mean_squared_error(housing_labels, housing_predictions) svm_rmse = np.sqrt(svm_mse) svm_rmse
6.模型微调
1.网格搜索:Scikit-Learn 的GridSearchCV
2.随机搜索:RandomizedSearchCV
3.集成方法:将最好模型组合起来
6.1网格搜索
from sklearn.model_selection import GridSearchCV param_grid = [ #try 12(3*4) combinations of hyperparameters {'n_estimators': [3, 10, 30], 'max_features': [2, 4, 6, 8]}, #then try 6(2*3) combinations with bootstrap set as False {'bootstrap':[False], 'n_estimators':[3,10], 'max_features':[2,3,4]} ] forest_reg = RandomForestRegressor() #train across 5 folds, that's a total of (12+6)*5 = 90 rounds of training grid_search = GridSearchCV(forest_reg, param_grid,cv = 5, scoring = 'neg_mean_squared_error', return_train_score = True) grid_search.fit(housing_prepared, housing_labels) grid_search.best_params_ #h还能得到最佳的估计器: grid_search.best_estimator_ #也可以得到评估得分 cvres = grid_search.cv_results_ for mean_scores, params in zip(cvres['mean_test_score'], cvres['params']): print(np.sqrt(-mean_scores), params) from sklearn.model_selection import GridSearchCV 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) a = pd.DataFrame(grid_search.cv_results_)
6.2随机搜索
from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint param_distribs = { 'n_estimators': randint(low=1, high=200), 'max_features': randint(low=1, high=8), } forest_reg = RandomForestRegressor(random_state=42) rnd_search = RandomizedSearchCV(forest_reg, param_distributions=param_distribs, n_iter=10, cv=5, scoring='neg_mean_squared_error', random_state=42) rnd_search.fit(housing_prepared, housing_labels) cvres = rnd_search.cv_results_ for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]): print(np.sqrt(-mean_score), params)
在这里插入图片描述
查看哪个属性值重要性最大
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
#cat_encoder = cat_pipeline.named_steps["cat_encoder"] # old solution
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)
1
2
3
4
5
6
7
8
9
在这里插入图片描述
7.给出解决方案
#给出解决方案
final_model = grid_search.best_estimator_
X_test = strat_test_set.drop("median_house_value", axis=1)
y_test = strat_test_set["median_house_value"].copy()
X_test_prepared = full_pipeline.transform(X_test)
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
final_rmse
#95%的置信区间来计算RMSE
from scipy import stats
confidence = 0.95
squared_errors = (final_predictions - y_test) ** 2
mean = squared_errors.mean()
m = len(squared_errors)
np.sqrt(stats.t.interval(confidence, m - 1,
loc=np.mean(squared_errors),
scale=stats.sem(squared_errors)))
#进行t-score检验
tscore = stats.t.ppf((1 + confidence) / 2, df=m - 1)
tmargin = tscore * squared_errors.std(ddof=1) / np.sqrt(m)
np.sqrt(mean - tmargin), np.sqrt(mean + tmargin)
#进行z-score检验
zscore = stats.norm.ppf((1 + confidence) / 2)
zmargin = zscore * squared_errors.std(ddof=1) / np.sqrt(m)
np.sqrt(mean - zmargin), np.sqrt(mean + zmargin)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
8.启动、监控、维护系统
很好,你被允许启动系统了!你需要为实际生产做好准备,特别是接入输入数据源,并编写测试。
你还需要编写监控代码,以固定间隔检测系统的实时表现,当发生下降时触发报警。
这对于捕获突然的系统崩溃和性能下降十分重要。做监控很常见,是因为模型会随着数据的演化而性能下降,
除非模型用新数据定期训练。
评估系统的表现需要对预测值采样并进行评估。这通常需要人来分析。分析者可能是领域专家,或者是众包平台(比如 Amazon Mechanical Turk 或 CrowdFlower)的工人。不管采用哪种方法,你都需要将人工评估的流水线植入系统。
你还要评估系统输入数据的质量。有时因为低质量的信号(比如失灵的传感器发送随机值,或另一个团队的输出停滞),
系统的表现会逐渐变差,但可能需要一段时间,系统的表现才能下降到一定程度,触发警报。如果监测了系统的输入,
你就可能尽量早的发现问题。对于线上学习系统,监测输入数据是非常重要的
最后,你可能想定期用新数据训练模型。你应该尽可能自动化这个过程。
如果不这么做,非常有可能你需要每隔至少六个月更新模型,系统的表现就会产生严重波动。
如果你的系统是一个线上学习系统,你需要定期保存系统状态快照,好能方便地回滚到之前的工作状态。
reference:《Sklearn 与 TensorFlow 机器学习实用指南》