今更?人口無能っぽいものを作ってみようかなと思った話

LINEで送る
Pocket

—PR —



—PRここまで—
がじです。

前回python3でMeCabを使えるようにしたので、いまさらながら人口無能的なものを作ってみようかなと考えてしまいました。

現在Deeplearningを切っ掛けとして何度目かのAIブームが来ているのは皆さんご承知の通りかと思います。であるなら、わざわざ自前で作るよりも、DeepLearning用のライブラリとか、機械学習のツールとか使えば実用的で簡単にボットなど作れそうな気がします。

しかし、このブログは当たり前のことを自分でやってみて自己満足にひたるのがそもそも目的で立ち上げたものです。私が作って見たいんだからしょうがない。

と前置きはこのくらいにして本題に入りましょう。
参考にしたのはこの本

若干古い(というか10年前)の本ですが、まあ当時から興味があって持ってはいたのです。(ちゃんと読んだのはわりと最近)

n-gram処理

自然言語処理の基本でn-gramというものがあります。要は文章をn文字単位で区切ったリストを作成することです。でそのリストを分析していろいろ処理していきます。
nがいくつが妥当なのかはなかなか難しいらしいのでここでは議論しません。

日本語だとなんとなく3gramあたりが良さそうな感じなので3gramのテキストを作成するプログラムを書いてみました。

# -*- coding: utf-8 -*-
import sys
import codecs

# Textを読み込む
def getsource(src):
  n = 0
  for i in src:
    n += len(i)
  return n

# 3-gramの作成
def output_target(target):
  i = 0
  ret_list=[]
  output=target
  s = len(target)
  while(i+2 < s):
      if (output[i] != "\n" and output[i+1] != "\n" and output[i+2] !="\n"):
          ret_list.append(output[i]+output[i+1]+output[i+2])
      i += 1
  return ret_list

if __name__ == "__main__":
  source = ""
  out_s=[]
  param = sys.argv
  f = codecs.open(param[1], "r", "utf-8")
  count=getsource(f)
  f.close()

  f = open(param[1], "r")
  for i in f:
    source += i
  f.close()

  out_s=output_target(source)
  out_s.sort() 

  fout = codecs.open("3gram.txt", "w", "utf-8")
  for i in out_s:
fout.write(i + "\n")

このプログラムの実行方法ですが

$ python make3gram.py (utf-8で書かれたテキストファイル)

で3gram.txtが生成されます。

なお、青空文庫で公開されている夏目漱石の坊っちゃんをutf-8に変換してこのプログラムにくわせて3gram.txtを作成してみました。
まあ、パブリックドメインですから。

なおこれで作った3gram.txtを統計処理してみると頻出する3文字の文字列のTop3は

  1. から、 354回
  2. った。 315回
  3. ない。 306回

うーん文のつなぎの言葉ばかりですね。
なお、名詞だと「シャツ」が169回で最多、「赤シャ」が167回で次点でした。まあ「坊っちゃん」ですからなんとなく分かりますよね。

単純マルコフ連鎖(っぽいなにか)

でこの3gramの列をベースに単純マルコフ連鎖っていう方法で文字列が入力されたら次に来る単語は何が出る可能性が高いかを計算して導いていきます。
今回は単純マルコフ連鎖というよりもっと単純に擬似的単純マルコフ連鎖を組み込んでいます。

単純に言うとある単語が入力されたらその文字で始まる3gramを辞書からランダムに選ぶ。
選択された3gramの最後の文字が次の3gramの開始文字として辞書からランダムに選ぶ。
という方法を繰り返しています。
3gram自体は集計しない生データになっているので、同じ文字の3gramが複数含まれています。よってランダムに選択すれば頻出する単語のほうが選択される可能性が高くなります。

統計としてそのアルゴリズムは怪しいんじゃないのっていう気も実はするのですが、まあ厳密さは気にしません。で適当に文章を作成させるプログラムを書いてみました。

# -*- coding : utf-8 -*-
import sys
import random

# 3-gramのファイル読み込み
def read3gram(source):
  f = open("3gram.txt", "r")
  n = 0
  for cnt in f:
    source += cnt
  f.close()
  return source

# 文字列集合の作成
def create_aggregation(top, source):
  # print(top)
  aggregate=[]
  for i in source:
    tmp=len(i)
    if i.startswith(top) != False :
      aggregate.append(i[0:tmp])
  return aggregate

