暇さえあればアルゴリズムいじり

暇があればアルゴリズムいじり

Obsessed with algorithms whenever I have a free moment.

AI & IT Engineer / Father of 3

"Dream shall be realized with dream — Always tinkering with algorithms"

RAGで固有名詞を扱う方法について調査(2)

 

先日話をしたRAGで固有名詞の対応を出来るようにするという話について、続編を本日扱っていこうと思います。

前回扱ったことは文章を入力してから自動で固有名詞を抽出する方法についてでした。 今回は抽出した文章から固有名詞の意味を橋渡しするより良い表現にするにはどうべきかということについて調査・検討していきます。

従来技術

組織固有の固有名詞(社内用語・略語・プロジェクト名など)をLLMに理解させて、精度の高いRAGを実現する工夫について調べてみました。 いくつかの研究・解説文献が存在します。

見ていると 5つのアプローチ に整理できます。以下、代表的な文献とともに解説します。

1. 用語辞書・ジャーゴン辞書でクエリを拡張する(Query Augmentation)

最も直接的なアプローチは、組織固有の用語を辞書化し、検索前にクエリを「意味を補って」拡張する方法です。

代表的研究:Golden-Retriever

  • 論文: Golden-Retriever: High-Fidelity Agentic Retrieval Augmented Generation for Industrial Knowledge Base(arXiv:2408.00798、Western Digital)
  • 内容:
    検索の前に「リフレクションベースの質問拡張」ステップを導入し、ジャーゴン(専門用語)を特定し、文脈に基づいてその意味を明確化し、質問を拡張する手法です。
  • 具体的には、入力質問中のすべてのジャーゴンや略語を抽出・列挙し、事前定義したリストと照合して文脈を判定し、ジャーゴン辞書に問い合わせて拡張された定義や説明を取得することで、曖昧さを解消して検索精度を大きく向上させています。

関連研究:現場用語のアライメント

  • 論文: Shopfloor Terminology for Retrieval-Augmented Generation (RAG)(AHFE)
  • 内容:
    作業者が使う口語的・現場固有の用語が、技術文書の標準用語と異なることで検索失敗が起きる問題に対処し、キュレーション(※1)した同義語で拡張したクエリと通常のクエリを比較した研究です。
  • 結果として、用語で拡張したクエリは平均67%の正答率を達成したのに対し、拡張しないクエリはわずか11%だったと報告しており、用語アライメントの重要性を定量的に示しています。

※1 キュレーション: 情報を集めて、選別し、整理し、使いやすい形に整えることです。 RAGや社内ナレッジの文脈では、単にデータを大量に集めるだけではなく、「どの情報を使うべきか」「どう整理すべきか」「どれを正しい情報として扱うか」を人間や仕組みで整備することを指します。

効果あることは分かってるのですが、自動でより良い文章にするという主眼なので。 但し、効果を数値で示してくれたことはありがたいです。

関連研究:略語(頭字語)ギャップの解消

  • リポジトリ/論文: Ontology-Guided RAG Augmentation("Closing the acronym gap")
  • 内容:
    コーパス規模の略語オントロジーを多段階の抽出・曖昧性解消で構築し、クエリ時(QTA)と取り込み時(ITE)の2箇所で検索を拡張する手法です。
  • 略語クエリでRecall@10が最大102%改善するという大きな効果が報告されています。

2. 埋め込みモデルを組織固有の言語でファインチューニングする

汎用の埋め込みモデルは、組織固有の用語を理解できないため、自社データで埋め込みモデルをファインチューニングするアプローチです。

実務解説:Glean のケース

  • 記事: Fine-Tuning Embedding Models for Enterprise RAG: Lessons from Glean
  • 内容:
    どの組織も独自の用語、略語、プロジェクト名、言語パターンを発展させており、汎用の埋め込みモデルは特別な学習なしにはそれらを理解できないという課題を指摘しています。

研究:金融ドメインの埋め込み(BAM embeddings)

  • 論文: Greenback Bears and Fiscal Hawks: Finance is a Jungle and Text Embeddings Must Adapt(EMNLP 2024 Industry)
  • 内容:
    1,430万件のクエリ-パッセージのペアでファインチューニングした金融特化の埋め込みBAMは、テスト集合でRecall@1が62.8%を達成し、OpenAIの最良の汎用埋め込みの39.2%を上回ったと報告しています。

検索精度を向上させていい文章と引き合わせるという方法です。 LLMだけの底上げではないということでノミネートですが、検索がボトムネックの場合に取りえる方法です。

