机器学习基础1-线性回归及C++实现

回归问题

给定多个自变量,一个因变量以及代表它们之间关系的一些训练样本,如何来确定它们之间的关系。

线性模型

设自变量的个数即特征数量为 $n$ 即,自变量为 $x$ ,自变量的参数为 $\theta$ ,我们定义它们的关系如下:

\begin{align} h_\theta(x) & = \theta_0 + \theta_1x_1 + \theta_2x_2 + \cdots + \theta_nx_n \\ & = x\theta \\ \\ \theta & = \begin{bmatrix} \theta_0 \\ \theta_1 \\ \vdots \\ \theta_n \end{bmatrix} \\ \\ x & = \begin{bmatrix} 1 \ x_1 \ \cdots \ x_n \end{bmatrix} \\ \end{align}

设训练样本数为 $m$,训练样本集为 $X$ ,训练输出集为 $Y$ ,如下: \begin{align} X & = \begin{bmatrix} x^{0} \\ x^{1} \\ \cdots \\ x^{m-1} \end{bmatrix} \\ \\ Y & = \begin{bmatrix} y^{0} \\ y^{1} \\ \vdots \\ y^{m-1} \end{bmatrix} \\ \end{align}

我们的目标是已知 $X$ 和 $Y$ 的情况下得到最优的 $\theta$。

损失函数

哪个 $\theta$ 是最优的?我们需要先定义损失函数: $$ J(\theta)=\frac{1}{2}\sum_{i=0}^{m-1}(h_\theta(x^i)-y^i)^2 $$ 很明显损失函数最小值对应的 $\theta$ 就是我们求解的目标,所以问题变为: $$ \min_\theta J_\theta $$

梯度下降法

使用梯度下降法可以帮助我们找到损失函数的最小值,参数 $\theta_j$的梯度为:

$$ \frac{\partial J(\theta)}{\partial \theta_j}=\frac{\partial}{\partial \theta_j} \frac{1}{2}\sum_{i=0}^{m-1}(h_\theta(x^i)-y^i)^2 $$

假设样本数为1,则得到如下: $$ \frac{\partial J(\theta)}{\partial \theta_j}=\frac{\partial}{\partial \theta_j} \frac{1}{2}(h_\theta(x)-y)^2 $$

$$ \frac{\partial J(\theta)}{\partial \theta_j}=(h_\theta(x)-y)\frac{\partial}{\partial \theta_j}(h_\theta(x)-y) $$

$$ \frac{\partial J(\theta)}{\partial \theta_j}=(h_\theta(x)-y)x_j $$

多个样本的正确公式如下: $$ \frac{\partial J(\theta)}{\partial \theta_j}=\sum_{i=0}^{m-1}((h_\theta(x^i)-y^i)x_j^i) $$

$\theta_j$的更新公式为:($\alpha$ 为学习速度) $$ \theta_j := \theta_j - \alpha \frac{\partial J(\theta)}{\partial \theta_j} $$

$$ \theta_j := \theta_j - \alpha \sum_{i=0}^{m-1}((h_\theta(x^i)-y^i)x_j^i) $$

等价的矩阵形式更新公式为: $$ \theta := \theta - X^T \cdot (X \cdot \theta-Y) \cdot \alpha $$

随机梯度和批量梯度

如果我们每次更新 $\theta$ 都使用所有的训练样本,在训练样本总量很大的情况下可能会耗费很多的资源,虽然这样训练的效果会很好。

我们也可以每次只选择一个训练样本来进行更新,这就是随机梯度下降法,相比于梯度下降法随机梯度下降法可能收敛较慢。

除此之外我们也可以选择部分训练样本来更新,用以平衡收敛速度和耗费资源的情况,这种方式称为批量梯度下降。

学习速度

在公式中我们还看到一个学习速度的参数 $\alpha$ ,该值需要取正数。如果该值设置很小会导致收敛速度很慢,如果设置很大会导致在最优点左右震荡。

C++代码实现

我们定义如下的接口:

    typedef LMatrix<float> LRegressionMatrix;

    class CLinearRegression;

    /// @brief 线性回归类
    class LLinearRegression
    {
    public:
        /// @brief 构造函数
        LLinearRegression();

        /// @brief 析构函数
        ~LLinearRegression();

        /// @brief 训练模型
        /// 如果一次训练的样本数量为1, 则为随机梯度下降
        /// 如果一次训练的样本数量为M(样本总数), 则为梯度下降
        /// 如果一次训练的样本数量为m(1 < m < M), 则为批量梯度下降
        /// @param[in] xMatrix 样本矩阵, 每一行代表一个样本, 每一列代表样本的一个特征
        /// @param[in] yVector(列向量) 样本输出向量, 每一行代表一个样本
        /// @param[in] alpha 学习速度, 该值必须大于0.0f
        /// @return 成功返回true, 失败返回false(参数错误的情况下会返回失败)
        bool TrainModel(IN const LRegressionMatrix& xMatrix, IN const LRegressionMatrix& yVector, IN float alpha);

        /// @brief 使用训练好的模型预测数据
        /// @param[in] xMatrix 需要预测的样本矩阵
        /// @param[out] yVector 存储预测的结果向量(列向量)
        /// @return 成功返回true, 失败返回false(模型未训练或参数错误的情况下会返回失败)
        bool Predict(IN const LRegressionMatrix& xMatrix, OUT LRegressionMatrix& yVector) const;

        /// @brief 计算损失值, 损失值为大于等于0的数, 损失值越小模型越好
        /// @param[in] xMatrix 样本矩阵, 每一行代表一个样本, 每一列代表样本的一个特征
        /// @param[in] yVector(列向量) 样本输出向量, 每一行代表一个样本
        /// @return 成功返回损失值, 失败返回-1.0f(参数错误的情况下会返回失败)
        float LossValue(IN const LRegressionMatrix& xMatrix, IN const LRegressionMatrix& yVector) const;

    private:
        CLinearRegression* m_pLinearRegression; ///< 线性回归实现对象
    };

