Prompt Learning是當前NLP的一個重要話題,已經有許多文章進行論述。
從本質上來說,Prompt Learning 可以理解為一種下游任務的重定義方法,將幾乎所有的下游任務均統一為預訓練語言模型任務,從而避免了預訓練模型和下游任務之間存在的 gap。
如此一來,幾乎所有的下游 NLP 任務均可以使用,不需要訓練數據,在小樣本數據集的基礎上也可以取得超越 Fine-Tuning 的效果,使得所有任務在使用方法上變得更加一致,而局限于字面意義上的理解還遠遠不夠,我們可以通過一種簡單、明了的方式進行講述。
為了解決這一問題,本文主要從預訓練語言模型看MLM預測任務、引入prompt_template的MLM預測任務、引入verblize類別映射的Prompt-MLM預測、基于zero-shot的prompt情感分類實踐以及基于zero-shot的promptNER實體識別實踐五個方面,進行代碼介紹,供大家一起思考。
一、從預訓練語言模型看MLM預測任務
MLM和NSP兩個任務是目前BERT等預訓練語言模型預訓任務,其中MLM要求指定周圍詞來預測中心詞,其模型機構十分簡單,如下所示:
importtorch.nnasnn
fromtransformersimportBertModel,BertForMaskedLM
classBert_Model(nn.Module):
def__init__(self,bert_path,config_file):
super(Bert_Model,self).__init__()
self.bert=BertForMaskedLM.from_pretrained(bert_path,config=config_file)#加載預訓練模型權重
defforward(self,input_ids,attention_mask,token_type_ids):
outputs=self.bert(input_ids,attention_mask,token_type_ids)#maskedLM輸出的是mask的值對應的ids的概率,輸出會是詞表大小,里面是概率
logit=outputs[0]#池化后的輸出[bs,config.hidden_size]
returnlogit
下面一段代碼,簡單的使用了hugging face中的bert-base-uncased進行空缺詞預測,先可以得到預訓練模型對指定[MASK]位置上概率最大的詞語【詞語來自于預訓練語言模型的詞表】。
例如給定句子"natural language processing is a [MASK] technology.",要求預測出其中的[MASK]的詞:
>>>fromtransformersimportpipeline
>>>unmasker=pipeline('fill-mask',model='bert-base-uncased')
>>>unmasker("naturallanguageprocessingisa[MASK]technology.")
[{'score':0.18927036225795746,'token':3274,'token_str':'computer','sequence':'naturallanguageprocessingisacomputertechnology.'},
{'score':0.14354903995990753,'token':4807,'token_str':'communication','sequence':'naturallanguageprocessingisacommunicationtechnology.'},
{'score':0.09429361671209335,'token':2047,'token_str':'new','sequence':'naturallanguageprocessingisanewtechnology.'},
{'score':0.05184786394238472,'token':2653,'token_str':'language','sequence':'naturallanguageprocessingisalanguagetechnology.'},
{'score':0.04084266722202301,'token':15078,'token_str':'computational','sequence':'naturallanguageprocessingisacomputationaltechnology.'}]
從結果中,可以顯然的看到,[MASK]按照概率從大到小排序后得到的結果是,computer、communication、new、language以及computational,這直接反饋出了預訓練語言模型能夠有效刻畫出NLP是一種計算機、交流以及語言技術。
二、引入prompt_template的MLM預測任務
因此,既然語言模型中的MLM預測結果能夠較好地預測出指定的結果,那么其就必定包含了很重要的上下文知識,即上下文特征,那么,我們是否可以進一步地讓它來執行文本分類任務?即使用[MASK]的預測方式來預測相應分類類別的詞,然后再將詞做下一步與具體類別的預測?
實際上,這種思想就是prompt的思想,將下游任務對齊為預訓練語言模型的預訓練任務,如NPS和MLM,至于怎么對齊,其中引入兩個概念,一個是prompt_template,即提示模版,以告訴模型要生成與任務相關的詞語。因此,將任務原文text和prompt_template進行拼接,就可以構造與預訓練語言模型相同的預訓練任務。
例如,
>>>fromtransformersimportpipeline
>>>unmasker=pipeline('fill-mask',model='bert-base-uncased')
>>>text="Ireallylikethefilmalot."
>>>prompt_template="Becauseitwas[MASK]."
>>>pred1=unmasker(text+prompt_template)
>>>pred1
[
{'score':0.14730973541736603,'token':2307,'token_str':'great','sequence':'ireallylikethefilmalot.becauseitwasgreat.'},
{'score':0.10884211212396622,'token':6429,'token_str':'amazing','sequence':'ireallylikethefilmalot.becauseitwasamazing.'},
{'score':0.09781625121831894,'token':2204,'token_str':'good','sequence':'ireallylikethefilmalot.becauseitwasgood.'},
{'score':0.04627735912799835,'token':4569,'token_str':'fun','sequence':'ireallylikethefilmalot.becauseitwasfun.'},
{'score':0.043138038367033005,'token':10392,'token_str':'fantastic','sequence':'ireallylikethefilmalot.becauseitwasfantastic.'}]
>>>text="thismoviemakesmeverydisgusting."
>>>prompt_template="Becauseitwas[MASK]."
>>>pred2=unmasker(text+prompt_template)
>>>pred2
[
{'score':0.05464331805706024,'token':9643,'token_str':'awful','sequence':'thismoviemakesmeverydisgusting.becauseitwasawful.'},
{'score':0.050322480499744415,'token':2204,'token_str':'good','sequence':'thismoviemakesmeverydisgusting.becauseitwasgood.'},
{'score':0.04008950665593147,'token':9202,'token_str':'horrible','sequence':'thismoviemakesmeverydisgusting.becauseitwashorrible.'},
{'score':0.03569378703832626,'token':3308,'token_str':'wrong','sequence':'thismoviemakesmeverydisgusting.becauseitwaswrong.'},
{'score':0.033358603715896606,'token':2613,'token_str':'real','sequence':'thismoviemakesmeverydisgusting.becauseitwasreal.'}]
上面,我們使用了表達正面和負面的兩個句子,模型得到最高的均是與類型相關的詞語,這也驗證了這種方法的可行性。
三、引入verblize類別映射的Prompt-MLM預測
與構造prompt-template之外,另一個重要的點是verblize,做詞語到類型的映射,因為MLM模型預測的詞語很不確定,需要將詞語與具體的類別進行對齊,比如將"great", "amazing", "good", "fun", "fantastic", "better"等詞對齊到"positive"上,當模型預測結果出現這些詞時,就可以將整個預測的類別設定為positive;
同理,將"awful", "horrible", "bad", "wrong", "ugly"等詞映射為“negative”時,即可以將整個預測的類別設定為negative;
>>>verblize_dict={"pos":["great","amazing","good","fun","fantastic","better"],"neg":["awful","horrible","bad","wrong","ugly"]
...}
>>>hash_dict=dict()
>>>fork,vinverblize_dict.items():
...forv_inv:
...hash_dict[v_]=k
>>>hash_dict
{'great':'pos','amazing':'pos','good':'pos','fun':'pos','fantastic':'pos','better':'pos','awful':'neg','horrible':'neg','bad':'neg','wrong':'neg','ugly':'neg'}
因此,我們可以將這類方法直接加入到上面的預測結果當中進行修正,得到以下結果,
>>>[{"label":hash_dict[i["token_str"]],"score":i["score"]}foriinpred1]
[{'label':'pos','score':0.14730973541736603},{'label':'pos','score':0.10884211212396622},{'label':'pos','score':0.09781625121831894},{'label':'pos','score':0.04627735912799835},{'label':'pos','score':0.043138038367033005}]
>>>[{"label":hash_dict.get(i["token_str"],i["token_str"]),"score":i["score"]}foriinpred2]
[{'label':'neg','score':0.05464331805706024},{'label':'pos','score':0.050322480499744415},{'label':'neg','score':0.04008950665593147},{'label':'neg','score':0.03569378703832626},{'label':'real','score':0.033358603715896606}]
通過取top1,可直接得到類別分類結果,當然也可以綜合多個預測結果,可以獲top10中各個類別的比重,以得到最終結果:
{
"text":"Ireallylikethefilmalot.","label":"pos"
"text":"thismoviemakesmeverydisgusting.","label":"neg"
}
至此,我們可以大致就可以大致了解在zero-shot場景下,prompt的核心所在。而我們可以進一步的想到,如果我們有標注數據,又如何進行繼續訓練,如何更好的設計prompt-template以及做好這個詞語映射詞表,這也是prompt-learning的后續研究問題。
因此,我們可以進一步地形成一個完整的基于訓練數據的prompt分類模型,其代碼實現樣例具體如下,從中我們可以大致在看出具體的算法思想,我們命名為prompt.py
fromtransformersimportAutoModelForMaskedLM,AutoTokenizer
importtorch
classPrompting(object):
def__init__(self,**kwargs):
model_path=kwargs['model']
tokenizer_path=kwargs['model']
if"tokenizer"inkwargs.keys():
tokenizer_path=kwargs['tokenizer']
self.model=AutoModelForMaskedLM.from_pretrained(model_path)
self.tokenizer=AutoTokenizer.from_pretrained(model_path)
defprompt_pred(self,text):
"""
輸入帶有[MASK]的序列,輸出LM模型Vocab中的詞語列表及其概率
"""
indexed_tokens=self.tokenizer(text,return_tensors="pt").input_ids
tokenized_text=self.tokenizer.convert_ids_to_tokens(indexed_tokens[0])
mask_pos=tokenized_text.index(self.tokenizer.mask_token)
self.model.eval()
withtorch.no_grad():
outputs=self.model(indexed_tokens)
predictions=outputs[0]
values,indices=torch.sort(predictions[0,mask_pos],descending=True)
result=list(zip(self.tokenizer.convert_ids_to_tokens(indices),values))
self.scores_dict={a:bfora,binresult}
returnresult
defcompute_tokens_prob(self,text,token_list1,token_list2):
"""
給定兩個詞表,token_list1表示表示正面情感positive的詞,如good,great,token_list2表示表示負面情感positive的詞,如good,great,bad,terrible.
在計算概率時候,統計每個類別詞所占的比例,score1/(score1+score2)并歸一化,作為最終類別概率。
"""
_=self.prompt_pred(text)
score1=[self.scores_dict[token1]iftoken1inself.scores_dict.keys()else0
fortoken1intoken_list1]
score1=sum(score1)
score2=[self.scores_dict[token2]iftoken2inself.scores_dict.keys()else0
fortoken2intoken_list2]
score2=sum(score2)
softmax_rt=torch.nn.functional.softmax(torch.Tensor([score1,score2]),dim=0)
returnsoftmax_rt
deffine_tune(self,sentences,labels,prompt="Sinceitwas[MASK].",goodToken="good",badToken="bad"):
"""
對已有標注數據進行Fine tune訓練。
"""
good=tokenizer.convert_tokens_to_ids(goodToken)
bad=tokenizer.convert_tokens_to_ids(badToken)
fromtransformersimportAdamW
optimizer=AdamW(self.model.parameters(),lr=1e-3)
forsen,labelinzip(sentences,labels):
tokenized_text=self.tokenizer.tokenize(sen+prompt)
indexed_tokens=self.tokenizer.convert_tokens_to_ids(tokenized_text)
tokens_tensor=torch.tensor([indexed_tokens])
mask_pos=tokenized_text.index(self.tokenizer.mask_token)
outputs=self.model(tokens_tensor)
predictions=outputs[0]
pred=predictions[0,mask_pos][[good,bad]]
prob=torch.nn.functional.softmax(pred,dim=0)
lossFunc=torch.nn.CrossEntropyLoss()
loss=lossFunc(prob.unsqueeze(0),torch.tensor([label]))
loss.backward()
optimizer.step()
四、基于zero-shot的prompt情感分類實踐
下面我們直接以imdb中的例子進行zero-shot的prompt分類實踐,大家可以看看其中的大致邏輯:
1、加入
>>fromtransformersimportAutoModelForMaskedLM,AutoTokenizer
>>importtorch
>>model_path="bert-base-uncased"
>>tokenizer=AutoTokenizer.from_pretrained(model_path)
>>frompromptimportPrompting
>>prompting=Prompting(model=model_path)
2、使用prompt_pred直接進行情感預測
>>prompt="Becauseitwas[MASK]."
>>text="Ireallylikethefilmalot."
>>prompting.prompt_pred(text+prompt)[:10]
[('great',tensor(9.5558)),
('amazing',tensor(9.2532)),
('good',tensor(9.1464)),
('fun',tensor(8.3979)),
('fantastic',tensor(8.3277)),
('wonderful',tensor(8.2719)),
('beautiful',tensor(8.1584)),
('awesome',tensor(8.1071)),
('incredible',tensor(8.0140)),
('funny',tensor(7.8785))]
>>text="Ididnotlikethefilm."
>>prompting.prompt_pred(text+prompt)[:10]
[('bad',tensor(8.6784)),
('funny',tensor(8.1660)),
('good',tensor(7.9858)),
('awful',tensor(7.7454)),
('scary',tensor(7.3526)),
('boring',tensor(7.1553)),
('wrong',tensor(7.1402)),
('terrible',tensor(7.1296)),
('horrible',tensor(6.9923)),
('ridiculous',tensor(6.7731))]
2、加入neg/pos詞語vervlize進行情感預測
>>text="notworthwatching"
>>prompting.compute_tokens_prob(text+prompt,token_list1=["great","amazin","good"],token_list2=["bad","awfull","terrible"])
tensor([0.1496,0.8504])
>>text="Istronglyrecommendthatmoview"
>>prompting.compute_tokens_prob(text+prompt,token_list1=["great","amazin","good"],token_list2=["bad","awfull","terrible"])
tensor([0.9321,0.0679])
>>text="Istronglyrecommendthatmoview"
>>prompting.compute_tokens_prob(text+prompt,token_list1=["good"],token_list2=["bad"])
tensor([0.9223,0.0777])
五、基于zero-shot的promptNER實體識別實踐
進一步的,我們可以想到,既然分類任務可以進行分類任務,那么是否可以進一步用這種方法來做實體識別任務呢?
實際上是可行的,暴力的方式,通過獲取候選span,然后詢問其中實體所屬的類型集合。
1、設定prompt-template
同樣的,我們可以設定template,以一個人物為例,John是一個非常常見的名字,模型可以直接知道它是一個人,而不需要上下文
Sentence.Johnisatypeof[MASK]
2、使用prompt_pred直接進行預測我們直接進行處理,可以看看效果:
>>prompting.prompt_pred("JohnwenttoParistovisittheUniversity.Johnisatypeof[MASK].")[:5]
[('man',tensor(8.1382)),
('john',tensor(7.1325)),
('guy',tensor(6.9672)),
('writer',tensor(6.4336)),
('philosopher',tensor(6.3823))]
>>prompting.prompt_pred("Sava?wenttoParistovisittheuniversity.Sava?isatypeof[MASK].")[:5]
[('philosopher',tensor(7.6558)),
('poet',tensor(7.5621)),
('saint',tensor(7.0104)),
('man',tensor(6.8890)),
('pigeon',tensor(6.6780))]
2、加入類別詞語vervlize進行情感預測
進一步的,我們加入類別詞,進行預測,因為我們需要做的識別是人物person識別,因此我們可以將person類別相關的詞作為token_list1,如["person","man"],其他類型的,作為其他詞語,如token_list2為["location","city","place"]),而在其他類別時,也可以通過構造wordlist字典完成預測。
>>>prompting.compute_tokens_prob("Itisatypeof[MASK].",
token_list1=["person","man"],token_list2=["location","city","place"])
tensor([0.7603,0.2397])
>>>prompting.compute_tokens_prob("Sava?wenttoParistovisittheparliament.Sava?isatypeof[MASK].",
token_list1=["person","man"],token_list2=["location","city","place"])//確定概率為0.76,將大于0.76的作為判定為person的概率
tensor([9.9987e-01,1.2744e-04])
從上面的結果中,我們可以看到,利用分類方式來實現zero shot實體識別,是直接有效的,“Sava?”判定為person的概率為0.99,
prompting.compute_tokens_prob("Sava?wenttoLaristovisittheparliament.Larisisatypeof[MASK].",
token_list1=["person","man"],token_list2=["location","city","place"])
tensor([0.3263,0.6737])
而在這個例子中,將“Laris”這一地點判定為person的概率僅僅為0.3263,也證明其有效性。
總結
本文主要從預訓練語言模型看MLM預測任務、引入prompt_template的MLM預測任務、引入verblize類別映射的Prompt-MLM預測、基于zero-shot的prompt情感分類實踐以及基于zero-shot的promptNER實體識別實踐五個方面,進行了代碼介紹。
關于prompt-learning,我們可以看到,其核心就在于將下游任務統一建模為了預訓練語言模型的訓練任務,從而能夠最大地挖掘出預訓模型的潛力,而其中的prompt-template以及對應詞的構造,這個十分有趣,大家可以多關注。
審核編輯 :李倩
-
語言模型
+關注
關注
0文章
521瀏覽量
10268 -
nlp
+關注
關注
1文章
488瀏覽量
22033
原文標題:Prompt總結 | 從MLM預訓任務到Prompt Learning原理解析與Zero-shot分類、NER簡單實踐
文章出處:【微信號:zenRRan,微信公眾號:深度學習自然語言處理】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論