3. オントロジー・ナレッジグラフに固有名詞を「意味」として構造化する(GraphRAG系

固有名詞を単なる文字列ではなく、エンティティ+関係性としてナレッジグラフやオントロジー(※2)に構造化することで、意味を理解させるアプローチです。

研究:OG-RAG(オントロジーグラウンディング)

  • 論文: OG-RAG: Ontology-Grounded Retrieval-Augmented Generation(EMNLP 2025 / Microsoft Research)
  • 内容:
    ドメイン固有のオントロジーに検索プロセスをアンカーすることでLLMの応答を強化する手法で、ドキュメントをハイパーグラフとして表現し、クエリに対して最小限のハイパーエッジ集合を取得するものです。
  • 評価では、正確な事実のrecallを55%、応答の正確性を40%向上させたと報告されています。

研究:GraphRAGのスキーマ設計の影響

  • 論文: GraphRAG on Technical Documents – Impact of Knowledge Graph Schema
  • 内容:
    ドメインに関連したナレッジグラフのスキーマが、専門用語が豊富な技術レポートに対するMicrosoft GraphRAGの結果に与える影響を検証した研究です。
  • ドメイン特化のシンプルなスキーマが他の選択肢より約10%多くエンティティを抽出し、最も事実的に正確でハルシネーションの少ない回答を生成したと示しています。

※2 オントロジー: オントロジーとは、簡単に言うと、ある分野で使う「概念・用語・関係性のルール」を整理した設計図です。 RAGやナレッジグラフの文脈では、「この固有名詞は何の種類なのか」「何と何がどう関係するのか」を、LLMや検索システムが扱いやすい形で定義するものです。 1. 直感的に言うと「社内用語の分類表+関係ルール」 たとえば、社内RAGで次のような言葉を扱いたいとします。

  • ネコプロジェクト
  • 営業企画部
  • 山田部長
  • 顧客対応AI
  • 2024年度ロードマップ 人間ならなんとなく、
  • 「ネコプロジェクト」はプロジェクト名
  • 「営業企画部」は部署名
  • 「山田部長」は人物
  • 「顧客対応AI」は製品・システム
  • 「2024年度ロードマップ」は文書 と分かります。 しかし、システムにはそれを明示してあげる必要があります。そこで、オントロジーでは例えばこう定義します。
エンティティの種類:
- Project
- Department
- Person
- System
- Document

関係の種類:
- Project は Department によって推進される
- Person は Department に所属する
- Person は Project の責任者である
- Document は Project について記述する
- System は Project によって開発される

これがオントロジーです。 2. ナレッジグラフとの違い よく混同されるのが、オントロジーナレッジグラフです。 ざっくり言うと、

用語 役割
オントロジー どんな種類のものがあり、どう関係できるかを決める「設計図」
ナレッジグラフ 実際のデータを、点と線で表したもの
例えるなら、  
  • オントロジー:データベースの設計書、ER図、分類ルール
  • ナレッジグラフ:その設計に従って入れた実データ です。

4. エンティティの名寄せ・曖昧性解消(Entity Resolution)

同じ組織・固有名詞が表記ゆれで複数に分裂すると検索が壊れるため、それらを一つのエンティティに統合するアプローチです。

解説:Entity Resolution in RAG Pipelines

  • 記事: Entity Resolution in RAG Pipelines(TypeGraph)
  • 内容:
    「JPMorgan Chase」「J.P. Morgan」「JPMC」「Chase」など、明らかに同じ組織を指す複数の異なるノードがグラフ内に存在してしまうという「エンティティ断片化」の問題を扱っています。
  • 対策として、表層形の類似度だけでなく、埋め込みベースの意味的類似度、文脈的共起、グラフの構造的特徴を組み合わせる必要があると述べています。

この事実は結構重要と感じます。 というのは呼び名の揺れがある場合に検索精度が低下することになる。 このため、事前に固有名詞を理解して統一する必要があるし、実際の検索時にも同一用語を検索時に統一された代表する用語に変換する必要があるということになります。

5. ドメイン特化のハイブリッド検索・ルーティング

組織固有の用語を含む質問に対し、用語で補強したクエリとハイブリッド検索(ベクトル+キーワード)を組み合わせるアプローチです。

研究:Telco-oRAG(通信ドメイン)

  • 論文: Telco-oRAG: Optimizing Retrieval-augmented Generation for Telecom Queries(arXiv:2505.11856、Huawei)
  • 内容:
    3GPPドメイン特化の検索とWeb検索を組み合わせたハイブリッド検索戦略に、用語集で補強したクエリ精緻化とニューラルルーターを組み込んだフレームワークです。
  • 3GPP関連の質問の正答率を最大17.6%向上させ、語彙クエリで10.6%の改善を達成したと報告しています。

まとめ:アプローチ別の整理

ここまで出てきた手段について整理します。

 

アプローチ 代表文献 特徴
① 用語辞書でクエリ拡張 Golden-Retriever, Shopfloor Terminology 検索前にジャーゴンの定義を注入。実装が比較的容易
② 埋め込みのファインチューニング Glean, BAM embeddings 自社言語を埋め込みモデルに学習。効果大だがデータ・コスト要
③ オントロジー/グラフ構造化 OG-RAG, GraphRAG schema 固有名詞を意味・関係として構造化。事実性・多段推論に強い
④ エンティティ名寄せ Entity Resolution (TypeGraph) 表記ゆれを統合し検索の断片化を防ぐ
⑤ ハイブリッド検索・ルーティング Telco-oRAG 用語補強+ベクトル/キーワード検索の併用

選定のポイント

  • 手軽に始めたい → ①の用語辞書によるクエリ拡張(Golden-Retriever方式)。再インデックス不要で効果も高い。
  • 検索精度を根本から上げたい → ②埋め込みのファインチューニング。
  • 固有名詞の「意味・関係」まで扱い、複雑な質問に答えたい → ③オントロジー/GraphRAG。
  • 表記ゆれが多い組織名を扱う → ④エンティティ名寄せ。

実務では、①(用語辞書)+④(名寄せ)を土台に、必要に応じて②や③を追加するのが現実的な組み合わせです。

ベストペアの考察

手段は色々あるということは分かりました。 RAGはシステムでしょう。 ですから、先述の手法から相性の良い手法を組み合わせて精度を追求するとどうなるかについても考察してみます。 結論から言うと、最も実装がお手軽で汎用的に効果が出やすく、かつ他の手法とも独立して掛け合わせやすいペアは、

① 用語辞書・ジャーゴン辞書によるクエリ拡張
×
④ エンティティの名寄せ・曖昧性解消

だと思います。

実装をしっかり頑張って、「複雑な関係性をたどる質問」まで含めて最高精度を狙う場合は、

③ オントロジー・ナレッジグラフ
×
④ エンティティの名寄せ・曖昧性解消

が最有力です。

整理するとこんな感じでしょうか。

目的 最も有力なペア
まずRAG全体の検索精度を安定して上げたい ① クエリ拡張 × ④ 名寄せ
組織内の複雑な関係性までたどりたい ③ GraphRAG × ④ 名寄せ
大量データ・高頻度検索で検索基盤を根本強化したい ② 埋め込みFT × ④ 名寄せ

1. ① クエリ拡張 × ④ 名寄せ

なぜこのペアが強いか

この2つは、RAGにおける別々の失敗原因を潰します。

① クエリ拡張が解決する問題

ユーザーがこう聞くとします。

「NPJの最新方針を教えて」

でも、社内文書にはこう書かれているかもしれません。

「ネコプロジェクトにおける2025年度の運用方針」

この場合、NPJネコプロジェクト が同じだと分からないと、検索に失敗します。

そこで①では、クエリをこう補強します。

元の質問:
NPJの最新方針を教えて

拡張後:
NPJ、正式名称: ネコプロジェクト。
ネコプロジェクトは顧客対応AI導入に関する社内プロジェクト。
このプロジェクトの最新方針を検索する。

つまり①は、ユーザーの質問側を賢くする方法です。

④ 名寄せが解決する問題

一方で、社内文書側には表記ゆれがあります。

NPJ
Neko PJ
ネコPJ
ネコプロジェクト
顧客対応AI導入PJ

これらが全部バラバラに扱われると、検索結果が分散します。

そこで④では、これらを同じエンティティとして統一します。

代表名: ネコプロジェクト
別名:
- NPJ
- Neko PJ
- ネコPJ
- 顧客対応AI導入PJ

つまり④は、データ側・エンティティ側をきれいにする方法です。

この2つを組み合わせると何が起きるか

①だけだと、クエリは改善されますが、文書側の表記ゆれが残ります。

④だけだと、文書側は整理されますが、ユーザーが略称や口語で聞いたときに取りこぼす可能性があります。

しかし、①と④を組み合わせると、

ユーザー質問:
NPJの最新方針を教えて

① クエリ拡張:
NPJ = ネコプロジェクト と補足

④ 名寄せ:
NPJ / ネコPJ / Neko PJ / ネコプロジェクト を同一エンティティとして扱う

検索:
ネコプロジェクトに関する文書群を広く正確に取得

生成:
取得文書を根拠に回答

という流れになります。

このため、ユーザー側の言い方の揺れ文書側の表記ゆれの両方に対応できます。

2. このペアが「独立して効く」と考えられる理由

①と④は、似ているようで効いている場所が違います。

手法 効く場所 解決する問題
① クエリ拡張 検索前の質問処理 ユーザーの質問が短い・略語・曖昧
④ 名寄せ インデックス・エンティティ管理 文書内の表記ゆれ・別名・略称の分裂

つまり、

  • ①は「質問を正規化・補強する
  • ④は「知識ベース内の固有名詞を統一する

という役割です。

この2つは重複が少なく、掛け合わせたときに効果が足し算以上になりやすいです。

3. 具体例

何もしない場合

質問:

LHの障害対応方針は?

文書には以下のように書かれている。

Lighthouse CRM刷新プロジェクトにおける障害対応方針

汎用RAGは、LHLighthouse CRM刷新プロジェクト が同じだと分からない可能性があります。

① クエリ拡張を入れる

LHとは、Lighthouse CRM刷新プロジェクトの略称。
営業部門のCRM基盤刷新を目的とした社内プロジェクト。
このプロジェクトの障害対応方針を検索する。

これで検索語が豊かになります。

④ 名寄せを入れる

代表エンティティ:
Lighthouse CRM刷新プロジェクト

別名:
- LH
- Lighthouse
- CRM Lighthouse
- ライトハウスPJ

これで、文書側の表記ゆれも吸収できます。

結果

LH
↓
Lighthouse CRM刷新プロジェクト
↓
関連文書を取得
↓
障害対応方針を回答

このように、検索前の理解検索対象側の整理が両方効きます。

2. ③ オントロジー構築 × ④ 名寄せ

もし質問が単純な用語検索ではなく、次のようなものなら、①×④だけでは足りない場合があります。

「ネコプロジェクトの意思決定に関わった部署と、その決定が影響したシステムを教えて」

この場合、必要なのは単なる検索ではなく、

ネコプロジェクト
  ├─ 関係部署: 営業企画部
  ├─ 責任者: 山田部長
  ├─ 関連会議: 第5回推進会議
  ├─ 決定事項: 顧客対応AIを先行導入
  └─ 影響システム: CRM基盤

のような関係性の探索です。 ここで効くのが③のオントロジー・ナレッジグラフです。 GraphRAGでは④の名寄せが非常に重要です。 なぜなら、もしグラフ内で次のように分裂していたら、

ネコプロジェクト
ネコPJ
NPJ
Neko Project

グラフの関係も分裂してしまいます。

ネコプロジェクト --責任者--> 山田部長
NPJ --関連部署--> 営業企画部
Neko Project --関連システム--> CRM基盤

この状態では、関係を正しくたどれません。

そのため、③ GraphRAGを使うなら、④ 名寄せはほぼ必須です。

総括

組織固有の固有名詞を含むRAGでは、主な課題は 「LLMや検索器が、その言葉の意味・別名・関係性を知らないこと」 です。

主な課題

固有名詞に関わるRAGの課題を整理します。

課題 内容
1. 用語の意味不明 NPJLH などの略語・社内用語をLLMが理解できない
2. 表記ゆれ ネコプロジェクトNPJNeko PJ などが別物として扱われる
3. 検索漏れ ユーザーの呼び方と文書中の呼び方が違うと、関連文書を取れない
4. 関係性を追えない 人物・部署・プロジェクト・文書・意思決定のつながりを扱いにくい
5. 汎用埋め込みの限界 一般的なEmbeddingモデルは社内固有語の意味を十分に表現できない

おすすめ対応策

最初にやるべき対応

① 用語辞書によるクエリ拡張 × ④ エンティティ名寄せ

これが最も現実的で効果が出やすいです。

  • 用語辞書で、略語や社内用語の意味を補う
  • 名寄せで、同じ固有名詞の表記ゆれを統一する
  • 検索時には代表名・別名・定義を使って検索する

例:

NPJ → ネコプロジェクト
別名: NPJ, ネコPJ, Neko PJ
意味: 顧客対応AI導入に関する社内プロジェクト

高度化する場合

複雑な質問まで扱うなら、

③ オントロジー・GraphRAG × ④ エンティティ名寄せ

が有効です。

  • 固有名詞を「単語」ではなく「エンティティ」として扱う
  • 部署・人物・プロジェクト・文書・意思決定の関係をグラフ化する
  • ただし、名寄せが不十分だとグラフが分裂して精度が落ちる

結論

固有名詞対応RAGでまず重要なのは、

  1. 固有名詞を抽出する
  2. 同じ意味の表記を名寄せする
  3. 代表名・別名・定義を用語辞書化する
  4. 検索時にその定義でクエリを拡張する

ことです。

最短で効果を出すなら、

用語辞書によるクエリ拡張 × エンティティ名寄せ

が第一候補です。
その上で、関係性まで扱いたい場合に GraphRAG を追加するのがよいと考えます。

 

 

SAM2の認識精度向上への取り組み・PoC

 

SAMは万能セグメンテーションモデルとして非常に有用です。 ですが、以下のような課題があります。

  • 意味的にはあまり重要でない部分に対しても過剰な検知を行う
  • 検知して欲しい部分を検知しない検知力不足

昨日取り扱ったSAMの過剰検知を抑制するという方法について取り扱いしました。

本日は課題のうち後者である検知力不足について対策となる方法について扱っていきます。

論文概要

検知力不足を補う手法は以前の記事で複数案検討しました。

LoRAを用いる手法など複数提案しましたが、うちHQ-SAMが手法として面白かったため本日はHQ-SAMについて説明・PoCしていきます。 HQ-SAM(Segment Anything in High Quality)は、MetaのSAM(Segment Anything Model)のマスク品質を大幅に向上させる手法として、NeurIPS 2023 に採択された論文です。

論文の基本情報

実は国際学会 NeurIPS に採択されている論文です。

項目 内容
タイトル Segment Anything in High Quality
著者 Lei Ke, Mingqiao Ye, Martin Danelljan, Yifan Liu, Yu-Wing Tai, Chi-Keung Tang, Fisher Yu
所属 ETH Zürich, HKUST, Dartmouth College
発表 NeurIPS 2023
arXiv 2306.01567
コード https://github.com/SysCV/sam-hq
論文URL https://arxiv.org/pdf/2306.01567

研究の背景と問題意識

SAMは11億個のマスクで学習された強力な基盤モデルですが、論文では以下の問題を指摘しています。

  • 粗いマスク境界:オブジェクトの輪郭がぼやけたり、細い構造(髪の毛、凧の糸など)が無視されたりする
  • 誤った予測:複雑な構造に対して壊れたマスクや大きなエラーを生じる

特に、自動アノテーションや画像・動画編集など、高精度なマスクが必須の場面ではSAMの品質が不十分でした。

提案手法:HQ-SAM

HQ-SAMは、SAMの元のプロンプト可能な設計・効率性・ゼロショット汎化性を維持しつつ、マスク品質を高める最小限のアダプテーションです。

主な技術的貢献

  1. 学習可能な High-Quality Output Token

    • SAMのマスクデコーダに、新たな「HQ-Output Token」を追加
    • このトークンと関連する3層のMLPが、高品質なマスクを直接予測
    • 元のSAMのoutput tokenとは別に機能
  2. Global-local Feature Fusion(グローバル・ローカル特徴融合)

    • SAMのマスクデコーダ特徴だけでなく、ViTエンコーダの初期・最終特徴マップも融合
    • 大域的なセマンティック情報と、局所的な細粒度の詳細を両方活用
  3. SAMのパラメータを固定した効率的学習

    • SAMの事前学習済みパラメータはすべて固定(凍結)
    • 学習するのはHQ-Output Token、関連MLP、小さな特徴融合ブロックのみ
    • 追加パラメータは0.5%未満と極めて少ない

データセット:HQSeg-44K

高品質な学習には細粒度なアノテーションが必要なため、論文では新たなデータセット HQSeg-44K を構築しています。

  • 6つの既存データセットを統合
  • 44,000 の非常に高精度なマスクアノテーション
  • 1,000以上の多様なセマンティッククラスをカバー

学習効率

  • 8台のRTX 3090 GPUでわずか4時間の学習で完成
  • 大規模データセット(SA-1Bの11億マスク)を使わず、小規模な高品質データセットで効率的に学習

実験結果

10の多様なセグメンテーションデータセット(COCO、UVO、LVIS、BIG、COIFT、HR-SODなど)で評価され、うち8つはゼロショット転送の設定です。

  • SAMより高品質なマスクを生成
  • ゼロショット能力を維持(SAMの強みを失わない)
  • 速度・モデルサイズのトレードオフも良好

実験のこんな例が合わせて掲載されています。 SAMに比べると検知して欲しい場所を検知できるようになっていると考えられますよね。

提案手法

HQ-SAMのGood Pointは、巨大な基な基盤モデルを壊さずに、最小限の追加で品質だけを高めるという設計です。 そんな手法について解説していきます。

1. 基本設計思想

HQ-SAMは、SAMの事前学習済みパラメータをすべて凍結した上で、極めて少ない追加パラメータ(0.5%未満)だけを学習することで、SAMのゼロショット汎化性を損なわずにマスク品質を向上させます。

論文では「SAMのデコーダを直接ファインチューニングしたり、新しいデコーダモジュールを導入したりすると、ゼロショット性能が著しく劣化する」ことが示されています。そのため、既存のSAM構造を緊密に統合・再利用する設計が採用されています。

2. 2つの主要な追加要素

(1) High-Quality Output Token(HQ-Output Token)

SAMのマスクデコーダには、もともと「IoUトークン」と「マスクトークン(複数)」が入力されています。HQ-SAMでは、これらに加えて新たな学習可能なHQ-Output Tokenを注入します。

  • 役割: 元のマスクトークンとは独立に、高品質なマスクを直接予測する
  • 関連パラメータ: HQ-Output Tokenに対応する3層のMLPhf_mlp)も新たに学習
  • デコーダ内のトランスフォーマー処理自体はSAMの既存ものをそのまま使用

