端到端的机器学习项目(求加州住房价格中位数)

下载数据

当数据会定期变化时,写一个脚本,在需要的时候直接运行(或者设置一个定期自动运行的计划任务),或者需要在多台机器上安装数据集,自动化函数非常好用

注:因为网站的问题,所以没有科学上网的话可能不能执行
所以数据集放在这里
加州房价数据集

import os
import tarfile
import urllib

DOWNLOAD_ROOT = "http://raw.githubusercontent.com/ageron/handson-ml2/master/"
HOUSING_PATH = os.path.join("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")
    # print(tgz_path)
    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()  # 只有第一次运行时需要运行这个代码,之后将这个代码注释掉即可

使用pandas加载数据

import pandas as pd
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()
housing.head()

运行结果:
数据集部分数据展示

查看数据

housing.info()  # 从中可以看出ocean_proximity为非数值数据,total_bedrooms是缺值数据

运行结果:
housing.info()从上述运行结果可以看到不同指标的Non-Null Count值不完全相同,在这里可以看出total_bedrooms对应的值要小,说明他存在空值,并且可以看到Dtype(数据类型)中除去ocean_proximity是object之外均是float64,为数值类型。

housing["ocean_proximity"].value_counts()  # 非数值部分的数据

运行结果:
非数值数据

housing.describe()  # 只统计数值数据

非数值数据

%matplotlib inline  # 注意,这行代码只适用于jupyter notebook
import matplotlib.pyplot as plt
housing.hist(bins=55, figsize=(20, 15))
plt.show()

数据数值分布
数据数值分布

数据划分

1.随机划分

import numpy as np

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) 

优点:便于理解
缺点:这种方法虽然可以进行划分。但是每次的运行结果都不一样
解决方法:(1)第一次运行之后就保存下来(当新数据加入后需要重新进行)(2)设置随机种子如np.random.seed(42)(3)哈希

2.哈希方法

from zlib import crc32

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()  # 添加索引, housing_with_id是添加之后的数据
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "index")

注:使用行索引作为标识符必须确保只在末尾添加数据,并且不会删除任何行,在本问题中因为经纬度不会重复,所以可以利用经纬度来进行唯一标识。

housing_with_id["id"] = housing["longitude"] * 1000 + housing["latitude"]
train_set, test_set = split_train_test_by_id(housing_with_id, 0.2, "id")

3.scikit-Learn划分子集

from sklearn.model_selection import train_test_split

train_set, test_set = train_test_split(housing, test_size=0.2, random_state=42)

下面来思考一个问题,如果说数据分成多个层次,每个层次数据量不同,如果按照随机划分的话数据量大的数据明显比数据量小的数据有优势。所以要采用分层
4.纯随机的抽样会出现显著偏差,需要分层取数据

设定标签并分层

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]) 
# 分层不宜过多,这里分了5层
housing.head()  # income_cat存的是pd.cut之后的标签

运行结果(由于数据过长,图只截了一部分,具体结果请大家自己运行一下):
标签化后的数据

housing["income_cat"].hist()  # 显示各个标签的个数

income_cat各标签个数
按照收入类别划分训练集和测试集

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]
strat_train_set["income_cat"].value_counts() / len(strat_train_set)

运行结果
训练集划分

strat_test_set["income_cat"].value_counts() / len(strat_test_set)

运行结果:
测试集划分
去除income_cat

for set_ in (strat_train_set, strat_test_set):
    set_.drop("income_cat", axis=1, inplace=True)
strat_train_set.describe() # 可以看到这里没有了income_cat,数据恢复了原样

除去后的数据

通过数据可视化观测数据

创建副本(为了防止原数据被修改)

housing = strat_train_set.copy() # 现在来讲能探索的只是训练集,为了不损害数据集,先要创建一个副本

地理位置可视化

housing.plot(kind="scatter", x="longitude", y="latitude")  # 用经度当x坐标,纬度为y坐标,画散点图

运行结果:
按照地理位置可视化
看起来和加州的地图很像(因为图本来就是按照经纬度画的),但是在这个图中除了得到房子位置信息之外我们得不到其他信息了。假如我们要突出高密度区域,我们可以在函数中加入alpha参数(参数大小具体视情况而定),这样可以突出高密度地区,弱化低密度地区(抓大放小)。

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),  # s表示的是圆点的大小,这里是使用人口数量作为圆的大小
             c="median_house_value", cmap=plt.get_cmap("jet"), colorbar=True,  # 颜色代表价格(选项c), jet定义颜色表(选项cmap)来可视化
             # 这里是用颜色来表示平均房价
             # 颜色范围从蓝(低)到红(高)
            )
plt.legend()

# 图像表明房价和地理位置和人口密度相关,通常使用聚类算法检测主集群,然后再为每个集群中心添加一个新的衡量临近距离的特征。
# 海洋临近度是个很有用的特征,但是在加州沿海地区的房价也不是很高,所以这个简单的规则也不是万能的

运行结果:
在这里插入图片描述
现在对于数据已经有了一个基本的了解了,接下来开始寻找数据的相关性(在机器学习中,更偏好使用和预测目标相关性较高的指标)

# 由于数据量不是很大,可以使用corr()的方式计算每对属性之间的标准相关系数(也称皮尔逊r)
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)  # 数据靠近0说明两者之间没有线性关系

运行结果:
皮尔逊相关系数
相关系数越接近1相关性越强,可以看到这里边除去平均房价本身是1,最高的是平均收入。

另一种查看相关性的方式

from pandas.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)

运行结果:
平均收入和平均房价的关系
可以看到出现了一些直线,如50万,45万,35万,28万…为了防止将来算法出现这种怪异的数据,可能要删除一部分数据,那一部分可能是异常值或者噪声。

试验不同属性的组合

因为有些原始数据相关性较小,而经过处理后相关性会增加即

# 在准备给机器学习算法输入数据前,最后一件事是尝试各种属性的组合
housing["rooms_per_houshold"] = housing["total_rooms"] / housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"] / housing["total_rooms"]
housing["popluation_per_houshold"] = housing["population"] / housing["households"]
corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

运行结果:
不同属性的组合
可以看到bedrooms_per_room比rooms_per_household和population_per_househoud与房价中位数的相关性更好

数据准备

准备数据时应该编写函数,而不是手动进行
1.可以在任意数据集上重现这些转换
2.可以逐渐建立转换函数的函数库,可以在以后的项目中使用
3.可以在实时系统中使用这些函数来转换新数据,再输入给出算法
4.可以尝试是、多种转换方式,查看哪种最佳

现在我们先回到一个干净的训练集

housing = strat_train_set.drop("median_house_value", axis=1)
housing_labels = strat_train_set["median_house_value"].copy()
housing_labels.head()

运行结果:
标签

数据清洗

大部分的机器学习算法都无法在缺失的特征上工作,如total_bedrooms属性有部分值的缺失

方法:
1.放弃这些区域的数据
2.放弃整个属性
3.将缺失的值设置为某个值(0, 平均数或中位数)

实现方式:
方法1:housing.dropna(subset=[“total_bedrooms”])
方法2:housing.drop(“total_bedrooms”, axis=1)
方法3:median = housing[“total_bedrooms”].median()
housing[“total_bedrooms”].fillna(median, inplace=True)

scikit-learn提供了一个非常容易上手的类处理缺失值

from sklearn.impute import SimpleImputer

# 新建一个SimppleImputer实例,指定要用属性的中位数替换该属性的缺失值
imputer = SimpleImputer(strategy="median")

# 由于中位数值只能在数值属性上计算,所以我们需要创建一个没有文本属性ocean_proximity数据副本
housing_num = housing.drop("ocean_proximity", axis=1)
# 使用fit()方法,将imputer实例适配到训练数据
imputer.fit(housing_num)
# 这里imputer仅仅只是计算了每个属性的中位数值,并将结果存储在其实例变量statistics_中,
# 虽然只有total_bedrooms这个属性存在缺失,但是我们没办法确定系统启动后新数据是否一定不存在任何缺值,
# 稳妥起见将imputer应用于所有的数值属性值
imputer.statistics_

运行结果:
statistics_

housing_num.median().values

运行结果:
modian_value
用imputer将缺失值替换成中位数值从而完成训练集转换

X = imputer.transform(housing_num)  # 这是一个numpy格式的数据
housing_tr = pd.DataFrame(X, columns=housing_num.columns, index=housing_num.index)  # 因为X得出的是numpy格式的,要将其转换为DataFrame
housing_tr.head()  # 与之前的housing对比发现少了ocean_proximity(因为imputer只能用于数值的计算,在之前的处理中已经将其去掉了)

housing_tr

处理文本和分类属性

# 到目前为止只处理数值属性,现在看一下文本
housing_cat = housing[["ocean_proximity"]]
housing_cat.head(10)  # 默认参数是5,现在选用的参数是10,所以有10条

