线性回归 - 机器学习
· 9.4 KiB · Text
Raw
## 什么是线性回归
回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系
线性回归(Linear Regression)是一种用于研究因变量(y)和一个或多个自变量(x)之间的线性关系的统计方法
## 线性回归的形式
### 简单线性回归(只有一个变量)
基本公式:
$$
y = wx + b
$$
- $y$:目标值(因变量)
- $x$:输入特征(自变量)
- $w$:斜率(权重,weight)
- $b$:截距项(bias)
### 多元线性回归(多个输入变量)
基本公式:
$$
y = w_{1}x_{1}+w_{2}x_{2}+\dots+w_{n}x_{n}+b=\mathbf{w}^\mathrm{T}\mathbf{x} + b
$$
- $\mathbf{x} = [x_1, x_2, \ldots, x_n]^\mathrm{T}$: 特征向量
- $\mathbf{w} = [w_1, w_2, \ldots, w_n]^\mathrm{T}$: 权重向量
## 线性回归的目标:最小化误差
训练线性回归的目的是让模型的预测值 $\hat{y}$ 尽可能接近真实值 $y$
$$
\hat{y}_{i}=\mathbf{w}^\mathrm{T}\mathbf{x}_{i}+b
$$
### 损失函数:均方误差(MSE)
$$
J(w, b) = \frac{1}{m}{\sum^{m}_{i=1}(\hat{y}_{i}-y_{i})^2}
$$
- $m$:样本数量
- $\hat{y}_{i}$:第 $i$ 个样本第预测值
- $y_{i}$:真实值
我们希望平方误差的平均值越小越好
## 训练线性回归模型
### 梯度下降(Gradient Descent)
通过迭代调整 $w$ 和 $b$ 来最小化损失函数
1. 随机初始化 $w$,$b$
2. 重复以下过程直到收敛(即找到最小损失函数)
- 计算梯度(对 $w$ 和 $b$)
- 更新参数:
$$
w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b}
$$
### 确定一个好的学习率
- 学习率太大:步子太大,可能跳过最优解,甚至导致损失函数发散(误差越来越大)
- 学习率太小:步子太小,学习速度慢,可能需要很多次迭代才能收敛,甚至卡在局部最优解
#### 从一个小的学习率开始
通常可以从一个较小的学习率开始尝试,比如 0.01 或 0.001,然后根据训练情况调整。这是一个安全的策略,避免一开始就发散
可能尝试从 0.001 开始,每次逐渐递增三倍,观察学习曲线
#### 画出学习曲线
通过学习曲线(损失函数)来观察是否稳定下降,如果曲线平滑下降,学习率可能比较合适;如果曲线震荡或者上升,学习率可能太大;如果下降很慢,则说明太小
### 特征缩放
特征缩放(包括归一化和标准化)是一种数据预处理方法,目的是让不同特征的数值范围变得相似,避免某些特征因为数值范围大而在模型中占据主导地位
#### 为什么要使用特征缩放
梯度下降法对特征的数值范围非常敏感。如果特征的范围不一致,梯度的大小会差异很大,导致权重更新时“步子”不均匀,有的特征更新快,有的慢,模型可能需要更多迭代才能收敛,甚至可能无法收敛
### 什么时候需要进行特征缩放
通常情况下建议对所有数据进行特征缩放,以保证模型训练的公平性和稳定性,但如果特征范围本来就相似,或者算法对范围不敏感,可以不缩放
#### 算法
#### 最小 - 最大归一化
此算法的范围是 $[0, 1]$ 或者 $[-1, 1]$
$$
x' = \frac{{x - x_{min}}}{x_{max}-x_{min}}
$$
#### 标准化(Standardization, Z - Score Scaling)
$$
X'=\frac{X-\mu}{\sigma}
$$
这里 $X$ 是原始特征值,$\mu$ 是该特征的均值,$\sigma$ 是标准差
## 手写一个简单线性回归
需要使用两个库,`numpy` 和 `matplotlib`,一个用来做数学计算,另外一个用来画图,做可视化
```python
# 导入基础库并且生成模拟数据
import numpy as np
import matplotlib.pyplot as plt
```
使用 `numpy` 来生成一些模拟数据,生成用于模拟的数据集
```python
# 生成模拟数据
np.random.seed(0) # 设置随机种子
X = np.random.rand(100, 1) # 100个样本,一个特征
y = 2 * X + np.random.rand(100, 1) * 0.6 # 高斯噪声,随机点生成
```
之后来初始化和定义用于计算的一些参数和函数,下面函数的一些概念,均在上面基础篇上出现过
```python
# 初始化参数
w = np.random.randn() # 初始化一个随机的w数值,即刚开始的随机变量
b = np.random.randn()
# 学习率
lr = 0.01
# 损失函数:均方误差(ESM)
def compute_loss(y_pred, y_true):
return np.mean((y_pred - y_true) ** 2) # 使用预测减去实际的计算损失
# 预测函数
def predict(X):
return w * X + b # 使用原来的参数进行预测
# 梯度计算,直到收敛
def compute_gradients(X, y, y_pred):
n = len(X) # 样本数量
dw = (2/n) * np.sum((y_pred - y) * X) # 对w的梯度
db = (2/n) * np.sum(y_pred - y) # 对b的梯度
return dw, db
```
正式开始训练模型
```python
# 训练模型
epochs = 3000 # 确定训练代数
for epoch in range(epochs):
y_pred = predict(X) # 确定预测的y数值
loss = compute_loss(y_pred, y) # 计算损失
dw, db = compute_gradients(X, y, y_pred) # 计算梯度
# 更新参数
w -= lr * dw
b -= lr * db
# 每100次打印一次参数
if epoch % 100 == 0:
print(f"当前迭代 {epoch}:Loss = {loss}, w = {w}, b = {b}")
```
这里的更新参数遵从:
$$
w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b}
$$
在前面使用 `compute_gradients` 函数对其进行了梯度计算
```python
dw = (2/n) * np.sum((y_pred - y) * X)
```
这个 `np.sum (...)` 就是把每个样本的梯度项全部加起来(累加),最终得到平均的梯度
最终训练的结果为
```
当前迭代 0:Loss = 3.360457010379737, w = 1.1451367960151249, b = -1.0437374740069902
当前迭代 100:Loss = 0.05000333971000506, w = 1.8210078929333788, b = 0.2606548461628321
当前迭代 200:Loss = 0.028025721164853103, w = 1.8852749691368647, b = 0.36186703590328345
当前迭代 300:Loss = 0.02777880142277748, w = 1.8986843397860884, b = 0.36593878510327527
当前迭代 400:Loss = 0.027699259584641443, w = 1.9069340963491677, b = 0.36264078935988026
当前迭代 500:Loss = 0.02763916198454221, w = 1.9138655152547173, b = 0.35919983431381447
当前迭代 600:Loss = 0.027593215478616283, w = 1.9199035531556587, b = 0.35614573793057736
当前迭代 700:Loss = 0.027558084323772734, w = 1.9251814722129978, b = 0.35347149826393925
当前迭代 800:Loss = 0.027531222670007408, w = 1.9297964397055116, b = 0.35113279056033386
当前迭代 900:Loss = 0.027510683964356013, w = 1.9338318480268912, b = 0.3490877531373423
当前迭代 1000:Loss = 0.027494979851960415, w = 1.9373604894195964, b = 0.3472995292155738
当前迭代 1100:Loss = 0.027482972320895915, w = 1.9404460045107752, b = 0.34573587000715716
当前迭代 1200:Loss = 0.027473791235162914, w = 1.943144041104862, b = 0.3443685748584561
当前迭代 1300:Loss = 0.02746677127953805, w = 1.9455032586954581, b = 0.3431729844142777
当前迭代 1400:Loss = 0.027461403746938083, w = 1.9475662061191967, b = 0.3421275360263249
当前迭代 1500:Loss = 0.02745729967452634, w = 1.9493700889361771, b = 0.3412133748793251
当前迭代 1600:Loss = 0.02745416165718836, w = 1.950947440438186, b = 0.3404140139426129
当前迭代 1700:Loss = 0.027451762295816154, w = 1.9523267083914417, b = 0.3397150366241853
当前迭代 1800:Loss = 0.02744992771863752, w = 1.9535327680962429, b = 0.33910383676535083
当前迭代 1900:Loss = 0.027448524981449462, w = 1.9545873710166628, b = 0.338569391286805
当前迭代 2000:Loss = 0.02744745243370295, w = 1.9555095370714028, b = 0.3381020613857766
当前迭代 2100:Loss = 0.027446632352308983, w = 1.9563158976608237, b = 0.33769341869880004
当前迭代 2200:Loss = 0.027446005309368537, w = 1.957020995616696, b = 0.3373360932949432
当前迭代 2300:Loss = 0.027445525865679117, w = 1.9576375474843057, b = 0.3370236407580188
当前迭代 2400:Loss = 0.027445159277944046, w = 1.958176672867222, b = 0.3367504259605912
...
当前迭代 2600:Loss = 0.02744466466310397, w = 1.9590603159625233, b = 0.33630261849885573
当前迭代 2700:Loss = 0.027444500793311913, w = 1.9594207703080337, b = 0.33611994956114444
当前迭代 2800:Loss = 0.027444375496729134, w = 1.9597359588546208, b = 0.3359602201593469
当前迭代 2900:Loss = 0.02744427969363169, w = 1.960011566074055, b = 0.3358205495502205
```
下面进行可视化
```python
# 可视化
plt.scatter(X, y, label='Data')
plt.plot(X, predict(X), color='red', label='Fitted Line')
plt.legend()
plt.title("Simple Linear Regression")
plt.xlabel("x")
plt.ylabel("y")
plt.show()
```