(2) Global-local Feature Fusion(グローバル・ローカル特徴融合)

SAMのマスクデコーダ特徴だけでは細部の再現が不十分なため、ViTエンコーダの異なる階層の特徴を融合します。

  • Global特徴: SAMのマスクデコーダが生成する特徴(セマンティックな大域情報)
  • Local特徴: ViTエンコーダの初期層(1st global attention block後)の特徴マップ(細粒度な局所情報)

(2)の方は以下のモデル図の下の方に見えているGlobal-local Featureのラインをご覧頂ければと思います。

これらを以下の3つの畳み込み層で融合・変換します。

モジュール名 役割
compress_vit_feat ViTの初期特徴をデコーダ次元に変換・アップスケール
embedding_encoder SAMのデコーダ特徴をアップスケール
embedding_maskfeature 融合後の特徴をさらに精緻化

3. ネットワークの詳細な流れ(実装ベース)

学習時の処理フロー

1. ViTエンコーダ
   ↓
   ├─→ 初期層特徴 (interm_embeddings[0]) ──→ compress_vit_feat ──┐
   └─→ 最終層特徴 (image_embeddings) ─────→ embedding_encoder ─┤
                                                                 ↓
                                                          hq_features
                                                                 ↓
2. マスクデコーダ
   - IoU Token + Mask Tokens + HQ-Output Token を結合
   - SAMのトランスフォーマーで処理
   - src(デコーダ特徴)をアップスケール
                                                                 ↓
   ├─→ upscaled_embedding_sam ──→ masks_sam(元のSAM予測)
   └─→ upscaled_embedding_sam + hq_features ──→ upscaled_embedding_hq
                                          ↓
                                    masks_hq(HQ-SAM予測)

マスク予測の仕組み

SAMと同様に、Hypernetwork(ハイパーネットワーク)方式を使用します。

  • 各マスクトークンに対応するMLPが、マスク埋め込み(upscaled embedding)の係数(重み)を生成
  • 元のSAMマスク: hyper_in @ upscaled_embedding_sam
  • HQマスク: hf_mlp(hq_token) @ upscaled_embedding_hq

最終的に、SAMの予測マスクとHQ-SAMの予測マスクを結合して出力します。

4. 学習戦略

凍結するパラメータ

  • SAMの画像エンコーダ(ViT)全体
  • SAMのマスクデコーダの既存パラメータ(output_upscaling, output_hypernetworks_mlps, iou_prediction_headなど)

学習するパラメータ

  • hf_token(HQ-Output Token)
  • hf_mlp(HQ用3層MLP)
  • compress_vit_feat(ViT特徴変換層)
  • embedding_encoder(デコーダ特徴アップスケール層)
  • embedding_maskfeature(融合特徴精緻化層)

損失関数

  • 標準的なバイナリクロスエントロピー損失ダイス損失の組み合わせ
  • HQSeg-44Kデータセットの高精度マスクに対して学習

5. 推論時の動作

推論時には2つのモードがあります。

