誤検出を0にしても採用しない NER改善100回超の勝ち筋

GitHub技術語の誤検出を0にしつつ既存性能も上げるまで、100回超のNER改善で得た難しい負例、局所損失、蒸留、残差修復、種類別スコア補正の実務上の要点を紹介します。

誤検出を0にしても採用しない NER改善100回超の勝ち筋

こんにちは、Qualiteg研究部です。

私たちは PII-FI という、個人識別情報や機密情報の検出とマスキングを行うツールを開発・提供しています。

あるとき、いくつかのGitHubのリポジトリをPII検査したところ、製品名や識別子に含まれる短い技術語を、組織名や社内用語として拾う誤検出が見つかりました。

数件の除外語を足せば、その画面だけは直せます。

しかし未知のリポジトリでは、別の技術語、issue番号、バージョン番号、識別子の断片へ誤検出先が移ります。

PII-FIのパイプライン処理の一部でNER(固有表現認識)を使用しているのですが、今回はNERの継続学習に関する技術記事となります。

当社では、これまで数年間をかけて構築した大規模コーパスを使い学習した強力なNERを扱っていますが、これを便宜上「強NER」と呼ぶことにして、本記事を執筆してまいりたいとおもいます。

さて、冒頭ご説明した誤検出について、私たちは、辞書による例外処理ではなく、強NERそのものを継続学習で直しました。

今回正式採用までに作成したNERモデルは100を超えます。目的の誤検出を0にできた候補は途中にもありましたが、既存文書の再現率や別の固有表現の種類を少しでも悪化させた候補は採用しませんでした。

本記事では、こうした ポロポロ とでてくる誤検出にたいして、学習済モデルをどのように進化させていくか、という点について私たちの実験で得られた知見を共有させていただければとおもいます。

結論から言うと、勝ち筋は「大きな学習を一度当てる」ことではありませんでした。
現行モデル自身から難しい負例(ハードネガティブ)を採掘し、未知表現の多様性と既存モデルが失いやすい弱い正例境界を別々に守り、残った数件だけを最小限の学習で修復し、最後に種類別の未正規化スコア(ロジット)を必要な幅だけ補正する。
この順序が、誤検出をなくしながら既存性能も上げる交点を作りました。

この記事では、モデル名、顧客データ、内部の学習文書そのものは公開せず、再利用できる学習設計、数式、失敗の読み方、採用ゲートを紹介します。

1. 最初に「合格」を数式で固定する

改善を始める前に、採用条件を固定しました。

全体F1が上がればよい、ではありません

固有表現の種類を \(c\)、現行モデルを \(\mathrm{base}\)、新候補を \(\mathrm{new}\) とすると、製品評価では少なくとも次を満たすことを条件にしました。

$$\begin{aligned} \mathrm{TP}_{\mathrm{new}}(c) &\ge \mathrm{TP}_{\mathrm{base}}(c) \\ \mathrm{FP}_{\mathrm{new}}(c) &\le \mathrm{FP}_{\mathrm{base}}(c) \\ \mathrm{FN}_{\mathrm{new}}(c) &\le \mathrm{FN}_{\mathrm{base}}(c) \qquad \forall c \end{aligned}$$

ひとつの型でも正解を失えば不採用です。

目的の技術語で誤検出が0でも、別分野の人名を1件落とした候補は不採用にしました。この基準を後から緩めないことが重要です。

候補を長く追い込むほど、「ここまで良くなったのだから採用したい」という心理が働くからです。
(とくに10回、20回と学習を重ねると、なんかの値が多少さがっていても、他の値が圧倒的にのびていれば、「もーこれでいいじゃん」という悪魔のささやきに完全にまけそうになります)

適合率(Precision)、再現率(Recall)、F1も確認します。

$$\begin{aligned} P &= \frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FP}} \\ R &= \frac{\mathrm{TP}}{\mathrm{TP}+\mathrm{FN}} \\ F_1 &= \frac{2PR}{P+R} \end{aligned}$$

ただし、これらの集計値は最後の要約です。採否を決めるのは、種類別のTP、FP、FNと、実際に増減した検出範囲です。

図1 誤検出の発見から正式採用までの改善ループ

誤検出の発見から難しい負例の採掘、正例境界の保護、局所学習と評価、種類別補正を経て正式採用する流れ
失敗差分を次の小実験へ変え、変更範囲を徐々に狭めます。

