Tokenizer分词

分词的一般流程

在使用神经网络处理自然语言处理任务时,我们首先需要对数据进行预处理,将数据从字符串转换为神经网络可以接受的格式,一般会分为如下几步:

(1)分词:使用分词器对文本数据进行分词(字、字词)得到token;

(2)构建词典:根据数据集分词的结果,构建词典映射,形成一个vocabulary dict用于存放每一个token(这一步并不绝对,如果采用预训练词向量,词典映射要根据词向量文件进行处理),vocabulary dict形式如下:

{token1:0,token2:1,token3:2,token4:4........}

(3)数据转换:根据构建好的词典,将分词处理后的数据做映射,将文本序列转换为数字序列;

(4)数据填充与截断:在以batch输入到模型的方式中,需要对过短的数据进行填充,过长的数据进行截断,保证数据长度符合模型能接受的范围,同时batch内的数据维度大小一致。

分词的三种粒度

(1)粗糙的有词粒度(word)

例如“我来做中国”会被分成[我,来,自,中国]

优点:能够保存较为完整的语义信息

缺点:

1、词汇表会非常大,大的词汇表对应模型需要使用很大的embedding层,这既增加了内存,又增加了时间复杂度。通常,transformer模型的词汇量很少会超过50,000,特别是如果仅使用一种语言进行预训练的话,而transformerxl使用了常规的分词方式,词汇表高达267735;

2、 word-level级别的分词略显粗糙,无法发现更加细节的语义信息,例如模型学到的“old”, “older”, and “oldest”之间的关系无法泛化到“smart”, “smarter”, and “smartest”。

3、word-level级别的分词对于拼写错误等情况的鲁棒性不好;

4、 oov(out of vocabulary)问题不好解决,例如分词时数据集中只有cat,测试时遇到cats边无法处理

(2)精细的有字粒度(char)

例如“我来做中国”会被分成[我,来,自,中,国]

一个简单的方法就是将word-level的分词方法改成 char-level的分词方法,对于英文来说,就是字母界别的,比如 "China"拆分为"C","h","i","n","a",对于中文来说,"中国"拆分为"中","国",

优点:

1、这可以大大降低embedding部分计算的内存和时间复杂度,以英文为例,英文字母总共就26个,中文常用字也就几千个。

2、char-level的文本中蕴含了一些word-level的文本所难以描述的模式,因此一方面出现了可以学习到char-level特征的词向量FastText,另一方面在有监督任务中开始通过浅层CNN、HIghwayNet、RNN等网络引入char-level文本的表示;

缺点:

1、但是这样使得任务的难度大大增加了,毕竟使用字符大大扭曲了词的意义,一个字母或者一个单中文字实际上并没有任何语义意义,单纯使用char-level往往伴随着模型性能的下降;

2、增加了输入的计算压力,原本”I love you“是3个embedding进入后面的cnn、rnn之类的网络结构,而进行char-level拆分之后则变成 8个embedding进入后面的cnn或者rnn之类的网络结构,这样计算起来非常慢;

(3)现在最常用的是子词粒度sub-word,介于两者之间。

为了两全其美,transformer使用了混合了char-level和word-level的分词方式,称之为subword-level的分词方式。

subword-level的分词方式遵循的原则是:尽量不分解常用词,而是将不常用词分解为常用的子词

例如,"annoyingly"可能被认为是一个罕见的单词,并且可以分解为"annoying"和"ly"。"annoying"并"ly"作为独立的子词会更频繁地出现,同时,"annoyingly"是由"annoying"和"ly"这两个子词的复合含义构成的复杂含义,这在诸如土耳其语之类的凝集性语言中特别有用,在该语言中,可以通过将子词串在一起来形成(几乎)任意长的复杂词。

