首先我们需要解释线性的重要性。线性之所以如此重要,其本质原因在于两方面:
在广义线性模型中,线性体现在,我们的参数 θ \theta θ和各个属性 x i x_i xi之间的结合是线性的形式,即 θ T x \theta^T x θTx,直观地看,我们是在找一个方向 θ \theta θ对 x x x进行投影(严格上应该是做内积)。在广义线性模型的各个特例中,这一线性的结合形式是始终不变的。
现在我们讨论“广义”的含义。在简单线性模型中,我们假设标签值 y y y服从 N ( u , σ 2 ) N(u,\sigma^2) N(u,σ2)。从而自然假设输出的形式为 h θ ( x ) = θ T x h_\theta(x) = \theta^T x hθ(x)=θTx。这一输出形式带来的问题是,输出的范围不受限制,例如上篇我们讲分类问题的时候,对于取1概率p的输出估计值显然需要落在 [ 0 , 1 ] [0,1] [0,1]之间。因此广义的含义在于,输出的形式限制可以打开,为
h θ ( x ) = g ( θ T x ) h_\theta(x) = g(\theta^T x) hθ(x)=g(θTx)这样通过适当选取映射 g ( ) g() g()就能达到范围限制。这里我们称映射 g ( ) g() g()为响应函数,称映射 g − 1 ( ) g^{-1}() g−1()为联系函数。可以看到,简单线性模型选用的响应函数为恒等函数。
响应函数的选择可以有多种,比如在上一篇的二分类问题中,对范围在 [ 0 , 1 ] [0,1] [0,1]的概率p,我们可以选择logistic函数和标准正态的分布函数。本小节讨论一种特殊的响应函数,称为典则响应函数。其来源的基本思想是,我们希望响应函数是从标签值的分布中自然导出来的,因此我们需要对预先假设的分布的密度函数进行变形,使得我们可以 θ T x \theta^Tx θTx来估计某个无范围限制的参数。
显然,密度函数的值必须大于0,因此自然的变换为
这样我们就可以提炼出 e x p ( f ( η ) ) exp(f(\eta)) exp(f(η))中的参数 η \eta η,参数 η \eta η必须是充分的,即我们给出它的估计后,这个分布就可以被完全确定下来了。显然 η \eta η的范围在 R R R中均是有意义的,因为密度函数值始终大于0。此时就可以直接假设 η = θ T x \eta = \theta^Tx η=θTx,假设未进行变换前的参数是 ϕ \phi ϕ,那么根据 η \eta η的选取,总有 η = g − 1 ( ϕ ) \eta = g^{-1}(\phi) η=g−1(ϕ),从而可以反解出 ϕ = g ( η ) \phi = g(\eta) ϕ=g(η)。从而得到
ϕ = g ( η ) = g ( θ T x ) \phi = g(\eta) = g(\theta^Tx) ϕ=g(η)=g(θTx)因此这实际上就等价与我们选用了上式的 g ( ) g() g()作为响应函数,这样选取得到的 g ( ) g() g()称为典则响应函数,其逆函数 g − 1 ( ) g^{-1}() g−1()称为典则联系函数。
指数型分布簇
密度函数可以改写为如下形式的分布,属于指数型分布簇:
p ( y ; η ) = b ( y ) e x p ( η ′ T ( y ) − a ( η ) ) p(y;\eta) = b(y)exp(\eta^{'}T(y)-a(\eta)) p(y;η)=b(y)exp(η′T(y)−a(η))其中, η \eta η是一个充分统计量,也就是我们要利用 θ T x \theta^Tx θTx去估计的, η \eta η给定后,分布就可以确定下来。 a ′ ( η ) a^{'}(\eta) a′(η)是y的期望,而 T ( y ) T(y) T(y)大部分情况下是 y y y,而 η = g − 1 ( u ) \eta = g^{-1}(u) η=g−1(u),其中 u u u为原来的参数值。
E ( T ( y ) ∣ x ) = E ( y ∣ x ) = u = g ( η ) = g ( θ T x ) E(T(y)|x) = E(y|x) = u = g(\eta) = g(\theta^Tx) E(T(y)∣x)=E(y∣x)=u=g(η)=g(θTx)
我们记 h θ ( x ) = g ( θ T x ) h_\theta(x) = g(\theta^Tx) hθ(x)=g(θTx),则模型的训练必须确定参数 θ \theta θ的值。显然我们可以用最小二乘的思想,但是这里我们将使用极大似然估计的方法。我们将看到,利用梯度上升的数值算法求解其偏导函数的零点时,广义线性模型具有统一的梯度更新公式。
现在我们证明这个事情。我们将原来的密度函数 p ( y ; α ) p(y;\alpha) p(y;α)变化形式为 p ( y ; η ) p(y;\eta) p(y;η),那么似然函数可以表示为:
L ( θ ; x ) = ∏ i = 1 m p ( y i ; η ) L(\theta;x) = \prod_{i = 1}^{m}p(y_i;\eta) L(θ;x)=i=1∏mp(yi;η)目标为 θ = m a x θ ∈ Θ L ( θ ; x ) \theta = max_{\theta \in \Theta}L(\theta;x) θ=maxθ∈ΘL(θ;x)对数之,求导,得到
ℓ ′ ( θ ) = ∂ l o g L ( θ , x ) ∂ θ = ∑ i = 1 m 1 p ( y i ; η ) ∂ p ( y i ; η ) ∂ y i ∂ η ∂ θ = ∑ i = 1 m 1 p ( y i ; η ) [ p ( y i ; η ) ( y i − a ′ ( η ) ) ] x i = ∑ i = 1 m ( y i − u ) x i = ∑ i = 1 m ( y i − h θ ( x ) ) x i \begin{aligned} \ell^{'}(\theta) = & \frac{\partial{logL(\theta,x)}}{\partial{\theta}} \\ = & \sum_{i = 1}^{m}\frac{1}{p(y_i;\eta)}\frac{\partial p(y_i;\eta)}{\partial y_i}\frac{\partial \eta}{\partial \theta} \\ = & \sum_{i = 1}^{m}\frac{1}{p(y_i;\eta)}[p(y_i;\eta)(y_i-a^{'}(\eta))]x_i \\ = & \sum_{i = 1}^{m}(y_i - u)x_i \\ = & \sum_{i = 1}^{m}(y_i - h_\theta(x))x_i \end{aligned} ℓ′(θ)=====∂θ∂logL(θ,x)i=1∑mp(yi;η)1∂yi∂p(yi;η)∂θ∂ηi=1∑mp(yi;η)1[p(yi;η)(yi−a′(η))]xii=1∑m(yi−u)xii=1∑m(yi−hθ(x))xi这里就解释了上一篇中,为什么简单线性回归和logistic回归具有相同的梯度更新公式,其原因在于logistic函数 η = l o g ( ϕ 1 − ϕ ) \eta = log(\frac{\phi}{1-\phi}) η=log(1−ϕϕ)是二项分布的典则联系函数,而恒等函数 y = x y = x y=x是正态分布的典则联系函数,因此在MLE的意义下,二者必然有相同的梯度更新公式。
有很多的分布是属于指数型分布簇,比如正态分布、二项分布、多项分布、泊松分布、几何分布、伽马分布等等。现在我们选择一些,看看它们在广义线性模型下的典则响应函数的形式。
概率分布 | 参数 | 均值(向量) | 典则联系函数 ( η = θ T x ) \eta = \theta^Tx) η=θTx) | 典则响应函数 h θ ( x ) h_\theta(x) hθ(x) |
---|---|---|---|---|
正态分布 | μ \mu μ | μ \mu μ = μ \mu μ | η = μ \eta = \mu η=μ | μ = η \mu = \eta μ=η |
二项分布 | ϕ \phi ϕ | μ \mu μ = ϕ \phi ϕ | η = l o g ( μ 1 − μ ) \eta = log(\frac{\mu}{1-\mu}) η=log(1−μμ) | μ = e η 1 + e η \mu = \frac{e^{\eta}}{1+e^{\eta}} μ=1+eηeη |
泊松分布 | λ \lambda λ | μ \mu μ = λ \lambda λ | η = l o g ( μ ) \eta = log(\mu) η=log(μ) | μ = e η \mu = e^{\eta} μ=eη |
几何分布 | ϕ \phi ϕ | μ \mu μ = 1 ϕ \frac{1}{\phi} ϕ1 | η = l o g ( μ − 1 μ ) \eta = log(\frac{\mu-1}{\mu}) η=log(μμ−1) | μ = 1 1 − e η \mu = \frac{1}{1-e^{\eta}} μ=1−eη1 |
多项分布 | ϕ \phi ϕ | μ = ϕ \mu = \phi μ=ϕ | η i = l o g ( ϕ i ϕ k ) \eta_{i} = log(\frac{\phi_i}{\phi_k}) ηi=log(ϕkϕi) | ϕ i = e η i 1 + ∑ j = 1 k − 1 e η j \phi_i = \frac{e^{\eta_i}}{1+\sum_{j = 1}^{k-1}e^{\eta_j}} ϕi=1+∑j=1k−1eηjeηi |
上述公式的推导是简单的,我们只需要将概率密度函数变换为指数型分布簇的形式,就可以得到 η \eta η与原参数 ϕ \phi ϕ的联系函数,从而进一步可推得典则联系函数和典则响应函数。
注意,以上各种情况的参数都可采用MLE进行估计。其解可以利用牛顿迭代法或者梯度上升算法进行迭代,值得注意的是,通过上述的证明,我们知道在梯度上升中更新公式具有统一的形式,在随机梯度上升中,其为:
θ : = θ + α ( y i − h θ ( x i ) ) x i \theta: = \theta + \alpha(y_i - h_\theta(x_i))x_i θ:=θ+α(yi−hθ(xi))xi
注:在本实验中,我们可以通过上述梯度上升公式使得预测准确率达到72%左右,对外部的测试数据也有72%左右的预测准确率。通过实践,可以认识到随机梯度上升可以在较短的迭代次数下达到收敛。本实验中,对迭代步长进行一些动态的修改,具体来讲,就是求解函数我们允许用户传入最大最小两个步长值,在迭代过程中进行步长修改。修改可以是线性也可以是非线性的,但方向应该是从大步长逐渐减弱到小步长,个中原因在于在迭代伊始时,大步长有利于加速收敛,而越接近局部优解时,小步长有利于收敛的稳定。本例采用线性的步长下降。
myLogistic.py
import numpy as np
from numpy import dot
import random
def sigmoid(X):
return 1/(1+np.exp(-1*X))
def logistic(X,Y,alphaMax = 0.01,alphaMin = 0.001,epsilon = 0.00000001,numIter = 2000,method = 'SGD'):
'''
本函数利用Logstic模型,利用梯度下降法求出似然函数意义下的最优参数
'''
# X^T(y - h(X*theta)) \patial
if(method == 'BGD'):
theta_0 = np.ones(X.shape[1])
# alpha = 0.01
# epsilon = 0.000001
logValue = sigmoid(dot(X,theta_0))
errorV = Y - logValue
error_0 = dot(errorV,errorV)
error = np.inf
#n = 0
while((error > epsilon) & (n < numIter)):
alpha = alphaMax - (n/numIter)*(alphaMax-alphaMin)
#alpha = alphaMax
patial = dot(X.T,errorV)
theta_0 = theta_0 + alpha*patial
logValue = sigmoid(dot(X,theta_0))
errorV = Y - logValue
error_1 = dot(errorV,errorV)
error = abs(error_1 - error_0)
error_0 = error_1
#n = n + 1
#print('---------------第',n,'轮迭代--------------')
# print('theta:',theta_0)
# print('error:',error)
#print('errorSUM:',error_1)
#print('alpha:',alpha)
#print('patial:',patial)
return theta_0
if(method == 'SGD'):
theta_0 = np.ones(X.shape[1])
error_0 = 0
error = np.inf
k = list(range(X.shape[0]))
#n = 0
while((error > epsilon)&(n < numIter)):
random.shuffle(k)
alpha = alphaMin + (1-n/numIter)*(alphaMax-alphaMin)
for i in k:
patial = (Y[i] - sigmoid(dot(X[i,:],theta_0)))*X[i,:]
theta_0 = theta_0 + alpha*patial
logValue = sigmoid(dot(X,theta_0))
errorV = Y - logValue
error_1 = dot(errorV,errorV)
error = abs(error_1 - error_0)
error_0 = error_1
#n = n + 1
logValue = sigmoid(dot(X,theta_0))
errorV = Y - logValue
patial1 = dot(X.T,errorV)
# print('---------------第',n,'轮迭代--------------')
# print('theta:',theta_0)
# print('error:',error)
# print('patial:',patial1)
# print('errorSUM:',error_1)
# print('alpha:',alpha)
# print(n)
return theta_0
测试代码logisticReg.py
import myLogistic
import numpy as np
import pandas as pd
if __name__ == '__main__':
#simpleRegre.txt
samData = pd.read_table('C:/Users/Administrator/Desktop/MLCourseOfWSQ/pythonProject/mlData/Logistic/TestSet.txt',
header = None)
# 构造样本矩阵
sample = np.array(samData)
sampleY = sample[:,sample.shape[1]-1]
oneCol = np.ones([sample.shape[0],1])
sampleX = np.hstack((oneCol,sample[:,0:sample.shape[1]-1]))
# 调用函数估计参数并预测
theta = myLogistic.logistic(sampleX,sampleY,alphaMax = 0.0001,alphaMin = 0.0000001,epsilon = 0.000001,numIter = 4000,method = 'SGD')
predictConfidence = np.dot(sampleX,theta)
predictY = myLogistic.sigmoid(predictConfidence)
#输出预测结果与真实结果的比较
result = np.zeros(sample.shape[0])
result[predictY>0.5] = 1
compareResult = (sampleY == result)
for i in range(sample.shape[0]):
print('Y:',sampleY[i],' Predict Y:',result[i],' boolCompare:',compareResult[i],' predictP:',predictY[i])
print('right discrimination number:',sum(compareResult))
print('right discrimination ratio :',sum(compareResult)/sample.shape[0])
# 测试代码部分
# samData = pd.read_table('C:/Users/Administrator/Desktop/MLCourseOfWSQ/pythonProject/mlData/Logistic/HorseColicTest.txt',
# header = None)
# testSample = np.array(samData)
# testY = testSample[:,testSample.shape[1]-1]
# oneCol = np.ones([testSample.shape[0],1])
# testX = np.hstack((oneCol,testSample[:,0:testSample.shape[1]-1]))
# predictConfidence = np.dot(testX,theta)
# predictY = myLogistic.sigmoid(predictConfidence)
# result = np.zeros(testSample.shape[0])
# result[predictY>0.5] = 1
# compareResult = (testY == result)
# for i in range(testSample.shape[0]):
# print('Y:',testY[i],' Predict Y:',result[i],' boolCompare:',compareResult[i],' predictP:',predictY[i])
# print('right discrimination number:',sum(compareResult))
# print('right discrimination ratio :',sum(compareResult)/testSample.shape[0])