多元逻辑回归 · 数学推导过程及代码实现完全解析

文章目录

  • 多元逻辑回归
    • 模型解释
    • 参数的估计
    • 如何预测
    • 代码实现

最近修改:2021/6/22

本文是《从二元逻辑回归到多元逻辑回归 · 数学推导过程完全解析》的第二部分,具体介绍多元逻辑回归,及其背后的数学公式推导和具体python代码实现

对于模型概述请参见第一部分:
二元逻辑回归 · 数学推导过程及代码实现完全解析


多元逻辑回归


模型解释

在这个模型中,自变量,因变量和参数的样子如下,可以看到 y i y_i yi的取值由两元变成了多元。

多元逻辑回归有次序多元逻辑回归无序多元逻辑回归之分,两者区别就在于因变量 y y y的取值是否有次序,如 y y y的取值类似于1,2,3,4…,则称其为次序多元逻辑回归。

本文这讨论更宽泛的无序情况。

因变量 y y y是一个nx1向量, y i y_{i} yi∈{ w 1 , w 2 , w 3 , . . . , w K w_1,w_2,w_3,...,w_K w1,w2,w3,...,wK}, i = 1 , 2 , 3 , . . . , n i=1,2,3,...,n i=1,2,3,...,n

y = [ y 1 y 2 ⋮ y n ] y= \left[ \begin{matrix} y_1 \\ y_2 \\ \vdots\\ y_n \end{matrix} \right] y=y1y2yn

带截距项的自变量 X X X是一个nx(m+1)的矩阵
X = [ 1 x 11 x 12 . . . x 1 m 1 x 21 x 21 . . . x 2 m ⋮ ⋮ ⋮ 1 x n 1 x n 2 . . . x n m ] X= \left[ \begin{matrix} 1&x_{11}&x_{12}&...&x_{1m}\\ 1&x_{21}&x_{21}&...&x_{2m}\\ \vdots&\vdots&&\vdots\\ 1&x_{n1}&x_{n2}&...&x_{nm} \end{matrix} \right] X=111x11x21xn1x12x21xn2.........x1mx2mxnm

带截距项的参数 β k β_k βk是一个mx1向量, k = 1 , 2 , 3 , . . . . , K k=1,2,3,....,K k=1,2,3,....,K
β k = [ β k 0 β k 1 β k 2 ⋮ β k m ] β_k= \left[ \begin{matrix} β_{k0} \\ β_{k1} \\ β_{k2} \\ \vdots\\ β_{km} \end{matrix} \right] βk=βk0βk1βk2βkm

与二元逻辑回归的另一个区别就是,多元逻辑回归中用的是softmax函数。

于是我们的自变量和因变量的概率之间的关系可以表示如下

p i k ≜ P ( y i = w k ) = e x i β k ∑ j = 1 K e x i β j (5) p_{ik}\triangleq P(y_i=w_k)=\frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}}\tag{5} pikP(yi=wk)=j=1Kexiβjexiβk(5)

怕之后忘记,先来用代码来巩固一下

X是我们的数据dataframe,beta是参数矩阵
X.iloc[i]是测试集的第i行,beta[:,k]是参数矩阵的第k列

np.exp(np.array(X.iloc[i]).dot(beta[:,k])是(5)式的分子部分,但为了严格与矩阵运算对应,还需要对其做一下修改:
np.exp(np.array(X.iloc[i]).reshape(1,X.shape[1]).dot(beta[:,k].reshape(X.shape[1],1)))
但这个还只是一个数组,因此为了得到具体的值,还得在后面加上[0][0]

(5)式的分母部分
np.exp(np.array(X.iloc[i]).reshape(1,X.shape[1]).dot(beta)).sum()

整理后的函数p_ik()完整代码请见 代码实现 部分

:在实际建模中,可能会遇到一个问题, (5)的分母过大,超过计算机能表示的范围,即数据溢出, p i k p_{ik} pik会显示为nan,出现提示:

RuntimeWarning: overflow encountered in exp

解决方案有两个:

法一:
取(5)分母中 x i β j ( j = 1 , 2 , 3 , . . . , K ) x_iβ_j(j=1,2,3,...,K) xiβj(j=1,2,3,...,K)中的最大值,令其为max
e x i β k ∑ j = 1 K e x i β j = e − m a x e − m a x e x i β k ∑ j = 1 K e x i β j = e x i β k − m a x ∑ j = 1 K e x i β j − m a x \frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}}=\frac{e^{-max}}{e^{-max}}\frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}}=\frac{e^{x_iβ_k-max}}{\sum_{j=1}^{K}e^{x_iβ_j-max}} j=1Kexiβjexiβk=emaxemaxj=1Kexiβjexiβk=j=1Kexiβjmaxexiβkmax
此方法有个问题就是,max可能会很大,而导致数据下溢,分子分母变为0,0/0会出现如下错误

