当你迷茫的时候,请回头看看 目录大纲,也许有你意想不到的收获

预告

本篇将通过 多分类逻辑回归 知识实现识别二进制对应的数字 0-9,用来模拟手写数字,每个二进制位是一个输入:

样本 i x i 4 x_{i4} xi4 x i 3 x_{i3} xi3 x i 2 x_{i2} xi2 x i 1 x_{i1} xi1 y i y_i yi的分类
i=1 0 0 0 0 0
i=2 0 0 0 1 1
i=3 0 0 1 0 2
i=4 0 0 1 1 3
i=5 0 1 0 0 4
i=6 0 1 0 1 5
i=7 0 1 1 0 6
i=8 0 1 1 1 7
i=9 1 0 0 0 8
i=10 1 0 0 1 9

前言

上篇我们讲到了 二分类逻辑回归问题 的求解,并且以 异或门 举例论证,那么本篇就来讲讲 多分类逻辑回归 问题:
在这里插入图片描述

多分类

二分类 其实可以看作是特殊的多分类,结果就只有是或否,可以看作为分类与分类,当其中一个概率为 1 时(true),另一个分类概率肯定为 0(false),因此不需要多分类那样多个输出,只需要知晓其中的一个输出 z,另一个对立的输出可以通过 1-z 求出来。

多分类 就是每个分类都有一个输出的概率,假设总共有 m 个样本,总类别有 C 个,例如手写数字,输出会有10个类别(C=10),即 0-9 这10个数字的概率。那么它的似然函数就变成了:

L ( w ) = ∏ i = 1 m ∏ j = 1 C z i j y i j w j = [ w j 1 w j 2 … w j n ] , x i = [ x i 1 x i 2 … x i n ] z i j = f ( x i T w j ) z i = [ z i 1 z i 2 … z i c ] = [ f ( x i T w 1 ) f ( x i T w 2 ) … f ( x i T w c ) ] % 似然函数 L(w)=\prod\limits_{i=1}^{m}\prod\limits_{j=1}^{C} z_{ij}^{y_{ij}}\\[10pt] % w_j 参数 w_j=\begin{bmatrix} w_{j1}\\w_{j2}\\\dots\\ w_{jn} \end{bmatrix}, % x_i 输入 x_i=\begin{bmatrix} x_{i1}\\x_{i2}\\\dots\\ x_{in} \end{bmatrix}\\[10pt] z_{ij}=f(x_i^Tw_j)\\[10pt] z_i=\begin{bmatrix} z_{i1}\\ z_{i2}\\ \dots\\ z_{ic}\\ \end{bmatrix} =\begin{bmatrix} f(x_i^Tw_1)\\ f(x_i^Tw_2)\\ \dots\\ f(x_i^Tw_c)\\ \end{bmatrix}\\[10pt] L(w)=i=1mj=1Czijyijwj= wj1wj2wjn ,xi= xi1xi2xin zij=f(xiTwj)zi= zi1zi2zic = f(xiTw1)f(xiTw2)f(xiTwc)

这里其实有个中间矩阵(第i个样本) u i ∈ R c × 1 u_i \in R^{c \times 1} uiRc×1
u i = [ u i 1 u i 2 … u i c ] = [ x i T w 1 x i T w 2 … x i T w c ] = [ w 1 T x i w 2 T x i … w c T x i ] = [ w 1 T w 2 T … w c T ] x i = [ w 11 w 12 … w 1 n w 21 w 22 … w 2 n … w c 1 w c 2 … w c n ] [ x i 1 x i 2 … x i n ] u_i=\begin{bmatrix} u_{i1}\\ u_{i2}\\ \dots\\ u_{ic}\\ \end{bmatrix} =\begin{bmatrix} x_i^Tw_1\\ x_i^Tw_2\\ \dots\\ x_i^Tw_c\\ \end{bmatrix} =\begin{bmatrix} w_1^T x_i\\ w_2^T x_i\\ \dots\\ w_c^T x_i\\ \end{bmatrix}\\[10pt] =\begin{bmatrix} w_1^T\\ w_2^T\\ \dots\\ w_c^T\\ \end{bmatrix}x_i =\begin{bmatrix} w_{11}&w_{12}&\dots&w_{1n}\\ w_{21}&w_{22}&\dots&w_{2n}\\ \dots\\ w_{c1}&w_{c2}&\dots&w_{cn}\\ \end{bmatrix} \begin{bmatrix} x_{i1}\\x_{i2}\\\dots\\ x_{in} \end{bmatrix}\\[10pt] ui= ui1ui2uic = xiTw1xiTw2xiTwc = w1Txiw2TxiwcTxi = w1Tw2TwcT xi= w11w21wc1w12w22wc2w1nw2nwcn xi1xi2xin