2. データを4区画に分け、見た評価は検証用と呼ぶ

最初に、実PIIを含まない合成技術文を用意しました。重要なのは件数より分離方法です。

区画用途運用規則
学習用(train)勾配更新と難しい負例の採掘学習へ直接使う
開発用(dev)候補の途中比較保存時点、学習率、損失の重みを選ぶ
追加検証用未知表現の追加確認本文は学習へ戻さないが、結果は候補選択へ使う
固定回帰用広い非劣化確認初回は未開封。開いた後は最終テストと呼ばず、同じ内容で回帰を追う

重複を避ける対象は文書全文だけではありません。語彙、識別子の構成部品、接頭辞・接尾辞、文型テンプレートも分離しました。たとえば同じ語を別のテンプレートへ入れただけでは、語を暗記したモデルでも合格してしまいます。逆に単語を変えても識別子の部品や文型が同じなら、未知性を過大評価します。

各集合は件数とハッシュを固定しました。再生成のたびに内容が変わる評価では、候補間の差を学習効果として説明できません。

ここで大切なのは呼び方です。

追加検証用の結果を見て学習条件を変えた時点で、その集合は独立した最終テストではなく検証用です。固定回帰用も、最初の有望候補で開くまでは未開封でしたが、その後は複数候補の非劣化確認へ再利用しました。したがって、この記事では「最後まで未使用のテストで性能を保証した」とは書きません。100回超の適応的な探索で見た集合は、正直に開発・回帰用として扱います。

3. 選んだ単語ではなく、モデル自身の誤検出を採掘する

最初は、誤検出した技術語を負例として教えました。しかし、その単語を抑えると、同じ文のissue番号やバージョン番号、隣接する短い断片へ誤検出が移りました。

そこで、現行モデルを学習側に固定した合成技術文へ通し、対象の定義上は固有表現でないと生成規則または注釈で確認できた位置のうち、モデルが実際に固有表現と判定した検出範囲を採掘しました。開発用、追加検証用、固定回帰用の本文は採掘へ使っていません。難しい負例は、人が「難しそう」と選んだ語ではなく、現在の判定境界が実際に負けた位置です。

対象種類の未正規化スコアを \(z_e\)、固有表現ではないOのスコアを \(z_O\)、必要な余裕幅を \(m\) とすると、負例位置では次のような二者間のマージン損失を使えます。

$$\mathcal{L}_{\mathrm{neg}}=\max\!\left(0,\,m+z_e-z_O\right)$$

すでに \(z_O\) が十分高い負例には勾配を掛けず、誤検出している境界だけを必要な幅まで押し戻します。文書全体をOとして学習するより、変更範囲を狭くできます。

4. GitHub文書を丸ごとOにすると、正しい固有表現まで壊れる

大きな失敗のひとつは、技術文書の未注釈単位(token)をすべてOとして学習したことでした。学習内のスコアは高くなり、未知技術語の誤検出も大幅に減りました。しかし、別分野の実文書では、本来拾うべき社内固有語の再現率が落ちました。

理由はtoken数の偏りです。ひとつの文書には少数の訂正対象と大量の背景tokenがあります。背景をすべてOとして交差エントロピー損失へ入れると、今回直す必要のない位置までO方向へ押します。本件は新しい固有表現クラスを順に増やすclass-incremental NERそのものではありません。ただし、未注釈の固有表現までOとして学習すると既存の識別能力を壊し得る、という構造は継続NERの既往研究と共通しています。

訂正対象tokenの集合を \(M\) とすると、訂正箇所に限定した交差エントロピー損失は次のように書けます。

$$\mathcal{L}_{\mathrm{focused}} =-\frac{1}{|M|}\sum_{t\in M}\log p_{\theta}(y_t\mid x)$$

\(M\) 以外は単純なO正解として扱いません。損失計算から除外するか、旧モデルを教師モデルとして、その出力を保護します。

主要な失敗系統を比較する

試した方向良くなった点不採用理由次に残った知見
選んだ技術語だけを負例化対象語の信頼度低下番号や隣接断片へ誤検出が移動モデル自身の全誤検出範囲を採掘する
技術文書を全文Oで学習未知技術語のFP低下既存固有表現の再現率低下訂正位置だけへ損失を掛ける
対象種類の分類行だけ更新出力方向を限定文脈差を分離できないエンコーダー側の小さな更新が必要
LoRAと疑似ラベル目的集合のFPが0異分野の適合率と再現率が低下訂正箇所以外のスコアも教師モデルで守る
訂正箇所限定損失と蒸留目的集合と未知語を両立既存製品の弱い正例範囲を少数損失弱い境界の可視窓を正例側へ加える
残差の局所修復目的FP 0、既存TP回復別分野の適合率がわずかに未達誤差が集中した種類だけを最小幅で補正する

