arxivデータを利用したトピック解析

# この記事の目的

趣味で進めていた、arxivの論文データの解析の紹介です。
トピック解析を試してみたい人、arxiv論文の流行りの変遷の解析などに興味がある人を対象としています。
得られたデータの可視化は別の記事で投稿する予定です。→投稿しました！[arxivデータを利用したトピック解析：可視化](https://qiita.com/hnakatsuka/items/b01e2da86bc9e50bb607)


# arxivデータとは

arxivとは、世界最大の論文のプレプリントサーバーであり、現在では多くの論文が無償で公開されている。arxivはapiを提供しており、python環境からでも簡単に論文の書誌データ(タイトル、著者、アブストなど)を得ることができる。またデータセットも公開されているので、自然言語処理で大量のデータを扱ってみたい場合に良い対象になる。

参考にした記事様

- Kaggle, Arxiv metadata exploration by Andrew Lukyanenko
- LDA学習：[LDAとそれでニュース記事レコメンドを作った。](http://tdual.hatenablog.com/entry/2018/04/09/133000#%E5%AE%9F%E8%B7%B5)
- LDAの可視化:[LDAによるトピック解析 with Gensim](https://qiita.com/Spooky_Maskman/items/0d03ea499b88abf56819)


# 今回使った手法

トピック解析とは、教師なし学習の一種である。
複数の文章を入力データとして与えたときに、それらを単語の頻度などを元にクラスタリングして、適切な分類を提示することが目的となる。
例えば、ニュース記事の分類やレコメンドなどに用いられる。

特に今回はLDA(Latent Dirichlet Allocation)というトピックモデルに基づいた手法を利用して、解析を行う。これは簡単に言うと、出現単語に応じて文章中の`トピック`を学習していき、文章がどのトピックに属しているかを確率的に判定するモデルである。
入力データとして、過去30年分のarxivの abstract 情報を用いる。全分野のデータを用いるのは負荷が重いので、ひとまず自身で結果を判定できる`hep`(high energy physics)のみに絞って解析を行う。

# データの前処理

トピック分析をする上で、トピックに関係ないデータは解析の邪魔になる。特に、abstractでは数式や数値がそのまま乗っているので、それらはトピックモデルの精度を損なう要因になる。
そのため、適切な前処理を行い、データを解析可能な形に整理する必要がある。

次のような前処理を行った。内容としては、urlの除去、改行コード(`\n`)の除去、stop words(`a`や`the`など文章に関係ない文字)の除去、記号の除去を行った。
以下、参考のコード

```
def tokenize(text):
　　　　　#URL
        text = re.sub(r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+", "", text)   
        text = re.sub(r"\"?([-a-zA-Z0-9.`?{}]+\.jp)\"?" ,"", text)  # xxx.jp
        
        #数字もおそらく学習の邪魔なので消す。
        text = re.sub(r"[0-9]" ,"", text)  # xxx.jp   
        text = text.lower()

　　　　　#改行コードの除去
        text = text.replace( '\n' , ' ' )
        
        #remove stop words
        for i in text.split():
            if i in stop:
                my_regex = r"\b" + re.escape(i) + r"\b"
                text =  re.sub(my_regex,"", text)
        
        table=str.maketrans('','','"?!#$%&\'()*+,./:;<=>[\\]^_`{|}~', )
        text = text.translate(table)
        
        #- は合成語に使われるのでスペースを開ける。
        table=str.maketrans('-',' ','', )
        text = text.translate(table)
        
        return text
