机器学习入门篇——一、线性回归(两个公式就踏入门槛)

  • 一、准备工作

1. 入门之前你至少得了解一点数学中的的知识,比如说导数,编导,链式求导等,再比如说矩阵,向量,矩阵的简单运算等,还有一点概率论的知识,例如概率,条件概率等。

2. 接下来还得准备一个写代码的环境,这里我推荐anaconda,这是一个集成开发环境,可以让你省去好多事,不论你是Windows平台还是Linux平台都可以安装使用它(具体的安装步骤自行百度,论坛中的教程很多)刚开始学习的时候并不需要太多的第三方库,有python环境,加上numpy(这个用来做一些数学运算)和matplotlib(这个东东的话是用来将我们做的一些过程和结果可视化)即可。

3. 最重要的是你得掌握一点python的基础知识,应该能够熟练阅读和编写包含基础编程结构(例如,函数定义/调用、列表和字典、循环和条件表达式)的 Python 代码。

  • 二、基本的概念

  1.    机器学习又分为有监督学习(Supervised Learning)无监督学习(Unsupervised Learning)强化学习(Reinforcement Learning)(这一部分暂时涉及不到,那就以后再说吧!!!),大致的话就是下面这种情况。

                                               

      有监督学习   用已知某种或某些特性的样本作为训练集,以建立一个数学模型,再用已建立的模型来预测未知样本,此种方法称为有监督学习,是最常见的机器学习方法之一。常见的有监督学习算法有线性回归(linear regression),逻辑回归(logistic regression),支持向量机(Support Vector Machines),决策树(decision trees),K-近邻(k-nearest neighbor algorithm)等

      无监督学习  现实生活中常常会有这样的问题:缺乏足够的先验知识,因此难以人工标注类别或进行人工类别标注的成本太高。很自然地,我们希望计算机能代我们完成这些工作,或至少提供一些帮助。根据类别未知(没有被标记)的训练样本解决模式识别中的各种问题,称之为无监督学习。典型的无监督学习算法就是聚类。

三、回归问题——线性回归

对于机器学习,拿线性回归来入门再好不过了,因为它可以是最简单的如y=wx形式,也可以是稍微复杂一点的y=wx+b形式,其实我们都知道这就是一个线性方程,或者说这是一个一次函数,用官方一点的术语描述就是输入向量x,经过函数映射:𝑓𝜃: 𝒙 → 𝑦后得到输出𝑦,其中𝜃为函数𝑓自 身的参数,这里我们拿y=wx+b来说事,即线性变换:。此时我们可以绘制出输出𝑦和输入𝑥的变化趋势,如下图 所示,随着输入信号𝑥 的增加,输出电平𝑦也随之线性增加,其中𝑤参数可以理解为直线的斜率(Slope),b 参数为 直线的偏置(Bias)。

                                                                  

对于某一点来说,x和y的映射关系是未知但是确定的。两点即可确定一条直线,为了估计w和b 的值,我们只需要在上图中采样任意两个数据点:

                                                                            

时,通过求解上式便可计算出𝑤和𝑏的值。考虑某个具体的例子:, 代入上式中可得:

                                                                            

这就是我们初中时代学习过的二元一次方程组,通过消元法可以轻松计算出𝑤和𝑏的解析 解:

那么,看到这里是不是觉得这也不是机器学习啊?当然你可以轻而易举的得出这个方程的解,可是对于计算机来说,要做的工作远远不止这些,其次,这只是针对w和b这两个参数,实际情况下参数有很多很多,此时才能彰显出来机器的优势。

那么,怎么让机器来习得这个模型,就是我们要考虑的。

    对于上面的模型来说,假设有N个输入,那么就需要N+1组采样点,这样的话会有什么问题?我们知道在实际情况中任何采样点都有可能存在观测误差,我们将这个观测误差记为\epsilon,假设这个\epsilon属于均值为\mu,方差为\sigma {^2}的正态分布(Normal Distribution)或者高斯分布(Gaussian Distribution):𝒩(𝜇, \sigma {^2} ),则采样到的样本符合如下公式:

                                                                          𝑦 = 𝑤𝑥 + 𝑏 + 𝜖, 𝜖~𝒩(𝜇, \sigma {^2})

       而引入误差后,即使简单如线性模型,如果仅采样两个数据点,可能会带来较大估 计偏差。如图下所示,图中的数据点均带有观测误差,如果基于蓝色矩形块的两个数据点进行估计,则计算出的蓝色虚线与真实橙色直线存在较大偏差。为了减少观测误差引入 的估计偏差,可以通过采样多组数据样本集合𝔻 = {(𝑥 (1) ,𝑦 (1) ), (𝑥 (2) ,𝑦 (2) ),… , (𝑥 (𝑛) , 𝑦 (𝑛) )},然后找出一条“最好”的直线,使得它尽可能地让所有采样点到该直线的误差(Error,或损失 Loss)之和最小。

                                                           

    也就是说,由于观测误差𝜖的存在,当我们采集了多个数据点𝔻时,可能不存在一条直 线完美的穿过所有采样点。退而求其次,我们希望能找到一条比较“好”的位于采样点中间的直线。那么怎么衡量“好”与“不好”呢?一个很自然的想法就是,求出当前模型的所有采样点上的预测值wx^i+ b与真实值y^i之间差的平方和作为总误差L:

                                                                     L=\frac{1}{n}\sum_{i=1}^{n}(wx^i+b-y^i)^2

