逻辑回归与信用违约预测:从S曲线到分类器

栏导语 :本专栏系统讲解数学建模的经典方法与 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、随机森林的关系)
  • 九、小结

📌本文是专栏「数学建模 × 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 评价。


三、模型假设

  1. 样本独立同分布:每个客户的违约事件相互独立,且来自同一个数据生成机制
  2. 对数几率线性 :特征对"违约几率"的影响是线性的,即 ln ⁡ p 1 − p = w T x + b \ln\frac{p}{1-p}=\mathbf{w}^T\mathbf{x}+b ln1−pp=wTx+b(这是逻辑回归最核心的假设)
  3. 特征无完全共线性:解释变量之间不存在严格的线性关系,否则系数不可辨识
  4. 标签可靠:历史数据中的"违约 / 未违约"记录准确无误,不存在标签噪声
  5. 特征已清洗并标准化:缺失值、异常值已处理;连续特征在建模前做 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

两个细节值得注意:

  1. stratify=y:分层抽样让训练集和测试集的违约比例一致(都约 32.6%)。否则随机切分可能让测试集违约率偏差很大,指标失真。
  2. 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------一个强特征的价值,比换模型大得多。


七、模型评价

优点:

  1. 输出是概率,不是硬标签,可直接用于定价、排序、风险分级
  2. 可解释性极强:系数符号说明方向,优势比说明倍数,天然适合写进论文的"因素分析"
  3. 凸优化:损失函数是凸的,梯度下降必达全局最优,不存在"调参调不出来"的玄学
  4. 训练快、预测快 :参数只有 d + 1 d+1 d+1 个,十万级样本秒级收敛
  5. 是分类模型的基准线:任何复杂模型(SVM、随机森林、神经网络)都应该先和逻辑回归比一比,赢不了它就说明复杂化没有价值

局限:

  1. 只能学线性边界:特征与对数几率的关系必须是线性的,复杂的非线性关系需要人工构造特征(分箱、交互项、多项式)或换模型
  2. 对共线性敏感:两个高度相关的特征会导致系数不稳定(符号甚至反转),解释时需先做相关性筛查
  3. 对异常值和量纲敏感:标准化几乎是必做步骤
  4. 完全分离时不收敛:需要正则化兜底
  5. 样本量要求:每个特征至少需要 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 只关心边界附近的支撑向量。这就是"统计视角"与"几何视角"的差异,也是下一阶段分类模型的分岔口。

至于随机森林、梯度提升树,它们用非线性切分 代替线性边界,能自动捕捉交互效应,但牺牲了系数可解释性。"逻辑回归打底、复杂模型冲上限、可解释性兜底",是竞赛和工业里都很实用的组合策略。


九、小结

这一篇从"会不会违约"出发,走完了分类模型的第一站:

  1. 为什么用概率 :分类要的是 P ( y = 1 ∣ x ) P(y=1\mid\mathbf{x}) P(y=1∣x),不是硬标签;概率可排序、可定价、可分级
  2. sigmoid 的来历 :用线性函数建模"对数几率",反解得到 p = 1 1 + e − z p=\frac{1}{1+e^{-z}} p=1+e−z1------这条推导是逻辑回归的灵魂
  3. 损失函数 :极大似然推出交叉熵 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),只惩罚预测错的一侧
  4. 梯度与求解 : ∂ 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),"残差 × 特征",和线性回归同构;损失凸,梯度下降必达全局最优
  5. 决策边界 : p = 0.5 p=0.5 p=0.5 对应 w T x + b = 0 \mathbf{w}^T\mathbf{x}+b=0 wTx+b=0,是线性边界;阈值是业务选择,不是模型参数
  6. 评价体系:混淆矩阵 → 精确率/召回率/F1 → ROC-AUC;类别不平衡时,准确率具有欺骗性,AUC 和召回率更值得看
  7. 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 不是天经地义"讲透
  8. AI 伏笔 :逻辑回归 = 一个 sigmoid 神经元;交叉熵是分类任务的标准损失;softmax 是它的多分类推广;概率校准让它至今活跃在 CTR 预估与评分卡中

下一篇进入仿真类模型 :蒙特卡洛方法------当解析解算不出来时,如何用随机抽样"数"出答案,以及它如何一路演化成今天的扩散模型与贝叶斯推断。


📌 本文是专栏「数学建模 × AI:从经典模型到智能方法」的第 10 篇文章。

如果你在阅读过程中有任何疑问,欢迎在评论区留言交流。😊

相关推荐
安睿杰翻译(上海)有限公司1 小时前
AI 同传技术链路拆解:准确率、时延瓶颈、术语库工程化与会议落地选型方案
人工智能
不老刘1 小时前
异构双 Agent 协同:构建无人值守的长任务自动化研发流水线
运维·人工智能·自动化·ai编程·1024程序员节
迷途呀1 小时前
强化学习概念
人工智能·深度学习·机器学习·强化学习·1024程序员节
AI开源软件2 小时前
87.4%:OpenAI 让模型写出 20 种语言 SQLite
人工智能·ai·openai·fortran·cobol·技术翻译·银行核心系统
爱喝雪碧的可乐2 小时前
OpenAI Codex完成率
大数据·人工智能·1024程序员节
Solange__2 小时前
文旅景区怎么用AI生成3D导览?从航拍采集、地标重建到网页漫游的完整方案
人工智能·3d·语言模型·自然语言处理·材质·贴图·1024程序员节
Profile排查笔记2 小时前
浏览器指纹逆向怎么分析:从网络请求追踪到 JavaScript 采集链
前端·人工智能·后端·自动化
智能运维指南2 小时前
企业运维监控平台哪家好?2026年五大全栈可观测方案对比与选型推荐
大数据·运维·人工智能
STQY燊桐启元(深圳)电子科技2 小时前
高导热型电磁抑制吸波材料:当“导热”不再是吸波片的附属功能
人工智能·导热片·电磁抑制吸波材料、·导热绝缘