LLM(大語言模型)解碼時是怎麼生成文本的?

Part1配置及參數

transformers==4.28.1

源碼地址:transformers/configuration_utils.py at v4.28.1 · huggingface/transformers (github.com)

文檔地址:Generation (huggingface.co)

對於生成任務而言:text-decoder, text-to-text, speech-to-text, and vision-to-text models,有以下幾種生成的方法:

  • greedy decoding by calling [~generation.GenerationMixin.greedy_search] if num_beams=1 and do_sample=False
  • contrastive search by calling [~generation.GenerationMixin.contrastive_search] if penalty_alpha>0. and top_k>1
  • multinomial sampling by calling [~generation.GenerationMixin.sample] if num_beams=1 and do_sample=True
  • beam-search decoding by calling [~generation.GenerationMixin.beam_search] if num_beams>1 and do_sample=False
  • beam-search multinomial sampling by calling [~generation.GenerationMixin.beam_sample] if num_beams>1 and do_sample=True
  • diverse beam-search decoding by calling [~generation.GenerationMixin.group_beam_search], if num_beams>1 and num_beam_groups>1
  • constrained beam-search decoding by calling [~generation.GenerationMixin.constrained_beam_search], if constraints!=None or force_words_ids!=None

具體有以下參數可供選擇:

(1)控制輸出長度的參數

  • max_length (int, optional, defaults to 20) - 生成的tokens的最大長度。對應於輸入提示的長度+max_new_tokens。如果還設置了max_new_tokens,則其作用被max_new_tokens覆蓋。
  • max_new_tokens (int, optional) - 要生成的最大數量的tokens,忽略提示中的tokens數量。
  • min_length (int, optional, defaults to 0) - 要生成的序列的最小長度。對應於輸入提示的長度+min_new_tokens。如果還設置了min_new_tokens,它的作用將被 min_new_tokens覆蓋。
  • min_new_tokens (int, optional) - 要生成的最小數量的tokens,忽略提示中的tokens數量。
  • early_stopping (bool or str, optional, defaults to False) - 控制基於beam-based的停止條件,比如beam-search。是否在至少生成 num_beams 個句子後停止 beam search,默認是False。
  • max_time(float, optional) - 你允許計算運行的最大時間,以秒爲單位。在分配的時間過後,生成仍然會完成當前的傳遞。

(2)控制輸出策略的參數

  • do_sample (bool, optional, defaults to False) - 是否使用採樣,否則使用貪婪解碼 。

  • num_beams (int, optional, defaults to 1) - 集束搜索的集束數量。1意味着沒有集束搜索 。

  • num_beam_groups (int, optional, defaults to 1) - 將num_beam分成的組數,以確保不同組的beams的多樣性。https://arxiv.org/pdf/1610.02424.pdf

  • penalty_alpha (float, optional) - 平衡模型置信度和對比搜索解碼中的退化懲罰的數值。

  • use_cache (bool, optional, defaults to True) - 模型是否應該使用過去最後的鍵/值注意力(如果適用於模型)來加速解碼。

