西瓜书3.3 尝试解题(python)对率回归 极大似然估计

数据如下:

x01=[0.697,0.774,0.634,0.608,0.556,0.403,0.481,0.437,0.666,
     0.243,0.245,0.343,0.639,0.657,0.360,0.593,0.719]
x02=[0.460,0.376,0.264,0.318,0.215,0.237,0.149,0.211,0.091,
     0.267,0.057,0.099,0.161,0.198,0.370,0.042,0.103]
y=[1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0]

x01表示密度,x02表示含糖率,y标志瓜的好坏(1为好,0为坏)

这道题的目的是实现对率回归,那么从3.3节看起。

可以看到我们要求的其实是一个系数序列ω和一个偏差b。简单看过对率回归的介绍后,直接来看推导过程。

将前面的3.19稍微改写一下就可以得到3.22,而由于又有p(y=1|x)+p(y=0|x)=1,所以可以得到3.23和3.24这样的式子。

3.19:ln(y/(1-y))=ωΤx+b

3.22:ln p(y=1|x)/p(y=0|x) =ωΤx+b

按照提示,跳到7.2查看极大似然估计。第一句话指出在估计参数前要假定其具有某种确定的概率分布形式,而在这一题中

我们的概率分布形式就是对率函数,使用极大似然估计就是根据已知的样本来估计参数,解释一下类条件概率指的是在

某种前提下发生某一事件的概率,则P(x|θc)的具体含义就是参数向量为θc时事件x的发生概率。需要厘清一下“概率”和“似然”的区别,

简单来讲概率就是在给定参数时事件发生的可能性,而似然就是在已知随机事件后、概率分布的参数为某一值的可能性(相对于其他

参数取值而言)。具体的解释指路大佬的博客:https://blog.csdn.net/songyu0120/article/details/85059149 讲得非常详尽易懂

而7.9告诉我们的就应当是,已知发生c类事件时,参数θ的值为θc的概率等于参数值为θc时每一个c类事件发生的概率的乘积,

理所当然的,对问题而言最合适的θc就是使7.9式等号右侧的值最大的那个。而7.10中的对数转换操作原理就是将乘法转化为加法,

从而有效防止下溢(超出数据类型所能表示的最小数字),而对数的转换也不会带来信息的损失。

再回到3.25就能看明白它的含义了:对于给定的数据集{(xi,yi)} i∈(1,m) 和一组参数集(ω,b),每个样本属于其真实标记的概率p

的累乘就是这组参数集的似然。而在式中使用了对数似然来防止下溢。

3.25到3.26的操作只是进行了符号的合并简化以及将两种情况的概率巧妙地分离开。

而将3.23与3.34的p1、p0式子代入3.26后,分类讨论y=0、y=1的情况而后观察,就可以把结果合并为3.27的形式,再取负就得到3.27。

因为取负了,所以本来我们要求的是式子的最大值现在变成求式子的最小值。书上应用的是牛顿法。

牛顿法,其核心思想就是泰勒展开。首先讲讲其在方程求解中的应用,然后理解其在最优化的应用会方便很多。

在求解方程的求根公式很复杂甚至没有求根公式时,可以利用牛顿法进行迭代求解,过程就是利用泰勒公式在x0处展开到

一阶,使f(x)=f(x0)+(x-x0)f'(x0)=0,求得一个x1使f(x)比较接近于0,显然,要是我们重复使用这个方法也就是迭代,我们就可以

得到更加准确的答案直到最后在f(x*)=0收敛。

在最优化问题中,如果我们要求一个函数的极大极小问题,显然可以转化为求解函数导数=0的问题,那么就可以使用牛顿法

在方程求解中的方法。因为要求一阶导数的结果等于0,所以对原函数进行两阶泰勒展开,得到

f(x+△x)=f(x+△x)+f'(x+△x)△x+f''(x+△x)△x²/2,两侧对△x求导,在△x无限趋近于0的时候,就可以忽略函数中的△x,从而得到

f'(x)+f''(x)△x=0。则△x=-f'(x)/f''(x)。把△x视作Xn+1(第n+1个迭代解)与Xn(第n个迭代解)的差值,我们就可以得到

迭代解的更新公式,Xn+1=Xn-f'(x)/f''(x)。用矩阵计算代换就可以得到3.29。其中一阶项和二阶项的计算方法都在后面给出,

那么我们只要使用编程实现就行了。

首先,为了方便起见我们要引入numpy这个库来进行矩阵计算,所以对于数据集我们要做一些改动:

x=np.array([[0.697,0.774,0.634,0.608,0.556,0.403,0.481,0.437,0.666,
     0.243,0.245,0.343,0.639,0.657,0.360,0.593,0.719],
     [0.460,0.376,0.264,0.318,0.215,0.237,0.149,0.211,0.091,
     0.267,0.057,0.099,0.161,0.198,0.370,0.042,0.103],
     [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]])
y=np.array([1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0])
before=np.array([[0],[0],[1]])

将x的数据合并并加入一行1,这个操作对应于3.25和3.26之间那段中的操作,before矩阵就是参数的数据集合,我们不妨设起始参数为0,0,1。

编写程序时要时刻记住x、y、before在书上推导过程中都是单列矩阵,但操作中我们的一些操作会使之变成行矩阵,在后续计算中要及时转置

回来。比如:

np.array([x[:,i]])

这样的操作,目的是从x中取出第i列作为一个单独矩阵,取出后默认是行矩阵,要及时转置。

清楚这点后着手编写各部分的代码。

首先是一阶导的计算:

def fd():
     b1 = 0
     for i in range(17):
          k=np.exp(np.dot(before.T,np.array([x[:,i]]).T))
          b1=b1-np.array([x[:,i]])*( y[i]-(k/(1+k)))
     return b1

而后是二阶导的计算:

def sd():
     b2 = 0
     for i in range(17):
          k = np.exp(np.dot(before.T,np.array([x[:,i]]).T))
          b2=b2+np.dot(np.array([x[:,i]]).T,np.array([x[:,i]])) * (k/(1+k)) * (1-(k/(1+k)))
     return b2

最后是主函数,偷个懒不追求最终结果是否收敛的话,限定一个迭代次数上限就完事:

k=1000
while(k):
     k-=1
     b1=fd()
     b2=sd()
     before=before-np.dot(linalg.inv(b2),b1.T)

print(before)

如果要寻求更精确的结果,可以稍微修改主函数的内容:

mae=0
ima=0
n=0
while(1):
ima=0
for i in range(17):
k=np.dot(before.T,np.array([x[:,i]]).T)
ima=ima+(-y[i]*k+np.log(1+np.exp(k)))
if(np.abs(ima-mae)<=0.001):
break
mae=ima
n=n+1
b1=fd()
b2=sd()
before=before-np.dot(linalg.inv(b2),b1.T)

print('迭代次数:',n)
print('最终参数:',before)

其实就是每次计算出3.27式的值并且在和前次差值小于等于0.00001时退出并输出结果。

结果:

迭代次数: 4
最终参数: [[ 3.15832738]
 [12.52119012]
 [-4.42886222]]

 完整代码:

import math
import numpy as np
from numpy import linalg

x=np.array([[0.697,0.774,0.634,0.608,0.556,0.403,0.481,0.437,0.666,
     0.243,0.245,0.343,0.639,0.657,0.360,0.593,0.719],
     [0.460,0.376,0.264,0.318,0.215,0.237,0.149,0.211,0.091,
     0.267,0.057,0.099,0.161,0.198,0.370,0.042,0.103],
     [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]])
y=np.array([1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0])
before=np.array([[0],[0],[1]])

def fd():
     b1 = 0
     for i in range(17):
          k=np.exp(np.dot(before.T,np.array([x[:,i]]).T))
          b1=b1-np.array([x[:,i]])*( y[i]-(k/(1+k)))
     return b1

def sd():
     b2 = 0
     for i in range(17):
          k = np.exp(np.dot(before.T,np.array([x[:,i]]).T))
          b2=b2+np.dot(np.array([x[:,i]]).T,np.array([x[:,i]])) * (k/(1+k)) * (1-(k/(1+k)))
     return b2


mae=0
ima=0
n=0
while(1):
     ima=0
     for i in range(17):
          k=np.dot(before.T,np.array([x[:,i]]).T)
          ima=ima+(-y[i]*k+np.log(1+np.exp(k)))
     if(np.abs(ima-mae)<=0.000001):
          break
     mae=ima
     n=n+1
     b1=fd()

     b2=sd()
     before=before-np.dot(linalg.inv(b2),b1.T)

print('迭代次数:',n)
print('最终参数:',before)
原文地址:https://www.cnblogs.com/forever3329/p/13809015.html