如何在应用平台上部署预先训练好的问题和答案TensorFlow.js模型

610 阅读15分钟

作者选择了Code 2040,作为Write for DOnations项目的一部分接受捐赠。

简介

随着机器学习(ML)领域的发展,使用这种技术的环境清单也在不断增加。这些环境之一是网络浏览器,近年来,与数据相关的框架激增,目标是基于网络的机器学习模型。这些框架的一个例子是TensorFlow.js,TensorFlow的JavaScript对应库,用于在浏览器中训练、执行和部署机器学习模型。为了使TensorFlow.js能够被具有有限或没有ML经验的开发者使用,该库带有几个预训练的模型,开箱即可使用。

预训练的机器学习模型是随时可用的机器学习,你不需要训练。TensorFlow.js包括14个适合各种使用情况的模型。例如,有一个用于识别常见物体的图像分类模型和一个用于识别身体部位的身体分割模型。这些模型的主要便利之处在于,正如其名称所述,你不需要训练它们。相反,你在你的应用程序中加载它们。除了它们的易用性和预先训练的性质外,这些模型是经过策划的--它们是准确和快速的,有时是由算法的作者训练的,并为网络浏览器优化。

在本教程中,你将创建一个Web应用程序,使用TensorFlow.js提供问题和答案(QnA)的预训练模型。你将部署的模型是一个双向编码器表示的变形器BERT)模型,它使用一段话和一个问题作为输入,并试图从这段话中回答问题。

你将在DigitalOcean的应用平台上部署该应用,这是一个管理解决方案,可以在几下子内从GitHub等来源构建、部署和扩展应用。你将创建的应用程序包括一个静态页面,有两个输入字段,一个是段落,一个是问题。最后,你将使用TensorFlow.js的一个预训练模型和DigitalOcean的应用平台,从GitHub上编写和部署一个问答应用程序。

先决条件

要完成本教程,你将需要。

第1步 - 创建应用程序的界面并导入所需的库

在这一步,你将编写应用程序的HTML代码,它将定义其界面并为应用程序导入库。这些库中的第一个是TensorFlow.js,你将从_内容交付网络_或CDN加载该包,而不是在本地安装该包。CDN是一个横跨多个地点的服务器网络,存储他们提供给互联网的内容。这些内容包括JavaScript文件,如TensorFlow.js库,从CDN加载它可以节省你在你的应用程序中打包它们。同样,你将导入包含问题和答案模型的库。在下一步,你将编写应用程序的JavaScript,它使用该模型来回答一个给定的问题。

在本教程中,你将构建一个看起来像这样的应用程序。

The app

该应用程序有五个主要元素。

  • 一个按钮,加载一个预定义的段落,你可以用它来测试这个应用程序。
  • 一个输入段落的文本字段(如果你选择编写或复制你自己的)。
  • 一个输入问题的文本字段。
  • 触发预测的按钮,以回答问题。
  • 在 "**答案!"**按钮下面有一个显示模型输出的区域(目前是空白的白色空间)。

首先在你喜欢的地方创建一个名为tfjs-qna-do 的新目录。在这个目录中,使用你选择的文本编辑器,创建一个名为index.html 的新的HTML文件,并粘贴以下代码。

index.html

<!DOCTYPE html>
<html lang="en-US">

<head>
    <meta charset="utf-8" />
    <!-- Load TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
    <!-- Load the QnA model -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/qna"></script>
    <link href="./style.css" rel="stylesheet">
</head>

<body>
    <div class="main-centered-container">
        <h1>TensorFlow.js Pre-trained "QnA" BERT model</h1>
        <h3 class="header-border">Introduction</h3>
        <p>This application hosts TensorFlow.js' pre-trained Question and Answer model, and it attempts to answer the question
        using a given passage. To use it, write a passage (any!) in the input area below and a question. Then, click the
        "answer!" button to answer the question using the given passage as a reference. You could also click the test
        button to load a pre-defined input text.</p>

        <h4>Try the test passage!</h4>
        <div id='test-buttons'></div>

        <div>
            <h4>Enter the model's input passage here</h4>
            <textarea id='input-text' rows="20" cols="100" placeholder="Write the input text..."></textarea>
        </div>

        <div>
            <h4>Enter the question to ask</h4>
            <textarea id='question' rows="5" cols="100" placeholder="Write the input text..."></textarea>
        </div>
        <h4>Click to answer the question</h4>
        <div id="answer-button"></div>
        <h4>The model's answers</h4>
        <div id='answer'></div>

        <script src="./index.js"></script>


    </div>
