美文网首页
机器学习基础-如何评价分类结果的优劣?

机器学习基础-如何评价分类结果的优劣?

作者: 从来只看自己_7faa | 来源:发表于2019-11-24 15:10 被阅读0次

    How to measure the quality of classification results?

    在刚接触机器学习的时候,我们可以自己动手完成一个简单的KNN算法,但是我们不禁会疑惑,它的效果怎样?在机器学习中如何评价一个算法的好坏?我们在机器学习过程中还有需要注意那些其他的问题呢?

    我们仍然以一个著名的鸢尾花数据集来学习机器学习中判断模型的有些的各种指标。

    一、数据准备

    对于算法优劣的度量,通常的做法是将原始数据中的一部分作为训练数据、另一部分作为测试数据。使用训练数据训练模型,再用测试数据对模型进行验证。

    1.1、导入鸢尾花数据集

    import numpy as np
    from sklearn import datasets
    import matplotlib.pyplot as plt
    import pandas as pd
    
    iris = datasets.load_iris()
    X = iris.data
    y = iris.target
    
    X.shape
    y.shape
    
    (150,)
    

    可以看出数据集保存在X中,分类的结果保存在了y中

    1.2、拆分数据

    一般情况下我们按照0.8:0.2的比例进行拆分,但是有时候我们不能简单地把前n个数据作为训练数据集,后n个作为测试数据集,比如鸢尾花数据集中的数据是有序排列的,如下:

    y
    
    array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
           0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
           1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
           2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
           2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
    

    从以上的数据可知,要想使拆分数据集变得有意义,为了解决这个问题,我们可以将数据集打乱,做一个shuffle操作。但是本数据集的特征和标签是分开的,也就是说我们分别乱序后,原来的对应关系就不存在了。有两种方法解决这一问题:

    1.将X和y合并为同一个矩阵,然后对矩阵进行shuffle,之后再分解
    
    2.对y的索引进行乱序,根据索引确定与X的对应关系,最后再通过乱序的索引进行赋值
    

    为了巩固基础,下面我们自己动手来完成数据集的拆分

    第一种方式

    # 使用concatenate函数进行拼接,因为传入的矩阵必须具有相同的形状。
    # 因此需要对label进行reshape操作,reshape(-1,1)表示行数自动计算,1列。
    # axis=1表示纵向拼接。
    tempConcat = np.concatenate((X, y.reshape(-1,1)), axis=1)
    # 拼接好后,直接进行乱序操作
    np.random.shuffle(tempConcat)
    # 再将shuffle后的数组使用split方法拆分
    shuffle_X,shuffle_y = np.split(tempConcat, [4], axis=1)
    # 设置划分的比例
    test_ratio = 0.2
    test_size = int(len(X) * test_ratio)
    X_train = shuffle_X[test_size:]
    y_train = shuffle_y[test_size:]
    X_test = shuffle_X[:test_size]
    y_test = shuffle_y[:test_size]
    

    第二种方式

    # 将x长度这么多的数,返回一个新的打乱顺序的数组,注意,数组中的元素不是原来的数据,而是混乱的索引
    shuffle_index = np.random.permutation(len(X))
    # 指定测试数据的比例
    test_ratio = 0.2
    test_size = int(len(X) * test_ratio)
    test_index = shuffle_index[:test_size]
    train_index = shuffle_index[test_size:]
    X_train = X[train_index]
    X_test = X[test_index]
    y_train = y[train_index]
    y_test = y[test_index]
    

    将拆分方法封装成与sklearn中同名的函数

    import numpy as np
    
    def train_test_split_temp(X, y, test_ratio=0.2, seed=None):
        """将矩阵X和标签y按照test_ration分割成X_train, X_test, y_train, y_test"""
        assert X.shape[0] == y.shape[0],         "the size of X must be equal to the size of y"
        assert 0.0 <= test_ratio <= 1.0,         "test_train must be valid"
    
        if seed:    # 是否使用随机种子,使随机结果相同,方便debug
            np.random.seed(seed)    # permutation(n) 可直接生成一个随机排列的数组,含有n个元素
        shuffle_index = np.random.permutation(len(X))
    
        test_size = int(len(X) * test_ratio)
        test_index = shuffle_index[:test_size]
        train_index = shuffle_index[test_size:]
        X_train = X[train_index]
        X_test = X[test_index]
        y_train = y[train_index]
        y_test = y[test_index]    
        return X_train, X_test, y_train, y_test
    
    X_train, X_test, y_train, y_test = train_test_split_temp(X, y)
    

    sklearn中的train_test_split

    我们自己写的train_test_split其实也是在模仿sklearn风格,更多的时候我们可以直接调用。

    from sklearn.model_selection import train_test_split
    
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=666)
    
    print(X_train.shape)
    print(X_test.shape)
    print(y_train.shape)
    print(y_test.shape)
    
    (120, 4)
    (30, 4)
    (120,)
    (30,)
    
    from sklearn.neighbors import KNeighborsClassifier
    # 创建kNN_classifier实例
    kNN_classifier = KNeighborsClassifier(n_neighbors=3)
    # kNN_classifier做一遍fit(拟合)的过程,没有返回值,
    # 模型就存储在kNN_classifier实例中
    kNN_classifier.fit(X_train, y_train)
    
    # kNN进行预测predict,需要传入一个矩阵,而不能是一个数组。reshape()成一个二维数组,第一个参数是1表示只有一个数据,第二个参数-1,numpy自动决定第二维度有多少
    y_predict = kNN_classifier.predict(X_test)
    y_predict
    
    array([1, 2, 1, 2, 0, 1, 1, 2, 1, 1, 1, 0, 0, 0, 2, 1, 0, 2, 2, 2, 1, 0,
           2, 0, 1, 1, 0, 1, 2, 2])
    

    二、分类准确度

    在划分出测试数据集后,我们就可以验证其模型准确率了。在这了引出一个非常简单且常用的概念:accuracy(分类准确度)

    accuracy_score:函数计算分类准确率,返回被正确分类的样本比例(default)或者是数量(normalize=False)
    在多标签分类问题中,该函数返回子集的准确率,对于一个给定的多标签样本,如果预测得到的标签集合与该样本真正的标签集合严格吻合,则subset accuracy =1.0否则是0.0

    因accuracy定义清洗、计算方法简单,因此经常被使用。但是它在某些情况下并不一定是评估模型的最佳工具。精度(查准率)和召回率(查全率)等指标对衡量机器学习的模型性能在某些场合下要比accuracy更好。

    当然这些指标在后续都会介绍。在这里我们就使用分类精准度,并将其作用于一个新的手写数字识别分类算法上。

    对于上面的鸢尾花分类,准确度=分类正确的数量/全部测试样本的数量

    print(sum(y_test == y_predict)/len(y_test))
    
    1.0
    

    再通过一个例子来巩固一下

    2.1、数据探索

    import numpy as np
    import matplotlib
    import matplotlib.pyplot as plt
    from sklearn import datasets
    from sklearn.model_selection import train_test_split
    from sklearn.neighbors import KNeighborsClassifier
    # 手写数字数据集,封装好的对象,可以理解为一个字段
    digits = datasets.load_digits()
    # 可以使用keys()方法来看一下数据集的详情
    digits.keys()
    
    
    dict_keys(['data', 'target', 'target_names', 'images', 'DESCR'])
    

    我们可以看一下sklearn.datasets提供的数据描述:

    5620张图片,每张图片有64个像素点即特征(8*8整数像素图像),每个特征的取值范围是1~16(sklearn中的不全),对应的分类结果是10个数字print(digits.DESCR)

    下面我们根据datasets提供的方法,进行简单的数据探索。

    # 特征的shape
    X = digits.data
    X.shape
    
    
    (1797, 64)
    
    # 标签的shape
    y = digits.target
    y.shape
    
    (1797,)
    
    # # 标签分类
    digits.target_names
    
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    
    # # 去除某一个具体的数据,查看其特征以及标签信息
    some_digit = X[0]
    some_digit
    
    array([ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.,  0.,  0., 13., 15., 10.,
           15.,  5.,  0.,  0.,  3., 15.,  2.,  0., 11.,  8.,  0.,  0.,  4.,
           12.,  0.,  0.,  8.,  8.,  0.,  0.,  5.,  8.,  0.,  0.,  9.,  8.,
            0.,  0.,  4., 11.,  0.,  1., 12.,  7.,  0.,  0.,  2., 14.,  5.,
           10., 12.,  0.,  0.,  0.,  0.,  6., 13., 10.,  0.,  0.,  0.])
    
    # # 也可以这条数据进行可视化
    some_digmit_image = some_digit.reshape(8, 8)
    plt.imshow(some_digmit_image, cmap = matplotlib.cm.binary)
    plt.show()
    

    2.2、自己实现分类准确度

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=666)
    print(X_train.shape)
    print(X_test.shape)
    print(y_train.shape)
    print(y_test.shape)
    
    (1437, 64)
    (360, 64)
    (1437,)
    (360,)
    
    np.ceil(np.log2(X.shape[0]))
    
    11.0
    
    knn = KNeighborsClassifier(n_neighbors=11)
    knn.fit(X_train, y_train)
    y_predict = knn.predict(X_test)
    print(sum(y_test == y_predict)/len(y_test))
    
    0.9861111111111112
    

    2.3、sklearn中的准确度

    from sklearn.metrics import accuracy_score
    
    accuracy_score(y_test, y_predict)
    
    0.9861111111111112
    

    三、混淆矩阵

    讨论混淆矩阵之前,我们先思考这样一个问题:

    对于一个癌症预测系统,输入检查指标,判断是否患有癌症,预测准确度99.9%。这个系统是好是坏呢?

    如果癌症产生的概率是0.1%,那其实根本不需要任何机器学习算法,只要系统预测所有人都是健康的,即可达到99.9%的准确率。也就是说对于极度偏斜(Skewed Data)的数据,只使用分类准确度是不能衡量。

    这是就需要使用混淆矩阵(Confusion Matrix)做进一步分析。

    3.1、什么是混淆矩阵?

    对于二分类问题来说,所有的问题被分为0和1两类,混淆矩阵是2*2的矩阵:

    预测值0 预测值1
    真实值0 TN FP
    真实值1 FN TP
    • TN:真实值是0,预测值也是0,即我们预测是negative,预测正确了。
    • FP:真实值是0,预测值是1,即我们预测是positive,但是预测错误了。
    • FN:真实值是1,预测值是0,即我们预测是negative,但预测错误了。
    • TP:真实值是1,预测值是1,即我们预测是positive,预测正确了。

    现在假设有1万人进行预测,填入混淆矩阵如下:

    预测值0 预测值1
    真实值0 9978 12
    真实值1 2 8

    对于1万个人中,有9978个人本身并没有癌症,我们的算法也判断他没有癌症;有12个人本身没有癌症,但是我们的算法却错误地预测他有癌症;有2个人确实有癌症,但我们算法预测他没有癌症;有8个人确实有癌症,而且我们也预测对了。

    因为混淆矩阵表达的信息比简单的分类准确度更全面,因此可以通过混淆矩阵得到一些有效的指标。

    3.2、混淆矩阵的代码实现

    实现一个逻辑回归算法

    import numpy as np
    from sklearn import datasets
    from sklearn.model_selection import train_test_split
    from sklearn.linear_model import LogisticRegression
    
    digits = datasets.load_digits()
    X = digits.data
    y = digits.target.copy()
    
    # 要构造偏斜数据,将数字9的对应索引的元素设置为1,0~8设置为0
    y[digits.target==9]=1
    y[digits.target!=9]=0
    
    # 使用逻辑回归做一个分类
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
    
    log_reg = LogisticRegression()
    log_reg.fit(X_train,y_train)
    # 得到X_test所对应的预测值
    y_log_predict = log_reg.predict(X_test)
    log_reg.score(X_test, y_test)
    
    
    0.9755555555555555
    

    定义混淆矩阵的四个指标:TN

    def TN(y_true, y_predict):
        assert len(y_true) == len(y_predict)
        # (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
        # 向量与向量按位与,结果还是布尔向量
        # np.sum 计算布尔向量中True的个数(True记为1,False记为0)
        return np.sum((y_true == 0) & (y_predict == 0))  
        # 向量与向量按位与,结果还是向量
    TN(y_test, y_log_predict)
    
    403
    

    定义混淆矩阵的四个指标:FP

    def FP(y_true, y_predict):
        assert len(y_true) == len(y_predict)
        # (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
        # 向量与向量按位与,结果还是布尔向量
        # np.sum 计算布尔向量中True的个数(True记为1,False记为0)
        return np.sum((y_true == 0) & (y_predict == 1))  # 向量与向量按位与,结果还是向量
    FP(y_test, y_log_predict)
    
    2
    

    定义混淆矩阵的四个指标:FN

    def FN(y_true, y_predict):
        assert len(y_true) == len(y_predict)
        # (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
        # 向量与向量按位与,结果还是布尔向量
        # np.sum 计算布尔向量中True的个数(True记为1,False记为0)
        return np.sum((y_true == 1) & (y_predict == 0))  # 向量与向量按位与,结果还是向量
    FN(y_test, y_log_predict)
    
    9
    

    定义混淆矩阵的四个指标:TP

    def TP(y_true, y_predict):
        assert len(y_true) == len(y_predict)
        # (y_true == 0):向量与数值按位比较,得到的是一个布尔向量
        # 向量与向量按位与,结果还是布尔向量
        # np.sum 计算布尔向量中True的个数(True记为1,False记为0)
        return np.sum((y_true == 1) & (y_predict == 1))  # 向量与向量按位与,结果还是向量
    TP(y_test, y_log_predict)
    
    36
    

    输出混淆矩阵

    import pandas as pd
    def confusion_matrix(y_true, y_predict):
        return pd.DataFrame(np.array([
            [TN(y_true, y_predict), FP(y_true, y_predict)],
            [FN(y_true, y_predict), TP(y_true, y_predict)]]) 
                            ,index=['实际值0', '实际值1'] 
                            ,columns=["预测值0", '预测值1'])
    
    confusion_matrix(y_test, y_log_predict)
    # pd.DataFrame(confusion_matrix(y_test, y_log_predict)
    #              ,index=['实际值0', '实际值1']
    #              ,columns=["预测值0", '预测值1'])
    
    pandas输出混淆矩阵

    3.3、scikit-learn中的混淆矩阵

    from sklearn.metrics import confusion_matrix
    
    # cm = confusion_matrix(y_test, y_log_predict)
    
    cm = pd.crosstab(y_log_predict,y_test)
    # cm
    
    # 导入第三方模块
    import seaborn as sns
    
    # 将混淆矩阵构造成数据框,并加上字段名和行名称,用于行或列的含义说明
    cm = pd.DataFrame(cm)
    # 绘制热力图
    sns.heatmap(cm, annot = True,cmap = 'GnBu', fmt='g')
    # 添加x轴和y轴的标签
    plt.xlabel(' Real Lable')
    plt.ylabel(' Predict Lable')
    # 图形显示
    
    Text(33,0.5,' Predict Lable')
    
    seaborn构建的混淆矩阵

    四、精准率与召回率

    精准率:precision= \frac{TP}{TP+FP}

    即精准率为8/(8+12)=40%。所谓的精准率是:分母为所有预测为1的个数,分子是其中预测对了的个数,即预测值为1,且预测对了的比例

    为什么管它叫精准率呢?在有偏的数据中,我们通常更关注值为1的特征,比如“患病”,比如“有风险”。在100次结果为患病的预测,平均有40次预测是对的。即精准率为我们关注的那个事件,预测的有多准

    召回率:recall= \frac{TP}{TP+FN}

    即精准率为8/(8+2)=80%。所谓召回率是:所有真实值为1的数据中,预测对了的个数。每当有100个癌症患者,算法可以成功的预测出8个 。也就是我们关注的那个事件真实的发生情况下,我们成功预测的比例是多少

    那么为什么需要精准率和召回率呢?还是下面的这个例子,有10000个人,混淆矩阵如下:

    预测值0 预测值1
    真实值0 9978 12
    真实值1 2 8

    如果我们粗暴的认为所有人都是健康的,那算法的准确率是99.78%,但这是毫无意义的。如果算精准率则是40%,召回率是80%。

    4.1、精准率的代码实现

    def precision_score(y_true, y_predict):
        tp = TP(y_true, y_predict)
        fp = FP(y_true, y_predict)
        try:
            return tp / (tp + fp)
        except:
            return 0.0
    precision_score(y_test, y_log_predict)
    
    0.9473684210526315
    

    4.2、召回率的代码实现

    def recall_score(y_true, y_predict):
        tp = TP(y_true, y_predict)
        fn = FN(y_true, y_predict)
        try:
            return tp / (tp + fn)
        except:
            return 0.0
    recall_score(y_test, y_log_predict)
    
    0.8
    

    4.3、scikit-learn中的精准率与召回率

    from sklearn.metrics import precision_score
    
    precision_score(y_test, y_log_predict)
    
    0.9473684210526315
    
    from sklearn.metrics import recall_score
    
    recall_score(y_test, y_log_predict)
    
    0.8
    

    4.4、精准率与召回率的关系与侧重点

    • 精准率(查准率):预测值为1,且预测对了的比例,即:我们关注的那个事件,预测的有多准。

    • 召回率(查全率):所有真实值为1的数据中,预测对了的个数,即:我们关注的那个事件真实的发生情况下,我们成功预测的比例是多少。

    有的时候,对于一个算法而言,精准率与召回率之间呈现负相关的关系。精准率高一些,召回率就低一些;或者召回率高一些,精准率就低一些。那么如何取舍呢?

    其实在衡量机器学习的其他指标中,我们也需要进行取舍,通常只需要把握一个原则:

    视场景而定。

    比如我们做了一个股票预测系统,未来股票是📈还是📉这样一个二分类问题。很显然“涨”才是我们关注的焦点,那么我们肯定希望:系统预测上涨的股票中,真正上涨的比例越大越好,这就是希望查准率高。那么我们是否关注查全率呢?在大盘中有太多的真实上涨股票,虽然我们漏掉了一些上升周期,但是我们没有买进,也就没有损失。但是如果查准率不高,预测上涨的结果下跌了,那就是实实在在的亏钱了。所以在这个场景中,查准率更重要。

    当然也有追求召回率的场景,在医疗领域做疾病诊断,如果召回率低,意味着本来有一个病人得病了,但是没有正确预测出来,病情就恶化了。我们希望尽可能地将所有有病的患者都预测出来,而不是在看在预测有病的样例中有多准。

    五、F1 Score

    5.1、F1 Score 的定义

    在实际业务场景中,也有很多没有这么明显的选择。那么在同时需要关注精准率和召回率,如何在两个指标中取得平衡呢?在这种情况下,我们使用一种新的指标:F1 Score。

    如果要我们综合精准率和召回率这两个指标,我们可能会想到取平均值这样的方法。F1 Score的思想也差不多:

    F1 Score 是精准率和召回率的调和平均值。

    F1= \frac{2*precision*recall}{precision+recall}

    什么是调和平均值?为什么要取调和平均值?调和平均值的特点是如果二者极度不平衡,如某一个值特别高、另一个值特别低时,得到的F1 Score值也特别低;只有二者都非常高,F1才会高。这样才符合我们对精准率和召回率的衡量标准。

    \frac{1}{F1}= \frac{1}{2}(\frac{1}{precision} + \frac{1}{recall})

    5.2、代码演示

    def f1_score(precision, recall):
        try:
            return 2 * precision * recall / (precision + recall)
        except:
            return 0.0
    
    precision = 0.5
    recall = 0.5
    f1_score(precision, recall)
    
    0.5
    

    假设精准率和召回率同时为0.5,则二者的算数平均值为0.5,计算F1 Score:

    precision = 0.5
    recall = 0.5
    f1_score(precision, recall)
    
    0.5
    

    假设精准率为0.9,召回率同时为0.1,则二者的算数平均值为0.5,计算F1 Score:

    precision = 0.5
    recall = 0.5
    f1_score(precision, recall)
    
    0.5
    

    六、ROC曲线

    6.1、分类阈值、TPR和FPR

    在了解ROC曲线之前,先看三个概念:分类阈值、TPR和FPR

    6.1.1、分类阈值

    分类阈值,即设置判断样本为正例的阈值threshold,

    如果某个逻辑回归模型对某封电子邮件进行预测时返回的概率为 0.9995,则表示该模型预测这封邮件非常可能是垃圾邮件。相反,在同一个逻辑回归模型中预测分数为 0.0003 的另一封电子邮件很可能不是垃圾邮件。可如果某封电子邮件的预测分数为 0.6 呢?为了将逻辑回归值映射到二元类别,您必须指定分类阈值(也称为判定阈值)。如果值高于该阈值,则表示“垃圾邮件”;如果值低于该阈值,则表示“非垃圾邮件”。人们往往会认为分类阈值应始终为 0.5,但阈值取决于具体问题,因此必须对其进行调整。

    在sklearn中有一个方法叫:decision_function,即返回分类阈值

    decision_scores = log_reg.decision_function(X_test)
    y_predict = np.array(decision_scores >= 5, dtype='int')
    # decision_scores
    # X_test
    
    array([[ 0.,  0.,  4., ...,  3.,  0.,  0.],
           [ 0.,  0.,  4., ...,  2.,  0.,  0.],
           [ 0.,  2., 11., ..., 10.,  0.,  0.],
           ...,
           [ 0.,  0.,  1., ...,  0.,  0.,  0.],
           [ 0.,  0.,  0., ...,  0.,  0.,  0.],
           [ 0.,  0., 12., ...,  8.,  0.,  0.]])
    

    我们知道,精准率和召回率这两个指标有内在的联系,并且相互冲突。precision随着threshold的增加而降低,recall随着threshold的增大而减小。如果某些场景需要precision,recall都保持在80%,可以通过这种方式求出threshold

    6.1.2、TPR

    预测值0 预测值1
    真实值0 TN FP
    真实值1 FN TP

    TPR:预测为1,且预测对了的数量,占真实值为1的数据百分比。很好理解,就是召回率。
    TPR = recall= \frac{TP}{TP+FN}

    6.1.3、FPR

    FPR:预测为1,但预测错了的数量,占真实值不为1的数据百分比。与TPR相对应,FPR除以真实值为0的这一行所有的数字和 。
    FPR = \frac{FP}{TN+FP}

    预测值0 预测值1
    真实值0 9978 12
    真实值1 2 8

    TPR和FPR之间是成正比的,TPR高,FPR也高。ROC曲线就是刻画这两个指标之间的关系。

    6.2、 什么是ROC曲线

    ROC曲线(Receiver Operation Characteristic Cureve),描述TPR和FPR之间的关系。x轴是FPR,y轴是TPR。

    我们已经知道,TPR就是所有正例中,有多少被正确地判定为正;FPR是所有负例中,有多少被错误地判定为正。 分类阈值取不同值,TPR和FPR的计算结果也不同,最理想情况下,我们希望所有正例 & 负例 都被成功预测 TPR=1,FPR=0,即 所有的正例预测值 > 所有的负例预测值,此时阈值取最小正例预测值与最大负例预测值之间的值即可。

    TPR越大越好,FPR越小越好,但这两个指标通常是矛盾的。为了增大TPR,可以预测更多的样本为正例,与此同时也增加了更多负例被误判为正例的情况。

    6.2.1、代码实现

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import datasets
    from sklearn.model_selection import train_test_split
    from sklearn.linear_model import LogisticRegression
    
    digits = datasets.load_digits()
    X = digits.data
    y = digits.target.copy()
    
    # 要构造偏斜数据,将数字9的对应索引的元素设置为1,0~8设置为0
    y[digits.target==9]=1
    y[digits.target!=9]=0
    
    # 使用逻辑回归做一个分类
    X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
    
    log_reg = LogisticRegression()
    log_reg.fit(X_train,y_train)
    # 计算逻辑回归给予X_test样本的决策数据值
    # 通过decision_function可以调整精准率和召回率
    decision_scores = log_reg.decision_function(X_test)
    # print(decision_scores)
    # TPR
    def TPR(y_true, y_predict):
        tp = TP(y_true, y_predict)
        fn = FN(y_true, y_predict)
        try:
            return tp / (tp + fn)
        except:
            return 0.0
    
    # FPR
    def FPR(y_true, y_predict):
        fp = FP(y_true, y_predict)
        tn = TN(y_true, y_predict)
        try:
            return fp / (fp + tn)
        except:
            return 0.0
    
    fprs = []    
    tprs = []
    
    # 以0.1为步长,遍历decision_scores中的最小值到最大值的所有数据点,将其作为阈值集合
    thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)
    print(thresholds, len(thresholds))
    for threshold in thresholds:
        # decision_scores >= threshold 是布尔型向量,用dtype设置为int
        # 大于等于阈值threshold分类为1,小于为0,用这种方法得到预测值
        y_predict = np.array(decision_scores >= threshold, dtype=int)
        #print(y_predict)
        # print(y_test)
        #print(FPR(y_test, y_predict))
        # 对于每个阈值,所得到的FPR和TPR都添加到相应的队列中
        fprs.append(FPR(y_test, y_predict))
        tprs.append(TPR(y_test, y_predict))
        
    # 绘制ROC曲线,x轴是fpr的值,y轴是tpr的值
    plt.plot(fprs, tprs)
    plt.show()
    
    [-85.68608523 -85.58608523 -85.48608523 ...  19.61391477  19.71391477
      19.81391477] 1056
    

    可以看到曲线每次都是一个“爬坡”,遇到正例往上爬一格,错了往右爬一格,显然往上爬对于算法性能来说是最好的。

    sklearn中的ROC曲线:

    from sklearn.metrics import roc_curve
    
    fprs, tprs, thresholds = roc_curve(y_test, decision_scores)
    plt.plot(fprs, tprs)
    plt.show()
    

    6.2.2 分析

    ROC曲线距离左上角越近,证明分类器效果越好。如果一条算法1的ROC曲线完全包含算法2,则可以断定性能算法1>算法2。这很好理解,此时任做一条 横线(纵线),任意相同TPR(FPR) 时,算法1的FPR更低(TPR更高),故显然更优。

    从上面ROC图中的几个标记点,我们可以做一些直观分析:

    我们可以看出,左上角的点(TPR=1,FPR=0),为完美分类,也就是这个医生医术高明,诊断全对。点A(TPR>FPR),说明医生A的判断大体是正确的。中线上的点B(TPR=FPR),也就是医生B全都是蒙的,蒙对一半,蒙错一半;下半平面的点C(TPR<FPR),这个医生说你有病,那么你很可能没有病,医生C的话我们要反着听,为真庸医。

    很多时候两个分类器的ROC曲线交叉,无法判断哪个分类器性能更好,这时可以计算曲线下的面积AUC,作为性能度量。

    七、AUC

    一般在ROC曲线中,我们关注是曲线下面的面积, 称为AUC(Area Under Curve)。这个AUC是横轴范围(0,1 ),纵轴是(0,1)所以总面积是小于1的。

    ROC和AUC的主要应用:比较两个模型哪个好?主要通过AUC能够直观看出来。

    ROC曲线下方由梯形组成,矩形可以看成特征的梯形。因此,AUC的面积可以这样算:(上底+下底)* 高 / 2,曲线下面的面积可以由多个梯形面积叠加得到。AUC越大,分类器分类效果越好。

    • AUC = 1,是完美分类器,采用这个预测模型时,不管设定什么阈值都能得出完美预测。绝大多数预测的场合,不存在完美分类器。
    • 0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。
    • AUC = 0.5,跟随机猜测一样,模型没有预测价值。
    • AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。

    可以在sklearn中求出AUC值

    from sklearn.metrics import roc_auc_score
    roc_auc_score(y_test, decision_scores)
    
    0.9830452674897119
    

    八、总结

    相关文章

      网友评论

          本文标题:机器学习基础-如何评价分类结果的优劣?

          本文链接:https://www.haomeiwen.com/subject/gmpzictx.html