逻辑回归LR(Logistic Regression)原理以及代码实践

简介

逻辑回归应该算得上是机器学习领域必须掌握的经典算法之一,并且由于其简单有效、可并行化、可解释性强等优点,至今仍然也是分类问题中最基础和最受欢迎的算法之一。尽管它的名字里面有“回归”两字,但是实际上它是用来做分类的,“逻辑”两字是其英文名字“Logistic”的音译,实际上是来自于该方法中使用的Logit函数。逻辑回归可以用于一些常见的分类问题,比如垃圾邮件过滤、网上虚假交易识别、肿瘤恶性或者良性的判断等。逻辑回归处理的问题可以简单地看成判断是“0”还是“1”,其输出是一个介于0~1之间的值,表明预测结果属于0或者1的概率。


LR的类型

LR主要可以被分成以下3种:

  • 二值逻辑回归
    预测的目标只有2种可能的输出,比如肿瘤的恶性和良性、是否是垃圾邮件等。
  • 多项式逻辑回归
    预测的目标类别有三种及其以上,并且不考虑顺序。比如,预测那种食物更受欢迎(苹果,香蕉,橘子等)。
  • 有序逻辑回归
    预测的目标类别有三种及其以上,但是顺序很重要。比如,预测用户对电影的评分,从1到5,分值越大说明越受用户喜欢。

决策边界

LR主要是用来处理分类问题,其输出是一个介于0~1之间的概率值,为了预测一个数据究竟属于哪个类别,我们可以设置一个阈值。基于这个阈值,我们就可以来对数据进行分类。下面是逻辑回归过程中决策平面的变化过程:

决策边界
决策平面有助于将模型输出的预测概率分为正负两类,比如下面这个例子:
其中黑色直线的方程为x_1+x_2=3,我们此时就可以设置阈值为3。对于红色叉形的数据,其标签为1,将其带入方程有x_1+x_2 \geqslant 3;对于蓝色圆圈的数据,带入方程有x_1+x_2 \leqslant 3。因此我们就可以说黑色的直线是我们找到的一个合理的决策边界。


线性回归和逻辑回归的区别

线性回归的输出是输入的加权和。逻辑回归并没有直接输出输入的加权和,而是通过了一个激活函数将加权和映射到0~1之间。通过下面的图例来展示他们之间的区别:

线性回归和逻辑回归的区别
可以看到,LR就是在线性回归的基础上增加了一个激活函数。这个激活函数是Sigmoid函数,其表达式为:
Sigmoid表达式
图像如下:
Sigmoid函数
可以看到Sigmoid函数可以把任意的x值压缩到y \in [0,1]之间,而概率值本来就是处于[0,1]之间的,因此把LR的输出看成预测目标属于“0”或者“1”的概率值也是说得通的。


逻辑回归的数学定义

根据上节的内容,我们知道逻辑回归就是在线性回归模型的基础上增加了激活函数,因此我们先来看下线性回归模型的定义:

它其实就是对输入向量x的各个特征赋予相应的权重,来表示各个特征的重要程度,然后再进行加权求和,得到最后的输出。它的输出是一个数值y,因此它是用来解决回归问题的模型。为了表达和计算方便,我们也可以消掉后面的常数项b,可以给输入向量x前面添加一个常数项1,同时把b添加到权重矩阵w中去,即令x' = [1, x]^T, w' = [b, w]^T,那么线性回归的表达式就可以转化为:
为了表达方便,我们还是统一使用f(x) = w^Tx这个表达式,但是读者要清楚,完整的线性回归表达式中还有常数项b的存在。
上节已经介绍了Sigmoid激活函数,LR其实就是将线性回归的表达式再放进Sigmoid激活函数中,仅此而已。于是我们就可以得到LR的完整数学表达式:
逻辑回归表达式
对于标准的逻辑回归模型而言,要确定的参数就是特征向量对应的权重向量w。一旦我们得到了权重向量w,我们只需要把输入特征向量x带入方程,得到一个处于[0,1]之间的输出概率值,然后根据提前设置好的阈值来决定输入数据的类别。比如概率值大于0.5,我们认为预测目标类别为1,反之为0。


逻辑回归的损失函数

