doc2vecでredmineのチケット(csv)を学習させてみる

## やったこと

問い合わせ履歴(redmineのチケット)を機械学習し
検索ワードに対して、類似する過去チケットをリコメンドするPoCを作ってみました。

#### 背景

私はコールセンター的な業務をすることがあり、問い合わせの記録をredmineにチケットとして記録してます。

そんな中、最近流行りの機械学習を使った検索で、業務の効率化をできそうかを試してみたく、doc2vecでPoC作ってみました。

#### 結論

結論としては、作りが雑すぎたためか、使い物にならない精度でした。
しかし、前処理とかもう少し頑張れば実運用でも使えるかもしれない可能性は感じました。

## 実装の流れ

大きく3段階あります。
① docker上のubuntuでjupyter-notebookを起動し、機械学習＆チケットの検索の環境を用意
② doc2vecでチケット(csv)を機械学習させ、モデルを作成
③ 検索ワードを入力し、類似するチケットをリコメンド

## 環境

- docker (windows10)
  - ubuntu:16.04
    - jupyter-notebook
      - python
        - doc2vec
- redmineのチケット（csvファイル）

全体的に、[すぐに使える！業務で実践できる！Pythonによる AI・機械学習・深層学習アプリのつくり方](https://github.com/kujirahand/book-mlearn-gyomu)を参考にさせて頂いております。

## 作り方

※環境構築関連は、[ありがちだけどPythonでチャットボット作ってみた](https://qiita.com/hugepeach/items/595e7f35cca70fd52531)でもう少し詳しく説明してます

**1. dockerを起動し、[Dockerfile](https://github.com/kujirahand/book-mlearn-gyomu/blob/master/src/Dockerfile)を`build`する**

**2. イメージを`run`する**

```sh
docker run -it -p 8888:8888 -v //c/Users/xxx/xxx:/xxx book-mlearn-image
```

`xxx`の部分にパスを入力して、ホストのディレクトリをマウントする

**3. `issues.csv`(問い合わせ対応のcsv)をマウント済みのディレクトリに格納する**

**4. jupyter-notebookを起動する**

```sh
jupyter notebook --no-browser --ip=0.0.0.0 --allow-root --NotebookApp.iopub_data_rate_limit=100000000
```

実行すると、`http://127.0.0.1:8888/?token=xxx`にアクセスしてね、と表示されますが、docker上で動いてるので`docker-machine env`で出てくるipアドレスに置き換えてアクセスしてください。

例：`http://192.168.99.100:8888/id=xxx`

**5. 新規でpython3のファイルを作成する**

**6. 必要なものをインストールする**

以下をコピペし実行します。時間かかります。

```py
!curl -kL https://bootstrap.pypa.io/get-pip.py | python3

!apt-get update -y
!apt-get upgrade -y
# 👆について、十分な空き容量がないとエラーになる場合がある。エラーメッセージでググったら解決法でる。

!apt-get -yV install swig-doc
!apt-get -yV install swig-examples
!apt-get -yV install swig2.0-doc
!apt-get -yV install swig2.0-
!apt-get -yV install swig2.0 
!apt-get -yV install swig

!apt-get install mecab libmecab-dev mecab-ipadic-utf8 -y

!git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git /tmp/work/
# 👆のclone先をホストOSとの共有ディレクトリにしているとエラーとなる場合がある。
# そのためtmp/workに保存している

!mkdir /var/lib/mecab/dic/mecab-ipadic-neologd

!apt-get install file -y

!/tmp/work/bin/install-mecab-ipadic-neologd -n -p /var/lib/mecab/dic/mecab-ipadic-neologd -y
# 👆はdockerのVM VirtualMachineのメモリ割り当てが1024MBの場合エラーが出ることがある。1.5GB(1538MB)くらいに増やすと私はエラー出なくなりました。

!pip install mecab-python3

import MeCab
import sys
import MeCab
m = MeCab.Tagger ("-d /var/lib/mecab/dic/mecab-ipadic-neologd")
# mecabの稼働確認
print(m.parse ("すもももももももものうち"))

# word2vecの準備
!pip install gensim
```

**7. チケットを学習しモデルを作成**

以下を実行します。

```py
import pandas as pd
import zipfile
import os.path
import urllib.request as req
import MeCab
from gensim import models
from gensim.models.doc2vec import TaggedDocument

# ここの/xxx/はissues.csvのパスに修正してください
df = pd.read_csv("/xxx/issues.csv", encoding='cp932')

list =[] # リストへの格納方法が冗長な感じがしますがお許しください

#Mecabの初期化
mecab = MeCab.Tagger()
mecab.parse("")

for i in range(len(df["No"])):
    list.append(
        {"ticket":{
            "No":str(df["No"][i]), # No・・・チケットのナンバー(#)
            },
        "content":[
            {"title":str(df["題名"][i]),  # 題名・・・チケットのタイトル
            # csvの学習させたい項目を単純に連結させる
            # 担当者/内容/システム名/機能名/ページ名/説明・・・チケットの各項目名のサンプルです
            "detail":str(df["担当者"][i]) + str(df["内容"][i]) + str(df["システム名"][i]) + str(df["ページ名"][i]) + str(df["説明"][i])},
        ]},
    )
    
def ticket_list():
    for ticketlist in list:
        ticket = ticketlist["ticket"]
        for content in ticketlist["content"]:
            yield ticket, content

#引数のテキストを分かち書きして配列にする
def split_words(text):
    text = text.replace('\n','')
    text = text.replace('\r','')
    text = text.replace('\u3000','')
    node = mecab.parseToNode(text)
    wakati_words = []
    while node is not None:
        hinshi = node.feature.split(",")[0]
        if  hinshi in ["名詞"]:
            wakati_words.append(node.surface)
        elif hinshi in ["動詞", "形容詞"]:
            wakati_words.append(node.feature.split(",")[6])
        node = node.next
    return wakati_words

#リストをDoc2Vecが読めるTaggedDocument形式にし、配列に追加する
documents = []
#チケットリストをループで回す
for ticket, content in ticket_list():
    #文字列を取得
    words = content["detail"]
    #文字列を分かち書きに
    wakati_words = split_words(words)
    #TaggedDocumentの作成　文書=分かち書きにしたチケット詳細　タグ=No:題名
    document = TaggedDocument(
        wakati_words, [ticket["No"] + " : " + content["title"]])
    documents.append(document)
    
#TaggedDocumentの配列を使ってDoc2Vecの学習モデルを作成
model = models.Doc2Vec(
    documents, dm=1, vector_size=300, window=5, min_count=1)

#Doc2Vecの学習モデルを保存
model.save('redmine.model')

print("モデル作成完了")
```

**8. 検索ワードを入れ、類似するチケットを表示させる**

以下を実行します。

```py
import urllib.request as req
import zipfile
import os.path 
import MeCab
from gensim import models

#Mecabの初期化
mecab = MeCab.Tagger()
mecab.parse("")

#保存したDoc2Vec学習モデルを読み込み
model = models.Doc2Vec.load('redmine.model')

#引数のテキストを分かち書きして配列にする
def split_words(text):
    node = mecab.parseToNode(text)
    wakati_words = []
    while node is not None:
        hinshi = node.feature.split(",")[0]
        if  hinshi in ["名詞"]:
            wakati_words.append(node.surface)
        elif hinshi in ["動詞", "形容詞"]:
            wakati_words.append(node.feature.split(",")[6])
        node = node.next
    return wakati_words

def similar(search_word):
    words = search_word
    wakati_words = split_words(words)
    vector = model.infer_vector(wakati_words)
    print("--- 「" + search_word + '」に類似するチケットは? ---')
    print(model.docvecs.most_similar([vector],topn=3))
    print("")

#
similar("ここに検索したい文章を入れる")
```

すると...

```md
--- 「aaaしたいがbbbできない」に類似するチケットは? ---
[('1234 : cccしたいができない', 0.9499524831771851), ('2345 : dddする方法を教えて欲しい', 0.9499462246894836), ('3456 : eeeでエラーが発生した', 0.9499049782752991)]
```

と、類似するチケットが表示されます。

ここでいう、`1234 `のような数字はチケットのNo、
`cccしたいができない`はチケットの題名、
`0.9499524831771851`はチケットの詳細と、similar("～")に入れた文章との類似度です。

類似度は1に近いほど似てるという意味です。
ここでは類似度が高い上位3件を表示させています。

---

以上です。

リコメンド精度が低い理由は、いろいろ考えられますが、一番の問題は前処理がテキトウすぎることかなと思ってます。
さすがに雑すぎでしたかね...。

ちなみに、redmineのチケットをcsv形式で出力して使っている理由は、DBにアクセスするコードを書くのが面倒だったためです。

また、問い合わせ履歴をいい感じに処理してくれる機械学習として、watson discoveryも少し触ってみたのですが、色々と覚えるのに時間がかかりそうだったためすぐ作れそうなdoc2vecで実装しました。

何か間違いやアドバイスあればコメントください。