Last active
July 2, 2026 15:59
-
-
Save 45deg/436ae740673b892a1c20dcd96bd21844 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| title: "ブラウザ完結型・日本語句読点復元モデルの設計と軽量化技術" | |
| description: "キャラクターレベルTransformerの構築、ONNX変換、およびINT8動的量子化によるモデル軽量化を行い、ブラウザ上でのセキュアかつ低遅延な句読点復元推論を実現するプロセス。" | |
| blocks: | |
| - type: card | |
| title: "1. 開発背景と音声文字起こしの課題" | |
| icon: "mic" | |
| color: "context" | |
| body: | |
| - type: markdown | |
| text: | | |
| 音声認識技術の進歩により、会議やインタビューの文字起こしは容易になりました。しかし、音声認識の出力は句読点や改行が失われやすく、人間が読むには文頭・文末の判別が困難で非効率的です。 | |
| この文章構造の復元において、大規模言語モデル(LLM)のAPIを利用する方法もありますが、実用上いくつかの課題があります。 | |
| - type: list | |
| variant: "definitions" | |
| items: | |
| - term: "意図しない言い換えの発生" | |
| body: "LLMは入力された文章を「要約」や「補完」してしまい、元の発話テキストをそのまま維持できない場合があります。" | |
| - term: "情報漏洩リスク" | |
| body: "社外秘の会議や個人情報を含む音声を、外部サーバーのAPIへ送信することにはセキュリティ上の懸念があります。" | |
| - term: "通信遅延とAPIコスト" | |
| body: "長文を処理する際の通信待ち時間(レイテンシ)と、利用トークン量に応じたAPIコストが発生し続けます。" | |
| - type: card | |
| title: "2. 設計目標とタスク定義" | |
| icon: "target" | |
| color: "concept" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1.2, 1] | |
| columns: | |
| - | |
| - type: markdown | |
| text: | | |
| 本プロジェクトでは、「元の発話文を完全に維持し、かつサーバーを介さずブラウザローカルで安全かつ瞬時に句読点と改行を挿入する」ことを目標としました。 | |
| このため、テキストの「生成」ではなく**「各文字境界の分類問題」**としてタスクをシンプルに再定義しました。 | |
| - **入力**: 既存の「、」「。」「?」や改行、不要な空白などをすべて取り除いた、平坦な文字の並び。 | |
| - **出力**: 元の文字列の順序を完全に保子、予測された文字境界に「、」「。」および改行コードのみを挿入したテキスト。 | |
| - | |
| - type: list | |
| title: "出力ヘッドの分離設計" | |
| variant: "definitions" | |
| items: | |
| - term: "句読点ヘッド" | |
| body: "各文字の直後に「なし(NONE)」「読点(、)」「句点(。)」のいずれを入れるかを予測する3クラス分類。" | |
| - term: "改行ヘッド" | |
| body: "句読点予測とは独立して、各文字の直後で改行を入れるべきかを判定する二値分類(0〜1の確率)。" | |
| - type: card | |
| title: "3. なぜ「キャラクターレベル(文字単位)」なのか" | |
| icon: "network" | |
| color: "term" | |
| body: | | |
| 日本語のNLP(自然言語処理)では、形態素解析を用いて単語に分割してから処理することが一般的です。しかし、本モデルでは一文字単位(文字トークン)で処理するアプローチを採用しました。 | |
| - **境界の曖昧性の回避**: 句読点が存在しない話し言葉テキストは、単語の区切り自体が曖昧になり、単語分割器がエラーを起こしやすくなります。 | |
| - **未知語への強さ**: 固有名詞や新語、話し言葉特有の表現、誤字脱字が含まれていても、文字レベルであれば「辞書にない単語(Out-of-Vocabulary)」として処理が破綻することがありません。 | |
| - **語彙サイズの圧縮**: 単語単位では数万〜数十万の語彙テーブルが必要ですが、文字単位であれば約8,300文字でカバーでき、モデルの省メモリ化に貢献します。 | |
| - type: card | |
| title: "4. モデル構造:自己注意機構とSwiGLUの採用" | |
| icon: "brain" | |
| color: "procedure" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1.3, 1] | |
| columns: | |
| - | |
| - type: markdown | |
| text: | | |
| 本モデルは、4層のTransformer Encoderを核とする軽量ニューラルネットワークです。 | |
| - **自己注意機構 (Self-Attention)**: | |
| 文中のすべての文字の関係性を並列で計算します。文頭から文末まで同時に文脈を見渡すことができるため、「〜ですが」のような接続表現や「〜します」といった語尾の特徴から適切な挿入境界を予測します。 | |
| - **埋め込み層**: | |
| 文字トークン埋め込み $E(t_i)$ に、最大192文字の絶対位置エンコーディング $P(i)$ を加算。 | |
| $$x_i = E(t_i) + P(i)$$ | |
| - **SwiGLU FFNの採用**: | |
| 近年の大規模言語モデル(LLaMAなど)でも採用されている活性化関数を導入しました。 | |
| $$\text{SwiGLU}(x) = (\text{SiLU}(x W_1) \otimes x W_2) W_3$$ | |
| 従来のGELU FFNに比べてパラメータあたりの表現能力が優れており、モデル層数を増やさずに予測精度を高めることが可能です。 | |
| - | |
| - type: diagram | |
| format: "mermaid" | |
| width: 100 | |
| body: | | |
| flowchart TD | |
| Input["入力: 文字ID列"] --> Embed["Token + Position Embedding"] | |
| Embed --> Norm["LayerNorm + Dropout"] | |
| Norm --> Enc["Transformer Encoder (4 Layers)"] | |
| Enc --> LN["Final LayerNorm"] | |
| LN --> PHead["Punctuation Head"] | |
| LN --> NHead["Newline Head"] | |
| PHead --> POutput["NONE / 、 / 。"] | |
| NHead --> NOutput["改行確率"] | |
| style Input fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0369a1 | |
| style Embed fill:#e0f2fe,stroke:#0284c7,stroke-width:1px,color:#0369a1 | |
| style Norm fill:#f3f4f6,stroke:#4b5563,stroke-width:1px,color:#374151 | |
| style Enc fill:#faf5ff,stroke:#7c3aed,stroke-width:2px,color:#6d28d9 | |
| style LN fill:#f3f4f6,stroke:#4b5563,stroke-width:1px,color:#374151 | |
| style PHead fill:#ecfdf5,stroke:#059669,stroke-width:1px,color:#047857 | |
| style NHead fill:#ecfdf5,stroke:#059669,stroke-width:1px,color:#047857 | |
| style POutput fill:#fff1f2,stroke:#e11d48,stroke-width:1px,color:#be123c | |
| style NOutput fill:#fff1f2,stroke:#e11d48,stroke-width:1px,color:#be123c | |
| caption: "共通のエンコーダーから2つの予測ヘッドへと分岐するデュアルヘッド構造" | |
| - type: card | |
| title: "5. 学習データ:LLM-jp Corpus v4 の選定と混合" | |
| icon: "database" | |
| color: "context" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1, 1] | |
| columns: | |
| - | |
| - type: markdown | |
| text: | | |
| 学習データには、大規模言語モデルの事前学習用コーパスである **LLM-jp Corpus v4** の日本語サブコーパスを利用しました。 | |
| 音声文字起こしに近い話し言葉や、多様な表記スタイルをカバーするため、独自の比率でデータを混合しています。 | |
| - **ja_fineweb-2 (40%)**: Webサイト由来のカジュアルな表現や多様な言い回し。 | |
| - **ja_wiki (Wikipedia; 30%)**: 正確な固有名詞、論理的で端正な表記構造。 | |
| - **ja_kokkai_giji (国会会議録; 20%)**: 口語でのやり取り、発話特有の長文パターン。 | |
| - **ja_aozorabunko (青空文庫; 10%)**: 文末表現や丁寧な句読点の打ち方。 | |
| - | |
| - type: diagram | |
| format: "vega_lite" | |
| width: 100 | |
| body: | |
| mark: | |
| type: arc | |
| innerRadius: 45 | |
| data: | |
| values: | |
| - corpus: "ja_fineweb-2 (Web)" | |
| ratio: 40 | |
| - corpus: "ja_wiki (百科事典)" | |
| ratio: 30 | |
| - corpus: "ja_kokkai_giji (発話)" | |
| ratio: 20 | |
| - corpus: "ja_aozorabunko (文学)" | |
| ratio: 10 | |
| encoding: | |
| theta: | |
| field: "ratio" | |
| type: "quantitative" | |
| color: | |
| field: "corpus" | |
| type: "nominal" | |
| tooltip: | |
| - field: "corpus" | |
| type: "nominal" | |
| - field: "ratio" | |
| type: "quantitative" | |
| view: | |
| stroke: null | |
| caption: "学習時における日本語サブコーパスの混合比" | |
| - type: card | |
| title: "6. クラス不均衡問題とエポック毎の学習推移" | |
| icon: "trending-up" | |
| color: "important" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1.2, 1] | |
| columns: | |
| - | |
| - type: diagram | |
| format: "vega_lite" | |
| width: 100 | |
| body: | |
| data: | |
| values: | |
| - epoch: 1 | |
| metric: "読点F1" | |
| value: 0.5096 | |
| - epoch: 2 | |
| metric: "読点F1" | |
| value: 0.5481 | |
| - epoch: 3 | |
| metric: "読点F1" | |
| value: 0.5803 | |
| - epoch: 4 | |
| metric: "読点F1" | |
| value: 0.5981 | |
| - epoch: 5 | |
| metric: "読点F1" | |
| value: 0.6117 | |
| - epoch: 1 | |
| metric: "句点F1" | |
| value: 0.6356 | |
| - epoch: 2 | |
| metric: "句点F1" | |
| value: 0.6923 | |
| - epoch: 3 | |
| metric: "句点F1" | |
| value: 0.7286 | |
| - epoch: 4 | |
| metric: "句点F1" | |
| value: 0.7436 | |
| - epoch: 5 | |
| metric: "句点F1" | |
| value: 0.7602 | |
| - epoch: 1 | |
| metric: "改行F1" | |
| value: 0.4579 | |
| - epoch: 2 | |
| metric: "改行F1" | |
| value: 0.5227 | |
| - epoch: 3 | |
| metric: "改行F1" | |
| value: 0.5294 | |
| - epoch: 4 | |
| metric: "改行F1" | |
| value: 0.5588 | |
| - epoch: 5 | |
| metric: "改行F1" | |
| value: 0.5668 | |
| mark: | |
| type: line | |
| point: true | |
| encoding: | |
| x: | |
| field: "epoch" | |
| type: "ordinal" | |
| y: | |
| field: "value" | |
| type: "quantitative" | |
| scale: | |
| domain: [0.4, 0.8] | |
| color: | |
| field: "metric" | |
| type: "nominal" | |
| width: 340 | |
| height: 200 | |
| caption: "エポックごとの各指標(F1値)の推移" | |
| - | |
| - type: markdown | |
| text: | | |
| **クラス不均衡(少数ラベル)への対策** | |
| 文字境界の9割以上は「記号なし(NONE)」です。「、」や「。」は極めてまれにしか出現しないため、単純に学習させるとモデルがすべてNONEと予測するようになってしまいます。 | |
| これを防ぐため、クロスエントロピー損失関数において、句読点と改行の正例クラスに重みを付けてインバランスを解消しました。 | |
| **指標の分析**: | |
| - **句点 F1 (0.7602)**: 文末は「〜です」「〜ます」などの定型パターンが多く学習しやすいため、高スコアを獲得。 | |
| - **読点 F1 (0.6117)**: 挿入位置に個人差や文体の依存度が高く、最も難度の高いタスクとなりました。 | |
| - **改行 F1 (0.5668)**: 文脈や発話リズムに依存するため、安定化に時間を要しました。 | |
| - type: card | |
| title: "7. ONNXフォーマットへのエクスポート" | |
| icon: "package" | |
| color: "procedure" | |
| body: | | |
| PythonやPyTorchによる開発環境は重厚であり、そのままブラウザ上で動かすことはできません。そこで、学習済みのモデルを共通モデル形式である **ONNX (Open Neural Network Exchange; opset 17)** へ書き出しました。 | |
| - **静的グラフ化**: 実行モデルを数式演算の静的なつながり(計算グラフ)としてエクスポートすることで、Pythonのコード実行系に依存せず、V8などのJavaScript実行エンジンから呼び出せるようにしました。 | |
| - **推論用マルチ出力ラッパー**: 句読点用と改行用の2つのヘッドの演算を一度に並列実行して結果を返すよう、ONNXモデルの出力を一つの実行ユニットにラッピングしました。 | |
| - **前処理メタデータの分離**: 文字とIDをひも付ける語彙テーブル、しきい値やスライディングウィンドウサイズなどの推論パラメータを「メタデータJSON」として別途切り出しました。これにより、Python側とブラウザ側の前処理の整合性を完全に維持しています。 | |
| - type: card | |
| title: "8. INT8動的量子化によるモデル容量と実行速度の最適化" | |
| icon: "scale" | |
| color: "supplement" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1.2, 1] | |
| columns: | |
| - | |
| - type: markdown | |
| text: | | |
| Webアプリケーションでモデルを配信するためには、ファイルサイズの軽量化と、CPU上での実行性能の向上が不可欠です。本プロジェクトでは **動的量子化 (Dynamic Quantization)** を適用しました。 | |
| $$q = \text{round}\left(\frac{r}{S}\right) + Z$$ | |
| - **値の変換**: 浮動小数点数(FP32、32ビット)で表現されている重みを、情報をある程度保ちながら8ビット整数(INT8)へ丸め込みました。 | |
| - **サイズの圧縮**: 学習済みのPyTorchチェックポイント(119 MiB)から、ONNX変換と量子化により **10 MiB** へと約12分の1に圧縮。一般的なブラウザでも高速ダウンロード可能なサイズに収めました。 | |
| - **演算の高速化**: INT8への量子化により、一般的なCPUに備わっている整数並列演算命令(WASM SIMDなど)を活用した、より単純で高速な行列計算が実行可能になりました。 | |
| - | |
| - type: diagram | |
| format: "vega_lite" | |
| width: 100 | |
| body: | |
| data: | |
| values: | |
| - stage: "Checkpoint" | |
| size: 119 | |
| - stage: "FP32 ONNX" | |
| size: 33.8 | |
| - stage: "INT8 ONNX" | |
| size: 10 | |
| mark: "bar" | |
| encoding: | |
| x: | |
| field: "stage" | |
| type: "nominal" | |
| sort: null | |
| axis: | |
| title: "開発フェーズ" | |
| labelAngle: 0 | |
| y: | |
| field: "size" | |
| type: "quantitative" | |
| axis: | |
| title: "ファイルサイズ (MiB)" | |
| tooltip: | |
| - field: "stage" | |
| type: "nominal" | |
| - field: "size" | |
| type: "quantitative" | |
| width: 250 | |
| height: 140 | |
| caption: "各開発フェーズにおけるファイルサイズの圧縮推移" | |
| - type: card | |
| title: "9. フロントエンドでの長文処理:スライディングウィンドウ" | |
| icon: "monitor" | |
| color: "procedure" | |
| body: | |
| - type: layout | |
| variant: "side_by_side" | |
| size: [1.2, 1] | |
| columns: | |
| - | |
| - type: markdown | |
| text: | | |
| 開発したTransformerモデルが一度に処理できるテキスト長は最大192文字です。しかし実業務での議事録や書き起こしテキストは数千文字以上に及びます。 | |
| これを処理するため、**スライディングウィンドウ方式**を実装しました。 | |
| - **境界情報の連続性の維持**: | |
| 長文を単に192文字で細切れにすると、分割した端の文字では前後の文脈が不足し、句読点予測のブレや誤判定が多発します。 | |
| そこで、隣り合うウィンドウ同士を**75%(144文字)重複(ストライド)**させながら切り出して推論を実行します。 | |
| - **予測結果の重み付き平均**: | |
| 同じ文字に対して複数回の予測結果(重なり部分)が生じるため、これらを単純平均するのではなく、各ウィンドウの**「中心に近い予測ほど信頼し、端に近い予測は重みを小さくする」**加重平均処理(Center Weighting)を適用してマージします。 | |
| $$\text{logit}_{\text{final}}(x) = \frac{\sum_w W_w(x) \cdot \text{logit}_w(x)}{\sum_w W_w(x)}$$ | |
| この工夫により、ウィンドウの継ぎ目における予測の不連続性を解消しました。 | |
| - | |
| - type: diagram | |
| format: "mermaid" | |
| width: 100 | |
| body: | | |
| flowchart TD | |
| A["入力テキスト"] --> B["前処理: 記号・空白除去"] | |
| B --> C["192文字の重複ウィンドウに分割"] | |
| C --> D["WASM上のONNX Runtime推論"] | |
| D --> E["Center Weighting加重平均統合"] | |
| E --> F["判定(マージン 0.75 / 閾値 0.40)"] | |
| F --> G["結果のテキスト結合出力"] | |
| style A fill:#fdf2f8,stroke:#db2777,stroke-width:1px,color:#9d174d | |
| style G fill:#fdf2f8,stroke:#db2777,stroke-width:1px,color:#9d174d | |
| style B fill:#f0fdf4,stroke:#16a34a,stroke-width:1px,color:#15803d | |
| style C fill:#f0fdf4,stroke:#16a34a,stroke-width:1px,color:#15803d | |
| style E fill:#f0fdf4,stroke:#16a34a,stroke-width:1px,color:#15803d | |
| style F fill:#f0fdf4,stroke:#16a34a,stroke-width:1px,color:#15803d | |
| style D fill:#eff6ff,stroke:#2563eb,stroke-width:2px,color:#1d4ed8 | |
| caption: "スライディングウィンドウと統合推論の全体処理フロー" | |
| - type: card | |
| title: "10. まとめと得られた知見" | |
| icon: "wrench" | |
| color: "note" | |
| body: | |
| - type: list | |
| title: "本プロジェクトから得られた知見" | |
| variant: "definitions" | |
| items: | |
| - term: "タスク再定義による軽量化" | |
| body: "「文章の再生成」ではなく「文字境界への挿入判定(分類タスク)」に特化させたため、10 MiBの超軽量・高性能なエッジ推論が実現しました。" | |
| - term: "ローカル実行とプライバシー" | |
| body: "サーバー通信が一切不要なクライアント完結型にしたことで、機密文書や会議録を完全に保護できる実用的なセキュリティ体制が担保されました。" | |
| - term: "一貫した前処理設計" | |
| body: "前処理ルールやしきい値をメタデータとしてモデルと分離して管理する手法は、学習時と推論環境の不一致を防ぐ上で有効に機能しました。" |
45deg
commented
Jul 2, 2026
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment