Skip to content

Latest commit

 

History

History
712 lines (571 loc) · 30.1 KB

File metadata and controls

712 lines (571 loc) · 30.1 KB
Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 3 column 1
---
- oeasy Python 0811
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
本教程同步发布在: 

     个人网站: `https://oeasy.org` 
     蓝桥云课: `https://www.lanqiao.cn/courses/3584` 
     GitHub: `https://github.com/overmind1980/oeasy-python-tutorial` 
     Gitee: `https://gitee.com/overmind1980/oeasypython` 
---

从零开始

回忆

  • 上次研究的是 分段线性回归
中文术语 英文标准对应 核心概念/定义说明
分段线性回归 Piecewise Linear Regression 将数据按分界点分成多个区间
每个区间单独拟合一条直线
贴合数据的分段变化规律
分段点 / 分界点 Breakpoint 划分数据区间的临界值
是分段线性回归的关键参数
分段拟合公式 Piecewise Fitting Formula 各区间对应的线性公式 $y=kt+b$
不同区间的斜率$k$、截距$b$各不相同
  • 分段数量越多
    • 均方根误差越小
    • 决定系数越趋向于1

图片描述

  • 可是目前是伽利略的8条数据
    • 如果数据集大一些的话
    • 也不能这么无限的切分啊
  • 有什么更好的办法吗?🤔

伽利略数据集

Number Time Distance
1 1 33
2 2 130
3 3 298
4 4 526
5 5 824
6 6 1192
7 7 1620
8 8 2123
  • 33 130 298...
    • 数列 不是 线性的等差数列
    • 有什么规律吗?

图片描述

规律

  • 差距越来越大
Serial Number Time ($t$) Distance ($S$) First-Order Difference ($\Delta S_1=S_{n+1}-S_n$)
1 1 33 -
2 2 130 $130-33=\boldsymbol{97}$
3 3 298 $298-130=\boldsymbol{168}$
4 4 526 $526-298=\boldsymbol{228}$
5 5 824 $824-526=\boldsymbol{298}$
6 6 1192 $1192-824=368$
7 7 1620 $1620-1192=428$
8 8 2123 $2123-1620=503$
  • 97,168,228,298,368...
    • 差值 趋近于 等差数列

图片描述

差值的差值

时间 距离 $S$ 一阶差值 $\Delta S_1$ (后项-前项) 二阶差值 $\Delta S_2$ (一阶差值的后项-前项)
1 33 - -
2 130 $130-33=97$ -
3 298 $298-130=168$ $168-97=71$
4 526 $526-298=228$ $228-168=60$
5 824 $824-526=298$ $298-228=70$
6 1192 $1192-824=368$ $368-298=70$
7 1620 $1620-1192=428$ $428-368=60$
8 2123 $2123-1620=503$ $503-428=75$
  • 伽利略从 通过定量测量+数学分析+逻辑推理
    • 归纳出了匀加速直线运动的核心规律
    • 并进一步推导得到自由落体定律
  • $$S \propto t^2$$

图片描述

  • 这是关于t二次函数

代码

# 伽利略 斜面实验 原始数据 二次回归 (完美修正警告 + 直接保存PNG + y = a*t² + bt + c)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score, mean_squared_error

# ===================== 1. 构造伽利略原始真实数据集【纯三列 序号、时间、距离】=====================
data = pd.DataFrame({
    'Serial': [1,2,3,4,5,6,7,8],
    'Time': [1,2,3,4,5,6,7,8],
    'Distance': [33,130,298,526,824,1192,1620,2123]
})
# 特征/目标值 提取
X = data[['Time']]  # 保持DataFrame格式+特征名,彻底解决警告
y = data['Distance']

# ===================== 2. 构造二次特征 + 二次回归建模 核心步骤 =====================
poly = PolynomialFeatures(degree=2, include_bias=False)
X_2 = poly.fit_transform(X)  # 生成 [t, t²] 二次特征,带特征名拟合

# 训练二次回归模型
model = LinearRegression()
model.fit(X_2, y)

# ===================== 3. 模型预测 + 拟合效果评估 =====================
y_pred = model.predict(X_2)
r2 = r2_score(y, y_pred)
rmse = np.sqrt(mean_squared_error(y, y_pred))