(3)控制模型輸出Logits的參數

  • temperature(float, optional, defaults to 1.0) - 用於調節下一個標記概率的值。
  • top_k (int, optional, defaults to 50) - 爲top-k過濾而保留的最高概率詞彙標記的數量。
  • top_p (float, optional, defaults to 1.0) - 已知生成各個詞的總概率是1(即默認是1.0)如果top_p小於1,則從高到低累加直到top_p,取這前N個詞作爲候選。
  • typical_p (float, optional, defaults to 1.0) - 局部典型性度量:在給定已生成的部分文本的情況下,預測下一個目標標記的條件概率與預測下一個隨機標記的預期條件概率的相似程度。如果設置爲float < 1,則保留概率加起來等於typical_p或更高的最小的本地典型tokens集以供生成。https://arxiv.org/pdf/2202.00666.pdf
  • epsilon_cutoff (float, optional, defaults to 0.0) - 如果設置爲嚴格介於0和1之間的浮點數 ,只有條件概率大於epsilon_cutoff的標記纔會被採樣。在論文中,建議的值在3e-4到 9e-4之間,取決於模型的大小。https://arxiv.org/abs/2210.15191
  • eta_cutoff (float, optional, defaults to 0.0) - Eta採樣是局部典型採樣和ε採樣的混合體。 如果設置爲嚴格介於0和1之間的浮點數,只有當一個token大於eta_cutoff或 sqrt(eta_cutoff) * exp(- entropy(softmax(next_token_logits)))時纔會被考 慮。後者直觀地是預期的下一個令牌概率,以sqrt(eta_cutoff)爲尺度。在論文中 ,建議值從3e-4到2e-3不等,取決於模型的大小。https://arxiv.org/abs/2210.15191
  • diversity_penalty (float, optional, defaults to 0.0) - 如果一個beam在某一特定時間產生一 個與其他組的任何beam相同的標記,這個值將從beam的分數中減去。請注意,多樣性懲罰只有在group-beam-search被啓用時纔有效。
  • repetition_penalty (float, optional, defaults to 1.0) - 重複處罰的參數。1.0意味着沒有懲罰。https://arxiv.org/pdf/1909.05858.pdf
  • encoder_repetition_penalty (float, optional, defaults to 1.0) - encoder_repetition_penalty的參數。對不在原始輸入中的序列進行指數式懲罰。 1.0意味着沒有懲罰。
  • length_penalty (float, optional, defaults to 1.0) - 對長度的指數懲罰,用於beam-based的生成 。它作爲指數應用於序列的長度,反過來用於劃分序列的分數。由於分數是序列的對數 能性(即負數),length_penalty > 0.0會促進更長的序列,而length_penalty < 0.0會鼓勵更短的序列。
  • no_repeat_ngram_size (int, optional, defaults to 0) - 如果設置爲int > 0,所有該尺寸的 ngrams只能出現一次。
  • bad_words_ids(List[List[int]], optional) - 不允許生成的標記ID的列表。爲了獲得不 應該出現在生成的文本中的詞的標記ID,使用tokenizer(bad_words, add_prefix_space=True, add_special_tokens=False).input_ids。
  • force_words_ids(List[List[int]] or List[List[List[int]]], optional) - 必鬚生成的 token ids列表。如果給定的是List[List[int]],這將被視爲一個必須包含的簡單單詞列表,與bad_words_ids相反。如果給定的是List[List[List[int]]],這將觸發一個 disjunctive約束,即可以允許每個詞的不同形式。https://github.com/huggingface/transformers/issues/14081
  • renormalize_logits (bool, optional, defaults to False) - 在應用所有的logits處理器或 warpers(包括自定義的)之後,是否重新規範化logits。強烈建議將這個標誌設置爲 "True",因爲搜索算法認爲分數對數是正常化的,但一些對數處理器或翹曲器會破壞正常化。
  • constraints (List[Constraint], optional) - 自定義約束,可以添加到生成中,以確保輸出將包含使用Constraint對象定義的某些標記,以最合理的方式。
  • forced_bos_token_id (int, optional, defaults to model.config.forced_bos_token_id) - 強制作爲解碼器_start_token_id之後第一個生成的令牌的id。對於像mBART這樣的多語言模型,第一個生成的標記需要是目標語言的標記,這很有用。
  • forced_eos_token_id (Union[int, List[int]], optional, defaults to model.config.forced_eos_token_id) - 當達到max_length時,強制作爲最後生成的令牌的id。可以選擇使用一個列表來設置多個序列結束的標記。
  • remove_invalid_values (bool, optional, defaults to model.config.remove_invalid_values) - 是否刪除模型可能的nan和inf輸出以防 止生成方法崩潰。注意,使用remove_invalid_values會減慢生成速度。
  • exponential_decay_length_penalty (tuple(int, float), optional) - 這個Tuple在生成一 定數量的標記後,增加一個指數級增長的長度懲罰。該元組應包括: (start_index, decay_factor) 其中start_index表示懲罰開始的位置, decay_factor表示指數衰減的係數。
  • suppress_tokens (List[int], optional) - 在生成時將被抑制的tokens列表。 SupressTokens日誌處理器將把它們的日誌probs設置爲-inf,這樣它們就不會被採樣 了。
  • forced_decoder_ids (List[List[int]], optional) - 一對整數的列表,表示從生成索引到token索引的映射,在採樣前會被強制執行。例如,[[1, 123]]意味着第二個生成的token將總是索引爲token的令牌。

