iT邦幫忙

2024 iThome 鐵人賽

DAY 14
1
AI/ ML & Data

一個Kaggle金牌解法是如何誕生的?跟隨Kaggle NLP競賽高手的討論,探索解題脈絡系列 第 14

[Day14]🧟成為特級LLM咒言師的第三天 - All you need is just "lucrarea" :淺談文本對抗攻擊(Adversarial Attack)原理篇

  • 分享至 

  • xImage
  •  

對圖像做 adversarial attack 可能你已經很熟悉了,今天帶大家來看看怎麼對文本做對抗攻擊。
第一名的 solution 就使用到 adversarial attack 的技巧!

Adversatial Attack 介紹

相信大家或多或少都有聽過深度學習的對抗攻擊(adversarial attack),這是一種在機器學習和深度學習模型中使用的技術,這個技術最早是在2014年由 Ian J. Goodfellow 等人提出的(請參考:Explaining and Harnessing Adversarial Examples),目的是人為地干擾或欺騙模型,使其產生錯誤的預測或分類。
用來解釋對抗攻擊最有名的一組圖,應該是下面這張(reference to 1):
image
兩個熊貓照片人眼看起來根本一模一樣,但是模型卻一個預測是 "panda" 另一個預測是 "gibbon",就是長臂猿。雖然長臂猿長得跟 panda 一點都不像,但是被攻擊過的圖片,卻成功誤導模型分類錯誤,而且信心值還高達 99%。
image

一開始做 Adversarial Attack 的相關研究都是在圖像上面做攻擊,這部分有很多科普文章在介紹,大家有興趣可以參考[2],或是李宏毅老師的課程。

圖像 vs. 文本

  • 圖像:圖像數據是連續的,例如每個像素的值可以是從 0 到 255 的浮點數。這種連續性使得基於梯度的攻擊(如 FGSM)可以直接對像素進行小幅度的修改,從而影響模型的輸出。
  • 文本:文本數據是離散的,也就是說,每個 token(詞或字)都是一個詞彙表中的整數索引。這種離散性使得基於梯度的攻擊不容易直接應用,因為我們不能直接對 token 值進行連續的修改。

❓❓那麼問題就來了:如何在離散的文本數據上進行基於梯度的攻擊❓❓


我們先來看一下,在文本上做攻擊是什麼意思呢?

舉例來說,我們訓練一個 BERTSequenceClassification model,來區分輸入的句子是正向積極的,還是負向消極的,模型本來都分得好好的:

"I like you." -> Positive
"I hate you." -> Negative

今天如果你想搞破壞,讓這個模型的預測全部都變錯!

這時候我們如果加上一個神秘的 token 變成:

"zoning tapping fiennes I like you" -> Negative*
"zoning tapping fiennes I hate you" -> Positive*

咦?發現了嗎? 加上 zoning tapping fiennes 這幾個 token 之後,本來分類正確的,通通都分錯了!

這是怎麼做到的呢?

轉換為 Embedding Space 中的操作

解決這個問題的關鍵在於嵌入空間 Embedding Space

NLP 模型,尤其是基於深度學習的模型(如 BERT、GPT 等),會將文本的每個 token 轉換為連續空間中的向量,即嵌入空間。這樣,我們就能夠在這個連續空間中應用基於梯度的優化技術,進而找到最佳的詞替換。

我們舉一個具體的例子來說明:

  1. Embedding Layer
  • 當你將文本輸入到模型中時,每個 token(例如 "dog")會被轉換為一個嵌入向量。例如,"dog" 可能對應到一個 768 維的向量 𝑒_dog。
  • 嵌入空間是連續的,這意味著我們可以通過基於梯度的技術對這些嵌入向量進行修改,就像我們對圖片的像素點進行修改一樣。
  1. 基於Embedding的梯度計算:
  • 假設我們想要找到一個對抗性詞替換,我們會計算每個 token 的嵌入向量對損失函數的梯度,即 ∂𝐿/∂𝑒_𝑖,其中 𝑒_𝑖是當前 token 的嵌入向量。
  • 這個梯度告訴我們,嵌入空間中的哪個方向可以最大化損失。
  1. Embedding到詞語的替換:
  • 我們現在有了每個 token 的梯度向量。接下來的挑戰是如何從這些梯度中找到一個新的 token,來替換當前的 token。
  • 這裡可以用很多不同方法,今天會提到的是 HotFlip 攻擊,即計算當前嵌入向量與詞彙表中其他嵌入向量之間的梯度內積,並找到能使損失最大化變化的詞語。