# ===================== 4. 输出核心结果 =====================
print("="*70)
print("✅ 伽利略原始数据 二次回归结果 (y = a*t² + b*t + c)")
print(f"二次回归方程:Distance = {model.coef_[1]:.2f}*Time² + {model.coef_[0]:.2f}*Time + {model.intercept_:.2f}")
print(f"拟合优度 R² = {r2:.6f}  |  均方根误差 RMSE = {rmse:.2f}")
print("="*70)
print("✅ 原始实测值 vs 二次回归预测值")
compare = pd.DataFrame({
    'Time':X['Time'], 
    'True_Distance':y, 
    'Pred_Distance':np.round(y_pred,1)
})
print(compare)
print("="*70)

# ===================== 5. 可视化:原始数据+二次拟合曲线 ✔️直接保存PNG 无plt.show() ✔️=====================
plt.rcParams['font.sans-serif']=['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(8,5),dpi=120)

# 伽利略原始实测数据点
plt.scatter(X['Time'], y, color='darkred', s=120, label='Galileo Raw Data', edgecolors='white', linewidth=2)

# 生成平滑的时间序列+二次拟合曲线【修正警告关键:保持DataFrame+同名特征】
t_range = pd.DataFrame({'Time':np.linspace(0,9,100)}) 
t_range_2 = poly.transform(t_range)
s_pred = model.predict(t_range_2)

# 绘制二次拟合曲线
plt.plot(t_range['Time'], s_pred, color='darkblue', lw=3, label='Quadratic Fit (S ∝ t²)')

plt.xlabel('Time (equal intervals)')
plt.ylabel('Distance (scale divisions)')
plt.title('Galileo Inclined Plane Experiment | Quadratic Regression')
plt.legend(loc='upper left')
plt.grid(alpha=0.3, linestyle='--')

# ✅ 直接保存高清PNG到实验楼路径,无弹窗,无plt.show()
plt.savefig('/home/project/伽利略二次回归拟合图.png', dpi=120, bbox_inches='tight')

拟合曲线

  • 二次曲线 刚好拟合 这个数据集

图片描述

  • 距离
    • 和 下落时间 有关

图片描述

  • 距离还和什么有关系呢?

伽利略斜面实验数据

  • 距离跟角度(θ)有关系

图片描述

  • 角度(θ)
    • 范围:2°~10°
      • 小倾角,延长运动时间
      • 适配水钟精度
Serial Angle (°) Distance (scale divisions)
1 10 2282
2 20 4480
3 30 6550
4 40 8380
5 50 9960
6 60 11290
7 70 12220
8 80 12830
  • 先试试线性回归

线性回归

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error

# ===================== 1. 纯净数据集 =====================
data = pd.DataFrame({
    'Serial': [1,2,3,4,5,6,7,8],
    'Angle': [10, 20, 30, 40, 50, 60, 70, 80],
    'Distance': [2282, 4480, 6550, 8380, 9960, 11290, 12220, 12830]
})

# 自变量+因变量
X_angle = data[['Angle']]
y = data['Distance']

# ===================== 2. 仅保留:角度 一次线性回归 =====================
model_angle_linear = LinearRegression()
model_angle_linear.fit(X_angle, y)
y_pred_angle_linear = model_angle_linear.predict(X_angle)
r2_angle_linear = r2_score(y, y_pred_angle_linear)
rmse_angle_linear = np.sqrt(mean_squared_error(y, y_pred_angle_linear))

# ===================== 3. 评估指标输出 =====================
print("="*80)
print("✅ 伽利略斜面实验-大角度(10°~80°) 角度→距离 一次线性回归评估")
print("="*80)
print(f"角度一次线性回归  | R² = {r2_angle_linear:.4f} | RMSE = {rmse_angle_linear:.1f}")
print(f"拟合方程:Distance = {model_angle_linear.coef_[0]:.1f}*Angle + {model_angle_linear.intercept_:.0f}")
print("="*80)

# 实测值vs预测值对比表
compare_table = pd.DataFrame({
    'Angle(°)': data['Angle'],
    '实测Distance': y,
    '角度线性预测': np.round(y_pred_angle_linear,1)
})
print(compare_table)
print("="*80)

# ===================== 4. 残差分析 =====================
data['残差_角度线性'] = np.round(y - y_pred_angle_linear,1)
print("📈 残差分析(残差越小越好,随机分布最优)")
print(data[['Angle','Distance','残差_角度线性']].rename(columns={'Angle':'Angle(°)','Distance':'实测Distance'}))
print("="*80)

# ===================== 5. 可视化拟合图 自动保存无弹窗 =====================
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(10,6),dpi=120)