然后搜索一组参数w^*,b^*使得L最小,而它对应的直线就我我们要寻找的最优直线:

                                                             

其中𝑛表示采样点的个数。这种误差计算方法称为均方差(Mean Squared Error,简称 MSE)

                                                                                

 四、优化方案

       结合上面说的,明确一下我们的目的就是需要找出最优参数(Optimal Parameter)w^*b^*,使得输入和输出满足线性关系。但是由于观测误差的存在,需要通过采样足够多的数据样本组成的数据集(Dataset):𝔻 = {{(x^{(1)},y^{(1)})},(x^{(2)},y^{(2)}),...,(x^{(n)},y^{(n)})},找到一组最优的参数w^*b^*使得均方差L=\frac{1}{n}\sum_{i=1}^{n}(wx^i+b-y^i)^2最小。

       对于上述说的简单线性模型,只需要两个样本点就可以利用消元法解出来,而对于复杂的情形下,我们不可能按照这种思路来解决,一种通用的办法就是借助数值方法去优化(Optimize)出一个近似的解,之所以叫优化,是因为在计算机环境下,计算速度非常快,所以我们可以利用它强大的计算能力去不断的“搜索”和“试错”,说白了就是一组和一组的数据不断去试,那个合适!!!然后在试错的过程中计算每一组数据的情况下它的误差L,然后以某种方法让这个误差逐渐的减小,最后从测试过的数据中,挑出最好的一组,就是我们要找的最优w^*b^*

       同样的问题来了,上面说的方法确实很简单很暴力,在面对大规模,高纬度的数据优化问题时计算效率极低,基本不可行。那么考虑换一种方法就是大名鼎鼎的梯度下降算法(Gradient Descend)。

      先看一个高中学过的知识:导数 (Derivative),高中的时候经常做一类题就是给你一个函数,让你求解该函数的极值,通常我们的解题方法是先对函数求导,再令导函数为0,求出对应的驻点,在去检验驻点是否满足我们要求的条件即可。举一个简单的例子:f(x) = x^2\cdot sin(x),下面的图形展示了该函数和其导函数在x \epsilon [-10,10]区间的图像,蓝色实现为f(x),黄色虚线为\frac{\mathrm{d}f(x) }{\mathrm{d} x},在图中很明显的可以看到函数导数(虚线)为0的点即为f(x)的驻点,函数的极值就在驻点处。

                                                              

         而梯度的定义,简单来说就是函数对各个自变量的偏导数(Partial Derivative)组成的向量。例如对于3维函数:

                                                                            z = f(x,y)

         其中,函数对自变量x的偏导数:,函数对自变量y的偏导数:,则梯度:∇f

        再举一个例子就是:f(x) = -(cos^2x+cos^2y)^2,图中xy平面的红色箭头的长度表示梯度向量的模,箭头的方向表示梯度向量的方向。或许,图像看的不是那么清楚,我们只需要知道:箭头的方向总是指向当前位置函数增速最大的方向,函数曲面月陡峭,箭头的长度也就越大,梯度的模也越大。

                                                      

      结合上面说的,我们有:函数在各处的梯度方向∇f总是指向函数值增大的方向,那么梯度的反方向-∇f应指向函数值减小的方向。利用这个性质,只需要按照:

                                                                                                                              \left ( 1 \right )

来迭代更新x',就能获得越来越小的函数值,其中的\eta用来缩放梯度向量(大家都叫它学习率),通常设置为某较小的值如0.01,0.001等。在一维函数的情况下,上述详细、两形式可以退化成标量形式:

                                                                                        x' = x-\eta \frac{\mathrm{d} y}{\mathrm{d} x}

