因子分析で斜交回転すると累積寄与率が1を超える
1. 解決したいこと
学習済み Doc2Vec の 文章ベクトルを 因子分析しています。
複数のプログラムで試したのですが、いずれも累積寄与率が1を超える場合がありました。
原因の解明をお手伝いいただけませんでしょうか?
2. 発生している問題
python の factor-analyzer
モジュール (https://pypi.org/project/factor-analyzer/)
および、 Rの標準関数 factanal
の2つでそれぞれ因子分析を行いました。
どちらも次の条件下で、「累積寄与率が1を超える」という問題が発生しました。
累積寄与率とは、各因子が全体のどれだけの割合を説明できているかを表す値なので、1を超えることは通常起こりえないはずです。
項目 | 値 |
---|---|
データ | 後述する 300項目、サイズ1135267のデータ |
データの前処理 | 各項目で平均値0, 標本標準偏差(ddof=1)1に標準化 |
因子軸回転方法 | promax(斜交回転) |
因子数 | 100 |
参考: 問題が発生しない条件
- 因子軸回転方法を varimax(直交回転) にすれば、因子数に拘わらず、問題は発生しませんでした。
- 因子数が充分小さければ、因子軸回転方法に拘わらず、問題は発生しませんでした。
(参考 終わり)
2-1. データについて
データは 1135267行300列の numpy
行列 "jawiki.doc2vec.dmpv300d.model.docvecs.vectors_docs.npy"
です。(カレントディレクトリに保存)
中身は300次元の学習済み doc2vec 文章ベクトルたちです。
具体的には https://yag-ays.github.io/project/pretrained_doc2vec_wikipedia/ の dmpv300d を利用しました。
上記モデルでは 1135267 個の 日本語版 Wikipedia 記事 を学習していました。
データは、各次元ごとに標準化して利用しました。
2-2. 目的
(質問と本質的に関係ないので折りたたみました)
文章ベクトルに対して因子軸を取り出すことで、「基底」となる文章を取り出す目的で、今回因子分析を行いました。
例えば、 因子数が 2 で、各因子軸に最も近い文章ベクトルが $\overrightarrow{正義}$ と $\overrightarrow{悪}$ であり、累積寄与率が0.6 だった場合、任意の文章 は 実数$a,b$ を用いて、$a \overrightarrow{正義} + b \overrightarrow{悪}$ により、60% 程度説明がつくのではないかと考えた次第です。
3. 問題の起きるソースコード
まず、 python の factor-analyzer
モジュールで当該の問題が起こりました。
同モジュールのバグの可能性を疑い、別の環境としてRの標準関数 factanal
でも同じ実験を行ったのですが、同様の問題が発生しました。
3-1. factor-analyzer
モジュールの場合
import numpy as np
vecs = np.load("jawiki.doc2vec.dmpv300d.model.docvecs.vectors_docs.npy")
assert vecs.shape == (1135267, 300)
def standardize(v):
v = np.array(v)
v_mean = v.mean(axis=0, keepdims=True)
v_std = v.std(axis=0, keepdims=True, ddof=1)
return (v - v_mean) / v_std
vecs = standardize(vecs)
# np.mean(vecs[:,0]) は-2.9144826e-06,
# np.std(vecs[:,0],ddof=1) は 1.0004338 だった
from factor_analyzer import FactorAnalyzer
fa = FactorAnalyzer(n_factors = 100, rotation="promax")
fa.fit(docvecs_std)
cumulative_vars = fa.get_factor_variance()[2]
# [0]が 負荷量二乗和, [1]が寄与率, [2]が累積寄与率
cumulative_var = cumulative_vars[-1]
# 最後の因子における累積寄与率
print(cumulative_var)
結果
1.7611056920601675
3-2. factanal
関数の場合
import numpy as np
vecs = np.load("jawiki.doc2vec.dmpv300d.model.docvecs.vectors_docs.npy")
assert vecs.shape == (1135267, 300)
def standardize(v):
v = np.array(v)
v_mean = v.mean(axis=0, keepdims=True)
v_std = v.std(axis=0, keepdims=True, ddof=1)
return (v - v_mean) / v_std
vecs = standardize(vecs)
from rpy2 import robjects
from rpy2.robjects.conversion import py2rpy
from rpy2.robjects import numpy2ri
numpy2ri.activate()
R = robjects.r
def numpy2r(npy, name):
R.assign(name, py2rpy(npy))
R.assign_numpy = numpy2r
R.assign_numpy(vecs, "data") # R の "data" 変数に vecs を入れる
R("factanal_result <- factanal(x=data, factor=100, rotation='promax')")
R("sink('output.txt')")
R("print(factanal_result)")
R("sink()")
# output.txt に 因子分析結果を出力
結果
Factor100
SS loadings 1.743
Proportion Var 0.006
Cumulative Var 1.593
累積寄与率が 1.593 となっています。
4. 自分で考えた原因
- そもそも累積寄与率は1を超えても問題ない?
-
factor-analyzer
やfactanal
から累積寄与率を正しく取得できていない? - 300項目、1135267サイズ、あるいは100因子が大きすぎる?
計算中の丸め誤差などが積み重なり、累積寄与率が実際と異なる数値になっている?
5. 参考サイト
-
factor-analyzer
モジュールで累積寄与率を得る方法は
https://corvus-window.com/python_factor-analysis/ から知りました -
factanal
関数の使い方は
https://qiita.com/yoshd/items/e322eecfb66ad5cd1a0a を参考にしました。 - 累積寄与率について
https://bellcurve.jp/statistics/glossary/739.html