日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

nlp-with-transformers系列-04_多语言命名实体识别

發布時間:2023/12/4 综合教程 40 生活家
生活随笔 收集整理的這篇文章主要介紹了 nlp-with-transformers系列-04_多语言命名实体识别 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

到本章為止,我們已經使用Transformers模型來解決英文語料的NLP任務,但如果我們語料是用Greek, Swahili或者Klingon等語言組成,現在怎么辦? 一種方法是在Hugging Face Hub上搜索合適的預訓練語言模型,并在手頭的任務上對其進行微調。 然而,這些預訓練的模型往往只存在于像德語、俄語或普通話這樣的 "豐富資源 "的語言,這些語言有大量的網絡文本可供預訓練。 當語料庫是多語言的時候,另一個常見的挑戰出現了,在產品化環境中維護多個單語模型對我們以及工程團隊來說是沒有樂趣的。

幸運的是,有一類多語言Transformers前來救場。 與BERT一樣,這些模型使用遮蔽語言模型作為預訓練目標,在一百多種語言的語料上聯合訓練的。 通過對多種語言的巨大語料庫進行預訓練,這些多語言Transformers能夠實現零距離的跨語言遷移。 這意味著,在一種語言上經過微調的模型可以應用于其他語言,而不需要任何進一步的訓練! 這也使得這些模型非常適合于 “code-switching”,即說話者在一次對話中交替使用兩種或更多的語言或方言。

在本章中,我們將探討如何對一個名為XLM-RoBERTa的單一Transformers模型(在第三章中介紹)進行微調,以便在幾種語言中進行命名實體識別(NER)。 正如我們在第一章中所看到的,NER是一項常見的NLP任務,用于識別文本中的實體,如人物、組織或地點。 這些實體可用于各種應用,如從公司文件中獲得關鍵信息,提高搜索引擎的質量,或只是從語料庫中建立一個結構化數據庫。

XLM-RoBERTa 是 RoBERTa 的多語言版本,它在包含 100 種語言的 2.5TB 過濾后的 CommonCrawl 數據上進行了預訓練。

在本章中假設我們要為一個位于瑞士的客戶進行NER,有四種國家語言(英語通常作為它們之間的橋梁), 我們首先要為這個問題獲得一個合適的多語言語料庫。

溫馨提示

零樣本遷移或零樣本學習通常是指在一組標簽上訓練一個模型,然后在另一組標簽上對其進行評估的任務。 在Transformers的背景下,零樣本學習也可以指像GPT-3這樣的語言模型在下游任務上被評估,而它甚至沒有被微調過的情況。

數據集

在本章中,我們將使用多語言編碼器的跨語言TRansfer評估(XTREME)基準的一個子集,稱為WikiANN或PAN-X。 該數據集由多種語言的維基百科文章組成,包括瑞士最常用的四種語言。 德語(62.9%)、法語(22.9%)、意大利語(8.4%)和英語(5.9%)。 每篇文章都用LOC(地點)、PER(人物)和ORG(組織)標簽以 " inside-outside-beginning"(IOB2)的格式進行了注釋。 在這種格式中,B-前綴表示一個實體的開始,而屬于同一實體的連續標記被賦予I-前綴。 一個O標記表示該標記不屬于任何實體。 例如,下面這句話:

Jeff Dean is a computer scientist at Google in California would be labeled in IOB2 format as shown in Table 4-1.

要在XTREME中加載PAN-X子集之一,我們需要知道哪種數據集配置要傳遞給load_dataset()函數。 每當你處理一個有多個域的數據集時,你可以使用get_dataset_config_names()函數來找出哪些子集可用:

from datasets import get_dataset_config_names 
xtreme_subsets = get_dataset_config_names("xtreme") 
print(f"XTREME has {len(xtreme_subsets)} configurations") 
XTREME has 183 configurations

我們可以看到,那是一個很全的配置! 我們縮小搜索范圍,只尋找以 "PAN "開頭的配置:

panx_subsets = [s for s in xtreme_subsets if s.startswith("PAN")]
panx_subsets[:3] 
['PAN-X.af', 'PAN-X.ar', 'PAN-X.bg']

到此為止,我們已經確定了PAN-X子集的語法, 每個語言都有一個兩個字母的后綴,似乎是一個ISO 639-1語言代碼。 這意味著,為了加載德語語料庫,我們將de代碼傳遞給load_dataset()的name參數,如下所示:

from datasets import load_dataset 
load_dataset("xtreme", name="PAN-X.de")

為了制作一個真實的瑞士語料庫,我們將根據口語比例對PAN-X的德語(de)、法語(fr)、意大利語(it)和英語(en)語料庫進行采樣。 這將造成語言的不平衡,這在現實世界的數據集中是非常常見的,由于缺乏精通該語言的領域專家,獲取少數語言的標注實例可能會很昂貴。 這個不平衡的數據集將模擬多語言應用工作中的常見情況,我們將看到我們如何建立一個對所有語言都有效的模型。

為了跟蹤每一種語言,讓我們創建一個Python defaultdict,將語言代碼作為鍵,將DatasetDict類型的PAN-X語料庫作為值:

from collections import defaultdict 
from datasets import DatasetDict 
langs = ["de", "fr", "it", "en"] 
fracs = [0.629, 0.229, 0.084, 0.059] 
# Return a DatasetDict if a key doesn't exist 
panx_ch = defaultdict(DatasetDict) 
for lang, frac in zip(langs, fracs): # Load monolingual corpus ds = load_dataset("xtreme", name=f"PAN-X.{lang}") # Shuffle and downsample each split according to spoken proportion for split in ds: panx_ch[lang][split] = ( ds[split] .shuffle(seed=0) .select(range(int(frac * ds[split].num_rows))))

在這里,我們使用shuffle()方法來確保我們不會意外地偏離我們的數據集拆分,而select()允許我們根據fracs中的值對每個語料庫進行欠采樣。 讓我們通過訪問Dataset.num_rows屬性來看看我們在訓練集中每個語言有多少個例子:

import pandas as pd 
pd.DataFrame({lang: [panx_ch[lang]["train"].num_rows] for lang in langs}, index=["Number of training examples"])

根據設計,我們在德語中的例子比其他所有語言的總和還要多,所以我們將以德語為起點,對法語、意大利語和英語進行Zeroshot跨語言轉移。 讓我們檢查一下德語語料庫中的一個例子:

element = panx_ch["de"]["train"][0] 
for key, value in element.items(): print(f"{key}: {value}") 
langs: ['de', 'de', 'de', 'de', 'de', 'de', 'de', 'de', 'de', 'de', 'de', 'de'] 
ner_tags: [0, 0, 0, 0, 5, 6, 0, 0, 5, 5, 6, 0] 
tokens: ['2.000', 'Einwohnern', 'an', 'der', 'Danziger', 'Bucht', 'in', 'der', 'polnischen', 'Woiwodschaft', 'Pommern', '.']

與我們之前遇到的數據集對象一樣,我們的例子中的鍵對應于Arrow表中的列名,而值則表示每一列中的條目。 特別是,我們看到ner_tags列對應于每個實體與一個類ID的映射。 這樣看起來有點麻煩,接下來我們用熟悉的LOC、PER和ORG標簽創建一個新列。 要做到這一點,首先要注意的是,我們的數據集對象有一個特征屬性,指定與每一列相關的基礎數據類型:

for key, value in panx_ch["de"]["train"].features.items(): print(f"{key}: {value}") 
tokens: Sequence(feature=Value(dtype='string', id=None), length=-1, id=None)
ner_tags: Sequence(feature=ClassLabel(num_classes=7, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC'], names_file=None, id=None), length=-1, id=None) 
langs: Sequence(feature=Value(dtype='string', id=None), length=-1, id=None)

序列類指定該字段包含一個特征列表,在ner_tags的情況下,它對應于ClassLabel特征列表。 讓我們從訓練集中挑出這個特征,如下:

tags = panx_ch["de"]["train"].features["ner_tags"].feature 
print(tags) 
ClassLabel(num_classes=7, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC'], names_file=None, id=None)

我們可以使用第二章中遇到的ClassLabel.int2str()方法,在我們的訓練集中為每個標簽創建一個帶有類名的新列。 我們將使用map()方法返回一個dict,其鍵對應于新的列名,其值是一個類名的列表:

def create_tag_names(batch): return {"ner_tags_str": [tags.int2str(idx) for idx in batch["ner_tags"]]} 
panx_de = panx_ch["de"].map(create_tag_names)

現在我們有了人類可讀格式的標簽,讓我們看看訓練集中第一個例子的標記和標簽是如何對齊的:

de_example = panx_de["train"][0] 
pd.DataFrame([de_example["tokens"], de_example["ner_tags_str"]], ['Tokens', 'Tags'])

LOC標簽的存在是有意義的,因為句子 "2,000 Einwohnern an der Danziger Bucht in der polnischen Woiwodschaft Pommern "在英語中是指 “波蘭波美拉尼亞省格但斯克灣的2,000名居民”,而格但斯克灣是波羅的海的一個海灣,而 "voivodeship "對應的是波蘭的一個州。

作為快速檢查,我們沒有考慮在標簽中出現任何不尋常的不平衡,讓我們計算每個實體在每個子集中的頻率:

from collections import Counter 
split2freqs = defaultdict(Counter) 
for split, dataset in panx_de.items(): for row in dataset["ner_tags_str"]: for tag in row: if tag.startswith("B"): tag_type = tag.split("-")[1] split2freqs[split][tag_type] += 1 pd.DataFrame.from_dict(split2freqs, orient="index")

這看起來不錯–PER、LOC和ORG頻率的分布在每個分組中大致相同,因此驗證集和測試集應該能夠很好地衡量我們的NER抽取器的識別能力。 接下來,讓我們看看幾個流行的多語言Transformers,以及如何調整它們來處理我們的NER任務。

多語言Transformers

多語言Transformers涉及類似于單語言Transformers的架構和訓練程序,只是用于預訓練的語料庫由多種語言的文件組成。 這種方法的一個顯著特點是,盡管沒有收到區分語言的明確信息,但所產生的語言表征能夠在各種下游任務中很好地跨語言進行概括。 在某些情況下,這種進行跨語言轉移的能力可以產生與單語言模型相競爭的結果,這就規避了為每一種語言訓練一個模型的需要。

為了衡量NER的跨語言轉移的進展,CoNLL2002和CoNLL-2003數據集通常被用作英語、荷蘭語、西班牙語和德語的基準。 這個基準由新聞文章組成,其標注的LOC、PER和ORG類別與PAN-X相同,但它包含一個額外的MISC標簽,用于標注不屬于前三組的雜項實體。 多語言Transformers模型通常以三種不同的方式進行評估:

en
在英語訓練數據上進行微調,然后在每種語言的測試集中進行評估。

every
在單語測試數據上進行微調和評估,以衡量perlanguage的性能。

all

在所有的訓練數據上進行微調,在每種語言的測試集上進行評估。

我們將對我們的NER任務采取類似的評估策略,但首先我們需要選擇一個模型來評估。 最早的多語言Transformers之一是mBERT,它使用與BERT相同的架構和預訓練目標,但在預訓練語料庫中加入了許多語言的維基百科文章。 從那時起,mBERT已經被XLM-RoBERTa(簡稱XLM-R)所取代,所以這就是我們在本章要考慮的模型。

正如我們在第3章中所看到的,XLM-R只使用MLM作為100種語言的預訓練目標,但與它的前輩相比,它的預訓練語料庫的規模巨大,因此而與眾不同。 每種語言的維基百科轉儲和2.5TB的網絡通用抓取數據。 這個語料庫比早期模型所使用的語料庫要大幾個數量級,并為像緬甸語和斯瓦希里語這樣只有少量維基百科文章的低資源語言提供了顯著的信號提升。

該模型名稱中的RoBERTa部分是指預訓練方法與單語RoBERTa模型相同。

RoBERTa的開發者對BERT的幾個方面進行了改進,特別是完全取消了下一句話的預測任務。 XLM-R還放棄了XLM中使用的語言嵌入,使用SentencePiece直接對原始文本進行標記。 除了多語言性質,XLM-R和RoBERTa之間的一個顯著區別是各自詞匯表的規模。 250,000個標記對55,000個!

XLM-R是多語言NLU任務的最佳選擇。 在下一節中,我們將探討它如何在多種語言中有效地進行標記化。

進一步了解Tokenization

XLM-R沒有使用WordPiece標記器,而是使用一個名為SentencePiece的標記器,該標記器是在所有一百種語言的原始文本上訓練出來的。 為了感受一下SentencePiece與WordPiece的比較,讓我們以通常的方式用Transformers加載BERT和XLM-R標記器:

from transformers import AutoTokenizer 
bert_model_name = "bert-base-cased" 
xlmr_model_name = "xlm-roberta-base" 
bert_tokenizer = AutoTokenizer.from_pretrained(bert_model_name) 
xlmr_tokenizer = AutoTokenizer.from_pretrained(xlmr_model_name)

通過對一小段文字的編碼,我們也可以檢索到每個模型在預訓練時使用的特殊標記:

text = "Jack Sparrow loves New York!" 
bert_tokens = bert_tokenizer(text).tokens() 
xlmr_tokens = xlmr_tokenizer(text).tokens()


這里我們看到,XLM-R使用< s>和< /s>來表示一個序列的開始和結束,而不是BERT用于句子分類任務的[CLS]和[SEP]標記。 這些令牌是在標記化的最后階段添加的,我們接下來會看到。

Tokenizer流水線

到目前為止,我們已經將標記化作為一個單一的操作,將字符串轉換為我們可以通過模型傳遞的整數。 這并不完全準確,如果我們仔細觀察,可以發現它實際上是一個完整的處理流水線,通常由四個步驟組成,如圖4-1所示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IFKDItpU-1647525716286)(images/chapter4/image-20220213161546813.png)]

讓我們仔細看看每個處理步驟,并通過 "杰克-斯派洛愛紐約!"這個例子說明它們的效果:

Normalization

這一步對應于你對原始字符串進行的一系列操作,以使其 “更干凈”。 Unicode規范化是另一種常見的規范化操作,由許多標記器應用,以處理同一字符經常存在各種寫法的事實。 這可以使 "相同 "字符串的兩個版本(即具有相同的抽象字符序列)看起來不同。 像NFC、NFD、NFKC和NFKD這樣的Unicode規范化方案用標準形式取代了書寫同一字符的各種方式。 規范化的另一個例子是小寫字母。 如果預期模型只接受和使用小寫字母,那么可以用這種技術來減少它所需要的詞匯量的大小。 經過規范化處理后,我們的例子字符串將看起來像 “Jack Sparrow loves new york!”。

Pretokenization

這一步將文本分割成更小的對象,為訓練結束時的標記提供一個上限。 一個好的方法是,預編碼器將把你的文本分成 “詞”,而你的最終標記將是這些詞的一部分。 對于允許這樣做的語言(英語、德語和許多印歐語系語言),字符串通常可以在空白處和標點符號上被分割成單詞。 例如,這一步可能會改變我們的[“jack”, “sparrow”, “loves”, “new”, “york”, “!”]。 然后,在流水線的下一個步驟中,這些詞被更簡單地用字節對編碼(BPE)或單字算法分割成子字。 然而,分割成 "字 "并不總是一個微不足道的確定操作,甚至不是一個有意義的操作。 例如,在中文、日文或韓文等語言中,在語義單位(如印歐語詞)中對符號進行分組可以是一種非確定性的操作,有幾個同樣有效的分組。 在這種情況下,最好不要對文本進行預編碼,而是使用特定的語言庫進行預編碼。

Tokenizer model

一旦輸入文本被規范化和預標記化,標記化器就會在單詞上應用一個子詞分割模型。這是流水線的一部分,需要在你的語料庫上進行訓練(如果你使用的是預訓練的標記器,則是已經訓練過的)。該模型的作用是將詞分成子詞,以減少詞匯量的大小,并試圖減少詞匯外標記的數量。存在幾種子詞標記化算法,包括BPE、Unigram和WordPiece。例如,我們運行的例子在應用標記化模型后可能看起來像[jack, spa, rrow, loves, new, york, !] 。請注意,此時我們不再有一個字符串的列表,而是一個整數的列表(輸入ID);為了保持這個例子的說明性,我們保留了單詞,但去掉了引號以表示轉換。

Postprocessing

這是標記化流水線的最后一步,在這一步中,可以對標記列表進行一些額外的轉換–例如,在輸入的標記索引序列的開頭或結尾添加特殊標記。例如,一個BERT風格的標記器會添加分類和分隔符。[CLS, jack, spa, rrow, loves, new, york, !, SEP]。這個序列(請記住,這將是一個整數序列,而不是你在這里看到的標記)然后可以被送入模型。

回到我們對XLM-R和BERT的比較,我們現在明白SentencePiece在后處理步驟中添加了< s>和< /s>,而不是[CLS]和[SEP](作為慣例,我們將在圖形說明中繼續使用[CLS]和[SEP])。讓我們回到SentencePiece標記器,看看它的特殊之處。

SentencePiece Tokenizer

SentencePiece標記器是基于一種稱為Unigram的子詞分割,并將每個輸入文本編碼為Unicode字符序列。這最后一個特點對多語言語料庫特別有用,因為它允許SentencePiece對口音、標點符號以及許多語言(如日語)沒有空白字符的事實不加考慮。SentencePiece的另一個特點是空白字符被分配到Unicode符號U+2581,即▁字符,也叫下四分之一塊字符。這使得SentencePiece能夠在沒有歧義的情況下對一個序列進行去標記,而不需要依賴特定語言的預標記器。例如,在我們上一節的例子中,我們可以看到WordPiece丟失了 "York “和”!"之間沒有空白的信息。相比之下,SentencePiece保留了標記化文本中的空白,因此我們可以毫無歧義地轉換回原始文本:

"".join(xlmr_tokens).replace(u"\u2581", " ")
'<s> Jack Sparrow loves New York!</s>'

現在我們了解了SentencePiece的工作原理,讓我們看看如何將我們的簡單例子編碼成適合NER的形式。首先要做的是給預訓練的模型加載一個標記分類頭。但我們不是直接從Transformers中加載這個頭,而是自己建立它! 通過深入研究Transformers API,我們只需幾個步驟就可以做到這一點。

命名實體識別的Transformers

在第2章中,我們看到,對于文本分類,BERT使用特殊的[CLS]標記來表示整個文本序列。然后,該表示法通過一個全連接或dense層來輸出所有離散標簽值的分布,如圖4-2所示。

BERT和其他僅有編碼器的變換器對NER采取了類似的方法,只是每個單獨的輸入標記的表示被送入同一個全連接層以輸出標記的實體。由于這個原因,NER經常被看作是一個標記分類任務。這個過程看起來像圖4-3中的圖表。

到目前為止,情況還不錯,但是在標記分類任務中,我們應該如何處理子詞呢?例如,圖4-3中的名字 "Christa "被標記為子詞 "Chr "和 “##ista”,那么哪一個子詞應該被賦予B-PER標簽呢?

在BERT的論文中,作者將這個標簽分配給了第一個子詞(在我們的例子中是 “Chr”),而忽略了后面的子詞("##ista")。這就是我們在這里采用的慣例,我們將用IGN來表示被忽略的子詞。我們以后可以在后處理步驟中輕松地將第一個子詞的預測標簽傳播到后面的子詞。我們可以 也可以選擇通過給它分配一個B-LOC標簽的副本來包括 "##ista "子詞的表示,但這違反了IOB2的格式。

幸運的是,我們在BERT中看到的所有架構方面都延續到了XLM-R,因為它的架構是基于RoBERTa的,與BERT完全相同。接下來我們將看到Transformers是如何通過微小的修改來支持許多其他任務的。

Transformers 模型類的剖析

Transformers 是圍繞每個架構和任務的專用類來組織的。與不同任務相關的模型類是根據For慣例命名的,當使用AutoModel類時,則是AutoModelFor。

然而,這種方法有其局限性,為了激勵深入了解Transformer API,請考慮以下情況。假設你有一個很好的想法,用一個Transformers 模型來解決一個你想了很久的NLP問題。因此,你安排了一次與老板的會議,并通過一個藝術性的PowerPoint演示文稿,推銷說如果你能最終解決這個問題,就能增加你部門的收入。你豐富多彩的演示和對利潤的談論給你留下了深刻印象,你的老板慷慨地同意給你一個星期的時間來建立一個概念驗證。對結果感到滿意,你馬上開始工作。你啟動了你的GPU并打開了一個筆記本。你執行從transformers導入BertForTaskXY(注意,TaskXY是你想解決的假想任務),當可怕的紅色充滿你的屏幕時,你的臉色一下子變了。ImportError: cannot import name BertForTaskXY. 哦,不,沒有適合你的用例的BERT模型! 如果你必須自己實現整個模型,你怎么能在一周內完成這個項目呢!?你甚至應該從哪里開始?

不要驚慌! Transformers 的設計是為了使你能夠為你的特定使用情況輕松地擴展現有的模型。你可以從預訓練的模型中加載權重,并且你可以訪問特定任務的輔助函數。這讓你可以用很少的開銷為特定目標建立自定義模型。在本節中,我們將看到我們如何實現我們自己的自定義模型。

主體和頭部

使得Transformers 如此多才多藝的主要概念是將架構分成主體和頭部(正如我們在第一章中看到的)。我們已經看到,當我們從預訓練任務切換到下游任務時,我們需要將模型的最后一層替換成適合該任務的一層。這最后一層被稱為模型頭;它是特定任務的部分。模型的其余部分被稱為主體;它包括與任務無關的標記嵌入和Transformers層。這種結構也反映在Transformers代碼中:模型的主體由BertModel或GPT2Model這樣的類來實現,它返回最后一層的隱藏狀態。特定任務的模型,如BertForMaskedLM或BertForSequenceClassification使用基礎模型,并在隱藏狀態的基礎上添加必要的頭,如圖4-4所示。

正如我們接下來要看到的,這種主體和頭部的分離使我們能夠為任何任務建立一個定制的頭,并將其安裝在一個預訓練的模型之上即可。

為標記分類創建一個自定義模型

讓我們經歷一下為XLM-R建立一個自定義的標記分類頭的練習。由于XLM-R使用與RoBERTa相同的模型架構,我們將使用RoBERTa作為基礎模型,但用XLM-R的特定設置進行增強。請注意,這是一個教育性練習,向你展示如何為你自己的任務建立一個自定義模型。對于標記分類,XLMRobertaForTokenClassification類已經存在,你可以從Transformers導入。如果你愿意,你可以跳到下一節,直接使用那個。

為了開始工作,我們需要一個數據結構來表示我們的XLM-R NER標記器。首先,我們需要一個配置對象來初始化模型,以及一個forward()函數來生成輸出。讓我們繼續建立我們的XLM-R類,用于標記分類:

import torch.nn as nn from transformers
import XLMRobertaConfig from transformers.modeling_outputs
import TokenClassifierOutput from transformers.models.roberta.modeling_roberta
import RobertaModel from transformers.models.roberta.modeling_roberta
import RobertaPreTrainedModel 
class XLMRobertaForTokenClassification( RobertaPreTrainedModel): config_class = XLMRobertaConfig def __init__(self, config): super().__init__(config) self.num_labels = config.num_labels# Load model body self.roberta = RobertaModel( config, add_pooling_layer = False)# Set up token classification head self.dropout = nn.Dropout(config.hidden_dropout_prob) self.classifier = nn.Linear(config.hidden_size, config.num_labels)# Load and initialize weights self.init_weights() def forward(self, input_ids = None, attention_mask = None, token_type_ids = None, labels = None, ** kwargs): #Use model body to get encoder representations outputs = self.roberta(input_ids, attention_mask = attention_mask,token_type_ids = token_type_ids, * * kwargs)# Apply classifier to encoder representation sequence_output =  self.dropout(outputs[0]) logits = self.classifier( sequence_output)# Calculate losses loss = Noneif labels is not None: loss_fct = nn.CrossEntropyLoss() loss =loss_fct(logits.view(-1, self.num_labels), labels.view(-1))# Return model output objectreturn TokenClassifierOutput(loss = loss, logits = logits, hidden_states = outputs.hidden_states, attentions =  outputs.attentions)

config_class確保我們在初始化一個新模型時使用標準的XLM-R設置。如果你想改變默認參數,你可以通過覆蓋配置中的默認設置來實現。通過super()方法,我們調用RobertaPreTrainedModel類的初始化函數。這個抽象類處理初始化或加載預訓練的權重。然后我們加載我們的模型主體,也就是RobertaModel,并用我們自己的分類頭來擴展它,包括一個dropout和一個標準前饋層。注意,我們設置add_pooling_layer=False,以確保所有的隱藏狀態都被返回,而不僅僅是與[CLS]標記相關的一個。最后,我們通過調用從RobertaPreTrainedModel繼承的init_weights()方法來初始化所有權重,該方法將加載模型主體的預訓練權重,并隨機初始化我們標記分類頭的權重。

唯一要做的就是用forward()方法定義模型在前向傳遞中應該做什么。在前向傳遞過程中,數據首先通過模型主體被輸入。有許多輸入變量,但我們現在唯一需要的是input_ids 和 attention_mask。隱藏狀態是模型主體輸出的一部分,然后通過dropout和分類層進行反饋。如果我們在前向傳遞中也提供標簽,我們可以直接計算損失。如果有一個注意力掩碼,我們需要多做一點工作,以確保我們只計算未掩碼的標記的損失。最后,我們將所有的輸出包在一個TokenClassifierOutput對象中,允許我們訪問前幾章中熟悉的命名元組中的元素。

通過實現一個簡單類的兩個函數,我們就可以建立我們自己的自定義Transformers模型。由于我們繼承了PreTrainedModel,我們可以立即獲得所有有用的Transformer工具,比如from_pretrained()! 讓我們來看看我們如何將預訓練的權重加載到我們的自定義模型中。

加載一個自定義模型

現在我們準備加載我們的標記分類模型。我們需要在模型名稱之外提供一些額外的信息,包括我們將用于標記每個實體的標簽,以及每個標簽與ID的映射,反之亦然。所有這些信息都可以從我們的tags變量中得到,作為一個ClassLabel對象,它有一個names屬性,我們可以用它來導出映射。

index2tag = {idx: tag for idx, tag in enumerate(tags.names)} 
tag2index = {tag: idx for idx, tag in enumerate(tags.names)}

我們將把這些映射和tags.num_classes屬性存儲在我們在第三章遇到的AutoConfig對象中。向from_pretrained()方法傳遞關鍵字參數會覆蓋默認值:

from transformers import AutoConfig 
xlmr_config = AutoConfig.from_pretrained(xlmr_model_name, num_labels=tags.num_classes, id2label=index2tag, label2id=tag2index)

AutoConfig類包含了一個模型的架構藍圖。當我們用AutoModel.from_pretrained(model_ckpt)加載一個模型時,與該模型相關的配置文件會自動下載。然而,如果我們想修改諸如 類或標簽名稱,那么我們可以先用我們想定制的參數加載配置。

現在,我們可以像往常一樣用帶有額外配置參數的from_pretrained()函數加載模型權重。注意,我們沒有在我們的自定義模型類中實現加載預訓練的權重;我們通過繼承RobertaPreTrainedModel免費獲得這個功能:

import torch device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 
xlmr_model = (XLMRobertaForTokenClassification .from_pretrained(xlmr_model_name, config=xlmr_config) .to(device))

作為一個快速檢查,我們已經正確地初始化了標記器和模型,讓我們在已知實體的小序列上測試預測:

input_ids = xlmr_tokenizer.encode(text, return_tensors="pt") 
pd.DataFrame([xlmr_tokens, input_ids[0].numpy()], index=["Tokens", "Input IDs"])

正如你在這里看到的,開始和結束標記分別被賦予0和2的ID。

最后,我們需要將輸入傳遞給模型,并通過獲取argmax來提取預測,以獲得每個標記最可能的類別:

outputs = xlmr_model(input_ids.to(device)).logits 
predictions = torch.argmax(outputs, dim=-1) 
print(f"Number of tokens in sequence: {len(xlmr_tokens)}") 
print(f"Shape of outputs: {outputs.shape}") Number of tokens in sequence: 10 
Shape of outputs: torch.Size([1, 10, 7])

這里我們看到對數的形狀是[batch_size, num_tokens, num_tags],每個標記在七個可能的NER標記中都有一個對數。通過對序列的枚舉,我們可以很快看到預訓練模型的預測:

preds = [tags.names[p] for p in predictions[0].cpu().numpy()] 
pd.DataFrame([xlmr_tokens, preds], index=["Tokens", "Tags"])

不出所料,我們的隨機權重的標記分類層還有很多不足之處;讓我們在一些標記的數據上進行微調,使其變得更好!在這之前,讓我們把前面的步驟封裝在以后使用的輔助函數中。在這樣做之前,讓我們把前面的步驟打包成一個輔助函數,供以后使用:

def tag_text(text, tags, model, tokenizer):# Get tokens with special characters tokens = tokenizer(text).tokens() # Encode the sequence into IDs input_ids = xlmr_tokenizer(text, return_tensors="pt").input_ids.to(device) # Get predictions as distribution over 7 possible classes outputs = model(inputs)[0] # Take argmax to get most likely class per token predictions = torch.argmax(outputs, dim=2) # Convert to DataFrame preds = [tags.names[p] for p in predictions[0].cpu().numpy()] return pd.DataFrame([tokens, preds], index=["Tokens", "Tags"])

在我們訓練模型之前,我們還需要對輸入進行標記化,并準備好標簽。我們接下來會做這個。

將文本標記化以用于NER

現在我們已經確定標記器和模型可以對單個例子進行編碼,我們的下一步是對整個數據集進行標記,以便我們可以將其傳遞給XLM-R模型進行微調。正如我們在第二章中所看到的,Datasets提供了一種快速的方法,用map()操作對數據集對象進行標記化。要實現這一點,請回憶一下,我們首先需要定義一個具有最小簽名的函數:

function(examples: Dict[str, List]) -> Dict[str, List]

其中examples相當于數據集的一個片斷,例如panx_de[‘train’][:10]。由于XLM-R標記器為模型的輸入返回了輸入ID,我們只需要用注意力掩碼和標簽ID來增加這些信息,這些標簽編碼了與每個NER標記相關的標記信息。

按照Transformers文檔中的方法,讓我們看看這在我們的單一德語例子中是如何工作的,首先收集單詞和標簽作為普通列表:

words, labels = de_example["tokens"], de_example["ner_tags"]

接下來,我們對每個詞進行標記,并使用is_split_into_words參數來告訴標記器,我們的輸入序列已經被分割成單詞:

tokenized_input = xlmr_tokenizer(de_example["tokens"], is_split_into_words=True) 
tokens = xlmr_tokenizer.convert_ids_to_tokens(tokenized_input["input_ids"]) 
pd.DataFrame([tokens], index=["Tokens"])

在這個例子中,我們可以看到標記器將 "Einwohnern "分成兩個子詞,"▁Einwohner "和 “n”。由于我們遵循的慣例是只有"▁Einwohner "應該與B-LOC標簽相關聯,我們需要一種方法來掩蓋第一個子詞之后的子詞表示。幸運的是,tokenized_input是一個包含word_ids()函數的類,可以幫助我們實現這個目標:

word_ids = tokenized_input.word_ids() 
pd.DataFrame([tokens, word_ids], index=["Tokens", "Word IDs"])

在這里我們可以看到,word_ids已經將每個子詞映射到單詞序列中的相應索引,所以第一個子詞"▁2.000 "被分配到索引0,而"▁Einwohner "和 "n "被分配到索引1(因為 "Einwohnern "是單詞中的第二個單詞)。我們還可以看到,像< s>和< /s>這樣的特殊標記被映射為無。讓我們把-100設為這些特殊標記和我們希望在訓練中屏蔽的子詞的標簽:

previous_word_idx = None 
label_ids = [] 
for word_idx in word_ids: if word_idx is None or word_idx == previous_word_idx: label_ids.append(-100) elif word_idx != previous_word_idx: label_ids.append(labels[word_idx]) previous_word_idx = word_idx 
labels = [index2tag[l] if l != -100 else "IGN" for l in label_ids] 
index = ["Tokens", "Word IDs", "Label IDs", "Labels"] 
pd.DataFrame([tokens, word_ids, label_ids, labels], index=index)

注意事項

為什么我們選擇-100作為屏蔽子詞表示的ID?原因是在PyTorch中,交叉熵損失類torch.nn.CrossEntropyLoss有一個名為ignore_index的屬性,其值為-100。這個指數在訓練過程中會被忽略,所以我們可以用它來忽略與連續子詞相關的標記。

就這樣了 我們可以清楚地看到標簽ID是如何與標記對齊的,所以讓我們通過定義一個包含所有邏輯的單一函數,將其擴展到整個數據集:

def tokenize_and_align_labels(examples): tokenized_inputs = xlmr_tokenizer(examples["tokens"], truncation=True, is_split_into_words=True) labels = [] for idx, label in enumerate(examples["ner_tags"]): word_ids = tokenized_inputs.word_ids(batch_index=idx) previous_word_idx = None label_ids = [] for word_idx in word_ids: if word_idx is None or word_idx == previous_word_idx: label_ids.append(-100) else: label_ids.append(label[word_idx]) previous_word_idx = word_idx labels.append(label_ids) tokenized_inputs["labels"] = labels return tokenized_inputs

我們現在有了對每個分裂進行編碼所需的所有成分,所以讓我們寫一個我們可以迭代的函數:

def encode_panx_dataset(corpus): return corpus.map(tokenize_and_align_labels, batched=True, remove_columns=['langs', 'ner_tags', 'tokens'])

將這個函數應用于DatasetDict對象,我們就可以得到每個分割的編碼數據集對象。讓我們用它來對我們的德語語料庫進行編碼:

panx_de_encoded = encode_panx_dataset(panx_ch["de"])

現在我們有一個模型和一個數據集,我們需要定義一個性能指標。

性能評估

評估NER模型與評估文本分類模型類似,通常報告精度、召回率和F-score的結果。唯一的微妙之處在于,一個實體的所有單詞都需要被正確預測,這樣才能算作正確的預測。幸運的是,有一個叫seqeval的漂亮庫,是為這類任務設計的。例如,給定一些占位的NER標簽和模型預測,我們可以通過seqeval的classification_report()函數來計算度量:

from seqeval.metrics import classification_report 
y_true = [["O", "O", "O", "B-MISC", "I-MISC", "I-MISC", "O"], ["B-PER", "I-PER", "O"]] 
y_pred = [["O", "O", "B-MISC", "I-MISC", "I-MISC", "I-MISC", "O"], ["B-PER", "I-PER", "O"]] 
print(classification_report(y_true, y_pred))

正如我們所看到的,seqeval期望預測和標簽為列表,每個列表對應于我們驗證集或測試集中的一個例子。為了在訓練過程中整合這些指標,我們需要一個函數來獲取模型的輸出并將其轉換為seqeval所期望的列表。下面的函數通過確保我們忽略與后續子詞相關的標簽ID來完成這個任務:

import numpy as np 
def align_predictions(predictions, label_ids): preds = np.argmax(predictions, axis=2) batch_size, seq_len = preds.shape labels_list, preds_list = [], [] for batch_idx in range(batch_size): example_labels, example_preds = [], [] for seq_idx in range(seq_len): # Ignore label IDs = -100 if label_ids[batch_idx, seq_idx] != -100: example_labels.append(index2tag[label_ids[batch_idx] [seq_idx]]) example_preds.append(index2tag[preds[batch_idx][seq_idx]]) labels_list.append(example_labels) preds_list.append(example_preds) return preds_list, labels_list

有了性能指標,我們就可以開始實際訓練模型了。

微調 XLM-RoBERTa

我們現在有了對我們的模型進行微調的所有材料!我們的第一個策略是在PAN-X的德語子集上對我們的基本模型進行微調,然后評估它在法語和意大利語上的零散跨語言表現。我們的第一個策略是在PAN-X的德語子集上微調我們的基礎模型,然后評估它在法語、意大利語和英語上的零起點跨語言性能。像往常一樣,我們將使用變形金剛訓練器來處理我們的訓練循環,所以首先我們需要使用TrainingArguments類來定義訓練屬性:

from transformers import TrainingArguments 
num_epochs = 3 
batch_size = 24 
logging_steps = len(panx_de_encoded["train"]) // batch_size 
model_name = f"{xlmr_model_name}-finetuned-panx-de" 
training_args = TrainingArguments( output_dir=model_name, log_level="error", num_train_epochs=num_epochs, per_device_train_batch_size=batch_size, per_device_eval_batch_size=batch_size, evaluation_strategy="epoch", save_steps=1e6, weight_decay=0.01, disable_tqdm=False, logging_steps=logging_steps, push_to_hub=True)

在這里,我們在每個歷時結束時評估模型在驗證集上的預測,調整權重衰減,并將save_steps設置為一個大數字,以禁用檢查點,從而加快訓練速度。

這也是確保我們登錄到Hugging Face Hub的一個好時機(如果你在終端工作,你可以執行huggingface-cli login命令)。

from huggingface_hub 
import notebook_login notebook_login()

我們還需要告訴Trainer如何在驗證集上計算指標,所以在這里我們可以使用之前定義的align_predictions()函數,以seqeval需要的格式提取預測和標簽,以計算F-score:

from seqeval.metrics import f1_score 
def compute_metrics(eval_pred): y_pred, y_true = align_predictions(eval_pred.predictions, eval_pred.label_ids)return {"f1": f1_score(y_true, y_pred)}

最后一步是定義一個數據整理器,這樣我們就可以把每個輸入序列填充到一個批次的最大序列長度。Transformers提供了一個 專用于標記分類的數據整理器,它將與輸入一起填充標簽。

from transformers import DataCollatorForTokenClassification 
data_collator = DataCollatorForTokenClassification(xlmr_tokenizer)

填充標簽是必要的,因為與文本分類任務不同,標簽也是序列。這里的一個重要細節是,標簽序列被填充了-100的值,正如我們所看到的,PyTorch損失函數會忽略這個值。

我們將在本章中訓練幾個模型,所以我們將通過創建model_init()方法來避免為每個訓練者初始化一個新的模型。這個方法會加載一個未訓練過的模型,并在調用train()的開始階段被調用:

def model_init(): return (XLMRobertaForTokenClassification .from_pretrained(xlmr_model_name, config=xlmr_config) .to(device))

現在我們可以將所有這些信息連同編碼的數據集一起傳遞給Trainer:

from transformers import Trainer 
trainer = Trainer(model_init=model_init, args=training_args, data_collator=data_collator, compute_metrics=compute_metrics, train_dataset=panx_de_encoded["train"], eval_dataset=panx_de_encoded["validation"], tokenizer=xlmr_tokenizer)

然后按如下方式運行訓練循環,并將最終模型推送給Hub:

trainer.train() trainer.push_to_hub(commit_message="Training completed!")

這些F1分數對于一個NER模型來說是相當不錯的。為了確認我們的模型按預期工作,讓我們在我們的簡單例子的德語翻譯上測試它:

text_de = "Jeff Dean ist ein Informatiker bei Google in Kalifornien" 
tag_text(text_de, tags, trainer.model, xlmr_tokenizer)

上圖可以看出模型是有效的! 但是,我們永遠不應該根據一個單一的例子而對性能過于自信。相反,我們應該對模型的錯誤進行適當和細致的驗證。在下一節中,我們將探討如何在NER任務中做到這一點。

錯誤分析

在我們深入研究XLM-R的多語言方面之前,讓我們花點時間調查一下我們模型的錯誤。正如我們在第二章中所看到的,在訓練和調試變換器(以及一般的機器學習模型)時,對你的模型進行徹底的錯誤分析是最重要的方面之一。有幾種失敗模式,在這些模式下,可能看起來模型表現良好,而實際上它有一些嚴重的缺陷。訓練可能失敗的例子包括:

  • 我們可能不小心掩蓋了太多的標記,也掩蓋了一些我們的標簽,從而得到一個真正有希望的損失下降。

  • compute_metrics()函數可能有一個錯誤,高估了真實的性能。

  • 我們可能會把NER中的零類或O類實體作為一個正常的類,這將嚴重歪曲準確率和F-score,因為它是大多數人的類,差距很大。

當模型的表現比預期的要差得多時,查看錯誤可以產生有用的見解,并揭示出僅通過查看代碼很難發現的錯誤。而且,即使模型表現良好,代碼中沒有錯誤,錯誤分析仍然是了解模型的優點和缺點的有用工具。當我們在生產環境中部署模型時,這些方面我們始終需要牢記。

對于我們的分析,我們將再次使用我們所掌握的最強大的工具之一,那就是查看損失最大的驗證例子。我們可以重新使用我們在第二章中為分析序列分類模型而建立的大部分函數,但是我們現在要計算樣本序列中每個標記的損失。

讓我們定義一個我們可以應用于驗證集的方法:

from torch.nn.functional import cross_entropy 
def forward_pass_with_label(batch): # Convert dict of lists to list of dicts suitable for data collator features = [dict(zip(batch, t)) for t in zip(*batch.values())] # Pad inputs and labels and put all tensors on device batch = data_collator(features) input_ids = batch["input_ids"].to(device) attention_mask = batch["attention_mask"].to(device) labels = batch["labels"].to(device) with torch.no_grad(): # Pass data through model output = trainer.model(input_ids, attention_mask) # logit.size: [batch_size, sequence_length, classes] # Predict class with largest logit value on classes axis predicted_label = torch.argmax(output.logits, axis=-1).cpu().numpy() # Calculate loss per token after flattening batch dimension with view loss = cross_entropy(output.logits.view(-1, 7), labels.view(-1), reduction="none")# Unflatten batch dimension and convert to numpy arrayloss = loss.view(len(input_ids), -1).cpu().numpy() return {"loss":loss, "predicted_label": predicted_label}

現在我們可以使用map()將這個函數應用于整個驗證集,并將所有的數據加載到一個DataFrame中進行進一步分析:

valid_set = panx_de_encoded["validation"] 
valid_set = valid_set.map(forward_pass_with_label, batched=True, batch_size=32) 
df = valid_set.to_pandas()

代幣和標簽仍然是用它們的ID編碼的,所以讓我們把代幣和標簽映射回字符串,以便更容易閱讀結果。對于標簽為-100的填充代幣,我們分配一個特殊的標簽,即IGN,這樣我們就可以在以后過濾它們。我們還通過將損失和預測標簽字段截斷到輸入的長度來擺脫所有的填充物:

index2tag[-100] = "IGN" 
df["input_tokens"] = df["input_ids"].apply( lambda x: xlmr_tokenizer.convert_ids_to_tokens(x)) 
df["predicted_label"] = df["predicted_label"].apply( lambda x: [index2tag[i] for i in x]) 
df["labels"] = df["labels"].apply( lambda x: [index2tag[i] for i in x]) 
df['loss'] = df.apply( lambda x: x['loss'][:len(x['input_ids'])], axis=1) 
df['predicted_label'] = df.apply( lambda x: x['predicted_label'][:len(x['input_ids'])], axis=1) 
df.head(1)

每一列包含每個樣本的標記、標簽、預測標簽等的列表。讓我們通過拆開這些列表來逐一看看這些標記。這些列表。pandas.Series.explode()函數允許我們在一行中完全做到這一點,它為原始行列表中的每個元素創建一個行。由于一行中的所有列表都有相同的長度,我們可以對所有列進行并行處理。我們還放棄了我們命名為IGN的填充代幣,因為它們的損失反正是零。最后,我們將損失(仍然是numpy.Array對象)轉換成標準的浮點數:

df_tokens = df.apply(pd.Series.explode) 
df_tokens = df_tokens.query("labels != 'IGN'") 
df_tokens["loss"] = df_tokens["loss"].astype(float).round(2) 
df_tokens.head(7)

有了這種形式的數據,我們現在可以按輸入標記進行分組,并用計數、平均值和總和對每個標記的損失進行匯總。最后,我們根據損失的總和對匯總的數據進行排序,看看哪些標記在驗證集中積累了最多的損失:

( 
df_tokens.groupby("input_tokens")[["loss"]] 
.agg(["count", "mean", "sum"]) 
.droplevel(level=0, axis=1) 
# Get rid of multi-level columns 
.sort_values(by="sum", ascending=False) 
.reset_index() 
.round(2) 
.head(10) 
.T 
)

我們可以在這個列表中觀察到幾種模式。

  • 空白符號的總損失最高,這并不令人驚訝,因為它也是列表中最常見的符號。然而,它的平均損失要比列表中的其他標記低得多。這意味著模型對它的分類并不費力。

  • 像 “in”、“von”、"der "和 "und "這樣的詞出現得相對頻繁。它們經常與命名的實體一起出現,有時是它們的一部分,這解釋了為什么模型可能會把它們混在一起。

  • 括號、斜線和單詞開頭的大寫字母比較少見,但其平均損失相對較高。我們將進一步調查它們。

    我們還可以對標簽ID進行分組,看看每一類的損失:

( 
df_tokens.groupby("labels")[["loss"]] 
.agg(["count", "mean", "sum"]) 
.droplevel(level=0, axis=1) 
.sort_values(by="mean", ascending=False) 
.reset_index() 
.round(2) 
.T 
)

我們看到,B-ORG的平均損失最高,這意味著確定一個組織的開始對我們的模型構成了挑戰。

我們可以通過繪制標記分類的混淆矩陣來進一步分解,我們看到一個組織的開始經常與隨后的I-ORG標記相混淆:

from sklearn.metrics import ConfusionMatrixDisplay, confusion_matrix 
def plot_confusion_matrix(y_preds, y_true, labels): cm = confusion_matrix(y_true, y_preds, normalize="true") fig, ax = plt.subplots(figsize=(6, 6)) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels) disp.plot(cmap="Blues", values_format=".2f", ax=ax, colorbar=False) plt.title("Normalized confusion matrix") plt.show() 
plot_confusion_matrix(df_tokens["labels"], df_tokens["predicted_label"], tags.names

從圖中我們可以看出,我們的模型傾向于混淆B-ORG和IORG實體最多。除此之外,它在對其余實體進行分類時表現得相當好,這一點從混淆矩陣的近對角線性質可以看出。

現在我們已經檢查了標記水平上的錯誤,讓我們繼續看一下具有高損失的序列。在這個計算中,我們將重新審視我們的 "未爆炸 "數據框架,通過對每個標記的損失進行加總來計算總損失。要做到這一點,首先讓我們寫一個函數,幫助我們顯示帶有標簽和損失的標記序列。


很明顯,這些樣本的標簽有問題;例如,聯合國和中非共和國分別被標為一個人!這是不對的。同時,第一個例子中的 "8.Juli "被標記為一個組織。事實證明,PAN-X數據集的注釋是通過一個自動過程產生的。這樣的注釋通常被稱為 “銀質標準”(與人類生成的注釋的 "黃金標準 "形成對比),而且毫不奇怪,有些情況下,自動方法未能產生合理的標簽。事實上,這樣的失敗模式并不是自動方法所獨有的;即使人類仔細地注釋數據,當注釋者的注意力減退或者他們只是誤解了句子,也會發生錯誤。

我們先前注意到的另一件事是,括號和斜線的損失相對較高。讓我們來看看幾個帶有開頭小括號的序列的例子:

df_tmp = df.loc[df["input_tokens"].apply(lambda x: u"\u2581(" in x)].head(2) 
for sample in get_samples(df_tmp): display(sample)

一般來說,我們不會把括號及其內容作為命名實體的一部分,但這似乎是自動提取注釋文件的方式。在其他的例子中,括號里包含了一個地理規范。雖然這確實也是一個位置,但我們可能希望在注釋中把它與原始位置斷開。這個數據集由不同語言的維基百科文章組成,文章標題經常在括號中包含某種解釋。例如,在第一個例子中,括號里的文字表明Hama是一個 “Unternehmen”,即英文中的公司。當我們推出這個模型時,這些細節是很重要的,因為它們可能對模型所在的整個管道的下游性能產生影響。

通過一個相對簡單的分析,我們已經發現了我們的模型和數據集的一些弱點。在一個真實的用例中,我們會反復進行這個步驟,清理數據集,重新訓練模型,分析新的錯誤,直到我們對性能感到滿意。

在這里,我們分析了單一語言的錯誤,但我們也對跨語言的性能感興趣。在下一節中,我們將進行一些實驗,看看XLM-R的跨語言轉移的效果如何。

跨語言遷移

現在我們已經在德語上對XLM-R進行了微調,我們可以通過Trainer的predict()方法來評估它轉移到其他語言的能力。由于我們計劃評估多種語言,讓我們創建一個簡單的函數,為我們做這件事:

def get_f1_score(trainer, dataset): return trainer.predict(dataset).metrics["test_f1"]

我們可以用這個函數來檢查測試集的性能,并在一個dict中記錄我們的分數:

f1_scores = defaultdict(dict) 
f1_scores["de"]["de"] = get_f1_score(trainer, panx_de_encoded["test"]) 
print(f"F1-score of [de] model on [de] dataset: {f1_scores['de']['de']:.3f}")F1-score of [de] model on [de] dataset: 0.868

對于一個NER任務來說,這些結果相當不錯。我們的指標在85%左右,我們可以看到該模型在ORG實體上似乎最吃力,可能是因為這些實體在訓練數據中最不常見,而且許多組織名稱在XLM-R的詞匯中很罕見。其他語言的情況如何?為了熱身,讓我們看看我們在德語上微調的模型在法語上的表現如何:

text_fr = "Jeff Dean est informaticien chez Google en Californie" 
tag_text(text_fr, tags, trainer.model, xlmr_tokenizer)

還不錯! 雖然這兩種語言的名稱和組織都是一樣的,但該模型確實能夠正確標記 "Kalifornien "的法語翻譯。接下來,讓我們通過編寫一個簡單的函數來量化我們的德語模型在整個法語測試集上的表現,該函數對數據集進行編碼并生成分類報告:

def evaluate_lang_performance(lang, trainer):panx_ds = encode_panx_dataset(panx_ch[lang]) return get_f1_score(trainer, panx_ds["test"]) 
f1_scores["de"]["fr"] = evaluate_lang_performance("fr", trainer) 
print(f"F1-score of [de] model on [fr] dataset: {f1_scores['de']['fr']:.3f}") F1-score of [de] model on [fr] dataset: 0.714

雖然我們看到微觀平均指標下降了約15分,但請記住,我們的模型還沒有看到一個貼有標簽的法語例子!一般來說,性能的下降與語言之間的 "距離 "有關。一般來說,性能下降的大小與語言之間的 "距離 "有關。雖然德語和法語被歸類為印歐語系,但從技術上講,它們屬于不同的語系。分別是日耳曼語和羅曼語。

接下來,讓我們評估一下在意大利語上的表現。由于意大利語也是一種羅曼語,我們期望得到一個與法語類似的結果:

f1_scores["de"]["it"] = evaluate_lang_performance("it", trainer) 
print(f"F1-score of [de] model on [it] dataset: {f1_scores['de']['it']:.3f}") F1-score of [de] model on [it] dataset: 0.692

事實上,我們的期望得到了F-scores的證實。最后,讓我們來看看英語的表現,它屬于日耳曼語系的語言:

f1_scores["de"]["en"] = evaluate_lang_performance("en", trainer) 
print(f"F1-score of [de] model on [en] dataset: {f1_scores['de']['en']:.3f}") F1-score of [de] model on [en] dataset: 0.589

令人驚訝的是,我們的模型在英語上的表現最差,盡管我們可能直觀地期望德語比法語更類似于英語。在對德語進行了微調并對法語和英語進行了零點轉移之后,接下來讓我們看看什么時候直接對目標語言進行微調是有意義的。

零樣本遷移何時才能生效?

到目前為止,我們已經看到,在德語語料庫上對XLM-R進行微調,可以得到85%左右的F-score,而且不需要任何額外的訓練,該模型就能夠在我們的語料庫中的其他語言上取得適度的表現。問題是,這些結果有多好,它們與在單語語料庫上微調的XLM-R模型相比如何?

在本節中,我們將通過在越來越大的訓練集上對XLM-R進行微調,來探索法語語料庫的這個問題。通過這種方式跟蹤性能,我們可以確定在哪一點上零點跨語言轉移更有優勢,這在實踐中對指導關于是否收集更多標記數據的決定很有用。

為了簡單起見,我們將保持對德語語料庫進行微調時的超參數,只是我們將調整TrainingArguments的logging_steps參數,以考慮到訓練集規模的變化。我們可以用一個簡單的函數把這一切包起來,該函數接收一個對應于單語語料庫的DatasetDict對象,通過num_samples對其進行降樣,并對XLM-R進行微調,以返回最佳歷時的度量:

def train_on_subset(dataset, num_samples): train_ds = dataset["train"].shuffle(seed=42).select(range(num_samples)) valid_ds = dataset["validation"] test_ds = dataset["test"] training_args.logging_steps = len(train_ds) // batch_size trainer = Trainer(model_init=model_init, args=training_args, data_collator=data_collator, compute_metrics=compute_metrics, train_dataset=train_ds, eval_dataset=valid_ds, tokenizer=xlmr_tokenizer) trainer.train() if training_args.push_to_hub: trainer.push_to_hub(commit_message="Training completed!") f1_score = get_f1_score(trainer, test_ds) return pd.DataFrame.from_dict( {"num_samples": [len(train_ds)], "f1_score": [f1_score]})

正如我們對德語語料庫的微調一樣,我們也需要將法語語料庫編碼為輸入ID、注意力掩碼和標簽ID:

panx_fr_encoded = encode_panx_dataset(panx_ch["fr"])

接下來,讓我們通過在250個例子的小型訓練集上運行來檢查我們的函數是否有效:

training_args.push_to_hub = False 
metrics_df = train_on_subset(panx_fr_encoded, 250) metrics_df

我們可以看到,在只有250個例子的情況下,法語的微調在很大程度上低于德語的零槍轉移。現在讓我們把訓練集的大小增加到500、1000、2000和4000個例子,以了解性能的提高:

for num_samples in [500, 1000, 2000, 4000]: metrics_df = metrics_df.append( train_on_subset(panx_fr_encoded, num_samples), ignore_index=True)

我們可以通過繪制測試集上的F-scores作為增加訓練集大小的函數,來比較法語樣本的微調與德語的零點跨語言轉移之間的比較:

fig, ax = plt.subplots() 
ax.axhline(f1_scores["de"]["fr"], ls="--", color="r") 
metrics_df.set_index("num_samples").plot(ax=ax) 
plt.legend(["Zero-shot from de", "Fine-tuned on fr"], loc="lower right") plt.ylim((0, 1)) 
plt.xlabel("Number of Training Samples") 
plt.ylabel("F1 Score") 
plt.show()

從圖中我們可以看到,在大約750個訓練實例之前,零點轉移一直是有競爭力的,在這之后,對法語的微調達到了與我們對德語微調時類似的性能水平。然而,這個結果是不容忽視的。根據我們的經驗,讓領域專家給幾百個文檔貼上標簽的成本很高,特別是對NER來說,貼標簽的過程很細而且很耗時。

我們可以嘗試最后一種技術來評估多語言學習:同時對多種語言進行微調!讓我們來看看如何進行微調。讓我們來看看我們如何做到這一點。

一次性對多種語言進行微調

到目前為止,我們已經看到,從德語到法語或意大利語的零拍跨語言轉移產生了約15點的性能下降。緩解這種情況的一個方法是同時對多種語言進行微調。為了看看我們能得到什么類型的收益,讓我們首先使用 concatenate_datasets()函數,將德語和法語語料庫連接起來:

from datasets import concatenate_datasets 
def concatenate_splits(corpora): multi_corpus = DatasetDict() for split in corpora[0].keys(): multi_corpus[split] = concatenate_datasets( [corpus[split] for corpus in corpora]).shuffle(seed=42) return multi_corpus 
panx_de_fr_encoded = concatenate_splits([panx_de_encoded, panx_fr_encoded])

對于訓練,我們將再次使用前幾節的超參數,因此我們可以簡單地更新訓練器中的記錄步驟、模型和數據集:

training_args.logging_steps = len(panx_de_fr_encoded["train"]) // batch_size 
training_args.push_to_hub = True 
training_args.output_dir = "xlm-roberta-base-finetuned-panx-de-fr" 
trainer = Trainer(model_init=model_init, args=training_args, data_collator=data_collator, compute_metrics=compute_metrics, tokenizer=xlmr_tokenizer, train_dataset=panx_de_fr_encoded["train"], eval_dataset=panx_de_fr_encoded["validation"])
trainer.train() 
trainer.push_to_hub(commit_message="Training completed!")

讓我們來看看該模型在每種語言的測試集上的表現:

for lang in langs: f1 = evaluate_lang_performance(lang, trainer) print(f"F1-score of [de-fr] model on [{lang}] dataset: {f1:.3f}") 
 F1-score of [de-fr] model on [de] dataset: 0.866 F1-score of [de-fr] model on [fr] dataset: 0.868 F1-score of [de-fr] model on [it] dataset: 0.815 F1-score of [de-fr] model on [en] dataset: 0.677

它在法語分詞上的表現比以前好得多,與德語測試集上的表現相當。有趣的是,它在意大利語和英語部分的表現也提高了大約10個百分點。因此,即使增加另一種語言的訓練數據,也能提高該模型在未見過的語言上的表現。

讓我們通過比較在每種語言上的微調和在所有語料庫上的多語言學習的性能來完成我們的分析。由于我們已經對德語語料庫進行了微調,我們可以用train_on_subset()函數對其余語言進行微調,num_samples等于訓練集的例子數量。

corpora = [panx_de_encoded] 
# Exclude German from iteration 
for lang in langs[1:]: training_args.output_dir = f"xlm-roberta-base-finetuned-panx-{lang}" # Fine-tune on monolingual corpus ds_encoded = encode_panx_dataset(panx_ch[lang]) metrics = train_on_subset(ds_encoded, ds_encoded["train"].num_rows) # Collect F1-scores in common dict f1_scores[lang][lang] = metrics["f1_score"][0]# Add monolingual corpus to list of corpora to concatenate corpora.append(ds_encoded)

現在我們已經對每種語言的語料庫進行了微調,下一步是將所有的分片串聯起來,創建一個所有四種語言的多語言語料庫。與之前的德語和法語分析一樣,我們可以使用concatenate_splits()函數來為我們在上一步生成的語料庫列表上完成這一步驟:

corpora_encoded = concatenate_splits(corpora)

現在我們有了我們的多語言語料庫,我們用訓練器運行熟悉的步驟:

training_args.logging_steps = len(corpora_encoded["train"]) // batch_size 
training_args.output_dir = "xlm-roberta-base-finetuned-panx-all"
trainer = Trainer(model_init=model_init, args=training_args, data_collator=data_collator, compute_metrics=compute_metrics, tokenizer=xlmr_tokenizer, train_dataset=corpora_encoded["train"], eval_dataset=corpora_encoded["validation"]) 
trainer.train() 
trainer.push_to_hub(commit_message="Training completed!")

最后一步是在每種語言的測試集上生成訓練器的預測結果。這將使我們深入了解多語言學習的真正效果。我們將在f1_scores字典中收集F-scores,然后創建一個DataFrame,總結我們多語言實驗的主要結果:

for idx, lang in enumerate(langs): f1_scores["all"][lang] = get_f1_score(trainer, corpora[idx]["test"]) 
scores_data = {"de": f1_scores["de"], "each": {lang: f1_scores[lang][lang] for lang in langs}, "all": f1_scores["all"]} 
f1_scores_df = pd.DataFrame(scores_data).T.round(4) 
f1_scores_df.rename_axis(index="Fine-tune on", columns="Evaluated on", inplace=True)f1_scores_df

從這些結果中,我們可以得出一些一般性的結論。

  • 多語言學習可以帶來顯著的性能提升,尤其是當跨語言轉移的低資源語言屬于類似的語言家族時。在我們的實驗中,我們可以看到德語、法語和意大利語在所有類別中都取得了相似的表現,這表明這些語言之間的相似度要高于英語。
  • 作為一個一般的策略,把注意力集中在語言家族內的跨語言轉移是一個好主意,特別是在處理像日語這樣的不同文字時。

發布模型部件

在這一章中,我們已經推送了很多微調過的模型到 Hub 上。盡管我們可以使用 pipeline() 函數在我們的本地機器上與它們進行交互,但 Hub 提供了非常適合這種工作流程的部件。圖4-5是我們的transformersbook/xlm-roberta-base-finetuned-panx-all檢查點的一個例子,你可以看到它在識別一個德語文本的所有實體方面做得很好。

小結

在本章中,我們看到了如何使用一個在100種語言上預訓練過的單一Transformers來處理一個多語言語料庫的NLP任務: XLM-R。盡管我們能夠證明,當只有少量的標記例子可供微調時,從德語到法語的跨語言轉換是有效的的,但如果目標語言與基礎模型被微調的語言有很大不同,或者不是預訓練時使用的100種語言之一,這種良好的性能通常就不會出現。最近的建議,如MAD-X,正是為這些低資源的情況而設計的,由于MAD-X是建立在Transformers之上的,你可以很容易地調整本章的代碼來使用它。

到目前為止,我們看了兩個任務:序列分類和標記分類。這兩個任務都屬于自然語言理解的范疇,即把文本合成為預測。在下一章中,我們將首次看到文本生成,在這里,不僅輸入內容是文本,模型的輸出也是文本。

總結

以上是生活随笔為你收集整理的nlp-with-transformers系列-04_多语言命名实体识别的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

av网站地址| 一区免费视频 | 色综合久久88色综合天天 | 国产精品18久久久久久vr | 精品亚洲免a | www.天天干.com | 亚洲精品在线观看免费 | 日韩av电影免费在线观看 | 日日躁你夜夜躁你av蜜 | 爱色av.com| 成人av免费在线 | 91精品视频观看 | 成年人网站免费观看 | 婷婷综合电影 | 国产精品99久久久久久久久久久久 | 在线观看亚洲成人 | 免费看久久久 | 日韩欧美国产激情在线播放 | 国产中文视频 | 亚洲午夜久久久久久久久 | 玖玖视频精品 | 黄网站免费大全入口 | 99久国产 | 精品国产三级a∨在线欧美 免费一级片在线观看 | 亚洲干视频在线观看 | 亚洲精品麻豆视频 | 日韩电影在线视频 | 久久国产欧美日韩 | 欧美日韩在线播放一区 | 国产精品成人一区二区三区吃奶 | 五月天激情综合 | 国产精品久久久久久久久久久久午 | 久久精品第一页 | 国产精品美女久久久久久久网站 | 日日噜噜噜噜夜夜爽亚洲精品 | 国产精品一区二区av影院萌芽 | 国产精品久久嫩一区二区免费 | 国产区精品区 | 国际av在线 | 人成免费网站 | 在线观看免费国产小视频 | 超碰99人人 | 欧美性色综合 | 国产精品欧美久久久久三级 | 九九热只有这里有精品 | 草久久av| a级国产乱理论片在线观看 伊人宗合网 | 91日韩在线播放 | 黄色成人在线网站 | 手机成人免费视频 | 久久视频在线免费观看 | 91在线看| 国产高清免费观看 | 国产精品久久久久久久久久久久冷 | 91av视频免费在线观看 | 97视频在线观看成人 | 伊色综合久久之综合久久 | 国产精品成人一区二区三区吃奶 | 国产精品不卡在线观看 | 在线日韩| www.久热| 日韩黄色在线电影 | 探花视频免费观看高清视频 | 美女国内精品自产拍在线播放 | 国产做aⅴ在线视频播放 | 97夜夜澡人人爽人人免费 | 久久精品国产第一区二区三区 | 九九九视频精品 | 久久久黄色免费网站 | 色综合天天做天天爱 | 久久国产精品99久久久久久丝袜 | 亚洲热视频 | 91av资源网 | av怡红院| 美女国产在线 | 又黄又爽又刺激视频 | 97免费在线观看视频 | 国产精品久久中文字幕 | 在线免费高清视频 | 日韩在线观看精品 | 99国产视频| 日韩在线观看三区 | 中文字幕2021 | 国产精品欧美日韩在线观看 | 免费一级毛毛片 | 国产成人一区二区三区久久精品 | 国产网站av | 正在播放五月婷婷狠狠干 | 国产精品av免费在线观看 | 日韩av成人免费看 | 黄色片视频免费 | 香蕉视频国产在线观看 | 最近久乱中文字幕 | 色狠狠干 | 狠狠地操 | 精品欧美日韩 | 久久久国产精品网站 | 中文字幕在线观看免费高清电影 | 日本精品一 | 成 人 黄 色视频免费播放 | 国产麻豆精品久久 | 天堂av观看 | 在线观看日本高清mv视频 | 在线只有精品 | 免费a网 | 99精品欧美一区二区三区黑人哦 | 亚洲精品18p | 成人在线电影观看 | 911亚洲精品第一 | 片网站| 国产精品一区二区久久精品爱微奶 | 岛国片在线 | 久久久福利影院 | 亚洲视频aaa| 天天操天天综合网 | 美女视频黄频 | www.亚洲激情.com | 国产黄色电影 | 在线观看中文字幕2021 | 亚洲国产一区在线观看 | 精品亚洲va在线va天堂资源站 | 免费观看丰满少妇做爰 | 337p西西人体大胆瓣开下部 | www.久草.com| 91精品国自产拍天天拍 | 国产日韩中文在线 | 激情五月婷婷激情 | 激情视频二区 | 色先锋av资源中文字幕 | 探花视频在线观看免费 | 久久爱992xxoo | 麻豆网站免费观看 | 五月婷婷综 | 日韩欧美一区二区不卡 | 911久久| 亚洲午夜大片 | 亚洲精品美女在线 | 婷婷香蕉| 一区二区三区在线免费观看 | 99精品色 | 日韩欧美一区二区不卡 | 国产成人精品亚洲日本在线观看 | 中文字幕在线视频一区二区三区 | 日韩久久片 | 国产一区二区视频在线 | 日韩精品第1页 | 在线观影网站 | 国产福利在线不卡 | 91九色精品女同系列 | 精品国产乱码久久久久久1区2匹 | 爱情影院aqdy鲁丝片二区 | 欧美日韩在线视频观看 | 婷婷久久丁香 | 91手机电影 | 国产婷婷久久 | 欧美日韩一区二区在线观看 | 色综合久久综合中文综合网 | 亚洲视频六区 | 欧美日本在线视频 | 日韩免费电影网 | 欧美一级片在线观看视频 | 免费视频久久久久久久 | 国内精品小视频 | 天天操一操 | 久久久午夜影院 | 69视频永久免费观看 | 国产在线观看黄 | 欧美精品视 | 国产精品videossex国产高清 | 97人人人 | 中文字幕在线观看完整版电影 | 久草在线在线精品观看 | 国产精品专区一 | 日日干天天操 | 国产中文在线视频 | avwww在线观看 | 日日日操操 | 国产h片在线观看 | 久久免费激情视频 | 毛片网站在线 | 97视频在线观看播放 | 久久成人精品 | 伊人久久av | 日韩美女高潮 | 国产成人av网站 | 亚洲日本激情 | 日韩久久片 | 日本特黄特色aaa大片免费 | 日韩美在线观看 | 亚洲久在线| 久久资源在线 | 91精品国产成人观看 | 久久国产精品免费一区 | 成年免费在线视频 | 国产精品久久久久一区二区 | 午夜国产一区二区三区四区 | 成人精品久久久 | 色91av| 国产一区视频免费在线观看 | 国产精品婷婷 | 三级黄色a | 视频1区2区 | 中文字幕专区高清在线观看 | 99国内精品 | 精品国产免费久久 | 在线视频18在线视频4k | 中文字幕日韩一区二区三区不卡 | 国产一区二区三区四区大秀 | 婷婷5月色 | 欧美日本一二三 | 国产三级精品三级在线观看 | 国产精品久久久久久爽爽爽 | 激情黄色av| 精品国产乱码久久久久久天美 | 香蕉视频在线观看免费 | 中文av在线播放 | 久久99深爱久久99精品 | 国产一级高清视频 | 久久婷婷一区二区三区 | 午夜男人影院 | 少妇bbw撒尿| 色先锋av资源中文字幕 | 免费人成网 | 日本韩国在线不卡 | 91在线中字 | 最新中文字幕在线播放 | 狠狠躁夜夜躁人人爽超碰91 | 精品国产人成亚洲区 | 激情综合五月天 | 九九欧美| 伊人狠狠色丁香婷婷综合 | 国产一级在线观看 | 日韩三级成人 | 国产伦理精品一区二区 | 一级全黄毛片 | 999国内精品永久免费视频 | 日本99干网| 99免费看片 | 久久视频网址 | 久久久久久蜜桃一区二区 | 国产成人精品一区二区三区网站观看 | 97在线超碰 | 我要看黄色一级片 | 久久99国产精品自在自在app | 免费看久久久 | 日批视频在线 | 国产午夜一级毛片 | 三上悠亚一区二区在线观看 | 五月天国产| 国产精品高潮呻吟久久久久 | 精品国产伦一区二区三区免费 | 欧美亚洲另类在线视频 | 91av网站在线观看 | 国产精品麻豆果冻传媒在线播放 | 日韩高清在线一区二区 | 国内丰满少妇猛烈精品播 | 99国内精品久久久久久久 | 国产高清视频在线 | 在线播放你懂 | 亚洲爱视频 | 色综合天天天天做夜夜夜夜做 | 成年人视频在线观看免费 | 日韩成人在线免费观看 | 国产粉嫩在线观看 | 狠狠色狠狠色综合日日92 | 婷婷午夜天 | 欧美在线91 | 欧美大香线蕉线伊人久久 | 青青草华人在线视频 | 免费久久久久久 | 成人精品一区二区三区中文字幕 | 天天干天天插伊人网 | av黄色在线播放 | 你操综合 | 日韩一区在线播放 | 免费三级在线 | 欧美国产日韩一区二区 | 久久国产女人 | 欧美韩国在线 | 国产视频在线一区二区 | 亚洲手机天堂 | 国产一级免费av | 久久午夜影视 | 99精品免费 | 国产国产人免费人成免费视频 | 美女视频久久 | 五月天综合色激情 | 黄色网www | 国产人在线成免费视频 | 亚洲午夜精 | 一级片免费观看视频 | 五月天高清欧美mv | 国产精品porn | 天天综合导航 | 亚洲黄色一级大片 | 伊人中文字幕在线 | 国内精品久久久久久久 | 国产伦理精品一区二区 | 日本特黄特色aaa大片免费 | 久久99中文字幕 | 国产资源av| 四虎影视成人永久免费观看视频 | 亚洲午夜久久久影院 | 久久久久久毛片精品免费不卡 | 国产无套视频 | 国产不卡视频在线播放 | 日韩美一区二区三区 | 婷婷视频在线 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 91av视频| 国产精品视屏 | 91精品国产一区二区三区 | 国产午夜精品视频 | 激情综合啪 | 成人在线观看资源 | 综合亚洲视频 | 韩国一区在线 | 日韩精品一区在线播放 | 免费av试看 | 色婷婷成人网 | 亚洲天天综合 | 欧美性免费 | 久久综合九色99 | 91高清免费看 | 黄色av一区 | 久久久久久网站 | 色综合天天色综合 | 在线小视频你懂得 | 国产精品 久久 | 91网站观看 | 成人黄色小说视频 | 日本巨乳在线 | 欧美一级片播放 | 97国产精品 | 91av电影在线 | www.午夜视频 | 日韩欧美不卡 | 婷婷播播网 | 丰满少妇久久久 | 91精品视频在线观看免费 | 久久精品影片 | 久久一及片 | av大全免费在线观看 | 国产理论影院 | 成人黄在线观看 | 国产色久 | 色姑娘综合网 | 日韩av播放在线 | 国模一二三区 | 日韩中文字幕免费在线播放 | 色噜噜在线观看 | 亚洲精品美女久久久 | 久久一区91 | 99热99re6国产在线播放 | 成人在线免费小视频 | 久久久在线免费观看 | 视频福利在线观看 | 97视频免费 | 国产在线视频不卡 | 欧美精品亚洲精品 | 在线观看v片 | 国产成人久久精品一区二区三区 | 精品麻豆入口免费 | 日韩免费福利 | 国产1区在线| 国产一区免费观看 | 亚洲精品国内 | 激情在线免费视频 | 又黄又爽又色无遮挡免费 | 97夜夜澡人人爽人人免费 | 久久精品屋 | 免费黄在线看 | 伊人狠狠色 | 亚洲黄色成人 | 欧美国产日韩久久 | 成人免费xyz网站 | 成人性生交大片免费看中文网站 | 日韩在线免费 | 六月久久婷婷 | 国产一二三四在线视频 | 成年人免费在线观看 | 亚洲精品国产第一综合99久久 | 欧美在线一二 | 久草a在线 | 蜜桃av人人夜夜澡人人爽 | 啪啪动态视频 | 国产午夜三级一二三区 | 国产日韩欧美自拍 | 在线免费试看 | 久久91久久久久麻豆精品 | 看污网站 | 久草视频首页 | 天天天干天天天操 | 国产中文字幕一区二区三区 | 国产精品久久人 | 高潮久久久| 国产精品男女视频 | 日韩3区 | 亚洲精品tv久久久久久久久久 | av一级黄| 综合激情婷婷 | 国产不卡一区二区视频 | 欧洲色吧 | 国产v在线播放 | 五月天综合婷婷 | 国产精品第一页在线 | 欧美日韩一级久久久久久免费看 | 最近乱久中文字幕 | 欧美精品中文字幕亚洲专区 | 国产精品成人自产拍在线观看 | 毛片网站在线看 | 99久热在线精品视频成人一区 | 欧美一区二区在线看 | 国产亚洲字幕 | 99久久电影| 天天综合导航 | 日韩r级电影在线观看 | 日韩大片在线看 | 午夜三级影院 | 久久亚洲区 | 91成人网在线观看 | a资源在线 | 91精品视频在线免费观看 | 韩国av一区二区三区 | 91在线看黄 | 国产成人精品久久二区二区 | 免费看三级黄色片 | 九九视频热| www.黄色 | 成人播放器 | 国产一区二区精品久久 | 狠狠躁18三区二区一区ai明星 | 97视频精品 | 五月婷综合网 | 在线a视频免费观看 | 91桃色免费观看 | 国产在线视频导航 | 国产精品正在播放 | 精品一区二区免费 | 一区二区三区电影大全 | 国产精品精品久久久 | 日韩一区二区三 | 九色精品免费永久在线 | 久草在线视频国产 | 看片黄网站 | 精品视频免费在线 | 在线观看亚洲精品视频 | 国产做a爱一级久久 | 香蕉国产91 | 在线日韩av | 久久a免费视频 | 国产精品久久网站 | 欧美日韩不卡一区二区 | 国产 在线 日韩 | 日韩久久久久久久久久 | 国产精品福利久久久 | av夜夜操 | 久久免费福利视频 | 97精品国产97久久久久久久久久久久 | 天天操天天干天天爽 | 色99导航| 久久久久久伊人 | 999久久a精品合区久久久 | 91精品电影| 丝袜美腿在线 | 久草久草久草久草 | 黄色片软件网站 | 久草在线免 | 黄色国产区 | 成人免费视频免费观看 | 亚洲国产精品久久久久 | 久久综合五月 | www.天天色.com| 国产精品美女久久久 | 中文在线字幕观看电影 | 欧美一区二区三区在线视频观看 | 波多野结衣小视频 | 亚洲一区精品二人人爽久久 | 欧美日韩性视频 | 在线国产精品视频 | 日韩欧美成人网 | 久久一二区 | 久久国产手机看片 | 97国产超碰| 香蕉蜜桃视频 | 久久99精品一区二区三区三区 | 99精品在线看 | 久久视频在线免费观看 | 婷婷色中文 | 91视频在线自拍 | www99精品 | 久久色中文字幕 | 播五月综合| 91视频在线自拍 | 国产黄色观看 | 成人免费一级片 | 国产在线va | 91精品国产成人www | 色视频网站免费观看 | 日韩欧美高清不卡 | 亚洲精品中文在线资源 | 久久久精品在线观看 | 国产精品久久久久av | av片在线观看免费 | 91天堂影院 | 在线观看岛国片 | 91超在线 | 欧美日韩国产二区三区 | 久久免费高清 | 四虎影视成人精品 | 久草热久草视频 | 国产精品毛片一区视频播 | 久久伊人色综合 | 日韩簧片在线观看 | 免费视频99 | 国产白浆视频 | 日韩欧美高清免费 | 六月激情 | 最近2019中文免费高清视频观看www99 | 欧美日bb| 91自拍视频在线观看 | 国模视频一区二区 | 日韩视频免费播放 | 97高清视频 | 国产无遮挡又黄又爽在线观看 | 一区二区三区免费网站 | 91麻豆操| 国产在线第三页 | 国产综合精品一区二区三区 | 在线欧美中文字幕 | 激情欧美xxxx| 青春草国产视频 | 天天爽夜夜爽人人爽曰av | 欧美日韩国产精品爽爽 | 在线视频精品播放 | 久久精品视频网站 | 成人性生交大片免费观看网站 | 日本乱视频 | 国内成人精品视频 | 午夜一级免费电影 | 中文字幕在线观看av | 麻豆视频免费入口 | 91激情 | 亚洲全部视频 | 欧美作爱视频 | 超碰在线免费福利 | 中文字幕一区av | 日日色综合 | 手机av看片 | 毛片网站在线看 | 天天爱天天射 | 精品天堂av| 日韩精品视频免费看 | 69国产在线观看 | 女人高潮特级毛片 | 国产成人一区二区在线观看 | 啪啪免费试看 | 丁香在线观看完整电影视频 | 亚洲一区二区精品在线 | 久久99热久久99精品 | 国产成人av在线影院 | 日韩大片在线免费观看 | 色网站在线看 | 成人国产电影在线观看 | 亚洲视频在线观看免费 | 色婷婷激情综合 | 国产清纯在线 | 国产小视频精品 | 在线观看免费版高清版 | 国产成人一区二区三区电影 | 亚洲成av人片 | 国产一级片免费播放 | av蜜桃在线 | 免费看v片网站 | 麻豆视频在线观看 | 久久精品小视频 | 久久人人爽人人片 | 免费观看久久久 | 久草免费在线 | 干综合网| 亚洲国产精品久久久久 | av网站地址 | 日韩午夜电影网 | 在线观看日韩中文字幕 | 欧美aⅴ在线观看 | 高清国产午夜精品久久久久久 | 国产一区二区三区免费在线观看 | 高清av网| 免费99精品国产自在在线 | 一区二区三区免费看 | 国产精品9999 | 亚洲欧美日韩国产一区二区 | 韩国精品在线观看 | 免费在线观看成人小视频 | 亚洲国产午夜视频 | 国产资源精品在线观看 | 在线视频久 | 午夜精品一区二区国产 | 国产精品毛片久久蜜 | 在线观看视频99 | 六月婷婷色 | 久久爱资源网 | 亚洲乱码精品久久久 | 天堂av影院| 成年人三级网站 | 中文字幕传媒 | 国产精品成人免费精品自在线观看 | 国产视频1区2区3区 久久夜视频 | av性网站| 综合天天久久 | 国产精品久久久久久av | 免费电影一区二区三区 | 国产在线第三页 | 香蕉视频在线免费 | 亚洲精品视频在线观看免费 | 人人干干人人 | 天天操天天干天天综合网 | 91麻豆精品国产91久久久无限制版 | 中文字幕一区二区三区久久蜜桃 | 91精品啪在线观看国产线免费 | 在线亚洲成人 | 日本精品一区二区在线观看 | 99精品欧美一区二区三区黑人哦 | 精品视频免费 | 黄污网| 欧美激情精品一区 | 很黄很黄的网站免费的 | 激情五月色播五月 | 精品国产视频在线观看 | 欧美精品亚洲精品日韩精品 | 五月婷婷电影网 | 999久久| 91精品一区二区在线观看 | 黄色中文字幕在线 | 天堂av色婷婷一区二区三区 | 久久国产三级 | 久久久久国产一区二区三区四区 | 黄色最新网址 | 91亚色视频在线观看 | 国产精品免费小视频 | 亚洲伊人第一页 | 国产精品高清一区二区三区 | 黄色在线免费观看网站 | 欧美日韩一级久久久久久免费看 | 91av在线免费看 | 美女很黄免费网站 | 国产精彩视频一区 | 精品99999 | 中文字幕中文字幕在线中文字幕三区 | 97超碰资源总站 | 啪啪小视频网站 | 午夜精品久久久久 | 深爱激情站 | 99久久免费看 | 久久优| 欧美日韩亚洲在线观看 | 久久成年人视频 | 8x成人在线 | 日日干天夜夜 | 天天综合网国产 | 最新日韩在线 | 日韩精品一区二区在线观看 | 久久精品com | 91大神免费在线观看 | 天堂av一区二区 | 最近高清中文在线字幕在线观看 | 婷婷色综合 | 国产精品精品久久久 | 综合色婷婷| 国产精品久久久久永久免费观看 | 精品国产免费一区二区三区五区 | 91一区一区三区 | 成人在线免费视频观看 | 欧美在线视频一区二区 | 在线观看国产v片 | 亚洲一区二区观看 | 精品国产成人在线影院 | 日韩精品欧美一区 | 国产成人精品一二三区 | av久久久久久| 人人爱爱人人 | 免费色网站 | 国内精品久久久久久久久久 | 二区视频在线观看 | 中文字幕亚洲精品日韩 | 国产精品国产亚洲精品看不卡 | a色视频 | 婷婷婷国产在线视频 | 一区二区三区在线观看免费 | 国产五十路毛片 | 久久成人欧美 | 黄色片网站免费 | av高清网站在线观看 | 国产精品小视频网站 | 久久只精品99品免费久23小说 | 午夜免费福利视频 | www国产精品com | 日本天天操 | 热久久国产 | 性色av香蕉一区二区 | www黄com | 色www精品视频在线观看 | 国产午夜一区 | 欧美激情综合色综合啪啪五月 | 亚洲综合色丁香婷婷六月图片 | 精品女同一区二区三区在线观看 | 国内精品久久久久久中文字幕 | 午夜精品成人一区二区三区 | 中文字幕在线网址 | 中文字幕中文字幕中文字幕 | 免费一级日韩欧美性大片 | 国产香蕉在线 | 亚洲另类视频 | 日韩动漫免费观看高清完整版在线观看 | 成人作爱视频 | 99久久99视频只有精品 | 久久久蜜桃 | 亚洲午夜久久久久久久久久久 | 亚洲综合欧美精品电影 | 日韩精品一卡 | 亚洲一区二区三区毛片 | 久久黄色免费视频 | 日韩在线免费播放 | 国内精品久久天天躁人人爽 | 中文字幕五区 | 9在线观看免费高清完整版在线观看明 | 国产精品综合av一区二区国产馆 | 国产精品久久9 | 777xxx欧美| 日韩一区二区三区免费电影 | 色姑娘综合天天 | 精品国产一区二区三区久久久蜜月 | 亚洲精品激情 | www.久久久.cum | 91少妇精拍在线播放 | 久久久久久视频 | 日操操| 久久免费大片 | 狠狠色丁香婷婷综合久小说久 | 精品国产一区二区在线 | 日韩精品一区二区在线观看视频 | 就要色综合 | 久久成年人视频 | 国产在线观看二区 | 高潮久久久久久 | av黄在线播放| 伊人网站 | 91麻豆精品一区二区三区 | 久久久网址 | 国产传媒一区在线 | av爱干 | 国产精品一区二区av麻豆 | 国产精品白浆视频 | 国产黄色看片 | 日韩中文字幕免费视频 | 亚洲精品国精品久久99热 | 国产一级视频在线观看 | 国产精品福利无圣光在线一区 | 色婷婷综合五月 | 成人在线超碰 | www色片| 夜夜视频 | 国产成人精品一区二区三区免费 | 亚洲精品在线免费播放 | 成人午夜黄色影院 | 久久精品亚洲精品国产欧美 | 精品国产乱码一区二 | 福利在线看片 | 精品999在线观看 | 999精品在线 | 91精品伦理 | 99精品免费久久久久久久久 | 奇米7777狠狠狠琪琪视频 | 久久国语| 久久1区 | 精品国产一区二区久久 | 日日婷婷夜日日天干 | 欧美a视频在线观看 | 国产精品日韩高清 | 超碰av在线| 99久久综合国产精品二区 | 精品久久综合 | 国产精品专区h在线观看 | 国产亚洲精品xxoo | 久久中国精品 | 欧美激情精品久久久久久变态 | 久久久网站| 在线免费观看黄 | 一级片观看 | 99精品欧美一区二区 | 在线免费高清一区二区三区 | 亚洲美女免费精品视频在线观看 | 91网免费看| 天天操婷婷 | 亚洲精品久久久久www | 国产不卡网站 | 国产成人一区二区三区在线观看 | 国产精品免费看久久久8精臀av | av在线之家电影网站 | 丁香婷婷激情国产高清秒播 | 国产精品久久久久久久久久免费看 | 国产亚洲高清视频 | 国产精品免费观看国产网曝瓜 | 久久精品一二三区白丝高潮 | 精品一区二区影视 | 天天爱天天 | 久久综合五月婷婷 | 国产欧美日韩精品一区二区免费 | 日韩二区在线观看 | 欧美成人视 | 亚洲aaa毛片 | 免费看搞黄视频网站 | 丁香婷婷色月天 | 在线观看 国产 | 五月激情丁香图片 | 欧美成人a在线 | 国产99区| 精品久久久999 | 亚洲精品视频在线 | 日韩手机视频 | av超碰免费在线 | 四虎欧美| 日韩啪视频 | 国产麻豆剧传媒免费观看 | 久久99精品国产麻豆婷婷 | 国产视频一区在线免费观看 | 国产精品99久久久久的智能播放 | 中文字幕在线观看一区 | 国产精品久久久久久久久久久免费 | 91在线观看高清 | 天天综合亚洲 | 亚洲欧美综合精品久久成人 | 亚州国产精品 | 国产999精品| 国产午夜三级 | 日日夜夜狠狠干 | 深夜成人av | 精品视频| 人人玩人人爽 | 探花在线观看 | 怡红院av| 亚洲精品乱码久久久久久蜜桃91 | 91九色免费视频 | 一区二区三区福利 | 久久精品一区二区 | 亚洲天堂网站视频 | 色多多污污在线观看 | 久久久久久美女 | 欧美日韩一区二区久久 | 国产在线播放观看 | 国产成人一区在线 | 国产高清在线观看 | 中文国产字幕 | 欧亚日韩精品一区二区在线 | 一级国产视频 | 99久久久久久 | 综合色天天 | 视频在线一区 | 欧美va电影| 日韩欧美精品在线 | 日韩影视在线 | 不卡的av在线 | 婷婷激情综合五月天 | 天天操天天干天天插 | 精品欧美小视频在线观看 | 青春草免费在线视频 | 国产成人av电影在线 | 丁香花在线观看免费完整版视频 | 亚洲片在线观看 | 五月激情天 | 成人免费视频视频在线观看 免费 | 97国产人人 | 黄色国产在线 | 精品国产乱码久久久久久天美 | 欧美三级免费 | 欧美日韩国产网站 | 一本大道久久精品懂色aⅴ 五月婷社区 | 黄色三级久久 | 精品免费久久久久 | 亚洲精品乱码久久久久久蜜桃欧美 | 97精品久久 | 久草com| 亚洲每日更新 | 成人免费精品 | 亚洲精品黄网站 | 97热视频| 西西444www大胆高清图片 | 99精品久久久 | 成av人电影 | 天天操夜 | 97国产视频 | 久久精品香蕉视频 | 久久久www成人免费精品 | 麻豆 videos | 国产精品小视频网站 | av成人免费在线看 | 视频在线观看一区 | 六月色婷婷 | 欧美日韩激情视频8区 | 五月天久久 | 日韩欧美观看 | 96精品视频 | 亚洲精品在线视频网站 | 黄色片网站大全 | 国精产品一二三线999 | 日韩久久视频 | 亚洲午夜在线视频 | 91精品国自产在线观看 | 久久欧美精品 | 日韩大片在线 | 日韩av片在线 | 日本一区二区高清不卡 | 日韩va在线观看 | 在线观看免费版高清版 | 国产视频在 | 久久久久成人精品亚洲国产 | 黄色日批网站 | 国产精品一区二区三区免费视频 | 91尤物在线播放 | 久久婷婷网| 国产精品a久久 | 精品一区二区免费在线观看 | 成人午夜剧场在线观看 | 色www免费视频 | 久久影视一区二区 | 亚洲va韩国va欧美va精四季 | 国产视频久 | 999精品视频 | 99色免费 | 91视频在线观看免费 | 欧美色图视频一区 | 一区二区电影在线观看 | 午夜精品一区二区三区在线观看 | 99久久婷婷国产 | 亚洲精品小区久久久久久 | 五月婷婷中文 | 日日色综合 | 亚洲五月激情 | 黄色在线视频网址 | 亚洲精品1区2区3区 超碰成人网 | 最新日韩视频 | 国产一区视频导航 | 99久久精品免费看国产四区 | 激情五月伊人 | 97干com| 狠狠干夜夜操 | 一区二区久久 | 国产日韩在线视频 | 国产999精品久久久影片官网 | 97电院网手机版 | 视频一区二区视频 | 国内精品久久久久影院日本资源 | 国产日韩欧美在线 | 高清av在线免费观看 | 久久免费国产电影 | 国产麻豆精品久久一二三 | 欧美综合国产 | 最近日本中文字幕 | 久久久91精品国产一区二区精品 | 久久伊人婷婷 | 日本三级不卡视频 | 中文字幕在线观看第三页 | 麻豆视频观看 | 99精品在线 | 日韩免费一区二区在线观看 | 国产成人一区二区三区免费看 | 国产黄在线 | 久久久久日本精品一区二区三区 | 精品亚洲一区二区 | 五月激情姐姐 | 中文字幕免费高 | 亚洲精品美女免费 | 亚洲精品视频久久 | 91视频在线自拍 | 久久综合九色综合欧美狠狠 | 免费色视频在线 | 亚洲综合在线观看视频 | 最近最新中文字幕视频 | 精品久久久久久久久久岛国gif | 69精品久久| 亚洲免费av片 | 国产网红在线观看 | 欧美黄色高清 | 国产亚洲精品电影 | 亚洲六月丁香色婷婷综合久久 | 国产精品综合久久久 | 婷婷精品国产一区二区三区日韩 | 九九精品视频在线观看 | 精品国产亚洲日本 | 亚洲精品在线视频观看 | 在线观看中文av | 天天干,天天草 | 亚洲综合色网站 | 综合久久久 | 中文字幕日韩精品有码视频 | 一区二区精品视频 | 丁香影院在线 | 天天色成人 |