AlgoC++第二课:线性回归

news2024/11/16 20:43:11

目录

  • 线性回归
    • 前言
    • 1. 定义
    • 2. 房价预测案例
      • 2.1 定义问题:
      • 2.2 MSE是如何被定义的?(如何推导的?)
      • 2.3 总结
    • 3. 代码
      • 3.1 C++实现
        • 3.1.1 数据读取和处理
        • 3.1.2 线性回归模型
        • 3.1.3 参数输出和预测
        • 3.1.4 完整示例代码
        • 3.1.5 C++知识点
      • 3.2 python实现
        • 3.2.1 房价预测示例
        • 3.2.2 可视化
      • 3.3 思考
      • 3.4 正则化
      • 3.5 范数
      • 3.6 P范数绘图
    • 总结

线性回归

前言

手写AI推出的全新面向 AI 算法的 C++ 课程 Algo C++,链接。记录下个人学习笔记,仅供自己参考。

本次课程主要讲解线性回归

课程大纲可看下面的思维导图

在这里插入图片描述

1. 定义

预测值定性分析,即离散变量预测时,我们称之为分类(如图片分类)

预测值定量分析,即连续变量预测时,我们称之为回归(如房价预测)

回归模型可以分为一元线性回归和多元线性回归

一元线性回归是指只有一个自变量与一个因变量之间的线性关系的回归模型。例如,我们可以用一元线性回归模型来研究温度与冰淇淋销量之间的关系,其中温度是自变量,冰淇淋销量是因变量。一元线性回归模型的描述如下:
h θ ( x ) = θ 0 x 0 + θ 1 x 1 ( x 0 = 1 ) y = k x + b h_{\theta}(x)=\theta_0x_0+\theta_1x_1(x_0=1) \\ y=kx+b hθ(x)=θ0x0+θ1x1(x0=1)y=kx+b
多元线性回归是指包含两个或两个以上自变量与一个因变量之间的线性关系的回归模型。例如,我们可以用多元线性回归模型来研究房屋售价与房屋面积、房间数量、地理位置等多个因素之间的关系。多元线性回归模型的描述如下:
h θ ( x ) = θ 0 x 0 + θ 1 x 1 + ⋯ + θ n x n = ∑ i = 0 n θ i x i = θ T X ( x 0 = 1 ) h_{\theta}(x)=\theta_{0}x_{0}+\theta_{1}x_{1}+\cdots+\theta_{n}x_{n}=\sum_{i=0}^{n}\theta_{i}x_{i}=\theta^{T}X(x_{0}=1) hθ(x)=θ0x0+θ1x1++θnxn=i=0nθixi=θTX(x0=1)

2. 房价预测案例

在房价问题中我们可以以房子面积、离地铁站的距离等属性作为自变量,房价是因变量

x 1 x_1 x1:房子面积

x 2 x_2 x2:离最近地铁站距离

x 3 x_3 x3:绿化规模

. . . ... ...:其它

线性回归可以理解为将这些特征线性组合起来,即可得到房价 h θ ( x ) h_{\theta}(x) hθ(x)

而我们的目标就是求出一组合理的权重参数 θ \theta θ 能够较为准确的预测出真实的 h θ ( x ) h_{\theta}(x) hθ(x)

2.1 定义问题:

给定两个样本的年份 x x x 和对应的房价 y y y

设计目标函数
L ( x ( 1 ) ) = 1 2 [ ( k x ( 1 ) + b ) − y ( 1 ) ] 2 L ( x ( 2 ) ) = 1 2 [ ( k x ( 2 ) + b ) − y ( 2 ) ] 2 L = L ( x ( 1 ) ) + L ( x ( 2 ) ) 2 \begin{gathered} L(x^{(1)})=\frac{1}{2}[(k x^{(1)}+b)-y^{(1)}]^{2} \\ L(x^{(2)})=\frac{1}{2}[(k x^{(2)}+b)-y^{(2)}]^{2} \\ L=\frac{L(x^{(1)})+L(x^{(2)})}{2} \end{gathered} L(x(1))=21[(kx(1)+b)y(1)]2L(x(2))=21[(kx(2)+b)y(2)]2L=2L(x(1))+L(x(2))
此时 L L L 取0时,对应的 k k k b b b 就是我们要的结果,然后使用梯度下降法,求解函数极值,得到合适的 k k k b b b,就是线性回归要做的事情

这里设计的 L L L 函数,我们称之为损失函数/代价函数,而 L = 1 2 ( h θ ( x ) − y ) 2 L=\frac{1}{2}(h_{\theta}(x)-y)^{2} L=21(hθ(x)y)2 形式的函数,我们称之为 MSE/L2Loss

这里的最小化 L L L,也称之为经验风险最小化(Empirical Risk Minimization, ERM)

2.2 MSE是如何被定义的?(如何推导的?)

对于只有2个样本的时候 y = k x + b y=kx+b y=kx+b 有唯一解,但是样本数大于2时, k k k b b b 只有近似解,也就是说不可能通过 k k k b b b 完全预测准确,总会带来一些差异,因此真实值和预测值的关系如下:
y = h θ ( x ) + ϵ y=h_{\theta}(x)+\epsilon y=hθ(x)+ϵ
其中 y y y 代表真实值, h θ ( x ) h_{\theta}(x) hθ(x) 代表预测值, ϵ \epsilon ϵ 代表误差值