通常モード(hq_token_only=False

  • SAMの予測マスク(masks_sam)とHQ予測マスク(masks_hq)を足し合わせる
  • SAMの大域的な正確性とHQの細部品質を両立

HQのみモード(hq_token_only=True

  • HQ予測マスク(masks_hq)のみを使用
  • より細部にフォーカスしたい場合に有効

6. なぜこの設計が有効か

SAMのデコーダの解像度制約

  • トランスフォーマー処理後の特徴 src は空間的に粗い解像度(256×256程度のアップスケール前)
  • 2段階のConvTranspose2dでアップスケールし、最終的に256×256のマスクを生成→この過程で、 細い構造(髪の毛、凧の糸、網目など)の情報が失われやすい

論文中でも、SAMの失敗例として「凧の糸のような細い構造を誤解釈し、壊れたマスクや大きな穴を生じる」ことが指摘されています。これはデコーダの特徴空間が粗すぎて、細部を保持できないことが主因です。

ViTエンコーダ初期層の高解像度情報

HQ-SAMはこの問題に対し、ViTエンコーダの初期層(1st global attention block後)の特徴マップを活用します。

特徴 デコーダ特徴 ViT初期層特徴
解像度 低(粗い) 相対的に高(細かい)
保持する情報 大域的セマンティクス 局所的なエッジ・テクスチャ・細部構造
役割 「何が」オブジェクトか 「どこまで」が境界か

エンコーダの一般的な特徴ですが、interm_embeddings[0](ViTの初期層特徴)は、エンコーダの後期層よりも空間的な解像度が高く、細かい局所構造の情報をより多く保持しています。 HQ-SAMはこれを compress_vit_feat によってデコーダの次元に変換し、デコーダ特徴と融合することで、失われた細部情報を復元します。

「単なるアップスケーリング」では不十分だったか

HQ-SAMの精度向上は「デコーダの低解像度特徴の限界を、エンコーダの高解像度・高周波特徴で補完したこと」 に加えて、「HQ-Output Tokenによる直接的な高品質マスク予測機構」 の両方が組み合わさった結果と考えています。

論文では、HQ-SAMと異なるアプローチ(後処理リファインメントや、別デコーダの導入)を比較し、以下の問題を指摘しています:

  • CRF(条件付きランダム場)ベースの後処理: 低レベルの色境界に依存し、高レベルセマンティック文脈を十分活用できない。大きなセグメンテーションエラーを修正できない。
  • 別ネットワークによるカスケードリファインメント: 過学習しやすく、ゼロショット汎化性を損なう。

つまり、単に「解像度を上げる」だけではなく、SAMの既存知識を保持しつつ、デコーダ内で自然に高解像度情報を統合する必要がありました。

HQ-Output Tokenの役割(「補完」だけではない)

ただし、HQ-SAMの精度向上は単なる特徴融合(補完)だけでは説明できません

HQ-SAMの核心は、HQ-Output Tokenが「高品質マスク専用の予測ヘッド」として機能する点にあります:

  • SAMの元のマスクトークンは、粗いマスクを予測するよう学習済み
  • HQ-Output Tokenは、融合後の高品質特徴(upscaled_embedding_hq)を使って、直接高品質マスクを予測するよう新たに学習
  • 最終的に masks_sam + masks_hq として、SAMの大域的正確性とHQの細部品質を両立

つまり、「高解像度特徴の補完」+「その特徴を使う専用予測ヘッドの学習」の両方が必要でした。

総括するとHQ-SAMの以下の特徴によりSAMのゼロショットの性能を維持しつつ、高解像度の情報をSAMに干渉しないように保持することで、SAMの精度を改善したということです。

要素 役割
ViT初期層特徴の融合 デコーダの低解像度特徴に、高解像度・高周波の局所情報を補完
HQ-Output Token + hf_mlp 補完された特徴を使って、高品質マスクを直接予測する専用ヘッド
SAMパラメータ凍結 11億マスクで学習されたゼロショット知識を保持
最終出力(SAM + HQ) 大域的な正確性と細部の精緻さを両立

PoC

HQ-SAM 2 vs SAM 2.1 輪郭精度比較 PoC を行ってみます。

自動マスク生成による物体検出と境界精度の定量的比較

1. PoC の目的

画像内の物体をプロンプトなし(bbox・点指定なし)で自動検出し、SAM 2.1HQ-SAM 2 のセグメンテーション品質、特に輪郭(境界)精度を比較することを目的とする。

  • 人が物体位置を指定しなくても、画像内の主要物体を自動で検出できるか
  • HQ-SAM 2 は SAM 2.1 と比較して、境界の細部がどれだけ改善されるか
  • 輪郭の複雑さ(点数・長さ)を定量的に評価する

2. 使用モデル

両モデルとも、論文著者・公式リポジトリから配布されている正規の学習済み重みを使用します。

項目 SAM 2.1(ベースライン) HQ-SAM 2
配布元 Meta AI / FAIR SysCV(lkeab)
重みファイル sam2.1_hiera_tiny.pt sam2.1_hq_hiera_tiny.pt
VRAM使用量 ~2 GB ~2 GB

補足: VRAM制約(14.56GB)を考慮し、LargeモデルからTinyモデルに切り替え。比較の公平性は保持される。

3. 実行手順

ステップ 内容
1. 環境構築 sam-hq2 リポジトリをcloneし、必要なパッケージをインストール
2. 重みダウンロード 公式URLから SAM 2.1 Tiny / HQ-SAM 2 Tiny のチェックポイントを取得
3. ベースラインSAM 2.1のパッチ HQ-SAM 2のpredictorが渡す追加引数(hq_token_only / interm_embeddings)を無視するようforwardを書き換え、互換性を確保
4. 自動マスク生成(SAM 2.1) SAM2AutomaticMaskGenerator を使用し、画像全体をグリッド走査して物体を自動検出。結果をCPUに保存後、モデルをGPUから解放
5. 自動マスク生成(HQ-SAM 2) 同様に自動検出を実行。2つのモデルを同時にGPUに載せないことでメモリを確保
6. 比較対象の選択 検出された複数マスクの中から「面積最大」のものを自動選択(画像の主要物体と定)
7. 輪郭抽出 OpenCV の findContours で両マスクの輪郭を抽出
8. 定量的比較 輪郭の点数・長さを計測し、HQ-SAM 2 の改善率を算出
9. 可視化 マスク・差分・拡大表示を画像として保存

4. 遭遇した課題と解決策

色々エラーが出ました。 解決策はこんな感じでした。

課題 1: デコーダ引数の互換性エラー

問題: ベースラインSAM 2.1のデコーダは hq_token_only / interm_embeddings 引数を受け取れないため、TypeError が発生。

解決策: ベースラインの forward / decoder.forward をモンキーパッチし、HQ-SAM 2用の追加引数を自動的に無視するようにした。

def _patched_forward(*args, **kwargs):
    kwargs.pop("hq_token_only", None)
    kwargs.pop("interm_embeddings", None)
    return _orig_forward(*args, **kwargs)

課題 2: GPUメモリ不足(OutOfMemoryError)

問題: Largeモデル(~14GB)を2つ同時にGPUに載せると、自動マスク生成の計算量でVRAMを使い果たした。

解決策: 以下の複合的な対を実施。

対策 効果
モデルをTiny(~2GB)に変更 VRAM使用量を約1/7に削減
逐次ロード(1モデルずつGPU使用) 同時メモリ占有を回避
torch.cuda.empty_cache() キャッシュを即時解放
画像リサイズ(512px制限) 計算コストを低減
crop_n_layers=0 クロップ検出によるメモリ消費を停止
points_per_side=16 グリッド密度を半減

5. 結果確認

Anythingモードで画像を入力して実際にマスキングされた結果を比較してみました。 正直改善しているとはいいがたい結果に見えます。

ちょっとHQ-SAM2は敏感な感じがします。 例えば背景に隠れた見分けがつきづらい木を他とは違うと検出しています。

個体識別という能力であればSAM-2よりも使えるかもしれません。

モデルがおかしいかと思い、ポイント指定するモードでやるときちんと人を識別してくれます。

不特定多数を検知という意味だと敏感すぎる、ということのようでした。

総括

何を解決したか

MetaのSAMは11億マスクで学習した強力な基盤モデルだが、境界がぼやける・細い構造(髪の毛・糸など)が消えるという品質問題があった。HQ-SAMはこれをSAMのパラメータを一切触らず、極めて少ない追加(0.5%未満)で解決した。

技術の核心

SAMのデコーダ特徴は解像度が粗く、細部が失われる。HQ-SAMは以下の2つで補完する:

要素 役割
HQ-Output Token 高品質マスクを直接予測する専用ヘッド
ViT初期層特徴の融合 エンコーダの高解像度・高周波特徴をデコーダに補完

SAMの11億マスクで学習されたゼロショット能力は凍結して保持し、HQ部分だけをHQSeg-44K(高精度4.4万マスク)で学習する。

PoCで判明したこと

モード 結果
自動検出(Anything) ❌ 過敏になり過剰分割。背景の木なども別個体として検出
点プロンプト指定 ✅ 正常動作。指定物体の境界が高精度にセグメント化

結論

HQ-SAMは「不特定多数を勝手に検出するツール」ではなく、「指定した物体の境界を高精度に切り出すツール」

自動検出には向かないが、プロンプトで対象を絞ればSAMより明確に品質が向上する。用途は画像編集・自動アノテーション・個体の精密マスキングなど。

 

 

 

 

SAMの過剰検知を抑制する手法の実験

 

先日SAMの検知力改善のアイデアで説明したCLIPを適用してSAMの優れた境界検知能力による不要な検知カ所の抑制を行いたいと思います。

本日テーマ:

SAM×CLIPにより主張するような効果が確認できるか確認

モチベーション

前回記事では現状やりたいことを

  1. 誤検知の多さを抑制する(背景や無関係な輪郭まで細かく拾ってしまう問題の回避)
  2. グラデーションで背景と見分けがつきづらい対象を検知する(低コントラスト・輪郭が不鮮明な領域の切り出し)

と定義して誤検知の多さはプロンプトで指定する方法を押しました。 しかし実際の用途は 不特定多数の対象を検知する ということでプロンプトで指定しなかったものも検知したいということにありました。

とした場合とりえる対応法はプロンプトなしでもSAMの結果を、同じく汎用性に優れるCLIPで検証することが妥当かと考えました。

簡単に本手法の仕組み

SAM-CLIP は、Metaが開発した高精度な画像領域分割モデル SAM (Segment Anything Model) と、OpenAIが開発した言語-画像相互理解モデル 「CLIP」 の強みを統合したフレームワーク(および統合モデル)です。

一言で言えば、「物体の境界線を正確にくり抜く能力(SAM)」「それが何であるかを言葉で理解する能力(CLIP)」 を1つのモデルに合体させた仕組みです。

なぜ統合が必要なのか?

  • SAMの課題:「画像のどこに物体があるか(境界・マスク)」を抽出するのは極めて得意ですが、「その物体が何という名前か(セマンティッククラス)」を識別する概念理解が弱い。
  • CLIPの課題:「テキスト(言葉)と画像(概念)」を結びつける理解力は非常に高いですが、「物体の具体的な形状や細かな輪郭」を特定する空間認識は苦手。

SAM-CLIPは、これら2つの基盤モデルの相補的な能力を組み合わせることで、「任意のテキスト指定で、画像内の該当オブジェクトを正確な輪郭とともに切り出す(オープンボキャブラリー領域分割)」 を実現しています。

手法の主な仕組み

1. 特徴量エンコーダの統合・蒸留

推論時に「SAMの巨大エンコーダ」と「CLIPのエンコーダ」を2つ同時に動かすと、計算コストやメモリ負荷が非常に大きくなります。SAM-CLIPでは、単一のバックボーン(ビジョンエンコーダ) に両者の能力を蒸留・融合させるアプローチをとっています。

  • SAMが持つ幾何学的・局所的な構造特徴
  • CLIPが持つ言語とアラインメントされた大域的な意味特徴

これらを1つのエンコーダで同時に出力できるように統一学習(または特徴統合)を行います。

2. オープンボキャブラリーなマスク生成

  • 画像を入力すると、統合エンコーダが「領域情報」と「意味情報」を含んだマルチスケールな特徴マップを抽出します。
  • ユーザーが「a cat sitting on a red sofa(赤いソファに座る猫)」といった自由なテキストを入力すると、テキストの特徴量(CLIPテキストエンコーダ経由)と画像特徴マップの類似度を計算し、「何がどこにあるか」を一致させます
  • 最終的にSAMのマスクデコーダ(Mask Decoder)を利用して、該当するテキストが指す物体の正確なピクセル単位の領域(マスク) を生成します。

主なメリット・特徴

  • 推論の効率化: 2つのモデルを別々に実行して結果を組み合わせる後処理方式に比べ、単一の統合型ネットワークで処理できるため、高速かつ省メモリで動作します。
  • ゼロショット対応: 事前に学習していない未知のカテゴリや複雑な説明文に対しても、追加学習なしで即座に精度高く領域を抽出可能です。

本モデルの構成は以下のようにSAMとCLIP双方の結果をcatしたものをベースにMLP→マスクのデコーダを行ったというものです。

実験方法

SAMの高い領域分割能力(過剰検知傾向)に対し、CLIPのテキスト-画像のアライメント能力を組み合わせて意味的なフィルタリングを行う「SAM-CLIP」のPoCに向け実験手順を検討しました。

1. 背景とPoCの目的

  • 背景・課題: SAM(Segment Anything Model)はあらゆるオブジェクトの境界を精密に検出できますが、意味的コンテキストの判定を持たないため、目的外の領域まで過剰に検知してしまう課題があります。
  • 解決のアプローチ: CLIPの汎用的な視覚-言語アライメント能力を検証メカニズムとして組み合わせ、指定したテキストプロンプトに合致する領域のみを正確に抽出・フィルタリングします。
  • PoCの狙い: 本格的な大規模学習に入る前に、小規模データ(200枚程度のミニデータセット)を用いた高速なバッチテストを実施し、学習・検証パイプラインが正常に回ることを確認します。

2. 準備

  1. 環境の整理と不足パッケージの追加:

今回PoCに必要なリソース一式をDLの上、環境にインストールするbashです。

# 1. リポジトリのクローン
!git clone https://github.com/YiyuanLinXX/SAM-CLIP.git
%cd SAM-CLIP

# 2. 必要なライブラリのインストール
!pip install -q git+https://github.com/facebookresearch/segment-anything.git
!pip install -q ftfy regex tqdm
!pip install -q git+https://github.com/openai/CLIP.git

# 入力用・出力用フォルダの作成
!mkdir -p input_images output_masks

!wget -q -O input_images/test_image.jpg "https://raw.githubusercontent.com/facebookresearch/segment-anything/main/notebooks/images/dog.jpg"
!mkdir -p ckpt

!wget -q https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth

!pip install asttokens executing icecream monai nptyping pynrrd SimpleITK slicerio torchio
!pip install tensorboardX
  1. PoC用ミニデータセットの自動生成:

CLIPやSAMは学習済みなので、アダプター(CLIPとSAMの接続パラメータ)さえ学習できれば動作させることが出来ると思います。 COCO val2017を活用したプロトタイプデータ作成を行いました。

  • テストを迅速に行うため、COCO val2017データセットから20枚(Train: 160枚 / Val: 40枚)を抽出。
  • アノテーションデータ(JSON)からSAM-CLIP学習用のマスク画像(PNG)を自動生成するスクリプトを作成しました。
import os
import json
import urllib.request
import numpy as np
from PIL import Image
from pycocotools.coco import COCO

base_dir = "mini_dataset_v2"
img_dir = os.path.join(base_dir, "images")
mask_dir = os.path.join(base_dir, "masks")
os.makedirs(img_dir, exist_ok=True)
os.makedirs(mask_dir, exist_ok=True)

# 1. COCO val2017 アノテーションの取得
ann_file = "annotations/instances_val2017.json"
if not os.path.exists(ann_file):
    print("アノテーションファイルをダウンロード中...")
    ann_url = "http://images.cocodataset.org/annotations/annotations_trainval2017.zip"
    urllib.request.urlretrieve(ann_url, "annotations.zip")
    os.system("unzip -q annotations.zip -d .")
    os.remove("annotations.zip")

coco = COCO(ann_file)
img_ids = coco.getImgIds()[:200]
images = coco.loadImgs(img_ids)

train_lines = []
val_lines = []

base_img_url = "http://images.cocodataset.org/val2017/"

print("画像とマスクを生成中...")
for idx, img_info in enumerate(images):
    file_name = img_info["file_name"]
    name_without_ext = os.path.splitext(file_name)[0]
    mask_file_name = f"{name_without_ext}.png"
    
    # 画像ダウンロード
    img_save_path = os.path.join(img_dir, file_name)
    if not os.path.exists(img_save_path):
        urllib.request.urlretrieve(base_img_url + file_name, img_save_path)
        
    # マスク画像の生成 (PNG)
    ann_ids = coco.getAnnIds(imgIds=img_info["id"])
    anns = coco.loadAnns(ann_ids)
    
    mask = np.zeros((img_info["height"], img_info["width"]), dtype=np.uint8)
    for ann in anns:
        mask = np.maximum(mask, coco.annToMask(ann))
        
    mask_save_path = os.path.join(mask_dir, mask_file_name)
    Image.fromarray((mask * 255).astype(np.uint8)).save(mask_save_path)
    
    # リスト形式: ファイル名のみ (画像ファイル名,マスクファイル名)
    line_entry = f"{file_name},{mask_file_name}"
    
    if idx < 40:
        train_lines.append(line_entry)
    else:
        val_lines.append(line_entry)

# テキストファイルの保存
train_list_path = os.path.join(base_dir, "train.txt")
val_list_path = os.path.join(base_dir, "val.txt")

with open(train_list_path, "w") as f:
    f.write("\n".join(train_lines))

with open(val_list_path, "w") as f:
    f.write("\n".join(val_lines))

print("\nリストファイルをファイル名のみの形式に更新しました!")

3. 完成した検証用パイプラインの全貌

準備が整ったら早速ファインチューニングの開始です。

データ構造

前項にて実施した学習用データの構成です。 cocoからデータを200枚抽出して以下のように分けています。

  • 画像ディレクトリ (mini_dataset_v2/images/): COCOから抽出した画像ファイル
  • マスクディレクトリ (mini_dataset_v2/masks/): 対応するセグメンテーションマスク (PNG)
  • アノテーションリスト (mini_dataset_v2/train.txt, val.txt):
000000397133.jpg,000000397133.png
000000039761.jpg,000000039761.png
...

実行コマンド仕様

準備出来たらファインチューニングです。 コマンドは一旦以下通りで大丈夫です。 もし気になったら学習率やエポックは変更ください。

!python train.py \
    -sam_ckpt sam_vit_b_01ec64.pth \
    -dir_checkpoint ckpt \
    -img_folder mini_dataset_v2/images \
    -mask_folder mini_dataset_v2/masks \
    -train_img_list mini_dataset_v2/train.txt \
    -val_img_list mini_dataset_v2/val.txt \
    -text_prompt "dog" \
    -epochs 5 \
    -lr 0.0001 \
    -num_cls 3 \
    -gpu True \
    -gpu_device 0

4. 次のステップ(PoCの検証フェーズ)

パイプラインの動作が確認でき次第、以下の観点から検証を進めます。

  • 動作検証: ミニデータセットで Loss が正常に収束するか確認。
  • 過剰検知抑制の評価: ファインチューニングした後のパラメータを用いて推論してみます。背景や無関係な領域のオーバーセグメンテーションがCLIPのガイドによって抑制されているか視覚的に評価。
  • スケールアップ: 小さなトライアルで良好な結果が得られた場合、より大規模なデータセットや独自の目的データセットへの適用を検討。

学習と検証

学習の推移

学習時のエポックとLOSSの値の推移です。 初めの大きなロスは抑えられていることが確認出来ます。 データ数が少ないのでまだ収束はしていませんが、PoCを行う程度であれば十分でしょう。

学習後の検証

学習が終わった状態のモデルで評価を行いました。 結果は以下のようになります。(上段:SAMのみで検知したマスク、下段:SAM+CLIPで検知したマスク)

いずれもSAMで検知したときは不要と思われる対象まで検知していますが、SAM+CLIPとすることで過剰な検知が抑制されていることが確認出来ます。 プロンプトの"dog"である必要もありません。

結果の見方

  • SAMは入力画像→SAMの検知結果
  • SAM+CLIPは入力画像→今回手法での検知結果をマスク化した画像→実際の画像にマスクをオーバーレイした画像

SAM VS SAM+CLIPの結果を比較していきます。

一見良いように見えて、自然背景や空にまでマスクをかけています。

CLIPは当然知らないわけで得られるマスクは自然背景、空はマスクなしです。

机の上の対象、窓まで検知します。

こちらもCLIPは窓を分からないのでマスクなしです。

SAMは公園の中でも検知を沢山してきます。

がCLIPは公園の建造物に対しては認識できません。

今回のファインチューニングはcocoデータセットの200件のみで、上記の結果でした。 恐らくアダプターと、アダプター付近のネットワークパラメータがパラメータ分布が変わることになったと考えています。

結果に対してはPoCとしては上出来では? というのはSAMの見境のない検知を意味を捉えて認識しなおし、結果、過剰な検知を抑制する効果を確認できたからです。

総括

SAMは「クラス不可知」なため、何でも検出しようとして過検出・未検出が起きやすいという課題があります。
これを制御する本質的な方法は、以下の3つに集約できます。

1. 前段で「何を検出したいか」を限定する(Grounded SAM)

  • SAM単体では「何がターゲットか」が分からないため、テキストで指定できる検出器(Grounding DINOなど)を前段に置く
  • 「person」「damaged part」などテキストで指定 → バウンディングボックスを取得 → その領域だけSAMに渡す。
  • 誤検知(背景まで細かく切る)を原理的に防げる

2. プロンプトと後処理で「出力を絞り込む」

  • ポジティブ/ネガティブポイント
    検出したい場所をクリック(ポジティブ)、不要な場所をクリック(ネガティブ)してSAMに指示。
  • 後処理フィルタ
    SAMが出したマスク候補から、面積・形状・IoU(重複)・CLIP分類などで不要なマスクを除外
  • 曖昧さ出力の制御
    1つのプロンプトで複数のマスク候補が出る場合、目的に合う粒度のマスクだけを選ぶ。

3. ドメインに合わせて「モデルを少しだけ学習させる」(LoRA / Adapter)

  • SAMは自然画像に強いが、グラデーションや低コントラスト、特殊な形状には弱い。
  • LoRAやSAM-Adapterで、
    • 画像エンコーダやマスクデコーダの一部だけを
    • 少量のデータ(数十〜数百枚)で微調整する。
  • これにより、境界が曖昧な対象でも安定して検出できるように「モデルの注目の仕方」を調整できる。

まとめ

  • SAMの検知を制御する本質は、
    「何を検出したいか」を前段で限定し(Grounded SAM)、プロンプトと後処理で出力を絞り込み、必要なら少量学習でモデルをドメインに合わせる」 ことです。
  • これにより、過検出・未検出を抑えつつ、グラデーションや特殊形状にも対応できます。

グラフ理論(2):NetworkXを用いたグラフの基本操作

グラフの応用を扱うにあたり、NetworkXの基本操作を練習していこうと思います。 基本的な操作→例題の流れで説明を進めていきます。

 

基本的操作

NetworkXの基本的な操作は、主に次の流れで行います。

  1. グラフオブジェクトの作成
  2. ノード・エッジの追加・削除
  3. 属性(重み・ラベルなど)の付与
  4. グラフ情報の取得(ノード数・エッジ数・隣接情報など)
  5. 簡単な可視化

以下、それぞれをコード例とともに説明します。

1. グラフオブジェクトの作成

import networkx as nx

# 無向グラフ
G = nx.Graph()

# 有向グラフ
DG = nx.DiGraph()

# 多重辺を持つ無向グラフ
MG = nx.MultiGraph()

# 多重辺を持つ有向グラフ
MDG = nx.MultiDiGraph()

2. ノードの追加・削除

ノードの追加

# 1つずつ追加
G.add_node("A")
G.add_node("B")

# リストでまとめて追加
G.add_nodes_from(["C", "D", "E"])

# ノードに属性を付ける
G.add_node("F", color="red", size=10)

ノードの削除

# 1つずつ削除
G.remove_node("A")

# リストでまとめて削除
G.remove_nodes_from(["B", "C"])

3. エッジの追加・削除

エッジの追加

# 1つずつ追加(重みなし)
G.add_edge("A", "B")

# 重み付きで追加
G.add_edge("A", "C", weight=3)

# リストでまとめて追加
edge_list = [("A", "D"), ("B", "C"), ("C", "D")]
G.add_edges_from(edge_list)

# 重み付きエッジをまとめて追加
weighted_edges = [("A", "B", 2.0), ("B", "C", 1.5), ("C", "A", 3.0)]
G.add_weighted_edges_from(weighted_edges)

エッジの削除

# 1つずつ削除
G.remove_edge("A", "B")

# リストでまとめて削除
edges_to_remove = [("A", "C"), ("B", "D")]
G.remove_edges_from(edges_to_remove)

4. 属性の付与・参照

ノード属性

# 追加時に属性を付与
G.add_node("Tokyo", population=14000000, country="Japan")

# 後から属性を追加・更新
G.nodes["Tokyo"]["region"] = "Kanto"

# 属性の参照
print(G.nodes["Tokyo"]["population"])  # 14000000

エッジ属性

# 追加時に属性を付与
G.add_edge("Tokyo", "Osaka", distance=500, time=150)

# 後から属性を追加・更新
G.edges["Tokyo", "Osaka"]["type"] = "Shinkansen"

# 属性の参照
print(G.edges["Tokyo", "Osaka"]["distance"])  # 500

5. 基本的な情報取得

# ノード一覧
print("Nodes:", list(G.nodes()))

# エッジ一覧
print("Edges:", list(G.edges()))

# ノード数・エッジ数
print("Number of nodes:", G.number_of_nodes())
print("Number of edges:", G.number_of_edges())

# 特定ノードの隣接ノード
print("Neighbors of Tokyo:", list(G.neighbors("Tokyo")))

# 特定ノードの次数(接続エッジ数)
print("Degree of Tokyo:", G.degree("Tokyo"))

# 重み付きエッジの属性も含めて確認
for u, v, d in G.edges(data=True):
    print(f"Edge {u}-{v}: {d}")

6. 簡単な可視化

import matplotlib.pyplot as plt

# レイアウトを計算
pos = nx.spring_layout(G)

# グラフを描画
nx.draw(G, pos, with_labels=True, node_color="lightblue", node_size=500)

# エッジラベル(例:重み)を表示
edge_labels = {(u, v): d.get("weight", "") for u, v, d in G.edges(data=True)}
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels)

plt.title("Simple NetworkX Graph")
plt.show()

7. その他の基本操作(例)

  • 部分グラフの抽出

    nodes_subset = ["Tokyo", "Osaka", "Kyoto"]
    H = G.subgraph(nodes_subset)
    
  • グラフの結合

    G1 = nx.Graph()
    G1.add_edges_from([("A", "B"), ("B", "C")])
    
    G2 = nx.Graph()
    G2.add_edges_from([("C", "D"), ("D", "E")])
    
    G_combined = nx.union(G1, G2)
    
  • 隣接行列・隣接リストの取得

    # 隣接行列(SciPy sparse matrix)
    A = nx.adjacency_matrix(G)
    
    # 隣接辞書
    adj_dict = dict(G.adjacency())
    

例題

早速基本操作を組み合わせてグラフの操作を行ってみましょう。

この例題では、

  • グラフの構築(ノード・エッジ追加)
  • 属性の付与・参照
  • 次数(入次数・出次数)の計算
  • 中心性の簡易評価
  • 有向グラフの可視化
  • エッジ追加後の再計算

といった、NetworkX の基本操作を一通り組み合わせて実装する練習になります。

問題設定: SNS のフォロー関係ネットワークを構築・分析する

ある SNS において、次のようなフォロー関係があるとします。

  • ユーザー:"Alice", "Bob", "Charlie", "Diana", "Eve"
  • フォロー関係(矢印は「フォローしている」方向):
    • Alice → Bob
    • Alice → Charlie
    • Bob → Charlie
    • Bob → Diana
    • Charlie → Diana
    • Diana → Eve
    • Eve → Alice

※自己フォローはなしとします。

問1:グラフの構築と基本情報

  1. 有向グラフとしてこのフォロー関係ネットワークを NetworkX で構築してください。
  2. 各ユーザー(ノード)に、次の属性を付与してください。
    • role"admin"(Alice のみ)、それ以外は "user"
  3. グラフの基本情報を出力してください。
    • ノード一覧
    • エッジ一覧
    • ノード数・エッジ数
    • 各ノードの入次数(フォロワー数)出次数(フォロー数)

問2:中心的なユーザーの特定

  1. 入次数中心性(フォロワー数)が最も高いユーザーを特定し、そのユーザー名とフォロワー数を出力してください。
  2. 出次数中心性(フォロー数)が最も高いユーザーを特定し、そのユーザー名とフォロー数を出力してください。

問3:フォロー関係の可視化

  1. NetworkX と matplotlib を使って、このフォロー関係ネットワークを有向グラフとして可視化してください。
    • ノードはラベル付きで表示
    • エッジは矢印付きで表示
    • role="admin" のノード(Alice)は色を変えて強調
  2. グラフのタイトルを付け、軸を非表示にしてください。

問4:追加操作(発展)

  1. 「Alice が Eve をフォローする」という新しいエッジを追加してください。
  2. 追加後のグラフについて、Alice から Eve への最短パス(フォロー関係をたどる経路)を求め、その経路と長さ(エッジ数)を出力してください。
  3. 追加前と追加後で、Alice の出次数入次数がどう変化したか確認してください。

ヒント(必要に応じて)

  • 有向グラフは nx.DiGraph() で作成します。
  • 入次数は G.in_degree(node)、出次数は G.out_degree(node) で取得できます。
  • 中心的なユーザーは、G.in_degree()G.out_degree() の結果を max() で比較して特定できます。
  • 可視化では nx.draw_networkx_edges(..., arrows=True) を使うと矢印が表示されます。
  • 最短パスは nx.shortest_path(G, source, target) で取得できます(重みなし・エッジ数ベース)。

解答

問1〜問4に対応する解答 Python コードです。

問1〜問4 対応コード

import networkx as nx
import matplotlib.pyplot as plt

print("=== 問1:グラフの構築と基本情報 ===")

# 1. 有向グラフとして構築
G = nx.DiGraph()
G.add_nodes_from(["Alice", "Bob", "Charlie", "Diana", "Eve"])

edges = [
    ("Alice", "Bob"),
    ("Alice", "Charlie"),
    ("Bob", "Charlie"),
    ("Bob", "Diana"),
    ("Charlie", "Diana"),
    ("Diana", "Eve"),
    ("Eve", "Alice"),
]
G.add_edges_from(edges)

# 2. 各ノードに role 属性を付与
for node in G.nodes():
    if node == "Alice":
        G.nodes[node]["role"] = "admin"
    else:
        G.nodes[node]["role"] = "user"

# 3. 基本情報の出力
print("Nodes with attributes:")
for node in G.nodes():
    role = G.nodes[node].get("role", "N/A")
    print(f"  {node}: role={role}")

print("\nEdges:", list(G.edges()))
print(f"Number of nodes: {G.number_of_nodes()}")
print(f"Number of edges: {G.number_of_edges()}")

print("\nIn-degree and Out-degree:")
for node in G.nodes():
    in_deg = G.in_degree(node)
    out_deg = G.out_degree(node)
    print(f"  {node}: in_degree={in_deg}, out_degree={out_deg}")

print("\n=== 問2:中心的なユーザーの特定 ===")

# 1. 入次数中心性(フォロワー数)が最大のユーザー
in_degrees = dict(G.in_degree())
max_in_node = max(in_degrees, key=in_degrees.get)
print(f"Most followed user: {max_in_node} (followers: {in_degrees[max_in_node]})")

# 2. 出次数中心性(フォロー数)が最大のユーザー
out_degrees = dict(G.out_degree())
max_out_node = max(out_degrees, key=out_degrees.get)
print(f"User following the most: {max_out_node} (following: {out_degrees[max_out_node]})")

print("\n=== 問3:フォロー関係の可視化 ===")

# 1. 可視化の準備
pos = nx.spring_layout(G, seed=42)

# ノードの色を role に応じて設定
node_colors = []
for node in G.nodes():
    if G.nodes[node].get("role") == "admin":
        node_colors.append("red")   # Alice(admin)は赤
    else:
        node_colors.append("lightblue")

plt.figure(figsize=(8, 6))

# ノード描画
nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=800)
nx.draw_networkx_labels(G, pos, font_size=12)

