使用BERT的问题回答(Q&A)入门
本教程将介绍如何使用Googles BERT模型构建一个问题回答的网络应用。
前提条件
要学习本教程,读者需要熟悉以下内容。
- [React]
- [TensorFlow.js]
- [Visual studio代码]
什么是BERT
变换器双向编码表示法(Bidirectional Encoder Representations from Transformers,BERT)是一个自然语言处理模型,它使用变换器来完成各种NLP任务。该模型表现良好的一些任务包括问题回答、自然语言推理和命名实体识别。
如何安装BERT模型并导入其依赖项
我们将使用来自TensorFlow.js的预构建的BERT模型进行此次构建。预训练的模型使我们能够轻松地开始使用巨大的模型,而不需要参与很多设置和训练。
我们将安装三个软件包。@tensorflow/tfjs 来安装 tensorflow.js 库,@tensorflow-models/qna 来下载问题回答模型,react-loader-spinner 来在我们的 BERT 模型被下载时给我们一个漂亮的加载界面。这是一个相当大的模型。因此,一个漂亮的加载界面将派上用场。
npm i @tensorflow/tfjs @tensorflow-models/qna react-loader-spinner
安装成功后,你可以启动react应用程序,看看是否一切正常。我们使用npm start 命令来启动它,如下图所示。
npm start
如果你看到屏幕上出现 "Hello World!",我们就可以开始了。这意味着该应用运行良好。你可以在终端上同时按下CTRL+C ,来停止这个应用程序。我们现在可以继续前进并导入所需的依赖性。
import * as tf from "@tensorflow/tfjs";
import * as qna from "@tensorflow-models/qna";
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import Loader from "react-loader-spinner";
import { Fragment } from 'react';
我们的第一个导入允许我们在代码中引用tensorflow.js库,即tf 。第二个导入允许我们在代码中引用BERT模型,即qna 。第三个和第四个导入为我们的加载器导入了我们上面提到的css样式。最后,我们导入了Fragment ,它允许我们返回多个元素,而无需向DOM添加额外的节点。
下一步涉及设置我们的引用和状态钩子。钩子往往创建一个符号链接到各种元素。默认情况下,React库导入了以下钩子;useRef,useEffect, 和useState 。
你可以在App.js 文件的顶部看到它。useState 允许我们在我们的react应用中处理状态。它将被用来存储我们的问题和段落。
const passageRef = useRef(null);
const questionRef = useRef(null);
const [answer, setAnswer] = useState();
const [model, setModel] = useState(null);
我们已经设置了两个引用和状态钩子。
加载tensorflow模型
const loadModel = async ()=>{
const loadedModel = await qna.load()
setModel(loadedModel);
console.log('Model loaded successfully!')
}
上面的代码前进并加载我们的qna 模型。我们写了一个新的函数,使其成为异步的,并命名为loadModel 。在该函数中,我们创建了一个新的变量,称为loadedModel ,然后我们等待我们的qna 模型加载。
这将会把回答问题的模型加载到我们的应用程序中。最终,如果我们的模型加载成功,我们应该在控制台看到Model loaded successfully 。
为了运行这个函数,我们可以继续写下面的代码。
useEffect(()=>{loadModel()}, [])
[] 方括号告诉我们,我们希望模型加载多少次。在我们的例子中,只有一次。我们不希望每次都加载模型,因为它太大了。所以方括号告诉它使用useEffect 钩子只运行一次。
在这一点上,你可以尝试重新启动你的react应用,看看应用是否启动并成功加载模型。请给它一些时间来加载。如果一切正常,你会在控制台日志中看到Model loaded successfully! 。现在你可以关闭该应用了。
现在让我们定义一个函数,允许我们向模型提出问题。设置好这个函数后,我们将把它与我们的用户界面连接起来。
const questionAnswer = async (e) =>{
if (e.which === 13 && model !== null ){
console.log('Successfully submitted a question')
const passage = passageRef.current.value
const question = questionRef.current.value
const answers = await model.findAnswers(question, passage)
setAnswer(answers);
console.log(answers)
}
}
现在,我们的questionAnswer 函数已经完成了。我们把这个函数做成了异步的,因为我们必须 "等待 "我们的模型来回应我们的答案。这一行代码(e.which === 13 && model !== null ) ,检查用户是否按下了键盘上的Enter 按钮来提交问题。它还确保我们的模型在提交问题之前已经加载。
变量passage ,和question ,分别抓取段落和问题的值,以便能够继续提出该问题。请记住,我们的段落和问题是与passageRef 和questionRef 挂钩的,以便我们参考。然后我们把我们的段落和问题传递给我们的模型,这样它就可以找到答案。这些答案被存储在一个称为answers 的变量中。然后我们将这些答案推送到setAnswer 状态。
现在让我们继续创建我们的用户界面,以便所有这些代码都能有意义,并看到我们应用程序的整个流程。
创建一个界面来捕捉问题和答案
return (
<div className="App">
<header className="App-header">
{model ==null ?
<div>
<div>The model is loading</div>
<Loader
type="TailSpin"
color="#028A0F"
height={100}
width={100}/>
</div>
:
<React.Fragment>
Passage
<textarea ref={passageRef} rows="30" cols="100"></textarea>
Ask a Question
<input ref={questionRef} onKeyPress={questionAnswer} size="80"></input>
<br />
Answers
{answer ? answer.map((ans, idx) =><div><b>Answer {idx+1} - </b> {ans.text} ({Math.floor(ans.score*100)/100})</div>) : ""}
</React.Fragment>
}
</header>
</div>
);
}
这就是我们的用户界面代码的全部内容。那里有相当多的代码。让我解释一下。
首先,我们使用model ==null ? ,检查我们的模型是否已经加载。如果模型正在加载,我们将显示一个div ,其中有一个字符串,The model is loading 。然后我们使用之前导入的Loader 函数来显示一个漂亮的加载器界面。
你可以玩一玩type,color,height, 和width 。如果我们的模型被加载,我们使用React.Fragment ,去定义我们的用户界面。它允许我们渲染多个元素。
默认情况下,React只允许我们渲染一个元素。
我们的用户界面有三个关键部分。Passage,Ask a Question, 和Answers 部分。Passage 是一个文本区域,我们在这里放上一段我们希望模型能读完的文字。Ask a Question 区域允许我们传递我们的输入问题。按键时(Enter)。
这将触发我们先前定义的questionAnswer 函数,向模型运行我们的段落和问题,并返回一个答案。如果我们没有答案,那么我们将不显示任何东西。最后,模型进行评估,并从模型中读出分数。
总结
本教程展示了我们如何利用预先训练好的BERT模型来建立一个由BERT驱动的问答式Web应用程序。我们可以通过一段话,向它提出问题,通过这段话,模型可以从这段话中推断并回馈给你一个答案。
你可以自己尝试一下。从维基百科上抓取任何段落,然后等着看它会给出什么答案以及它们的分数值。