而我们总希望预测值足够的接近真实值,也意味着希望不可控的误差值足够接近于0,因此需要对误差项进行分析

假设误差是服从均值为 0 方差为 σ 2 \sigma^2 σ2 的正态分布

  • 为什么均值为0:方便我们对误差项进行统计分析和建模,误差分布均值为0只是一个常见的假设,并不一定适用于所有数据集和模型(from chatGPT)
  • 为什么是这台分布:因为我们可以发现现实生活中绝大部分事物都很容易的服从正态分布。这是统计出来的规律做的假设,使得对误差的假设能够尽可能满足更多的场景。若数据不满足正态分布,则假设失败,模型容易出现非预期结果

假设有 n n n 个样本,每个样本都有可能出现误差,而每个样本出现的误差的可能性乘在一起,就是整体出现误差的可能性(即联合概率),故我们需要优化的函数如下:
L ( θ ) = ∏ i = 1 n 1 2 π σ ⋅ e − ϵ i 2 2 σ 2 L\left(\theta\right)=\prod\limits_{i=1}^{n}\dfrac{1}{\sqrt{2\pi}\sigma}\cdot e^{-\frac{\epsilon_i^2}{2\sigma^2}} L(θ)=i=1n2π σ1e2σ2ϵi2
我们的目的是尽量让每个样本的误差 ϵ \epsilon ϵ 等于均值0,而当所有误差都接近均值时,其发生的概率最大,因此我们需要最大化概率密度函数,即
a r g m a x   L ( θ ) = ∏ i = 1 n 1 2 π σ ⋅ e − ϵ i 2 2 σ 2 argmax \ L\left(\theta\right)=\prod\limits_{i=1}^{n}\dfrac{1}{\sqrt{2\pi}\sigma}\cdot e^{-\frac{\epsilon_i^2}{2\sigma^2}} argmax L(θ)=i=1n2π σ1e2σ2ϵi2
可能还是难以理解,我们来看下面的概率密度图,当 ϵ \epsilon ϵ 越接近均值 μ \mu μ 发生的概率最大,而这正是我们想要的优化的结果

在这里插入图片描述

接下来就是公式推导了,我们在两边分别取对数
log ⁡ L ( θ ) = log ⁡ ∏ i = 1 n 1 2 π σ ⋅ e − ε i 2 2 σ 2 = ∑ i = 1 n log ⁡ 1 2 π σ ⋅ e − ε i 2 2 σ 2 = ∑ i = 1 n ( log ⁡ 1 2 π σ + log ⁡ e − ε i 2 2 σ 2 ) = n log ⁡ 1 2 π σ + ∑ i = 1 n − ε i 2 2 σ 2 = n log ⁡ 1 2 π σ − 1 2 σ 2 ∑ i = 1 n ε i 2 \begin{aligned} \log^{L(\theta)}&=\log^{\prod_{i=1}^n\frac{1}{\sqrt{2\pi}\sigma}\cdot e^{-\frac{\varepsilon_i^2}{2\sigma^2}}} \\ &=\sum_{i=1}^n\log\frac{1}{\sqrt{2\pi}\sigma}\cdot e^{-\frac{\varepsilon_i^2}{2\sigma^2}} \\ &=\sum_{i=1}^{n}(\log\frac{1}{\sqrt{2\pi}\sigma}+\log e^{-\frac{\varepsilon_i^2}{2\sigma^2}}) \\ &=n\log^{\frac{1}{\sqrt{2\pi}\sigma}}+\sum_{i=1}^{n}-\frac{\varepsilon_{i}^{2}}{2\sigma^{2}} \\ &=n\log^{\frac{1}{\sqrt{2\pi\sigma}}}-\frac{1}{2\sigma^{2}}\sum_{i=1}^{n}\varepsilon_{i}^{2} \end{aligned} logL(θ)=logi=1n2π σ1e2σ2εi2=i=1nlog2π σ1e2σ2εi2=i=1n(log2π σ1+loge2σ2εi2)=nlog2π σ1+i=1n2σ2εi2=nlog2πσ 12σ21i=1nεi2
注意,这里的 log ⁡ \log log 其实是 ln ⁡ \ln ln,是代码写习惯了

由于方差 σ 2 \sigma^2 σ2 是数据集的方差,是确定的值,在这里可以忽略掉。我们只保留需要考虑的部分,即
log ⁡ L ( θ ) = − 1 2 ∑ i = 1 n ε i 2 \log^{L(\theta)}=-\frac{1}{2}\sum_{i=1}^{n}\varepsilon_{i}^{2} logL(θ)=21i=1nεi2
又由于 ε = y − h θ ( x ) \varepsilon=y-h_{\theta}(x) ε=yhθ(x),所以最大似然推导为最大化 J ( θ ) J(\theta) J(θ)
J ( θ ) = − 1 2 ∑ i = 1 n ( y i − h θ ( x ) ) 2 J(\theta)=-\frac{1}{2}\sum_{i=1}^{n}(y_i-h_\theta(x))^2 J(θ)=21i=1n(yihθ(x))2
转化为最小化 J ( θ ) J(\theta) J(θ)
J ( θ ) = 1 2 ∑ i = 1 n ( y i − h θ ( x ) ) 2 J(\theta)=\frac{1}{2}\sum_{i=1}^{n}(y_i-h_\theta(x))^2 J(θ)=21i=1n(yihθ(x))2
和我们之前定义的 MSE 损失函数的形式一样

