在Logistic回归中,
可以看到它的值域(0,1),通过sigma函数计算出最终结果,以0.5为分界线,最终结果大于0.5则属于正类(类别值为1),反之属于负类(类别值为0)。
其中X为输入的矩阵样本,W为回归系数,上面的小T说明经过转置。(转置不转置看维度,运算中转置是看计算是否符合矩阵运算,才转置的)
P( y=1) = g(Z)
P( y=0) =1-g(Z)
上述两式即为在已知样本X和回归系数W的情况下,样本X属性正类(y=1)和负类(y=0)的条件概率。
将两个公式合并成一个,即为:P(y) = g(Z) y * (1-g(Z))(1-y)
大家可以试一下,如果y=1,P(y=1) = g(Z) 1 * 1 = g(Z) ; 则y=0,P(y=0)=1*(1-g(Z))(1-0)=1-g(Z)
既然概率出来了,那么最大似然估计也该出场了。假定样本与样本之间相互独立,那么整个样本集生成的概率即为所有样本生成概率的乘积:
L= ∏(P(yi) )=∏ ( g(Zi) yi * (1-g(Zi))(1-yi) ),其中 ∏ 是相乘,那个 ∑ 是相加,记好哦
为了简化问题,我们对整个表达式求对数,(将指数问题对数化是处理数学问题常见的方法,可以相加了,相加比较简单):
l=log(L)=ylog(g(Z))+(1-y)log(1-g(Z))
满足似然函数 l 的最大的W值即是我们需要求解的模型。(这里面的log实际上是In)
首先将刚获得的公式拿出来:
Z=WT X
g(Z)=1/(1+e-Z)
l=ylog(g(Z))+(1-y)log(1-g(Z))-----简化为---->l=ylog(g) + (1-y)log(1-g)
然后倒着求偏导(大家不会的看下高数,用链式法则求导):
(1)对l求g(Z),将σl/σg记做dg:
dg = y/g +[ (1-y)*(-1/1-g) ] = y/g - (1-y)/(1-g)
(2)对l求导Z,那么根据链式法则,σl/σZ=σl/σg * σg/σZ = dg * g' 记做dZ:(其中g'=[1/(1+e-Z)]'=g*(1-g),这个可以自己求下到,这里求导比较麻烦,不过下图可以看看)
dZ =dg * g' = [ y/g - (1-y)/(1-g) ] *[ g*(1-g) ]= y - g
(3)对l求导W,那么根据链式法则,σl/σW = σl/σg * σg/σZ * σZ/σW =dZ * Z' 记做dW:( 其中 Z'= [WT X]' =X )
dW = (y-g) * X (后面代码中会用到这个公式,要知道怎么来的)
W = W + α * dW (最佳系数 = 原来的系数 + 学习率 * 梯度) (有限次循环,获得最佳系数)
你在代码中会看到:
error = y - g = 真实类别 - 预测类别
W=W+α*error*X
python3版本代码(对于小白,在每次测试时候,可以打断点到测试那里,更容易理解,对于print过多,自行删除):
from numpy import *
import matplotlib.pyplot as plt
def loadDateSet():
dataMat = []
labelMat = []
fr = open('testSet.txt') #加载文本,第一列为X,第二列为Y,第三列为分类
for line in fr.readlines(): #一行一行读取文本
lineArr = line.strip().split() #strip() 方法用于移除字符串头尾指定的字符(默认为空格),split()方法以空格进行分割切片
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) #在(x,y)前面加个1.0,为啥要有1这个特征呢,其实他是直线的常量y=ax+b的b,不懂得到画图时就明白了
labelMat.append(int(lineArr[2])) #分类
return dataMat, labelMat
#测试
dataArr, labelMat = loadDateSet()
print("dataArr:", dataArr)
print("labelMat:", labelMat)
#####################################################################################################################
#Sigmoid函数f(x)=1/(1+e的(-x)次方)
def sigmoid(inX):
"""
:param inX: 输入的X
:return: 返回Y
"""
return 1.0/(1+exp(-inX))
#####################################################################################################################
#梯度上升
"""
基于最优化方法(梯度上升)对最佳回归系数确定
梯度上升算法:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻
对于回归系数:
Sigmoid函数的输入的inX记做z, 则z=w0*x0 + w1*x1 + w2*x2,他写成矩阵形式就是Z=W.T*X,至于是否转置看维度
最重要是是对梯度的理解。
"""
def gradAscent(dataMatIn, classLabels):
"""
:param dataMatIn: 数据集
:param classLabels: 分类标签
:return:每个特征的最佳回归系数矩阵,特征是指输入矩阵的列
"""
dataMatrix = mat(dataMatIn) #mat()函数可以将数组转化为矩阵
labelMat = mat(classLabels).T #.T矩阵的转置,列向量现在转换为行向量
m, n = shape(dataMatrix) #数据集的行和列数
alpha = 0.001 #学习率α = 步长
maxCycles = 500 #迭代次数 = 循环次数
weights = ones((n, 1)) #最佳回归系数 = 初始化为1的列向量
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) #预测的类别 = 先计算Z = W*X,这个维度不用转置在计算sigmoid(Z)
error = labelMat - h #误差 = 真是类别和预测类别的差值
weights = weights + alpha * dataMatrix.T * error #梯度上升公式:w = w + 学习率 * 梯度,而梯度 = 误差 * 输入值矩阵,至于转置是看计算是否符合矩阵运算,才转置的
return weights
#测试
weights = gradAscent(dataArr, labelMat)
print("对于3个特征的最佳回归系数:", weights)
##################################################################################################################
#将分类给画出来,画出最佳的拟合直线
def plotBestFit(weights):
"""
:param weights: 最佳回归系数数组
:return:
"""
dataMat, labelMat = loadDateSet() #加载数据集,返回数据集合分类
dataArr = array(dataMat) #将数据集数组化
dataShape = shape(dataArr) #data的形状
print("data的形状:", dataShape)
n = dataShape[0] #形状的第一个是行数(即数据集的个数),第二个是列数(即数据集的特征)
xcord1 = [] #分类为1的点
ycord1 = []
xcord0 = [] #分类为0的点
ycord0 = []
for i in range(n):
if int(labelMat[i]) == 1: #如果分类为1,添加到1分类的点集,否者返回到0分类的点集
xcord1.append(dataArr[i, 1]) #这个dataArr有3列,其中第0列为1,第1,2列为x,y
ycord1.append(dataArr[i, 2])
else:
xcord0.append(dataArr[i, 1])
ycord0.append(dataArr[i, 2])
fig = plt.figure() #figure()函数创建一个新的图
ax = fig.add_subplot(111) #add_subplot()函数在一张figure里面生成多张子图参数111,表示1行1列第1个位置
ax.scatter(xcord1, ycord1, s=30, c='red', marker='s') #散点图
ax.scatter(xcord0, ycord0, s=30, c='green')
# 直线 x 的范围
x = arange(-3.0, 3.0, 0.1) # (start, end, step)可以调step执行起来,看看图
# 画出直线,weights[0]*1.0+weights[1]*x+weights[2]*y=0
# 之前计算时对原始数据做了拓展,将两维拓展为三维,第一维全部设置为1.0,实际他是一个 y=ax+b, b常量
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x, y) #画出直线
plt.xlabel('x1') #X轴标记为x1
plt.ylabel('x0') #Y轴标记为x0
plt.show()
plotBestFit(weights.getA()) #getA()将weights矩阵转换为数组,getA()函数与mat()函数的功能相反
"""
如果是矩阵的话会报这样的错:
"have shapes {} and {}".format(x.shape, y.shape))
ValueError: x and y must have same first dimension, but have shapes (60,) and (1, 60)
为啥要用数组呢?因为 x = arange(-3.0, 3.0, 0.1),len(x) = [3-(-3)]/0.1 = 60
而weights是矩阵的话,y = (-weights[0]-weights[1]*x)/weights[2],len(y) = 1,有60个x,y只有一个,你这样都画不了线
而weights是数据的话,len(y) = 60
"""
-0.017612 14.053064 0
-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
0.406704 7.067335 1
0.667394 12.741452 0
-2.460150 6.866805 1
0.569411 9.548755 0
-0.026632 10.427743 0
0.850433 6.920334 1
1.347183 13.175500 0
1.176813 3.167020 1
-1.781871 9.097953 0
-0.566606 5.749003 1
0.931635 1.589505 1
-0.024205 6.151823 1
-0.036453 2.690988 1
-0.196949 0.444165 1
1.014459 5.754399 1
1.985298 3.230619 1
-1.693453 -0.557540 1
-0.576525 11.778922 0
-0.346811 -1.678730 1
-2.124484 2.672471 1
1.217916 9.597015 0
-0.733928 9.098687 0
-3.642001 -1.618087 1
0.315985 3.523953 1
1.416614 9.619232 0
-0.386323 3.989286 1
0.556921 8.294984 1
1.224863 11.587360 0
-1.347803 -2.406051 1
1.196604 4.951851 1
0.275221 9.543647 0
0.470575 9.332488 0
-1.889567 9.542662 0
-1.527893 12.150579 0
-1.185247 11.309318 0
-0.445678 3.297303 1
1.042222 6.105155 1
-0.618787 10.320986 0
1.152083 0.548467 1
0.828534 2.676045 1
-1.237728 10.549033 0
-0.683565 -2.166125 1
0.229456 5.921938 1
-0.959885 11.555336 0
0.492911 10.993324 0
0.184992 8.721488 0
-0.355715 10.325976 0
-0.397822 8.058397 0
0.824839 13.730343 0
1.507278 5.027866 1
0.099671 6.835839 1
-0.344008 10.717485 0
1.785928 7.718645 1
-0.918801 11.560217 0
-0.364009 4.747300 1
-0.841722 4.119083 1
0.490426 1.960539 1
-0.007194 9.075792 0
0.356107 12.447863 0
0.342578 12.281162 0
-0.810823 -1.466018 1
2.530777 6.476801 1
1.296683 11.607559 0
0.475487 12.040035 0
-0.783277 11.009725 0
0.074798 11.023650 0
-1.337472 0.468339 1
-0.102781 13.763651 0
-0.147324 2.874846 1
0.518389 9.887035 0
1.015399 7.571882 0
-1.658086 -0.027255 1
1.319944 2.171228 1
2.056216 5.019981 1
-0.851633 4.375691 1
-1.510047 6.061992 0
-1.076637 -3.181888 1
1.821096 10.283990 0
3.010150 8.401766 1
-1.099458 1.688274 1
-0.834872 -1.733869 1
-0.846637 3.849075 1
1.400102 12.628781 0
1.752842 5.468166 1
0.078557 0.059736 1
0.089392 -0.715300 1
1.825662 12.693808 0
0.197445 9.744638 0
0.126117 0.922311 1
-0.679797 1.220530 1
0.677983 2.556666 1
0.761349 10.693862 0
-2.168791 0.143632 1
1.388610 9.341997 0
0.317029 14.739025 0