import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import os
import tarfile
from six.moves import urllib
fetch_housing_data(),获取housing.csv数据,当调用fetch_housing_data(),就会在工作空间创建一个datasets/housing目录, 并且下载housing.tgz,解压housing.tgz
DOWNLOAD_ROOTDOWNLOA = "https://raw.githubusercontent.com/ageron/handson-ml/master/"
HOUSING_PATH = "datasets/housing"
HOUSING_URL = DOWNLOAD_ROOT + HOUSING_PATH + "/housing.tgz"
def fetch_housing_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()
fetch_housing_data()
load_housing_data(),加载加州房产数据
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()
关于train_test_split常用参数说明(详细可见sklearn文档说明):
test_size : float, int or None, optional (default=0.25)
random_state :用于设置随机数生成器的种子,目的是保证当多次运行此段代码能够得到完全一样的分割结果,常设为42
shuffle:布尔值。默认为True,设为True时代表在分割数据集前先对数据进行洗牌(随机打乱数据集)
stratify:默认为None.当shuffle=True时,才能不为None,如果不是None,则数据集以分层方式拆分,并使用此作为类标签。
from sklearn.model_selection import train_test_split
train_set,test_set=train_test_split(housing,test_size=0.2,random_state=42)
关于StratifiedShuffleSplit说明(详细可见sklearn文档说明):
这个函数主要是为了用于实现交叉验证(见后续),实现分层方式分割。
其创建的每一组划分将保证每组类比例相同与原数据集中各类的比例保持相同,即第一组训练数据类别比例为2:1,则后面每组类别都满足这个比例 参数说明:
n_splits是将训练数据分成train/test对的组数,可根据需要进行设置,默认为10
(分层方式是指保持原数据集各个类的比例进行分割。比如原来数据集有两类A和B,A:B=5:2,那么在分割后训练数据集和测试数据集中A和B的比例也各自均为5:2。这样利用分层采样可以避免产生严重偏差)
为了进行分层分割,首先我们的数据集应该有类别。假设收入中位数是预测房价中位数非常重要的属性,我们根据多种收入分类。
首先看一下原数据的收入中位数分布
然后我们对收入中位数进行处理:
(1)首先将每个收入中位数除以1.5(用于限制收入分类的数量),用ceil对值舍入,向上取整(以产生离散的分类)
(2)将所有大于5的收入中位数归入到类别5,小于5的收入中位数保持对应的数值作为其类别(1,2,3,4).关于where的使用见http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.where.html (需要尤其注意它的other参数,对于不满足cond参数的部分的值将变为other参数的值5.0)
housing["income_cat"] = np.ceil(housing["median_income"] / 1.5)
# print (housing["income_cat"])
# print (type(housing["income_cat"]))
housing["income_cat"].where(housing["income_cat"] < 5, 5.0, inplace=True)
# print (housing["income_cat"])
from sklearn.model_selection import StratifiedShuffleSplit
ss=StratifiedShuffleSplit(n_splits=1,test_size=0.2,random_state=42)
for train_index,test_index in ss.split(housing,housing["income_cat"]):
strat_train_set=housing.loc[train_index]
strat_test_set=housing.loc[test_index]
验证分割后的数据的类别比例与原数据的类别比例保持一致
最后在利用收入中位数类别对原数据集进行分层分割后,删除数据的income_cat属性,恢复数据的初始状态
for set in (strat_train_set,strat_test_set):
set.drop(["income_cat"],axis=1,inplace=True)
获得特征矩阵X以及只含有median_house_value的y作为真实的label值
housing=strat_train_set.drop("median_house_value",axis=1)
housing_labels=strat_train_set["median_house_value"].copy()
1.对于一些有缺失值的特征,需要进行处理,方法有:
(1).去掉对应的正行数据
(2).去掉这个属性对应的整列
(3).对缺失值用(0,平均值,中位数)进行替换
查看每个属性的缺失值情况
total_bedrooms有缺失值,需要处理,此处利用total_bedrooms的中位数进行填充
from sklearn.impute import SimpleImputer
simputer=SimpleImputer(strategy="median")
因为只有数值属性才能算出中位数,我们需要创建一份不包括文本属性ocean_proximity的数据副本
现在,利用fit()方法将simputer实例拟合到训练数据
simputer计算出了每个属性的中位数,并将结果保存在了实例变量statistics_中。虽然此时只有属性total_bedrooms存在缺失值,但我们不能确定在以后的新的数据中会不会有其他属性也存在缺失值,所以安全的做法是将imputer应用到每个数值
现在,就可以使用这个“训练过的”simputer来对训练集进行转换,将缺失值替换为中位数
得到的X是包含转换后特征的普通的 Numpy 数组,将其转换为DataFrame
2.处理文本类型数据,转化为数值。主要运用sklearn提供的OneHotEncode编码器
首先看到ocean_proximity属性值是文本类型,需要进行处理
housing_ocean_pro=housing[["ocean_proximity"]]
housing_ocean_pro.head(10)
关于独热编码推荐看这个手记里的讲解(https://www.imooc.com/article/35900)
sklearn的OneHotEncoder实现了独热编码
from sklearn.preprocessing import OneHotEncoder
cat_encoder=OneHotEncoder()
housing_ocean_pro_1hot=cat_encoder.fit_transform(housing_ocean_pro)
housing_ocean_pro_1hot
可以看到得到的housing_ocean_pro_1hot是sparse matrix(稀疏矩阵),将其转化为numpy数组
利用编码器查看一下ocean_proximity特征有哪些值
这里手动实现一个类,熟悉sklearn是依赖鸭子类型(英语:duck typing是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定,关注的不是对象的类型本身,而是它是如何使用的)
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):
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]
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)
除了个别情况,当输入的数值属性量度不同时,机器学习算法的性能都不会好。
有两种常见的方法可以让所有的属性有相同的量度:线性函数归一化(Min-Max scaling)和标准化(standardization)。
1.归一化(normalization)
对属性值进行缩放至(0,1)范围内的某个数。
通过用属性值减去最小值,然后再除以最大值与最小值的差值,来进行归一化。
Scikit-Learn 提供了一个转换器MinMaxScaler来实现这个功能。如果不希望范围是 0 到 1,它有一个超参数feature_range,可以让你改变范围。
2.标准化
使每个特征中的数值平均值变为0(将每个特征的值都减掉原始资料中该特征的平均)、标准差变为1
对于某个特征首先将该特征的每个值减去该特征的平均值(所以标准化值的平均值总是 0),然后除以方差,使得到的分布具有单位方差
Scikit-Learn 提供了一个转换器StandardScaler来进行标准化
3.归一化和标准化对比:
区别:归一化是将样本的特征值转换到同一量纲下把数据映射(0,1)。标准化是依照特征矩阵的列处理数据,其通过求z-score的方法,转换为标准正态分布,和整体样本分布相关,每个样本点都能对标准化产生影响。标准化不会限定值到某个特定的范围,标准化受到异常值的影响很小
相同:都能取消由于量纲不同引起的误差;都是一种线性变换,都是对向量X按照比例压缩再进行平移
构建数值型特征列,文本类别型特征列名
1.创建数值型特征的流水线
fromfrom sklearn.pipelinesklearn.p import Pipeline
from sklearn.preprocessing import StandardScaler
num_type_pipeline=Pipeline([
('simputer',SimpleImputer(strategy="median")),
('attribs_adder', CombinedAttributesAdder()),
('std_scaler',StandardScaler())
])
2.利用sklearn的ColumnTransformer组合流水线
关于ColumnTransformer参考(https://www.codercto.com/a/31047.html)
from sklearn.compose import ColumnTransformer
full_pipeline = ColumnTransformer([
("num", num_type_pipeline, num_attribs),
("cat", OneHotEncoder(), cat_attribs),
])
选取一些训练数据进行模型评估
从上面的预测结果与真实结果比对,可以看出有很大偏差
接下来,看一下利用线性回归整个训练集的RMSE
可以看到预测值与真实房价的rmse为68628,模型欠拟合
修复欠拟合的主要方法是选择一个更强大的模型,给训练算法提供更好的特征,或去掉模型上的限制。
1.用训练集评估模型
这个结果并不是表示训练结果有多好,反而表明存在着严重的过拟合
2.利用交叉验证做更好的评估
交叉验证可以参考(https://www.cnblogs.com/sddai/p/5696834.html)
特别注意cross_val_score中的参数score的取值,参考(https://scikit-learn.org/stable/modules/model_evaluation.html#the-scoring-parameter-defining-model-evaluation-rules)
这里利用K折交叉验证,随机地将训练集分成十个不同的子集,成为“折”,然后训练评估决策树模型 10 次,每次选一个不用的折来做评估,用其它 9 个来做训练。结果是一个包含 10 个评分的数组:
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)
# print(scores)
rmse_scores = np.sqrt(-scores)
def display_scores(scores):
print("Scores:", scores)
print("Mean:", scores.mean())
print("Standard deviation:", scores.std())
对线性回归同样进行K折交叉验证,两者结果进行比较
交叉验证不仅可以让你得到模型性能的评估,还能测量评估的准确性(即,它的标准差)
对比发现决策树模型过拟合很严重,它的性能比线性回归模型还差。
K折交叉验证评估模型
训练集的评分仍然比验证集的评分低很多。
使用 Scikit-Learn 的GridSearchCV来做这项搜索工作。它存在的意义就是自动调参,只要把参数输进去,就能给出最优化的结果和参数。
需要做的是告诉GridSearchCV要试验有哪些超参数,要试验什么值,GridSearchCV就能用交叉验证试验所有可能超参数值的组合。
class sklearn.model_selection.GridSearchCV(estimator, param_grid, scoring=None, fit_params=None, n_jobs=None, iid=’warn’, refit=True, cv=’warn’, verbose=0, pre_dispatch=‘2*n_jobs’, error_score=’raise-deprecating’, return_train_score=’warn’)
参数说明(详见https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html):
estimator:选择使用的分类器,并且传入除需要确定最佳的参数之外的其他参数。每一个分类器都需要一个scoring参数,或者score方法
param_grid:需要最优化的参数的取值,值为字典或者列表
cv=None:交叉验证参数,默认None,使用三折交叉验证。指定fold数量,默认为3,也可以是yield训练/测试数据的生成器
例如,下面的代码搜索了RandomForestRegressor超参数值的最佳组合:
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)
param_grid告诉 Scikit-Learn 首先评估所有的列在第一个dict中的n_estimators和max_features的3 × 4 = 12种组合(不用担心这些超参数的含义,会在后面的随机森林解释)。然后尝试第二个dict中超参数的2 × 3 = 6种组合,这次会将超参数bootstrap设为False而不是True(后者是该超参数的默认值)。
总之,网格搜索会探索12 + 6 = 18种RandomForestRegressor的超参数组合,会训练每个模型五次(因为用的是五折交叉验证)。换句话说,训练总共有18 × 5 = 90轮!K 折将要花费大量时间,完成后,就能获得参数的最佳组合,如下所示:
查看最佳的估计器:
查看每一个参数组合的评估得分:
我们通过设定超参数max_features为8,n_estimators为30,得到了最佳方案。对这个组合,RMSE 的值是 49987,这比之前使用默认的超参数的值(52583)要稍微好一些
当超参数的搜索空间很大时,最好使用RandomizedSearchCV。这个类的使用方法和类GridSearchCV很相似,但它不是尝试所有可能的组合,而是通过选择每个超参数的一个随机值的特定数量的随机组合。这个方法有两个优点:
1).如果让随机搜索运行,比如 1000 次,它会探索每个超参数的 1000 个不同的值(而不是像网格搜索那样,只搜索每个超参数的几个值)。
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)
extra_attribs = ["rooms_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
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)
有了这个信息,就可以丢弃一些不那么重要的特征(比如,显然只要一个ocean_proximity的类型(INLAND)就够了,所以可以丢弃掉其它的)
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)
这个是之前自己参照《Hands-On Machine Learning with Scikit-Learn and TensorFlow》这本书,结合官方文档查看用到的类和函数的使用,自己动手实现的一个小项目,现在,抽空写出来,一个是进行一下回顾,另外也希望能对需要的人有些帮助,通过以上七大步骤可以看到机器学习项目是什么样的,流程是什么样的。这中间数据处理是一个很重要的环节,我们用于训练的特征很大程度决定我们最后模型的性能。