端到端机器学习项目-加州房价预测(一)

456 阅读9分钟

上一篇笔记大致了解了机器学习的基础知识,以及实现了一个简单的Demo。接下来开始将要深入了解如何实现一个端到端的项目-加州房价预测

1. 什么是端到端?

MBA智库·百科给出的定义是:

端到端流程指以客户、市场、外部政府或机构及企业利益相关者为输入或输出点的,一系列连贯、有序的活动的组合。是从客户需求端出发,到满足客户需求端去,提供端到端服务,端到端的输入端是市场,输出端也是市场。

可以简单的理解为,一个端到端项目也就是由输入端,经过机器学习的处理进行直接输出。那么一个端到端的机器学习项目应该怎么进行开发呢?

一般开发项目时会经历以下的步骤:

  1. 从大局了解项目
  2. 获得数据
  3. 探索和可视化数据,并从中获取想法
  4. 为算法预处理数据
  5. 选择并训练模型
  6. 微调模型
  7. 展示解决方案
  8. 启动、监控和维护方案

后面的项目开发流程,会按照这个清单的步骤执行。

2. 知识预备

标签和特征值

特征是用于来自数据的属性和其值,他是取决于上下文的,可能有多个含义。

标签则是我们所需要的解决方案。

机器学习就是提取有用的特征,构造出特征到标签的映射

之后还会有各种公式会用到如下几种机器学习符号:

  • m\textit{m} : 表示数据集的实例数,以小写斜体字体表示标量
  • x(i)\boldsymbol{x}^{(i)} :表示数据集中第i个实例的所有特征值(不包含标签)的向量,以小写粗斜体字体表示向量
  • y(i)\boldsymbol{y}^{(i)} :表示数据集中第i个实例的标签
  • h\mathit{h} : 表示预测函数,当系统输入一个实例的特征向量x(i)\boldsymbol{x}^{(i)}时,他会输出一个预测值y^=h(x(i))\hat{ \textit{y} } = \mathit{h}( \textit{x}^{(i)} )
  • X\boldsymbol{X} : 表示包含所有实例的所有特征值(不包括标签)的矩阵,以大写粗斜体字体表示矩阵
    • 矩阵表示 X=((x(1))T(x(2))T(x(i1))T(x(i))T)=(112211231)\boldsymbol{X} = { \begin{pmatrix} (\boldsymbol{x}^{(1)})^{T} \\ (\boldsymbol{x}^{(2)})^{T} \\ \vdots \\(\boldsymbol{x}^{(i-1)})^{T} \\ (\boldsymbol{x}^{(i)})^{T} \\ \end{pmatrix} } = {\begin{pmatrix} -11 & 22 & 112 & 31 & \\ \vdots&\vdots &\vdots & \vdots & \\ \end{pmatrix} }

3 开始项目:加州房价预测

根据已有的数据,来预测未来加州某区域内房价是否值得投资

3.1 从大局了解项目

我们已知道的数据的指标将含有:街区人口数量、收入中位数、房价中位数等等。 (注:街区是美国人口调查发布样本数据的最小地理单位)

3.2 明确问题

将数据进行分析,最后将输出对未来的房价中位数的预测。要清楚这些问题是很重要,因为往往我们制作的项目,可能只是一个完整的流水线系统的模块组件,本模块接受上个模块输入,输出给下一个模块需要的数据。如果不能够明确本模块的任务,将会导致之后模块执行或者判断错误。

一个序列的数据处理组件称为一个数据流水线。组件通常异步执行的,每个组件之间基本以数据仓库对接。一个模块从数据仓库拉取数据并处理完之后放入下一个数据仓库,下一个模块就可以根据需要从数据仓库拉取数据。

本模块部分流水线.png

本模块流水线

3.3 选择模型

现在所拥有的是数千个区域的房价中位数和其他数据,而要实现的是对未来房价中位数的预测,也就是判断是否值得将来的投资。