### Q&A
#### 如何确定学习率(Lr)
1. 一般来说按照经验值,从 0.1 和 0.01 开始
2. 调参实验:试几个值比如 0.001、0.01、0.1、0.5 比较 loss 收敛情况。
3. 画出 loss 曲线:观察 loss 曲线的趋势是否收敛平稳
4. 学习率衰减(进阶):开始时大、逐渐减小
#### 如何确定要迭代(Epochs)多少次
1. 看 Loss 是否还在下降,如果 loss 曲线已经趋于平稳,说明训练快结束了
2. 设置最大值,如 1000 次,人工设一个上限,比如 100、500、1000
3. 提前停止(Early Stopping),如果连续 N 次迭代 loss 没下降,就提前结束
4. 使用验证集监控过拟合,如果在验证集上效果开始变差,就说明模型训练太久了
1 | ## 什么是线性回归 |
2 | |
3 | 回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系 |
4 | |
5 | 线性回归(Linear Regression)是一种用于研究因变量(y)和一个或多个自变量(x)之间的线性关系的统计方法 |
6 | |
7 | ## 线性回归的形式 |
8 | |
9 | ### 简单线性回归(只有一个变量) |
10 | |
11 | 基本公式: |
12 | |
13 | $$ |
14 | y = wx + b |
15 | $$ |
16 | |
17 | - $y$:目标值(因变量) |
18 | - $x$:输入特征(自变量) |
19 | - $w$:斜率(权重,weight) |
20 | - $b$:截距项(bias) |
21 | |
22 | ### 多元线性回归(多个输入变量) |
23 | |
24 | 基本公式: |
25 | |
26 | $$ |
27 | y = w_{1}x_{1}+w_{2}x_{2}+\dots+w_{n}x_{n}+b=\mathbf{w}^\mathrm{T}\mathbf{x} + b |
28 | $$ |
29 | |
30 | - $\mathbf{x} = [x_1, x_2, \ldots, x_n]^\mathrm{T}$: 特征向量 |
31 | - $\mathbf{w} = [w_1, w_2, \ldots, w_n]^\mathrm{T}$: 权重向量 |
32 | |
33 | ## 线性回归的目标:最小化误差 |
34 | |
35 | 训练线性回归的目的是让模型的预测值 $\hat{y}$ 尽可能接近真实值 $y$ |
36 | |
37 | $$ |
38 | \hat{y}_{i}=\mathbf{w}^\mathrm{T}\mathbf{x}_{i}+b |
39 | $$ |
40 | |
41 | ### 损失函数:均方误差(MSE) |
42 | |
43 | $$ |
44 | J(w, b) = \frac{1}{m}{\sum^{m}_{i=1}(\hat{y}_{i}-y_{i})^2} |
45 | $$ |
46 | |
47 | - $m$:样本数量 |
48 | - $\hat{y}_{i}$:第 $i$ 个样本第预测值 |
49 | - $y_{i}$:真实值 |
50 | |
51 | 我们希望平方误差的平均值越小越好 |
52 | |
53 | ## 训练线性回归模型 |
54 | |
55 | ### 梯度下降(Gradient Descent) |
56 | |
57 | 通过迭代调整 $w$ 和 $b$ 来最小化损失函数 |
58 | |
59 | 1. 随机初始化 $w$,$b$ |
60 | 2. 重复以下过程直到收敛(即找到最小损失函数) |
61 | - 计算梯度(对 $w$ 和 $b$) |
62 | - 更新参数: |
63 | $$ |
64 | w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b} |
65 | $$ |
66 | ### 确定一个好的学习率 |
67 | |
68 | - 学习率太大:步子太大,可能跳过最优解,甚至导致损失函数发散(误差越来越大) |
69 | - 学习率太小:步子太小,学习速度慢,可能需要很多次迭代才能收敛,甚至卡在局部最优解 |
70 | |
71 | #### 从一个小的学习率开始 |
72 | |
73 | 通常可以从一个较小的学习率开始尝试,比如 0.01 或 0.001,然后根据训练情况调整。这是一个安全的策略,避免一开始就发散 |
74 | |
75 | 可能尝试从 0.001 开始,每次逐渐递增三倍,观察学习曲线 |
76 | |
77 | #### 画出学习曲线 |
78 | |
79 | 通过学习曲线(损失函数)来观察是否稳定下降,如果曲线平滑下降,学习率可能比较合适;如果曲线震荡或者上升,学习率可能太大;如果下降很慢,则说明太小 |
80 | |
81 | ### 特征缩放 |
82 | |
83 | 特征缩放(包括归一化和标准化)是一种数据预处理方法,目的是让不同特征的数值范围变得相似,避免某些特征因为数值范围大而在模型中占据主导地位 |
84 | |
85 | #### 为什么要使用特征缩放 |
86 | |
87 | 梯度下降法对特征的数值范围非常敏感。如果特征的范围不一致,梯度的大小会差异很大,导致权重更新时“步子”不均匀,有的特征更新快,有的慢,模型可能需要更多迭代才能收敛,甚至可能无法收敛 |
88 | |
89 | ### 什么时候需要进行特征缩放 |
90 | |
91 | 通常情况下建议对所有数据进行特征缩放,以保证模型训练的公平性和稳定性,但如果特征范围本来就相似,或者算法对范围不敏感,可以不缩放 |
92 | |
93 | #### 算法 |
94 | |
95 | #### 最小 - 最大归一化 |
96 | |
97 | 此算法的范围是 $[0, 1]$ 或者 $[-1, 1]$ |
98 | |
99 | $$ |
100 | x' = \frac{{x - x_{min}}}{x_{max}-x_{min}} |
101 | $$ |
102 | #### 标准化(Standardization, Z - Score Scaling) |
103 | |
104 | $$ |
105 | X'=\frac{X-\mu}{\sigma} |
106 | $$ |
107 | |
108 | 这里 $X$ 是原始特征值,$\mu$ 是该特征的均值,$\sigma$ 是标准差 |
109 | |
110 | ## 手写一个简单线性回归 |
111 | |
112 | 需要使用两个库,`numpy` 和 `matplotlib`,一个用来做数学计算,另外一个用来画图,做可视化 |
113 | |
114 | ```python |
115 | # 导入基础库并且生成模拟数据 |
116 | import numpy as np |
117 | import matplotlib.pyplot as plt |
118 | ``` |
119 | |
120 | 使用 `numpy` 来生成一些模拟数据,生成用于模拟的数据集 |
121 | |
122 | ```python |
123 | # 生成模拟数据 |
124 | np.random.seed(0) # 设置随机种子 |
125 | X = np.random.rand(100, 1) # 100个样本,一个特征 |
126 | y = 2 * X + np.random.rand(100, 1) * 0.6 # 高斯噪声,随机点生成 |
127 | ``` |
128 | |
129 | 之后来初始化和定义用于计算的一些参数和函数,下面函数的一些概念,均在上面基础篇上出现过 |
130 | |
131 | ```python |
132 | # 初始化参数 |
133 | w = np.random.randn() # 初始化一个随机的w数值,即刚开始的随机变量 |
134 | b = np.random.randn() |
135 | |
136 | # 学习率 |
137 | lr = 0.01 |
138 | |
139 | # 损失函数:均方误差(ESM) |
140 | def compute_loss(y_pred, y_true): |
141 | return np.mean((y_pred - y_true) ** 2) # 使用预测减去实际的计算损失 |
142 | |
143 | # 预测函数 |
144 | def predict(X): |
145 | return w * X + b # 使用原来的参数进行预测 |
146 | |
147 | # 梯度计算,直到收敛 |
148 | def compute_gradients(X, y, y_pred): |
149 | n = len(X) # 样本数量 |
150 | dw = (2/n) * np.sum((y_pred - y) * X) # 对w的梯度 |
151 | db = (2/n) * np.sum(y_pred - y) # 对b的梯度 |
152 | return dw, db |
153 | ``` |
154 | |
155 | 正式开始训练模型 |
156 | |
157 | ```python |
158 | # 训练模型 |
159 | epochs = 3000 # 确定训练代数 |
160 | for epoch in range(epochs): |
161 | y_pred = predict(X) # 确定预测的y数值 |
162 | loss = compute_loss(y_pred, y) # 计算损失 |
163 | dw, db = compute_gradients(X, y, y_pred) # 计算梯度 |
164 | |
165 | # 更新参数 |
166 | w -= lr * dw |
167 | b -= lr * db |
168 | |
169 | # 每100次打印一次参数 |
170 | if epoch % 100 == 0: |
171 | print(f"当前迭代 {epoch}:Loss = {loss}, w = {w}, b = {b}") |
172 | ``` |
173 | |
174 | 这里的更新参数遵从: |
175 | |
176 | $$ |
177 | w := w - \eta \frac{\partial \text{J}}{\partial w}, \quad b := b - \eta \frac{\partial \text{J}}{\partial b} |
178 | $$ |
179 | |
180 | 在前面使用 `compute_gradients` 函数对其进行了梯度计算 |
181 | |
182 | ```python |
183 | dw = (2/n) * np.sum((y_pred - y) * X) |
184 | ``` |
185 | |
186 | 这个 `np.sum (...)` 就是把每个样本的梯度项全部加起来(累加),最终得到平均的梯度 |
187 | |
188 | 最终训练的结果为 |
189 | |
190 | ``` |
191 | 当前迭代 0:Loss = 3.360457010379737, w = 1.1451367960151249, b = -1.0437374740069902 |
192 | 当前迭代 100:Loss = 0.05000333971000506, w = 1.8210078929333788, b = 0.2606548461628321 |
193 | 当前迭代 200:Loss = 0.028025721164853103, w = 1.8852749691368647, b = 0.36186703590328345 |
194 | 当前迭代 300:Loss = 0.02777880142277748, w = 1.8986843397860884, b = 0.36593878510327527 |
195 | 当前迭代 400:Loss = 0.027699259584641443, w = 1.9069340963491677, b = 0.36264078935988026 |
196 | 当前迭代 500:Loss = 0.02763916198454221, w = 1.9138655152547173, b = 0.35919983431381447 |
197 | 当前迭代 600:Loss = 0.027593215478616283, w = 1.9199035531556587, b = 0.35614573793057736 |
198 | 当前迭代 700:Loss = 0.027558084323772734, w = 1.9251814722129978, b = 0.35347149826393925 |
199 | 当前迭代 800:Loss = 0.027531222670007408, w = 1.9297964397055116, b = 0.35113279056033386 |
200 | 当前迭代 900:Loss = 0.027510683964356013, w = 1.9338318480268912, b = 0.3490877531373423 |
201 | 当前迭代 1000:Loss = 0.027494979851960415, w = 1.9373604894195964, b = 0.3472995292155738 |
202 | 当前迭代 1100:Loss = 0.027482972320895915, w = 1.9404460045107752, b = 0.34573587000715716 |
203 | 当前迭代 1200:Loss = 0.027473791235162914, w = 1.943144041104862, b = 0.3443685748584561 |
204 | 当前迭代 1300:Loss = 0.02746677127953805, w = 1.9455032586954581, b = 0.3431729844142777 |
205 | 当前迭代 1400:Loss = 0.027461403746938083, w = 1.9475662061191967, b = 0.3421275360263249 |
206 | 当前迭代 1500:Loss = 0.02745729967452634, w = 1.9493700889361771, b = 0.3412133748793251 |
207 | 当前迭代 1600:Loss = 0.02745416165718836, w = 1.950947440438186, b = 0.3404140139426129 |
208 | 当前迭代 1700:Loss = 0.027451762295816154, w = 1.9523267083914417, b = 0.3397150366241853 |
209 | 当前迭代 1800:Loss = 0.02744992771863752, w = 1.9535327680962429, b = 0.33910383676535083 |
210 | 当前迭代 1900:Loss = 0.027448524981449462, w = 1.9545873710166628, b = 0.338569391286805 |
211 | 当前迭代 2000:Loss = 0.02744745243370295, w = 1.9555095370714028, b = 0.3381020613857766 |
212 | 当前迭代 2100:Loss = 0.027446632352308983, w = 1.9563158976608237, b = 0.33769341869880004 |
213 | 当前迭代 2200:Loss = 0.027446005309368537, w = 1.957020995616696, b = 0.3373360932949432 |
214 | 当前迭代 2300:Loss = 0.027445525865679117, w = 1.9576375474843057, b = 0.3370236407580188 |
215 | 当前迭代 2400:Loss = 0.027445159277944046, w = 1.958176672867222, b = 0.3367504259605912 |
216 | ... |
217 | 当前迭代 2600:Loss = 0.02744466466310397, w = 1.9590603159625233, b = 0.33630261849885573 |
218 | 当前迭代 2700:Loss = 0.027444500793311913, w = 1.9594207703080337, b = 0.33611994956114444 |
219 | 当前迭代 2800:Loss = 0.027444375496729134, w = 1.9597359588546208, b = 0.3359602201593469 |
220 | 当前迭代 2900:Loss = 0.02744427969363169, w = 1.960011566074055, b = 0.3358205495502205 |
221 | ``` |
222 | |
223 | 下面进行可视化 |
224 | |
225 | ```python |
226 | # 可视化 |
227 | plt.scatter(X, y, label='Data') |
228 | plt.plot(X, predict(X), color='red', label='Fitted Line') |
229 | plt.legend() |
230 | plt.title("Simple Linear Regression") |
231 | plt.xlabel("x") |
232 | plt.ylabel("y") |
233 | plt.show() |
234 | ``` |
235 | |
236 |  |
237 | |
238 | ### Q&A |
239 | |
240 | #### 如何确定学习率(Lr) |
241 | |
242 | 1. 一般来说按照经验值,从 0.1 和 0.01 开始 |
243 | 2. 调参实验:试几个值比如 0.001、0.01、0.1、0.5 比较 loss 收敛情况。 |
244 | 3. 画出 loss 曲线:观察 loss 曲线的趋势是否收敛平稳 |
245 | 4. 学习率衰减(进阶):开始时大、逐渐减小 |
246 | |
247 | #### 如何确定要迭代(Epochs)多少次 |
248 | |
249 | 1. 看 Loss 是否还在下降,如果 loss 曲线已经趋于平稳,说明训练快结束了 |
250 | 2. 设置最大值,如 1000 次,人工设一个上限,比如 100、500、1000 |
251 | 3. 提前停止(Early Stopping),如果连续 N 次迭代 loss 没下降,就提前结束 |
252 | 4. 使用验证集监控过拟合,如果在验证集上效果开始变差,就说明模型训练太久了 |