(4)定義generate輸出變量的參數

  • num_return_sequences(int, optional, defaults to 1) - 批次中每個元素獨立計算的返回序列的數量。
  • output_attentions (bool, optional, defaults to False) - 是否返回所有注意力層的注意力張量。更多細節請參見返回的張量下的注意力。
  • output_hidden_states (bool, optional, defaults to False) - 是否要返回所有層的隱藏狀 態。更多細節請參見返回張量下的hidden_states。
  • output_scores (bool, optional, defaults to False) - 是否返回預測的分數。更多細節請參見返回張量下的分數。
  • return_dict_in_generate (bool, optional, defaults to False) - 是否返回ModelOutput而不是普通元組。
  • synced_gpus (bool, optional, defaults to False) - 是否繼續運行while循環直到max_length(ZeRO第三階段需要)。

(5)可在生成時使用的特殊參數

  • pad_token_id (int, optional) - 填充token的ID。
  • bos_token_id (int, optional) - 序列開始標記的id。
  • eos_token_id (Union[int, List[int]], optional) - 序列結束標記的id。可以選擇使用 一個列表來設置多個序列結束標記。

(6)編碼器-解碼器模型獨有的生成參數

  • encoder_no_repeat_ngram_size (int, optional, defaults to 0) - 如果設置爲int > 0,所有出現在encoder_input_ids中的該大小的ngrams都不能出現在decoder_input_ids中 。

  • decoder_start_token_id (int, optional) - 如果一個編碼器-解碼器模型以不同於bos的 token開始解碼,則這就是該token的id。

Part2配置基本使用

1使用預訓練模型定義的生成參數

我們可以這麼使用、保存預訓練模型已經定義好的參數:

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, GenerationConfig
model_name_or_path = "uer/gpt2-chinese-cluecorpussmall"
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path)

generation_config = model.generation_config
generation_config_dict = generation_config.to_dict()
generation_config_dict["num_beams"] = 2
generation_config = GenerationConfig.from_dict(generation_config_dict)
print(generation_config)

generation_config.save_pretrained("./")
"""
{
  "_from_model_config": true,
  "bos_token_id": 50256,
  "eos_token_id": 50256,
  "num_beams": 2,
  "transformers_version": "4.28.1"
}
"""

需要注意的是,如果參數是默認的值得話,則不會顯示出來。另外,GenerationConfig類裏面有許多可用的方法,具體可以去看看源代碼。

2一般使用方法

在定義好config之後,我們可以這麼使用:

from transformers import AutoModelForSeq2SeqLM, AutoTokenizer, GenerationConfig

tokenizer = AutoTokenizer.from_pretrained("t5-small")
model = AutoModelForSeq2SeqLM.from_pretrained("t5-small")

translation_generation_config = GenerationConfig(
    num_beams=4,
    early_stopping=True,
    decoder_start_token_id=0,
    eos_token_id=model.config.eos_token_id,
    pad_token=model.config.pad_token_id,
)

translation_generation_config.save_pretrained("t5-small""translation_generation_config.json", push_to_hub=True)

# You could then use the named generation config file to parameterize generation
# 可以加載我們自己本地保存的generation_config
generation_config = GenerationConfig.from_pretrained("t5-small""translation_generation_config.json")
inputs = tokenizer("translate English to French: Configuration files are easy to use!", return_tensors="pt")
outputs = model.generate(**inputs, generation_config=generation_config)
print(tokenizer.batch_decode(outputs, skip_special_tokens=True))

Part3生成結果

使用transformers庫的生成模型生成結果有三種方式,暫時不要在意參數:

3pipeline

指定爲text-generation

from transformers import pipeline

generator = pipeline(
    'text-generation'
    model="uer/gpt2-chinese-cluecorpussmall",
    )
text_inputs = ["昨天已經過去,"]
generator(text_inputs, max_length=100)

4TextGenerationPipeline

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline

tokenizer = AutoTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
model = AutoModelForCausalLM.from_pretrained("uer/gpt2-chinese-cluecorpussmall")

text_generator = TextGenerationPipeline(model, tokenizer)
text_inputs = ["昨天已經過去,"]
text_generator(text_inputs, max_length=100)

5model.generate()

from transformers import AutoTokenizer, AutoModelForCausalLM
import torch, os

tokenizer = AutoTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
model = AutoModelForCausalLM.from_pretrained("uer/gpt2-chinese-cluecorpussmall")

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = model.to(device)
texts = ["昨天已經過去,"]
#用batch輸入的時候一定要設置padding
encoding = tokenizer(texts, return_tensors='pt', padding=True).to(device)

