Benutzerdefinierte Excel-Funktionen für BERT NLP-Aufgaben in JavaScript mit ONNX Runtime

In diesem Tutorial erfahren Sie, wie Sie benutzerdefinierte Excel-Funktionen (ORT.Sentiment() und ORT.Question()) erstellen können, um BERT NLP-Modelle mit ONNX Runtime Web zu implementieren und Deep Learning in Tabellenkalkulationsaufgaben zu ermöglichen. Die Inferenz erfolgt lokal, direkt in Excel!

Image of browser inferencing on sample images.

Inhalt

Voraussetzungen

Was sind benutzerdefinierte Funktionen?

Excel verfügt über viele native Funktionen wie SUM(), mit denen Sie wahrscheinlich vertraut sind. Benutzerdefinierte Funktionen sind ein nützliches Werkzeug, um neue Funktionen für Excel zu erstellen und hinzuzufügen, indem diese Funktionen in JavaScript als Teil eines Add-Ins definiert werden. Diese Funktionen können in Excel genauso aufgerufen werden, wie Sie jede native Funktion in Excel aufrufen würden.

Erstellen des Projekts für benutzerdefinierte Funktionen

Nachdem wir nun wissen, was benutzerdefinierte Funktionen sind, sehen wir uns an, wie wir Funktionen erstellen können, die ein Modell lokal inferieren, um die Sentiment-Texte in einer Zelle zu erhalten, oder Informationen aus einer Zelle extrahieren, indem wir eine Frage stellen und die Antwort in die Zelle zurückgeben.

npm install
npm run build
  • Der folgende Befehl führt das Add-In in Excel Web aus und lädt das Add-In seitenweise in die im Befehl angegebene Tabellenkalkulation.
// Command to run on the web.
// Replace "{url}" with the URL of an Excel document.
npm run start:web -- --document {url}
  • Verwenden Sie den folgenden Befehl, um im Excel-Client auszuführen.
// Command to run on desktop (Windows or Mac)
npm run start:desktop
  • Beim ersten Ausführen des Projekts werden zwei Aufforderungen angezeigt
    • Eine fragt nach der Aktivierung des Entwicklermodus. Dies ist für das seitenweise Laden von Plug-ins erforderlich.
    • Akzeptieren Sie als Nächstes, wenn Sie dazu aufgefordert werden, das Zertifikat für den Plug-in-Dienst.
  • Um auf die benutzerdefinierte Funktion zuzugreifen, geben Sie =ORT.Sentiment("TEXT") und =ORT.Question("FRAGE","KONTEXT") in eine leere Zelle ein und übergeben Sie die Parameter.

Jetzt sind wir bereit, in den Code einzutauchen!

Die Datei manifest.xml

Die Datei manifest.xml gibt an, dass alle benutzerdefinierten Funktionen zum Namespace ORT gehören. Sie verwenden den Namespace, um auf die benutzerdefinierten Funktionen in Excel zuzugreifen. Aktualisieren Sie die Werte in manifest.xml auf ORT.

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

Erfahren Sie mehr über die Konfiguration der Manifestdatei hier.

Die Datei functions.ts

In der Datei function.ts definieren wir den Funktionsnamen, die Parameter, die Logik und den Rückgabetyp.

  • Importieren Sie die Funktionen inferenceQuestion und inferenceSentiment am Anfang der Datei function.ts. (Die Logik in diesen Funktionen behandeln wir später in diesem Tutorial.)
/* global console */
import { inferenceQuestion } from "./bert/inferenceQuestion";
import { inferenceSentiment } from "./bert/inferenceSentiment";
  • Fügen Sie als Nächstes die Funktionen sentiment und question hinzu.
/**
* Returns the sentiment of a string.
* @customfunction
* @param text Text string
* @returns sentiment string.
*/
export async function sentiment(text: string): Promise<string> {
const result = await inferenceSentiment(text);
console.log(result[1][0]);
return result[1][0].toString();
}
/**
 * Returns the sentiment of a string.
 * @customfunction
 * @param question Question string
 * @param context Context string
 * @returns answer string.
 */
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";
}

Die Datei inferenceQuestion.ts

Die Datei inferenceQuestion.ts enthält die Logik zur Verarbeitung des Question-and-Answer-BERT-Modells. Dieses Modell wurde mithilfe dieses Tutorials erstellt. Anschließend haben wir das ORT Quantization Tool verwendet, um die Größe des Modells zu reduzieren. Erfahren Sie mehr über Quantisierung hier.

  • Importieren Sie zuerst onnxruntime-web und die Hilfsfunktionen aus question_answer.ts. question_answer.ts ist eine bearbeitete Version aus dem TensorFlow-Beispiel, das hier zu finden ist. Sie können die bearbeitete Version im Quellcode dieses Projekts hier finden.
/* eslint-disable no-undef */
import * as ort from "onnxruntime-web";
import { create_model_input, Feature, getBestAnswers, Answer } from "./utils/question_answer";
  • Die Funktion inferenceQuestion nimmt die Frage und den Kontext entgegen und liefert die Antworten basierend auf dem Inferenz-Ergebnis. Dann legen wir den Pfad zum Modell fest. Dieser Pfad wird in webpack.config.js mit dem CopyWebpackPlugin gesetzt. Dieses Plugin kopiert die beim Erstellen benötigten Assets in den dist-Ordner.
