pytorch(8-2) 文本语言处理 拆分成字符统计词频并从高到底分配ID

发布时间 2023-10-09 21:24:43作者: MKT-porter

https://zh.d2l.ai/chapter_recurrent-neural-networks/language-models-and-dataset.html

 

 

import collections
import re
from d2l import torch as d2l

#@save
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',
                                '090b5e7e70c295757f55df93cb0a180b9691891a')

def read_time_machine():  #@save
    """将时间机器数据集加载到文本行的列表中"""
    with open(d2l.download('time_machine'), 'r') as f:
        lines = f.readlines()
        #re.sub('[^A-Za-z]+', ' ', line):将line字符串中的 连续多个非字母的字符 变成 空格
        #re.sub('[^A-Za-z]+', ' ', 'I\n')
        #Out[6]: 'I '
    return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]

#lines = read_time_machine()
#print(f'# 文本总行数: {len(lines)}')
#print(lines[0])
#print(lines[10])


def tokenize(lines, token='word'):  #@save
    """将文本行拆分为单词或字符词元"""
    if token == 'word': # 按照单词拆分
        return [line.split() for line in lines]
    elif token == 'char': #按照字符拆分
        return [list(line) for line in lines]
    else:
        print('错误:未知词元类型:' + token)

#tokens = tokenize(lines)
# for i in range(16):
#     print("tokens",i,tokens[i])



def count_corpus(tokens):#
    """统计词元的频率"""
    # 这里的tokens是1D列表或2D列表
    if len(tokens) == 0 or isinstance(tokens[0], list):
        # 将词元列表展平成一个列表
        tokens1=[]
        for line in tokens:
            #print("line",line)
            for token in line:
                #print("token",token)
                tokens1.append(token)
        tokens=tokens1
        #tokens = [token for line in tokens for token in line]
        #print(tokens.shape)
    #最主要的作用是计算“可迭代序列”中各个元素(element)的数量
    '''
    #对字符串作用
    temp=Counter('aabbcdeffgg')
    print(temp)   #Counter({'a': 2, 'b': 2, 'f': 2, 'g': 2, 'c': 1, 'd': 1, 'e': 1})
    '''
    return collections.Counter(tokens)

#count_corpus(tokens)

#词表
#构建一个字典,通常也叫做词表(vocabulary),用来将字符串类型的词元映射到从开始的数字索引中.
# 我们先将训练集中的所有文档合并在一起,对它们的唯一词元进行统计, 得到的统计结果称之为语料(corpus)。 
# 然后根据每个唯一词元的出现频率,为其分配一个数字索引。 很少出现的词元通常被移除,这可以降低复杂性。
# 另外,语料库中不存在或已删除的任何词元都将映射到一个特定的未知词元“<unk>”。 
# 我们可以选择增加一个列表,用于保存那些被保留的词元, 例如:填充词元(“<pad>”); 序列开始词元(“<bos>”); 序列结束词元
class Vocab:  #@save
    """文本词表"""
    def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
        if tokens is None:
            tokens = []
        if reserved_tokens is None:
            #reserved_tokens = ["a","b"]
            reserved_tokens = []
        # 按出现频率排序
        '''
        #对字符串作用
        temp=Counter('aabbcdeffgg')
        print(temp)   #Counter({'a': 2, 'b': 2, 'f': 2, 'g': 2, 'c': 1, 'd': 1, 'e': 1})
        '''
        counter = count_corpus(tokens)
        print("词频统计结果",counter)
        '''
        词频统计结果 Counter({' ': 29927, 'e': 17838, 't': 13515, 'a': 11704, 
        'i': 10138, 'n': 9917, 'o': 9758, 's': 8486, 
        'h': 8257, 'r': 7674, 'd': 6337, 'l': 6146, 'm': 4043, 
        'u': 3805, 'c': 3424, 'f': 3354, 'w': 3225, 'g': 3075, 
        'y': 2679, 'p': 2427, 'b': 1897, 'v': 1295, 'k': 1087,
        'x': 236, 'z': 144, 'j': 97, 'q': 95})
        '''

        # 排序 已知词汇 reverse=True 从高到低
        self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
                                   reverse=True)
        # 未知词元的索引为0
        self.idx_to_token = ['<unk>'] + reserved_tokens
        #{'<unk>': 0}
        #{'<unk>': 0, 'a': 1, 'b': 2}
        self.token_to_idx = {token: idx
                             for idx, token in enumerate(self.idx_to_token)}
        #print(self.token_to_idx )
        
        '''
        1最开头是 未知词元 假设是
         词源   idx_to_token['<unk>', ‘a’ ,'b']
         词源ID token_to_idx[0,1,2]
        2将文本中统计出来的有效词源 加进去 例如 b1
          2-1 剔除小于阈值的
          2-2 加入新词源
              频率 从高到低
              词源   idx_to_token['<unk>', ‘a’ ,'b','b1']
              词源ID token_to_idx[0,1,2,3]
        '''
        # 最开头是 未知词元 假设是 idx_to_token['<unk>', ‘a’ ,'b']
        # 将文本中统计出来的有效词源 加进去 例如 b1

        #
        for token, freq in self._token_freqs:
            # 从高到低访问
            if freq < min_freq:# 剔除小于阈值的词
                break
            if token not in self.token_to_idx:# 未知词汇剔除
                # 未知词元 添加 单词进入列表
                # '<unk>' a b 加入 d1
                self.idx_to_token.append(token)
                # token_to_idx【'd1'】= 4-1=3 
                self.token_to_idx[token] = len(self.idx_to_token) - 1 #从0开始 添加

    def __len__(self):
        return len(self.idx_to_token)

    def __getitem__(self, tokens):
        if not isinstance(tokens, (list, tuple)):
            return self.token_to_idx.get(tokens, self.unk)
        return [self.__getitem__(token) for token in tokens]

    def to_tokens(self, indices):
        if not isinstance(indices, (list, tuple)):
            return self.idx_to_token[indices]
        return [self.idx_to_token[index] for index in indices]

    @property
    def unk(self):  # 未知词元的索引为0
        return 0

    @property
    def token_freqs(self):
        return self._token_freqs

