# 注:在jupyter上运行

# 从一堆数据到训练好数据的机器学习模型,中间有很多过程,每个过程往往又有很多种方法去实现
# 往往新手入门学习时就被淹没在中间的繁杂过程与N种实现之中,我们不妨换一个思路来看,
# 我们准备好数据后,直接切入进行模型训练,然后再进行中间的各个过程优化模型。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

%matplotlib inline

# 首先,我们加载数据集
housing_base = pd.read_csv('housing.csv')
housing = housing_base.copy()

# 简单介绍下数据集
# 这是基于1990年加州普查的加州房产价格数据集
# 里面的特征包括 经度、维度、房屋年龄中位数、总房间数、总卧室数、人口数、家庭数、收入中位数、房屋价值中位数、离大海距离

# 我们来简单想一下这是一个什么样子的任务
# 我们需要使用标注好的数据来预测一个值
# 这是一个需要监督的回归任务,而且是多变量回归任务
# 我们先来使用最基本的线性回顾模型来试一下

# 我们使用线性回归模型来训练数据,并预测
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

def run_linear(data):
    # 我们把数据集分为训练集和测试集两部分
    train_set, test_set = train_test_split(data, test_size=0.2, random_state=0)
    train_set_label = train_set['median_house_value'].copy()
    train_set_data = train_set.drop(columns=['median_house_value'])

    # 拟合模型
    lin_reg = LinearRegression()
    lin_reg.fit(train_set_data, train_set_label)

    # 效果指标的话,我们选择的是回归问题的典型指标 均方根误差(RMSE)
    # 看看模型拟合效果
    housing_predictions = lin_reg.predict(train_set_data)
    lin_mse = mean_squared_error(train_set_label, housing_predictions)
    lin_rmse = np.sqrt(lin_mse)
    print(lin_rmse)

    return train_set, test_set, lin_reg

# ocean_proximity 是文本类型,这里我们先去掉,后面再说为什么
housing_bak = housing.drop(columns=['ocean_proximity'])

#run_linear(housing_bak)
# 上面的方法报错,我们注释掉了,提示数据集中有缺失特征,看来我们要想跑通这个模型,必须要先处理下数据

# 数据清洗
# 首先,我们看一下哪些数据里面有缺失特征
housing.info()

# 我们可以看到,训练数据总量是 16512个,但是total_bedrooms只有16354个,这一列中有缺失特征
# 我们这里使用中位数来填充缺失特征
# 注意,这里中位数的选取,要取用整个数据集的
median = housing['total_bedrooms'].median()
housing['total_bedrooms'].fillna(median, inplace=True)

# 我们来看下填充完后的数据
housing.info()

# 我们可以看到,现在已经简单清洗数据,解决了特征缺失的问题
# 我们继续用这些数据来跑通线性回归模型
housing_bak = housing.drop(columns=['ocean_proximity'])
print("第一次运行线性归回模型")
train_set, tset_set, lin_reg = run_linear(housing_bak)

# 可以看到,现在模型已经跑通了,我们在训练集中挑选一些数据,来看下效果怎么样
train_set_label = train_set['median_house_value'].copy()
train_set_data = train_set.drop(columns=['median_house_value'])
some_data = train_set_data.iloc[:5]
some_label = train_set_label.iloc[:5]

print("predictions:\t", lin_reg.predict(some_data))
print("labels:\t", list(some_label))

# 我们可以看到,效果比较差,但是也还有几分神似,除了第一个数据偏离实际值很多之外,其他四个还算接近

# 我们再来看下整体效果
# 训练集的rmse是69619.27336015804
# 我们来看一下房屋中位价的数据信息

housing['median_house_value'].value_counts()

# 我们可以看到,大多数房屋价格位于20w以内,我们现在的预测标准差是 69619,可以说是非常大了
# 所以,我们的模型还太差,我们需要进一步来优化模型
# 这里简单说一下什么是 RMSE
# RMSE就是均方根误差,一种模型的性能指标,用来衡量模型的效果

# 接下来,我们来逐步优化我们的线性模型
# 我们再来看一下我们的数据集信息
housing.info()

# 我们能够看到,数据总量是 20640,但是total_bedrooms只有20433,所以这个有特征缺失
# 我们在上面训练线性模型的时候也提到这个问题并采用了一种解决方法
# 解决特征缺失的方法有很多,我们这里不啰嗦,就用中位数填充解决就好了

# 我们继续查看数据集信息
housing.head()

# 我们又有两个新的发现

# 一个是不同特征数据集之间,数据范围差别特别大
# median_income大多数是个位数,total_rooms却是几千
# 如果不同特征之间数据范围很大的话,会很影响训练的效果,至于为啥,我们这里也不啰嗦

# 另外一个问题是 ocean_proximity 这一列不是数值类型,而是文本类型
# 我们的模型只能处理数值类型,不能处理这种文本类型,可以看到,上面我们训练的时候
# 是采用去掉这一列来解决的,但是这样很浪费数据,所以我们需要把文本类型特征转化为数值类型

# 我们现在来解决上面的三个问题

def clear_data(housing):
    # 解决特征缺失
    median = housing['total_bedrooms'].median()
    housing['total_bedrooms'].fillna(median, inplace=True)

    # 解决文本特征数值化
    from sklearn.preprocessing import LabelEncoder
    encoder = LabelEncoder()

    housing["ocean_proximity"] = encoder.fit_transform(housing["ocean_proximity"])
    housing.info()

    # 解决数据范围不一致,特征缩放
    # 我们这里使用标准化来进行特征缩放
    from sklearn.preprocessing import MinMaxScaler
    scaler = MinMaxScaler()
    scaler.fit(housing)
    housing = pd.DataFrame(data=scaler.transform(housing), columns=housing.columns)

    return housing

