COTOHA 類似度算出APIを使って、FAQ検索システムを構築してみた

# 本文書について

[COTOHA API](https://api.ce-cotoha.com/)の類似度算出を使って、FAQ検索システムを構築してみた

# COTOHA APIの類似度算出について

COTOHAの類似度算出APIは２つの文章の類似性を数値化し出力してくれる。
少し使ってみたが、思いの外精度良く文間の類似度を算出してくれた。

### 入出力例

```python
import os
import json
import requests
import pickle

CLIENT_ID = 'XXX'
CLIENT_SECRET = 'XXX'
API_BASE_URL = 'https://api.ce-cotoha.com/api/dev/nlp/'
ACCESS_TOKEN_PUBLISH_URL = 'https://api.ce-cotoha.com/v1/oauth/accesstokens'

def get_access_token():
    headers = {'Content-Type': 'application/json',
               'charset': 'UTF-8',}
    data = {'grantType':'client_credentials',
            'clientId':CLIENT_ID,
            'clientSecret':CLIENT_SECRET}
    data = json.dumps(data)
    response = requests.post(ACCESS_TOKEN_PUBLISH_URL, headers=headers, data=data)
    response = json.loads(response.text)
    return response['access_token']

if not os.path.isfile('./ACCESS_TOKEN.pickle'):
    ACCESS_TOKEN = get_access_token()
    with open('ACCESS_TOKEN.pickle', mode='wb') as f:
        pickle.dump(ACCESS_TOKEN, f)
with open('ACCESS_TOKEN.pickle', mode='rb') as f:
    ACCESS_TOKEN = pickle.load(f)

def similarity(s1,s2):
    global ACCESS_TOKEN
    headers = {'Content-Type': 'application/json',
               'charset': 'UTF-8',
               'Authorization': 'Bearer '+ACCESS_TOKEN}
    data = {'s1':s1,
            's2':s2}
    data= json.dumps(data)
    response = requests.post(API_BASE_URL+'v1/similarity', headers=headers, data=data)
    response = json.loads(response.text)
    if response['status'] == 99998:
        ACCESS_TOKEN = get_access_token()
        with open('ACCESS_TOKEN.pickle', mode='wb') as f:
            pickle.dump(ACCESS_TOKEN, f)
        return similarity(a,b)
    return response['result']['score']

print(similarity('近くのレストランはどこですか','この辺りの定食屋はどこにありますか'))
print(similarity('この辺りの定食屋はどこにありますか','本日のおすすめ定食'))
```

```
0.91079295 #'近くのレストランはどこですか'：'この辺りの定食屋はどこにありますか'
0.46422136 #'この辺りの定食屋はどこにありますか':'本日のおすすめ定食'
```
上記の例だと、同じ`定食`という単語を持つ文同士よりも、`レストラン`と`定食屋`という意味は似ているが異なる単語を持つ文同士のほうが、類似度が高い結果となっている。
単語の表層だけでなく、意味情報まで類似度算出に利用しているように見受けられる。

# FAQ検索システムの構築

本記事では、類似度算出APIの出力結果を利用して、簡単なFAQ検索システムを構築してみる。
題材としては、[qiitaのFAQデータ](https://help.qiita.com/)のすべての質問テキストを用いることとし、
新たな質問を入れた場合に、FAQ中の似ている質問をスコア順で出力させる。

### 動作環境

python3.6.2

### FAQ検索システム

```python
class FAQ:
    def __init__(self):
        self.questions = list()
        self._set_questions()

    # FAQはテキストデータから読み込み
    def _set_questions(self):
        with open('faq_texts.txt', mode='r') as fin:
            for line in fin:
                line = line.strip()
                self.questions.append(line)

    # 似ているQを返す
    def extract_similar_questions(self,text):
        question_scores = dict()
        for q in self.questions:
            score = similarity(q,text)
            question_scores[q] = score
        return [k for k,v in sorted(question_scores.items(), key=lambda x: x[1], reverse=True)]
```

## 使ってみる

### 「英語に変えたい」
```python
faq = FAQ()
result = faq.extract_similar_questions('英語に変えたい')
for i,q in enumerate(result[:3]):
    print(i+1,q)
```

```
1 言語の変更について
2 投稿を非公開にするには
3 Qiitaの投稿をMarkdown表示にする方法
```
キーワードとして一致していないにもかかわらず、`英語`と`言語`や`変えたい`と`変更`が類似していることを読み取った出力結果になっている。

### 「良い記事を書きたい」
```python
faq = FAQ()
result = faq.extract_similar_questions('良い記事を書きたい')
for i,q in enumerate(result[:3]):
    print(i+1,q)
```
```
1 良い記事を書くためのガイドライン
2 良い投稿記事にいいねしよう
3 記事を投稿する
```
ガイドライン、読んでおきます。ありがとうございます。

### 「いいねを増やしたい」
```python
faq = FAQ()
result = faq.extract_similar_questions('いいねを増やしたい')
for i,q in enumerate(result[:3]):
    print(i+1,q)
```
```
1 絵文字の利用について
2 タグやユーザーをフォローしよう
3 Twitter・Github連携を解除してしまった際の再ログイン方法
```

:point_up::grinning::ok::ok_hand:

# まとめ :beginner:
COTOHAの類似度算出APIについて、出力をいくつか見てみたが意味等の解析もできており、悪くないと感じた:laughing:
一方で、FAQ検索に使おうとすると、無料環境の制限(1000回/日？)に簡単に引っかかってしまう:worried:
実用的に利用するとなると、Enterprise版の検討が必要そうだ:loudspeaker: