ナード戦隊データマン

データサイエンスを用いて悪と戦うぞ

テンプレートベースの言語生成を試してみる

data2text1とは、あるデータを説明するためのテキスト等を生成するタスクです。今回は、「感情」「行動」を入力すると、それに対する反応を生成する、というモデルを考えます。

簡単な仕組み

入力する特徴量に対して出力したいテンプレートをラベル付します。あとは、ニューラルネットを使うにしろ使わないにしろ、通常の機械学習のプロセスとして学習させるだけです。

入力と出力の説明

純化のために、入力する感情は1つ、行動は1つと考え、さらに行動は「スポーツ」「勉強」「その他のイベント」のいずれかであるとします。

出力は、その入力に対しての反応です。以下は出力のテンプレートを定義したファイルです:

0    n,sports    お疲れ様です。{}これからも頑張ってください。
1   p,sports    いいね!{}はエキサイティング!
2   n,study     がんばりましたね。休憩も必要ですよ。
3   p,study     いいね!{}って簡単?
4   n,event     それは大変でしたね。||大変でしたね。||大丈夫ですか?
5   p,event     いいね!||やったね!||グッジョブ!

これを、templates.txtという名前で保存しておきます。

事前準備

gensimによるword2vecをjawikiのダンプファイルで訓練しておきます。

訓練データ

訓練データを手作業で作成します。以下のリンクで訓練データを公開しています。 https://pastebin.com/B73Ykydc

このファイルを、train.txtという名前で保存します。

コード

データのロード、訓練、サンプルデータに対する予測を行うコードが以下です:

import numpy as np
import MeCab
from gensim.models import KeyedVectors
from scipy import spatial
from sklearn.linear_model import LogisticRegression

def load_data(datafile="./train.txt", vecfile="./model.wv", mecabopts="-d /usr/local/lib/mecab/dic/mecab-ipadic-neologd"):
    data = []
    with open("train.txt") as f:
        for i,line in enumerate(f):
            if i==0:
                continue
            a = line.strip().split("\t")
            a = [x for x in a if x != '']
            print(a)
            emo,tags,label = a
            tags = tags.split(",")
            label = int(label)
            data.append((emo, tags, label))

    tpos = ["名詞", "動詞", "形容詞"]
    
    tagger = MeCab.Tagger(mecabopts)
    wv = KeyedVectors.load(vecfile, mmap='r')
    return {"wv": wv, "tagger":tagger,"data":data, "tpos": tpos}


def data2vec(wv, emo, tags, tagger, tpos, dim):
    evec = tags2vecs(wv, [emo], tagger, tpos, dim)
    tvec = tags2vecs(wv, tags, tagger, tpos, dim)
    return {"evec":evec, "tvec": tvec}

    
def fix_data(data, dim=200):
    X = []
    y = []
    for e,tags,l in data["data"]:
        tmp = data2vec(data["wv"], e,tags, data["tagger"], data["tpos"], dim)
        tmp = np.hstack((tmp["evec"], tmp["tvec"]))
        X.append(tmp)
        y.append(l)
    return X, y


def train(X, y):
    from sklearn.linear_model import LogisticRegression 
    clf = LogisticRegression()
    clf.fit(X, y)
    return clf


def test(clf, emo, tags, data, dim=200):    
    tmp = data2vec(data["wv"], emo, tags, data["tagger"], data["tpos"], dim)
    vec = [np.hstack((tmp["evec"],tmp["tvec"]))]
    return clf.predict(vec)


def execute(emo, tags):
    import random
    data = load_data()
    X, y = fix_data(data)
    clf = train(X, y)
    label = test(clf, emo, tags, data, dim=200)[0]
    ts = {}
    with open("./templates.txt") as f:
        for line in f:
            tmp = line.strip().split("\t")
            tmp = [x for x in tmp if '' != x]
            idx, _, sentence = tmp
            idx = int(idx)
            sentence = sentence.split("||")
            if isinstance(sentence, str):
                sentence = [sentence]
            ts[idx] = sentence
    
    sent = random.choice(ts[label]).format(tags[0])
    return sent
    
def tags2vecs(wv, tags, tagger, tpos, dim):
    vecs = []
    if isinstance(tags, str):
        tags = [tags]
    for tag in tags:
        for ts in tagger.parse(tag).strip().split('\n'):
            if isinstance(ts, str):
                ts = [ts]
            for t in ts:
                if "\t" not in t:
                    continue
                flag = sum([p in t for p in tpos])
                if flag < 1:
                    continue
                t = t.split("\t")[0]
                if len(t) < 2:
                    continue
                try:
                    vecs.append(wv[t])
                except KeyError:
                    continue
    if vecs:
        return np.mean(vecs, axis=0)
    else:
        return np.zeros(dim)

if __name__ == "__main__":
    emo = "楽しい"
    tags = ["心理学"]
    print(execute(emo, tags))

入力: emo=楽しい, tags=["心理学"]

出力:

いいね!心理学って簡単?