1
本文作者: skura | 2020-02-20 11:42 |
在過(guò)去的幾周里,我們對(duì) transformers 和 tokenizers 庫(kù)進(jìn)行了一些改進(jìn),目的是讓從頭開(kāi)始訓(xùn)練新的語(yǔ)言模型變得更加容易。
在本文中,我們將演示如何用世界語(yǔ)訓(xùn)練一個(gè)「小」模型(84 M,6 個(gè)層,768 個(gè)隱藏層,12 個(gè)注意力頭)——這與 DistilBERT 的層數(shù)和注意力頭數(shù)相同。然后,我們將在詞性標(biāo)記的下游任務(wù)上微調(diào)模型。
世界語(yǔ)是一種以易學(xué)性為目標(biāo)的結(jié)構(gòu)化語(yǔ)言。我們選擇它有幾個(gè)原因:
它是一種資源相對(duì)較少的語(yǔ)言(盡管大約有 200 萬(wàn)人使用它),所以這個(gè)演示不像訓(xùn)練一個(gè)英語(yǔ)模型那樣枯燥?。
它的語(yǔ)法規(guī)則性很強(qiáng)(例如所有常用名詞都以 -o 結(jié)尾,所有形容詞都以 -a 結(jié)尾),所以即使是在一個(gè)小的數(shù)據(jù)集上,我們也可以得到有趣的結(jié)果。
最后,語(yǔ)言的總體目標(biāo)是縮短人與人之間的距離,促進(jìn)世界和平和國(guó)際理解,可以說(shuō)是與 NLP 社區(qū)的目標(biāo)一致?。
PS:你不需要了解世界語(yǔ)就可以理解這篇文章。
我們的模型將被稱為…「wait for it… EsperBERTo」?。
1.查找數(shù)據(jù)集
首先,讓我們找到一個(gè)世界語(yǔ)文本的語(yǔ)料庫(kù)。這里我們將使用來(lái)自 INRIA 的 OSCAR 語(yǔ)料庫(kù)(https://traces1.inria.fr/oscar/ )中的世界語(yǔ)部分。
OSCAR 是一個(gè)龐大的多語(yǔ)種語(yǔ)料庫(kù),它是通過(guò)對(duì) Web 上爬取的文本進(jìn)行語(yǔ)言分類和過(guò)濾而獲得的。
數(shù)據(jù)集的世界語(yǔ)部分只有 299M,因此我們將與 Leipzig 語(yǔ)料庫(kù)集合(https://wortschatz.uni-leipzig.de/en/download )中的世界語(yǔ)子語(yǔ)料庫(kù)相連接,該語(yǔ)料庫(kù)由來(lái)自新聞、文學(xué)和維基百科等不同來(lái)源的文本組成。
最終的訓(xùn)練語(yǔ)料庫(kù)的大小為3 GB,仍然很小。當(dāng)然,對(duì)于你的模型,你可以獲得更多的數(shù)據(jù)來(lái)進(jìn)行預(yù)訓(xùn)練,從而獲得更好的結(jié)果。
2.訓(xùn)練標(biāo)記器
我們選擇使用與 RoBERTa 相同的特殊令牌來(lái)訓(xùn)練字節(jié)級(jí)字節(jié)對(duì)編碼標(biāo)記器(與 GPT-2 相同)。讓我們?nèi)我膺x擇它的大小,這里設(shè)置為 52000。
我們建議訓(xùn)練字節(jié)級(jí)的 BPE(而不是像 BERT 這樣的詞條標(biāo)記器),因?yàn)樗鼘膯蝹€(gè)字節(jié)的字母表開(kāi)始構(gòu)建詞匯表,所以所有單詞都可以分解為標(biāo)記(不再是 <unk> 標(biāo)記)。
#! pip install tokenizers==0.4.2
from pathlib import Path
from tokenizers import ByteLevelBPETokenizer
paths = [str(x) for x in Path("./eo_data/").glob("**/*.txt")]
# Initialize a tokenizer
tokenizer = ByteLevelBPETokenizer()
# Customize trainingtokenizer.train(files=paths, vocab_size=52_000, min_frequency=2, special_tokens=[
"<s>",
"<pad>",
"</s>",
"<unk>",
"<mask>",
])
# Save files to disk
tokenizer.save(".", "esperberto")
這里有一個(gè)對(duì)輸出的捕獲,圖片稍微進(jìn)行了加速:
在我們數(shù)據(jù)集上的訓(xùn)練大約花了 5 分鐘。
?? 哇,太快了!???
我們現(xiàn)在有一個(gè) vocab.json,它是按頻率排列的最常用標(biāo)記列表,還有一個(gè) merges.txt 合并列表。
{
"<s>": 0,
"<pad>": 1,
"</s>": 2,
"<unk>": 3,
"<mask>": 4,
"!": 5,
"\"": 6,
"#": 7,
"$": 8,
"%": 9,
"&": 10,
"'": 11,
"(": 12,
")": 13,
# ...
}
# merges.txt
l a
? k
o n
? la
t a
? e
? d
? p
# ...
最棒的是,我們的標(biāo)記器為世界語(yǔ)進(jìn)行了優(yōu)化。與為英語(yǔ)訓(xùn)練的通用標(biāo)記器相比,更多的本機(jī)單詞由一個(gè)單獨(dú)的、未加修飾的標(biāo)記表示。變音符號(hào),即在世界語(yǔ)中使用的重音字符 -?、?、?、?、? 和 ?- 是本機(jī)編碼的。我們還以更有效的方式表示序列。在這個(gè)語(yǔ)料庫(kù)中,編碼序列的平均長(zhǎng)度比使用預(yù)先訓(xùn)練的 GPT-2 標(biāo)記器時(shí)減小了約 30%。
下面是如何在標(biāo)記器中使用它的方法,包括處理 RoBERTa 特殊標(biāo)記——當(dāng)然,你也可以直接從 transformer 中使用它。
from tokenizers.implementations import ByteLevelBPETokenizer
from tokenizers.processors import BertProcessing
tokenizer = ByteLevelBPETokenizer(
"./models/EsperBERTo-small/vocab.json",
"./models/EsperBERTo-small/merges.txt",
)
tokenizer._tokenizer.post_processor = BertProcessing(
("</s>", tokenizer.token_to_id("</s>")),
("<s>", tokenizer.token_to_id("<s>")),
)
tokenizer.enable_truncation(max_length=512)
print(
tokenizer.encode("Mi estas Julien.")
)
# Encoding(num_tokens=7, ...)
# tokens: ['<s>', 'Mi', '?estas', '?Juli', 'en', '.', '</s>']
3.從頭開(kāi)始訓(xùn)練語(yǔ)言模型
我們現(xiàn)在將使用來(lái)自 transformer 的 run_language_modeling.py 腳本(https://github.com/huggingface/transformers/blob/master/examples/run_language_modeling.py )(由 run_lm_finetuning.py 重新命名而來(lái),因?yàn)樗F(xiàn)在更無(wú)縫地支持從頭開(kāi)始的訓(xùn)練)來(lái)訓(xùn)練我們的語(yǔ)言模型。只需記住從零開(kāi)始訓(xùn)練,而不是從現(xiàn)有的模型或檢查點(diǎn)開(kāi)始訓(xùn)練。
我們將訓(xùn)練一個(gè)類似于 RoBERTa 的模型,這是一個(gè)類似于 BERT 的模型,并進(jìn)行了一些更改(查看文檔https://huggingface.co/transformers/model_doc/roberta.html 了解更多細(xì)節(jié))。
由于該模型類似于 BERT,我們將對(duì)其進(jìn)行屏蔽語(yǔ)言建模任務(wù)的訓(xùn)練,即預(yù)測(cè)如何填充我們?cè)跀?shù)據(jù)集中隨機(jī)屏蔽的任意令牌。這由示例腳本處理。
我們只需要做兩件事:
實(shí)現(xiàn)從文本文件加載數(shù)據(jù)集的簡(jiǎn)單子類。
根據(jù)你的用例,如果所提供的示例(TextDataset 和 LineByLineTextDataset)中的一個(gè)有效,你甚至可能不需要編寫自己的 Dataset 子類,但是你可能希望根據(jù)你的語(yǔ)料庫(kù)的實(shí)際情況添加許多自定義調(diào)整。
選擇并實(shí)驗(yàn)不同的超參數(shù)集。
這是我們世界語(yǔ)數(shù)據(jù)集的一個(gè)簡(jiǎn)單版本。
class EsperantoDataset(Dataset):
def __init__(self, evaluate: bool = false):
tokenizer = ByteLevelBPETokenizer(
"./models/EsperBERTo-small/vocab.json",
"./models/EsperBERTo-small/merges.txt",
)
tokenizer._tokenizer.post_processor = BertProcessing(
("</s>", tokenizer.token_to_id("</s>")),
("<s>", tokenizer.token_to_id("<s>")),
)
tokenizer.enable_truncation(max_length=512)
# or use the RobertaTokenizer from `transformers` directly.
self.examples = []
src_files = Path("./data/").glob("*-eval.txt") if evaluate else Path("./data/").glob("*-train.txt")
for src_file in src_files:
print("?", src_file)
lines = src_file.read_text(encoding="utf-8").splitlines()
self.examples += [x.ids for x in tokenizer.encode_batch(lines)]
def __len__(self):
return len(self.examples)
def __getitem__(self, i):
# We’ll pad at the batch level.
return torch.tensor(self.examples[i])
如果數(shù)據(jù)集非常大,可以選擇動(dòng)態(tài)加載和標(biāo)記示例,而不是將其作為預(yù)處理步驟。
下面是我們傳遞給腳本的一組特定的超參數(shù)和參數(shù):
--output_dir ./models/EsperBERTo-small-v1
--model_type roberta
--mlm
--config_name ./models/EsperBERTo-small
--tokenizer_name ./models/EsperBERTo-small
--do_train
--do_eval
--learning_rate 1e-4
--num_train_epochs 5
--save_total_limit 2
--save_steps 2000
--per_gpu_train_batch_size 16
--evaluate_during_training
--seed 42
像往常一樣,選擇最大的批量大小,你可以適合你的 GPU。
??? 我們開(kāi)始訓(xùn)練吧??!???
在這里,你可以查看我們的 Tensorboard(https://tensorboard.dev/experiment/8AjtzdgPR1qG6bDIe1eKfw/#scalars )以獲取一組特定的超參數(shù):
默認(rèn)情況下,我們的示例腳本會(huì)登錄到 Tensorboard 格式,在 runs/ 下。然后,要查看你的面板,只需運(yùn)行 tensorboard dev upload --logdir runs,這將設(shè)置 tensorboard.dev,它是一個(gè) Google 托管的版本,允許你與任何人共享 ML 實(shí)驗(yàn)。
4.檢查 LM 是否受過(guò)訓(xùn)練
除了觀察正在下降的訓(xùn)練和評(píng)估損失之外,檢查我們的語(yǔ)言模型是否學(xué)習(xí)到了有趣的東西的最簡(jiǎn)單方法是使用 FillMaskPipeline。
管道是標(biāo)記器和模型周圍的簡(jiǎn)單包裝器,「填充掩碼」允許你輸入一個(gè)包含屏蔽令牌的序列(這里是 <mask>),并返回一個(gè)最可能填充序列的列表及其概率。
from transformers import pipeline
fill_mask = pipeline(
"fill-mask",
model="./models/EspertBERTo-small",
tokenizer="./models/EspertBERTo-small"
)
# The sun <mask>.
# =>
result = fill_mask("La suno <mask>.")
# {'score': 0.2526160776615143, 'sequence': '<s> La suno brilis.</s>', 'token': 10820}
# {'score': 0.0999930202960968, 'sequence': '<s> La suno lumis.</s>', 'token': 23833}
# {'score': 0.04382849484682083, 'sequence': '<s> La suno brilas.</s>', 'token': 15006}
# {'score': 0.026011141017079353, 'sequence': '<s> La suno falas.</s>', 'token': 7392}
# {'score': 0.016859788447618484, 'sequence': '<s> La suno pasis.</s>', 'token': 4552}
OK,使用簡(jiǎn)單的語(yǔ)法就可以了。讓我們嘗試一個(gè)更有趣的提示:
fill_mask("Jen la komenco de bela <mask>.")
# This is the beginning of a beautiful <mask>.
# =>
# {
# 'score':0.06502299010753632
# 'sequence':'<s> Jen la komenco de bela vivo.</s>'
# 'token':1099
# }
# {
# 'score':0.0421181358397007
# 'sequence':'<s> Jen la komenco de bela vespero.</s>'
# 'token':5100
# }
# {
# 'score':0.024884626269340515
# 'sequence':'<s> Jen la komenco de bela laboro.</s>'
# 'token':1570
# }
# {
# 'score':0.02324388362467289
# 'sequence':'<s> Jen la komenco de bela tago.</s>'
# 'token':1688
# }
# {
# 'score':0.020378097891807556
# 'sequence':'<s> Jen la komenco de bela festo.</s>'
# 'token':4580
# }
通過(guò)更復(fù)雜的提示,你可以探究你的語(yǔ)言模型是否捕獲了更多的語(yǔ)義知識(shí),甚至某種統(tǒng)計(jì)常識(shí)推理。
5.在下游任務(wù)上微調(diào) LM
我們現(xiàn)在可以在詞性標(biāo)注的下游任務(wù)上微調(diào)我們的新的世界語(yǔ)語(yǔ)言模型。
如前所述,世界語(yǔ)是一種規(guī)則性很強(qiáng)的語(yǔ)言,詞尾通常制約著詞性的語(yǔ)法部分。使用 CoNLL-2003 格式的帶注釋的世界語(yǔ) POS 標(biāo)記數(shù)據(jù)集(見(jiàn)下面的示例),我們可以使用 transformer 中的 run_ner.py(https://github.com/huggingface/transformers/blob/master/examples/run_ner.py )腳本。
POS 標(biāo)記和 NER 一樣是一個(gè)令牌分類任務(wù),因此我們可以使用完全相同的腳本。
再次強(qiáng)調(diào),這里是這個(gè)微調(diào)的托管 Tensorboard。我們使用每 GPU 64 的批處理大小訓(xùn)練 3 個(gè)階段。
訓(xùn)練和評(píng)估損失會(huì)收斂到很小的殘值,因?yàn)槿蝿?wù)相當(dāng)簡(jiǎn)單:語(yǔ)言是規(guī)則的,能夠端到端地訓(xùn)練。
這次,讓我們使用 TokenClassificationPipeline:
from transformers import TokenClassificationPipeline, pipeline
MODEL_PATH = "./models/EsperBERTo-small-pos/"
nlp = pipeline(
"ner",
model=MODEL_PATH,
tokenizer=MODEL_PATH,
)
# or instantiate a TokenClassificationPipeline directly.
nlp("Mi estas viro kej estas tago varma.")
# {'entity': 'PRON', 'score': 0.9979867339134216, 'word': ' Mi'}
# {'entity': 'VERB', 'score': 0.9683094620704651, 'word': ' estas'}
# {'entity': 'VERB', 'score': 0.9797462821006775, 'word': ' estas'}
# {'entity': 'NOUN', 'score': 0.8509314060211182, 'word': ' tago'}
# {'entity': 'ADJ', 'score': 0.9996201395988464, 'word': ' varma'}
看起來(lái)很有效!?
6.分享你的模型?
最后,當(dāng)你有一個(gè)好的模型時(shí),請(qǐng)考慮與社區(qū)分享:
使用 CLI 上載模型:transformers CLI upload
編寫 README.md 模型卡并將其添加到 model_cards/ 下的存儲(chǔ)庫(kù)中。理想情況下,你的模型卡應(yīng)包括:
模型描述
訓(xùn)練參數(shù)(數(shù)據(jù)集、預(yù)處理、超參數(shù))
評(píng)估結(jié)果
預(yù)期用途和限制
任何其他有用的!?
??你的模型在 http://huggingface.co/models 上有一個(gè)頁(yè)面,每個(gè)人都可以使用 AutoModel.from_pretrained(“用戶名/模型名”)加載它。
via:https://huggingface.co/blog/how-to-train
雷鋒網(wǎng)雷鋒網(wǎng)雷鋒網(wǎng)
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見(jiàn)轉(zhuǎn)載須知。