原始文本 → 规范化文本 → token 序列 → 词表(token↔id) → 数字序列(id 序列)
核心:构建词表(Vocab)
词表是文本数字化的关键,它维护两种映射:
-
token → index(token_to_idx):把词映射成数字 id
-
index → token(idx_to_token):把数字 id 还原成词
构建词表时,一般要处理三个关键机制:
- 未知词
<unk>
现实中一定会遇到训练时没见过的词。因此词表会预留一个特殊 token:<unk>,代表"未知词"。
当查不到某个 token 时,用 <unk> 的 id 代替。常见约定:<unk> 的 id 是 0。
- 保留 token(reserved_tokens)
有些 token 是任务必需的特殊标记,比如:
-
<pad>:填充到固定长度 -
<bos>:句子开始 -
<eos>:句子结束 -
<mask>:掩码(BERT 类模型)
这些 token 通常放在词表最前面(紧跟 <unk>),确保它们有稳定的 id。
- 最小频次过滤(min_freq)
文本里有大量低频词,如果全部纳入词表:
-
词表会非常大,占内存、训练慢
-
很多低频词对模型帮助小,但会引入稀疏噪声
因此常设置 min_freq,只保留出现次数 ≥ min_freq 的 token。低频词统一用 <unk> 替代。
2.代码
def read_time_machine(payh='D:/PycharmDocument/limu/data/timemachine.txt'):
with open(payh,'r',encoding='utf-8') as f:
lines=f.readlines()
return [re.sub('[^A-Za-z]+',' ',line).strip().lower() for line in lines]
lines=read_time_machine()
# print(lines[10])
# print(len(lines))
def tokenize(lines,token='word'):
if token=='word':
return [line.split() for line in lines]
elif token=='char':
return [list(line) for line in lines]
else:
print('wrong token')
tokens=tokenize(lines,'word')
# for i in range(11):
# print(tokens[i])

#词汇表,将字符串映射到数字索引
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=[]
counter=count_cropus(tokens)
self.token_freqs=sorted(counter.items(),key=lambda x:x[1],reverse=True)#讲词频排序
self.unk,uniq_tokens=0,['<unk>']+reserved_tokens#初始化,uniq_tokens起始设置为unk和reserved_tokens
uniq_tokens+=[token for token,freq in self.token_freqs#讲词频高且不在reserved_tokens里的token依次加载,此时为全部的token
if freq>=min_freq and token not in reserved_tokens]
self.idx_to_token,self.token_to_idx=[],dict()
for token in uniq_tokens:
self.idx_to_token.append(token)
self.token_to_idx[token]=len(self.idx_to_token)-1#构建词汇-数字索引,一一对应
def __len__(self):
return len(self.token_to_idx)
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):#获取char
if not isinstance(indices,(list,tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]
def count_cropus(tokens):
if len(tokens)==0 or isinstance(tokens[0],list):
tokens=[token for line in tokens for token in line]
return collections.Counter(tokens)
vocab=Vocab(tokens)
# print(list(vocab.token_to_idx.items())[:10])

for i in [0,10]:
print(tokens[i])
print(vocab[tokens[i]])

def load_crops_time_machine(max_tokens=-1):
lines=read_time_machine()
tokens=tokenize(lines,'char')
vocab=Vocab(tokens)
corpus=[vocab[token] for line in tokens for token in line]
if max_tokens>0:
corpus=corpus[:max_tokens]
return corpus,vocab
corpus,vocab=load_crops_time_machine()
print(len(corpus))
print(len(vocab))