Skip to content

Latest commit

 

History

History
338 lines (268 loc) · 11.8 KB

File metadata and controls

338 lines (268 loc) · 11.8 KB
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(特征) $X$ 输入模型的量化数据(你的身高、臂展、体重数据)
Label(标签) $y$ 数据的目标结果(你的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分类

颗粒度(Granularity)

  • 从「输出 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 即可。