机器学习算法——线性回归

8 阅读3分钟

  有监督的机器学习是一种基于已标记的数据集进行训练的方法。其目的是利用已知输入和输出的数据建立模型,进而对新的未标记的数据进行预测或分类。

  线性回归是一种有监督的机器学习算法,通过学习已经标记的数据集并将这些数据映射到一个最优化的线性函数,并最终用这个线性函数预测新的数据。此外,线性回归还是其他更复杂、更高级算法的基础。

⒈ 线性回归分类

  线性回归通过将线性函数拟合到已经标记的数据来计算自变量(特征)与因变量(目标)之间的线性关系。根据自变量数量的不同,线性回归又分为简单线性回归和多元线性回归。

⑴ 简单线性回归

  简单线性回归只有一个自变量和一个因变量:

y=β0+β1xy=\beta_0 + \beta_1 * x

其中:
  yy 是因变量(目标)
  xx 是自变量(特征)
  β0\beta_0 是截距(当 x=0x = 0yy 的值)
  β1\beta_1 是斜率(xxyy 的影响)

⑵ 多元线性回归

  多元线性回归中不止有一个自变量:

y=β0+β1x1+β2x2+...+βnxny = \beta_0 + \beta_1 * x_1 + \beta_2 * x_2 + ... + \beta_n * x_n

其中:
  yy 是因变量
  x1,x2...xnx_1, x_2 ... x_n 是自变量
  β0\beta_0 是截距
  β1,β2...βn\beta_1, \beta_2 ... \beta_n 是斜率

⒉ 代价函数(Cost Function)

  线性回归的最终目的是通过调整线性函数的系数(截距和斜率),得到一个最优化的线性函数,形成一条最佳拟合线。最佳拟合线可以使得线性回归的预测值与实际值之间的偏差最小。

  代价函数用于衡量线性回归预测值和实际值之间的偏差,通过最小化代价函数,线性函数可以找到最优化的系数,使得预测值尽可能的接近实际值。

  线性回归中通常使用均方误差(MSE Mean Squared Error)作为代价函数。之所以使用 MSE 作为代价函数是因为 MSE 是一个凸函数,只有一个全局最小值,这保证了在使用梯度下降算法求解时可以找到一个最优解。

J=1ni=1n(prediyi)2J = \frac{1}{n}\sum_{i = 1}^n(pred_i - y_i)^2

其中:
  JJ 为代价函数
  nn 为样本数量
  predipred_i 为第 ii 个样本的预测值,即 y=β0+β1x1+...+βnxny = \beta_0 + \beta_1 * x_1 + ... + \beta_n * x_n
  yiy_i 为第 ii 个样本的实际值

所谓凸函数,是指函数图像形如 \cup 的函数

