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

来自菜鸟教程
跳转至:导航、​搜索

作者选择 Code 2040 作为 Write for DOnations 计划的一部分来接受捐赠。

介绍

随着机器学习 (ML) 领域的发展,使用该技术的环境列表也在增长。 这些环境之一是 Web 浏览器,近年来,针对基于 Web 的机器学习模型的数据相关框架激增。 这些框架的一个例子是 TensorFlow.js,它是 TensorFlow 的 JavaScript 对应库,用于在浏览器中训练、执行和部署机器学习模型。 为了使 TensorFlow.js 可供具有有限或没有 ML 经验的开发人员访问,该库附带了几个开箱即用的预训练模型。

预训练的机器学习模型是无需训练的即用型机器学习。 TensorFlow.js 包括 14 个适合各种用例的。 例如,有用于识别常见物体的图像分类模型和用于识别身体部位的身体分割模型。 这些模型的主要方便之处在于,正如名称所述,您不必训练它们。 相反,您将它们加载到您的应用程序中。 除了易于使用和预先训练的特性之外,这些都是精选模型——它们准确且快速,有时由算法作者进行训练并针对 Web 浏览器进行了优化。

在本教程中,您将创建一个使用 TensorFlow.js 为 Question and Answer (QnA) 预训练模型提供服务的 Web 应用程序。 您将部署的模型是来自 Transformers (BERT) 模型的 双向编码器表示,它使用段落和问题作为输入,并尝试回答段落中的问题。

您将在 DigitalOcean 的 App Platform 中部署该应用程序,这是一个托管解决方案,只需单击几下即可从 GitHub 等来源构建、部署和扩展应用程序。 您将创建的应用程序包含一个带有两个输入字段的静态页面,一个用于段落,一个用于问题。 最后,您将使用 TensorFlow.js 的预训练模型和 DigitalOcean 的应用平台之一从 GitHub 编写并部署一个问答应用程序。

先决条件

要完成本教程,您需要:

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

在此步骤中,您将编写应用程序的 HTML 代码,该代码将定义其接口并为应用程序导入库。 这些库中的第一个是 TensorFlow.js,而不是在本地安装包,您将从 内容交付网络 或 CDN 加载它。 CDN 是跨越多个位置的服务器网络,用于存储它们提供给 Internet 的内容。 此内容包括 JavaScript 文件,例如 TensorFlow.js 库,从 CDN 加载它可以避免将它们打包到应用程序中。 同样,您将导入包含 Question and Answer 模型的库。 在下一步中,您将编写应用程序的 JavaScript,它使用模型来回答给定的问题。

在本教程中,您将构建一个如下所示的应用程序:

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

  • 加载预定义段落的按钮,您可以使用它来测试应用程序。
  • 段落的输入文本字段(如果您选择自己编写或复制)。
  • 问题的输入文本字段。
  • 触发回答问题的预测的按钮。
  • Answer! 按钮下方显示模型输出的区域(当前为空白区域)。

首先在您的首选位置创建一个名为 tfjs-qna-do 的新目录。 在此目录中,使用您选择的文本编辑器,创建一个名为 index.html 的新 HTML 文件并粘贴以下代码:

索引.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>——文档的内容。 在其中,有一个包含页面元素的 main-centered-container 类的 <div> 标记。 第一个是带有应用程序标题的 <h1> 标头和一个较小的 <h3> 标头,然后是一个简短的介绍,说明它是如何工作的。
  • 在简介下方,有一个 <h4> 标题和一个 <div> ,您将在其中附加按钮,用示例文本填充段落输入文本字段。
  • 然后是应用程序的输入字段:一个用于段落(您希望模型阅读的内容)和一个用于问题(您希望模型回答的内容)。 如果您想调整它们的大小,请更改 rowscols 属性。
  • 在文本字段之后,有一个 ID 为 button<div>,稍后您将在其中附加一个按钮,单击该按钮后,将读取文本字段的文本并将它们用作模型的输入。
  • 最后,有一个 ID 为 answer<div> 用于显示模型的输出,还有一个 <script> 标记用于包含您将在下一节中编写的 JavaScript 代码。

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

样式.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,它会添加边距、内边距并更改默认字体。 对于 button,它会增加边距并增加字体大小。 对于段落 p,它修改了 line-height 属性。 CSS 有一个类 main-centered-container 使内容居中,另一个类 header-border 为元素添加实线。

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

第 2 步——使用预训练模型进行预测

在本节中,您将实现 Web 应用程序的 JavaScript 代码,该代码读取应用程序的输入字段、进行预测并以 HTML 格式写入预测的答案。 您将在单击“回答”按钮时触发的一个功能中执行此操作。 单击后,它将引用两个输入字段以获取它们的值,将它们用作模型的输入,并将其输出写入您在步骤 1 中定义的 ID 为 output<div>。 然后,您将在本地运行该应用程序以对其进行测试,然后再将其添加到 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。 然后,它设置按钮的 innerTextiddisabled 属性。 该按钮使用单击事件侦听器,该侦听器在您单击它时执行回调函数。 该函数的最后一行将按钮附加到 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();