全部样本输出
[ u 1 u 2 … u m ] = [ w 11 w 12 … w 1 n w 21 w 22 … w 2 n … w c 1 w c 2 … w c n ] [ x 11 x 21 … x m 1 x 12 x 22 … x m 2 … x 1 n x 2 n … x m n ] \begin{bmatrix} u_1&u_2&\dots&u_m \end{bmatrix} =\begin{bmatrix} w_{11}&w_{12}&\dots&w_{1n}\\ w_{21}&w_{22}&\dots&w_{2n}\\ \dots\\ w_{c1}&w_{c2}&\dots&w_{cn}\\ \end{bmatrix} \begin{bmatrix} x_{11}&x_{21}&\dots&x_{m1}\\ x_{12}&x_{22}&\dots&x_{m2}\\ \dots\\ x_{1n}&x_{2n}&\dots&x_{mn}\\ \end{bmatrix}\\[10pt] [u1u2um]= w11w21wc1w12w22wc2w1nw2nwcn x11x12x1nx21x22x2nxm1xm2xmn

不过通通常习惯写成 X 在前,W 在后,那么其实就是转置一下, U ∈ R m × c U \in R^{m \times c} URm×c
U = [ u 1 T u 2 T … u m T ] = [ u 11 u 12 … u 1 c u 21 u 22 … u 2 c … u m 1 u m 2 … u m c ] = [ x 11 x 12 … x 1 n x 21 x 22 … x 2 n … x m 1 x m 2 … x m n ] [ w 11 w 21 … w c 1 w 12 w 22 … w c 2 … w 1 n w 2 n … w c n ] = X W U=\begin{bmatrix} u_1^T\\u_2^T\\\dots\\ u_m^T \end{bmatrix} =\begin{bmatrix} u_{11}&u_{12}&\dots&u_{1c}\\ u_{21}&u_{22}&\dots&u_{2c}\\ \dots\\ u_{m1}&u_{m2}&\dots&u_{mc}\\ \end{bmatrix} =\begin{bmatrix} x_{11}&x_{12}&\dots&x_{1n}\\ x_{21}&x_{22}&\dots&x_{2n}\\ \dots\\ x_{m1}&x_{m2}&\dots&x_{mn}\\ \end{bmatrix} \begin{bmatrix} w_{11}&w_{21}&\dots&w_{c1}\\ w_{12}&w_{22}&\dots&w_{c2}\\ \dots\\ w_{1n}&w_{2n}&\dots&w_{cn}\\ \end{bmatrix} =XW U= u1Tu2TumT = u11u21um1u12u22um2u1cu2cumc = x11x21xm1x12x22xm2x1nx2nxmn w11w12w1nw21w22w2nwc1wc2wcn =XW

那么 Z 矩阵为:
Z = [ f ( u 11 ) f ( u 12 ) … f ( u 1 c ) f ( u 21 ) f ( u 22 ) … f ( u 2 c ) … … … … f ( u m 1 ) f ( u m 2 ) … f ( u m c ) ] Z=\begin{bmatrix} f{(u_{11})}&f{(u_{12})}&\dots&f{(u_{1c})}\\ f{(u_{21})}&f{(u_{22})}&\dots&f{(u_{2c})}\\ \dots&\dots&\dots&\dots\\ f{(u_{m1})}&f{(u_{m2})}&\dots&f{(u_{mc})}\\ \end{bmatrix} Z= f(u11)f(u21)f(um1)f(u12)f(u22)f(um2)f(u1c)f(u2c)f(umc)

它里面每个项 z i j = f ( u i j ) z_{ij}=f(u_{ij}) zij=f(uij),i 为样本,j 为分类,这里只是用来展示全部数据清晰,于实际没多大用处,实际上还是处理小矩阵运算

二分类 问题中, z i = σ ( u i ) = 1 1 + e − u i z_i=\sigma({u_i})=\frac{1}{1+e^{-u_i}} zi=σ(ui)=1+eui1

而在 多分类 问题中,我们一般用 softmax 函数,在 某个样本 中,softmax 函数如下:

z k = P ( y = k ∣ x ; W ) = e w k T x ∑ j = 1 C e w j T x z_k = P(y=k|x;W) = \frac{e^{w_k^T x}}{\sum_{j=1}^{C} e^{w_j^T x}} zk=P(y=kx;W)=j=1CewjTxewkTx