model.eval()
with torch.no_grad():
    generated_ids = model.generate(**encoding, max_length=100
 generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)

for text in generated_texts:
  print(text)

我們捋一捋它們之間的關係:最基礎的還是model.generate(),而TextGenerationPipeline在_forward裏面調用了model.generate(),pipeline實際上是對TextGenerationPipeline的進一步封裝:

    "text-generation": {
        "impl": TextGenerationPipeline,
        "tf": TFAutoModelForCausalLM if is_tf_available() else None,
        "pt": AutoModelForCausalLM if is_torch_available() else None,
        "default": {"model": {"pt""gpt2""tf""gpt2"}},
    },

6流式打印

在介紹不同的生成方法之前,先介紹下流式打印。使用過ChatGPT的玩家都知道,在生成結果的時候,它是一部分一部分的返回生成的文本並展示的,transformers該版本也有這個功能,我們接下來看。

from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer

tokenizer = AutoTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
model = AutoModelForCausalLM.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
input_text = "昨天已經過去,"
inputs = tokenizer([input_text], return_tensors="pt", add_special_tokens=False)
streamer = TextStreamer(tokenizer)

# Despite returning the usual output, the streamer will also print the generated text to stdout.
_ = model.generate(**inputs, streamer=streamer, max_new_tokens=86)

如果想要一次性返回結果再打印,則是這樣的:

from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from threading import Thread

tokenizer = AutoTokenizer.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
model = AutoModelForCausalLM.from_pretrained("uer/gpt2-chinese-cluecorpussmall")
input_text = "昨天已經過去,"
inputs = tokenizer([input_text], return_tensors="pt", add_special_tokens=False)
streamer = TextIteratorStreamer(tokenizer)

# Run the generation in a separate thread, so that we can fetch the generated text in a non-blocking way.
generation_kwargs = dict(inputs, streamer=streamer, max_new_tokens=100)
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
generated_text = ""
for new_text in streamer:
    generated_text += new_text
generated_text

Part4多種生成方式

接下來將以之前訓練好的觀點評論生成的GPT來生成不同的結果,我們每次都使用三種方式對比看看結果。

7Greedy Search

generate默認使用貪婪的搜索解碼,所以你不需要傳遞任何參數來啓用它。這意味着參數num_beams被設置爲1,do_sample=False。

img
img

如圖上所屬,每次選擇概率值最高的詞。貪心搜索的主要缺點是它錯過了隱藏在低概率詞後面的高概率詞,比如has=0.9不會被選擇到。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0
                    num_beams=1
                    do_sample=False
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=1
           do_sample=False,
           pad_token_id=0))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"1
  "do_sample"False,
}
print(generator(input_text, **generation_config))

"""
雖然說是4星級,不過
['雖 然 說 是 4 星 級 , 不 過 感 覺 和 3 星 沒 什 麼 兩 樣 , 只 是 服 務 水 準 差 了 點 而 已']
[{'generated_text': '雖然說是4星級,不過 感 覺 和 3 星 沒 什 麼 兩 樣 , 只 是 服 務 水 準 差 了 點 而 已'}]
[{'generated_text': '雖然說是4星級,不過 感 覺 和 3 星 沒 什 麼 兩 樣 , 只 是 服 務 水 準 差 了 點 而 已'}]
"""

答案是一致的,和我們之前的推測一樣,但需要注意的是model.gneerate()對單條預測的時候我們在tokenizer的時候設置padding爲False了,如果設置爲True,則得不到相同的結果。

8Contrastive search

對比搜索解碼策略是在2022年的論文A Contrastive Framework for Neural Text Generation https://arxiv.org/abs/2202.06417中提出的。它展示了生成非重複但連貫的長輸出的優越結果。要了解對比性搜索的工作原理,請查看這篇博文https://huggingface.co/blog/introducing-csearch。啓用和控制對比性搜索行爲的兩個主要參數是punice_alpha和top_k:

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
# text = dataset["train"][0]
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0,
                    do_sample=False,
                    num_beams=1
                    penalty_alpha=0.6
                    top_k=4
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=1
           do_sample=False,
           pad_token_id=0,
           penalty_alpha=0.6
           top_k=4
           ))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"1
  "do_sample"False,
  # "penalty_alpha":0.6, 
  # "top_k":4,
}
print(generator(input_text, **generation_config))

