数学公式堆砌的教科书总让人望而生畏?本文带你用Python的Matplotlib和NumPy,通过三维动态可视化直观理解多元函数微分学的核心概念。我们将从代码实现出发,逐步拆解"偏导数存在但函数不可微"的反例、全微分的几何意义,以及方向导数与梯度的关系。
在开始前,我们需要配置好Jupyter Notebook环境并导入必要的库。建议使用Anaconda发行版,它已经集成了我们所需的大部分工具。
python复制import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
%matplotlib widget # 启用交互式3D绘图
提示:如果使用Jupyter Lab而非Notebook,可能需要额外安装
ipympl扩展:pip install ipympl
为了确保可视化效果,我们创建一个辅助函数来美化3D图形:
python复制def setup_3d_plot(ax, title, elev=30, azim=-45):
ax.set_title(title, pad=20)
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_zlabel('Z轴')
ax.view_init(elev=elev, azim=azim)
ax.grid(True)
return ax
教科书告诉我们:对于多元函数,即使所有偏导数都存在,函数也不一定可微。这句话听起来抽象,让我们用Python构造一个经典反例——Peano函数:
python复制def peano_function(x, y):
"""Peano函数:偏导数存在但不可微的经典例子"""
return np.where(x**2 + y**2 != 0,
(x*y)/(x**2 + y**2),
0)
# 生成网格数据
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
X, Y = np.meshgrid(x, y)
Z = peano_function(X, Y)
# 绘制3D曲面
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
setup_3d_plot(ax, "Peano函数:偏导数存在但不可微")
plt.colorbar(surf)
plt.show()
运行这段代码,你会看到一个在原点附近剧烈震荡的曲面。为什么这个函数在(0,0)点不可微?让我们从三个角度分析:
偏导数计算:
可微性验证:
几何直观:
理解了反例,我们来看可微函数的典型代表——双曲抛物面z = x² - y²。我们将绘制曲面及其在(1,1)点的切平面:
python复制def hyperbolic_paraboloid(x, y):
return x**2 - y**2
# 计算切平面方程
def tangent_plane(x, y, x0, y0):
z0 = hyperbolic_paraboloid(x0, y0)
fx = 2*x0 # ∂z/∂x at (x0,y0)
fy = -2*y0 # ∂z/∂y at (x0,y0)
return z0 + fx*(x-x0) + fy*(y-y0)
# 生成数据
x = np.linspace(-2, 2, 50)
y = np.linspace(-2, 2, 50)
X, Y = np.meshgrid(x, y)
Z_surf = hyperbolic_paraboloid(X, Y)
Z_tangent = tangent_plane(X, Y, 1, 1)
# 绘制图形
fig = plt.figure(figsize=(14, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z_surf, alpha=0.7, cmap=cm.viridis)
ax.plot_surface(X, Y, Z_tangent, alpha=0.5, color='orange')
ax.scatter([1], [1], [hyperbolic_paraboloid(1,1)], color='red', s=100)
setup_3d_plot(ax, "双曲抛物面及其在(1,1)点的切平面", elev=25, azim=-120)
plt.show()
全微分的几何意义在这张图中清晰呈现:
python复制# 计算逼近误差
error = np.abs(Z_surf - Z_tangent)
# 绘制误差热力图
plt.figure(figsize=(10, 8))
plt.contourf(X, Y, error, levels=20, cmap=cm.hot)
plt.colorbar(label='绝对误差')
plt.scatter([1], [1], color='white', s=100)
plt.title("切平面逼近误差分布")
plt.xlabel("x")
plt.ylabel("y")
plt.grid(True)
plt.show()
理解方向导数和梯度的关系是多元微积分的核心。我们将创建一个交互式可视化,展示函数z = sin(x) + cos(y)在不同方向上的变化率。
首先定义计算方向导数和梯度的函数:
python复制def func(x, y):
return np.sin(x) + np.cos(y)
def gradient(x, y):
"""计算函数的梯度"""
return np.array([np.cos(x), -np.sin(y)])
def directional_derivative(x, y, direction):
"""计算给定方向的方向导数"""
grad = gradient(x, y)
unit_dir = direction / np.linalg.norm(direction)
return np.dot(grad, unit_dir)
接着创建一个动态可视化,展示梯度与方向导数的关系:
python复制from matplotlib.animation import FuncAnimation
# 设置绘图区域
fig = plt.figure(figsize=(14, 8))
ax = fig.add_subplot(111, projection='3d')
# 生成基础曲面
x = np.linspace(-3, 3, 50)
y = np.linspace(-3, 3, 50)
X, Y = np.meshgrid(x, y)
Z = func(X, Y)
ax.plot_surface(X, Y, Z, alpha=0.7, cmap=cm.coolwarm)
# 固定一个参考点
point = np.array([1.5, 1.0])
z_point = func(*point)
# 初始化箭头和文本
quiver = ax.quiver([], [], [], [], [], [], color='red', length=0.2)
text = ax.text(0, 0, 0, '', fontsize=12)
def update(frame):
# 计算当前方向
angle = frame * np.pi / 180
direction = np.array([np.cos(angle), np.sin(angle)])
# 计算方向导数和梯度
dir_deriv = directional_derivative(*point, direction)
grad = gradient(*point)
# 更新箭头
ax.collections.remove(quiver)
quiver = ax.quiver(*point, z_point,
*direction, dir_deriv,
color='red', length=0.5)
# 更新梯度箭头
ax.quiver(*point, z_point,
*grad, 0,
color='green', length=0.5)
# 更新文本
ax.texts.remove(text)
text = ax.text(-2, -2, 3,
f'方向角度: {frame}°\n'
f'方向导数: {dir_deriv:.3f}\n'
f'梯度: ({grad[0]:.3f}, {grad[1]:.3f})',
fontsize=12)
return quiver, text
# 创建动画
ani = FuncAnimation(fig, update, frames=np.arange(0, 360, 5),
interval=100, blit=False)
setup_3d_plot(ax, "方向导数与梯度关系动态演示", elev=30, azim=45)
plt.tight_layout()
plt.show()
这个动画展示了几个关键点:
让我们将这些技术应用于一个更复杂的函数:f(x,y) = x³y - xy³。我们将全面分析其在(1,1)点的微分性质。
python复制def complex_func(x, y):
return x**3 * y - x * y**3
# 计算偏导数和梯度
def complex_gradient(x, y):
df_dx = 3 * x**2 * y - y**3
df_dy = x**3 - 3 * x * y**2
return np.array([df_dx, df_dy])
# 计算Hessian矩阵
def complex_hessian(x, y):
d2f_dx2 = 6 * x * y
d2f_dxdy = 3 * x**2 - 3 * y**2
d2f_dydx = d2f_dxdy
d2f_dy2 = -6 * x * y
return np.array([[d2f_dx2, d2f_dxdy],
[d2f_dydx, d2f_dy2]])
# 在(1,1)点分析
point = np.array([1.0, 1.0])
grad = complex_gradient(*point)
hess = complex_hessian(*point)
print(f"在点{point}处:")
print(f"梯度向量:{grad}")
print(f"Hessian矩阵:\n{hess}")
# 可视化函数行为
x = np.linspace(0.5, 1.5, 50)
y = np.linspace(0.5, 1.5, 50)
X, Y = np.meshgrid(x, y)
Z = complex_func(X, Y)
fig = plt.figure(figsize=(16, 6))
# 3D曲面图
ax1 = fig.add_subplot(121, projection='3d')
ax1.plot_surface(X, Y, Z, cmap=cm.viridis)
setup_3d_plot(ax1, "f(x,y) = x³y - xy³ 的曲面图")
# 等高线图
ax2 = fig.add_subplot(122)
contour = ax2.contour(X, Y, Z, levels=20, cmap=cm.viridis)
ax2.scatter(*point, color='red', s=100)
ax2.quiver(*point, *grad, color='red', scale=20)
plt.colorbar(contour)
ax2.set_title("等高线图与梯度向量")
ax2.set_xlabel("x")
ax2.set_ylabel("y")
ax2.grid(True)
plt.tight_layout()
plt.show()
通过这个案例,我们可以:
在实际应用中,有几个常见问题需要注意:
数值稳定性问题:
python复制def safe_peano(x, y, eps=1e-10):
return (x*y)/(x**2 + y**2 + eps)
提高可视化清晰度:
view_init参数)性能优化技巧:
rstride和cstride参数降低绘制密度python复制ax.plot_surface(X, Y, Z, rstride=2, cstride=2, alpha=0.7)
交互式探索工具:
%matplotlib widget启用交互模式interact函数创建动态调节参数python复制from ipywidgets import interact
@interact(x0=(-2.0, 2.0), y0=(-2.0, 2.0))
def explore_tangent_plane(x0=1.0, y0=1.0):
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z_surf, alpha=0.7)
Z_tangent = tangent_plane(X, Y, x0, y0)
ax.plot_surface(X, Y, Z_tangent, alpha=0.5, color='orange')
ax.scatter([x0], [y0], [func(x0,y0)], color='red', s=100)
setup_3d_plot(ax, f"在({x0},{y0})点的切平面")
plt.show()
保存和分享结果:
python复制plt.savefig('gradient_visualization.png', dpi=300, bbox_inches='tight')
python复制import plotly.graph_objects as go
fig = go.Figure(data=[go.Surface(z=Z, x=X, y=Y)])
fig.write_html("interactive_surface.html")