plt.scatter(data['Angle'], y, color='darkred', s=180, label='Galileo Raw Data', edgecolors='white', linewidth=2)
angle_range = pd.DataFrame({'Angle': np.linspace(9,85,200)})

# 仅绘制一次线性拟合线
plt.plot(angle_range, model_angle_linear.predict(angle_range), color='darkblue', lw=3, label=f'Angle Linear Fit (R²={r2_angle_linear:.4f})')

plt.xlabel('Angle (°)')
plt.ylabel('Distance (scale divisions)')
plt.title('Large Angle Linear Regression | Galileo Inclined Plane Experiment (10°~80°)')
plt.legend(loc='upper left')
plt.grid(alpha=0.3)
plt.savefig('/home/project/伽利略_大角度纯角度回归.png', dpi=120, bbox_inches='tight')

效果

=========================================================
✅ 伽利略斜面实验-大角度(10°~80°) 角度→距离 一次线性回归评估
==========================================================
角度一次线性回归  | R² = 0.9693 | RMSE = 623.2
拟合方程:Distance = 152.8*Angle + 1624
===========================================================
   Angle(°)  实测Distance   角度线性预测
0        10        2282   3151.7
1        20        4480   4679.5
2        30        6550   6207.3
3        40        8380   7735.1
4        50        9960   9262.9
5        60       11290  10790.7
6        70       12220  12318.5
7        80       12830  13846.3
========================================================
📈 残差分析(残差越小越好,随机分布最优)
   Angle(°)  实测Distance  残差_角度线性
0        10        2282   -869.7
1        20        4480   -199.5
2        30        6550    342.7
3        40        8380    644.9
4        50        9960    697.1
5        60       11290    499.3
6        70       12220    -98.5
7        80       12830  -1016.3
=====================================================
  • 这看起来 拟合的不太好

图片描述

  • 尝试二阶回归

二阶回归

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score, mean_squared_error

# ===================== 1. 纯净数据集 =====================
data = pd.DataFrame({
    'Serial': [1,2,3,4,5,6,7,8],
    'Angle': [10, 20, 30, 40, 50, 60, 70, 80],
    'Distance': [2282, 4480, 6550, 8380, 9960, 11290, 12220, 12830]
})

# 自变量+因变量
X_angle = data[['Angle']]
y = data['Distance']

# ===================== 2. 角度线性回归 + 角度二次回归 =====================
# 模型1 角度线性回归
model_angle_linear = LinearRegression()
model_angle_linear.fit(X_angle, y)
y_pred_angle_linear = model_angle_linear.predict(X_angle)
r2_angle_linear = r2_score(y, y_pred_angle_linear)
rmse_angle_linear = np.sqrt(mean_squared_error(y, y_pred_angle_linear))

# 模型2 角度二次回归
poly = PolynomialFeatures(degree=2, include_bias=False)
X_angle_2 = poly.fit_transform(X_angle)
model_angle_quad = LinearRegression()
model_angle_quad.fit(X_angle_2, y)
y_pred_angle_quad = model_angle_quad.predict(X_angle_2)
r2_angle_quad = r2_score(y, y_pred_angle_quad)
rmse_angle_quad = np.sqrt(mean_squared_error(y, y_pred_angle_quad))

# ===================== 3. 评估指标输出 =====================
print("="*80)
print("✅ 伽利略斜面实验-大角度(10°~80°) 角度→距离 回归评估")
print("="*80)
print(f"角度线性回归  | R² = {r2_angle_linear:.4f} | RMSE = {rmse_angle_linear:.1f}")
print(f"方程:Distance = {model_angle_linear.coef_[0]:.1f}*Angle + {model_angle_linear.intercept_:.0f}")
print("-"*80)
print(f"角度二次回归  | R² = {r2_angle_quad:.4f} | RMSE = {rmse_angle_quad:.1f}")
print(f"方程:Distance = {model_angle_quad.coef_[1]:.1f}*Angle² + {model_angle_quad.coef_[0]:.1f}*Angle + {model_angle_quad.intercept_:.0f}")
print("="*80)

