ConceptNetをローカル環境で実装する

# ConceptNetとは

> ConceptNet aims to give computers access to common-sense knowledge, the kind of information that ordinary people know but usually leave unstated.[公式githubより]

端的に言うと、コンピュータに常識を持たせることを目指した常識データベースです

# 常識が何の役に立つか

考えればきりがないのですが、一例として挙げてみます

## ユーザの発話の正しい意味や文脈をコンピュータが認識する

```
（サウザンドドレッシングの話をしていて）
女性A：そう、そう、そう。何かね、でも沖縄の人は、何にでもあれかけるから。
女性B：沖縄すごい。じゃあ、最初からバラで売らなくて混ざったのを。
女性C：隠れた味だね、沖縄の方は。
女性A：混ざったやつは売らない。売ってはないかなー。
レストランとかも作りがいがないとかいうの聞いたことがある。
何にでもかけちゃうから。微妙な味を出してもー。

引用元「名大会話コーパス」
```

これを正しく理解しようとすれば

1. [ドレッシング]は[食べ物]にかけるものである
2. [サウザンドドレッシング]は[ドレッシング]の一種である
1. [ドレッシング]は普通は[食べ物-サラダ]にかける
1. [サウザンドドレッシング]は[沖縄の人]が[食べ物]によくかける
1. [沖縄の人]は[沖縄]に住んでいる
2. [サウザンドドレッシング]は普通は[ドレッシング]を[混ぜて]作る
3. [お店]は[サウザンドドレッシング]を普通は売っていない
4. [お店]は[ドレッシング]を売っている
5. [沖縄]の[お店]は[サウザンドドレッシング]を売っている
6. ....

まだまだあると思いますが、この会話の裏にはこういった事実や主張、前提があると人なら理解できます
例えば[1,2,3,8]といった内容は、特に教わらなくても日本人として生きていれば10人中9人くらいは知っていそうです
しかし、コンピュータには当然[1,2,3,8]という知識が最初から備わっているわけではありません
上にあるような知識・常識などを仮にコンピュータが持っていれば

```
・ [4]番の主張が常識とは反している、つまり女性Aが他の女性達に伝えたい内容である可能性が高い
・「バラで売らなくて」＝「サウザンドドレッシングはドレッシングを混ぜて作るから、普通はバラバラにドレッシングを買って作る。加えてお店ではドレッシングを売っているから、お店でドレッシングを買うことでサウザンドドレッシングを作ることが普通である。そうではなくて」と機械が認識する
```

といったことができそうです
こういった認識ができると、対話システム、文章要約、tweet自動生成、教育ロボット、会議の合意形成プロセスの補助ツールなど、いろいろな応用が考えられます
そのための一助として常識知識は活用できそうです

# ConceptNetのインストール