首先很容易知道,这是一个有监督学习模型,因为拥有房价中位数(标签)的,而且还是一个回归任务(回归是对已有的数据样本点进行拟合,再根据拟合出来的函数,对未来进行预测),需要预测未来的房价中位数。同时还是多重回归问题,会用到多个特征(区域人口、收入中位数等)。

当前的数据量不算太大,如果数据庞大可能还需要拆分成多个批处理学习或在线学习技术。

3.4 选择性能指标

回归任务衡量性能指标通常会用到两个公式,均方根误差(Root Mean Square Error,RMSE)平均绝对误差(Mean Absolute Error,MAE),也是用于假设h(预测函数)在一组实例中测量的成本函数

从上一篇笔记知道,我们需要做优化模型,使成本函数的差距最小化。

均方根误差(RMSE):他给出了系统通常会在预测中产生多大误差,对于较大的误差,权重较高。 公式:均方根误差(RMSE)

RMSE(X,h)=1mi=1m(h(x(i))y(i))2RMSE(\boldsymbol{X} , h)=\sqrt{\frac{1}{\textit{m}}\sum_{i=1}^{\textit{m}}(\mathit{h}(\boldsymbol{x}^{(i)}) - \boldsymbol{y}^{(i)} )^2}

平均绝对误差(MAE):是所有单个观测值与算术平均值的偏差的绝对值的平均。平均绝对误差可以避免误差相互抵消的问题,因而可以准确反映实际预测误差的大小。 公式:平均绝对误差(MAE)

MAE(X,h)=1mi=1mh(x(i))y(i)MAE(\boldsymbol{X} , h)= \frac{1}{\textit{m}}\sum_{i=1}^{\textit{m}}\left|h(\boldsymbol{x}^{(i)}) - y^{(i)} \right|

这两个公式其实就是测量两个向量(预测值向量和目标值向量)之间距离的方法。还有其他各种距离度量或范数(具有“距离”概念的函数):

  • 计算平方和的根(RMSE)和欧几里得范数相对应,也成为L2L_{2} 范数,记作2\left\| \cdot \right\|_{2}
  • 计算绝对值之和(MAE)对应L1L_{1} 范数,记作1\left\| \cdot \right\|_{1} ,有时也称为"曼哈顿范数",如果是在正交的城市街区行动,它可以测量城市中两点的距离
  • 包含n个元素的向量v\boldsymbol{v}LkL_{k} 范数定义为vk=(v0k+v1k++vnk)k\left\| \boldsymbol{v} \right\|_{k}=\sqrt[k]{( \left| \boldsymbol{v}_{0} \right|^{k} + \left| \boldsymbol{v}_{1} \right|^{k} + \cdot\cdot\cdot + \left| \boldsymbol{v}_{n} \right|^{k} )}L0L_{0} 给出了向量中的非零元素数量,LL_{\infty } 给出了向量中最大绝对值
  • 范数指数越大,他越关注大值而忽略小值。也就是RMSE对异常值比MAE更加敏感。但是当离群值呈指数形式稀有时(如钟形曲线),RMSE表现非常好。

4. 获取数据

本项目的数据文件保存在远程服务器上,因此会用到urllib包(Python的URL处理包,可用于访问网页,并且与网页交互)。当我们的数据不在本地是,我们通常是需要先编写脚本去自动获取数据,因此获取数据函数可以单独抽取出来做成函数或者工具包。

# 远程下载数据代码
# 数据下载目录地址
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
# 本地保存地址
HOUSING_PATH = os.path.join("datasets", "housing")
# 完整数据压缩包URL
HOUSING_URL = DOWNLOAD_ROOT + "datasets/housing/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()

urllib包一般可以用于爬虫和网络交互,其他的功能还有:

# 1.自定义Request请求,获取网页内容
# 定义好要访问的网页URL
url=''
# 根据网站需求配置到请求头,以防止无法登录网页
headers = {
        'User-Agent': '',
        'Accept':'*/*',
        'Connection':'keep-alive',
        'Cookie':''
    }
# 发送请求获得网页内容,最后要注意设置好对应的网页编码
request = request.Request(url=url, headers=headers)
response = request.urlopen(request)
content = response.read().decode('utf-8')