# エッジ描画(有向グラフなので矢印付き)
nx.draw_networkx_edges(G, pos, edge_color="gray", width=1.5,
                        arrows=True, arrowsize=20, arrowstyle="-|>")

plt.title("SNS Follow Network (Directed Graph)", fontsize=14)
plt.axis("off")
plt.tight_layout()
plt.show()

print("\n=== 問4:追加操作(発展) ===")

# 1. Alice が Eve をフォローするエッジを追加
print("Adding edge: Alice -> Eve")
G.add_edge("Alice", "Eve")

# 2. Alice から Eve への最短パス(エッジ数ベース)
try:
    path = nx.shortest_path(G, source="Alice", target="Eve")
    path_length = len(path) - 1  # エッジ数 = ノード数 - 1
    print(f"Shortest path from Alice to Eve: {' -> '.join(path)}")
    print(f"Path length (number of edges): {path_length}")
except nx.NetworkXNoPath:
    print("No path from Alice to Eve")

# 3. 追加前後の Alice の次数変化
# 追加前の次数(問1で計算済みの値)
alice_in_before = 1
alice_out_before = 2

alice_in_after = G.in_degree("Alice")
alice_out_after = G.out_degree("Alice")

print(f"\nAlice's degree changes:")
print(f"  In-degree:  {alice_in_before} -> {alice_in_after}")
print(f"  Out-degree: {alice_out_before} -> {alice_out_after}")