损失函数是用来衡量模型输出与真实输出之间的差异,它可以指导我们朝着将差异最小化的方向去优化我们的模型。逻辑回归处理的问题的标签就只有"0"和"1"两种,故我们可以令预测结果为正样本(类别为1)的概率为p,预测结果为负样本(类别为0)的概率为1-p,可有如下表达式:

其中p是通过上一节介绍的LR的表达式计算出来的:

我们将预测为“1”和”0“的概率综合起来,可以写成如下形式:

其实这个转变不涉及到什么数学公式,只是为了便于计算和表示简洁而已。它表达的意思跟上面式子是一样的。当y_i=1的时候,结果依旧为p;当y_i=0时,结果为1-p

论数学的简洁性。😜

上述表达式可以表示单个样本x_i的标签为y_i的概率。假设我们的训练数据包含N个已经标记好的样本,设为x = \left\{\left(\boldsymbol{x}_{1}, y_{1}\right),\left(\boldsymbol{x}_{2}, y_{2}\right),\left(\boldsymbol{x}_{3}, y_{3}\right) \ldots\left(\boldsymbol{x}_{N}, y_{N}\right)\right\},这些都是已经发生过的事实,我们需要根据这些样本数据来估计出逻辑回归模型的参数。根据极大似然估计的原理,我们可以写出似然函数:

似然函数
这里的P_总代表的是这些事件同时发生的概率,我们的目的是让这个概率最大(因为这些事件都是已经发生的事实)。
似然函数连乘的形式不便于求导,我们对上式两侧取对数将其变成累加的形式,可以得到:
根据极大似然估计原理,我们需要F(w)越大越好,但是直觉上,这跟损失函数的定义有点违背,因为我们通常希望损失越小越好。处理方法也很简单,我们可以在前面乘以一个-1,这就将求最大值问题转换成了求最小值问题,我们再乘以1/N来计算所有样本数据的平均损失,故最终的损失函数形式为:
怎么来理解这个损失函数呢?下面给出一张图来说明:
当实际标签为1但是模型预测为0的时候,损失函数应该剧烈惩罚这种情况,反之亦然。正如从上图中看到的那样,先看蓝色曲线-log(h(x)),当h(x)接近1的时候,即模型预测为1的时候,损失接近于0;当h(x)接近0的时候,损失接近无穷大。同理,对于绿色曲线-log(1-h(x)),当实际标签为0,模型预测也为0的时候,损失为0;当模型预测为1的时候,损失趋于无穷大。总得来说,就是当模型预测值和实际值一样的时候,损失很小。否则,损失趋于无穷大。这样就可以在模型预测错误的时候,损失也很大,在误差反向传播的时候,计算出来的梯度也很大,可以使模型朝着损失减小的方向快速收敛。

注意,上图中的h(x)对应我们公式中的p,即模型的预测值。


逻辑回归的训练

得到了逻辑回归的目标函数之后,接下来我们需要求出F(w)的梯度,以便后续使用SGD等算法进行优化。在对F(w)求导之前,我们先做一些准备工作,即先对p求导,回想一下概率p的计算公式如下:

p是关于参数w的函数,通过链式求导法则,展开得:
知道了p'之后,也很容易得到(1-p)' = -p(1-p)x
下面正式对F(w)求导,求导过程如下:
我们将上式中的p展开,则得到了最终的梯度表达式如下:
在得到了梯度之后,我们就可以使用SGD来对模型的参数进行更新。核心思想是先随机初始化一个w_0,然后给定一个步长\eta,通过不断地修改参数w,从而使得损失函数的值不断降低,直到达到指定的迭代次数,或者梯度等于0为止。参数更新公式如下:

关于梯度下降算法可以参考 深入浅出--梯度下降法及其实现- 简书


两个问题

1. 为什么不用线性回归来做分类?

假设我们有一个关于肿瘤的数据集,它包含一个特征,即肿瘤大小,标签是肿瘤是否是恶性,我们可以将其画出来,如下:

注意看,所有的数据要么是0要么是1。如果我们使用线性回归的话,我们需要找到一条直线来拟合这些数据,假如图中的蓝色直线就是我们使用线性回归得到的直线。我们可以设定一个阈值为0.5,这样就线性回归同样也可以用来做分类,如下图所示:

