人工智能机器学习算法总结--1.线性回归算法(单变量及多变量)

574 阅读19分钟

1.定义和目的

线性回归算法的目的是建立一个线性模型,用来描述自变量(输入特征)和因变量(输出)之间的关系。通过线性回归算法,我们可以预测因变量的值,基于给定的自变量。这种算法在许多领域都有着广泛的应用,例如经济学、统计学、机器学习等。

线性回归的意义在于:

  1. 建模与预测:通过线性回归,我们可以建立一个数学模型来描述变量之间的关系,并用这个模型进行预测。这对于预测未来趋势、分析数据之间的关联等具有重要意义。
  2. 特征分析:线性回归可以帮助我们了解自变量对因变量的影响程度,即哪些特征对输出有较大的影响,从而进行特征选择和分析。
  3. 评估模型:线性回归还可以用于评估模型的性能,通过代价函数等指标来衡量模型的拟合程度,以及对模型进行改进和优化。
  4. 解释性强:线性回归模型相对简单,并且具有很强的解释性,可以帮助我们理解变量之间的关系及其影响。

总的来说,线性回归算法在数据分析和预测建模中扮演着重要的角色,帮助我们理解数据背后的规律,做出合理的预测和决策。

2.算法结构

线性回归算法的结构相对简单,主要包括以下几个关键步骤:

  1. 数据准备:首先,需要准备包含自变量(输入特征X,一般写作x(i)),因变量(输出,一般写作h(x^(i)))和 目标变量集(真实值Y, 一般写作y(i)) 的数据集。通常将数据集分为训练集和测试集,用于模型的训练和评估 (后面也会有交叉验证集,此三级共同完成模型的训练以及优化)。
  2. 模型假设:线性回归算法基于一些假设,如线性关系假设、残差独立同分布假设等。这些假设在建立模型和进行推断时起着重要作用。
  3. 模型建立:线性回归模型的目标是找到最佳拟合的线性关系。通过最小化残差平方和来确定最佳拟合的直线(或超平面),通常使用最小二乘法来实现。
  4. 模型评估:一旦模型建立完成,需要对模型进行评估。常见的评估指标包括均方误差(Mean Squared Error, MSE)、决定系数(Coefficient of Determination, R-squared)等,用于衡量模型的拟合程度和预测能力。
  5. 预测:最终目的是使用训练好的线性回归模型对未知数据进行预测。通过输入新的自变量数据,可以利用模型得出对应的因变量预测值。

这些步骤构成了线性回归算法的基本结构。通过这个结构,我们可以理解线性回归算法是如何建立起自变量和因变量之间的线性关系,并用于预测和分析数据。

3.算法组成One-数据准备

  1. 读取文件,简化数据
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 引入所需要的库

path = 'ex1data2.txt'
#写入存储文件路径
data = pd.read_csv(path, header=None, names=['Size', 'Bedrooms', 'Price'])
#将文件数据存进data变量中
data.head()
#在 Python 中,`data.head()` 是 Pandas 库中 DataFrame 对象的方法之一。当你调用 `data.head()` 时,它会返回 DataFrame 中的前几行数据,默认情况下是前五行。这个方法通常用于快速查看数据的开头部分,以便对数据的结构和内容有一个初步的了解。

2.准备一些整体的基础数据

# 保存mean、std、mins、maxs、data
means = data.mean().values
#平均值
stds = data.std().values
#标准差
mins = data.min().values
maxs = data.max().values
data_ = data.values
data.describe()
#后面的公式运算中有用

3.特征缩放

# 特征缩放
data = (data - data.mean()) / data.std()
data.head()

特征缩放的目的:归一化(Normalization)  将特征的数值缩放到一个固定的范围,通常是[0, 1]或[-1, 1]之间。归一化可以确保所有特征的取值范围相似,避免某些特征(一些异常特征数值较大,容易影响整体模型的拟合)对模型训练产生过大的影响

特征缩放的优势:特征缩放有助于提高模型的收敛速度,避免某些特征对模型产生过大的影响,同时还可以提高模型的泛化能力。在使用线性回归等基于距离度量的算法时,特征缩放尤为重要,因为这些算法对特征的尺度敏感。

4.数据赋值

# add ones column
data.insert(0, 'Ones', 1)

# set X (training data) and Y (target variable)
cols = data.shape[1]
#取列数
X = data.iloc[:, :cols-1]
Y = data.iloc[:, cols-1:cols]

