在职前端Leader学习/转行 AI Agent -DAY55

9 阅读5分钟

2026.09.15 09:56

学习路线

Python -> Python数据分析 -> LangChain -> LangGraph -> FastAPI + SQLAIchemy -> 机器学习 -> 神经网络 -> NLP -> Coze -> Dify -> 大模型应用基础 -> 大模型微调 -> 多模态 -> vibeCoding -> Hermes -> Docker

复习巩固总结

1. 回归模型_评估方法

公式要点单位特点
MAE绝对误差的平均yy 相同不容易被个别离谱点拉爆
MSE平方误差的平均yy 的平方惩罚大误差,训练常用
RMSEMSE\sqrt{\text{MSE}}yy 相同评估时比 MSE 好看懂,但对异常点更敏感

MAE 和 RMSE 都能反映真实误差,但不能说 RMSE 更好,也不该只盯着 RMSE 把误差压下去:

  • 若训练时只把 RMSE 压得很低,说明模型把异常点(噪声)也拟合得很紧
  • 这样很容易过拟合
  • 所以评估要几个指标一起看,不要只信一个数

代码示例如 2.2

2. 梯度下降法

2.1 算法分类

2.1.1 随机梯度下降 SGD(Stochastic Gradient Descent)

每次迭代随机抽 1 个样本,只用这一条的梯度更新:

θjθjα(hθ(x(i))y(i))xj(i)\theta_j\leftarrow\theta_j-\alpha\bigl(h_\theta(x^{(i)})-y^{(i)}\bigr)\,x^{(i)}_j

ii 是这一步抽到的那个人。简单、高效,但不稳定:每步只看一条,碰上噪声样本容易被带偏