# 整合所有功能
def load_corpus_time_machine(max_tokens=-1):  #@save
    """返回时光机器数据集的词元索引列表和词表"""
    # 1 读取所有文本 逐行存
    lines = read_time_machine()
    # 2 
    # 2-1将所有行的字母拆出来,汇总到一个列表里面
    # 2-2将所有字母,对每一个字母统计词频
    tokens = tokenize(lines, 'char')
    # print("tokens",tokens)
    '''
    tokens= [['a', 'b'],['c' 'd'],...]
    '''
    # 3 获取词汇列表
    '''
        (频率   从高到底 前面是未知词源)
        词源   idx_to_token['<unk>', ‘a’ ,'b','b1']
        词源ID token_to_idx[0,1,2,3]
    '''
    vocab = Vocab(tokens)

    # 因为时光机器数据集中的每个文本行不一定是一个句子或一个段落,
    # 所以将所有文本行拆分成一个个字符,然后展平到一个列表中
    corpus=[]
    '''
    tokens= [['a', 'b'],['c' 'd'],...]
    '''
    for line in tokens:
        for token in line:
            corpus.append(vocab[token]) # vocab[token] 字母’x‘的编码ID

    #corpus = [vocab[token] for line in tokens for token in line]
    if max_tokens > 0:
        corpus = corpus[:max_tokens]
    #corpus 全文变成单独字符的列表结合
    #vocab  vocab[token] 字母’x‘的编码ID(非次数 ID就是从高到低按照次数排列到的) 集合 {“a”,1,"b","2",...}
    return corpus, vocab

#===============================测试1 按照 单个字符拆分==============================================
#在使用上述函数时,我们将所有功能打包到load_corpus_time_machine函数中, 
#该函数返回corpus(词元索引列表)和vocab(时光机器语料库的词表)。
'''
为了简化后面章节中的训练,我们使用字符(而不是单词)实现文本词元化;
时光机器数据集中的每个文本行不一定是一个句子或一个段落,还可能是一个单词,因此返回的corpus仅处理为单个列表,而不是使用多词元列表构成的一个列表。
'''
corpus, vocab = load_corpus_time_machine()
#170580 28   一共有corpus 170580个字符,其中重复字符的有vocab 28个(ABCD-FG 28个英文字符)
print(len(corpus), len(vocab))
'''
词频统计结果 Counter({' ': 29927, 'e': 17838, 't': 13515, 'a': 11704, 
'i': 10138, 'n': 9917, 'o': 9758, 's': 8486, 
'h': 8257, 'r': 7674, 'd': 6337, 'l': 6146, 'm': 4043, 
'u': 3805, 'c': 3424, 'f': 3354, 'w': 3225, 'g': 3075, 
'y': 2679, 'p': 2427, 'b': 1897, 'v': 1295, 'k': 1087,
 'x': 236, 'z': 144, 'j': 97, 'q': 95})
'''