所以回到一開始的問題:如何在離散的文本上做 gradient-based 的對抗攻擊呢?

其實這部分基於 NLP 模型中的embedding layer來完成的,具體來說,雖然 token 是離散的數據,但通過embedding layer,它們被映射到了連續的嵌入空間。我們可以在這個連續空間中進行梯度計算和優化,然後根據優化的結果回到離散的 token 空間。

雖然過去曾經困擾NLP界一陣子到底要怎麼對文本做對抗攻擊,但現在 adverarial attack on text 也算是滿成熟的技術了,大家可能比較常聽到的像是 TextAttack,或是 Universal Adversarial Triggers

由於前者只能針對 "Text" 做攻擊,而後者可以針對更小的單位 "token" 做攻擊,今天我們就來介紹一下 Universal Adversarial Triggers的演算法。

Universal Adversarial Triggers

Universal Adversarial Triggers 是一種對抗性攻擊技術,專門用來針對自然語言處理(NLP)模型。在文本數據中,這種方法的目的是生成一組「觸發詞」(triggers),這些詞可以在不考慮輸入內容的情況下,系統性地欺騙模型,從而誘使模型輸出特定的結果。

如果我們要數學化地解釋 Universal Adversarial Triggers(UAT)的原理,可以將其理解為一個基於梯度優化的對抗性攻擊問題,其目的是生成一組通用的觸發詞,使模型在不同的輸入下都會生成目標輸出。

基本原理

假設一個 NLP 模型 𝑓(𝑥)是一個從輸入文本(𝑥)映射到輸出空間的函數。我們的目標是找到一組通用的詞(𝑡),即 adversarial triggers,使得無論輸入 𝑥 是什麼,當附加這些觸發詞時,模型的輸出都會接近某個特定目標,或是最大化/最小化某個損失函數。

數學上,我們的目標可以表述為一個優化問題:
image

x 是原始的輸入文本向量(例如一句話的 token 序列)。
𝑡 是我們想要學習的 adversarial trigger,即一組觸發詞。
𝑓(𝑥+𝑡) 是模型輸出。
𝑦 是目標輸出或我們希望模型偏離的正確標籤。
𝐿(⋅) 是損失函數,用來衡量輸入 𝑥+𝑡 對應的輸出 𝑓(𝑥+𝑡) 與目標 𝑦之間的差距。
常見的損失函數有 交叉熵損失 或是 句子相似度損失。

目標:我們想要找到一組觸發詞 𝑡 使得模型的預測 𝑓(𝑥+𝑡) 偏離或逼近目標𝑦。

這個優化過程依賴於模型對輸入文本的梯度。我們通過計算損失函數對每個 token 的梯度,來決定如何修改句子中的 token。具體過程如下:

  1. 初始化:

初始化一個提示詞 𝑡(可以是隨機的詞語序列,或者是簡單的詞語,如 "Rewrite this text")。
image

  1. 損失計算:

給定輸入 𝑥 , 我們將其與當前的提示詞 𝑡 拼接,並將其輸入模型。這時可以計算輸出與目標之間的損失:
image

如果是相似度問題,我們可以使用句子嵌入的相似度作為損失,如餘弦相似度:

image

  1. 計算梯度:

使用反向傳播計算損失函數相對於每個 token 的梯度:

image

這表示每個 token 如何影響損失值,告訴我們如何修改該 token 以最小化損失。

  1. HotFlip 替換:

HotFlip 攻擊 是一種基於梯度的技術,用來選擇能最有效替換當前 token 的新 token。具體來說,對於每個 token 𝑡_𝑖,我們根據梯度選擇能最大化損失變化的替換 token。
用數學表示,HotFlip 攻擊的目的是找到新的 token 𝑡_𝑖′,使得損失變化最大:
image

其中 𝜖 是學習率或步長。通過這種方式,我們可以逐個 token 地進行替換,找到最佳的替換詞。