5. 正例は「強くする」のではなく、2種類に分けて守る

負例だけを学習すると、本物の固有表現まで弱くなります。では正例を大量に反復すればよいかというと、これも失敗しました。正例の交差エントロピー損失を強くすると、未知の一般語まで固有表現側へ寄り、適合率が下がりました。

最終的に必要だった正例は、役割の違う2種類でした。

正例群守るもの不足した場合
多様な合成正例未知表現への一般化固定評価用データの正例を落とす
弱い境界の可視窓現行製品で辛うじて取れている検出範囲既存TPを少数失う

多様な合成正例だけでは、既存製品にある弱い境界を守れません。弱い境界だけでは、その文脈を覚えて未知表現へ広がりません。この2軸を同じ学習単位(バッチ)で負例と競合させたとき、未知正例と既存TPを同時に保てる候補が初めて現れました。

図2 負例修正と3種類の保護を釣り合わせる

難しい負例の修正と、正例保護、旧モデルの蒸留、既存データ保持を釣り合わせる学習設計
負例を直しながら、正例、訂正箇所以外の出力、既存分野の能力を別々に守ります。

6. 旧モデルは正解ラベルではなく、動かさない基準として使う

既存能力を守るため、現行モデルを教師モデルとして使いました。訂正対象以外のtoken集合を \(U\)、教師モデルと生徒モデルの未正規化スコアをそれぞれ \(z_t^T\)、\(z_t^S\) とすると、文脈安定化損失は次の形です。

$$\mathcal{L}_{\mathrm{KD}} =\frac{1}{|U|}\sum_{t\in U}\left\lVert z_t^S-z_t^T\right\rVert_2^2$$

または温度付きソフトマックスに対するKLダイバージェンスを使えます。重要なのは、分類器の既存行を凍結しただけでは既存種類を守れないことです。

LoRAでエンコーダーが変われば、固定した分類器へ入る表現も変わり、最終的な未正規化スコアは動きます。分類器の凍結と出力蒸留は別の保護です。

今回の全体損失は、概念的には次の組み合わせになりました。

$$\mathcal{L} =\mathcal{L}_{\mathrm{neg}} +\alpha\mathcal{L}_{\mathrm{pos}} +\beta\mathcal{L}_{\mathrm{KD}} +\gamma\mathcal{L}_{\mathrm{preserve}}$$

\(\mathcal{L}_{\mathrm{neg}}\)は誤検出境界の修正、\(\mathcal{L}_{\mathrm{pos}}\)は多様な合成正例と弱い正例境界に対する教師あり損失、\(\mathcal{L}_{\mathrm{KD}}\)は訂正箇所以外の出力維持、\(\mathcal{L}_{\mathrm{preserve}}\)は既存分野から固定したラベル付き保持データに対する教師あり損失です。

係数は大きいほど安全なのではありません。正例保護が強すぎれば誤検出が戻り、負例修正が強すぎれば再現率が落ちます。各損失値の桁は目安として確認し、最終的には開発用データのFP、FN、検出範囲の差分を見て、一方の目的だけが改善する設定を除外しました。

7. 一度に完成させず、残差だけを局所修復する

広い学習で誤検出を数件まで減らした後は、同じ規模の学習を繰り返しませんでした。

残った誤検出範囲と、新候補が失った正解範囲を差分で特定し、そのトークナイザーから見える文脈窓だけを使って小さく修復しました。

修復へ戻した本文は、学習用データと正式な学習源から再生成した開発用回帰窓に限定しました。固定回帰用データで見つかった本文を、そのまま学習へ戻してはいません。

ここでは「差し引きで改善している」だけでは足りません。種類ごとに改善と悪化を分けます。ある種類でTPが差し引き同数でも、別の正解を1件失い、新しい正解を1件得ている可能性があります。失った検出範囲を特定できれば、その1件だけを正例窓として残差負例と同時に学習できます。

