見出し画像

NMFでとらえるデータの構造


こんにちは。電通デジタルのエンジニアの市川です。

普段の業務ではデータ分析・機械学習案件等に携わっており、その中で「NMF(Non-negative Matrix Factorization:非負値行列因子分解)」に触れる機会がありました。NMFは、レコメンドやトピック抽出などで古くから活用される有名なアルゴリズムですが、「名前は聞いたことがあるけれど、仕組みまではよく分からない」という方も多いのではないでしょうか。そこで今回は、NMFの基本的な考え方から実際の活用シーンまでを、整理も兼ねてまとめてみたいと思います。2026年2月時点での情報をもとに執筆しています。
(本記事には株式会社電通デジタルの宣伝内容を含みます。)

目次
1.NMFとは
2.NMFを用いる利点
3.実際にNMFでデータを抽出してみる
4.まとめ

1.NMF とは

NMF(Non-negative Matrix Factorization)は、元の非負値行列Vを、2つの非負行列W(特徴行列)とH(重み行列)の積に近似する手法です。仮に今m ✕ nの非負値行列Vがあったと仮定すると、NMFではこれを非負値行列であるV(m ✕ r)とH(r ✕ n)の積に分解します。

このように元データを意味のある部品として分解し、足し合わせで表現することができます。

2.NMFを用いる利点

他の特徴量分解手法と比較して、非負値のみで表現されるNMFは、抽出されたトピックの解釈性が高いといわれています。例えば、ある商品の口コミからどのような意見が含まれているかを分析したいとします。口コミには、利便性について言及したものや、商品の色やデザインに関するものなど、複数のトピックが含まれています。先ほどの式にこの口コミデータの結果を当てはめて考えてみます。例えば、口コミデータを「文書×単語」の行列 V とすると、NMFではこれを

と分解します。ここで、V は各口コミにおける単語の出現頻度を表す行列、W は「文書×トピック」の行列で各口コミがどのトピックをどの程度含むかを表し、H は「トピック×単語」の行列で各トピックがどの単語と強く結びついているかを表します。つまり、H の各行を見ることで「利便性に関する単語群」「デザインに関する単語群」といったトピックの意味を把握でき、W の各行を見ることで各口コミがどのトピックの組み合わせで構成されているかを理解できます。

NMFでは、各口コミはこれら複数のトピックの「加法的な組み合わせ」として表現されます。つまり、ある口コミは「利便性トピックが0.6、デザイントピックが0.4」といった形で表現されます。このように、マイナスの寄与を含まず、トピックの重み付けによって文書を説明できるため、各トピックの意味や文書との関係を直感的に理解しやすく、解釈性が高いとされています。

NMFと似たものとして、文書×単語行列を低ランク近似する次元削減手法に特異値分解(SVD)があります。SVDは、テキスト分析においても潜在意味解析(LSA)などで広く用いられている代表的な手法であり、行列を直交基底に基づく低次元空間へ射影することで情報を圧縮します。この方法は再構成誤差を最小化するという理論的に明確な性質を持ち、効率的な次元削減が可能です。SVDでは分解後の行列に正負の値が含まれるため、あるトピックに対して「正に寄与する単語」と「負に寄与する単語」が同時に現れることがあります。そのため、解釈の際には「ある概念を足し合わせる」というよりも「ある方向への増減」として理解する必要があります。これに対しNMFも同様に低ランク近似を行う次元削減手法ですが、非負制約のもとで分解を行う点が特徴です。その結果、各トピックは単語の非負の重み付き集合として表され、各文書もトピックの加法的な組み合わせとして表現されます。したがって、再構成精度という観点ではSVDに分がある場合もありますが、解釈性やユースケースへの適用という観点では、NMFのほうがトピックの内容や文書との対応関係を把握しやすいという利点があります。


3.実際にNMFでデータを抽出してみる

ここまでNMFについて簡単に説明してきました。次に実データに対してNMFを適用してみます。今回は「Dentsu Digital Tech Blog」に過去掲載されていた121件の記事のタイトルを抜いた文章データを対象に、どのような話題が過去になされてきたのかNMFで簡易的に分析してみました。まず始めに121件の文章データについて、データの前処理を行います。今回は簡略化のために日本語の名詞のみを解析対象としました。

# -------------------------  

# 1. データ読み込み  

# -------------------------  

with open(”dd_techblog_title_body.json”, ”r”, encoding="utf-8") as f:  

