说明:本博客中的分析思路、举例、部分插图等均来源于吴恩达教授在斯坦福大学公开课《机器学习》中的讲解内容!
一、概述
我们可以简单的把逻辑回归所要解决的问题理解为“分类”问题,在分类问题中最为简单的为二分类,即我们只需要把一组数据分为两类即可。而在二分类问题中,最为简单的又属“线性分类”,即我们只需要用一条直线即可将两类数据分开。本博文就以最简单的线性二分类为例讲一下逻辑回归的思想。
二、从一个例子引入
在斯坦福公开课中,吴恩达教授通过一个“判断肿瘤是否是良性”的问题引入逻辑回归问题。即我们要根据已有的数据集(包括肿瘤的厚度、大小与对应的良性或恶性)来在良性肿瘤与恶性肿瘤间画出一条分界线。有了这条分界线后,我们以后就可以根据肿瘤的厚度与大小来预测该肿瘤是否是良性。如图2.1所示(图中以X1代表肿瘤大小,X2代表肿瘤厚度,○代表良性,×代表恶性)。我们把良性部分贴标签为y=0/y=1,把恶性部分贴标签为y=1/y=0。
三、假设函数h的形式与分界线的寻找
首先,假设的形式如图3.1所示,其中z=a*X1+b*X2+c
在预测时,当h(x)<0.5时就认为y=1/0;在h(x)>0.5时,就认为y=0/1。于是我们可以得到如下推导过程,如图3.2所示。
通过图3.2我们就可以很明显的看出,y=0/1与y=1/0的分界线实际上就是z=a*X1+b*X2+c=0这条直线。那么接下来任务就非常明确——通过算法对数据集进行学习以得出合适的参数值a,b,c
四、“代价函数形式”的确定
在上一篇博文中已经说了单边变量线性回归代价函数的形式,如图4.1所示
但由于在逻辑回归中,假设函数的形式是非线性的,这就可能导致将其代入线性回归的代价函数后得到一个“非凹函数”,从而导致在利用“梯度下降法”求代价函数最小值时使代价函数陷入局部最低点而无法得到全局最低点,如图4.2所示。
出于上述原因,我们必须寻求新的代价函数形式。首先,我们知道的是,代价函数值是和“实际值y”与“预测值h”相关的,并且代价函数值是“在各个样本点处代价和的平均值”。因此暂时可把代价函数值定义为如下形式,如图4.3
接下来的工作,就是探究Cost函数的形式。我们做如下分析:当y=1时,如果我们的预测值h=1则说明预测十分准确,此时代价为0;反之,如预测值h=0,则说明预测值完全反了,此时代价应该为无穷大。当y=0时,如果我们的预测值h=0则说明预测十分准确,此时代价为0;反之,如预测值h=1,则说明预测值完全反了,此时代价应该为无穷大。根据上述分析,我们可以得到Cost函数的形式,如图4.4所示,该分段函数形式完全满足了我们的分析。
由于y的取值只有0与1,因此我们可以将分段函数进行合并,最终得到逻辑回归中代价函数的形式,如图4.5所示。
五、求代价函数极小值点——梯度下降算法
与我的上篇博文《单变量线性回归算法分析及python源码》中所讲的梯度下降算法完全一致,请参考那篇博文,本文不再赘述。
六、逻辑回归算法的Python实现及运行结果
注:由于本次特征较少,对于“梯度下降算法”中用到的公式均为手算(手算结果附在最后,如图6.3、6.4),但计算量仍然较大。在后面的博文中会讲“特征映射”的内容,在那里我们将采用矩阵操作实现算法。
#####1.产生两组数点作为训练集,每组50个:第一组在数点0<x1<1,0<x2<=1之间,第二组在数点1<x1<2,1<x2<2之间########################
#####同时产生每个训练样本对应的y值############################################################################################
##(1)产生训练集
import random
x1=[]
x2=[]
i=50
while i:
x1.append(random.uniform(0,1))
x2.append(random.uniform(0,1))
i-=1
i=50
while i:
x1.append(random.uniform(1,2))
x2.append(random.uniform(1,2))
i-=1
##(2)产生训练集对应的y值(前25个数据点对应y=0,后25个数据点对应y=1)
y=[0]*50+[1]*50
# #####2.实现算法###########################################################################################################
import math
k=1 #学习率
a=b=c=0.5 #参数初始值
i=1 #记录迭代次数
j=0 #记录求和时的循环次数
sum=0 #记录求和值
J=[] #记录每次迭代后的代价函数值
temp_a=[] #记录a每次迭代后的值
temp_b=[] #记录b每次迭代后的值
temp_c=[] #记录c每次迭代后的值
temp_a.insert(0,0.5) #给记录a,b,c变化的数组初值赋为0.5
temp_b.insert(0,0.5)
temp_c.insert(0,0.5)
while j<=99: #计算代价函数值的初始值
z=a*x1[j]+b*x2[j]+c
h=1/(1+math.exp(-z))
sum+=y[j]*math.log(h)+(1-y[j])*math.log(1-h)
j+=1
sum/=(-100)
J.insert(0,sum)
while i<=10000: #通过循环进行一万次迭代
j=1 #设置初始值
sum=0
while j<=99: #计算a迭代后的数值
z=a*x1[j]+b*x2[j]+c
sum+=x1[j]*y[j]-x1[j]/(1+math.exp(-z))
j+=1
sum/=(-100)
temp_a.insert(i,a-k*sum)
j=1 #设置初始值
sum=0
while j<=99: #计算b迭代后的数值
z=a*x1[j]+b*x2[j]+c
sum+=x2[j]*y[j]-x2[j]/(1+math.exp(-z))
j+=1
sum/=(-100)
temp_b.insert(i,b-k*sum)
j=1 #设置初始值
sum=0
while j<=99: #计算c迭代后的数值
z=a*x1[j]+b*x2[j]+c
sum+=y[j]-1/(1+math.exp(-z))
j+=1
sum/=(-100)
temp_c.insert(i,c-k*sum)
a=temp_a[i] #同步更新
b=temp_b[i]
c=temp_c[i]
j=1 #设置初始值
sum=0
while j<=99: #计算代价函数值的初始值
z=a*x1[j]+b*x2[j]+c
h=1/(1+math.exp(-z))
sum+=y[j]*math.log(h)+(1-y[j])*math.log(1-h)
j+=1
sum/=(-100)
J.insert(i,sum)
i+=1 #迭代次数加1
#####3.画图################################################################################################################################
##(1)画代价函数图
import matplotlib.pyplot as plt
import numpy as np
plt.figure()
plt.xlabel('times')
plt.ylabel('J(a,b,c)')
i=np.linspace(0,10000,10001)
plt.plot(i,J,'-r',label='cost function')
plt.legend() #给图像加图例,图例格式默认
##(2)画训练集图和边界
plt.figure()
plt.xlabel('x1')
plt.ylabel('x2')
plt.scatter(x1[0:50:1],x2[0:50:1],s=np.pi*2**2,label='class 1')
plt.scatter(x1[50:101:1],x2[50:101:1],s=np.pi*2**2,marker='x',label='class 2') #x1[a:b:c]的含义为:取列表a中下标在区间[a,b)之间所有元素,每隔c个元素取一个
i=0
m=[]
n=[]
while i<50:
m.append(random.uniform(0,2))
n.append((-a/b)*m[i]-c/b)
i+=1
plt.plot(m,n,'-y',label='judgment boundary')
plt.legend(loc='upper left') #为了尽可能不遮挡所画的图,因此把图例放到左上角
plt.show()
运行结果如图6.1与6.2
左肩理想右肩担当,君子不怨永远不会停下脚步!