下一篇我們會詳細介紹並實作HotFlip攻擊!可以參考:[Day 15]🧟成為特級LLM咒言師的第四天 - 為什麼"lucrarea"咒語會這麼強大?一些實驗設計與思考 - 淺談文本對抗攻擊(Adversarial Attack)實作篇

  1. 更新觸發詞:

將找到的最佳 token 替換當前的 token,然後重複上述步驟直到損失最小化或達到預設的迭代次數。

6th Solution

製作 Dataset

由於主辦方沒有公佈訓練資料集,第六名的 team 他們首先先做了一個大約有 8000 多筆 (origin_text, rewrite_prompt, rewrite_text) 的 dataset。

他們製造 dataset 的流程如下:
為了生成重寫提示詞(rewrite prompt)和重寫後的文本(rewritten text),他們使用了以下算法。為了使數據集更加多樣化,他們使用了Gemini、GPT-3.5和GPT-4模型來生成數據。

  1. 生成虛擬重寫文本:
    使用一個非常長的公開mean prompt(例如“Please improve the following text using the writing style”,長度為0.61)來生成一個虛擬的重寫文本。
  2. 生成重寫提示詞:
    使用原始文本和步驟1中生成的虛擬重寫文本,要求模型輸出一個可能用於執行該轉換的提示詞(prompt)。
  3. 生成重寫後的文本:
    使用原始文本和步驟2中生成的重寫提示詞,要求模型生成重寫後的文本。

通過這種方法,他們總共生成了8000多行數據(每行包含原始文本、重寫提示詞和重寫後的文本)。其中大約2000行數據被用於訓練我們選擇的最佳模型。

用 Adversarial Attack 找到 Mean Prompt

我們直接從 code 去解釋:

train_dataset = [dataset of rewrite prompts]
model = sentence_t5
target_embeddings = model.encode(train_dataset)
initial_string = "Rewrite this text to make it more helpful."
trigger_tokens = tokenizer.encode(initial_string)
for iter in range(n):
    for token_idx in range(len(trigger_tokens)):
        loss =  1 - similarity(target_embeddings, model.encode(trigger_tokens))
        loss.backward() 
        replacement_token = hotflip_attack(loss, embedding_matrix, token_idx) 
        trigger_tokens[token_idx] = replacement_token

具體來解釋一下:

train_dataset = [dataset of rewrite prompts]
model = sentence_t5
target_embeddings = model.encode(train_dataset)

這段代碼首先加載一個他們自己做好的訓練數據集 train_dataset,該數據集包含了rewrite prompt。
接著,使用預訓練的 sentence_t5 模型對這些訓練數據進行嵌入,並將這些嵌入保存為 target_embeddings,作為相似度的目標。

initial_string = "Rewrite this text to make it more helpful."
trigger_tokens = tokenizer.encode(initial_string)

這裡選擇了一個初始提示詞,"Rewrite this text to make it more helpful.",並將其通過 tokenizer 編碼為一組 token,保存為 trigger_tokens。這些 token 代表了句子的初始形式,接下來會通過對抗性攻擊逐步修改這些 token。

接下來是重點:

for iter in range(n):
    for token_idx in range(len(trigger_tokens)):
        loss =  1 - similarity(target_embeddings, model.encode(trigger_tokens))
        loss.backward() 
        replacement_token = hotflip_attack(loss, token_idx) 
        trigger_tokens[token_idx] = replacement_token

這是主要的迭代過程,逐步進行對提示詞的 token 替換。具體步驟如下:

  1. 外部迭代(for iter in range(n)):這是外部的迭代循環,總共執行 n 次,每次都會遍歷整個句子的 token。
  2. 內部迭代(for token_idx in range(len(trigger_tokens))):對句子中的每個 token 進行遍歷,逐個 token 地進行修改。
  3. 計算相似度損失(loss = 1 - similarity):
loss =  1 - similarity(target_embeddings, model.encode(trigger_tokens))
  1. 計算梯度
loss.backward()

但這邊的梯度有別於我們平常訓練模型,是計算模型權重的梯度,這邊計算的是輸入文字的 token 梯度,這樣我們就能知道如何修改這些 token 來最大化相似度。
5. 使用 HotFlip 攻擊替換 token

