# 精读《设计机器学习系统》-ch04: 训练数据

不同于 Chapter03 从系统的角度来处理数据，这一章从数据科学的视角来处理数据。这章的标题是“training Data”，而非“training dataset”，因为 **数据集(dataset)** 意味着有限(finite)和固定(stationary), 而现实生产环境中的 **数据(data)** 通常是 **无限** 并且 **不固定** 的。

# 抽样

抽样方法在 ML 项目的生命周期中无处不在，在这一节中，我们使用生成训练数据作为例子。

那为什么需要抽样？直接使用全部数据不可以吗？

首先，在现实世界中，并不是所有数据都可以被访问，模型训练只能使用部分数据集。 其次，处理你能够访问到的所有数据是不现实的，需要大量的时间和资源。好的抽样方法可以帮助你以更低的成本和更快的时间来完成任务。

理解不同的抽样方法，可以帮助我们避免潜在的抽样偏差(biases), 也可以帮助我们提升抽样的速度。

抽样方式主要分为两类: **非概率抽样(nonprobability sampling)** 和 **随机抽样(random sampling)**

## 非概率抽样(nonprobability sampling)

非概率抽样，人如其名，选择数据不基于任何概率标准，有以下几类:

*   任意抽样(Convenience sampling) 选择可以用的数据就好。这种方法很受环境，因为方便。
    
*   滚雪球抽样(Snowball sampling) 未来的样本是根据现有的样本来选择的。例如，要在没有问Twitter数据库的情况下抓取合法的Twitter账户，你先从少量的账户开始，然后抓取他们关注的所有账户，以此类推。
    
*   判断抽样(Judgment sampling) 专家决定包括哪些样本。
    
*   配额抽样(Quota sampling) 根据某些数据分段的配额来选择样本，不需要任何随机化。举个例子: 在做调查时，你可能希望每个年龄组有100个结果：30岁以下，30至60岁之间，60岁以上，而不考虑实际年龄分布。
    

通过非概率抽样选择的样本，并不能有代表性的反应真实世界的数据分布，它们充满了由于选择造成的偏差。然而，处于成本和便捷性的考虑，非概率性抽样在某些场景下依旧被采用。以下是具体的应用例子。

*   **语言模型** 的训练通常不是用代表所有可能文本的数据，而是用容易收集到的数据--维基百科、Common Crawl、Reddit。
    
*   很多 **情感分析** 模型基于 IMBD 和 Amazon 评论，但这些评论并不能代表那些不能上网的人或不愿意把评论放在网上的人。
    
*   训练 **自动驾驶**，最初的自动驾驶数据是从亚利桑那州的凤凰城（因为其法规宽松），以及加利福尼亚州的湾区（因为许多制造自动驾驶汽车的公司都在这里）。 但这两个地方都阳光明媚。当2016年，自动驾驶业务在阴雨天较多的Kirkland, Washington 测试时，对于阴雨天气下的自动驾驶，效果并不好。
    

## 简单随机抽样(Simple Random Sampling)

[总体(statistical population)](https://en.wikipedia.org/wiki/Statistical_population)中的所有样本都有均等的机会被选择。

*   优点: 容易实现
    
*   缺点: 对于某些稀少的种类，很难被选择。
    

## 分层抽样(Stratified Sampling)

为了避免简单随机抽样的缺点，我们将总体数据划分到不同的群体中，对于群体单独进行选择。无论哪个群里多么稀少，都保证可以被选择。

*   缺点: 在某些情况下是不可行的，假如对多分类任务和多标签任务。
    

## 加权抽样(Weighted Sampling)

针对每一个样本都会被赋予一个权重，来决定被选择的可能性。

*   优点: 可以得到领域知识的加成。当你知道哪部分数据可以为模型带来更好的效果时，可以增加这部分数据的被选择的机会。
    
*   当你拥有的数据与真实数据相比来自不同的分布时，这也有助于处理这种情况。 在 python 中的 `random` 就可以帮助我们实现这一点。
    

```python
# Choose two items from the list such that 1, 2, 3, 4 each has
# 20% chance of being selected, while 100 and 1000 each have only 10% chance. import random
random.choices(population=[1, 2, 3, 4, 100, 1000], weights=[0.2, 0.2, 0.2, 0.2, 0.1, 0.1],k=2)
# This is equivalent to the following
random.choices(population=[1, 1, 2, 2, 3, 3, 4, 4, 100, 1000],k=2)
```

在ML中还有一个和加权抽样(weighted sampling)相似的概念: 样本权重(ample weights)。加权抽样是用来选择样本来训练你的模型，而样本权重是用来给训练样本分配 "权重 "或 "重要性"。权重较高的样本对损失函数的影响更大。改变样本权重可以大大改变你的模型的决策边界。如下图

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670636555403/eobMb4QDP.png align="center")

