tf-idfをWikiepdiaダンプファイルからMeCabで計算--EC2(CentOS)で--

# はじめに
次の記事
[nora(野良)-idf-dic](https://github.com/GINK03/tiny-japanese-wikipedia-tfidf-dic-generator)
を参考にさせていただき、EC2(CentOS)で、WikiepdiaダンプファイルからMeCabによって単語のidf値をJsonファイルに出力させました。

# EC2(CentOS)でMeCabを使えるようにする

作業用ディレクトリ作成
```
mkdir ~/mytmp
```
python3.7インストール
```
sudo yum install python37 –y
```

git、gcc、g++、cmakeインストール
```
sudo yum install -y git
sudo yum install gcc gcc-c++
cd ~/mytmp
wget https://cmake.org/files/v3.12/cmake-3.12.3.tar.gz
tar zxvf cmake-3.12.3.tar.gz
cd cmake-3.12.3/
sudo ./bootstrap
sudo make
sudo make install
```

MeCabインストール
```
cd ~/mytmp
wget 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE' -O mecab-0.996.tar.gz 
tar zxvf mecab-0.996.tar.gz
cd mecab-0.996
sudo mkdir -p /usr/bin/mecab
sudo ./configure --prefix=/usr/bin/mecab --with-charset=utf8 --enable-utf8-only
echo "export PATH=/usr/bin/mecab/bin:\$PATH" >> ~/.bashrc
sudo make
sudo make install
source ~/.bashrc
mecab-config --libs-only-L | sudo tee /etc/ld.so.conf.d/mecab.conf 
sudo ldconfig
```

MeCabインストール先確認
```
which mecab
（/usr/bin/mecab/bin/mecab）
```

mecab-python3インストール
```
pip3 install mecab-python3
```

leveldb(Googleが作った高速なkey-valueDB)、plyvelインストール
```
pip install plyvel
cd ~/mytmp
git clone https://github.com/google/leveldb.git
cd leveldb
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build .
```

Neologd(MeCab 用辞書)インストール
```
git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
cd mecab-ipadic-neologd
./mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -y
```
Neologdインストール先確認
```
echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
（/usr/bin/mecab/lib/mecab/dic/mecab-ipadic-neologd）
```
テスト
```
echo "私は学生です。" | mecab -d /usr/bin/mecab/lib/mecab/dic/mecab-ipadic-neologd
```

# IDF計算プログラム
###分かち書きデータの保存###
Wikipediaダンプデータの各記事の分かち書きを、LevelDBへの保存します。
分かち書きはMeCabを使い、正規化のために単語の原型を採用しています。

```my_wakati.py
# coding: utf-8
from __future__ import print_function
import os
import sys
import re
import xml.etree.ElementTree as et
import MeCab
from collections import Counter 
import json
import math
import pickle
import plyvel
import time
import unicodedata

def _my_wakati_split(mecab,text):
  parsed_split = []
  node = mecab.parseToNode(text)
  while node:
    word = node.surface
    genkei = node.feature.split(",")[6]#正規語句
    if(genkei == '*'):#正規語句がない場合
      genkei = word
    if(genkei):
      genkei = unicodedata.normalize('NFKC', genkei)#英数字記号半角・カタカナ全角
      genkei = genkei.lower()#小文字英字
      parsed_split.append(genkei)
    node = node.next
  return parsed_split

start = time.time()
db = plyvel.DB('title_context.ldb', create_if_missing=True)
mecab = MeCab.Tagger ("-Owakati -d /usr/bin/mecab/lib/mecab/dic/mecab-ipadic-neologd")
cursol = None#記事タイトル
count  = 0#記事のカウント
for i, (event, elem) in enumerate(et.iterparse('./jawiki-20230220-pages-articles.xml')):
  if count%2500 == 0:
    print("now docs %d"%count)
    print(cursol)
  if 'title' in elem.tag:
    cursol = elem.text.replace('\n', '')
    continue
  if 'text' in elem.tag:
    count += 1
    if db.get(bytes(cursol, 'utf-8')) is not None:
      continue
    if elem.text == None:
      continue
    text = elem.text.replace('\n', '')
    for remove in [re.compile(x, re.MULTILINE) for x in  [r'\(.*\)', r'\[\[', r'\]\]', r'<.*?>', r'\[.*?\]', r'{{.*?}}']]:
      text = re.sub(remove, '', text)
      parsed_split = _my_wakati_split(mecab,text)
      if len(parsed_split) == 0:
        continue
      cursol = unicodedata.normalize('NFKC', cursol)
      cursol = cursol.lower()
      db.put(bytes(cursol, 'utf-8'), pickle.dumps(parsed_split) )
  #if count == 2500:#テスト用
  #  break
  elem.clear()
print("total len=%d"%i)
end = time.time() - start
print(f"処理時間：{end}")
```

###IDF値Jsonファイルの作成###
LevelDBに保存された分かち書きデータから、全単語のidf値を計算して、Jsonファイルに保存します。
```my_save_idf.py
# coding: utf-8
from __future__ import print_function
import os
import sys
import re
import xml.etree.ElementTree as et
import MeCab
from collections import Counter 
import json
import math
import pickle
import plyvel
import time
import unicodedata

db_source = plyvel.DB('title_context.ldb', create_if_missing=False)
maxdocs = 1
title_bow = {}
words_freq = {}
for title, serialized in db_source:
  maxdocs += 1
  context = pickle.loads(serialized)
  title_bow.update({ title:dict(Counter(context)) } )
  for word in set(context):
    if words_freq.get(word) is None: words_freq[word] = 0.
    words_freq[word] += 1.
  if maxdocs%10000 == 0:
    print("now loading... at %d"%maxdocs)
print("total words len = %d"%len(words_freq))

words_idf = {}
for e, (word, freq) in enumerate(words_freq.items()):
  if e%1000 == 0:
    print("now evaluating... at %d/%d"%(e,len(words_freq)))
  weight = math.log( float(maxdocs) / freq )
  words_idf[word] = math.log( float(maxdocs) / freq )
open('words_idf.json', 'w').write(json.dumps(words_idf))
```


# IDF計算手順

１．Wikipediaのダンプデータのダウンロードと展開
```
wget https://dumps.wikimedia.org/jawiki/20230220/jawiki-20230220-pages-articles.xml.bz2
bunzip2 jawiki-20230220-pages-articles.xml.bz2
```
（最新データのURLは次を参照：https://dumps.wikimedia.org/jawiki/ ）
これで、「jawiki-20230220-pages-articles.xml」のデータファイルができます。

２．各記事の分かち書きデータをLevelDBへ保存
展開した「jawiki-20230220-pages-articles.xml」と同じディレクトリ内に、添付の「my-nora-idf-dic.py」を置き、次を実行します。
```
python3 my_wakati.py
```
（c5 largeで約40分）
これで、各記事の分かち書きデータが入った「title_context.ldb」というディレクトリができます。

３．idf辞書の作成
続いて、次を実行します。
```
python3 my_save_idf.py
```
（c5 largeですとメモリ不足でKilledがでます。c5 4xlargeですと処理時間は約10分です）
これで、idf辞書である「words_idf.json」というjsonファイルが作成されます。

以上です。