我们在y=0.5的位置画一条线,与线性回归拟合的直线相交,再做与x轴的垂线,交点为S,如上图中黄色所示。令所有所有处于S点左侧的点为负样本,令右侧的为正样本,看起来好像也可以对样本点进行很好的分类。但是考虑训练数据中可能会有一些异常值,这些值可能会影响到最终的预测结果。还是设置阈值为0.5,假如现在多加了一个异常样本点进去训练,那么情况可能会变成下图这样:
由于多了一个异常点数据,上图中的蓝色直线是重新进行线性回归之后拟合出来的直线。绿色的点线是我们设置的决策边界。可以看到此时如果还是设置阈值为0.5的话,会将一些恶性肿瘤分成良性肿瘤,真实的决策边界应该在黄色直线所处的位置。所以,如果使用线性回归来做分类问题,一个小小的异常值就会干扰整个线性回归预测结果。

2. 为什么逻辑回归不用MSE作为损失函数?

我们知道在线性回归中,是使用的MSE(最小均方误差)来作为损失函数的。而在逻辑回归中却变成了对数损失函数,这是为什么呢?我们先写出它们各自的表达式:

  • 对数损失函数
    L(y - \hat y) = -\sum_{i=1}^ny_i log(\hat y_i)
  • MSE损失函数
    L(y - \hat y) = -\sum_{i=1}^n (y_i - \hat y_i)^2

在上述式子中,y代表真实的标签,\hat y代表模型预测的标签,n代表标签的数量,我们假设n=2,即标签要么是“0”,要么是“1”。
我们来计算一下,当真实的标签与模型预测的标签不一致时,这两种损失函数的损失值分别是多少,以及对数损失究竟比MSE好在哪里。

例子

假如我们有一个样本数据,其真实标签为“1”,模型预测的标签为“0”。
使用MSE损失函数计算出来的损失值为:
(1-0)^2=1
使用对数损失函数计算出来的损失值为:
-(1*log(0) + 0*log(1)) = tends \ to \ infinity 因为对数函数的曲线如下:

$f(x) = log(x)$
x趋于0时,log(0)趋于负无穷。
综上所述,可以看到,MSE损失函数的值与对数损失函数值相比,不值一提。因此,当真实值与模型输出值不一致的时候,对数损失函数对逻辑回归模型预测错误的惩罚力度是非常大的。
当然,在模型预测值与真实值一致的情况下,这两个损失函数计算出来的损失值都是一样的,都是0。我们可以看出,MSE对于二分类问题并不是一个很好的选择,是因为在模型分错的情况下,损失很小,惩罚力度不够。同样,在多分类情况下,即标签是通过one-hot进行编码时,MSE仍然不是一个好的选择。

在分类场景下,我们经常使用基于梯度的方法(比如拟牛顿法,梯度下降等)来最小化损失函数,从而找到参数的最优解。然后,如果损失函数是非凸的,这类方法不能保证我们找到全局最优解,相反很可能陷入局部最小值。凸函数和非凸函数如下:
凸函数和非凸函数

上图中蓝色的点便是函数的极小值点,对于右边的非凸函数,可以找到多个极小值点,这并不是我们希望的。

注意,这里的凸函数和我们直观理解上的意义相反。

我们先来了解一下什么是凸函数。先给出维基百科上关于凸函数的定义:

凸函数是具有如下特性的一个定义在某个向量空间的凸子集C(区间)上的实值函数f:对其定义域C上的任意两点x_1,x_2,总有f({\frac {x_{1}+x_{2}}{2}})\leq {\frac {f(x_{1})+f(x_{2})}{2}}

如下图所示:

可以看到函数f介于[x_1,x_2]之间的函数值均处于红色直线的下方。如果f是二阶可微的,我们可有以下结论:
如果对于所有的x,均有f''(x) \ge 0,那么f就是凸函数。因此,如果我们能够证明我们的损失函数的二阶导数始终大于等于0,那么我们就可以证明它是一个凸函数,那就意味着一定有一个全局最小值。接下来,我们从数学的角度来证明一下,MSE损失函数是非凸函数的,而对数损失函数是凸函数。为了简化计算,我们假设样本数据只有一个特征x和一个二值标签。

  • MSE损失函数
    由上述前提可得,MSE损失函数可写成:


    g(x)f(x)的一阶导数,计算可得:
    继续计算f(x)的二阶导数,如下:
    其中x^2 \hat y(1-\hat y)始终大于等于0,故为了简化,先将它们去掉,令简化后的二阶导数为H(\hat y),如下:
    我们知道y只有"0"和"1"两个取值,我们在这两种情况下检查一下MSE损失函数的二阶导数的正负情况。
    y=0时,
    \hat y\in [0, 2/3]的时候,二阶导数H(\hat y) \geqslant 0;当\hat \in [2/3,1]时,二阶导数H(\hat y) \leqslant 0,故此时MSE损失函数非凸。
    y=1时,
    \hat y\in [0, 1/3]的时候,二阶导数H(\hat y) \leqslant 0;当\hat \in [1/3,1]时,二阶导数H(\hat y) \geqslant 0,故此时MSE损失函数非凸。
    综上所述,如果逻辑回归使用MSE损失函数的话,损失函数是非凸的,模型训练的时候可能陷入局部最优解,因此是不推荐的。

  • 对数损失函数
    根据上述前提,先写成其损失函数:

    整理得:
    f(x)求一阶导数得:
    继续求二阶导得:
    image.png
    由上式可知,对于任意的xe^{-\theta x}总是大于0,f''(x)均大于等于0,故对数损失函数是一个凸函数,一定存在全局最优解。故使用拟牛顿法或者SGD等方法,一定可以找到一个参数w,使得损失函数取全局最小值。

代码实践

以下代码是《机器学习实战》中的一个例子,使用逻辑回归来预测患有疝病的马的存活率问题。代码如下:

import numpy as np
import matplotlib.pyplot as plt