## 蓄水池抽样(Reservoir Sampling)

蓄水池抽样用来处理流式数据，在生产环境中经常会用到。

假设你想从源源不断的推文中选取特定数量 `k` 个, 来分析和训练模型，但你不知道推文的数量，也无法保存所有推文在内存中。但你要确保:

1.  所有推文都有 **同样的可能性** 被选择。
    
2.  可以 **在任何时间停止** 抽样，并且所有推文被抽样的可能性相同。
    

蓄水池抽样就是可以解决这一类问题的 **在线算法**。 使用了一个代表蓄水池的**数组**，共有三步:

1.  选择最初的`k`个样本进入蓄水池.
    
2.  对于后面的第`n`个样本，生成一个随机数`i`, 1 &lt;= `i` &lt;= `n`.
    
3.  如果 1 &lt;= `i` &lt;= `k`, 那么则替换第`i`个元素。否则，就什么也不做。 可以[证明](https://mrfzh.github.io/2019/08/31/%E8%93%84%E6%B0%B4%E6%B1%A0%E6%8A%BD%E6%A0%B7%E7%AE%97%E6%B3%95%EF%BC%88Reservoir-Sampling%EF%BC%89/), 每个在蓄水池中的样本，都有`k/n`的概率被选中。
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1670636577771/fCJm_RKjD.png align="center")
    

对应的有一道 [leetcode 382](https://leetcode.com/problems/linked-list-random-node/)

在大规模集群下，有对应的 [Parallel Streaming Random Sampling](https://arxiv.org/abs/1906.04120)

## 重要性抽样(Importance Sampling)

重要性抽样是最重要的抽样方法之一，不仅仅是在ML中。它允许我们在只能获得另一个分布的情况下从一个分布中取样。

# 标注

ML模型的优劣很大程度上取决用于训练的标注数据的数量和质量。

## 手动标注

在生产环境中，获得手工标注的标签是非常困难的，有以下几个原因:

1.  手工标注数据的成本很高，尤其是在需要领域知识的情况下。比方说需要为X光胸片做标注，需要寻找专业的医生的帮助，并且他们的时间是非常有限和昂贵的。
    
2.  手动标注数据对数据隐私构成了威胁。
    
3.  手动标注数据很慢。例如，在语音上准确转录语音语料可能需要比语料时间长400倍的时间。缓慢的标注会导致缓慢的迭代速度，使你的模型对不断变化的环境和要求的适应性降低。
    

### 标签的多重性

通常，为了标注足够多的数据，公司会使用多个来源的数据，并且依靠拥有不同层次经验的注释者。这样就会造成一个问题: 这些不同的数据源和注释者也有不同的准确性。

要求的领域专业知识水平越高，注释分歧的可能性就越大。

为了尽量减少注释者之间的分歧，首先要有一个明确的问题定义。其次，你需要将该定义纳入注释者的培训中，以确保所有注释者都能理解这些规则。

### 数据脉络(Data lineage)

跟踪你的每个数据样本的来源以及它的标签是一个很好的做法，这种技术被称为 **数据脉络(Data lineage)** 。数据脉络有助于你在数据中标明潜在的偏差，并调试你的模型。
