哈喽,我是小白~
今儿和大家聊一个非常重要的话题:数据插值法。
在机器学习实验中,数据插值方法可以有效处理缺失数据,帮助模型更好地利用现有信息。合理的插值方法可以保持数据分布的完整性,避免引入偏差或误差。数据插值还能提高模型的泛化能力,使其在面对不完整数据时表现更稳定。
涉及到最重要的十种方法有:
线性插值 多项式插值 样条插值 拉格朗日插值 牛顿插值 最近邻插值 分段常数插值 高斯过程回归插值 克里金插值 双线性/三线性插值
如果需要 本文PDF版本 的同学,文末获取~
另外,文末有总结性的干货~
一起来看下具体细化内容~
1. 线性插值
线性插值是最基本的一种插值方法,它假设两个已知数据点之间的关系是线性的,因此可以通过直线来估算未知点的值。在线性插值中,插值函数就是连接两个点的直线。
核心公式
假设我们有两个数据点 和 ,并且我们需要在这两个点之间找到 位置的值 。
这个公式表示,在 和 之间, 按照线性方式变化。
设 是通过两点的线性函数:
将已知点 和 代入:
联立方程解得 和 :
最终插值公式为:
这就是线性插值的推导过程。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
# 生成带有缺失值的虚拟数据集
np.random.seed(0)
x = np.linspace(0, 10, 20)
y = np.sin(x) + 0.5 * np.random.normal(size=len(x))
# 随机选择一部分数据置为空以模拟缺失
mask = np.random.choice([True, False], size=y.shape, p=[0.3, 0.7])
y_missing = y.copy()
y_missing[mask] = np.nan # 将随机部分置为NaN模拟缺失值
# 使用线性插值填补缺失值
x_valid = x[~mask] # 有效的x值(非NaN)
y_valid = y[~mask] # 有效的y值(非NaN)
# 生成插值函数
linear_interp_func = interp1d(x_valid, y_valid, kind='linear', fill_value="extrapolate")
y_interpolated = linear_interp_func(x) # 生成插值数据
# 计算插值误差(仅对原始非缺失位置)
error = np.abs(y - y_interpolated)
# 绘制图形
plt.figure(figsize=(12, 8))
# 图1:原始数据和插值数据对比
plt.subplot(3, 1, 1)
plt.plot(x, y, 'o-', color="royalblue", label='Original Data', alpha=0.6)
plt.plot(x, y_missing, 'o', color="tomato", label='Data with Missing Values', alpha=0.5)
plt.plot(x, y_interpolated, 'o--', color="forestgreen", label='Interpolated Data', alpha=0.7)
plt.title('Original Data, Missing Data, and Interpolated Data')
plt.legend()
# 图2:插值误差图
plt.subplot(3, 1, 2)
plt.plot(x, error, 's-', color="purple", markerfacecolor="yellow", label='Interpolation Error')
plt.title('Interpolation Error')
plt.xlabel('X')
plt.ylabel('Error')
plt.legend()
# 图3:插值数据的趋势
plt.subplot(3, 1, 3)
plt.plot(x, y_interpolated, 'o-', color="darkorange", label='Interpolated Data')
plt.title('Trend of Interpolated Data')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.tight_layout()
plt.show()
原始数据与插值数据对比图: 这张图通过三种颜色区分出原始数据、包含缺失值的数据以及插值后的数据,让我们一眼就能看到插值效果以及原始数据的变化情况。 插值误差图:插值的误差图帮助我们了解插值后数据与原始数据的偏差,尤其是缺失值较多时,观察插值是否引入了较大的误差。 插值数据的趋势图:趋势图展示了插值后数据的整体形态。可以观察到插值后数据的趋势与原数据是否一致,判断插值是否合理。
2. 多项式插值
多项式插值通过一个多项式来拟合给定的 个数据点。多项式的次数是 ,因此插值函数是一个 次多项式,能够通过所有的已知点。
核心公式
给定 个数据点 ,我们需要找到一个 多项式,使得:
多项式的形式为:
其中 是需要通过已知数据点来求解的系数。
设插值多项式为:
将每个已知点代入方程:
以此类推,构成一个包含 个方程的线性方程组:
解线性方程组得到系数 ,从而得到多项式 。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from numpy.polynomial import Polynomial
# 生成虚拟数据集
np.random.seed(42)
x = np.linspace(-5, 5, 10) # 原始数据的x坐标
y = np.sin(x) + np.random.normal(0, 0.1, len(x)) # 带噪声的y数据
# 生成高精度的插值点,用于绘制插值曲线
x_interp = np.linspace(-5, 5, 500) # 插值后高分辨率的x坐标
# 多项式插值
degree = 9 # 多项式的阶数
coefficients = np.polyfit(x, y, degree) # 拟合多项式的系数
poly = np.poly1d(coefficients) # 创建多项式函数
y_interp = poly(x_interp) # 插值后的y数据
# 计算误差:真实函数和插值函数之间的差异
true_function = np.sin(x_interp)
error = true_function - y_interp
# 创建图像
plt.figure(figsize=(12, 6))
# 图1:数据点和插值曲线
plt.subplot(1, 2, 1)
plt.scatter(x, y, color='red', label='Data points') # 原始数据点
plt.plot(x_interp, y_interp, color='blue', label=f'Polynomial (degree {degree})') # 插值曲线
plt.plot(x_interp, true_function, color='green', linestyle='--', label='True function') # 真实函数
plt.title('Polynomial Interpolation')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
# 图2:插值误差曲线
plt.subplot(1, 2, 2)
plt.plot(x_interp, error, color='purple', label='Error')
plt.axhline(0, color='black', linestyle='--') # y=0的参考线
plt.title('Interpolation Error')
plt.xlabel('x')
plt.ylabel('Error')
plt.legend()
plt.grid(True)
# 显示图像
plt.tight_layout()
plt.show()
虚拟数据集生成:通过 np.linspace
生成原始的x
数据,使用np.sin
函数作为目标函数,并加入一定的噪声,模拟真实数据。多项式插值:通过 np.polyfit
进行多项式插值,生成一个阶数为 9 的多项式,并通过np.poly1d
创建插值函数。高精度插值点:使用 x_interp
提高插值点的密度,以得到更平滑的插值曲线。误差分析:插值后的结果与真实函数值作对比,计算误差并绘制误差曲线。 可视化图形:创建了两个子图,分别展示了插值曲线和误差曲线,颜色使用鲜艳的红、蓝、绿和紫来区分数据点、插值结果、真实函数和误差。
图1(Polynomial Interpolation):展示了多项式拟合的效果,红色表示原始数据点,蓝色曲线是拟合的多项式,绿色虚线是真实的 sin
函数。通过对比可以看到拟合曲线在数据点附近的表现。图2(Interpolation Error):展示了多项式插值的误差。误差曲线帮助我们理解在不同区域中插值函数的准确性。
3. 样条插值
样条插值是一种通过低阶多项式(通常是三次多项式)构造的插值方法。每一段小区间内使用一个三次多项式,而在各个区间的连接处保证函数值和导数的连续性,从而实现平滑插值。
核心公式
假设给定 个数据点 ,三次样条函数在每个区间 上表示为:
在每个区间内,样条函数是三次的,具有四个未知系数 。
对每个区间 采用三次多项式拟合。
在节点 和 处,要求 和 。
保证各区间的连续性:
一阶导数连续: 二阶导数连续:
边界条件:常见的边界条件有:
自然边界条件: 和 第一类边界条件:给定边界点的导数值。
将这些条件代入,形成一个关于 的线性方程组,解得系数。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import CubicSpline
# 生成不规则的温度数据
np.random.seed(42) # 固定随机种子
time_points = np.sort(np.random.choice(np.linspace(0, 24, 50), 12, replace=False)) # 随机12个时间点,范围0到24小时
temperature_points = np.sin(time_points * np.pi / 12) * 10 + np.random.normal(0, 1, len(time_points)) # 温度数据模拟
# 使用样条插值进行数据补全
cs = CubicSpline(time_points, temperature_points) # 创建样条插值对象
# 生成插值点
time_fine = np.linspace(0, 24, 200)
temperature_fine = cs(time_fine)
temperature_derivative2 = cs(time_fine, 2) # 计算二阶导数
# 设置画布和子图布局
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), sharex=True)
# 图形1:原始数据点与样条插值曲线
ax1.plot(time_fine, temperature_fine, label="Cubic Spline Interpolation", color="blue", linewidth=2)
ax1.scatter(time_points, temperature_points, color="red", label="Original Data Points", s=50)
ax1.set_title("Temperature over Time with Cubic Spline Interpolation")
ax1.set_ylabel("Temperature (°C)")
ax1.legend()
ax1.grid(True)
# 图形2:样条插值后温度的二阶导数(加速度)
ax2.plot(time_fine, temperature_derivative2, label="Second Derivative (Acceleration)", color="green", linewidth=2)
ax2.set_title("Second Derivative of Temperature (Acceleration)")
ax2.set_xlabel("Time (hours)")
ax2.set_ylabel("Acceleration of Temperature Change")
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
生成数据:我们随机生成12个时间点的温度数据,使用时间点与正弦函数相乘并加上小的随机噪声,模拟不规则的温度数据。 样条插值:我们使用 CubicSpline
来生成样条插值对象,并在0到24小时的范围内对时间点进行插值。
插值图和二阶导数图:分别绘制了插值曲线和二阶导数曲线,这些图形帮助分析温度的平滑趋势和波动加速度。
4. 拉格朗日插值
拉格朗日插值使用拉格朗日基函数来构建插值多项式。每个基函数在其对应的节点处为 1,而在其他节点处为 0。通过加权这些基函数来构建插值多项式。
核心公式
插值多项式 表示为:
其中, 是第 个拉格朗日基函数:
该函数在 处为 1,其他点为 0。
通过构造基函数 ,确保每个基函数 在 处为 1,而在其他点 处为 0。 然后插值多项式 是基函数的加权和:
这保证了多项式在每个已知点处准确地插值。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import lagrange
# 生成虚拟数据集
# 原始数据点
x = np.array([0, 1, 2, 3, 4, 5, 6])
y = np.array([1, 2, 0, 5, 3, 4, 2])
# 创建拉格朗日插值多项式
poly = lagrange(x, y)
# 生成更密集的x,用于绘制平滑的插值曲线
x_dense = np.linspace(min(x), max(x), 500)
y_dense = poly(x_dense)
# 生成测试数据,模拟"真实值" (在这个案例中, 假设我们知道真实值)
y_true = np.sin(x_dense) + 2 # 真实值可以人为定义为一个复杂的函数
# 计算插值误差
error = y_true - y_dense
# 绘制插值曲线和误差图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 10), dpi=100)
# 1. 原始数据点与插值曲线图
ax1.plot(x_dense, y_dense, label='Lagrange Interpolation', color='blue', linewidth=2)
ax1.scatter(x, y, color='red', label='Original Data Points', zorder=5)
ax1.plot(x_dense, y_true, label='True Values (sin(x) + 2)', color='green', linestyle='--', linewidth=2)
ax1.set_title('Lagrange Interpolation and Original Data')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.legend()
ax1.grid(True)
# 2. 插值误差图
ax2.plot(x_dense, error, label='Interpolation Error', color='purple', linewidth=2)
ax2.axhline(0, color='black', linestyle='--', linewidth=1)
ax2.set_title('Interpolation Error (True Value - Interpolated Value)')
ax2.set_xlabel('x')
ax2.set_ylabel('Error')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
数据生成: x
和y
为原始数据点。x_dense
是在[0, 6]
的区间中生成的更多点,用于绘制平滑的插值曲线。y_true
为模拟的真实数据值(在这个例子中,我们使用了sin(x) + 2
,作为复杂函数的真实值)。拉格朗日插值:使用 scipy.interpolate.lagrange
函数生成拉格朗日插值的多项式,并在x_dense
上计算出插值后的值y_dense
。误差计算:计算插值值与真实值之间的误差 error = y_true - y_dense
,用于绘制误差图。
拉格朗日插值的曲线与原始数据点:蓝色的平滑曲线是插值结果,红色的点为原始数据点,绿色的虚线是真实值曲线。可以清晰地看到拉格朗日插值的曲线是如何拟合原始数据的。这对于插值方法的效果有直观的展示意义。 插值误差图:紫色曲线显示了插值误差,误差为插值结果与真实值之间的差值。可以帮助评估拉格朗日插值的准确性及在不同点上的表现。
5. 牛顿插值
牛顿插值利用差商来递归构造插值多项式,插值多项式的构造过程能够动态地增加点,并且无需重新计算整个多项式。牛顿插值的优点是能够在已有多项式的基础上添加新数据点。
核心公式
牛顿插值的多项式可以表示为:
其中, 是 和 之间的差商。
计算差商:
0阶差商: 1阶差商: 2阶差商: 以此类推,计算更高阶差商。
构造牛顿插值多项式。
Python实现
import numpy as np
import matplotlib.pyplot as plt
# 生成虚拟数据
np.random.seed(0)
x = np.linspace(0, 10, 10) # 原始数据x点
y = np.sin(x) + np.random.normal(0, 0.1, len(x)) # 添加一些噪声的原始y值
# 牛顿插值算法实现
def newton_interpolation(x, y, x_new):
n = len(x)
divided_diff = np.zeros((n, n))
divided_diff[:, 0] = y
for j in range(1, n):
for i in range(n - j):
divided_diff[i, j] = (divided_diff[i + 1, j - 1] - divided_diff[i, j - 1]) / (x[i + j] - x[i])
# 计算插值值
y_new = np.zeros_like(x_new)
for i in range(len(x_new)):
term = 1
y_new[i] = divided_diff[0, 0]
for j in range(1, n):
term *= (x_new[i] - x[j - 1])
y_new[i] += divided_diff[0, j] * term
return y_new
# 插值计算
x_new = np.linspace(0, 10, 100) # 新的x点
y_interp = newton_interpolation(x, y, x_new) # 牛顿插值获得的y值
# 真实的sin(x)曲线
y_true = np.sin(x_new)
# 误差计算
error = y_true - y_interp
# 绘图
plt.figure(figsize=(12, 8))
# 图1:原始数据点与插值曲线
plt.subplot(2, 1, 1)
plt.scatter(x, y, color='red', label='Original Data Points')
plt.plot(x_new, y_interp, color='blue', label='Newton Interpolation Curve')
plt.plot(x_new, y_true, color='green', linestyle='--', label='True sin(x)')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Newton Interpolation vs. True Function')
plt.legend()
plt.grid()
# 图2:误差分析图
plt.subplot(2, 1, 2)
plt.plot(x_new, error, color='purple', label='Interpolation Error')
plt.xlabel('x')
plt.ylabel('Error')
plt.title('Interpolation Error Analysis')
plt.legend()
plt.grid()
# 展示图形
plt.tight_layout()
plt.show()
原始数据点与插值曲线:在第一个子图中,我们绘制了红色的原始数据点散点图、蓝色的插值曲线以及绿色的真实曲线。这有助于观察牛顿插值的平滑度和数据拟合情况。
误差分析图:第二个子图展示了插值误差(即插值值与真实值的差异)。通过误差分析,我们可以识别插值的精确度,特别是在数据点较少的情况下,插值的误差可能较大。
6. 最近邻插值
最近邻插值是一种最简单的插值方法,它为每个插值点选择离其最近的已知点作为插值值。这种方法的优点是计算非常简单,但由于其对邻近数据的忽略,插值结果通常会有较大的误差,表现为不平滑。
核心公式
给定一个插值点 和已知的离散数据点 ,最近邻插值选择距离 最近的点 并赋予它 的值。公式为:
其中, 是距离插值点 最近的已知点, 是对应的函数值。
对于给定的插值点 ,计算其与所有已知点 的距离。 选择使得距离 最小的点 ,即:
然后返回对应的值 ,即:
这个方法没有插值公式的推导过程,因为它只是简单地选择最邻近的数据点。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
# 生成二维数据集
np.random.seed(0)
x = np.random.rand(100) * 10
y = np.random.rand(100) * 10
z = np.sin(x/2) * np.cos(y/2)
# 添加一些NaN值模拟缺失数据
mask = np.random.choice([True, False], size=z.shape, p=[0.1, 0.9])
z[mask] = np.nan
# 定义插值网格
xi = np.linspace(0, 10, 100)
yi = np.linspace(0, 10, 100)
xi, yi = np.meshgrid(xi, yi)
# 使用最近邻插值填补缺失值
zi_nn = griddata((x[~mask], y[~mask]), z[~mask], (xi, yi), method='nearest')
# 创建图形窗口
fig, axs = plt.subplots(1, 3, figsize=(18, 6))
# 1. 原始数据分布图(包含NaN)
sc1 = axs[0].scatter(x, y, c=z, cmap='jet', s=50)
axs[0].set_title('Original Data with NaN')
axs[0].set_xlabel('X-axis')
axs[0].set_ylabel('Y-axis')
fig.colorbar(sc1, ax=axs[0])
# 2. 最近邻插值后数据分布图
sc2 = axs[1].contourf(xi, yi, zi_nn, cmap='rainbow', levels=100)
axs[1].set_title('Data after Nearest Neighbor Interpolation')
axs[1].set_xlabel('X-axis')
axs[1].set_ylabel('Y-axis')
fig.colorbar(sc2, ax=axs[1])
# 3. 插值前后差异图
zi_original = griddata((x, y), z, (xi, yi), method='nearest', fill_value=np.nan)
diff = np.abs(zi_nn - zi_original)
sc3 = axs[2].contourf(xi, yi, diff, cmap='plasma', levels=100)
axs[2].set_title('Difference between Original and Interpolated Data')
axs[2].set_xlabel('X-axis')
axs[2].set_ylabel('Y-axis')
fig.colorbar(sc3, ax=axs[2])
# 调整图像布局
plt.tight_layout()
plt.show()
数据生成:使用 np.random
生成随机的二维坐标x
和y
,并用z
存储对应的函数值。我们使用np.sin
和np.cos
生成一些有规律的波动数据。缺失数据模拟:通过一个掩码 mask
随机将10%的数据标记为缺失(NaN
),模拟现实中不完整的数据集。插值处理:使用 scipy.interpolate.griddata
函数进行最近邻插值,将缺失数据进行填补。插值的结果保存在zi_nn
中。
第一幅图展示了原始数据,包含随机的 NaN
值。让我们看到原始数据中缺失值的位置,并可观察数据分布的特点。第二幅图展示了经过最近邻插值后的数据,清晰展示了插值后数据的平滑效果及如何填补空缺。 第三幅图直观地显示了插值前后的差异,突出插值后的变化区域,帮助我们理解插值对数据的影响。
7. 分段常数插值
分段常数插值也称为阶梯插值,它将每个区间内的值都定为区间的某个已知点的值。通常,这个已知点可以是左端点、右端点,或者区间内的某个点。插值结果会在各区间之间出现不连续性。
核心公式
如果已知一组数据点 ,对于插值点 位于区间 ,其插值值为:
这种方法直接在每个区间内使用常数值 来表示该区间的插值结果。
对于给定的插值点 ,找出所在的区间 ,即:
然后直接返回对应的值 :
这就是分段常数插值的方法,不需要复杂的推导过程。
Python实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
# 生成虚拟数据
np.random.seed(42)
time_stamps = np.sort(np.random.choice(range(0, 100), size=15, replace=False)) # 时间点
temperature_group_1 = np.random.uniform(20, 30, size=len(time_stamps)) # 区域1温度
temperature_group_2 = np.random.uniform(25, 35, size=len(time_stamps)) # 区域2温度
# 创建DataFrame
df = pd.DataFrame({
'Time': time_stamps,
'Temperature_Group_1': temperature_group_1,
'Temperature_Group_2': temperature_group_2
})
# 分段常数插值
time_full = np.arange(0, 100) # 定义插值后的完整时间范围
f_const_1 = interp1d(df['Time'], df['Temperature_Group_1'], kind='previous', fill_value="extrapolate")
f_const_2 = interp1d(df['Time'], df['Temperature_Group_2'], kind='previous', fill_value="extrapolate")
temperature_interpolated_1 = f_const_1(time_full)
temperature_interpolated_2 = f_const_2(time_full)
# 计算区域平均温度
mean_temp_1 = np.mean(temperature_interpolated_1)
mean_temp_2 = np.mean(temperature_interpolated_2)
# 绘图
plt.figure(figsize=(12, 8))
# 图1:原始数据点
plt.subplot(3, 1, 1)
plt.plot(df['Time'], df['Temperature_Group_1'], 'o-', label="Group 1 - Original", color='blue')
plt.plot(df['Time'], df['Temperature_Group_2'], 'o-', label="Group 2 - Original", color='green')
plt.xlabel("Time")
plt.ylabel("Temperature (°C)")
plt.title("Original Temperature Data Points")
plt.legend()
plt.grid(True)
# 图2:分段常数插值
plt.subplot(3, 1, 2)
plt.step(time_full, temperature_interpolated_1, where='post', label="Group 1 - Step Interpolated", color='blue')
plt.step(time_full, temperature_interpolated_2, where='post', label="Group 2 - Step Interpolated", color='green')
plt.xlabel("Time")
plt.ylabel("Temperature (°C)")
plt.title("Temperature Data with Stepwise Constant Interpolation")
plt.legend()
plt.grid(True)
# 图3:区域平均温度对比
plt.subplot(3, 1, 3)
plt.bar(['Group 1', 'Group 2'], [mean_temp_1, mean_temp_2], color=['blue', 'green'])
plt.xlabel("Group")
plt.ylabel("Average Temperature (°C)")
plt.title("Average Temperature Comparison After Interpolation")
plt.grid(True)
# 调整布局和显示
plt.tight_layout()
plt.show()
原始数据图:展示了区域1和区域2在原始记录时的温度数据。可以看到数据记录时刻是离散的,并不是连续的。 分段常数插值图:利用分段常数插值填补了温度数据的空缺部分,使得温度随时间变化呈阶梯状,符合分段常数插值的特点。这有助于在不连续的记录间补充数据,适用于需要保持数据原始特征的分析。 区域平均温度对比图:展示了插值后两个区域的平均温度对比,帮助我们分析两个区域的温度水平是否存在差异。
8. 高斯过程回归插值
高斯过程回归插值使用高斯过程(Gaussian Process, GP)模型进行插值,它假设数据之间存在某种形式的分布,通过协方差函数来描述数据点之间的相似性。该方法将数据点建模为多变量高斯分布,从而在未知点处预测数据的分布。
核心公式
高斯过程回归的预测公式为:
其中 $ \mu(x_) x_ \sigma^2(x_*) $ 是预测点的方差。
预测均值和方差由以下公式给出:
其中:
是已知数据点之间的协方差矩阵。 是预测点与已知数据点之间的协方差向量。 是已知数据点的观测值。
1. 定义高斯过程:假设数据是一个由高斯分布生成的过程,可以用均值和协方差函数来描述。给定输入 ,输出 满足高斯分布:
2. 推导预测分布:根据已知数据点和协方差函数,使用条件概率公式可以得到预测点 的条件分布:
3. 计算预测均值和方差:
预测均值 :
预测方差 :
Python实现
import numpy as np
import matplotlib.pyplot as plt
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C
# 1. 生成虚拟数据
np.random.seed(42)
X = np.sort(5 * np.random.rand(30, 1), axis=0) # 输入数据
y = np.sin(X).ravel() + np.random.normal(0, 0.2, X.shape[0]) # 真实值 + 噪声
# 测试点
X_test = np.linspace(0, 5, 1000).reshape(-1, 1)
# 2. 配置高斯过程回归模型
# 使用常数核和RBF核的乘积
kernel = C(1.0, (1e-4, 1e1)) * RBF(1.0, (1e-4, 1e1))
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=10, alpha=0.1)
# 3. 拟合数据
gp.fit(X, y)
# 4. 预测
y_pred, sigma = gp.predict(X_test, return_std=True)
# 5. 计算残差
y_train_pred, _ = gp.predict(X, return_std=True)
residuals = y - y_train_pred
# 6. 绘图
plt.figure(figsize=(12, 8))
# 第一张图:原始数据与预测插值曲线
plt.subplot(2, 1, 1)
plt.plot(X_test, y_pred, 'b-', label='Prediction (mean)', lw=2) # 预测均值曲线
plt.fill_between(X_test.ravel(), y_pred - 2*sigma, y_pred + 2*sigma, color='lightgray', alpha=0.5, label='Confidence interval (±2σ)') # 置信区间
plt.scatter(X, y, c='r', s=50, zorder=10, label='Observed data') # 真实数据点
plt.title('Gaussian Process Regression with Confidence Interval')
plt.xlabel('Input')
plt.ylabel('Output')
plt.legend(loc='upper left')
# 第二张图:残差分析
plt.subplot(2, 1, 2)
plt.scatter(X, residuals, c='purple', s=50, zorder=10)
plt.axhline(y=0, color='k', linestyle='--', lw=2)
plt.title('Residuals Analysis')
plt.xlabel('Input')
plt.ylabel('Residuals (True - Predicted)')
plt.tight_layout()
plt.show()
数据生成:我们使用 np.random.rand()
生成了一些随机的X
数据,并且用np.sin(X)
创建了非线性数据,同时加入了一些噪声。高斯过程模型的定义:我们定义了一个常数核 C
和 RBF 核的乘积来作为高斯过程的核函数,RBF 核可以很好地处理非线性数据。通过GaussianProcessRegressor
进行拟合和预测。预测结果:模型输出预测的均值 y_pred
和标准差sigma
,用来绘制插值曲线和置信区间。残差计算:通过计算模型在训练集上的预测值和真实值的差异,得到残差,用于残差分析。
原始数据与预测插值曲线:该图展示了模型的拟合效果,蓝色的预测曲线代表模型的插值结果,红点为原始数据。阴影部分表示模型的不确定性,显示置信区间,帮助理解模型预测的置信度。 残差分析:通过绘制残差图,可以看到模型在不同输入点的拟合误差情况。如果残差均匀分布,说明模型的拟合较好;如果残差呈现某种系统性的模式,则说明模型可能存在某些偏差或欠拟合问题。
9. 克里金插值
克里金插值是一种基于变异函数(或协方差函数)的方法,广泛应用于地质学、环境科学等领域。克里金插值假设数据是空间相关的,通过最大化加权平均的线性估计来推断未知点的值。
核心公式
克里金插值的预测公式为:
其中, 是克里金权重, 是已知点的观测值。
1. 构建变异函数:克里金插值假设数据之间的相关性可以通过变异函数(或协方差函数)描述,常用的协方差函数如:
其中, 是两个点之间的距离。 2. 设定加权平均:在已知点 上,构建一个加权和:
3. 求解权重:通过最小化误差的平方和,或者通过协方差函数最大化线性估计,来解得克里金权重 。
Python实现
import numpy as np
import matplotlib.pyplot as plt
from pykrige.ok import OrdinaryKriging
# 生成虚拟数据集
np.random.seed(42)
num_samples = 30 # 原始观测点的数量
x_observed = np.random.uniform(0, 100, num_samples)
y_observed = np.random.uniform(0, 100, num_samples)
z_observed = np.sin(x_observed / 10) * np.cos(y_observed / 10) + np.random.normal(0, 0.1, num_samples)
# 定义插值网格
grid_x = np.linspace(0, 100, 200)
grid_y = np.linspace(0, 100, 200)
grid_x, grid_y = np.meshgrid(grid_x, grid_y)
# 使用普通克里金插值 (Ordinary Kriging)
OK = OrdinaryKriging(
x_observed, y_observed, z_observed,
variogram_model="spherical",
verbose=False, enable_plotting=False
)
z_kriging, z_variance = OK.execute("grid", grid_x[0], grid_y[:, 0])
# 创建图形
fig, ax = plt.subplots(1, 3, figsize=(18, 6))
# 原始数据分布图
scatter = ax[0].scatter(x_observed, y_observed, c=z_observed, cmap="coolwarm", s=50, edgecolor='k')
ax[0].set_title("Observed Data Distribution")
ax[0].set_xlabel("X coordinate")
ax[0].set_ylabel("Y coordinate")
fig.colorbar(scatter, ax=ax[0], label="Observed Value")
# 克里金插值结果图
im = ax[1].imshow(z_kriging, extent=(0, 100, 0, 100), origin="lower", cmap="coolwarm")
ax[1].set_title("Kriging Interpolation Result")
ax[1].set_xlabel("X coordinate")
ax[1].set_ylabel("Y coordinate")
fig.colorbar(im, ax=ax[1], label="Interpolated Value")
# 插值误差(方差)分布图
im_var = ax[2].imshow(z_variance, extent=(0, 100, 0, 100), origin="lower", cmap="YlOrBr")
ax[2].set_title("Interpolation Variance")
ax[2].set_xlabel("X coordinate")
ax[2].set_ylabel("Y coordinate")
fig.colorbar(im_var, ax=ax[2], label="Variance")
# 显示所有图形
plt.tight_layout()
plt.show()
原始数据分布图:展示采样点的位置和浓度的数值大小。我们使用散点图,设置颜色映射来显示不同的浓度值,以便观察数据的原始分布。该图可以帮助我们直观地理解采样点的空间分布,了解哪些区域浓度较高或较低。
克里金插值结果图:展示插值后的浓度分布,使用平滑的插值值填充整个区域。该图可以帮助我们观察插值之后整个区域的浓度变化和趋势,展示了克里金插值对于未知区域浓度的预测。
插值误差(方差)分布图:展示每个插值点的预测方差(误差),颜色越深的区域表示预测的误差越大。该图可以帮助我们识别插值的不确定性,在采样较少的区域误差通常更高。这对于评估插值结果的可靠性非常有用。
10. 双线性/三线性插值
双线性插值和三线性插值是针对二维和三维数据进行插值的方法。双线性插值适用于二维格点数据,它在一个矩形区域内插值;三线性插值则适用于三维数据,用于插值一个立方体内的点。
核心公式
1. 双线性插值公式:对于二维数据,插值点 在矩形区域 内时,双线性插值的公式为:
其中, 和 是标准化的比例因子。
2. 三线性插值公式:对于三维数据,三线性插值公式类似于双线性插值,适用于一个立方体区域内的插值。公式为:
这里 是对应于 坐标的标准化比例因子。
Python实现
案例1:双线性插值
创建一个二维的网格数据(例如温度分布数据),通过在稀疏的网格点上生成数据,采用双线性插值填充中间的未知数据。通过分析数据插值前后的误差和插值效果,来说明其有效性。
案例2:三线性插值
接下来在三维网格(例如体积密度数据)中,生成稀疏的点并使用三线性插值填充三维空间中的数据。这种插值方式可用于体积数据处理,如流体模拟、CT扫描数据重建等。
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
# 创建虚拟数据
def generate_2d_data():
x = np.linspace(0, 4, 5)
y = np.linspace(0, 4, 5)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
return X, Y, Z
def generate_3d_data():
x = np.linspace(0, 4, 5)
y = np.linspace(0, 4, 5)
z = np.linspace(0, 4, 5)
X, Y, Z = np.meshgrid(x, y, z)
V = np.sin(X) * np.cos(Y) * np.sin(Z)
return X, Y, Z, V
# 进行双线性插值
def bilinear_interpolation(X, Y, Z):
xi = np.linspace(0, 4, 100)
yi = np.linspace(0, 4, 100)
XI, YI = np.meshgrid(xi, yi)
Zi = griddata((X.flatten(), Y.flatten()), Z.flatten(), (XI, YI), method='linear')
return XI, YI, Zi
# 进行三线性插值
def trilinear_interpolation(X, Y, Z, V):
xi = np.linspace(0, 4, 20)
yi = np.linspace(0, 4, 20)
zi = np.linspace(0, 4, 20)
XI, YI, ZI = np.meshgrid(xi, yi, zi)
Vi = griddata((X.flatten(), Y.flatten(), Z.flatten()), V.flatten(), (XI, YI, ZI), method='linear')
return XI, YI, ZI, Vi
# 绘制双线性插值结果
def plot_bilinear(X, Y, Z, XI, YI, Zi):
plt.figure(figsize=(10, 8))
plt.subplot(2, 2, 1)
plt.pcolormesh(X, Y, Z, shading='auto', cmap='jet')
plt.title("Original Data (2D)")
plt.colorbar()
plt.subplot(2, 2, 2)
plt.contourf(XI, YI, Zi, levels=15, cmap='jet')
plt.title("Bilinear Interpolated Data (2D)")
plt.colorbar()
# 绘制三线性插值结果
def plot_trilinear(X, Y, Z, V, XI, YI, ZI, Vi):
fig = plt.figure(figsize=(10, 8))
# 绘制原始数据切片
ax1 = fig.add_subplot(2, 2, 1, projection='3d')
slice_z = V[:, :, 2]
ax1.plot_surface(X[:, :, 2], Y[:, :, 2], slice_z, cmap='jet')
ax1.set_title('Original Data (Slice z=2)')
# 绘制插值后的切片
ax2 = fig.add_subplot(2, 2, 2, projection='3d')
slice_zi = Vi[:, :, 10]
ax2.plot_surface(XI[:, :, 10], YI[:, :, 10], slice_zi, cmap='jet')
ax2.set_title('Trilinear Interpolation (Slice z=10)')
# 绘制体积可视化
ax3 = fig.add_subplot(2, 2, 3, projection='3d')
ax3.scatter(X.flatten(), Y.flatten(), Z.flatten(), c=V.flatten(), cmap='jet')
ax3.set_title('Original 3D Data Points')
ax4 = fig.add_subplot(2, 2, 4, projection='3d')
ax4.scatter(XI.flatten(), YI.flatten(), ZI.flatten(), c=Vi.flatten(), cmap='jet')
ax4.set_title('Trilinear Interpolated 3D Data')
# 主程序
X2, Y2, Z2 = generate_2d_data()
XI2, YI2, Zi2 = bilinear_interpolation(X2, Y2, Z2)
plot_bilinear(X2, Y2, Z2, XI2, YI2, Zi2)
X3, Y3, Z3, V3 = generate_3d_data()
XI3, YI3, ZI3, Vi3 = trilinear_interpolation(X3, Y3, Z3, V3)
plot_trilinear(X3, Y3, Z3, V3, XI3, YI3, ZI3, Vi3)
plt.tight_layout()
plt.show()
双线性插值(2D)
数据生成:生成一个二维网格 (X, Y)
,对应的Z
值通过sin(X) * cos(Y)
函数生成虚拟数据。插值:使用 scipy.interpolate.griddata
进行双线性插值,把稀疏数据扩展到更密集的网格。
图1:绘制插值前的二维原始数据分布。 图2:插值后的二维数据,展示双线性插值的效果。
三线性插值(3D)
数据生成:三维网格 (X, Y, Z)
,其值V
通过三维函数sin(X) * cos(Y) * sin(Z)
生成。插值:使用 griddata
进行三线性插值,将三维稀疏点数据扩展为更密集的三维数据。
图1:显示原始三维数据在 z=2 切片下的情况。 图2:展示 z=10 切片下的插值数据效果。 图3:三维数据的散点图,展示原始数据点在三维空间的分布。 图4:展示插值后的三维数据散点图,显示插值后填充的三维数据分布。
最后