# 文字列の生成
def generate_sentence(inp, ngram):
  if  inp.startswith(inp) == True:
    top_char = inp[0]
  else:
    top_char = "ゃ"
  print("開始文字は" + "'" + inp + "'")

  size_ng=len(ngram)
  word = random.randrange(size_ng-1)
  
  prn_string=""
  out_str=top_char
  count = 0
  for count in range(1, 30):
    i = 0
    extr_str=create_aggregation(top_char, ngram)
    if (extr_str == False) or (extr_str is None):
      prn_string+="にゃ?"
      top_char ="?"
     continue
    size_ng=len(extr_str)
    if (size_ng < 2) or (size_ng is None):
      prn_string += "にょお"
      top_char ="お"
      continue
    word = random.randrange(size_ng)
    out_str=extr_str[word]
    prn_string = prn_string + out_str[0] + out_str[1]

    i += 1
    top_char = out_str[-1]
  return prn_string
if __name__ == "__main__":
  # 乱数の初期化
  rnd = random.seed()
  source = ""
  # 3-gram ファイルの読み込み
  source = read3gram(source)
  #print(type(source))

  ngram = source.split("\n")

  input_char=""
  while(input_char =="" ):
    print("開始文字を入力してください")
    input_char = input()
  
  print(generate_sentence(input_char,ngram))

このプログラムを「坊っちゃん」から作成した3gram.txtを使って走らせてみた結果がこちら

$ python generate_fm3b.py 
開始文字を入力してください
あ
開始文字は'あ'
あなためにくったのと云って、当の本中されちたろう」と聞いて、直員は磯のよう理窟が分はれの言わなにいましいの人物だ。

うーん、部分部分はいいのですが、文章としては微妙な感じですね。

MeCabを使った単語の連鎖

次に、前回MeCabをインストールしたのでMeCabの機能を使って文章を分かち書きし、単語と次に続く単語の2Wordの組み合わせで分割するプログラム、make_n_list.pyを書いてみました。

# -*- coding: utf-8 -*-
import sys
import MeCab
import codecs

# Textを読み込む
def getsource(src):
  n = 0
  for i in src:
    n += len(i)
  return n

def create_word_list(src):
  out_list=[]
  mecab=MeCab.Tagger("-Owakati")
  tmplist = mecab.parse(src)
  t_list = tmplist.split(' ')
  l_size=len(t_list)
  for i in range(0, l_size-2):
    tmplist=[t_list[i], t_list[i+1]]
    out_list.append(tmplist)

  return out_list

if __name__ == "__main__":
  source = ""
  param = sys.argv
  f = open(param[1], "r")
  for i in f:
    source += i
  f.close
  
  out_s=[]

  out_s=create_word_list(source)
  out_s.sort()

  fout = codecs.open("n_list.txt", "w", "utf-8")
  for i in out_s:
    fout.write(i[0] + " " + i[1] + "\n")

このプログラムで作成したn_lit.txtから単語の連結を同じようにランダムで抽出してくるプログラムを作成してみました。

$ python generate_fm_hm.py 
開始文字を入力してください
あ
開始文字は'あ'
あ岩の、自分のような誰を出すん事がな大だう。それでも隣り同志で中学は小をして、

・・・あまり変わらないかも。
とりあえず、n_list.txtを使ってコマンドラインで会話的なものをするソフトpipitanを作成しました。

プログラム本体はgithubに登録したのでそちらを見ていただくとして、実行例を載せておきます。

$ python pipi_if.py
ぴぴたん Version: 0.01 Start!!
ぴぴたん:メッセージをどうぞ
あなた :こんにちは
ぴぴたん:こんにちは直行なにがとは私なんぞ、当局なにがと、人家なにがしか清は情ない。おれは生来なにがしか入り乱れなにがしかお蔭様なにがと
あなた :一人称がおれなのですね。
ぴぴたん:が、赤シャツのしくじりなにがと無茶苦茶に読んで懲りたけれども、跛なにがと、ぽかん」「貴様もおれなんか、これ
あなた :赤シャツが何をしくじったのですか?
ぴぴたん:か詳しい話だ。野だそうだ、あなた、校長宛てみたら、とはおれに異状なにがしかこの辺をお貰いに
あなた :
ぴぴたん:バイばーい

カオスだ。
次回はないかもしれません<無責任な男

なお、github上においてあるn_list.txtや3gram.txtは流石に「坊っちゃん」のものではまずいかと思い、参考辞書seigaji.txtから作成したものに差し替えています。
実は一部コードも変えていて、単語が見つからなかった時の次の単語は坊っちゃん版では「なにが」にしていますが、github上に登録したスクリプトでは「メール」にしています。この辺課題ですねぇ。

---スポンサードリンク---

LINEで送る
Pocket