</body>

</html>

以下是该HTML的分解方式。

  • 最初的<html> 标签有<head> 标签,用于定义元数据、样式和加载脚本。它的第一个元素是<meta> ,在这里你将把页面的charset 编码设置为utf-8 。在它之后,有两个<script> 标签用于从CDN加载TensorFlow.js和问答模型。
  • 在这两个<script> 标签之后,有一个<link> 标签,用于加载一个CSS文件(接下来你将创建这个文件)。
  • 接下来,是HTML的<body>,即文档的内容。在它里面,有一个<div> 标签,其类别为main-centered-container ,包含页面的元素。首先是一个带有应用程序标题的<h1> 页眉和一个较小的<h3> 页眉,然后是一个解释如何工作的简短介绍。
  • 在介绍下,有一个<h4> 标题和一个<div> ,你将在这里添加按钮,用样本文本填充段落输入文本字段。
  • 然后,是应用程序的输入字段:一个是段落(你希望模型阅读的内容),一个是问题(你希望模型回答的内容)。如果你想调整它们的大小,请改变rowscols 属性。
  • 在文本字段之后,有一个ID为button<div> ,以后你将在这里附加一个按钮,点击后,读取文本字段的文本,并将它们作为模型的输入。
  • 最后,有一个ID为answer<div> ,用来显示模型的输出,还有一个<script> 标签,包括你将在下一节写的JavaScript代码。

接下来,你将向该项目添加CSS。在你添加index.html 文件的同一目录下,创建一个名为style.css 的新文件并添加以下代码。

style.css

body {
  margin: 50px 0;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial;
}

button {
  margin: 10px 10px;
  font-size: 100%;
}

p {
  line-height: 1.8;
}

.main-centered-container {
  padding: 60px;
  display: flex;
  flex-direction: column;
  margin: 0 auto;
  max-width: 960px;
}

.header-border {
  border: solid #d0d0d0;
  border-width: 0 0 1px;
  padding: 0 0 5px;
}

这个CSS为三个HTML元素添加样式:正文、按钮和段落(<p>)。在body ,它添加了margin、padding,并改变了默认字体。对button ,它增加了边距并增大了字体大小。在段落p ,它修改了line-height 属性。CSS有一个类main-centered-container ,可以使内容居中,另一个类header-border ,可以为元素添加实线。

在这一步,你写了应用程序的HTML文件。你导入了TensorFlow.js库和QnA模型,并定义了你以后用来添加段落和你要回答的问题的元素。在接下来的步骤中,你将编写读取这些元素并触发预测的JavaScript代码。

第2步 - 用预先训练好的模型进行预测

在这一节中,你将实现网络应用的JavaScript代码,读取应用的输入字段,进行预测,并将预测的答案写入HTML中。你将在一个函数中实现,当你点击 "答案 "按钮时触发。一旦点击,它将引用两个输入字段来获取它们的值,将它们作为模型的输入,并将其输出写入你在步骤1中定义的<div> ,ID为output 。然后,你将在本地运行该应用程序,在将其添加到GitHub之前对其进行测试。

在项目的目录tfjs-qna-do/ ,创建一个名为index.js 的新文件,并声明以下变量。

index.js

let model;

// The text field containing the input text
let inputText;

// The text field containing the question
let questionText;

// The div where we will write the model's answer
let answersOutput;

其中第一个变量,model ,是你将存储QnA模型的地方;inputTextquestionText 是对输入和问题文本字段的引用;answersOutput 是对输出的引用<div>