您已完成应用程序。 要对其进行测试,请打开您的 Web 浏览器并在地址栏中写入项目的目录绝对路径,并在其后附加 /index.html。 您还可以打开文件管理器应用程序(例如 Mac 上的 Finder)并单击“index.html”以打开 Web 应用程序。 在这种情况下,地址看起来像 your_filepath/tfjs-qna-do/index.html

要查找绝对路径,请转到终端并从项目目录中执行以下命令:

pwd

它的输出将如下所示:

Output/Users/your_filepath/tfjs-qna-do

启动应用程序后,您需要等待几秒钟,它会下载并加载模型。 当应用程序启用 Answer! 按钮时,您将知道它已准备就绪。 然后,您可以使用测试段落按钮(DigitalOcean)或编写自己的段落,然后输入问题。

要测试应用程序,请单击“DigitalOcean”按钮。 在段落输入区域,您将看到有关 DigitalOcean 的示例文本。 在问题输入区写下“DigitalOcean 总部在哪里?” 从这段话中,我们可以看出答案是“纽约”。 但是模型会说同样的话吗? 点击【X6X】回答!【X17X】一探究竟。

输出应与此类似:

正确的! 虽然略有不同,但五个答案中有四个表示 DigitalOcean 的总部位于纽约市。

在此步骤中,您完成并测试了 Web 应用程序。 您编写的 JavaScript 代码读取输入段落和问题,并将其用作 QnA 模型的输入来回答问题。 接下来,您将把代码添加到 GitHub。

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

在本节中,您将把 Web 应用程序添加到 GitHub 存储库。 稍后,您会将存储库连接到 DigitalOcean 的应用平台并部署应用。

首先登录 GitHub。 在其主页上,单击您名字下方的绿色按钮或屏幕右上角的加号以创建新存储库。

任何一种选择都会将您带到 创建新存储库 屏幕。 在 Repository name 字段中(在您的用户名之后),将存储库命名为 tfjs-qna-do。 选择其隐私设置,然后单击创建存储库进行创建。

打开终端并进入项目目录 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 存储库并刷新页面。 你应该看到你的代码。

在此步骤中,您已将 Web 应用程序的代码推送到远程 GitHub 存储库。 接下来,您将此存储库链接到您的 DigitalOcean 帐户并部署应用程序。

第 4 步 — 在 DigitalOcean 应用平台中部署 Web 应用程序

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

首先登录您的 DigitalOcean 帐户。 登录后点击右上角的【X26X】创建【X36X】绿色按钮,然后点击【X93X】Apps【X101X】; 这会将您带到 创建新应用程序 屏幕:

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

如果您尚未将 DigitalOcean 帐户与 GitHub 关联,它会要求您授权 DigitalOcean 访问您的 GitHub。 完成后,选择要链接的存储库:tfjs-qna-do

返回 App Platform 窗口,选择应用程序的存储库 (tfjs-qna-do)、部署应用程序的分支 (main),然后检查 自动部署代码更改 框; 此选项可确保每次将代码推送到 main 分支时重新部署应用程序。

在下一个屏幕中,配置您的应用程序,使用默认配置值。 作为附加步骤,如果您希望更改 Web 应用程序的 HTTP 请求路由,例如从默认的 https://example.ondigitalocean.app/tfjs-qna-do 更改为 https://example.ondigitalocean.app/my-custom-route,点击HTTP Routes下的Edit并写入my- ROUTES 输入中的自定义路由

接下来,您将命名该站点。 同样,您可以保留默认的 tfjs-qna-do 名称或将其替换为另一个名称。

单击 Next 将带您到最后一个屏幕 Finalize 并启动 ,您将在其中选择应用程序的定价层。 由于应用是静态网页,App Platform 会自动选择免费的 Starter 计划。 最后,单击 Launch Starter App 按钮来构建和部署应用程序。

部署应用程序后,您将看到与此类似的屏幕:

部署后,您将看到:

要访问应用程序,请单击应用程序的 URL 以访问您的应用程序,该应用程序现已部署在云中。

结论

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

在本教程中,您构建并部署了一个使用 TensorFlow.js 预训练模型的 Web 应用程序。 您的 Question and Answer Web 应用程序将段落和问题作为输入,并使用预训练的 BERT 模型根据段落回答问题。 开发应用程序后,您将 GitHub 帐户与 DigitalOcean 相关联,并在 App Platform 中部署应用程序,无需额外代码。

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

更多关于 TensorFlow.js 的信息,请参考其【X55X】官方文档【X81X】。