最近修改: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=⎣⎢⎢⎢⎡y1y2⋮yn⎦⎥⎥⎥⎤
带截距项的自变量 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=⎣⎢⎢⎢⎡11⋮1x11x21⋮xn1x12x21xn2......⋮...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} pik≜P(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=e−maxe−max∑j=1Kexiβjexiβk=∑j=1Kexiβj−maxexiβk−max
此方法有个问题就是,max可能会很大,而导致数据下溢,分子分母变为0,0/0会出现如下错误
RuntimeWarning: invalid value encountered in true_divide
法二:
之所以出现数据溢出,不就是因为np.array默认的是np.float64
吗,问题出在数据精度不够,那我们可以通过扩大精度的方式来处理溢出的问题,即设定dtype=np.float128
,潜在问题就是,数字无论多大或多小都会完全显示出来,有点点显示问题。
这个无序多元逻辑回归模型有以下几个事实
通过上面的分析我们知道,无序多元逻辑回归实质上是一个多项分布
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=1∑KnkLogpk=k=1∑Ki=1∑nI(yi=wk)Log∑j=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=1∑Ki=1∑nI(yi=wk)Log∑j=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}) ∂βkT∂log(pik)=exiβk∑⋅∑2exiβk⋅xiT⋅∑−exiβk⋅exiβk⋅xiT=xiT(1−pik)
∂ 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} ∂βmT∂log(pik)(m=k)=exiβk∑⋅∑20⋅∑−exiβk⋅exiβk⋅xiT=−xiT⋅pik
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}) ∂βmT∂log(pik)=exiβk∑⋅∑20⋅∑−exiβk⋅exiβk⋅xiT=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}) ∂βkT∂L(β)=−i=1∑nxiT⋅(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(t−1)−l⋅∂βkT∂L(β)
其中 β 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
以上就是全部内容了,希望能帮助到各位读者,谢谢阅读~