目标:对一个区域房价中位数的预测
1、使用真实的数据
2、观察大局
2.1 框架问题
需要回答框架问题:
是有监督学习,无监督学习还是强化学习?是分类任务,回归任务还是其他任务?应该使用批量学习还是在线学习技术?
2.2 选择性能指标
1、均方根误差(RMSE)
R M S E ( X , h ) = 1 m ∑ i − 1 m ( h ( x ( i ) ) − y ( i ) ) 2 RMSE(X,h)=\sqrt{\frac{1}{m}\sum_{i-1}^m(h(x^{(i)})-y^{(i)})^2} RMSE(X,h)=m1i−1∑m(h(x(i))−y(i))2
说明:
- m为数据集中的实例数
- x ( i ) x^{(i)} x(i)是数据集中第i个实例的所有特征值(不包括标签)的向量,即第i个样本
- y ( i ) y{(i)} y(i)是其标签(该实例的期望输出值)
- X是一个矩阵,其中包含数据集中所有实例的所有特征值
- h是系统的预测函数, y ^ ( i ) = h ( x ( i ) ) \hat{y}^{(i)}=h(x^{(i)}) y^(i)=h(x(i))
- R M S E ( X , h ) RMSE(X,h) RMSE(X,h)是使用假设 h h h在一组事例中测量的成本函数
2、平均绝对误差
M A E ( X , h ) = 1 m ∑ i = 1 m ∣ h ( x ( i ) ) − y ( i ) ∣ MAE(X,h)=\frac{1}{m}\sum_{i=1}^m|h(x^{(i)})-y^{(i)}| MAE(X,h)=m1i=1∑m∣h(x(i))−y(i)∣
范数指标越高,它越关注大值而忽略小值。这就是RMSE对异常值比对MAE更敏感的原因。但是,当离群值呈指数形式稀有时(如钟形曲线),RMSE表现非常好,通常是首选)
2.3 获取数据
1、下载数据
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("datasets", "housing") # 连接两个或更多的路径名组件 'datasets\\housing' 保存的本地地址
HOUSING_URL = DOWNLOAD_ROOT+ "datasets/housing/housing.tgz" # 下载的链接地址
def fetch_housing_data(housing_url = HOUSING_URL, housing_path = HOUSING_PATH):
os.makedirs(housing_path, exist_ok=True)# 用于递归创建目录。
tgz_path = os.path.join(housing_path, "housing.tgz") # 'datasets\\housing\\housing.tgz'
urllib.request.urlretrieve(housing_url, tgz_path)# 下载数据
housing_tgz = tarfile.open(tgz_path) # 打开压缩文件,tgz格式一般是Linux下的
housing_tgz.extractall(path=housing_path)# 解压缩文件
housing_tgz.close()#关闭
函数备注:
- os.path.join():连接两个或更多的路径名组件
- os.makedirs(path, mode=0o777):用于递归创建目录。
- urlretrieve(url, filename=None, reporthook=None, data=None):直接将远程数据下载到本地
参数url:下载链接地址
参数filename:指定了保存本地路径(如果参数未指定,urllib会生成一个临时文件保存数据。)
参数reporthook:是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,可以利用这个回调函数来显示当前的下载进度。
参数data:指post导服务器的数据,该方法返回一个包含两个元素的(filename, headers) 元组,filename 表示保存到本地的路径,header表示服务器的响应头
- tarfile的extractall():解压缩文件
- zipfile的extractall(path=None, members=None, pwd=None)
path指定解压后文件的存储位置
members(可选)指定要Zip文件中要解压的文件,这个文件名称必须是通过namelist()方法返回列表的子集
pwd指定Zip文件的解压密码
弄了好久好久好久,还是无法下载数据集,最后在kaggle上手动下载了一份:
https://www.kaggle.com/camnugent/california-housing-prices
2、读取数据
def load_housing_data(housing_path=HOUSING_PATH):
csv_path = os.path.join(housing_path, "housing.csv")
return pd.read_csv(csv_path)
3、创建测试集
- 方法一
不完美,如果再运行一次,又会产生不同的数据集
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]
- 方法二
def test_set_check(identifier, test_ratio):
return crc32(np.int64(identifier))& 0xffffffff < test_ratio * 2**32
def split_train_test_by_id(data, test_ratio, id_column):
ids = data[id_column]
in_test_set = ids.apply(lambda id_:test_set_check(id_, test_ratio))
return data.loc[~in_test_set], data.loc[in_test_set]
housing_with_id = housing.reset_index()
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, 'index')
crc32用于计算 data 的 CRC (循环冗余校验) 值。计算的结果是一个 32 位的整数。
参数 value 是校验时的起始值,其默认值为 0。
借助参数 value 可为分段的输入计算校验值。此算法没有加密强度,不应用于身份验证和数字签名。
此算法的目的仅为验证数据的正确性,不适合作为通用散列算法。
在python 3.0 之后: 返回值永远是无符号数。要在所有的 Python 版本和平台上获得相同的值,请使用 crc32(data) & 0xffffffff。
- 方法三
from sklearn.model_selection import train_test_split
train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)
- 分层抽样
1)数据分箱
housing['income_cat'] = pd.cut(housing["median_income"],
bins=[0., 1.5, 3.0, 4.5, 6., np.inf],
labels=[1,2,3,4,5])
housing["income_cat"].hist()
pandas.cut(x, bins, right=True, labels=None, retbins=False, precision=3, include_lowest=False, duplicates='raise')
x:被切分的类数组(array-like)数据,必须是1维的(不能用DataFrame);
bins:bins是被切割后的区间(或者叫“桶”、“箱”、“面元”),有3中形式:一个int型的标量、标量序列(数组)或者pandas.IntervalIndex 。一个int型的标量. 当bins为一个int型的标量时,代表将x平分成bins份。x的范围在每侧扩展0.1%,以包括x的最大值和最小值。标量序列. 标量序列定义了被分割后每一个bin的区间边缘,此时x没有扩 .pandas.IntervalIndex
定义要使用的精确区间。
right:bool型参数,默认为True,表示是否包含区间右部。比如如果bins=[1,2,3],right=True,则区间为(1,2],(2,3];right=False,则区间为(1,2),(2,3)。
labels:给分割后的bins打标签,比如把年龄x分割成年龄段bins后,可以给年龄段打上诸如青年、中年的标签。labels的长度必须和划分后的区间长度相等,比如bins=[1,2,3],划分后有2个区间(1,2],(2,3],则labels的长度必须为2。如果指定labels=False,则返回x中的数据在第几个bin中(从0开始)。
retbins:bool型的参数,表示是否将分割后的bins返回,当bins为一个int型的标量时比较有用,这样可以得到划分后的区间,默认为False。
precision:保留区间小数点的位数,默认为3.
include_lowest:bool型的参数,表示区间的左边是开还是闭的,默认为false,也就是不包含区间左部(闭)duplicates:是否允许重复区间。有两种选择:raise:不允许,drop:允许。
2)分层抽样
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]
StratifiedShuffleSplit()提供分层抽样功能,确保每个标签对应的样本的比例
-----------------------------------------------------------------------
n_splits:是将训练数据分成train/test对的组数,可根据需要进行设置,默认为10
test_size和train_size: 是用来设置train/test对中train和test所占的比例。例如:
1.提供10个数据num进行训练和测试集划分
2.设置train_size=0.8 test_size=0.2
3.train_num=numtrain_size=8 test_num=numtest_size=2
random_state:随机数种子,和random中的seed种子一样,保证每次抽样到的数据一样,便于调试
删除income_cat属性,将数据恢复原样
for set_ in(strat_train_set, strat_test_set):
set_.drop("income_cat", axis=1, inplace=True)
2.4 从数据探索和可视化中获得洞见
1、将地理数据可视化
housing.plot(kind="scatter", x="longitude", y="latitude", alpha=0.1)
2、寻找相关性
corr_matrix = housing.corr()
corr_matrix['median_house_value'].sort_values(ascending=False)
- corr():计算出每对属性之间的标准相关系数(也称为皮尔逊r)
corr()计算的是两个属性的线性关系,但属性之间还存在非线性关系。还有一种方法检测属性之间的相关性。使用pandas的scatter_matrix函数,它会绘制出每个数值属性的相关性
attributes = ["median_house_value", "median_income", "total_rooms", "housing_median_age"]
scatter_matrix(housing[attributes], figsize=(12,8))
3、实验不同属性的组合
准备给机器学习算法输入数据之前,要做的最后一件事应该是尝试各种属性的组合
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)
2.5 机器学习算法的数据准备
1、数据清洗
处理缺失值时有以下三种方法:
- 放弃这些相关的区域
- 放弃整个属性
- 将缺失值设置为某个值(0,平均数或者中位数等)
housing.dropna(subset=["total_bedrooms"])
housing.drop("total_bedrooms", axis=1)
median = housing["total_bedrooms"].median()
housing["total_bedrooms"].fillna(median, inplace=True)
# 使用imputer将缺失值替换成中位数值从而完成训练集转换
X=imputer.transform(housing_num)
housing_tr=pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)
2、处理文本和分类属性
- 将类别从文本转到向量
ordinal_encoder = OrdinalEncoder()
ordinal_cat_encoder = ordinal_encoder.fit_transform(housing_cat)
ordinal_cat_encoder[:10]
这种表征产生的一个问题是,机器学习算法会认为两个相近的值比两个离得远的值更为相似一些
但这并不完全适应于所有情况
为解决这个问题,常见的解决方案是给每个类别创建一个二进制属性
- 独热编码
cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)
housing_cat_1hot
输出的是一个Scipy稀疏矩阵,若想将它转换成一个Numpy数据,需要调用toarray()方法
3、自定义转换器
Scikit-Learn提供许多有用的转换器,但仍需要为一些自定义清理操作或组合特定属性等任务编写自己的转换器
Scikit-Learn是鸭子类型的编译,而不是继承,所以需要的只是创建一个类,然后应用:fit()(返回self),transform(),fit_transform()
鸭子类型的编译:对象的类型不再由继承等方式决定,而由实际运行时所表现出的具体行为来决定。
这是前面组合属性的一个简单转换类:
from sklearn.base import BaseEstimator, TransformerMixin
rooms_ix, bedrooms_ix, population_ix, households_ix = 3,4,5,6
class CombineAttributesAdder(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):
rooms_per_household = X[:, rooms_ix]/X[:, households_ix]
population_per_household = X[:, population_ix]/X[:, households_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 = CombineAttributesAdder(add_bedrooms_per_room=False)
housing_extra_attribs = attr_adder.transform(housing.values)
4、特征缩放
同比例缩放所有属性的两种常用方法是最小-最大缩放(归一化)和标准化
-
最小-最大缩放(归一化):将值减去最小值并除以最大值和最小值的差
scikit-learn提供MinMaxScaler
(神经网络期望输入的值范围通常是0~1) -
标准化:首先减去平均值,然后除以方差,标准化不将值绑定在特定范围,但标准化的方法受异常值的影响更小
scikit-learn提供StandadScaler
5、转换流水线
许多数据转换的步骤需要以正确的顺序来执行。而scikit-learn正好提供了Pipeline类来支持这样的转换
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
num_pipeline = Pipeline([
('imputer', SimpleImputer(strategy="median")),
('attribs_adder', CombineAttributesAdder()),
('std_scaler', StandardScaler()),
])
housing_num_tr = num_pipeline.fit_transform(housing_num)
除了最后一个是估算器之外,前面都必须是转换器(就是说必须有fit_transform()方法)
调用流水线的fit()方法时,会在所有转换器上按顺序依次调用fit_transform(),将一个调用的输出作为参数传递给下一个调用方法,知道最后的估算器只会调用fit()方法
最后的估算器StandardScaler是转换器,所以流水线有transform()方法
到目前为止处理分别处理了类别列和数值列,拥有一个能处理所有列的转换器会更方便,将适当的转换应用于每个列中
from sklearn.compose 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)
ColumnTransformer类包含的元组里面包含一个名字,一个转换器,以及一个该转换器能够应用的列名字(或索引)。
ColumnTransformer将每个转换器应用于适当的列,并沿用二个轴合并输出(转换器必须返回相同数量的行)
如果希望这些列保持不变,可以指定“pass through”。默认情况下,其余列(即未列出的列)将被删除
2.6 选择和训练模型
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:', lin_reg.predict(some_data_prepared))
print('labels', list(some_labels))
测量整个训练集上回归模型的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)
print(lin_rmse)
结果表示这是一个欠拟合的模型
通常意味着这些特征可能无法提供足够的信息来做出更好的预测,或者是模型本身不够强大。
想要修正欠拟合,可以通过选择更强大的模型或为算法训练提供更好的特征,有或者减少对模型的限制等方法。
- 决策树模型
能从数据中找到复杂的非线性关系
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)
print(tree_rmse)
结果为0, 很可能模型对数据严重过拟合了。在有信心启动模型之前都不要碰触测试集。所以需要那训练集中的一部分用于训练,另一部分用于模型验证。
2、使用交叉验证来更好地进行评估
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)
tree_rmse_scores = np.sqrt(-scores)
sklearn的交叉验证功能更倾向于使用效用函数(越大越好),而不是成本函数(越小越好),所以计算分数的函数实际上是负的MSE
def display_scores(scores):
print('Score:',scores)
print("Mean", scores.mean())
print("Standard deviation:", scores.std())
display_scores(tree_rmse_scores)
严重过拟合了,表现得比线性回归模型还要糟糕
- 随机森林
from sklearn.ensemble import RandomForestRegressor
forest_reg = RandomForestRegressor()
forest_reg.fit(housing_prepared, housing_labels)
scores = cross_val_score(forest_reg, housing_prepared, housing_labels, scoring="neg_mean_squared_error", cv=10)
forest_rmse_scores = np.sqrt(-scores)
display_scores(forest_rmse_scores)
随机森林效果好点,但是训练集上的分数仍然远低于验证集,意味着该模型仍然对训练集过拟合
2.7 微调模型
1、网格搜索
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', return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
print(np.sqrt(-mean_score), params)
首先评估定一个dict然后再尝试第二个dict中的超参数值
2、随机搜索
当超参数的搜索范围较大时,通常会选择使用RandomizedSearchCV。这个类用起来和GridSearchCV类似,但它不会尝试所有组合。
3、集成方法
还有一种微调系统的方法是将表现最优的模型组合起来。组合(或集成)的方法通常比最佳的单一模型要好,特别是单一模型会产生不同类型误差时更是如此。
4、分析最佳模型及误差
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances
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)
有了这些信息可以尝试删除一些不太能用的特征。
5、通过测试集评估系统
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)
计算泛化误差95%的置信区间:
from scipy import stats
confidence = 0.95
squared_errors = (final_predictions-y_test)**2
np.sqrt(stats.t.interval(confidence, len(squared_errors)-1,
loc=squared_errors.mean(),
scale=stats.sem(squared_errors)))
泛化误差知识点补充:
https://blog.youkuaiyun.com/pannn0504/article/details/82455934