data = json.load(f) 

 

df = pd.DataFrame(data)  

df = df[df["body"].notna()].copy() 

 

# -------------------------  

# 2. 前処理: HTML/コード/英数字などを削除し、日本語のみ抽出  

# ------------------------- 

 

df = pd.DataFrame(data)  

df = df[df["body"].notna()].copy() 

 

 

# -------------------------  

# 2. 前処理: HTML/コード/英数字などを削除し、日本語のみ抽出  

# ------------------------- 

 

RE_TAG = re.compile(r"<[^>]+>")  

RE_CODE_FENCE = re.compile(r"```.*?```", re.DOTALL)  

RE_INLINE_CODE = re.compile(r"`[^`]+`")  

RE_URL = re.compile(r"https?://\S+")  

RE_ENTITY = re.compile(r"&[a-zA-Z]+;")  

RE_NON_JA = re.compile(r"[^ぁ-んァ-ン一-龥ー々\s]") # 日本語以外を空白へ(記号・英数など) 

 

def clean_text(text: str) -> str: 

 

text = str(text)  

text = RE_CODE_FENCE.sub(" ", text) # ``` ``` のコードブロック削除  

text = RE_INLINE_CODE.sub(" ", text) # `inline code` 削除  

text = RE_TAG.sub(" ", text) # HTMLタグ削除  

text = RE_URL.sub(" ", text) # URL削除  

text = RE_ENTITY.sub(" ", text) # &nbsp; 等  

text = RE_NON_JA.sub(" ", text) # 日本語以外を除去  

text = re.sub(r"\s+", " ", text).strip()  

 return text 

 

df["body_clean"] = df["body"].apply(clean_text) 

 

# -------------------------  

# 3. 形態素解析(名詞中心 + ストップワード)  

# -------------------------  

tokenizer = Tokenizer() 

 

custom_stopwords = set([ "する","なる","ある","いる","みる","使う","できる", "思う", 

"考える","まとめ","方法","今回","これ","それ", "ため","よう","もの","こと", 

"ところ","とき","場合", "さん","など","そして","また","一方", ]) 

 

 

def tokenize_ja_nouns(text: str) -> str:  

tokens = []  

for t in tokenizer.tokenize(text):  

  pos = t.part_of_speech.split(",")[0]  

  base = t.base_form # 名詞だけ  

  if pos != "名詞":  

    continue # 1文字やストップワード除外  

  if len(base) <= 1:  

    continue  

  if base in custom_stopwords:  

    continue  

  tokens.append(base)  

return " ".join(tokens) 

 

df["body_tokenized"] = df["body_clean"].apply(tokenize_ja_nouns) 

 

df = df[df["body_tokenized"].str.len() > 0].copy() 

前処理によって、各記事を名詞の集合として整理することができました。しかし、このまま単純な単語の出現回数を用いてNMFを適用すると、「データ」や「分析」といった多くの記事に共通して現れる単語が強く影響し、トピックの意味が曖昧になってしまう可能性があります。

そこで本ブログでは、各記事にとって特徴的な単語を強調するためにTF-IDF(Term Frequency–Inverse Document Frequency)を用いて文書をベクトル化します。TF-IDFを用いることで、全体で頻出する一般的な単語の影響を抑えつつ、特定の記事群に特徴的な語をより強く反映させることができます。これにより、NMFによるトピック抽出の解釈性の向上を目指します。

# -------------------------  

# 4. TF-IDF 

# ------------------------- 

 

vectorizer = TfidfVectorizer(  

    max_df=0.90,  

    min_df=3,  

    max_features=8000,   

    ngram_range=(1, 2),  

    )  

X = vectorizer.fit_transform(df["body_tokenized"]) 

次にTFIDFで集められたテキストデータに対して、NMFを適用します。NMFにおけるトピック数(K)の決定には明確な正解があるわけではなく、再構成誤差(reconstruction error)の推移やトピックの解釈可能性などを参考にしながら決めるのが一般的です。再構成誤差とは、元の文書×単語行列 VVV と、分解によって得られた WWW と HHH の積 WHWHWH との差の大きさを表す指標であり、値が小さいほど元のデータをよく近似できていることを意味します。実務上は、いくつかのKを試し、再構成誤差の減少幅や得られたトピックの意味の妥当性を確認しながら選択することが多いです。今回は試しに6つのトピックを対象にNMFを適用してみます。

# -------------------------  

