論文の実装（STF-IDF：TF-IDFの改善）

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

#1.論文の内容
従来のtf-idf（下記式２）では以下の状況で重要な言葉が低くスコアづけされてしまう。
>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類似度でスコアリング対象のスコアを更新する重みづけ（係数）を計算する。
* スコアを更新する。これを所定回数繰り返す。


式１と３を眺めると、
>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位以内の周辺語）に、スコアの高いワードがあれば、（スコアリング対象のワードのスコアが低くても）スコアの高いワードが重みづけに使われる、というアイデア（と思う）。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/0e64e792-98b2-ed7b-3c38-265b69ce85d7.png)と、word2vecを利用したスコア更新後の![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/bcc53109-ea95-25ae-40b3-c796c5df4797.png)のcosine距離が近ければ（0に近い）、スコアの減少率が下がっていき、cosine距離が遠ければ最大1/2にされる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/0714e1e2-9d4e-5fec-6ba2-cc4e6186e04b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/09f2287b-05cd-3821-7c78-8c2e5e7e7965.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/385220/97ea98ef-a493-259e-75be-9698dec0046e.png)


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


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

```python
#@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.出力結果
適当な特許文献の要約文章（５０文献分）を取得。
tfidfで差が出そうなワードをピックアップしてスコア更新させてみる。

```python
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)

```

結果

```python
「 情報 」の初期スコア：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のスコアの分散や期待値が一定になっていくことを言ってるのか？
* 公式で出してくれると助かる（切実）。クラスの使い方はこれでいいのか・・・？