会計データを学習して、仕訳の入力時に摘要の内容から勘定科目を予測してみる

# はじめに

確定申告の時期になり、会計データを再確認すると勘定科目の間違いがちらほら見つかるので、機械学習で会計データを学習させて、勘定科目を予測できると良いのではないかと思い試してみました。

# 会計データの読込

会計ソフトからCSV形式でデータをエクスポートし、それを使います。
ちなみに、今回のデータは「JDL IBEX出納帳」というソフトのものを例に使用しています。

http://www.jdl.co.jp/co/soft/ibex-ab/

以下のコードでデータを読み込みます。

```py3:
import pandas as pd

filename = "JDL出納帳-xxxx-xxxx-仕訳.csv"
df = pd.read_csv(filename, encoding="Shift-JIS", skiprows=3)
```

読み込んだデータから使用するデータを絞り込む。
ここでは、摘要と借方科目のコードと名称を使用します。

```py3:
columns = ["摘要", "借方科目", "借方科目正式名称"]
df_counts = df[columns].dropna()
```

# 形態素解析

摘要のデータについて形態素解析により、文字データを指向性のある数値としてベクトル化します。

形態素解析にはJanomeというライブラリを使用します。

http://mocobeta.github.io/janome/

もし、インストールされていなければ、以下のコマンドでインストールしておく必要があります。

```sh:
$ pip install janome
```

以下のコードで、摘要のデータをトークンにわけたデータに変換します。

```py3:
from janome.tokenizer import Tokenizer

t = Tokenizer()

notes = []
for ix in df_counts.index:
    note = df_counts.ix[ix,"摘要"]
    tokens = t.tokenize(note.replace('　',' '))
    words = ""
    for token in tokens:
        words += " " + token.surface
    notes.append(words.replace(' \u3000', ''))
```

この結果、以下のような変換が行われ、単語毎に半角スペースを入れた文字列になります。

元の摘要データ　「手土産代　BLUESKY羽田」
変化後のデータ　「手土産 代 BLUESKY 羽田」

この文字列を以下のコードでベクトル化し、入力データとして使用します。

```py3:
from sklearn.feature_extraction.text import TfidfVectorizer

vect = TfidfVectorizer()
vect.fit(notes)

X = vect.transform(notes)
```

また、教師データとして勘定科目のコードを使用します。

```py3:
y = df_counts.借方科目.as_matrix().astype("int").flatten()
```

# 機械学習

数値に変換したデータをクロスバリデーションで学習データと検証データに分割します。

```py3:
from sklearn import cross_validation

test_size = 0.2
X_train, X_test, y_train, y_test = cross_validation.train_test_split(X, y, test_size=test_size)
```

分割したデータを使って学習します。
ここではモデルとしてLinearSVCを使用します。

```py3:
from sklearn.svm import LinearSVC

clf = LinearSVC(C=120.0, random_state=42)
clf.fit(X_train, y_train)

clf.score(X_test, y_test)
```

スコアは「0.89932885906040272」でした。

# 予測

学習した結果から、以下のようにテストデータを入力して、どんな勘定科目が予測されるか確認してみます。

```py3:
tests = [
    "高速道路利用料",
    "パソコン部品代",
    "切手代"
]

notes = []
for note in tests:
    tokens = t.tokenize(note)
    words = ""
    for token in tokens:
        words += " " + token.surface
    notes.append(words)

X = vect.transform(notes)

result = clf.predict(X)

df_rs = df_counts[["借方科目正式名称", "借方科目"]]
df_rs.index = df_counts["借方科目"].astype("int")
df_rs = df_rs[~df_rs.index.duplicated()]["借方科目正式名称"]

for i in range(len(tests)):
    print(tests[i], "\t[",df_rs.ix[result[i]], "]")
```

出力結果は...

```sh:
高速道路利用料 	[ 旅費交通 ]
パソコン部品代 	[ 消耗品費 ]
切手代 	[ 通信費 ]
```

かなり良い感じ(^-^)

ちなみに振替伝票はもう少し工夫が必要ですね。

他にも月や曜日、決算の情報とかも利用できるともっと良いかなと思ったのですが、学習データの扱い方がいまいちわからなかったので、また後日調べようと思います。

さて、次は何しようかな。

# （おまけ）学習と予測の切り離し

上述のプログラムをそのまま使おうとすると、実行するたびに実データを読込、学習してから予測するため、効率的とは言えません。
そこで、学習データを保存し、予測する部分では学習済データを読み込んで予測するように変更してみます。

## 学習結果の保存

前述のプログラムの最後に以下のようなコードを追加すると、学習データを保存することができます。

```py3:
from sklearn.externals import joblib

joblib.dump(vect, 'data/vect.pkl')
joblib.dump(clf, 'data/clf.pkl')
df_rs.to_csv("data/code.csv")
```

## 学習結果の読込

新しいプログラムで、以下のように学習データを読み込みます。

```py3:
import pandas as pd

filename = "data/code.csv"
df = pd.read_csv(filename, header=None)
df.index = df.pop(0)
df_rs = df.pop(1)

from sklearn.externals import joblib

clf = joblib.load('data/clf.pkl')
vect = joblib.load('data/vect.pkl')
```

## 予測

学習データを読み込んだら、続けて予測を実行します。

```py3:
from janome.tokenizer import Tokenizer

t = Tokenizer()
tests = [
    "高速道路利用料",
    "パソコン部品代",
    "切手代",
]

notes = []
for note in tests:
    tokens = t.tokenize(note)
    words = ""
    for token in tokens:
        words += " " + token.surface
    notes.append(words)

X = vect.transform(notes)

result = clf.predict(X)

for i in range(len(tests)):
    print(tests[i], "\t[",df_rs.loc[result[i]], "]")
```

実行結果は...

```sh:
高速道路利用料 	[ 旅費交通 ]
パソコン部品代 	[ 消耗品費 ]
切手代 	[ 通信費 ]
```

できた！