1. 项目概述
"从0开始的机器学习(笔记系列)——导数代码系列"是一个面向机器学习初学者的实践性教程项目。这个系列的核心目标是通过代码实现的方式,帮助读者从最基础的数学概念——导数入手,逐步构建对机器学习底层原理的直观理解。
在实际教学和自学过程中,我发现很多初学者在接触机器学习时,往往直接跳入各种复杂的算法和框架,而忽略了最基础的数学工具。这就像试图建造高楼却忽视了地基的稳固性。导数作为微积分的核心概念,是理解梯度下降、反向传播等机器学习关键技术的基石。
2. 导数在机器学习中的核心作用
2.1 为什么导数如此重要
在机器学习中,导数扮演着至关重要的角色。它不仅是优化算法的核心工具,更是理解模型如何"学习"的关键。以最简单的线性回归为例,模型通过最小化损失函数来调整参数,而这个最小化过程正是通过计算导数来实现的。
导数的本质是描述函数在某一点的变化率。在机器学习中,我们需要知道参数的小幅调整会对整体预测产生怎样的影响,这正是导数能够告诉我们的信息。
2.2 常见机器学习场景中的导数应用
- 梯度下降算法:通过计算损失函数对各个参数的偏导数,确定参数更新的方向和幅度
- 反向传播算法:神经网络训练的核心,本质上是导数链式法则的巧妙应用
- 特征工程:通过导数分析可以了解不同特征对模型输出的影响程度
- 正则化:L1/L2正则化项的导数形式决定了它们对模型参数的不同约束方式
3. 导数的基础实现
3.1 数值微分的基本原理
数值微分是计算导数的基本方法之一,特别适合在计算机上实现。其核心思想是利用极限的定义来近似计算导数:
f'(x) ≈ (f(x+h) - f(x))/h
其中h是一个很小的数,通常取1e-5左右。这种方法虽然简单,但存在截断误差和舍入误差的权衡问题。
3.2 Python实现数值微分
python复制def numerical_diff(f, x, h=1e-5):
"""
计算函数f在点x处的数值导数
参数:
f: 目标函数
x: 求导点
h: 微小变化量,默认为1e-5
返回:
导数近似值
"""
return (f(x + h) - f(x - h)) / (2 * h) # 中心差分公式,精度更高
这个实现使用了中心差分公式,相比前向差分有更高的精度。我们可以测试一下:
python复制# 测试函数:f(x) = x^2
def test_func(x):
return x**2
print(numerical_diff(test_func, 3)) # 理论导数为6,实际输出接近6
3.3 常见函数的导数实现
在实际机器学习中,我们经常需要处理一些特定函数的导数。以下是几个典型示例:
- Sigmoid函数导数:
python复制def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
s = sigmoid(x)
return s * (1 - s) # 导数有简洁的表达式
- ReLU函数导数:
python复制def relu_derivative(x):
return np.where(x > 0, 1, 0) # x>0时为1,否则为0
- Softmax函数导数:
python复制def softmax_derivative(s):
# s是softmax输出向量
return np.diag(s) - np.outer(s, s) # 返回Jacobian矩阵
4. 自动微分与机器学习框架
4.1 从手动求导到自动微分
在实际机器学习项目中,我们很少需要手动实现导数计算。现代机器学习框架如TensorFlow和PyTorch都提供了自动微分(Automatic Differentiation)功能。自动微分结合了数值微分和符号微分的优点,能够高效准确地计算任意复杂函数的导数。
4.2 PyTorch中的自动微分示例
python复制import torch
# 创建需要求导的张量
x = torch.tensor(3.0, requires_grad=True)
# 定义函数
y = x**2 + 2*x + 1
# 自动计算导数
y.backward()
print(x.grad) # 输出导数 dy/dx = 2x + 2 = 8
4.3 自动微分的工作原理
自动微分通过构建计算图来追踪所有操作,然后应用链式法则反向传播梯度。这个过程可以分为两个阶段:
- 前向传播:记录所有操作和中间结果,构建计算图
- 反向传播:从输出开始,按照链式法则计算各变量的梯度
这种机制使得我们可以轻松计算复杂函数的导数,而不必手动推导数学表达式。
5. 导数在梯度下降中的应用
5.1 梯度下降的基本原理
梯度下降是机器学习中最常用的优化算法,其核心思想是通过不断沿着函数梯度的反方向调整参数,逐步逼近最小值。参数更新公式为:
θ = θ - η·∇J(θ)
其中η是学习率,∇J(θ)是损失函数J关于参数θ的梯度(即偏导数向量)。
5.2 梯度下降的Python实现
python复制def gradient_descent(f, init_x, lr=0.01, step_num=100):
"""
梯度下降法实现
参数:
f: 目标函数(需要实现梯度计算)
init_x: 初始值
lr: 学习率
step_num: 迭代次数
返回:
优化后的x值
"""
x = init_x.copy()
for i in range(step_num):
grad = numerical_gradient(f, x) # 计算梯度
x -= lr * grad # 参数更新
return x
5.3 梯度下降的变体
在实际应用中,基础的梯度下降法有几个重要变体:
- 随机梯度下降(SGD):每次使用单个样本计算梯度,计算快但波动大
- 小批量梯度下降:折中方案,使用小批量样本计算梯度
- 带动量的梯度下降:引入动量项减少震荡,加速收敛
- 自适应学习率算法:如Adam、RMSprop等,自动调整各参数的学习率
6. 高阶导数与优化
6.1 二阶导数的意义
在优化问题中,二阶导数(Hessian矩阵)提供了函数曲率的信息。它可以帮助我们:
- 判断临界点是最小值、最大值还是鞍点
- 实现更高效的二阶优化方法(如牛顿法)
- 分析优化问题的局部几何性质
6.2 Hessian矩阵的计算
对于多元函数f(x₁,x₂,...,xₙ),其Hessian矩阵H是一个n×n矩阵,其中Hᵢⱼ = ∂²f/∂xᵢ∂xⱼ。数值计算Hessian矩阵的实现:
python复制def numerical_hessian(f, x, h=1e-5):
"""
计算函数f在点x处的数值Hessian矩阵
参数:
f: 目标函数
x: 求导点(向量)
h: 微小变化量
返回:
Hessian矩阵
"""
n = x.shape[0]
hessian = np.zeros((n, n))
# 计算对角元素
for i in range(n):
# 中心差分计算二阶偏导
temp = x[i]
x[i] = temp + h
fx_plus = f(x)
x[i] = temp - h
fx_minus = f(x)
x[i] = temp
hessian[i,i] = (fx_plus - 2*f(x) + fx_minus) / (h**2)
# 计算非对角元素
for i in range(n):
for j in range(i+1, n):
# 混合偏导
temp1, temp2 = x[i], x[j]
# f(x_i + h, x_j + h)
x[i] = temp1 + h
x[j] = temp2 + h
fx1 = f(x)
# f(x_i + h, x_j - h)
x[j] = temp2 - h
fx2 = f(x)
# f(x_i - h, x_j + h)
x[i] = temp1 - h
x[j] = temp2 + h
fx3 = f(x)
# f(x_i - h, x_j - h)
x[j] = temp2 - h
fx4 = f(x)
# 恢复原始值
x[i], x[j] = temp1, temp2
hessian[i,j] = (fx1 - fx2 - fx3 + fx4) / (4 * h**2)
hessian[j,i] = hessian[i,j] # 对称矩阵
return hessian
6.3 牛顿法优化
利用Hessian矩阵可以实现二阶优化方法——牛顿法:
python复制def newton_method(f, init_x, step_num=10, h=1e-5):
"""
牛顿法优化实现
参数:
f: 目标函数
init_x: 初始值(向量)
step_num: 迭代次数
h: 数值微分步长
返回:
优化后的x值
"""
x = init_x.copy()
for i in range(step_num):
grad = numerical_gradient(f, x, h) # 一阶梯度
hessian = numerical_hessian(f, x, h) # Hessian矩阵
x -= np.linalg.solve(hessian, grad) # 解线性方程组HΔx=-g
return x
7. 导数计算的实用技巧与常见问题
7.1 数值稳定性的处理
在实现导数计算时,数值稳定性是需要特别注意的问题:
- 选择合适的h值:h太小会导致舍入误差,太大会引入截断误差。通常1e-5到1e-7是不错的选择
- 使用中心差分公式:(f(x+h)-f(x-h))/2h比前向差分精度更高
- 对数域计算:对于涉及指数函数的计算,可以先取对数再求导
- 梯度裁剪:防止梯度爆炸问题,特别是在RNN中
7.2 常见问题排查
- 梯度检查(Gradient Checking):
python复制def gradient_check(f, grad_f, x, h=1e-5, tol=1e-7):
"""
比较解析梯度与数值梯度的差异
参数:
f: 目标函数
grad_f: 解析梯度函数
x: 检查点
h: 数值微分步长
tol: 允许误差
返回:
是否通过检查
"""
numerical_grad = numerical_gradient(f, x, h)
analytic_grad = grad_f(x)
diff = np.linalg.norm(numerical_grad - analytic_grad) / np.linalg.norm(numerical_grad + analytic_grad)
return diff < tol
- 梯度消失/爆炸问题:
- 现象:训练早期梯度变得极小(消失)或极大(爆炸)
- 解决方案:
- 使用ReLU等改良的激活函数
- 应用Batch Normalization
- 使用梯度裁剪
- 调整网络深度
- 局部最优与鞍点问题:
- 现象:优化过程停滞,梯度接近零但未达到全局最优
- 解决方案:
- 使用带动量的优化器
- 尝试不同的初始化方法
- 增加噪声扰动
7.3 性能优化建议
- 向量化计算:使用NumPy等库的向量化操作替代循环
- 利用对称性:Hessian矩阵是对称的,可以减少一半计算量
- 并行计算:独立的分量可以并行计算
- 符号计算:对于固定形式的函数,可以使用SymPy等库进行符号求导
8. 从导数到机器学习模型的完整案例
8.1 线性回归实现
让我们通过一个完整的线性回归案例,将导数知识应用到实际问题中:
python复制class LinearRegression:
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate
self.n_iters = n_iters
self.weights = None
self.bias = None
def fit(self, X, y):
n_samples, n_features = X.shape
# 初始化参数
self.weights = np.zeros(n_features)
self.bias = 0
# 梯度下降
for _ in range(self.n_iters):
# 预测值
y_pred = np.dot(X, self.weights) + self.bias
# 计算梯度
dw = (1/n_samples) * np.dot(X.T, (y_pred - y))
db = (1/n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X):
return np.dot(X, self.weights) + self.bias
8.2 逻辑回归实现
逻辑回归虽然名为"回归",实际上是分类算法,同样基于梯度下降:
python复制class LogisticRegression:
def __init__(self, learning_rate=0.01, n_iters=1000):
self.lr = learning_rate
self.n_iters = n_iters
self.weights = None
self.bias = None
def _sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def fit(self, X, y):
n_samples, n_features = X.shape
# 初始化参数
self.weights = np.zeros(n_features)
self.bias = 0
# 梯度下降
for _ in range(self.n_iters):
# 线性组合
linear_model = np.dot(X, self.weights) + self.bias
# Sigmoid激活
y_pred = self._sigmoid(linear_model)
# 计算梯度
dw = (1/n_samples) * np.dot(X.T, (y_pred - y))
db = (1/n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X, threshold=0.5):
linear_model = np.dot(X, self.weights) + self.bias
y_pred = self._sigmoid(linear_model)
return (y_pred >= threshold).astype(int)
8.3 神经网络实现
最后,我们实现一个简单的两层神经网络,展示导数在反向传播中的应用:
python复制class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros((1, output_size))
def _sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def _sigmoid_derivative(self, x):
return x * (1 - x)
def forward(self, X):
# 前向传播
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = self._sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = self._sigmoid(self.z2)
return self.a2
def backward(self, X, y, learning_rate):
m = X.shape[0]
# 输出层误差
dZ2 = self.a2 - y
dW2 = np.dot(self.a1.T, dZ2) / m
db2 = np.sum(dZ2, axis=0, keepdims=True) / m
# 隐藏层误差
dA1 = np.dot(dZ2, self.W2.T)
dZ1 = dA1 * self._sigmoid_derivative(self.a1)
dW1 = np.dot(X.T, dZ1) / m
db1 = np.sum(dZ1, axis=0, keepdims=True) / m
# 更新参数
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1
def train(self, X, y, epochs, learning_rate):
for i in range(epochs):
output = self.forward(X)
self.backward(X, y, learning_rate)
def predict(self, X, threshold=0.5):
output = self.forward(X)
return (output >= threshold).astype(int)
9. 导数计算的进阶话题
9.1 符号微分与自动微分
除了数值微分,还有两种重要的微分方法:
-
符号微分:通过代数运算直接得到导数的解析表达式
- 优点:精确,可以得到解析解
- 缺点:表达式可能非常复杂,存在表达式膨胀问题
- 工具:SymPy、Mathematica等
-
自动微分:现代深度学习框架采用的技术
- 优点:计算精确,效率高
- 实现方式:前向模式与反向模式
- 工具:TensorFlow、PyTorch等
9.2 复杂函数的导数计算
对于包含条件、循环等复杂控制流的函数,导数计算需要特殊处理:
- 分支函数:在每个分支点,导数可能不连续,需要分别处理
- 循环结构:需要展开循环或使用动态规划方法
- 递归函数:可以使用自动微分或手动推导递推关系
9.3 高阶自动微分
现代框架支持高阶导数计算,这在以下场景很有用:
- 元学习:优化优化过程本身
- 物理模拟:需要高阶导数来保证精度
- 概率建模:高阶统计量的计算
PyTorch示例:
python复制x = torch.tensor(2.0, requires_grad=True)
y = x**3 + x**2
# 一阶导数
grad1 = torch.autograd.grad(y, x, create_graph=True)[0]
# 二阶导数
grad2 = torch.autograd.grad(grad1, x)[0]
print(grad2) # 6x + 2 = 14
10. 导数可视化技术
10.1 梯度场可视化
梯度场展示了函数在每个点的梯度方向和大小:
python复制import matplotlib.pyplot as plt
def plot_gradient_field(f, x_range, y_range):
x = np.linspace(x_range[0], x_range[1], 20)
y = np.linspace(y_range[0], y_range[1], 20)
X, Y = np.meshgrid(x, y)
# 计算梯度
U = np.zeros_like(X)
V = np.zeros_like(Y)
for i in range(X.shape[0]):
for j in range(X.shape[1]):
point = np.array([X[i,j], Y[i,j]])
grad = numerical_gradient(f, point)
U[i,j] = grad[0]
V[i,j] = grad[1]
plt.quiver(X, Y, U, V)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Gradient Field')
plt.show()
# 示例函数
def test_func_2d(x):
return x[0]**2 + x[1]**2
plot_gradient_field(test_func_2d, [-2,2], [-2,2])
10.2 优化路径可视化
展示梯度下降过程中参数的轨迹:
python复制def plot_optimization_path(f, init_x, lr=0.1, steps=20):
x_history = [init_x.copy()]
x = init_x.copy()
for _ in range(steps):
grad = numerical_gradient(f, x)
x -= lr * grad
x_history.append(x.copy())
x_history = np.array(x_history)
# 绘制等高线
x_vals = np.linspace(-3, 3, 100)
y_vals = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x_vals, y_vals)
Z = np.array([f([x,y]) for x,y in zip(X.ravel(), Y.ravel())]).reshape(X.shape)
plt.contour(X, Y, Z, levels=20)
plt.plot(x_history[:,0], x_history[:,1], 'ro-')
plt.xlabel('x1')
plt.ylabel('x2')
plt.title('Optimization Path')
plt.show()
plot_optimization_path(test_func_2d, np.array([2.5, 2.5]))
10.3 导数与函数形状的关系
通过可视化可以直观理解导数与函数形状的关系:
- 一阶导数:反映函数单调性
- 二阶导数:反映函数凹凸性
- 偏导数:反映多维函数在不同方向的变化率
11. 导数在深度学习中的特殊应用
11.1 自定义层的导数实现
在构建自定义神经网络层时,需要手动实现前向传播和反向传播:
python复制class CustomLayer:
def __init__(self, input_dim, output_dim):
self.weights = np.random.randn(input_dim, output_dim) * 0.01
self.bias = np.zeros((1, output_dim))
def forward(self, X):
self.X = X # 缓存输入用于反向传播
return np.dot(X, self.weights) + self.bias
def backward(self, dout, learning_rate):
# dout是上一层的梯度
dX = np.dot(dout, self.weights.T)
dW = np.dot(self.X.T, dout)
db = np.sum(dout, axis=0, keepdims=True)
# 更新参数
self.weights -= learning_rate * dW
self.bias -= learning_rate * db
return dX # 传递给下一层
11.2 激活函数的导数特性
不同激活函数的导数特性直接影响神经网络的学习:
-
Sigmoid:
- 导数范围(0, 0.25]
- 容易导致梯度消失
-
Tanh:
- 导数范围(0, 1]
- 比sigmoid稍好但仍可能梯度消失
-
ReLU:
- 导数在正区间为1,负区间为0
- 缓解梯度消失但可能导致神经元"死亡"
-
LeakyReLU:
- 负区间有小的斜率(如0.01)
- 缓解神经元死亡问题
11.3 损失函数的导数分析
常见损失函数的导数特性:
-
均方误差(MSE):
- 导数线性依赖于误差
- 对异常值敏感
-
交叉熵(Cross-Entropy):
- 对分类任务更合适
- 梯度与误差成正比,学习速度稳定
-
Huber Loss:
- 结合MSE和MAE优点
- 对异常值更鲁棒
12. 性能优化与数值稳定性
12.1 梯度计算的数值问题
在实际实现中,梯度计算可能遇到各种数值问题:
-
下溢/上溢:
- 解决方案:使用对数域计算,或对中间结果进行缩放
-
数值不稳定:
- 原因:病态条件数,接近奇异的Hessian矩阵
- 解决方案:正则化,添加小扰动
-
梯度消失/爆炸:
- 解决方案:梯度裁剪,权重初始化技巧,BatchNorm
12.2 高效梯度计算技巧
- 并行计算:利用GPU或分布式计算加速
- 内存优化:复用中间结果,减少内存占用
- 计算图优化:合并操作,减少计算量
- 混合精度训练:使用FP16加速计算
12.3 梯度检查的高级技巧
更全面的梯度检查实现:
python复制def advanced_gradient_check(f, grad_f, x, h=1e-5, tol=1e-7, num_checks=10):
"""
更全面的梯度检查
参数:
f: 目标函数
grad_f: 解析梯度函数
x: 检查点
h: 数值微分步长
tol: 允许误差
num_checks: 随机检查的次数
返回:
是否通过检查
"""
if isinstance(x, (int, float)):
x = np.array([x])
n = x.shape[0]
passed = True
for _ in range(num_checks):
# 随机选择检查的维度
i = np.random.randint(0, n)
# 保存原始值
original = x[i]
# 计算数值梯度
x[i] = original + h
fx_plus = f(x)
x[i] = original - h
fx_minus = f(x)
x[i] = original # 恢复
numerical_grad_i = (fx_plus - fx_minus) / (2*h)
analytic_grad = grad_f(x)
analytic_grad_i = analytic_grad[i]
# 计算相对误差
numerator = abs(numerical_grad_i - analytic_grad_i)
denominator = abs(numerical_grad_i) + abs(analytic_grad_i) + 1e-12
relative_error = numerator / denominator
if relative_error > tol:
print(f"Gradient check failed at dimension {i}")
print(f"Numerical: {numerical_grad_i}, Analytic: {analytic_grad_i}")
print(f"Relative error: {relative_error}")
passed = False
return passed
13. 导数计算的硬件加速
13.1 GPU加速
现代GPU非常适合并行计算梯度:
- CUDA实现:使用Numba或直接CUDA编程
- 框架支持:PyTorch、TensorFlow自动利用GPU
- 批处理:同时计算多个样本的梯度
13.2 分布式梯度计算
对于大规模问题,可以分布式计算梯度:
- 数据并行:不同worker处理不同数据批次
- 模型并行:将模型拆分到不同设备
- 梯度聚合:AllReduce操作同步梯度
13.3 专用硬件
- TPU:Google的专用张量处理器
- FPGA:可编程硬件,低延迟
- ASIC:定制化芯片,高效但缺乏灵活性
14. 导数在强化学习中的应用
14.1 策略梯度方法
策略梯度定理是强化学习中基于导数的核心方法:
∇J(θ) = E[∇logπ(a|s) Q(s,a)]
其中:
- J(θ)是期望回报
- π(a|s)是策略函数
- Q(s,a)是动作价值函数
14.2 REINFORCE算法实现
python复制def reinforce(policy_network, optimizer, episodes, gamma=0.99):
for episode in range(episodes):
states, actions, rewards = [], [], []
state = env.reset()
# 收集轨迹
done = False
while not done:
action_probs = policy_network(state)
action = np.random.choice(len(action_probs), p=action_probs)
next_state, reward, done, _ = env.step(action)
states.append(state)
actions.append(action)
rewards.append(reward)
state = next_state
# 计算回报
returns = []
G = 0
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
# 归一化回报
returns = torch.tensor(returns)
returns = (returns - returns.mean()) / (returns.std() + 1e-9)
# 计算策略梯度
policy_loss = []
for state, action, G in zip(states, actions, returns):
action_probs = policy_network(state)
log_prob = torch.log(action_probs[action])
policy_loss.append(-log_prob * G)
# 更新策略
optimizer.zero_grad()
policy_loss = torch.stack(policy_loss).sum()
policy_loss.backward()
optimizer.step()
14.3 演员-评论家方法
结合值函数(评论家)和策略梯度(演员):
python复制class ActorCritic:
def __init__(self, state_dim, action_dim, hidden_dim=64):
self.actor = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, action_dim),
nn.Softmax(dim=-1)
)
self.critic = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, 1)
)
def act(self, state):
action_probs = self.actor(state)
dist = Categorical(action_probs)
action = dist.sample()
return action.item()
def evaluate(self, state, action):
action_probs = self.actor(state)
dist = Categorical(action_probs)
action_logprobs = dist.log_prob(action)
dist_entropy = dist.entropy()
state_value = self.critic(state)
return action_logprobs, state_value, dist_entropy
15. 导数在生成模型中的应用
15.1 变分自编码器(VAE)
VAE通过最大化证据下界(ELBO)来训练:
∇ELBO = E[∇logp(x|z)] - ∇KL(q(z|x)||p(z))
实现关键:
python复制def vae_loss(recon_x, x, mu, logvar):
# 重构损失
BCE = F.binary_cross_entropy(recon_x, x, reduction='sum')
# KL散度
KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return BCE + KLD
15.2 生成对抗网络(GAN)
GAN的对抗训练涉及两个网络的梯度:
python复制def train_gan(generator, discriminator, dataloader, epochs):
for epoch in range(epochs):
for real_data, _ in dataloader:
# 训练判别器
optimizer_D.zero_grad()
# 真实数据
real_output = discriminator(real_data)
d_loss_real = -torch.mean(real_output)
# 生成数据
noise = torch.randn(batch_size, latent_dim)
fake_data = generator(noise)
fake_output = discriminator(fake_data.detach())
d_loss_fake = torch.mean(fake_output)
d_loss = d_loss_real + d_loss_fake
d_loss.backward()
optimizer_D.step()
# 训练生成器
optimizer_G.zero_grad()
fake_output = discriminator(fake_data)
g_loss = -torch.mean(fake_output)
g_loss.backward()
optimizer_G.step()
15.3 归一化流(Normalizing Flows)
通过变量变换和链式法则计算概率密度:
logp(x) = logp(z) + log|det(∂f⁻¹(x)/∂x)|
其中f是可逆变换,z=f⁻¹(x)。
16. 导数在元学习中的应用
16.1 MAML算法
模型无关的元学习(Model-Agnostic Meta-Learning)通过二阶导数实现快速适应:
python复制def maml_train(model, tasks, inner_lr, meta_lr, inner_steps=1):
meta_optimizer = torch.optim.Adam(model.parameters(), lr=meta_lr)
for task_batch in tasks:
# 保存初始参数
fast_weights = OrderedDict(model.named_parameters())
# 内循环(任务特定适应)
for task in task_batch:
for step in range(inner_steps):
# 计算支持集损失
support_loss = compute_loss(model, task['support'])
# 计算梯度并更新fast_weights
grads = torch.autograd.grad(support_loss, fast_weights.values(),
create_graph=True)
fast_weights = OrderedDict(
(name, param - inner_lr * grad)
for (name, param), grad in zip(fast_weights.items(), grads)
)
# 计算查询集损失(用于元更新)
query_loss = compute_loss(fast_weights, task['query'])
query_loss.backward()
# 元更新
meta_optimizer.step()
meta_optimizer.zero_grad()
16.2 高阶导数的重要性
MAML的关键是内循环梯度计算时设置create_graph=True,保留计算图以计算二阶导数:
- 一阶近似:忽略二阶导数,计算更快但性能下降
- 隐式微分:对于内循环迭代较多的情况更高效
17. 导数在概率编程中的应用
17.1 变分推断
通过梯度下降优化变分分布:
python复制def vi(model, guide, data, lr=0.01, steps=1000):
optimizer = Adam(guide.parameters(), lr=lr)
for step in range(steps):
optimizer.zero_grad()
# 计算ELBO
trace = poutine.trace(guide).get_trace(data)
elbo = pyro.infer.Trace_ELBO().differentiable_loss(model, guide, data)
# 梯度下降
elbo.backward()
optimizer.step()
17.2 哈密顿蒙特卡洛(HMC)
利用梯度信息进行更高效的MCMC采样:
python复制def hmc(U, grad_U, epsilon, L, current_q):
q = current_q
p = torch.randn(len(q)) # 随机动量
current_p = p
# 蛙跳积分
p = p - epsilon * grad_U(q) / 2
for i in range(L):
q = q + epsilon * p
if i != L-1:
p = p - epsilon * grad_U(q)
p = p - epsilon * grad_U(q) / 2
# 接受/拒绝
current_U = U(current_q)
current_K = (current_p**2).sum() / 2
proposed_U = U(q)
proposed_K = (