コンテンツにスキップ

JavaScriptでBERTタスク用のカスタムExcel関数

JavaScriptでBERT NLPタスク用のONNX Runtimeカスタム Excel関数

Section titled “JavaScriptでBERT NLPタスク用のONNX Runtimeカスタム Excel関数”

このチュートリアルでは、ONNX Runtime WebでBERT NLPモデルを実装するカスタムExcel関数(ORT.Sentiment()ORT.Question())を作成して、スプレッドシートタスクでディープラーニングを有効にする方法を見ていきます。推論はExcel内でローカルに実行されます!

サンプル画像でブラウザ推論を行っている画像。

Excelには、おそらくよく知っているSUM()などの多くのネイティブ関数があります。カスタム関数は、アドインの一部としてJavaScriptでそれらの関数を定義することで、Excelに新しい関数を作成して追加するための便利なツールです。これらの関数は、Excelのネイティブ関数と同じようにExcel内でアクセスできます。

カスタム関数プロジェクトの作成

Section titled “カスタム関数プロジェクトの作成”

カスタム関数が何かがわかったので、セル内のテキストの感情を取得したり、質問をして答えがセルに返されることでセルから情報を抽出したりするために、モデルをローカルで推論する関数を作成する方法を見てみましょう。

Terminal window
npm install
npm run build
  • 以下のコマンドは、Excel webでアドインを実行し、コマンドで提供されたスプレッドシートにアドインをサイドロードします。
Terminal window
// Webで実行するコマンド。
// "{url}"をExcelドキュメントのURLに置き換えてください。
npm run start:web -- --document {url}
  • Excelクライアントで実行するには、以下のコマンドを使用します。
Terminal window
// デスクトップ(WindowsまたはMac)で実行するコマンド
npm run start:desktop
  • プロジェクトを初回実行する際には、2つのプロンプトが表示されます:

    • 1つはEnable Developer Modeを求めるものです。これはプラグインのサイドローディングに必要です。
    • 次に、プラグインサービスの証明書を受け入れるよう求められます。
  • カスタム関数にアクセスするには、空のセルに=ORT.Sentiment("TEXT")=ORT.Question("QUESTION","CONTEXT")と入力し、パラメータを渡します。

これでコードに飛び込む準備ができました!

manifest.xmlファイルは、すべてのカスタム関数がORT名前空間に属することを指定します。Excelでカスタム関数にアクセスするために名前空間を使用します。manifest.xmlの値をORTに更新します。

<bt:String id="Functions.Namespace" DefaultValue="ORT"/>
<ProviderName>ORT</ProviderName>

マニフェストファイルの設定についてはこちらで詳しく学んでください

function.tsファイルでは、関数名、パラメータ、ロジック、戻り値の型を定義します。

  • function.tsファイルの上部で関数inferenceQuestioninferenceSentimentをインポートします。(これらの関数のロジックについては、このチュートリアルの後半で説明します。)
/* global console */
import { inferenceQuestion } from "./bert/inferenceQuestion";
import { inferenceSentiment } from "./bert/inferenceSentiment";
  • 次にsentimentquestion関数を追加します。
/**
* 文字列の感情を返します。
* @customfunction
* @param text テキスト文字列
* @returns 感情文字列。
*/
export async function sentiment(text: string): Promise<string> {
const result = await inferenceSentiment(text);
console.log(result[1][0]);
return result[1][0].toString();
}
/**
* 文字列の感情を返します。
* @customfunction
* @param question 質問文字列
* @param context コンテキスト文字列
* @returns 回答文字列。
*/
export async function question(question: string, context: string): Promise<string> {
const result = await inferenceQuestion(question, context);
if (result.length > 0) {
console.log(result[0].text);
return result[0].text.toString();
}
return "Unable to find answer";
}

inferenceQuestion.tsファイルには、質問応答BERTモデルを処理するロジックがあります。このモデルはこのチュートリアルを使用して作成されました。その後、ORT量子化ツールを使用してモデルのサイズを削減しました。量子化についてはこちらで詳しく学んでください

  • まずonnxruntime-webquestion_answer.tsからのヘルパー関数をインポートします。question_answer.tsこちらにあるtensorflowの例から編集されたバージョンです。このプロジェクトのソースで編集されたバージョンをこちらで見つけることができます。
