深度学习理论推导--多分类逻辑回归
文章目录
当你迷茫的时候,请回头看看 目录大纲,也许有你意想不到的收获
预告
本篇将通过 多分类逻辑回归 知识实现识别二进制对应的数字 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=1∏mj=1∏Czijyijwj= wj1wj2…wjn ,xi= xi1xi2…xin zij=f(xiTwj)zi= zi1zi2…zic = f(xiTw1)f(xiTw2)…f(xiTwc)
这里其实有个中间矩阵(第i个样本) u i ∈ R c × 1 u_i \in R^{c \times 1} ui∈Rc×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=
ui1ui2…uic
=
xiTw1xiTw2…xiTwc
=
w1Txiw2Txi…wcTxi
=
w1Tw2T…wcT
xi=
w11w21…wc1w12w22wc2………w1nw2nwcn
xi1xi2…xin
全部样本输出
[ 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] [u1u2…um]=
w11w21…wc1w12w22wc2………w1nw2nwcn
x11x12…x1nx21x22x2n………xm1xm2xmn
不过通通常习惯写成 X 在前,W 在后,那么其实就是转置一下, U ∈ R m × c U \in R^{m \times c} U∈Rm×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=
u1Tu2T…umT
=
u11u21…um1u12u22um2………u1cu2cumc
=
x11x21…xm1x12x22xm2………x1nx2nxmn
w11w12…w1nw21w22w2n………wc1wc2wcn
=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+e−ui1
而在 多分类 问题中,我们一般用 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=k∣x;W)=∑j=1CewjTxewkTx
对于K个类别,输入 x ∈ R n x \in \mathbb{R}^{n} x∈Rn,参数矩阵 W ∈ R C × n W \in \mathbb{R}^{C \times n} W∈RC×n,其中 w k ∈ R n w_k \in \mathbb{R}^{n} wk∈Rn 是第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=1∏mj=1∏Czijyij)=i=1∑mj=1∑C(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=1∑mj=1∑C(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=1∑mj=1∑C(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=1∑mj=1∑C(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=1∑m(yiklogzik)=−i=1∑mlogzik
也就是只要盯着某一个类别 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=1∑mj=1∑C(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=[xi1xi2…1]
wj1wj2…b
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] ∂wjk∂uij=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=1∑Ceuik−euijzij=f(uij)=∑k=1Ceuikeuij=A+euijeuij=1−A+euijA
所以 ∂ z i j ∂ u i j \frac{\partial z_{ij}}{\partial u_{ij}} ∂uij∂zij 为:
∴ ∂ 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} ∴∂uij∂zij=(A+euij)2A⋅euij=A+euijA⋅A+euijeuij=(1−A+euijeuij)⋅A+euijeuij=(1−zij)⋅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=yijlnzij∂zij∂Eij=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} ∂wjk∂Eij=∂zij∂Eij⋅∂uij∂zij⋅∂wjk∂uij=zijyij⋅zij(1−zij)⋅xik=yij⋅(1−zij)⋅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=1∑mj=1∑C(yijlogzij)=−m1i=1∑mj=1∑CEij∴∂wjk∂E=−m1i=1∑m∂wjk∂Eij=−m1i=1∑m(yij⋅(1−zij)⋅xik)=m1i=1∑m(yij⋅(zij−1)⋅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)−ηΔr∂wjk∂E=wjk(t)−ηΔrm1i=1∑m(yij⋅(zij−1)⋅xik)
在实用中, η Δ r \eta\Delta{r} ηΔr 一般用一个很小的数来代替,也就是 学习率 , η Δ r ∂ E ∂ w j k \eta\Delta{r}\frac{\partial E}{\partial w_{jk}} ηΔr∂wjk∂E 叫 步长。
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
总结
- Softmax回归成功地将二分类逻辑回归扩展到多分类问题
- 使用多项式特征可以提升模型性能(更多特征维度)
- 添加L2正则化防止过拟合
- 模型学习到了从二进制表示到数字类别的映射关系
- 证明了Softmax函数在多分类问题中的有效性
更多推荐
所有评论(0)