# 实测值vs预测值对比表
compare_table = pd.DataFrame({
    'Angle(°)': data['Angle'],
    '实测Distance': y,
    '角度线性预测': np.round(y_pred_angle_linear,1),
    '角度二次预测': np.round(y_pred_angle_quad,1)
})
print(compare_table)
print("="*80)

# ===================== 4. 残差分析 =====================
data['残差_角度线性'] = np.round(y - y_pred_angle_linear,1)
data['残差_角度二次'] = np.round(y - y_pred_angle_quad,1)
print("📈 残差分析(残差越小越好,随机分布最优)")
print(data[['Angle','Distance','残差_角度线性','残差_角度二次']].rename(columns={'Angle':'Angle(°)','Distance':'实测Distance'}))
print("="*80)

# ===================== 5. 可视化拟合图 自动保存无弹窗 =====================
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(10,6),dpi=120)

plt.scatter(data['Angle'], y, color='darkred', s=180, label='Galileo Raw Data', edgecolors='white', linewidth=2)
angle_range = pd.DataFrame({'Angle': np.linspace(9,85,200)})

# 线性拟合线
plt.plot(angle_range, model_angle_linear.predict(angle_range), color='darkblue', lw=3, label=f'Angle Linear (R²={r2_angle_linear:.4f})')
# 二次拟合线
plt.plot(angle_range, model_angle_quad.predict(poly.transform(angle_range)), color='orange', lw=3, linestyle='--', label=f'Angle Quadratic (R²={r2_angle_quad:.4f})')

plt.xlabel('Angle (°)')
plt.ylabel('Distance (scale divisions)')
plt.title('Large Angle Regression | Galileo Inclined Plane Experiment (10°~80°)')
plt.legend(loc='upper left')
plt.grid(alpha=0.3)
plt.savefig('/home/project/伽利略_大角度纯角度回归.png', dpi=120, bbox_inches='tight')

特别注意

  • 最佳工程实践!!!
    • 参数里面的
    • include_bias=False
    • 一定要这样写清楚
poly = PolynomialFeatures(degree=2, include_bias=False)
  • 否则的话 会出问题

运行结果

图片描述

========================================================================
✅ 伽利略斜面实验-大角度(10°~80°) 角度→距离 回归评估
========================================================================
角度线性回归  | R² = 0.9693 | RMSE = 623.2
方程:Distance = 152.8*Angle + 1624
--------------------------------------------------------------------------------
角度二次回归  | R² = 0.9997 | RMSE = 61.6
方程:Distance = -1.4*Angle² + 274.6*Angle + -406
==========================================================================
   Angle(°)  实测Distance   角度线性预测   角度二次预测
0        10        2282   3151.7   2204.3
1        20        4480   4679.5   4544.1
2        30        6550   6207.3   6613.3
3        40        8380   7735.1   8411.8
4        50        9960   9262.9   9939.6
5        60       11290  10790.7  11196.7
6        70       12220  12318.5  12183.2
7        80       12830  13846.3  12899.0
==========================================================================
📈 残差分析(残差越小越好,随机分布最优)
   Angle(°)  实测Distance  残差_角度线性  残差_角度二次
0        10        2282   -869.7     77.7
1        20        4480   -199.5    -64.1
2        30        6550    342.7    -63.3
3        40        8380    644.9    -31.8
4        50        9960    697.1     20.4
5        60       11290    499.3     93.3
6        70       12220    -98.5     36.8
7        80       12830  -1016.3    -69.0
=========================================================================

对比

  • 这次研究了
    • 非线性回归
    • 二次回归
  • 二次回归是线性回归的「扩展形态」
    • 线性回归是二次回归的特例
      • 二次项系数为0
    • 二次回归线性回归 通过特征工程
      • 拓展出的非线性拟合能力
      • 前者适合线性关系
      • 后者适合可被抛物线近似的非线性关系
