modernbert-ja-30m-utterance-tagger

日本語の音声認識(ASR)の結果を後処理する、小さな分類モデルです。

入出力

入力は、ASR が出した文章です。発話の区間 1 つ分を渡します。発話文が次の区間へ続くかを判定するときは、次の区間の先頭も一緒に渡します。

出力は次の 4 つです。

  • 文の切れ目:文章中の句読点の位置ごとに、そこで文が終わるかどうか。
  • 継続:区間の最後の発話文が、次の区間へ続くかどうか。
  • フィラー・言い直し・相槌:文章のどの範囲がフィラー、言い直し、相槌か。
  • 優先度:発話文ごとに、内容のある発話(PRIMARY)か、相槌やフィラーだけのような内容の小さい発話(LOW_PRIORITY)か。

文の区切り方は、BTSJ(基本的な文字化の原則)の「発話文」の定義に倣っています。

ファイル

パス 内容
onnx/ClauseTagger.onnx ONNX(opset 18、float32)。Linux / Windows の ONNX Runtime(CPU)用
onnx/tokenizer.json Unigram tokenizer
onnx/export.json 書き出しの記録(hash、入出力の形、一致検査)
onnx/LICENSE.txt ベースモデルの MIT ライセンス表示
coreml/ClauseTagger.mlpackage macOS 用の Core ML package(CPU のみ、float32)
coreml/tokenizer.json onnx/tokenizer.json とバイト単位で同じ tokenizer

モデルの revision は 90501ea32d56d71f34287039512346537e0aaa4c8fd830e0c3b5746d8fb544c9 です。これは、ソートした state key のバイト列とテンソルのバイト列から計算した SHA-256 です。ONNX 版と Core ML 版は同じ重みで、出力の差は 2.5e-6 以内です。

ONNX を直接使う場合

ONNX モデルが受け取るのはトークン id だけです。文章からトークン id への変換は、呼び出す側で行います。

テンソル:

  • 入力:input_ids int64 [1, seq]
  • 出力:boundary_probs float32 [1, seq, 2]
  • 出力:span_probs float32 [1, seq, 4](O、F、D、B の順)
  • 出力:priority_probs float32 [1, 2]

文章は次の手順でトークン id にします。

  1. 句読点「、。?!?!」を全部「、」にそろえ、末尾の「、」を消します。ASR が付けた句読点はそのままでは信用できないため、モデルは各「、」が文の切れ目かどうかを自分で判定します。学習も、この形の文章で行いました。
  2. 「、」で区切った部分ごとにトークン化し、「、」のトークン(id 275)でつなぎます。語彙には「%、」「)、」のように「、」と前後の文字がくっついたものがあり、そのままトークン化すると「、」を境界の候補として取り出せないためです。こうした語を含まない普通の文章では、そのままトークン化した結果と同じになります。
  3. 先頭に <cls>(id 6)、末尾に <sep>(id 4)を付けます。長さは最大 192 トークンです。これより長い文章は、呼び出す側で「、」の位置で分けて入力してください。

出力の読み方は次のとおりです。位置 i は input_ids の中の位置で、先頭の <cls> が 0 です。

  • 文の切れ目:後ろに文字が続く「、」のトークンが候補です。もとの文章で「。」や「?」だった位置も、手順 1 で「、」にしているので、ほかの「、」と同じように判定します。boundary_probs[0, i, 1] が 0.5 を超えれば文の切れ目です。
  • フィラー・言い直し・相槌:各トークンの span_probs[0, i] の argmax がラベルです(0 = O、1 = F、2 = D、3 = B)。トークンの文字範囲は tokenizer の offsets でわかります。同じラベルが続くトークンをまとめると、文字の範囲になります。
  • 優先度:文の切れ目で区切った発話文 1 つから、フィラー(F)と相槌(B)を除いた文章を、同じ手順で入力します。言い直し(D)は除きません。priority_probs[0, 0] が PRIMARY、priority_probs[0, 1] が LOW_PRIORITY の確率です。
  • 継続:区間の文章の後ろに「、」と次の区間の先頭 60 文字をつないで入力します。つなぎ目の「、」の boundary_probs[0, i, 1] が 0.5 を超えれば、区間の終わりで文が終わります。0.5 以下なら、発話文は次の区間へ続きます。

使用例

import numpy as np
import onnxruntime as ort
from tokenizers import Tokenizer

tk = Tokenizer.from_file("onnx/tokenizer.json")
sess = ort.InferenceSession("onnx/ClauseTagger.onnx", providers=["CPUExecutionProvider"])
CLS, SEP, COMMA = tk.token_to_id("<cls>"), tk.token_to_id("<sep>"), tk.token_to_id("、")


def neutral(text):
    return "".join("、" if c in "、。?!?!" else c for c in text).rstrip("、")


def encode(text):
    ids, offsets, pos = [CLS], [None], 0
    pieces = text.split("、")
    for n, piece in enumerate(pieces):
        e = tk.encode(piece, add_special_tokens=False)
        ids += e.ids
        offsets += [(pos + a, pos + b) for a, b in e.offsets]
        pos += len(piece)
        if n < len(pieces) - 1:
            ids.append(COMMA)
            offsets.append((pos, pos + 1))
            pos += 1
    return ids + [SEP], offsets + [None]