2.1.2 对比
算法每步用几个样本优点缺点
FGD全部 mm方向准。§43 手算的就是这个数据大就慢
SGD随机 11简单、快。SGDRegressor不稳定,噪声样本会带偏
mini-batch随机 bb 个(1<b<m1<b<m快和稳折中,目前用得最多要选 bb
SAG11 个重算,用历史梯度均值更新后期接近 FGD 的稳、又省扫描初期慢(梯度先当 00,还掺旧值)

2.2 多元线性回归 - 使用梯度下降SGDRegressor

h(w)=w1x1+w2x2+w3x3++b=wTx+bh(w) = w_1x_1 + w_2x_2 + w_3x_3 + \cdots + b = w^T x + b

"""
案例:
    演示:随机梯度下降法 线性回归对象 完成 波士顿房价预测案例

回顾:
    线性回归算法 属于 有监督学习之 有特征,有标签,且标签是连续的
    线性回归分类:
        一元线性回归:1个特征列,1个标签列
        多元线性回归:多个特征列,1个标签列
    线性回归大白话解释:
        它是用线性公式来描述 特征 和 标签 之间的关系的,方便做预测,公式如下:
        一元线性回归:y = w * x + b
        多元线性回归:y = w1 * x1 + w2 * x2 + ... + wn * xn + b = w的转置 * x + b
    如何衡量线性回归模型的好坏?
        思路:
            预测值和真实值之间的误差,误差越小,模型越好 => 损失函数
        具体的方案:
            1. 最小二乘法:         每个(样本)误差平方和
            2. 均方误差(MSE)      每个(样本)误差平方和 / 样本总数
            3. 均方根误差(RMSE)   每个(样本)误差平方和 / 样本总数 的 平方根
            4. 平均绝对误差(MAE)  每个(样本)误差绝对值和 / 样本总数
    如何让损失函数最小?
        思路1:梯度下降法
            全梯度下降(Full Gradient Descent,FGD)
            随机梯度下降(Stochastic Gradient Descent,SGD)
            小批量梯度下降(推荐,Mini-batch Gradient Descent,MBGD)
            随机平均梯度下降(Stochastic Average Gradient Descent,SAG)
        思路2:正规方程法
    机器学习开发流程:
        1. 加载数据
        2. 数据的预处理
        3. 特征工程(特征提取,特征预处理...)
        4. 模型训练
        5. 模型预测
        6. 模型评估
"""
# 导包
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.3 一元线性回归 - 使用正规方程LinearRegression

Y=kX+bY = kX + b

# 导包
from sklearn.linear_model import LinearRegression

# 案例:演示线性回归API入门

# 1. 准备数据
x_train = [[160], [166], [172], [174], [180]]  # 训练集的特征
y_train = [56.3, 60.6, 65.1, 68.5, 75]         # 训练集的标签
x_test = [[176]]                               # 测试集的特征

# 2. 数据的预处理,这里不需要
# 3. 特征工程(特征提取,特征预处理),这里不需要

# 4. 模型训练
# 4.1 创建模型对象
estimator = LinearRegression()
# 4.2 具体的训练动作
estimator.fit(x_train, y_train)
# 4.3 因为是线性回归模型,我们可以查看下:斜率(w,权重),截距(b,偏置)
print(f'斜率(w,权重):{estimator.coef_},截距(b,偏置):{estimator.intercept_}')
# 0.92942177  -93.27346938775517

# 5. 模型评估
y_pre = estimator.predict(x_test)
print(f'模型预测:{y_pre}')  # 70.3047619
# 6. 模型评估

2.4 正规方程 vs 梯度下降

梯度下降正规方程
学习率要选 α\alpha不需要
怎么求迭代多步一次算完
特征很多 / 数据很大能用,深度学习主要靠它求逆太慢或没逆
噪声、特征相关仍能走(SGD 还会抖)容易被带偏,相关时可能没逆
适用损失各种可微损失基本是线性最小二乘
典型场景嘈杂、大数据、深度模型小数据、要精确的线性回归

3. 拟合

情况训练集测试集曲线长什么样
欠拟合 under-fitting很差很差直线硬切弯数据,趋势都没学到
正好拟合 Just right平滑曲线跟着大趋势走,不追每个点
过拟合 over-fitting很好很差折线穿过几乎每个点,把噪声也记住了

3.1 欠拟合

3.1.1 代码模拟
# 1. 定义函数,模拟:欠拟合
def dm01_under_fitting():
    # pass
    # 1. 准备数据
    # 1.1 指定随机种子,则每次生成(噪声)的数据都是固定的
    np.random.seed(23)

    # 1.2. 随机生成x轴 100 个数据,模拟:特征
    # 参1:x轴最小值  参2:x轴最大值  参3:生成个数
    x = np.random.uniform(-3, 3, size=100)

    # 1.3. 基于x轴值,通过线性公式,生成y轴 100 个数据,模拟:标签
    # 线性公式:y = kx + b = 0.5 * x ** 2 + x + 2 + 噪声
    # 参1:平均值  参2:标准差  参3:生成个数
    y = 0.5 * x ** 2 + x + 2 + np.random.normal(0, 1, size=100)

    # 1.4 查看生成的 x轴(特征)和 y轴(标签)的数据
    # print(f'x轴(特征):{x}')
    # print(f'y轴(标签):{y}')

    # 2. 数据预处理,把x轴(特征)转成 多行1列的形式
    X = x.reshape(-1, 1)
    # print(f'处理后的特征:{X}')

    # 3. 特征工程,这里不做了,直接用100条数据,先训练,后预测

    # 4. 训练模型
    # 4.1 创建模型对象
    estimator = LinearRegression()  # 正规方程 线性回归模型
    # 4.2 模型训练
    estimator.fit(X, y)             # 参1:处理后的特征数据  参2:标签数据

    # 5. 模型预测
    y_pre = estimator.predict(X)    # 处理后的特征数据

    # 6. 模型评估
    print(f'均方误差:{mean_squared_error(y, y_pre)}')        # 参1:真实值,参2:预测值

    # 7. 绘图
    plt.scatter(x, y)                # 以散点图的形式绘制 真实值
    plt.plot(x, y_pre, color='red')  # 以线图的形式绘制 预测值
    plt.show()
3.1.2 解决办法
  • 组合:把已有特征拼在一起
  • 泛化:把具体值做成更宽的表征
  • 相关性:把和目标相关、但还没进模型的特征加进来
# 2. 定义函数,模拟:恰好拟合
# def dm01_under_fitting():
def dm02_just_fitting():
    # ...

    # 2. 数据预处理,把x轴(特征)转成 多行1列的形式
    # 2.1 把上述的x轴(特征)转成 多行1列的形式,即:[[1], [2], [3], ..., [100]]
    X = x.reshape(-1, 1)
    # print(f'处理后的特征:{X}')

    # 2.2 因为目前特征列只有1列,模型过于简单,会出现欠拟合的问题,我们增加1列 特征列,从而增加模型的复杂度
    # 即:把数据从 [[1], [2], [3], ..., [100]] => [[1, 1], [2, 4], [3, 9], ..., [100, 10000]]
    X2 = np.hstack([X, X ** 2])  # 该函数作用:横向拼接,即:拼接2个数组,拼接后数组的行数不变,拼接后数组的列数等于拼接前的列数之和

    # ...

    # 7. 绘图
    plt.scatter(x, y)                  # 以散点图的形式绘制 真实值
    # np.sort(x):对x轴(特征)进行排序,默认是:升序
    # np.argsort(x):对x轴(特征)进行排序,返回的是排序后的索引
    # 例如:排序前x轴是 [11, 33, 22] -> 对应索引:[0, 1, 2]
    # 排序后:x轴是 [11, 22, 33] -> 对应索引:[0, 2, 1]
    # plt.plot(x, y_pre, color='red')  # 以线图的形式绘制 预测值
    plt.plot(np.sort(x), y_pre[np.argsort(x)], color='red')
    plt.show() 

3.2 过拟合

3.2.1 代码模拟
# 3. 定义函数,模拟:过拟合
def dm03_over_fitting():
    # ...

    # 2.2 因为目前特征列只有1列,模型过于简单,为了模拟过拟合,我们增加9列 特征列,从而增加模型的复杂度
    # 即:把数据从 [[1], [2], [3], ..., [100]] => [[1, 1², 1³, ..., 1⁹], [2, 4, 8, ..., 512], [3, 9, 27, ..., 729], ..., [100, 10000, 1000000, ..., 1000000000]]
    # 该函数作用:横向拼接,即:拼接多个数组,拼接后数组的行数不变,拼接后数组的列数等于拼接前的列数之和
    # X2 = np.hstack([X, X ** 2])
    X3 = np.hstack([X, X ** 2, X ** 3, X ** 4, X ** 5, X ** 6, X ** 7, X ** 8, X ** 9])

    # ...
3.2.2 解决办法
  • 重新清洗数据:异常点过多、数据不纯的地方再处理一遍(异常值检测、去噪声),少让模型去记脏特征。
  • 增大训练量:原来那点数据被训练得太过了;样本变多,同样复杂度的模型更难把噪声当成规律,过拟合会缓解。
  • 正则化:专门用来压过拟合,机器学习和深度学习里都大量用。
  • 减少特征维度,防止维灾难:特征太多、样本太少时,空间被撑得很空,学不充分,泛化差。降维(少特征)等于把模型从「太复杂」往回拉。

正则化分为L1正则化和L2正则化:

  • L1 会让权重趋向 0,甚至等于 0。等于 0 的那些特征等于失效,所以 L1 能做特征筛选
  • L2 会让权重趋向 0,一般不等于 0(减弱影响,不删特征)。
3.2.2.1 L1正则化
J(w)=MSE(w)+αi=1nwiJ(w)=\mathrm{MSE}(w)+\alpha\sum_{i=1}^{n}\lvert w_i\rvert

α\alpha惩罚系数(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

    # ...
3.2.2.2 L2正则化
J(w)=MSE(w)+αi=1nwi2J(w)=\mathrm{MSE}(w)+\alpha\sum_{i=1}^{n}w_i^2

α\alpha:同样是惩罚系数。越大,对特征权重罚得越狠。

使用 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)

    # ...

4. 逻辑回归

4.1 简介

逻辑回归 = 有监督学习,有特征、有标签,且标签是离散的(分类)= 适用于二分类。

线性回归的标签是连续的(房价、授信额度);逻辑回归的标签是离散的(是/否、A/B)。它仍是有监督学习:有特征、有标签,但干的是分类,而且课件这一章主要讲二分类

数据 线性回归 预测值 Sigmoid 激活 概率[0,1] 阈值 A 或 B\text{数据}\ \xrightarrow{\text{线性回归}}\ \text{预测值}\ \xrightarrow{\text{Sigmoid 激活}}\ \text{概率}\in[0,1]\ \xrightarrow{\text{阈值}}\ \text{A 或 B}

Sigmoid 也叫 S 型函数,在神经网络里常当激活函数。线性回归吐出来的是任意实数,分类要的是 (0,1)(0,1) 之间的概率,中间就靠它压。

  1. 先按线性回归算出一个实数(可正可负、可很大很小)
  2. 丢进 Sigmoid,压成 (0,1)(0,1) 之间的概率
  3. 拿你写的阈值去切:课件例子是 >0.6>0.6 判 A(正样本),否则判 B(负样本)

逻辑回归是解决二分类问题的利器:输出只有两边,是或不是

4.2 相关概念

4.2.1 精确率/召回率
精确率 Precision=TPTP+FP,召回率 Recall=TPTP+FN\text{精确率 Precision}=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FP}},\qquad \text{召回率 Recall}=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FN}}
4.2.2 混淆矩阵
预测值(正例)预测值(反例)
真实值(正例)真正例 TP(True Positive)伪反例 FN(False Negative)
真实值(假,反例)伪正例 FP(False Positive)真反例 TN(True Negative)
  • TP:真是正的,也预测成正的
  • FN:真是正的,却预测成负的(漏检)
  • FP:真是负的,却预测成正的(误报)
  • TN:真是负的,也预测成负的

4.3 数学基础 - Sigmoid 函数

f(x)=11+exf(x)=\frac{1}{1+e^{-x}}

xx 这里是线性回归的预测值wTx+bw^T x+b,可正可负、可到 ±\pm\infty)。作用:把 (,+)(-\infty,+\infty) 映射到 (0,1)(0,1)

几个常用点:

f(0)=11+1=0.5,x+  f(x)1,x  f(x)0f(0)=\frac{1}{1+1}=0.5,\qquad x\to+\infty\ \Rightarrow\ f(x)\to 1,\qquad x\to-\infty\ \Rightarrow\ f(x)\to 0
  • 单调递增:线性预测越大,概率越大,不会扭回来
  • 拐点x=0, y=0.5x=0,\ y=0.5:曲线在这儿从下凸换成上凸,也是默认阈值常取 0.50.5 的原因
  • 两端饱和:离 0 很远时曲线几乎贴着 0 或 1,梯度很小

导数:

