机器学习

机器学习的应用:

语音识别,计算机视觉,增强现实,自动驾驶,大规模农业,医疗保健

机器学习的分类:

  • 有监督学习:回归,分类
  • 无监督学习:聚类

监督学习

从x到y的预测,函数的复杂版

  • 电子邮件->垃圾邮件?(0/1) 垃圾邮件过滤
  • 音频->文本转录 语音识别
  • 英语->西班牙语 机器翻译

  • 广告->用户信息 点击?(0/1) 在线广告

  • 图像,雷达信息->其他车的位置 自动驾驶汽车

回归

最简单的回归是用一元一次线性函数预测,根据已有的点来拟合出一个一元一次线性函数,然后用线性函数预测其他点。也可以将目标函数从y=ax+b替换成更复杂的其他函数,比如二次函数等。

分类

分类只输出一小部分的可能或2元类别(是否/对错)。即值域是不连续的,取值范围是有限多个值。常用的简单的函数模型有$\frac{1}{1+e^{x} } $。

无监督学习

无监督学习不需要从x到y的映射。输入为有一系列特征的数据集,如:(年龄,肿瘤大小):(23,3)、(35,6)、(60,10).无监督学习的目标是从数据集中寻找数据的结构和模式。

使用聚类算法对dna进行分类,将不同的人分成不同类别。

聚类

异常检测:检测系统中的异常数据,比如金融系统的欺诈操作流水。

降维:将大数据压缩成小得多的数据集并尽可能少丢失信息。

Jupyter Notebook

从零开始!Jupyter Notebook的安装教程(附带pip和Python的安装教程)_jupyter notebook安装教程-CSDN博客

线性回归模型

线性回归是最简单的监督学习模型。我们假设只有一个影响因素,即特征的维度是1.所以模型函数如下:

f(w,b)(x)=wx+b

给定一组关于(x,y)的坐标,显然两点确定一条直线,当坐标的数量大于2时,不止一条直线可以选择。此时如何确定w和b?

使用损失函数(loss)(代价函数)(cost function):

常见的损失函数有:平方误差成本函数

然后,我们的目标就是最小化损失函数。当我们把所有点代入损失函数后,实际上得到了一个关于w和b的二次函数。

我们假设这组坐标是(x1,y1),(x2,y2)….(xm,ym),一共m个点。那原式=

如果我们先把b当做常数,求使得loss函数最小的w值,那这就是关于w的一个开口向上的二次函数,对称轴是-b/2a,即

对于(1,1),(2,2),(3,3).我们假设b=0.那么关于w的二次函数的对称轴:

很明显,对于(1,1),(2,2),(3,3)的原函数是y=x,即w=1,b=0,刚好对应上。

如果考虑到b,即推广到三维。

我们将原式化简,我们得到:

现在,我们分析函数 f(w,b) 的图像。函数 f(w,b) 是两个平方项的和,每个平方项都是关于 w 和 b的二次函数。因此,f(w,b)是一个二次函数,其图像在三维空间中是一个抛物面。

具体来说,由于两个平方项都是非负的,函数 f(w,b)有一个最小值,当且仅当两个平方项都为零时取得。

梯度下降

梯度下降的基本流程

初始化参数:
选择一个初始点 $ \theta0 $ 作为参数向量的起始值。通常可以随机初始化或设置为零向量。
选择一个合适的学习率 $ \alpha $,它决定了每次迭代时参数更新的步长。学习率的选择非常重要,过大可能导致算法不收敛,过小则可能导致收敛速度过慢。
计算损失函数的梯度:
对于给定的参数 $ \theta $,计算损失函数 $ J(\theta) $ 关于每个参数的偏导数,即梯度 $ \nabla
\theta J(\theta) $。梯度指出了损失函数在当前参数点处增长最快的方向。
更新参数:
根据梯度的负方向更新参数,因为梯度指向的是损失函数增加最快的方向,所以我们需要沿相反方向移动以减小损失函数。更新公式为: $ \theta := \theta - \alpha \nabla\theta J(\theta) $
这里的 $ \alpha $ 是学习率,控制每次更新的步长;$ \nabla
\theta J(\theta) $ 是损失函数关于参数的梯度。
重复迭代:
重复上述步骤,直到满足某个停止条件。常见的停止条件包括:
损失函数的变化非常小,即 $ |J(\theta^{(t+1)}) - J(\theta^{(t)})| < \epsilon $,其中 $ \epsilon $ 是一个小的阈值。
参数的变化非常小,即 $ |\theta^{(t+1)} - \theta^{(t)}| < \epsilon $.
达到预设的最大迭代次数。
输出最优参数:
当满足停止条件时,输出最终的参数 $ \theta $,这些参数使得损失函数达到最小值或接近最小值。