除了这些变量之外,你还需要一个常量来存储你将用来测试应用程序的样本文本。我们将使用维基百科上关于DigitalOcean的文章作为一个样本段落。基于这段文字,你可以向模型提出诸如 "DigitalOcean的总部在哪里?"的问题,希望它能输出 "纽约"。

将此块复制到你的index.js 文件中。

index.js

// Sample passage from Wikipedia.
const doText = `DigitalOcean, Inc. is an American cloud infrastructure provider[2] headquartered in New York City with data centers worldwide.[3] 
DigitalOcean provides developers cloud services that help to deploy and scale applications that run simultaneously on multiple computers.
DigitalOcean also runs Hacktoberfest which is a month-long celebration (October 1-31) of open source software run in partnership with GitHub and Twilio.
`;

现在,你将定义应用程序的功能,从createButton() 。这个函数创建一个按钮并将其附加到一个HTML元素上。

index.js

function createButton(innerText, id, listener, selector, disabled = false) {
  const btn = document.createElement('BUTTON');
  btn.innerText = innerText;
  btn.id = id;
  btn.disabled = disabled;

  btn.addEventListener('click', listener);
  document.querySelector(selector).appendChild(btn);
}

createButton() 是一个创建应用程序的两个按钮的函数;由于所有的按钮工作原理相似,这个函数避免了重复代码。该函数有五个参数。

  • innerText: 按钮的文本。
  • id: 按钮的ID。
  • listener :一个回调函数,当用户点击按钮时执行。
  • selector :你将附加按钮的<div> 元素。
  • disabled :一个布尔值,用于禁用或启用该按钮。这个参数的默认值是false

createButton() 首先创建一个按钮的实例,并将其分配给变量btn 。然后,它设置按钮的innerText,id, 和disabled 属性。这个按钮使用了一个点击事件监听器,每当你点击它时,它就会执行一个回调函数。该函数的最后一行将按钮附加到selector 参数中指定的<div> 元素。

接下来,你将创建一个名为setupButtons() 的新函数,调用createButton() 两次来创建应用程序的按钮。首先,创建应用程序的**Answer!**按钮。

index.js

function setupButtons() {
  // Button to predict
  createButton('Answer!', 'answer-btn',
    () => {
      model.findAnswers(questionText.value, inputText.value).then((answers) => {
        // Write the answers to the output div as an unordered list.
        // It uses map create a new list of the answers while adding the list tags.
        // Then, we use join to concatenate the answers as an array with a line break
        // between answers.
        const answersList = answers.map((answer) => `<li>${answer.text} (confidence: ${answer.score})</li>`)
          .join('<br>');

        answersOutput.innerHTML = `<ul>${answersList}</ul>`;
      }).catch((e) => console.log(e));
    }, '#answer-button', true);
}

该函数创建的第一个按钮是触发预测的那个。它的前两个参数,innerTextid ,是按钮的文本和你想分配给按钮的标识符。第三个参数是监听器的回调(在下面解释)。第四个参数,selector ,是你想添加按钮的

的id (#answer-button),第五个参数,disabled ,被设置为true ,这将禁用按钮(以避免在应用程序加载模型之前进行预测)。

倾听者的回调是一个函数,一旦你点击**Answer!**按钮就会执行。一旦点击,回调函数首先调用预训练模型的函数findAnswers() 。(关于findAnswers() 函数的更多信息,见产品文档)。findAnswers() 使用输入段落(从questionText )和问题(从inputText )作为参数。它以一个数组的形式返回模型的输出,该数组看起来如下。

Model's output[
    {
        "text": "New York City",
        "score": 19.08431625366211,
        "startIndex": 84,
        "endIndex": 97
    },
    {
        "text": "in New York City",
        "score": 8.737937569618225,
        "startIndex": 81,
        "endIndex": 97
    },
    {
        "text": "New York",
        "score": 7.998648166656494,
        "startIndex": 84,
        "endIndex": 92
    },
    {
        "text": "York City",
        "score": 7.5290607213974,
        "startIndex": 88,
        "endIndex": 97
    },
    {
        "text": "headquartered in New York City",
        "score": 6.888534069061279,
        "startIndex": 67,
        "endIndex": 97
    }
]