对于K个类别,输入 x ∈ R n x \in \mathbb{R}^{n} xRn,参数矩阵 W ∈ R C × n W \in \mathbb{R}^{C \times n} WRC×n,其中 w k ∈ R n w_k \in \mathbb{R}^{n} wkRn 是第k类的参数向量。

其中:

  • z k z_k zk:第k类的预测概率
  • z = [ z 1 , z 2 , . . . , z C ] T z = [z_1, z_2, ..., z_C]^T z=[z1,z2,...,zC]T 是概率向量

多分类 对数似然函数

ℓ ( w ) = log ⁡ L ( w ) = log ⁡ ( ∏ i = 1 m ∏ j = 1 C z i j y i j ) = ∑ i = 1 m ∑ j = 1 C ( y i j log ⁡ z i j ) \ell(w)=\log{L(w)}=\log{(\prod\limits_{i=1}^{m}\prod\limits_{j=1}^{C} z_{ij}^{y_{ij}})}\\[10pt] =\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}({y_{ij}\log{z_{ij}}}) (w)=logL(w)=log(i=1mj=1Czijyij)=i=1mj=1C(yijlogzij)

交叉熵损失函数

交叉熵通常形式:

H ( p , q ) = − ∑ i = 1 m ∑ j = 1 C ( p j log ⁡ q j ) H(p,q)=-\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}{(p_j\log{q_j})} H(p,q)=i=1mj=1C(pjlogqj)

与多分类的 对数似然函数 很像
ℓ ( w ) = ∑ i = 1 m ∑ j = 1 C ( y i j log ⁡ z i j ) \ell(w)=\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}({y_{ij}\log{z_{ij}}}) (w)=i=1mj=1C(yijlogzij)

只不过交叉熵比对数似然函数多了一个负号,即交叉熵为 负对数似然函数
H ( p , q ) = H ( y i , z i ) = − ℓ ( w ) = − ∑ i = 1 m ∑ j = 1 C ( y i j log ⁡ z i j ) H(p,q)=H(y_i,z_i)=-\ell(w)=-\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}({y_{ij}\log{z_{ij}}}) H(p,q)=H(yi,zi)=(w)=i=1mj=1C(yijlogzij)

p = y i p =y_i p=yi 为 独热编码(one-hot)时,只有 1 个 p k = y i k = 1 p_k=y_{ik}=1 pk=yik=1,其他的 p j = 0 p_j=0 pj=0,则可以简化为:

H ( p , q ) = H ( y i , z i ) = − ℓ ( w ) = − ∑ i = 1 m ( y i k log ⁡ z i k ) = − ∑ i = 1 m log ⁡ z i k H(p,q)=H(y_i,z_i)=-\ell(w)=-\sum\limits_{i=1}^{m}({y_{ik}\log{z_{ik}}})=-\sum\limits_{i=1}^{m}\log{z_{ik}} H(p,q)=H(yi,zi)=(w)=i=1m(yiklogzik)=i=1mlogzik

也就是只要盯着某一个类别 k 进行统计。

梯度下降法

线性问题回归通过 RSS 来判断函数拟合的优劣,对于回归回题,目标则是求极大似然估计,似然函数或对数似然函数越大,函数拟合的就越好,也等效于 负对数似然 或交叉熵越小拟合越好,也可以采用梯度下降法进行求解。

这里我们令 交叉熵求均值 作为损失函数 E
E = − 1 m ∑ i = 1 m ∑ j = 1 C ( y i j log ⁡ z i j ) E = -\frac{1}{m}\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}({y_{ij}\log{z_{ij}}}) E=m1i=1mj=1C(yijlogzij)

求解准备

令 i 为样本号,j 为分类号,eg. z i j z_{ij} zij 为第 i 个样本,第 j 类预测输出的概率, y i j y_{ij} yij 为真实值
u i j = x i T w j = w j 1 x i 1 + w j 2 x i 2 + . . . + b = [ x i 1 x i 2 … 1 ] [ w j 1 w j 2 … b ] z i j = f ( u i j ) = e u i j ∑ k = 1 C e u i k u_{ij}=x_i^T w_j=w_{j1}x_{i1}+w_{j2}x_{i2}+...+b\\[10pt] =\begin{bmatrix} x_{i1}& x_{i2}&\dots& 1 \end{bmatrix} \begin{bmatrix} w_{j1}\\ w_{j2}\\\dots\\ b \end{bmatrix} \\[10pt] z_{ij}=f(u_{ij})=\frac{e^{u_{ij}}}{\sum_{k=1}^{C} e^{u_{ik}}}\\[10pt] uij=xiTwj=wj1xi1+wj2xi2+...+b=[xi1xi21] wj1wj2b zij=f(uij)=k=1Ceuikeuij

