学习笔记(二):逻辑斯蒂回归算法(Logistic Regression)

一、算法简介

代码下载:https://download.csdn.net/download/xdg2008/16744956?spm=1001.2014.3001.5503

1.1 定义

逻辑斯蒂回归(Logistic Regression) 虽然名字中有回归,但模型最初是为了解决二分类问题,后来可以解决多分类问题。

线性回归模型帮助我们用最简单的线性方程实现了对数据的拟合,但只实现了回归而无法进行分类。因此LR就是在线性回归的基础上,构造的一种分类模型。

对线性模型进行分类如二分类任务,简单的是通过阶跃函数(unit-step function),即将线性模型的输出值套上一个函数进行分割,大于z的判定为0,小于z的判定为1。

但这样的分段函数数学性质不好,既不连续也不可微。因此有人提出了对数几率函数,见上图右,简称Sigmoid函数。

该函数具有很好的数学性质,既可以用于预测类别,并且任意阶可微,因此可用于求解最优解。将函数带进去,可得LR模型为

其实,LR 模型就是在拟合 z = w^T x +b 这条直线,使得这条直线尽可能地将原始数据中的两个类别正确的划分开

1.2 损失函数表达式

    回归问题的损失函数一般为平均误差平方损失 MSE,LR解决二分类问题中,损失函数为如下形式

    L=∑ylogy+(1-y)log(1-y)

   

1.3梯度下降

     得到了逻辑回归的表达式,下一步跟线性回归类似,构建似然函数,然后最大似然估计,最终推导出θ的迭代更新表达式。这个思路不清楚的请参考文章《线性回归、梯度下降》,只不过这里用的不是梯度下降,而是梯度上升,因为这里是最大化似然函数不是最小化似然函数

     L=∑ylogy+(1-y)log(1-y)

    转换后的似然函数对w求偏导,在这里我们以只有一个训练样本的情况为例:

    这个求偏导过程第一步是对θ偏导的转化,依据偏导公式:y=lnx y'=1/x。

    第二步是根据g(z)求导的特性g'(z) = g(z)(1 - g(z)) 。

    第三步就是普通的变换。

    这样我们就得到了梯度上升每次迭代的更新方向,那么θ的迭代表达式为:

     逻辑回归误差对w和b的偏导数∂1,∂2 有以下公式:

      ∂1_w=X*(H(X)-Y)

    其中:H(X)=sigmoid(Feature*Weight),Y=Label,X=Feature.T

二、案列实现

    2.1 案列背景 

       杜紫藤女士, 一直在某婚恋网站上寻找自己中意的他, 网站给她推荐了很多男士, 但是她并不是每个都喜欢, 网站统计了这些男士的情况,

        这些男士可以划分为如下几类:

        (1)不喜欢的人(didntLike)

        (2)魅力一般的人(smallDoses)

        (3)极具魅力的人(largeDoses)

    2.2 特征数字

        杜紫藤女士收集了一些样本数据,她把样本数据存放在datingTestSet.txt文本文件中,每个样本数据占据一行,总共有1000行。

        这些样本数据主要有以下3个特征:

       (1)每年获得的飞行常客里程数

         (2)玩视频游戏所消耗的时间百分比

         (3)每周消费的冰淇淋公升数

   3.3算法及代码:

        import numpy as np

       def filetomatrix(filename):
            fr = open(filename)
            arrayOLines = fr.readlines()             #读取文件的所有行
           numberOfLines = len(arrayOLines)        #获取文件行数
           returnMat =np.zeros((numberOfLines,3))  #返回Numpy矩阵,解析完成的数据为numberOfLines行,3列零数据矩阵classLabelVector = []  #返回的分类标签向量 
           classLabelVector = []                   #返回的分类标签向量 
           index = 0
          for line in arrayOLines:    
              line = line.strip()                         #使用line.strip()函数截取掉所有的空白符,s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
              listFromLine = line.split('\t')             #将line根据'\t'分隔符进行切片
             returnMat[index,:] = listFromLine[0:3]      #选取前三列元素,将它们存储到特征矩阵中
             classLabelVector.append(listFromLine[-1])   #将最后一列数据加入classLabelVector中,作为情感分类标签
             index += 1
         return returnMat,classLabelVector

      rmat1,label1=filetomatrix('D:\Train_test\datingTestSet.txt')

       lab=[]
       l=[] 

      for i,item in enumerate(label1):
            if item =='largeDoses':
                lab.append(0)
           elif item =='smallDoses':
                lab.append(1)
          elif item =='didntLike':
                lab.append(2)

     one-hot编码
    label11=np.array(lab).astype(int)
    label12=np.eye(3)[label11]

     def sigmoid(z):
          return 1 / (1 + np.exp(-z))

def gradentdecent(train,target):
    #feature = train[:,0:1]
    feature = train
    ones = np.ones((len(feature),1))
    Feature = np.hstack((feature ,ones))
    Label = target
    weight = np.ones((Feature.shape[1],target.shape[1]))
    changeweight  = np.zeros((Feature.shape[1],target.shape[1]))
    msehistory = []
    learningrate = 1
    for i in range(10000):
        y = np.dot(Feature,weight)
        error=sigmoid(y)-Label
        mse = np.sum(np.power(error,2))
        msehistory.append(mse)
        if len(msehistory)>=2:
            if(msehistory[-1]>msehistory[-2]):
                learningrate = learningrate/2
            else:
                learningrate = learningrate * 1.1
        change = np.dot(Feature.T,error)
        changeweight = changeweight + change**2       
        weight = weight - learningrate* change/np.sqrt(np.float32(changeweight))
        if(np.sum(np.square(change))<0.01):
            break
    return weight

def datingClassTest(train,target,lab):
    Ratio = 0.10                                                                  #使用Ratio比例来分割预测数据和测试数据
    m = train.shape[0]
    numTestVecs = int(m*Ratio)                 #计算测试向
    weight=gradentdecent(train[numTestVecs:m,:],target[numTestVecs:m])
    errorCount = 0.0
    for i in range(numTestVecs):               #0到numTestVecs为测试数据集,numTestVecs到为训练数据集
        #classifierResult = knn(feature[numTestVecs:m,:],datingLabels[numTestVecs:m],feature[i,:],8)
        value = train[i,:].reshape(1,-1)
        ones = np.ones((len(value),1))
        vas = np.hstack((value ,ones))
        np.set_printoptions(suppress=True)
        result=np.exp(np.dot(vas,weight))/np.sum(np.exp(np.dot(vas,weight)))
        expect = np.argmax(result)
        if expect==0:
            classifierResult="largeDose"
        elif expect==1:
            classifierResult="smallDose"
        elif expect==2:
            classifierResult="didntLike"
        
        if lab[i]==0:
            datingLabels="largeDose"
        elif lab[i]==1:
            datingLabels="smallDose"
        elif lab[i]==2:
            datingLabels="didntLike"
        print("预测分类类别:%s,真实列表类别:%s" % (classifierResult,datingLabels))
        if (classifierResult != datingLabels) : errorCount +=1.0
           print ("错误率:%f " % (errorCount/float(numTestVecs)))

     datingClassTest(rmat1,label12,lab)