# 1.2.文本数据

## 1. 元素袋：自然文本转换成扁平向量

    结果的可解释性是模型处理的目的，可解释的特征和模型更加易于调试。对于文本数据，我们可以从一个单词的数量统计列表开始，这称为**词袋**（Bag-Of-Word，BoW），——此时这个单词数量列表不能从语义层面表示它很有意义，但是它在文本中出现了多次，远高于其他词汇。这种方法对下边两种任务已经够用了：

* 文档分类
* 信息提取

### 1.1. 词袋

    词袋特征化会将一篇文档转换成一个计数向量（`n`个数值的集合），向量中包含了词汇表中所有可能出现的单词。该向量是“扁平”的，是因为它不包含原始文本中的任何结构（只有计数）。

1. 向量中单词的顺序不重要。
2. 向量中不包含层次结构：如`animal`包括`dog, cat, raven`等。
3. 所有单词在向量中都是平等元素（无权重）。

    词袋表示中重要的是特征空间中的**数据分布**，如果词汇表中有`n`个单词，那么一篇文档就是`n`维空间中的一个点（**文档向量**）——每个单词都是该文档向量的一个**维度**。使用坐标轴表示时（可视化只能限于**二维和三维**）：

1. **词袋**：在特征空间中查看数据向量：
   1. 坐标轴表示单词——即特征。
   2. 空间中的点就是**数据**（文本文档）。
2. **文档袋**：在数据空间中查看特征向量（词贷的矩阵转置）：
   1. 坐标轴表示文档——即数据。
   2. 空间中的点就是**特征**。

    词袋是一种简单而有效的启发式方法，但和正确语义文本相差甚远。

### 1.2. n元词袋

    n元词贷（Bad-Of-n-grams）是词袋的一种自然扩展：

1. `N-Gram`（n元词）是n个标记（token）组成的序列。
2. `1-Gram`就是一个单词（中文中的一个字），又称为一元词（Unigram）。
3. 理论上，有$$k$$个不同的单词，就会有$$k^2$$个不同的`2-Gram`二元词，由于不是每个单词都可以跟在另外一个单词后边，实际`n-gram`比单词多很多。
4. `n`元词袋模型实际是一个更大更稀疏的特征空间。

> 例如：Emma knocked on the door，使用N-Gram（结果是2-Gram）计算会生成： Emma knocked、knocked on、on the、the door

    示例，1-Gram, 2-Gram, 3-Gram：

```python
# book/feature/source/0301.ngram.py
import pandas as pd
import json
from sklearn.feature_extraction.text import CountVectorizer

# 加载10000条评论
f = open('data/yelp_academic_dataset_review.json')
js = []
for i in range(1000):
    js.append(json.loads(f.readline()))
f.close()
review_df = pd.DataFrame(js)
# 1-Gram
gram = CountVectorizer(token_pattern='(?u)\\b\\w+\\b')
# 2-Gram
bigram = CountVectorizer(token_pattern='(?u)\\b\\w+\\b',ngram_range=(2,2))
# 3-Gram
tigram = CountVectorizer(token_pattern='(?u)\\b\\w+\\b',ngram_range=(3,3))

# 查看词汇大小
gram.fit(review_df['text'])
# 旧版代码 get_feature_names
# 警告：FutureWarning: Function get_feature_names is deprecated; get_feature_names
# is deprecated in 1.0 and will be removed in 1.2.
# Please use get_feature_names_out instead. warnings.warn(msg, category=FutureWarning)
gramW = gram.get_feature_names_out()
bigram.fit(review_df['text'])
bigramW = bigram.get_feature_names_out()
tigram.fit(review_df['text'])
tigramW = tigram.get_feature_names_out()
print(len(gramW), len(bigramW), len(tigramW))
```

## 2. 使用过滤获取清洁特征

    如何通过单词将文本中的**信号**和**噪声**准确区分？

### 2.1. 停用词

    分类和提取通常不要求对文本进行深入理解，**停用词**是指在信息检索中，为节省存储空间和提高搜索效率，在NLP之前或之后会自动过滤掉某些字或词，这些字或词通常会被称为停用词（Stop Words）。

> 例如：Emma knocked on the door中的on、the。

1. 在分类这样的粗粒度任务中，代词、冠词、介词没有什么价值。
2. 在情感分析中，情况截然不同，它需要对语义有更深入的理解。

### 2.2. 基于频率的过滤

    停用词列表是剔除无意义单词的方法，还可以使用统计方法找出某些实际意义的词。

#### 2.2.1. 高频词

    频率统计是一种很强大的过滤技术，既可以过滤语料库专用的单词，也可以过滤通用的停用词；典型的停用词列表会包含通用的停用词，但不包括语料库专用的停用词。检查频率出现最高的单词：此处频率是表示包含了这个词语的文档（点评）数，而不是它在一篇文档中出现的次数。

#### 2.2.2. 罕见词

    罕见词可能是真正的生僻词、也可能是拼写错误的普通词。罕见次不仅无法做预测的凭据，还会增加计算上的开销。在`Yelp`点评案例中，`60%`以上的词都是罕见词——这就是**重尾分布**。

    **垃圾箱特征**：在分析语料库时将罕见词的数量累加到一个特殊垃圾箱，作为附加特征。而垃圾箱特征在语料库统计之前系统是不知的，所以这个特征只能用于后处理阶段进行收集。

#### 2.2.3. 词干提取

    同一个词的不同形态会视为不同词的计数，如：`flower`和`flowers`在技术上是两个不同标记；`swimmer`和`swimming`也是不同的标记；所以我们更期望不同变体能映射为同一个单词。

    **词干提取**是一种将每个单词转换为语言学中的基本词干形式的NLP技术，它可以基于语言学规则，也可以基于统计规则，若算法综合词性标注和语言规则，这种处理过程就是**词形还原**。

## 3. 意义单位

### 3.1. 解析与分词

    如果字符串不只包含纯文本，解析就是必须的。

    分词程序要知道哪些字符表示一个标记已经结束另外一个标记已经开始，空格通常是非常好的分隔符，标点符号也一样。有时候分析需要在句子上而不是整个文档上进行：

1. `n-gram`是单词概念推广，不可超过句子的边界。
2. `word2vec`是工作在句子或段落上的，此时要执行转换：`文档 -> 句子 -> 单词`。

### 3.2. 搭配提取/短语检测

    NLP处理中，我们更希望从语义层面去理解，而不是`n`元词语。**搭配**：有用的短语被称为搭配（collocation），它是一种表达方式，由两个或两个以上的单词组成，并对应于某种约定俗成的事物说明。搭配能表达的意义比组成它的各个单词的综合都多。

    如何从文本中提取搭配呢？

1. 基于频率的方法
2. 用于搭配提取的假设检验
3. 文本分块和词性标注
