Keras阅读理解机器人如何实现精准问答?

99ANYc3cd6 机器人 8

这个机器人将能够理解一篇给定的文章(上下文),并回答关于这篇文章的问题,这在技术上是经典的机器阅读理解 任务。

Keras阅读理解机器人如何实现精准问答?-第1张图片-广州国自机器人
(图片来源网络,侵删)

我们将使用最经典和最直观的模型架构之一:Bi-LSTM + Attention,这个模型结构清晰,非常适合初学者理解 MRC 的核心思想。


核心思想

想象一下这个场景:

  1. 你给机器人一篇文章(上下文)和一个问题。
  2. 机器人会逐字分析问题和文章。
  3. 它会利用一个注意力机制,在阅读问题时,去文章中“寻找”与问题最相关的词语和句子。
  4. 它会在文章中定位出能够直接回答问题的文本片段。

我们的模型就是模拟这个过程。


项目架构

我们将构建一个序列到序列的模型,但它不生成文本,而是从原文中“提取”答案,它更像是一个序列标注问答模型。

Keras阅读理解机器人如何实现精准问答?-第2张图片-广州国自机器人
(图片来源网络,侵删)
  1. 输入
    • 上下文: 一段长文本。
    • 问题: 一个关于上下文的短文本。
  2. 模型:
    • 词嵌入层: 将词语转换为有意义的向量。
    • 双向 LSTM 层: 理解词语在句子中的上下文信息。
    • 注意力机制: 让模型在回答问题时,能够“聚焦”到上下文中最相关的部分。
    • 输出层: 预测答案在上下文中的起始位置结束位置
  3. 输出:

    两个概率分布:一个表示每个词是答案开头的概率,另一个表示每个词是答案结尾的概率。


步骤 1: 环境准备

确保你已经安装了必要的库。

pip install tensorflow numpy scikit-learn

步骤 2: 准备数据

为了演示,我们使用一个非常经典且简单的数据集:SQuAD v1.1 的一个极小化版本,你可以从 这里 下载,但为了简单起见,我们在这里手动创建一小部分数据。

在实际应用中,你需要一个 JSON 文件,其中包含 context(文章)、question(问题)和 answer(答案,包含起始和结束位置)。

Keras阅读理解机器人如何实现精准问答?-第3张图片-广州国自机器人
(图片来源网络,侵删)
import numpy as np
import json
from sklearn.model_selection import train_test_split
# 1. 创建模拟数据
# 在实际项目中,你应该从文件(如 SQuAD 的 JSON 文件)中加载这些数据。
data = [
    {
        "context": "Keras is an open-source neural-network library written in Python. It is capable of running on top of TensorFlow, Microsoft Cognitive Toolkit, Theano, or PlaidML.",
        "question": "What is Keras?",
        "answer_text": "an open-source neural-network library",
        "answer_start": 9 # "an" 在 context 中的起始位置
    },
    {
        "context": "The primary user interface is a Python-based application. It allows for fast prototyping and supports both convolutional networks and recurrent networks.",
        "question": "What does the primary user interface allow for?",
        "answer_text": "fast prototyping",
        "answer_start": 43
    },
    {
        "context": "Keras was developed with a focus on enabling fast experimentation. Being able to go from idea to result with the least possible delay is key to doing good research.",
        "question": "What is key to doing good research?",
        "answer_text": "Being able to go from idea to result with the least possible delay",
        "answer_start": 60
    },
    {
        "context": "Theano is a Python library that allows you to define, optimize, and evaluate mathematical expressions involving multi-dimensional arrays efficiently.",
        "question": "What is Theano?",
        "answer_text": "a Python library",
        "answer_start": 9
    }
]
# 2. 数据预处理
# 创建词汇表
all_texts = [d['context'] + " " + d['question'] for d in data]
all_words = " ".join(all_texts).split()
vocab = sorted(set(all_words))
word2idx = {word: i + 1 for i, word in enumerate(vocab)} # 0 保留给 padding
idx2word = {i + 1: word for i, word in enumerate(vocab)}
vocab_size = len(word2idx) + 1
max_len_context = 100  # 上下文最大长度
max_len_question = 30  # 问题最大长度
def tokenize_and_align(data):
    contexts = []
    questions = []
    answer_starts = []
    answer_ends = []
    for item in data:
        # Tokenize and pad context
        context_tokens = item['context'].split()[:max_len_context]
        context_ids = [word2idx.get(w, 0) for w in context_tokens]
        context_ids += [0] * (max_len_context - len(context_ids))
        # Tokenize and pad question
        question_tokens = item['question'].split()[:max_len_question]
        question_ids = [word2idx.get(w, 0) for w in question_tokens]
        question_ids += [0] * (max_len_question - len(question_ids))
        # Find answer start and end indices
        answer_text_tokens = item['answer_text'].split()
        answer_start_token = item['answer_start']
        # 在实际项目中,这里需要更精确地计算,因为我们简化了数据,可以这样处理
        # 注意:这是一个简化的处理,对于真实数据,你需要根据词边界来调整
        start_idx = -1
        end_idx = -1
        for i in range(len(context_tokens)):
            if " ".join(context_tokens[i:i+len(answer_text_tokens)]) == item['answer_text']:
                start_idx = i
                end_idx = i + len(answer_text_tokens) - 1
                break
        if start_idx == -1: # 如果找不到,则跳过此样本或设为默认值
            continue
        contexts.append(context_ids)
        questions.append(question_ids)
        answer_starts.append(start_idx)
        answer_ends.append(end_idx)
    return np.array(contexts), np.array(questions), np.array(answer_starts), np.array(answer_ends)
X_context, X_question, y_start, y_end = tokenize_and_align(data)
# 划分训练集和验证集
X_context_train, X_context_val, X_question_train, X_question_val, y_start_train, y_start_val, y_end_train, y_end_val = train_test_split(
    X_context, X_question, y_start, y_end, test_size=0.25, random_state=42
)
print("数据预处理完成。")
print(f"词汇表大小: {vocab_size}")
print(f"上下文形状: {X_context_train.shape}")
print(f"问题形状: {X_question_train.shape}")
print(f"答案起始位置形状: {y_start_train.shape}")

步骤 3: 构建 Keras 模型

现在我们来搭建 Bi-LSTM + Attention 模型。

from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Embedding, LSTM, Bidirectional, Dense, Dropout, Lambda, TimeDistributed
import tensorflow.keras.backend as K
# 模型超参数
embedding_dim = 64
lstm_units = 64
# --- 模型输入 ---
context_input = Input(shape=(max_len_context,), name='context_input')
question_input = Input(shape=(max_len_question,), name='question_input')
# --- 共享的嵌入层 ---
# 上下文和问题共享同一个嵌入矩阵
embedding_layer = Embedding(input_dim=vocab_size, output_dim=embedding_dim, mask_zero=True)
context_embedding = embedding_layer(context_input)
question_embedding = embedding_layer(question_input)
# --- 上下文编码器 (Bi-LSTM) ---
context_encoded = Bidirectional(LSTM(lstm_units, return_sequences=True))(context_embedding)
# --- 问题编码器 (Bi-LSTM) ---
question_encoded = Bidirectional(LSTM(lstm_units, return_sequences=True))(question_embedding)
# --- 注意力机制 ---
# 我们需要一个函数来计算注意力权重
def attention_layer(context, question):
    # context shape: (batch_size, context_len, 2 * lstm_units)
    # question shape: (batch_size, question_len, 2 * lstm_units)
    # 将问题编码为单个向量(取最后一个时间步的输出)
    question_vec = Lambda(lambda q: q[:, -1, :])(question) # (batch_size, 2 * lstm_units)
    # 扩展维度以便广播
    question_vec_expanded = K.expand_dims(question_vec, 1) # (batch_size, 1, 2 * lstm_units)
    # 计算上下文每个部分与问题向量的相似度
    # 使用点积作为相似度度量
    similarity = K.sum(context * question_vec_expanded, axis=-1) # (batch_size, context_len)
    # Softmax 归一化,得到注意力权重
    attention_weights = K.softmax(similarity, axis=-1) # (batch_size, context_len)
    # 将注意力权重应用到上下文编码上
    context_weighted = K.sum(K.expand_dims(attention_weights, -1) * context, axis=1) # (batch_size, 2 * lstm_units)
    return context_weighted, attention_weights
# 获取加权的上下文向量(用于最终预测)
# 注意:这里我们简化了注意力,直接用它来指导最终预测
# 更复杂的做法是将其与上下文编码拼接
context_weighted, _ = attention_layer(context_encoded, question_encoded)
# --- 输出层 ---
# 预测答案的起始位置
start_output = Dense(max_len_context, activation='softmax', name='start_output')(context_encoded)
# 预测答案的结束位置
end_output = Dense(max_len_context, activation='softmax', name='end_output')(context_encoded)
# --- 创建模型 ---
model = Model(
    inputs=[context_input, question_input],
    outputs=[start_output, end_output]
)
# 编译模型
model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)
model.summary()

步骤 4: 训练模型

现在我们可以开始训练了。

# 训练模型
history = model.fit(
    {
        'context_input': X_context_train,
        'question_input': X_question_train
    },
    {
        'start_output': y_start_train,
        'end_output': y_end_train
    },
    epochs=50,
    batch_size=2, # 因为数据量小,batch_size 也设小
    validation_data=(
        {
            'context_input': X_context_val,
            'question_input': X_question_val
        },
        {
            'start_output': y_start_val,
            'end_output': y_end_val
        }
    )
)

步骤 5: 评估与使用机器人

训练完成后,我们就可以使用这个机器人来回答新问题了。

def get_answer(context, question, model, word2idx, idx2word, max_len_context, max_len_question):
    # 1. 预处理输入
    context_tokens = context.split()[:max_len_context]
    question_tokens = question.split()[:max_len_question]
    context_ids = [word2idx.get(w, 0) for w in context_tokens]
    context_ids += [0] * (max_len_context - len(context_ids))
    question_ids = [word2idx.get(w, 0) for w in question_tokens]
    question_ids += [0] * (max_len_question - len(question_ids))
    # 2. 预测
    pred_start, pred_end = model.predict(
        {
            'context_input': np.array([context_ids]),
            'question_input': np.array([question_ids])
        }
    )
    # 3. 解析结果
    # 获取概率最高的起始和结束位置
    start_idx = np.argmax(pred_start[0])
    end_idx = np.argmax(pred_end[0])
    # 确保结束位置不小于起始位置
    if end_idx < start_idx:
        end_idx = start_idx
    # 4. 提取答案文本
    answer_tokens = context_tokens[start_idx : end_idx + 1]
    answer = " ".join(answer_tokens)
    return answer if answer else "无法从文章中找到答案。"
# --- 测试机器人 ---
new_context = "Keras is a high-level neural networks API, written in Python and capable of running on top of TensorFlow, CNTK, or Theano. It was developed with a focus on enabling fast experimentation."
new_question1 = "What is Keras written in?"
new_question2 = "What can Keras run on top of?"
new_question3 = "Who developed Keras?" # 这个问题数据里没有,应该无法回答
print(f"上下文: {new_context}\n")
print(f"问题: {new_question1}")
print(f"机器人回答: {get_answer(new_context, new_question1, model, word2idx, idx2word, max_len_context, max_len_question)}\n")
print(f"问题: {new_question2}")
print(f"机器人回答: {get_answer(new_context, new_question2, model, word2idx, idx2word, max_len_context, max_len_question)}\n")
print(f"问题: {new_question3}")
print(f"机器人回答: {get_answer(new_context, new_question3, model, word2idx, idx2word, max_len_context, max_len_question)}")

恭喜!你已经成功构建了一个基础的 Keras 阅读理解机器人。

这个模型的优点:

  • 结构清晰: Bi-LSTM 捕获上下文,Attention 机制定位关键信息,逻辑直观。
  • 可解释性强: 通过分析注意力权重,可以大致理解模型为什么会选择某个答案。

这个模型的局限性与改进方向:

  1. 数据规模: 我们使用了极少的样本,模型性能会很差,在实际应用中,需要像 SQuAD 这样的大规模数据集进行训练。
  2. 预处理: 我们的分词和答案对齐非常简陋,真实项目需要更复杂的分词工具(如 Jieba, NLTK, SpaCy)和精确的答案起始/结束位置匹配。
  3. 模型架构:
    • 更先进的注意力: 可以尝试 Self-Attention 或 Transformer 结构(如 BERT),它们在捕捉长距离依赖关系上表现更好。
    • 更复杂的融合: 当前模型将问题和上下文编码后通过简单的注意力连接,可以尝试更复杂的特征融合方式,如将问题向量与上下文向量拼接后再送入 LSTM。
    • 预训练模型: 目前最先进的方案是使用预训练语言模型(如 BERT, RoBERTa, ALBERT)进行微调,它们在海量文本上学习到了丰富的语言知识,只需在特定的阅读理解任务上做少量调整,就能达到极高的准确率。

这个项目为你进入机器阅读理解领域打下了坚实的基础,你可以基于此,尝试更复杂的数据、更强大的模型,来打造一个真正聪明的阅读理解机器人!

标签: Keras阅读理解精准问答实现 Keras问答机器人精准度提升方法 Keras阅读理解机器人问答优化技巧

抱歉,评论功能暂时关闭!