Skip to content

Latest commit

 

History

History
523 lines (416 loc) · 18.9 KB

File metadata and controls

523 lines (416 loc) · 18.9 KB
Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 3 column 1
---
- oeasy Python 0814
- 这是 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` 
---

从零开始

回忆

  • 上次跟随 伽利略的视角

    • 构造了斜板实验
    • 在 没有解析几何的时代
      • 用纯粹数列
      • 发现了 距离、时间和角度之间的关系
  • 而且 他发现了

    • 距离、速度 与 重量 没有关系

图片描述

  • 他没有迷信 自然哲学的权威 亚里士多德
    • 提出自己的观点
    • 不愧为**“近代物理学之父”**
  • 他还做过那些实验呢?🤔

单摆实验

  • 伽利略的单摆实验
    • 没有留下像斜面实验那样完整、精确的数值数据集
    • 但在他的著作中记录了定性观察、关键结论和部分实验描述
    • 后人基于这些资料重建了符合他发现的单摆数据
    • 以下是详细情况:
著作 核心内容 关键描述
《关于两门新科学的对话》 质量无关性实验 "我取了两个球,一个是铅制的,一个是软木制的,前者至少比后者重一百倍,将它们系在等长的细线上(长约4-5肘,1肘≈54-62厘米),从垂直位置移开后同时释放,它们沿着等长细线所描述的圆周下落,经过100次甚至1000次摆动后,两者的周期始终保持一致。"
同上 周期与摆长关系 "对于悬挂在不同长度细线上的物体的振动时间,它们之间的比例与长度的平方根成正比;因此,要获得一个振动时间是另一个两倍的单摆,需要将前者的长度设为后者的四倍。"
早期信件(1602年) 等时性发现 首次解释长单摆的等时性,指出周期与振幅无关(小角度下)

图片描述

单摆实验的核心结论

  1. 等时性原理

    • 在小角度摆动时
    • 单摆的周期与振幅无关
      • 无论摆幅大小
      • 完成一次摆动的时间相同
  2. 质量无关性

    • 单摆周期与摆球质量、材料无关
    • 铅球与软木球周期相同

图片描述

周期 vs 摆长

图片描述

摆长 (米) 周期 (秒)
0.20 0.89
0.30 1.10
0.40 1.27
0.54 1.50
0.60 1.56
0.80 1.79
0.81 1.81
1.00 2.01
1.08 2.12
1.35 2.40
1.62 2.65
1.89 2.88
2.00 2.84
2.16 3.00
2.43 3.19
2.70 3.36
3.00 3.48
3.24 3.67
3.78 3.96
4.00 4.01
4.32 4.22
4.86 4.50
5.40 4.72
6.48 5.09
7.00 5.20
8.64 5.99
10.00 6.35
10.80 6.63

特征集合

import pandas as pd
# 伽利略单摆 摆长(米) - 周期(秒) 核心数据集
data = pd.DataFrame({
    '摆长(米)': [0.20,0.30,0.40,0.54,0.60,0.80,0.81,1.00,1.08,1.35,1.62,1.89,2.00,2.16,2.43,2.70,3.00,3.24,3.78,4.00,4.32,4.86,5.40,6.48,7.00,8.64,10.00,10.80],
    '周期(秒)': [0.89,1.10,1.27,1.50,1.56,1.79,1.81,2.01,2.12,2.40,2.65,2.88,2.84,3.00,3.19,3.36,3.48,3.67,3.96,4.01,4.22,4.50,4.72,5.09,5.20,5.99,6.35,6.63]
})
print(data)

先画个图

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# ========== 1. 你的原始数据 一字未改 ==========
data = pd.DataFrame({
    '摆长(米)': [0.20,0.30,0.40,0.54,0.60,0.80,0.81,1.00,1.08,1.35,1.62,1.89,2.00,2.16,2.43,2.70,3.00,3.24,3.78,4.00,4.32,4.86,5.40,6.48,7.00,8.64,10.00,10.80],
    '周期(秒)': [0.89,1.10,1.27,1.50,1.56,1.79,1.81,2.01,2.12,2.40,2.65,2.88,2.84,3.00,3.19,3.36,3.48,3.67,3.96,4.01,4.22,4.50,4.72,5.09,5.20,5.99,6.35,6.63]
})

# ========== 2. 解决中文显示乱码问题 ==========
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False

# ========== 3. 绘图核心代码 ==========
plt.figure(figsize=(10, 6), dpi=100)  # 设置画布大小和清晰度

# 画原始数据散点(重点:真实数据点,红色实心圆点,醒目)
plt.scatter(data['摆长(米)'], data['周期(秒)'], color='darkred', s=80, label='原始实验数据', edgecolors='white', linewidth=1)

# 画平滑趋势曲线(连接数据点,看清整体曲线形态,蓝色实线)
x_smooth = np.sort(data['摆长(米)'])
y_smooth = np.array(data['周期(秒)'])[np.argsort(data['摆长(米)'])]
plt.plot(x_smooth, y_smooth, color='royalblue', linewidth=2, label='趋势曲线')

# ========== 4. 图表标注(清晰易懂) ==========
plt.title('伽利略单摆实验:摆长 与 周期 关系曲线', fontsize=16, pad=20)
plt.xlabel('摆长 (米)', fontsize=14)
plt.ylabel('周期 (秒)', fontsize=14)
plt.grid(True, alpha=0.3, linestyle='-')  # 浅色网格,方便看数据
plt.legend(fontsize=12)

# ========== 5. 直接保存图片【替换掉plt.show(),核心修改处】 ==========
plt.savefig('伽利略单摆_摆长周期关系曲线.png', dpi=100, bbox_inches='tight')
# bbox_inches='tight' :自动裁剪图片周围的空白边距,图片更紧凑美观
# dpi=100 :和画布清晰度一致,保证高清

效果

  • 如果这俩 一样快的话
    • 会是一条直线
    • 那就是 呈现线性关系
  • 现在是 周期 跟不上 摆长

图片描述

  • 我们当然可以拟合成一个
    • 开口向下的抛物线
  • 不过摆长
    • 可以无限延长
    • 抛物线对称轴的就会无限向右移动
    • 这导致使用抛物线拟合 不可能
  • 那怎么办?!

横纵坐标切换

  • 原来

    • 摆长 是 自变量
    • 周期 是 因变量
  • 也可以 调换一下

    • 反正 两个变量之间 相关性没变
    • 找到相关性就好了
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# ========== 1. 原始数据 一字未改 ==========
data = pd.DataFrame({
    '摆长(米)': [0.20,0.30,0.40,0.54,0.60,0.80,0.81,1.00,1.08,1.35,1.62,1.89,2.00,2.16,2.43,2.70,3.00,3.24,3.78,4.00,4.32,4.86,5.40,6.48,7.00,8.64,10.00,10.80],
    '周期(秒)': [0.89,1.10,1.27,1.50,1.56,1.79,1.81,2.01,2.12,2.40,2.65,2.88,2.84,3.00,3.19,3.36,3.48,3.67,3.96,4.01,4.22,4.50,4.72,5.09,5.20,5.99,6.35,6.63]
})

# ========== 2. 解决中文显示乱码问题 ==========
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False

# ========== 3. 绘图核心代码【仅互换横纵坐标,其余不变】 ==========
plt.figure(figsize=(10, 6), dpi=100)  

# ✅ 核心修改1:散点图 周期(秒)→横轴X,摆长(米)→纵轴Y
plt.scatter(data['周期(秒)'], data['摆长(米)'], color='darkred', s=80, label='原始实验数据', edgecolors='white', linewidth=1)

# ✅ 核心修改2:平滑曲线 同步互换+对应排序,保证曲线连贯
x_smooth = np.sort(data['周期(秒)'])
y_smooth = np.array(data['摆长(米)'])[np.argsort(data['周期(秒)'])]
plt.plot(x_smooth, y_smooth, color='royalblue', linewidth=2, label='趋势曲线')

# ========== 4. 图表标注【仅修改标题和轴标签,对应互换】 ==========
plt.title('伽利略单摆实验:周期 与 摆长 关系曲线', fontsize=16, pad=20)
plt.xlabel('周期 (秒)', fontsize=14)
plt.ylabel('摆长 (米)', fontsize=14)
plt.grid(True, alpha=0.3, linestyle='-')
plt.legend(fontsize=12)

# ========== 5. 直接保存图片 不弹窗 ==========
plt.savefig('伽利略单摆_周期摆长关系曲线.png', dpi=100, bbox_inches='tight')

效果

  • 看起来 像一个 幂律分布的曲线

图片描述

  • 先用二阶多项式回归
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import r2_score

data = pd.DataFrame({
    '摆长(米)': [0.20,0.30,0.40,0.54,0.60,0.80,0.81,1.00,1.08,1.35,1.62,1.89,2.00,2.16,2.43,2.70,3.00,3.24,3.78,4.00,4.32,4.86,5.40,6.48,7.00,8.64,10.00,10.80],
    '周期(秒)': [0.89,1.10,1.27,1.50,1.56,1.79,1.81,2.01,2.12,2.40,2.65,2.88,2.84,3.00,3.19,3.36,3.48,3.67,3.96,4.01,4.22,4.50,4.72,5.09,5.20,5.99,6.35,6.63]
})

# ========== 文泉驿字体 极简配置 无任何警告 ==========
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False

plt.figure(figsize=(10,6),dpi=100)

# 自变量 周期T  因变量 摆长L
T = data['周期(秒)'].values.reshape(-1, 1)
L = data['摆长(米)'].values

# ========== 核心:关闭多余偏置项 根治系数负数问题 ==========
poly = PolynomialFeatures(degree=2, include_bias=False)
T_poly = poly.fit_transform(T) # 特征顺序:[T, T²] 绝对正确无坑

# 拟合模型
model = LinearRegression()
model.fit(T_poly, L)
L_pred = model.predict(T_poly)
r2 = r2_score(L, L_pred)

# ========== 绝对正确的系数提取 必出正数a ==========
b = model.coef_[0]  # T^1 一次项系数
a = model.coef_[1]  # T^2 二次项系数 【正数,开口向上】
c = model.intercept_ # 常数项

# ========== 绘图 纯文字 无特殊符号 ==========
plt.scatter(T, L, color='darkred', s=80, edgecolors='white', linewidth=1, label='原始实验数据')
T_smooth = np.linspace(T.min(), T.max(), 200).reshape(-1,1)
T_smooth_poly = poly.transform(T_smooth)
L_smooth = model.predict(T_smooth_poly)
plt.plot(T_smooth, L_smooth, color='royalblue', linewidth=3, label=f'二阶拟合:L={a:.3f}T²{b:+.3f}T{c:+.3f}')

plt.title('伽利略单摆实验 - 二阶回归 (开口向上)', fontsize=16)
plt.xlabel('周期 T (秒)', fontsize=14)
plt.ylabel('摆长 L (米)', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend(fontsize=12)
plt.savefig('伽利略单摆_二阶回归_无警告.png', dpi=150, bbox_inches='tight')
plt.close()

# ========== 打印结果 纯文字 无特殊符号 无语法错误 ==========
print("="*80)
print("二阶多项式回归结果 L = a*T² + b*T + c")
print("="*80)
print(f"T² 二次项系数 (开口系数) a = {a:.3f} 【正数,开口向上】")
print(f"T  一次项系数          b = {b:.3f}")
print(f"常数项                c = {c:.3f}")
print(f"拟合公式:摆长 = {a:.3f}×周期² {b:+.3f}×周期 {c:+.3f}")
print(f"拟合优度 R² = {r2:.6f}")
print("="*80)

效果

图片描述

  • b、c 近似于0
======================================
二阶多项式回归结果 L = a*T² + b*T + c
=========================================
T² 二次项系数 (开口系数) a = 0.253 【正数,开口向上】
T  一次项系数          b = -0.047
常数项                c = 0.040
拟合公式:摆长 = 0.253×周期² -0.047×周期 +0.040
拟合优度 R² = 0.998978
===========================
  • 能不能用什么方法
    • 让b、c更收敛呢?

分水岭

  • 红色山岭有很多

    • 最重要的是分水岭
  • 苗岭 就是一条分水岭

    • 分开了长江水系 / 珠江水系
      • 北侧:乌江、清水江(入长江)
      • 南侧:红水河、都柳江(入珠江)

图片描述

  • 保持 重要的山岭的权重
    • 降低 次要山岭的权重
  • 就是 岭回归模型

代码

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

# 1. 制造“有问题”的单摆数据(增加噪声+减少样本,放大普通回归的不稳定性)
np.random.seed(42)  # 固定随机种子,结果可复现
data = pd.DataFrame({
    '摆长(米)': [0.20,0.30,0.40,0.54,0.60,0.80,0.81,1.00,1.08,1.35],  # 只保留10个样本
    '周期(秒)': [0.89,1.10,1.27,1.50,1.56,1.79,1.81,2.01,2.12,2.40]
})
# 给摆长添加强噪声(放大普通回归的过拟合)
data['摆长(米)'] = data['摆长(米)'] + np.random.normal(0, 0.1, len(data))

# 2. 特征处理(二阶多项式)
T = data['周期(秒)'].values.reshape(-1, 1)
L = data['摆长(米)'].values
poly = PolynomialFeatures(degree=2, include_bias=False)
T_poly = poly.fit_transform(T)

# 3. 对比不同α的岭回归 vs 普通回归
# 普通线性回归(易过拟合)
model_linear = LinearRegression()
model_linear.fit(T_poly, L)
L_pred_linear = model_linear.predict(T_poly)
r2_linear = r2_score(L, L_pred_linear)

# 岭回归(不同α值,体现惩罚效果)
alphas = [0.01, 0.5, 5, 50]  # 从弱到强的惩罚力度
ridge_results = []
for alpha in alphas:
    model_ridge = Ridge(alpha=alpha)
    model_ridge.fit(T_poly, L)
    L_pred_ridge = model_ridge.predict(T_poly)
    r2_ridge = r2_score(L, L_pred_ridge)
    ridge_results.append({
        'alpha': alpha,
        'a': model_ridge.coef_[1],
        'b': model_ridge.coef_[0],
        'c': model_ridge.intercept_,
        'r2': r2_ridge
    })

# 4. 输出对比结果
print("="*80)
print("【普通线性回归(易过拟合)】")
print(f"T²系数 a = {model_linear.coef_[1]:.3f} | T系数 b = {model_linear.coef_[0]:.3f} | 常数项 c = {model_linear.intercept_:.3f}")
print(f"R² = {r2_linear:.6f}")
print("="*80)
print("【岭回归(不同α值的系数收缩效果)】")
for res in ridge_results:
    print(f"α={res['alpha']}:a={res['a']:.3f}, b={res['b']:.3f}, c={res['c']:.3f}, R²={res['r2']:.6f}")
print("="*80)

# 5. 可视化对比(普通回归 vs 强惩罚岭回归)
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(10,6),dpi=100)

# 原始噪声数据
plt.scatter(T, L, color='darkred', s=80, edgecolors='white', label='带噪声实验数据')

# 平滑T序列
T_smooth = np.linspace(T.min(), T.max(), 200).reshape(-1,1)
T_smooth_poly = poly.transform(T_smooth)

# 普通回归曲线(过拟合,波动大)
L_smooth_linear = model_linear.predict(T_smooth_poly)
plt.plot(T_smooth, L_smooth_linear, color='red', linewidth=2, linestyle='--', label='普通回归(过拟合)')

# 强惩罚岭回归(α=5,系数更稳定)
model_ridge_5 = Ridge(alpha=5)
model_ridge_5.fit(T_poly, L)
L_smooth_ridge = model_ridge_5.predict(T_smooth_poly)
plt.plot(T_smooth, L_smooth_ridge, color='green', linewidth=3, label='岭回归(α=5)(更稳定)')

plt.title('带噪声单摆数据 - 普通回归 vs 岭回归(系数收缩效果)', fontsize=16)
plt.xlabel('周期 T (秒)', fontsize=14)
plt.ylabel('摆长 L (米)', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend()
plt.savefig('岭回归_系数收缩效果.png', dpi=150, bbox_inches='tight')
plt.close()

作用

图片描述

==============================================
【普通线性回归(易过拟合)】
T²系数 a = 0.137 | T系数 b = 0.321 | 常数项 c = -0.174
R² = 0.963965
====================================================
【岭回归(不同α值的系数收缩效果)】
α=0.01:a=0.154, b=0.265, c=-0.131, R²=0.963866
α=0.5:a=0.205, b=0.080, c=0.025, R²=0.961770
α=5:a=0.177, b=0.055, c=0.148, R²=0.933006
α=50:a=0.069, b=0.021, c=0.517, R²=0.520722
=======================
  • 为什么会有岭回归?

岭回归

  • 为什么会有岭回归?

    • 解决普通线性回归的“痛点”
    • 岭回归(Ridge Regression)的诞生
    • 是为了弥补普通线性回归在特定场景下的缺陷
  • 当特征之间高度相关

    • 比如多项式回归中的 $T$$T^2$
    • 普通线性回归的系数会变得极端且无意义
  • 在普通线性回归的损失函数中

    • 加入了 L2 正则化惩罚项
    • 由此诞生了岭回归

什么是岭回归?

— 带“紧箍咒”的线性回归岭回归是 - 带 L2 正则化的线性回归

岭回归的损失函数 = 普通回归的残差平方和 + L2 惩罚项
  • $$\min_{w}\quad \sum_{i=1}^n(y_i - w^Tx_i - b)^2 + \alpha\sum_{j=1}^p w_j^2$$
  • 第一部分 $\sum(y_i-\hat{y}_i)^2$:和普通回归一样,代表模型对训练数据的拟合程度;
  • 第二部分 $\alpha\sum w_j^2$L2 惩罚项,是所有系数(不包含截距 $b$)的平方和;
  • $\alpha$:正则化强度,控制惩罚力度的核心参数:
    • $\alpha=0$:惩罚项失效,岭回归等价于普通线性回归;
    • $\alpha\uparrow$:惩罚力度增大,系数会被强制向 0 收缩
    • $\alpha\to\infty$:所有系数趋近于 0,模型退化成“均值模型”,出现欠拟合。

通俗解释:给系数加“紧箍咒”

  • 可以把岭回归理解为
    • 在“拟合数据”和“系数不能太大”之间找平衡

Ridge 词根

  • Ridge
    • /rɪdʒ/
    • 山岭

图片描述

  • 背脊含义
    • 古挪威语
    • 日耳曼语同源词

图片描述

  • 其实我们也这样用
    • 房脊
    • 船脊
    • 世界屋脊

总结

  • 这次 伽利略发现了
    • 单摆的周期T 和 摆长L 有关系
  • 从 因果的角度上来说
    • 周期T 和 摆长的平方根 成正比
  • 从 相关性的角度来说
    • 因果反转
    • 摆长 和 周期的平方成正比
分类 中文名词 英文名词 简要说明
模型类 二阶多项式回归 Second-Order Polynomial Regression 基于二次多项式特征构建的线性回归模型,用于拟合非线性关系
岭回归 Ridge Regression 带L2正则化的线性回归,解决多重共线性、稳定系数
多项式岭回归 Polynomial Ridge Regression 先做多项式特征转换,再用岭回归拟合的组合模型
普通线性回归 Ordinary Linear Regression (OLR) 无正则化的基础线性回归,最小化残差平方和
岭回归交叉验证 Ridge Cross Validation (RidgeCV) 自动选择最优α的岭回归变体
特征处理类 多项式特征 Polynomial Features 由自变量的一次项、二次项(或更高阶)组成的新特征
特征转换 Feature Transformation 将原始特征映射为多项式特征等新特征空间的过程
偏置项 Bias Term 模型中的常数项(截距),include_bias参数控制是否生成
模型参数类 系数 Coefficient 模型中特征对应的权重(如多项式回归的$w_1,w_2$)
截距项 Intercept 模型的常数项,fit_intercept参数控制是否拟合
最优α Optimal Alpha 使岭回归模型泛化能力最优的正则化强度值
问题场景类 多重共线性 Multicollinearity 特征间高度相关导致普通回归系数不稳定的问题
泛化能力 Generalization Ability 模型在新的未知数据上的预测性能
  • 岭回归 可以降低 参数的绝对值
    • 有什么 方法 把参数 直接降成0吗?🤔
  • 我们下次再说👋

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