Transformers的RoBERTa model怎么使用word level的tokenizer

2022年8月25日更新:

昨天改了tokenizer之后以为好了,结果发现还是有问题。具体来说,用后面方法训练的tokenizer,并不能被正确加载为RobertaTokenizerFast,会导致只对输入序列中的逗号进行编码。解决方法是:用类似于

tokenizer.save(model_dir+'/wordlevel.json')

这种形式将tokenizer保存成一个json文件,然后用RobertaTokenizerFast(RoBERTa)的init function中的tokenizer_file参数指定加载tokenizer文件。具体我举一个例子(省去了pretraining部分):

from transformers import RobertaTokenizerFast

model_dir='Language-Model-Roberta'
tokenizer1 = RobertaTokenizerFast.from_pretrained("./"+model_dir)
tokenizer2 = RobertaTokenizerFast(tokenizer_file=model_dir+'/wordlevel.json')

corpora_line="push r15 push r14 mov r15 , r8 push r13 push r12 mov r12 , rdi push rbp push rbx mov r14d"

batch_encoding=tokenizer1(corpora_line)
print(batch_encoding.tokens())
print(tokenizer1.encode(corpora_line))

batch_encoding=tokenizer2(corpora_line)
print(batch_encoding.tokens())
print(tokenizer2.encode(corpora_line))

from tokenizers import Tokenizer
tokenizer3 = Tokenizer.from_file(model_dir+"/wordlevel.json")
print(tokenizer3.encode(corpora_line).ids)

对应的输出是:

['<s>', ',', ',', '</s>']
[0, 6, 6, 2]
['push', 'r15', 'push', 'r14', 'mov', 'r15', ',', 'r8', 'push', 'r13', 'push', 'r12', 'mov', 'r12', ',', 'rdi', 'push', 'rbp', 'push', 'rbx', 'mov', 'r14d']
[52, 38, 52, 36, 7, 38, 6, 58, 52, 34, 52, 42, 7, 42, 6, 22, 52, 24, 52, 18, 7, 109]
[52, 38, 52, 36, 7, 38, 6, 58, 52, 34, 52, 42, 7, 42, 6, 22, 52, 24, 52, 18, 7, 109]

可以看到第一种方法是不行的!不过,博客原先的内容也有参考和总结的价值,pre-training部分也就稍微有点变化:

import torch
import os
import shutil
print(torch.cuda.is_available())

############################################################
from pathlib import Path
from tokenizers import Tokenizer
from tokenizers.models import WordLevel
from tokenizers.trainers import WordLevelTrainer

repo_name="data/out"

model_dir='Language-Model-Roberta'
if(os.path.exists(model_dir)):
    shutil.rmtree(model_dir)
os.mkdir(model_dir)

