目录
- 线性回归
- 前言
- 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=0∑nθ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=1∏n2πσ1⋅e−2σ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=1∏n2πσ1⋅e−2σ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(θ)=log∏i=1n2πσ1⋅e−2σ2εi2=i=1∑nlog2πσ1⋅e−2σ2εi2=i=1∑n(log2πσ1+loge−2σ2εi2)=nlog2πσ1+i=1∑n−2σ2εi2=nlog2πσ1−2σ21i=1∑nε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=1∑nεi2
又由于
ε
=
y
−
h
θ
(
x
)
\varepsilon=y-h_{\theta}(x)
ε=y−hθ(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=1∑n(yi−hθ(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=1∑n(yi−hθ(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)
- 结构体的定义和使用。在代码中,使用了一个结构体
Item
来表示每个数据项,包括年份和房价。结构体的成员变量可以通过.
来访问。- 文件读写的操作。在代码中,使用了
fstream
库来读取 csv 格式的数据文件,其中涉及到了文件的打开、读取、关闭等操作。- 向量的使用。在代码中,使用了
std::vector
来存储读取的数据。vector
是一个动态数组,可以在运行时动态增加或删除元素。- 使用 tuple 进行多个返回值的返回。在代码中,使用了
std::tuple
来同时返回均值和标准差。tuple
是一个泛型容器,可以包含任意数量的元素。- 数学函数的使用。在代码中,使用了
std::pow
和std::sqrt
等数学函数来计算平方和平方根等操作。- printf 和 cout 的使用。在代码中,使用了
printf
和cout
来输出结果。printf
是 C 语言风格的输出函数,cout
是 C++ 中的流式输出方式。- 命名空间的使用。在代码中,使用了命名空间来组织函数和结构体,以避免命名冲突。
- 2.0f是一个浮点型字面量,表示一个单精度浮点数,即
float
类型。而2.0则是一个双精度浮点数字面量,即double
类型。在 C++ 中,当两个不同类型的数值进行运算时,系统会自动将其转换为同一种类型进行计算- 使用
std::tie
的作用是将compute_mean_std
函数返回的两个Item
结构体类型的变量mean
和stdval
绑定在一起。这样可以在一次函数调用中返回多个变量,避免使用std::pair
或std::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)+b−y]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)+b−y]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)+b−y]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}}
∥x∥p=(i=1∑n∣xi∣p)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}
∥x∥2=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|
∥x∥1=∣x1∣+∣x2∣+⋯+∣xn∣
当
p
p
p 趋近于无穷大时,我们称其为无穷范数(infinity norm),它等价于向量的各个分量绝对值的最大值,即:
∥
x
∥
∞
=
max
i
∣
x
i
∣
\|x\|_\infty=\max\limits_i|x_i|
∥x∥∞=imax∣xi∣
除了这三种常见的范数之外,还有其他的范数,比如负一范数、核范数等等。每种范数都有不同的特点和应用,我们需要根据具体问题来选择合适的范数。
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 ∣x∣p+∣y∣p=∣r∣p
- 由于是单位圆 r = 1 r=1 r=1,则可以推导得到 ∣ y ∣ = 1 − ∣ x ∣ p p |y|={\sqrt[p]{1-|x|^{p}}} ∣y∣=p1−∣x∣p
代码实现如下:
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范数效果图如下:
总结
本次课程主要学习了线性回归,通过房价预测案例了解了回归模型,并通过代码实现了房价预测。同时了解了正则化,通过正则化项能够降低模型复杂度,同时减少过拟合,期待下次课程😄