⒊ 梯度下降

  梯度下降通过迭代的调整目标函数的参数,使得目标函数最终达到最小化或最大化。在线性回归中,梯度下降的目标是使得成本函数最小化。

  梯度下降的基本思路是沿着成本函数梯度的反方向移动,逐步减小成本函数的值。具体步骤包括:

  1. 初始化参数的起始值
  2. 计算梯度
  3. 更新参数
  4. 重复第二、第三步,直到成本函数的值达到最小

  计算梯度需要对成本函数的各个参数求偏导数,以简单线性回归为例:

  Jβ0=β0[1ni=1n(prediyi)2]\frac{\partial J}{\partial \beta_0} = \frac{\partial}{\partial \beta_0} [\frac{1}{n}\sum_{i = 1}^n(pred_i - y_i)^2]

    =β0[1ni=1n(β0+β1xiyi)2]= \frac{\partial}{\partial \beta_0} [\frac{1}{n}\sum_{i = 1}^n(\beta_0 + \beta_1 * x_i - y_i)^2]

    =1n[i=1n2(β0+β1xiyi)(β0(β0+β1xiyi))]= \frac{1}{n}[\sum_{i = 1}^n2(\beta_0 + \beta_1 * x_i - y_i)(\frac{\partial}{\partial \beta_0} (\beta_0 + \beta_1 * x_i - y_i))]

    =1n[i=1n2(β0+β1xiyi)(1+00)= \frac{1}{n}[\sum_{i = 1}^n2(\beta_0 + \beta_1 * x_i - y_i)(1 + 0 - 0)

    =2n[i=1n(β0+β1xiyi)= \frac{2}{n}[\sum_{i = 1}^n(\beta_0 + \beta_1 * x_i - y_i)

    =2ni=1n(prediyi)= \frac{2}{n}\sum_{i = 1}^n(pred_i - y_i)

  Jβ1=β1[1ni=1n(prediyi)2]\frac{\partial J}{\partial \beta_1} = \frac{\partial}{\partial \beta_1} [\frac{1}{n}\sum_{i = 1}^n(pred_i - y_i)^2]

    =β1[1ni=1n(β0+β1xiyi)2]= \frac{\partial}{\partial \beta_1} [\frac{1}{n}\sum_{i = 1}^n(\beta_0 + \beta_1 * x_i - y_i)^2]

    =1n[i=1n2(β0+β1xiyi)(β1(β0+β1xiyi))]= \frac{1}{n}[\sum_{i = 1}^n2(\beta_0 + \beta_1 * x_i - y_i)(\frac{\partial}{\partial \beta_1} (\beta_0 + \beta_1 * x_i - y_i))]

    =1n[i=1n2(β0+β1xiyi)(0+xi0)= \frac{1}{n}[\sum_{i = 1}^n2(\beta_0 + \beta_1 * x_i - y_i)(0 + x_i - 0)

    =2n[i=1n(β0+β1xiyi)xi= \frac{2}{n}[\sum_{i = 1}^n(\beta_0 + \beta_1 * x_i - y_i) * x_i

    =2ni=1n(prediyi)xi= \frac{2}{n}\sum_{i = 1}^n(pred_i - y_i) * x_i

偏导数计算法则:
常数:x(c)=0\frac{\partial}{\partial x}(c) = 0
幂函数:x(xn)=nxn1\frac{\partial}{\partial x}(x^n) = n*x^{n - 1}
对数函数:x(lnx)=1x\frac{\partial}{\partial x}(\ln x) = \frac{1}{x}
三角函数:x(sinx)=cosx\frac{\partial}{\partial x}(\sin x) = \cos x x(cosx)=sinx\frac{\partial}{\partial x}(\cos x) = - \sin x
乘法法则(积的偏导数):x(f1(x,y)f2(x,y))=f1xf2(x,y)+f1(x,y)f2x\frac{\partial}{\partial x}(f_1(x,y)*f_2(x,y)) = \frac{\partial f_1}{\partial x}f_2(x,y) + f_1(x,y)\frac{\partial f_2}{\partial x}
链式法则(复合函数的偏导数):x(f1(f2(x,y)))=f1f2f2x\frac{\partial}{\partial x}(f_1(f_2(x, y))) = \frac{\partial f_1}{\partial f_2}\frac{\partial f_2}{\partial x}

  通过计算偏导数,得到各个系数的梯度,然后根据梯度更新系数:

β0=β0α2ni=1n(prediyi)\beta_0 = \beta_0 - \alpha * \frac{2}{n}\sum_{i = 1}^n(pred_i - y_i)
β1=β1α2ni=1n(prediyi)xi\beta_1 = \beta_1 - \alpha * \frac{2}{n}\sum_{i = 1}^n(pred_i - y_i) * x_i

  其中,α\alpha 为学习率,用于控制每次更新系数时的步长。学习率是超参数,无法从数据中学习得到,而是在训练开始之前设置。如果学习率设置的过小,会使得收敛速度变慢,需要花很长的时间才能得到最优解;而如果学习率设置的偏大,可能会导致结果发散,错过最优解。

学习率设置过小

学习率设置偏大

⒋ 评估指标

  评估指标的目的在于评价模型性能的好坏。常用于评估线性回归模型性能的指标有均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)、决定系数(R²)、调整后的 R²。

⑴ MSE 与 RMSE
MSE=1ni=1n(yiyi^)2MSE = \frac{1}{n}\sum_{i=1}^n(y_i - \hat{y_i})^2
RMSE=MSERMSE = \sqrt{MSE}

  其中,n 为样本数量,yiy_i 为实际值,yi^\hat{y_i} 为预测值。

  MSE 是回归模型中常见的评估指标,是实际值与预测值之间误差值的平方的平均值,之所以进行平方是为了保证正负误差不会相互抵消。MSE 量化了模型预测的准确性,但对异常值非常敏感,因为异常值对整体结果影响非常大。RMSE 描述了模型与数据的拟合程度,相较于 MSE 更加直观。

⑵ MAE
MAE=1ni=1nyiyi^MAE = \frac{1}{n}\sum_{i = 1}^n|y_i - \hat{y_i}|

  其中,n 为样本数量,yiy_i 为实际值,yi^\hat{y_i} 为预测值。

  MAE 是样本实际值与预测值之间绝对误差的平均值,MAE 对异常值不敏感,MAE 值越小表明模型的性能越好。

⑶ R² 与调整后的 R²
R2=1i=1n(yiyi^)2i=1n(yiyˉ)2=1RSSTSSR^2 = 1 - \frac{\sum_{i = 1}^n(y_i - \hat{y_i})^2}{\sum_{i = 1}^n(y_i - \bar{y})^2} = 1 - \frac{RSS}{TSS}
Radjusted2=1(1R2)n1nk1R^2_{adjusted} = 1 - (1 - R^2)\frac{n - 1}{n - k - 1}

  其中,n 为样本数量,k 为自变量的数量,yiy_i 为实际值,yi^\hat{y_i} 为预测值,yˉ\bar{y} 为实际值的平均值。

  RSS 为样本真实值与预测值的差的平方的和,RSS 的值越大表示模型预测的结果中误差越大;TSS 为样本真实值与样本平均值的差的平方的和,TSS 的值越大表示样本中数据点分布的范围越广。RSS 与 TSS 的比值越小,表示模型的性能越好,反之则表示模型的性能越差。所以,R² 的值越大表示模型性能越好,反之则表示模型的性能越差。通常,R² 的值介于 0 和 1 之间,如果出现 R² 的值为负的情形,则表明模型预测结果比样本的平均值还要差。

  模型中自变量数量的增加往往会导致 R² 结果的增大。有些自变量对预测结果的影响很小甚至毫不相干,但由于这些自变量的加入而引起的 R² 结果的增大使模型显得过拟合。调整后的 R² 考虑了样本的数量和自变量的数量,通过增大 R² 中的惩罚项的值限制了模型的过拟合,同时还使得不同自变量数量的模型性能具有可比性。

由于模型中自变量的数量不少于 1 个,所以 k ≥ 1,n1nk1\frac{n - 1}{n - k - 1} 的值永远大于 1,并且模型自变量数量越多,这个值就越大。

⒌ 正则化

  在训练模型的过程中有可能会出现模型过拟合的情况,即模型在训练数据上的性能表现很好,准确度很高,但在测试数据和验证数据上的性能表现却很差,准确度很低。之所以会出现这种情况是因为训练数据中存在噪声,而模型也学到了这些噪声,模型只是记住了训练数据,并没有学到数据中的模式。

  为了解决模型过拟合的问题,需要引入正则化。正则化通过在模型的代价函数中加入惩罚项,防止模型对单个特征或系数赋予过多的权重,从而尽量消除噪声的干扰。

⑴ L₁ 正则化

  L₁ 正则化在代价函数中加入惩罚项 λi=1mβi\lambda\sum_{i = 1}^m|\beta_i|。对于一些不太重要或毫不相干的特征,L₁ 正则化可以通过将这些特征的系数调整为 0 实现特征筛选的目的。

⑵ L₂ 正则化

  L₂ 正则化在代价函数中加入惩罚项 λi=1mβi2\lambda\sum_{i = 1}^m\beta_i^2,L₂ 正则化不会将某些特征的系数设置为 0,但会使所有特征的系数趋向于 0,从而减小模型对某些特征的依赖。

⑶ 弹性网络正则化

  弹性网络正则化结合了 L₁ 正则化和 L₂ 正则化,在代价函数中加入了惩罚项 λ((1α)i=1mβi+αi=1mβi2)\lambda((1 - \alpha)\sum_{i = 1}^m|\beta_i| + \alpha\sum_{i = 1}^m\beta_i^2)

α 和 λ 均为超参数,λ 控制正则化的强度,λ 的值越大,最终模型参数的值就越小。α 用于控制弹性网络正则化中 L₁ 和 L₂ 的比例。

⒍ DEMO

样本数据来源于 Kaggle

⑴ 简单线性回归
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.impute import SimpleImputer
import matplotlib.pyplot as plt


# 导入数据
data = pd.read_csv("./simple_linear_regression.csv")
data.info()


# 判断是否有缺失值,如果有,则需要处理
if data.isnull().values.any():
    # 使用均值插补法补齐缺失值
    # mean_imputer = SimpleImputer()
    # data = pd.DataFrame(mean_imputer.fit_transform(data), columns=data.columns)
    # 直接将有空值的记录丢弃
    data = data.dropna()


print(data.isnull().sum())


# 拆分特征和目标
features = data.drop(columns="y")
target = data["y"]


# 拆分训练集与测试集
x_train, x_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
print("x_train_shape", x_train.shape)
print("y_train_shape", y_train.shape)
print("x_test_shape", x_test.shape)
print("y_test_shape", y_test.shape)


# 开始训练模型
model = LinearRegression()
model.fit(x_train, y_train)
print(model.score(x_train, y_train))
# 测试
predict = np.round(model.predict(x_test), decimals=6)


# 模型评估
mse = mean_squared_error(y_test, predict)
r2 = r2_score(y_test, predict)
print("mse = ", mse)
print("r2 = ", r2)


# 输出训练结果系数
print(model.intercept_)
print(model.coef_)


# 绘图
plt.scatter(x_test, y_test)
plt.plot(x_test, predict, "r-")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Simple Linear Regression")
plt.show()

  在进行训练之前,首先要检查样本数据中是否有缺失值,如果有缺失值,则需要对相应的记录进行处理。之后将数据拆分为训练集和测试集,在拆分之前打乱样本数据顺序,这样可以保证训练集和测试集每次结果都不相同,避免在模型训练时出现过拟合。

对样本数据中缺失值的处理可以有多种方法:直接删除、均值插补、常量插补、中位数插补、K临近插补等,不同的处理方法训练出来的模型性能差别可能会非常大。

  最终训练得到的模型的线性函数为:

y=0.09664630862199175+1.00121676xy = -0.09664630862199175 + 1.00121676 * x

简单线性回归.png

⑵ 多元线性回归
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import LabelEncoder


# 导入数据
data = pd.read_csv("./Student_Performance.csv")
data.info()


# 判断是否有缺失值,如果有,则需要处理
if data.isnull().values.any():
    data = data.dropna()


# 判断是否有重复项,如果有,则需要删除重复项
if not data.duplicated().any():
    data = data.drop_duplicates()


# 对样本数据中非数值类型的值进行类型转换
encoder = LabelEncoder()
data["Extracurricular Activities"] = encoder.fit_transform(data["Extracurricular Activities"])


print(data.isnull().sum())


# 拆分特征和目标
features = data.drop(columns="Performance Index")
target = data["Performance Index"]


# 拆分训练集与测试集
x_train, x_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
print("x_train_shape", x_train.shape)
print("y_train_shape", y_train.shape)
print("x_test_shape", x_test.shape)
print("y_test_shape", y_test.shape)


# 开始训练模型
model = LinearRegression()
model.fit(x_train, y_train)
print(model.score(x_train, y_train))
# 测试
predict = np.round(model.predict(x_test), decimals=1)


# 模型评估
mse = mean_squared_error(y_test, predict)
r2 = r2_score(y_test, predict)
print("mse = ", mse)
print("r2 = ", r2)


# 输出训练结果系数
print("intercept = ", model.intercept_)
print("coefficient = ", model.coef_)

  处理样本数据的过程中,除了处理缺失值外,还应该去除重复项。另外,有些特征的值并非数值类型,对于这种情况需要提前将数据类型进行转换。最终得到的模型的线性函数为:

PerformanceIndex=33.92194621555638+2.85248393HoursStudied+1.0169882PreviousScores+0.60861668ExtracurricularActivities+0.47694148SleepHours+0.19183144SampleQuestionPapersPracticedPerformanceIndex = -33.92194621555638 + 2.85248393 * HoursStudied + 1.0169882 * PreviousScores + 0.60861668 * ExtracurricularActivities + 0.47694148 * SleepHours + 0.19183144 * SampleQuestionPapersPracticed

⑶ 正则化的多元线性回归
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression, ElasticNetCV
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.preprocessing import LabelEncoder


# 导入数据
data = pd.read_csv("./Student_Performance.csv")
data.info()


# 判断是否有缺失值,如果有,则需要处理
if data.isnull().values.any():
    data = data.dropna()


# 判断是否有重复项,如果有,则需要删除重复项
if not data.duplicated().any():
    data = data.drop_duplicates()


# 对样本数据中非数值类型的值进行类型转换
encoder = LabelEncoder()
data["Extracurricular Activities"] = encoder.fit_transform(data["Extracurricular Activities"])


print(data.isnull().sum())


# 拆分特征和目标
features = data.drop(columns="Performance Index")
target = data["Performance Index"]


# 拆分训练集与测试集
x_train, x_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
print("x_train_shape", x_train.shape)
print("y_train_shape", y_train.shape)
print("x_test_shape", x_test.shape)
print("y_test_shape", y_test.shape)


# 设置 α(λ) 参数的初始范围
# 通常在确定 α 参数的范围时需要考虑特征数、样本数、模型复杂度、数据稀疏性、特征尺度等因素
# 如果样本数小于 1000,特征数小于 50,则初始范围可以确定为 [10E-4, 10E2]
# 如果样本数介于 1000 到 100000,特征数介于 50 到 1000,则初始范围可以确定为 [10E-3, 10E3]
# 如果样本数大于 100000,特征数大于 1000,则初始范围可以确定为 [10E-2, 10E4]
alphas = np.logspace(-3, 3, 1000)
l1_ratios = [0.1, 0.5, 0.7, 0.9, 0.95, 0.99, 1]

# 开始训练模型
model = ElasticNetCV(alphas=alphas, l1_ratio=l1_ratios, cv=5, random_state=0)
model.fit(x_train, y_train)
print(model.score(x_train, y_train))
# 测试
predict = np.round(model.predict(x_test), decimals=1)


# 模型评估
mse = mean_squared_error(y_test, predict)
r2 = r2_score(y_test, predict)
print("mse = ", mse)
print("r2 = ", r2)


# 输出训练结果系数
print("intercept = ", model.intercept_)
print("coefficient = ", model.coef_)
print("alpha = ", model.alpha_)
print("l1_ratio = ", model.l1_ratio_)

  通过弹性网络对多元线性回归进行正则化,避免模型对某些特征赋予过大的权重。在模型训练过程中,经过多次交叉验证,确定了最佳的 α 值以及 L₁ 和 L₂ 的比例。