paths = [str(x) for x in Path(repo_name).glob("**/*.static2")]
tokenizer = Tokenizer(WordLevel())
trainer = WordLevelTrainer(special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()

tokenizer.train(files=paths,trainer=trainer)

tokenizer.model.save(model_dir)
merges=open(model_dir+'/merges.txt','w')
tokenizer.save(model_dir+'/wordlevel.json')

2022年8月25日更新的内容就到这里,下面是原博客的内容:

---------------------------------------------------------------------------------------------------------------------------------

其实这个问题应该是很好解决的,想不到竟然花了3个多小时。相信这并不是我一个人有这样的需求,如果vacabulary很小的话,我们并不需要BPE之类的tokenizer,按道理来说应该很好换成word level的啊,但实际并不然:

按照网上到处都有的教程,例如(基于RoBERTa 训练一个MLM掩码语言模型 - 知乎),包括我自己之前的博客:使用huggingface‘s transformers预训练自己模型时报:Assertion ‘srcIndex < srcSelectDimSize‘ failed. 的解决办法_蛐蛐蛐的博客-CSDN博客,RoBERTa的tokenizer可以通过以下代码实现:

import torch
import os
import shutil
print(torch.cuda.is_available())

############################################################
from pathlib import Path
from tokenizers import ByteLevelBPETokenizer

repo_name="data/out"

model_dir='Language-Model-Roberta'
if(os.path.exists(model_dir)):
    shutil.rmtree(model_dir)
os.mkdir(model_dir)

paths = [str(x) for x in Path(repo_name).glob("**/*.static")]
tokenizer = ByteLevelBPETokenizer()
tokenizer.train(files=paths, vocab_size=18_000, min_frequency=2, special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

tokenizer.save_model(model_dir)

这里先吐槽一点:从tokenizer的document中(Tokenizers)是看不到这个类的,但是网上所有的教程都是这么用,竟然也能正常跑起来。

我就很想当然地以为,按照相同的格式换成其他tokenizer即可,但实际上并不是这样,经过参考tokenizer的GitHub Readme文件,可以写成如下的形式:

import torch
import os
import shutil
print(torch.cuda.is_available())

############################################################
from pathlib import Path
from tokenizers import Tokenizer
from tokenizers.models import WordLevel
from tokenizers.trainers import WordLevelTrainer

repo_name="data/out"

model_dir='Language-Model-Roberta'
if(os.path.exists(model_dir)):
    shutil.rmtree(model_dir)
os.mkdir(model_dir)

paths = [str(x) for x in Path(repo_name).glob("**/*.static2")]
tokenizer = Tokenizer(WordLevel())
trainer = WordLevelTrainer(special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()

tokenizer.train(files=paths,trainer=trainer)

不得不说,和上面相比,差别有点大啊(例如:tokenizer = Tokenizer(WordLevel()),以及必需要加tokenizer.pre_tokenizer = Whitespace()这一行)。但是GitHub上并没有说怎么保存这个tokenizer,如果这时候用:

tokenizer.save_model(model_dir)

就会报错:

AttributeError: 'tokenizers.Tokenizer' object has no attribute 'save_model'

心中不禁草泥马啊。那换成save_pretrained呢(例如3-3 Transformers Tokenizer API 的使用 - 知乎)?还是会报错:

AttributeError: 'tokenizers.Tokenizer' object has no attribute 'save_pretrained'

这个其实也是可以预料到了,因为上面这个tokenizer是transformers中的(from transformers import AutoTokenizer),也不理解为啥会这么混乱!

如果参考tokenizer的文档换成save呢:Tokenizer

会报错说:

Exception: Is a directory (os error 21)

言下之意,通过save可以保存成一个文件。从之前说的RoBERTa的示例代码可以知道, 需要再重新加载的tokenizer需要在一个文件夹下,所以这个也不行。

又找了找,发现了这里的举例:transformers的tokenizer - 知乎

tokenizer.model.save(model_dir)

果然这个时候可以保存到这个model_dir文件夹了。但是如果就这样,还是会报错:

TypeError: expected str, bytes or os.PathLike object, not NoneType

在网上看到了这篇博客:避坑系列-transformers - 我就是叶子吖 - 博客园

我去,这个原因我也是醉了,所以最后手动建立一个空白的merges.txt文件,然后就可以正常保存和使用这个tokenizer了。呵呵呵呵呵,最后总结一下训练和使用这个tokenizer的过程,训练部分的完整代码是:

import torch
import os
import shutil
print(torch.cuda.is_available())

############################################################
from pathlib import Path
from tokenizers import Tokenizer
from tokenizers.models import WordLevel
from tokenizers.trainers import WordLevelTrainer

repo_name="data/out"

model_dir='Language-Model-Roberta'
if(os.path.exists(model_dir)):
    shutil.rmtree(model_dir)
os.mkdir(model_dir)

paths = [str(x) for x in Path(repo_name).glob("**/*.static2")]
tokenizer = Tokenizer(WordLevel())
trainer = WordLevelTrainer(special_tokens=[
    "<s>",
    "<pad>",
    "</s>",
    "<unk>",
    "<mask>",
])

from tokenizers.pre_tokenizers import Whitespace
tokenizer.pre_tokenizer = Whitespace()

tokenizer.train(files=paths,trainer=trainer)

tokenizer.model.save(model_dir)
merges=open(model_dir+'/merges.txt','w')

使用的时候还和之前一样:

from transformers import RobertaTokenizerFast
tokenizer = RobertaTokenizerFast.from_pretrained("./"+model_dir, max_len=512)

然后就可以愉快地训练RoBERTa了。不得不说,这些库的设计,和文档的组织形式,还是有很多无力吐槽的地方啊。

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...