f(x)=f(x)(1f(x))f'(x)=f(x)\bigl(1-f(x)\bigr)

机器学习

55. 概率相关知识回顾

课件这块是给后面 P(y=1x)P(y=1\mid x) 打底:先分清「单独发生」「一起发生」「已知一件再发生另一件」。

55.1 概率

概率:事件发生的可能性,取值 [0,1][0,1]。联合概率、条件概率都是概率论里描述随机变量之间关系的基本概念。

课件例子:北京堵车

事件含义概率
AA早上堵车P(A)=0.7P(A)=0.7
BB中午堵车P(B)=0.3P(B)=0.3
CC晚上堵车P(C)=0.4P(C)=0.4
55.1.1 联合概率

两个或多个事件同时发生的概率,记 P(AB)P(A\cap B)P(A,B)P(A,B)

只有在相互独立时才能拆成相乘:

P(AB)=P(A)P(B)(独立时)P(A\cap B)=P(A)\,P(B)\qquad\text{(独立时)}

课件:周一早上、周二早上都堵。若两天相互独立、每天早上堵车都是 0.70.7

P(周一早堵周二早堵)=0.7×0.7=0.49P(\text{周一早堵}\cap\text{周二早堵})=0.7\times 0.7=0.49

不独立就不能直接乘。比如「早上堵」和「中午堵」往往相关(早高峰堵了,中午更可能还堵),P(AB)P(A\cap B) 不一定等于 0.7×0.30.7\times 0.3

55.1.2 条件概率

条件概率:在事件 AA 已经发生的前提下,事件 BB 再发生的概率:

P(BA)=P(AB)P(A)P(B\mid A)=\frac{P(A\cap B)}{P(A)}

分母是「已知那件」的概率,分子是「两件一起发生」。乘法公式反过来说就是 P(AB)=P(A)P(BA)P(A\cap B)=P(A)\,P(B\mid A)

对照:

公式课件那组数(先假设独立)含义
联合P(AB)=P(A)P(B)P(A\cap B)=P(A)P(B)0.7×0.3=0.210.7\times 0.3=0.21早上中午都堵
条件P(BA)=P(AB)/P(A)P(B\mid A)=P(A\cap B)/P(A)0.21/0.7=0.30.21/0.7=0.3已知早上堵了,中午还堵

记法统一用 P(BA)P(B\mid A):竖线右边是已经知道的

55.2 极大似然估计

核心思想:根据已经观测到的结果,反推模型里那个未知参数最像是多少。

未知参数 pp 先「产生」出数据(人、样本、对话),再用这些数据去估计 pp。正向是「参数 → 数据」,极大似然是倒过来「数据 → 参数」。

55.2.1 硬币例子

一枚不均匀的硬币,正面概率记 θ\theta,反面就是 1θ1-\theta。抛 6 次,现象

D={, 反, 反, 正, 正, 正}D=\{\text{正},\ \text{反},\ \text{反},\ \text{正},\ \text{正},\ \text{正}\}

4 正 2 反。每次投掷相互独立,所以整段序列的联合概率可以拆成相乘(55.1.1):

P(Dθ)=P(θ)P(θ)P(θ)P(θ)P(θ)P(θ)=θ(1θ)(1θ)θθθ=θ4(1θ)2\begin{aligned} P(D\mid\theta) &=P(\text{正}\mid\theta)\,P(\text{反}\mid\theta)\,P(\text{反}\mid\theta)\,P(\text{正}\mid\theta)\,P(\text{正}\mid\theta)\,P(\text{正}\mid\theta)\\ &=\theta\cdot(1-\theta)\cdot(1-\theta)\cdot\theta\cdot\theta\cdot\theta =\theta^4(1-\theta)^2 \end{aligned}

P(Dθ)P(D\mid\theta) 是「参数取 θ\theta 时,碰巧看到这份数据的概率」。θ\theta 未知,这份 DD 已经发生了,于是把上式看成 θ\theta 的函数,叫似然函数

L(θ)=θ4(1θ)2L(\theta)=\theta^4(1-\theta)^2

问题变成:哪个 θ\thetaL(θ)L(\theta) 最大?——最能「解释」这 6 次结果的那个正面概率。

55.2.2 求导找极大值

LL 求导,令导数为 0:

L(θ)=4θ3(1θ)2+θ42(1θ)(1)=4θ3(1θ)22θ4(1θ)=θ3(1θ)[4(1θ)2θ]=θ3(1θ)(46θ)\begin{aligned} L'(\theta) &=4\theta^3(1-\theta)^2+\theta^4\cdot 2(1-\theta)\cdot(-1)\\ &=4\theta^3(1-\theta)^2-2\theta^4(1-\theta)\\ &=\theta^3(1-\theta)\bigl[4(1-\theta)-2\theta\bigr]\\ &=\theta^3(1-\theta)(4-6\theta) \end{aligned}

L(θ)=0L'(\theta)=0 给出三个根:

θ1=0,θ2=1,θ3=46=23\theta_1=0,\qquad \theta_2=1,\qquad \theta_3=\frac{4}{6}=\frac{2}{3}

θ=0\theta=011 时,L=0L=0(数据里正、反都出现过,说「永远正面」或「永远反面」完全解释不了 DD)。真正的极大值在

θ^=23\hat\theta=\frac{2}{3}

和直觉一致:6 次里 4 次正面,频率就是 4/64/6。伯努利试验的极大似然估计,就是样本里「成功」的比例。

55.3 对数函数

55.3.1 定义

ab=Na^b=Na>0a>0a1a\neq 1),则 bb 叫做以 aa 为底 NN 的对数:

b=logaNb=\log_a N

例子:log10100=2\log_{10}100=2(因为 102=10010^2=100),log216=4\log_2 16=4(因为 24=162^4=16)。

55.3.2 三条运算性质

定义域:a>0, a1a>0,\ a\neq 1M>0, N>0M>0,\ N>0。(课件写成「a0a\neq 0」,底数排除的是 11 不是 00。)

loga(MN)=logaM+logaNlogaMN=logaMlogaNloga(Mn)=nlogaM\begin{aligned} \log_a(MN)&=\log_a M+\log_a N\\ \log_a\frac{M}{N}&=\log_a M-\log_a N\\ \log_a(M^n)&=n\log_a M \end{aligned}

第一条就是课件那句话:几个概率连乘,可以改成 log 相加。

55.3.3 用回硬币的似然

上一节似然 L(θ)=θ4(1θ)2L(\theta)=\theta^4(1-\theta)^2连乘。样本一多,0.7100000.7^{10000} 这类数会下溢成 00。对数能把乘法变加法,极大值的位置不变(log\log 单调递增),所以实际都改成最大化 logL\log L

L(θ)=θ4(1θ)2logL(θ)=4logθ+2log(1θ)L(\theta)=\theta^4(1-\theta)^2 \qquad\Rightarrow\qquad \log L(\theta)=4\log\theta+2\log(1-\theta)

连乘变成两项相加,求导不再用乘法法则硬拆:

ddθlogL=4θ21θ=04(1θ)=2θθ=23\frac{d}{d\theta}\log L=\frac{4}{\theta}-\frac{2}{1-\theta}=0 \quad\Rightarrow\quad 4(1-\theta)=2\theta \quad\Rightarrow\quad \theta=\frac{2}{3}

55.2.2 直接对 LL 求导的结果一样,步骤短一截。逻辑回归里每条样本一个 σ(wTx)\sigma(w^T x)1σ(wTx)1-\sigma(w^T x),似然是 mm 项连乘,训练时优化的就是这个对数似然(再取负变成损失去最小化)。

