为何逻辑斯蒂曲线拟合R²≈0.99但预测值偏差极大?
逻辑斯蒂函数拟合GDP时早期年份预测值偏差问题
问题背景
使用scipy.optimize.curve_fit拟合中国GDP数据集的逻辑斯蒂函数,拟合结果的R²≈0.99,曲线与数据整体匹配度高,但预测1970年GDP时得到约1.92e11,远高于真实值9.15e10,怀疑缩放处理、函数定义或初始参数设置存在问题。
代码与现象
执行代码
import numpy as np import pandas as pd from scipy.optimize import curve_fit from scipy.special import expit df = pd.read_csv("china_gdp.csv") msk = np.random.rand(len(df)) < 0.8 x = df['Year'].values.astype(float) y = df['Value'].values.astype(float) # 归一化x, y到[0,1] x_s = (x - x.min()) / (x.max() - x.min()) y_s = (y - y.min()) / (y.max() - y.min()) def logistic(x, B1, B2, B3, B4): return B3 + B4 * expit(B1*(x - B2)) # 初始猜测和边界 p0 = (5.0, 0.5, 0.0, 1.0) bounds = ([0.0, 0.0, -np.inf, 0.0], [np.inf, 1.0, np.inf, np.inf]) popt, pcov = curve_fit(logistic, x_s, y_s, p0=p0, bounds=bounds) print(dict(zip(['B1','B2','B3','B4'], popt))) # 查看拟合效果 import matplotlib.pyplot as plt xx = np.linspace(0, 1, 300) yy = logistic(xx, *popt) plt.scatter(x_s, y_s, s=15, label='data') plt.plot(xx, yy, label='fit') plt.legend(); plt.show() from sklearn.metrics import r2_score xtest = x_s[~msk] ytest = y_s[~msk] ypred = logistic(xtest, *popt) print(r2_score(ytest, ypred)) # 预测1970年GDP x_in = 1970 rs = logistic((x_in - x.min()) / (x.max() - x.min()), *popt) r = rs * (y.max() - y.min()) + y.min() print(r)
输出结果
0.9985891574981185 192285528141.3661
真实值
1970年GDP为91506211306.3745
问题分析
- R²的误导性:R²衡量整体拟合度,GDP数据后期数值远大于早期,早期数据的残差平方在总平方和中占比极低,因此即使早期点偏差明显,整体R²依然会保持高位。
- 参数拟合偏差:当前使用的4参数逻辑斯蒂函数边界设置过于宽松,容易拟合出偏离预期的参数(如B3为负、B4大于1),导致反归一化后早期年份的预测值被放大。
- 归一化的潜在影响:虽然归一化逻辑本身正确,但当拟合参数偏离归一化后的y范围[0,1]时,反归一化会进一步放大偏差。
解决方案
方案1:使用标准逻辑斯蒂增长模型
减少参数数量,贴合GDP增长的实际逻辑,直接用原始数据拟合无需归一化:
def standard_logistic(x, L, k, x0): # L: 饱和值, k: 增长速率, x0: 拐点年份 return L / (1 + np.exp(-k*(x - x0))) # 初始参数参考数据设置 p0 = [1.2e13, 0.1, 2000] # 饱和值略高于2014年GDP,增长速率0.1,拐点2000年 bounds = ([1e12, 0.01, 1980], [2e13, 0.5, 2010]) popt, pcov = curve_fit(standard_logistic, x, y, p0=p0, bounds=bounds) # 预测1970年GDP print(standard_logistic(1970, *popt))
该模型更符合增长趋势,能显著降低早期年份的预测偏差。
方案2:收紧参数边界(保留原有函数)
如果坚持使用原有函数和归一化,需限制参数范围以贴合归一化后的y值区间:
# 限制B3在[-0.1,0.1],B4在[0.9,1.1],避免偏离归一化范围 bounds = ([0.0, 0.0, -0.1, 0.9], [10.0, 1.0, 0.1, 1.1]) popt, pcov = curve_fit(logistic, x_s, y_s, p0=p0, bounds=bounds)
内容的提问来源于stack exchange,提问作者MSo
相关产品推荐
相关产品推荐

