Bootstrap

深度学习入门:1.Logistic回归实现分类

目录

源码下载:

一、Logistic回归和Sigmoid函数

二、基于最优化方法的最佳回归系数确定

  1.梯度上升法

2.训练算法:使用梯度上升找到最佳参数

3.训练算法:随机梯度上升

三、示例:从疝气病症预测兵马的死亡率

1.准备数据:处理数据集中的缺失值

2 测试算法:用Logistics回归进行分类


源码下载:


https://github.com/1578630119/Logisitic_Regression

代码和数据集都有。

用Logistic回归进行分类的主要思想:根据现有数据对分类边界线建立回归公式,以此进行分类。

一、Logistic回归和Sigmoid函数

        Logistic回归

优点:计算代价不高,易于理解和实现。

缺点:容易欠拟合,分类精度不高。

适用数据类型:数值型和标称型数据。

如上第一张表格所示,每一行为一个样本,每个样本有三个数据,前两个数据x_1,x_2为该样本的特征值,后一个数据label为样本的类别。

在第二张图像是第一张表格的二维象限图,横坐标纵坐标分别代表x_1,x_2,绿色圆点代表label为0的样本,红色方框代表label为1的样本。我们需要找到一条分界线线划分两个类别,这条直线的一侧为一类,另一侧为另外一类。通过已有的数据集得到这条分界线后,当后续样本只有x_1,x_2特征,在不知道具体的label时,通过这条分界线就可以轻松得出它的label值。

假设分界线线的公式为:

z=w_0 +w_1 x_1+w_2 x_2

其中x_1,x_2为特征值,w_0,w_1,w_2为参数,而我们的目标是利用第一张表格中已有数据集,得到最佳参数w_0,w_1,w_2

Sigmoid函数:

\sigma(z)=\frac{1}{1+\mathrm{e}^{-z}}

Sigmoid函数作用:回归最终的到z可能为正、为负,不同的结果值的大小差距很大。利用Sigmoid函数可以将所有的值收缩到0~1之间,在最终分类的时候,将高于或等于0.5的结果设为一类,低于0.5的结果设置为另一类。

二、基于最优化方法的最佳回归系数确定

回归公式可以写为:

z=w_0 x_0+w_1 x_1+w_2 x_2+\cdots+w_n x_n


  1.梯度上升法

梯度上升法基于的思想是:要找到某函数的最大值,最好的方法是沿着该函数的梯度方向探寻。如果梯度记为\nabla。则函数f(x,y)的梯度由下式表示:

\nabla f(x, y)=\binom{\frac{\partial f(x, y)}{\partial x}}{\frac{\partial f(x, y)}{\partial y}}

就是在高数中的偏导数,这个梯度意味着要沿着x的方向移动\frac{\partial f(x, y)}{\partial x},沿着y的方向移动\frac{\partial f(x, y)}{\partial y},其中函数f(x,y)必须要在待计算的点上有定义并且可微。

梯度上升算法沿梯度方向移动了一步。可以看到,梯度算子总是指向函数值增长最快的方向。这里所说的是移动方向,而未提到移动量的大小。该量值称为步长,记做α。用向量来表示的话,梯度算法的迭代公式如下:

w:=w+\alpha \nabla_w f(w)

该公式将一直被迭代执行,直至达到某个停止条件为止,比如迭代次数达到某个指定值或算法达到某个可以允许的误差范围。

 注:如果就这样看书本上的这些文字还是很难理解的,需要通过下面的代码和解释进行理解Logistics回归的原理。

2.训练算法:使用梯度上升找到最佳参数

注:对应代码文件为logRegres.py。数据集对应文件为Dataset/testSet.txt。

用到的数据集格式如上所示(Dataset/testSet.txt),每一行为一个样本,每行的第一个和第二个数据代表样本的特征值(X1和X2),最后一个数据代表样本的类别,总共100行。

将txt文本中的100个样本其在二维象限中展示图如上,红色方框代表类别为1的样本,绿色圆点代表类别为0的样本。

梯度上升的伪代码如下(可以对比后续的实际代码更容易理解):

        每个回归系数初始化为1       

        重复R次:

                计算整个数据集的梯度

                使用alpha ✖ gradient更新回归系数

                返回回归系数

def load_Dataset():
    #load_Dataset是为了将testSet.txt文本中的数据提取出来,并进行预处理
    data_Mat = []
    label_Mat = []

    with open('Dataset/testSet.txt', 'r') as f:
        for line in f.readlines():
            line_Arr = line.strip().split()
            data_Mat.append([1.0, float(line_Arr[0]), float(line_Arr[1])])
      #将数据集中第一二列中的特征值放在data_Mat中,每一行都添加参数1.0,是将w0x0设置为常数w0

            label_Mat.append(int(line_Arr[2]))  #将数据集中最后一列中的标签放在label_Mat中
    return data_Mat, label_Mat


