在nlp的数据预处理中,我们通常需要根据原始数据集做出如题目所示的三种结构。但是新手(我自己)常常会感到混乱,因此特意整理一下

1.词库

词库是最先需要处理出的数据形式,即将原数据集按空格分词或者使用分词的包如jieba等,将原始文章分割成一个个词语所表示的list,一般是一维或者二维的,二维词库往往是以行为第一维。
比如下面我们对ptb数据集进行处理产生对应的词库

with open('ptb/ptb.train.txt') as f:
    raw_txt = f.read()
sentences = [line.split() for line in raw_txt.split('\n')]

或者在一些情况下,我们只需要统计出现过的词汇,使用set结构体进行处理即可
wordSet = set([word for word in line for line in sentence])

2.词典

在词典中我们主要做的工作是,统计词频,按照频率进行排序,排序主要是为了让频率高的词有较小的编号,若某些单词出现的次数低于某个界限值,如10,我们通常将它转化为特殊词元如''等,然后对单词进行token到id的转化和id到token的转化。下面的代码来自李沐老师的d2l库,相当完美的实现了字典的功能,可以直接使用vocab[token]来查询到id,使用len函数查询长度等。

#构建词表
class Vocab:
    def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
        if tokens is None:
            tokens = []
        if reserved_tokens is None:
            reserved_tokens = []
        #将二维token转化为一维列表
        if(isinstance(tokens[0], list)):
            tokens = [token for line in tokens for token in line]
        counter = collections.Counter(tokens)
        self._token_freqs = sorted(counter.items(), key=lambda x:x[1],reverse = True)#降序
        self.idx_to_token = ['<unk>'] + reserved_tokens
        #先对特定的token进行编号
        self.token_to_idx = {token : idx for idx, token in enumerate(self.idx_to_token)}
        #接着对词语token进行编号
        for token, freq in self._token_freqs:
            if freq < min_freq:
                break
            if token not in self.token_to_idx:
                self.idx_to_token.append(token)
                self.token_to_idx[token] = len(self.idx_to_token) - 1
    
    def __len__(self):
        return len(self.idx_to_token)

    def __getitem__(self, tokens):
        #如果tokens不是列表或元组,就直接查询token,若不存在返回unk,此函数可以直接数组形态访问获得id
        if not isinstance(tokens, (list, tuple)):
            return self.token_to_idx.get(tokens, self.unk)
        return [self.__getitem__(token) for token in tokens]
    #装饰器,使得可以不带括号的访问函数
    @property
    def unk(self):
        return 0
    
    @property
    def token_freqs(self):
        return self._token_freqs

3.语料库corpus

在词典生成完成后,我们就可以将词库中的一个个单词转化为对应的标号,比如原文是['I','LIKE', 'YOU']就可以转化为19, 90, 127,我们使用语料库来进行训练。值得注意的是,在生成语料库之前,由于某些高频词或者停用词的存在(停用词通常也是高频词),提前使用停用词表(github上有资源)将停用词删掉可以有效帮助训练,因为停用词没有意义;或者使用下采样方法,将高频率词按一定概率删除,频率越高,则被删除概率越高,通常使用如下的概率公式:

\[P(w_i) = max(1-\sqrt{\frac{t}{f(w_i)}}, 0) \]

即单词\(w_i\)被删除的概率。其中t是一个常数,实验取1e-4,\(f(w_i)\)是该单词词频,词频大于t时,才有可能被删除。代码:

# 如果在下采样期间保留词元,则返回True
 def keep(token):
     return(random.uniform(0, 1) <
         math.sqrt(1e-4 / counter[token] * num_tokens))

最后正式转化语料库就很简单了
corpus = [vocab[line] for line in sentences] 还是需要通过多次的实践编写代码才能熟练。
代码都是参考李沐老师的DIVE INTO DEEP LEARNING.

石中之火,即使无可燃烧之物,也要尽力发亮