情報系大学院生のブログ

M1河田。研究の備忘録として論文やプログラムについて書いています。

しりとりプログラム

今回はしりとりゲームをいかに長く続けることができるかに挑戦している。
こちらを参考にさせていただきました。
http://catindog.hatenablog.com/entry/2017/01/19/214348

プログラムはこっち
https://github.com/NaotakaKawata/shiritori

しりとりゲームとは
しりとりのルールについてはローカルルールなどを含むときりがないので割愛する。恐らく全員が納得する「ん」で終わる単語が出ればゲーム終了というルールを基本としている。

ルール設定
「ん」で終わる単語を出力したら終了
・単語の在庫がなくなりしりとりを継続することができなくても終了
・制限時間1分以内、これを超えて出力されたものはカウントしない

辞書データの準備
今回しりとりに用いる辞書データに毎日新聞コーパスを用いた。コーパスMecab分かち書きを行い、しりとりの単語として使われるのに妥当だと思われる以下の品詞を抽出してしりとり用の辞書データを作成した。
・名詞
・動詞(基本形)
・形容詞(基本形)
・副詞
正直その単語使うのは変じゃない?ということはナシにしてください、課題で与えられたので仕方ないのです。

それではプログラムについて説明します。以下のサイトを参考にしてプログラムを作成しました。というかほぼまんまです。一応作成したプログラムはGitに上げています。変更したのは最初の単語を任意の入力にしたことと、使用するパラメータです。

プログラムの説明

def search(tail):
    ps = list(map(float, sys.argv[1:]))
    not_found = True
    if random.random() > 0.98:
        random.shuffle(bases)
    for e, _ in enumerate(bases):
        n = bases[e]
        if tail == n.head and "ン" != n.tail:
            if 'ル' == n.tail and random.random() < 1: continue
            if 'ズ' == n.tail and random.random() < 1 : continue
            if 'ヂ' == n.tail and random.random() < 1 : continue
            if 'ウ' == n.tail and random.random() < ps[0]: continue
            if 'ク' == n.tail and random.random() < ps[1]: continue
            if 'ツ' == n.tail and random.random() < ps[2]: continue
            if 'プ' == n.tail and random.random() < ps[3]: continue
            if 'ラ' == n.tail and random.random() < ps[4]: continue
            if 'リ' == n.tail and random.random() < ps[5]: continue
            if 'マ' == n.tail and random.random() < ps[6]: continue
            if 'イ' == n.tail and random.random() < ps[7]: continue
            if 'ワ' == n.tail and random.random() < ps[8]: continue
            not_found = False
            break

search()は語尾の採択確率から出現する単語を制限する関数。変数tailには語末の文字が含まれています。psにパラメータをいれることで任意の語尾を含む単語の出現確率を操作します。

seed = bases.pop(random.randint(1, len(bases)))
chain.append(seed)
for i in range(100000):
    tail = chain[-1].tail
    t = search(tail)
    chain.append(t)
    print(len(chain), len(bases), t.orig, t.yomi)

大体こんなかんじで単語をつなげていってます。

わりとランダムでもしりとりはつながると思ったがどうもつながらない。その理由の一つとして語頭と語尾の分布の違いが挙げられる。しりとりが終了するのは「ん」で終わる単語を出す以外に単語が出てこないことでも終了する。例を図に示す。このように語尾の数が語頭よりも多い場合、次につながる単語を発見できずしりとりが終了してします。このことから語頭と語尾の偏りを確率を用いて平滑化しようと試みた。
ベイズ最適化
これ読んで理解しました。
https://qiita.com/masasora/items/cc2f10cb79f8c0a6bbaa
https://www.slideshare.net/hoxo_m/ss-77421091

