你有没有遇到过这样的情况:训练一个神经网络时,损失函数像过山车一样忽高忽低,明明学习率调了又调,模型就是不收敛?你盯着那条波动剧烈的loss曲线,内心开始怀疑人生:是我的数据有问题,还是算法有问题?更让人崩溃的是,隔壁同事用同样的数据,同样的网络结构,训练出来的模型却稳定得像教科书案例。
我曾经请教过一位在Google工作的师兄,他告诉我一句话:"深度学习的本质是在高维空间中寻找最优解,如果你不理解损失函数的几何形状,你就是在黑暗中摸索。"这句话让我开始重新审视优化算法背后的数学原理,特别是"凸性"这个看似抽象却至关重要的概念。
今天,我想和你分享我在理解凸优化过程中的心路历程,以及它如何彻底改变了我对深度学习训练的认知。
什么是凸函数?用一个苹果来理解
想象你手里有一个完美的苹果,光滑圆润。如果把这个苹果放在桌子上,无论从哪个角度看,它的轮廓都是向外凸起的弧线。这就是凸函数的几何直观:函数图像像苹果的轮廓一样,任意两点之间的连线都在函数图像的上方。

用数学语言表达,对于函数f(x),如果对于任意两点x₁和x₂,以及任意λ ∈ [0,1],都有:
f(λ*x1 + (1-λ)*x2) ≤ λ*f(x1) + (1-λ)*f(x2)
那么f(x)就是凸函数。这个不等式的含义是:函数上任意两点的连线,都位于函数曲线的上方或与之重合。
让我们用代码来验证一个简单的凸函数:
import numpy as np
import matplotlib.pyplot as plt
def f(x):
return x**2 # 简单的二次函数,是凸函数
# 验证凸性
x1, x2 = 1, 3
lambda_val = 0.3
# 计算凸组合
x_combo = lambda_val * x1 + (1 - lambda_val) * x2
f_combo = f(x_combo)
# 计算函数值的凸组合
f_val_combo = lambda_val * f(x1) + (1 - lambda_val) * f(x2)
print(f"f({
x_combo:.2f}) = {
f_combo:.2f}")
print(f"λf(x1) + (1-λ)f(x2) = {
f_val_combo:.2f}")
print(f"凸性验证: {
f_combo <= f_val_combo}")
这段代码的输出会告诉你,对于二次函数,凸性条件确实成立。但在深度学习中,我们面对的函数复杂得多。
深度学习中的非凸陷阱
现实很骨感。深度神经网络的损失函数几乎都是非凸的,这意味着存在无数个局部最优点,就像一个布满山峰和谷底的崎岖地形。
我记得第一次训练ResNet时,损失函数在训练初期就卡在了一个很高的值上,无论怎么调整超参数都无法下降。后来我才明白,这是典型的陷入局部最优的表现。
让我们用代码模拟一个非凸函数的优化过程:
import numpy as np
import matplotlib.pyplot as plt
def non_convex_function(x):
"""一个典型的非凸函数,模拟神经网络损失函数的复杂性"""
return 0.1 * x**4 - 0.5 * x**3 + 0.3 * x**2 + 0.8 * x + np.sin(5*x) * 0.2
def gradient(x):
"""函数的梯度"""
return 0.4 * x**3 - 1.5 * x**2 + 0.6 * x + 0.8 + np.cos(5*x) * 1.0
# 梯度下降优化
def sgd_optimization(start_point, learning_rate, iterations):
x = start_point
trajectory = [x]
for i in range(iterations):
grad = gradient(x)
x = x - learning_rate * grad
trajectory.append(x)
# 打印关键信息
if i % 20 == 0:
print(f"Iteration {
i}: x={
x:.4f}, f(x)={
non_convex_function(x):.4f}")
return trajectory
# 从不同起点开始优化
trajectory1 = sgd_optimization(start_point=-2.0, learning_rate=0.01, iterations=100)
trajectory2 = sgd_optimization(start_point=1.5, learning_rate=0.01, iterations=100)
print(f"起点-2.0的最终结果: {
trajectory1[-1]:.4f}")
print(f"起点1.5的最终结果: {
trajectory2[-1]:.4f}")
运行这段代码,你会发现从不同起点开始,梯度下降会收敛到不同的局部最优点。这就是非凸优化的挑战所在。
SGD在非凸世界中的智慧
既然深度学习的损失函数是非凸的,那SGD(随机梯度下降)是如何工作的呢?答案可能会让你意外:正是因为SGD的"随机性",它反而能够逃离局部最优点。

让我们深入理解SGD的核心机制:
import numpy as np
from sklearn.datasets import make_regression
from sklearn.preprocessing import StandardScaler
class SimpleNeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 权重初始化
self.W1 = np.random.randn(input_size, hidden_size) * 0.1
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * 0.1
self.b2 = np.zeros((1, output_size))
def relu(self, x):
return np.maximum(0, x)
def relu_derivative(self, x):
return (x > 0).astype(float)
def forward(self, X):
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = self.relu(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
return self.z2
def compute_loss(self, y_true, y_pred):
return np.mean((y_true

最低0.47元/天 解锁文章
789

被折叠的 条评论
为什么被折叠?