# convert to matrices and initialize theta
X = np.matrix(X.values)
Y = np.matrix(Y.values)
theta = np.matrix(np.array([0, 0, 0]))

# perform linear regression on the data set
alpha = 0.01
#学习率
iters = 1000
#迭代次数
g, cost = gradientDescent(X, Y, theta, alpha, iters)

# get the cost(error) of the model
computeCost(X, Y, g)
add ones column目的:

这段代码 data.insert(0, 'Ones', 1) 的作用是在 DataFrame 中插入一列名为 'Ones',并将该列的所有值设为 1。让我解释一下:

  • data 是一个 DataFrame 对象,可能包含各种数据。

  • insert() 是 DataFrame 对象的方法,用于在指定位置插入一列数据。

  • 参数解释:

    • 0:表示要将新列插入的位置,这里是在第一列之前插入。
    • 'Ones':是新插入列的列名,这里命名为 'Ones'。
    • 1:是要插入到新列中的值,这里将 'Ones' 列的所有值设为 1。

这段代码通常用于添加一个全为 1 的列,通常用于线性回归等机器学习算法中的截距项。通过在数据集中添加一个全为 1 的列,可以使模型学习到一个截距项,有助于更好地拟合数据。这种操作在数据准备阶段是常见的,以便为模型提供必要的输入数据。

X,Y,theta等赋值解析
  1. X = data.iloc[:, :cols-1]:这行代码从数据中选择特征列,即输入数据。iloc 是 Pandas DataFrame 提供的方法,用于通过行和列的索引值来选择数据。这里 :cols-1 表示从第一列到倒数第二列的所有特征列。
  2. Y = data.iloc[:, cols-1:cols]:这行代码选择目标变量列,即要预测的输出数据。在这里,cols-1:cols 表示最后一列,通常是目标变量列。(这里的X,Y之所以要这么处理是因为特征值和真实值一起存在同一个文件里面,只是各自所处的列不同)
  3. X = np.matrix(X.values) 和 Y = np.matrix(Y.values):这两行代码将特征和目标变量转换为矩阵形式,以便后续进行矩阵运算。
  4. theta = np.matrix(np.array([0, 0, 0])):这行代码初始化了参数 theta,即线性回归模型的系数,这里假设初始值为 [0, 0, 0]。
  5. alpha = 0.01 和 iters = 1000:这两行代码设置了学习率 alpha 和迭代次数 iters,用于梯度下降算法的优化。
  6. g, cost = gradientDescent(X, Y, theta, alpha, iters):这行代码调用了梯度下降函数 gradientDescent,传入特征矩阵 X、目标变量矩阵 Y、初始参数 theta、学习率 alpha 和迭代次数 iters,并返回优化后的参数 g 和损失值 cost
  7. computeCost(X, Y, g):最后一行代码计算了模型的损失值,即代价函数的值,传入特征矩阵 X、目标变量矩阵 Y 和优化后的参数 g

注意!

  • (以吴恩达机器学习里面提供的数据素材为例)

  • 当所有的变量数据完成赋值以及变换之后:

  • X(特征矩阵):(43,3) 即43行3列,43行样本数据,3列的特征值,每一列的特征都不一样

  • Y(真实值):(43,1) 即43行1列,43行数据样本,1列真实数据值

  • θ(参数值):(1,3) 即1行3列,共3个参数,即3个变量

  • 根据以上给出的矩阵数据,再代入相关函数里面,理解代码和公式就会特别简单了

4.算法组成Tow-代价函数

cost.png

我们最终的目的就是得到这个模型函数h(x),又称假设函数(这里我省略它的下标θ),为此我们需要通过计算和训练来得到一组合适的θ参数值(θ是一组矩阵,后面我可能会用theta来代替,因为θ键盘比较难打),所以我们后面所做的工作,例如代价函数,正则化等等都是为了使这组θ值变得更合适,使函数的拟合效果更好(随带提一嘴,多变量的话也就无非在假设函数hθ(x)多加上θ*x^(i)个未知数项,在假设函数中每一个x^(i)都代表了一个特征)

代码复现:

def computeCost(X,Y,theta):
    inner=np.power((X*theta.T)-Y,2)
    return np.sum(inner)/(2*len(X))