class LogisticRegression():
    def __init__(self, filename, alpha=0.001, MaxCycle=500):
        self.filename = filename
        self.maxCycles = MaxCycle
        self.alpha = alpha #learning rate

    def load_data_set(self):
        dataMat = []; labelMat = []
        fr = open(self.filename, 'r')
        for line in fr.readlines():
            lineArr = line.strip().split()
            dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
            labelMat.append(int(lineArr[2]))
        return dataMat, labelMat

    def sigmoid(self, inx):
        return 1.0/(1+np.exp(-inx))

    def gradientAscent(self, dataMatIn, classLabels):
        dataMatrix = np.mat(dataMatIn)
        labelMat = np.mat(classLabels).transpose()
        m,n = np.shape(dataMatrix)
        weights = np.ones((n, 1))
        for k in range(self.maxCycles):
            h = self.sigmoid(dataMatrix*weights)
            error = labelMat - h #equals to Yi-w^TXi
            #updata weights
            weights = weights + self.alpha*dataMatrix.transpose()*error
        return weights

    def stoc_grad_ascent0(self, data_mat, class_labels):
        """
        随机梯度上升,只使用一个样本点来更新回归系数
        :param data_mat: 输入数据的数据特征(除去最后一列),ndarray
        :param class_labels: 输入数据的类别标签(最后一列数据)
        :return: 得到的最佳回归系数
        :随机梯度上升的效果不怎么好,大概错分了三分之一的样本,但是迭代的次数与m一样,即100次,而梯度下降发迭代了500次,并且没有矩阵
        :转置的运算,减少了计算量
        """
        m, n = np.shape(data_mat)
        alpha = 0.01
        weights = np.ones(n)
        for i in range(m):
            # sum(data_mat[i]*weights)为了求 f(x)的值, f(x)=a1*x1+b2*x2+..+nn*xn,
            # 此处求出的 h 是一个具体的数值,而不是一个矩阵
            h = self.sigmoid(sum(data_mat[i] * weights))
            error = class_labels[i] - h
            # 还是和上面一样,这个先去看推导,再写程序
            weights = weights + alpha * error * data_mat[i]
        return weights

    def stoc_grad_ascent1(self, data_mat, class_labels, num_iter=150):
        """
        改进版的随机梯度上升,使用随机的一个样本来更新回归系数, 为了解决随机梯度上升算法中,由于数据集并非线性可分的问题,
        在每次迭代的时候都会导致系数的剧烈变化的问题,我们期望算法能够避免来回波动,从而收敛到某个值,通过改变alpha学习率因子
        :param data_mat: 输入数据的数据特征(除去最后一列),ndarray
        :param class_labels: 输入数据的类别标签(最后一列数据
        :param num_iter: 迭代次数
        :return: 得到的最佳回归系数
        """
        m, n = np.shape(data_mat)
        weights = np.ones(n)
        for j in range(num_iter):#默认迭代次数为150
            # 这里必须要用list,不然后面的del没法使用
            data_index = list(range(m))
            for i in range(m):
                # i和j的不断增大,导致alpha的值不断减少,但是不为0
                alpha = 4 / (1.0 + j + i) + 0.01
                # 随机产生一个 0~len()之间的一个值
                # random.uniform(x, y) 方法将随机生成下一个实数,它在[x,y]范围内,x是这个范围内的最小值,y是这个范围内的最大值。
                rand_index = int(np.random.uniform(0, len(data_index)))
                h = self.sigmoid(np.sum(data_mat[data_index[rand_index]] * weights))
                error = class_labels[data_index[rand_index]] - h
                weights = weights + alpha * error * data_mat[data_index[rand_index]]
                del (data_index[rand_index]) #每次随机选一个值,然后使用完之后删掉
        return weights

    def plot_best_fit(self, weights):
        dataMat, labelMat = self.load_data_set()
        dataArr = np.array(dataMat)
        n = np.shape(dataArr)[0]
        xcord1 = []; ycord1 = []
        xcord2 = []; ycord2 = []
        for i in range(n):
            if int(labelMat[i]) == 1:
                xcord1.append(dataArr[i,1])
                ycord1.append(dataArr[i,2])
            else:
                xcord2.append(dataArr[i, 1])
                ycord2.append(dataArr[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)
        y = (-weights[0] - weights[1]*x)/weights[2]
        ax.plot(x, y)
        plt.xlabel('X1'); plt.ylabel('X2')
        plt.show()

    def colic_test(self):
        """
        打开测试集和训练集,并对数据进行格式化处理,其实最主要的的部分,比如缺失值的补充(真的需要学会的),人家已经做了
        :return:
        """
        f_train = open('./5.Logistic/HorseColicTraining.txt', 'r')
        f_test = open('./5.Logistic/HorseColicTest.txt', 'r')
        training_set = []
        training_labels = []
        # 解析训练数据集中的数据特征和Labels
        # trainingSet 中存储训练数据集的特征,trainingLabels 存储训练数据集的样本对应的分类标签
        for line in f_train.readlines():
            curr_line = line.strip().split('\t')
            if len(curr_line) == 1:
                continue  # 这里如果就一个空的元素,则跳过本次循环
            line_arr = [float(curr_line[i]) for i in range(21)] #每一行21个数据
            training_set.append(line_arr)
            training_labels.append(float(curr_line[21]))
        # 使用 改进后的 随机梯度下降算法 求得在此数据集上的最佳回归系数 trainWeights
        train_weights = self.stoc_grad_ascent1(np.array(training_set), training_labels, 500)
        error_count = 0
        num_test_vec = 0.0
        # 读取 测试数据集 进行测试,计算分类错误的样本条数和最终的错误率
        for line in f_test.readlines():
            num_test_vec += 1
            curr_line = line.strip().split('\t')
            if len(curr_line) == 1:
                continue  # 这里如果就一个空的元素,则跳过本次循环
            line_arr = [float(curr_line[i]) for i in range(21)]
            if int(self.classify_vector(np.array(line_arr), train_weights)) != int(curr_line[21]):
                error_count += 1
        error_rate = error_count / num_test_vec
        print('the error rate is {}'.format(error_rate))
        return error_rate

    def multi_test(self):
        """
        调用 colicTest() 10次并求结果的平均值
        :return: nothing
        """
        num_tests = 10
        error_sum = 0
        for k in range(num_tests):
            error_sum += self.colic_test()
        print('after {} iteration the average error rate is {}'.format(num_tests, error_sum / num_tests))

    # do the classification work
    def classify_vector(self, inx, weights):
        prob = self.sigmoid(np.sum(inx * weights))
        if prob > 0.5:
            return 1.0
        return 0.0

    def test(self, method=1):
        data_arr, class_labels = self.load_data_set()
        if method == 1:
            # 注意,这里的grad_ascent返回的是一个 matrix, 所以要使用getA方法变成ndarray类型
            weights = self.gradientAscent(data_arr, class_labels).getA()
        elif method == 2:
            weights = self.stoc_grad_ascent0(np.array(data_arr), class_labels)
        else:
            weights = self.stoc_grad_ascent1(np.array(data_arr), class_labels)
        self.plot_best_fit(weights)

if __name__ == '__main__':
    lr = LogisticRegression('./5.Logistic/TestSet.txt');
    lr.test(3)
    lr.multi_test()

以下是程序拟合出来的决策平面:

这个是使用逻辑回归进行10次存活率预测,计算每次预测的错误率:

关于代码使用的疝病数据集,可以从这里下载。https://github.com/apachecn/AiLearning/blob/master/docs/ml/5.md

上述代码是手动实现了包括梯度下降等训练方法,实际上Sklearn库为我们高度封装了这些基础算法。下面以莺尾花分类例子来展示如何在Sklearn中使用逻辑回归模型。
首先导入必要的包

# 导入必要的几个包
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_iris   
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression 

sklearn.datasets中的鸢尾花数据集一共包含4个特征变量,1个类别变量。共有150个样本,这里存储了其萼片和花瓣的长宽,共4个属性,鸢尾植物分三类,种类分别为山鸢尾、杂色鸢尾、维吉尼亚鸢尾。
莺尾花数据集信息

加载数据集并且进行划分:

# 载入数据集,Y的值有0,1,2三种情况,每种特征50个样本
iris = load_iris()         
X = iris.data[:, :2]   #获取花卉两列数据集
Y = iris.target

# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(X,Y, test_size = 0.3, random_state = 0)

构造逻辑回归模型实例,并且进行训练:

#逻辑回归模型,C=1e5表示目标函数。
lr = LogisticRegression(C=1e5)  
lr = lr.fit(X,Y)

对模型进行评估:

print("Logistic Regression模型训练集的准确率:%.3f" %lr.score(x_train, y_train))
print("Logistic Regression模型测试集的准确率:%.3f" %lr.score(x_test, y_test))

输出:

在测试集上进行测试:

from sklearn import metrics
y_hat = lr.predict(x_test)
accuracy = metrics.accuracy_score(y_test, y_hat) #错误率,也就是np.average(y_test==y_pred)
print("Logistic Regression模型正确率:%.3f" %accuracy)

结果:

对数据进行可视化:

# Plot the decision boundary. For that, we will assign a color to each
# point in the mesh [x_min, x_max]x[y_min, y_max].
x1_min, x1_max = X[:, 0].min() - .5, X[:, 0].max() + .5 # 第0列的范围
x2_min, x2_max = X[:, 1].min() - .5, X[:, 1].max() + .5 # 第1列的范围
h = .02
x1, x2 = np.meshgrid(np.arange(x1_min, x1_max, h), np.arange(x2_min, x2_max, h)) # 生成网格采样点

grid_test = np.stack((x1.flat, x2.flat), axis=1)  # 测试点
grid_hat = lr.predict(grid_test)                  # 预测分类值
# grid_hat = lr.predict(np.c_[x1.ravel(), x2.ravel()])
grid_hat = grid_hat.reshape(x1.shape) 

plt.figure(1, figsize=(6, 5))
# 预测值的显示, 输出为三个颜色区块,分布表示分类的三类区域
plt.pcolormesh(x1, x2, grid_hat,cmap=plt.cm.Paired) 

# plt.scatter(X[:, 0], X[:, 1], c=Y,edgecolors='k', cmap=plt.cm.Paired)
plt.scatter(X[:50, 0], X[:50, 1], marker = '*', edgecolors='red', label='setosa')
plt.scatter(X[50:100, 0], X[50:100, 1], marker = '+', edgecolors='k', label='versicolor')
plt.scatter(X[100:150, 0], X[100:150, 1], marker = 'o', edgecolors='k', label='virginica')
plt.xlabel('Sepal length')
plt.ylabel('Sepal width')
plt.legend(loc = 2)

plt.xlim(x1.min(), x1.max())
plt.ylim(x2.min(), x2.max())
plt.title("Logistic Regression classification result", fontsize = 15)
plt.xticks(())
plt.yticks(())
plt.grid()

plt.show()

结果:

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容