隨著電商平台的蓬勃發展,客戶評論成為重要的資訊來源。然而,大量評論也讓使用者難以快速找到所需資訊。本文將探討如何利用自然語言處理技術,特別是問答系統,來解決這個問題。問答系統能從評論中精準擷取答案,提升使用者經驗。目前的主流方法是擷取式問答,它能從文字中定位答案的區間。建構這類別系統通常需要兩個步驟:檔案檢索和答案擷取。Transformers模型的出現,大幅提升了問答系統的效能,能更精準地理解問題和上下文,並從大量文字中擷取答案。
問答系統的應用與挑戰
在眾多的自然語言處理任務中,問答系統(Question Answering, QA)是一項極具挑戰性的任務。問答系統旨在根據給定的文欄位落或檔案提供問題的答案。與其他任務(如情感分析、命名實體識別或文字摘要)相比,問答系統需要更深入的文字理解能力。
問答系統的型別
問答系統有多種型別,其中最常見的是擷取式問答(Extractive QA)。擷取式問答涉及的問題,其答案可以被識別為檔案中某個文字片段的跨度。檔案可以是網頁、法律契約或新聞文章等。
擷取式問答的流程
擷取式問答的流程通常包括兩個階段:
- 檔案檢索:首先檢索與問題相關的檔案或段落。
- 答案擷取:然後從檢索到的檔案中擷取答案。
客戶評論中的問答系統
在電子商務網站中,客戶評論是幫助消費者評估產品的重要資訊來源。然而,流行的產品往往有成百上千條評論,因此很難找到相關的評論來回答特定的問題。
程式碼範例:使用Transformers進行問答
from transformers import pipeline
# 初始化問答模型
qa_model = pipeline("question-answering")
# 定義問題和上下文
question = "這款吉他是否配備了撥弦片?"
context = "這款吉他非常適合初學者,配備了撥弦片和琴絃。"
# 執行問答
result = qa_model(question=question, context=context)
# 輸出答案
print(result["answer"])
#### 內容解密:
1. `pipeline("question-answering")`:初始化一個根據Transformers的問答模型,能夠根據給定的問題和上下文提供答案。
2. `qa_model(question=question, context=context)`:將定義好的問題和上下文傳入模型,進行問答推理。
3. `result["answer"]`:輸出模型的答案,提供給使用者作為參考。
未來的問答系統將繼續朝著更智慧、更準確的方向發展。隨著Transformers等模型的出現,問答系統的能力得到了顯著提升。然而,如何處理長檔案或多檔案的問答仍然是一個開放的研究問題。
### 未來研究方向
未來的研究可以探索如何進一步提高問答系統的準確性和效率,例如透過改進模型架構或引入新的訓練資料。同時,如何將問答系統應用於更廣泛的領域,如醫療、金融等,也是值得探索的方向。
### 問答系統流程圖
```plantuml
@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle
title 評論問答系統建構與技術挑戰
package "評論問答系統" {
package "問答流程" {
component [檔案檢索] as retrieval
component [答案擷取] as extraction
component [區間定位] as span
}
package "技術堆疊" {
component [Transformers] as transformer
component [SubjQA 資料集] as subjqa
component [SQuAD 資料集] as squad
}
package "挑戰與未來" {
component [長文字處理] as long_text
component [主觀性問題] as subjectivity
component [上下文理解] as context
}
}
retrieval --> extraction : 兩階段流程
transformer --> span : 精準定位
long_text --> subjectivity : 研究方向
note bottom of extraction
從評論中
擷取答案區間
end note
collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型
note right of feature
特徵工程包含:
- 特徵選擇
- 特徵轉換
- 降維處理
end note
note right of eval
評估指標:
- 準確率/召回率
- F1 Score
- AUC-ROC
end note
@enduml
此圖示展示了問答系統的基本流程,從問題輸入到輸出答案的整個過程。
圖表解密:
- 問題輸入:使用者輸入問題,啟動問答流程。
- 檔案檢索:系統檢索相關檔案或段落,為答案擷取做準備。
- 答案擷取:從檢索到的檔案中擷取最相關的答案。
- 輸出答案:將擷取到的答案輸出給使用者。
- 使用者評估:使用者評估答案的準確性和相關性,回饋給系統以改進效能。
建構根據評論的問答系統
在許多線上平台,如亞馬遜(Amazon),使用者經常會對產品或服務提出問題,但通常需要等待數天才能得到答案(如果有的話)。如果我們能夠像圖 7-1 中的 Google 示例一樣立即獲得答案,那將會非常方便。讓我們看看是否可以使用 Transformer 來實作這一點!
資料集介紹
為了構建我們的問答系統,我們將使用 SubjQA 資料集。該資料集包含超過 10,000 篇英文客戶評論,涵蓋六個領域:TripAdvisor、餐廳、電影、書籍、電子產品和雜貨。如圖 7-2 所示,每篇評論都與一個問題相關聯,該問題可以透過評論中的一句或多句話來回答。
圖 7-2:關於產品的問題和相應的評論(答案範圍下劃線)
SubjQA 資料集的有趣之處在於,大多數問題和答案都是主觀的;也就是說,它們取決於使用者的個人體驗。圖 7-2 中的例子說明瞭為什麼這個特性使任務比回答事實性問題(如“英國的貨幣是什麼?”)更具挑戰性。首先,查詢的是“品質差”,這是主觀的,取決於使用者對品質的定義。其次,查詢中的重要部分根本沒有出現在評論中,這意味著無法透過關鍵字搜尋或輸入問題的釋義等捷徑來回答這些問題。這些特性使 SubjQA 成為評估我們的根據評論的問答模型的一個現實資料集,因為像圖 7-2 中所示的使用者生成內容類別似於我們在實際中可能遇到的情況。
問答系統的分類別
問答系統通常根據其在回應查詢時所存取的資料域進行分類別。封閉域問答處理的是關於狹窄主題(例如,單一產品類別)的問題,而開放域問答處理的是關於幾乎任何主題(例如,亞馬遜的整個產品目錄)的問題。一般來說,封閉域問答涉及搜尋的檔案比開放域情況少。
使用 Hugging Face Hub 下載資料集
首先,讓我們從 Hugging Face Hub 下載資料集。如同第 4 章所做,我們可以使用 get_dataset_config_names() 函式來找出可用的子集:
from datasets import get_dataset_config_names
domains = get_dataset_config_names("subjqa")
print(domains)
# ['books', 'electronics', 'grocery', 'movies', 'restaurants', 'tripadvisor']
對於我們的用途,我們將專注於為電子產品領域構建一個問答系統。要下載電子產品子集,我們只需將這個值傳遞給 load_dataset() 函式的 name 引數:
from datasets import load_dataset
subjqa = load_dataset("subjqa", name="electronics")
資料集結構與預處理
像 Hub 上的其他問答資料集一樣,SubjQA 將每個問題的答案儲存為巢狀字典。例如,如果我們檢查 answers 列中的一行:
print(subjqa["train"]["answers"][1])
# {'text': ['Bass is weak as expected', 'Bass is weak as expected, even with EQ adjusted up'],
# 'answer_start': [1302, 1302],
# 'answer_subj_level': [1, 1],
# 'ans_subj_score': [0.5083333253860474, 0.5083333253860474],
# 'is_ans_subjective': [True, True]}
我們可以看到答案儲存在 text 欄位中,而起始字元索引在 answer_start 中提供。為了更方便地探索資料集,我們將使用 flatten() 方法展平這些巢狀列,並將每個分割轉換為 Pandas DataFrame,如下所示:
import pandas as pd
dfs = {split: dset.to_pandas() for split, dset in subjqa.flatten().items()}
for split, df in dfs.items():
print(f"Number of questions in {split}: {df['id'].nunique()}")
# Number of questions in train: 1295
# Number of questions in test: 358
# Number of questions in validation: 255
#### 內容解密:
get_dataset_config_names("subjqa"):此函式用於取得指定資料集(此處為 “subjqa”)的所有可用子集名稱。load_dataset("subjqa", name="electronics"):載入 “subjqa” 資料集中的 “electronics” 子集,用於構建特定領域的問答系統。flatten()方法:用於展平資料集中的巢狀結構,使其更易於處理和分析。to_pandas()方法:將資料集轉換為 Pandas DataFrame,便於進行進一步的資料分析和處理。
請注意,資料集相對較小,總共只有 1,908 個示例。這模擬了一個真實世界的場景,因為讓領域專家標記提取式問答資料集是勞動密集且昂貴的。例如,用於法律契約提取式問答的 CUAD 資料集估計價值為 200 萬美元,以考慮標記其 13,000 個示例所需的法律專業知識!
SubjQA 資料集中的關鍵欄位
SubjQA 資料集中有相當多的欄位,但對於構建我們的問答系統最有趣的欄位如表 7-1 所示。
表 7-1:SubjQA 資料集中的欄位名稱及其描述
| 欄位名稱 | 描述 |
|---|---|
| title | 與每個產品相關聯的亞馬遜標準識別號(ASIN) |
| question | 問題 |
| answers.answer_text | 由註解者標記的評論中的文字範圍 |
| answers.answer_start | 答案範圍的起始字元索引 |
| context | 客戶評論 |
讓我們關注這些欄位,並檢視一些訓練示例。我們可以使用 sample() 方法選擇一個隨機樣本:
qa_cols = ["title", "question", "answers.text", "answers.answer_start", "context"]
sample_df = dfs["train"][qa_cols].sample(2, random_state=7)
print(sample_df)
#### 內容解密:
qa_cols:定義了我們感興趣的欄位,包括標題、問題、答案文字、答案起始索引和上下文(客戶評論)。sample()方法:用於從訓練資料集中隨機選擇示例,以便進行檢查和理解資料內容。random_state=7:確保隨機抽樣的可重複性。
透過這種方式,我們可以開始瞭解 SubjQA 資料集的結構和內容,為構建根據評論的問答系統奠定基礎。
問答系統的建構:從資料集到實際應用
在自然語言處理(NLP)的領域中,問答系統(Question Answering, QA)是一項極具挑戰性的任務。問答系統的目標是使機器能夠閱讀一段文字並回答與之相關的問題。本篇文章將探討如何建構一個根據評論的問答系統,並介紹相關的資料集和技術。
資料集介紹
在問答系統的研究中,資料集扮演著至關重要的角色。SubjQA是一個專門為評論式問答設計的資料集,它包含了來自電子商務網站的評論和問題。SubjQA的特點在於其問題和答案都是根據主觀的評論,這使得它比其他問答資料集更具挑戰性。
SQuAD資料集
另一個著名的問答資料集是SQuAD(Stanford Question Answering Dataset)。SQuAD包含了數萬個問題和答案,並且每個答案都對應到一段文字中的某個片段。SQuAD 2.0更進一步,加入了無法回答的問題,使得模型需要具備判斷問題是否可回答的能力。
SQuAD 2.0的進展
SQuAD 2.0的推出使得問答系統的研究邁向了一個新的階段。隨著模型的進步,目前在SQuAD 2.0上的表現已經超越了人類。然而,這種超人表現並不完全代表真正的閱讀理解能力,因為模型可以透過文字中的模式(如反義詞)來識別無法回答的問題。
從文字中提取答案
要建構一個問答系統,第一步是需要從文字中提取答案。這涉及到如何將監督學習問題進行框架化,以及如何對文字進行標記和編碼。
跨度分類別
最常見的方法是將答案提取問題轉化為跨度分類別任務,即預測答案在文字中的起始和結束位置。這種方法需要模型具備強大的閱讀理解能力。
建構根據評論的問答系統
由於我們的訓練資料集相對較小,只有1,295個例子,因此一個好的策略是從已經在大規模問答資料集(如SQuAD)上進行微調的語言模型開始。這類別模型具有強大的閱讀理解能力,可以作為建立更準確系統的基礎。
實作細節
要實作一個根據評論的問答系統,我們需要:
- 框架化監督學習問題:將問答任務轉化為模型可以處理的形式。
- 標記和編碼文字:使用適當的標記和編碼技術來處理輸入文字。
- 處理長文字:開發策略來處理超出模型最大上下文大小的長文字。
程式碼範例
import pandas as pd
import torch
from transformers import AutoModelForQuestionAnswering, AutoTokenizer
# 載入預訓練模型和標記器
model_name = "distilbert-base-cased-distilled-squad"
model = AutoModelForQuestionAnswering.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 定義函式來提取答案
def extract_answer(question, context):
inputs = tokenizer(question, context, return_tensors='pt')
outputs = model(**inputs)
answer_start = torch.argmax(outputs.start_logits)
answer_end = torch.argmax(outputs.end_logits) + 1
return tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(inputs['input_ids'][0][answer_start:answer_end]))
# 示例用法
question = "這個鍵盤是否輕便?"
context = "我非常喜歡這個鍵盤。它很緊湊,適合我的需求。"
print(extract_answer(question, context))
內容解密:
這段程式碼展示瞭如何使用預訓練的模型來提取答案。首先,我們載入了一個在SQuAD資料集上微調的模型和相應的標記器。然後,我們定義了一個函式extract_answer,該函式接受一個問題和一段文字,並傳回模型預測的答案。這個函式首先對問題和文字進行標記和編碼,然後透過模型進行前向傳播,最後根據模型的輸出來確定答案的位置並將其轉換為可讀的文字。
建構根據評論的問答系統
在許多自然語言處理(NLP)任務中,我們通常從預訓練模型開始,並自行微調任務特定的頭部。例如,在第2章中,我們需要微調分類別頭,因為類別數量與當前的資料集相關。對於抽取式問答(extractive QA),我們實際上可以從微調好的模型開始,因為標籤的結構在不同資料集中保持一致。
您可以在Hugging Face Hub上透過在“模型”標籤中搜尋“squad”找到抽取式問答模型的列表(圖7-5)。
選擇適合的問答模型
正如您所見,截至撰寫本文時,有超過350個問答模型可供選擇,那麼應該選擇哪一個呢?一般來說,答案取決於各種因素,例如您的語料函式庫是單語言還是多語言,以及在生產環境中執行模型的約束條件。表7-2列出了一些提供良好基礎的模型。
基線變換器模型
| 變換器模型 | 描述 | 引數數量 | SQuAD 2.0上的F1得分 |
|---|---|---|---|
| MiniLM | BERT-base的精簡版本,保留了99%的效能,同時速度提高一倍 | 66M | 79.5 |
| RoBERTa-base | RoBERTa模型的效能優於BERT,能夠在大多數問答資料集上使用單個GPU進行微調 | 125M | 83.0 |
| ALBERT-XXL | 在SQuAD 2.0上具有最先進的效能,但計算密集且難以佈署 | 235M | 88.1 |
| XLM-RoBERTa-large | 支援100種語言的多語言模型,具有強大的零樣本學習能力 | 570M | 83.8 |
為了本章的目的,我們將使用微調好的MiniLM模型,因為它訓練速度快,能夠讓我們快速迭代所探索的技術。
為問答任務進行文字標記化
為了編碼我們的文字,我們將從Hugging Face Hub載入MiniLM模型的檢查點:
from transformers import AutoTokenizer
model_ckpt = "deepset/minilm-uncased-squad2"
tokenizer = AutoTokenizer.from_pretrained(model_ckpt)
讓我們首先嘗試從一段短文字中提取答案。在抽取式問答任務中,輸入以(問題,上下文)對的形式提供,因此我們將它們都傳遞給標記器:
question = "How much music can this hold?"
context = """An MP3 is about 1 MB/minute, so about 6000 hours depending on file size."""
inputs = tokenizer(question, context, return_tensors="pt")
內容解密:
tokenizer負責將輸入的文字轉換為模型可以理解的格式。return_tensors="pt"指定傳回PyTorch張量。inputs包含了模型的輸入,包括input_ids、token_type_ids和attention_mask。
檢視標記化的輸入:
input_ids = inputs["input_ids"]
token_type_ids = inputs["token_type_ids"]
attention_mask = inputs["attention_mask"]
輸出結果如下:
input_ids
101 2129 2172 2189 2064 2023 ... 5834 2006 5371 2946 1012 102
token_type_ids
0 0 0 0 0 0 ... 1 1 1 1 1 1
attention_mask
1 1 1 1 1 1 ... 1 1 1 1 1 1
內容解密:
input_ids代表輸入文字的標記ID。token_type_ids用於區分問題和上下文的標記。attention_mask指示哪些標記應該被模型關注。
解碼input_ids以瞭解標記化的輸入格式:
print(tokenizer.decode(inputs["input_ids"][0]))
輸出結果:
[CLS] how much music can this hold? [SEP] an mp3 is about 1 mb / minute, so about 6000 hours depending on file size. [SEP]
內容解密:
- 輸入格式為:
[CLS] question tokens [SEP] context tokens [SEP]。 [CLS]和[SEP]是特殊的標記,分別代表句子的開始和分隔。
現在我們的文字已經被標記化,接下來我們需要例項化帶有問答頭部的模型,並執行前向傳遞:
import torch
from transformers import AutoModelForQuestionAnswering
model = AutoModelForQuestionAnswering.from_pretrained(model_ckpt)
with torch.no_grad():
outputs = model(**inputs)
print(outputs)
輸出結果:
QuestionAnsweringModelOutput(loss=None, start_logits=tensor([[-0.9862, -4.7750, ...]]), end_logits=tensor([[-0.9623, -5.4733, ...]]), hidden_states=None, attentions=None)
內容解密:
start_logits和end_logits分別代表答案開始和結束位置的機率。QuestionAnsweringModelOutput是模型的輸出物件。
取得開始和結束標記的logits:
start_logits = outputs.start_logits
end_logits = outputs.end_logits
比較這些logits的形狀與輸入ID:
print(f"Input IDs shape: {inputs.input_ids.size()}")
print(f"Start logits shape: {start_logits.size()}")
print(f"End logits shape: {end_logits.size()}")
內容解密:
- 輸入ID、開始logits和結束logits的形狀應該一致,以確保模型正確處理輸入。