本模块预计时长: 1.5-2小时
目标: 构建一个完整的提取式问答系统,接收上下文文本和问题,并从文本中直接提取最可能的答案。
要求: 仅需之前模块学到的知识 + 代码编辑器(或Google Colab)。
开始之前,让我们明确要构建的QA类型。
🔹 生成式问答:
模型发明一个新的答案,用自己的话。
问题:"什么是Transformer?"
生成的答案:"Transformer是一种使用注意力机制处理序列的神经网络架构..."
🔹 提取式问答(我们要做的):
模型从上下文文本中提取一个字面片段。
问题:"什么是Transformer?"
上下文:"...Transformer,于2017年引入,是一种基于注意力的架构,同时处理所有单词..."
提取的答案:"一种基于注意力的架构,同时处理所有单词"
✅ 提取式问答的优势:
对于这个项目,我们将使用仅编码器模型,专门训练用于提取式问答。
🔹 选定模型: deepset/roberta-base-squad2
🌐 你可以在模型中心查看
首先,安装(如果之前未完成)并加载模型和分词器。
from transformers import AutoTokenizer, AutoModelForQuestionAnswering, pipeline
# 模型名称
model_name = "deepset/roberta-base-squad2"
# 加载分词器和模型
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForQuestionAnswering.from_pretrained(model_name)
# 可选:创建管道以简化
qa_pipeline = pipeline("question-answering", model=model, tokenizer=tokenizer)
我们将使用示例文本。可以是任何内容:文章、书籍摘录、手册等。
context = """
Transformers是2017年由Vaswani等人在论文"Attention Is All You Need"中引入的神经网络架构。
与递归网络不同,Transformers同时处理序列中的所有单词,
使用一种称为"注意力"的机制,允许每个单词与句子中的任何其他单词关联。
这种架构是BERT、GPT、T5等模型的基础,这些模型如今主导着自然语言处理。
"""
question = "Transformers使用什么机制来关联单词?"
result = qa_pipeline(question=question, context=context)
print("问题:", question)
print("答案:", result['answer'])
print("得分:", result['score'])
print("开始:", result['start'])
print("结束:", result['end'])
预期输出:
问题: Transformers使用什么机制来关联单词?
答案: 注意力
得分: 0.9321
开始: 234
结束: 242
有效!模型提取了"注意力"作为答案。
现在,让我们逐步进行,如模块5中所示,看看内部发生了什么。
# 对问题+上下文进行分词(一起)
inputs = tokenizer(question, context, return_tensors="pt", truncation=True)
# 通过模型传递
outputs = model(**inputs)
# 获取开始和结束logits
start_logits = outputs.start_logits
end_logits = outputs.end_logits
# 找到最高概率的位置
start_index = torch.argmax(start_logits)
end_index = torch.argmax(end_logits)
# 将标记转换回文本
answer_tokens = inputs.input_ids[0][start_index:end_index + 1]
answer = tokenizer.decode(answer_tokens)
print("答案(手动):", answer)
输出:
答案(手动): 注意力
🔹 模型做什么?
它预测两件事:
然后,它提取这两个位置之间的所有标记。
有时,如果只取start_index和end_index的最高分,模型可能会出错。更好的做法是考虑有效组合(start <= end)并选择组合得分最高的。
import torch
def get_best_answer(start_logits, end_logits, input_ids, tokenizer, top_k=5):
# 获取开始和结束的top_k索引
start_probs, start_indices = torch.topk(start_logits, top_k)
end_probs, end_indices = torch.topk(end_logits, top_k)
best_score = -float('inf')
best_answer = ""
# 测试有效组合
for i in range(top_k):
for j in range(top_k):
start = start_indices[i].item()
end = end_indices[j].item()
if start <= end: # 有效
score = (start_probs[i] + end_probs[j]).item()
if score > best_score:
best_score = score
answer_tokens = input_ids[0][start:end+1]
best_answer = tokenizer.decode(answer_tokens, skip_special_tokens=True)
return best_answer, best_score
# 使用函数
answer, score = get_best_answer(start_logits, end_logits, inputs.input_ids, tokenizer)
print("最佳答案:", answer)
print("最佳得分:", score)
这使系统对偶发错误更具鲁棒性。
现在轮到你实验了!试试:
context2 = """
生成式人工智能能够创建新内容:文本、图像、音乐、代码。
DALL-E、Stable Diffusion和GPT-4是流行示例。
这些模型从大型数据集中学习模式,然后根据用户给出的提示或指令生成原创输出。
"""
question2 = "生成式AI可以创建什么类型的内容?"
result2 = qa_pipeline(question=question2, context=context2)
print(result2['answer']) # 预期: "文本、图像、音乐、代码"
或者中文:
context_zh = """
巴塞罗那是位于西班牙地中海沿岸的城市。
它以其独特的建筑而闻名,特别是安东尼·高迪的作品,
如圣家堂和古埃尔公园。它也以其美食、
海滩和充满活力的文化生活而著名。
"""
question_zh = "巴塞罗那哪位建筑师很有名?"
result_zh = qa_pipeline(question=question_zh, context=context_zh)
print(result_zh['answer']) # 预期: "安东尼·高迪"
🔹 局限性1:模型只能回答文本中存在的答案。
如果你问"法国首都是什么?"而文本没有提到巴黎,模型可能编造或给出错误答案。
🔹 解决方案:
handle_impossible_answer=True(如果模型支持,如SQuAD 2.0)。 if result['score'] < 0.1:
print("我在文本中找不到可靠的答案。")
else:
print("答案:", result['answer'])
🔹 局限性2:上下文有长度限制(~512个标记)。
如果文本很长,会被截断并丢失信息。
🔹 解决方案:
选择一篇你喜欢的维基百科文章(或书籍章节)。
复制3-4段作为上下文。
提出5个不同问题(简单、困难、模糊)。
运行QA系统并评估:
- 多少答案是正确的?
- 在哪里失败?
- 如何改进?
[问题 + 上下文] → 分词器 → input_ids → QA模型 → start_logits + end_logits →
↑ ↑ ↑ ↑
纯文本 转换为 标记IDs 预测开始
标记 + (问题 + 和结束位置
分隔符 上下文)
→ 选择最佳开始-结束组合 → 提取标记 → 解码 → [最终答案]
恭喜!你刚刚构建了一个功能性的AI系统,基于世界上最先进的模型之一(Transformer),无需训练任何东西,无需昂贵GPU,不到50行代码。
这个系统可以作为以下基础:
- 技术手册的聊天机器人。
- 阅读科学文章的助手。
- 回答书籍问题的导师。
最重要的是:你现在知道它内部如何工作!这不是黑盒子。你知道什么是嵌入,注意力做什么,位置如何编码,以及模型如何选择答案。