梯度下降的不同变体

根据每次更新时使用的样本数量,梯度下降有以下几种常见变体:

  1. 批量梯度下降(Batch Gradient Descent, BGD)
    特点:每次迭代使用所有训练样本计算梯度,并更新参数。
    优点:每次更新都基于全局信息,理论上可以找到全局最优解(对于凸函数)。
    缺点:计算复杂度高,尤其是当数据集较大时,每次迭代都需要遍历整个数据集,计算时间较长。
  2. 随机梯度下降(Stochastic Gradient Descent, SGD)
    特点:每次迭代只使用一个随机样本计算梯度,并更新参数。
    优点:每次迭代的计算量小,适合处理大规模数据集;由于引入了随机性,可以在某些情况下更快地跳出局部极小值。
    缺点:更新过程较为波动,可能会导致收敛速度较慢,且容易在接近最优解时来回振荡。
  3. 小批量梯度下降(Mini-batch Gradient Descent, MBGD)
    特点:每次迭代使用一小批(mini-batch)样本计算梯度,并更新参数。批大小通常介于1和整个数据集之间。
    优点:结合了批量梯度下降和随机梯度下降的优点,既能减少计算量,又能保持一定的稳定性,适合大多数实际应用。
    缺点:需要选择合适的批大小,批大小过小可能导致波动,过大则可能增加计算时间。
    梯度下降的改进方法
    为了提高梯度下降的性能,研究人员提出了一些改进方法:

  4. 动量(Momentum)
    原理:引入动量项,使得参数更新不仅依赖于当前梯度,还考虑了之前几次迭代的梯度方向。这可以帮助算法更快地穿越平坦区域,并减少振荡。
    更新公式: $ v := \beta v - \alpha \nabla_\theta J(\theta) $ $ \theta := \theta + v $ 其中,$ v $ 是动量项,$ \beta $ 是动量系数(通常取0.9左右)。

  5. Nesterov加速梯度(Nesterov Accelerated Gradient, NAG)
    原理:NAG是对动量方法的改进,它在计算梯度时提前考虑了动量的影响,从而更准确地预测下一步的位置。
    更新公式: $ v := \beta v - \alpha \nabla_\theta J(\theta + \beta v) $ $ \theta := \theta + v $
  6. 自适应学习率方法
    Adagrad:根据每个参数的历史梯度调整学习率,使得频繁更新的参数具有较小的学习率,而较少更新的参数具有较大的学习率。
    Adadelta:改进了Adagrad的累积梯度问题,使用滑动窗口来计算历史梯度的平方和。
    RMSprop:类似于Adadelta,但使用指数加权平均来平滑历史梯度的平方和。
    Adam(Adaptive Moment Estimation):结合了动量和RMSprop的优点,使用一阶矩估计(动量)和二阶矩估计(梯度平方的平滑)来动态调整学习率。

正规方程

当参数只有2个时,可以使用数学方法直接计算函数的最小值。但当参数越来越多,函数就会越来越复杂。假设原式是$y=w_1 x_1+w_2 x_2+…+w_n x_n+b$

。那如何求解损失函数的最小值来获得w1到wn呢?

给定的原式是线性回归模型: $ y = w_1 x_1 + w_2 x_2 + \cdots + w_n x_n + b $

其中,$ y $ 是目标变量,$ x_1, x_2, \ldots, x_n $ 是特征变量,$ w_1, w_2, \ldots, w_n $ 是权重,$ b $ 是偏置项。

使用的损失函数是平方误差成本函数: $ J(w1, w_2, \ldots, w_n, b) = \frac{1}{2m} \sum{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2 $

其中,$ \hat{y}^{(i)} $ 是模型预测的值,$ y^{(i)} $ 是实际值,$ m $ 是样本数量。

关于 $ w_1, w_2, \ldots, w_n $ 的函数 对于每个权重 $ w_j $(其中 $ j = 1, 2, \ldots, n $),损失函数 $ J $ 是关于 $ w_j $ 的二次函数。

具体来说,对于 $ wj $ 的偏导数为: $ \frac{\partial J}{\partial w_j} = \frac{1}{m} \sum{i=1}^{m} (\hat{y}^{(i)} - y^{(i)}) x_j^{(i)} $

找到最低点 为了找到损失函数 $ J $ 的最低点,我们需要求解所有偏导数并将其设为零,即: $ \frac{\partial J}{\partial w_j} = 0 \quad \text{for all} \quad j = 1, 2, \ldots, n $ $ \frac{\partial J}{\partial b} = 0 $ 这将给出一组线性方程,称为正规方程(Normal Equations)。

求解这组方程可以得到 $ w_1, w_2, \ldots, w_n $ 和 $ b $ 的最优值。

最小值的存在性 由于损失函数 $ J $ 是关于 $ w_1, w_2, \ldots, w_n $ 和 $ b $ 的二次函数,且二次项系数为正,因此函数是凸的。凸函数的性质之一是它们有且只有一个最小值点。因此,通过求解偏导数并将其设为零,我们可以找到损失函数 $ J $ 的全局最小值点。

总结

关于 $ w_1, w_2, \ldots, w_n $ 的函数是二次函数,可以通过求解偏导数并将其设为零来找到最低点。由于损失函数是凸的,所以存在且只有一个最小值点。

梯度下降与解析解

梯度下降(Gradient Descent)在处理大规模数据集时,通常比解析解(如正规方程)表现更好,主要原因在于计算效率、内存占用和可扩展性等方面的优势。以下是详细的解释:

1. 计算复杂度

  • 正规方程:正规方程的计算复杂度为 ( O(n^3) ),其中 ( n ) 是特征的数量。这是因为需要计算 ( X^T X ) 的逆矩阵,而求逆矩阵的复杂度是立方级别的。对于高维数据集(即特征数量较多的情况),这个复杂度会变得非常大,导致计算时间过长。

  • 梯度下降:梯度下降的计算复杂度为 ( O(mn) ),其中 ( m ) 是样本数量,( n ) 是特征数量。每次迭代中,梯度下降只需要计算代价函数的梯度,并更新参数。虽然需要多次迭代才能收敛到最优解,但每次迭代的计算量相对较小,尤其是当使用随机梯度下降(SGD)或小批量梯度下降(Mini-batch Gradient Descent)时,计算量可以进一步减少。

2. 内存占用

  • 正规方程:正规方程需要存储设计矩阵 ( X ) 和其转置 ( X^T ),并且还需要存储 ( X^T X ) 这个 ( n \times n ) 的方阵。对于大规模数据集,尤其是当特征数量 ( n ) 较大时,这些矩阵的存储需求会非常大,可能超出计算机的内存限制。

  • 梯度下降:梯度下降只需要存储设计矩阵 ( X ) 和输出向量 ( y ),并且每次迭代只涉及少量的矩阵运算。因此,梯度下降的内存占用相对较小,尤其适用于处理大规模数据集。

3. 可扩展性

  • 正规方程:由于正规方程的计算复杂度和内存占用都随着特征数量 ( n ) 的增加而急剧增长,它在处理高维数据集时的可扩展性较差。当特征数量达到数千甚至数万时,正规方程的计算几乎不可行。

  • 梯度下降:梯度下降具有良好的可扩展性,尤其是在使用随机梯度下降(SGD)或小批量梯度下降(Mini-batch Gradient Descent)时。这两种方法每次只使用一个或少量样本进行更新,因此可以有效地处理大规模数据集,甚至是流式数据。此外,梯度下降还可以通过分布式计算框架(如Spark、Hadoop等)进行并行化,进一步提高处理大规模数据的能力。

4. 处理多重共线性

  • 正规方程:如果特征之间存在高度相关性(即多重共线性),( X^T X ) 可能会变得接近奇异矩阵,导致逆矩阵不稳定或不存在。此时,正规方程可能无法正常工作,或者得到的结果不准确。

  • 梯度下降:梯度下降对多重共线性的敏感性较低。即使特征之间存在相关性,梯度下降仍然可以通过迭代逐步逼近最优解。此外,梯度下降可以通过引入正则化项(如L2正则化或L1正则化)来缓解多重共线性问题,从而提高模型的稳定性和泛化能力。

5. 实时更新与增量学习

  • 正规方程:正规方程是一次性求解最优参数的方法,无法方便地进行实时更新。如果数据集发生变化(例如新增了样本或特征),必须重新计算整个模型,这在实际应用中往往是不现实的。

  • 梯度下降:梯度下降可以轻松实现增量学习,即在新数据到来时,只需基于现有模型进行少量的参数更新即可。这对于在线学习和实时系统非常重要。随机梯度下降(SGD)特别适合处理流式数据,因为它每次只使用一个样本进行更新,能够快速适应新的数据变化。

6. 非线性模型的支持

  • 正规方程:正规方程主要用于线性回归模型,对于非线性模型(如神经网络、支持向量机等),无法直接应用。虽然可以通过引入多项式特征等方式将非线性问题转化为线性问题,但这会大大增加特征数量,进而增加计算复杂度和内存占用。

  • 梯度下降:梯度下降不仅可以用于线性模型,还可以广泛应用于各种非线性模型,如神经网络、逻辑回归等。对于这些复杂的模型,梯度下降仍然是最常用的优化算法之一。通过反向传播算法,梯度下降可以有效地训练深度神经网络,处理高维非线性数据。

7. 总结

综上所述,梯度下降在处理大规模数据集时表现出色,主要归功于其较低的计算复杂度、较小的内存占用、良好的可扩展性以及对多重共线性和非线性模型的支持。相比之下,正规方程虽然具有理论上的精确性,但在处理大规模数据集时,计算成本过高且内存占用过大,因此在实际应用中往往不如梯度下降实用。

在选择优化算法时,应该根据具体的应用场景和数据规模来决定。对于小规模数据集或低维特征空间,正规方程可能是更好的选择;而对于大规模数据集或高维特征空间,梯度下降及其变体(如SGD、Mini-batch GD)通常是更优的选择。

常见凸与非凸损失函数

常见的凸损失函数:

平方误差损失(Mean Squared Error, MSE):
$ J(w) = \frac{1}{2m} \sum{i=1}^{m} (\hat{y}^{(i)} - y^{(i)})^2 $
这是一个典型的凸函数。它的二阶导数(Hessian矩阵)是正定的,因此它是严格凸的。平方误差损失广泛用于线性回归、最小二乘法等问题中。
对数损失(Log Loss / Cross-Entropy Loss):
$ J(w) = -\frac{1}{m} \sum
{i=1}^{m} \left[ y^{(i)} \log(\hat{y}^{(i)}) + (1 - y^{(i)}) \log(1 - \hat{y}^{(i)}) \right] $
适用于逻辑回归和分类问题。对数损失也是凸函数,确保了优化过程中可以找到全局最优解。
Huber损失:
Huber损失结合了平方误差损失和绝对误差损失的优点,既保持了平方误差损失在小误差时的平滑性,又避免了大误差时的敏感性。它在一定范围内是二次的,超出该范围后变为线性,因此也是凸函数。
L2正则化项:
$ R(w) = \frac{\lambda}{2} | w |^2 $
L2正则化项是凸的,因为它是一个二次函数。它可以与上述损失函数结合使用,形成带正则化的凸损失函数。
非凸损失函数的例子:

非线性激活函数的组合:
在神经网络中,如果使用非线性激活函数(如ReLU、Sigmoid等),并且网络结构较为复杂(例如深度较深),那么整体的损失函数可能是非凸的。这种情况下,损失函数可能有多个局部极小值,优化过程可能会陷入局部最优解。
高阶多项式损失:
如果损失函数包含高阶多项式项(如四次或更高次项),那么它可能是非凸的。高阶多项式可能会导致函数图像出现多个波峰和波谷,从而使得优化过程难以找到全局最优解。
交叉熵损失与非线性模型结合:
虽然交叉熵损失本身是凸的,但如果与复杂的非线性模型(如深度神经网络)结合,整体的损失函数可能会变得非凸

批量梯度下降实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.xjcares.extensionservice.controller;

public class BatchGradientDescent {

private double learningRate;
private int epochs;

public BatchGradientDescent(double learningRate, int epochs) {
this.learningRate = learningRate;
this.epochs = epochs;
}

public double[] train(double[] X, double[] y) {
int n = X.length;
double w = 0.0;
double b = 0.0;

for (int epoch = 0; epoch < epochs; epoch++) {
double dw = 0.0;
double db = 0.0;

for (int i = 0; i < n; i++) {
double prediction = w * X[i] + b;
double error = prediction - y[i];
dw += error * X[i];
db += error;
}

w -= learningRate * (dw / n);
b -= learningRate * (db / n);

// Optional: Print the loss for every 100 epochs
if (epoch % 100 == 0) {
double loss = calculateLoss(X, y, w, b);
System.out.println("Epoch " + epoch + ": Loss = " + loss);
}
}

return new double[]{w, b};
}

private double calculateLoss(double[] X, double[] y, double w, double b) {
int n = X.length;
double loss = 0.0;
for (int i = 0; i < n; i++) {
double prediction = w * X[i] + b;
double error = prediction - y[i];
loss += error * error;
}
return loss / (2 * n);
}

public static void main(String[] args) {
double[] X = {1, 2, 3, 4, 5};
double[] y = {2, 4, 6, 8, 10};

BatchGradientDescent bgd = new BatchGradientDescent(0.00001, 1000000000);
double[] parameters = bgd.train(X, y);

System.out.println("Optimal weight (w): " + parameters[0]);
System.out.println("Optimal bias (b): " + parameters[1]);
}
}