17ec084
@17ec084 (智剛 平田)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

因子分析で斜交回転すると累積寄与率が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 に 因子分析結果を出力

結果

output.txt (一部抜粋)
               Factor100
SS loadings        1.743
Proportion Var     0.006
Cumulative Var     1.593

累積寄与率が 1.593 となっています。

4. 自分で考えた原因

  1. そもそも累積寄与率は1を超えても問題ない?
  2. factor-analyzerfactanal から累積寄与率を正しく取得できていない?
  3. 300項目、1135267サイズ、あるいは100因子が大きすぎる?
    計算中の丸め誤差などが積み重なり、累積寄与率が実際と異なる数値になっている?

5. 参考サイト

0

1Answer

こんにちは。

その道のプロではありませんが、
因子数は多すぎる気がします。

標準化処理をした後、小さな誤差が積みあがり、
累積寄与率をバグらせているのではと思いました。

0Like

Your answer might help someone💌