学習率、学習ステップ数、正例損失の重みは小刻みに比較しました。残る誤検出の信頼度と検出範囲の長さも追います。誤検出が2文字から1文字へ縮み、Oとの余裕幅が0へ近づいているなら、改善方向へ動いている兆候とみなし、追加の小さな学習を試す材料にしました。逆に件数が同じでも信頼度が上がっていれば、その系統は打ち切れます。

最終候補へ近づいた局面では、数回の学習ステップの差、正例重みの小さな差が、未知正例の維持と最後の誤検出0を分けました。

100回超の探索で得たのは、大規模なハイパーパラメータ探索ではなく、
失敗差分を次の小実験へ変換する方法
です。

8. 評価は軽い門から重い門へ進める

全候補へ製品全体の評価を実行すると、時間を浪費します。

前の門を通った候補だけ次へ進める漏斗にしました。

  1. 目的の誤検出集合
  2. 学習と分離した追加検証用の未知正例
  3. 基本的な技術負例
  4. 大規模な負例耐性試験
  5. 開封後は再利用する固定回帰用データ
  6. 製品文書の型別TP・FP・FN
  7. 異分野の適合率・再現率
  8. 実際のランタイム契約
  9. 正式基準一式

目的集合で1件でも残る候補を重い評価へ進めません。

未知正例が基準未達なら負例耐性試験を回しません。製品評価では全体F1だけでなく種類別の検出範囲の差分まで保存します。ここに並べた門は、いずれも最終候補を選ぶための開発・回帰用です。不合格理由を次の学習データまたは損失設計へ戻したため、独立した最終テストとは区別します。

図3 候補を正式採用まで絞り込む評価漏斗

目的誤検出から追加検証、負例耐性、固定回帰、製品全種類、異分野、ランタイム契約、正式基準へ進む評価漏斗
軽い門を通った候補だけを重い製品評価へ進めます。すべて候補選択に使う開発・回帰用の門です。

9. 最後は全種類一律ではなく、種類別スコアを最小幅だけ補正する

学習後の有望候補は、目的の技術誤検出と製品評価を通りましたが、別分野の適合率が合格線へわずかに届きませんでした。

Oの未正規化スコアを全種類に対して一律に上げると適合率は改善します。しかし、本物の未知固有表現までまとめて消えました。

そこで、誤検出が集中した2種類だけへ負方向のバイアスを加え、該当種類の未正規化スコアをわずかに下げました。

$$z'_c=z_c+b_c,\qquad b_c<0$$

これは特定単語の除外語リストではありません。同じ種類の全候補に作用します。

また、確率と正答率を合わせる通常の意味での「校正」ではなく、種類別のロジット補正です。補正幅は開発用の異分野評価で比較し、合格線を越える最小値を採用しました。その後、未知正例、固定回帰、製品の全種類、正式基準をすべて再実行しました。それ以上強い補正は、適合率が上がっても本物を余計に落とすため捨てました。

10. 最終結果と、成功の決め手

正式候補は、固定したGitHub技術語評価、追加検証用の未知正例、基本負例、大規模な負例耐性試験、固定回帰用データを通過しました。

製品評価でも、旧モデルに対して正解が増え、同一の固定製品回帰データ上で誤検出が約400件減り、見逃しも減りました。

主要な固有表現の種類を含め、種類別TP・FP・FNの悪化は0でした。

ここでいう誤検出0は、固定した評価集合上の実測値であり、あらゆる未知リポジトリで誤検出が起きないという意味ではありません。

観点旧モデルとの比較
固定したGitHub技術語の誤検出0まで削減
製品評価のTP増加
製品評価のFP約400件減少
製品評価のFN減少
型別非劣化全型で合格
未知正例・固定回帰合格

成功をひとつの手法へ要約するなら、LoRAでも蒸留でもマージン損失でもありません。

次の組み合わせです。

  1. モデル自身の誤検出を難しい負例にする
  2. 未注釈tokenを安易にOへ落とさない
  3. 未知表現の多様性と、既存の弱い正例境界を別々に守る
  4. 旧モデルを教師モデルとして訂正箇所以外の出力を固定する
  5. 残差だけを最小限の学習で修復する
  6. 最後は誤差が集中した種類だけを最小幅で補正する
  7. 全体F1ではなく全型のTP・FP・FNで採用する

今回、強NERの主要な改善は100回超の学習を経て実現し、新しいモデル成果物として正式採用しました。

まとめ

