机器学习——分类算法4:Logistic回归 梯度上升 思想 和 代码解释

思想(这个只有输出层的神经网络也有用到):

在Logistic回归中,

1、首先介绍Sigmoid函数:

      

可以看到它的值域(0,1),通过sigma函数计算出最终结果,以0.5为分界线,最终结果大于0.5则属于正类(类别值为1),反之属于负类(类别值为0)。

2、将Sigmoid函数g(z)作为阀门,令Z=WT X,此时这里的大写符号都是向量,然后将Z代入到g(Z)里面。

其中X为输入的矩阵样本,W为回归系数,上面的小T说明经过转置。(转置不转置看维度,运算中转置是看计算是否符合矩阵运算,才转置的)

3、对于sigmoid函数的特性:

P( y=1) = g(Z)                   

P( y=0) =1-g(Z)

上述两式即为在已知样本X和回归系数W的情况下,样本X属性正类(y=1)和负类(y=0)的条件概率。

两个公式合并成一个,即为:P(y) = g(Z) * (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)

4、最大似然估计

既然概率出来了,那么最大似然估计也该出场了。假定样本与样本之间相互独立,那么整个样本集生成的概率即为所有样本生成概率的乘积:

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)

5、求梯度dW

首先将刚获得的公式拿出来:

        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/σσl/σg * σg/σZ * σZ/σW =dZ * Z'  记做dW:( 其中 Z'= [WT X]' =X )

           dW = (y-g) * X      (后面代码中会用到这个公式,要知道怎么来的)

6、对回归系数进行梯度上升,获得最佳回归系数:

             W = W + α * dW      (最佳系数 =  原来的系数  +   学习率  *  梯度) (有限次循环,获得最佳系数)

  你在代码中会看到:

error = y - g = 真实类别 - 预测类别

W=W+α*error*X


7、学会灵活运用,如果将Sigmoid函数换为其他函数,步骤一样,先计算似然函数 l,在倒过来用链式法则求偏导dW




代码:

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
"""

testSet.txt数据集:(拷贝过去自己命名)

-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


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值