(二)当人工智能是一个函数时,怎么去训练它?
还记得上次我们讲到,人工智能本质上就是一个函数。那么今天,让我们更进一步:这个函数是怎么练成的?
一、从最简单的函数说起
假设我们有这样一组数据:
x | y |
---|---|
1 | 2.1 |
2 | 3.8 |
3 | 6.2 |
4 | 8.9 |
5 | 11.8 |
看着这组数据,你是否觉得它们之间存在某种关系?没错,它们大致符合一个二次函数的形态!
1. 函数的形式
我们假设这个函数的形式是:
def f(x, a, b, c):
return a * x * x + b * x + 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 + c
error = predicted - y
da += error * x * x
db += error * x
dc += error
# 更新参数
a -= learning_rate * da
b -= learning_rate * db
c -= learning_rate * c
return 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. 保存模型
import pickle
with open('model.pkl', 'wb') as f:
pickle.dump(model, f)
2. 加载使用
with open('model.pkl', 'rb') as f:
model = pickle.load(f)
result = model.predict(new_data)
总结:三种方法的对比
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
直接求解 | 精确,计算快 | 只适用于简单问题 | 方程组规模小,无噪声 |
最小二乘 | 有闭式解,计算稳定 | 计算复杂度高 | 回归问题,曲线拟合 |
梯度下降 | 通用性强,可处理大规模问题 | 可能陷入局部最优 | 深度学习,复杂非线性问题 |
理解了这些基础,你就会发现:人工智能不过是一个不断调整参数、优化结果的过程。它的神奇之处不在于多么复杂,而在于如何把复杂的问题简化成可解的数学模型。
下次我们将继续探讨更多人工智能的有趣话题,记得点赞关注!