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年) | 等时性发现 | 首次解释长单摆的等时性,指出周期与振幅无关(小角度下) |
-
等时性原理
- 在小角度摆动时
- 单摆的周期与振幅无关
- 无论摆幅大小
- 完成一次摆动的时间相同
-
质量无关性
- 单摆周期与摆球质量、材料无关
- 铅球与软木球周期相同
| 摆长 (米) | 周期 (秒) |
|---|---|
| 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
- /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 即可。