subword-level的分词方式使模型相对合理的词汇量(不会太多也不会太少),同时能够学习有意义的与上下文无关的表示形式(另外,subword-level的分词方式通过将词分解成已知的子词,使模型能够处理以前从未见过的词(oov问题得到了很大程度上的缓解)。

subword-level又分为不同的切法,这里就到huggingface的tokenizers的实现部分了,常规的char-level或者word-level的分词用spacy,nltk之类的工具包就可以胜任了。

subword的分词往往包含了两个阶段,一个是encode阶段,形成subword的vocabulary dict,一个是decode阶段,将原始的文本通过subword的vocabulary dict 转化为 token的index然后进入embedding层。主要是因为不同的model可能在分token层面做了一些微调,并且根据使用的语料的不同,最后的subword vocabulary dict也会不同。

transfomers中tokenizer使用示例

导入分词器

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
print("tokenizer:\n", tokenizer)
'''
BertTokenizerFast(name_or_path='bert-base-chinese', 
vocab_size=21128, model_max_length=512, is_fast=True,
 padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': 
'[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'})
'''

sentence = "我是中国人"
# 查看分词结果
tokens = tokenizer.tokenize(sentence)
print("tokens:\n", tokens)  #  ['我', '是', '中', '国', '人']

# 查看词典。词典是在预训练模型时就构建好了的
# print("词典\n", tokenizer.vocab)

# 根据词典将词序列转化为数字序列
ids = tokenizer.convert_tokens_to_ids(tokens=tokens)
print("ids\n", ids) # [2769, 3221, 704, 1744, 782]

# 直接调用encode方法也能实现上述目标,但会自动增加起始和终止数字序号
ids = tokenizer.encode(sentence)
print("ids\n", ids)  #[101, 2769, 3221, 704, 1744, 782, 102]

#填充
ids = tokenizer.encode(sentence, padding="max_length", max_length=12)
print("ids\n", ids)  # [101, 2769, 3221, 704, 1744, 782, 102, 0, 0, 0, 0, 0]

# 裁剪
ids = tokenizer.encode(sentence, max_length=5, truncation=True)
print("ids\n", ids)  #  [101, 2769, 3221, 704, 102]

# attention_mask 与 token_type_id
ids = tokenizer.encode(sentence, padding="max_length", max_length=15)
attention_mask = [1 if idx != 0 else 0 for idx in ids]
token_type_ids = [0] * len(ids)
print(attention_mask)  # [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
print(token_type_ids)  # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

# 两种快速调用方式
# 快速调用方式1
inputs = tokenizer.encode_plus(sentence, padding="max_length", max_length=15)
print(inputs)
# {
# 'input_ids': [101, 2769, 3221, 704, 1744, 782, 102, 0, 0, 0, 0, 0, 0, 0, 0], 
# 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
# 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]
# }

# 快速调用方式2, 直接调用tokenizer本身
inputs = tokenizer(sentence, padding="max_length", max_length=15)

# batch数据和单条数据使用方式是一模一样的
sens = ["我是中国人",
        "追逐梦想的心,比梦想本身,更可贵"]
res = tokenizer(sens, padding="max_length", max_length=12)
print(res)
#{'input_ids': [[101, 2769, 3221, 704, 1744, 782, 102, 0, 0, 0, 0, 0], 
#               [101, 6841, 6852, 3457, 2682, 4638, 2552, 8024, 3683, 3457, 2682, 3315, 6716, 8024, 3291, 1377, 6586, 102]], 
# 'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
#                    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 
# 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
#                    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

常用方式 

# 普通编码
print("="*100)
sents= ["选择珠江花园的原因就是方便。", "笔记本的键盘确实爽。"]
out = tokenizer.encode(text=sents[0],
                       text_pair=sents[1],
                       padding="max_length",
                       max_length=30,
                       truncation=True,
                       add_special_tokens=True,
                       return_tensors=None)
print(out)
print(tokenizer.decode(out))

# 增强编码
print("="*100)
sents= ["选择珠江花园的原因就是方便。", "笔记本的键盘确实爽。"]
out = tokenizer.encode_plus(text=sents[0],
                            text_pair=sents[1],
                            padding="max_length",
                            max_length=30,
                            truncation=True,
                            add_special_tokens=True,
                            return_tensors=None,
                            return_token_type_ids=True,
                            return_attention_mask=True,
                            return_special_tokens_mask=True,
                            return_length=True)
for k,v in out.items():
    print(k, ":", v)
print(tokenizer.decode(out["input_ids"]))
'''
返回值
input_ids:编码后的词
token_type_ids含义:第一个句子和特殊符号位置是0,第二个句子的位置是1
special_tokens_mask:特殊符号位置是1,其余是0
attention_mask:padding的位置是0,其余位置是1
length: 句子长度
'''


# 增强编码, 批量数据时采用 batch_tokenizer.encoder_plus()函数
print("/"*100)
sents= ["选择珠江花园的原因就是方便。", "笔记本的键盘确实爽。"]
out = tokenizer.batch_encode_plus(batch_text_or_text_pairs=[sents[0],sents[1]],
                            padding="max_length",
                            max_length=30,
                            truncation=True,
                            add_special_tokens=True,
                            return_tensors=None,
                            return_token_type_ids=True,
                            return_attention_mask=True,
                            return_special_tokens_mask=True,
                            return_length=True)
for k,v in out.items():
    print(k, ":", v)
print(tokenizer.decode(out["input_ids"][0]))
print(tokenizer.decode(out["input_ids"][1]))

 encode函数返回值含义

input_ids:编码后的词
token_type_ids含义:第一个句子和特殊符号位置是0,第二个句子和其特殊符号的位置是1
special_tokens_mask:特殊符号位置是1,其余是0
attention_mask:padding的位置是0,其余位置是1
length: 句子长度

获取字典和添加新词/新符号

#获取字典
zidian = tokenizer.get_vocab()
print(type(zidian))
print(len(zidian))
print("月光" in zidian)

# 添加新词
tokenizer.add_tokens(new_tokens=["月光", "希望"])

# 添加新符号
# [EOS]不添加的话,在字典中为[UNK]表示未知符号
tokenizer.add_special_tokens({'eos_token':'[EOS]'})

参考链接

tokenizers小结

Transformers中Tokenizer模块快速使用

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
很抱歉,我之前的回答可能有误。根据您的描述,您需要使用BERT或transformers模型训练中文文本语料,获取中文词汇的词向量,并计算每个中文词汇与种子词的余弦相似度。 以下是一种可能的修改方案,供您参考: ```python from transformers import BertTokenizer, BertModel import torch from sklearn.metrics.pairwise import cosine_similarity # 加载BERT模型分词tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = BertModel.from_pretrained('bert-base-chinese') # 种子词列表 seed_words = ['个人信息', '隐私', '泄露', '安全'] # 加载微博用户文本语料(假设存储在corpus.txt文件中) with open('corpus.txt', 'r', encoding='utf-8') as f: corpus = f.readlines() # 预处理文本语料,获取每个中文词汇的词向量 corpus_vectors = [] for text in corpus: # 使用BERT分词器将文本分成词汇 tokens = tokenizer.tokenize(text) # 将词汇转换为对应的id input_ids = tokenizer.convert_tokens_to_ids(tokens) # 将id序列转换为PyTorch张量 input_ids = torch.tensor(input_ids).unsqueeze(0) # 使用BERT模型计算词向量 with torch.no_grad(): outputs = model(input_ids) last_hidden_state = outputs[0][:, 1:-1, :] avg_pooling = torch.mean(last_hidden_state, dim=1) corpus_vectors.append(avg_pooling.numpy()) # 计算每个中文词汇与种子词的余弦相似度 similarity_threshold = 0.8 privacy_words = set() for seed_word in seed_words: # 将种子词转换为对应的id seed_word_ids = tokenizer.convert_tokens_to_ids(seed_word) # 将id序列转换为PyTorch张量 seed_word_ids = torch.tensor(seed_word_ids).unsqueeze(0) # 使用BERT模型计算种子词的词向量 with torch.no_grad(): outputs = model(seed_word_ids) last_hidden_state = outputs[0][:, 1:-1, :] avg_pooling = torch.mean(last_hidden_state, dim=1) seed_word_vector = avg_pooling.numpy() # 计算每个中文词汇与种子词的余弦相似度 for i, vector in enumerate(corpus_vectors): sim = cosine_similarity([seed_word_vector], [vector])[0][0] if sim >= similarity_threshold: privacy_words.add(corpus[i]) print(privacy_words) ``` 在上述代码中,我们使用了BERT模型分词器来处理中文文本语料。为了获取每个中文词汇的词向量,我们使用了BERT模型的输出,并对每个词汇的词向量求平均值作为代表。然后,我们计算了每个中文词汇与种子词的余弦相似度,并根据相似度阈值筛选出与种子词相关的中文词汇。最后,我们将这些中文词汇加入到隐私词库中。 请注意,上述代码仅为示例代码,您可能需要对其进行一些修改和调整以适应您的具体场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值