Hessian 在 C 语言中通常指的是 Hessian 矩阵,它是一个在数学和优化领域中非常重要的概念,在 C 语言中实现它,通常意味着你需要手动计算一个函数的二阶偏导数矩阵。

(图片来源网络,侵删)
下面我将从以下几个方面为你详细解释:
- 什么是 Hessian 矩阵? (数学概念)
- 为什么要在 C 语言中计算 Hessian? (应用场景)
- 如何在 C 语言中实现 Hessian 矩阵的计算? (核心方法,包括代码示例)
- 实现中的注意事项和挑战
什么是 Hessian 矩阵?
Hessian 矩阵是一个多元实值函数的二阶偏导数方阵,对于一个标量函数 f(x₁, x₂, ..., xₙ),其 Hessian 矩阵 H 是一个 n x n 的矩阵,其定义如下:
H(f) = [ ∂²f/∂x₁² ∂²f/∂x₁∂x₂ ... ∂²f/∂x₁∂xₙ ]
[ ∂²f/∂x₂∂x₁ ∂²f/∂x₂² ... ∂²f/∂x₂∂xₙ ]
[ ... ... ... ... ]
[ ∂²f/∂xₙ∂x₁ ∂²f/∂xₙ∂x₂ ... ∂²f/∂xₙ² ]
- Hessian 矩阵描述了函数
f在某一点的局部曲率。 - 它是函数梯度(一阶导数)的导数。
- 在优化算法中,它用于判断一个点是极小值点、极大值点还是鞍点。
判断法则:
假设在某点 x*,梯度为零(即 ∇f(x*) = 0):

(图片来源网络,侵删)
- Hessian 矩阵
H(x*)是正定的,则x*是一个局部极小值点。 - Hessian 矩阵
H(x*)是负定的,则x*是一个局部极大值点。 - Hessian 矩阵
H(x*)有正有负(不定),则x*是一个鞍点。
为什么要在 C 语言中计算 Hessian?
在 C 语言这种底层语言中手动计算 Hessian,通常出现在以下场景:
- 科学计算与工程仿真: 在物理、力学、金融等领域,需要求解复杂的非线性方程或进行优化,很多高级算法(如牛顿法)需要 Hessian 矩阵作为输入。
- 机器学习/深度学习框架: 虽然高层框架(如 Python 的 TensorFlow, PyTorch)会自动求导,但一些底层的、高性能的 C++/C 实现可能需要手动推导或近似计算 Hessian,用于模型训练或分析。
- 嵌入式系统: 在资源受限的嵌入式设备上,如果需要进行特定的优化计算,可能会用 C 语言来实现,因为它没有运行时开销。
- 学术研究或教学: 为了理解算法的内部工作原理,手动实现 Hessian 计算是一个很好的练习。
如何在 C 语言中实现 Hessian 矩阵的计算?
计算 Hessian 主要有两种方法:
- 符号求导: 手动推导出函数的二阶偏导数公式,然后用 C 语言代码实现这个公式,这种方法精确,但对于复杂的函数来说,推导过程非常繁琐且容易出错。
- 数值求导: 使用数值方法近似计算二阶偏导数,这种方法实现简单,通用性强,但存在精度误差。
下面我们重点介绍这两种方法,并提供 C 语言代码示例。
示例函数
我们以一个简单的二维函数为例:
f(x, y) = x² + xy + y² + 3x + 2y + 1

