Error in user YAML: (<unknown>): found a tab character that violate indentation while scanning a plain scalar at line 3 column 3
---
- oeasy Python 0807
- 这是 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`
---| 核心术语 | 对应代码 | 本质含义 |
|---|---|---|
| Feature(特征) | 输入模型的量化数据(你的身高、臂展、体重数据) | |
| Label(标签) | 数据的目标结果(你的0/1球员潜质标签) | |
| Estimator(估计器) | RandomForestClassifier() |
机器学习模型/算法本身 |
| Fitting(拟合) | .fit(X, y) |
训练模型,学习特征与标签的规律 |
| Predicting(预测) | clf.predict(X_new) |
用训练好的模型对新数据做推断 |
| Pipeline(流水线/管道) | Pipeline([('scaler',StandardScaler()),('rf',...)]) |
串联预处理+模型,避免数据泄露、简化流程 |
| Cross Validation(交叉验证) | RandomizedSearchCV(cv=5) |
切分数据集轮换训练测试,评估结果更客观 |
| Hyperparameter Search(超参数搜索) | RandomizedSearchCV() |
寻找最优超参数组合的过程 |
| Serialization(序列化) | joblib.dump(model,"球员潜质预测最优模型.pkl") |
将模型转为二进制.pkl文件保存 |
| Deserialization(反序列化) | joblib.load("球员潜质预测最优模型.pkl") |
将.pkl文件还原为原模型对象 |
- 可以 直接把 球员潜力
分级吗?
import numpy as np
import pandas as pd
import warnings
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV
# ========== 新增:导入模型持久化核心库 ==========
from joblib import dump, load
warnings.filterwarnings('ignore')
# 原始数据集
df = pd.DataFrame({
"身高(m)": [1.75, 1.65, 1.83, 1.70, 1.91, 1.88, 1.98, 2.03, 2.08, 2.16],
"臂展(m)": [1.78, 1.68, 1.85, 1.73, 1.96, 1.93, 2.11, 2.21, 2.13, np.nan],
"体重(kg)": [70, 55, 85, 60, 95, 82, 90, 102, 111, 116]
})
print("【含np.nan缺失值的球员基础数据集】")
print(df)
print("-"*60)
# 二元线性回归填补臂展缺失值(身高+体重 双特征,区别一元单特征)
train_df = df[df['臂展(m)'].notna()]
X_train = train_df[['身高(m)', '体重(kg)']]
y_train = train_df['臂展(m)']
lr_model = LinearRegression()
lr_model.fit(X_train, y_train)
df['预测臂展(m)'] = lr_model.predict(df[['身高(m)', '体重(kg)']])
fill_val = round(df.loc[9, '预测臂展(m)'], 2)
df['臂展(m)'] = df['臂展(m)'].fillna(fill_val)
# 输出填补结果
w1, w2 = lr_model.coef_
b = lr_model.intercept_
print(f"✅ 二元线性公式:臂展 = {w1:.4f} × 身高 + {w2:.4f} × 体重 + {b:.4f}")
print(f"✅ 填补臂展值 = {fill_val} m")
print("-"*60)
print("【补全后完整数据】")
print(df.round(2))
print("-"*60)
# 建模:三特征+流水线+5折交叉验证+随机超参搜索
y = np.array([0,0,0,0,0,1,1,1,1,1])
X = df[['身高(m)', '臂展(m)', '体重(kg)']]
pipe = Pipeline([('scaler', StandardScaler()),('rf', RandomForestClassifier(random_state=42))])
param_grid = {'rf__n_estimators': [50,80,100],'rf__max_depth': [2,3,4],'rf__min_samples_split': [2,3],'rf__min_samples_leaf': [1,2]}
rscv = RandomizedSearchCV(estimator=pipe,param_distributions=param_grid,n_iter=10,cv=5,scoring='accuracy',n_jobs=-1,random_state=42,verbose=0)
rscv.fit(X, y)
best_estimator = rscv.best_estimator_
# ========== ✅ 新增1:模型持久化-保存最优模型到本地 ✅
dump(best_estimator, '球员潜质预测最优模型.pkl')
print("✅ 模型已持久化保存为:球员潜质预测最优模型.pkl")
# ===== 万能前置:加载你的球探模型(所有玩法的基础,必须先运行) =====
import joblib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei'] # 解决中文乱码
plt.rcParams['axes.unicode_minus'] = False
# 加载你的最优球探模型
model = joblib.load("球员潜质预测最优模型.pkl")
# 取出标准化器+随机森林(部分玩法会用到)
scaler = model.named_steps['scaler']
rf = model.named_steps['rf']
# 特征名称(身高/臂展/体重),所有玩法统一用这个
feat_names = ['身高(m)', '臂展(m)', '体重(kg)']
- 读取之后 如何预测呢?
- 根据成为球员的可能性
- 分成S、A、B、C 四档
# ========== 球员潜质百分制评分==========
def get_player_score(player_data):
"""输入球员数据[[身高,臂展,体重]],返回:潜质等级+0-100分潜质评分"""
pred_proba = model.predict_proba(player_data)[0][1] # 取高潜质1类的概率
score = int(pred_proba * 100)
# 对应评分定潜质等级(球探通用分级)
if score >= 80:
level = "S级 【天赋炸裂,重点培养核心】"
elif score >= 65:
level = "A级 【高潜质,值得重点关注】"
elif score >= 50:
level = "B级 【有潜质,可观察培养】"
else:
level = "C级 【潜质普通,无培养价值】"
return level, score
# 测试:输入任意球员数据,立刻出评分
player = [1.95,2.05,92]
level, score = get_player_score([player])
print(f"{score}分 → {level}")
- 有一个warning
- X 没有合适的特征名字
- 可以 通过忽略 指定消息 来避免
import warnings
warnings.filterwarnings('ignore', message='X does not have valid feature names')
- 如果我想要给X 特征名字呢?
feat_names = ['身高(m)', '臂展(m)', '体重(kg)']
- 然后在加载数据帧的时候
player_data = pd.DataFrame(player_data, columns=feat_names)- 完整代码
# ===== 万能前置:加载你的球探模型(所有玩法的基础,必须先运行) ======
import joblib
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei'] # 解决中文乱码
plt.rcParams['axes.unicode_minus'] = False
# 加载你的最优球探模型
model = joblib.load("球员潜质预测最优模型.pkl")
# 取出标准化器+随机森林(部分玩法会用到)
scaler = model.named_steps['scaler']
rf = model.named_steps['rf']
# 特征名称(身高/臂展/体重),所有玩法统一用这个
feat_names = ['身高(m)', '臂展(m)', '体重(kg)']
# ========== 球员潜质百分制评分==========
def get_player_score(player_data):
"""输入球员数据[[身高,臂展,体重]],返回:潜质等级+0-100分潜质评分"""
# ========== ✅ 新增这一行:给X数据 补上特征名 核心代码 ✅ ==========
player_data = pd.DataFrame(player_data, columns=feat_names)
# ========== 原代码完全不变 ==========
pred_proba = model.predict_proba(player_data)[0][1] # 取高潜质1类的概率
score = int(pred_proba * 100)
# 对应评分定潜质等级(球探通用分级)
if score >= 80:
level = "S级 【天赋炸裂,重点培养核心】"
elif score >= 65:
level = "A级 【高潜质,值得重点关注】"
elif score >= 50:
level = "B级 【有潜质,可观察培养】"
else:
level = "C级 【潜质普通,无培养价值】"
return level, score
# 测试:输入任意球员数据,立刻出评分
player = [1.95,2.05,92]
level, score = get_player_score([player])
print(f"{score}分 → {level}")
- 模型不是已经在pipeline里
- 添加了标准化器了吗?
- 为什么后面用具体的数字还好使?
- 直接传「原始数字」能正常预测
- 正是因为 Pipeline 在「预测的一瞬间」
- 帮你【自动、静默、按顺序】
- 完成了「标准化」这个步骤
Pipeline 结构是固定的
Pipeline(steps=[
('scaler', StandardScaler()),
('rf', RandomForestClassifier())
])
- 训练的时候 用的也是这个流水线
- 预测的时候 用的也是这个流水线
- 那现在这种S、A、B、C的方式
- 还是分类问题吗?
- 原来是2分类
- 现在是4分类
- 从「输出 0/1」
- 到「输出 S/A/B/C」
- 把分类的颗粒度
- 从 2 档变成了 4 档
- 本质还是给球员「贴类别标签」
- 属于分类问题
| 对比维度 | 分类问题(你的S/A/B/C分级) | 回归问题(比如预测球员未来身高) |
|---|---|---|
| 预测目标 | 球员的「潜质等级」(S/A/B/C) | 球员成年后的「身高数值」(比如1.98m) |
| 输出本质 | 离散的类别标签 → 非黑即白,无中间态 | 连续的数值 → 可以是1.98/1.99/2.00m |
| 核心特点 | 互斥性:一个球员只能属于一个等级 | 连续性:数值之间有无数个中间值 |
| 你的模型例子 | 概率0.88 → 映射成S级(类别) | 输入17岁身高1.95m → 输出1.98m(数值) |
| 判断标准 | 看最终输出是不是「类别」 | 看最终输出是不是「连续数值」 |
- 预测中的球员潜质概率也是连续的
- 从0到1
- 可以理解为一种回归吗?
- 「分类模型的概率输出是连续的」≠「任务是回归」
- 核心区别在于
- 这个连续的概率值
- 到底是「工具」还是「最终目标」
- 核心区别在于
-
给球员贴
S/A/B/C这种离散、互斥的等级标签- 这是分类任务的核心标志
- 概率
0.88- → 你把它当成「分级依据」
- 映射成
S级(离散类别)
- 概率
0.70- → 映射成
A级(另一离散类别)
- → 映射成
-
这算是
- 基于概率的分类
-
如果这是回归任务
- 目标会变成什么?
- 如果任务是回归
- 那最终目标就会变成 直接预测
- 「这个球员的臂展概率具体是多少」
| 对比维度 | 你的分类模型(输出0~1概率) | 回归模型(输出1.98m身高) |
|---|---|---|
| 数值意义 | 「类别归属的置信度」 概率越高」 属于1类的可能性越大 |
「预测的目标本身」」 数值就是最终要的结果」 (身高、体重等) |
| 数值用途 | 用来判断「该归为哪一类」」 是分类的「中间判断依据」 |
直接作为任务结果」 是回归的「最终答案」 |
| 评估方式 | 看「类别判断对不对」」 (用准确率、F1值) |
看「数值预测准不准」」 (用MSE、MAE) |
| 任务本质 | 分类 | 回归 |
- 随机森林输出的
0~1概率- 和回归模型输出的
1.98m(臂展) - 虽然都是连续值
- 但本质意义天差地别
- 和回归模型输出的
- 回忆两种线性回归
- 一元
- 二元
- 一元
- 根据 身高 预测 臂展
- 根据 体重 预测 臂展
- 二元
- 根据 身高、体重 预测 臂展
- 除了线性回归之外
- 还有没有其他类型的回归呢?🤔
- 我们下次再说👋
- 本文来自 oeasy Python 系统教程。
- 想完整、扎实学 Python,
- 搜索 oeasy 即可。