这段代码是一个简单的代价函数计算函数,用于计算线性回归模型的代价函数。让我逐步解释一下这段代码:

  1. def computeCost(X, Y, theta)::这是一个Python函数的定义,函数名为computeCost,接受三个参数X(特征矩阵)、Y(目标变量)和theta(模型参数),这里的X,Y,theta均是三个行矩阵或者列矩阵
  2. 接着,能够实现公式中求和的关键就是对公式中h(x^(i))-y^(i)的转化,h(x^(i))指将特征矩阵X中的第i个数据点(也就是x^(i))代入假设函数h(x)(即拟合模型)中计算求值后得出的输出值h(x^(i))(也就是拟合的效果值),接着再将此输出值减去该数据在Y矩阵中对应的第i个真实值后就能够得到相应误差值(另提一下,这里之所以将此误差值平方是为了放大误差,以更好地优化模型)
  3. inner = np.power((X * theta.T) - Y, 2):有了上面第2点对公式核心部分的解释后,我们对该求和公式进行代码设计。该行代码计算了代价函数的内部部分。首先,它计算了模型的预测值,使用了矩阵乘法X * theta.T(解释一下,这里的X矩阵代表了全部的x^(i)数据点,将X乘以θ矩阵的转置,也就是一次性将X中的每个x(i)与θ矩阵相乘,这个过程也就是每个x^(i)求值h(x^(i))的过程),然后减去实际的目标值Y(解释一下,只要在输入数据的时候将对应数据排列好形成矩阵后这里相减的就是对应的值,那么这里Y也是代表全部的y(i)数据点)。接着,对这个差值取平方。总结一下,利用矩阵的加减法以及其能对数据进行整合处理的特点在代码中使用X,Y,theta矩阵的输入和计算简化每个i值求误差的过程,以此来达到求和的效果
  4. return np.sum(inner) / (2 * len(X)):这一行代码计算了整个代价函数。它对内部部分inner进行求和,然后除以2 * len(X),其中len(X)是特征矩阵X的长度(也就是公式中的m,即单个特征中样本的总数量)。最终返回的是平均代价值。

J(θ)=12mi=1m(hθ(x(i))y(i))2J(\theta) = \frac{1}{2m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)})^2

其中:

  • J(θ) 是代价函数
  • m 是训练样本的数量
  • hθ(x^(i)) 是模型对第 ( i ) 个样本的预测值
  • y(i) 是第 ( i ) 个样本的实际值

在代价函数中,我们通过对所有样本的预测值与实际值之间的差异进行平方求和,然后取平均值来衡量模型的预测准确度。最小化代价函数可以帮助我们找到最优的模型参数 ( \theta ),使得模型的预测值与实际值之间的差异最小化。

希望这个公式能帮助您更好地理解线性回归中的代价函数。

5.算法组成Three-梯度下降

代价函数的目的及作用仅仅是帮助我们计算得到当参数值为θ(θ为一个行矩阵)时代价函数Jθ(x)的总误差值,为了更好地拟合模型函数,我们需要根据误差值不断更新θ矩阵,以此来找到总误差的最低点(也就是拟合效果较好的点)从而得出拟合效果较好的假设函数

屏幕截图 2024-03-16 165909.png

gradient.png

代码复现:

# 梯度下降
def gradientDescent(X, Y, theta, alpha, iters):
    temp = np.matrix(np.zeros(theta.shape))
    parameters = int(theta.shape[1])
    cost = np.zeros(iters)
    
    for i in range(iters):
        error = X * theta.T - Y
        
        for j in range(parameters):
            term = np.multiply(error, X[:, j])
            temp[0, j] = temp[0, j] - alpha / len(X) * np.sum(term)
        
        theta = temp
        cost[i] = computeCost(X, Y, theta)
    
    return theta, cost