def sigmoid(z):
    #sigmodi函数 将得到的值收缩在0~1之间,方便与实际标签计算
    return 1.0 / (1 + np.exp(-z))


def grad_ascent(data_mat_in, class_labels):
    #梯度上升法
    data_matrix = np.mat(data_mat_in)
    label_mat = np.mat(class_labels).transpose()  #将得到的特征和标签转换为Numpy矩阵数据类型

    m, n = np.shape(data_matrix)   #获得特征矩阵的形状,m(m=100)行,n(n=3)列
    alpha = 0.001                  #更新梯度的步长
    max_cycles = 500               #迭代次数
    weights = np.ones((n, 1))         #w0,w1,w2初始赋值为1.0
    for k in range(max_cycles):
        h = sigmoid(data_matrix * weights)  
    #得到特征x0,x1,x2和参数w0,w1,w2相乘后,用sigmoid处理后的值
        error = (label_mat - h) #计算label矩阵和预测值矩阵的差
        weights = weights + alpha * data_matrix.transpose() * error
        #用得到的error和特征矩阵和alpha参数相乘结果作为w0,w1,w2的更新幅度
    return weights

针对于这个数据集,在代码中对应函数为:

y=w_0 x_0 +w_1 x_1+w_2 x_2

在代码的特征矩阵(data_matrix)中第二列和第三列对应x1和x2,同时也对应着testSet.txt文本中第一二列,而在特征矩阵(data_matrix)中第一列的值全是1,x0的值固定位1,则实际对应的函数应该为

y=w_0 +w_1 x_1+w_2 x_2

相当于我们在高中大学学习的函数y=ax_1+bx_2+c,这样就是w0相当于c,w0作为一个常数参数,w1,w2相当于系数。

如果大家想要更好地理解,建议手动写一次以上代码,同时在def grad_ascent(data_mat_in, class_labels)函数中将h,error,weights用print输出看一下它们的形状结构。

最终结果图展示如下:

3.训练算法:随机梯度上升

梯度上升算法中,每次更新回归系数时会一次性计算整个数据集,现在用的只是100个样本的数据集,一次性进行矩阵运算尚可,当以后遇到成千上万的数据集如果在一次将所有的数据进行运算,可能对硬件要求就太高了。一种改进方法是每次用一个或者一部分样本进行运算更新回归系数,这种方法称为随机梯度上升算法。一次处理的所有数据被称作是“批处理”。

以下是随机梯度上升算法的实现代码。

def stoc_grad_ascent0(data_matrix, class_labels):
    #随机梯度上升法
    m, n = np.shape(data_matrix)
    alpha = 0.01
    weights = np.ones(n)
    #用数据集的每个样本单独更新一次weights,总共更新100次
    for i in range(m):
        h = sigmoid(np.sum(data_matrix[i] * weights)) #每次取出一个样本计算
        error = class_labels[i] - h
        weights = weights + alpha * error * np.array(data_matrix[i])
    return weights

由代码可以看见,随机梯度上升法,每次用一个样本计算更新weights,总共更新weights次数为样本总数(100)次,仅仅遍历了一次数据集,相对于梯度上升法运行500次(遍历了500次数据集)最终的结果较差。针对这种情况需要加大随机梯度上升法的迭代次数。同时为改变在系数收敛较稳定的时候更新幅度过大,步长alpha在每次迭代时候会进行调整,在迭代初期步长较大,随着迭代次数逐渐减小,最终使weights收敛到更好。

def stoc_grad_ascent1(data_matrix, class_labels, num_iter=150):
    m, n = np.shape(data_matrix)
    weights = np.ones(n)
    for j in range(num_iter):
        data_index = list(range(m))
        for i in range(m):
            alpha = 4 / (1.0 + j + i) + 0.01 #每次迭代更新alpha
            rand_index = int(random.uniform(0, len(data_index))) 
            # 随机选择更新,减少周期性的波动
            h = sigmoid(np.sum(data_matrix[rand_index] * weights))
            error = class_labels[rand_index] - h
            weights = weights + alpha * error * np.array(data_matrix[rand_index])
            del(data_index[rand_index]) #删除已经使用过的样本
    return weights

在代码中不仅增加了迭代次数,alpha更新机制,还增加了随机选择样本,即每次迭代时,样本都是随机选择来更新系数weights。最终得到的结果如函数图像所示,分类效果更好,而且这种方法只需要遍历数据集150次。

三、示例:从疝气病症预测兵马的死亡率