运行结果:
文本数据
直接使用OrdinalEncoder将文本转换成数值

# 他是有限可能的取值,现在由文本转换为数字
from sklearn.preprocessing import OrdinalEncoder

ordinal_encoder = OrdinalEncoder()
housing_cat_encoded = ordinal_encoder.fit_transform(housing_cat)
housing_cat_encoded[:10]

文本转数值

ordinal_encoder.categories_

运行结果:
categories_
转换成one_hot编码

# 转换为one_hot编码
from sklearn.preprocessing import OneHotEncoder

cat_encoder = OneHotEncoder()
housing_cat_1hot = cat_encoder.fit_transform(housing_cat)  # 输出的是一个scipy的稀疏矩阵而不是numpy数组(只存储非0),这样可以防止存储过多的0元

转换成array格式

housing_cat_1hot.toarray()

运行结果:
array格式的one_hot

cat_encoder.categories_

运行结果:
categories_

自定义转换器

创建一个类,应用fit()(返回self)、transform()、fit_transform()

from sklearn.base import BaseEstimator, TransformerMixin

rooms_ix, bedrooms_ix, population_ix, households_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    # 添加TransformerMixin作为基类可直接得到fit_transform
    # 添加BaseEstimator作为基类(并在构造函数中避免*args和**kargs方法),
    # 可以额外获得两种非常有用的自动调整超参数的方法(get_params()和set_params)
    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:
            bedroom_per_room = X[:, bedrooms_ix] / X[:,rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household, bedroom_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)

转换流水线

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipline = Pipeline([
    ('imputer', SimpleImputer(strategy="median")),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler()),
])

housing_num_tr = num_pipline.fit_transform(housing_num)

转换后结果
转换后

训练和评估训练集

线性回归

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[:5]
some_data_prepared = full_pipeline.transform(some_data)
print("Predictions:", lin_reg.predict(some_data_prepared))
print("Labels:", list(some_labels))

运行结果:
预测
均方差误差

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)

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_predictions, housing_labels)
tree_rmse = np.sqrt(tree_mse)
tree_rmse

运行结果:
0.0

使用交叉验证更好地进行评估

1.划分训练集和验证集
2.K折

交叉验证决策树

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_score = np.sqrt(-scores)
tree_rmse_score

tree_rmse_score

def display_scores(scores):
    print("Scores:", scores)
    print("Mean:", scores.mean())
    print("Standard deviation:", scores.std())
display_scores(tree_rmse_score)

display_scores
交叉验证线性回归

lin_scores = cross_val_score(lin_reg, housing_prepared, housing_labels, scoring="neg_mean_squared_error", cv=10)
lin_rmse = np.sqrt(-lin_scores)
display_scores(lin_rmse)

display_scores_lin_rmse
交叉验证随机森林

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_predictions, housing_labels)
forest_rmse = np.sqrt(forest_mse)
print(forest_rmse)

运行结果:
18746.865963012133

在这里插入代码片
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)

forest_rmse_scores

模型保存和加载

import joblib
joblib.dump(lin_reg, "line_reg.pkl")
my_model_loaded = joblib.load("line_reg.pkl")

模型微调

网格搜索

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, scoring='neg_mean_squared_error', return_train_score=True)
grid_search.fit(housing_prepared, housing_labels)

运行结果:
grid_search

grid_search.best_params_

best_params_

grid_search.best_estimator_

best_estimator_

cvres = grid_search.cv_results_
cvres.keys()

keys

for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)

参数对应的均方差误差
随机搜索:超参数范围较大时优先使用RandomizeSearchCV

分析最佳模型及误差

# 随机森林中重要程度
feature_importances = grid_search.best_estimator_.feature_importances_
feature_importances

importances

extra_attribs = ["room_per_hhold", "pop_per_hhold", "bedrooms_per_room"]
cat_encoder = full_pipeline.named_transformers_["cat"]
cat_one_hot_attribs = list(cat_encoder.categories_[0])
print(num_attribs)
print(extra_attribs)
print(cat_one_hot_attribs)
attributes = num_attribs + extra_attribs + cat_one_hot_attribs
sorted(zip(feature_importances, attributes), reverse=True)

extra_attribs

通过测试集评估系统¶

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)
print(final_mse)
print(final_rmse)

运行结果:
均方误差

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)))

stats

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艾醒(AiXing-w)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值