这段代码是一个简单的梯度下降算法实现,用于线性回归的参数优化。让我们逐句分析和解释这段代码:

  1. def gradientDescent(X, Y, theta, alpha, iters)::这是定义一个名为 gradientDescent 的函数,它接受输入参数 X(特征矩阵)、Y(标签)、theta(初始参数向量)、学习率 alpha 和迭代次数 iters。
  2. temp = np.matrix(np.zeros(theta.shape)):创建一个临时矩阵 temp,用于存储更新后的参数值。
  3. parameters = int(theta.shape[1]):获取参数向量 theta 的长度,即参数的数量。
  4. cost = np.zeros(iters):初始化一个数组 cost,用于存储每次迭代后的损失值。
  5. for i in range(iters)::开始迭代优化过程,共进行 iters 次迭代。
  6. error = X * theta.T - Y:计算当前预测值与实际标签值之间的误差。
  7. for j in range(parameters)::对每个参数进行更新。
  8. term = np.multiply(error, X[:, j]):计算误差和特征矩阵 X 的乘积,用于更新参数term = np.multiply(error, X[:, j]) 的意思是将 error 与 X 的第 j 列逐元素相乘,得到一个新的数组 term。
  9. temp[0, j] = temp[0, j] - alpha / len(X) * np.sum(term):更新临时矩阵中的参数值。
  10. theta = temp:将更新后的参数值赋给参数向量 theta。
  11. cost[i] = computeCost(X, Y, theta):计算当前迭代下的损失值,并存储在 cost 数组中。
  12. return theta, cost:返回优化后的参数值 theta 和每次迭代的损失值。

在这段代码中,"temp[0, j]"表示在临时矩阵 temp 中的第一行第 j 列的元素。在梯度下降算法中,temp 矩阵被用来存储临时计算出的参数更新值。在这里,temp[0, j] 是指对应于参数矩阵 theta 中第 j 个参数的临时值。temp[0, j] 的作用是在每次迭代中计算出的临时参数更新值,用于更新参数 theta

这段代码实现了梯度下降算法,通过迭代更新参数 theta,使得模型在训练数据上的预测结果逐渐接近实际标签值,并最小化损失函数。

Tips:

  • np.matrix 是 NumPy 中用于创建矩阵的类。在 NumPy 中,矩阵是二维的数组,与普通的数组(numpy array)有一些区别。使用 np.matrix 可以创建一个特殊的二维矩阵对象,而不是普通的 NumPy 数组。

    你可以使用 np.matrix 来表示二维矩阵,并且可以执行矩阵乘法等矩阵运算。这在线性代数和一些数学运算中非常有用。下面是一个简单的示例,展示如何使用 np.matrix 创建一个矩阵:

import numpy as np

# 创建一个 2x2 的矩阵
matrix = np.matrix([[1, 2], [3, 4]])

print(matrix)

这段代码将创建一个 2x2 的矩阵,并将其打印出来。使用 np.matrix 可以方便地进行矩阵运算和操作。

注意!

  • 理清外循环以及内循环的关系:

外循环的本质就是假设函数迭代,每一次迭代的目的不仅仅是为了每次都能更新全部的θ值,更是每一次梯度下降的过程,每迭代一次其实也就是假设函数完成一次梯度下降,输出得到一个代价函数的总误差值。此外,在每一次外循环中,总误差值总是已经计算好确定的,用以提供给内循环中θ值更新的计算,即代码error = X * theta.T - Y

内循环的本质就是给每一个θ进行更新,其中循环次数parameters也就是θ的总数,其中注意[0, j]的意思是一个θ就行(特别提醒,一定要等每一个θ都成功更新之后在进行θ矩阵的全部赋值)

6.算法组成Four-结果数据可视化

代码复现

# 画出cost图像
fig, ax = plt.subplots(figsize=(12, 8))
#创建画布
ax.plot(np.arange(iters), cost, 'r')
#X,Y轴赋值确定
ax.set_xlabel('Iterations')
ax.set_ylabel('Cost')
ax.set_title('Error vs Training Epoch')
#标题,x,y轴名称标注
plt.show()
#描绘图像

最终代价函数图像可能为(x为迭代次数,y为总误差值,也就是代价函数每次迭代后的值):

屏幕截图 2024-03-16 203612.png 这一部分代码没什么好讲的,只要学过Matplotlib应该能够知道画图三步走。

注意!

  • 在根据此结果图像选取我们需要的参数时要慎重,因为此处会涉及欠拟合和过拟合问题:

屏幕截图 2024-03-16 203612.png

通常,我们在选择时会选择当迭代次数为红点时的θ值,因为在该点以后总误差值(也就是代价函数的输出值)趋于平坦,变化较小,那明明越往后误差就越小,为什么不往后选呢?这就是个过拟合的问题了。因为随着迭代次数的增加,总误差确实是越来越小的,拟合效果也会越来越好,但过度的拟合会使整个函数固定化,泛化效果极差,就无法起到一个很好的预测数据的作用了,如果选的过早又容易导致总误差值还是不小,收敛还不充分,所以就引出了后面的正则化来起到互补的作用(过拟合指的是模型在训练数据上表现很好,但在未见过的测试数据上表现较差的情况。)。

  • 当然,我们也可以画出拟合函数的图像,代码如下:
# 画出拟合图像
x = np.linspace(data.Population.min(), data.Population.max(), 100)
f = g[0, 0] + g[0, 1] * x

plt.figure(figsize=(12, 8))
plt.xlabel('Population')
plt.ylabel('Profit')
l1 = plt.plot(x, f, label='Prediction', color='red')
l2 = plt.scatter(data.Population, data.Profit, label='Traing Data', )
plt.legend(loc='best')
plt.title('Predicted Profit vs Population Size')
plt.show()

7.线性回归算法进阶--正规方程(直接求θ)

regularization.png

我们可以利用梯度下降来求θ参数矩阵,当然,经过一群数学家和计算机专家的推算我们也得到了一个公式可以直接用来求θ值。

代码复现:

def normalEqn(X, Y):
    theta = np.linalg.inv(X.T@X)@X.T@Y
    return theta
#注意:这里得出的theta便是完整的一个θ参数矩阵,函数normalEqn完全取代了代价函数繁琐的步骤
    
theta = normalEqn(X, Y)
# 梯度下降的theta为 matrix([[-3.24140214,  1.1272942 ]])

总结:此方法能够快速的求出θ参数矩阵

注意:正规方程与梯度下降这两种方法的优缺点比较及区别:

正规方程和梯度下降是两种常用的方法用于求解机器学习模型参数的技术。它们各自有一些优点和缺点,让我来为你详细解释一下:

正规方程:

优点:

  1. 解析解:正规方程提供了参数的解析解,可以直接通过矩阵运算得到最优参数,不需要迭代优化过程。
  2. 计算简单:对于小型数据集,正规方程通常计算速度较快。
  3. 不需要调节学习率:正规方程不需要手动调节学习率等超参数,因为它直接求解最优参数。

缺点:

  1. 计算复杂度:对于大型数据集,计算逆矩阵可能会变得复杂和耗时。
  2. 可能不稳定:当特征矩阵 (X^T X) 接近奇异矩阵时,求逆可能会导致数值不稳定。
  3. 不适用于特征数量大的情况:当特征数量很大时,正规方程的计算开销会变得很高。

梯度下降:

优点:

  1. 适用于大型数据集:梯度下降可以有效处理大规模数据集,因为它是基于迭代的优化算法。
  2. 灵活性:可以应用于各种模型和损失函数,适用于不同的机器学习问题。
  3. 可以优化非凸函数:梯度下降可以优化非凸函数,因此适用于更广泛的问题。

缺点:

  1. 需要调节学习率:梯度下降需要手动调节学习率等超参数,选择不当可能导致收敛速度慢或无法收敛。
  2. 局部最优解:梯度下降可能陷入局部最优解,特别是对于非凸函数。
  3. 迭代次数:收敛到最优解可能需要较多的迭代次数,尤其是在高维空间中。

综上所述,正规方程适用于小型数据集和特征数量不大的情况,提供了解析解;而梯度下降适用于大型数据集和复杂模型,但需要调节学习率等超参数。选择使用哪种方法取决于具体问题的性质和数据集的规模。

8.线性回归算法进阶--正则化(惩戒项)

目的:正则化通过在损失函数中引入惩罚项,限制模型参数的大小,从而使模型更加简单且泛化能力更强,在欠拟合时保持较小的惩戒力度,在过拟合时惩戒力度增大(可以理解为减法,以此来促进函数更好的收敛)

正则方程:

regularized_cost.png

代码复现:

def regularized_cost(theta, X, Y, l=1):
    theta_1n = theta[1:]
    regularized_term = l / (2 * len(X)) * np.power(theta_1n, 2).sum()
    return cost(theta, X, Y) + regularized_term
    #其中l代表的是λ

这是就只大概提一下正则化,到第二篇关于逻辑回归的时候会着重讲这个。

So,In summary,以上就是我对线性回归的全部理解以及笔记,这是一个比较简单,相对普遍的算法模型,如果你有什么问题,欢迎与我探讨!!!

算法这么简单的,我直接酷酷学!嘿嘿