2.3 总结

MSE 可以用对数据误差采用正态分布假设推导而得来,这里也体现了正态分布在模型预测中的重要性,如果不服从假设,容易出现训练异常、不收敛、效率低等各种问题

3. 代码

3.1 C++实现

3.1.1 数据读取和处理

数据读取和处理代码如下

#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <tuple>
#include <iomanip>

using namespace std;

namespace io{

    /* 通过 csv 文件加载数据*/
    vector<Item> load_data(const string& file){

        vector<Item> output;
        fstream ifile(file, ios::binary | ios::in);

        string line;
        getline(ifile, line);

        while(getline(ifile, line)){
            int p = line.find(",");
            Item item;
            item.year  = atof(line.substr(0, p).c_str());
            item.price = atof(line.substr(p + 1).c_str());

            // cout << item.year << "," << item.price << endl;
            output.emplace_back(item);
        }
        return output;
    }
};

namespace statistics{

    /* 计算数据的均值和标准差 */
    tuple<Item, Item> compute_mean_std(const vector<Item>& items){

        Item mean{0, 0}, stdval{0, 0};

        for(auto& item : items){
            mean.year += item.year;
            mean.price += item.price;
        }
        mean.year  /= items.size();
        mean.price /= items.size();

        for(auto& item : items){
            stdval.year  += std::pow(item.year - mean.year, 2.0f);
            stdval.price += std::pow(item.price - mean.price, 2.0f);
        }
        stdval.year  = std::sqrt(stdval.year / items.size());
        stdval.price = std::sqrt(stdval.price / items.size());
        return make_tuple(mean, stdval);
    }
};

上述示例代码中,在 io 命名空间下的 load_data 函数中,通过读取 csv 文件,将数据加载到一个 vector<Item> 类型的数组中,每个 Item 结构体包含一个年份和相应的房价数据。然后使用 statistics 命名空间下的 compute_mean_std 函数计算数据的均值和标准差,对数据进行归一化处理,使其均值为0,标准差为1。

3.1.2 线性回归模型

线性回归模型的构建代码如下