RuntimeWarning: invalid value encountered in true_divide

法二:
之所以出现数据溢出,不就是因为np.array默认的是np.float64吗,问题出在数据精度不够,那我们可以通过扩大精度的方式来处理溢出的问题,即设定dtype=np.float128,潜在问题就是,数字无论多大或多小都会完全显示出来,有点点显示问题。


参数的估计

这个无序多元逻辑回归模型有以下几个事实

  • 我们总共进行了n次试验
  • 每次试验的可能结果有K种,分别是 y i ( i = 1 , 2 , 3 , . . . n ) = w 1 , w 2 , w 3 , . . . w K y_i(i=1,2,3,...n)=w_1,w_2,w_3,...w_K yi(i=1,2,3,...n)=w1,w2,w3,...wK
  • 每种结果发生的概率 p k p_k pk如(5)式所示, p k ≜ P ( y i = w k ) p_k\triangleq P(y_i=w_k) pkP(yi=wk)
  • 并且(5)具有这样的特点 ∑ i = 1 K p i = 1 \sum_{i=1}^Kp_i=1 i=1Kpi=1
  • n次试验中,结果 k k k发生次数 n k n_k nk ∑ i = 1 n I ( y i = w k ) \sum_{i=1}^{n}I(y_i=w_k) i=1nI(yi=wk),这里 I ( y i = w k ) I(y_i=w_k) I(yi=wk)指示函数(indicator function),当 y i = w k y_i=w_k yi=wk成立时取1,不成立时取0

通过上面的分析我们知道,无序多元逻辑回归实质上是一个多项分布
P ( n 1 , n 2 , n 3 , . . . , n K ∣ β 1 , β 2 , . . . β K ) = n ! n 1 ! n 2 ! n 3 ! . . . n K ! p 1 n 1 p 2 n 2 . . . p K n K (6) P(n_1,n_2,n_3,...,n_K|β_1,β_2,...β_K)=\frac{n!}{n_1!n_2!n_3!...n_K!}p_1^{n_1}p_2^{n_2}...p_K^{n_K}\tag6 P(n1,n2,n3,...,nKβ1,β2,...βK)=n1!n2!n3!...nK!n!p1n1p2n2...pKnK(6)

将(6)式两边取对数,有

L o g P ( n 1 , n 2 , n 3 , . . . , n K ∣ β 1 , β 2 , . . . β K ) ∝ ∑ k = 1 K n k L o g p k = ∑ k = 1 K ∑ i = 1 n I ( y i = w k ) L o g e x i β k ∑ j = 1 K e x i β j (7) LogP(n_1,n_2,n_3,...,n_K|β_1,β_2,...β_K) \propto \\ \sum_{k=1}^Kn_kLogp_k=\sum_{k=1}^K\sum_{i=1}^{n}I(y_i=w_k)Log\frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}}\tag7 LogP(n1,n2,n3,...,nKβ1,β2,...βK)k=1KnkLogpk=k=1Ki=1nI(yi=wk)Logj=1Kexiβjexiβk(7)

∝ \propto 是正比符号,省略的部分是常数,求导之后为零,因此这里就直接省掉了

极大似然估计的方法是最大化(7)式,这里我们采用梯度下降的办法,于是我们的损失函数可以写成

L ( β ) = − ∑ k = 1 K ∑ i = 1 n I ( y i = w k ) L o g e x i β k ∑ j = 1 K e x i β j (8) L(β)=-\sum_{k=1}^K\sum_{i=1}^{n}I(y_i=w_k)Log\frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}}\tag8 L(β)=k=1Ki=1nI(yi=wk)Logj=1Kexiβjexiβk(8)

