以下为我的机器学习笔记,参考了黄海广博士的笔记

课程:机器学习 - 斯坦福大学 - 吴恩达 2014 @ Coursera

https://www.coursera.org/course/ml

# 第二周 单变量线性回归 Univariate Linear Regression

这一节通过房价预测的例子介绍了线性回归算法以及监督学习的完整流程

# 模型描述

🌰​ 例子呈现

书接上回,我们想要预测住房的价格。我们有一个数据集,它包含俄勒冈州波特兰市的住房价格。现在你的朋友张三有一个 1250 平方尺的房子想要出手,你要根据这些数据告诉他这房子能卖多少钱。

image-20210116235709508

要解决这个问题,你可以做的一件事就是构建一个模型,也许是条直线,从这个数据模型上来看,也许你可以告诉你的朋友,他能以大约 220000 (美元) 左右的价格卖掉这个房子。这就是监督学习算法的一个例子。

在这个例子中,我们有一个数据集,换用成专业术语,我们把它叫做 训练集(Training Set) ,上一个问题的训练集如下表所示:

image-20210117000105297

我们对这些数据用不同的标记来描述
mm 代表训练集中实例的数量

xx 代表特征 / 输入变量

yy 代表目标变量 / 输出变量

(x,y)(x,y) 代表训练集中的实例

(x(i),y(i))(x^{^{(i)}} , y^{^{(i)}}) 代表第ii 个观察实例

hh 代表学习算法的解决方案或函数也称为假设(hypothesis

image-20210117000825387

这就是一个监督学习算法的工作方式,我们可以看到这里有我们的训练集里房屋价格 我们把它喂给我们的学习算法,学习算法的工作了,然后输出一个函数,通常表示为小写hh 表示。hh 代表 hypothesis(假设),hh 表示一个函数,输入是房屋尺寸大小,就像你朋友想出售的房屋,因此hh 根据输入的xx 值来得出yy 值,yy 值对应房子的价格 因此,hh 是一个从xxyy 的函数映射。

我将选择最初的使用规则hh 代表 hypothesis,因而,要解决房价预测问题,我们实际上是要将训练集 “喂” 给我们的学习算法,进而学习得到一个假设hh,然后将我们要预测的房屋的尺寸作为输入变量输入给hh,预测出该房屋的交易价格作为输出变量输出为结果。那么,对于我们的房价预测问题,我们该如何表达 hh

一种可能的表达方式为:hθ(x)=θ0+θ1xh_{\theta }(x)=\theta _{0}+\theta _{1}x,因为只含有一个特征 / 输入变量,因此这样的问题叫作单变量线性回归问题。

# 代价函数

这一节定义了代价函数的概念,这有助于我们弄清楚如何得到和数据相拟合的最优直线。

如图:

在线性回归中我们有一个像这样的训练集,mm 代表了训练样本的数量,比如 m=47m = 47。而我们的假设函数,也就是用来进行预测的函数,是这样的线性函数形式:hθ(x)=θ0+θ1xh_\theta \left( x \right)=\theta_{0}+\theta_{1}x。我们现在要做的便是为我们的模型选择合适的参数parametersθ0\theta_{0}θ1\theta_{1},在房价问题这个例子中便是直线的斜率和在yy 轴上的截距。

我们选择的参数决定了我们得到的直线相对于我们的训练集的准确程度,模型所预测的值与训练集中实际值之间的差距(下图中蓝线所指)就是建模误差modeling error)。

我们的目标是选择出可以使得建模误差的平方和能够最小的模型参数。 即使得代价函数 J(θ0,θ1)=12mi=1m(hθ(x(i))y(i))2J \left( \theta_0, \theta_1 \right) = \frac{1}{2m}\sum\limits_{i=1}^m \left( h_{\theta}(x^{(i)})-y^{(i)} \right)^{2} 最小。

我们绘制一个等高线图,三个坐标分别为θ0\theta_{0}θ1\theta_{1}J(θ0,θ1)J(\theta_{0}, \theta_{1})