可以把 b 作为 w j n w_{jn} wjn,那么 x i n = 1 x_{in}=1 xin=1 它们的偏导为:
∂ u i j ∂ w j k = x i k \frac{\partial u_{ij}}{\partial w_{jk}} =x_{ik}\\[10pt] wjkuij=xik

可引入一个 A,A 是与 u i j u_{ij} uij 无关的量,对 u i j u_{ij} uij 偏导时 A 可以视为常量:
A = ∑ k = 1 C e u i k − e u i j z i j = f ( u i j ) = e u i j ∑ k = 1 C e u i k = e u i j A + e u i j = 1 − A A + e u i j A=\sum_{k=1}^{C} e^{u_{ik}}-e^{u_{ij}}\\[10pt] z_{ij}=f(u_{ij})=\frac{e^{u_{ij}}}{\sum_{k=1}^{C} e^{u_{ik}}} =\frac{e^{u_{ij}}}{A+e^{u_{ij}}}=1-\frac{A}{A+e^{u_{ij}}}\\[10pt] A=k=1Ceuikeuijzij=f(uij)=k=1Ceuikeuij=A+euijeuij=1A+euijA

所以 ∂ z i j ∂ u i j \frac{\partial z_{ij}}{\partial u_{ij}} uijzij 为:
∴ ∂ z i j ∂ u i j = A ⋅ e u i j ( A + e u i j ) 2 = A A + e u i j ⋅ e u i j A + e u i j = ( 1 − e u i j A + e u i j ) ⋅ e u i j A + e u i j = ( 1 − z i j ) ⋅ z i j \therefore\frac{\partial z_{ij}}{\partial u_{ij}} =\frac{A \cdot e^{u_{ij}}}{(A+e^{u_{ij}})^2}\\[10pt] =\frac{A}{A+e^{u_{ij}}}\cdot\frac{e^{u_{ij}}}{A+e^{u_{ij}}}\\[10pt] =(1-\frac{e^{u_{ij}}}{A+e^{u_{ij}}})\cdot\frac{e^{u_{ij}}}{A+e^{u_{ij}}}\\[10pt] =(1-z_{ij})\cdot z_{ij} uijzij=(A+euij)2Aeuij=A+euijAA+euijeuij=(1A+euijeuij)A+euijeuij=(1zij)zij


E i j = y i j ln ⁡ z i j ∂ E i j ∂ z i j = y i j z i j E_{ij}={y_{ij}\ln{z_{ij}}}\\[10pt] \frac{\partial E_{ij}}{\partial z_{ij}}=\frac{y_{ij}}{z_{ij}}\\[10pt] Eij=yijlnzijzijEij=zijyij

所以有:

∂ E i j ∂ w j k = ∂ E i j ∂ z i j ⋅ ∂ z i j ∂ u i j ⋅ ∂ u i j ∂ w j k = y i j z i j ⋅ z i j ( 1 − z i j ) ⋅ x i k = y i j ⋅ ( 1 − z i j ) ⋅ x i k \frac{\partial E_{ij}}{\partial w_{jk}} =\frac{\partial E_{ij}}{\partial z_{ij}} \cdot \frac{\partial z_{ij}}{\partial u_{ij}}\cdot \frac{\partial u_{ij}}{\partial w_{jk}}\\[10pt] =\frac{y_{ij}}{z_{ij}} \cdot z_{ij}(1-z_{ij}) \cdot x_{ik}\\[10pt] =y_{ij} \cdot (1-z_{ij}) \cdot x_{ik} wjkEij=zijEijuijzijwjkuij=zijyijzij(1zij)xik=yij(1zij)xik

损失函数