int run(){

auto datas = io::load_data("shanghai.csv");

Item mean, stdval;
tie(mean, stdval) = statistics::compute_mean_std(datas);

/* 对数据进行减去均值除以标准差,使得均值为0,标准差为1 */
for(auto& item : datas){
item.year  = (item.year - mean.year) / stdval.year;
item.price = (item.price - mean.price) / stdval.price;
}

float k_identity = 0.1;
float k_sin      = 0.1;
float k_cos      = 0.1;
float lambda     = 1e-5;
float b  = 0;
float lr = 0.01;

for(int iter = 0; iter < 1000; ++iter){

float loss = 0;
float delta_k_identity = 0;
float delta_k_sin      = 0;
float delta_k_cos      = 0;
float delta_b          = 0;

// 计算Loss
for(auto& item : datas){
float predict = k_identity * item.year + k_sin * std::sin(item.year) + k_cos * std::cos(item.year) + b;
float L = 0.5 * std::pow(predict - item.price, 2.0f) + lambda * (k_identity*k_identity + k_sin*k_sin + k_cos*k_cos + b*b);

// 求Loss对各部分的导数
delta_k_identity += (predict - item.price) * item.year + k_identity * lambda;
delta_k_sin      += (predict - item.price) * std::sin(item.year) + k_sin * lambda;
delta_k_cos      += (predict - item.price) * std::cos(item.year) + k_cos * lambda;
delta_b          += (predict - item.price) + b * lambda;

loss += L;
}

if(iter % 100 == 0)
cout << "Iter " << iter << ", Loss: " << setprecision(3) << loss << endl;

// 更新
k_identity = k_identity - lr * delta_k_identity;
k_sin      = k_sin      - lr * delta_k_sin;
k_cos      = k_cos      - lr * delta_k_cos;
b          = b          - lr * delta_b;
}

在上述示例代码中,我们采用线性回归模型来做房价的预测,其中 K_identity、k_sin、k_cos 分别是权重系数,b 是偏置项,lambda 是正则化系数,防止过拟合。对每个数据项计算预测值 predict 和损失函数 L。然后分别计算权重系数和偏置项的梯度 delta_k_identity、delta_k_sin、delta_k_cos和delat_b,用于更新权重和偏置项。

3.1.3 参数输出和预测

参数输出和预测的代码如下

printf(
    "模型参数: k_identity = %f, k_sin = %f, k_cos = %f, b = %f\n"
    "数据集: xm = %f, ym = %f, xs = %f, ys = %f\n", 
    k_identity,  k_sin, k_cos, b, mean.year, mean.price, stdval.year, stdval.price
);

printf("参数:\nk1, k2, k3, b, xm, ym, xstd, ystd = %f, %f, %f, %f, %f, %f, %f, %f\n", k_identity,  k_sin, k_cos, b, mean.year, mean.price, stdval.year, stdval.price);

float year          = 2023;
float x             = (year - mean.year) / stdval.year;
float predict       = x * k_identity + std::sin(x) * k_sin + std::cos(x) * k_cos + b;
float predict_price = predict * stdval.price + mean.price;

printf("预计 %d 年,上海房价将会是:%.3f 元\n", (int)year, predict_price);

上述示例代码中输出了模型参数和数据集的均值、标准差等参数。最后使用模型进行预测,输出指定年份的预测房价。

3.1.4 完整示例代码

#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <cmath>
#include <tuple>
#include <iomanip>

using namespace std;

namespace Application{

    struct Item{
        float year;
        float price;
    };

    namespace io{
        
        /* 通过 csv 文件加载数据*/
        vector<Item> load_data(const string& file){

            vector<Item> output;
            fstream ifile(file, ios::binary | ios::in);

            string line;
            getline(ifile, line);
            
            while(getline(ifile, line)){
                int p = line.find(",");
                Item item;
                item.year  = atof(line.substr(0, p).c_str());
                item.price = atof(line.substr(p + 1).c_str());

                // cout << item.year << "," << item.price << endl;
                output.emplace_back(item);
            }
            return output;
        }
    };

    namespace statistics{

        /* 计算数据的均值和标准差 */
        tuple<Item, Item> compute_mean_std(const vector<Item>& items){

            Item mean{0, 0}, stdval{0, 0};
            
            for(auto& item : items){
                mean.year += item.year;
                mean.price += item.price;
            }
            mean.year  /= items.size();
            mean.price /= items.size();

            for(auto& item : items){
                stdval.year  += std::pow(item.year - mean.year, 2.0f);
                stdval.price += std::pow(item.price - mean.price, 2.0f);
            }
            stdval.year  = std::sqrt(stdval.year / items.size());
            stdval.price = std::sqrt(stdval.price / items.size());
            return make_tuple(mean, stdval);
        }
    };

    int run(){
        
        auto datas = io::load_data("shanghai.csv");
        
        Item mean, stdval;
        tie(mean, stdval) = statistics::compute_mean_std(datas);

        /* 对数据进行减去均值除以标准差,使得均值为0,标准差为1 */
        for(auto& item : datas){
            item.year  = (item.year - mean.year) / stdval.year;
            item.price = (item.price - mean.price) / stdval.price;
        }

        float k_identity = 0.1;
        float k_sin      = 0.1;
        float k_cos      = 0.1;
        float lambda     = 1e-5;
        float b  = 0;
        float lr = 0.01;

        for(int iter = 0; iter < 1000; ++iter){

            float loss = 0;
            float delta_k_identity = 0;
            float delta_k_sin      = 0;
            float delta_k_cos      = 0;
            float delta_b          = 0;

            // 计算Loss
            for(auto& item : datas){
                float predict = k_identity * item.year + k_sin * std::sin(item.year) + k_cos * std::cos(item.year) + b;
                float L = 0.5 * std::pow(predict - item.price, 2.0f) + lambda * (k_identity*k_identity + k_sin*k_sin + k_cos*k_cos + b*b);

                // 求Loss对各部分的导数
                delta_k_identity += (predict - item.price) * item.year + k_identity * lambda;
                delta_k_sin      += (predict - item.price) * std::sin(item.year) + k_sin * lambda;
                delta_k_cos      += (predict - item.price) * std::cos(item.year) + k_cos * lambda;
                delta_b          += (predict - item.price) + b * lambda;

                loss += L;
            }

            if(iter % 100 == 0)
                cout << "Iter " << iter << ", Loss: " << setprecision(3) << loss << endl;
            
            // 更新
            k_identity = k_identity - lr * delta_k_identity;
            k_sin      = k_sin      - lr * delta_k_sin;
            k_cos      = k_cos      - lr * delta_k_cos;
            b          = b          - lr * delta_b;
        }

        printf(
            "模型参数: k_identity = %f, k_sin = %f, k_cos = %f, b = %f\n"
            "数据集: xm = %f, ym = %f, xs = %f, ys = %f\n", 
            k_identity,  k_sin, k_cos, b, mean.year, mean.price, stdval.year, stdval.price
        );

        printf("参数:\nk1, k2, k3, b, xm, ym, xstd, ystd = %f, %f, %f, %f, %f, %f, %f, %f\n", k_identity,  k_sin, k_cos, b, mean.year, mean.price, stdval.year, stdval.price);

        float year          = 2023;
        float x             = (year - mean.year) / stdval.year;
        float predict       = x * k_identity + std::sin(x) * k_sin + std::cos(x) * k_cos + b;
        float predict_price = predict * stdval.price + mean.price;

        printf("预计 %d 年,上海房价将会是:%.3f 元\n", (int)year, predict_price);
        return 0;
    }
};

int main(){
    return Application::run();
}

运行结果如下图所示:

在这里插入图片描述

3.1.5 C++知识点

在上述的示例代码中,涉及到了以下几个需要注意的 C++ 知识点:(from chatGPT)

  1. 结构体的定义和使用。在代码中,使用了一个结构体 Item 来表示每个数据项,包括年份和房价。结构体的成员变量可以通过 . 来访问。
  2. 文件读写的操作。在代码中,使用了 fstream 库来读取 csv 格式的数据文件,其中涉及到了文件的打开、读取、关闭等操作。
  3. 向量的使用。在代码中,使用了 std::vector 来存储读取的数据。vector 是一个动态数组,可以在运行时动态增加或删除元素。
  4. 使用 tuple 进行多个返回值的返回。在代码中,使用了 std::tuple 来同时返回均值和标准差。tuple 是一个泛型容器,可以包含任意数量的元素。
  5. 数学函数的使用。在代码中,使用了 std::powstd::sqrt 等数学函数来计算平方和平方根等操作。
  6. printf 和 cout 的使用。在代码中,使用了 printfcout 来输出结果。printf 是 C 语言风格的输出函数,cout 是 C++ 中的流式输出方式。
  7. 命名空间的使用。在代码中,使用了命名空间来组织函数和结构体,以避免命名冲突。
  8. 2.0f是一个浮点型字面量,表示一个单精度浮点数,即 float 类型。而2.0则是一个双精度浮点数字面量,即 double 类型。在 C++ 中,当两个不同类型的数值进行运算时,系统会自动将其转换为同一种类型进行计算
  9. 使用 std::tie 的作用是将 compute_mean_std 函数返回的两个 Item 结构体类型的变量 meanstdval 绑定在一起。这样可以在一次函数调用中返回多个变量,避免使用 std::pairstd::tuple 类型等间接的方式来返回多个值。

3.2 python实现

3.2.1 房价预测示例

根据 C++ 的房价预测的示例代码分析,Python 版本的相对容易,完整的示例代码如下:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

data = pd.read_csv("workspace/shanghai.csv").to_numpy()
x,y = data.T # 转置,2行N列的数组

# 归一化
xm, ym = np.mean(data, axis=0)
xs, ys = np.std(data, axis=0)
x = (x - xm) / xs
y = (y - ym) / ys

############################# 训练流程 ####################################

k_identity = 0.01
k_sin      = 0.1
k_cos      = 0.1
b          = 0
lr         = 0.01

# 未添加正则化项
for i in range(1000):
    predict = x * k_identity + np.sin(x) * k_sin + np.cos(x) * k_cos + b
    loss = 0.5 * ((predict - y) ** 2).sum()

    if i % 100 == 0:
        print(f"Iter: {i}, Loss: {loss:.3f}")

    dk_identity = ((predict - y) * x).sum()
    dk_sin      = ((predict - y) * np.sin(x)).sum()
    dk_cos      = ((predict - y) * np.cos(x)).sum()
    db          = (predict - y).sum()

    # 更新
    k_identity  = k_identity - dk_identity * lr
    k_sin       = k_sin      - dk_sin * lr
    k_cos       = k_cos      - dk_cos * lr
    b           = b - db * lr

restore_x = x * xs + xm
restore_y = predict * ys + xm

print(f"模型参数: k_identity = {k_identity:.6f}, k_sin = {k_sin:.6f}, k_cos = {k_cos:.6f}, b = {b:.6f}")
print(f"数据集: xm = {xm:.6f}, ym = {ym:.6f}, xs = {xs:.6f}, b = {ys:.6f}")

year = 2023
x = (year - xm) / xs
predict = x * k_identity + np.sin(x) * k_sin + np.cos(x) * k_cos + b
predict_price = predict * ys + ym
print(f"预计 {year} 年,上海的房价将会是:{predict_price:.3f}元")

plt.plot(restore_x, restore_y, "r-")
plt.savefig("figure.jpg", bbox_inches='tight')

运行结果如下图所示:

在这里插入图片描述

3.2.2 可视化

我们可以将预测的和实际的年份-房价曲线图画出来,可视化代码如下:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

k1, k2, k3, b, xm, ym, xstd, ystd = 0.063115, 1.222273, -0.574371, 0.327224, 2011.000000, 23469.565217, 6.633250, 19034.006976

data = pd.read_csv("workspace/shanghai.csv")
data_array = data.to_numpy()
x, y = data_array.T

x = (x - xm) / xstd
p = x * k1 + np.sin(x) * k2 + np.cos(x) * k3 + b
pred = p * ystd + ym

plt.plot(data.year, data.price, "b-", label="data")
plt.plot(data.year, pred, "r-", label="predict")
plt.xlim([2000, 2022])
plt.ylim([0, 60000])
plt.legend()
plt.savefig("figure.jpg")

可视化曲线如下图所示:

在这里插入图片描述

3.3 思考

在上述代码中,我们多引入了两个 k k k 分别是 k s i n k_{sin} ksin k c o s k_{cos} kcos,使得模型比直线更加复杂:
y = k i d e n t i t y x + k s i n sin ⁡ ( x ) + k c o s cos ⁡ ( x ) + b y=k_{identity}x+k_{sin}\sin(x)+k_{cos}\cos(x)+b y=kidentityx+ksinsin(x)+kcoscos(x)+b
若其中某一个或多个 k k k 为0或者接近0,则模型复杂度将会降低。那么,我们可以定义如下损失函数:
L = 1 2 [ k i d e n i t y x + k s i n sin ⁡ ( x ) + k c o s cos ⁡ ( x ) + b − y ] 2 + λ ( k i d e a n i t y 2 + k s i n 2 + k c o s 2 + b 2 ) L=\frac{1}{2}[k_{i d e n i t y}x+k_{s i n}\sin(x)+k_{c o s}\cos(x)+b-y]^{2}+\lambda(k_{i d e a n i t y}^{2}+k_{s i n}^{2}+k_{c o s}^{2}+b^{2}) L=21[kidenityx+ksinsin(x)+kcoscos(x)+by]2+λ(kideanity2+ksin2+kcos2+b2)
λ \lambda λ 取一个较小的数字,例如1e-5,这里增加的约束 λ ( k i d e a n i t y 2 + k s i n 2 + k c o s 2 + b 2 ) \lambda(k_{i d e a n i t y}^{2}+k_{s i n}^{2}+k_{c o s}^{2}+b^{2}) λ(kideanity2+ksin2+kcos2+b2) 称之为对模型的正则化(Regularization),相当于对模型的参数做了约束,要求其越简单越好,最小化模型参数,也称为结构化风险最小(Structural Risk Minimization,SRM)

3.4 正则化

正则化也称之为对参数的惩罚项,也有称之为权重衰减,即Penalty,WeightDecay

对于如下平方和,称之为 L2 正则化,也叫(ridge回归,岭回归)
L = 1 2 [ k i d e n i t y x + k s i n sin ⁡ ( x ) + k c o s cos ⁡ ( x ) + b − y ] 2 + λ ( k i d e a n i t y 2 + k s i n 2 + k c o s 2 + b 2 ) L=\frac{1}{2}[k_{i d e n i t y}x+k_{s i n}\sin(x)+k_{c o s}\cos(x)+b-y]^{2}+\lambda(k_{i d e a n i t y}^{2}+k_{s i n}^{2}+k_{c o s}^{2}+b^{2}) L=21[kidenityx+ksinsin(x)+kcoscos(x)+by]2+λ(kideanity2+ksin2+kcos2+b2)
对于如下绝对值和,称之为 L1 正则化,也叫(lasso回归)
L = 1 2 [ k i d e n t i t y x + k s i n sin ⁡ ( x ) + k c o s cos ⁡ ( x ) + b − y ] 2 + λ ( ∣ k i d e n t i t y ∣ + ∣ k s i n ∣ + ∣ k c o s ∣ + ∣ b ∣ ) L=\frac{1}{2}[k_{identity}x+k_{sin}\sin(x)+k_{cos}\cos(x)+b-y]^{2}+\lambda(\lvert k_{identity}\rvert+\lvert k_{s i n}\rvert+\lvert k_{cos}\rvert+\lvert b\rvert) L=21[kidentityx+ksinsin(x)+kcoscos(x)+by]2+λ(∣kidentity+ksin+kcos+b∣)
因此带正则化的模型可以表示为如下形式
L = L θ ( x ) + λ ∣ ∣ θ ∣ ∣ p p L=L_{\theta}(x)+\lambda||\theta||_p^p L=Lθ(x)+λ∣∣θpp
此时表示最小化经验风险 L θ ( x ) L_{\theta}(x) Lθ(x) 和最小化结构风险 λ ∣ ∣ θ ∣ ∣ p p \lambda||\theta||_p^p λ∣∣θpp. 结构风险的参数在这里是 p p p 范数的 p p p 次方

正则化是增加了对模型复杂度的约束,要求模型使用更少的参数表示问题,是抑制过拟合的现象发生的一种常见手段。如下图所示,左图为欠拟合(模型太简单,就好比你去参加考试,考什么题型都不知道?以为只有填空题,上考场发现还有简答题🤣),右图为过拟合(模型太复杂,就好比你去参加考试,把上届的考试试卷完全背下来,然后发现这次参数换了,你就不知道做了😂),中间的图为恰当拟合,是我们想要的结果。

在这里插入图片描述

3.5 范数

范数是一种将向量映射到非负实数的函数,它可以用来度量向量的大小或距离。在数学和计算机科学中,范数是一种重要的概念,在很多领域都有广泛的应用,比如矩阵论、优化理论、机器学习等。(from Wikipedia)

对于一个 n n n 维实向量 x x x,它的 p p p 范数(p-norm)定义为:
∥ x ∥ p = ( ∑ i = 1 n ∣ x i ∣ p ) 1 p \|x\|_p=\left(\sum\limits_{i=1}^n|x_i|^p\right)^{\frac{1}{p}} xp=(i=1nxip)p1
其中p是一个正整数, ∣ x i ∣ |x_i| xi 表示 x x x 的第 i i i 个分量的绝对值。当 p = 2 p=2 p=2 时,我们称其为欧几里得范数(Euclidean norm),它等价于向量的长度或模长,即:
∥ x ∥ 2 = x 1 2 + x 2 2 + ⋯ + x n 2 \|x\|_2=\sqrt{x_1^2+x_2^2+\cdots+x_n^2} x2=x12+x22++xn2
p = 1 p=1 p=1 时,我们称其为曼哈顿范数(Manhattan norm),它等价于向量各个分量绝对值之和,即:
∥ x ∥ 1 = ∣ x 1 ∣ + ∣ x 2 ∣ + ⋯ + ∣ x n ∣ \|x\|_1=|x_1|+|x_2|+\cdots+|x_n| x1=x1+x2++xn
p p p 趋近于无穷大时,我们称其为无穷范数(infinity norm),它等价于向量的各个分量绝对值的最大值,即:
∥ x ∥ ∞ = max ⁡ i ∣ x i ∣ \|x\|_\infty=\max\limits_i|x_i| x=imaxxi
除了这三种常见的范数之外,还有其他的范数,比如负一范数、核范数等等。每种范数都有不同的特点和应用,我们需要根据具体问题来选择合适的范数。

3.6 P范数绘图

  • P范数绘图,就是把P范数这个函数作为距离度量,在范数空间内绘制这个单位圆
  • 在欧式空间中,它就是个老老实实的圆圈,但是如果度量距离采用不同的形式,其显示的图形则不同
  • 在单位圆中,x、y到原点的距离是1,也就是半径为r=1的圆,这个距离的度量方式,可以用范数代替,使得在范数空间进行距离度量
    • 在2范数时,表达式为: x 2 + y 2 = r \sqrt{x^2+y^2}=r x2+y2 =r
    • 其等价于 x 2 + y 2 = r 2 x^2+y^2=r^2 x2+y2=r2
    • 在P范数的时候,其距离度量就成了 ∣ x ∣ p + ∣ y ∣ p = ∣ r ∣ p |x|^p+|y|^p=|r|^p xp+yp=rp
    • 由于是单位圆 r = 1 r=1 r=1,则可以推导得到 ∣ y ∣ = 1 − ∣ x ∣ p p |y|={\sqrt[p]{1-|x|^{p}}} y=p1xp

代码实现如下

from cProfile import label
from turtle import color
import numpy as np
import matplotlib.pyplot as plt

plt.figure(figsize=(10,10))
x = np.concatenate([np.linspace(1, 0, 100), np.linspace(0, -1, 100)])

params = [0.5, 1, 2, 999]
colors = ["b-", "g-", "r-", "y-"]
for p, c in zip(params, colors):
    y = (1 - np.abs(x)**p)**(1/p)
    plt.plot(x, y, c, label=f"p={p}")
    plt.plot(x, -y, c)

plt.legend(title="PNorm", loc="upper left")
plt.savefig("./PNorm.png", bbox_inches='tight')
plt.show()

P范数效果图如下:

在这里插入图片描述

总结

本次课程主要学习了线性回归,通过房价预测案例了解了回归模型,并通过代码实现了房价预测。同时了解了正则化,通过正则化项能够降低模型复杂度,同时减少过拟合,期待下次课程😄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/445812.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【移动端网页布局】移动端网页布局基础概念 ① ( 移动端浏览器 | 移动端屏幕分辨率 | 移动端网页调试方法 )

文章目录 一、移动端浏览器二、移动端屏幕分辨率三、移动端网页调试方法 一、移动端浏览器 移动端浏览器 比 PC 端浏览器发展要晚 , 使用的技术比较新 , 对 HTML5 CSS3 支持较好 , 常见的浏览器如下 : UC / QQ / Opera / Chrom / 360 / 百度 / 搜狗 / 猎豹 国内的浏览器 基本…

算法套路十一 ——回溯法之组合型回溯

算法套路十一 ——回溯法之组合型回溯 该节是在上一节回溯法之子集型回溯的基础上进行描写&#xff0c;组合型回溯会在子集型回溯的基础上判断所选子集是否符合组合要求&#xff0c; 故请首先阅读上一节算法套路十——回溯法之子集型回溯 算法示例&#xff1a;LeetCode77. 组合…

【C++ 十八】C++ map/ multimap容器

C map/ multimap 容器 文章目录 C map/ multimap 容器前言1 map 基本概念2 map 构造和赋值3 map 大小和交换4 map 插入和删除5 map 查找和统计6 map 容器排序 总结 前言 本文包含map基本概念、map构造和赋值、map大小和交换、map插入和删除、map查找和统计、map容器排序。 1 m…

使用Glib中测试框架对C代码进行单元测试

C项目的测试框架比较常见的是Google的gtest&#xff08;前文CMake项目使用ctestgtest进行单元测试有使用实例介绍gtest&#xff0c;感兴趣的读者可以去看看&#xff09;&#xff0c;也有一些其它框架&#xff0c;比如Boost中的测试框架。这些框架虽然也可以测试C代码&#xff0…

Vue 消息订阅与发布

消息订阅与发布&#xff0c;也可以实现任意组件之间的通信。 订阅者&#xff1a;就相当于是我们&#xff0c;用于接收数据。 发布者&#xff1a;就相当于是媒体&#xff0c;用于传递数据。 安装消息订阅与发布插件&#xff1a; 在原生 JS 中 不太容易实现消息订阅与发布&…

Unity-ML-Agents-代码解读-RollerBall

使用版本&#xff1a;https://github.com/Unity-Technologies/ml-agents/releases/tag/release_19 文件路径&#xff1a;ml-agents-release_19/docs/Learning-Environment-Create-New.md 20和19的在rollerBall上一样&#xff1a;https://github.com/Unity-Technologies/ml-ag…

CSDN博客编写教程

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

osg widget 试用 方法

按钮 一个常见的 osg::Widget 就是按钮。下面的代码展示了如何使用 osg::Switch 和 osgText 创建一个简单的按钮&#xff1a; osg::ref_ptr<osg::Switch> buttonSwitch new osg::Switch(); osg::ref_ptr<osgText::Text> buttonText new osgText::Text(); buttonT…

浏览器不好用?插件来帮忙

一、目的 浏览器本身具备的功能并不完善&#xff0c;不同的用户可以为自己浏览器增加想要功能&#xff0c;使得浏览器更能符合自己的需求&#xff0c;提高浏览器使用的舒适度 二、推荐插件 AdblockPlus LastPass&#xff08;密码记录&#xff0c;全平台通用&#xff09; Dar…

JSON的用法和说明

JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式。 JSON建构于两种结构&#xff1a; "名称/值"对的集合。理解为对象 值的有序列表。理解为数组 JSON具有以下这些形式&#xff1a; 对象是一个无序的“ ’名称/值‘ 对”集合。一个…

阿里 Arthas (阿尔萨斯)工具的使用

目录 使用 一、安装与启动命令行控制台使用 使用 这款工具可以监控线上、测试或者其他环境的java运行中程序的情况&#xff0c;用于定位线上、测试等环境的问题。 一、安装与启动 通过termius远程登录测试或者线上环境&#xff0c;cd到指定目录下&#xff0c;输入命令&#…

高效部署Redis Sentinel模式(哨兵模式),手把手教学

Redis Sentinel模式部署 前言一、服务器部署同版本的redis1、换软件源在yum拉取包的时候启用remi源 二、修改配置文件1.修改/etc/redis.conf2.配置/etc/redis/sentinel.conf 三、启动redis服务1、启动服务2、连接redis3、检查redis 前言 这里就不过多的解释高可用的好处了&…

设计模式:行为型模式 - 迭代器模式

文章目录 1.概述2.结构3.案例实现4.优缺点5.使用场景6.JDK源码解析 1.概述 定义&#xff1a; 提供一个对象来顺序访问聚合对象中的一系列数据&#xff0c;而不暴露聚合对象的内部表示。 2.结构 迭代器模式主要包含以下角色&#xff1a; 抽象聚合&#xff08;Aggregate&…

Learn OpenCV by Examples - with Python

目录 关于OpenCV 新增内容 Content 1.锐化 2.阈值&#xff0c;二值化和自适应阈值 本文是自己在kaggle上学习OpenCV的学习笔记&#xff0c;如果对你有所帮助再好不过了。这是原文链接Learn OpenCV by Examples - with Python | Kaggle里面不仅有代码还有图片等。如果你还没…

网络安全-CDN绕过寻找真实IP

网络安全-CDN绕过寻找真实IP CDN就是CDN加速&#xff0c;就是根据你的目标让你访问的更快 CDN CDN&#xff0c;即内容分发网络&#xff0c;主要解决因传输距离和不同运营商节点造成的网络速度性能低下的问题。说得简单点&#xff0c;就是一组在不同运营商之间的对接节点上的…

Docker AIGC等大模型深度学习环境搭建(完整详细版)

本文是《Python从零开始进行AIGC大模型训练与推理》&#xff08;https://blog.csdn.net/suiyingy/article/details/130169592&#xff09;专栏的一部分&#xff0c;所述方法和步骤基本上是通用的&#xff0c;不局限于AIGC大模型深度学习环境。 Docker AIGC等大模型深度学习环境…

Go语言之反射(反射的简单使用,原理)

一、反射的基础 1.什么是反射 Go语言中&#xff0c;反射的机制就是在运行的时候&#xff0c;可以获取到其变量的类型和值&#xff0c;且可以对其类型和值进行检查&#xff0c;对其值进行修改。即在不知道具体的类型的情况下&#xff0c;可以用反射机制来查看变量类型、更新变…

50 Projects 50 Days - Hidden Search Widget 学习记录

项目地址 Hidden Search Widget 展示效果 Hidden Search Widget 实现思路 点击搜索按钮&#xff0c;展开输入框&#xff0c;主要元素就两个&#xff1a;input输入框和button&#xff0c;这两个本身就是行内元素。点击触发的动作拆分为两个&#xff0c;第一个是input输入框…

Vue核心 事件处理

1.8. 事件处理 1.8.1.事件的基本使用: 使用v-on:xxx或**xxx**绑定事件&#xff0c;其中 xxx 是事件名事件的回调需要配置在methods对象中&#xff0c;最终会在vm上methods中配置的函数&#xff0c;不要用箭头函数&#xff0c;否则this就不是vm了methods中配置的函数&#xff…

手撕Twitter推荐算法

Twitter近期开源了其推荐系统源码[1,2,3]&#xff0c;截止现在已经接近36k star。但网上公开的文章都是blog[1]直译&#xff0c;很拗口&#xff0c;因此特地开个系列系统分享下。系列涵盖&#xff1a; Twitter整体推荐系统架构&#xff1a;涵盖图数据挖掘、召回、精排、规则多…