実行結果イメージ

上記を実行すると以下の出力が得られます。

=== 問1:グラフの構築と基本情報 ===
Nodes with attributes:
  Alice: role=admin
  Bob: role=user
  Charlie: role=user
  Diana: role=user
  Eve: role=user

Edges: [('Alice', 'Bob'), ('Alice', 'Charlie'), ('Bob', 'Charlie'), ('Bob', 'Diana'), ('Charlie', 'Diana'), ('Diana', 'Eve'), ('Eve', 'Alice')]
Number of nodes: 5
Number of edges: 7

In-degree and Out-degree:
  Alice: in_degree=1, out_degree=2
  Bob: in_degree=1, out_degree=2
  Charlie: in_degree=2, out_degree=1
  Diana: in_degree=2, out_degree=1
  Eve: in_degree=1, out_degree=1

=== 問2:中心的なユーザーの特定 ===
Most followed user: Charlie (followers: 2)
User following the most: Alice (following: 2)

=== 問4:追加操作(発展) ===
Adding edge: Alice -> Eve
Shortest path from Alice to Eve: Alice -> Eve
Path length (number of edges): 1

Alice's degree changes:
  In-degree:  1 -> 1
  Out-degree: 2 -> 3
  • 問3 の可視化は、実際に実行するとウィンドウにグラフが表示されます(Alice が赤、他が水色のノード、矢印付きの有向エッジ)。
  • 問4 では、Alice → Eve の直接エッジを追加したため、最短パスは Alice → Eve(長さ 1)になり、Alice の出次数が 2 → 3 に増えています。

