分类 机器学习 下的文章

信息量、信息熵、交叉熵是非常重要的数学概念。它们非常重要,相关书籍和资料也很多,不过都不够友好——世上的事情总是如此,你尚不理解的,对于你而言太难;而你已然理解的,对你而言又太过简单。

所以很难有适合所有人的学习资料。这是我以程序员的视角,向自己介绍这几个相关概念。

这篇笔记题目起得太大,不是面向程序员的直观解释,而是面向我这个程序员的直观解释。

信息量

考虑一个抛硬币的游戏:抛出一个硬币,问表示这个事件发生的结果,最多需要几个比特?显然,一个比特位就够了(\(2^1=2\)),比如规定:

  • 1: 代表正面
  • 0: 代表反面

让我们整理一下这里的术语:

  • 随机事件:表示一次抛硬币的事件,要么正面朝上,要么反面朝上,只能是其中之一。
  • 随机变量:表示一个变量,其值是各个随机事件。对于抛硬币来说,可能是正面朝上,也可能是反面朝上。
  • 编码:用数字来对随机事件进行唯一编号。
  • 比特:计算机术语,一个存储位,可以表示两种情况。可以用灯来比喻。

我们重新描述一下上面的问题:

用随机变量\(X\)表示一次抛硬币的结果。我们可以用1来编码正面朝上这个随机事件,用0来编码反面朝上这个事件。需要分配1个比特位(一盏灯)就足够了。
如果硬币被人做了手脚,必然正面朝上,那么对于这种必然事件,我们连一个比特位都不需要分配,即需要0个比特。
同理,如果硬币被人做了手脚,必然反面朝上,我们也不需要分配任何比特位,即需要0个比特。。

如果你的数学直觉足够好,你可能会意识到,要编码上面丢硬币的结果,需要的比特数和随机事件发生的概率有关:

  • 当硬币是正面朝上和反面朝上的概率均是50%时,我们需要1个比特;
  • 而当正面朝上是100%的概率时,我们需要0个比特;
  • 而当反面朝上是100%的概率时,我们也需要0个比特;
  • 如果我们把比特从整数扩展到实数,当正面朝上和反面朝上的概率取其它值时,需要几个比特来编码结果?从直觉上,我们可以猜测,需要的比特数应该介于\((0,1)\)之间。

甚至,我们可以猜测:

  • 当概率构成是(0.5, 0.5)时,我们所需要的1个比特有一半分给了编码正面朝上、另一半分给了反面朝上。
  • 而当概率构成是(1.0, 0.0)时,正面朝上是必然事件,无需比特进行编码;反面朝下也是必然事件,也无需比特进行编码。
  • 而当概率构成是(0.0, 1.0)时,正面朝下是必然事件,无需比特进行编码;反面朝上也是必然事件,也无需比特进行编码。
  • 当概率构成是(p, 1-p)时,这个需要的比特量里有一部分被分给了对正面朝上编码,另一部分属于反面朝上进行编码。至于这个构成是多少,我们留待下面进行更多的探究。

再考虑需要的比特量稍大一点的情况。已知有一个随机整数,取值范围是\([1,16]\)。那么表示这个数到底是多少,需要几个比特?显然,\(2^4=16\),也就是需要4个比特。这个问题也可以换个角度观察:由于有4个比特位,如果逐一确定这里的四个比特位分别是多少,我们共需要测试四次。

阅读剩余部分

如何绘制PR曲线?

基本思想

二元预测函数的输出是一个得分。从预测得分到判定是否属于某类,还需要结合阈值来完成。比如大于某个阈值,就认为是某个类。调节阈值,会影响预测的结果类别,最终会影响精准率和召回率。在直觉上,精准率和召回率在一定程度上会呈现负相关关系——漏杀低了,容易过杀;过杀低了,又容易漏杀。我们想把这个关系量化表示,一个简单办法就是绘制P-R曲线。

示例

假设我们有一个二元分类问题,我们对每一行样本都进行了预测,并给出了预测得分:

序号 真实值 预测分数
1    0    0.1
2    1    0.4
3    1    0.35
4    0    0.8
5    1    0.9
6    0    0.2
7    1    0.5
8    0    0.3
9    0    0.6
10    1    0.85

既然要调节阈值来观测输出,不妨把上面各行先按预测得分来排列:

阅读剩余部分

今天在使用sktime库的时候遇到了解码失败的问题:

from sktime.datasets import load_airline

报错类似于:

{
    "name": "UnicodeDecodeError",
    "message": "'gbk' codec can't decode byte 0xb8 in position 4507: illegal multibyte sequence",
    "stack": "---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
Cell In[19], line 1
----> 1 from sktime.datasets import load_airline

显然这是个编码问题,我第一时间检查了VSCode中的默认编码,确保是utf-8无误。

于是我怀疑大概率是程序编码出了问题,试了修改默认编码:

阅读剩余部分

DictVectorizer

考虑一个两列构成的输入数据:

citytemperature
New York70
San Francisco50

可以看到,这里第一列是一个类别特征,第二列是一个实数型特征。前面我们通过LabelBinarizer对标签进行one-hot编码,这里我们可以通过DictVectorizer实现对输入特征的自动one-hot编码:

阅读剩余部分

one-hot 编码

对于一个分类问题,假设有N种类别,不妨记作 [ "A类", "B类", "C类", "D类", ...],有时候我们可能倾向于直接用数字编号表示,表示成 [ 0, 1, 2, 3, ...]。但是问题是数字编号预设了一个不应该存在的限制:我们在任意两个类别之间强加了比较关系,比如"D类"(类别3) > "B类"(类别2)。一方面,这种类别之间的大小比较毫无意义。另一方面,这种顺序会导致距离测算错误。

假设某个情况是"D类"(类别3) 。在第一种情况下,它被我们错误的分类成了"A类"(类别0) ,这时候二者距离是 3 - 0 =3;在第二种情况下,它被我们错误分类成了"C类"(类别2) ,这时候二者的距离是 3 -2 = 1。这表明,在这种机制下,把"D类"归类成"C类"这种情况,会被当做优于把"D类"归类成"A类"这种情况,这会极大程度上误导机器学习朝着更优的方向进行。

一种解决办法是采用one-hot编码。拿上面的例子来说:

  • A 类被编码成 [1, 0, 0, 0, ...]
  • B 类被编码成 [0, 1, 0, 0, ...]
  • C 类被编码成 [0, 0, 1, 0, ...]
  • D 类被编码成 [0, 0, 0, 1, ...]
  • ...

由于这些向量都是单位正交向量,任意两个向量之间的余弦距离都是一样的,这表明类别归类错误程度("距离")在这种机制下可以被正确计算。

阅读剩余部分