"""
['極 差 ! 停 車 收 十 元 錢 ! 窮 則 思 變 ! 房 間 潮 溼 得 不 得 了 , 晚 上 居 然 停 了 一 個 多 小 時 , 上 網 一 會 有 信 號 一 會 沒 有 。 電 視 遙 控 器 不 管 用 , 打 電 話 給 客 房 中 心 , 得 到 的 回 復 居 然 是 壞 的 房 間 在 維 修 , 不 知 道']
[{'generated_text': '極差!停車收十元錢! 窮 則 思 變 ! 房 間 潮 溼 得 不 得 了 , 晚 上 居 然 停 了 一 個 多 小 時 , 上 網 一 會 有 信 號 一 會 沒 有 。 電 視 遙 控 器 不 管 用 , 打 電 話 給 客 房 中 心 , 得 到 的 回 復 居 然 是 壞 的 房 間 在 維 修 , 不 知 道'}]
[{'generated_text': '極差!停車收十元錢! 窮 則 思 變 ! 房 間 設 施 差 就 一 個 招 待 所 , 最 多 三 星 級 !'}]
"""

可以對比和貪婪解碼看一下結果。

9Multinomial sampling

與總是選擇概率最高的標記作爲下一個標記的貪婪搜索相反,多項式抽樣(也稱爲祖先抽樣)根據模型給出的整個詞彙的概率分佈來隨機選擇下一個標記。每個概率不爲零的符號都有機會被選中,從而減少了重複的風險。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
# text = dataset["train"][0]
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0,
                    do_sample=True,
                    num_beams=1
                    ) 
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=1
           do_sample=True,
           pad_token_id=0,
           ))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"1
  "do_sample"True,
}
print(generator(input_text, **generation_config))

"""
['房 間 : 建 築 風 格 比 較 獨 特 , 但 不 顯 現 空 間 特 色 。 地 理 位 置 不 是 很 好 , 離 九 華 山 比 較 遠 , 出 租 車 還 比 較 難 找 。 門 童 服 務 蠻 好 , 門 口 迎 賓 也 很 熱 情 。 房 間 設 施 : 住 9 樓 標 房 , 朝 西 , 馬 路 上 的 喧 囂 比 較']
[{'generated_text': '房間:建築風格比較獨 特 , 牆 壁 由 黑 色 爲 主 , 給 人 一 種 溫 馨 的 感 覺 , 房 間 內 少 點 什 麼 裝 飾 , 總 體 還 算 可 以 。 交 通 : 訂 一 輛 出 租 車 , 一 天 之 內 送 完 了 , 一 天 後 再 打 車 , 車 子 要 走 到 春 熙 路 , 十 分 方 便'}]
[{'generated_text': '房間:建築風格比較獨 特 , 比 較 特 別 的 是 窗 外 的 自 然 環 境 , 很 漂 亮 , 房 間 內 的 設 施 也 不 錯 , 有 獨 立 的 陽 臺 , 所 謂 的 山 景 房 看 風 景 也 能 看 到 大 草 坪 和 遠 處 的 大 海 。 服 務 : 因 爲 我 和 的 朋 友 預 定 的 是 山'}]
"""

10Beam-search decoding

與貪婪搜索不同的是,集束搜索解碼在每個時間步驟中保留幾個假設,並最終選擇對整個序列具有最高概率的假設。這具有識別高概率序列的優勢,這些序列從較低概率的初始標記開始,會被貪婪搜索所忽略。

要啓用這種解碼策略,需要指定num_beams(又稱要跟蹤的假說數量)大於1。集束搜索通過在每個時間步保留最可能的 num_beams 個詞,並從中最終選擇出概率最高的序列來降低丟失潛在的高概率序列的風險。以 num_beams=2 爲例:

img
img

最終得到:the dog has (0.4+0.9) > the nice woman (0.5+0.4)。

缺點:雖然結果比貪心搜索更流暢,但輸出中仍然包含重複。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
# text = dataset["train"][0]
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0,
                    do_sample=False,
                    num_beams=4
                    ) 
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=4
           do_sample=False,
           pad_token_id=0,
           ))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"4
  "do_sample"False,
}
print(generator(input_text, **generation_config))