对比维度 线性回归(一次回归) 二次回归(二次多项式回归)
模型公式 $y = w_1 \cdot x + w_0$ $y = w_2 \cdot x^2 + w_1 \cdot x + w_0$
特征形态 仅用原始特征 $x$(如角度 $\theta$ 用原始特征 $x$ + 幂次特征 $x^2$(特征工程产物)
拟合曲线 一条直线,斜率固定 一条抛物线,斜率随 $x$ 变化
适用关系 自变量和因变量呈 严格线性相关 自变量和因变量呈 单调非线性相关(如大角度下 $\theta$ 与位移的关系)
模型复杂度 低,参数少(2个:$w_1、w_0$) 高,参数多(3个:$w_2、w_1、w_0$)
过拟合风险 低,泛化能力强(线性假设简单) 高,若数据量少易过拟合(需正则化约束)
伽利略实验表现(大角度) 完全失效,$R^2$ 低,残差呈递增趋势,系统性高估位移 拟合精准,$R^2$ 接近1,残差随机分布,能捕捉位移饱和趋势

大角度问题

根据真实的物理规律

生成[0,10],[70-89]部分真实数据

并验证模型

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score, mean_squared_error

# 1. 完整保留原训练集+验证集 无任何修改 (仅保留角度+距离,剔除sin列)
data = pd.DataFrame({'Serial': [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],
                     'Angle': [0,1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80],
                     'Distance': [0,225,450,675,900,1125,1350,1575,1800,2025,2270,4455,6520,8360,9940,11275,12200,12810]})
data_val = pd.DataFrame({'Serial': [19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],
                         'Angle':[70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89],
                         'Distance':[12200,12278,12351,12419,12482,12540,12593,12641,12684,12722,12810,12844,12874,12900,12922,12940,12955,12966,12975,12980]})
X_train, y_train = data[['Angle']], data['Distance']
X_val, y_val = data_val[['Angle']], data_val['Distance']

# 2. 双模型训练+预测 (角度线性回归 + 角度二次回归)
model_linear = LinearRegression().fit(X_train, y_train)
y_pred_linear = model_linear.predict(X_train)
y_val_pred_linear = model_linear.predict(X_val)

poly = PolynomialFeatures(degree=2, include_bias=False) # 修复实验楼兼容报错
X_train_poly = poly.fit_transform(X_train)
model_quad = LinearRegression().fit(X_train_poly, y_train)
y_pred_quad = model_quad.predict(X_train_poly)
y_val_pred_quad = model_quad.predict(poly.transform(X_val))

# 3. 计算评估指标 R² + RMSE
r2_l, rmse_l = r2_score(y_train,y_pred_linear), np.sqrt(mean_squared_error(y_train,y_pred_linear))
r2_q, rmse_q = r2_score(y_train,y_pred_quad), np.sqrt(mean_squared_error(y_train,y_pred_quad))
r2_l_val, rmse_l_val = r2_score(y_val,y_val_pred_linear), np.sqrt(mean_squared_error(y_val,y_val_pred_linear))
r2_q_val, rmse_q_val = r2_score(y_val,y_val_pred_quad), np.sqrt(mean_squared_error(y_val,y_val_pred_quad))

# 4. 输出评估报告 + 0-10°数据对比表
print("="*80)
print("伽利略斜面实验 双模型评估报告 (纯角度版 | 无sin)")
print("="*80)
print(f"【角度线性回归】训练集 R²={r2_l:.4f} | RMSE={rmse_l:.1f} | 验证集 R²={r2_l_val:.4f} | RMSE={rmse_l_val:.1f}")
print(f"【角度二次回归】训练集 R²={r2_q:.4f} | RMSE={rmse_q:.1f} | 验证集 R²={r2_q_val:.4f} | RMSE={rmse_q_val:.1f}")
print("="*80)
print("📊 0°~10° 实测值 & 双模型预测值对比表")
print(pd.DataFrame({
    '斜面倾角(°)': data['Angle'][:11],
    '实测滚动距离': y_train[:11],
    '角度线性预测值': np.round(y_pred_linear[:11],1),
    '角度二次预测值': np.round(y_pred_quad[:11],1)
}))

# 5. 可视化:双模型拟合曲线+训练/验证散点 完整绘图+保存
plt.rcParams['font.sans-serif']=['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus']=False
plt.figure(figsize=(12,7),dpi=120)
plt.scatter(X_train, y_train, c='darkred', s=150, label='训练集(0°~80°)', edgecolors='white', lw=2, zorder=5)
plt.scatter(X_val, y_val, c='navy', s=120, label='验证集(70°~89°)', edgecolors='white', lw=2, zorder=5)

# 绘制0-90°拟合曲线
angle_range = pd.DataFrame({'Angle':np.linspace(0,90,200)})
plt.plot(angle_range, model_linear.predict(angle_range), c='darkblue', lw=3, label=f'角度线性 R²={r2_l:.4f}', zorder=3)
plt.plot(angle_range, model_quad.predict(poly.transform(angle_range)), c='orange', lw=3, ls='--', label=f'角度二次 R²={r2_q:.4f}', zorder=4)

plt.xlabel('斜面倾角 (°)', fontsize=12)
plt.ylabel('小球滚动距离 (刻度)', fontsize=12)
plt.title('伽利略斜面实验 - 角度线性回归 vs 角度二次回归 (0°~90°)', fontsize=14)
plt.legend(loc='upper left', fontsize=11)
plt.grid(alpha=0.3, linestyle='--')
plt.savefig('/home/project/伽利略_纯角度双模型拟合图.png', dpi=120, bbox_inches='tight')

结果

图片描述

  • 0°~10° 实测值与双模型预测值对比
    • 二阶多项式回归 明显比 线性 拟合效果好
模型类型 训练集 R² 训练集 RMSE 验证集 R² 验证集 RMSE
角度线性回归 0.9831 575.9 -45.4451 1681.2
角度二次多项式回归 0.9996 84.2 0.0782 236.8
  • 但问题就是
    • 训练用的是 红色部分
    • 测试用的是 蓝色部分
  • 这个模型还可以调优吗?

分析

  • 训练集包括角度
    • [0,1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80]
# 1. 完整保留原训练集+验证集 无任何修改 (仅保留角度+距离,剔除sin列)
data = pd.DataFrame({'Serial': [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],
                     'Angle': [0,1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80],
                     'Distance': [0,225,450,675,900,1125,1350,1575,1800,2025,2270,4455,6520,8360,9940,11275,12200,12810]})

X_train, y_train = data[['Angle']], data['Distance']
  • 测试集包括
    • [70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89]
data_val = pd.DataFrame({'Serial': [19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],
                         'Angle':[70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89],
                         'Distance':[12200,12278,12351,12419,12482,12540,12593,12641,12684,12722,12810,12844,12874,12900,12922,12940,12955,12966,12975,12980]})
X_val, y_val = data_val[['Angle']], data_val['Distance']
  • 集合分布不合理
    • 而且相互覆盖

控制特征矩阵

  • 随机拆分 训练集/测试集
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score, mean_squared_error
from sklearn.model_selection import train_test_split

# ✅ 核心要求全部满足:Serial用list(range)生成 + 直接赋值all_data + 无任何拼接
all_data = pd.DataFrame({
    'Serial': list(range(1, 39)),  # 完美实现:Serial从1到38 自动生成
    'Angle':  [0,1,2,3,4,5,6,7,8,9,10,20,30,40,50,60,70,80,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89],
    'Distance':[0,225,450,675,900,1125,1350,1575,1800,2025,2270,4455,6520,8360,9940,11275,12200,12810,12200,12278,12351,12419,12482,12540,12593,12641,12684,12722,12810,12844,12874,12900,12922,12940,12955,12966,12975,12980]
})

# 提取自变量和因变量 【和你原代码写法完全一致】
X = all_data[['Angle']]
y = all_data['Distance']

# 分层随机拆分:70%训练集,30%验证集 【你原代码参数完全不变】
X_train, X_val, y_train, y_val = train_test_split(
    X, y, 
    test_size=0.3,        # 验证集占30%
    random_state=42,      # 随机种子,结果可复现
    stratify=pd.cut(all_data['Angle'], bins=3)
)

# 2. 双模型训练+预测 (原代码完全不变)
model_linear = LinearRegression().fit(X_train, y_train)
y_pred_linear = model_linear.predict(X_train)
y_val_pred_linear = model_linear.predict(X_val)

poly = PolynomialFeatures(degree=2, include_bias=False)
X_train_poly = poly.fit_transform(X_train)
model_quad = LinearRegression().fit(X_train_poly, y_train)
y_pred_quad = model_quad.predict(X_train_poly)
y_val_pred_quad = model_quad.predict(poly.transform(X_val))

# 3. 计算评估指标 R² + RMSE (原代码完全不变)
r2_l, rmse_l = r2_score(y_train,y_pred_linear), np.sqrt(mean_squared_error(y_train,y_pred_linear))
r2_q, rmse_q = r2_score(y_train,y_pred_quad), np.sqrt(mean_squared_error(y_train,y_pred_quad))
r2_l_val, rmse_l_val = r2_score(y_val,y_val_pred_linear), np.sqrt(mean_squared_error(y_val,y_val_pred_linear))
r2_q_val, rmse_q_val = r2_score(y_val,y_val_pred_quad), np.sqrt(mean_squared_error(y_val,y_val_pred_quad))

# 4. 输出评估报告 (原代码完全不变,保留所有打印内容)
print("="*80)
print("伽利略斜面实验 双模型评估报告 (纯角度版 | 分层随机拆分)")
print("="*80)
print(f"【角度线性回归】训练集 R²={r2_l:.4f} | RMSE={rmse_l:.1f} | 验证集 R²={r2_l_val:.4f} | RMSE={rmse_l_val:.1f}")
print(f"【角度二次回归】训练集 R²={r2_q:.4f} | RMSE={rmse_q:.1f} | 验证集 R²={r2_q_val:.4f} | RMSE={rmse_q_val:.1f}")
print("="*80)

# 补充:打印训练集/验证集的角度分布
print("📌 训练集角度范围:", X_train['Angle'].min(), "° ~", X_train['Angle'].max(), "°")
print("📌 验证集角度范围:", X_val['Angle'].min(), "° ~", X_val['Angle'].max(), "°")
print("="*80)

# 5. 可视化:双模型拟合曲线+训练/验证散点 (原代码完全不变,保存路径也不变)
plt.rcParams['font.sans-serif']=['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus']=False
plt.figure(figsize=(12,7),dpi=120)
plt.scatter(X_train, y_train, c='darkred', s=150, label='训练集(随机70%)', edgecolors='white', lw=2, zorder=5)
plt.scatter(X_val, y_val, c='navy', s=120, label='验证集(随机30%)', edgecolors='white', lw=2, zorder=5, alpha=0.8)

# 绘制0-90°拟合曲线 (原代码完全不变)
angle_range = pd.DataFrame({'Angle':np.linspace(0,90,200)})
plt.plot(angle_range, model_linear.predict(angle_range), c='darkblue', lw=3, label=f'角度线性 R²={r2_l:.4f}', zorder=3)
plt.plot(angle_range, model_quad.predict(poly.transform(angle_range)), c='orange', lw=3, ls='--', label=f'角度二次 R²={r2_q:.4f}', zorder=4)

plt.xlabel('斜面倾角 (°)', fontsize=12)
plt.ylabel('小球滚动距离 (刻度)', fontsize=12)
plt.title('伽利略斜面实验 - 角度线性回归 vs 角度二次回归 (0°~90°)', fontsize=14)
plt.legend(loc='upper left', fontsize=11)
plt.grid(alpha=0.3, linestyle='--')
plt.savefig('/home/project/伽利略_纯角度双模型_随机拆分.png', dpi=120, bbox_inches='tight')

总结

图片描述

  • R²=0.9997 已经很厉害了!
=============================================================
伽利略斜面实验 双模型评估报告 (纯角度版 | 分层随机拆分)
====================================================
【角度线性回归】训练集 R²=0.9856 | RMSE=615.3 | 验证集 R²=0.9690 | RMSE=928.3
【角度二次回归】训练集 R²=0.9997 | RMSE=91.0 | 验证集 R²=0.9993 | RMSE=135.0
=============================================
📌 训练集角度范围: 0 ° ~ 86 °
📌 验证集角度范围: 3 ° ~ 89 °
=================================================
  • 还可以有更好的拟合方式吗?🤔
  • 我们下次再说👋

  • 本文来自 oeasy Python 系统教程。
  • 想完整、扎实学 Python,
  • 搜索 oeasy 即可。