2026.09.16 11:16
学习路线
Python -> Python数据分析 -> LangChain -> LangGraph -> FastAPI + SQLAIchemy -> 机器学习 -> 神经网络 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding -> Hermes -> Docker
复习巩固总结
1. 随机梯度下降 SGD(Stochastic Gradient Descent)
每次迭代随机抽 1 个样本,只用这一条的梯度更新:
是这一步抽到的那个人。简单、高效,但不稳定:每步只看一条,碰上噪声样本容易被带偏
"""
案例:
演示:随机梯度下降法 线性回归对象 完成 波士顿房价预测案例
"""
# 导包
from sklearn.preprocessing import StandardScaler # 特征处理
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.linear_model import LinearRegression # 正规方程的回归模型
from sklearn.linear_model import SGDRegressor # 梯度下降的回归模型
from sklearn.metrics import mean_absolute_error, mean_squared_error, root_mean_squared_error # 均方误差评估
from sklearn.linear_model import Ridge, RidgeCV # 岭回归
import pandas as pd # 数据处理
import numpy as np # 数组处理
# 1. 加载波士顿房价数据
data_url = "https://raw.githubusercontent.com/Csengupta1101/Datasets/main/BostonHousing.csv"
raw_df = pd.read_csv(data_url, storage_options={"User-Agent": "Mozilla/5.0"})
data = raw_df.drop(columns=["medv"]).values # 前 13 列是特征
target = raw_df["medv"].values # 最后一列房价是标签
# 2. 数据的预处理,按照 8:2 切分 训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(data, target, test_size=0.2, random_state=23)
# 3. 特征工程(特征提取,特征预处理...)
# 3.1 创建标准化对象
transfer = StandardScaler()
# 3.2 对训练集进行标准化
x_train = transfer.fit_transform(x_train)
# 3.3 对测试集进行标准化
x_test = transfer.transform(x_test)
# 4. 模型训练
# estimator = LinearRegression(fit_intercept=True) # 正规方程法 线性回归对象
# 4.1 创建 线性回归 随机梯度下降 模型对象
# 参1:fit_intercept:是否计算截距(Bias,偏置),默认是True
# 参2:learning_rate:学习率模式 -> 常量,即:不会发生改变
# 参3:eta0:学习率
estimator = SGDRegressor(fit_intercept=True, learning_rate="constant", eta0=0.01)
# 4.2 模型训练
estimator.fit(x_train, y_train)
# 4.3 打印模型计算出的 w(权重,weight)和 b(偏置,bias)
print(f'权重:{estimator.coef_}')
print(f'偏置:{estimator.intercept_}')
# 5. 模型预测
y_pre = estimator.predict(x_test)
# 6. 模型评估
# MSE: 均方误差,每个样本的误差平方和 / 样本总数
print(f'均方误差:{mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
# RMSE: 均方根误差,均方误差的平方根
print(f'均方根误差:{root_mean_squared_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
# MAE: 平均绝对误差,每个样本的误差绝对值和 / 样本总数
print(f'平均绝对误差:{mean_absolute_error(y_test, y_pre)}') # 参1:测试集的真实标签,参2:测试集的预测标签
2. 解决过拟合
- L1 会让权重趋向 0,甚至等于 0。等于 0 的那些特征等于失效,所以 L1 能做特征筛选。
- L2 会让权重趋向 0,一般不等于 0(减弱影响,不删特征)。
2.1 L1正则化
:惩罚系数(sklearn 里叫 alpha)。越大,对特征权重罚得越狠,权重被往 0 拉的幅度越大。
使用 L1 的线性回归叫 Lasso:
from sklearn.linear_model import Lasso
# 4. 定义函数,模拟:L1正则化
def dm04_l1_regularization():
# ...
# 4. 训练模型
# 4.1 创建模型对象
# estimator = LinearRegression() # 正规方程 线性回归模型
# 改为创建 L1正则化对象
estimator = Lasso(alpha=0.1) # alpha:正则化系数(惩罚系数),默认是1
# ...
2.2 L2正则化
:同样是惩罚系数。越大,对特征权重罚得越狠。
使用 L2 的线性回归叫岭回归:
from sklearn.linear_model import Ridge
# 5. 定义函数,模拟:L2正则化
def dm05_l2_regularization():
# ...
# 4. 训练模型
# 4.1 创建模型对象
# estimator = Lasso(alpha=0.1) # alpha:正则化系数(惩罚系数),默认是1
# 改为创建 L2正则化对象
# estimator = Ridge(alpha=0.1)
estimator = Ridge(alpha=10)
# ...
3. 概率相关知识回顾
3.1 联合概率
两个或多个事件同时发生的概率,记 或 。
只有在相互独立时才能拆成相乘:
课件:周一早上、周二早上都堵。若两天相互独立、每天早上堵车都是 :
不独立就不能直接乘。比如「早上堵」和「中午堵」往往相关(早高峰堵了,中午更可能还堵), 不一定等于 。
3.2 条件概率
条件概率:在事件 已经发生的前提下,事件 再发生的概率:
分母是「已知那件」的概率,分子是「两件一起发生」。乘法公式反过来说就是 。
对照:
| 公式 | 课件那组数(先假设独立) | 含义 | |
|---|---|---|---|
| 联合 | 早上且中午都堵 | ||
| 条件 | 已知早上堵了,中午还堵 |
记法统一用 :竖线右边是已经知道的。
3.3 对数函数
若 ( 且 ),则 叫做以 为底 的对数:
例子:(因为 ),(因为 )。
三条运算性质
3.4 极大似然估计
核心思想:根据已经观测到的结果,反推模型里那个未知参数最像是多少。
未知参数 先「产生」出数据(人、样本、对话),再用这些数据去估计 。正向是「参数 → 数据」,极大似然是倒过来「数据 → 参数」。
硬币例子
一枚不均匀的硬币,正面概率记 ,反面就是 。抛 6 次,现象
4 正 2 反。每次投掷相互独立,所以整段序列的联合概率可以拆成相乘
是「参数取 时,碰巧看到这份数据的概率」。 未知,这份 已经发生了,于是把上式看成 的函数,叫似然函数:
问题变成:哪个 让 最大?——最能「解释」这 6 次结果的那个正面概率。
对 求导,令导数为 0:
给出三个根:
或 时,(数据里正、反都出现过,说「永远正面」或「永远反面」完全解释不了 )。真正的极大值在
和直觉一致:6 次里 4 次正面,频率就是 。伯努利试验的极大似然估计,就是样本里「成功」的比例。
似然 是连乘。样本一多, 这类数会下溢成 。对数能把乘法变加法,极大值的位置不变( 单调递增),所以实际都改成最大化 。
连乘变成两项相加,求导不再用乘法法则硬拆:
和直接对 求导的结果一样,步骤短一截。逻辑回归里每条样本一个 或 ,似然是 项连乘,训练时优化的就是这个对数似然(再取负变成损失去最小化)。
4. 逻辑回归
逻辑回归(Logistic Regression)是一种分类模型:把线性回归的输出,当作逻辑回归的输入。模型吐出来的是 之间的值(正类概率),不是房价那种连续标签。
逻辑回归 = 有监督学习,有特征、有标签,且标签是离散的(分类)= 适用于二分类。
Sigmoid 也叫 S 型函数,在神经网络里常当激活函数。线性回归吐出来的是任意实数,分类要的是 之间的概率,中间就靠它压。
- 先按线性回归算出一个实数(可正可负、可很大很小)
- 丢进 Sigmoid,压成 之间的概率
- 拿你写的阈值去切:比如 判 A(正样本),否则判 B(负样本)
逻辑回归是解决二分类问题的利器:输出只有两边,是或不是
4.1 Sigmoid 函数
这里是线性回归的预测值(,可正可负、可到 )。作用:把 映射到 。
几个常用点:
- 单调递增:线性预测越大,概率越大,不会扭回来
- 拐点在 :曲线在这儿从下凸换成上凸,也是默认阈值常取 的原因
- 两端饱和:离 0 很远时曲线几乎贴着 0 或 1,梯度很小
导数:
4.2 损失函数
线性回归用 MSE 看「差了多少」;逻辑回归输出的是概率,损失改成对数损失(二分类交叉熵)。它就是极大似然估计里似然取 再加负号:最大化「看到这些标签」的概率,等价于把这个损失压下去。
编码:(正样本),(负样本)。 是真实标签,预估的是 。
每个样本模型都给出两个数:(是 A 的概率)和 (是 B 的概率)。损失只盯真实那一类对应的那个概率:越大越好。外面的负号把「越大越好」翻成「越小越好」,才能当损失。
只能是 或 ,两项里永远只活一项:
| 真实 | 活下来的那项 | 含义 |
|---|---|---|
| (正样本) | 真实是 A,希望 靠近 | |
| (负样本) | 真实是 B,希望 靠近 |
例如:
| 特征 | 回归预测值 | 逻辑回归 | 预测(阈值 ) | 真实 |
|---|---|---|---|---|
| B | A | |||
| A | B | |||
| B | A | |||
| B | B | |||
| A | A |
| 代入 | 这一项 | |||
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 |
总损失是这些数求和再取负:
4.3 案例_癌症预测
正类要自己对上业务,一般将类别数量少的当做正例,但不要默认少数类就是正例。
"""
案例:
演示逻辑回归模型实现 癌症预测
逻辑回归模型介绍:
概述:
属于有监督学习,即:有特征,有标签,且表示离散的
主要适用于:二分类
原理:
把线性回归处理后的预测值 -> 通过 Sigmoid 激活函数,映射到 (0, 1) 概率 -> 基于自定义的阈值,结合概率来分类
损失函数:
极大似然估计函数的 负数形式
回顾:机器学习项目流程
1. 加载数据
2. 数据预处理
3. 特征工程(预处理...)
4. 模型训练
5. 模型预测
6. 模型评估
"""
# 导包
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression # 逻辑回归模型
from sklearn.preprocessing import StandardScaler # 标准化
from sklearn.model_selection import train_test_split # 训练集和测试集分割
from sklearn.metrics import accuracy_score # 模型评估
# 1. 加载数据
data_path = Path(__file__).resolve().parent / 'data' / 'breast-cancer-wisconsin.csv'
data = pd.read_csv(data_path)
data.info() # 查看数据信息
# 2. 数据预处理
# 2.1 把 ? 替换成 np.nan
# 参1:要被替换的值 参2:用来替换的值 参3:是否替换源数据,默认为False
data.replace('?', np.nan, inplace=True)
# 2.2 缺失值处理 -> 删除
# 参1:axis=0,表示删除包含缺失值的行 参2:inplace=True,表示替换源数据
data.dropna(axis=0, inplace=True)
# 2.3 打印处理后的信息
data.info()
# 3. 特征工程(提取,预处理...)
# 3.1 提取特征之提取特征和标签
x = data.iloc[:, 1:-1]
y = data.Class # 获取最后一列
# 3.2 查看下特征 和 标签
print(x.head())
print(y.head())
print(x.shape, y.shape)
# 3.3 切割训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
# 3.4 特征工程:标准化
# 3.4.1 创建标准化对象
transfer = StandardScaler()
# 3.4.2 对训练集进行标准化,训练 + 标准化
x_train = transfer.fit_transform(x_train)
# 3.4.3 对测试集进行标准化,标准化
x_test = transfer.transform(x_test)
# 4. 模型训练
# 4.1 创建模型对象 -> 逻辑回归模型
estimator = LogisticRegression()
# 4.2 训练模型
estimator.fit(x_train, y_train)
# 5. 模型预测
y_pre = estimator.predict(x_test)
print(f'预测值:{y_pre}')
# 6. 模型评估
# 正确率(准确率),公式为:预测对的 / 样本总数
print(f'预测前评估,正确率:{estimator.score(x_test, y_test)}') # 训练集的特征,标签
print(f'预测后评估,正确率:{accuracy_score(y_test, y_pre)}') # 测试集的标签,预测值
# 思考:逻辑回归模型能用准确率来评测吗?
# 答:可以,但是结果不精确,因为逻辑回归模型主要用于二分类,即:A类还是B类,不能说 97%的A类,3%的B类
# 所以要通过混淆矩阵来评估,即:精确率,召回率,F1值(F1-score),ROC曲线,AUC值
相关知识点:
solver: 怎么把交叉熵压下去(优化器),不是换模型。
| solver | 适合 | 正则 |
|---|---|---|
| liblinear | 小数据更快(坐标下降那一类) | L1、L2 |
| sag | 大数据更快(随机平均梯度,DAY9 的 SAG) | L2,或不要正则 |
| saga | 大数据,sag 的改进版 | L1、L2,或不要正则 |
penalty:'l1'或'l2',和岭回归 / Lasso 同一套:L2 把权重往 0 缩,L1 能缩到正好 0。C:正则强弱的倒数。损失里是 正则项。C越大,罚得越轻,越接近不正则(容易过拟合)C越小,罚得越狠,权重越扁
4.4 混淆矩阵
| 词 | 意思 |
|---|---|
| True | 真(预测对了) |
| False | 假 / 伪(预测错了) |
| Positive | 正例(模型说是正类) |
| Negative | 反例(模型说是负类) |
| 预测值(正例) | 预测值(反例) | |
|---|---|---|
| 真实值(正例) | 真正例 TP(True Positive) | 伪反例 FN(False Negative) |
| 真实值(假,反例) | 伪正例 FP(False Positive) | 真反例 TN(True Negative) |
- TP:真是正的,也预测成正的
- FN:真是正的,却预测成负的(漏检)
- FP:真是负的,却预测成正的(误报)
- TN:真是负的,也预测成负的
4.5 精确率/召回率/F1-score
4.5.1 精确率 Precision
也叫查准率:在模型喊出来的正例里,有多少真是正例。癌症里恶性当正例,问的就是——报成恶性的那些人里,到底几个真是恶性。看混淆矩阵预测正例那一列(TP 和 FP),和 FN、TN 无关:
4.5.2 召回率 Recall
也叫查全率:所有真实正例里,被找出来的占多少。恶性当正例时问的是——病人里有没有漏掉的,能不能把恶性都报出来。
看混淆矩阵真实正例那一行(TP 和 FN),和 FP、TN 无关:
4.5.3 F1-score
精确率和召回率都要看、又经常对着干时,用一个数反映综合能力:F1。它是两者的调和平均(不是算术平均 ):
调和平均的脾气:有一个很低,F1 就被拖下去;两个都高,F1 才高。一边 100%、一边 0%,F1 是 0——不会出现「平均 50% 还不错」的错觉。
4.5.4 举个栗子
10 个样本:6 个恶性、4 个良性,恶性当正例。恒等式:。真实正例一行必须加起来是 6,真实反例一行必须是 4。
模型 A:恶性对了 3 个,良性对了 4 个。
6 个恶性里只找对 3 个 → ,剩下 3 个漏掉 → 。4 个良性全对 → ,没有把良性说成恶性 → 。
| 预测正例 | 预测假例 | |
|---|---|---|
| 真实正例(6 恶) | TP | FN |
| 真实假例(4 良) | FP | TN |
查出来的全是真恶性(精确率 1),但一半恶性漏了(召回率 0.5)。
4.5.5 代码实现
"""
案例:
演示混淆矩阵,精确率,召回率,F1值
回顾:逻辑回归
概述:
属于有监督学习,即:有特征,有标签,且标签是离散的
适用于二分类
评估:
精确率,召回率,F1值
混淆矩阵:
概述:
用来描述 真实值 和 预测值 之间关系的
图解:
预测标签(正例) 预测标签(反例)
真实标签(正例) 真正例(TP) 伪反例(FN)
真实标签(反例) 伪正例(FP) 真反例(TN)
单词:
True:真
False:假
Positive:正例
Negative:反例
结论:
1. 模型使用 分类少的 充当正例
2. 精确率 = 真正例 在 预测正例中的占比,即:TP / (TP + FP)
3. 召回率 = 真正例 在 真实正例中的占比,即:TP / (TP + FN)
4. F1值 = 2 * (精确率 * 召回率) / (精确率 + 召回率)
"""
# 导包
import pandas as pd
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score # 混淆矩阵, 精确率, 召回率, F1值
# 需求:已知有10个样本,6个恶性肿瘤(正例),4个良性肿瘤(反例)
# 模型A预测结果为:预测对了3个恶性肿瘤,预测对了4个良性肿瘤
# 模型B预测结果为:预测对了6个恶性肿瘤,预测对了1个良性肿瘤
# 请针对于上述的数据集,搭建混淆矩阵,分别计算模型A,模型B的 精确率,召回率,F1值
# 1. 定义变量,记录:样本数据
y_train = ['恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '良性', '良性', '良性', '良性']
# 2. 定义变量,记录:模型A的预测结果
y_pre_A = ['恶性', '恶性', '恶性', '良性', '良性', '良性', '良性', '良性', '良性', '良性']
# 3. 定义变量,记录:模型B的预测结果
y_pre_B = ['恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '恶性', '良性']
# 4. 用标签标记 正例,反例
label = ['恶性', '良性']
df_label = ['恶性(正例)', '良性(反例)']
# 5. 针对于 真实值(y_train)和 模型A的预测结果(y_pre_A),搭建混淆矩阵
cm_A = confusion_matrix(y_train, y_pre_A, labels=label)
print(f'模型A的混淆矩阵:\n{cm_A}')
# 6. 为了测试结果更好看,把上述的混淆矩阵转换成 DataFrame
df_A = pd.DataFrame(cm_A, index=df_label, columns=df_label)
print(f'模型A的混淆矩阵的 DataFrame 形式:\n{df_A}')
# 7. 针对于 真实值(y_train)和 模型B的预测结果(y_pre_B),搭建混淆矩阵
cm_B = confusion_matrix(y_train, y_pre_B, labels=label)
print(f'模型B的混淆矩阵:\n{cm_B}')
# 8. 为了测试结果更好看,把上述的混淆矩阵转换成 DataFrame
df_B = pd.DataFrame(cm_B, index=df_label, columns=df_label)
print(f'模型B的混淆矩阵的 DataFrame 形式:\n{df_B}')
# 9. 计算A模型的 精确率,召回率,F1值
# 参1:真实值 参2:预测值 参3:正例的标签
print(f'模型A的精确率:{precision_score(y_train, y_pre_A, pos_label='恶性')}')
# 参1:真实值 参2:预测值 参3:正例的标签
print(f'模型A的召回率:{recall_score(y_train, y_pre_A, pos_label='恶性')}')
# 参1:真实值 参2:预测值 参3:正例的标签
print(f'模型A的F1值:{f1_score(y_train, y_pre_A, pos_label='恶性')}')
# 10. 计算B模型的 精确率,召回率,F1值
print(f'模型B的精确率:{precision_score(y_train, y_pre_B, pos_label='恶性')}')
print(f'模型B的召回率:{recall_score(y_train, y_pre_B, pos_label='恶性')}')
print(f'模型B的F1值:{f1_score(y_train, y_pre_B, pos_label='恶性')}')
4.6 案例_电信用户流失预测
"""
案例:
通过逻辑回归算法,针对于电信用户数据建模,进行流失预测分析
"""
# 导包
import numpy as np
import pandas as pd
from pathlib import Path
# 1. 定义函数,演示:数据的预处理
def dm01_data_preprocess():
# pass
# 1. 读取csv文件,获取到df对象
data_path = Path(__file__).resolve().parent / 'data' / 'churn.csv'
churn_df = pd.read_csv(data_path)
# 2. 查看(处理前)数据集
# churn_df.info()
# print(churn_df.head())
# 3. 因为 Churn 和 gender 列是字符串,所以需要进行one-hot编码(热编码处理)
churn_df = pd.get_dummies(churn_df, columns=['Churn', 'gender'])
# 4. 查看(处理后)的数据集
# churn_df.info()
# print(churn_df.head())
# 5. 删除one-hot编码后,冗余的列
# 参1:要删除的列,参2:axis=1,表示删除列,参3:inplace=True,表示直接修改原数据
churn_df.drop(['Churn_No', 'gender_Male'], axis=1, inplace=True)
# 6. 修改列名,将Churn_Yes -> flag,充当标签列
churn_df.rename(columns={'Churn_Yes': 'flag'}, inplace=True)
churn_df.info()
print(churn_df.head()) # False -> 不流失,True -> 流失
# 7. 查案数据值的分布
print(churn_df['flag'].value_counts()) # False: 5174, True: 1869
# 2. 定义函数,演示:数据的可视化
# 3. 定义函数,演示:逻辑回归算法的模型训练,预测,评估
# 4. 测试
if __name__ == '__main__':
dm01_data_preprocess()
机器学习
63. 逻辑回归_电信用户流失预测_数据可视化
# 2. 定义函数,演示:数据的可视化
def dm02_data_visualization():
# 1. 读取csv文件,获取到df对象
churn_df = pd.read_csv(Path(__file__).resolve().parent / 'data' / 'churn.csv')
# 2. 对object类型的列(数据)做 one-hot 编码处理
churn_df = pd.get_dummies(churn_df, columns=['Churn', 'gender'])
# 3. 删除one-hot编码后,冗余的列
churn_df.drop(['Churn_No', 'gender_Male'], axis=1, inplace=True)
# 4. 修改列名,将Churn_Yes -> flag,充当标签列
churn_df.rename(columns={'Churn_Yes': 'flag'}, inplace=True)
# 6. 查看数据值的分布
print(churn_df['flag'].value_counts())
# 7. 查看列名,方便我们一会儿抽取特征
"""
Index(['Partner_att', 'Dependents_att', 'landline', 'internet_att',
'internet_other', 'StreamingTV', 'StreamingMovies', 'Contract_Month',
'Contract_1YR', 'PaymentBank', 'PaymentCreditcard', 'PaymentElectronic',
'MonthlyCharges', 'TotalCharges', 'flag', 'gender_Female'],
dtype='object')
"""
print(churn_df.columns)
# 7. 数据的可视化,绘制计数柱状图
# 参1:数据集;参2:x的列名(月度会员);参3:hue表示分组,根据分组进行绘制,这里是:是否流失(False -> 不流失,True -> 流失)
sns.countplot(data=churn_df, x='Contract_Month', hue='flag')
plt.show()
64. 模型训练预测评估
# 3. 定义函数,演示:逻辑回归算法的模型训练,预测,评估
def dm03_logistic_regression():
# 1. 加载数据集
churn_df = pd.read_csv(Path(__file__).resolve().parent / 'data' / 'churn.csv')
# 2. 数据的预处理
# 2.1 对object类型的列(数据)做 one-hot 编码处理
churn_df = pd.get_dummies(churn_df, columns=['Churn', 'gender'])
# 2.2 删除one-hot编码后,冗余的列
churn_df.drop(['Churn_No', 'gender_Male'], axis=1, inplace=True)
# 2.3 修改列名,将Churn_Yes -> flag,充当标签列
churn_df.rename(columns={'Churn_Yes': 'flag'}, inplace=True)
# 2.4 提取特征列和标签列
# x的特征列:月度会员,是否有互联网服务,是否是电子支付
x = churn_df[['Contract_Month', 'internet_other', 'PaymentElectronic']]
y = churn_df['flag'] # False -> 不流失,True -> 流失
# 2.5 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=23)
# 3. 特征工程(例如:特征提取,特征预处理 -> 归一化,标准化,...),暂不处理
# 4. 模型训练
# 4.1 创建(逻辑回归)模型对象
estimator = LogisticRegression()
# 4.2 模型训练
estimator.fit(x_train, y_train)
# 5. 模型预测
y_pre = estimator.predict(x_test)
print(f"预测值:{y_pre}")
# 6. 模型评估
print(f"准确率:{estimator.score(x_test, y_test)}") # 预测前 0.7679205110007097
print(f"准确率:{accuracy_score(y_test, y_pre)}") # 预测后 0.7679205110007097
print(f"精确率:{precision_score(y_test, y_pre)}") # 0.580769230769230
print(f"召回率:{recall_score(y_test, y_pre)}") # 0.4092140921409214
print(f"F1值:{f1_score(y_test, y_pre)}") # 0.48012718600953896
# macro avg:宏平均,即:不考虑样本权重,直接求平均,适用于:数据均衡的情况
# weighted avg:样本权重平均,即:考虑样本权重,求平均,适用于:数据不均衡的情况
print(f"分类评估报告:\n{classification_report(y_test, y_pre)}")
拓展QA:
问:classification_report 里的 macro avg 和 weighted avg 都是怎么算的?
报告每一行先算好这一类当正例时的精确率、召回率、F1,两行平均才得到 avg。本次测试集:
| precision | recall | f1-score | support | |
|---|---|---|---|---|
| False(不流失) | 0.81 | 0.90 | 0.85 | 1040 |
| True(流失) | 0.58 | 0.41 | 0.48 | 369 |
| 合计 | 1409 |
precision_score / recall_score / f1_score 默认只报 True 那一行(0.58 / 0.41 / 0.48)。
macro avg:两类各一份,简单平均,不管谁人多。 适合类别比较均衡时。
F1 的 macro 是两个 F1 再平均,不是先平均 P、R 再套 F1 公式。
weighted avg:按 support 加权。 不均衡时多数类会把分数拉高。
加权召回率一定等于准确率(这里都是 0.77)。流失是少数类,看「流失抓得好不好」盯 True 那一行或 macro,别只看 weighted。
65. 决策树_简介
65.1 大纲
把 ID3 / C4.5 绑在一起:都用信息熵系选特征;CART 另走 Gini(基尼值)。后面是分类案例、回归树、剪枝。
决策树简介
├── ID3 / C4.5(信息熵系)
│ ├── ID3决策树 ── 信息增益
│ └── C4.5决策树 ── 信息增益率
├── CART决策树 ── Gini,基尼值
├── 案例:泰坦尼克号生存预测
├── CART回归树
└── 决策树剪枝
65.2 生活中的决策树
决策树就是一串 if-else:内部节点是问特征,叶子是给结论。从根往下走,每条边一个取值,走到叶子就是预测。
例子:女孩相亲。先问年龄,再问长相、收入、是不是公务员,最后只剩「见」或「不见」。
年龄
├── <=30 → 长相
│ ├── 帅或中等 → 收入
│ │ ├── 高 → 见
│ │ ├── 中等 → 是否公务员
│ │ │ ├── 是 → 见
│ │ │ └── 不是 → 不见
│ │ └── 低 → 不见
│ └── 丑 → 不见
└── >30 → 不见
在树上走一遍:26 岁(≤30)→ 挺帅 → 收入中等 → 是公务员(税务局)→ 见。年龄 >30、长相丑、收入低,这三处直接「不见」,后面的特征根本不问。
下表三个人走一遍:
| 年龄 | 长相 | 收入 | 是否公务员 | 怎么走 | 预测值 |
|---|---|---|---|---|---|
| 28 | 帅 | 高 | 否 | 28≤30 → 帅 → 收入高 | 见 |
| 30 | 丑 | 高 | 是 | 30≤30 → 丑,后面不问 | 不见 |
| 39 | 帅 | 中等 | 否 | 39>30,后面不问 | 不见 |
第二、三行收入再高、是不是公务员都没用:树在前面已经剪掉这条路了。机器学习里的决策树就是自动学出「先问谁、怎么分叉」。
65.3 树形结构和建立过程
决策树是一种树形结构。相亲那棵树对上三个名词:
| 名词 | 是什么 | 例子 |
|---|---|---|
| 内部节点 | 某个特征上的判断 | 年龄、长相、收入、是否公务员 |
| 分支 | 一次判断结果的出口 | ≤30 / >30、帅或中等 / 丑、高 / 中等 / 低、是 / 不是 |
| 叶子节点 | 一种分类结果 | 见、不见 |
从根走到叶子 = 对一条样本做完分类。
建立过程三步:
- 特征选择:先选分类能力强的特征当根、当内部节点。相亲树把「年龄」放最上面,因为 >30 直接不见,这一刀切得干净。后面 ID3 用信息增益、C4.5 用信息增益率、CART 用基尼值,都是在比「谁分类更强」。
- 决策树生成:按选中的特征一层层分叉,直到叶子能给出类别。
- 剪枝:树太深、问题问得太细,容易把训练集噪声也学进去(过拟合)。剪掉一些枝,让树简单一点,泛化更好。
66. 信息熵简介
建树第一步要选分类能力强的特征。ID3 用的尺子是信息熵:这堆样本的标签有多乱。越乱熵越大,切一刀之后熵掉得越多,这个特征越好。
66.1 熵是什么
熵 Entropy:信息论里对随机变量不确定度的度量。
- 熵越大:不确定度越高,平均要带的信息越多
- 熵越小:越确定。全是同一类时熵为 (纯)
决策树要的是:切完以后子节点尽量纯,熵尽量小。
66.2 公式
是第 类出现的概率(占比)。 所以单位是 bit。全 类等概率时熵最大,等于 。
有几类就加几项即可。约定:(纯节点里没有的那一类不贡献熵)。
66.3 栗子:字符串 /
把字母当类别,8 个字符算占比。
栗子 1-1 ABCDEFGH,8 种各 :
栗子 1-2 AAAABBCD → A 四个、B 两个、C / D 各一个:
8 种还均匀,; 只有 4 种且 A 占一半,。更乱的熵更大。
67. 信息增益计算
熵只说「这堆有多乱」。信息增益说:用特征 切一刀,乱的程度掉了多少。掉得越多,这个特征越适合当节点。ID3 就挑增益最大的那个。
67.1 概念和公式
特征 对训练集 的信息增益:
是切之前整堆标签的熵; 是已知 取了哪个值之后,各子集熵再按样本占比加权平均。差就是「这一刀买到的确定度」。
67.2 条件熵
有 个取值,第 个取值对应子集 (里面 条),该子集标签熵仍用 66.2:
条件熵 = 各分支样本占比 × 该分支信息熵,再相加。
展开 时别漏负号:
:子集 里第 类有几条。
67.3 六个样本走一遍
| 特征 | 目标值 |
|---|---|
| A | |
| A | |
| B | |
| A | |
| B | |
| B |
根上 3A + 3B。按 劈开: 枝 AAAB(3A 1B,4 条), 枝 BB(2B,2 条)。
3A 3B
/ \
α/ \β
3A 1B 2B
1. 信息熵 两类各 :
2. 条件熵
枝 是 A、 是 B:
枝纯 B:
占 , 占 :
3. 信息增益
枝已经纯了(熵 0),乱只剩 那 4 条里混进来的 1 个 B。增益 就是这一刀从「对半乱」收到的确定度。
68. ID3决策树_搭建
68.1 ID3 构建流程
- 算每个特征的信息增益
- 用增益最大的那个特征把数据集拆成子集
- 该特征当作当前节点
- 子集上用剩下的特征重复 1~3
直到子集纯了、没特征了、或增益太小,就停成叶子。
68.2 论坛流失:性别 vs 活跃度
某个论坛 15 条客户,问:性别、活跃度哪个对流失影响更大(谁当根节点)。is_lost=1 流失(正样本 5 条),=0 未流失(负样本 10 条)。
| uin | gender | act_info | is_lost |
|---|---|---|---|
| 1 | 男 | 高 | 0 |
| 2 | 女 | 中 | 0 |
| 3 | 男 | 低 | 1 |
| 4 | 女 | 高 | 0 |
| 5 | 男 | 高 | 0 |
| 6 | 男 | 中 | 0 |
| 7 | 男 | 中 | 1 |
| 8 | 女 | 中 | 0 |
| 9 | 女 | 低 | 1 |
| 10 | 女 | 中 | 0 |
| 11 | 女 | 高 | 0 |
| 12 | 男 | 低 | 1 |
| 13 | 女 | 低 | 1 |
| 14 | 男 | 高 | 0 |
| 15 | 男 | 高 | 0 |
先数格子(后面加权用):
| 流失 1 | 未流失 0 | 合计 | |
|---|---|---|---|
| 整体 | 5 | 10 | 15 |
| 男 | 3 | 5 | 8 |
| 女 | 2 | 5 | 7 |
| 活跃度高 | 0 | 6 | 6 |
| 活跃度中 | 1 | 4 | 5 |
| 活跃度低 | 4 | 0 | 4 |
68.2.1 整堆熵
68.2.2 性别的信息增益
男 8 条、女 7 条:
性别几乎切不开:男、女两边都还是「少数流失、多数留下」。
68.2.3 活跃度的信息增益
高 6 条全是 0(纯),低 4 条全是 1(纯),只有「中」5 条里 1 流失 4 未流失:
68.2.4 比较、当根、长树
,活跃度对流失影响比性别大,根节点用活跃度。低 / 高已经纯,直接当叶子;只有「中」还要再问性别。
「中」这 5 条:
| uin | gender | is_lost |
|---|---|---|
| 2 | 女 | 0 |
| 8 | 女 | 0 |
| 10 | 女 | 0 |
| 6 | 男 | 0 |
| 7 | 男 | 1 |
女 3 条全是 0;男 2 条 1:1。课件右边那个空圈就是 中 → 男,不是漏画「女」。
活跃度
├── 低 → 流失 (1) ← 4 条全是 1,纯
├── 中 → 性别 ← 1 流失 / 4 未流失,继续切
│ ├── 女 → 非流失 (0) ← 3 条全是 0,纯
│ └── 男 → 不纯 ← uin6=0、uin7=1,没特征了
└── 高 → 非流失 (0) ← 6 条全是 0,纯
多数类也打不成(1:1)。这就是两个特征能搭完的整棵 ID3。
68.3 练习
1. 下列关于决策树的概念描述错误的是?
- A)决策树算法需要构建树结构
- B)决策树上的每一个节点代表一个判断条件
- C)决策树上的每一个叶节点代表一种分类结果
- D)通过决策树不能明确特征的重要性程度
答案:D。
A、C 就是 65.3 原话:要搭树,叶子给类别。B 把「节点」说成判断条件——有 C 把叶节点单列时,这里的节点多半指内部节点(根、分叉),和课件「内部节点 = 特征上的判断」一致;真把叶子也算进去,B 才不严谨。D 反了:ID3 用信息增益比谁重要,68.2 活跃度增益远大于性别,就是在明确重要性。
2. 下列关于熵和信息熵的描述错误的是?
- A)熵越大,系统的混乱程度越小
- B)信息熵是用来描述信息的完整性和有序性的
- C)信息的有序状态越一致、数据越集中,信息熵越小,反之越大
答案:A。
66.1:熵越大越乱、越不确定;越小越确定。A 把大小说反了。C 就是这件事:越集中越纯,熵越小。B 用「完整性」不标准,熵量的是不确定度;「有序」和 C 对得上,不是这题要抓的错。
3. 下列关于信息增益的描述正确的是?(多选)
- A)表达的是在用某个特征对数据集进行分裂
- B)是 ID3 算法中的核心
- C)消除的不确定性越大,信息增益越小,表示这个特征越不重要
- D)对类别数较多的特征比较青睐
答案:ABD。
- A:67.1 增益就是「用特征 切一刀,熵掉了多少」
- B:67.4 / 68.1 每次拿增益最大的特征当节点
- C 反了:消除的不确定度越大 → 增益越大 → 特征越重要
- D:取值特别多的特征(极端:每条样本一个 ID)一切就纯,增益虚高。这是 ID3 的毛病,所以大纲里 C4.5 改用信息增益率压这个偏好
69. C4.5树_信息增益率
69.1 ID3 的不足
ID3 只看信息增益,会偏向取值种类多的特征。68.3 第 3 题 D 就是这句话。极端情况:特征几乎是每条样本一个 ID,一切全纯,增益虚高,树却学不到能泛化的规则。
六个样本:
| 特征 | 特征 | 目标值 |
|---|---|---|
| 1 | A | |
| 2 | A | |
| 3 | B | |
| 4 | A | |
| 5 | B | |
| 6 | B |
只有 2 个取值(), 有 6 个取值(1~6,一条一个)。
按 切:就是 67.3 那棵, 枝 3A1B 还不纯,增益 。
按 切:六个取值各一条,每条都纯,条件熵 ,增益 ,比 大,ID3 会选 当根:
根(按 b 切)
/ / / \ \ \
1 2 4 3 5 6
└─┬─┘ └───┬───┘
目标值 A 目标值 B
1、2、4 → A;3、5、6 → B。训练集上几乎全对。
- 用它分裂,会得到一棵深度为 2 的树(根 + 叶子),训练集准确率可以非常高
- 整棵树过于依赖这一个特征,新来一个没见过的编号就不会了 → 过拟合
C4.5 就是冲着这个毛病来的:不用裸增益,改用信息增益率,把「取值多」这件事惩罚掉。
69.2 信息增益率
叫固有值 / 特征熵 / split information:把特征 的取值分布当随机变量,算它的熵(看的是枝有多碎,不是标签乱不乱):
取值越多、分得越碎, 越大(一条一个编号时 )。
取值个数多 → 大 → 小 → 乘上去增益率被压;取值少则反过来。本质就是:增益 特征的内在信息,给裸增益加一个惩罚。
69.3 六个样本走一遍
同一张表。67.3 已经算过 、。这里补特征熵,再除出增益率。
看的是特征取值碎不碎,不是标签乱不乱。
特征
4 条、 2 条。
- 信息增益(67.3):,条件熵 ,增益
- 特征熵:
- 增益率:
特征
1~6 各 1 条,每枝都纯,条件熵 。
- 信息增益:(ID3 会因此选 )
- 特征熵:六条取值均匀,就是 :
- 增益率:
,C4.5 选 特征 当分裂特征。 裸增益最大,但枝太碎, 把增益率压下去了。
69.4 练习
1. 下列关于信息增益率的说法错误的是?
- A. 能有效缓解信息增益所带来的弊端
- B. 是在信息增益的基础上除以当前特征的固有值
- C. 是 C4.5 算法中的核心
- D. 倾向于选择取值多的特征
答案:D
2. 在机器学习中,信息增益率是用来衡量什么的?
- A. 属性对分类的贡献
- B. 分类的准确度
- C. 样本的数量
- D. 计算机存储空间的使用率
答案:A
增益率是选特征的尺子,量的是这一刀对分类有多大用,不是准确率、条数或占磁盘。