先对 l o g ( p k ) log(p_k) log(pk)求导, p i k p_{ik} pik如(5)式所示, ∑ \sum ∑ j = 1 K e x i β j \sum_{j=1}^{K}e^{x_iβ_j} j=1Kexiβj的简写,矩阵的求导请参考我的另一篇博文统计里的矩阵导数

∂ l o g ( p i k ) ∂ β k T = ∑ e x i β k ⋅ e x i β k ⋅ x i T ⋅ ∑ − e x i β k ⋅ e x i β k ⋅ x i T ∑ 2 = x i T ( 1 − p i k ) \frac{\partial log(p_{ik})}{\partial β_k^T}=\frac{\sum}{e^{x_iβ_k}} · \frac{e^{x_iβ_k}·x_i^T·\sum-e^{x_iβ_k} ·e^{x_iβ_k}· x_i^T}{\sum^2}\\ =x_i^T(1-p_{ik}) βkTlog(pik)=exiβk2exiβkxiTexiβkexiβkxiT=xiT(1pik)
∂ l o g ( p i k ) ∂ β m T ( m ≠ k ) = ∑ e x i β k ⋅ 0 ⋅ ∑ − e x i β k ⋅ e x i β k ⋅ x i T ∑ 2 = − x i T ⋅ p i k \frac{\partial log(p_{ik})}{\partial β_m^T}(m\neq k)=\frac{\sum}{e^{x_iβ_k}} · \frac{0·\sum-e^{x_iβ_k} ·e^{x_iβ_k}· x_i^T}{\sum^2}\\ =-x_i^T·p_{ik} βmTlog(pik)(m=k)=exiβk20exiβkexiβkxiT=xiTpik

y i = k y_i=k yi=k对应的参数是 β k β_k βk,在更新参数 β k β_k βk时,会用 β k β_k βk的偏导。简单的说, m = k m=k m=k y i = k y_i=k yi=k是一回事。综上所述, m ∈ m∈ m{ 1 , 2 , . . . , K 1,2,...,K 1,2,...,K}
∂ l o g ( p i k ) ∂ β m T = ∑ e x i β k ⋅ 0 ⋅ ∑ − e x i β k ⋅ e x i β k ⋅ x i T ∑ 2 = x i T ( I ( y i = w k ) − p i k ) \frac{\partial log(p_{ik})}{\partial β_m^T}=\frac{\sum}{e^{x_iβ_k}} · \frac{0·\sum-e^{x_iβ_k} ·e^{x_iβ_k}· x_i^T}{\sum^2}\\ =x_i^T(I(y_i=w_k)-p_{ik}) βmTlog(pik)=exiβk20exiβkexiβkxiT=xiT(I(yi=wk)pik)

β m β_m βm是列向量
所以(8)式的导数(向量形式):
∂ L ( β ) ∂ β k T = − ∑ i = 1 n x i T ⋅ ( I ( y i = w k ) − p i k ) \frac{\partial L(β)}{\partial β_k^T}=-\sum_{i=1}^{n}x_i^T·(I(y_i=w_k)-p_{ik}) βkTL(β)=i=1nxiT(I(yi=wk)pik)
其中 x i x_i xi是1x(m+1)的向量

于是 β m β_m βm的更新如下
β m ( t ) = β m ( t − 1 ) − l ⋅ ∂ L ( β ) ∂ β k T β_m^{(t)}=β_m^{(t-1)}-l·\frac{\partial L(β)}{\partial β_k^T} βm(t)=βm(t1)lβkTL(β)
其中 β m ( t ) β_m^{(t)} βm(t)是(m+1)x1的向量, l l l是学习速率


如何预测

这里只是大致介绍下思路,具体请见代码实现部分
回忆一下(5)式
P ( y i = w k ) = e x i β k ∑ j = 1 K e x i β j P(y_i=w_k)=\frac{e^{x_iβ_k}}{\sum_{j=1}^{K}e^{x_iβ_j}} P(yi=wk)=j=1Kexiβjexiβk
到目前为止,我们通过迭代得到了我们的参数 β β β矩阵(是(m+1)xK的矩阵),现在就需要把测试集带入查看预测的准确率,即测试集里第i个样本,与 β β β矩阵第k列对应的(5)式的值p_ik(X_test,beta,i,k)