E = − 1 m ∑ i = 1 m ∑ j = 1 C ( y i j log ⁡ z i j ) = − 1 m ∑ i = 1 m ∑ j = 1 C E i j ∴ ∂ E ∂ w j k = − 1 m ∑ i = 1 m ∂ E i j ∂ w j k = − 1 m ∑ i = 1 m ( y i j ⋅ ( 1 − z i j ) ⋅ x i k ) = 1 m ∑ i = 1 m ( y i j ⋅ ( z i j − 1 ) ⋅ x i k ) E = -\frac{1}{m}\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}({y_{ij}\log{z_{ij}}}) =-\frac{1}{m}\sum\limits_{i=1}^{m}\sum\limits_{j=1}^{C}E_{ij}\\[10pt] \therefore \frac{\partial E}{\partial w_{jk}} =-\frac{1}{m}\sum\limits_{i=1}^{m}\frac{\partial E_{ij}}{\partial w_{jk}} =-\frac{1}{m}\sum\limits_{i=1}^{m}(y_{ij} \cdot (1-z_{ij}) \cdot x_{ik}) =\frac{1}{m}\sum\limits_{i=1}^{m}(y_{ij} \cdot (z_{ij}-1) \cdot x_{ik}) E=m1i=1mj=1C(yijlogzij)=m1i=1mj=1CEijwjkE=m1i=1mwjkEij=m1i=1m(yij(1zij)xik)=m1i=1m(yij(zij1)xik)

所以 w j k w_{jk} wjk 更新如下,j 为分类,k为 w 向量中的一个
w j k ( t + 1 ) = w j k ( t ) − η Δ r ∂ E ∂ w j k = w j k ( t ) − η Δ r 1 m ∑ i = 1 m ( y i j ⋅ ( z i j − 1 ) ⋅ x i k ) w_{jk}^{(t+1)}=w_{jk}^{(t)} - \eta\Delta{r}\frac{\partial E}{\partial w_{jk}}\\[10pt] =w_{jk}^{(t)} - \eta\Delta{r}\frac{1}{m}\sum\limits_{i=1}^{m}(y_{ij} \cdot (z_{ij}-1) \cdot x_{ik}) wjk(t+1)=wjk(t)ηΔrwjkE=wjk(t)ηΔrm1i=1m(yij(zij1)xik)

在实用中, η Δ r \eta\Delta{r} ηΔr 一般用一个很小的数来代替,也就是 学习率 η Δ r ∂ E ∂ w j k \eta\Delta{r}\frac{\partial E}{\partial w_{jk}} ηΔrwjkE步长

python 实战

我们就不用手写数字了,因为要用到卷积神经网络,后面会讲到,这里简化一下,用二进制代替手写数字图片,每个二进制位是一个输入:

样本 i x i 4 x_{i4} xi4 x i 3 x_{i3} xi3 x i 2 x_{i2} xi2 x i 1 x_{i1} xi1 y i y_i yi的分类
i=1 0 0 0 0 0
i=2 0 0 0 1 1
i=3 0 0 1 0 2
i=4 0 0 1 1 3
i=5 0 1 0 0 4
i=6 0 1 0 1 5
i=7 0 1 1 0 6
i=8 0 1 1 1 7
i=9 1 0 0 0 8
i=10 1 0 0 1 9

SoftmaxRegression

定义训练类及相应函数:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, classification_report
import seaborn as sns


class SoftmaxRegression:
    """多分类逻辑回归(Softmax回归)"""

    def __init__(self,
                 n_classes,
                 learning_rate=0.1,
                 n_iterations=5000,
                 lambda_reg=0.01):
        self.n_classes = n_classes
        self.lr = learning_rate
        self.n_iter = n_iterations
        self.lambda_reg = lambda_reg  # L2正则化系数
        self.theta = None
        self.loss_history = []
        self.accuracy_history = []

    def softmax(self, z):
        """Softmax函数,数值稳定实现"""
        # 减去最大值防止数值溢出
        z_stable = z - np.max(z, axis=1, keepdims=True)
        exp_z = np.exp(z_stable)
        return exp_z / np.sum(exp_z, axis=1, keepdims=True)

    def one_hot_encode(self, y):
        """将标签转换为one-hot编码"""
        m = len(y)
        y_one_hot = np.zeros((m, self.n_classes))
        y_one_hot[np.arange(m), y] = 1
        return y_one_hot

    def compute_loss(self, X, y):
        """计算交叉熵损失"""
        m = X.shape[0]
        # 添加偏置项
        X_bias = np.c_[np.ones((m, 1)), X]  # 添加这行
        scores = X_bias.dot(self.theta.T)  # 使用X_bias
        probs = self.softmax(scores)

        # 交叉熵损失
        y_one_hot = self.one_hot_encode(y)
        loss = -np.sum(y_one_hot * np.log(probs + 1e-15)) / m

        # 添加L2正则化
        loss += (self.lambda_reg / (2 * m)) * np.sum(self.theta**2)

        return loss

    def fit(self, X, y, verbose=True):
        """训练模型"""
        m, n = X.shape  # m个样本,n个特征

        # 添加偏置项(增加一列全1)
        X_bias = np.c_[np.ones((m, 1)), X]
        n_features = n + 1

        # 初始化参数矩阵(K×(n+1))
        np.random.seed(42)
        self.theta = np.random.randn(self.n_classes, n_features) * 0.01

        # 训练循环
        for iteration in range(self.n_iter):
            # 前向传播
            scores = X_bias.dot(self.theta.T)  # m×K
            probs = self.softmax(scores)  # m×K

            # 将标签转换为one-hot编码
            y_one_hot = self.one_hot_encode(y)  # m×K

            # 计算梯度
            error = probs - y_one_hot  # m×K
            grad = (1 / m) * error.T.dot(X_bias)  # K×(n+1)

            # 添加L2正则化梯度
            grad += (self.lambda_reg / m) * self.theta

            # 更新参数
            self.theta -= self.lr * grad

            # 记录损失和准确率
            if iteration % 100 == 0:
                loss = self.compute_loss(X, y)
                y_pred = self.predict(X)
                accuracy = np.mean(y_pred == y)

                self.loss_history.append(loss)
                self.accuracy_history.append(accuracy)

                if verbose and iteration % 500 == 0:
                    print(
                        f"Iteration {iteration}: Loss = {loss:.4f}, Accuracy = {accuracy:.4f}"
                    )

    def predict_proba(self, X):
        """预测概率"""
        m = X.shape[0]
        X_bias = np.c_[np.ones((m, 1)), X]
        scores = X_bias.dot(self.theta.T)
        return self.softmax(scores)

    def predict(self, X):
        """预测类别"""
        probs = self.predict_proba(X)
        return np.argmax(probs, axis=1)

    def score(self, X, y):
        """计算准确率"""
        y_pred = self.predict(X)
        return np.mean(y_pred == y)