/* eslint-disable no-undef */
import * as ort from "onnxruntime-web";
import { create_model_input, Feature, getBestAnswers, Answer } from "./utils/question_answer";
  • inferenceQuestion関数は質問とコンテキストを受け取り、推論結果に基づいて回答を提供します。次に、モデルへのパスを設定します。このパスはCopyWebpackPluginを使用してwebpack.config.jsで設定されます。このプラグインは、ビルド時に必要なアセットをdistフォルダにコピーします。
export async function inferenceQuestion(question: string, context: string): Promise<Answer[]> {
const model: string = "./bert-large-uncased-int8.onnx";
  • 次に、ONNX Runtime推論セッションを作成してオプションを設定しましょう。すべてのSessionOptionsについてこちらで詳しく学んでください。
// セッションを作成し、オプションを設定
const options: ort.InferenceSession.SessionOptions = {
executionProviders: ["wasm"],
// executionProviders: ['webgl']
graphOptimizationLevel: "all",
};
console.log("Creating session");
const session = await ort.InferenceSession.create(model, options);
  • 次に、question_answer.tscreate_model_input関数を使用してquestioncontextをエンコードします。これはFeatureを返します。
// テキストトークナイザーからエンコードされたIDを取得
const encoded: Feature = await create_model_input(question, context);
console.log("encoded", encoded);
export interface Feature {
input_ids: Array<any>;
input_mask: Array<any>;
segment_ids: Array<any>;
origTokens: Token[];
tokenToOrigMap: { [key: number]: number };
}
  • エンコードされたFeatureを取得したので、ort.Tensor入力を作成するためにBigInt型の配列(input_idsattention_masktoken_type_ids)を作成する必要があります。
// 正しい長さの配列を作成
const length = encoded.input_ids.length;
var input_ids = new Array(length);
var attention_mask = new Array(length);
var token_type_ids = new Array(length);
// encoded.input_idsをBigIntとして取得
input_ids[0] = BigInt(101);
attention_mask[0] = BigInt(1);
token_type_ids[0] = BigInt(0);
var i = 0;
for (; i < length; i++) {
input_ids[i + 1] = BigInt(encoded.input_ids[i]);
attention_mask[i + 1] = BigInt(1);
token_type_ids[i + 1] = BigInt(0);
}
input_ids[i + 1] = BigInt(102);
attention_mask[i + 1] = BigInt(1);
token_type_ids[i + 1] = BigInt(0);
console.log("arrays", input_ids, attention_mask, token_type_ids);
  • Arraysからort.Tensorを作成します。
const sequence_length = input_ids.length;
var input_ids_tensor: ort.Tensor = new ort.Tensor("int64", BigInt64Array.from(input_ids), [1, sequence_length]);
var attention_mask_tensor: ort.Tensor = new ort.Tensor("int64", BigInt64Array.from(attention_mask), [ 1, sequence_length]);
var token_type_ids_tensor: ort.Tensor = new ort.Tensor("int64", BigInt64Array.from(token_type_ids), [ 1, sequence_length]);
  • 推論を実行する準備ができました!ここでOnnxValueMapType(入力オブジェクト)とFetchesType(戻りラベル)を作成します。型を宣言せずにオブジェクトと文字列配列を送信できますが、型を追加することは有用です。
const model_input: ort.InferenceSession.OnnxValueMapType = {
input_ids: input_ids_tensor,
input_mask: attention_mask_tensor,
segment_ids: token_type_ids_tensor,
};
const output_names: ort.InferenceSession.FetchesType = ["start_logits", "end_logits"];
const output = await session.run(model_input, output_names);
const result_length = output["start_logits"].data.length;
  • 次に結果をループし、結果のstart_logitsend_logitsからnumber配列を作成します。
const start_logits: number[] = Array();
const end_logits: number[] = Array();
console.log("start_logits", start_logits);
console.log("end_logits", end_logits);
for (let i = 0; i <= result_length; i++) {
start_logits.push(Number(output["start_logits"].data[i]));
}
for (let i = 0; i <= result_length; i++) {
end_logits.push(Number(output["end_logits"].data[i]));
}
  • 最後にquestion_answer.tsからgetBestAnswersを呼び出します。これは結果を受け取り、推論結果から回答を得るための後処理を行います。
const answers: Answer[] = getBestAnswers(
start_logits,
end_logits,
encoded.origTokens,
encoded.tokenToOrigMap,
context
);
console.log("answers", answers);
return answers;
}
  • answersfunctions.tsquestionに返され、結果の文字列が返されてExcelセルに入力されます。
