(二)当人工智能是一个函数时,怎么去训练它?
还记得上次我们讲到,人工智能本质上就是一个函数。那么今天,让我们更进一步:这个函数是怎么练成的?
一、从最简单的函数说起
假设我们有这样一组数据:
x | y |
---|---|
1 | 2.1 |
2 | 3.8 |
3 | 6.2 |
4 | 8.9 |
5 | 11.8 |
看着这组数据,你是否觉得它们之间存在某种关系?没错,它们大致符合一个二次函数的形态!
1. 函数的形式
我们假设这个函数的形式是:
f ( x ) = a x 2 + b x + c f(x) = a x^2 + b x + c f(x)=ax2+bx+c
这里:
- x 是输入变量
- a, b, c 是函数的参数
- f(x) 是输出结果
2. 如何确定参数?
方法一:直接求解法
如果我们选择其中三个点,可以列出三个方程:
a(1)² + b(1) + c = 2.1
a(2)² + b(2) + c = 3.8
a(3)² + b(3) + c = 6.2
解这个方程组就能得到 a, b, c 的值。但这种方法有局限性:
- 只能用有限的点
- 方程组可能无解
- 不能处理有噪声的数据
方法二:最小二乘法
同样,因为我们希望拟合的函数形式为:
f ( x ) = a x 2 + b x + c f(x) = a x^2 + b x + c f(x)=ax2+bx+c
其中:
- x x x 是输入变量。
- y i y_i yi 是已知的真实输出值。
- a a a, b b b, c c c 是需要计算的参数。
我们用预测值 y ^ i = a x i 2 + b x i + c \hat{y}_i = a x_i^2 + b x_i + c y^i=axi2+bxi+c 表示模型生成的输出。目标是最小化预测值与真实值的误差平方和,即损失函数:
J ( a , b , c ) = ∑ i = 1 n ( y i − y ^ i ) 2 = ∑ i = 1 n ( y i − ( a x i 2 + b x i + c ) ) 2 J(a, b, c) = \sum_{i=1}^n (y_i - \hat{y}_i)^2 = \sum_{i=1}^n (y_i - (a x_i^2 + b x_i + c))^2 J(a,b,c)=i=1∑n(yi−y^i)2=i=1∑n(yi−(axi2+bxi+c))2
为了让上式的误差最小化,我们需要对 a a a, b b b, c c c 求偏导数,并让导数为零。这时,可以得到一组关于 a a a, b b b, c c c 的线性方程组。以矩阵形式表示为:
求解这个方程组即可得到参数 a a a, b b b, c c c。
以下是基于 Python 的最小二乘法计算二次函数参数的代码实现:
import numpy as np# 输入数据:x 和 y
x = np.array([1, 2, 3, 4, 5]) # 自变量
y = np.array([2.1, 3.8, 6.2, 8.9, 11.8]) # 因变量# 构造矩阵
X = np.vstack([x**2, x, np.ones(len(x))]).T # 包括 x^2、x 和常数项1
Y = y.reshape(-1, 1) # 转换为列向量# 最小二乘法计算: (X^T * X)^(-1) * (X^T * Y)
theta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Y) # 求解线性方程
a, b, c = theta.flatten() # 提取参数print(f"拟合结果:a = {a:.3f}, b = {b:.3f}, c = {c:.3f}")
方法三:梯度下降
这是机器学习中最常用的方法。其核心思想是:
- 随机初始化参数
- 计算损失函数对各参数的梯度
- 沿着梯度的反方向更新参数
- 重复步骤2-3直到收敛
def gradient_descent(x_data, y_data, learning_rate=0.0001, epochs=1000):a, b, c = 0, 0, 0 # 初始化参数for epoch in range(epochs):da, db, dc = 0, 0, 0 # 梯度初始化# 计算梯度for x, y in zip(x_data, y_data):predicted = a * x * x + b * x + cerror = predicted - yda += error * x * xdb += error * xdc += error# 更新参数a -= learning_rate * dab -= learning_rate * dbc -= learning_rate * creturn a, b, c
二、推广到神经网络
实际的神经网络比这个复杂得多,但基本思想是一样的:
1. 更复杂的函数形式
def neural_network(x, weights, biases):layer1 = sigmoid(np.dot(x, weights[0]) + biases[0])layer2 = sigmoid(np.dot(layer1, weights[1]) + biases[1])return layer2
2. 更多的参数
- 不再是简单的a, b, c
- 可能有成千上万个参数
- 参数之间有复杂的关联关系
3. 更复杂的训练过程
- 需要大量的训练数据
- 使用反向传播算法计算梯度
- 采用各种优化算法(如Adam)来更新参数
三、如何判断训练成功?
1. 验证集评估
把数据分成训练集和验证集,用验证集检验模型性能。
2. 常用评估指标
- 回归问题:MSE(均方误差)、MAE(平均绝对误差)
- 分类问题:准确率、召回率、F1分数
3. 过拟合问题
注意这张图片仅仅是一个示意图,最左边的图片表示欠拟合,也就是函数训练不到位;中间的图片表示训练得比较好,最右边的图为过拟合,也就是说这种情况训练得到的函数过于逼近原始数据,倒是其泛化性能不够好。注意这里提到的泛化性能是一个人工智能领域经常会遇到的概念,后面的文章再展开讨论。
四、训练好的函数如何使用?
1. 基于训练好的参数构建完整的函数
就像第一部分假设的,我们有如下数据集:
x | y |
---|---|
1 | 2.1 |
2 | 3.8 |
3 | 6.2 |
4 | 8.9 |
5 | 11.8 |
如下函数:
f ( x ) = a ∗ x 2 + b ∗ x + c f(x) = a * x² + b * x + c f(x)=a∗x2+b∗x+c
假如我们通过前面介绍的方法(直接求解、最小二乘或者梯度下降)训练得到了参数如下:
- a = 0.38
- b = 1.65
- c = 0.07
这里的 a
、b
和 c
通常也被称为模型的参数。
此时,完整的函数就变成:
f ( x ) = 0.38 ∗ x 2 + 1.65 ∗ x + 0.07 f(x) = 0.38 * x² + 1.65 * x + 0.07 f(x)=0.38∗x2+1.65∗x+0.07
2. 使用训练好的函数(模型推理)
模型推理的过程,其实就是将输入值代入训练好的函数,输出结果。这是它发挥作用的关键步骤。我们可以用训练好的函数预测新的输入数据下的结果。
- 输入
x = 6
,得:
f(6) = 0.38 * 6² + 1.65 * 6 + 0.07 = 15.53 - 输入
x = 7
,得:
f(7) = 0.38 * 7² + 1.65 * 7 + 0.07 = 19.74
可以通过下面的图更直观看到训练的效果:
总结:三种方法的对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
直接求解 | 精确,计算快 | 只适用于简单问题 | 方程组规模小,无噪声 |
最小二乘 | 有闭式解,计算稳定 | 计算复杂度高 | 回归问题,曲线拟合 |
梯度下降 | 通用性强,可处理大规模问题 | 可能陷入局部最优 | 深度学习,复杂非线性问题 |
理解了这些基础,你就会发现:人工智能不过是一个不断调整参数、优化结果的过程。它的神奇之处不在于多么复杂,而在于如何把复杂的问题简化成可解的数学模型。
下次我们将继续探讨更多人工智能的有趣话题,记得点赞关注!