LMatrix是我们自定义的矩阵类,用于方便机器学习的一些矩阵计算,关于它的详细代码可以查看链接:猛戳我

我们为LLinearRegression设计了三个方法TrainModel,Predict以及LossValue,用于训练模型,预测新数据以及计算损失值。

我们看一下TrainModel的实现:

    LRegressionMatrix X;
    Regression::SamplexAddConstant(xMatrix, X);

    const LRegressionMatrix& Y = yVector;
    LRegressionMatrix& W = m_wVector;

    LRegressionMatrix XT = X.T();

    LRegressionMatrix XW;
    LRegressionMatrix DW;

    /*
    h(x) = X * W
    wj = wj - α * ∑((h(x)-y) * xj)
    */
    LRegressionMatrix::MUL(X, W, XW);
    LRegressionMatrix::SUB(XW, Y, XW);
    LRegressionMatrix::MUL(XT, XW, DW);
    LRegressionMatrix::SCALARMUL(DW, -1.0f * alpha, DW);
    LRegressionMatrix::ADD(W, DW, W);

在代码中我们给样本数据最后一列加上一个常数项1.0,之后就根据上面的矩阵公式更新参数。

我们再看一下Predict的实现:

    LRegressionMatrix X;
    Regression::SamplexAddConstant(xMatrix, X);

    LRegressionMatrix::MUL(X, m_wVector, yVector);

我们只要在样本最后一列增加一个常数项,将样本矩阵乘以参数(权重)向量就得到预测结果。

下面我们来测试一下我们的线性回归算法:

    int main()
    {
        // 定义训练样本
        float trainX[4] =
        {
            2.0f,
            4.0f,
            6.0f,
            8.0f
        };
        LRegressionMatrix xMatrix(4, 1, trainX);

        // 定义训练样本输出
        float trainY[4] =
        {
            1.0f,
            2.0f,
            3.0f,
            4.0f
        };
        LRegressionMatrix yMatrix(4, 1, trainY);


        // 定义线性回归对象
        LLinearRegression linearReg;

        // 训练模型
        // 计算每一次训练后的损失值
        for (unsigned int i = 0; i < 10; i++)
        {
            linearReg.TrainModel(xMatrix, yMatrix, 0.01f);
            float loss = linearReg.LossValue(xMatrix, yMatrix);
            printf("Train Time: %u  ", i);
            printf("Loss Value: %f\n", loss);
        }

        // 进行预测
        LRegressionMatrix yVector;
        linearReg.Predict(xMatrix, yVector);

        printf("Predict Value: ");
        for (unsigned int i = 0; i < yVector.RowLen; i++)
        {
            printf("%.5f  ", yVector[i][0]);
        }
        printf("\n");

        system("pause");

        return 0;
    }

我们得到如下结果:

我们看到随着训练次数的增多损失值一直在减小,并且我们预测的结果也很贴近正确结果。

局部加权回归

线性回归要求数据是线性的,针对非线性数据我们可以使用局部加权回归方法,非线性数据在局部可能是线性的。我们需要定义如下的损失函数:

$$ J(\theta)=\frac{1}{2}\sum_{i=0}^{m-1}k_i(h_\theta(x^i)-y^i)^2 $$

$$ k_i=e^{-\frac{(x^i-x^t)^2}{2}} $$

我们看到公式中有一个 $x^t$ ,它代表我们想要预测的目标点,$(x^i-x^t)^2$ 为两点之间的距离,从公式中可以看出远离我们目标点的训练数据占的权重会比较小,该损失函数会更在意靠近我们目标点的训练数据。

以上完整的代码可以在链接:猛戳我查看,我们的线性回归被定义在文件LRegression.h和LRegression.cpp中。

其他章节链接

机器学习基础1-线性回归及C++实现

机器学习基础2-二项逻辑回归及C++实现

机器学习基础3-Softmax回归及C++实现

机器学习基础4-增强学习初探


赞助作者写出更好文章


您还未登录,登录GitHub账号发表精彩评论

 GitHub登录


最新评论

    还没有人评论...

 

 

刘杰

29岁, 现居苏州

微信:

CoderJieLiu

邮箱:

coderjie@outlook.com

Github:

https://github.com/BurnellLiu

简介:

弱小和无知不是生存的障碍,傲慢才是!

Think Twice, Code Once!

本站: 登录 注册

苏ICP备16059872号-1 苏ICP备16059872号-2 . Copyright © 2017. http://www.coderjie.com. All rights reserved.

账号登录

注册 忘记密码