使用BERT的问题回答(Q&A)入门教程

296 阅读6分钟

使用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 ,分别抓取段落和问题的值,以便能够继续提出该问题。请记住,我们的段落和问题是与passageRefquestionRef 挂钩的,以便我们参考。然后我们把我们的段落和问题传递给我们的模型,这样它就可以找到答案。这些答案被存储在一个称为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应用程序。我们可以通过一段话,向它提出问题,通过这段话,模型可以从这段话中推断并回馈给你一个答案。

你可以自己尝试一下。从维基百科上抓取任何段落,然后等着看它会给出什么答案以及它们的分数值。