则可以看出在三维空间中存在一个使得J(θ0,θ1)J(\theta_{0}, \theta_{1}) 最小的点。

代价函数也被称作平方误差函数,有时也被称为平方误差代价函数。我们之所以要求出误差的平方和,是因为误差平方代价函数,对于大多数问题,特别是回归问题,都是一个合理的选择。还有其他的代价函数也能很好地发挥作用,但是平方误差代价函数可能是解决回归问题最常用的手段了。

也许这个函数J(θ0,θ1)J(\theta_{0}, \theta_{1}) 有点抽象,可能你仍然不知道它的内涵,在接下来的内容中,我们会更进一步解释代价函数 J 的工作原理,并尝试更直观地解释它在计算什么,以及我们使用它的目的。

# 代价函数的直观理解 I

这一节内容会将上述的代价函数具象化,看看代价函数到底是在干什么。

# 代价函数的直观理解 II

上面是代价函数的样子,这样画出来很麻烦,我们不可能每次都画三维图来找,等高线图可以帮我们解决这个问题,它可以在二维平面用不同的线条围成的圈表示不同的高度。无论是三维图还是等高线图,我们都可以看出在三维空间中存在一个使得J(θ0,θ1)J(\theta_{0}, \theta_{1}) 最小的点。

那么问题来了,我们需要一种有效的算法,能够自动地找出这些使代价函数JJ 取最小值的参数θ0\theta_{0}θ1\theta_{1} 来。我们不希望编个程序把这些点画出来,然后人工的方法来读出这些点的数值,这太麻烦了。以后我们会遇到更复杂、更高维度、更多参数的情况,这些情况是很难画出图的。因此我们真正需要的是编写程序来找出这些最小化代价函数的θ0\theta_{0}θ1\theta_{1} 的值,在下面的内容中,我们将介绍一种算法,能够自动地找出能使代价函数JJ 最小化的参数θ0\theta_{0}θ1\theta_{1} 的值。

# 梯度下降

梯度下降是一个用来求函数最小值的算法,我们将使用梯度下降算法来求出代价函数J(θ0,θ1)J(\theta_{0}, \theta_{1}) 的最小值。

梯度下降背后的思想是:开始时我们随机选择一个参数的组合(θ0,θ1,......,θn)\left( {\theta_{0}},{\theta_{1}},......,{\theta_{n}} \right),计算代价函数,然后我们寻找下一个能让代价函数值下降最多的参数组合。我们持续这么做直到找到一个局部最小值 local minimum ,因为我们并没有尝试完所有的参数组合,所以不能确定我们得到的局部最小值是否便是全局最小值 global minimum ,选择不同的初始参数组合,可能会找到不同的局部最小值。

想象一下这是一个公园,而你站在这座山的红色区域,现在你想尽快地下山,你会怎么做?在梯度下降算法中,我们要做的就是旋转 360 度,看看我们的周围,诶,这边比较陡,从这儿下山肯定快,那我就向着这个方向走一小段。走了一段之后,再次观察,发现另外一个方向更陡,于是我们又向着那个方向移动了一小段,然后一步步地重复上述步骤,直到我们到达了最低点。

批量梯度下降 batch gradient descent 算法的公式为:

其中aa 是学习率 learning rate ,它决定了我们沿着能让代价函数下降程度最大的方向向下迈出的步子有多大,在批量梯度下降中,我们每一次都同时让所有的参数减去学习速率乘以代价函数的导数。

在梯度下降算法中,还有一个更微妙的问题,梯度下降中,我们要更新θ0{\theta_{0}}θ1{\theta_{1}} ,当 j=0j=0j=1j=1 时,会产生更新,所以你将更新J(θ0)J\left( {\theta_{0}} \right)J(θ1)J\left( {\theta_{1}} \right)。实现梯度下降算法的微妙之处是,在这个表达式中,如果你要更新这个等式,你需要同时更新θ0{\theta_{0}}θ1{\theta_{1}},我的意思是在这个等式中,我们要这样更新:

θ0{\theta_{0}}:= θ0{\theta_{0}} ,并更新θ1{\theta_{1}}:= θ1{\theta_{1}}

实现方法是:你应该计算公式右边的部分,通过那一部分计算出θ0{\theta_{0}}θ1{\theta_{1}} 的值,然后同时更新θ0{\theta_{0}}θ1{\theta_{1}}

让我进一步阐述这个过程:

在梯度下降算法中,这是正确实现同时更新的方法,同时也是更自然的实现方法。当人们谈到梯度下降时,他们的意思就是同步更新。

# 梯度下降的直观理解

在之前的视频中,我们给出了一个数学上关于梯度下降的定义,本次视频我们更深入研究一下,更直观地感受一下这个算法是做什么的,以及梯度下降算法的更新过程有什么意义。梯度下降算法如下:

θj:=θjαθjJ(θ){\theta_{j}}:={\theta_{j}}-\alpha \frac{\partial }{\partial {\theta_{j}}}J\left(\theta \right)

描述:对θ\theta 赋值,使得J(θ)J\left( \theta \right) 按梯度下降最快方向进行,一直迭代下去,最终得到局部最小值。其中aa 是学习率,它决定了我们沿着能让代价函数下降程度最大的方向向下迈出的步子有多大。

对于这个问题,求导的目的,基本上可以说取这个红点的切线,就是这样一条红色的直线,刚好与函数相切于这一点,让我们看看这条红色直线的斜率,就是这条刚好与函数曲线相切的这条直线,这条直线的斜率正好是这个三角形的高度除以这个水平长度,现在,这条线有一个正斜率,也就是说它有正导数,因此,我得到的新的θ1{\theta_{1}}θ1{\theta_{1}} 更新后等于θ1{\theta_{1}} 减去一个正数乘以aa

这就是我梯度下降法的更新规则:θj:=θjαθjJ(θ){\theta_{j}}:={\theta_{j}}-\alpha \frac{\partial }{\partial {\theta_{j}}}J\left( \theta \right)

让我们来看看如果aa 太小或aa 太大会出现什么情况:

如果aa 太小了,即我的学习速率太小,结果就是只能这样像小宝宝一样一点点地挪动,去努力接近最低点,这样就需要很多步才能到达最低点,所以如果aa 太小的话,可能会很慢,因为它会一点点挪动,它会需要很多步才能到达全局最低点。

如果aa 太大,那么梯度下降法可能会越过最低点,甚至可能无法收敛,下一次迭代又移动了一大步,越过一次,又越过一次,一次次越过最低点,直到你发现实际上离最低点越来越远,所以,如果aa 太大,它会导致无法收敛,甚至发散。

现在,我还有一个问题,当我第一次学习这个地方时,我花了很长一段时间才理解这个问题,如果我们预先把θ1{\theta_{1}} 放在一个局部的最低点,你认为下一步梯度下降法会怎样工作?

假设你将θ1{\theta_{1}} 初始化在局部最低点,在这儿,它已经在一个局部的最优处或局部最低点。结果是局部最优点的导数将等于零,因为它是那条切线的斜率。这意味着你已经在局部最优点,它使得θ1{\theta_{1}} 不再改变,也就是新的θ1{\theta_{1}} 等于原来的θ1{\theta_{1}},因此,如果你的参数已经处于局部最低点,那么梯度下降法更新其实什么都没做,它不会改变参数的值。这也解释了为什么即使学习速率aa 保持不变时,梯度下降也可以收敛到局部最低点。

我们来看一个例子,这是代价函数J(θ)J\left( \theta \right)