55.4 练习:正态均值的极大似然

总体 XX 的密度是

f(x;μ)=12πe12(xμ)2,<x<+f(x;\mu)=\frac{1}{\sqrt{2\pi}}\,e^{-\frac12(x-\mu)^2},\qquad -\infty<x<+\infty

这是 N(μ,1)\mathcal N(\mu,1)(方差钉死为 11,只估均值 μ\mu)。抽到独立样本 x1,,xnx_1,\ldots,x_n,求 μ\mu 的极大似然估计。

55.4.1 写似然,再取 ln\ln

各样本独立,联合密度是连乘(55.1.1):

L(μ)=i=1n12πe12(xiμ)2=(2π)n/2exp{12i=1n(xiμ)2}L(\mu)=\prod_{i=1}^{n}\frac{1}{\sqrt{2\pi}}\,e^{-\frac12(x_i-\mu)^2} =\bigl(2\pi\bigr)^{-n/2}\exp\Bigl\{-\frac12\sum_{i=1}^{n}(x_i-\mu)^2\Bigr\}

连乘不好下手,两边取 ln\ln55.3)。ln(ab)=lna+lnb\ln(ab)=\ln a+\ln bln(eu)=u\ln(e^u)=u

lnL(μ)=n2ln(2π)12i=1n(xiμ)2\ln L(\mu)=-\frac n2\ln(2\pi)-\frac12\sum_{i=1}^{n}(x_i-\mu)^2

第一项不含 μ\mu,最大化 lnL\ln L 等价于最小化 (xiμ)2\sum(x_i-\mu)^2——点离均值越近,似然越大。

55.4.2 对 μ\mu 求导
μ(xiμ)2=2(xiμ)(1)=2(xiμ)\frac{\partial}{\partial\mu}(x_i-\mu)^2=2(x_i-\mu)\cdot(-1)=-2(x_i-\mu)

所以

dlnLdμ=12i=1n(2(xiμ))=i=1n(xiμ)\frac{d\ln L}{d\mu}=-\frac12\sum_{i=1}^{n}\bigl(-2(x_i-\mu)\bigr)=\sum_{i=1}^{n}(x_i-\mu)

令导数为 00

i=1n(xiμ)=0nμ=i=1nxiμ^=1ni=1nxi=xˉ\sum_{i=1}^{n}(x_i-\mu)=0\qquad\Rightarrow\qquad n\mu=\sum_{i=1}^{n}x_i\qquad\Rightarrow\qquad \hat\mu=\frac1n\sum_{i=1}^{n}x_i=\bar x

μ\mu 的极大似然估计就是样本均值。课件写「当 x=μx=\mu 时概率最大」,应为 μ=xˉ\mu=\bar x 时似然最大(xx 是已观测的一串数,不能再当未知量)。

和硬币那题同一套路:硬币的 θ^\hat\theta 是正面出现的频率;正态的 μ^\hat\mu 是样本的平均。都是「用数据里最像那个参数的统计量去估」。

56. 逻辑回归_原理介绍

56.1 概念

逻辑回归(Logistic Regression)是一种分类模型:把线性回归的输出,当作逻辑回归的输入。模型吐出来的是 (0,1)(0,1) 之间的值(正类概率),不是房价那种连续标签。

整条链就一句话:

线性回归预测值 → Sigmoid 映射到 [0,1][0,1] → 结合阈值,划分正样本 / 负样本。

56.1.1 基本思想
  1. 用线性模型按特征重要性打一个分:
f(x)=wTx+bf(x)=w^T x+b

ww 大的特征话语权大。这个 f(x)f(x) 可正可负、可到 ±\pm\infty,还不是概率。

  1. 用 Sigmoid 把 f(x)f(x) 压成概率 P(y=1x)(0,1)P(y=1\mid x)\in(0,1)。再设阈值(课件常用 0.50.50.60.6):
    • 概率 大于 阈值 → 判 1 类(正类 / A)
    • 否则 → 判 0 类(负类 / B)
56.1.2 假设函数
h(w)=sigmoid(wTx+b)=11+e(wTx+b)h(w)=\operatorname{sigmoid}(w^T x+b)=\frac{1}{1+e^{-(w^T x+b)}}

括号里仍是线性回归那一项;外面套上 Sigmoid,才变成逻辑回归。所以:线性回归的输出 = 逻辑回归的输入。

56.1.3 预测过程(阈值 0.60.6

约定:Sigmoid 给出的是属于 A 的概率>0.6>0.6 判 A,否则判 B。

样本先和权重做回归计算得到 zz,再过 Sigmoid 得到 pp,最后按 0.60.6 切开:

特征回归预测值 zz逻辑回归 p[0,1]p\in[0,1]预测(阈值 0.60.6真实
12.3, 20.0, 1612.3,\ 20.0,\ 1682.482.40.400.40BA
9.4, 21.1, 7.29.4,\ 21.1,\ 7.289.189.10.680.68AB
34.4, 18.7, 8.134.4,\ 18.7,\ 8.180.280.20.410.41BA
10.2, 16.0, 12.510.2,\ 16.0,\ 12.581.381.30.550.55BB
5.6, 10.0, 6.35.6,\ 10.0,\ 6.390.490.40.710.71AA

0.68>0.60.68>0.60.71>0.60.71>0.6 判 A;0.400.400.410.410.550.55 都不到 0.60.6,判 B。五条里对了后两条,前三条预测和真实反着——权重还没训好,或阈值和数据对不齐,后面用极大似然去调 ww

表里 zz 动辄 80809090,真代入 Sigmoid 会几乎贴死 11,不会出现 0.400.40。课件这组数是在演示流程,不是按公式手算出来的;真实计算仍是 p=sigmoid(z)p=\operatorname{sigmoid}(z)

56.2 损失函数

线性回归用 MSE 看「差了多少」;逻辑回归输出的是概率,损失改成对数损失(二分类交叉熵)。它就是 55.2 里似然取 log\log 再加负号:最大化「看到这些标签」的概率,等价于把这个损失压下去。

Loss(w)=i=1m[yilogpi+(1yi)log(1pi)]\operatorname{Loss}(w)=-\sum_{i=1}^{m}\Bigl[y_i\log p_i+(1-y_i)\log(1-p_i)\Bigr]
pi=sigmoid(wTx(i)+b)p_i=\operatorname{sigmoid}(w^T x^{(i)}+b)

编码:AA(正样本)y=1\to y=1BB(负样本)y=0\to y=0yy 是真实标签,预估的是 pip_i

56.2.1 在干什么

每个样本模型都给出两个数:pp(是 A 的概率)和 1p1-p(是 B 的概率)。损失只盯真实那一类对应的那个概率:越大越好。外面的负号把「越大越好」翻成「越小越好」,才能当损失。

yy 只能是 0011,两项里永远只活一项:

真实活下来的那项含义
y=1y=1(正样本)logp-\log p真实是 A,希望 pp 靠近 11
y=0y=0(负样本)log(1p)-\log(1-p)真实是 B,希望 pp 靠近 00

pp\to 真实类时,log\log 靠近 00,损失靠近 00;预测反了(真实是 A 却 p0p\to 0),logp\log p\to-\infty,损失爆炸。

56.2.2 手工计算

沿用 56.1.3 那五条,pp 仍用课件表上的数,yy 按真实 A/B 编成 1/01/0

iipip_iyiy_i代入这一项
10.400.40111log0.40+(11)log(10.40)1\cdot\log 0.40+(1-1)\log(1-0.40)log0.40\log 0.40
20.680.68000log0.68+(10)log(10.68)0\cdot\log 0.68+(1-0)\log(1-0.68)log0.32\log 0.32
30.410.41111log0.41+(11)log(10.41)1\cdot\log 0.41+(1-1)\log(1-0.41)log0.41\log 0.41
40.550.55000log0.55+(10)log(10.55)0\cdot\log 0.55+(1-0)\log(1-0.55)log0.45\log 0.45
50.710.71111log0.71+(11)log(10.71)1\cdot\log 0.71+(1-1)\log(1-0.71)log0.71\log 0.71

总损失是这些数求和再取负:

Loss=(log0.40+log0.32+log0.41+log0.45+log0.71)\operatorname{Loss}=-\bigl(\log 0.40+\log 0.32+\log 0.41+\log 0.45+\log 0.71\bigr)

第 1 条真实是 A 却只给了 0.400.40 的把握,log0.40-\log 0.40 比较大,在罚;第 5 条 p=0.71p=0.71 更贴近真实正类,log0.71-\log 0.71 更小。把 ww 调到让这份 Loss 下降,就是在用极大似然估逻辑回归的参数。

56.2.3 损失函数从哪来

推导拆成「1 个样本 → n 个样本 → 让联合概率最大」。公式不是拍出来的,就是硬币那道极大似然(55.2),把共用的 θ\theta 换成每条样本自己的 pip_i

1. 一个样本的概率

两类:0011。模型认为这条样本是 11 类的概率为 p=sigmoid(wTx+b)p=\operatorname{sigmoid}(w^T x+b),是 00 类就是 1p1-p。真实标签对应的那一项,就是这条样本对似然的贡献(课件写成 LL,容易和后面的 Loss 撞名,这里用 P(yx)P(y\mid x)):

P(yx)={py=11py=0合成P(yx)=py(1p)1yP(y\mid x)=\begin{cases}p & y=1\\ 1-p & y=0\end{cases} \qquad\text{合成}\qquad P(y\mid x)=p^{y}(1-p)^{1-y}

yy 当开关:y=1y=1p1(1p)0=pp^1(1-p)^0=py=0y=0p0(1p)1=1pp^0(1-p)^1=1-p

真实标签在模型看来有多大概率发生。真实是 11,希望 pp 越大越好;真实是 00,希望 1p1-p 越大越好。

2. nn 个样本的概率

训练集 {(x1,y1),,(xn,yn)}\{(x_1,y_1),\ldots,(x_n,y_n)\}。各样本独立,联合概率连乘:

P=i=1nP(yixi)=i=1npiyi(1pi)1yiP=\prod_{i=1}^{n}P(y_i\mid x_i)=\prod_{i=1}^{n}p_i^{y_i}(1-p_i)^{1-y_i}
  • pip_i:第 ii 条被判成 11 类的概率, pi=sigmoid(wTxi+b)\ p_i=\operatorname{sigmoid}(w^T x_i+b)
  • yiy_i:第 ii 条的真实类别,0011

3. 极大似然 → 对数似然 → 损失

连乘会下溢,log\log 单调,改成最大化对数似然 HH(记 H(L)H(L),就是 logP\log P):

H=i=1m[yilogpi+(1yi)log(1pi)]H=\sum_{i=1}^{m}\bigl[y_i\log p_i+(1-y_i)\log(1-p_i)\bigr]

pip_i 仍是 Sigmoid:

pi=11+e(wTxi+b)p_i=\frac{1}{1+e^{-(w^T x_i+b)}}

1ez1-e^{-z}z=0z=0 时是 00,会除零;Sigmoid 在 z=0z=0 必须是 0.50.5,只有 1+ez1+e^{-z} 才对。

梯度下降默认下山,把最大化 HH 翻成最小化 H-H

Loss=H=i=1m[yilogpi+(1yi)log(1pi)]\operatorname{Loss}=-H=-\sum_{i=1}^{m}\bigl[y_i\log p_i+(1-y_i)\log(1-p_i)\bigr]

逻辑回归损失函数 == - 极大似然(对数似然)函数。 这就是 56.2 的式子。

4. 用梯度下降更新 wwbb

Loss\operatorname{Loss}wwbb 的梯度,按下式迭代(和线性回归同一套路,只是损失换成了交叉熵):

wwαLossw,bbαLossbw\leftarrow w-\alpha\frac{\partial\operatorname{Loss}}{\partial w},\qquad b\leftarrow b-\alpha\frac{\partial\operatorname{Loss}}{\partial b}

α\alpha 是学习率。损失下降 \Leftrightarrow 对数似然上升 \Leftrightarrow 越来越能解释这批真实标签。

56.2.4 练习

以下关于逻辑回归的说法正确的是?(多选)

  • A)逻辑回归应用在分类场景中
  • B)逻辑回归使用了回归将特征数据进行拟合
  • C)逻辑回归使用了 sigmoid 激活函数将回归的结果映射到了 (0,1)(0,1) 值域中
  • D)逻辑回归的损失函数使用了极大似然估计,因为其输出值是一个概率

答案:ABCD。

  • A:名字带「回归」,干的是二分类(疾病阴阳性、放贷与否)。
  • B:先有线性式 wTx+bw^T x+b 按特征打分,这块就是回归拟合。
  • C:Sigmoid 把 (,+)(-\infty,+\infty) 压到 (0,1)(0,1),当正类概率。
  • D:输出是概率,似然按伯努利来写,损失就是 - 对数似然。

57. 逻辑回归案例_癌症预测

57.1 API 介绍

sklearn 封装好了逻辑回归,损失、Sigmoid、梯度更新都在 fit 里面:

from sklearn.linear_model import LogisticRegression

estimator = LogisticRegression(solver='liblinear', penalty='l2', C=1.0)
57.1.1 solver:优化方法

solver 是怎么把交叉熵压下去(优化器),不是换模型。

solver适合正则
liblinear小数据更快(坐标下降那一类)L1、L2
sag大数据更快(随机平均梯度,DAY9 的 SAG)L2,或不要正则
saga大数据,sag 的改进版L1、L2,或不要正则

课件口诀:小数据用 liblinear;数据大改 sag / saga。要 L1(稀疏、特征筛选)只能 liblinear 或 saga,纯 sag 没有 L1。

现在 sklearn 默认 solver 多半是 'lbfgs'(二分类 L2 够用)。solverpenalty 必须配对,否则会报错。

57.1.2 penaltyC
  • penalty'l1''l2',和岭回归 / Lasso 同一套:L2 把权重往 0 缩,L1 能缩到正好 0。
  • C正则强弱的倒数。损失里是 Loss+1C×\operatorname{Loss} + \frac{1}{C}\times 正则项。
    • C 越大,罚得越轻,越接近不正则(容易过拟合)
    • C 越小,罚得越狠,权重越扁

Ridge 里是 alpha 越大罚越狠;这里不要把 C 当成 alpha。默认 C=1.0

57.1.3 谁当正例

正类要自己对上业务,一般将类别数量少的当做正例,但不要默认少数类就是正例。

57.2 数据描述

UCI 威斯康星乳腺癌原表(Original Breast Cancer),二分类:良性 / 恶性。

