栏导语 :本专栏系统讲解数学建模的经典方法与 AI 智能方法,并打通两者之间的联系。上一篇我们走进优化类模型 ,用线性规划与单纯形法回答了"资源怎么分配最划算",还顺手拆开了影子价格 与对偶理论 这层包装(线性规划与单纯形法:生产调度的最优解)。这一篇回到分类 :当输出不是"多少",而是"是 / 否"------这个客户会不会违约、这封邮件是不是垃圾邮件------该如何建模?答案是一条 S 形曲线:逻辑回归 。它既是数模竞赛里分类问题的第一主力,也是一条重要暗线的起点------逻辑回归就是神经网络里的一个 sigmoid 神经元:交叉熵、softmax、反向传播,都能在这一个模型里找到雏形。本专栏 B 线的深度学习伏笔,从这里继续延伸。
文章目录
- 一、问题引入:从"预测一个数"到"判断一件事"
- 二、问题分析:三个必须回答的问题
- 三、模型假设
- 四、符号说明
- 五、模型建立
-
- [5.1 从"几率"到 sigmoid](#5.1 从"几率"到 sigmoid)
- [5.2 极大似然 → 交叉熵损失](#5.2 极大似然 → 交叉熵损失)
- [5.3 梯度推导:残差 × 特征](#5.3 梯度推导:残差 × 特征)
- [5.4 为什么必须用迭代法,而没有闭式解](#5.4 为什么必须用迭代法,而没有闭式解)
- [5.5 决策边界与阈值](#5.5 决策边界与阈值)
- [5.6 正则化:当"完全分离"发生时](#5.6 正则化:当"完全分离"发生时)
- [六、Python 实现](#六、Python 实现)
-
- [6.1 数据生成与划分](#6.1 数据生成与划分)
- [6.2 手写梯度下降](#6.2 手写梯度下降)
- [6.3 与 sklearn 对照,并计算分类指标](#6.3 与 sklearn 对照,并计算分类指标)
- [6.4 阈值敏感性:0.5 不是天经地义](#6.4 阈值敏感性:0.5 不是天经地义)
- [6.5 结果可视化](#6.5 结果可视化)
- 七、模型评价
- [八、AI 视角:逻辑回归是神经网络的"单个神经元"](#八、AI 视角:逻辑回归是神经网络的"单个神经元")
-
- [8.1 一个神经元 = 一次线性变换 + 一次激活](#8.1 一个神经元 = 一次线性变换 + 一次激活)
- [8.2 从 sigmoid 到 softmax:二分类 → 多分类](#8.2 从 sigmoid 到 softmax:二分类 → 多分类)
- [8.3 工业界的逻辑回归:CTR 预估与风控评分卡](#8.3 工业界的逻辑回归:CTR 预估与风控评分卡)
- [8.4 与 SVM、随机森林的关系](#8.4 与 SVM、随机森林的关系)
- 九、小结
个人主页 :蒲公英eric
专栏传送门 :数学建模 × AI:从经典模型到智能方法
学习方向:大数据 / 数学建模 / AI交叉领域
人生格言:建模没有终点,只有不断迭代的更优解
📌本文是专栏「数学建模 × AI:从经典模型到智能方法」的第 10 篇文章。
一、问题引入:从"预测一个数"到"判断一件事"
前面的文章都在回答一个问题:给定了输入,输出会是多少?------房价多少、销量多少。这类问题用线性回归、岭回归就能对付。
但数模里还有另一类同样高频的问题,它只回答两个字:是,或否。
-
这家企业会不会在未来一年违约?
-
这名用户会不会点击这条广告?
-
这封邮件是不是垃圾邮件?
-
这位病人有没有患某种疾病?
粗看似乎可以"用回归硬套":把"违约"记作 1、"不违约"记作 0,跑一个线性回归,输出大于 0.5 就算违约。这个想法是错的,而且是初学者最常踩的坑,我们会在第七节详细拆解。
正确的做法是:不直接预测 0/1,而是预测属于某一类的概率 p p p,再用一个阈值把概率变成类别。于是问题变成------
怎样把"线性打分"映射成一个合法的概率(落在 0 , 1 0,1 0,1 内),并且让这个映射可解释、可求导、可优化?
逻辑回归给出的答案是 sigmoid 函数 。本文以信用违约预测为例,走完从建模到评价的完整链条。
二、问题分析:三个必须回答的问题
问题 1:为什么输出必须是概率,而不是直接的 0/1?
因为真实世界有不确定性。同样是"年收入 18 万、负债收入比 70%",有的客户还上了,有的没还上。模型能给出的最诚实答案是"这类客户大约有 90% 会违约"------概率携带的信息远多于一个硬标签,而且能直接用于定价(风险越高、利率越高)。
问题 2:为什么不能用线性函数直接输出概率?
线性函数 z = w T x + b z=\mathbf{w}^T\mathbf{x}+b z=wTx+b 的取值范围是 ( − ∞ , + ∞ ) (-\infty,+\infty) (−∞,+∞),而概率必须落在 ( 0 , 1 ) (0,1) (0,1)。硬截断(小于 0 记 0、大于 1 记 1)会带来两个后果:截断区间的梯度为 0,无法优化;且"概率随收入线性下降"也不符合现实------收入从 10 万到 20 万,违约率的下降幅度通常远大于从 100 万到 110 万。
问题 3:怎样把"打分"翻译成"概率"?
关键是找到一个单调、值域为 ( 0 , 1 ) (0,1) (0,1)、且能把 ( − ∞ , + ∞ ) (-\infty,+\infty) (−∞,+∞) 完整映射进来的函数。满足这三条的经典选择就是 sigmoid:
σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+e−z1
它把任意实数压到 ( 0 , 1 ) (0,1) (0,1): z = 0 z=0 z=0 时正好 0.5, z → + ∞ z\to+\infty z→+∞ 趋近 1, z → − ∞ z\to-\infty z→−∞ 趋近 0。更妙的是,它有一个极简的导数形式 σ ′ ( z ) = σ ( z ) ( 1 − σ ( z ) ) \sigma'(z)=\sigma(z)(1-\sigma(z)) σ′(z)=σ(z)(1−σ(z)),这让后面的梯度下降非常干净。
建模路线 :线性打分 z = w T x + b z=\mathbf{w}^T\mathbf{x}+b z=wTx+b → sigmoid 得概率 → 极大似然估计参数 → 梯度下降求解 → 用阈值把概率转成类别 → 用混淆矩阵、ROC-AUC 评价。
三、模型假设
- 样本独立同分布:每个客户的违约事件相互独立,且来自同一个数据生成机制
- 对数几率线性 :特征对"违约几率"的影响是线性的,即 ln p 1 − p = w T x + b \ln\frac{p}{1-p}=\mathbf{w}^T\mathbf{x}+b ln1−pp=wTx+b(这是逻辑回归最核心的假设)
- 特征无完全共线性:解释变量之间不存在严格的线性关系,否则系数不可辨识
- 标签可靠:历史数据中的"违约 / 未违约"记录准确无误,不存在标签噪声
- 特征已清洗并标准化:缺失值、异常值已处理;连续特征在建模前做 Z-score 标准化(原因见 6.2 节)
第 2 条是"模型假设",也是最容易被忽视的一条:逻辑回归的决策边界是线性的,它假设"特征每增加一个单位,对数几率平移一个固定量"。如果真实关系是"收入低于某个阈值才急剧违约",就需要构造分段特征或换用非线性模型。
四、符号说明
| 符号 | 含义 | 备注 |
|---|---|---|
| n n n | 样本数 | 本文 800 个客户 |
| d d d | 特征数 | 本文 3 个:年收入、负债收入比、逾期次数 |
| x i ∈ R d \mathbf{x}_i\in\mathbb{R}^d xi∈Rd | 第 i i i 个样本的特征向量 | 已标准化 |
| y i ∈ { 0 , 1 } y_i\in\{0,1\} yi∈{0,1} | 标签 | 1 = 违约,0 = 未违约 |
| w ∈ R d \mathbf{w}\in\mathbb{R}^d w∈Rd | 特征权重 | 待估计参数 |
| b ∈ R b\in\mathbb{R} b∈R | 截距(偏置) | 待估计参数 |
| z i = w T x i + b z_i=\mathbf{w}^T\mathbf{x}_i+b zi=wTxi+b | 线性打分 | 也称 logit |
| p i = σ ( z i ) p_i=\sigma(z_i) pi=σ(zi) | 预测违约概率 | σ \sigma σ 为 sigmoid |
| J J J | 损失函数(负对数似然均值) | 越小越好 |
| α \alpha α | 学习率 | 梯度下降步长 |
| λ \lambda λ | L2 正则强度 | 抑制系数过大 |
五、模型建立
5.1 从"几率"到 sigmoid
先定义一个中间量------几率(odds):
odds = p 1 − p \text{odds}=\frac{p}{1-p} odds=1−pp
它表示"发生与不发生"的相对可能性。比如 p = 0.8 p=0.8 p=0.8,则 odds = 4 =4 =4,即"违约的可能性是不违约的 4 倍"。几率的取值范围是 ( 0 , + ∞ ) (0,+\infty) (0,+∞),仍然不是全实数轴。
再取对数,得到对数几率(log-odds,又称 logit):
logit ( p ) = ln p 1 − p \text{logit}(p)=\ln\frac{p}{1-p} logit(p)=ln1−pp
这一步把 ( 0 , 1 ) (0,1) (0,1) 完整地拉伸到 ( − ∞ , + ∞ ) (-\infty,+\infty) (−∞,+∞)------概率 0.5 对应 logit 0,概率趋近 1 对应 + ∞ +\infty +∞,趋近 0 对应 − ∞ -\infty −∞。
逻辑回归的核心假设就是:对数几率是特征的线性函数。
ln p 1 − p = w T x + b = z \ln\frac{p}{1-p}=\mathbf{w}^T\mathbf{x}+b=z ln1−pp=wTx+b=z
反解出 p p p:
p 1 − p = e z ⟹ p = e z ( 1 − p ) ⟹ p ( 1 + e z ) = e z ⟹ p = e z 1 + e z = 1 1 + e − z \frac{p}{1-p}=e^{z}\;\Longrightarrow\;p=e^{z}(1-p)\;\Longrightarrow\;p(1+e^{z})=e^{z}\;\Longrightarrow\;p=\frac{e^{z}}{1+e^{z}}=\frac{1}{1+e^{-z}} 1−pp=ez⟹p=ez(1−p)⟹p(1+ez)=ez⟹p=1+ezez=1+e−z1
这正是 sigmoid。整条推导可以概括成一句话:
逻辑回归 = 用线性函数建模"对数几率",再用 sigmoid 反解回概率。
| z z z | − 3 -3 −3 | − 1 -1 −1 | 0 0 0 | 1 1 1 | 3 3 3 |
|---|---|---|---|---|---|
| p = σ ( z ) p=\sigma(z) p=σ(z) | 0.047 | 0.269 | 0.500 | 0.731 | 0.953 |
注意 z z z 每增加 1,概率并不是"增加固定值"------在 0 附近增加最快(约 0.25),两端趋于平缓。这正是 sigmoid 的 S 形特征,也符合"风险从低到高时敏感度先升后降"的直觉。
5.2 极大似然 → 交叉熵损失
参数 w , b \mathbf{w},b w,b 怎么估?用极大似然。
对第 i i i 个样本,模型给出 p i = P ( y i = 1 ∣ x i ) p_i=P(y_i=1\mid\mathbf{x}_i) pi=P(yi=1∣xi)。若真实标签 y i = 1 y_i=1 yi=1,出现这个结果的概率是 p i p_i pi;若 y i = 0 y_i=0 yi=0,则是 1 − p i 1-p_i 1−pi。两者可以统一写成:
P ( y i ∣ x i ) = p i y i ( 1 − p i ) 1 − y i P(y_i\mid\mathbf{x}_i)=p_i^{y_i}(1-p_i)^{1-y_i} P(yi∣xi)=piyi(1−pi)1−yi
(验证: y i = 1 y_i=1 yi=1 时右边 = p i =p_i =pi; y i = 0 y_i=0 yi=0 时右边 = 1 − p i =1-p_i =1−pi。)
假设样本独立,全部样本的似然函数为连乘:
L ( w , b ) = ∏ i = 1 n p i y i ( 1 − p i ) 1 − y i L(\mathbf{w},b)=\prod_{i=1}^{n}p_i^{y_i}(1-p_i)^{1-y_i} L(w,b)=i=1∏npiyi(1−pi)1−yi
连乘容易下溢,取对数变成求和:
ℓ ( w , b ) = ln L = ∑ i = 1 n y i ln p i + ( 1 − y i ) ln ( 1 − p i ) \ell(\mathbf{w},b)=\ln L=\sum_{i=1}^{n}\Bigy_i\\ln p_i+(1-y_i)\\ln(1-p_i)\\Big ℓ(w,b)=lnL=i=1∑nyilnpi+(1−yi)ln(1−pi)
极大似然要求 ℓ \ell ℓ 最大。优化习惯求最小,于是取负号并除以 n n n,得到交叉熵损失(cross-entropy loss):
J ( w , b ) = − 1 n ∑ i = 1 n y i ln p i + ( 1 − y i ) ln ( 1 − p i ) J(\mathbf{w},b)=-\frac{1}{n}\sum_{i=1}^{n}\Bigy_i\\ln p_i+(1-y_i)\\ln(1-p_i)\\Big J(w,b)=−n1i=1∑nyilnpi+(1−yi)ln(1−pi)
其中, y i ln p i y_i\ln p_i yilnpi 只在真实标签为 1 时"激活", ( 1 − y i ) ln ( 1 − p i ) (1-y_i)\ln(1-p_i) (1−yi)ln(1−pi) 只在标签为 0 时"激活"------所以交叉熵本质上是"只惩罚预测错的那一侧":
-
真实为 1、预测 p → 0 p\to0 p→0: ln p → − ∞ \ln p\to-\infty lnp→−∞,惩罚趋于无穷
-
真实为 1、预测 p → 1 p\to1 p→1: ln p → 0 \ln p\to0 lnp→0,几乎不惩罚
这个"错了就重罚、对了就不罚"的形状,比均方误差更适合分类,第七节 Q2 会展开。
5.3 梯度推导:残差 × 特征
对 J J J 关于 w \mathbf{w} w 求偏导。先算单个样本:
∂ ∂ w − y ln p − ( 1 − y ) ln ( 1 − p ) = − y p ∂ p ∂ w + 1 − y 1 − p ∂ p ∂ w \frac{\partial}{\partial \mathbf{w}}\Big-y\\ln p-(1-y)\\ln(1-p)\\Big =-\frac{y}{p}\frac{\partial p}{\partial \mathbf{w}}+\frac{1-y}{1-p}\frac{\partial p}{\partial \mathbf{w}} ∂w∂−ylnp−(1−y)ln(1−p)=−py∂w∂p+1−p1−y∂w∂p
利用 ∂ p ∂ z = p ( 1 − p ) \frac{\partial p}{\partial z}=p(1-p) ∂z∂p=p(1−p) 和 ∂ z ∂ w = x \frac{\partial z}{\partial \mathbf{w}}=\mathbf{x} ∂w∂z=x:
∂ J i ∂ w = − y p + 1 − y 1 − p p ( 1 − p ) x = ( p − y ) x \frac{\partial J_i}{\partial \mathbf{w}}=\Big-\\frac{y}{p}+\\frac{1-y}{1-p}\\Bigp(1-p)\mathbf{x}=(p-y)\mathbf{x} ∂w∂Ji=−py+1−p1−yp(1−p)x=(p−y)x
对所有样本取平均,写成矩阵形式:
∂ J ∂ w = 1 n X T ( p − y ) , ∂ J ∂ b = 1 n ∑ i = 1 n ( p i − y i ) \boxed{\;\frac{\partial J}{\partial \mathbf{w}}=\frac{1}{n}X^T(\mathbf{p}-\mathbf{y}),\qquad \frac{\partial J}{\partial b}=\frac{1}{n}\sum_{i=1}^{n}(p_i-y_i)\;} ∂w∂J=n1XT(p−y),∂b∂J=n1i=1∑n(pi−yi)
其中, X ∈ R n × d X\in\mathbb{R}^{n\times d} X∈Rn×d 是特征矩阵, p , y ∈ R n \mathbf{p},\mathbf{y}\in\mathbb{R}^n p,y∈Rn 分别是预测概率向量和真实标签向量。
这个结果有一个非常漂亮的解释:梯度 = 残差 × 特征 。预测得越离谱( p i − y i p_i-y_i pi−yi 越大),该样本的特征对梯度的推动越强。把它和第 7 篇线性回归的梯度 1 n X T ( X w − y ) \frac{1}{n}X^T(X\mathbf{w}-\mathbf{y}) n1XT(Xw−y) 放在一起看,会发现结构完全一样------只是把"预测值"从 X w X\mathbf{w} Xw 换成了 sigmoid 输出 p \mathbf{p} p。这是本专栏反复出现的一条暗线:不同模型的训练,常常只是"预测 → 残差 → 沿特征回传"这一套动作的不同变体。
5.4 为什么必须用迭代法,而没有闭式解
线性回归可以一步解出 w ^ = ( X T X ) − 1 X T y \hat{\mathbf{w}}=(X^TX)^{-1}X^T\mathbf{y} w^=(XTX)−1XTy,逻辑回归不行。因为 sigmoid 是非线性的,令梯度为 0 得到的是超越方程,没有解析解。
但有个好消息:交叉熵损失关于 w \mathbf{w} w 是凸函数。其 Hessian 为
H = 1 n X T S X , S = diag ( p i ( 1 − p i ) ) H=\frac{1}{n}X^TSX,\qquad S=\text{diag}\big(p_i(1-p_i)\big) H=n1XTSX,S=diag(pi(1−pi))
因为 p i ( 1 − p i ) > 0 p_i(1-p_i)>0 pi(1−pi)>0, S S S 正定,所以 H H H 半正定, J J J 是凸函数。凸性保证了梯度下降无论从哪初始化,都能收敛到全局最优------这是逻辑回归在工业界经久不衰的重要原因之一。
5.5 决策边界与阈值
模型输出的是概率,要变成"违约 / 不违约",需要设一个阈值 t t t(默认 0.5):
y ^ = { 1 , p ≥ t 0 , p < t \hat y=\begin{cases}1,& p\ge t\\0,& p<t\end{cases} y^={1,0,p≥tp<t
阈值 0.5 等价于判断 z ≥ 0 z\ge0 z≥0,即
w T x + b = 0 \mathbf{w}^T\mathbf{x}+b=0 wTx+b=0
这是一张超平面(二维时是一条直线)。所以:
逻辑回归虽然用了非线性的 sigmoid,但它的决策边界仍然是线性的------sigmoid 只负责把"到平面的距离"翻译成概率,不改变边界的形状。
若阈值取 t ≠ 0.5 t\neq0.5 t=0.5,边界平移为 z = ln t 1 − t z=\ln\frac{t}{1-t} z=ln1−tt:
| 阈值 t t t | 对应的 z z z 临界值 | 业务含义 |
|---|---|---|
| 0.2 | − 1.386 -1.386 −1.386 | 宁可错杀,不可放过(高召回) |
| 0.5 | 0 0 0 | 默认平衡 |
| 0.8 | + 1.386 +1.386 +1.386 | 只抓最确定的(高精确) |
阈值不是模型参数,而是业务决策。风控里漏判一个违约客户的代价,通常远高于误拒一个好客户,所以实际阈值往往低于 0.5------6.4 节会用数据展示这一取舍。
5.6 正则化:当"完全分离"发生时
和线性回归一样,逻辑回归也会过拟合,尤其是特征多、样本少的时候。加 L2 惩罚:
J λ ( w , b ) = J ( w , b ) + λ 2 n ∥ w ∥ 2 2 J_{\lambda}(\mathbf{w},b)=J(\mathbf{w},b)+\frac{\lambda}{2n}\|\mathbf{w}\|_2^2 Jλ(w,b)=J(w,b)+2nλ∥w∥22
梯度相应变为
∂ J λ ∂ w = 1 n X T ( p − y ) + λ n w \frac{\partial J_\lambda}{\partial \mathbf{w}}=\frac{1}{n}X^T(\mathbf{p}-\mathbf{y})+\frac{\lambda}{n}\mathbf{w} ∂w∂Jλ=n1XT(p−y)+nλw
逻辑回归还有一个线性回归没有的"病"------完全分离(complete separation) :如果某个特征能完美分开两类(比如"逾期次数 ≥ 3 的客户全部违约,≤ 2 的全部没违约"),极大似然解会让对应的系数趋向无穷大 (因为 p p p 越接近 0/1,似然越大),算法不收敛。加入 L2 惩罚后,系数被拉回有限值,问题消失。这也是 sklearn 的 LogisticRegression 默认带正则的原因。
在 sklearn 中,正则强度用 C C C 控制, C C C 越小正则越强, C = ∞ C=\infty C=∞ 等价于不正则(对应 λ = 0 \lambda=0 λ=0)。本文数据没有完全分离,所以两种设置结果几乎一致。
六、Python 实现
6.1 数据生成与划分
我们按真实的违约逻辑生成 800 条模拟数据:收入越高越不容易违约,负债比越高、历史逾期越多越容易违约。
python
import numpy as np
np.set_printoptions(suppress=True, precision=4)
rng = np.random.default_rng(2024)
n = 800
income = np.clip(rng.normal(25, 8, n), 6, 60) # 年收入(万元)
dti = np.clip(rng.normal(45, 15, n), 5, 95) # 负债收入比(%)
overdue = np.clip(rng.poisson(1.2, n), 0, 5) # 历史逾期次数
# 真实的数据生成机制(建模时未知,这里只为造出有规律的样本)
# 注:overdue 未除标准差,仅为生成数据方便;建模时仍会统一标准化
z_true = -1.2*(income-25)/8 + 0.7*(dti-45)/15 + 1.1*(overdue-1.2) - 1.0
p_true = 1/(1+np.exp(-z_true))
y = rng.binomial(1, p_true) # 按概率抽样得到标签
X = np.column_stack([income, dti, overdue])
print("违约率:", round(y.mean(), 4), "违约数:", y.sum())
print("前5行 [收入, 负债比, 逾期, 标签]:")
print(np.column_stack([income[:5], dti[:5], overdue[:5], y[:5]]).round(2))
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y) # stratify 保证分层抽样
print("训练集:", X_tr.shape, "违约", y_tr.sum(),
"| 测试集:", X_te.shape, "违约", y_te.sum())
sc = StandardScaler().fit(X_tr) # 只用训练集拟合均值方差
X_tr_s, X_te_s = sc.transform(X_tr), sc.transform(X_te)
运行结果:
违约率: 0.3262 违约数: 261
前5行 [收入, 负债比, 逾期, 标签]:
[[33.23 41.52 1. 0. ]
[38.14 65.88 4. 1. ]
[34.17 55.44 3. 1. ]
[17.21 59.61 0. 0. ]
[13.86 46.4 2. 1. ]]
训练集: (560, 3) 违约 183 | 测试集: (240, 3) 违约 78
两个细节值得注意:
stratify=y:分层抽样让训练集和测试集的违约比例一致(都约 32.6%)。否则随机切分可能让测试集违约率偏差很大,指标失真。StandardScaler只在训练集上 fit :测试集只能transform,不能参与计算均值和方差------否则测试集信息泄漏进训练过程,评估结果会虚高。这是机器学习里最容易犯的错之一。
6.2 手写梯度下降
先把算法写出来,不依赖任何现成模型,看清每一步在做什么。
python
import numpy as np
np.set_printoptions(suppress=True, precision=4)
# ---- 数据段(与 6.1 节完全相同,保证本段可独立运行)----
rng = np.random.default_rng(2024)
n = 800
income = np.clip(rng.normal(25, 8, n), 6, 60)
dti = np.clip(rng.normal(45, 15, n), 5, 95)
overdue = np.clip(rng.poisson(1.2, n), 0, 5)
z_true = -1.2*(income-25)/8 + 0.7*(dti-45)/15 + 1.1*(overdue-1.2) - 1.0
y = rng.binomial(1, 1/(1+np.exp(-z_true)))
X = np.column_stack([income, dti, overdue])
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y)
sc = StandardScaler().fit(X_tr)
X_tr_s = sc.transform(X_tr)
# ---- 核心算法 ----
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def fit_logistic(X, y, lr=0.5, n_iter=3000):
"""批量梯度下降求解逻辑回归,X 须已标准化"""
m, d = X.shape
Xb = np.column_stack([np.ones(m), X]) # 第 0 列补 1,对应截距 b
w = np.zeros(d + 1) # 参数初始化为 0
losses = []
for _ in range(n_iter):
p = sigmoid(Xb @ w) # 前向:算概率
grad = Xb.T @ (p - y) / m # 梯度 X^T(p-y)/m
w -= lr * grad # 反向:更新参数
loss = -np.mean(y*np.log(p+1e-12) + (1-y)*np.log(1-p+1e-12))
losses.append(loss)
return w, losses
w, losses = fit_logistic(X_tr_s, y_tr)
print("标准化系数(含截距):", w)
print(f"损失: {losses[0]:.4f} -> {losses[-1]:.4f}")
print("前 10 轮损失:", np.round(losses[:10], 4))
# 把系数还原到原始尺度:z = b + Σ w_j (x_j-μ_j)/σ_j
mu, sigma = sc.mean_, sc.scale_
w_raw = w[1:] / sigma
b_raw = w[0] - np.sum(w[1:] * mu / sigma)
print("原始尺度系数:", np.round(w_raw, 4), "截距:", round(b_raw, 4))
print("优势比(每 +1 标准差):", np.round(np.exp(w[1:]), 4))
运行结果:
标准化系数(含截距): [-1.1324 -1.22 0.9237 1.4247]
损失: 0.6931 -> 0.4253
前 10 轮损失: [0.6931 0.6487 0.6137 0.5858 0.5634 0.5452 0.5303 0.5179 0.5074 0.4986]
原始尺度系数: [-0.1547 0.0588 1.3 ] 截距: -1.465
优势比(每 +1 标准差): [0.2952 2.5186 4.1565]
怎么读这组结果:
-
损失从 0.6931 降到 0.4253 。0.6931 是 − ln 0.5 -\ln 0.5 −ln0.5,正是"什么都不知道、一律猜 50%"的损失,也是二分类的基准线。降到 0.4253 说明模型确实学到了规律。
-
标准化系数 − 1.22 , 0.924 , 1.425 -1.22,\\ 0.924,\\ 1.425 −1.22, 0.924, 1.425:收入为负(越高越安全),负债比为正,逾期次数为正。绝对值可比(都已标准化),逾期次数的影响最大,负债比次之,收入最小。
-
优势比:收入每增加 1 个标准差,违约几率乘以 0.295(降为原来的 29.5%);负债比每增加 1 个标准差,几率乘以 2.52;逾期次数每增加 1 个标准差,几率乘以 4.16。这比"系数是 1.42"好懂太多,写论文解读时优先用它。
-
原始尺度系数:收入每多 1 万元,对数几率下降 0.155;负债比每多 1 个百分点,上升 0.059;每多一次逾期,上升 1.3。还原公式就是上面两行代码,本质是"标准化只改变坐标系,不改变模型"。
代入一个客户算算看:
| 客户 | 收入 | 负债比 | 逾期 | z z z | 违约概率 |
|---|---|---|---|---|---|
| 甲 | 18 万 | 70% | 2 次 | 2.466 | 92.2% |
| 乙 | 35 万 | 30% | 0 次 | -5.116 | 0.6% |
| 丙 | 25 万 | 45% | 1 次 | -1.387 | 20.0% |
计算过程(以甲为例): z = − 1.465 − 0.1547 × 18 + 0.0588 × 70 + 1.3 × 2 = 2.466 z=-1.465-0.1547\times18+0.0588\times70+1.3\times2=2.466 z=−1.465−0.1547×18+0.0588×70+1.3×2=2.466, p = σ ( 2.466 ) = 0.922 p=\sigma(2.466)=0.922 p=σ(2.466)=0.922。同一个模型,对不同客户给出的风险差异非常直观。
6.3 与 sklearn 对照,并计算分类指标
python
import numpy as np
np.set_printoptions(suppress=True, precision=4)
# ---- 数据段(与 6.1 节相同)----
rng = np.random.default_rng(2024)
n = 800
income = np.clip(rng.normal(25, 8, n), 6, 60)
dti = np.clip(rng.normal(45, 15, n), 5, 95)
overdue = np.clip(rng.poisson(1.2, n), 0, 5)
z_true = -1.2*(income-25)/8 + 0.7*(dti-45)/15 + 1.1*(overdue-1.2) - 1.0
y = rng.binomial(1, 1/(1+np.exp(-z_true)))
X = np.column_stack([income, dti, overdue])
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (confusion_matrix, precision_score,
recall_score, f1_score, roc_auc_score)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y)
sc = StandardScaler().fit(X_tr)
X_tr_s, X_te_s = sc.transform(X_tr), sc.transform(X_te)
# sklearn 求解(C=np.inf 表示不加正则,和 6.2 节手写版对齐)
sk = LogisticRegression(C=np.inf, max_iter=1000).fit(X_tr_s, y_tr)
print("sklearn 标准化系数:", sk.coef_)
print("sklearn 截距:", sk.intercept_)
p_te = sk.predict_proba(X_te_s)[:, 1] # 测试集违约概率
pred = (p_te >= 0.5).astype(int) # 阈值 0.5 转成类别
cm = confusion_matrix(y_te, pred)
print("\n混淆矩阵 [[TN FP],[FN TP]]:\n", cm)
print("准确率 %.4f" % (pred == y_te).mean())
print("精确率 %.4f" % precision_score(y_te, pred))
print("召回率 %.4f" % recall_score(y_te, pred))
print("F1 %.4f" % f1_score(y_te, pred))
print("AUC %.4f" % roc_auc_score(y_te, p_te))
运行结果:
sklearn 标准化系数: [[-1.22 0.9236 1.4252]]
sklearn 截距: [-1.1322]
混淆矩阵 [[TN FP],[FN TP]]:
[[144 18]
[ 28 50]]
准确率 0.8083
精确率 0.7353
召回率 0.6410
F1 0.6849
AUC 0.8446
sklearn 的系数与手写梯度下降吻合到小数点后三位左右 ,最大差异约 5 × 10 − 4 5\times10^{-4} 5×10−4([-1.22, 0.9236, 1.4252] vs [-1.22, 0.9237, 1.4247])------这是两个求解器各自的收敛容差所致,不是代码错误。手写版是固定 3000 步的批量梯度下降,sklearn 是拟牛顿类求解器,两者都落在了同一个最优点附近。这个"互相印证"是代码层面最直接的正确性检验。
这四个指标分别在看什么(记 TP=真正例、FP=假正例、FN=假负例、TN=真负例):
精确率 = T P T P + F P , 召回率 = T P T P + F N , F1 = 2 ⋅ P ⋅ R P + R \text{精确率}=\frac{TP}{TP+FP},\qquad \text{召回率}=\frac{TP}{TP+FN},\qquad \text{F1}=\frac{2\cdot P\cdot R}{P+R} 精确率=TP+FPTP,召回率=TP+FNTP,F1=P+R2⋅P⋅R
-
准确率 80.8% :全部 240 个里判对 194 个。听着不错,但在测试集违约率 32.5% 的数据上,"全判不违约"也能有 67.5% 的准确率------所以准确率在类别不平衡时具有欺骗性。
-
精确率 73.5%:模型说"会违约"的 68 人里,50 人真的违约了,18 人是误伤。
-
召回率 64.1% :真正违约的 78 人里,抓到了 50 个,漏掉 28 个。对银行来说,这 28 个才是真正的损失。
-
AUC 0.8446 :随机抽一个违约客户和一个未违约客户,模型给前者打分更高的概率是 84.5%。AUC 与阈值无关,衡量的是模型排序能力,是类别不平衡时最稳健的整体指标。
6.4 阈值敏感性:0.5 不是天经地义
python
import numpy as np
np.set_printoptions(suppress=True, precision=4)
# ---- 数据段与训练(与 6.3 节相同)----
rng = np.random.default_rng(2024)
n = 800
income = np.clip(rng.normal(25, 8, n), 6, 60)
dti = np.clip(rng.normal(45, 15, n), 5, 95)
overdue = np.clip(rng.poisson(1.2, n), 0, 5)
z_true = -1.2*(income-25)/8 + 0.7*(dti-45)/15 + 1.1*(overdue-1.2) - 1.0
y = rng.binomial(1, 1/(1+np.exp(-z_true)))
X = np.column_stack([income, dti, overdue])
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (confusion_matrix, precision_score,
recall_score, f1_score)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y)
sc = StandardScaler().fit(X_tr)
sk = LogisticRegression(C=np.inf, max_iter=1000).fit(sc.transform(X_tr), y_tr)
p_te = sk.predict_proba(sc.transform(X_te))[:, 1]
print("阈值 精确率 召回率 F1 FN FP 准确率")
for thr in [0.2, 0.3, 0.4, 0.5, 0.6]:
pred = (p_te >= thr).astype(int)
cm = confusion_matrix(y_te, pred)
print(f"{thr:.1f} {precision_score(y_te, pred):.4f} "
f"{recall_score(y_te, pred):.4f} {f1_score(y_te, pred):.4f} "
f"{cm[1,0]:3d} {cm[0,1]:3d} {(pred==y_te).mean():.4f}")
运行结果:
阈值 精确率 召回率 F1 FN FP 准确率
0.2 0.5074 0.8846 0.6449 9 67 0.6833
0.3 0.5596 0.7821 0.6524 17 48 0.7292
0.4 0.6512 0.7179 0.6829 22 30 0.7833
0.5 0.7353 0.6410 0.6849 28 18 0.8083
0.6 0.7500 0.5385 0.6269 36 14 0.7917
这张表把"阈值是业务决策"这句话讲透了:
-
阈值 0.2:召回率 88.5%,78 个违约客户只漏 9 个;代价是误伤 67 个好客户。适合"宁可错杀"的场景。
-
阈值 0.6:精确率 75.0%,误伤只有 14 个;但漏掉 36 个违约客户。适合"只抓最确定的"的场景。
-
F1 最高出现在 0.5 附近(0.6849),但 F1 只是一个折中指标------如果业务上漏判的代价是误判的 5 倍,最优阈值应明显低于 0.5。
竞赛写作提示:论文里不要只报 0.5 阈值下的指标,附一张这样的敏感性表,再结合业务说清"为什么选这个阈值",是分类题最容易加分的地方。
6.5 结果可视化
python
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, auc
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# ---- 数据段(与 6.1 节相同)----
rng = np.random.default_rng(2024)
n = 800
income = np.clip(rng.normal(25, 8, n), 6, 60)
dti = np.clip(rng.normal(45, 15, n), 5, 95)
overdue = np.clip(rng.poisson(1.2, n), 0, 5)
z_true = -1.2*(income-25)/8 + 0.7*(dti-45)/15 + 1.1*(overdue-1.2) - 1.0
y = rng.binomial(1, 1/(1+np.exp(-z_true)))
# 为了能画在平面上,只用两个特征:收入 + 负债比
X2 = np.column_stack([income, dti])
X_tr, X_te, y_tr, y_te = train_test_split(
X2, y, test_size=0.3, random_state=42, stratify=y)
sc = StandardScaler().fit(X_tr)
sk = LogisticRegression(C=np.inf, max_iter=1000).fit(sc.transform(X_tr), y_tr)
p_te = sk.predict_proba(sc.transform(X_te))[:, 1]
fig, axes = plt.subplots(1, 2, figsize=(12, 4.8))
# 左图:散点 + 决策边界
ax = axes[0]
ax.scatter(income[y == 0], dti[y == 0], s=22, c="#4C78A8", alpha=0.65, label="未违约 (y=0)")
ax.scatter(income[y == 1], dti[y == 1], s=22, c="#E45756", alpha=0.7, label="违约 (y=1)")
w, b = sk.coef_[0], sk.intercept_[0]
xs = np.linspace(6, 60, 200)
# 由 b + w1*(x1-μ1)/σ1 + w2*(x2-μ2)/σ2 = 0 反解 x2
ys = sc.mean_[1] - sc.scale_[1]/w[1] * (b + w[0]*(xs - sc.mean_[0])/sc.scale_[0])
ax.plot(xs, ys, color="#2E7D5B", lw=2.2, label="决策边界 p=0.5")
ax.set_xlabel("年收入(万元)")
ax.set_ylabel("负债收入比(%)")
ax.set_title("(a) 二维决策边界:一条直线分开两类")
ax.set_xlim(5, 62)
ax.set_ylim(0, 100)
ax.legend(loc="upper right", fontsize=9, frameon=True)
# 右图:ROC 曲线
ax = axes[1]
fpr, tpr, _ = roc_curve(y_te, p_te)
ax.plot(fpr, tpr, color="#2E7D5B", lw=2.2, label=f"ROC (AUC = {auc(fpr, tpr):.3f})")
ax.plot([0, 1], [0, 1], "--", color="#999999", lw=1.2, label="随机猜测 (AUC = 0.5)")
ax.set_xlabel("假正率 FPR")
ax.set_ylabel("真正率 TPR(召回率)")
ax.set_title("(b) ROC 曲线:阈值扫描的全景")
ax.legend(loc="lower right", fontsize=9)
ax.grid(alpha=0.25)
plt.tight_layout()
plt.show()
运行结果:

(图注:左图绿色直线为 p = 0.5 p=0.5 p=0.5 的决策边界,右下侧判为"未违约"、左上侧判为"违约";右图 ROC 曲线越靠近左上角,模型排序能力越强。)
为了把决策边界画在平面上,6.5 节只用"收入 + 负债比"两个特征重新训练了一个二维模型(AUC=0.728);作为对比,6.3 节的三维模型(再加入"逾期次数")AUC=0.8446。两图用的都是这个二维模型。
读图要点:
-
左图的边界是一条直线 ,不管 sigmoid 多弯曲------这再次说明逻辑回归是线性分类器。红点(违约)集中在左上(低收入、高负债),蓝点集中在右下,但交界处重叠严重,这是真实数据的常态,也是准确率上不去的原因。
-
右图的 ROC 曲线 :每取一个阈值,就在曲线上对应一个点(FPR, TPR)。阈值从 1 降到 0,点从左上角走到右下角。曲线越贴近左上角,AUC 越大。对角线是"随机猜",AUC=0.5。本文二维模型 AUC=0.728,加上"逾期次数"后升到 0.845------一个强特征的价值,比换模型大得多。
七、模型评价
优点:
- 输出是概率,不是硬标签,可直接用于定价、排序、风险分级
- 可解释性极强:系数符号说明方向,优势比说明倍数,天然适合写进论文的"因素分析"
- 凸优化:损失函数是凸的,梯度下降必达全局最优,不存在"调参调不出来"的玄学
- 训练快、预测快 :参数只有 d + 1 d+1 d+1 个,十万级样本秒级收敛
- 是分类模型的基准线:任何复杂模型(SVM、随机森林、神经网络)都应该先和逻辑回归比一比,赢不了它就说明复杂化没有价值
局限:
- 只能学线性边界:特征与对数几率的关系必须是线性的,复杂的非线性关系需要人工构造特征(分箱、交互项、多项式)或换模型
- 对共线性敏感:两个高度相关的特征会导致系数不稳定(符号甚至反转),解释时需先做相关性筛查
- 对异常值和量纲敏感:标准化几乎是必做步骤
- 完全分离时不收敛:需要正则化兜底
- 样本量要求:每个特征至少需要 10 个左右的少数类样本(EPV 原则),否则估计不稳
常见问题:
Q1:为什么不能直接用线性回归做 0/1 分类?
三个理由。第一,输出无界 :线性回归的输出可以是 − 3 -3 −3 或 + 5 +5 +5,不是概率,硬截断后无法解释。第二,误差假设不成立 :线性回归假设残差服从正态分布、方差齐性,而 0/1 标签的残差只能是 p p p 或 1 − p 1-p 1−p,方差随 p p p 变化(异方差)。第三,损失与目标的匹配度不同 :0/1 标签本质是伯努利分布,平方损失等价于假设高斯噪声,与数据生成机制不符;而且平方损失的梯度会被"特征极端但标签相反"的离群点放大,把回归面拽偏。逻辑回归的梯度残差因子 ( p − y ) (p-y) (p−y) 始终落在 − 1 , 1 -1,1 −1,1,单个异常点最多贡献一倍的修正量,不会失控。
Q2:为什么用交叉熵而不是均方误差?
若用 MSE, J = 1 n ∑ ( p i − y i ) 2 J=\frac{1}{n}\sum(p_i-y_i)^2 J=n1∑(pi−yi)2,对 sigmoid 求导会多出一个因子 p ( 1 − p ) p(1-p) p(1−p):
∂ J ∂ z = ( p − y ) ⋅ p ( 1 − p ) \frac{\partial J}{\partial z}=(p-y)\cdot p(1-p) ∂z∂J=(p−y)⋅p(1−p)
当预测严重错误( p → 0 p\to0 p→0 而 y = 1 y=1 y=1)时, p ( 1 − p ) → 0 p(1-p)\to0 p(1−p)→0,梯度反而消失了 ------模型错得越狠,学得越慢。而交叉熵的梯度是 ( p − y ) (p-y) (p−y),错误越大梯度越大,正好相反。此外,MSE + sigmoid 的损失函数一般非凸,优化时可能陷入局部极小;交叉熵 + sigmoid 是凸的。所以分类问题默认用交叉熵。
Q3:准确率 80% 算好吗?
要看基线。测试集违约率 32.5%,"全判不违约"的准确率是 67.5%,模型只比它高约 13 个百分点。更值得看的是 AUC(0.845)和召回率(0.641) :前者说明排序能力确实不错,后者说明还有 36% 的违约客户被漏掉。类别不平衡时,准确率是最不该单独汇报的指标。
Q4:样本不平衡怎么办?
三种常见手段:一是调阈值 (最简单,本文 0.2 阈值把召回拉到 88.5%);二是重采样 (对少数类过采样或对多数类欠采样,注意只在训练集做);三是代价敏感学习 (给少数类样本更大的损失权重,sklearn 的 class_weight='balanced')。注意:改采样会改变预测概率的校准性,评估时应改用 PR-AUC 或直接看业务代价。
Q5:什么是"完全分离",怎么判断?
如果存在某个系数使得所有正样本得分都高于所有负样本,就是完全分离。表现是训练时系数持续增大、损失趋近 0 但 n_iter 用完仍未收敛,sklearn 会给出 ConvergenceWarning。解决办法:加 L2 正则(把 C 调小)或删掉那个"完美特征"。完美特征在业务上往往是数据泄漏(比如"是否已进入催收流程"直接决定了"是否违约"),要特别警惕。
Q6:系数大小能直接比较重要性吗?
不能直接比原始尺度系数------收入以"万元"计、负债比以"百分点"计,单位不同,系数不可比。先标准化再比较 (本文标准化系数 − 1.22 , 0.924 , 1.425 -1.22,\ 0.924,\ 1.425 −1.22, 0.924, 1.425 才可比),或者比较优势比。这和第 8 篇 岭回归/LASSO 的结论一致:惩罚和比较都要求同一把尺子。
八、AI 视角:逻辑回归是神经网络的"单个神经元"
逻辑回归看起来古典,但它几乎是现代深度学习的最小单元。这一节把伏笔接上。
8.1 一个神经元 = 一次线性变换 + 一次激活
把逻辑回归的计算画成图:
x ⟶ z = w T x + b ⟶ p = σ ( z ) \mathbf{x}\ \longrightarrow\ z=\mathbf{w}^T\mathbf{x}+b\ \longrightarrow\ p=\sigma(z) x ⟶ z=wTx+b ⟶ p=σ(z)
这正是神经网络里一个神经元 的全部结构:线性加权求和( w , b \mathbf{w},b w,b)+ 非线性激活(sigmoid)。所谓"深度神经网络",不过是把很多个这样的神经元分层堆叠、把 sigmoid 换成 ReLU 等激活函数、用反向传播求梯度。逻辑回归的梯度公式 1 n X T ( p − y ) \frac{1}{n}X^T(\mathbf{p}-\mathbf{y}) n1XT(p−y),就是反向传播在单层网络上的特例。
8.2 从 sigmoid 到 softmax:二分类 → 多分类
二分类用 sigmoid 把打分压成概率;多分类(识别 10 种物体)用 softmax:
p k = e z k ∑ j = 1 K e z j , k = 1 , ... , K p_k=\frac{e^{z_k}}{\sum_{j=1}^{K}e^{z_j}},\qquad k=1,\dots,K pk=∑j=1Kezjezk,k=1,...,K
softmax 是 sigmoid 在多类别上的推广( K = 2 K=2 K=2 时两者等价)。损失函数也顺理成章地推广为多类交叉熵。所以"逻辑回归 → softmax 回归 → 深度分类网络"是同一条线的自然延伸。
8.3 工业界的逻辑回归:CTR 预估与风控评分卡
逻辑回归至今仍是广告点击率(CTR)预估、信用评分卡的主力 baseline,原因不是它最强,而是它又准又稳又可解释:
-
CTR 预估:输入是用户、广告、上下文的上百维稀疏特征(one-hot 后可达上亿维),逻辑回归配合特征交叉(FM、DeepFM)仍是工业标配;
-
评分卡:银行把逻辑回归系数转换成"每个特征每个分箱得多少分",形成一张可审计的评分表------这是监管要求可解释性的场景下,复杂模型难以替代的。
这里还藏着一个重要概念:概率校准(calibration) 。逻辑回归输出的概率通常"说 30% 就真有 30% 的样本违约",这种校准性让它能直接用于定价。原因在于它直接建模 P ( y = 1 ∣ x ) P(y=1\mid\mathbf{x}) P(y=1∣x) 并用极大似然估计------只要模型设定正确,输出就是校准的;而随机森林、SVM 的输出本质是"打分"而非概率,需要额外的 Platt scaling 或 isotonic regression 才能变成可靠的概率。"能排序"和"概率准"是两件事,业务用概率做决策时,后者更重要。
8.4 与 SVM、随机森林的关系
逻辑回归用交叉熵损失 (对数损失),SVM 用合页损失(hinge loss)------两者都在学一个线性边界,但"惩罚错分"的方式不同:交叉熵对每个样本都给梯度,SVM 只关心边界附近的支撑向量。这就是"统计视角"与"几何视角"的差异,也是下一阶段分类模型的分岔口。
至于随机森林、梯度提升树,它们用非线性切分 代替线性边界,能自动捕捉交互效应,但牺牲了系数可解释性。"逻辑回归打底、复杂模型冲上限、可解释性兜底",是竞赛和工业里都很实用的组合策略。
九、小结
这一篇从"会不会违约"出发,走完了分类模型的第一站:
- 为什么用概率 :分类要的是 P ( y = 1 ∣ x ) P(y=1\mid\mathbf{x}) P(y=1∣x),不是硬标签;概率可排序、可定价、可分级
- sigmoid 的来历 :用线性函数建模"对数几率",反解得到 p = 1 1 + e − z p=\frac{1}{1+e^{-z}} p=1+e−z1------这条推导是逻辑回归的灵魂
- 损失函数 :极大似然推出交叉熵 J = − 1 n ∑ y ln p + ( 1 − y ) ln ( 1 − p ) J=-\frac{1}{n}\sumy\\ln p+(1-y)\\ln(1-p) J=−n1∑ylnp+(1−y)ln(1−p),只惩罚预测错的一侧
- 梯度与求解 : ∂ J ∂ w = 1 n X T ( p − y ) \frac{\partial J}{\partial\mathbf{w}}=\frac{1}{n}X^T(\mathbf{p}-\mathbf{y}) ∂w∂J=n1XT(p−y),"残差 × 特征",和线性回归同构;损失凸,梯度下降必达全局最优
- 决策边界 : p = 0.5 p=0.5 p=0.5 对应 w T x + b = 0 \mathbf{w}^T\mathbf{x}+b=0 wTx+b=0,是线性边界;阈值是业务选择,不是模型参数
- 评价体系:混淆矩阵 → 精确率/召回率/F1 → ROC-AUC;类别不平衡时,准确率具有欺骗性,AUC 和召回率更值得看
- Python 实战 :手写梯度下降与 sklearn 系数吻合到三位小数( − 1.22 , 0.924 , 1.425 -1.22,0.924,1.425 −1.22,0.924,1.425),测试集 AUC 0.845;阈值敏感性表把"0.5 不是天经地义"讲透
- AI 伏笔 :逻辑回归 = 一个 sigmoid 神经元;交叉熵是分类任务的标准损失;softmax 是它的多分类推广;概率校准让它至今活跃在 CTR 预估与评分卡中
下一篇进入仿真类模型 :蒙特卡洛方法------当解析解算不出来时,如何用随机抽样"数"出答案,以及它如何一路演化成今天的扩散模型与贝叶斯推断。
📌 本文是专栏「数学建模 × AI:从经典模型到智能方法」的第 10 篇文章。
-
上一篇回顾 :线性规划与单纯形法:生产调度的最优解
-
下一篇预告 :蒙特卡洛方法:用随机抽样求解确定性问题。
👉 点击订阅专栏,第一时间收到更新通知!**
如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