本节将使用Logistic回归来预测患有疝气病的马的存活问题。这里数据包含368个样本和28个特征。

示例:使用Logisitic回归估计马疝病的死亡率

  • (1)收集数据:给定数据文件。
  • (2)准备数据:用Python解析文本文件并填充缺失值。
  • (3)分析数据:可视化并观察数据。
  • (4)训练算法:使用优化算法,找到最佳的系数
  • (5)测试算法:为了量化回归的效果,需要观察错误率。根据错误率决定是否回退到训练阶段,通过改变迭代的次数和步长等参数来得到更好的回归参数。
  • (6)使用算法:实现一个简单的命令行程序来收集马的症状并输出预测结果1并非难事,这可以作为留给读者的一道习题。

1.准备数据:处理数据集中的缺失值

在数据中有缺失值是很棘手的问题,缺失的样本在代码中无法直接运算,因系数不可能和空白相乘得到一个结果,所以必须采用一些方法来解决这个问题。

以下是一些可选的做法

  • 使用可用特征的均值来填补缺失值
  • 使用特殊值来填补缺失值,如-1;
  • 忽略有缺失值的样本
  • 使用相似样本的均值添补缺失值;
  • 使用另外的机器学习算法预测缺失值。

针对于本节使用的马的疝气病数据集,所有缺失值使用实数0来进行替换。

回归系数的更新公式如下:

weights = weights + alpha * data_matrix* error

当data_matrix中的某特征(i)因缺失而设置为0,alpha * data_matrix[i]* error结果也为0,对应的系数weights(i)不变。

针对于标签缺失的样本只能丢弃,标签丢失和特征值丢失不同,标签是很难用某个合适的值来替换。同时将标签为“已经死亡”和“已经安乐死”和并为“未能存活”,最终处理好的数据集保存为两个文件:horseColicTest.txt和ihorseColicTraining.txt。

2 测试算法:用Logistics回归进行分类

代码如下:

def colic_test():
    fr_train = open('Dataset/horseColicTraining.txt')
    fr_test = open('Dataset/horseColicTest.txt')
    training_set = []
    training_labels = []

    for line in fr_train.readlines():
        curr_line = line.strip().split('\t')
        line_arr = []
        for i in range(21):
            line_arr.append(float(curr_line[i]))        #将训练集的所有特征值保存在training_set中
        training_set.append(line_arr)                   #最后一列的标签保存在training_labels
        training_labels.append(float(curr_line[21]))

    train_weights = stoc_grad_ascent1(np.array(training_set), training_labels, 500)
    #调用随机上升梯度法,根据训练集得到各个参数weights的值

    error_count = 0
    num_test_vec = 0.0
    for line in fr_test.readlines():
        num_test_vec += 1.0
        curr_line = line.strip().split('\t')
        line_arr = []
        for i in range(21):
            line_arr.append(float(curr_line[i]))    #取出测试集的所有信息保存在line_arr

        if int(classify_vector(np.array(line_arr), train_weights)) != int(curr_line[21]):
        #使用得到的参数weights与测试样本的特征相乘得到预测结果,预测结果和实际标签相比较
            error_count += 1    #统计预测错误数目

    error_rate = float(error_count) / num_test_vec #计算错误率
    print(f"the error rate of this test is {error_rate}")
    return error_rate


def multi_test():
    num_tests = 10
    error_sum = 0.0
    for k in range(num_tests):
        error_sum += colic_test()
    #平均num_tests次预测结果的错误率
    print(f"after {num_tests} iterations the average error rate is {error_sum/float(num_tests)}")

由测试结果看来错误率达到30%多,但针对于这个有缺失值的数据集,这个结果已很不错了。

完成代码如下:

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import random
matplotlib.use('TkAgg')


def load_Dataset():
    #load_Dataset是为了将testSet.txt文本中的数据提取出来,并进行预处理
    data_Mat = []
    label_Mat = []

    with open('Dataset/testSet.txt', 'r') as f:
        for line in f.readlines():
            line_Arr = line.strip().split()
            data_Mat.append([1.0, float(line_Arr[0]), float(line_Arr[1])])
            #将数据集中第一二列中的特征值放在data_Mat中,每一行都添加参数1.0,是将w0x0设置为常数w0
            label_Mat.append(int(line_Arr[2]))  #将数据集中第一二列中的特征值放在label_Mat中
    return data_Mat, label_Mat


def sigmoid(z):
    #sigmodi函数 将得到的值收缩在0~1之间,方便与实际标签计算
    return 1.0 / (1 + np.exp(-z))