# 2.远程下载文件(可以是视频、音频和图片等)
# 远程资源文件
source_url = ''
# 从source_url远程下载的文件,可以通过file_path保存在自定义的本地和修改文件名称
urllib.request.urlretrieve(source_url, file_path)


# 3.post请求需要使用urlencode(data)编码否则无法发送请求
data = {
    'key': 'value'
}
data = parse.urlencode(data).encode('utf-8')
new_req = request.Request(url=url, data=data, headers=headers)

# 4.使用代理访问
proxies={
    'https': proxy_url
}
# 模拟浏览器发送请求,获得响应
handler = request.ProxyHandler(proxies=proxies)
opener = request.build_opener(handler)
response = opener.open(Request)

需要注意的是本项目的fetch_housing_data()是需要联网下载的,有时候网不好可能会导致网络出现连接错误,如果数据资源在外网的,也可能需要通过代理来访问或者下载。其中需要注意一些错误,如网址URL写错了,会出现URLError,这是你需要检查URL的路径是不是错误,如果是资源的名字写错了,找不到该文件,可能会出现HTTPError,这时候要注意检查文件名。

OK!如果一切正常我们应该是可以拿到我们需要的文件,并且保存在我们定义好的路径下了。

房屋数据csv文件

4.1 检查数据和查看数据结构

既然拿到了数据我们,就需要看一下里面都有什么,首先需要先读取csv数据。代码如下

def load_housing_data():
    csv_path = os.path.join(HOUSING_PATH, 'housing.csv')
    return pd.read_csv(csv_path)

使用pandas的read_csv(csv_path)加载本地的csv数据,并且返回DataFrame二维数组。那么什么是DaraFrame呢?

Pandas的数据结构主要是两个: SeriesDataFrame

Series 是一种类似于一维数组的对象,它由一组数据(各种Numpy数据类型)以及一组与之相关的数据标签(即索引)组成。

DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共同用一个索引)。

DataFrame和Series.jpg

从上面的图来更直观可以看出什么是Series和DataFrame。

pandas.Series( data, index, dtype, name, copy)

  • data:一组数据(ndarray 类型)。
  • index:数据索引标签,如果不指定,默认从 0 开始。
  • dtype:数据类型,默认会自己判断。
  • name:设置名称。
  • copy:拷贝数据,默认为 False。

Series包含有索引(index)和数据(data),因此如果我们要访问series1中index=0的值可以用series1[0]或者series1.iloc[0]

pandas.DataFrame( data, index, columns, dtype, copy)

  • data:一组数据(ndarray、series, map, lists, dict 等类型)。
  • index:索引值,或者可以称为行标签。
  • columns:列标签,默认为 RangeIndex (0, 1, 2, …, n) 。
  • dtype:数据类型。
  • copy:拷贝数据,默认为 False。

DataFrame 不仅有索引和数据,还有列(columns),因此我们可以单独通过pd1['c1']查看DataFrame的c1这一列,当然由图也可以很容易就得知pd1['c1']也是一个Series。而我们查找df1某一行,就需要使用df1.iloc[index_num]访问index_num-1行。当然也可以使用别的方法访问,如下代码:

list1 = [1, 'hello', 3]
list2 = [4, 'hi', 6]
data = [list1, list2]
df1 = pd.DataFrame(data=data, columns={"c1", "c2", "c3"})
# 使用set_index把c2列设置为index
df1.set_index('c2', inplace=True)
print(df1)
# 使用loc可以获得列值为自定义的value的那一行
print("输出c1列中值为1的那一行")
print(df1.loc['hello'])

# output
       c3  c1
c2
hello   1   3
hi      4   6
输出c1列中值为1的那一行
c3    1
c1    3

现在我们基本知道了我们需要的数据格式,并且也能够使用一些简单的API去查看数据。当然这还是不够的,接下来还需要更加深入了解DataFrame的其他API,如head()、info()和describe()等。这部分我将其放入下一篇笔记,现在让我们先好好消化上面的内容吧!