数组中的每个元素都是一个有四个属性的对象。

  • text :答案。
  • score :模型的信心水平。
  • startIndex :回答该问题的段落的第一个字符的索引。
  • endIndex :答案的最后一个字符的索引。

回调不是在模型返回时显示输出,而是使用一个map() 函数,创建一个包含模型的答案和分数的新数组,同时添加列表<li> HTML标签。然后,它用答案之间的<br> (换行)来连接数组的元素,并将结果分配给answer <div> ,以列表形式显示它们。

接下来,添加对createButton() 的第二次调用。将高亮部分添加到第一个createButton 下方的setupButtons 函数中。

index.js

function setupButtons() {
  // Button to predict
  createButton('Answer!', 'answer-btn',
    () => {
      model.findAnswers(questionText.value, inputText.value).then((answers) => {
        // Write the answers to the output div as an unordered list.
        // It uses map create a new list of the answers while adding the list tags.
        // Then, we use join to concatenate the answers as an array with a line break
        // between answers.
        const answersList = answers.map((answer) => `<li>${answer.text} (confidence: ${answer.score})</li>`)
          .join('<br>');

        answersOutput.innerHTML = `<ul>${answersList}</ul>`;
      }).catch((e) => console.log(e));
    }, '#answer-button', true);

  createButton('DigitalOcean', 'test-case-do-btn',
    () => {
     document.getElementById('input-text').value = doText;
    }, '#test-buttons', false);
}

这个新的调用附加到test-buttons <div> 的按钮,加载你先前在变量doText 中定义的DigitalOcean样本文本。该函数的第一个参数是按钮的标签**(DigitalOcean**),第二个参数是id ,第三个是监听器的回调,在通道输入文本区写入doText 变量的值,第四个是selector ,最后一个是false 值(以避免禁用按钮)。

接下来,你将创建一个函数,命名为init() ,用来调用其他函数。

index.js

async function init() {
  setupButtons();
  answersOutput = document.getElementById('answer');
  inputText = document.getElementById('input-text');
  questionText = document.getElementById('question');

  model = await qna.load();
  document.getElementById('answer-btn').disabled = false;
}

init() 首先调用setupButtons() 。然后,它把应用程序的一些HTML元素分配给你在脚本顶部定义的变量。接下来,它加载QnA模型并将答案(answer-btn)按钮的禁用属性改为false

最后,调用init()

index.js

init();

你已经完成了这个应用程序。要测试它,请打开你的网络浏览器,在地址栏中写上项目目录的绝对路径,并将其附加到/index.html 。你也可以打开你的文件管理器应用程序(如Mac上的Finder),点击 "index.html "来打开网页应用程序。在这种情况下,地址会看起来像 your_filepath/tfjs-qna-do/index.html.

要找到绝对路径,请到终端,从项目的目录中执行以下命令。

pwd

它的输出将看起来像这样。

Output/Users/your_filepath/tfjs-qna-do

启动应用程序后,你需要等待几秒钟,让它下载并加载模型。当应用程序启用**Answer!按钮时,你将知道它已经准备好了。**然后,你可以使用测试段落按钮(DigitalOcean),或者自己写一段话,然后输入一个问题。

要测试该应用程序,请点击 "DigitalOcean "按钮。在段落输入区,你会看到关于DigitalOcean的样本文本。在问题输入区,写下 "DigitalOcean的总部在哪里?"从这段话中,我们可以看到答案是 "纽约"。但模型会不会也这么说呢?点击 "**回答!"**来了解一下。

输出结果应该与此类似。

Model's answers to "where is DO headquartered?"

正确!"。虽然有细微的差别,但五个答案中有四个说DigitalOcean的总部在纽约市。

在这一步,你完成并测试了网络应用程序。你写的JavaScript代码读取了输入的段落和问题,并将其作为QnA模型的输入来回答问题。接下来,你将把代码添加到GitHub上。

第3步 - 将应用程序推送到GitHub上

在这一部分,你将把网络应用添加到GitHub仓库。之后,你将把仓库连接到DigitalOcean的应用平台,并部署该应用。

首先登录GitHub。在主页上,点击姓名下的绿色按钮或屏幕右上角的加号,创建一个新仓库。

Click to create a repository

Click to create a repository

无论哪种选择都会带你到创建新仓库的界面。在版本库名称栏中(在你的用户名后面),将版本库命名为tfjs-qna-do。选择它的隐私设置,然后点击创建版本库来创建它。

Create the repository

打开一个终端,进入项目的目录tfjs-qna-do/ 。在那里,执行以下命令来创建一个新的本地 Git 仓库。

git init

接下来,将你想用 Git 追踪的文件分阶段,在本例中,就是目录中的所有文件。

git add .

并提交它们。

git commit -m "initial version of the app"

将仓库的主分支重命名为main

git branch -M main

然后将本地仓库与 GitHub 上的远程仓库连接起来。

git remote add origin git@github.com:your-github-username/tfjs-qna-do.git

最后,将本地代码库推送到远程仓库。

git push -u origin main

如果你是第一次推送代码,它会要求你输入 GitHub 的凭证。

推送完代码后,返回到GitHub仓库,刷新页面。你应该能看到你的代码。

The code in the repo

在这一步,你已经把你的Web应用的代码推送到了一个远程GitHub仓库。接下来,你将把这个仓库链接到你的DigitalOcean账户,并部署该应用。

第4步--在DigitalOcean应用平台中部署Web应用

在最后一节中,您将从步骤3中创建的GitHub仓库将问答应用程序部署到DigitalOcean的应用程序平台

首先登录你的DigitalOcean账户。登录后,点击右上角的 "创建"绿色按钮,然后点击 "应用程序",这将使您进入创建新的应用程序屏幕。

Create a new DO App

在这里,选择GitHub作为项目所在的来源。

Choose the source

如果你还没有把你的DigitalOcean账户与GitHub联系起来,它会要求你授权DigitalOcean访问你的GitHub。这样做之后,选择你要链接的仓库:tfjs-qna-do

Link the repo

回到应用平台窗口,选择应用的仓库**(tfjs-qna-do**),选择部署应用的分支**(main**),并勾选自动部署代码修改的选项;该选项确保每次你向分支推送代码时,应用都会被重新部署。

Choose the source (cont.)

在下一个屏幕,配置你的应用程序,使用默认的配置值。作为一个额外的步骤,如果你想改变Web应用的HTTP请求路由,从例如默认的example.ondigitalocean.app/tfjs-qna-do,改为example.ondigitalocean.app/my-custom-r…

Configure the app

接下来,你将为网站命名。同样,你可以保留默认的**tfjs-qna-**do的名字,或者用另一个名字代替它。

Name the site

点击 "下一步"将带你到最后一个屏幕,"最终确定和启动",在那里你将选择应用程序的定价层级。由于该应用程序是一个静态网页,App Platform将自动选择免费的入门计划。最后,点击 "启动初学者应用程序"按钮,建立和部署应用程序。

Finalize and launch

当应用程序被部署时,你会看到一个与此类似的屏幕。

Deploying app

而一旦部署,你会看到。

Deployed

要访问该应用,请点击该应用的URL来访问你的应用,该应用现在已经部署在云中。

Deployed app

结论

随着机器学习领域的扩大,它的用例以及它所涉及的环境和平台也在扩大,包括网络浏览器。

在本教程中,你已经建立并部署了一个使用TensorFlow.js预训练模型的Web应用。你的 "**问答 "**网络应用将一段话和一个问题作为输入,并使用预先训练好的BERT模型来根据这段话回答问题。开发完应用程序后,你将你的GitHub账户与DigitalOcean连接起来,并将应用程序部署在App Platform中,而不需要额外的代码。

作为未来的工作,你可以通过向应用程序添加更改并将其推送到GitHub来触发该应用程序的自动部署。你还可以添加一个新的测试段落,并将答案的输出格式化为一个表格,以提高其可读性。

关于TensorFlow.js的更多信息,请参考其官方文档