def create_digit_binary_data():
    """创建0-9数字的二进制数据"""
    # 数字0-9的4位二进制表示
    binary_digits = {
        0: [0, 0, 0, 0],
        1: [0, 0, 0, 1],
        2: [0, 0, 1, 0],
        3: [0, 0, 1, 1],
        4: [0, 1, 0, 0],
        5: [0, 1, 0, 1],
        6: [0, 1, 1, 0],
        7: [0, 1, 1, 1],
        8: [1, 0, 0, 0],
        9: [1, 0, 0, 1]
    }

    X = []
    y = []

    # 为每个数字创建多个样本(添加噪声)
    np.random.seed(42)
    samples_per_digit = 50

    for digit, binary in binary_digits.items():
        for _ in range(samples_per_digit):
            # 添加少量噪声
            noisy_binary = binary + np.random.normal(0, 0.1, 4)
            X.append(noisy_binary)
            y.append(digit)

    X = np.array(X)
    y = np.array(y)

    return X, y


def add_polynomial_features(X):
    """添加多项式特征"""
    # 原始特征:x1, x2, x3, x4
    # 添加二次项和交互项
    X_poly = np.hstack([
        X,
        X**2,  # 平方项
        X[:, 0:1] * X[:, 1:2],  # x1*x2
        X[:, 0:1] * X[:, 2:3],  # x1*x3
        X[:, 0:1] * X[:, 3:4],  # x1*x4
        X[:, 1:2] * X[:, 2:3],  # x2*x3
        X[:, 1:2] * X[:, 3:4],  # x2*x4
        X[:, 2:3] * X[:, 3:4],  # x3*x4
    ])
    return X_poly


def plot_training_history(model):
    """绘制训练历史"""
    fig, axes = plt.subplots(1, 2, figsize=(12, 4))

    # 损失曲线
    axes[0].plot(model.loss_history)
    axes[0].set_xlabel('Iteration (x100)')
    axes[0].set_ylabel('Loss')
    axes[0].set_title('Training Loss History')
    axes[0].grid(True, alpha=0.3)

    # 准确率曲线
    axes[1].plot(model.accuracy_history)
    axes[1].set_xlabel('Iteration (x100)')
    axes[1].set_ylabel('Accuracy')
    axes[1].set_title('Training Accuracy History')
    axes[1].grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()


def plot_confusion_matrix(y_true, y_pred, class_names):
    """绘制混淆矩阵"""
    cm = confusion_matrix(y_true, y_pred)

    plt.figure(figsize=(10, 8))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
                xticklabels=class_names,
                yticklabels=class_names)
    plt.xlabel('Predicted')
    plt.ylabel('True')
    plt.title('Confusion Matrix')
    plt.show()