replacement_token = hotflip_attack(loss, token_idx)

HotFlip 攻擊,這是一種基於梯度的對抗性攻擊技術,它會尋找在當前 token 位置可以替換的最優 token,來最小化損失(即最大化相似度)。具體怎麼實現這個對抗攻擊演算法,可以參考下一篇:[Day 15]🧟成為特級LLM咒言師的第四天 - 為什麼"lucrarea"咒語會這麼強大?一些實驗設計與思考 - 淺談文本對抗攻擊(Adversarial Attack)實作篇

  1. 更新當前 token
trigger_tokens[token_idx] = replacement_token

這邊其實他們就是實現了 Universal Adversarial Triggers 的流程,最後找到的 mean prompt 為:

, but alter THAT story info Create formally tone homind), send poetic presentation etc Res biggerlucrarealucrarea repaslucrareaDistinguished text functi Simplelucrareaably basically you would thrulucrareaphrase emulate sentiment.

直接每一題都輸入這樣用對抗攻擊找到的、類似亂碼的 mean prompt,這樣就完成了!

1th Solution

接下來,來到第一名的部分。

第一名更簡單了,他也是找到一個萬能咒語:

" 'it 's ' something Think A Human Plucrarealucrarealucrarealucrarealucrarealucrarealucrarealucrarea"

他們和剛剛的第六名一樣,發現只要在輸入文字後面不斷重複:lucrarea 就可以大幅提升 performance。

所以假設他們用模型預測這一題的 rewrite prompt 應該是:"Convert this to a shanty."
只要在後面加上咒語變成:"Convert this to a shanty. 'it 's ' something Think A Human Plucrarealucrarealucrarealucrarealucrarealucrarealucrarealucrarea""
就可以立刻提升 performance!

但是到底為什麼 "lucrarea" 會這麼有用呀?!

這邊先賣個關子,我們明天會透過一些實驗和分析提出一些可能的猜測。

最後....只能用這張圖來結尾了...
https://ithelp.ithome.com.tw/upload/images/20240928/201526681tKE6eGCWo.jpg

雖然有點無語,但很多參賽者最後都發現無法開發有效的 Modeling-based 的方法。
因為比賽使用一個非常不好的評估指標,也就是 t5-base 轉換出來的 embedding 相似度。
這個模型有很明顯的弱點,例如我們下面做的實驗(reference to []),比較text_list裡面所有詞和 "gt_text = "Convert this into a sea shanty."" 的 t5-base embedding similarity。
結果發現"lucrarea"這個詞只要加上去,不管你輸入的是什麼,相似度就會馬上提高,直到收斂。

gt_text = "Convert this into a sea shanty."
text_lis = [
    "rewrite next text",
    "rewrite next text lucrarea",
    "rewrite next text lucrarealucrarea",
    "rewrite next text lucrarealucrarealucrarea",
    "rewrite next text lucrarealucrarealucrarealucrarea",
    "rewrite next text lucrarealucrarealucrarealucrarealucrarea",
    "rewrite next text lucrarealucrarealucrarealucrarealucrarealucrarea",
]

pred_emb = embed_text(text_lis)

ret = scs(gt_emb, pred_emb)
ret

https://ithelp.ithome.com.tw/upload/images/20240928/20152668HWl0lGwyCR.png


謝謝讀到最後的你,希望你會覺得有趣!
如果喜歡這系列,別忘了按下訂閱,才不會錯過最新更新,也可以按讚給我鼓勵唷!
如果有任何回饋和建議,歡迎在留言區和我說✨✨


Kaggle - LLM Prompt Recovery 解法分享系列)


上一篇
[Day 13]🧟成為特級LLM咒言師的第二天 - 找 Mean Prompt 不用那麼麻煩:分佈相似度驅動的Mean Prompt優化
下一篇
[Day 15]🧟成為特級LLM咒言師的第四天 - 為什麼"lucrarea"咒語會這麼強大?一些實驗設計與思考 - 淺談文本對抗攻擊(Adversarial Attack)實作篇
系列文
一個Kaggle金牌解法是如何誕生的?跟隨Kaggle NLP競賽高手的討論,探索解題脈絡30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言