分类模型评价指标说明
分类模型评价指标说明
分类涉及到的指标特别容易搞混,不是这个率就是那个率,最后都分不清谁是谁,这份文档就是为此给大家梳理一下。
文章目录
- 分类模型评价指标说明
- 混淆矩阵
- 例子
- 混淆矩阵定义
- 混淆矩阵代码
- 正确率
- 真阳率和假阳率
- 真阳率
- 假阳率
- 真阳率和假阳率的公式比较
- ROC/AUC
- 例子
- 阈值对TPR和FPR的影响
- ROC曲线
- ROC曲线的用处
- AUC
- 精准率和召回率
- 精准率
- 召回率
- 两者公式比较
- 精准率和召回率的关系
- 阈值对精准率和召回率的影响
- P-R曲线
- P-R曲线的用处
- AP
- 原始计算方式
- 其他计算方式
- F1分数
- Matthews相关系数
混淆矩阵
混淆矩阵很重要,很多指标都是源于混淆矩阵,这个务必要弄懂。
例子
为了解释混淆矩阵,先来看看下面这个二分类的例子。
例:有20个病人来医院检查,是否患病的预测值和真实值如下表所示。
病号 | 预测值 | 真实值 | 病号 | 预测值 | 真实值 |
---|---|---|---|---|---|
1 | 1 | 1 | 11 | 0 | 0 |
2 | 0 | 0 | 12 | 0 | 0 |
3 | 1 | 1 | 13 | 0 | 0 |
4 | 0 | 0 | 14 | 1 | 1 |
5 | 0 | 0 | 15 | 0 | 0 |
6 | 1 | 1 | 16 | 1 | 0 |
7 | 0 | 0 | 17 | 1 | 1 |
8 | 0 | 0 | 18 | 0 | 0 |
9 | 0 | 1 | 19 | 0 | 0 |
10 | 0 | 0 | 20 | 0 | 1 |
其中,1表示患病,0表示不患病。
本文档默认用0和1来作为二分类符号。
你也可以用其他符号来表示,如1表示患病,-1表示不患病。只要能区分就行。
这样就出现4种结果:
- 预测为1,实际也为1,包括病号1,3,6,14,17,一共5个样本;
- 预测为1,实际为0,包括病号16,只有1个样本;
- 预测为0,实际为1,包括病号9,20,只有2个样本;
- 预测为0,实际也为0,包括病号2,4,5,7,8,10,11,12,13,15,18,19,一共12个样本。
我们把各个结果的数量填到下面这个表格中
这就是病患例子的混淆矩阵。
混淆矩阵定义
二分类混淆矩阵的一般定义只是将1和0叫做正例和负例,把4种结果的样本数量用符号来表示,用什么符号呢?
如果我们用P(Positive)代表1,用N(Negative)代表0,那这四种结果分别是PP,PN,NP,NN,但这样表示有点问题,譬如,PN的意思是预测为1实际为0还是预测为0实际为1?需要规定好了,还得记住,好麻烦。
干脆再引入符号T(True)代表预测正确,F(False)表示预测错误,那么之前的P和N代表预测是1还是0,T和F表示预测是否正确。
四种情况可以分别表示为
- TP:预测为1,预测正确,即实际也为1;
- FP:预测为1,预测错误,即实际为0;
- FN:预测为0,预测错误,即实际为1;
- TN:预测为0,预测正确,即实际也为0。
混淆矩阵的定义如下:
混淆矩阵代码
采用sklearn.metrics中的confusion_matrix函数计算混淆矩阵,数据用的还是之前那个病患检查的样本。
from sklearn.metrics import confusion_matrix# 真实值
y_true = [1,0,1,0,0,1,0,0,1,0,0,0,0,1,0,0,1,0,0,1]
# 预测值
y_pred = [1,0,1,0,0,1,0,0,0,0,0,0,0,1,0,1,1,0,0,0]c_matrix = confusion_matrix(y_true, y_pred)
print(c_matrix)
代码输出
[[12 1][ 2 5]]
有了混淆矩阵,就可以定义一些指标了。
正确率
准确率(Accuracy)的定义很简单,就是猜对的样本占总样本的比例,公式如下:
Accuracy=猜对的样本量样本总量=TP+TNTP+FP+FN+TN\text{Accuracy} = \frac{猜对的样本量}{样本总量} = \frac{TP+TN}{TP+FP+FN+TN} Accuracy=样本总量猜对的样本量=TP+FP+FN+TNTP+TN
正样本是实际为正例的样本,负样本是实际为负例的样本。
计算正确率可以调用sklearn.metrics的accuracy_score函数,代码如下:
from sklearn.metrics import accuracy_score# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]
# 预测值
y_pred = [1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0]mc = accuracy_score(y_true, y_pred)
print('Accuracy: %.2f'%mc)
结果为
Accuracy: 0.85
正确率作为评价指标有一个很致命的缺点,就是样本不平衡时正确率无法反映模型结果的好坏。
举个例子,预估某个网站上某一天广告的点击率,假如一天有1000个人浏览,实际有50个人点击广告,假如分类器预测没有人会点击,那么这个模型结果的正确率是多少呢?
我们算一下:分类器预测正确的有950个样本,一共有1000个样本,根据定义Accuracy=9501000=0.95\text{Accuracy} = \frac{950}{1000} = 0.95Accuracy=1000950=0.95,正确率为95%!!!
一个点击的人都没有预测对,正确率都能有95%,那这个指标对模型的评价不合理。
那样本不平衡的时候怎么办呢?
细心想想,样本不平衡的问题是正负样本在数量上有很大差距,数量少的那方被重视程度低,比较吃亏,要解决这个问题,把正负样本分开评价不就好啦,大家河水不犯井水。
按照这个思路,引入下面两个概念:真阳率和假阳率。
真阳率和假阳率
真阳率
真阳率(True Positive Rate, TPR)的定义是:正样本中猜对的比例。公式如下
TPR=TPTP+FNTPR = \frac{TP}{TP+FN} TPR=TP+FNTP
假阳率
假阳率(False Positive Rate, FPR)的定义是:负样本中猜错的比例。公式如下
FPR=FPTN+FPFPR = \frac{FP}{TN+FP} FPR=TN+FPFP
真阳率和假阳率的公式比较
TPR=TPTP+FNFPR=FPTN+FP\begin{aligned} TPR &= \frac{TP}{TP+FN} \\ FPR &= \frac{FP}{TN+FP} \end{aligned} TPRFPR=TP+FNTP=TN+FPFP
TPR公式的分母是正样本数量,FPR公式的分母是负样本数量,这就遵循了正负样本分开评价的思路。
TPR公式的分子是TP,说明这个指标关注正确率;FPR公式的分子是FP,说明这个指标关注错误率。
通常,这两个指标不单独使用,那要怎么用呢?
那就不得不介绍ROC/AUC的概念了。
ROC/AUC
例子
还是那个病患事例,不同在于预测值不是0和1的离散值,而是一个0到1的连续值,叫做置信度(confidence score),可以理解为”概率“,越接近1,结果越可能为1;越接近0,结果越可能为0。
病号 | 置信度 | 真实值 | 病号 | 置信度 | 真实值 |
---|---|---|---|---|---|
1 | 0.8 | 1 | 11 | 0.8 | 0 |
2 | 0.2 | 0 | 12 | 0.1 | 0 |
3 | 0.4 | 1 | 13 | 0.2 | 0 |
4 | 0.1 | 0 | 14 | 0.9 | 1 |
5 | 0.4 | 0 | 15 | 0.3 | 0 |
6 | 0.8 | 1 | 16 | 0.6 | 0 |
7 | 0.3 | 0 | 17 | 0.8 | 1 |
8 | 0.2 | 0 | 18 | 0.2 | 0 |
9 | 0.6 | 1 | 19 | 0.2 | 0 |
10 | 0.5 | 0 | 20 | 0.4 | 1 |
预测值是置信度的话,要怎么算TPR和FPR呢?
很简单,给个阈值就行,不小于这个阈值就设为1,小于设为0。
注意,在实际的做法中,一般不用卡阈值的方法,而是按照置信度排序,然后取前N条样本,其实效果等同取阈值。
但阈值设多大好呢?
这就很关键了,因为阈值的大小会影响TPR和FPR。
阈值对TPR和FPR的影响
假如病患例子的阈值设为0.9,阈值判决后的预测结果如下表。
病号 | 预测值 | 真实值 | 病号 | 预测值 | 真实值 |
---|---|---|---|---|---|
1 | 0 | 1 | 11 | 0 | 0 |
2 | 0 | 0 | 12 | 0 | 0 |
3 | 0 | 1 | 13 | 0 | 0 |
4 | 0 | 0 | 14 | 1 | 1 |
5 | 0 | 0 | 15 | 0 | 0 |
6 | 0 | 1 | 16 | 0 | 0 |
7 | 0 | 0 | 17 | 0 | 1 |
8 | 0 | 0 | 18 | 0 | 0 |
9 | 0 | 1 | 19 | 0 | 0 |
10 | 0 | 0 | 20 | 0 | 1 |
可以算出TP=1,TN=13,FP=0,FN=6,那么
TPR=TPTP+FN=11+6≈0.14FPR=FPTN+FP=0\begin{aligned} TPR &= \frac{TP}{TP+FN} = \frac{1}{1+6} \approx 0.14 \\ FPR &= \frac{FP}{TN+FP} = 0 \end{aligned} TPRFPR=TP+FNTP=1+61≈0.14=TN+FPFP=0
这结果TPR和FPR都很低,FPR低是好事,说明负样本的预测错误率低,但TPR也低就不好了,因为正样本的预测正确率不高。
那换个阈值再试试,阈值设为0.1,就是全部猜作正例,不列详细计算过程了,直接给出结果
TPR=1FPR=1\begin{aligned} TPR &= 1 \\ FPR &= 1 \end{aligned} TPRFPR=1=1
这结果刚好相反,TPR和FPR都很高,正样本的预测正确率上来了,负样本的预测错误率也变大了。
通过上面的比较,能看出来:阈值设得越高,TPR和FPR越低;阈值设得越低,TPR和FPR越高。
ROC曲线
上一节我们知道了TPR和FPR会随阈值变化而变化,你要是把所有阈值对应的TPR和FPR求出来,画个直角坐标系,以FPR为横轴,TPR为纵轴,把不同阈值下的(FPR,TPR)坐标点标上并连起来,你就能看到TPR和FPR的整个变化曲线,而这条曲线就称为ROC(Receiver Operating Characteristic)曲线。
Receiver Operating Characteristic这名字挺奇怪的,可能是因为最早出现在雷达信号检测领域,用于评价接收器(Receiver)侦测敌机的能力。
尝试画出病患事例的ROC曲线,先求不同阈值下的FPR和TPR,置信度从大到小(重复的不算)排列为[0.9, 0.8, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1],一共有8个阈值,如果手算TPR和FPR那太费劲了,幸好sklearn.metrics模块有现成的roc_curve函数来算,代码如下:
import pandas as pd
from sklearn.metrics import roc_curve# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]# 计算TPR和FPR
fpr, tpr, thresholds = roc_curve(y_true, y_score)# 把fpr,tpr,thresholds用DataFrame表格保存,方便显示
result = pd.DataFrame([thresholds,tpr,fpr], index=['threshold','TPR','FPR'])
print(result)
结果如下
0 1 2 3 4 5 6 \
threshold 1.9 0.900000 0.800000 0.600000 0.500000 0.400000 0.300000
TPR 0.0 0.142857 0.571429 0.714286 0.714286 1.000000 1.000000
FPR 0.0 0.000000 0.076923 0.153846 0.230769 0.307692 0.461538 7 8
threshold 0.200000 0.1
TPR 1.000000 1.0
FPR 0.846154 1.0
上面结果有两点需要注意:
- roc_curve函数结果的第一列没有什么实际意义,只是画ROC曲线图一般都会有原点(0,0),它直接帮用户给加上了。
- 关于第一列的threshold为什么是1.9?根据官方API的解释,它是用
max(y_score) + 1
算的,为什么要这么算?官方API没有说明,所以我也不知道这脑洞是怎么来的。
接下来,就是根据FPR和TPR结果画ROC曲线,画出来如下图。
画图代码如下:
import matplotlib.pyplot as pltplt.figure()
# 画散点图,标出点的位置
plt.scatter(fpr, tpr)
# 画ROC曲线图
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve')plt.xlim([-0.05, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()
如果样本多了之后,画出来的ROC曲线会平滑得多。
ROC曲线的用处
当你需要评价多个分类模型结果时,ROC曲线能帮你看出这些模型的优劣。
下面给出了A和B两个分类模型的ROC曲线图,哪一个模型的结果比较好呢?
很显然是模型A,为什么呢?
因为模型A的ROC曲线要比模型B的往左上凸,这样的话,如果固定FPR,模型A的TPR大于模型B;如果固定TPR,模型A的FPR要小于模型B。怎么样都是模型A比模型B强。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qHPaDfif-1603466581745)(pict/2019-07-13 19-13-08屏幕截图.png)]
模型C是有特殊意义的,如果抛硬币来做二分类预测(取任一类的概率是0.5),最后画出来的ROC曲线图就跟C很接近。
可以做个实验:用概率为0.5取0或1来预测真实值,看看算出来的TPR和FPR的结果。
先构造一个1000样本的真实值列表。
from sklearn.metrics import confusion_matrix import random# 构造真实值,正例有100个,负例有900个,用shuffle随机打乱顺序 y_true = [1]*100+[0]*900 random.shuffle(y_true)
用概率为0.5取0或1做预测,并计算TPR和FPR。
import numpy as np # 随机生成1000个0和1的预测值 y_pred = np.random.randint(0,2,size=1000)# 计算TPR和FPR tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel() print('FPR: %.2f'%(fp/(tn+fp))) print('TPR: %.2f'%(tp/(tp+fn)))
结果如下
FPR: 0.50 TPR: 0.53
由于预测值是随机的,每次出来结果会有不同,但基本都围绕在点(FPR,TRP)=(0.5,0.5)附近,也就是说,按概率为0.5取0或1的方式做预测,势必经过(0.5,0.5),其ROC曲线就会表现为一条往右上的对角线。
某个模型全面碾压的情况不太多,大多数情况会如下图所示,两个模型的ROC曲线是相交的。
那哪个模型的结果比较好呢?
需要分情况。比如,如果限定FPR要小于相交点,无疑模型A好于模型B。
AUC
如果没有特定的限制,那怎么选模型呢?有一招,直接算ROC曲线下的面积,称为AUC(Area Under Curve)。
AUC越大,模型结果越好,下面算算医患事例的AUC,用sklearn.metrics的roc_auc_score函数。
from sklearn.metrics import roc_auc_score# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]# 计算AUC
auc = roc_auc_score(y_true, y_score)
print('AUC: %.2f'%auc)
结果是
AUC: 0.89
AUC能评价二分类模型结果,其实是有概率解释的,AUC的概率含义是:随机从样本集中取一对正负样本,正样本得分(置信度)大于负样本的概率。实际上可以理解为,模型把正样本(按照置信度)排在负样本前面的概率。
具体的解释参考下面链接:
https://tracholar.github.io/machine-learning/2018/01/26/auc.html#auc%E5%AF%B9%E6%AD%A3%E8%B4%9F%E6%A0%B7%E6%9C%AC%E6%AF%94%E4%BE%8B%E4%B8%8D%E6%95%8F%E6%84%9F
精准率和召回率
在信息检索、Web搜索领域,时常会关心“检索的信息有多少是用户感兴趣的”、“用户感兴趣的信息有多少被检索出来”,为满足这样的评价需求,有了精准率和召回率这两个指标。
精准率
精准率(Precision)的定义是:预测为正的样本中猜对的比例。公式如下
Precision=TPTP+FP\text{Precision} = \frac{TP}{TP+FP} Precision=TP+FPTP
这个指标反映的是你预测正样本预测有多准,关键在准,因此Precision也被称为查准率。
召回率
召回率(Recall)的定义是:实际为正的样本中被猜对的比例。公式如下
Recall=TPTP+FN\text{Recall} = \frac{TP}{TP+FN} Recall=TP+FNTP
看定义,召回率是有点不好理解,举个例子吧。
假如患病的为正样本,不患病的为负样本,100个人里面有10个病患,医生检查出了病患中的8个,那这个结果的召回率是多少?
按照定义,先看实际为正的样本,也就是患病的人,共有10个,这里面医生猜对的有8个,那么Recall=810=0.8\text{Recall} = \frac{8}{10} = 0.8Recall=108=0.8。由此可知,召回率关注的是病患(正样本)是不是都找全了,关键在全,因此Recall也被称为查全率。
两者公式比较
Precision=TPTP+FPRecall=TPTP+FN\begin{aligned} \text{Precision} &= \frac{TP}{TP+FP} \\ \text{Recall} &= \frac{TP}{TP+FN} \end{aligned} PrecisionRecall=TP+FPTP=TP+FNTP
Precision和Recall公式的分子都是TP,这表示两者都关心有多少猜对的正样本。
差别在于分母:Precision的是TP+FP,即预测为1(Positive)的样本;Recall的是TP+FN,即实际为1的样本(FN表示预测为0但没猜对,实际是1)。
它们的关注点都是跟1(一般为少数类)有关的样本,根本没有考虑TN(预测为0,实际为0)。
所以,精准率和召回率这两个指标的本质是:从精确度和全面性的角度来考察少数类样本的预测结果。
由于精准率和召回率更关注少数类样本的预测情况,所以用它们作为评价指标也可以解决样本不平衡的问题。
精准率和召回率的关系
还是用那个病患事例(预测值是置信度的情况)说明。
病号 | 置信度 | 真实值 | 病号 | 置信度 | 真实值 |
---|---|---|---|---|---|
1 | 0.8 | 1 | 11 | 0.8 | 0 |
2 | 0.2 | 0 | 12 | 0.1 | 0 |
3 | 0.4 | 1 | 13 | 0.2 | 0 |
4 | 0.1 | 0 | 14 | 0.9 | 1 |
5 | 0.4 | 0 | 15 | 0.3 | 0 |
6 | 0.8 | 1 | 16 | 0.6 | 0 |
7 | 0.3 | 0 | 17 | 0.8 | 1 |
8 | 0.2 | 0 | 18 | 0.2 | 0 |
9 | 0.6 | 1 | 19 | 0.2 | 0 |
10 | 0.5 | 0 | 20 | 0.4 | 1 |
和TPR/FPR一样,需要对置信度卡阈值判定0和1后,才能计算Precision和Recall。
下面先看看阈值的大小对Precision和Recall的影响。
阈值对精准率和召回率的影响
阈值设为0.9,讲TPR/FPR的时候算过,为TP=1,TN=13,FP=0,FN=6,那么
Precision=TPTP+FP=11+0=1Recall=TPTP+FN=11+6≈0.14\begin{aligned} Precision &= \frac{TP}{TP+FP} = \frac{1}{1+0} = 1 \\ Recall &= \frac{TP}{TP+FN} = \frac{1}{1+6} \approx 0.14 \end{aligned} PrecisionRecall=TP+FPTP=1+01=1=TP+FNTP=1+61≈0.14
这结果Precision很高,Recall很低,说明猜正样本猜得很准,预测为正样本的都猜对了,只是猜得不全,还有好多正样本没猜到。
如果阈值为0.1,就是全部猜作正样本,不列详细计算过程了,直接给出结果
Precision=0.35Recall=1\begin{aligned} Precision &= 0.35 \\ Recall &= 1 \end{aligned} PrecisionRecall=0.35=1
这结果刚好相反,Precision很低,Recall很高,说明正样本都找全了,就是猜得不怎么准。
通过上面的比较,能看出来阈值对Precision和Recall的影响:
把阈值设得高,预测正样本的把握确实要大,但会漏掉好多正样本;
把阈值设得低,正样本都能找到,但是预测正样本的准度就不怎么样了。
举个现实的例子:
- 没有99.99%的概率(阈值设得高)会赚钱就不投资,当然投了基本都赚,但会失去很多赚大钱的机会;
- 热点项目不管能不能赚钱(阈值设得低)都投,当然很可能把大鱼(如初创的google,facebook)都逮到,但会有好多投资的项目是赔钱的。
P-R曲线
Precision和Recall是一对矛盾体,一方大了另一方就小,随着阈值的变动此起彼伏。
和ROC曲线一样,算出不同阈值下的Precision和Recall,以Recall为横轴,以Precision为纵轴,也可以画出一条曲线图,称为P-R(精确率-召回率)曲线。
尝试把病患事例的PR曲线图画出来,先算不同阈值下的Precision和Recall,调用sklearn.metrics中的precision_score和recall_score函数来算,代码如下:
# 阈值划分函数
def binary_by_thres(x,t):if x >= t:return 1else:return 0from sklearn.metrics import precision_score,recall_score# 阈值列表
thresholds = [0.1,0.2,0.3,0.4,0.5,0.6,0.8,0.9]
# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]precision = []
recall = []for t in thresholds:# 根据阈值t把预测值划分为0和1y_thres = list( map(binary_by_thres, y_score, [t]*len(y_score)) )precision.append( precision_score(y_thres, y_true) )recall.append( recall_score(y_thres, y_true) )result = pd.DataFrame([thresholds,precision,recall], index=['threshold','precision','recall'])
print(result)
结果如下:
0 1 2 3 4 5 6 \
threshold 0.10 0.200000 0.300000 0.400000 0.500000 0.600000 0.800000
precision 0.35 0.388889 0.538462 0.636364 0.625000 0.714286 0.800000
recall 1.00 1.000000 1.000000 1.000000 0.714286 0.714286 0.571429 7
threshold 0.900000
precision 1.000000
recall 0.142857
画个直角坐标系,以Recall为横轴,Precision为纵轴,看看不同阈值下的(Recall,Precision)坐标点的P-R曲线变化,画出的图如下:
上图有两点需要注意:
最后一个(Recall,Precision)坐标点规定是(0,1),跟阈值无关。官方API解释说是为了画图从纵轴开始。
画图代码计算Precision和Recall用的是sklearn.metrics中的precision_recall_curve函数,它的计算结果如下,和之前用precision_score和recall_score函数计算的结果不同,少了阈值为0.1,0.2,0.3的情况。
0 1 2 3 4 5 threshold 0.400000 0.500000 0.600000 0.800000 0.900000 NaN precision 0.636364 0.625000 0.714286 0.800000 1.000000 1.0 recall 1.000000 0.714286 0.714286 0.571429 0.142857 0.0
对这个的解释:首先这三种情况的Recall都是1,都在Recall=1的直线上,画阶梯图时考不考虑这三个点对最终的图没有影响,所以precision_recall_curve函数就懒得输出了吧。
画图所用代码如下:
from sklearn.metrics import precision_recall_curve
import matplotlib.pyplot as plt# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]# 计算precision和recall
precision, recall, thresholds = precision_recall_curve(y_true, y_score)# 规定画布的大小
plt.figure(figsize=(12,8))# 画填充图
plt.fill_between(recall, precision, alpha=0.2, color='b', step='post')
# 画散点图,凸显坐标点位置
plt.scatter(recall, precision, alpha=0.8, color='r')plt.xlabel('Recall')
plt.ylabel('Precision')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.05])
plt.show()
如果样本多了之后,画出来的P-R曲线会平滑得多。
P-R曲线的用处
和ROC曲线一样,P-R曲线能评价多个模型结果的优劣。
下面给出了两个分类模型的P-R曲线图,哪一个模型的结果比较好呢?
显然是紫色的模型B,为什么呢?
因为模型B的PR曲线要比模型A的往右上凸。
如果固定Recall,模型B的Precision大于模型A;如果固定Precision,模型B的Recall还是大于模型A。怎么样都是模型B比模型A强。
当然,绝大多数情况如下图所示,两个模型的PR曲线是相交的。
那哪个模型的结果比较好呢?跟ROC曲线一样,需要分情况讨论。
如,Recall大于交点时,模型A比模型C好。
实际工作中,如果是做搜索,在保证召回率的情况下要尽量提升准确率,那就更愿意选模型A;如果做疾病监测、反垃圾邮件等,则是保精确率的条件下提升召回率,那更更倾向于选模型C。
那P-R曲线有没有像ROC曲线中的AUC那样的评价指标呢?
有的,P-R曲线下的面积其实是AP(Average Precision)。
AP
原始计算方式
AP(Average Precision)英文的意思就是平均精准度,为什么P-R曲线下的面积就是平均精准度呢?
先来看P-R曲线下面积的计算公式
∑tP(t)ΔR(t)\sum_{t} P(t) \Delta R(t) t∑P(t)ΔR(t)
其中,ttt是阈值,P(t)P(t)P(t)是对应阈值ttt的Precision,R(t)R(t)R(t)是对应阈值ttt的Recall,ΔR(t)=R(t)−R(t−1)\Delta R(t)=R(t)-R(t-1)ΔR(t)=R(t)−R(t−1)。
把公式变一下形式
∑tΔR(t)P(t)\sum_{t} \Delta R(t) P(t) t∑ΔR(t)P(t)
而且∑tΔR(t)=1\sum_t \Delta R(t) = 1∑tΔR(t)=1。
上面公式就是加权平均值的计算形式,每个P(t)P(t)P(t)对应的权值是Recall的变化量ΔR(t)\Delta R(t)ΔR(t)。
根据上面的说明,我们可以求病患事例的AP,先列出之前算出的threshold,precision,recall。
0 1 2 3 4 5
threshold 0.400000 0.500000 0.600000 0.800000 0.900000 NaN
precision 0.636364 0.625000 0.714286 0.800000 1.000000 1.0
recall 1.000000 0.714286 0.714286 0.571429 0.142857 0.0
AP=1.0×(0.142857−0.0)+0.8×(0.571429−0.142857)+0.714286×(0.714286−0.571429)+0.625×(0.714286−0.714286)+0.636364×(1−0.714286)=0.769573\begin{aligned} AP =& 1.0 \times (0.142857 - 0.0) + 0.8 \times (0.571429 - 0.142857) + 0.714286 \times (0.714286 - 0.571429) \\ &+ 0.625 \times (0.714286 - 0.714286) + 0.636364 \times (1-0.714286) \\ =& 0.769573 \end{aligned} AP==1.0×(0.142857−0.0)+0.8×(0.571429−0.142857)+0.714286×(0.714286−0.571429)+0.625×(0.714286−0.714286)+0.636364×(1−0.714286)0.769573
用代码来验证一下结果:
from sklearn.metrics import average_precision_score# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]# 计算average precision
ap = average_precision_score(y_true, y_score)
print('Average Precision:%.6f' % ap)
输出为
Average Precision:0.769573
其他计算方式
实际上,原始的AP计算方式用的不多,常用的是PASCAL VOC CHALLENGE的计算方法,它有两种计算方式:
11-point Interpolated Average Precision
给Recall设定一组阈值,[0, 0.1, 0.2, … , 1],对于Recall大于等于每一个阈值,都有一个对应的最大precision,这样我们就计算出了11个precision,11-point Interpolated Average Precision即为这11个precision的平均值。
还是算算病患事例的11-point Interpolated Average Precision吧。
之前通过precision_score和recall_score算得的病患事例Precision和Recall如下:
0 1 2 3 4 5 6 \ threshold 0.10 0.200000 0.300000 0.400000 0.500000 0.600000 0.800000 precision 0.35 0.388889 0.538462 0.636364 0.625000 0.714286 0.800000 recall 1.00 1.000000 1.000000 1.000000 0.714286 0.714286 0.571429 7 threshold 0.900000 precision 1.000000 recall 0.142857
先算阈值0的情况。
按照定义,Recall大于等于阈值0对应的Precision都算,有哪些呢?全部。然后从中挑选一个最大的,那肯定就是1了。
这就算完了阈值为0的情况,其他的阈值也是依葫芦画瓢,不详细去算了,直接给吧。
Recall大于等于的阈值 最大Precision 0 1 0.1 1 0.2 0.8 0.3 0.8 0.4 0.8 0.5 0.8 0.6 0.714286 0.7 0.714286 0.8 0.636364 0.9 0.636364 1 0.636364 把上面11个最大Precision求平均,就得到11-point Interpolated Average Precision,为0.776151。
PASCAL VOC CHALLENGE 2010版计算方法
PASCAL VOC CHALLENGE自2010年后就换了另一种计算方法,跟11-point Interpolated Average Precision的区别不是特别大,差异在于给Recall设定阈值改为[1M,2M,⋯,M−1M,1][\frac{1}{M},\frac{2}{M},\cdots,\frac{M-1}{M},1][M1,M2,⋯,MM−1,1],M为正样本的个数。
老规矩,拿病患事例来说明,病患事例数据有7个正样本,M=7。
那阈值就确定了,为[17,27,⋯,67,1][\frac{1}{7},\frac{2}{7},\cdots,\frac{6}{7},1][71,72,⋯,76,1]。
跟11-point Interpolated Average Precision一样算阈值的最大Precision,结果如下:
Recall大于等于的阈值 最大Precision 17\frac{1}{7}71 1 27\frac{2}{7}72 1 37\frac{3}{7}73 1 47\frac{4}{7}74 0.8 57\frac{5}{7}75 0.714286 67\frac{6}{7}76 0.636364 1 0.636364 对最大Precision求平均,得0.826716。
AP的参考链接:
https://www.bbsmax.com/A/MAzAOw159p/
http://blog.sina.com.cn/s/blog_9db078090102whzw.html
F1分数
如果Precision和Recall两个指标都要求高,可以用F1分数来评价模型。
F1分数(F1 score)的计算公式
F1=2(Precision∗Recall)Precision+RecallF1 = \frac{2(Precision*Recall)}{Precision+Recall} F1=Precision+Recall2(Precision∗Recall)
F1分数采用的是调和平均数(Harmonic Average)。
什么是调和平均数?其实就是倒数的平均数,看下面公式
1y=1N(1x1+1x2+⋯+1xN)\frac{1}{y} = \frac{1}{N}(\frac{1}{x_1} + \frac{1}{x_2} + \cdots +\frac{1}{x_N}) y1=N1(x11+x21+⋯+xN1)
F1分数公式变换一下形式就能看出来是调和平均数
F1=2(Precision∗Recall)Precision+Recall⇒1F1=12(1Precision+1Recall)\begin{aligned} & F1 = \frac{2(Precision*Recall)}{Precision+Recall} \\ \Rightarrow \quad& \frac{1}{F1} = \frac{1}{2} (\frac{1}{Precision}+\frac{1}{Recall}) \end{aligned} ⇒F1=Precision+Recall2(Precision∗Recall)F11=21(Precision1+Recall1)
那为什么要用调和平均数?直接求算术平均数不行吗?
那我们举个极端的例子,Precision=0,Recall=1Precision=0,Recall=1Precision=0,Recall=1,现实中绝对不会出现这种情况,这只是为了凸显算术平均数和调和平均数之间的差异。
那么有
算术平均值:Mean=12(Precision+Recall)=0.5算术平均值:Mean = \frac{1}{2}(Precision+Recall)=0.5 算术平均值:Mean=21(Precision+Recall)=0.5
调和平均值:1F1=12(1Precision+1Recall)=+∞⇒F1=0调和平均值:\frac{1}{F1} = \frac{1}{2} (\frac{1}{Precision}+\frac{1}{Recall}) = +\infin \Rightarrow F1=0 调和平均值:F11=21(Precision1+Recall1)=+∞⇒F1=0
这说明:调和平均比算术平均更关注值较小的数,就像马云的财富和你的财富算术平均一下,你也是亿万富翁,如果是用调和平均,那马云的财富水平也会和你的相当。
所以,F1分数如果比较大,那PrecisionPrecisionPrecision和RecallRecallRecall都不会小,这样就能平衡地看待两者。
计算病患例子的F1分数,代码如下:
# 阈值划分函数
def binary_by_thres(x,t):if x >= t:return 1else:return 0import pandas as pd
from sklearn.metrics import f1_score# 阈值列表
thresholds = [0.1,0.2,0.3,0.4,0.5,0.6,0.8,0.9]
# 置信度
y_score = [0.8, 0.2, 0.4, 0.1, 0.4, 0.8, 0.3, 0.2, 0.6, 0.5,0.8, 0.1, 0.2, 0.9, 0.3, 0.6, 0.8, 0.2, 0.2, 0.4]
# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]fscore = []for t in thresholds:# 根据阈值t把预测值划分为0和1y_thres = list( map(binary_by_thres, y_score, [t]*len(y_score)) )# 计算F1分数fscore.append( f1_score(y_true, y_thres) )result = pd.DataFrame([thresholds,fscore], index=['threshold','f1_score'])
print(result)
输出为
0 1 2 3 4 5 6 7
threshold 0.100000 0.20 0.3 0.400000 0.500000 0.600000 0.800000 0.90
f1_score 0.518519 0.56 0.7 0.777778 0.666667 0.714286 0.666667 0.25
根据均值不等式(高中知识):21a+1b≤ab\frac{2}{\frac{1}{a}+\frac{1}{b}} \le \sqrt{ab}a1+b12≤ab,当且仅当a=ba=ba=b时取等号。
那么,F1=21Precision+1Recall≤Precision⋅RecallF1 = \frac{2}{\frac{1}{Precision}+\frac{1}{Recall}} \le \sqrt{Precision \cdot Recall}F1=Precision1+Recall12≤Precision⋅Recall,当且仅当Precision=RecallPrecision = RecallPrecision=Recall取等号。
所以,只有当Precision=RecallPrecision = RecallPrecision=Recall时F1分数取最大值。
在PR曲线图中,往右上的对角线与PR曲线的交点就是F1分数的最大点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSQgp1xz-1603466581791)(pict/2019-07-14 11-32-36屏幕截图.png)]
Matthews相关系数
Matthews相关性系数(Matthews Correlation Coefficient, MCC)跟之前的指标关系不大,所以放最后讲。
它的公式如下
MCC=TP⋅TN−FP⋅FN(TP+FP)(TP+FN)(TN+FP)(TN+FN)MCC= \frac{TP \cdot TN - FP \cdot FN}{\sqrt{(TP + FP)(TP + FN)(TN + FP)(TN + FN)}} MCC=(TP+FP)(TP+FN)(TN+FP)(TN+FN)TP⋅TN−FP⋅FN
乍看起来让人有点蒙,不知道怎么来的,下面我就来解释一下。
Matthews相关系数既然叫相关系数,我们第一联想到的会是什么呢?
就是概率论里面学的Pearson相关系数,先回归一下Pearson相关系数的知识。
Pearson相关系数计算公式如下:
ρ=E{[X−E(X)][Y−E(Y)]}Var(X)Var(Y)=E(XY)−E(X)E(Y)Var(X)Var(Y)\rho = \frac{E\{[X-E(X)][Y-E(Y)]\}}{\sqrt{Var(X)Var(Y)}} = \frac{E(XY)-E(X)E(Y)}{\sqrt{Var(X)Var(Y)}} ρ=Var(X)Var(Y)E{[X−E(X)][Y−E(Y)]}=Var(X)Var(Y)E(XY)−E(X)E(Y)
其中,XXX和YYY是两个变量,E(⋅)E(\cdot)E(⋅)和Var(⋅)Var(\cdot)Var(⋅)分别代表均值和方差。
Pearson相关系数的用处在于衡量XXX和YYY之间的线性关系,取值范围是[-1,1]。
- ρ\rhoρ越接近1,越正相关,XXX和YYY越趋近于同向变化;
- ρ\rhoρ越接近-1,越负相关,XXX和YYY越趋近于反向变化;
- ρ\rhoρ越接近0,相关性越弱,XXX和YYY之间的线性关系越小。
那Pearson相关系数和Matthews相关系数之间有什么关系呢?
其实Matthews相关系数就是特殊的Pearson相关系数,Matthews相关系数针对的是XXX和YYY都是0-1分布的情况。
在实际中,只知道XXX和YYY的样本,只能通过样本来求均值和方差。
现在假定XXX是二分类的预测值,YYY是二分类的真实值,两者的取值都是0和1。
先复习一下样本均值和样本方差的知识。
设X1,X2,⋯,XNX_1,X_2,\cdots,X_NX1,X2,⋯,XN是来自总体XXX的一个样本,x1,x2,⋯,xNx_1,x_2,\cdots,x_Nx1,x2,⋯,xN是对应的观察值。
样本均值的观察值计算公式:
xˉ=1N∑i=1Nxi\bar{x} = \frac{1}{N} \sum_{i=1}^N x_i xˉ=N1i=1∑Nxi
样本方差的观察值计算公式:
s2=1N(∑i=1Nxi2−Nxˉ2)s^2 = \frac{1}{N} (\sum_{i=1}^N x_i^2 - N\bar{x}^2) s2=N1(i=1∑Nxi2−Nxˉ2)
概率论的书上计算样本方差时除以的是N−1N-1N−1,从无偏性来考虑书上确实是对的,但实际应用中NNN都比较大,所以常常直接除以NNN,其实对结果影响不大。对于符合0-1分布的样本X,假设样本观察值取1的数量为N1N_1N1,样本总量为NNN。
那么X的样本均值和样本方差的观察值分别是
xˉ=N1N\bar{x} = \frac{N_1}{N} xˉ=NN1s2=1N(∑i=1Nxi2−Nxˉ2)=1N[N1−N(N1N)2]=N1N(1−N1N)s^2 = \frac{1}{N} (\sum_{i=1}^N x_i^2 - N\bar{x}^2) = \frac{1}{N} [N_1 - N(\frac{N_1}{N})^2] = \frac{N_1}{N} (1-\frac{N_1}{N}) s2=N1(i=1∑Nxi2−Nxˉ2)=N1[N1−N(NN1)2]=NN1(1−NN1)
XXX和YYY有关的样本均值和样本方差如下
E(X)=TP+FPN,(TP+FP)是X=1的样本数E(Y)=TP+FNN,(TP+FN)是Y=1的样本数E(XY)=TPN,TP是XY=1的样本数Var(X)=TP+FPN(1−TP+FPN)=TP+FPN⋅TN+FNNVar(Y)=TP+FNN(1−TP+FNN)=TP+FNN⋅TN+FPNE(X) = \frac{TP+FP}{N}, \quad (TP+FP)是X=1的样本数 \\ E(Y) = \frac{TP+FN}{N}, \quad (TP+FN)是Y=1的样本数 \\ E(XY) = \frac{TP}{N}, \quad TP是XY=1的样本数 \\ Var(X) = \frac{TP+FP}{N}(1-\frac{TP+FP}{N}) = \frac{TP+FP}{N} \cdot \frac{TN+FN}{N} \\ Var(Y) = \frac{TP+FN}{N}(1-\frac{TP+FN}{N}) = \frac{TP+FN}{N} \cdot \frac{TN+FP}{N} E(X)=NTP+FP,(TP+FP)是X=1的样本数E(Y)=NTP+FN,(TP+FN)是Y=1的样本数E(XY)=NTP,TP是XY=1的样本数Var(X)=NTP+FP(1−NTP+FP)=NTP+FP⋅NTN+FNVar(Y)=NTP+FN(1−NTP+FN)=NTP+FN⋅NTN+FP
其中N=TP+FP+TN+FNN=TP+FP+TN+FNN=TP+FP+TN+FN。
把这些结果代入Pearson相关系数
MCC=E(XY)−E(X)E(Y)Var(X)Var(Y)=TPN−TP+FPN⋅TP+FNNTP+FPN⋅TN+FNN⋅TP+FNN⋅TN+FPN=TP⋅N−(TP+FP)(TP+FN)(TP+FP)(TN+FN)(TP+FN)(TN+FP)=TP(TP+FP+TN+FN)−(TP+FP)(TP+FN)(TP+FP)(TN+FN)(TP+FN)(TN+FP)=TP⋅TN−FP⋅FN(TP+FP)(TN+FN)(TP+FN)(TN+FP)MCC = \frac{E(XY) - E(X)E(Y)}{\sqrt{Var(X)Var(Y)}} = \frac{\frac{TP}{N} - \frac{TP+FP}{N} \cdot \frac{TP+FN}{N}}{\sqrt{\frac{TP+FP}{N} \cdot \frac{TN+FN}{N} \cdot\frac{TP+FN}{N} \cdot \frac{TN+FP}{N}}} \\ = \frac{TP \cdot N - (TP+FP)(TP+FN)}{\sqrt{(TP+FP)(TN+FN)(TP+FN)(TN+FP)}} \\ = \frac{TP(TP+FP+TN+FN) - (TP+FP)(TP+FN)}{\sqrt{(TP+FP)(TN+FN)(TP+FN)(TN+FP)}} \\ = \frac{TP \cdot TN - FP \cdot FN}{\sqrt{(TP+FP)(TN+FN)(TP+FN)(TN+FP)}} MCC=Var(X)Var(Y)E(XY)−E(X)E(Y)=NTP+FP⋅NTN+FN⋅NTP+FN⋅NTN+FPNTP−NTP+FP⋅NTP+FN=(TP+FP)(TN+FN)(TP+FN)(TN+FP)TP⋅N−(TP+FP)(TP+FN)=(TP+FP)(TN+FN)(TP+FN)(TN+FP)TP(TP+FP+TN+FN)−(TP+FP)(TP+FN)=(TP+FP)(TN+FN)(TP+FN)(TN+FP)TP⋅TN−FP⋅FN
这就推出了Matthews相关系数。
从Pearson相关系数的含义可以很方便地去理解Matthews相关系数的意义:
Matthews相关系数的作用是衡量都服从0-1分布的XXX和YYY的线性关系。
- MCC=1MCC=1MCC=1,表示XXX预测是1,真实值YYY确实是1,XXX预测是0,真实值YYY也确实是0,两者同向变化,说明预测全是对的;
- MCC=−1MCC=-1MCC=−1,表示XXX预测是1,真实值YYY却是0,XXX预测是0,真实值YYY却是1,两者反向变化,说明预测全是错的;
- MCC=0MCC=0MCC=0,表示XXX和YYY没有线性关系,预测有时候是对的,有时候是错的,而且对的和错的一样多,相当于你抛硬币瞎猜。
计算Matthews相关系数可以调用sklearn.metrics的matthews_corrcoef函数,代码如下:
from sklearn.metrics import matthews_corrcoef# 真实值
y_true = [1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1]
# 预测值
y_pred = [1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0]# 计算Matthews相关系数
mc = matthews_corrcoef(y_true, y_pred)
print('Matthews Correlation Coefficient: %.2f' % mc)
结果为
Matthews Correlation Coefficient: 0.66
对Matthews相关系数的理解启发于https://stats.stackexchange.com/questions/59343/relationship-between-the-phi-matthews-and-pearson-correlation-coefficients
分类模型评价指标说明相关推荐
- 机器学习100天(二十):020 分类模型评价指标-PR曲线
机器学习100天!今天讲的是:分类模型评价指标-PR曲线! <机器学习100天>完整目录:目录 上一节我们已经了解了混淆矩阵的概念,并掌握了精确率.召回率的计算公式,在这里.现在我们来学习 ...
- 机器学习100天(二十二):022 分类模型评价指标-Python实现
机器学习100天!今天讲的是:分类模型评价指标-Python实现! <机器学习100天>完整目录:目录 打开spyder,首先,导入标准库. import numpy as np impo ...
- 机器学习分类模型评价指标之ROC 曲线、 ROC 的 AUC 、 ROI 和 KS
前文回顾: 机器学习模型评价指标之混淆矩阵 机器学习模型评价指标之Accuracy.Precision.Recall.F-Score.P-R Curve.AUC.AP 和 mAP 图裂的话请参考:ht ...
- 机器学习分类模型评价指标详述
问题建模 机器学习解决问题的通用流程:问题建模--特征工程--模型选择--模型融合 其中问题建模主要包括:设定评估指标,选择样本,交叉验证 解决一个机器学习问题都是从问题建模开始,首先需要收集问题的资 ...
- 深入探讨分类模型评价指标
每天给你送来NLP技术干货! 来自:AI算法小喵 前言 众所周知,机器学习分类模型常用评价指标有Accuracy, Precision, Recall和F1-score,而回归模型最常用指标有MAE和 ...
- 决策树及分类模型评价指标(混淆矩阵,LIFT曲线 重要)
决策树评价指标:ROC lift(提升度):类似提纯:按照decile从高到低排列,之后计算每个decile里响应数与该decile里行数的比值得到一个response rate,另外,单独计算所有行 ...
- 详细讲解分类模型评价指标(混淆矩阵)python示例
前言 1.回归模型(regression): 对于回归模型的评估方法,通常会采用平均绝对误差(MAE).均方误差(MSE).平均绝对百分比误差(MAPE)等方法. 2.聚类模型(clustering) ...
- 分类模型评价指标KS与IV的比较
KS与IV均是用来衡量分类模型准确度的方法,在平常的实验中,用这两种方法得出来的结果往往表现一致.但是,这种一致并不是绝对的,在分析场景时,两个指标发生互相偏离的结果还是存在的.这种偏离的存在主要来自 ...
- 机器学习分类模型评价指标之Accuracy、Precision、Recall、F-Score、P-R Curve、AUC、AP 和 mAP
前文:https://www.cnblogs.com/odesey/p/16902836.html 介绍了混淆矩阵.本文旨在说明其他机器学习模型的评价指标. 1. 准确率(Accuracy-Acc) ...
最新文章
- SVN 两种存储格式(BDB和FSFS)区别
- IDC与百度联合发报告:预测2019年人工智能十大趋势
- c语言链表拆分,C语言拆分链表程序
- Scala基础:定义变量和逻辑判断语句以及方法和函数
- Python调用茉莉机器人API接口
- 贪吃蛇游戏的C++代码
- 必须正确理解的---ng指令中的compile与link函数解析
- php获取页面a标签内容_AKCMS常用标签代码整理
- 机器人学习--pitch yaw roll
- maven 多模块项目:单独构建某个模块
- Intellij idea的Dependencies波浪线
- 容器学习 之 docker存储(十五)
- vue3.0js 非prop属性的值和setup函数的使用
- JavaScript内置对象Date----格式化时间
- layui 表单动态添加、删除input框
- 创业者需要知道的50句话
- python解析html的库_python解析html开发库pyquery使用方法
- 在Windows 下如何使用 AspNetCore Api 和 consul
- GB28181 PTZCmd控制指令笔记
- Java程序调用高德开放API——IP定位
热门文章
- 罗斯蒙特1056ph电极_Rosemount/罗斯蒙特1056-02-20-36-AN-UL 分析仪PH电极
- 完美世界16.65亿出售院线业务;共享床位涉黄被下线;宜家创始人去世丨价值早报
- 1108 String复读机(JAVA)
- 常见算法之Flood Fill算法
- /proc/cpuinfo 里的 CPU flags
- AD学习问题记录(四):AD21布线时如何更改线宽
- SCU - 4438 Censor
- 基于CentOS搭建个人Leanote云笔记本
- 有无可能在非IOS系统上实现苹果为网易/腾讯邮箱做的实时推送
- perl脚本实战总结