def visualize_decision_boundaries(X, y, model, title):
    """可视化决策边界(使用PCA降维到2D)"""
    from sklearn.decomposition import PCA

    # 使用PCA将特征降至2维以便可视化
    pca = PCA(n_components=2)
    X_2d = pca.fit_transform(X)

    # 创建网格点
    x_min, x_max = X_2d[:, 0].min() - 1, X_2d[:, 0].max() + 1
    y_min, y_max = X_2d[:, 1].min() - 1, X_2d[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                         np.arange(y_min, y_max, 0.1))

    # 预测网格点的类别
    grid_points = np.c_[xx.ravel(), yy.ravel()]

    # 逆变换回原始空间(近似)
    grid_original_space = pca.inverse_transform(grid_points)
    Z = model.predict(grid_original_space)
    Z = Z.reshape(xx.shape)

    # 绘制决策区域
    plt.figure(figsize=(10, 8))
    plt.contourf(xx, yy, Z, alpha=0.3, cmap=plt.cm.tab10)

    # 绘制数据点
    scatter = plt.scatter(X_2d[:, 0], X_2d[:, 1], c=y,
                         cmap=plt.cm.tab10, edgecolors='k', s=100)

    plt.xlabel('Principal Component 1')
    plt.ylabel('Principal Component 2')
    plt.title(f'Decision Boundaries - {title}\n(PCA Visualization)')
    plt.colorbar(scatter, label='Digit')
    plt.grid(True, alpha=0.3)
    plt.show()

训练

def main():
    print("=" * 60)
    print("多分类逻辑回归(Softmax回归)")
    print("数字0-9分类任务(二进制输入)")
    print("=" * 60)

    # 1. 创建数据
    X, y = create_digit_binary_data()
    print(f"数据集形状: X={X.shape}, y={y.shape}")
    print(f"类别分布: {np.bincount(y)}")
    print()

    # 2. 划分训练集和测试集
    np.random.seed(42)
    indices = np.random.permutation(len(X))
    split_idx = int(0.8 * len(X))

    X_train, X_test = X[indices[:split_idx]], X[indices[split_idx:]]
    y_train, y_test = y[indices[:split_idx]], y[indices[split_idx:]]

    print(f"训练集: {X_train.shape[0]} 个样本")
    print(f"测试集: {X_test.shape[0]} 个样本")
    print()

    # 3. 使用原始特征
    print("=" * 60)
    print("使用原始特征(4位二进制)")
    print("=" * 60)

    model_simple = SoftmaxRegression(n_classes=10, learning_rate=0.1,
                                     n_iterations=5000, lambda_reg=0.01)
    model_simple.fit(X_train, y_train)

    train_accuracy = model_simple.score(X_train, y_train)
    test_accuracy = model_simple.score(X_test, y_test)

    print(f"\n训练准确率: {train_accuracy:.4f}")
    print(f"测试准确率: {test_accuracy:.4f}")

    # 显示预测示例
    print("\n前10个测试样本的预测:")
    y_pred = model_simple.predict(X_test[:10])
    y_proba = model_simple.predict_proba(X_test[:10])

    for i in range(min(10, len(X_test))):
        true_label = y_test[i]
        pred_label = y_pred[i]
        prob = y_proba[i, pred_label]
        print(f"样本{i+1}: 真实={true_label}, 预测={pred_label}, 概率={prob:.4f}")

    plot_training_history(model_simple)
    plot_confusion_matrix(y_test, model_simple.predict(X_test),
                          [str(i) for i in range(10)])

    # 4. 使用多项式特征
    print("\n" + "=" * 60)
    print("使用多项式特征(增强特征空间)")
    print("=" * 60)

    X_train_poly = add_polynomial_features(X_train)
    X_test_poly = add_polynomial_features(X_test)

    print(f"多项式特征维度: {X_train_poly.shape[1]}")

    model_poly = SoftmaxRegression(n_classes=10, learning_rate=0.05,
                                   n_iterations=8000, lambda_reg=0.001)
    model_poly.fit(X_train_poly, y_train)

    train_accuracy_poly = model_poly.score(X_train_poly, y_train)
    test_accuracy_poly = model_poly.score(X_test_poly, y_test)

    print(f"\n训练准确率(多项式): {train_accuracy_poly:.4f}")
    print(f"测试准确率(多项式): {test_accuracy_poly:.4f}")

    plot_training_history(model_poly)

    # 5. 参数分析
    print("\n" + "=" * 60)
    print("参数分析")
    print("=" * 60)

    print("简单模型参数形状:", model_simple.theta.shape)
    print("\n数字0对应的参数(前5个):")
    print(model_simple.theta[0, :5])

    print("\n参数矩阵的L2范数:")
    for i in range(10):
        norm = np.linalg.norm(model_simple.theta[i])
        print(f"数字{i}: {norm:.4f}")

    # 6. 性能对比
    print("\n" + "=" * 60)
    print("性能对比总结")
    print("=" * 60)

    print(f"{'模型':<20} {'训练准确率':<15} {'测试准确率':<15}")
    print("-" * 50)
    print(f"{'原始特征':<20} {train_accuracy:<15.4f} {test_accuracy:<15.4f}")
    print(f"{'多项式特征':<20} {train_accuracy_poly:<15.4f} {test_accuracy_poly:<15.4f}")

