第5章 Logistic回归

1 引言

Logistic 是在回归的基础上进行分类(二分类),试想在二维平面上,是不是可以用一条直线把数据点分开呢(如果这条直线存在的话)?假如直线存在,在这条直线上的点为0,这条直线两侧的点,对于这条直线一侧大于0,一侧小于0,这就是进行了分类,现在呢,我们引入 sigmoid 函数,用来进一步转化为概率问题,sigmoid 函数如下所示


sigmoid函数

2 Sample Code

2.1 部分数据集如下,共计 100行3列,前两列为feature,最后一列为label
import numpy as np

# 从文本中读取数据集
def loadDataSet():
    x = []
    y = []
    fr = open(r'E:\tensorflow\Ch05\testSet.txt')
    for line in fr.readlines():
        # line.strip() 截取掉所有回车字符(即每行最后一个字符)
        # split(str="")  str - 分隔符,默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等分隔
        line_x = line.strip().split()
        # 此处第一列的 1,即为biases
        x.append([1.0, float(line_x[0]), float(line_x[1])])
        y.append([int(line_x[2])])
    return np.mat(x),np.mat(y)
# 测试 loadDataSet
x,y = loadDataSet()
print(x.shape)   # (100,3)
print(y.shape)   # (100,1)
# 读进数据集之后,现在让我们来以图像化显示一下数据集
def plotFigure():
    x, y = loadDataSet()
    xarr = np.array(x)
    n = np.shape(x)[0]
    x1 = []; y1 = []
    x2 = []; y2 = []
    for i in np.arange(n):
        if int(y[i]) == 1:
            x1.append(xarr[i,1]); y1.append(xarr[i,2])
        else:
            x2.append(xarr[i,1]); y2.append(xarr[i,2])
    
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)
    # scatter 画出散点图
    ax.scatter(x1, y1, s = 30, c = 'r', marker = 's')
    ax.scatter(x2, y2, s = 30, c = 'g')
    plt.show()

# 画图
plotFigure()
  • 数据集以图像化显示如上所示,现在我们就要来对它进行分类了(二分类问题)。也就是找到一条直线来使不同类别数据分开。
2.2 辅助函数
# 定义 sigmoid 函数
def sigmoid(x):
    return 1.0 / (1 + np.exp(- x))
print(sigmoid(0)) #  输出 0.5 验证通过
# 画出决策边界
# 用此函数来画出三种梯度上升算法的分类直线
import matplotlib.pyplot as plt

def plotBestFit(weights):
    x, y = loadDataSet()
    xarr = np.array(x)
    n = np.shape(x)[0]
    x1 = []; y1 = []
    x2 = []; y2 = []
    for i in np.arange(n):
        if int(y[i]) == 1:
            x1.append(xarr[i,1]); y1.append(xarr[i,2])
        else:
            x2.append(xarr[i,1]); y2.append(xarr[i,2])
    
    fig = plt.figure()
    ax = fig.add_subplot(1,1,1)
    # scatter 画出散点图
    ax.scatter(x1, y1, s = 30, c = 'r', marker = 's')
    ax.scatter(x2, y2, s = 30, c = 'g')
    
    # 画出Logistic 分类直线
    a = np.arange(-3.0, 3.0, 0.1) # (60,)
    # 由分类直线 weights[0] + weights[1] * a + weights[2] * b = 0 易得下式
    b = (-weights[0] - weights[1] * a) / weights[2]
    # print(b.shape)   # (1, 60)
    # print(b.T.shape) # (60, 1)
    ax.plot(a, b.T)
    plt.title('BestFit')
    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.show()
2.3 梯度上升算法

本文所使用的优化方法为 梯度上升算法,又逐步优化实现了 批量梯度上升算法,随机梯度上升算法,改进的随机梯度上升算法,并比较了三种优化方法的优越性。

2.3.1 批量梯度上升算法
# 批量梯度上升优化算法
def gradAscent(x, y):
    m, n = np.shape(x) # 100 3
    alpha = 0.001
    maxCycle = 500
    weights = np.ones((n, 1))  # 3行1列
    for k in np.arange(maxCycle):
        h = sigmoid(x * weights)
        error = y - h
        weights = weights + alpha * x.T * error  # 详细推导过程见参考      
    return weights # (3, 1)
x,y = loadDataSet()
weights = gradAscent(x, y)
print(weights)
    # [[ 4.12414349]
    #  [ 0.48007329]
    #  [-0.6168482 ]]