housing = clear_data(housing)
housing.head()

# 我们现在可以看到,刚刚的三个问题都已经解决了
# 上面我们看的时候,是通过数据的文本形式来观察的
# 下面,我们把数据可视化,通过可视化的途径来看看还有么有其他问题

housing.hist(bins=50, figsize=(20,15))
plt.show()

# 通过直方图,我们能够看到两个很明显的问题
# 就是 房屋年龄中位数 和 房屋价值中文数 末端凸起,这明显不是正常现象
# 原因是这两个特征设定上限了
# 除此之外,收入中位数也被设定上限了,末端也有小凸起
# 当然,不一定有明显凸起的都是被设定上限的,但是我们要能够发现这些问题
# 然后确认问题

# 我们现在再来看看模型效果怎么样了
print("第二次运行线性模型")
lin_reg = run_linear(housing)

# 感觉没卵用啊,我们换个模型试试,上面我们用的线性模型,我们换个随机森林模型试试
from sklearn.ensemble import RandomForestRegressor

def run_random(data):
    train_set, test_set = train_test_split(data, test_size=0.2, random_state=0)

    train_set_label = train_set['median_house_value'].copy()
    train_set_data = train_set.drop(columns=['median_house_value'])

    random_reg = RandomForestRegressor()
    random_reg.fit(train_set_data, train_set_label)

    housing_predictions = random_reg.predict(train_set_data)
    random_mse = mean_squared_error(train_set_label, housing_predictions)
    random_rmse = np.sqrt(random_mse)

    print(random_rmse)

    return train_set, test_set, random_reg

print("第一次运行随机森林模型")
train_set, test_set, random_reg = run_random(housing)

# 可以看到,这下牛逼了,效果提升了一个档次,看来模型选择特别重要

# 但是模型的优化还没有结束,我们继续来优化模型

# 我们前面使用了所有的特征来进行训练的,我们来看一下,这些特征之间有么有什么关联
# 我们使用皮尔逊相关系数,来看下各个特征和我们预测的房屋价格特征的关系

corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

# 我们看到,人口、维度、经度,这三个特征,和房屋价格竟然呈负相关,纳尼,去掉试试
housing_bak = housing.drop(columns=["population","longitude","latitude"])

print("第二次随机森林预测")
train_set, test_set, random_reg = run_random(housing_bak)

# 我们可以看到,去掉这三个负相关的特征后,效果变差了一些
# 这是为什么呢,是因为皮尔逊相关系数只是揭示的线性关系
# 比如经度、维度可以确定位置,不同位置的人口量和房屋价格的线性关系就不同
# 这也是为什么我们使用随机森林模型,要比线性模型效果好很多的原因
# 因为随机森林可以发现很多非线性关系

# 我们再来看看能不能组合现有的特征形成更好的特征

# 再看下所有特征的信息
housing.head()

# 我们用 total_rooms 和 households 组合尝试一下
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]

corr_matrix = housing.corr()
corr_matrix["median_house_value"].sort_values(ascending=False)

# 我们可以看到,这两个特征的组合,是要比他们单个特征和房屋价格的关系更相关
# 我们现在再来看看模型效果
housing = housing_base.copy()
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]

housing = clear_data(housing)

print("第三次随机森林预测")
train_set, test_set, random_reg = run_random(housing)

# 我们可以看到,这样的话,效果和之前几乎一样
# 虽然效果和之前几乎一样,但是这是一个不错的方法可以去发现更好的特征

# 下面,我们用测试数据,来看看模型效果
test_set_label = test_set['median_house_value'].copy()
test_set_data = test_set.drop(columns=['median_house_value'])

random_reg = RandomForestRegressor()
random_reg.fit(test_set_data, test_set_label)

housing_predictions = random_reg.predict(test_set_data)
random_mse = mean_squared_error(test_set_label, housing_predictions)
print("随机森林测试数据预测:")

some_data = test_set_data.iloc[:5]
some_label = test_set_label.iloc[:5]

print("predictions:\t", random_reg.predict(some_data))
print("labels:\t", list(some_label))

# 整个过程大致就这样了,我们的模型取得了还可以的效果
# 我们来梳理一下整个过程
# 我们的整个过程经历了大约有下面一系列过程

# 获取数据
# 确定任务目标、类型
# 发现数据集存在问题
# 清洗数据
# 确定模型
# 训练模型
# 模型调优

# 大致就是这个流程,这里不过分追究整个流程的合理性

# 其实我们整个这个笔记,最想说明的一个点是:
# 我们整个过程中的每个小过程,我们采取的都不是最佳实践
# 还有更多更好的实现方法,更好的方法可以采用
# 包括我们采用的机器学习库,除了sklearn之外,还有很多其他更优秀的机器学习库
# 但是我们要明白的是,我们入门学习的时候,不要过于挣扎在各种各样的方法之中
# 每个过程,先选择一个能用的方法,能够把问题解决掉
# 然后对整个流程有了一定认识,再去慢慢调优,去学习更好的实现
# 这样学习起来,会相对更顺利一些,更轻松一些

# 注:参考《sklearn与TensorFlow机器学习实用指南第二章》

原创文章,转载请注明地址: 文章地址