最後に、学習を成功させるための重要な論点を質問形式でまとめて書いておきます

  • 学習用、開発用、追加検証用、固定回帰用の役割を分けたか
  • 語彙だけでなく識別子部品と文型も分離したか
  • モデル自身の誤検出範囲を採掘したか
  • 未注釈tokenを全部Oとして学習していないか
  • 正例の多様性と弱い境界を両方保護したか
  • 分類器を凍結しただけで旧型を守ったつもりになっていないか
  • 候補ごとの改善と悪化を検出範囲単位で保存したか
  • 軽い門を通った候補だけ重い製品評価へ進めたか
  • 全体F1の改善で型別劣化を隠していないか
  • 種類別スコア補正は合格線を越える最小幅で止めたか

NERの継続学習では、目的データのスコアを上げることより、どの能力を動かさないかを測れることのほうが難しいものです。

100回超の失敗を候補名の列で終わらせず、誤検出の移動、信頼度の変化、失った検出範囲、種類別の改善と悪化を次の小実験へ変換する。

そこまで設計して、初めて改善作業が再現可能になるということが、実体験を経て理解できました。

以前の記事「モデルを「壊さずに」ドメインを広げる XLM-RoBERTa継続学習の設計ノート」では、追加学習で動かす範囲を限定する考え方を紹介しました。

今回は、その限定学習でも一度では合格できなかったとき、データと評価をどう組み直したかという続編です。

それでは、また次回お会いしましょう!


出典・参考

関連記事

Read more

TensorRT 10 × Blackwell 移行ガイド【後編】5D grid_sample をカスタムプラグインで通す

TensorRT 10 × Blackwell 移行ガイド【後編】5D grid_sample をカスタムプラグインで通す

こんにちは! 「TensorRT 10 × Blackwell 移行ガイド」シリーズもいよいよ最終回です。前編では TensorRT 化の基本と「5D(volumetric)の grid_sample はビルドが拒否される」という壁を、中編では「ビルドが通っても正しいとは限らない」沈黙劣化の数々を扱いました。 回テーマ 前編(Part 1)RTX 50 で推論資産が動かない — 基本と最初の壁 中編(Part 2)ビルドが通っても正しいとは限らない — 沈黙劣化 5 連発 後編(Part 3)5D grid_sample をカスタムプラグインで通す(本記事) 後編は、前編で保留にした最大の宿題に挑みます。TensorRT がネイティブに対応していない 5D grid_sample を、

By Qualiteg プロダクト開発部
Luckfox Pico Mを外出先から使う。3,750円・64MBのLinuxボードにWireCanalで公開URLをつけるまで

Luckfox Pico Mを外出先から使う。3,750円・64MBのLinuxボードにWireCanalで公開URLをつけるまで

秋葉原で買った3,750円のLinuxボード Luckfox Pico M(RV1103・64MB・USB-Cのみ)を、Web公開用の受信ポートを追加せずにインターネット側から使えるようにするまで。SDカード書き込み、RNDIS経由のネットワーク、WireCanal無料プランでの公開、busybox initでの常駐とブラウザからのLED操作、再起動復帰(33秒)まで実測手順で解説します。

By Qualiteg プロダクト開発部
Raspberry Pi 5のWebサーバーをインターネットに公開する。ポートは開けない、WireCanalのトンネルで

Raspberry Pi 5のWebサーバーをインターネットに公開する。ポートは開けない、WireCanalのトンネルで

受信ポートを閉じたまま、Raspberry Pi 5のWebサーバーをインターネットに公開します。WireCanalのAgentをワンライナーで入れ、canalを作り、systemdで常駐化。ポート開放もルーター設定も不要で、操作はおよそ5分。再起動後の自動復活まで実測で検収しました。

By Qualiteg プロダクト開発部
Raspberry Pi 5を堅牢化する。SSH鍵認証・UFW・fail2banと、再起動でIPv6が復活する罠

Raspberry Pi 5を堅牢化する。SSH鍵認証・UFW・fail2banと、再起動でIPv6が復活する罠

セットアップ直後のRaspberry Pi 5は、パスワード認証が有効なまま、ファイアウォールなし、更新可能なパッケージ172件と無防備です。SSHの鍵認証化・UFW・fail2ban・自動更新・IPv6無効化を実コマンドと実測検収で解説します。sysctlのIPv6無効化が再起動で復活する現象と恒久対策も実機で確認しました。

By Qualiteg プロダクト開発部