项目内容
样本量699 条
列数11 列:1 个 id + 9 个医学特征 + 1 个标签
缺失16 个,用 ? 标出(主要在 Bare Nuclei)
标签2 = 良性,4 = 恶性

第一列 Sample code number 只是检索用的编号,不能当特征(否则模型会记病人编号,没有泛化)。中间 9 列才是肿瘤相关特征,最后一列 Class 是要预测的肿瘤类型。

57.2.1 九列特征

课件表头(B~J)就是这九列,取值一般都在 1101\sim 10

英文含义(大意)
BClump Thickness肿块厚度
CUniformity of Cell Size细胞大小一致性
DUniformity of Cell Shape细胞形状一致性
EMarginal Adhesion边缘粘附
FSingle Epithelial Cell Size单上皮细胞大小
GBare Nuclei裸核(这列有 ?
HBland Chromatin染色质
INormal Nucleoli核仁
JMitoses有丝分裂

K 列 Class 是标签。表上 2 居多是良性,出现 4 是恶性。

57.2.2 读表时要注意
  • 标签已经是离散的 2/42/4,不是连续房价——所以用逻辑回归做分类,不是线性回归。
  • 2244 只是编码,进模型前常映射成 0/10/1(良性 00、恶性 11)。不映射也可以:sklearn 会按数值排序,classes_ = [2, 4]predict_proba 第二列就是恶性概率——和业务上「恶性当正例」碰巧一致。
  • 16 个 ? 必须先处理(删行或填补),否则 fit 进不去。
  • 699 条算小数据,solver='liblinear' 和课件示范匹配。
57.2.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 提取特征之提取特征和标签
# 按照行号,列索引获取数据
# : 表示所有行  
# 1:-1 表示从第1列到最后一列,包左不包右
x = data.iloc[:, 1:-1]
# y = data.iloc[:, -1]    # 获取最后一列
# y = data.iloc['Class']  # 获取最后一列,效果同上
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值
57.2.4 练习

下列关于逻辑回归 API 的使用正确的是?(多选)

  • A)需要在 sklearn 的线性模型 linear_model 中导出使用
  • B)可以通过 solver 参数指定损失的优化方法
  • C)可以通过 penalty 参数指定使用哪种正则化方式
  • D)它默认将样本中类别数较多的一类当做正例

答案:ABC。

  • A:from sklearn.linear_model import LogisticRegression
  • B:solver 是优化器(liblinear / sag / saga / lbfgs)
  • C:penalty='l1''l2'
  • D 错:数量的当正例,不是数量多的。sklearn 实际也不按多少来,而是 classes_ 排序后较大的那个标签当 predict_proba 的第二列。

58. 混淆矩阵及精确率,召回率,F1值介绍

癌症案例里准确率「能算、但不精确」:恶性很少时,全猜良性准确率也很高。分类要拆开看四种对错,这就是混淆矩阵。精确率、召回率、F1 都从这四个格子来。

58.1 混淆矩阵

四个英文拆开记:

意思
True真(预测对了)
False假 / 伪(预测错了)
Positive正例(模型说是正类)
Negative反例(模型说是负类)
预测正例预测假例(反例)
真实正例真正例 TP(True Positive)伪反例 FN(False Negative)
真实假例(反例)伪正例 FP(False Positive)真反例 TN(True Negative)
58.1.1 四个格子
  • TP:真实是正例,也分成了正例(找对了)
  • FN:真实是正例,却分成了假例(漏检,漏掉的病人)
  • FP:真实是假例,却分成了正例(误报,没病说成有病)
  • TN:真实是假例,也分成了假例(正确地排除)

癌症里若恶性当正例:TP 是恶性且查出恶性,FN 是恶性却当成良性,FP 是良性却当成恶性,TN 是良性且判断良性。

一行是「真实为正」的全部(TP+FN),一列是「预测为正」的全部(TP+FP)。后面精确率走列,召回率走行。

58.1.2 举个栗子

10 个样本:6 个恶性、4 个良性,恶性当正例。恒等式:TP+FN+FP+TN=10\mathrm{TP}+\mathrm{FN}+\mathrm{FP}+\mathrm{TN}=10。真实正例一行必须加起来是 6,真实反例一行必须是 4。

精确率=TPTP+FP,召回率=TPTP+FN,F1=2精确率召回率精确率+召回率\text{精确率}=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FP}},\qquad \text{召回率}=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FN}},\qquad \mathrm{F1}=\frac{2\cdot\text{精确率}\cdot\text{召回率}}{\text{精确率}+\text{召回率}}

模型 A:恶性对了 3 个,良性对了 4 个。

6 个恶性里只找对 3 个 → TP=3\mathrm{TP}=3,剩下 3 个漏掉 → FN=3\mathrm{FN}=3。4 个良性全对 → TN=4\mathrm{TN}=4,没有把良性说成恶性 → FP=0\mathrm{FP}=0

预测正例预测假例
真实正例(6 恶)TP =3=3FN =3=3
真实假例(4 良)FP =0=0TN =4=4
精确率=33+0=1,召回率=33+3=12,F1=21121+12=23\text{精确率}=\frac{3}{3+0}=1,\qquad \text{召回率}=\frac{3}{3+3}=\frac12,\qquad \mathrm{F1}=\frac{2\cdot 1\cdot \tfrac12}{1+\tfrac12}=\frac23

查出来的全是真恶性(精确率 1),但一半恶性漏了(召回率 0.5)。

模型 B:恶性对了 6 个,良性对了 1 个。(留作自测)

6 个恶性全找对 → TP=6\mathrm{TP}=6FN=0\mathrm{FN}=0。4 个良性只对了 1 个 → TN=1\mathrm{TN}=1,另外 3 个良性被说成恶性 → FP=3\mathrm{FP}=3

预测正例预测假例
真实正例(6 恶)TP =6=6FN =0=0
真实假例(4 良)FP =3=3TN =1=1
精确率=66+3=23,召回率=66+0=1,F1=223123+1=45\text{精确率}=\frac{6}{6+3}=\frac23,\qquad \text{召回率}=\frac{6}{6+0}=1,\qquad \mathrm{F1}=\frac{2\cdot\tfrac23\cdot 1}{\tfrac23+1}=\frac45

恶性一个没漏(召回率 1),但查出来的 9 个「恶性」里掺了 3 个良性(精确率 2/32/3)。

两个模型准确率都是 7/107/10,混淆矩阵却完全不同:A 宁漏勿错,B 宁错勿漏。所以分类不能只看准确率。

58.2 精确率 Precision

也叫查准率:在模型喊出来的正例里,有多少真是正例。癌症里恶性当正例,问的就是——报成恶性的那些人里,到底几个真是恶性。

看混淆矩阵预测正例那一列(TP 和 FP),和 FN、TN 无关:

P=TPTP+FPP=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FP}}

分母是「模型认为是正例」的全部;分子是其中真的那部分。P=1P=1 表示报恶性的没有一个是误报;PP 低说明误报多(良性被说成恶性)。

沿用 58.1.2 那 10 条(6 恶 4 良):

模型TPFP精确率读法
A(恶对 3,良对 4)33003/(3+0)=13/(3+0)=1报了 3 个恶性,全对,零误报
B(恶对 6,良对 1)66336/(6+3)=2/36/(6+3)=2/3报了 9 个恶性,3 个是误报

A 的精确率更高:嘴严,不乱报。B 把恶性全抓到了,但掺进 3 个良性,精确率被拉下来。精确率高不等于召回率高,下一节召回率看的是另一条边(真实正例那一行)。