(图片来源网络,侵删)
手动推导其 Hessian 矩阵:
- ∂f/∂x = 2x + y + 3
- ∂f/∂y = x + 2y + 2
- ∂²f/∂x² = 2
- ∂²f/∂y² = 2
- ∂²f/∂x∂y = 1
- ∂²f/∂y∂x = 1
这个函数的 Hessian 矩阵是恒定的:
H = [ 2 1 ] [ 1 2 ]
符号求导
这种方法直接将上面推导出的公式写入代码。
#include <stdio.h>
// 定义一个函数,用于计算给定点 (x, y) 处的 Hessian 矩阵
// 由于我们的示例函数 Hessian 是常数,所以输入参数 x, y 实际上不影响结果
void calculate_hessian_symbolic(double x, double y, double hessian[2][2]) {
// H[0][0] = d²f/dx²
hessian[0][0] = 2.0;
// H[0][1] = d²f/dxdy
hessian[0][1] = 1.0;
// H[1][0] = d²f/dydx
hessian[1][0] = 1.0;
// H[1][1] = d²f/dy²
hessian[1][1] = 2.0;
}
int main() {
double point_x = 1.0;
double point_y = 2.0;
double hessian_matrix[2][2];
calculate_hessian_symbolic(point_x, point_y, hessian_matrix);
printf("Hessian Matrix at (%.2f, %.2f):\n", point_x, point_y);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
printf("%.2f\t", hessian_matrix[i][j]);
}
printf("\n");
}
return 0;
}
优点:
- 精度高:没有截断误差,结果是精确的(在浮点数精度范围内)。
- 计算速度快:直接是几个算术运算。
缺点:
- 推导困难:对于复杂的函数,手动求导极其困难。
- 代码不通用:函数一变,整个推导和代码都要重写。
数值求导
数值求导的核心思想是用差分来近似导数,计算二阶偏导数通常需要两次差分。
最常用的中心差分公式(精度较高)为:
- 一阶导数近似:
f'(x) ≈ (f(x+h) - f(x-h)) / (2h) - 二阶导数近似:
f''(x) ≈ (f(x+h) - 2f(x) + f(x-h)) / h² - 二阶混合偏导数近似:
∂²f/∂x∂y ≈ (f(x+h, y+k) - f(x+h, y-k) - f(x-h, y+k) + f(x-h, y-k)) / (4hk)
h 和 k 是一个非常小的步长,1e-5。
#include <stdio.h>
#include <math.h> // 为了使用 fabs 函数检查是否为零
// 定义我们要求导的函数 f(x, y)
double f(double x, double y) {
return x*x + x*y + y*y + 3*x + 2*y + 1;
}
// 使用数值方法计算 Hessian 矩阵
void calculate_hessian_numeric(double x, double y, double hessian[2][2], double h) {
// 计算 d²f/dx²
double f_xph = f(x + h, y);
double f_xmh = f(x - h, y);
double f_xy = f(x, y);
hessian[0][0] = (f_xph - 2 * f_xy + f_xmh) / (h * h);
// 计算 d²f/dy²
double f_yph = f(x, y + h);
double f_ymh = f(x, y - h);
hessian[1][1] = (f_yph - 2 * f_xy + f_ymh) / (h * h);
// 计算 d²f/dxdy (等于 d²f/dydx)
double f_xph_yph = f(x + h, y + h);
double f_xph_ymh = f(x + h, y - h);
double f_xmh_yph = f(x - h, y + h);
double f_xmh_ymh = f(x - h, y - h);
hessian[0][1] = (f_xph_yph - f_xph_ymh - f_xmh_yph + f_xmh_ymh) / (4 * h * h);
hessian[1][0] = hessian[0][1]; // 对于光滑函数,混合偏导数相等
}
int main() {
double point_x = 1.0;
double point_y = 2.0;
double hessian_matrix[2][2];
double step_size = 1e-5; // 步长,需要仔细选择
calculate_hessian_numeric(point_x, point_y, hessian_matrix, step_size);
printf("Hessian Matrix at (%.2f, %.2f) using numeric method:\n", point_x, point_y);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
printf("%.6f\t", hessian_matrix[i][j]);
}
printf("\n");
}
return 0;
}
优点:
- 通用性强:只需要一个函数
f(x, y)的实现,无需任何数学推导。 - 实现简单:代码逻辑清晰,易于编写。
缺点:
- 存在精度误差:结果依赖于步长
h的选择。h太大,截断误差大;h太小,舍入误差大,通常需要实验来选择一个合适的h。 - 计算成本高:计算一个
n x n的 Hessian 矩阵,需要调用O(n²)次f函数,对于高维问题,计算量会非常大。
实现中的注意事项和挑战
- 步长
h的选择:这是数值求导中最关键也最棘手的问题,一个经验法则是h ≈ sqrt(ε), 是机器的浮点精度(对于double类型,通常在1e-15左右,h可以在1e-5到1e-8之间尝试)。 - 函数的计算成本:
f(x, y)本身计算就非常耗时(涉及复杂的模拟或深度学习模型的前向传播),那么数值求导 Hessian 的成本会变得非常高,这时可能会考虑更高效的近似方法,如 BFGS 算法(它构建 Hessian 的近似矩阵,而不直接计算)。 - 函数的光滑性:数值求导要求数函数是足够光滑(二阶可导)的,如果函数在某些点不连续或不可导,数值求导的结果会完全错误。
- 高维问题:当变量
n很大时(n > 100),存储和计算一个n x n的 Hessian 矩阵会变得非常消耗内存和计算资源,在这种情况下,通常会采用稀疏矩阵存储或者使用不需要显式构造 Hessian 的优化算法(如共轭梯度法)。
| 特性 | 符号求导 | 数值求导 |
|---|---|---|
| 原理 | 手动推导数学公式,用代码实现 | 用差分公式近似计算导数值 |
| 精度 | 高 (机器精度) | 较低 (受步长 h 影响) |
| 速度 | 快 (直接算术运算) | 慢 (需要多次调用原函数) |
| 实现难度 | 高 (推导复杂易错) | 低 (代码模板化,通用) |
| 适用性 | 函数简单、形式固定 | 函数复杂、形式未知、通用性强 |
在 C 语言中实现 Hessian 矩阵,数值求导是更常用和实用的方法,因为它提供了很好的灵活性和可维护性,除非你的函数极其简单且性能是绝对瓶颈,否则推荐使用数值方法。