"""
酒店的整體服務意識相
['酒 店 的 整 體 服 務 意 識 相 當 好 , 對 於 未 按 照 預 訂 時 間 到 達 的 客 戶 , 還 能 夠 保 留 預 訂 , 但 是 溝 通 技 巧 不 是 很 好 , 還 有 對 於 未 按 預 訂 時 間 到 達 的 客 戶 , 還 要 給 攜 程 的 工 作 帶 來 很 大 麻 煩 。']
[{'generated_text': '酒店的整體服務意識相 當 好 , 對 於 未 按 照 預 訂 時 間 到 達 的 客 戶 , 還 能 夠 保 留 預 訂 , 但 是 溝 通 技 巧 不 是 很 好 , 還 有 對 於 未 按 預 訂 時 間 到 達 的 客 戶 , 還 要 給 攜 程 的 工 作 帶 來 很 大 麻 煩 。'}]
[{'generated_text': '酒店的整體服務意識相 當 好 , 對 於 未 按 照 預 訂 時 間 到 達 的 客 戶 , 還 能 夠 保 留 預 訂 , 但 是 溝 通 技 巧 不 是 很 好 , 還 有 對 於 未 按 預 訂 時 間 到 達 的 客 戶 , 還 要 給 攜 程 的 工 作 帶 來 很 大 麻 煩 。'}]
"""

11Beam-search multinomial sampling

顧名思義,這種解碼策略結合了集束搜索和多指標採樣。你需要指定num_beams大於1,並設置do_sample=True來使用這種解碼策略。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
# text = dataset["train"][0]
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0,
                    do_sample=True,
                    num_beams=4
                    ) 
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=4
           do_sample=True,
           pad_token_id=0,
           ))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"4
  "do_sample"True,
}
print(generator(input_text, **generation_config))

"""
['酒 店 在 肇 慶 鬧 市 區 , 但 交 通 非 常 方 便 , 酒 店 服 務 員 態 度 非 常 好 , 酒 店 硬 件 條 件 還 可 以 , 就 是 房 間 隔 音 效 果 非 常 不 好 , 隔 壁 的 電 視 聲 音 、 走 廊 人 說 話 聲 等 清 清 楚 楚 , 住 在 一 樓 還 能 聽 到 隔 壁 房 間 的 電']
[{'generated_text': '酒店在肇慶鬧市區,但 交 通 非 常 方 便 , 酒 店 服 務 態 度 很 好 , 房 間 幹 淨 整 潔 , 下 次 去 肇 慶 還 會 選 擇 該 酒 店 。'}]
[{'generated_text': '酒店在肇慶鬧市區,但 交 通 非 常 方 便 , 酒 店 環 境 不 錯 , 房 間 比 較 幹 淨 , 服 務 員 態 度 也 很 好 , 總 的 來 說 是 一 家 不 錯 的 酒 店 。'}]
"""

12Diverse beam search decoding

多樣化集束搜索解碼策略是對集束搜索策略的擴展,可以生成更多樣化的集束序列供人們選擇。要了解它的工作原理,請參考《多樣化集束搜索》https://arxiv.org/pdf/1610.02424.pdf: 從神經序列模型解碼多樣化的解決方案。這種方法有兩個主要參數:num_beams和num_beam_groups。組的選擇是爲了確保它們與其他組相比有足夠的區別,並在每個組內使用常規集束搜索。

from transformers import AutoTokenizer, AutoModelForCausalLM, TextGenerationPipeline, pipeline

tokenizer = AutoTokenizer.from_pretrained("./gpt2-chinese")
model = AutoModelForCausalLM.from_pretrained("./gpt2-chinese")