通过上式迭代跟新x'若干次,得到的x'处的函数值y',总是更有可能比在x处的函数值y小。

        简单总结一下就是,通过上述的(1)式,循环计算函数的梯度∇f并更新待优化参数\theta(这里用到的参数是wb,从而得到函数f获得极小值时参数\theta的最优数值解。这样的优化方式就叫做梯度下降算法。

将上述方法放到具体的实例中,我们通过上述介绍的简单的梯度下降算法来求解参数w^*和b^*b^*。这里要最小化的是均方差误差函数L:

                                                                               

上述式子当中需要优化的参数是wb,按照上面(1)式的方式来循环更新参数有:

                                                                                  

再看一下计算梯度的过程,其实就是求导!!!

                                                        

                                                               =\frac{1}{n}\sum_{i=1}^{n}2(wx^{(i)}+b-y^{(i)})\cdot \frac{\partial (wx^{(i)}+b-y^{(i)})}{\partial w}

                                                               =\frac{1}{n}\sum_{i=1}^{n}2(wx^{(i)}+b-y^{(i)})\cdot x^{(i)}

                                                               =\frac{2}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)})\cdot x^{(i)}                                                             \left ( 2 \right )

 

 

同样的方法推导偏导数\frac{\partial \pounds }{\partial b}的表达式:

                                                          \frac{\partial \pounds }{\partial b}=\frac{\partial \frac{1}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)^2})}{\partial b}=\frac{1}{n}\sum_{i=1}^{n}\frac{\partial (wx^{(i)}+b-y^{(i)^2})}{\partial b}

                                                                 =\frac{1}{n}\sum_{i=1}^{n}2(wx^{(i)}+b-y^{(i)})\cdot \frac{\partial (wx^{(i)}+b-y^{(i)})}{\partial b}

                                                                 =\frac{1}{n}\sum_{i=1}^{n}2(wx^{(i)}+b-y^{(i)})\cdot 1

                                                                 =\frac{2}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)})                                                                  \left ( 3 \right )

到此,理论的部分暂停一下,说了那么多,怎么去描述上面的理论?

五、代码实现——线性模型实战

通常在机器学习中有一个通用的过程就是:

1、准备数据集

2、建立模型

3、优化模型

4、训练模型

5、测试

1、采样数据

这里,利用前面提到的线性模型,从指定的w=1.477b=0.089的真实模型中直接采样:

                                                                               y = 1.477*x+0.089

并且为了模拟真实样本的观测误差,给模型添加误差自变量\epsilon,它采样自均值为0,方差为0.01的高斯分布:

                                                                   y = 1.477*x+0.089+\epsilon ,\epsilon \sim N(0,0.01)

通过随机采样n = 100次,获得n个样本的训练数据集\mathbb{D}^{train}:

data = []# 保存样本集的列表
for i in range(100): # 循环采样 100 个点
    x = np.random.uniform(-10., 10.) # 随机采样输入 x
    # 采样高斯噪声
    eps = np.random.normal(0., 0.1)
     # 得到模型的输出
    y = 1.477 * x + 0.089 + eps
    data.append([x, y]) # 保存样本点
data = np.array(data) # 装换为2DNumpy数组

循环进行100次采样,每次从区间[-10,10]的均匀分布U(0,1)中随机采样一个数据x,同时从均值为0,方差为0.1^2的高斯分布N(0,0.1^2)中随机采样噪声\epsilon,根据真实模型生成y的数据,并保存为Numpy数组。

2、计算误差

循环计算在每个点(x^{(i)},y^{(i)})处的预测值与真实值之间差的平方并累加,从而获得训练集上的均方差损失值。最后的误差和和除以数据样本总数,从而得到每个样本上的平均误差。

def mse(b, w, points):
    # 根据当前的 w,b 参数计算均方差损失
    totalError = 0
    for i in range(0, len(points)): # 循环迭代所有点
        x = points[i, 0] # 获得 i 号点的输入 x
        y = points[i, 1] # 获得 i 号点的输出 y
        # 计算差的平方,并累加
        totalError += (y - (w * x + b)) ** 2
    # 将累加的误差求平均,得到均方差
     return totalError / float(len(points))

3、计算梯度

将上述的(2)式和(3)式拿过来:\frac{\partial \pounds }{\partial w}=\frac{2}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)})\cdot x^{(i)}

                                                        \frac{\partial \pounds }{\partial b}=\frac{2}{n}\sum_{i=1}^{n}(wx^{(i)}+b-y^{(i)})