export async function question(question: string, context: string): Promise<string> {
const result = await inferenceQuestion(question, context);
if (result.length > 0) {
console.log(result[0].text);
return result[0].text.toString();
}
return "Unable to find answer";
}
  • 以下のコマンドを実行して、アドインをビルドしてExcelスプレッドシートにサイドロードできます!
Terminal window
// Webで実行するコマンド。
// "{url}"をExcelドキュメントのURLに置き換えてください。
npm run start:web -- --document {url}

これがORT.Question()カスタム関数の内訳です。次にORT.Sentiment()の実装方法を分解します。

inferenceSentiment.tsは、Excelセル内のテキストの感情を推論して取得するロジックです。ここのコードはこの例から拡張されています。この部分がどのように機能するかを学びましょう。

  • まず必要なパッケージをインポートしましょう。このチュートリアルでわかるように、bertProcessing関数がモデル入力を作成します。bert_tokenizerはBERTモデル用のJavaScriptトークナイザーです。onnxruntime-webはブラウザでのJavaScript推論を可能にします。
/* eslint-disable no-undef */
import * as bertProcessing from "./bertProcessing";
import * as ort from "onnxruntime-web";
import { EMOJIS } from "./emoji";
import { loadTokenizer } from "./bert_tokenizer";
  • 次に、感情分析用にファインチューニングされた量子化BERTモデルを読み込みましょう。次にort.InferenceSessionort.InferenceSession.SessionOptionsを作成します。
export async function inferenceSentiment(text: string) {
// モデルパスを設定
const model: string = "./xtremedistill-go-emotion-int8.onnx";
const options: ort.InferenceSession.SessionOptions = {
executionProviders: ["wasm"],
// executionProviders: ['webgl']
graphOptimizationLevel: "all",
};
console.log("Creating session");
const session = await ort.InferenceSession.create(model, options);
  • 次に、テキストをトークン化してmodel_inputを作成し、出力ラベルoutput_0と一緒にsession.runに送信して推論結果を取得します。
// テキストトークナイザーからエンコードされたIDを取得
const tokenizer = loadTokenizer();
const encoded = await tokenizer.then((t) => {
return t.tokenize(text);
});
console.log("encoded", encoded);
const model_input = await bertProcessing.create_model_input(encoded);
console.log("run session");
const output = await session.run(model_input, ["output_0"]);
const outputResult = output["output_0"].data;
console.log("outputResult", outputResult);
  • 次に、出力を解析してトップ結果を取得し、ラベル、スコア、絵文字にマップします。
let probs = [];
for (let i = 0; i < outputResult.length; i++) {
let sig = bertProcessing.sigmoid(outputResult[i]);
probs.push(Math.floor(sig * 100));
}
console.log("probs", probs);
const result = [];
for (var i = 0; i < EMOJIS.length; i++) {
const t = [EMOJIS[i], probs[i]];
result[i] = t;
}
result.sort(bertProcessing.sortResult);
console.log(result);
const result_list = [];
result_list[0] = ["Emotion", "Score"];
for (i = 0; i < 6; i++) {
result_list[i + 1] = result[i];
}
console.log(result_list);
return result_list;
}
  • result_listが返され、解析されてトップ結果がExcelセルに返されます。
export async function sentiment(text: string): Promise<string> {
const result = await inferenceSentiment(text);
console.log(result[1][0]);
return result[1][0].toString();
}
  • 以下のコマンドを実行して、アドインをビルドしてExcelスプレッドシートにサイドロードできます!
Terminal window
// Webで実行するコマンド。
// "{url}"をExcelドキュメントのURLに置き換えてください。
npm run start:web -- --document {url}

ここでは、ONNX Runtime Webとオープンソースモデルを活用してJavaScriptでExcelアドインのカスタム関数を作成するために必要なロジックについて説明しました。ここから、このロジックを取得して、特定のモデルまたは使用ケースに更新できます。上記のタスクを完了するためのトークナイザーと前処理/後処理を含む完全なソースコードを必ず確認してください。