运行结果

使用原始特征(4位二进制)

============================================================
多分类逻辑回归(Softmax回归)
数字0-9分类任务(二进制输入)
============================================================
数据集形状: X=(500, 4), y=(500,)
类别分布: [50 50 50 50 50 50 50 50 50 50]

训练集: 400 个样本
测试集: 100 个样本

============================================================
使用原始特征(4位二进制)
============================================================
Iteration 0: Loss = 2.2989, Accuracy = 0.3050
Iteration 500: Loss = 0.7375, Accuracy = 1.0000
Iteration 1000: Loss = 0.4195, Accuracy = 1.0000
Iteration 1500: Loss = 0.2925, Accuracy = 1.0000
Iteration 2000: Loss = 0.2256, Accuracy = 1.0000
Iteration 2500: Loss = 0.1847, Accuracy = 1.0000
Iteration 3000: Loss = 0.1571, Accuracy = 1.0000
Iteration 3500: Loss = 0.1373, Accuracy = 1.0000
Iteration 4000: Loss = 0.1224, Accuracy = 1.0000
Iteration 4500: Loss = 0.1108, Accuracy = 1.0000

训练准确率: 1.0000
...
样本7: 真实=6, 预测=6, 概率=0.8818
样本8: 真实=7, 预测=7, 概率=0.9502
样本9: 真实=5, 预测=5, 概率=0.8706
样本10: 真实=4, 预测=4, 概率=0.9324


使用多项式特征(增强特征空间)

============================================================
使用多项式特征(增强特征空间)
============================================================
多项式特征维度: 14
Iteration 0: Loss = 2.2965, Accuracy = 0.2075
Iteration 500: Loss = 0.5923, Accuracy = 1.0000
Iteration 1000: Loss = 0.3176, Accuracy = 1.0000
Iteration 1500: Loss = 0.2137, Accuracy = 1.0000
Iteration 2000: Loss = 0.1607, Accuracy = 1.0000
Iteration 2500: Loss = 0.1289, Accuracy = 1.0000
Iteration 3000: Loss = 0.1078, Accuracy = 1.0000
Iteration 3500: Loss = 0.0928, Accuracy = 1.0000
Iteration 4000: Loss = 0.0815, Accuracy = 1.0000
Iteration 4500: Loss = 0.0728, Accuracy = 1.0000
Iteration 5000: Loss = 0.0658, Accuracy = 1.0000
Iteration 5500: Loss = 0.0601, Accuracy = 1.0000
Iteration 6000: Loss = 0.0554, Accuracy = 1.0000
Iteration 6500: Loss = 0.0513, Accuracy = 1.0000
Iteration 7000: Loss = 0.0479, Accuracy = 1.0000
Iteration 7500: Loss = 0.0449, Accuracy = 1.0000

训练准确率(多项式): 1.0000
测试准确率(多项式): 1.0000

参数分析与对比

============================================================
参数分析
============================================================
简单模型参数形状: (10, 5)

数字0对应的参数(前5个):
[ 4.91711321 -2.4092373  -2.79234297 -3.09025926 -3.17665747]

参数矩阵的L2范数:
数字0: 7.5776
数字1: 7.3548
数字2: 7.5739
数字3: 7.0336
数字4: 7.5037
数字5: 7.0036
数字6: 6.9899
数字7: 6.3217
数字8: 7.0914
数字9: 6.5200

============================================================
性能对比总结
============================================================
模型                   训练准确率           测试准确率
--------------------------------------------------
原始特征                 1.0000          1.0000
多项式特征                1.0000          1.0000

总结

  1. Softmax回归成功地将二分类逻辑回归扩展到多分类问题
  2. 使用多项式特征可以提升模型性能(更多特征维度)
  3. 添加L2正则化防止过拟合
  4. 模型学习到了从二进制表示到数字类别的映射关系
  5. 证明了Softmax函数在多分类问题中的有效性

更多推荐