当前位置:   article > 正文

【Transformers】预训练模型使用工具Transformer(2):文本分词_transformer分词

transformer分词

前序文章:

  1. 【Transformers】预训练模型使用工具Transformer(1):初识Hugging Face

背景

在深度学习中,将文本数据到模型中的第一步就是文本数字化处理。那么就需要我们就文本进行“分词”,然后通过词典映射到到计算机能够处理的数字。分词后的结果我们通常称为“tokens”。

对于预训练模型来说,通常已经存在一个字典了。而这个字典也是预训练模型从训练的语料中得到的。那么就需要我们将待处理文本进行“分词”,分词的结果最好都在已经的词典中。transformers提供了对应模型的分词工具,即tokenizer。这些tokens可能是词语,也可能是词语中的一部分或者就是字符,标点符号等。

bert在进行预训练的时候,会使用诸如wordpiece算法subword分词方法对文本进行数字化处理。当我们使用对应的预训练模型时,也自然而然地使用对应的分词方法最为合理。transformers提供了一个很便利的类AutoTokenizer来允许我们加载与预训练模型一致的分词方法,模型加载就使用这个类的from_pretrained()方法。下面我们来看看怎么进行分词吧。

分词

例如我们使用bert-base-chinese这个预训练模型来分词,示例代码如下:

from transformers import AutoTokenizer

model_ckpt = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
text = "预训练模型在nature language processing 领域蜂拥而至"
encoded_text = tokenizer(text)
print(encoded_text)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

当同级目录或者电脑缓存目录中没有bert-base-chinese这个模型时,程序会自动下载。输出的结果如下:

{'input_ids': [101, 7564, 6378, 5298, 3563, 1798, 1762, 10467, 8348, 12009, 8221, 7566, 1818, 6044, 2881, 5445, 5635, 102], 
'token_type_ids': [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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

  • 1
  • 2
  • 3
  • 4

AutoTokenizer会根据模型自动地适配对应的tokenizer分词器,当然也可以指定,如:

from transformers import DistilBertTokenizer
distilbert_tokenizer = DistilBertTokenizer.from_pretrained(model_ckpt)
  • 1
  • 2

输出的结果中,input_ids就是输入内容对应转换成数字后的结果。另外两个值我们在后面的内容再进行介绍。我们也可以根据input_ids的内容还原文本,使用tokenizer的convert_ids_to_tokens()方法,在上面的代码后增加如下代码:

tokens = tokenizer.convert_ids_to_tokens(encoded_text.input_ids)
print(tokens)
  • 1
  • 2

输出的结果如下:

['[CLS]', '预', '训', '练', '模', '型', '在', 'nature', 'language', 'process', '##ing', '领', '域', '蜂', '拥', '而', '至', '[SEP]']

  • 1
  • 2

从结果中我们可以看出,结果的首位增加了特殊的token:[CLS]、[SEP],这两个token的起着指示句首和句尾的作用;如果有大写的字母的话,大写的字母会变成小写;最后就是processing被拆分开来(subword),在##ing中的前缀##表示这个前面的字符串不是空。如果将这些tokens转换成字符串时,带有前缀的token会和前面的token合并,我们可以使用tokenizer的convert_tokens_to_string()方法完成,在前面的代码中加入如下程序:

print(tokenizer.convert_tokens_to_string(tokens))
  • 1

结果如下:

[CLS] 预 训 练 模 型 在 nature language processing 领 域 蜂 拥 而 至 [SEP]
  • 1

AutoTokenizer还有其他的属性,例如:检查这个字典中的大小:

print(tokenizer.vocab_size) # 21128

  • 1
  • 2

预训练模型支持的最大文本长度:

print(tokenizer.model_max_length) # 512
  • 1

另外我们还需要了解的是,模型在进行前向传播时,需要传入的参数:


print(tokenizer.model_input_names) # ['input_ids', 'token_type_ids', 'attention_mask']
  • 1
  • 2

经常做NLP任务的友友应该知道,在将文本转成数字的过程中我们经常会有几个对应的字符:

  • [PAD],对批次数据没有达到最长的数据进行pad对齐处理;
  • [UNK],字典中的数据没有覆盖实际生产中的全部字符,没有在字典中的字符统统使用unk来代替;
  • [CLS],语句的起始字符;
  • [SEP],语句的分割字符(语句结束)
  • [MASK],预训练时,随机遮蔽的符号

这些符号对应id是多少呢?我们可以通过下面的方法查看:

print(tokenizer.unk_token_id)  # 100
print(tokenizer.pad_token_id)  # 0
print(tokenizer.cls_token_id)  # 101
print(tokenizer.sep_token_id)  # 102
print(tokenizer.mask_token_id)  # 103
  • 1
  • 2
  • 3
  • 4
  • 5

数据的批处理

在实际的模型微调,数据预测时,通常使用的批次数据的处理。下面以两条也语料数据为例:

from transformers import AutoTokenizer

model_ckpt = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
batch_data = ["空间和外观,外观大气是让我最看重的",
              "毕竟是微型小面的底子,铁板薄,防锈处理基本等于没有,夏天玻璃像放大镜一样会把太阳温度放大很多倍,必须贴好的太阳膜呦!空调没有玻璃和驾驶舱一起吹的档位"]


def tokenize(batch):
    return tokenizer(batch, padding=True, truncation=True)


print(tokenize(batch_data))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出结果如下:

{'input_ids': [[101, 4958, 7313, 1469, 1912, 6225, 8024, 1912, 6225, 1920, 3698, 3221, 6375, 2769, 3297, 4692, 7028, 4638, 102, 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, 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], 
[101, 3684, 4994, 3221, 2544, 1798, 2207, 7481, 4638, 2419, 2094, 8024, 7188, 3352, 5946, 8024, 7344, 7224, 1905, 4415, 1825, 3315, 5023, 754, 3766, 3300, 8024, 1909, 1921, 4390, 4461, 1008, 3123, 1920, 7262, 671, 3416, 833, 2828, 1922, 7345, 3946, 2428, 3123, 1920, 2523, 1914, 945, 8024, 2553, 7557, 6585, 1962, 4638, 1922, 7345, 5606, 1452, 8013, 4958, 6444, 3766, 3300, 4390, 4461, 1469, 7730, 7724, 5665, 671, 6629, 1430, 4638, 3440, 855, 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, 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, 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, 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, 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, 0, 0]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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], 
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于两个文本长度不同,短的文本会使用pad对应的id进行对齐操作。对于另外两个参数:

  • token_type_ids:我们知道bert模型在训练的时候输入的是两个句子,这就是用来标记哪个是第一个句子,哪个是第二个句子,分段标记索引以指示输入的第一和第二部分。在 [0, 1] 中选择索引:0对应一个句子的token,1对应一个句子的token。
  • attention_mask: bert在训练的时候会随机对语句中的token进行遮蔽,也就是对输入数据进行mask,其在 [0, 1] 中选择的掩码值:1 表示未屏蔽的标记,0 表示已屏蔽的标记,同时也会告诉模型哪些内容是pad后的结果。

有了上面的数据batch数据去训练或者去预测还是不够的,我们知道pytorch中有DataLoader这个类去对dataset数据进行批次化处理,方面模型训练和预测,transformers当然也有。

从Huggingface上读取数据的方式这里暂不介绍。我们看看如何读取我们本地的语料数据。常见的读取本地数据的方式有三种,如下图:
在这里插入图片描述
除此之外还可以从pandas中的DataFrame中读取数据。

在加载数据之前,我们需要安装huggingface家族的datasets模块,如下:pip install datasets.
这里选取了几条tsv格式的数据,如下:

label	text
1	外观确实非常霸气,钻石切工有棱有角,3.0的动力在城市里绰绰有余,内饰考究,空间比较大,bose的音响非常给力,小众品牌不像德系三架马车那样成为街车,为个性代言。
1	外观漂亮,安全性佳,动力够强,油耗够低
1	后备箱大!!!
1	空间大。外观大气,中控台用料讲究简洁
1	外观漂亮,空间够大,动力家用也ok
1	空间和外观,外观大气是让我最看重的
0	毕竟是微型小面的底子,铁板薄,防锈处理基本等于没有,夏天玻璃像放大镜一样会把太阳温度放大很多倍,必须贴好的太阳膜呦!空调没有玻璃和驾驶舱一起吹的档位
0	轮胎挡板隔音不太好,小石子弹起来的时候声音有点大。
1	红绿灯经常第一个过线,甩个尾灯给别人看,完全迎合了我装逼又躁动的心思
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

更多类型的数据读取可参考官方文档以及前文介绍的书籍,这里不详细介绍。数据读取可参考如下:

import datasets
from transformers import AutoTokenizer


model_ckpt = "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)


def tokenize(batch):
    return tokenizer(batch['text'], padding=True, max_length=50, truncation=True)


# print(tokenize(batch_data))
segment_data = datasets.load_dataset("csv", data_files={"train": "data/data.csv"}, sep='\t', header=0)
# 数据打散
print(segment_data['train'][0])
segment_data = segment_data.shuffle(seed=2022)
print(segment_data['train'][0])
# 数据编码
segment_data_encoded = segment_data.map(tokenize, batched=False, batch_size=3)
# 编码查看
print(segment_data_encoded['train'][0])
print(segment_data_encoded['train'].column_names)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

结果如下:

{'label': 1, 'text': '外观确实非常霸气,钻石切工有棱有角,3.0的动力在城市里绰绰有余,内饰考究,空间比较大,bose的音响非常给力,小众品牌不像德系三架马车那样成为街车,为个性代言。'}
{'label': 0, 'text': '轮胎挡板隔音不太好,小石子弹起来的时候声音有点大。'}
{'label': 0, 'text': '轮胎挡板隔音不太好,小石子弹起来的时候声音有点大。', 'input_ids': [101, 6762, 5522, 2913, 3352, 7392, 7509, 679, 1922, 1962, 8024, 2207, 4767, 2094, 2486, 6629, 3341, 4638, 3198, 952, 1898, 7509, 3300, 4157, 1920, 511, 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], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
['label', 'text', 'input_ids', 'token_type_ids', 'attention_mask']
  • 1
  • 2
  • 3
  • 4

在上面的代码中还进行了一些操作,如在分词时限定了总长度为50;数据打散等。在经过map操作之后,数据集在原来基础上增加了token数据的结果。

好了本节内容就告一段落了,后面我将使用transformers实现一个微调的文本分类任务,有兴趣的保持关注哦。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/804250
推荐阅读
相关标签
  

闽ICP备14008679号