别再死记硬背了!用Python和NumPy可视化理解多元函数可微性(附代码)
用Python和NumPy可视化理解多元函数可微性
数学概念往往抽象难懂,尤其是多元函数的可微性,光靠公式推导很容易让人一头雾水。我在学习高等数学时就曾被这个概念困扰许久——为什么偏导数存在却不一定可微?切平面和全微分又有什么关系?直到我开始用Python将这些抽象概念可视化,一切才豁然开朗。
本文将带你用NumPy和Matplotlib,通过代码实现和3D可视化,直观理解多元函数可微性的本质。我们会从基础曲面绘制开始,逐步实现偏导数计算、切平面可视化,最后构造经典反例,让你亲眼看到"偏导数存在但函数不可微"的数学现象。
1. 环境准备与基础概念
在开始之前,确保你的Python环境已安装以下库:
import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from matplotlib import cm多元函数可微性的核心在于局部线性近似。对于二元函数z=f(x,y),如果在某点(x₀,y₀)可微,意味着在该点附近可以用一个平面很好地近似函数曲面。这个平面就是切平面,其方程为:
z = f(x₀,y₀) + f_x(x₀,y₀)(x-x₀) + f_y(x₀,y₀)(y-y₀)其中f_x和f_y分别是f对x和y的偏导数。可微性的关键判别条件是:
- 必要条件:偏导数f_x和f_y存在
- 充分条件:偏导数f_x和f_y在(x₀,y₀)点连续
注意:偏导数存在是可微的必要条件而非充分条件,这与一元函数的情况不同。
2. 绘制基础曲面与偏导数计算
让我们从一个简单的可微函数开始:z = x² + y²。首先定义函数和计算偏导数的函数:
def f(x, y): return x**2 + y**2 def partial_x(f, x, y, h=1e-5): return (f(x + h, y) - f(x - h, y)) / (2 * h) def partial_y(f, x, y, h=1e-5): return (f(x, y + h) - f(x, y - h)) / (2 * h)可视化这个抛物面:
x = np.linspace(-2, 2, 100) y = np.linspace(-2, 2, 100) X, Y = np.meshgrid(x, y) Z = f(X, Y) fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z, cmap=cm.viridis, alpha=0.8) plt.title("抛物面 z = x² + y²") plt.show()计算在点(1,1)处的偏导数:
x0, y0 = 1, 1 fx = partial_x(f, x0, y0) # 约为2 fy = partial_y(f, x0, y0) # 约为23. 可视化切平面与法线
根据前面计算的偏导数,我们可以构造切平面方程:
def tangent_plane(x, y, x0, y0, f, fx, fy): return f(x0, y0) + fx*(x - x0) + fy*(y - y0) Z_tangent = tangent_plane(X, Y, x0, y0, f, fx, fy)将切平面和原曲面一起绘制:
fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z, cmap=cm.viridis, alpha=0.5) ax.plot_surface(X, Y, Z_tangent, cmap=cm.plasma, alpha=0.5) ax.scatter([x0], [y0], [f(x0, y0)], color='r', s=100) plt.title("曲面与切平面") plt.show()法线向量可以通过切平面的法向量得到,即(f_x, f_y, -1)。我们可以添加法线可视化:
# 法线向量 normal_vec = np.array([fx, fy, -1]) normal_vec = normal_vec / np.linalg.norm(normal_vec) * 0.5 # 归一化并缩放 ax.quiver(x0, y0, f(x0, y0), normal_vec[0], normal_vec[1], normal_vec[2], color='r', arrow_length_ratio=0.1)4. 构造不可微函数的经典反例
最著名的例子是函数f(x,y) = xy/(x²+y²)(在(0,0)点补充定义为0)。这个函数在原点处偏导数存在但不可微。
定义函数:
def f_nd(x, y): """不可微的经典例子""" with np.errstate(divide='ignore', invalid='ignore'): z = np.where(x**2 + y**2 != 0, x*y/(x**2 + y**2), 0) return z可视化这个函数:
x = np.linspace(-0.5, 0.5, 100) y = np.linspace(-0.5, 0.5, 100) X, Y = np.meshgrid(x, y) Z_nd = f_nd(X, Y) fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z_nd, cmap=cm.coolwarm, alpha=0.8) plt.title("不可微函数示例 z = xy/(x²+y²)") plt.show()计算在(0,0)处的偏导数:
fx_nd = partial_x(f_nd, 0, 0) # 结果为0 fy_nd = partial_y(f_nd, 0, 0) # 结果为0尝试构造切平面:
Z_tangent_nd = tangent_plane(X, Y, 0, 0, f_nd, fx_nd, fy_nd) # 就是z=0平面将切平面和原曲面一起绘制:
fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z_nd, cmap=cm.coolwarm, alpha=0.5) ax.plot_surface(X, Y, Z_tangent_nd, cmap=cm.Greys, alpha=0.5) ax.scatter([0], [0], [0], color='r', s=100) plt.title("不可微函数与其'切平面'") plt.show()从可视化中可以明显看出,虽然偏导数存在且切平面(这里是z=0平面)也存在,但这个平面并不能很好地近似函数在原点的行为——沿着不同方向逼近原点时,函数值趋近于不同的值。
5. 可微性的几何意义与验证
为了更深入理解可微性,我们可以从几何角度验证几个关键点:
切平面的逼近精度:对于可微函数,当(x,y)接近(x₀,y₀)时,函数值与切平面值的差应该是距离的高阶无穷小:
f(x,y) - [f(x₀,y₀) + f_x(x₀,y₀)(x-x₀) + f_y(x₀,y₀)(y-y₀)] = o(√[(x-x₀)² + (y-y₀)²])方向导数的一致性:可微性保证了所有方向导数都存在且可以通过偏导数计算:
D_vf = f_x v_x + f_y v_y
让我们用代码验证第一个抛物面例子中的逼近误差:
# 计算逼近误差 error = np.abs(Z - Z_tangent) distance = np.sqrt((X - x0)**2 + (Y - y0)**2) relative_error = np.where(distance > 1e-10, error / distance, 0) # 绘制相对误差 fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, relative_error, cmap=cm.hot) plt.title("切平面逼近的相对误差") plt.show()而对于不可微的例子,相对误差在某些方向上不会趋近于0:
error_nd = np.abs(Z_nd - Z_tangent_nd) distance_nd = np.sqrt(X**2 + Y**2) relative_error_nd = np.where(distance_nd > 1e-10, error_nd / distance_nd, 0) fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, relative_error_nd, cmap=cm.hot) plt.title("不可微函数的相对误差") plt.show()6. 梯度下降法的直观演示
可微性在优化算法中至关重要。让我们用梯度下降法演示可微函数和不可微函数的区别:
def gradient_descent(f, x0, y0, learning_rate=0.1, steps=20): path = [(x0, y0)] x, y = x0, y0 for _ in range(steps): grad_x = partial_x(f, x, y) grad_y = partial_y(f, x, y) x -= learning_rate * grad_x y -= learning_rate * grad_y path.append((x, y)) return np.array(path)对可微函数z = x² + y²运行梯度下降:
path = gradient_descent(f, 1.5, 1.5) fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z, cmap=cm.viridis, alpha=0.5) ax.plot(path[:,0], path[:,1], f(path[:,0], path[:,1]), 'r-o') plt.title("可微函数的梯度下降路径") plt.show()尝试对不可微函数运行梯度下降:
path_nd = gradient_descent(f_nd, 0.4, 0.3) fig = plt.figure(figsize=(12, 6)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z_nd, cmap=cm.coolwarm, alpha=0.5) ax.plot(path_nd[:,0], path_nd[:,1], f_nd(path_nd[:,0], path_nd[:,1]), 'r-o') plt.title("不可微函数的梯度下降路径") plt.show()从可视化结果可以明显看出,对于可微函数,梯度下降能稳定收敛到最小值点;而对于不可微函数,梯度下降的行为则不稳定且难以预测。
