1. 为什么我们需要拟合算法
在数据分析的世界里,拟合就像一位经验丰富的侦探,能够从杂乱无章的线索中找出隐藏的规律。作为一名长期使用Python进行数据分析的从业者,我深刻体会到拟合技术在各个领域的价值。
线性拟合和非线性拟合是数据分析中最基础也最强大的工具之一。它们能帮助我们:
- 揭示变量间的数学关系
- 预测未来趋势
- 验证理论模型
- 优化系统参数
在工程领域,我们用它来校准传感器;在金融领域,用它预测股价走势;在生物医学领域,用它分析药物剂量反应曲线。可以说,掌握了拟合技术,就拥有了从数据中提取价值的钥匙。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Python中的拟合工具库全景
Python生态系统为我们提供了丰富的拟合工具,每种工具都有其独特的优势和应用场景。让我们来看看最常用的几个库:
2.1 NumPy和SciPy:科学计算的基础
python复制import numpy as np
from scipy.optimize import curve_fit
# 定义拟合函数
def linear_func(x, a, b):
return a * x + b
# 生成模拟数据
x_data = np.linspace(0, 10, 50)
y_data = 2.5 * x_data + 1.3 + np.random.normal(0, 1, 50)
# 执行拟合
params, covariance = curve_fit(linear_func, x_data, y_data)
SciPy的curve_fit函数是非线性拟合的瑞士军刀,它使用最小二乘法来估计参数,对于大多数常见问题都能提供良好的解决方案。
2.2 Statsmodels:统计建模的专业选择
python复制import statsmodels.api as sm
# 添加常数项
X = sm.add_constant(x_data)
# 创建并拟合模型
model = sm.OLS(y_data, X)
results = model.fit()
# 输出详细统计信息
print(results.summary())
Statsmodels提供了更丰富的统计输出,包括R平方、p值、置信区间等,特别适合需要严格统计验证的场景。
2.3 Scikit-learn:机器学习风格的接口
python复制from sklearn.linear_model import LinearRegression
# 重塑数据形状
X = x_data.reshape(-1, 1)
# 创建并拟合模型
model = LinearRegression()
model.fit(X, y_data)
# 获取参数
print(f"斜率: {model.coef_[0]}, 截距: {model.intercept_}")
Scikit-learn的统一API设计让它在机器学习工作流中集成更加方便,特别是当拟合只是整个分析流程的一部分时。
3. 线性拟合实战:从基础到进阶
3.1 简单线性回归的实现
让我们从一个完整的例子开始,展示如何使用Python进行线性拟合:
python复制import matplotlib.pyplot as plt
import numpy as np
from scipy import stats
# 生成带有噪声的线性数据
np.random.seed(42)
x = np.linspace(0, 10, 100)
y = 2.5 * x + 1.0 + np.random.normal(0, 1.5, 100)
# 使用scipy进行线性回归
slope, intercept, r_value, p_value, std_err = stats.linregress(x, y)
# 绘制结果
plt.figure(figsize=(10, 6))
plt.scatter(x, y, label='原始数据', alpha=0.6)
plt.plot(x, slope*x + intercept, 'r', label=f'拟合直线: y={slope:.2f}x+{intercept:.2f}')
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.title('线性拟合示例')
plt.legend()
plt.grid(True)
plt.show()
# 输出统计信息
print(f"R平方值: {r_value**2:.4f}")
print(f"斜率标准误差: {std_err:.4f}")
print(f"p值: {p_value:.4e}")
这个例子展示了完整的线性拟合流程,包括数据生成、模型拟合、可视化展示和统计量输出。
3.2 多元线性回归的挑战
当我们需要考虑多个自变量时,问题就变得复杂起来:
python复制import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# 创建模拟数据
data = {
'X1': np.random.rand(100) * 10,
'X2': np.random.rand(100) * 5,
'Y': 3 + 2.5 * np.random.rand(100) * 10 + 1.8 * np.random.rand(100) * 5 + np.random.normal(0, 2, 100)
}
df = pd.DataFrame(data)
# 准备数据
X = df[['X1', 'X2']]
y = df['Y']
# 分割训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 训练模型
model = LinearRegression()
model.fit(X_train, y_train)
# 评估模型
train_score = model.score(X_train, y_train)
test_score = model.score(X_test, y_test)
print(f"训练集R²: {train_score:.4f}")
print(f"测试集R²: {test_score:.4f}")
print(f"系数: {model.coef_}")
print(f"截距: {model.intercept_}")
多元线性回归中,我们需要特别注意多重共线性问题。可以使用方差膨胀因子(VIF)来检测:
python复制from statsmodels.stats.outliers_influence import variance_inflation_factor
# 计算VIF
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))]
print(vif_data)
一般来说,VIF大于5-10就表明存在多重共线性问题,需要考虑使用正则化方法或删除相关特征。
4. 非线性拟合的艺术与科学
4.1 常见非线性模型示例
非线性关系在现实世界中更为普遍。以下是几种常见的非线性模型及其Python实现:
指数模型:
python复制def exp_func(x, a, b, c):
return a * np.exp(b * x) + c
# 生成指数数据
x_data = np.linspace(0, 4, 50)
y_data = exp_func(x_data, 2.5, 1.3, 0.5) + np.random.normal(0, 0.2, 50)
# 拟合
popt, pcov = curve_fit(exp_func, x_data, y_data, p0=[1, 1, 0])
对数模型:
python复制def log_func(x, a, b):
return a * np.log(x) + b
# 注意:x必须大于0
x_data = np.linspace(0.1, 10, 50)
y_data = log_func(x_data, 1.8, 0.5) + np.random.normal(0, 0.1, 50)
# 拟合
popt, pcov = curve_fit(log_func, x_data, y_data)
幂律模型:
python复制def power_func(x, a, b):
return a * x**b
x_data = np.linspace(1, 10, 50)
y_data = power_func(x_data, 1.5, 2.3) + np.random.normal(0, 2, 50)
# 拟合
popt, pcov = curve_fit(power_func, x_data, y_data, p0=[1, 2])
4.2 拟合初始值选择的技巧
非线性拟合对初始参数值非常敏感。以下是一些选择初始值的经验法则:
- 物理意义法:如果模型参数有物理意义,可以根据领域知识估计初始值。
- 线性化法:对模型进行线性化变换,先用线性回归估计参数,再作为初始值。
- 网格搜索法:在小范围内尝试不同的参数组合,选择残差最小的作为初始值。
- 可视化法:先绘制数据和模型预测,手动调整参数使曲线大致匹配数据趋势。
python复制# 网格搜索示例
from itertools import product
def sse(params, x, y, func):
return np.sum((func(x, *params) - y)**2)
param_ranges = {
'a': np.linspace(0, 5, 10),
'b': np.linspace(0, 2, 10),
'c': np.linspace(-1, 1, 5)
}
best_sse = float('inf')
best_params = None
for a, b, c in product(param_ranges['a'], param_ranges['b'], param_ranges['c']):
current_sse = sse([a, b, c], x_data, y_data, exp_func)
if current_sse < best_sse:
best_sse = current_sse
best_params = [a, b, c]
print(f"最佳初始参数: {best_params}, 最小SSE: {best_sse}")
4.3 拟合优度评估指标
评估非线性拟合质量时,除了常见的R²,还应考虑以下指标:
-
调整R²:考虑了参数数量的影响
python复制n = len(y_data) p = len(popt) r_squared = 1 - (sum((y_data - exp_func(x_data, *popt))**2) / ((n - 1) * np.var(y_data, ddof=1))) adj_r_squared = 1 - (1 - r_squared) * (n - 1) / (n - p - 1) -
AIC/BIC:考虑模型复杂度的信息准则
python复制from scipy.stats import norm residuals = y_data - exp_func(x_data, *popt) sigma = np.std(residuals) log_likelihood = np.sum(norm.logpdf(residuals, 0, sigma)) aic = -2 * log_likelihood + 2 * p bic = -2 * log_likelihood + p * np.log(n) -
残差分析:检查残差是否随机分布
python复制plt.figure(figsize=(10, 4)) plt.scatter(x_data, residuals, alpha=0.6) plt.axhline(y=0, color='r', linestyle='--') plt.title('残差图') plt.xlabel('X') plt.ylabel('残差') plt.grid(True) plt.show()
5. 实战中的常见问题与解决方案
5.1 过拟合与正则化技术
当模型过于复杂时,容易出现过拟合现象。我们可以使用正则化技术来解决:
岭回归(Ridge Regression):
python复制from sklearn.linear_model import Ridge
# 高维数据容易过拟合
X = np.random.rand(100, 10)
y = 2 + np.dot(X, np.random.rand(10)) + np.random.normal(0, 0.5, 100)
# 普通线性回归
lr = LinearRegression()
lr.fit(X, y)
print(f"线性回归训练得分: {lr.score(X, y):.4f}")
# 岭回归
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)
print(f"岭回归训练得分: {ridge.score(X, y):.4f}")
Lasso回归:
python复制from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.1)
lasso.fit(X, y)
print(f"Lasso训练得分: {lasso.score(X, y):.4f}")
print(f"非零系数数量: {np.sum(lasso.coef_ != 0)}")
5.2 异常值处理策略
异常值会严重影响拟合结果,常见的处理方法包括:
-
可视化识别:
python复制plt.figure(figsize=(10, 6)) plt.scatter(x_data, y_data, alpha=0.6) plt.title('数据散点图') plt.show() -
统计方法检测:
python复制from scipy import stats z_scores = stats.zscore(np.column_stack((x_data, y_data))) outliers = np.where(np.abs(z_scores) > 3) print(f"异常值索引: {outliers}") -
稳健回归方法:
python复制from sklearn.linear_model import RANSACRegressor ransac = RANSACRegressor() ransac.fit(x_data.reshape(-1, 1), y_data) inlier_mask = ransac.inlier_mask_ outlier_mask = ~inlier_mask
5.3 模型选择与比较
当有多个候选模型时,我们需要系统的方法来选择最佳模型:
python复制from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import PolynomialFeatures
# 准备候选模型
models = {
'Linear': LinearRegression(),
'Quadratic': make_pipeline(PolynomialFeatures(2), LinearRegression()),
'Cubic': make_pipeline(PolynomialFeatures(3), LinearRegression())
}
# 交叉验证比较
for name, model in models.items():
scores = cross_val_score(model, x_data.reshape(-1, 1), y_data, cv=5, scoring='r2')
print(f"{name}模型: 平均R²={scores.mean():.4f}, 标准差={scores.std():.4f}")
对于非线性模型,可以使用AIC或BIC进行比较:
python复制from scipy.optimize import minimize
def aic(n, sse, k):
return n * np.log(sse/n) + 2 * k
def bic(n, sse, k):
return n * np.log(sse/n) + k * np.log(n)
# 拟合不同模型并计算信息准则
results = []
for model_name, model_func in [('线性', lambda x, a, b: a*x + b),
('二次', lambda x, a, b, c: a*x**2 + b*x + c),
('指数', lambda x, a, b, c: a*np.exp(b*x) + c)]:
try:
popt, _ = curve_fit(model_func, x_data, y_data)
sse = np.sum((y_data - model_func(x_data, *popt))**2)
k = len(popt)
results.append({
'model': model_name,
'AIC': aic(len(x_data), sse, k),
'BIC': bic(len(x_data), sse, k),
'params': popt
})
except RuntimeError:
continue
# 显示结果
pd.DataFrame(results).sort_values('AIC')
6. 高级主题与性能优化
6.1 大规模数据拟合策略
当数据量很大时,传统方法可能效率低下。可以考虑以下优化策略:
-
随机采样:
python复制sample_idx = np.random.choice(len(x_data), size=1000, replace=False) x_sample = x_data[sample_idx] y_sample = y_data[sample_idx] -
增量学习:
python复制from sklearn.linear_model import SGDRegressor sgd = SGDRegressor(max_iter=1000, tol=1e-3) for chunk in pd.read_csv('large_data.csv', chunksize=1000): X_chunk = chunk[['feature1', 'feature2']] y_chunk = chunk['target'] sgd.partial_fit(X_chunk, y_chunk) -
并行计算:
python复制from joblib import Parallel, delayed def fit_model(data_slice): x, y = data_slice return LinearRegression().fit(x, y) # 分割数据 n_splits = 4 slices = [(x_data[i::n_splits], y_data[i::n_splits]) for i in range(n_splits)] # 并行拟合 models = Parallel(n_jobs=4)(delayed(fit_model)(slice) for slice in slices) # 合并结果 avg_coef = np.mean([model.coef_ for model in models], axis=0) avg_intercept = np.mean([model.intercept_ for model in models], axis=0)
6.2 自定义损失函数
有时标准的最小二乘损失并不适合,我们可以定义自己的损失函数:
python复制def huber_loss(y_true, y_pred, delta=1.0):
residual = y_true - y_pred
condition = np.abs(residual) < delta
return np.where(condition, 0.5 * residual**2, delta * (np.abs(residual) - 0.5 * delta))
def fit_with_custom_loss(x, y, func, initial_guess, loss_func=huber_loss):
def objective(params):
return np.sum(loss_func(y, func(x, *params)))
result = minimize(objective, initial_guess, method='L-BFGS-B')
return result.x
# 使用示例
popt_custom = fit_with_custom_loss(x_data, y_data, linear_func, [1, 1])
6.3 贝叶斯拟合方法
对于需要考虑参数不确定性的场景,可以使用贝叶斯方法:
python复制import pymc3 as pm
with pm.Model() as linear_model:
# 先验分布
a = pm.Normal('a', mu=0, sigma=10)
b = pm.Normal('b', mu=0, sigma=10)
sigma = pm.HalfNormal('sigma', sigma=1)
# 线性模型
mu = a * x_data + b
# 似然函数
y_obs = pm.Normal('y_obs', mu=mu, sigma=sigma, observed=y_data)
# 采样
trace = pm.sample(2000, tune=1000, cores=2)
# 查看后验分布
pm.plot_posterior(trace, var_names=['a', 'b'])
plt.show()
贝叶斯方法不仅能给出参数估计,还能提供完整的后验分布,帮助我们理解估计的不确定性。
7. 拟合结果的可视化与解释
7.1 可视化最佳实践
好的可视化能帮助理解拟合结果:
python复制plt.figure(figsize=(12, 6))
# 原始数据
plt.scatter(x_data, y_data, color='blue', alpha=0.5, label='原始数据')
# 拟合曲线
x_fit = np.linspace(min(x_data), max(x_data), 100)
plt.plot(x_fit, exp_func(x_fit, *popt), 'r-', label='拟合曲线')
# 置信区间
perr = np.sqrt(np.diag(pcov))
y_fit = exp_func(x_fit, *popt)
y_lower = exp_func(x_fit, *(popt - 1.96 * perr))
y_upper = exp_func(x_fit, *(popt + 1.96 * perr))
plt.fill_between(x_fit, y_lower, y_upper, color='red', alpha=0.2, label='95%置信区间')
# 格式设置
plt.xlabel('X', fontsize=12)
plt.ylabel('Y', fontsize=12)
plt.title('非线性拟合结果可视化', fontsize=14)
plt.legend(fontsize=10)
plt.grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()
7.2 结果解释技巧
解释拟合结果时,需要注意:
- 参数单位:确保理解每个参数的实际意义和单位
- 置信区间:报告参数估计的不确定性
- 效应大小:比较参数大小,评估其实际重要性
- 模型限制:明确说明模型的适用范围和局限性
python复制# 参数解释示例
param_names = ['幅度', '增长率', '基线']
units = ['单位', '1/单位', '单位']
print("拟合参数解释:")
for name, value, error, unit in zip(param_names, popt, np.sqrt(np.diag(pcov)), units):
print(f"{name}: {value:.3f} ± {1.96*error:.3f} {unit} (95%置信区间)")
7.3 模型诊断图
全面的诊断图可以帮助发现模型问题:
python复制fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 拟合曲线与数据
axes[0, 0].scatter(x_data, y_data, alpha=0.6)
axes[0, 0].plot(x_fit, y_fit, 'r-')
axes[0, 0].set_title('拟合曲线')
axes[0, 0].grid(True)
# 残差图
residuals = y_data - exp_func(x_data, *popt)
axes[0, 1].scatter(x_data, residuals, alpha=0.6)
axes[0, 1].axhline(0, color='r', linestyle='--')
axes[0, 1].set_title('残差图')
axes[0, 1].grid(True)
# 残差直方图
axes[1, 0].hist(residuals, bins=15, alpha=0.7)
axes[1, 0].set_title('残差分布')
axes[1, 0].grid(True)
# Q-Q图
stats.probplot(residuals, plot=axes[1, 1])
axes[1, 1].set_title('Q-Q图')
axes[1, 1].grid(True)
plt.tight_layout()
plt.show()
8. 行业应用案例与经验分享
8.1 金融领域应用:股票价格预测
在金融领域,我们经常需要拟合时间序列数据:
python复制import yfinance as yf
# 获取股票数据
data = yf.download('AAPL', start='2020-01-01', end='2023-01-01')
close_prices = data['Close'].values
days = np.arange(len(close_prices))
# 尝试对数模型拟合
def log_model(x, a, b):
return a * np.log(x + 1) + b
popt, pcov = curve_fit(log_model, days, close_prices, p0=[50, 100])
# 可视化
plt.figure(figsize=(12, 6))
plt.plot(days, close_prices, label='实际价格')
plt.plot(days, log_model(days, *popt), 'r-', label='对数模型拟合')
plt.title('苹果股票价格对数模型拟合')
plt.xlabel('交易日')
plt.ylabel('价格(美元)')
plt.legend()
plt.grid(True)
plt.show()
金融数据拟合时需要特别注意:
- 时间序列的自相关性
- 波动聚集现象
- 非平稳性问题
- 极端事件的影响
8.2 生物医学应用:药物剂量反应曲线
在生物医学研究中,常用S型曲线拟合剂量反应关系:
python复制def sigmoid(x, bottom, top, ec50, hill):
return bottom + (top - bottom) / (1 + (ec50 / x)**hill)
# 模拟剂量反应数据
doses = np.logspace(-3, 3, 10)
response = sigmoid(doses, 0, 100, 1, 2) + np.random.normal(0, 5, 10)
# 拟合
popt, pcov = curve_fit(sigmoid, doses, response,
p0=[min(response), max(response), 1, 1],
bounds=([-np.inf, -np.inf, 0, 0], np.inf))
# 可视化
plt.figure(figsize=(10, 6))
plt.semilogx(doses, response, 'bo', label='实验数据')
x_fit = np.logspace(-3, 3, 100)
plt.semilogx(x_fit, sigmoid(x_fit, *popt), 'r-', label='拟合曲线')
plt.title('药物剂量反应曲线拟合')
plt.xlabel('剂量(log)')
plt.ylabel('反应(%)')
plt.legend()
plt.grid(True, which="both", ls="-")
plt.show()
print(f"EC50估计值: {popt[2]:.3f} (95%CI: {popt[2]-1.96*np.sqrt(pcov[2,2]):.3f}-{popt[2]+1.96*np.sqrt(pcov[2,2]):.3f})")
print(f"Hill系数: {popt[3]:.3f}")
生物医学拟合中的注意事项:
- 确保剂量单位一致
- 考虑生物学重复的影响
- 检查模型假设是否合理
- 报告关键参数(如EC50)的置信区间
8.3 工程应用:传感器校准
在工程领域,我们常用多项式拟合来校准传感器:
python复制# 模拟传感器数据
true_values = np.linspace(0, 10, 20)
sensor_readings = 0.8 * true_values + 0.1 * true_values**2 + np.random.normal(0, 0.2, 20)
# 二次多项式拟合
coeffs = np.polyfit(sensor_readings, true_values, 2)
poly_func = np.poly1d(coeffs)
# 校准后的值
calibrated = poly_func(sensor_readings)
# 可视化
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(true_values, sensor_readings)
plt.plot(true_values, sensor_readings, 'r--', alpha=0.5)
plt.title('原始传感器读数')
plt.xlabel('真实值')
plt.ylabel('传感器读数')
plt.grid(True)
plt.subplot(1, 2, 2)
plt.scatter(true_values, calibrated)
plt.plot([min(true_values), max(true_values)], [min(true_values), max(true_values)], 'k--')
plt.title('校准后结果')
plt.xlabel('真实值')
plt.ylabel('校准值')
plt.grid(True)
plt.tight_layout()
plt.show()
print(f"校准误差(MAE): {np.mean(np.abs(calibrated - true_values)):.4f}")
print(f"校准方程: y = {coeffs[0]:.4f}x² + {coeffs[1]:.4f}x + {coeffs[2]:.4f}")
传感器校准中的经验:
- 校准点应覆盖整个工作范围
- 高阶多项式可能导致过拟合
- 定期重新校准以补偿传感器漂移
- 考虑温度等环境因素的影响
9. 性能优化与高级技巧
9.1 使用Numba加速拟合
对于计算密集型的拟合问题,可以使用Numba进行加速:
python复制from numba import njit
@njit
def exp_func_numba(x, a, b, c):
return a * np.exp(b * x) + c
@njit
def sse_numba(params, x, y):
a, b, c = params
return np.sum((exp_func_numba(x, a, b, c) - y)**2)
from scipy.optimize import minimize
# 生成数据
x_data = np.linspace(0, 4, 10000)
y_data = exp_func_numba(x_data, 2.5, 1.3, 0.5) + np.random.normal(0, 0.2, 10000)
# 使用Numba加速的拟合
result = minimize(sse_numba, [1, 1, 0], args=(x_data, y_data), method='L-BFGS-B')
print(f"优化结果: {result.x}")
print(f"执行时间: {result.exec_time:.4f}秒")
9.2 使用SymPy进行符号计算
对于需要解析导数的复杂模型,可以结合符号计算:
python复制import sympy as sp
# 定义符号变量
x, a, b, c = sp.symbols('x a b c')
# 定义符号表达式
model = a * sp.exp(b * x) + c
# 计算符号导数
deriv_a = sp.diff(model, a)
deriv_b = sp.diff(model, b)
deriv_c = sp.diff(model, c)
print(f"关于a的导数: {deriv_a}")
print(f"关于b的导数: {deriv_b}")
print(f"关于c的导数: {deriv_c}")
# 转换为数值函数
model_func = sp.lambdify((x, a, b, c), model, 'numpy')
deriv_a_func = sp.lambdify((x, a, b, c), deriv_a, 'numpy')
deriv_b_func = sp.lambdify((x, a, b, c), deriv_b, 'numpy')
deriv_c_func = sp.lambdify((x, a, b, c), deriv_c, 'numpy')
# 在curve_fit中使用解析导数
def jac(x, a, b, c):
return np.column_stack([
deriv_a_func(x, a, b, c),
deriv_b_func(x, a, b, c),
deriv_c_func(x, a, b, c)
])
popt, pcov = curve_fit(model_func, x_data, y_data, jac=jac)
9.3 使用Dask进行分布式拟合
对于超大规模数据集,可以使用Dask进行分布式计算:
python复制import dask.array as da
from dask_ml.linear_model import LinearRegression
# 创建大型虚拟数据集
x_large = da.random.random((1000000, 10), chunks=(10000, 10))
y_large = da.dot(x_large, da.random.random(10)) + da.random.normal(0, 0.1, size=1000000, chunks=10000)
# 分布式线性回归
model = LinearRegression()
model.fit(x_large, y_large)
print(f"系数: {model.coef_}")
print(f"截距: {model.intercept_}")
10. 实用工具函数与代码片段
10.1 拟合结果报告生成器
python复制def generate_fit_report(func_name, popt, pcov, x_data, y_data):
"""
生成拟合结果的详细报告
参数:
func_name: 模型名称
popt: 最优参数数组
pcov: 参数协方差矩阵
x_data: 自变量数据
y_data: 因变量数据
返回:
包含详细拟合结果的字符串
"""
report = []
report.append(f"=== {func_name} 模型拟合报告 ===")
report.append(f"\n* 参数估计:")
perr = np.sqrt(np.diag(pcov))
for i, (param, err) in enumerate(zip(popt, perr)):
report.append(f" 参数 {i}: {param:.6f} ± {1.96*err:.6f} (95%置信区间)")
# 计算R²
residuals = y_data - func(x_data, *popt)
ss_res = np.sum(residuals**2)
ss_tot = np.sum((y_data - np.mean(y_data))**2)
r_squared = 1 - (ss_res / ss_tot)
report.append(f"\n* 拟合优度:")
report.append(f" R² = {r_squared:.6f}")
report.append(f" 调整R² = {1 - (1 - r_squared) * (len(y_data) - 1) / (len(y_data) - len(popt) - 1):.6f}")
report.append(f" 均方根误差(RMSE) = {np.sqrt(ss_res / len(y_data)):.6f}")
# 参数相关性
report.append("\n* 参数相关性矩阵:")
param_corr = pcov / np.outer(perr, perr)
for i in range(len(popt)):
row = " " + " ".join(f"{param_corr[i,j]:.3f}" for j in range(len(popt)))
report.append(row)
return "\n".join(report)
# 使用示例
print(generate_fit_report("指数模型", popt, pcov, x_data, y_data))
10.2 模型保存与加载工具
python复制import pickle
import json
def save_model(model, params, filepath):
"""
保存拟合模型到文件
参数:
model: 模型函数
params: 模型参数
filepath: 保存路径(不带扩展名)
"""
# 保存Python对象
with open(f"{filepath}.pkl", 'wb') as f:
pickle.dump({'model': model, 'params': params}, f)
# 保存可读参数
with open(f"{filepath}.json", 'w') as f:
json.dump({'params': params.tolist()}, f, indent=2)
def load_model(filepath):
"""
从文件加载拟合模型
参数:
filepath: 文件路径(不带扩展名)
返回:
(model, params) 元组
"""
with open(f"{filepath}.pkl", 'rb') as f:
data = pickle.load(f)
return data['model'], data['params']
# 使用示例
save_model(exp_func, popt, "my_best_fit_model")
loaded_func, loaded_params = load_model("my_best_fit_model")
10.3 自动化拟合流程
python复制def auto_fit(x, y, model_dict, criteria='aic'):
"""
自动化拟合多个模型并选择最佳
参数:
x: 自变量数据
y: 因变量数据
model_dict: 模型字典 {名称: (函数, 初始参数)}
criteria: 选择标准 ('aic', 'bic', 'r2')
返回:
最佳模型名称, 最优参数, 结果数据框
"""
results = []
for name, (func, p0) in model_dict.items():
try:
popt, pcov = curve_fit(func, x, y, p0=p0)
residuals = y - func(x, *popt)
sse = np.sum(residuals**2)
n = len(y)
k = len(p0)
# 计算各种指标
aic_value = n * np.log(sse/n) + 2 * k
bic_value = n * np.log(sse/n) + k * np.log(n)
r_squared = 1 - (sse / np.sum((y - np.mean(y))**2))
results.append({
'model': name,
'params': popt,
'aic': aic_value,
'bic': bic_value,
'r2': r_squared,
'sse': sse
})
except Exception as e:
print(f"模型 {name} 拟合失败: {str(e)}")
continue
df = pd.DataFrame(results)
if criteria == 'aic':
best_idx = df['aic'].idxmin()
elif criteria == 'bic':
best_idx = df['bic'].idxmin()
else:
best_idx = df['r2'].idxmax