58.3 召回率 Recall

也叫查全率:所有真实正例里,被找出来的占多少。恶性当正例时问的是——病人里有没有漏掉的,能不能把恶性都报出来。

看混淆矩阵真实正例那一行(TP 和 FN),和 FP、TN 无关:

R=TPTP+FNR=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FN}}

分母是「真实就是正例」的全部;分子是其中被模型抓住的。R=1R=1 表示恶性一个没漏;RR 低说明漏检多。

还是那 10 条(6 恶 4 良):

模型TPFN召回率读法
A33333/(3+3)=50%3/(3+3)=50\%6 个恶性只抓住 3 个,漏一半
B66006/(6+0)=100%6/(6+0)=100\%6 个恶性全抓住,零漏检

和精确率对照:A 精确率 100%、召回率 50%(宁漏勿错);B 精确率约 67%、召回率 100%(宁错勿漏)。癌症筛查通常更怕漏,召回率往往优先;误报代价极高时才把精确率压上去。两个指标常对着干,下一节用 F1 捏到一起。

58.4 F1-score

精确率和召回率都要看、又经常对着干时,用一个数反映综合能力:F1。它是两者的调和平均(不是算术平均 (P+R)/2(P+R)/2):

F1=2PRP+R\mathrm{F1}=\frac{2\cdot P\cdot R}{P+R}

调和平均的脾气:有一个很低,F1 就被拖下去;两个都高,F1 才高。一边 100%、一边 0%,F1 是 0——不会出现「平均 50% 还不错」的错觉。

还是 A、B 那组数:

模型精确率 PP召回率 RRF1百分数
A11(100%)1/21/2(50%)211/2/(1+1/2)=2/32\cdot 1\cdot 1/2/(1+1/2)=2/367%\approx 67\%
B2/32/367%\approx 67\%11(100%)2(2/3)1/(2/3+1)=4/52\cdot(2/3)\cdot 1/(2/3+1)=4/580%80\%

B 的 F1 更高:召回拉满的同时精确率也还过得去。A 精确率满分但漏了一半,综合分被召回率拖住。业务上若更怕漏或更怕误报,仍应单看 RRPP;两边都要交代时,才拿 F1 比模型。

58.5 练习

题目1:关于精确率、召回率和 F1 值的定义,下列说法正确的是:

  • A. 精确率是指预测为正例中实际为正例的比例,召回率是指实际为正例中被预测为正例的比例,F1 值是精确率和召回率的调和平均数。
  • B. 精确率是指实际为正例中被预测为正例的比例,召回率是指预测为正例中实际为正例的比例,F1 值是精确率和召回率的调和平均数。
  • C. 精确率是指实际为正例中被预测为正例的比例,召回率是指预测为正例中实际为正例的比例,F1 值是精确率和召回率的算术平均数。

答案:A。

  • 精确率 P=TP/(TP+FP)P=\mathrm{TP}/(\mathrm{TP}+\mathrm{FP}):预测为正里有多少真是正(查准,走列)
  • 召回率 R=TP/(TP+FN)R=\mathrm{TP}/(\mathrm{TP}+\mathrm{FN}):实际为正里有多少被找出(查全,走行)
  • F1 是调和平均 2PR/(P+R)2PR/(P+R),不是 (P+R)/2(P+R)/2

B 把精确率和召回率对调了。C 既对调,又把调和平均说成算术平均。

题目2:如果一个二分类模型的精确率为 0.80.8,召回率为 0.60.6,那么该模型的 F1 值为:

  • A. 0.440.44
  • B. 0.600.60
  • C. 0.690.69
  • D. 0.750.75

答案:C。

F1=2×0.8×0.60.8+0.6=0.961.40.68570.69\mathrm{F1}=\frac{2\times 0.8\times 0.6}{0.8+0.6}=\frac{0.96}{1.4}\approx 0.6857\approx 0.69

A 是直接相乘 0.8×0.60.8\times 0.6;B 抄了召回率;D 是算术平均 (0.8+0.6)/2=0.75(0.8+0.6)/2=0.75。F1 一定是调和平均,不会等于 (P+R)/2(P+R)/2

59. 混淆矩阵_精确率_召回率_F1值代码演示