export async function inferenceQuestion(question: string, context: string): Promise<Answer[]> {
  const model: string = "./bert-large-uncased-int8.onnx";
  • Erstellen wir nun die ONNX Runtime Inferenzsitzung und legen die Optionen fest. Erfahren Sie mehr über alle SessionOptions hier.
  // create session, set options
  const options: ort.InferenceSession.SessionOptions = {
    executionProviders: ["wasm"],
    // executionProviders: ['webgl']
    graphOptimizationLevel: "all",
  };
  console.log("Creating session");
  const session = await ort.InferenceSession.create(model, options);
  • Als Nächstes kodieren wir die Frage und den Kontext mit der Funktion create_model_input aus question_answer.ts. Dies gibt das Feature zurück.
  // Get encoded ids from text tokenizer.
  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 };
}
  • Nachdem wir das kodierte Feature haben, müssen wir Arrays (input_ids, attention_mask und token_type_ids) vom Typ BigInt erstellen, um einen ort.Tensor-Input zu erstellen.
  // Create arrays of correct length
  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);

  // Get encoded.input_ids as 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);
  • Erstellen Sie ort.Tensor aus den Arrays.
  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]);
  • Wir sind bereit für die Inferenz! Hier erstellen wir die OnnxValueMapType (Input-Objekt) und FetchesType (Return-Labels). Sie können das Objekt und das String-Array ohne Deklaration des Typs übergeben, aber das Hinzufügen von Typen ist nützlich.
  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;
  • Schleifen Sie als Nächstes durch das Ergebnis und erstellen Sie ein number-Array aus den resultierenden start_logits und end_logits.
  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]));
  }
  • Zuletzt rufen wir getBestAnswers aus question_answer.ts auf. Dies nimmt das Ergebnis und führt die Nachbearbeitung durch, um die Antwort aus dem Inferenz-Ergebnis zu erhalten.
  const answers: Answer[] = getBestAnswers(
    start_logits,
    end_logits,
    encoded.origTokens,
    encoded.tokenToOrigMap,
    context
  );
  console.log("answers", answers);
  return answers;
}
  • Die Antworten werden dann zurück an die functions.ts question-Funktion zurückgegeben, der resultierende String wird zurückgegeben und in die Excel-Zelle gefüllt.
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";
}
  • Jetzt können Sie den folgenden Befehl ausführen, um das Add-In zu erstellen und seitenweise in Ihre Excel-Tabellenkalkulation zu laden!
// Command to run on the web.
// Replace "{url}" with the URL of an Excel document.
npm run start:web -- --document {url}

Dies ist eine Aufschlüsselung für die benutzerdefinierte Funktion ORT.Question(). Als Nächstes werden wir aufschlüsseln, wie ORT.Sentiment() implementiert ist.

Die Datei inferenceSentiment.ts

Die Datei inferenceSentiment.ts enthält die Logik zur Inferenz und zum Abrufen des Sentiments für Text in einer Excel-Zelle. Der Code hier wurde aus diesem Beispiel erweitert. Lassen Sie uns eintauchen und lernen, wie dieser Teil funktioniert.

  • Zuerst importieren wir die benötigten Pakete. Wie Sie in diesem Tutorial sehen werden, erstellt die Funktion bertProcessing unseren Modell-Input. bert_tokenizer ist der JavaScript-Tokenizer für BERT-Modelle. onnxruntime-web ermöglicht die Inferenz in JavaScript im Browser.
/* eslint-disable no-undef */
import * as bertProcessing from "./bertProcessing";
import * as ort from "onnxruntime-web";
import { EMOJIS } from "./emoji";
import { loadTokenizer } from "./bert_tokenizer";
  • Laden wir nun das quantisierte BERT-Modell, das für die Sentiment-Analyse feinabgestimmt wurde. Erstellen Sie dann die ort.InferenceSession und ort.InferenceSession.SessionOptions.
export async function inferenceSentiment(text: string) {
  // Set model path.
  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);
  • Als Nächstes tokenisieren wir den Text, um den model_input zu erstellen und ihn an session.run mit dem Ausgabe-Label output_0 zu senden, um das Inferenz-Ergebnis zu erhalten.
  // Get encoded ids from text tokenizer.
  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);
  • Als Nächstes parsen wir die Ausgabe, um das Top-Ergebnis zu erhalten und es dem Label, dem Score und dem Emoji zuzuordnen.
  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;
}

  • Die result_list wird zurückgegeben und geparst, um das Top-Ergebnis an die Excel-Zelle zurückzugeben.
export async function sentiment(text: string): Promise<string> {
  const result = await inferenceSentiment(text);
  console.log(result[1][0]);
  return result[1][0].toString();
}
  • Jetzt können Sie den folgenden Befehl ausführen, um das Add-In zu erstellen und seitenweise in Ihre Excel-Tabellenkalkulation zu laden!
// Command to run on the web.
// Replace "{url}" with the URL of an Excel document.
npm run start:web -- --document {url}

Fazit

Hier haben wir die Logik zur Erstellung benutzerdefinierter Funktionen in einem Excel Add-In mit JavaScript unter Verwendung von ONNX Runtime Web und Open-Source-Modellen behandelt. Von hier aus könnten Sie diese Logik nehmen und sie für ein bestimmtes Modell oder Anwendungsfall aktualisieren. Schauen Sie sich unbedingt den vollständigen Quellcode an, der die Tokenizer und die Vor-/Nachbearbeitung zur Vollendung der oben genannten Aufgaben enthält.

Zusätzliche Ressourcen