我想找到它的最小值,首先初始化我的梯度下降算法,在那个品红色的点初始化,如果我更新一步梯度下降,也许它会带我到这个点,因为这个点的导数是相当陡的。现在,在这个绿色的点,如果我再更新一步,你会发现我的导数,也即斜率,是没那么陡的。随着我接近最低点,我的导数越来越接近零,所以,梯度下降一步后,新的导数会变小一点点。然后我想再梯度下降一步,在这个绿点,我自然会用一个稍微跟刚才在那个品红点时比,再小一点的一步,到了新的红色点,更接近全局最低点了,因此这点的导数会比在绿点时更小。所以,我再进行一步梯度下降时,我的导数项是更小的,θ1{\theta_{1}} 更新的幅度就会更小。所以随着梯度下降法的运行,你移动的幅度会自动变得越来越小,直到最终移动幅度非常小,你会发现,已经收敛到局部极小值。

回顾一下,在梯度下降法中,当我们接近局部最低点时,梯度下降法会自动采取更小的幅度,这是因为当我们接近局部最低点时,很显然在局部最低时导数等于零,所以当我们接近局部最低时,导数值会自动变得越来越小,所以梯度下降将自动采取较小的幅度,这就是梯度下降的做法。所以实际上没有必要再另外减小aa

这就是梯度下降算法,你可以用它来最小化任何代价函数JJ,不只是线性回归中的代价函数JJ

# 梯度下降的线性回归

在这一节中,我们要将梯度下降和代价函数相结合。并将其应用于具体的拟合直线的线性回归算法里。

梯度下降算法和线性回归算法比较如图:

对我们之前的线性回归问题运用梯度下降法,关键在于求出代价函数的导数,即:

θjJ(θ0,θ1)=θj12mi=1m(hθ(x(i))y(i))2\frac{\partial }{\partial { {\theta }_{j} } }J({ {\theta }_{0} },{ {\theta }_{1} })=\frac{\partial }{\partial { {\theta }_{j} } }\frac{1}{2m}{ {\sum\limits_{i=1}^{m}{\left( { {h}_{\theta } }({ {x}^{(i)} })-{ {y}^{(i)} } \right)} }^2}

j=0j=0 时:

θ0J(θ0,θ1)=1mi=1m(hθ(x(i))y(i))\frac{\partial }{\partial { {\theta }_{0} } }J({ {\theta }_{0} },{ {\theta }_{1} })=\frac{1}{m}{ {\sum\limits_{i=1}^{m}{\left( { {h}_{\theta } }({ {x}^{(i)} })-{ {y}^{(i)} } \right)} } }

j=1j=1 时:

θ1J(θ0,θ1)=1mi=1m((hθ(x(i))y(i))x(i))\frac{\partial }{\partial { {\theta }_{1} } }J({ {\theta }_{0} },{ {\theta }_{1} })=\frac{1}{m}\sum\limits_{i=1}^{m}{\left( \left( { {h}_{\theta } }({ {x}^{(i)} })-{ {y}^{(i)} } \right)\cdot { {x}^{(i)} } \right)}

则算法改写成:

Repeat {

θ0:=θ0a1mi=1m(hθ(x(i))y(i)){\theta_{0}}:={\theta_{0}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{ \left({ {h}_{\theta } }({ {x}^{(i)} })-{ {y}^{(i)} } \right)}

θ1:=θ1a1mi=1m((hθ(x(i))y(i))x(i)){\theta_{1} }:={\theta_{1} }-a\frac{1}{m}\sum\limits_{i=1}^{m}{\left( \left({ {h}_{\theta } }({ {x}^{(i)} })-{ {y}^{(i)} } \right)\cdot { {x}^{(i)} } \right)}

}

上面这一算法,有时也称为批量梯度下降,它指的是在梯度下降的每一步中,我们都用到了所有的训练样本,就是上式的求和项。因此,批量梯度下降法这个名字说明了我们需要考虑所有这一 "批" 训练样本。而事实上,有时也有其他类型的梯度下降法,不是这种 "批量" 型的,不考虑整个的训练集,而是每次只关注训练集中的一些小的子集。如果你对线性代数熟悉的话,你或许知道正规方程 normal equations ,后面我们也将介绍这些方法。实际上在数据量较大的情况下,梯度下降法比正规方程要更适用一些。

这就是这一节的内容,我们学到了第一个机器学习算法 —— 梯度下降法。

# 作业

  1. 简单练习

    输出一个 5*5 的单位矩阵

    代码:

    import numpy as np
    A = np.eye(5)

    2. 单变量线性回归

    这个部分需要根据城市人口数量,预测开小吃店的利润。
    数据在 ex1data1.txt 里,第一列是城市人口数量,第二列是该城市小吃店利润。

    代码:

    #2  Linear regression with one variable
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    path = './ex1data1.txt'
    data = pd.read_csv(path, header = None, names=['Population', 'Profit']) #读取数据并赋予列名
    data.plot(kind='scatter', x='Population', y='Profit', figsize=(12,8))
    plt.show()
    def computeCost(X, y, theta):
        inner = np.power( ( (X * theta.T) - y), 2)
        return np.sum( inner ) / ( 2 * len( X ) )
    #在训练集中添加一列,以便我们可以使用向量化的解决方案来计算代价和梯度。
    data.insert(0, 'Ones', 1)
    #变量初始化
    # set X (training data) and y (target variable)
    cols = data.shape[1]
    X = data.iloc[:, 0:cols-1]      #X 是所有行,去掉最后一列
    y = data.iloc[:, cols-1:cols]   #y 是所有行,最后一列
    #观察下 X (训练集) and y (目标变量) 是否正确.
    #print(X.head())
    #print(y.head())
    #代价函数是应该是 numpy 矩阵,所以我们需要转换 X 和 Y,然后才能使用它们。 我们还需要初始化 theta。
    X = np.matrix(X.values)
    y = np.matrix(y.values)
    theta = np.matrix(np.array([0,0]))
    #print (theta) #theta 是一个 (1,2) 矩阵
    #print (X.shape, theta.shape, y.shape)   #观察维度
    computeCost( X, y, theta)   #计算代价函数 (theta 初始值为 0)
    #批量梯度下降
    def gradientDescent( X, y, theta, alpha, iters):
        temp = np.matrix(np.zeros(theta.shape))
        parameters = int( theta.ravel().shape[1])
        cost = np.zeros(iters)
        for i in range(iters):
            error = ( X * theta.T) - y
            for j in range(parameters):
                term = np.multiply( error, X[:, j])
                temp[0,j] = theta[0,j] - (alpha / len(X)) * np.sum(term)
            theta = temp
            cost[i] = computeCost(X, y, theta)
        return theta, cost
    alpha = 0.01    #学习率
    iters = 1000    #迭代次数
    #现在让我们运行梯度下降算法来将我们的参数 θ 适合于训练集。
    g, cost = gradientDescent( X, y, theta, alpha, iters)   #theta & cost
    #print(g)
    #最后,我们可以使用我们拟合的参数计算训练模型的代价函数(误差)。
    computeCost(X, y, g)
    #现在我们来绘制线性模型以及数据,直观地看出它的拟合。
    x = np.linspace(data.Population.min(), data.Population.max(), 100)
    f = g[0,0] +(g[0,1] * x)
    fig, ax = plt.subplots(figsize = (12,8))
    ax.plot(x, f, 'r', label = 'Prediction')
    ax.scatter(data.Population, data.Profit, label = 'TrainingData')
    ax.legend(loc = 2)
    ax.set_xlabel('Population')
    ax.set_ylabel('Profit')
    ax.set_title('Predicted Profit vs. Population Size')
    plt.show()
    #由于梯度方程式函数也在每个训练迭代中输出一个代价的向量,所以我们也可以绘制。 请注意,代价总是降低 - 这是凸优化问题的一个例子。
    fig, ax = plt.subplots(figsize=(12,8))
    ax.plot(np.arange(iters), cost, 'r')
    ax.set_xlabel('Iterations')
    ax.set_ylabel('Cost')
    ax.set_title('Error vs. Training Epoch')
    plt.show()