前言

在机器学习的学习道路上,线性回归往往是每个人接触的第一个算法。它看似简单——不就是画一条直线或者一个平面去拟合数据点吗?但随着学习的深入,你会发现它背后藏着一整套完整的知识体系:损失函数、优化方法(正规方程 vs 梯度下降)、模型评估指标、以及让人头大的欠拟合与过拟合问题。

如果你也曾遇到过这样的困惑:为什么我的模型在训练集上表现完美,一到测试集就崩了?为什么加了那么多特征,效果反而不如一条简单的直线?那么,这篇文章就是为你准备的。


线性回归

线性回归介绍

线性回归

线性回归(Linear regression)是利用 回归方程(函数)一个或多个自变量(特征值)和因变量(目标值)之间 关系进行建模的一种分析方式。
在这里插入图片描述

线性回归分类

一元线性回归
y = kx +b
目标值只与一个因变量有关系
在这里插入图片描述
多元线性回归
在这里插入图片描述
目标值只与多个因变量有关系
在这里插入图片描述

应用场景

在这里插入图片描述

线性回归问题的求解

线性回归API的应用

预测播仔身高
已知数据:
在这里插入图片描述
在这里插入图片描述

from sklearn.linear_model import LinearRegression

# 准备数据
x_train = [[160], [166], [172], [174], [180]]
y_train = [[56.3], [60.6], [65.1], [68.5], [75]]

x_test = [[176]]

# 模型训练
# 创建模型对象
estimator = LinearRegression()
estimator.fit(x_train, y_train)

# 线性回归模型 可以查看斜率(w)截距(b)
print(f"权重:{estimator.coef_}")
print(f"截距:{estimator.intercept_}")

# 模型预测
y_pre = estimator.predict(x_test)
print(f"模型预测值为:{y_pre}")

在这里插入图片描述

损失函数

需要设置一个评判标准

在这里插入图片描述
误差概念:用预测值y – 真实值y就是误差

损失函数:衡量每个样本预测值与真实值效果的函数

“红色直线能更好的拟合所有点”也就是误差最小,误差和最小

在这里插入图片描述
当损失函数取最小值时,得到k就是最优解

想求一条直线更好的拟合所有点 y = kx + b

  • ​ 引入损失函数(衡量预测值和真实值效果) Loss(k, b)
  • ​ 通过一个优化方法,求损失函数最小值,得到K最优解

在这里插入图片描述
回归的损失函数:
均方误差 (Mean-Square Error, MSE)
在这里插入图片描述
平均绝对误差 (Mean Absolute Error , MAE)
在这里插入图片描述
在这里插入图片描述

一元线性回归的解析解

在这里插入图片描述
在这里插入图片描述

多元线性回归的解析解-正规方程法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

梯度下降算法

梯度下降算法思想

求解函数极值还有更通用的方法就是梯度下降法。顾名思义:沿着梯度下降的方向求解极小值 • 举个例子:坡度最陡下山法
在这里插入图片描述

  • 输入:初始化位置S;每步距离为a 。输出:从位置S到达山底
  • 步骤1:令初始化位置为山的任意位置S
  • 步骤2:在当前位置环顾四周,如果四周都比S高返回S;否则执行步骤3
  • 步骤3: 在当前位置环顾四周,寻找坡度最陡的方向,令其为x方向
  • 步骤4:沿着x方向往下走,长度为a,到达新的位置S‘
  • 步骤5:在S‘位置环顾四周,如果四周都比S‘高,则返回S‘。否则转到步骤3

通过循环迭代的方法不断更新位置S (相当于不断更新权重参数w)
在这里插入图片描述
最终找到最优解 这个方法可用来求损失函数最优解, 比正规方程更通用

梯度下降过程就和下山场景类似
可微分的损失函数,代表着一座山
寻找的函数的最小值,也就是山底

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

银行信贷案例

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

正规方程和梯度下降算法的对比

在这里插入图片描述

回归评估方法

平均绝对误差

Mean Absolute Error (MAE)

在这里插入图片描述

  • n 为样本数量, y 为实际值, y^\hat{y}y^ 为预测值
  • MAE 越小模型预测约准确

Sklearn 中MAE的API

from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test,y_predict)

均方误差

Mean Squared Error (MSE)
在这里插入图片描述

  • n 为样本数量, y 为实际值, y^\hat{y}y^ 为预测值
  • MSE 越小模型预测约准确

Sklearn 中MSE的API

from sklearn.metrics import mean_squared_error
mean_squared_error(y_test,y_predict)

均方根误差

Root Mean Squared Error (RMSE)
在这里插入图片描述

  • n 为样本数量, y 为实际值, y^\hat{y}y^ 为预测值
  • RMSE 越小模型预测约准确