[公式のgithub](https://github.com/commonsense/conceptnet5/wiki)で紹介されているインストール方法は３つほどあるそうです

1. web APIで試す
2. AWSでサーバーごとコピーする
3. Linuxで構築する

この記事ではオフラインで動作させるために3の方法に取り組んでみました

[こちら](https://github.com/commonsense/conceptnet5/wiki/Build-process)によると、３の方法でもPuppetを用いた方法と、git cloneする方法があるようです
ただPuppetの方法では**「do not recommend running it on a computer you use for anything else」**つまりそのPCはConceptNet専用機にしたほうがいいと忠告してくれています
開発するときにいちいちConceptNet用のPCと、開発用PCを両方立ち上げるのはちょっと面倒だったので、**「The hard way」**と言われている自前のシステムに実装する方法で行こうと思います

## PCの空き容量とメモリ
240 GBの空きディスク容量が必要とのこと
・・・多くないですか？という動揺を押し殺して確保します


```
$ df
Filesystem     1K-blocks      Used Available Use% Mounted on
/dev/nvme0n1p5 251849460 37725544 201307584  16% /
```

いろいろ消して、201GBです
これで無理なら新しいSSDに換装の必要がありますね
ちなみに200Gを下回るとプログラムの容量のチェックで弾かれます

メモリに関しては推奨は３２G

```
$ cat /proc/meminfo
MemTotal:       16197932 kB
```
・・・１６Gはちょっと無理があるかなぁ
とりあえずやってみます

(追記)結局新しくSSDとメモリ買ってしまいました。ストレスなく成功した時のものを参考までに載せておきます

- DDR4 PC4-25600 (3200MHz) 1.35V 32GBキット- PVS432G320C6K
- Western Digital SSD 1TB WD Blue

## 注意事項
- 低速なインターネット環境だと謎のエラーで止まることがあります(22GB超のconceptnet-raw-data-5.7.zipダウンロード時など)
- メモリーが足りないと、fasttextの関係でmemory overflowが起きます
- 一度失敗すると、その時の処理していたファイルは不完全なので削除を要求されます。conceptnetあたりで何度もエラーがでると２２G超えをそのたびにダウンロードし直すことになって大変です（おそらくファイルが存在するものはダウンロードがスキップされます）
- 前回のダウンロードが成功したフォルダが残っていても「build.sh開始時」には200GBの空き容量があるかどうかチェックされます。Snakefileでコメントアウトすれば回避できますが、推奨はしません

時間を無駄にしたくなければ、きちんと推奨要件を満たして方がいいかと、、~~（経験者は語る）~~


# 環境

- Ubuntu 16.04
- git導入済み、SSH通信可
- python 3.6.7

## ConceptNet

```
$ git clone git@github.com:commonsense/conceptnet5
$ cd conceptnet5
$ sudo apt install build-essential python3-pip python3-dev libhdf5-dev libmecab-dev mecab-ipadic-utf8
```

## ライブラリ

```
$ python --version
Python 3.6.7
$ pip install virtualenv
$ virtualenv venv
$ source venv/bin/activate
$ pip install numpy scipy
```

## PostgreSQL
versionが9.5以上という指定がある
[こちら](https://weblabo.oscasierra.net/postgresql10-ubuntu1604-install/)に従いインストールします

```
$ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
$ sudo apt -y install wget ca-certificates
$ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
$ sudo apt update
$ sudo apt install postgresql-10
```
[公式サイト](https://github.com/commonsense/conceptnet5/wiki/Build-process)によると、DB名や環境変数に指定があるようです
以下ではUbuntuのユーザ名をmyname、任意のパスワードをmypasswordとしておきます
**Ubuntuのユーザ名**は/home/**XXX**/conceptnet5の**XXX**にかかれている部分です、お間違えなく
**Ubuntuのユーザ名**＝userとかにしていると、postgresの予約語とバッティングしてうまく行きません。他のアカウントを作りましょう

```
$ sudo -u postgres psql
postgres=# create database conceptnet5;
postgres=# create user myname with password 'mypassword';
postgres=# alter role myname createdb;
postgres=# \q
$ mkdir data
$ pip install -e '.[vectors]'
```

```bash:.bash_profile
$ export CONCEPTNET_DB_PASSWORD="mypassword"
```

```
$ source ~/.bash_profile
```

## ビルド

外部ライブラリのwgetで問題が起こるため(2020.04)、ひとつずつ対処していきます
まずはcldrです

```bash:Snakefile
187:    shell:
188:        "wget -nv http://unicode.org/Public/cldr/34/cldr-common-34.0.zip -O {output}"
# -> これがconnection refusedになる
```

[github](https://github.com/unicode-org/cldr/tree/af4e70daf2dde8f7890761277f5772d0889780a6)からコマンドで配置します
zipを一度解凍して、もう一度zipにするという賢くないやり方ですが、、

```
$ mkdir data/raw
$ cd data/raw
$ wget https://github.com/unicode-org/cldr/archive/af4e70daf2dde8f7890761277f5772d0889780a6.zip
$ unzip af4e70daf2dde8f7890761277f5772d0889780a6.zip "cldr-af4e70daf2dde8f7890761277f5772d0889780a6/common/*"
$ mv cldr-af4e70daf2dde8f7890761277f5772d0889780a6/common common
$ zip -rq cldr-common-34.0.zip common/
$ cd ../..
```
次にConceptNetのrawデータです
ダウンロードはできるので、プログレスが出るようにします

```bash:Snakefile

180:    shell:
181: -      "wget -nv {RAW_DATA_URL} -O {output}"
181: +      "wget -nv {RAW_DATA_URL} -O {output} --show-progress"
# 理由：データサイズが大きく、時間がかかるため(2020.04)
```



```
$ ./build.sh
```

# テスト

```
$ pip install pytest PyLD
$ pytest

======================================== test session starts ========================================
platform linux -- Python 3.6.10, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /home/myname/conceptnet5, inifile: setup.cfg
collected 103 items                                                                                 

conceptnet5/edges.py .                                                                        [  0%]
conceptnet5/nodes.py ....                                                                     [  4%]
conceptnet5/uri.py ................                                                           [ 20%]
conceptnet5/builders/combine_assertions.py .                                                  [ 21%]
conceptnet5/formats/semantic_web.py ....                                                      [ 25%]
conceptnet5/language/token_utils.py .                                                         [ 26%]
conceptnet5/readers/dbpedia.py .                                                              [ 27%]
conceptnet5/tests/test_convert.py ..                                                          [ 29%]
conceptnet5/tests/test_propagate.py ....                                                      [ 33%]
conceptnet5/tests/test_sounds_like.py .                                                       [ 33%]
conceptnet5/tests/test_vectors.py ................                                            [ 49%]
conceptnet5/tests/full_build/test_all_relations_recorded.py s                                 [ 50%]
conceptnet5/tests/full_build/test_enough_data_exists.py ssssssssssssss                        [ 64%]
conceptnet5/tests/small_build/test_api.py ..                                                  [ 66%]
conceptnet5/tests/small_build/test_built.py .......                                           [ 72%]
conceptnet5/tests/small_build/test_json_ld.py ...                                             [ 75%]
conceptnet5/tests/small_build/test_lemmatizer.py .....                                        [ 80%]
conceptnet5/tests/small_build/test_queries.py ..........                                      [ 90%]
conceptnet5/util/sounds_like.py ..........                                                    [100%]

========================================= warnings summary ==========================================
conceptnet5/tests/small_build/test_api.py::test_related_query
  /home/XXX/.pyenv/versions/3.6.10/lib/python3.6/importlib/_bootstrap.py:219: RuntimeWarning: numpy.ufunc size changed, may indicate binary incompatibility. Expected 192 from C header, got 216 from PyObject
    return f(*args, **kwds)

-- Docs: https://docs.pytest.org/en/latest/warnings.html
============================ 88 passed, 15 skipped, 1 warning in 35.25s =============================
```

# 試しに使ってみる

まずはpathを通します

```bash:bash_profile
export PATH="$HOME/conceptnet5:$PATH"
```

```bash

$ source ~/.bash_profile
```

```python:sample.py
from conceptnet5.db.query import AssertionFinder
cnfinder = AssertionFinder()
for ele in cnfinder.lookup('/c/ja/ドレッシング'):
    print(ele)
```

```bash

$ python sample.py

<前略>
{
'end': {'@id': '/c/ja/ドレッシング', 'label': 'ドレッシング', 'language': 'ja', 'term': '/c/ja/ドレッシング', '@type': 'Node'}, 
'rel': {'@id': '/r/IsA', 'label': 'IsA', '@type': 'Relation'}, 
'start': {'@id': '/c/ja/サウザンドアイランドドレッシング', 'label': 'サウザンドアイランドドレッシング', 'language': 'ja', 'term': '/c/ja/サウザンドアイランドドレッシング', '@type': 'Node'}, 
'weight': 1.0, 
'dataset': '/d/kyoto_yahoo', 
'license': 'cc:by/4.0', 
'sources': [{'activity': '/s/activity/kyoto_yahoo', '@id': '/s/activity/kyoto_yahoo', '@type': 'Source'}], 
'surfaceText': None, 
'@id': '/a/[/r/IsA/,/c/ja/サウザンドアイランドドレッシング/,/c/ja/ドレッシング/]', 
'@type': 'Edge'
}
<後略>
```

ドレッシング IsA サウザンドアイランドドレッシング
とのこと
表記ゆれとかは別途加味しなくてはいけなさそうですが、きちんと入っていますね

見やすいように、入力した単語にまつわるcommon senseを日本語で表示してみます

```python:sample.py
from conceptnet5.db.query import AssertionFinder
cnfinder = AssertionFinder()

templates = {
	"Synonym":"XとYは類似語です",
	"AtLocation":"XはYに[あります/います]",
	"PartOf":"XはY[から作られます/の一部です]",
	"IsA":"XはYです",
	"HasContext":"XはYというコンテキストがあります",
	"DerivedFrom":"XはY[に由来します/を原料とします]",
	"RelatedTo":"XはYと関係があります",
	"EtymologicallyDerivedFrom":"XはYを別の表現にしたものです"
}

word = "ドレッシング"
for e in cnfinder.lookup('/c/ja/'+word):
    relation = e["rel"]['label']
    if relation in templates:
    	template = templates[relation]
    	template = template.replace("X",e["start"]['label']) 
    	template = template.replace("Y",e["end"]['label'])
    	print(template)
```

```
$ python sample.py

ドレッシングとvinaigrettesausは類似語です
ドレッシングとdressingは類似語です
ドレッシングはフリーザーに[あります/います]
ドレッシングとankleidenは類似語です
ドレッシングとvinaigretteは類似語です
ドレッシングとdressingは類似語です
ドレッシングとdressingは類似語です
ドレッシングは冷蔵庫に[あります/います]
フレンチドレッシングはドレッシング[から作られます/の一部です]
ドレッシングとslasausは類似語です
ドレッシングとumziehenは類似語です
ドレッシングとsalatsosseは類似語です
ドレッシングとdressingは類似語です
ドレッシングはbusinessというコンテキストがあります
フレンチドレッシングはドレッシングです
ランチ ドレッシングはドレッシング[に由来します/を原料とします]
ドレッシングはdecorationsと関係があります
ドレッシングはdressingと関係があります
サウザンドアイランドドレッシングはドレッシングです
ドレッシングはdressingを別の表現にしたものです
ドレッシングはmakeupと関係があります
ドレッシングはclothesと関係があります
ヴィネグレットソースはドレッシング[から作られます/の一部です]
サラダ ドレッシングはドレッシング[に由来します/を原料とします]
サウザンドアイランドドレッシングはドレッシング[から作られます/の一部です]
ビネグレットはドレッシング[から作られます/の一部です]
ドレッシングは料理です
```
使えそうなのはひとつの単語に数個くらいでしょうか
注意点ですが、同じ「ドレッシング」でも複数の意味がある場合があります
ドレッシングだとわかりにくいですが、例えば「みる」を例にあげると

```
みる(動詞)ー＞見る
　　label:みる, @id:/c/ja/みる/v
みる(名詞)ー＞海松（みる、と読む海藻の一種）
　　label:みる, @id:/c/ja/みる/n
```
というように、表記は同じでもそれが指し示す概念が異なる場合があります
そのため、lookupの引数には@idで取得したものを利用するようにしてください

# 遭遇したエラー集

```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/tmp/pip-build-uc6w1vdn/pandas/setup.py", line 42
    f"numpy >= {min_numpy_ver}",
                              ^
SyntaxError: invalid syntax

# 解決策
$ source ~/.bash_profile
``` 
numpyのバージョンがpandasとあってないというエラーらしいが、、
実際は再起動時にpyenvのpathが読み込まれていなくて、numpyのバージョンが取得できていないだけでした
公式にpipを叩ける権限に関して指定があったので、そのあたりをだいぶ疑ってしまった、、なんと初歩的な


```
Error in rule precompute_wiktionary:
    jobid: 0
    output: data/precomputed/wiktionary/parsed-2/de.jsons.gz

RuleException:
CalledProcessError in line 236 of /home/XXX/conceptnet5/Snakefile:
Command 'set -euo pipefail;  wget https://conceptnet.s3.amazonaws.com/precomputed-data/2016/wiktionary/parsed-2/de.jsons.gz -O data/precomputed/wiktionary/parsed-2/de.jsons.gz' returned non-zero exit status 139.
  File "/home/XXX/conceptnet5/Snakefile", line 236, in __rule_precompute_wiktionary
  File "/home/XXX/.pyenv/versions/3.6.10/lib/python3.6/concurrent/futures/thread.py", line 56, in run
Exiting because a job execution failed. Look above for error message

# 解決策
$ wget https://conceptnet.s3.amazonaws.com/precomputed-data/2016/wiktionary/parsed-2/de.jsons.gz
$ cp de.jsons.gz data/precomputed/wiktionary/parsed-2/de.jsons.gz
```

wiktionaryのdumpダウンロードエラー
エラーメッセージを見ろと書いてあるのに、エラーメッセージには大した情報が書いていないという~~公明の罠~~
とりあえず手動でwgetして配置した後、build.shを実行することで回避できました
ダウンロード環境が高速だったら起きなかったので、そのあたりの問題な気もする
**de.jsons.gz**だけでなく、**fr.jsons.gz**や**en.jsons.gz**でも同様です

```
Conceptnet-raw-dataのダウンロードエラー[具体的なログは保存し忘れました]

# 解決策
$ wget https://zenodo.org/record/2579347/files/conceptnet-raw-data-5.7.zip
$ cp conceptnet-raw-data-5.7.zip data/raw/conceptnet-raw-data-5.7.zip
```

wiktionaryと全く同じエラー
上記ではprogressを表示するとかしたのですが、何回か失敗した記憶があります
最終的にはこちらの方法で実行しました
これは22GBあるので、build.shでいちいちバグに遭遇して不完全なダウンロードファイルを手動で消してまた再ダウンロードとかやっているといくらでも時間がなくなります。手動でwgetして必要に応じてコピーするほうが精神衛生的に良いです

```
cldrのnot foundエラー[具体的なログは保存し忘れました]

# 解決策
# 上記のビルドの項目に書いてあります
```

conceptnet5のサーバーに保存しているcldr-commonのzipパッケージには、必要なものがいくつかない可能性[未確認]
zipファイル解凍時にnot foundでエラーがでます
CLDRの公式パッケージから取得する方法で代用しました

```
Memory overflow[具体的なログは保存し忘れました]

# 解決策
# メモリを32GB以上にする
```
fasttextのあたりででます（16GBで発生を確認）
きちんと推奨要件満たしておきましょう

# ビルドに失敗した時は、、

```bash
# snakefileがlockされてbuild.shが実行できない場合はこれ
$ rm -r .snakemake

#　ビルドを再度実行したい場合はこれ、一部だけ再実行したい場合はそれだけ消す
$ rm -r data

# テストのみを再実行したい場合はこれ
# postgresの権限設定などがうまく行っていないと、conceptnet-testが作成できないというエラーがでる。そういう場合に実行する必要が出てくる
$ rm -r .pytest_cache
$ rm -r testdata/current

$ ./build.sh
```

# コメント
確かにThe hard workでした、、笑
Puppet使ったほうがおすすめかも


# 参考文献

- 藤村逸子・大曽美恵子・大島ディヴィッド義和、2011
「会話コーパスの構築によるコミュニケーション研究」
藤村逸子、滝沢直宏編『言語研究の技法：データの収集と分析』p. 43-72、ひつじ書房
- Fujimura,Itsuko, Shoju Chiba, Mieko Ohso, 2012,
Lexical and Grammatical Features of Spoken and Written Japanese in Contrast:
Exploring a lexical profiling approach to comparing spoken and Written corpora,
Proceedings of the VIIth GSCP International Conference. Speech and Corpora, 393-398.
- Speer, Robyn, Joshua Chin, and Catherine Havasi. "Conceptnet 5.5: An open multilingual graph of general knowledge." Thirty-First AAAI Conference on Artificial Intelligence. 2017.