from datasets import load_dataset
data_file = "./ChnSentiCorp_htl_all.csv"
dataset = load_dataset("csv", data_files=data_file)
dataset = dataset.filter(lambda x: x["review"is not None)
dataset = dataset["train"].train_test_split(0.2, seed=123)

import random
example = random.choice(dataset["train"])
# text = dataset["train"][0]
text = example["review"]
input_text = text[:10]
print(input_text)

# greedy search
model.eval()
with torch.no_grad():
  encoding = tokenizer(input_text, 
              return_tensors='pt'
              padding=False
              add_special_tokens=False,
              return_token_type_ids=False,
              return_attention_mask=False,)
  
  generated_ids = model.generate(**encoding, 
                    max_length=100
                    eos_token_id=0
                    pad_token_id=0,
                    do_sample=False,
                    num_beams=4
                    num_beam_groups=4,
                    ) 
  generated_texts = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
  print(generated_texts)


text_generator = TextGenerationPipeline(model, tokenizer)  
print(text_generator(input_text, 
           max_length=100
           eos_token_id=0
           num_beams=4
           do_sample=False,
           pad_token_id=0,
           num_beam_groups=4,
           ))


generator = pipeline("text-generation", model=model, tokenizer=tokenizer)

generation_config = {
  "max_length"100,
  "eos_token_id"0,
  "pad_token_id"0,
  "num_beams"4
  "do_sample"False,
  "num_beam_groups"4,
}
print(generator(input_text, **generation_config))

"""
住過如此之多的如家酒
['住 過 如 此 之 多 的 如 家 酒 店 , 這 一 家 是 最 差 的 , 服 務 差 , 房 間 老 舊 , 而 且 價 格 還 不 低 。 下 次 不 會 再 住 了 。']
[{'generated_text': '住過如此之多的如家酒 店 , 這 一 家 是 最 差 的 , 服 務 差 , 房 間 老 舊 , 而 且 價 格 還 不 低 。 下 次 不 會 再 住 了 。'}]

[{'generated_text': '住過如此之多的如家酒 店 , 這 一 家 是 最 差 的 , 服 務 差 , 房 間 老 舊 , 而 且 價 格 還 不 低 。 下 次 不 會 再 住 了 。'}]
"""

Part5補充

13常用的一些參數:

  • no_repeat_ngram_size:限制任意 N-gram 不會出現兩次。但是, n-gram 懲罰使用時必須謹慎,如一篇關於 紐約 這個城市的文章就不應使用 2-gram 懲罰,否則,城市名稱在整個文本中將只出現一次!
  • num_return_sequences :選擇返回句子的數量,記得確保 num_return_sequences <= num_beams
  • top_p
  • top_k
  • temperature
  • repetition_penalty

14採樣

img
img

採樣意味着根據當前條件概率分佈隨機選擇輸出詞 ,使用採樣方法時文本生成本身不再是確定性的。對單詞序列進行採樣時的大問題: 模型通常會產生不連貫的亂碼。可以設置top_k=0關閉採樣。緩解這一問題的一個技巧是通過降低所謂的 softmax 的“溫度”使分佈 P(w|w_{t-1})更陡峭。而降低“溫度”,本質上是增加高概率單詞的似然並降低低概率單詞的似然。

將溫度應用到於我們的例子中後,結果如下圖所示。

img
img

時刻單詞的條件分佈變得更加陡峭,幾乎沒有機會選擇單詞 “car” 了。雖然溫度可以使分佈的隨機性降低,但極限條件下,當“溫度”設置爲 0 時,溫度縮放採樣就退化成貪心解碼了,因此會遇到與貪心解碼相同的問題。

15Top-K採樣

Top-K 採樣中,概率最大的 K 個詞會被選出,然後這 K 個詞的概率會被重新歸一化,最後就在這重新被歸一化概率後的 K 個詞中採樣。 GPT2 採用了這種採樣方案,這也是它在故事生成這樣的任務上取得成功的原因之一。

img
img

假設:top_k=6

輸入:the, the的下一個詞從概率最大的top6裏面採樣到car,the car的下一個詞從概率最大的top6裏面採樣。可以看到後面一些奇怪的詞就可以被忽略掉。

16Top-P採樣

Top-p 中,採樣不只是在最有可能的 K 個單詞中進行,而是在累積概率超過概率 p 的最小單詞集中進行。然後在這組詞中重新分配概率質量。這樣,詞集的大小 (又名 集合中的詞數) 可以根據下一個詞的概率分佈動態增加和減少。好吧,說的很囉嗦,一圖勝千言。

img
img

假設 p=0.92 , Top-p 採樣對單詞概率進行降序排列並累加,然後選擇概率和首次超過 p=0.92 的單詞集作爲採樣池,可以看出,在單詞比較不可預測時,它保留了更多的候選詞。而當單詞似乎更容易預測時,只保留了幾個候選詞。

一般而言,結合top_k和top_p會有不錯的效果。

Part6參考

Text generation strategies (huggingface.co)

transformers/configuration_utils.py at v4.28.1 · huggingface/transformers · GitHub

transformers/text_generation.py at v4.28.1 · huggingface/transformers · GitHub

基於 transformers 的 generate() 方法實現多樣化文本生成:參數含義和算法原理解讀_transformers generate_木堯大兄弟的博客-CSDN博客

https://zhuanlan.zhihu.com/p/624636122

文中部分文字和圖摘自上述文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章