論文の実装(STF-IDF:TF-IDFの改善)


最近発見した論文
Semantic Sensitive TF-IDF to Determine Word Relevance in Documents https://arxiv.org/abs/2001.09896

1.論文の内容

従来のtf-idf(下記式2)では以下の状況で重要な言葉が低くスコアづけされてしまう。

As an example, if a text contains informal words related to a specific ethnic communities, or words that have been used in a specific period of time, but not in the whole corpus, due to some changes in cultural expressions,…

  • コーパス全体にはないが、特定の時期には多く出現する言葉

Another example is informal conversations regarding formal topics like medical communities that provide rich information for users. Censuring the general semantic context, TF-IDF
fails to detect the context-sensitive content which plays an important role in informal texts

  • 非公式テキストで重要な役割を果たすコンテキスト依存コンテンツ ⇒こちらの方がよくわからないが、公式な言葉に類する隠語や略語などを指しているか?

これらの問題を解決するための方法がSemantic Sensitive TF-IDF(STF-IDF)。

各ワードの初期スコアは通常のtfidfで計算。
下記のステップでスコアを更新していく。
* スコアリング対象のワードの、word2vecでの分散表現Aを取得し
* スコアリング対象のワードの近くに存在するワードを利用した分散表現(重みづけ後の分散表現B)を得る。
* 分散表現AとBのcosine類似度でスコアリング対象のスコアを更新する重みづけ(係数)を計算する。
* スコアを更新する。これを所定回数繰り返す。

式1と3を眺めると、

the mean embedding of of √n most relevant words from previous iteration are selected and the weighted mean embedding of them is calculated.The idea is that words with higher scores represent the context more than those which are less relevant. I

スコアリング対象のワードの近く(word2vecでの√n位以内の周辺語)に、スコアの高いワードがあれば、(スコアリング対象のワードのスコアが低くても)スコアの高いワードが重みづけに使われる、というアイデア(と思う)。
と、word2vecを利用したスコア更新後ののcosine距離が近ければ(0に近い)、スコアの減少率が下がっていき、cosine距離が遠ければ最大1/2にされる。

 

特許文章は技術トレンドを反映するので、前者に当てはまるパターンの文章が多いのと、文章の書き手(主に特許事務所単位での用語の使い方)の違いや特許用語と言われる偏りがあるので、後者のパターンもあるのでは?と思い、実際にtfidfの結果と比較してみたくなった。

2.コード

公式で出てるとありがたいと思いつつ、現状ないようだったのでsklearn.feature_extraction.text.TfidfVectorizer を基底クラスにして作成してみた。

#@title クラス
from sklearn.feature_extraction.text import TfidfVectorizer
import scipy
class StfidfVectorizer(TfidfVectorizer):
    """
    Parameters
    ----------
    corpus:スペース区切りのコーパス
    model:word2vecのmodel
    n:?√nでword2vecの分散表現上での類似語上位
    epoch:スコア更新回数
    word:更新対象の言葉   
    """


    def __init__(self,corpus,model,n,epoch,token_pattern,ngram_range):
        TfidfVectorizer.__init__(self)
        self.token_pattern=token_pattern
        self.ngram_range = ngram_range
        self._corpus = corpus
        self._model = model
        self._n = n
        self._epoch = epoch

    def update_score(self,word):
        X = self.fit_transform(self._corpus)
        wds = self.get_feature_names()
        for wd in wds:
            S[wd] = X[0, self.vocabulary_[wd]]
        print("「 {} 」の初期スコア:{}".format(word,S[word]))

        for ep in range(1,self._epoch):
            prev_S = S[word]
            weight_e = self.relevant_word(word,prev_S)
            S[word] = prev_S * 1/(
                             1+np.linalg.norm(e(wd))*np.linalg.norm(weight_e)*scipy.spatial.distance.cosine(self.e(word), weight_e)
                             )
        print("「 {} 」の最終スコア:{}".format(word,S[word]))


    def relevant_word(self,wd,prev_S):
        root_n = math.floor(math.sqrt(self._n))
        weightened_expected_value = 0 
        for i in range(1,int(root_n)+1):
            weight=\
            1/(1-self.all_weight_score(wd,root_n,prev_S))\
            *self.e(wd)
            weightened_expected_value += weight
        return weightened_expected_value/root_n


    def all_weight_score(self,wd,root_n,prev_S):
        results = self._model.wv.most_similar(positive=[wd])
        words =[w[0] for w in results]
        return prev_S/self.score_sum(prev_S,words)


    def e(self,wd):
        return model.wv[wd]

    def score_sum(self,prev_S,words):
        scoresum = 0
        for word in words:
            #print(word)
            try:
                scoresum += S[word]
            except:
                pass
        return scoresum

3.出力結果

適当な特許文献の要約文章(50文献分)を取得。
tfidfで差が出そうなワードをピックアップしてスコア更新させてみる。

n = 100
epoch = 5
word = "feed"

stf = StfidfVectorizer(corpus,model,n,epoch,token_pattern='(?u)\\b\\w+\\b',ngram_range=(1,1))
#words=["feed","a","the","compact","stowage","and","deployment"]
words=["情報","メモリ","データ","双方","アドレス","その","合否","論理"]
for word in words:
    stf.update_score(word)

結果

 情報 の初期スコア0.15420399669975438
 情報 」の最終スコア0.15421994437978057
 メモリ の初期スコア0.33566142190885806
 メモリ 」の最終スコア3.046892329557471e-06
 データ の初期スコア0.30437316763351085
 データ 」の最終スコア0.0003224964928717751
 双方 の初期スコア0.2933946423415207
 双方 」の最終スコア2.7044373368360145e-05
 アドレス の初期スコア0.22004598175614054
 アドレス 」の最終スコア0.0003747402986696245
 その の初期スコア0.1779017297307105
 その 」の最終スコア0.1779017297307105
 合否 の初期スコア0.14669732117076034
 合否 」の最終スコア0.001960090673521161
 論理 の初期スコア0.14669732117076034
 論理 」の最終スコア0.0017787494826049482

う~ん。「その」がほぼスコア変わっていないことや、これは重要だろうと思われるワードがすごく低く更新されて
しまっているので何かコードがおかしい気がする。もう少し考えることに。

4.よくわからなかった部分

  • 数式中のnはどこから出てきたのか?√nはword2vec上の関連度上位の件数との説明があるけど。論文に説明がなく困る。その分野なら自明なのか??
  • 論文の後ろの方の議論は何なのか?分散表現を使ってワードの置き換えを繰り返すことにより、tfidfのスコアの分散や期待値が一定になっていくことを言ってるのか?
  • 公式で出してくれると助かる(切実)。クラスの使い方はこれでいいのか・・・?