三种指标的比较

我们绘制了一条直线 y = 2x +5 用来拟合 y = 2x + 5 + e. 这些数据点,其中e为噪声
在这里插入图片描述

  • 对比MAE 和 RMSE的公式,RMSE的计算公式中有一个平方项,因此:大的误差将被平方,因此会增加 RMSE 的值

  • 可以得出结论,RMSE 会放大预测误差较大的样本对结果的影响,而 MAE 只是给出了平均误差

  • 由于 RMSE 对误差的 平方和求平均 再开根号,大多数情况下RMSE>MAE

    举例 (1+3)/2 = 2 (12+32)/2=10/2=5=2.236\sqrt{(1^2+3^2)/2 }= \sqrt{10/2} = \sqrt{5} = 2.236(12+32)/2 =10/2 =5 =2.236
    在这里插入图片描述
    色线与第一张图中的直线一样:y = 2x +5

蓝色的点为: y = y + sin(x)*exp(x/20) + e 其中 exp() 表示指数函数

对比第一张图,所有的指标都变大了,RMSE 几乎是 MAE 值的两倍,因为它对预测误差较大的点比较敏感

是否可以得出结论: RMSE是更好的指标? 某些情况下MAE更有优势,例如:

  • 假设数据中有少数异常点偏差很大,如果此时根据 RMSE 选择线性回归模型,可能会选出过拟合的模型来
  • 在这种情况下,由于数据中的异常点极少,选择具有最低 MAE 的回归模型可能更合适
  • 除此之外,当两个模型计算RMSE时数据量不一致,也不适合在一起比较

波士顿房价预测案例

线性回归API

sklearn.linear_model.LinearRegression(fit_intercept=True)
  • 通过正规方程优化
  • 参数:fit_intercept,是否计算偏置
  • 属性:LinearRegression.coef_ (回归系数) LinearRegression.intercept_(偏置)
sklearn.linear_model.SGDRegressor(loss="squared_loss", fit_intercept=True, learning_rate ='constant', eta0=0.01)
  • 参数:loss(损失函数类型),fit_intercept(是否计算偏置)learning_rate (学习率)
  • 属性:SGDRegressor.coef_ (回归系数)SGDRegressor.intercept_ (偏置)

波士顿房价预测

在这里插入图片描述

案例背景介绍

数据介绍
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

给定的这些特征,是专家们得出的影响房价的结果属性。我们此阶段不需要自己去探究特征是否有用,只需要使用这些特征。到后面量化很多特征需要我们自己去寻找

案例分析

回归当中的数据大小不一致,是否会导致结果影响较大。所以需要做标准化处理。

  • 数据分割与标准化处理
  • 回归预测
  • 线性回归的算法效果评估

回归性能评估

均方误差(Mean Squared Error, MSE)评价机制:

MSE=1m∑i=1m(yi−y^)2\Large MSE = \frac{1}{m}\sum_{i=1}^{m}(y^i-\hat{y})^2MSE=m1i=1m(yiy^)2

sklearn中的API:sklearn.metrics.mean_squared_error(y_true, y_pred)

  • 均方误差回归损失
  • y_true:真实值
  • y_pred:预测值
  • return:浮点数结果

代码实现

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.datasets import fetch_california_housing
# 获取数据
housing = fetch_california_housing()

# 数据集划分
x_train, x_test, y_train, y_test = train_test_split(housing.data, housing.target, test_size=0.2, random_state=22)

# 特征工程- 标准化
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)

# 机器学习 线性回归方程
estimator = LinearRegression()
estimator.fit(x_train,y_train)

# 模型评估 获取模型系数
y_predict = estimator.predict(x_test)
print(f"模型预测值为:{y_predict}")
print(f"模型系数为:{estimator.coef_}")
print(f"模型截距为:{estimator.intercept_}")

#评价 均方误差
error = mean_squared_error(y_test, y_predict)
print(f"均方误差:{error}")

在这里插入图片描述

正则化

欠拟合与过拟合

过拟合:一个假设 在训练数据上能够获得比其他假设更好的拟合, 但是在测试数据集上却不能很好地拟合数据 (体现在准确率下降),此时认为这个假设出现了过拟合的现象。(模型过于复杂)

欠拟合:一个假设 在训练数据上不能获得更好的拟合,并且在测试数据集上也不能很好地拟合数据 ,此时认为这个假设出现了欠拟合的现象。(模型过于简单)

过拟合和欠拟合的区别:
在这里插入图片描述
欠拟合在训练集和测试集上的误差都较大

过拟合在训练集上误差较小,而测试集上误差较大
在这里插入图片描述

