DL专栏10-Token构建词表


torchtext.vocab分词

引言

在建模文本语料的相关模型时,避免不了的一个操作就是构建词表。通常来说一个常用的流程就是:①对原始语料进行分词或者是分字,即tokenize;②利用Counter来对tokenize后的结果进行计数处理,并去掉出现频率小于某个阈值的词;③根据计数结果分别建立一个字典和一个列表用于将词转换为索引以及将索引转换为词。

虽然这部分代码说多不多,但是我们还可以通过一个更方便的方法来直接根据文本构建一个词表。

构建词表

在正式构建词表之前,我们需要定义一个tokenize方法,即如何对原始文本进行切分。例如中文是分词还是分字等。

定义tokenize

如果是对类似英文这样的语料进行处理,那就是直接按空格切分即可。但是需要注意的是要把其中的逗号和句号也给分割出来。因此,这部分代码可以根据如下方式进行实现:

def my_tokenizer(s):
    """
    返回tokenize后的结果
    """
    s = s.replace(',', " ,").replace(".", " .")
    return s.split()

可以看到,其实也非常简单。例如对于如下文本来说

Two young, White males are outside near many bushes.

其tokenize后的结果为:

['Two', 'young', ',', 'White', 'males', 'are', 'outside', 'near', 'many', 'bushes', '.']

当然,如果是中文类的语料,那么可以通过如下方式进行实现:

import jieba
def tokenizer(s,word = False):
    if word:
        r = [w for w in s]
    else:
        s = jieba.cut(s, cut_all=False)
        r = " ".join(s).split()
    return r

其中word=True表示按字进行切分。例如对于如下文本来说

问君能有几多愁,恰似一江春水向东流。

其tokenize后的结果为:

# 按词切分
['问君', '能', '有', '几多', '愁', ',', '恰似', '一江春水向东流', '。']

# 按字切分
['问', '君', '能', '有', '几', '多', '愁', ',', '恰', '似', '一', '江', '春', '水', '向', '东', '流', '。']

建立词表

在介绍完tokenize的实现方法后,我们就可以正式通过torchtext.vocab中的Vocab方法来构建词典了,代码如下:

def build_vocab(tokenizer, filepath, word, min_freq, specials=None):
    if specials is None:
        specials = ['<unk>', '<pad>', '<bos>', '<eos>']
    counter = Counter()
    with open(filepath, encoding='utf8') as f:
        for string_ in f:
            counter.update(tokenizer(string_.strip(), word))
    return Vocab(counter, min_freq=min_freq, specials=specials)

在上述代码中,第3行代码用来指定特殊的字符;第5-7行代码用来遍历文件中的每一个样本(每行一个)并进行tokenize和计数,后续会对counter.update进行介绍;第8行则是返回最后得到词典。

例如在根据如下3行样本进行词表构建

问君能有几多愁,恰似一江春水向东流。
年年岁岁花相似,岁岁年年人不同。
人面不知何处去,桃花依旧笑春风。

将会得到如下所示的结果:

if __name__ == '__main__':
    filepath = './data.txt'
    vocab = build_vocab(tokenizer, filepath, word=True, min_freq=1,
                        specials=['<unk>', '<pad>', '<bos>', '<eos>'])
    print(vocab.freqs)  # 得到一个字典,返回语料中每个单词所出现的频率;
    # Counter({'年': 4, '岁': 4, ',': 3, '。': 3, '似': 2, '春': 2, '花': 2,...})
    print(vocab.itos)  # 得到一个列表,返回词表中的每一个词;
    # ['<unk>', '<pad>', '<bos>', '<eos>', '岁', '年', '。', ',', '不', '人',...]
    print(vocab.itos[2])  # 通过索引返回得到词表中对应的词;
    # <bos>
    print(vocab.stoi)  # 得到一个字典,返回词表中每个词的索引;
    # {'<unk>': 0, '<pad>': 1, '<bos>': 2, '<eos>': 3, '岁': 4, '年': 5,...}
    print(vocab.stoi['岁'])  # 通过单词返回得到词表中对应的索引
    print(vocab['岁']) # 同上
    # 4
    print(len(vocab))# 返回词表长度
    # 39

从上述示例可以看出,在得到构建好的词表类之后,只需要通过对应的方法便可以得到相应的结果。

介绍完词表的构建后,这里再来顺便介绍一下如何合并两个字典,也就是上面counter.update的用法。

合并字典与列表

合并字典

在某些场景下,我们需要对两个(多个)字典进行合并。例如需要将如下两个字典进行合并:

dict1 = {"a": 2, "b": 3, "c": 5}
dict2 = {"a": 1, "c": 3, "d": 8}

且合并后的结果为:

{'c': 8, 'd': 8, 'a': 3, 'b': 3}

那么应该如何操作呢?由于两个字典并不能够直接进行相加,因此首先需要将各个字典转换为Counter类,然后再进行相加。具体代码如下:

from collections import Counter
dict1 = {"a": 2, "b": 3, "c": 5}
dict2 = {"a": 1, "c": 3, "d": 8}
result = Counter({})
for item in [dict1,dict2]:
   result += Counter(item)
print(result) # Counter({'c': 8, 'd': 8, 'a': 3, 'b': 3})

当然,如果只是两个字典相加,那么只用一行代码即可:

result = Counter(dict1) + Counter(dict2)

合并列表

在某些场景下,我们需要对两个(多个)列表进行合并,从而得到一个包含有各个元素出现频率的字典。例如需要将如下两个列表:

a = ["天", "之", "道", "损", "有", "余", "而", "补", "不", "足"]
b = ["人", "之", "道", "损", "不", "足", "而", "补", "有", "余"]

合并为:

Counter({'之': 2, '道': 2, '损': 2, '有': 2, '余': 2, '而': 2, '补': 2, '不': 2, '足': 2, '天': 1, '人': 1})

那么只需要通过如下代码即可实现:

from collections import Counter
counter = Counter()
for item in [a, b]:
    counter.update(item)
print(counter)

当然,除了在构建词表的时候可以使用这种方法,在对列表进行重复元素查找或者统计时同样可以使用本方法。

总结

首先介绍如何对文本进行tokenize操作;然后介绍了如何通过torchtext.vocab中的Vocab方法来构建词典;最后顺便介绍了如何通过Counter来合并字典与列表。


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录