猫の手ツール

作成・資料確認日:2026年10月12日

公式資料と、このプロジェクトの実装・サンプルの実行をもとに説明します。人物の作例はAI生成、猫の写真は運営者の愛猫です。実行条件や確認できた範囲は各記事に記載しています。

MediaPipe文章分類入門:英語BERTモデルの感情とスコア

TextClassifierは、文章をモデルが決めたカテゴリへ分類します。ここでは英語の映画レビューをもとに学習したBERTモデルを使い、自作の英語例文をpositive / negativeへ分類します。「日本語サイトだから日本語の文章も使える」とは限らないことを、モデル選びと実装の両面から確認します。

1. この記事のゴールと使用する版

英語の文章をclassify()へ渡し、ラベルとスコアを表示できること。タスクの対応言語とモデルの対応言語を分けて確認し、分類カテゴリをコードだけで自由に増やせない理由を理解することを目指します。

前提はHTMLの要素、JavaScriptの変数・関数・イベント、async/awaitの基本です。モデルを自分で学習させる知識は不要です。CDN版のSDKを使用するため、このサンプルへMediaPipeをnpmで追加インストールする必要はありません。SDKとWASMは同じパッケージ・同じ版に固定します。

教材の構成:@mediapipe/tasks-text 1.1.0。モデルはbert_classifier、float32、モデル版1。SDKの最新版は2026年10月12日に公式npmレジストリで確認しました。この配布モデルは英語用です。

このサイトのAI画像ツールで使うSDKと配信方法、旧Solutionsとの違いは概要記事の現在のサイト構成に整理しています。音声・テキスト編は追加の学習教材です。今回のモデルは公式ストレージの版が分かるURLを指定し、コードの先頭で確認できます。

2. サンプルを試し、自分のPCで起動する

VS Codeでフォルダーを開く方法や、app.jsの編集・保存が初めてなら、導入記事のエディター準備と「保存 → ブラウザ更新 → 実行」の手順を先に確認してください。下では、このサンプルの起動に必要な操作を示します。

肯定的なレビューで「英語の感情を分類する」を押します。次に否定的なレビューと、賛否が混在する例文を試します。入力欄へ日本語を入れると、英語用サンプルの入力チェックで止まることも確認できます。英語チェックは完全な言語判定ではなく、教材用の簡単な文字種チェックです。

肯定的な自作英語レビューにpositiveの候補を表示した実行画面
Windows・Chrome 154、tasks-text 1.1.0、bert_classifier版1。英語の映画レビュー由来の感情分類モデルへ、肯定的な自作英文を入力しました。

肯定・否定の例文で確認できたこと

標準の肯定的な例文「This movie is delightful. The story is warm and the acting is wonderful.」は、positiveが0.99976でした。否定的な例文「This movie is boring. The story is confusing and the acting is terrible.」へ変えると、negativeが0.99985でした。

否定的な自作英語レビューにnegativeの候補を表示した実行画面
同じSDK・モデルへ否定的な英文を入力した結果。日本語の分類を確認した画面ではありません。

二つの短い英文に対して、入力の変更と分類結果の対応を確認できました。このスコアを、一般的なレビューの正解率や、人の満足度の測定値へ置き換えることはできません。

ZIPを展開して、HTMLを直接開かずHTTPで起動する

  1. ZIPを保存し、エクスプローラーの「すべて展開」で中身を展開します。ZIPの中を閲覧している状態で実行しません。
  2. index.htmlとapp.jsが見えるフォルダーで右クリックし、ターミナルを開きます。README.txtにも手順があります。
  3. 下の最初の2行を1行ずつ実行します。バージョンが表示されればNode.jsとnpmを利用できます。「見つからない」ならNode.js公式サイトのLTS版を導入し、ターミナルを開き直します。
  4. 3行目を実行します。http-serverの初回取得には通信が必要です。サーバーが起動したらターミナルは開いたままにします。
  5. ブラウザのアドレス欄へhttp://localhost:8080/を入力します。サンプルが表示されたら上の操作を試します。
  6. 編集後は保存してブラウザを再読み込みします。終了はターミナルでCtrl+Cです。
node --version
npm.cmd --version
npx.cmd --yes http-server . -p 8080

Windows PowerShellではnpm.cmdとnpx.cmdを使うと、スクリプト実行ポリシーによるつまずきを避けやすくなります。macOS・Linuxはnpm・npxで実行します。8080が使用中なら-p 8081へ変え、ブラウザ側もhttp://localhost:8081/に合わせます。