```

注意が必要なのは、単純にfor文で前処理を掛けると激遅なので、Pythonが得意な方法に書き直したほうがいいということ。今回は一つ一つのabstructが長めで、かつ30年分の書誌情報(=302439件)なので処理が遅いと終わらない。例えば、numpyを使う以下の方法だと早くなる。

```
#前処理の関数化
utokenize = lambda x : tokenize(x)
cleaned = np.frompyfunc(utokenize, 1, 1)(make_abst_list)
```

# 辞書の構成

学習を行う前に、`単語のベクトル化`という処理が必要になる。これは、機械学習ライブラリに単語をそのまま入れても学習してくれないので、一旦数値のベクトルに直して機械学習で処理できる形に直すというもの。今回は`gensim`ライブラリに付いている辞書化の関数を利用する。
まず、単語を`id`に変換する。

```
#前処理済みの書誌情報を単語を分割してdocsに保存
docs = []
   for text in cleaned:
   docs.append(text.split())
#単語をベクトルに変換
mydict = corpora.Dictionary(docs) 
```

このあと、低頻度語の除去なども`gensim`の備え付けの関数で実行できる。これができると、単語をいわゆる`bag-of-words`と呼ばれるベクトルに簡単に変換できる。これにより、文章を機械学習ライブラリが認識できる形に変換できた。

```
# copusの作成
corpus = [mydict.doc2bow(w) for w in docs]
```

# LDAによる学習

ここまで来ると、いよいよLDAで学習を実行するだけである。
学習を行う際、マシンスペックを最大に活かすには、`sigle core < multi-core < GPU`の活用を考えるべきである。手元の使用マシンはintel i7(4 core)なので、とりあえずmulti-core での実行を試みた。(今回の処理では8threadsで10分といったところ)
このとき、LDAではトピック数を最初に指定する必要がある。hep全体の30年分ということで、多めに50 topicを最初は指定してみる。

```
lda_model = models.ldamulticore.LdaMulticore(corpus=corpus,workers=NUM_CORES, id2word=mydict, num_topics=50, passes=10)
```

学習の進み具合は、例えば`perplexity`という量などを使うと評価できる。これは、簡単に言うと、トピックに対してモデルが提案する単語群がどれくらい多いかを表す。完全にランダムだと全文章に含まれる単語の数($N\sim 10^6$)になるが、今回はperplexityが200程度まで下がったので、よく学習されたモデルだとわかる。

# 学習の確認

例えば、次のabstructを入れてみる。(以下では参考として30 topicで学習した結果を使った。)

```
(C. Balazs, E. L. Berger, P. M. Nadolsky, C.-P. Yuan 2007)
' A fully differential calculation in perturbative quantum chromodynamics is\npresented for the production of massive photon pairs at hadron colliders. 
...
Distributions of the diphoton pairs from the decay of a Higgs\nboson are contrasted with those produced from QCD processes at the LHC, showing\nthat enhanced sensitivity to the signal can be obtained with judicious\nselection of events.\n'
```
おそらく加速器周りの散乱の計算の論文だと思われる。
LDAモデルを用いて、この文章がどのトピックに属するかを計算する、

```
topics = sorted(lda_model.get_document_topics(corpus[0]), key=lambda t:t[1], reverse=True)

4: 0.7309901714324951
22: 0.23881538212299347
18: 0.019959058612585068
```
ここで出力の見方は`4`番目のtopic に属する確率が`0.73`という意味である。つまり、このモデルは、上のabstructはTopic4に属すると判定している。

ではこの、Topic4を表す単語を見ていくと次のようになる。

```
Topic #  4
cross: 0.028737593442201614
section: 0.016772201284766197
production: 0.01603512279689312
leading: 0.015768470242619514
qcd: 0.015700438991189003
order: 0.014560075476765633
corrections: 0.013140544295310974
sections: 0.012774926610291004
scattering: 0.012026742100715637
next: 0.011518764309585094
```

確かに、`cross section`(散乱断面積)や`QCD`などといったワードが出てくるあたり、正しくabstructの文意に沿った分類ができてそうだと伺える。


#  次の予定

いかがでしたでしょうか。詳しいコードの内容は、最初に挙げた参考文献などを見るとわかりやすいかと思います。元気があれば、今回のコードもgithubで公開します。
次は、せっかく分類モデルができたので、可視化と時系列解析を行うつもりです。結果は別記事にまとめる予定です。