plotBestFit(weights)
2.3.2 随机梯度上升算法
# 随机梯度上升算法
def randgradAscent(x, y):
    m, n = np.shape(x) # 100 3
    alpha = 0.01
    weights = np.ones(n) #(3,)  [1. 1. 1.]
    for i in np.arange(m):
        h = sigmoid(np.sum(x[i] * weights))
        error = y[i] - h
        weights = weights + alpha * error * x[i]
    return (np.mat(weights)).T
x,y = loadDataSet()
weights = randgradAscent(np.array(x), np.array(y))
print(weights)
        # [[ 1.01702007]
        #  [ 0.85914348]
        #  [-0.36579921]]
# 画出决策边界
plotBestFit(weights)
2.3.3 改进的随机梯度上升算法
# 改进的随机梯度上升算法
def randgradAscent1(x, y, cycle = 150):
    m, n = np.shape(x) # 100 3
    weights = np.ones(n) # (3,) [1. 1. 1.]
    # 循环 150 次
    for j in np.arange(cycle):
        dataindex = np.arange(m) # 100
        # 在 150 次之内,每一次又循环 100 次
        for i in np.arange(m):
            # 定义学习率,随着 i,j 的增大,学习率越来越小,0.01保证学习率永远不为0
            alpha = 4 / (1.0 + j + i) + 0.01
            # 从 0 - len(dataindex) 中随机取出一个数
            randindex = int(np.random.uniform(0, len(dataindex)))
            # 预测类别的概率
            h = sigmoid(np.sum(x[randindex] * weights))
            # 误差
            error = y[randindex] - h
            # 梯度上升更新权重
            weights = weights + alpha * error * x[randindex]
            # dataindex 原来为 array ,转化为 list
            dataindex = dataindex.tolist()
            # 删除已取出的数
            del(dataindex[randindex])
            # dataindex 再次转化为 array
            dataindex = np.array(dataindex)
    # 注意,此处开始创建 weights 的shape为 (3,) ,现在把weight转化为matrix类型,再者转置,化为 (3, 1)
    return np.mat(weights).T
x,y = loadDataSet()
weights = randgradAscent1(np.array(x), np.array(y))
print(weights)
        # [[14.7744613 ]
        #  [ 0.93062723]
        #  [-2.03935598]]
# 画出决策边界
plotBestFit(weights)

3 实战: 从疝气病症预测病马的死亡率

# 预测类别
def classifyVector(x, weights):
    prob = sigmoid(np.sum(x * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0
# 读取文件
def colicTest():
    # 读取训练集
    frTrain = open(r'E:\tensorflow\Ch05\horseColicTraining.txt')
    # 读取测试集
    frTest = open(r'E:\tensorflow\Ch05\horseColicTest.txt')
    
    # 定义存放 feature 和 label 的容器
    trainingSet = []; trainingLabels = []
    # 一次取文本中的一行
    # 一行共计 22 列,前 21 列为 feature, 最后一列为 label
    for line in frTrain.readlines():
        currLine = line.strip().split('\t')
        lineArr = []
        for i in np.arange(21):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[21]))
    
    # 调用改进的随机梯度上升算法计算权重
    trainWeights = randgradAscent1(np.array(trainingSet), np.array(trainingLabels), 500)
    
    # 测试
    # 一行共计 22 列,前 21 列为 feature, 最后一列为 label
    # 测试错误计数
    errorCount = 0
    # 测试集总共样本数
    numTestVec = 0.0
    # 一次取出一行
    for line in frTest.readlines():
        # 测试集样本计数
        numTestVec += 1.0
        currLine = line.strip().split('\t')
        lineArr = []
        for i in np.arange(21):
            lineArr.append(float(currLine[i]))
        # 若预测和真实类别不符合,则错误计数加 1
        if int(classifyVector(np.array(lineArr), trainWeights) != int(currLine[21])):
            errorCount += 1
    
    # 计数这一次的错误率
    errorRate = (float(errorCount) / numTestVec)
    print("the error rate of this test is: %f" % errorRate )
    return errorRate
# 主函数
def multiTest():
    # 共计测试 10 次,即测试集读取10次,每次算出错误率,最后算出平均错误率
    numTests = 10
    # 10 次中,每一次的错误率
    errorSum = 0.0
    for k in np.arange(numTests):
        errorSum += colicTest()
    print("after %d iterations the average error rate is: %f" %(numTests, errorSum / float(numTests)))
# 开始运行,进行分类
multiTest()

参考

  • Logistic回归
  • GitHub源码

你可能感兴趣的:(第5章 Logistic回归)