初回のボタン操作でSDK・WASM・モデルを取得するので、しばらく待つ場合があります。HTTPサーバーはHTMLとJavaScriptを配信するためのもので、AI推論をサーバー側で行う構成ではありません。file://での直接起動は使いません。基本操作の確認はPCのブラウザで行い、スマホは画面幅の確認と実機での動作検証を区別します。

3. 分類の種類はモデルが決める

TextClassifierという名前だけでは、どんな分類を行うかは決まりません。今回のBERT分類モデルは感情分類で、肯定的・否定的のカテゴリを持ちます。文章の言語を調べたいならLanguageDetector、文章の似ている度合いを比較したいならTextEmbedderというように、目的ごとにタスクを選びます。

公式のモデル概要では、この感情分類モデルは英語の映画レビューを含むSST-2で学習され、英語のみを対象とすることが示されています。英語でも、製品仕様書やプログラムのエラーログなど、学習分野と違う文章では品質が変わり得ます。日本語の感情分類が必要なら、対応するモデルを探すか、カスタマイズの方法を別に検討します。

カテゴリを「猫」「犬」に変えたい場合、画面のラベル名だけを書き換えても認識は変わりません。モデルの出力を違う名前で表示しているだけです。タスク互換性、前処理、ラベル情報を持った対応モデルが必要です。最初は配布モデルのカテゴリをそのまま読み、処理の成功を確認してから応用へ進みましょう。

4. 初期化してclassify()を呼ぶ

初期化はTextClassifier.createFromOptions()です。WASMの準備にはFilesetResolver.forTextTasks()を使います。モデルを読み込んだ分類器を変数へ保存し、例文が変わっても同じインスタンスを使います。

const classifier = await TextClassifier.createFromOptions(files, {
  baseOptions: { modelAssetPath: MODEL },
  maxResults: 2,
});
const result = classifier.classify(inputText);

maxResults:2は上位の表示件数を指定する設定です。モデルのカテゴリ数を増やすものではありません。入力はJavaScriptの文字列で、トークン化等の処理はタスク側で行われます。このサンプルでは前後の空白を取り除き、空欄は実行前に止めます。

教材の入力欄は2,000文字までですが、長い文章をそのまま入れればすべての意味を理解するというわけではありません。モデルの入力長や切り詰めを含む前処理条件を確認してください。まずは短いレビューで肯定・否定の違いを観察し、長文や別分野は追加の実験として扱います。

5. categoryName・score・indexを読む

結果のclassificationsの最初の要素からcategoriesを取得します。categoryNameがラベル名、scoreがスコア、indexがモデルのラベル一覧内の番号です。画面では名前と値を表示し、JSONでは番号も含めて確認できます。

const categories = result.classifications[0].categories;
for (const category of categories) {
  console.log(category.categoryName, category.score, category.index);
}

メーターはスコアを見やすくする表示です。「positiveだから投稿者は満足している」と人の状態へ直結させるものではありません。皮肉、否定の重なり、引用、賛否の混在などは、短い文章でも解釈が難しくなることがあります。表と入力文を一緒に読む習慣を付けましょう。

スコア閾値で低い候補を非表示にする設定もありますが、何も返らない状況が増える場合があります。「候補なし」をアプリの異常と決めつけず、表示条件とモデルの限界を区別します。重要な分類を自動採用する機能へ進むなら、代表的なデータで誤りを観察し、曖昧な入力を人が確認できる設計を加えます。

6. 自作の例文を少しずつ変えて観察する

サンプルには肯定的・否定的・賛否の混在という3種類の例文を用意しています。まずボタンでこの3つを順に試し、JSONを残します。その後、一つの単語だけを変えると、どの変更で結果が変わるかを追いやすくなります。

const positive = "This movie is delightful. The story is warm and the acting is wonderful.";
const negative = "This movie is boring. The story is confusing and the acting is terrible.";

例えばwonderfulをterribleへ変える、notを入れる、肯定的な一文に否定的な一文を足す、といった実験ができます。値が変わることは観察できますが、数件の例でモデル全体の精度を数値化することはできません。キャプチャと本文は、実際に確認した入力だけの結果として示しています。

日本語を入力した場合、今回のサンプルでは非ラテン文字を含むかを簡単に調べて停止します。これは日本語へ対応させる前処理ではありません。また、ラテン文字だけでも英語とは限らないため、英語判定を保証するチェックではありません。本番で入力言語を分ける場合は、LanguageDetectorなどの候補を参考にしつつ、利用者が選択できるUIも考えます。