对每个样本,我们想知道其对应的每个p_ik的值,于是有 p i 1 = P ( y i = w 1 ) , p i 2 = P ( y i = w 2 ) , . . . . p i K = P ( y i = w K ) p_{i1}=P(y_i=w_1),p_{i2}=P(y_i=w_2),....p_{iK}=P(y_i=w_K) pi1=P(yi=w1),pi2=P(yi=w2),....piK=P(yi=wK),这K个值里,假设 p i m = P ( y i = w m ) p_{im}=P(y_i=w_m) pim=P(yi=wm)是最大的那个,则将 w m w_m wm作为 y i y_i yi的预测值


代码实现

# 式(5)p_ik的实现

def p_ik(X,beta,i,k):
 	'''
 	X是标准化+归一化后的训练集,nx(m+1)的dataframe
 	beta是(m+1)xK的参数np数组
 	'''
 	# 防止数据溢出,因为此处设定dtype=np.float128
 	# 分子
    top=np.exp(np.array(X.iloc[i],dtype=np.float128).reshape(1,X.shape[1]).dot(beta[:,k].reshape(X.shape[1],1)))[0][0]
    # 分母
    buttom=np.exp(np.array(X.iloc[i],dtype=np.float128).reshape(1,X.shape[1]).dot(beta)).sum()
    return (top/buttom)

# 式(8)损失函数的实现
def loss_fun(X,y,beta):
	'''
	X是标准化+归一化后的训练集因变量,nx(m+1)的dataframe
	y是训练集自变量,nx1的dataframe,有K种取值
	'''
    s=0
    for k in range(K):#自变量共有K类
        for i in range(y.shape[0]):
        	# 第二个lambda里面p_ik等于0时,np.log(p_ik)取-10**(10),以免报错
            s+=-(lambda x: 1 if x==k else 0)(y.iloc[i])*(lambda x: np.log(x) if x!=0 else 10**(-10))(p_ik(X,beta,i,k))
    return s

# 梯度下降,此处采用小批量梯度下降的方法
def mini_batch_gradient(X,y,max_iter=100,batch_size=10,alpha=.05,ep=0.001):
	'''
	X是标准化+归一化后的训练集因变量,nx(m+1)的dataframe
	y是训练集自变量,nx1的dataframe,有K种取值
	max_iter:最大迭代次数
	batch_size每次更新参数用的数据大小
	alpha 学习速率
	ep 停止迭代的阈值
	'''
    n_samples,n_features=X.shape
    #初始化参数
    beta=np.ones([n_features,len(np.unique(y))])
    loss=[0]
    iter=0
    while iter < max_iter:
        iter+=1
        print('{}st iteration'.format(iter))
        #shuffle
        X=X.sample(frac=1)
        y=y.loc[X.index]
        for j in tqdm(range(0,n_samples,batch_size)):
            mini_X,mini_y=X[j:j+batch_size],y[j:j+batch_size]
            for id_,w in enumerate(np.sort(np.unique(y))):
                #更新变量
                array=np.zeros([n_features,])
                for i in range(mini_X.shape[0]):
                    array +=(lambda x: 1-p_ik(mini_X,beta,i,id_) if x==w else 0-p_ik(mini_X,beta,i,id_))(mini_y.iloc[i])*mini_X.iloc[i]
                beta[:,id_]=beta[:,id_]+alpha*array
        loss.append(loss_fun(X,y,beta))
        delta_loss=loss[-1]-loss[-2]
        # 若前后两次迭代的损失函数之差小于给定的阈值,停止迭代
        if np.abs(delta_loss)<ep:
            print('delta_loss goes under the threshold')
            break
    return loss,beta
    
# 精确度
n_samples,n_features=X_test.shape
a=0
for i in range(n_samples):
	# 预测值对应的索引
    pred_index=np.array([p_ik(X_test,beta,i,k) for k in range(n_features)]).argmax()
    # 真实值
    y_real=y_test.iloc[i]
    # 如果预测值与真实值相等
    if np.sort(np.unique(y))[pred_index]==y_real:
        a += 1


以上就是全部内容了,希望能帮助到各位读者,谢谢阅读~

你可能感兴趣的:(机器学习背后的理论,机器学习,逻辑回归,python,numpy,算法)