最適化手法の一つであり、設定したパラメータを更新していき、目的関数が最もよくなるパラメータを探している。この手法の良いところは値が良くなる部分を重点的に探索してくれるため、グリッドサーチのように時間があまりかからないという点である。
今回はパラメータとして設定したのは「語尾」であり、偏りが大きいものパラメータにした。パラメータの数を増やしすぎるとかえって性能が落ちるし、適切でないパラメータを選択してもダメ。難しい・・・(´・ω・`)

結果
例として入力単語を「ウニ」としてしりとりを行った結果を表に示す。baselineとは最適化を行わず、単語をランダムに出現させた場合を示す。辞書の総単語数は81030個であった。
f:id:ayatakano:20180426162514p:plain

コメント

語頭と語尾の偏りを考慮することでしりとりは長く続くことが分かった。他の人がルールベースで書いた結果は約40000単語だったのでこのくらいの規模だと計算しきれるそうだ。辞書データが大きくなればなるほどこの方法は有効であり、語頭と語尾の偏りがなくなるため、よりしりとりが簡単につながると思われる。

ベイズ最適化により事前に確率を定義したが、しりとりをしている最中にある語尾の出現確率を変えることがベストであり、実装しようとしたが割とここから変更するのは手間であり、今の作りで語尾が出るごとに確率をいじったら途中でしりとりをつなげるスピードが遅くなり結果が出るまでに時間がかかるので断念。もしできたらルールベースで書いたものと同等の結果が出るだろう。

コーパスの加工(毎日新聞)

今回は自然言語処理の研究で行う

コーパスの処理」

について僕が行った方法などを紹介する。

使ったデータ

研究室で購入している毎日新聞のコーパスを使用した。大体こんなかんじでタイトルや本文ごとにタグが付与されている

\T1\○○山の桜、満開
\T2\ ○月○日、○○山の桜が見ごろを迎える。・・・
\P1\

コーパスでは加工しやすいようにタグ付けや区切り文字を入れている。
これを利用してデータを使える形に加工する。

やり方

今回はT2タグが付いている本文を形態素解析器にかけ、以下の要件を達成する。

(1)形態素を出現頻度の高い順に並び替える
(2)品詞を出現頻度の高い順に並び替える




まずはT2タグが付いている本文を抽出する。

grep “\T2\” <file>

つづいてT2タグを消去する。

sed “s/\T2\//g” <file>

形態素解析器は有名なmecabを使用した。

mecab <file>

mecab形態素解析を行うとこのようになる。

ない 形容詞,自立,*,*,形容詞・アウオ段,基本形,ない,ナイ,ナイ
高い 形容詞,自立,*,*,形容詞・アウオ段,基本形,高い,タカイ,タカイ
高い 形容詞,自立,*,*,形容詞・アウオ段,基本形,高い,タカイ,タカイ


形態素と品詞情報はタブで区切られているため(1)を行うにはこのように書くとよい。

cut –f 1 <file> 

(2)を行うには

cut –f 2 <file>|cut –d “,” –f 1

最後に出現頻度順に並び替える処理を行う。ここではsortとuniqを使って並び替えを行うが、ここで「なぜsortを2度行うか」について注意したい。uniqは連続で重複した行のみを削除するため、「うに→いか→うに」のような並びの場合「うに」は別の単語だと判断され削除されない。そのため事前にsortで並び替えておく必要がある。

sort <file>|uniq –c|sortr –n

以上の処理で(1),(2)を達成することが出来た。最後にパイプ処理を行うことですべての作業を1行にまとめることが出来るが、メモリの関係で処理が終わらない可能性大なのであまりおすすめしない。いちおうのっけとく。

(1)grep “\T2\” <file>| sed “s/\T2\//g”|mecab|cut –f 1| sort |uniq –c|sortr -n
(2)grep “\T2\” <file>| sed “s/\T2\//g”|mecab| cut –f 2 <file>|cut –d “,” –f 1| sort |uniq –c|sortr -n

おわりに

今回はUNIXコマンドのみでコーパスを処理したが、もし同様の処理を複数回行う場合、pythonなどで正規表現などを用いて処理した方が早く終わる。しかしUNIXコマンドだと1行で書くことが出来るためコードを書く手間を短縮できる。どちらで処理するかは場合によるだろう。

はじめに

大学生から内部進学をするにあたって、研究日誌というものを書いたことがないので、その代わりにブログで2年間で学んだことや興味をもったことを残していく。

文章が稚拙であるのでこの「はじめに」ものちに編集する予定。もし心優しい方がおられましたら、見やすい書き方をご教授ねがいます。