猫の手ツールへ応用するなら、英語の感想文を試す教材や、分類モデルの選び方を示す比較デモが候補です。現在のAI画像ツールに感情分類を搭載しているという意味ではありません。推論はローカルのブラウザで行いますが、モデルは初回にダウンロードします。画像用SDKとはパッケージとモデルを揃え直す必要がある点を、この教材で確かめてください。

7. 完全コードを確認する

以下はダウンロードZIPと同じソースです。index.htmlが画面と操作ボタン、app.jsがSDKの初期化・入力の準備・推論・出力表示を担当します。コードを途中だけコピーすると必要な要素が欠けるので、最初はZIPの完全版から起動してください。慣れたら設定を1つずつ変更し、JSONと画面の変化を見ます。

index.html:画面の完全コード
<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>MediaPipeで英語の感情を分類する | 猫の手ツール</title><style>:root{font-family:system-ui,sans-serif;color:#17243c;background:#eef2f8}*{box-sizing:border-box}body{margin:0}main{max-width:1080px;margin:auto;padding:28px 20px}h1{font-size:clamp(1.7rem,5vw,2.4rem)}p{line-height:1.8}button,input,select,textarea{font:inherit}button{background:#4338ca;color:white;border:0;border-radius:8px;padding:12px 18px;cursor:pointer}button:disabled{opacity:.55;cursor:wait}label{display:block;font-weight:650;margin:12px 0 6px}textarea{width:100%;min-height:100px;padding:12px;border:1px solid #aab7ce;border-radius:8px}select,input[type=file]{max-width:100%;padding:8px}section,.box{background:white;border:1px solid #dbe1eb;border-radius:14px;padding:18px;margin:18px 0}.controls{display:flex;flex-wrap:wrap;gap:12px;align-items:center}#status{border-left:4px solid #4338ca;padding:14px;background:white;min-height:54px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}canvas,video{max-width:100%;height:auto;display:block;border-radius:8px;background:#14243b}table{border-collapse:collapse;width:100%;font-size:.9rem}th,td{text-align:left;border-bottom:1px solid #dbe1eb;padding:10px}pre{background:#12203a;color:#e0e8ff;padding:16px;border-radius:8px;white-space:pre-wrap;overflow-wrap:anywhere;max-height:420px;overflow:auto}.scroll{overflow:auto}meter{width:160px;max-width:100%}a{color:#4338ca}small{color:#53637d}button:focus-visible,a:focus-visible{outline:3px solid #e99a23;outline-offset:3px}@media(max-width:640px){.grid{grid-template-columns:1fr}main{padding:18px 12px}section{padding:14px}}</style></head><body><main><p><a href="./README.txt">起動手順を開く</a></p><h1>MediaPipeで英語の感情を分類する</h1><p>このサンプルのBERTモデルは英語の映画レビュー由来の感情分類です。positive / negativeを返します。日本語の感情判定のデモではありません。</p><section><h2>実行する</h2><label for="sample">自作の英語例文</label><select id="sample"><option value="positive">肯定的なレビュー</option><option value="negative">否定的なレビュー</option><option value="mixed">賛否の混在</option></select><label for="text-input">分類する英語文章</label><textarea id="text-input" maxlength="2000" lang="en"></textarea><button id="run-button">英語の感情を分類する</button></section><p id="status" role="status" aria-live="polite">準備できました。ボタンを押すとSDK・モデルを読み込みます。</p><section><h2>分類結果</h2><table><thead><tr><th>ラベル</th><th>スコア</th><th>目安</th></tr></thead><tbody id="results-body"></tbody></table><p><small>スコアを人の気持ちやレビューの正確性の保証として使わないでください。</small></p></section><section><h2>実際の出力JSON・検証条件</h2><pre id="json-output">まだ実行していません。</pre></section><p><small>推論はブラウザ内で行います。SDK・モデル取得のためCDN / Googleの公開ストレージへ接続します。入力データを送信する処理はありません。記事の検証日:2026年10月12日。</small></p></main><script type="module" src="./app.js"></script></body></html>
app.js:処理の完全コード
const VERSION = '1.1.0';
const BASE = `https://cdn.jsdelivr.net/npm/@mediapipe/tasks-text@${VERSION}`;
const MODEL = 'https://storage.googleapis.com/mediapipe-models/text_classifier/bert_classifier/float32/1/bert_classifier.tflite';
const examples = { positive: 'This movie is delightful. The story is warm and the acting is wonderful.',
  negative: 'This movie is boring. The story is confusing and the acting is terrible.',
  mixed: 'The acting is great, but the story is slow and disappointing.' };
const select = document.querySelector('#sample');
const input = document.querySelector('#text-input');
const button = document.querySelector('#run-button');
const status = document.querySelector('#status');
const body = document.querySelector('#results-body');
const json = document.querySelector('#json-output');
let classifier;
function selectExample() { input.value = examples[select.value]; }
select.addEventListener('change', selectExample); selectExample();
button.addEventListener('click', async () => {
  const text = input.value.trim();
  if (!text) { status.textContent = '英語の文章を入力してください。'; body.replaceChildren(); json.textContent = '入力が空のため未実行。'; return; }
  if (/[^\u0000-\u024F\s\p{P}\p{N}]/u.test(text)) { status.textContent = 'このサンプルのモデルは英語用です。英語の例文で試してください。'; body.replaceChildren(); json.textContent = '対応言語外を含むためサンプル側で停止。'; return; }
  button.disabled = input.disabled = select.disabled = true;
  try {
    if (!classifier) {
      status.textContent = 'テキスト用SDK・WASM・英語BERTモデルを準備しています。';
      const { TextClassifier, FilesetResolver } = await import(`${BASE}/text_bundle.mjs`);
      const files = await FilesetResolver.forTextTasks(`${BASE}/wasm`);
      classifier = await TextClassifier.createFromOptions(files, { baseOptions: { modelAssetPath: MODEL }, maxResults: 2 });
    }
    await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
    const start = performance.now(); const result = classifier.classify(text); const elapsedMs = performance.now() - start;
    body.replaceChildren();
    for (const category of result.classifications[0]?.categories ?? []) {
      const row = body.insertRow(); row.insertCell().textContent = category.categoryName || `index ${category.index}`;
      row.insertCell().textContent = category.score.toFixed(5);
      const meter = document.createElement('meter'); meter.min = 0; meter.max = 1; meter.value = category.score; row.insertCell().append(meter);
    }
    json.textContent = JSON.stringify({ sdkVersion: VERSION, model: MODEL, inputText: text,
      elapsedMs, userAgent: navigator.userAgent, result }, null, 2);
    status.textContent = `分類完了:${elapsedMs.toFixed(2)} ms。英文の映画レビュー由来のモデルの結果です。`;
  } catch (error) { console.error(error); status.textContent = `実行できませんでした:${error.message}。HTTP起動・通信を確認してください。`; }
  finally { button.disabled = input.disabled = select.disabled = false; }
});
window.addEventListener('pagehide', () => { classifier?.close(); classifier = null; });

画面には入力文やモデルのラベルをtextContentで表示します。文字列をHTMLとして実行する必要はありません。ボタンを処理中に無効にするのは、初期化や推論を重ねて実行しないためです。推論が同期処理のタスクでは、async関数から呼んでも自動的に別スレッドにはなりません。

8. 困ったときの確認表

症状と、最初に確認すること
症状確認
日本語を入力すると実行されないモデルは英語用です。教材の文字種チェックによる停止で、英語例文から試します。
分類が2種類しかないモデルのカテゴリです。maxResultsで新しい分類種類は増えません。
英語でも結果が不自然映画レビュー以外の分野、皮肉、混在した評価などを確認します。
ラベル名だけ変えたら分類できたように見える表示名の変更と学習・認識範囲の変更は別です。対応モデルを確認します。
SDK・モデルを読み込めないHTTP起動か、通信できるか、固定URLと版が揃っているか確認します。開発者ツールのConsoleとNetworkで、最初のエラーと失敗した取得先を見ます。

問い合わせや自分のメモでは「動かない」だけでなく、使用OS・ブラウザ、選んだ入力、画面の状態、最初のエラー、SDK・モデルの版を残すと切り分けやすくなります。入力内容や私的なファイル名を公開する必要はありません。この記事のキャプチャは実行したサンプルの観察で、すべての端末・入力への品質を保証するものではありません。

参考資料

資料確認日:2026年10月12日。SDKとモデルは別々に更新されます。更新する際は固定した版のサンプルを残し、変更後の入力・結果・実行条件を比べてください。

入力データの推論は端末内で行います。SDK・WASM・モデルの取得には通信が発生します。MediaPipe公式ではAPIの性能・利用状況メトリクスの送信も案内されています。SDKの版・実行条件と、プライバシー方針を確認してください。