総括

本日のまとめです。 そのままですが、本日の操作の延長線にネットワークなどのグラフ解析が存在しています。

  • 基本操作

    1. グラフ作成(Graph / DiGraph など)
    2. ノード・エッジの追加・削除(add_node, add_edge など)
    3. 属性付与(G.nodes[node][attr], G.edges[u,v][attr]
    4. 情報取得(ノード数・エッジ数・次数・隣接行列など)
    5. 可視化(nx.draw + matplotlib
  • 例題(SNS フォロー関係)で学べること

    • 有向グラフで「誰が誰をフォローしているか」を表現
    • ノード属性(例:role="admin")で役割を区別
    • 入次数・出次数から「フォロワー数」「フォロー数」を計算
    • 中心的なユーザー(最もフォローされている/している人)を特定
    • 有向グラフを矢印付きで可視化し、特定ノードを色分け
    • エッジ追加後の最短パス・次数変化を確認

アプリケーションのセキュリティを担保するWAF

Web上で自分の作ったアプリケーションを公開する場合、ネットは色んな攻撃通信がされ、常に脅威にさらされています。

社内などのネットワークであれば入口にあたるポイントにファイアウォールを導入します。 ただ、ここですべての攻撃を防げるわけではありません。 アプリケーション固有の脆弱性に対する攻撃が行われれる可能性があり、そんなアプリケーション固有の脅威から防御する手段となりえるのがWAF(Web Application Firewall)と呼ばれるものです。

本日テーマ:

WAFについて説明し、どんな種類で選定すべきかまとめてみる

概要

WAFは、Webアプリケーションに対する攻撃を検知・遮断するためのセキュリティサービス/製品です。

WAFの基本的な役割

  • WebサーバやWebアプリケーション(ECサイト、会員サイト、社内システムなど)の前段に配置し、HTTP/HTTPSの通信を監視します。
  • SQLインジェクション、クロスサイトスクリプティング(XSS)、OSコマンドインジェクション、ディレクトリトラバーサルなど、アプリケーション層の攻撃を検知・ブロックします。
  • 一般的なファイアウォール(ネットワーク層)やIPS(侵入防止システム)では防ぎきれない、アプリケーション固有の脆弱性を狙った攻撃を防ぐことを目的としています。

主な機能

  1. シグネチャ型防御
    既知の攻撃パターン(シグネチャ)と通信内容を照合し、一致した場合にブロックします。

  2. 異常検知・挙動ベース防御
    通常の利用パターン(ベースライン)から外れたリクエストを「異常」とみなし、ブロックや警告を行います。

  3. ホワイトリスト/ブラックリスト制御

    • ホワイトリスト:許可するIPアドレス・URL・パラメータなどを指定
    • ブラックリスト:拒否するIP・URL・パラメータなどを指定
  4. ボット対策
    悪意のあるボット(スクレイピング、アカウント乗っ取り試行など)を検知・制限します。

  5. DDoS対策
    アプリケーション層への大量リクエスト(レイヤー7 DDoS)を検知し、レート制限やブロックを行います。

  6. ログ管理・レポート
    攻撃の種類、頻度、送信元IPなどをログとして記録し、レポートやダッシュボードで可視化します。

ファイアウォールとの違い

WAFとファイアウォール(FW)は、どちらも「通信を制御してセキュリティを高める」という点では共通していますが、守る対象・レイヤー・判断基準が大きく異なります。

1. 守る対象の違い

  • ファイアウォール(FW)
    主にネットワーク全体を守るための装置・ソフトウェアです。
    例:社内ネットワークとインターネットの境界、部門間のセグメント分離など。

  • WAF(Web Application Firewall)
    Webアプリケーションそのものを守るための装置・サービスです。
    例:ECサイト、会員サイト、社内Webシステムなど、特定のWebアプリの前段に配置されます。

2. 動作するレイヤーの違い(OSI参照モデル)

  • ファイアウォール
    主にネットワーク層(レイヤ3)トランスポート層(レイヤ4) で動作します。

    • IPアドレス、ポート番号、プロトコル(TCP/UDP)などを見て、通信を許可・拒否します。
    • 「どのIPから、どのポート宛ての通信か」を基準にします。
  • WAF
    あくまでアプリケーション層(レイヤ7) で動作します。

    • HTTP/HTTPSの中身(リクエスト・レスポンス) を解析します。
    • URL、パラメータ、Cookie、HTTPヘッダ、フォーム入力値などを見て、攻撃パターンかどうかを判断します。

3. 判断基準の違い

  • ファイアウォール

    • 「IPアドレス 192.168.1.100 からの 80番ポートへの通信は許可する」
    • 「外部からの 22番ポート(SSH)への通信は拒否する」
      といった、通信の“宛先”と“送信元” を基準にします。
  • WAF

    • /login へのPOSTリクエストに ' OR 1=1 -- のような文字列が含まれていたらブロックする」
    • <script>alert('XSS')</script> のようなスクリプトタグがパラメータに含まれていたらブロックする」
      といった、通信の“内容” を基準にします。

4. 防げる攻撃の違い

  • ファイアウォールで防げるものの例

    • 不要なポートへの外部からのアクセス
    • 特定IPからの一方的なスキャン攻撃
    • ネットワークレベルのDoS攻撃(レイヤ3〜4)
  • WAFで防げるものの例

    • SQLインジェクション
    • クロスサイトスクリプティング(XSS)
    • クロスサイトリクエストフォージェリ(CSRF)
    • ディレクトリトラバーサル
    • アプリケーション層DDoS(レイヤ7 DDoS)

5. 配置位置のイメージ

  • ファイアウォール
    [インターネット] ←→ [ファイアウォール] ←→ [社内ネットワーク全体]

  • WAF
    [インターネット] ←→ [WAF] ←→ [Webサーバ / Webアプリケーション]

WAFは、ファイアウォールで守られたネットワークのさらに内側、Webアプリケーションの直前に配置されることが多いです。

比較一覧

項目 ファイアウォール(FW) WAF
守る対象 ネットワーク全体 Webアプリケーション
動作レイヤ 主にレイヤ3・4(IP・ポート) レイヤ7(HTTP/HTTPSの中身)
判断基準 IPアドレス、ポート番号、プロトコル URL、パラメータ、Cookie、HTTPヘッダなど
防ぐ攻撃 ポートスキャン、不正アクセス(ネットワーク層) SQLインジェクション、XSS、アプリ層DDoSなど

ということでファイアウォールとWAFは住み分けがされています。アプリ個々が持ってしまう脆弱性に対応することが出来る点、パケットの中身まで確認して遮断すべきかを判断できるという点が異なります。

両者は「どちらか一方で十分」というものではなく、ネットワーク全体を守るFWと、アプリケーションの中身を守るWAFを組み合わせることで、より強固な防御が可能になります。

提供形態

提供形態の種類

WAFの形態は3種類あります。

  • クラウド型WAF(SaaS型)
    DNSの向き先をWAF提供元に変更するだけで導入できる形態です。自社でサーバを用意せず、初期費用を抑えやすいのが特徴です。

  • オンプレミス型WAF(アプライアンス/ソフトウェア)
    自社のデータセンターに専用機器やソフトウェアを導入する形態です。ネットワーク構成の自由度が高く、既存のロードバランサやプロキシと連携しやすい場合があります。

  • ホスティング型/CDN連携型
    CDN(コンテンツデリバリネットワーク)事業者が提供するWAF機能を利用する形態です。Webサイトの高速化とセキュリティ強化を同時に実現できます。

選定

上記の3種類の中からコスト、導入しやすさ、カスタマイズ性、性能・スケーラビリティ、セキュリティ機能の5基準で比較します。

以下に、3種類のWAF提供形態を5つの基準で比較した表を示します。

評価基準 クラウド型WAF オンプレミス型WAF CDN連携型WAF
1. コスト(初期費用・運用コスト) 初期費用が低く、月額課金中心。運用負荷も比較的軽い。 △〜× 機器・ライセンス購入で初期費用が高く、運用・保守も自社負担になりがち。 初期費用は低め。CDN利用料とセットで、スケールに応じた課金が多い。
2. 導入しやすさ(導入工数・期間) DNS変更や簡単な設定で数日〜数週間で開始可能。 × 機器調達・ネットワーク設計・設定が必要で、数ヶ月かかることも多い。 CDN利用と同時にWAFを有効化でき、導入は比較的容易。
3. カスタマイズ性(ルール・ネットワーク制御の自由度) ベンダー提供の機能範囲内でのカスタマイズが中心。ネットワーク制御は限定的。 自社ネットワーク内に設置するため、ロードバランサ・FWなどとの連携や細かい制御が可能。 CDNプラットフォーム上での制御が中心。独自ルールはある程度可能だが、自由度は中程度。
4. 性能・スケーラビリティ(処理性能・拡張性・DDoS耐性) ベンダーの大規模インフラ上で動作し、トラフィック増加に自動対応。DDoS耐性も高いことが多い。 自社機器の性能上限があり、トラフィック増加時は機器増強が必要。DDoS対策は別途検討が必要な場合も。 グローバルCDN上で動作し、キャッシュと組み合わせて高スループット・高DDoS耐性を実現。
5. セキュリティ機能(防御機能の充実度・最新性) シグネチャ・挙動ベース・ボット対策など機能が充実。脅威情報の反映も比較的早い。 〇〜△ 機能は充実しているが、シグネチャ更新や運用チューニングを自社で行う必要がある。 CDN事業者の大規模な脅威インテリジェンスを活用し、最新の攻撃にも対応しやすい。

選定の目安

  • コスト重視・導入スピード重視
    → クラウド型WAF or CDN連携型WAFが有利です。

  • ネットワーク制御の自由度・特殊要件対応重視
    → オンプレミス型WAFが有利です。

  • 大規模トラフィック・DDoS対策重視
    → クラウド型WAF or CDN連携型WAFが有利です。

  • セキュリティ機能の最新性・運用負荷軽減重視
    → クラウド型WAF or CDN連携型WAFが有利です。

このように、用途(どう使うか)と価格(いくらかかるか)のバランスを見ながら、上記5つの基準で比較すると、自社に合ったWAFの提供形態を選びやすくなります。

WAFを導入するメリット

WAFを導入する主なメリットは、以下のように整理できます。

1. Webアプリケーション層の攻撃を直接防げる

  • ファイアウォールやIPSでは防ぎきれない、アプリケーション層の攻撃(SQLインジェクション、XSS、OSコマンドインジェクションなど)を検知・遮断できます。
  • Webサーバやアプリケーションの入口で防御するため、脆弱性が残っていても攻撃をブロックできます。

2. 脆弱性修正までの「仮の防御」として有効

  • Webアプリケーションの脆弱性修正には、開発・テスト・リリースに時間がかかることが多いです。
  • WAFは、修正が完了するまでの間、攻撃を一時的に防ぐ「仮の防御」 として機能します。
  • 特に、外部委託開発やレガシーシステムで修正が難しい場合に効果的です。

3. ゼロデイ攻撃や未知の攻撃にも一定の効果

  • シグネチャ型だけでなく、挙動ベースの防御(通常と異なるリクエストを検知)や機械学習ベースの異常検知を行うWAFも多く、未知の攻撃パターンにも対応しやすくなります。
  • ベンダー側で最新の脅威情報が反映されるクラウド型WAFでは、ゼロデイ攻撃への対応スピードが速い傾向があります。

4. DDoS攻撃(特にレイヤ7)への耐性向上

  • アプリケーション層への大量リクエスト(レイヤ7 DDoS)を検知し、レート制限やブロックを行うことで、Webサーバの負荷を軽減できます。
  • CDN連携型やクラウド型WAFでは、グローバルな分散インフラを活用して大規模DDoSにも強い構成が取りやすいです。

5. ボット対策・不正アクセス防止

  • スクレイピング、アカウント乗っ取り試行、クレデンシャルスタッフィングなど、悪意のあるボットを検知・制限できます。
  • 正常なボット(検索エンジンなど)との識別や、CAPTCHA導入などの機能も備えたWAFもあります。

6. コンプライアンス要件の達成に役立つ

  • PCI DSS(クレジットカード情報のセキュリティ基準)など、WAF導入が推奨または事実上必須とされる規制・ガイドラインがあります。
  • 監査対応やレポート作成の際に、防御策の一つとして明確に説明できるメリットがあります。

7. ログ・可視化によるセキュリティ状況の把握

  • 攻撃の種類、頻度、送信元IP、対象URLなどをログとして記録・可視化できます。
  • ダッシュボードやレポート機能により、自社のWebアプリケーションがどのような攻撃を受けているかを把握しやすくなります。
  • インシデント発生時の原因調査・再発防止策の検討にも役立ちます。

8. 運用負荷の軽減(特にクラウド型)

  • クラウド型WAFでは、ベンダー側でシグネチャ更新・インフラ管理・監視を担うため、自社の運用負荷を軽減できます。
  • セキュリティ専門チームが薄い組織でも、比較的少ない工数で高度な防御を実現しやすくなります。

総括

WAFの用途の本質

  • Webアプリケーションの「入口」で、HTTP/HTTPS通信を検査し、攻撃を検知・遮断する防御層です。
  • アプリケーションの脆弱性が残っていても、攻撃が到達する前にブロックする「仮の防御」 として機能します。
  • ネットワーク層の防御(FW/IPS)では防げない、アプリケーション層特有の攻撃(SQLインジェクション、XSSなど) を対象とします。

アプリケーションに求められるセキュリティ

  • WAFは追加の防御層であり、アプリケーション自体のセキュアコーディングや脆弱性対策の代替にはなりません
  • 入力値検証、出力エスケープ、権限管理、セッション管理、ログ出力など、基本的なセキュリティ対策をアプリケーション側で実装することが前提です。
  • WAF導入後も、定期的な脆弱性診断・修正・パッチ適用を継続する必要があります。

作成した成果物をインターネットを経由して提供するという場面において提供者には情報資産の安全を確保することが求められます。 本日説明したWAFは「入口での防御+可視化」、実際に作成するアプリケーションには「本体の堅牢化」を行うという役割分担が存在します。
両方を組み合わせることで、多層防御(Defense in Depth) を実現し、リスクを低減して、情報資産の安全を確保し、機能提供が実現できることになります。