根据这两个式子,我们只需要计算每一个点上面的(wx^{(i)}+b-y^{(i)})\cdot x^{(i)}(wx^{(i)}+b-y^{(i)})值,平均后即可得到偏导数\frac{\partial \pounds }{\partial w}\frac{\partial \pounds }{\partial b}

def step_gradient(b_current, w_current, points, lr):
    # 计算误差函数在所有点上的导数,并更新 w,b
    b_gradient = 0
    w_gradient = 0
    M = float(len(points)) # 总样本数
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # 误差函数对 b 的导数:grad_b = 2(wx+b-y),参考公式(2.3)
        b_gradient += (2/M) * ((w_current * x + b_current) - y)
        # 误差函数对 w 的导数:grad_w = 2(wx+b-y)*x,参考公式(2.2)
        w_gradient += (2/M) * x * ((w_current * x + b_current) - y)
    # 根据梯度下降算法更新 w',b',其中 lr 为学习率
    new_b = b_current - (lr * b_gradient)
    new_w = w_current - (lr * w_gradient)
    return [new_b, new_w]

4、梯度更新

经过上一步计算出误差函数在wb处的梯度后,再利用上面提到的梯度更新的方式:

                                                                 

来更新wb的值,把对数据集所有样本训练一次称为一个Epoch,共循环迭代num_iterations个Epoch。

def gradient_descent(points, starting_b, starting_w, lr, num_iterations):
    # 循环更新 w,b 多次
    b = starting_b # b 的初始值
    w = starting_w # w 的初始值
    # 根据梯度下降算法更新多次
    for step in range(num_iterations):
        # 计算梯度并更新一次
        b, w = step_gradient(b, w, np.array(points), lr)
        loss = mse(b, w, points) # 计算当前的均方差,用于监控训练进度
        if step%50 == 0: # 打印误差和实时的 w,b 值
           print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")
    return [b, w] # 返回最后一次的 w,b

最后,附上完整的代码:

# 一个线性回归的小实例
#   y = 1.477*X+0.089

import numpy as np
import matplotlib.pyplot as plt


# step1:采样数据
# 加上误差变量E   y = 1.477*x+0.089+E,E~N(0,0.01)
def load_data():
    dataset = []
    for i in range(100):
        x = np.random.uniform(-10, 10)

        # 采样高斯噪声
        eps = np.random.normal(0, 0.01)

        y = 1.477 * x + 0.089 + eps
        dataset.append([x, y])
    dataset = np.array(dataset)  # 转换为2D numpy数组
    return dataset


# step2:计算误差
def mse(b, w, points):
    # 根据当前的w,b参数计算均方差损失
    totalError = 0
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        totalError += ((w * x + b)-y) ** 2
    return totalError / float(len(points))


# step3:计算梯度
def step_gradient(b_current, w_current, points, lr):
    b_gradient = 0
    w_gradient = 0
    M = float(len(points))
    for i in range(0, len(points)):
        x = points[i, 0]
        y = points[i, 1]
        # 误差函数对b的导数   grad_b = 2(wx+b-y)
        b_gradient += (2 / M) * ((w_current * x + b_current) - y)
        w_gradient += (2 / M) * x * ((w_current * x + b_current) - y)

    new_b = b_current - (lr * b_gradient)
    new_w = w_current - (lr * w_gradient)
    return [new_b, new_w]


# step4:梯度更新
def gradient_descent(points, start_b, start_w, lr, num_iterations):
    loss_list = []
    epoch_list = []
    b = start_b
    w = start_w
    for step in range(num_iterations):
        b, w = step_gradient(b, w, np.array(points), lr)
        loss = mse(b, w, points)
        loss_list.append(loss)
        epoch_list.append(step)
        print(f"iteration:{step}, loss:{loss}, w:{w}, b:{b}")

    plt.xlabel('Epoch')
    plt.ylabel('Cost')
    plt.plot(epoch_list, loss_list)
    plt.show()
    return [b, w]


if __name__ == '__main__':
    data = load_data()
    lr = 0.01
    initial_b = 0
    initial_w = 0
    num_iterations = 1000
    [b, w] = gradient_descent(data, initial_b, initial_w, lr, num_iterations)
    loss = mse(b, w, data)
    print(f'Final loss:{loss}, w:{w}, b:{b}')

最后的预测的wb的结果如下:

训练模型MSE下降曲线:

                                                 

可能每个人的机器不一样,最终得到的结果会有些许误差,但是总体来看,效果还是挺好,已经很接近真实值了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值