# 5. NMF 

# ------------------------- 

 

n_topics = 6  

nmf = NMF(n_components=n_topics, random_state=42)  

W = nmf.fit_transform(X)  

H = nmf.components_ 

最後に特徴量分解された各トピックを見てみます。

# -------------------------  

# 6. トピック表示 

# ------------------------- 

 

def print_topics(H, feature_names, n_top_words=12):  

for topic_idx, topic in enumerate(H):  

    top_words = [feature_names[i] for i in topic.argsort()[:-n_top_words - 1:-1]]  

    print(f"Topic {topic_idx}: {' | '.join(top_words)}")  

print("=== トピック ===")  

print_topics(H, vectorizer.get_feature_names_out()) 

下記が抽出された各トピックの単語群になります。

=== トピック ===
【Topic 0】
データ | 分析 | タグ | 計測 | 移行 | 広告 | 設定 | 検索 | イベント | ユーザー | データセット | ページ
【Topic 1】
モデル | 予測 | 学習 | データ | 特徴 | 機械 学習 | 機械 | 出力 | 購買 | モデル 学習 | 精度 | 曲線
【Topic 2】
インターン | 開発 | スクラム | 業務 | チーム | 参加 | プロダクト | 研修 | エンジニア | 課題 | 技術 | 時間
【Topic 3】
ファイル | 実行 | タスク | 設定 | 以下 | 利用 | 定義 | コード | アカウント | 必要 | 作成 | 関数
【Topic 4】
因果 | 推定 | 変数 | 観測 | 効果 | 介入 | モデル | 係数 | 因果 探索 | 探索 | 因果 効果 | アプローチ
【Topic 5】
検定 | 仮説 | 有意 | 仮説 検定 | サンプル サイズ | テスト | 分布 | 効果 | 統計 | 有意 水準 | 水準 | 棄却

分解されたトピックに含まれる単語群を見てみると、大まかに各トピックは

【Topic 0】
計測・タグ設計・分析運用(広告/イベント/データセット/ページ)
【Topic 1】
予測モデル・機械学習(予測/学習/特徴/精度/購買)
【Topic 2】
チーム開発・インターン体験(インターン/スクラム/研修/チーム/業務)
【Topic 3】
実装手順・設定・タスク実行(ファイル/実行/設定/コード/関数/アカウント)
【Topic 4】
因果推論・効果推定(因果/推定/介入/効果/変数/係数)
【Topic 5】
仮説検定・統計的判断(検定/仮説/有意/分布/サンプルサイズ/棄却)

に分かれているように見えます。各トピックごとに代表的な記事を3つほど見てみると下記のようになっていました。

【Topic 0】
- BigQueryデータクリーンルームを活用してセキュアにデータを共有する
- GA4 と Google Search Consoleデータをつなげた分析
- Universal Analyticsが2023年の夏に終了します

【Topic 1】
- SageMaker AutopilotをSDKを使って実行する
- AIを理解する技術ーSHAPの原理と実装ー
- Amazon SageMaker CanvasとDataRobotを比較してみた

【Topic 2】
- 電通デジタル就業型インターンの参加記録(2021年2月期エンジニアコース 渋谷)
- 電通デジタルのインターンに参加してみた(2020年12月期エンジニアコース 中山)
- 電通デジタルインターン参加記録(2020年12月期エンジニアコース 片瀬)

【Topic 3】
- AirflowでADHとBigQueryのクエリを実行する方法
- Go Protocol Buffer Message API V2 のReflectionとgRPC Server-side Interceptorを使ってAPIの呼び出し権限チェックを実現する
- 2020年に作ったDevOps内製ツール

【Topic 4】
- Google Colabで統計的因果探索手法BMLiNGAMを動かしてみた
- Google Colabで統計的因果探索手法LiNGAMを動かしてみた
- 時系列性を考慮した因果探索手法VAR-LiNGAMの紹介

【Topic 5】
- 非劣性検定を用いたA/Aテストの方法
- 統計的仮説検定における多重検定の問題とその対処法
- 統計的仮説検定における効果量の概念と必要サンプルサイズの算出

記事タイトルを見ても概ねトピックが分類できていそうです。


4.まとめ

今回はNMFの仕組みについての簡単な解説と、実データを使った特徴量分解をしてみました。NMFは派手さはないものの、解釈可能性の高さからとても扱いやすい手法だと感じています。

ご覧下さりありがとうございました。