text = neutral("えーっと今日はですね、機械学習の、あの、基礎について話します。まず最初に、データの準備から始めましょう")
ids, offsets = encode(text)
boundary, span, priority = sess.run(None, {"input_ids": np.array([ids], np.int64)})

この入力での出力(確率は小数第 3 位で丸めています):

トークン span(O / F / D / B) 判定 boundary
え 0.002 / 0.997 / 0 / 0 F
ーっと 0.002 / 0.997 / 0 / 0 F
今日は 1 / 0 / 0 / 0 O
ですね 1 / 0 / 0 / 0 O
、 0.000
機械学習 0.999 / 0 / 0.001 / 0 O
の 0.999 / 0 / 0.001 / 0 O
、 0.000
あの 0 / 1 / 0 / 0 F
、 0.000
基礎 1 / 0 / 0 / 0 O
について 1 / 0 / 0 / 0 O
話します 0.999 / 0 / 0.001 / 0 O
、 1.000
まず最初に 1 / 0 / 0 / 0 O
、 0.000
データ 1 / 0 / 0 / 0 O
の準備 1 / 0 / 0 / 0 O
から 1 / 0 / 0 / 0 O
始め 1 / 0 / 0 / 0 O
ましょう 1 / 0 / 0 / 0 O

「えーっと」と「あの」がフィラー、「話します」の後ろだけが文の切れ目になります。

優先度(priority_probs、PRIMARY / LOW_PRIORITY)の例:

発話文 PRIMARY LOW_PRIORITY
後ろの席まで聞こえてますか 1.000 0.000
マイク大丈夫ですかね 1.000 0.000
なるほど 0.000 1.000
えーと 0.001 0.999
でもー 0.002 0.998

学習

  • ベースモデル:sbintuitions/modernbert-ja-30m(revision 6a0406481a741f0f74f23c03a8b958acfd276b7c、MIT License)
  • データ
    • 名大会話コーパス(CC BY-NC-ND 4.0)
    • 作者が録音した講義音声の ASR 文字起こし
  • ラベル
    • 名大会話コーパスの発話文の境界には、コーパスの行末を使いました。
    • それ以外のラベルの一部は LLM で付けました。言い直し・フィラーの範囲は Claude Sonnet、優先度は Claude Haiku、講義のラベルは Claude Sonnet と OpenAI GPT です。
    • テスト用の正解ラベルは Claude Opus で付けました。

評価

テストデータは、学習に使っていない講義 3 回分の ASR 文字起こしです。正解ラベルは Claude Opus で付けました。比較対象は、以前使っていた LLM ベースの判定エンジン Jev(1.13.0 と 1.14.0)です。しきい値はどちらも上の説明と同じです。

文の境界(候補 2159 件、そのうち正解の切れ目 1028 件)と優先度(発話文 1023 件、そのうち LOW_PRIORITY 132 件)の F1(括弧内は適合率 / 再現率):

項目 このモデル Jev 1.14.0 Jev 1.13.0
区間内の文の境界 0.873(0.877 / 0.870) 0.195(0.737 / 0.113) 0.164(0.674 / 0.093)
ASR 区間の継ぎ目での文の終わり 0.711(0.936 / 0.574) 0.709(0.826 / 0.621) 0.714(0.658 / 0.781)
文の境界(全体) 0.817(0.894 / 0.753) 0.451(0.805 / 0.313) 0.470(0.660 / 0.365)
LOW_PRIORITY(確信度 0.90 以上) 0.946(0.968 / 0.924) 0.881(0.798 / 0.985) 0.841(0.734 / 0.985)
LOW_PRIORITY(確信度を問わない) 0.947(0.954 / 0.939) 0.592(0.420 / 1.000) 0.575(0.404 / 0.992)

フィラー(F)と言い直し(D)の文字単位の F1(300 発話文、句読点は除く):

項目 このモデル Jev 1.14.0 Jev 1.13.0
フィラー 0.968(0.982 / 0.953) 0.683(0.539 / 0.931) 0.697(0.556 / 0.936)
言い直し 0.426(0.451 / 0.404) 0.118(0.113 / 0.125) 0.173(0.163 / 0.184)

言い直しの検出はまだ精度が低いです。

ライセンス

このリポジトリのモデルの重みは CC BY-NC-ND 4.0(表示-非営利-改変禁止)です。学習に使った名大会話コーパスと同じライセンスにしています。詳しくは LICENSE.md を見てください。ベースモデル由来の部分には MIT ライセンスの表示が付き、onnx/LICENSE.txt と LICENSE.md に入れてあります。

名大会話コーパスの出典

藤村逸子・大曽美恵子・大島ディヴィッド義和、2011「会話コーパスの構築によるコミュニケーション研究」藤村逸子、滝沢直宏編『言語研究の技法:データの収集と分析』p. 43-72、ひつじ書房

Downloads last month
15
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Pokojaka/modernbert-ja-30m-utterance-tagger

Finetuned
(9)
this model