通过代码认识过拟合和欠拟合

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error  # 计算均方误差
from sklearn.model_selection import train_test_split


def dm01_欠拟合():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型
    X = x.reshape(-1, 1)
    estimator.fit(X, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)           # 散点图
    plt.plot(x, y_predict, color='r')   # 折线图(预测值, 拟合回归线)
    plt.show()                  # 具体的绘图



if __name__ == '__main__':
    dm01_欠拟合()

X = x.reshape(-1, 1)

  • x 是一个一维数组,形状为 (100,)。
  • Scikit-Learn 的 fit() 方法要求输入的特征必须是二维的(行=样本,列=特征)。
  • reshape(-1, 1) 表示:“自动计算行数(100),列数为 1”,将其变为形状 (100, 1) 的列向量。

蓝色散点:呈现明显的抛物线形状(中间凹,两头翘)。

红色直线:模型拟合出来的结果,就是一条笔直的斜线。

肉眼可见:无论直线怎么调整位置,它永远无法捕捉到数据的“弯曲”趋势。模型在训练集上的表现就很差,这就是典型的欠拟合(高偏差)——模型太简单,连训练数据的基本规律都没学好。
在这里插入图片描述
在这里插入图片描述

def dm02_模型ok():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型
    X = x.reshape(-1, 1)
    X2 = np.hstack([X, X ** 2])
    estimator.fit(X2, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X2)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    # plt.plot(x, y_predict)
    plt.show()  # 具体的绘图

X2 = np.hstack([X, X ** 2])

  • X ** 2:对 X 中的每个元素求平方,得到一个新的列向量
    (形状 (100, 1))。

  • np.hstack:将两个数组水平拼接(按列合并)。最终 X2 的形状为 (100, 2),每一行变成 [x, x²]
    在这里插入图片描述
    在这里插入图片描述
    再次加入高次项,绘制图像,观察均方误差结果

def dm03_过拟合():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化 线性回归模型.
    estimator = LinearRegression()
    # 3. 训练模型
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X**2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X3, y)

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.show()  # 具体的绘图

在这里插入图片描述
在这里插入图片描述
随着加入的高次项越来越多,拟合程度越来越高,均方误差也随着加入越来越小。说明已经不再欠拟合了。

欠拟合产生原因: 学习到数据的特征过少

解决办法:

(1)添加其他特征项,有时出现欠拟合是因为特征项不够导致的,可以添加其他特征项来解决

(2)添加多项式特征,模型过于简单时的常用套路,例如将线性模型通过添加二次项或三次项使模型泛化能力更强

过拟合产生原因: 原始特征过多,存在一些嘈杂特征, 模型过于复杂是因为模型尝试去兼顾所有测试样本

解决办法:

(1)重新清洗数据,导致过拟合的一个原因有可能是数据不纯,如果出现了过拟合就需要重新清洗数据。

(2)增大数据的训练量,还有一个原因就是我们用于训练的数据量太小导致的,训练数据占总数据的比例过小。

(3)正则化

(4)减少特征维度

正则化

在解决回归过拟合中,我们选择正则化。但是对于其他机器学习算法如分类算法来说也会出现这样的问题,除了一些算法本身作用之外(决策树、神经网络),我们更多的也是去自己做特征选择,包括之前说的删除、合并一些特征
在这里插入图片描述
在这里插入图片描述
在学习的时候,数据提供的特征有些影响模型复杂度或者这个特征的数据点异常较多,所以算法在学习的时候尽量减少这个特征的影响(甚至删除某个特征的影响),这就是正则化

注:调整时候,算法并不知道某个特征影响,而是去调整参数得出优化的结

L1正则化

  • 假设𝐿(𝑊)是未加正则项的损失,𝜆是一个超参,控制正则化项的大小。
  • 则最终的损失函数:𝐿=𝐿(𝑊)+λ∗∑i=1n∣wi∣𝐿=𝐿(𝑊)+ \lambda*\sum_{i=1}^{n}\lvert w_i\rvertL=L(W)+λi=1nwi

作用:用来进行特征选择,主要原因在于L1正则化会使得较多的参数为0,从而产生稀疏解,可以将0对应的特征遗弃,进而用来选择特征。一定程度上L1正则也可以防止模型过拟合。

在这里插入图片描述
L1正则为什么可以产生稀疏解(可以特征选择)**

稀疏性:向量中很多维度值为0

  • 对其中的一个参数 $ w_i $ 计算梯度,其他参数同理,α是学习率,sign(wi)是符号函数。

在这里插入图片描述
L1的梯度:

𝐿=𝐿(𝑊)+λ∗∑i=1n∣wi∣𝐿=𝐿(𝑊)+ \lambda*\sum_{i=1}^{n}\lvert w_i\rvertL=L(W)+λi=1nwi

∂L∂wi=∂L(W)∂wi+λsign(wi)\frac{\partial L}{\partial w_{i}} = \frac{\partial L(W)}{\partial w_{i}}+\lambda sign(w_{i})wiL=wiL(W)+λsign(wi)

LASSO回归: from sklearn.linear_model import Lasso

L2正则化

  • 假设𝐿(𝑊)是未加正则项的损失,𝜆是一个超参,控制正则化项的大小。
  • 则最终的损失函数:𝐿=𝐿(𝑊)+λ∗∑i=1nwi2𝐿=𝐿(𝑊)+ \lambda*\sum_{i=1}^{n}w_{i}^{2}L=L(W)+λi=1nwi2

作用:主要用来防止模型过拟合,可以减小特征的权重

优点:越小的参数说明模型越简单,越简单的模型则越不容易产生过拟合现象

Ridge回归: from sklearn.linear_model import Ridge

正则化案例

from sklearn.linear_model import Lasso  # L1正则
from sklearn.linear_model import Ridge  # 岭回归 L2正则

def dm04_模型过拟合_L1正则化():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化L1正则化模型, 做实验: alpha惩罚力度越来越大, k值越来越小.
    estimator = Lasso(alpha=0.005)
    # 3. 训练模型
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X**2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X3, y)
    print(f'权重: {estimator.coef_}')

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.show()  # 具体的绘图

alpha(通常也叫 λ)是超参数,用来平衡“拟合数据”和“简化模型”之间的权重:

alpha 越大(比如 alpha=1):惩罚力度极大。模型会倾向于把所有特征权重 w 压缩到接近 0,让模型变得非常简单(甚至只有截距),这容易导致欠拟合(高偏差)。

alpha 越小(比如你的 0.005):惩罚力度极弱。模型的行为会非常接近普通的 LinearRegression(线性回归),几乎不限制权重的大小,这容易导致过拟合(高风险)。

alpha = 0:等同于普通的 LinearRegression(无惩罚)。

针对0.005:这是一个比较小的值。这意味着你在模型中只加入了少量的正则化约束。模型会尽力去拟合数据,但会对那些“无关紧要”特征的系数稍作压缩,防止系数过大。
在这里插入图片描述
在这里插入图片描述

def dm05_模型过拟合_L2正则化():
    # 1. 准备x, y数据, 增加上噪声.
    # 用于设置随机数生成器的种子(seed), 种子一样, 每次生成相同序列.
    np.random.seed(666)
    # x: 随机数, 范围为 (-3, 3), 100个.
    x = np.random.uniform(-3, 3, size=100)
    # loc: 均值, scale: 标准差, normal: 正态分布.
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)
    # 2. 实例化L2正则化模型, 做实验: alpha惩罚力度越来越大, k值越来越小.
    estimator = Ridge(alpha=0.005)
    # 3. 训练模型
    X = x.reshape(-1, 1)
    # hstack() 函数用于将多个数组在行上堆叠起来, 即: 数据增加高次项.
    X3 = np.hstack([X, X**2, X**3, X**4, X**5, X**6, X**7, X**8, X**9, X**10])
    estimator.fit(X3, y)
    print(f'权重: {estimator.coef_}')

    # 4. 模型预测.
    y_predict = estimator.predict(X3)
    print("预测值:", y_predict)

    # 5. 计算均方误差 => 模型评估
    print(f'均方误差: {mean_squared_error(y, y_predict)}')
    # 6. 画图
    plt.scatter(x, y)  # 散点图
    # sort()  该函数直接返回一个排序后的新数组。
    # numpy.argsort()   该函数返回的是数组值从小到大排序时对应的索引值
    plt.plot(np.sort(x), y_predict[np.argsort(x)], color='r')  # 折线图(预测值, 拟合回归线)
    plt.show()  # 具体的绘图

在这里插入图片描述

在这里插入图片描述


总结

如果你正在刷题、做项目或者准备面试,请务必亲自运行文章中的三段核心代码:

  1. 欠拟合演示(只用 x 拟合二次函数)
  2. 正常拟合演示(加入 x² 特征)
  3. 过拟合 + 正则化演示(加入高次项并用 Lasso/Ridge 修正)

代码比任何公式都有说服力。 当你亲眼看到那条红色曲线从"直得离谱"到"完美抛物线",再到"剧烈抖动",最后被正则化"拉回正轨"时,你对这几个概念的理解会深深刻进脑子里。

希望这份笔记能成为你机器学习路上的一个小小里程碑。我们下次见!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