def grad_ascent(data_mat_in, class_labels):
    #梯度上升法
    data_matrix = np.mat(data_mat_in)
    label_mat = np.mat(class_labels).transpose()        #将得到的特征和标签转换为Numpy矩阵数据类型

    m, n = np.shape(data_matrix)   #获得特征矩阵的形状,m(m=100)行,n(n=3)列
    alpha = 0.001                  #更新梯度的步长
    max_cycles = 500               #迭代次数
    weights = np.ones((n, 1))         #w0,w1,w2初始赋值为1.0
    for k in range(max_cycles):
        h = sigmoid(data_matrix * weights)  #得到特征x0,x1,x2和参数w0,w1,w2相乘后,用sigmoid处理后的值
        error = (label_mat - h) #计算label矩阵和预测值矩阵的差
        weights = weights + alpha * data_matrix.transpose() * error
        #用得到的error和特征矩阵和alpha参数相乘结果作为w0,w1,w2的更新幅度
    return weights


def plot_best_fit(weights):

    data_mat, label_mat = load_Dataset()
    data_arr = np.array(data_mat)
    n = np.shape(data_arr)[0]

    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    for i in range(n):
        if int(label_mat[i]) == 1:
            xcord1.append(data_arr[i, 1])
            ycord1.append(data_arr[i, 2])
        else:
            xcord2.append(data_arr[i, 1])
            ycord2.append(data_arr[i, 2])

    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
    ax.scatter(xcord2, ycord2, s=30, c='green')

    # 最佳拟合直线
    x = np.arange(-3.0, 3.0, 0.1)
    print(weights)
    y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)

    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()


def stoc_grad_ascent0(data_matrix, class_labels):
    #随机梯度上升法
    m, n = np.shape(data_matrix)
    alpha = 0.01
    weights = np.ones(n)
    #用数据集的每个样本单独更新一次weights,总共更新100次
    for i in range(m):
        h = sigmoid(np.sum(data_matrix[i] * weights)) #每次取出一个样本计算
        error = class_labels[i] - h
        weights = weights + alpha * error * np.array(data_matrix[i])
    return weights

def stoc_grad_ascent1(data_matrix, class_labels, num_iter=150):
    m, n = np.shape(data_matrix)
    weights = np.ones(n)
    for j in range(num_iter):
        data_index = list(range(m))
        for i in range(m):
            alpha = 4 / (1.0 + j + i) + 0.01 #每次迭代更新alpha
            rand_index = int(random.uniform(0, len(data_index))) # 随机选择更新,减少周期性的波动
            h = sigmoid(np.sum(data_matrix[rand_index] * weights))
            error = class_labels[rand_index] - h
            weights = weights + alpha * error * np.array(data_matrix[rand_index])
            del(data_index[rand_index]) #删除已经使用过的样本
    return weights


def classify_vector(in_x, weights):
    prob = sigmoid(np.sum(in_x * weights))

    if prob > 0.5:
        return 1.0
    else:
        return 0.0


def colic_test():
    fr_train = open('Dataset/horseColicTraining.txt')
    fr_test = open('Dataset/horseColicTest.txt')
    training_set = []
    training_labels = []

    for line in fr_train.readlines():
        curr_line = line.strip().split('\t')
        line_arr = []
        for i in range(21):
            line_arr.append(float(curr_line[i]))        #将训练集的所有特征值保存在training_set中
        training_set.append(line_arr)                   #最后一列的标签保存在training_labels
        training_labels.append(float(curr_line[21]))

    train_weights = stoc_grad_ascent1(np.array(training_set), training_labels, 500)
    #调用随机上升梯度法,根据训练集得到各个参数weights的值

    error_count = 0
    num_test_vec = 0.0
    for line in fr_test.readlines():
        num_test_vec += 1.0
        curr_line = line.strip().split('\t')
        line_arr = []
        for i in range(21):
            line_arr.append(float(curr_line[i]))    #取出测试集的所有信息保存在line_arr

        if int(classify_vector(np.array(line_arr), train_weights)) != int(curr_line[21]):
            #使用得到的参数weights与测试样本的特征相乘得到预测结果,预测结果和实际标签相比较
            error_count += 1    #统计预测错误数目

    error_rate = float(error_count) / num_test_vec #计算错误率
    print(f"the error rate of this test is {error_rate}")
    return error_rate


def multi_test():
    num_tests = 10
    error_sum = 0.0
    for k in range(num_tests):
        error_sum += colic_test()
    #平均num_tests次预测结果的错误率
    print(f"after {num_tests} iterations the average error rate is {error_sum/float(num_tests)}")


if __name__ == "__main__":
    # 1. 测试
    data_arr, label_mat = load_Dataset()
    result = stoc_grad_ascent1(data_arr, label_mat)
    print(result)
    plot_best_fit(result)

    # 2. 从疝气病预测病马的死亡率
    multi_test()

;