"""
案例:
    演示混淆矩阵,精确率,召回率,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='恶性')}')

60. ROC曲线和AUC指标介绍(了解)

精确率、召回率、F1 都绑在某一个阈值上(0.50.50.60.6)。阈值一改,四个格子全变。ROC / AUC 把阈值从高扫到低,看模型整体把正负分得开不开。本章了解即可。

60.1 真正率 TPR 与假正率 FPR

TPR=TPTP+FN,FPR=FPFP+TN\mathrm{TPR}=\frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FN}},\qquad \mathrm{FPR}=\frac{\mathrm{FP}}{\mathrm{FP}+\mathrm{TN}}
  • TPR(True Positive Rate,真正率 / 灵敏度):正样本里被报成正的比例。公式和召回率是同一个,走真实正例那一行。
  • FPR(False Positive Rate,假正率):负样本里被错报成正的比例。走真实反例那一行:FP+TN\mathrm{FP}+\mathrm{TN} 是全部负类。

两个一起看:TPR 高、FPR 低,说明正的抓得全、负的又没乱报,分得开。

60.2 ROC 曲线

ROC(Receiver Operating Characteristic curve):评估分类模型的可视化工具。

  • 横轴:FPR(特异度)
  • 纵轴:TPR(灵敏度)
  • 曲线上每一点 = 某一个阈值下的 (FPR, TPR)(\mathrm{FPR},\ \mathrm{TPR})

逻辑回归输出概率后,阈值从 11 降到 00:门槛很高时几乎谁也不报正 → 靠近左下;门槛很低时几乎人人报正 → 靠近右上。把这些点连起来就是 ROC。虚线对角线是随机瞎猜(TPR = FPR)。

60.3 四个角上的点

坐标一律 (FPR, TPR)(\mathrm{FPR},\ \mathrm{TPR})

含义效果
(0,0)(0,0)负类全对(没人被报成正),正类全错(一个正的都没抓到)阈值极高,全判负
(1,0)(1,0)负类全错,正类也全错最差:该报的不报,不该报的全报
(1,1)(1,1)负类全错(全被报成正),正类全对阈值极低,全判正
(0,1)(0,1)负类全对,正类也全对最好:完美分类

曲线上每个点仍是「某个阈值下的表现」。从图像上看:曲线越靠近左上角 (0,1)(0,1),模型越好(TPR 高、FPR 低)。往右下是 Worse。

正常扫阈值时,曲线大致从 (0,0)(0,0) 走到 (1,1)(1,1),不会故意停在 (1,0)(1,0)(1,0)(1,0) 是「比随机还差、预测和真相反着」的角落。

60.4 AUC

AUC(Area Under the ROC Curve)= ROC 曲线下的面积,取值 [0,1][0,1]。面积越大,分类器越好。

AUC含义
=1=1完美分类器,正负全对。几乎不存在
=0.5=0.5等于随机猜测(对角线)
0.5\le 0.5正负分不清,甚至比瞎猜还差(把标签反一下会好过 0.5)
越接近 11越能把正负样本分开

一句话:精确率 / 召回率问「这个阈值好不好」;AUC 问「阈值怎么切,模型本身分得开不开」。

61. ROC曲线_案例(了解)

广告位展示 6 次,点了 2 次。正样本 {1,3}\{1,3\}(点了),负样本 {2,4,5,6}\{2,4,5,6\}(没点)。按不同阈值算 (FPR,TPR)(\mathrm{FPR},\mathrm{TPR}),再连成 ROC。

约定:预测概率 >> 阈值 → 判点击(正类 1),否则判 0。分母:正类 2 个,负类 4 个。

61.1 数据:先按概率从高到低排

样本是否点击 yy预测概率
110.90.9
310.80.8
200.70.7
400.60.6
500.50.5
600.40.4

两个正样本的分都高于全部负样本,排完序正类全在最上面——后面会看到曲线很快贴到左上,AUC 接近 1。

61.2 逐个阈值手算

TPR=TP2,FPR=FP4\mathrm{TPR}=\frac{\mathrm{TP}}{2},\qquad \mathrm{FPR}=\frac{\mathrm{FP}}{4}
阈值谁被判正(p>p> 阈值)TPRFPR(FPR,TPR)(\mathrm{FPR},\mathrm{TPR})
0.90.9没有(0.90.90.9\not> 0.90/2=00/2=00/4=00/4=0(0, 0)(0,\ 0)
0.80.8仅 1 号1/2=0.51/2=0.500(0, 0.5)(0,\ 0.5)
0.70.71、3 号2/2=12/2=100(0, 1)(0,\ 1)
0.60.61、3、2 号111/4=0.251/4=0.25(0.25, 1)(0.25,\ 1)
0.50.5再加 4 号112/4=0.52/4=0.5(0.5, 1)(0.5,\ 1)
0.40.4再加 5 号113/4=0.753/4=0.75(0.75, 1)(0.75,\ 1)
<0.4<0.46 个全判正114/4=14/4=1(1, 1)(1,\ 1)

(0,0.5), (0,1), (0.25,1), (0.5,1), (0.75,1), (1,1)(0,0.5),\ (0,1),\ (0.25,1),\ (0.5,1),\ (0.75,1),\ (1,1),再加上阈值 0.90.9(0,0)(0,0)。连起来:先沿纵轴爬到 (0,1)(0,1),再水平走到 (1,1)(1,1)

61.3 AUC 的计算 API

from sklearn.metrics import roc_auc_score

roc_auc_score(y_true, y_score)

算的就是 ROC 曲线下面积。

参数含义
y_true真实类别,必须是 00(反例)、11(正例)
y_score预测得分:正类概率、置信度,或 decision_function 的返回值

本例 y_true = [1,0,1,0,0,0]y_score = [0.9,0.7,0.8,0.6,0.5,0.4],两个正类分全高于负类,roc_auc_score 会得到 11

61.4 分类评估报告 API

from sklearn.metrics import classification_report

print(classification_report(y_true, y_pred, labels=None, target_names=None))

按类输出精确率、召回率、F1(还带 support)。这是某一个阈值切开之后的报表,和 AUC 不是同一个东西。

参数含义
y_true真实标签
y_pred模型预测的类别(已经切过阈值的 0/1,不是概率)
labels要报告的类别取值,顺序会反映到表上
target_names类别显示名,比如 ['良性','恶性']

61.5 练习

题目1:下列关于逻辑回归模型的评估说法正确的是?(多选)

  • A)我们在评估逻辑回归模型时只需要选择一种评估方法即可
  • B)混淆矩阵能够帮助我们快速计算出其它分类模型指标
  • C)召回率和精确率表达的是同样的概念
  • D)ROC 曲线下与坐标轴形成的闭合区域的面积即为 AUC 指标的值

答案:BD。

  • A 错:准确率、精确率、召回率、F1、AUC 看的不是同一件事,只盯一个会偏(癌症案例里准确率高也可能漏诊)。
  • B 对:四个格子齐了,精确率、召回率、F1、TPR、FPR 都能直接算。
  • C 错:精确率走预测正例那一列(查准),召回率走真实正例那一行(查全)。
  • D 对:AUC 就是 ROC 曲线下、和坐标轴围成的面积。

题目2:以下哪个描述最准确地解释了 AUC 指标的含义?

  • A. AUC 是 ROC 曲线下方的面积,用于比较分类器的性能。
  • B. AUC 是 ROC 曲线上某一点的斜率,表示分类器的灵敏度。
  • C. AUC 是 ROC 曲线上的最大误分类率,用于评估分类器的错误率。
  • D. AUC 是 ROC 曲线上的阈值,用于选择最佳分类器。

答案:A。 B 把 AUC 说成斜率(灵敏度是纵轴 TPR,不是 AUC);C 说成误分类率;D 把 AUC 说成阈值。阈值是扫出来画 ROC 的,AUC 是整条曲线下的面积。

题目3:下面哪个选项最能说明 ROC 曲线的作用?

  • A. ROC 曲线用于描述模型的参数。
  • B. ROC 曲线用于可视化模型的损失函数。
  • C. ROC 曲线用于评估二元分类模型的性能。
  • D. ROC 曲线用于选择最佳的回归模型。

答案:C。 它是二分类的可视化评估(TPR–FPR,不同阈值)。不是参数、不是损失曲线,也不用来挑回归模型。

62. 逻辑回归_电信用户流失预测_数据预处理

已知用户的个人、通话、上网等信息,要做两件事:找出可能造成流失的因素,再训练模型判断会不会流失,好做预警。这是二分类(流失 Yes / 留下 No),用逻辑回归。

数据在 churn.csv:7043 行、16 列,没有空值。

62.1 字段

含义
Churn标签:是否流失(Yes / No)
gender性别 Male / Female
Partner_att配偶是否也是该运营商用户
Dependents_att家人是否也是
landline是否用固话
internet_att / internet_other是否用该运营商 / 别家的宽带
StreamingTV / StreamingMovies是否用在线电视、电影
Contract_Month / Contract_1YR月付合约 / 一年合约
PaymentBank / PaymentCreditcard / PaymentElectronic付款方式(银行、信用卡、电子)
MonthlyCharges每月话费
TotalCharges累计话费

除 Churn、gender 外,其余已经是 0/10/1 或连续数。info() 里这两列是 object,进模型前要变成数。

标签分布:No 5174,Yes 1869,留下的远多于流失,后面训练要处理不平衡(正例一般取流失 Yes)。

62.2 整案四步(本节省第一步)

  1. 数据基本处理(本节):看行列、类别 one-hot、看标签分布
  2. 特征筛选:按流失/留下分组看均值,留下对标签影响大的列,拆成 xxyy
  3. 模型训练:样本均衡 / 不均衡各训一版,再网格搜索 + 交叉验证
  4. 模型评估:精确率、ROC-AUC(流失更怕漏,召回和 AUC 都要看)

62.3 数据基本处理在干什么

  • 确认 7043×16、无缺失。
  • One-hot / 映射类别列
    • gendergender_Femalegender_Male(或只留一列,避免共线)
    • Churn:Yes→1、No→0(当 yy,不必 one-hot 成两列)
  • value_counts():确认 Yes 是少数类。准确率会虚高(全猜 No 也有 5174/704373%5174/7043\approx 73\%),所以不能只看准确率。

62.4 代码实现

"""
案例:
    通过逻辑回归算法,针对于电信用户数据建模,进行流失预测分析
"""
# 导包
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 = churn_df.get_dummies(['Churn', 'gender'])
    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)
    # churn_df.info()
    # print(churn_df.head())
    # 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()