Coding Common Raven

イギリスの文系院卒(修士)のワタリガラスによるプログラミング挑戦記録

Pythonで芥川龍之介の短編小説『羅生門』をもとにしたシンプルなテキストゲームを作る

f:id:commonraven2019:20190620183606p:plain

 この記事について

今回はPython芥川龍之介の短編小説『羅生門』を基にしたシンプルなテキストゲーム「羅生門」を作ります。アドベンチャー要素のない単純なインタラクティブフィクションか紙媒体のゲームブックに近く、選択肢のある小説と言った方が的確で、ゲームとしての面白味は欠けていると思います。選択肢も2択だけで、主人公の心情に沿って小説の結末を目指し、作者の意図と異なる選択をするとその場でゲームは終了となります。選択肢は国語の試験や参考書で頻出する定番の問いをもとにしています。

以前の記事でGoogle Colaboratoryの無料GPUサービス上でKeras /TensorFlowを使って英詩であるポーの『大鴉』のような文章生成を行いました。その後LSTMモデルに採用した日本語小説の自動生成の練習として同作品『羅生門』で試みましたが、結果が面白くなかったので本ブログでの掲載を取り止め、何か他にできないかと考えて思い付いたのが今回のテキストゲームです。移動の待ち時間などに書いたので、文章生成とは打って変って簡易的なコードです。

参考記事・先行事例

    ↳小説テキストデータの出典です。

    ↳シンプルなテキストアドベンチャーのコードを紹介しています。

  • 他にもGitHubなどで有名なテキストゲームのコードが公開されています。

モジュールとコードの流れ

今回は追加のパッケージなどを使用しないコードで、Pythonを触れたばかりの方でもわかるような非常に初歩的なものです。小説なので、段落や場面の切り替えを視覚的に表すために、timeモジュールをインポートして、スリープ処理のみ行っています。あとは選択肢の答え方、つまり読者(プレイヤー)が入力すべき2択のキーを指定し、if関数で選択肢をわたしているだけです。青空文庫で公開されているパブリックドメインである芥川龍之介の『羅生門』(新字新仮名、ルビ有)のテキストデータを、場面で4分割して各場面を定義し、正しい選択肢を入力すると小説が進むコードになっています。

Pythonをインストールしていない環境でもブラウザ上で実際にコードを動かし、アウトプットをゲームのようにわかりやすく表示するためにTrinketのサービスを使用しました。モバイル・デスクトップなど端末を選ばず、ログインなしで、誰でも無料で確認でき、コードの共有も容易だったのでこのサービスを試してみました。(他に同様の用途・方式でコードを共有可能なサービスがあれば是非知りたいです。) 前述の通り、GPUも必要としない初歩的なコードなので、Google Colaboratoryのクラウドサービス上はもちろんのこと、他のPython向けのWebサービスでもコードを動かすことができます。

実際にTrinketのサービスにアップしたコードは以下のリンク先及び埋め込みの通りです。アウトプット表示欄を拡げて、答えが含まれているコード部分を隠すと、より楽しむことができると思います。リンク先は、モバイルでの表示だとアウトプット部分しか表示されないようになるので、iPhoneAndroidなどのモバイル端末のブラウザ上で開くことが最適かもしれません。

テキストゲーム「羅生門」

 全コード

長くなりますが、以下がコードの全体です。

import time

answer_A = ["A", "a"]
answer_B = ["B", "b"]
required = ("\nAかBを入力して答えてください\n")

def intro():
  print("テキストゲーム「羅生門」")
  time.sleep(2)
  print("")
  print("芥川龍之介『羅生門』")
  time.sleep(3)

def scene1_1():
  print("")
  print("ある日の暮れ方の事である。一人の下人(げにん)が、羅生門(らしょうもん)の下で雨(あま)やみを待っていた。")
  time.sleep(2)
  print("広い門の下には、この男のほかに誰もいない。ただ、所々丹塗(にぬり)の剥げた、大きな円柱(まるばしら)に、蟋蟀(きりぎりす)が一匹とまっている。")
  print("羅生門が、朱雀大路(すざくおおじ)にある以上は、この男のほかにも、雨やみをする市女笠(いちめがさ)や揉烏帽子(もみえぼし)が、もう二三人はありそうなものである。それが、この男のほかには誰もいない。")
  time.sleep(2)
  print("何故かと云うと、この二三年、京都には、地震とか辻風(つじかぜ)とか火事とか饑饉(ききん)とか云う災がつづいて起った。そこで洛中(らくちゅう)のさびれ方は一通りではない。")
  print("旧記によると、仏像や仏具を打砕いて、その丹がついたり、金銀の箔(はく)がついたりした木を、路ばたにつみ重ねて、薪の料(しろ)に売っていたと云う事である。")
  print("洛中がその始末であるから、羅生門の修理などは、元より誰も捨てて顧る者がなかった。するとその荒れ果てたのをよい事にして、狐狸(こり)が棲(す)む。盗人(ぬすびと)が棲む。")
  print("とうとうしまいには、引取り手のない死人を、この門へ持って来て、棄てて行くと云う習慣さえ出来た。")
  print("そこで、日の目が見えなくなると、誰でも気味を悪るがって、この門の近所へは足ぶみをしない事になってしまったのである。")
  time.sleep(2)
  print("その代りまた鴉(からす)がどこからか、たくさん集って来た。昼間見ると、その鴉が何羽となく輪を描いて、高い鴟尾(しび)のまわりを啼きながら、飛びまわっている。")
  print("ことに門の上の空が、夕焼けであかくなる時には、それが胡麻(ごま)をまいたようにはっきり見えた。鴉は、勿論、門の上にある死人の肉を、啄(ついば)みに来るのである。")
  print("――もっとも今日は、刻限が遅いせいか、一羽も見えない。ただ、所々、崩れかかった、そうしてその崩れ目に長い草のはえた石段の上に、鴉の糞(くそ)が、点々と白くこびりついているのが見える。")
  print("下人には:")
  time.sleep(2)
  print("A.雨が止んでも居場所がないのである。")
  print("B.雨がいつ止むのかを考えるよりほかなった。")
  choice = input(">>> ")
  if choice in answer_A:
    scene1_2()
  elif choice in answer_B:
    print("下人は、大きな嚔をして、それから、大儀そうに立ち上がり、差当り明日の暮しをどうにかしようと考えながら雨の朱雀大路にふらふらと消えていった。下人のその後の行方は、誰も知らない(End)")
  else:
    print (required)
    scene1_1()

def scene1_2():
  time.sleep(2)
  print("")
  print("下人は七段ある石段の一番上の段に、洗いざらした紺の襖(あお)の尻を据えて、右の頬に出来た、大きな面皰(にきび)を気にしながら、ぼんやり、雨のふるのを眺めていた。")
  time.sleep(2)
  print("作者はさっき、「下人が雨やみを待っていた」と書いた。しかし、下人は雨がやんでも、格別どうしようと云う当てはない。ふだんなら、勿論、主人の家へ帰る可き筈である。所がその主人からは、四五日前に暇を出された。")
  print("前にも書いたように、当時京都の町は一通りならず衰微していた。今この下人が、永年、使われていた主人から、暇を出されたのも、実はこの衰微の小さな余波にほかならない。")
  print("だから「下人が雨やみを待っていた」と云うよりも「雨にふりこめられた下人が、行き所がなくて、途方にくれていた」と云う方が、適当である。")
  print("その上、今日の空模様も少なからず、この平安朝の下人の Sentimentalisme (サンチマンタリスム)に影響した。申(さる)の刻下りからふり出した雨は、いまだに上るけしきがない。")
  print("そこで、下人は、何をおいても差当り明日の暮しをどうにかしようとして――云わばどうにもならない事を、どうにかしようとして、とりとめもない考えをたどりながら、さっきから朱雀大路にふる雨の音を、聞くともなく聞いていたのである。")
  time.sleep(2)  
  print("雨は、羅生門をつつんで、遠くから、ざあっと云う音をあつめて来る。夕闇は次第に空を低くして、見上げると、門の屋根が、斜につき出した甍(いらか)の先に、重たくうす暗い雲を支えている。")
  time.sleep(2)  
  print("どうにもならない事を、どうにかするためには、手段を選んでいる遑(いとま)はない。:")
  time.sleep(2)
  print("A.(考えるのをやめて飢え死にするのを受け入れよう。)")
  print("B.(生きるために盗人になるよりほかに仕方ない。)")
  choice = input(">>> ")
  if choice in answer_A:
    print("下人は道ばたの土の上で飢え死にした(End)")
  elif choice in answer_B:
    print("")
    print("選んでいれば、築土(ついじ)の下か、道ばたの土の上で、饑死(うえじに)をするばかりである。そうして、この門の上へ持って来て、犬のように棄てられてしまうばかりである。")
    print("選ばないとすれば――下人の考えは、何度も同じ道を低徊(ていかい)した揚句に、やっとこの局所へ逢着(ほうちゃく)した。")
    print("しかしこの「すれば」は、いつまでたっても、結局「すれば」であった。")
    print("下人は、手段を選ばないという事を肯定しながらも、この「すれば」のかたをつけるために、当然、その後に来る可き「盗人になるよりほかに仕方がない」と云う事を、積極的に肯定するだけの、勇気が出ずにいたのである。")
    scene2_1()
  else:
    print (required)
    scene1_2()

def scene2_1():  
  time.sleep(2)
  print("")
  print("下人は、大きな嚔(くさめ)をして、それから、大儀そうに立上った。夕冷えのする京都は、もう火桶(ひおけ)が欲しいほどの寒さである。風は門の柱と柱との間を、夕闇と共に遠慮なく、吹きぬける。")
  print("丹塗の柱にとまっていた蟋蟀も、もうどこかへ行ってしまった。")
  time.sleep(2)
  print("下人は、頸(くび)をちぢめながら、山吹(やまぶき)の汗袗(かざみ)に重ねた、紺の襖の肩を高くして門のまわりを見まわした。")
  print("すると、幸い門の上の楼へ上る、幅の広い、これも丹を塗った梯子(はしご)が眼についた。上なら、人がいたにしても、どうせ死人ばかりである。:")
  time.sleep(2)
  print("A.雨風の患のない、人目にかかる惧のない、一晩楽にねられそうな所があれば、そこでともかくも、夜を明かそう。")
  print("B.門の上の楼を盗人としての差し当たっての住処としよう。")
  choice = input(">>> ")
  if choice in answer_A:
    scene2_2()
  elif choice in answer_B:
    print("下人は門に棲む何者かに襲われ死骸の一つとなった(End)")
  else:
    print (required)
    scene2_1()

def scene2_2():
  time.sleep(2)
  print("")
  print("下人はそこで、腰にさげた聖柄(ひじりづか)の太刀が鞘走(さやばし)らないように気をつけながら、藁草履をはいた足を、その梯子の一番下の段へふみかけた。")
  time.sleep(2)
  print("それから、何分かの後である。羅生門の楼の上へ出る、幅の広い梯子の中段に、一人の男が、猫のように身をちぢめて、息を殺しながら、上の容子を窺っていた。")
  print("楼の上からさす火の光が、かすかに、その男の右の頬をぬらしている。短い鬚の中に、赤く膿(うみ)を持った面皰のある頬である。")
  print("下人は、始めから、この上にいる者は、死人ばかりだと高を括っていた。それが、梯子を二三段上って見ると、上では誰か火をとぼして、しかもその火をそこここと動かしているらしい。")
  print("これは、その濁った、黄いろい光が、隅々に蜘蛛(くも)の巣をかけた天井裏に、揺れながら映ったので、すぐにそれと知れたのである。この雨の夜に、この羅生門の上で、火をともしているからは、どうせただの者ではない。")
  print("下人は:")
  time.sleep(2)
  print("A.守宮(やもり)のように足音をぬすんで、やっと急な梯子を、一番上の段まで這うようにして上りつめた。")
  print("B.守宮のように足音をぬすんで、急な梯子の一番下の段まで降りた。")
  choice = input(">>> ")
  if choice in answer_A:
    scene2_3()
  elif choice in answer_B:
    print("そうして下人は冷え込む黒洞々たる夜の底に消えた(End)")
  else:
    print (required)
    scene2_2()

def scene2_3():
  time.sleep(2)
  print("")
  print("そうして体を出来るだけ、平にしながら、頸を出来るだけ、前へ出して、恐る恐る、楼の内を覗いて見た。")
  time.sleep(2)
  print("見ると、楼の内には、噂に聞いた通り、幾つかの死骸が、無造作に棄ててあるが、火の光の及ぶ範囲が、思ったより狭いので、数は幾つともわからない。ただ、おぼろげながら、知れるのは、その中に裸の死骸と、着物を着た死骸とがあるという事である。")
  print("勿論、中には女も男もまじっているらしい。そうして、その死骸は皆、それが、かつて、生きていた人間だと云う事実さえ疑われるほど、土を捏ねて造った人形のように、口を開いたり手を延ばしたりして、ごろごろ床の上にころがっていた。")
  print("しかも、肩とか胸とかの高くなっている部分に、ぼんやりした火の光をうけて、低くなっている部分の影を一層暗くしながら、永久に唖(おし)の如く黙っていた。")
  time.sleep(2)
  print("下人は、それらの死骸の腐爛した臭気に思わず、鼻を掩(おお)った。しかし、その手は、次の瞬間には、もう鼻を掩う事を忘れていた。ある強い感情が、ほとんどことごとくこの男の嗅覚を奪ってしまったからだ。")
  time.sleep(2)
  print("下人の眼は、その時、はじめてその死骸の中に蹲(うずくま)っている人間を見た。檜皮色(ひわだ)の着物を着た、背の低い、痩せた、白髪頭の、猿のような老婆である。")
  print("その老婆は、右の手に火をともした松の木片を持って、その死骸の一つの顔を覗きこむように眺めていた。髪の毛の長い所を見ると、多分女の死骸であろう。")
  time.sleep(2)
  print("下人は、六分の恐怖と四分の好奇心とに動かされて、暫時は呼吸をするのさえ忘れていた。旧記の記者の語を借りれば、「頭身の毛も太る」ように感じたのである。")
  print("すると老婆は、松の木片(きぎれ)を、床板の間に挿して、それから、今まで眺めていた死骸の首に両手をかけると、丁度、猿の親が猿の子の虱(しらみ)をとるように、その長い髪の毛を一本ずつ抜きはじめた。髪は手に従って抜けるらしい。")
  time.sleep(2)
  print("その髪の毛が、一本ずつ抜けるのに従って、下人の心からは、:")
  time.sleep(2)
  print("A.四分あった好奇心は少しずつ消えてゆき、この老婆に対する恐怖心が一分毎に増してきた。")
  print("B.恐怖が少しずつ消えて行った。そうして、それと同時に、この老婆に対するはげしい憎悪が、少しずつ動いて来た。")
  choice = input(">>> ")
  if choice in answer_A:
    print("恐怖心はこの男を梯子の口まで追いやり、下人はさっき門の下で飢え死にすることを選ぶべきだったと考えながらまたたく間に急な梯子を夜の底へかけ下りた(End)")
  elif choice in answer_B:
    print("")
    print("――いや、この老婆に対すると云っては、語弊があるかも知れない。むしろ、あらゆる悪に対する反感が、一分毎に強さを増して来たのである。")
    print("この時、誰かがこの下人に、さっき門の下でこの男が考えていた、饑死をするか盗人になるかと云う問題を、改めて持出したら、恐らく下人は、何の未練もなく、饑死を選んだ事であろう。")
    print("それほど、この男の悪を憎む心は、老婆の床に挿した松の木片のように、勢いよく燃え上り出していたのである。")
    scene3_1()
  else:
    print (required)
    scene2_3()

def scene3_1():
  time.sleep(2)
  print("")
  print("下人には、勿論、何故老婆が死人の髪の毛を抜くかわからなかった。従って、合理的には、それを善悪のいずれに片づけてよいか知らなかった。")
  print("しかし下人にとっては、この雨の夜に、この羅生門の上で、死人の髪の毛を抜くと云う事が、それだけで既に許すべからざる悪であった。勿論、下人は、さっきまで自分が、盗人になる気でいた事なぞは、とうに忘れていたのである。")
  time.sleep(2)
  print("そこで、下人は、両足に力を入れて、いきなり、梯子から上へ飛び上った。そうして聖柄の太刀に手をかけながら、大股に老婆の前へ歩みよった。老婆が驚いたのは云うまでもない。")
  time.sleep(2)
  print("老婆は、一目下人を見ると、まるで弩(いしゆみ)にでも弾かれたように、飛び上った。")
  time.sleep(2)
  print("「おのれ、どこへ行く。」")
  time.sleep(2)  
  print("下人は、老婆が死骸につまずきながら、慌てふためいて逃げようとする行く手を塞いで、こう罵った。老婆は、それでも下人をつきのけて行こうとする。下人はまた、それを行かすまいとして、押しもどす。")
  print("二人は死骸の中で、しばらく、無言のまま、つかみ合った。しかし勝敗は、はじめからわかっている。下人はとうとう、老婆の腕をつかんで、無理にそこへ?(ね)じ倒した。丁度、鶏(とり)の脚のような、骨と皮ばかりの腕である。")
  time.sleep(2)
  print("「何をしていた。云え。云わぬと、これだぞよ。」")
  time.sleep(2)
  print("下人は、老婆をつき放すと、いきなり、太刀の鞘を払って、白い鋼(はがね)の色をその眼の前へつきつけた。:")
  time.sleep(2)
  print("A.あらゆる悪に対する反感が下人を動かし、正義のために自分が老婆をどうにかしないといけない。")
  print("B.自分が優位に立っていることがわかった下人は、さきほどからの好奇心を満たすことにした。")
  choice = input(">>> ")
  if choice in answer_A:
    print("下人は検非違使の庁の役人を真似て老婆に縄をかけ、私刑を執行して悪人となった(End)")
  elif choice in answer_B:
    scene3_2()
  else:
    print (required)
    scene3_1()

def scene3_2():
  time.sleep(2)
  print("")
  print("けれども、老婆は黙っている。両手をわなわなふるわせて、肩で息を切りながら、眼を、眼球が?(まぶた)の外へ出そうになるほど、見開いて、唖のように執拗(しゅうね)く黙っている。")
  print("これを見ると、下人は始めて明白にこの老婆の生死が、全然、自分の意志に支配されていると云う事を意識した。そうしてこの意識は、今までけわしく燃えていた憎悪の心を、いつの間にか冷ましてしまった。")
  print("後に残ったのは、ただ、ある仕事をして、それが円満に成就した時の、安らかな得意と満足とがあるばかりである。そこで、下人は、老婆を見下しながら、少し声を柔らげてこう云った。")
  time.sleep(2)
  print("「己(おれ)は検非違使(けびいし)の庁の役人などではない。今し方この門の下を通りかかった旅の者だ。だからお前に縄をかけて、どうしようと云うような事はない。ただ、今時分この門の上で、何をして居たのだか、それを己に話しさえすればいいのだ。」")
  time.sleep(2)
  print("すると、老婆は、見開いていた眼を、一層大きくして、じっとその下人の顔を見守った。?の赤くなった、肉食鳥のような、鋭い眼で見たのである。それから、皺で、ほとんど、鼻と一つになった唇を、何か物でも噛んでいるように動かした。")
  print("細い喉で、尖った喉仏の動いているのが見える。その時、その喉から、鴉の啼くような声が、喘ぎ喘ぎ、下人の耳へ伝わって来た。")
  time.sleep(2)
  print("「この髪を抜いてな、この髪を抜いてな、鬘(かつら)にしようと思うたのじゃ。」")
  time.sleep(2)  
  print("下人は、:")
  time.sleep(2)
  print("A.老婆の答が存外、平凡なのに失望すると同時に、また前の憎悪が沸き上がり、冷やかな侮蔑と一緒に、心の中へはいって来た。")
  print("B.老婆の意外な答にひどく驚いたと同時に、死人の髪から鬘をつくるという行為への関心が沸き上がってきた。")
  choice = input(">>> ")
  if choice in answer_A:
    scene3_3()
  elif choice in answer_B:
    print("下人は、盗人ではなく、この老婆に取って代わって鬘をつくって暮らすことにした(End)")
  else:
    print (required)
    scene3_2()

def scene3_3():
  time.sleep(2)
  print("")
  print("すると、その気色が、先方へも通じたのであろう。老婆は、片手に、まだ死骸の頭から奪った長い抜け毛を持ったなり、蟇(ひき)のつぶやくような声で、口ごもりながら、こんな事を云った。")
  time.sleep(2)
  print("「成程な、死人(しびと)の髪の毛を抜くと云う事は、何ぼう悪い事かも知れぬ。じゃが、ここにいる死人どもは、皆、そのくらいな事を、されてもいい人間ばかりだぞよ。")
  print("現在、わしが今、髪を抜いた女などはな、蛇を四寸(しすん)ばかりずつに切って干したのを、干魚だと云うて、太刀帯(たてわき)の陣へ売りに往んだわ。疫病(えやみ)にかかって死ななんだら、今でも売りに往んでいた事であろ。")
  print("それもよ、この女の売る干魚は、味がよいと云うて、太刀帯どもが、欠かさず菜料(さいりょう)に買っていたそうな。")
  print("わしは、この女のした事が悪いとは思うていぬ。せねば、饑死をするのじゃて、仕方がなくした事であろ。されば、今また、わしのしていた事も悪い事とは思わぬぞよ。")
  print("これとてもやはりせねば、饑死をするじゃて、仕方がなくする事じゃわいの。じゃて、その仕方がない事を、よく知っていたこの女は、大方わしのする事も大目に見てくれるであろ。」")
  time.sleep(2)
  print("老婆は、大体こんな意味の事を云った。")
  print("つまり、この老婆は、:")
  time.sleep(2)
  print("A.干魚として売れた蛇のように、死人の髪からつくった鬘も需要があれば問題ないということを言いたいのであろう。")
  print("B.悪事であろうと、飢え死にを逃れるためなら許されるだろうということを言いたいのだろう。")
  choice = input(">>> ")
  if choice in answer_A:
    print("下人は、太刀を鞘におさめて、その太刀の柄を左の手でおさえながら、冷静にこの話を聞き、老婆をよそに、明日の暮らしを支えることができる需要のある商いについて考えることにした(End)")
  elif choice in answer_B:
    scene4_1()
  else:
    print (required)
    scene3_3()

def scene4_1():
  time.sleep(2)
  print("")
  print("下人は、太刀を鞘におさめて、その太刀の柄(つか)を左の手でおさえながら、冷然として、この話を聞いていた。勿論、右の手では、赤く頬に膿を持った大きな面皰を気にしながら、聞いているのである。")
  print("しかし、これを聞いている中に、下人の心には、ある勇気が生まれて来た。")
  print("そう、:")
  time.sleep(2)
  print("A.盗人になる勇気だ。")
  print("B.悪を憎み飢え死にする勇気だ。")
  choice = input(">>> ")
  if choice in answer_A:
    scene4_2()
  elif choice in answer_B:
    print("下人は死に場所を探すため急な梯子を夜の底へかけ下りた(End)")
  else:
    print (required)
    scene4_1()

def scene4_2():
  time.sleep(2)
  print("")
  print("それは、さっき門の下で、この男には欠けていた勇気である。そうして、またさっきこの門の上へ上って、この老婆を捕えた時の勇気とは、全然、反対な方向に動こうとする勇気である。")
  print("下人は、饑死をするか盗人になるかに、迷わなかったばかりではない。その時のこの男の心もちから云えば、饑死などと云う事は、ほとんど、考える事さえ出来ないほど、意識の外に追い出されていた。")
  time.sleep(2)
  print("「きっと、そうか。」")
  time.sleep(2)
  print("老婆の話が完(おわ)ると、下人は嘲(あざけ)るような声で念を押した。そうして、一足前へ出ると、不意に右の手を面皰から離して、老婆の襟上をつかみながら、噛みつくようにこう云った。")
  print("下人は、:")
  time.sleep(2)
  print("A.すばやく、老婆の着物を剥ぎとった。")
  print("B.すばやく、老婆の持っていた鬘のための髪をむしり取った。")
  choice = input(">>> ")
  if choice in answer_A:
    scene4_3()
  elif choice in answer_B:
    print("それから、足にしがみつこうとする老婆を、手荒く死骸の上へ蹴倒した。しかし、すぐさま、老婆は死骸の中から体を起こし、うめくような声を立てながら、まだ燃えている松の木片を下人に押し付けた。そうして、火に悶える下人の太刀を抜き、白い鋼の色を紺の襖からのぞく首めがけて振り下ろした(End)")
  else:
    print (required)
    scene4_2()

def scene4_3():
  time.sleep(2)
  print("")
  print("「では、己が引剥(ひはぎ)をしようと恨むまいな。己もそうしなければ、饑死をする体なのだ。」")
  time.sleep(2)
  print("それから、足にしがみつこうとする老婆を、手荒く死骸の上へ蹴倒した。梯子の口までは、僅に五歩を数えるばかりである。下人は、剥ぎとった檜皮色の着物をわきにかかえて、またたく間に急な梯子を夜の底へかけ下りた。")
  time.sleep(2)
  print("しばらく、死んだように倒れていた老婆が、死骸の中から、その裸の体を起したのは、それから間もなくの事である。老婆はつぶやくような、うめくような声を立てながら、まだ燃えている火の光をたよりに、梯子の口まで、這って行った。")
  print("そうして、そこから、短い白髪を倒(さかさま)にして、門の下を覗きこんだ。外には、ただ、黒洞々(こくとうとう)たる夜があるばかりである。")
  time.sleep(2)
  print("下人の行方は、誰も知らない。")
  time.sleep(2)
  print("完")
  print("データセットは『羅生門』(新字新仮名)‐青空文庫より")
  print("https://www.aozora.gr.jp/cards/000879/card127.html")
  exit()

intro()
scene1_1()
scene1_2()
scene2_1()
scene2_2()
scene2_3()
scene3_1()
scene3_2()
scene3_3()
scene4_1()
scene4_2()
scene4_3()

Google ColaboratoryのGPUクラウドサービス上でKeras /TensorFlowを使ってLSTM(ニューラルネットワーク)によるエドガー・アラン・ポー風の文章の自動生成を行う(自然言語処理/ディープラーニング)

f:id:commonraven2019:20190520063310j:plain

この記事について

ディープラーニング/深層学習(Deep Learning)のライブラリにTensorFlowの公式フロントエンド・ラッパーライブラリであるKeras(ケラス)を使って(KerasをTensorFlowバックエンドで実行して)、リカレントニューラルネットワーク/回帰型ニューラルネットワーク(Recurrent Neural Network: RNN)の一種であるLSTM/長・短期記憶(Long Short-Term Memory)をニューラルネットワーク(Neural Network: NN)に採用し、プロジェクト・グーテンベルク(Project Gutenberg: PG)で公開されているパブリックドメインであるエドガー・アラン・ポー(Edgar Allan Poe)の詩『大鴉』(“The Raven”)のテキストを訓練データとして自然言語処理(Natural Language Processing: NLP)である文章の自動生成を行います。簡単に言うとポーの詩『大鴉』のような文章の生成に挑戦します。

今回は無料でGPUクラウド(Cloud)サービス上で利用することができるGoogle Colaboratoryにてプログラミング/コードの実行をすることにしました。データ量が大変多いので、筆者の低スペックパソコンのローカルCUP処理では、実行結果の取得まで時間がかかることが予想されたからです。

文章の自動生成に挑戦しようと考えたきっかけは、CourseraのDeep Learning SpecialisationのCourse 5: Sequence Models,Week 1: Recurrent Neural NetworksにてLSTMとシェイクスピアの『ソネット集』("The Sonnets")風の文章生成を学んだことにあります。コードについても、そのコース内ものを模している部分が多いですが、同コースの参考文献にもなっているKeras teamがGitHubで公開しているLSTM text generatorのコードも倣ってもいます。文章の自動生成は何度も試みられ、同様のトピックについて書いた記事が、ネット上に既に多く存在しますが、このブログのディープラーニング練習ということで投稿することにしました。この試みの目的としては、成功例に倣ってコードを実行することで、Google Colaboratory上でのKeras /TensorFlowの使用スキルやLSTMによる文章の自動生成の手順を自分のものにするところにあります。



参考記事・先行事例

ディープラーニングで自動筆記 - Kerasを用いた文書生成(前編) - Deep Insider
ディープラーニングで自動筆記 - Kerasを用いた文書生成(後編) - Deep Insider
↳前後編ともにLSTM text generatorを使って日本語の小説を生成の解説。

Keras LSTMでサクッと文章生成をしてみる | cedro-blog
↳LSTM text generatorについての日本語での解説

KerasのSingle-LSTM文字生成サンプルコードを解説 – Qiita
↳LSTM text generatorの各コードを細かく解説

パッケージとライブラリ

最初に、必要なパッケージのライブラリ及びモジュールを取り込み(import)ます。今回はGoogle Colaboratoryを使うので、仮想環境の構築などの手間を省くことができ、ノートブックの設定を、ランタイムのタイプをPython 3、ハードウェアのアクセラレータ/device typeをGPUにするだけで使用できます。TensorFlow, Keras, NumPy(計算)と、Python標準ライブラリのrandomモジュール, sysモジュール, ioモジュールが必要です。

import tensorflow as tf
import keras
import numpy as np
import random
import sys
import io
from __future__ import print_function
from keras.callbacks import LambdaCallback
from keras.models import Model, load_model, Sequential
from keras.layers import Dense, Activation, Dropout, Input, Masking
from keras.layers import LSTM
from keras.utils.data_utils import get_file
from keras.preprocessing.sequence import pad_sequences

テキストの読み込み

エドガー・アラン・ポー(Edgar Allan Poe)の詩:『大鴉』(“The Raven”)のUTF-8形式でファイル名を「theraven.txt」で保存し、ファイルを読み込みます。

from google.colab import files
file = files.upload()
FILE_PATH = "./theraven.txt"
text=""
with open(FILE_PATH, 'r',encoding="utf-8") as f:
for line in f:
lines = line.split()
text += " ".join(lines)
text = text.lower()
len(text)

コードの流れ

テキストを読み込んだら、重複しているキャラクターを排除し、キャラクターを番号に変換、辞書を作成します。文字の区切り(maxlenパラメータ)、スキップ数(step)とシーケンスの設定をします。次にLSTMのモデルを作成します。その後、関数の設定をして、モデルの出力結果を指定し、Callbackを設定、文章を生成できるようにします。これらのコードの流れは、前述のLSTM text generatorコードとほぼ同じなので、本ブログでは割愛します。

今回の実行結果

実際に自動生成されたエドガー・アラン・ポーの詩『大鴉』のような文章は以下の通りです。

"f pallas just above my chamber door;and "

また、【Python】LSTMを使って文章を自動生成 | エンジニアの眠れない夜の記事で解説しているRNN_alice_pub_002のコードを写経し、条件を変更して実行すると以下の文章を生成しました。
HIDDEN_SIZE = 128; BATCH_SIZE = 128; NUM_ITERATIONS = 5; NUM_EPOCHS_PER_ITERATION = 5; NUM_PREDS_PER_EPOCH = 200の場合、

“oject gute oject gutenberg-tm electronic work is death and the poet's tounthe stance the menore and the foundation of the poet's tounthe stance the menore and the foundation of the poet's tounthe stance the menore and the”

HIDDEN_SIZE = 128; BATCH_SIZE = 128; NUM_ITERATIONS = 5; NUM_EPOCHS_PER_ITERATION = 10; NUM_PREDS_PER_EPOCH = 100の場合、

“riodic tax riodic taxed to owner the work and states the project gutenberg-tm electronic works in the poet's pooms, and t” 

雑感

思考回数/エポックを重ねた方が、単に長い文字列を生成しようとするよりも、文章として成り立っている結果を得やすいことがわかります。この記事は/も非常に読み難い悪文になっていますが、筆者の英語で行ってきた学習成果としての理解と日本語での表現・理解をすり合わせるために致し方ないと考えています。次回以降、SimpleRNNなど他のNNを使用した文章の自動生成や日本語の文章生成・自然言語処理にもチャレンジしてみたいです。また、LSTMによる株価・FX予想も試そうと思います。

余談

文章の自動生成の精度向上や、GANを用いた画像認識による判別や画像生成などにも挑戦したいですが、現在所有するマシンはGPUもなく、圧倒的にスペック不足なので、何か作るためにハードウェア環境を整えたいと思っています。今回のようにGPUクラウドサービスを活用する案として、アマゾン ウェブ サービス(Amazon Web Services: AWS)のGPUインスタンスの使用がありますが、インスタンスの止め忘れによるいわゆるクラウド破産が怖くて使用していません。インスタンスの止め忘れを防止のためにモニタリングサービスであるAmazon CloudWatch導入したとしても、リージョンにより料金に変動がある有料・従量制サービスであることが現在の筆者には抵抗があり、使用に二の足を踏んでしまいます。ハードウェア環境が整いかつ居所が定まるまでは、メモリ、連続学習時間、アイドリング時間などの制限があるものの、無料のGPUクラウドサービスであるGoogle Colaboratoryを工夫して使用するのが筆者にとっては現実的かと考えます。


このブログについてとプログラミング関連の学習歴

f:id:commonraven2019:20190504063623j:plain

はじめに

このブログは、文系出身の筆者がMinicondaの環境で主にPython 3を用いた様々なプログラミングに挑戦する記録です。初学者がCoursera Deep Learning Specializationからの学習内容の再現性を意識して、そこで扱っているパッケージやコードを中心にプログラミングにチャレンジするので、新味がない記事もあると予想されます。その点から、「プログラミングに関する知識を記録・共有するためのサービス」であるQiitaにて、ガイドラインで望ましいとされる「投稿者以外の人にとっても価値のある記事」を書くことは難しいかと考えます。しかし、筆者の日本語での備忘録もくしは公開学習メモという性格が強くとも、ブログの投稿内容や知識が誰かの役に立つことがあればとは思うので、はてなブログで記事を書いていきます。

また、ブログ名の通り、このブログを書いているのはあくまでも日本と欧州とをふらふらしているワタリガラスです。誤りなども多く見受けられるかと思います。コメントなどでご助言や間違いのご指摘をいただければ大変嬉しいです。

きっかけ

機械学習やその周辺のプログラミング技術を事業に取り入れることのハードルが随分と低くなり、近い将来にはどの領域の研究者にとってもOfficeソフトやEndnote等の文献管理ソフトの使用スキルのようにツールとして一般化するのではないでしょうか。その時がきた場合に遅れをとらないように、筆者もPython 3などの使用スキルや機械学習の基礎知識だけでも押さえておきたいです。新元号令和の時代の初めに合わせて、新しいチャレンジをしようと思い立ったことがきっかけです。自分の分野の研究に機械学習を研究方法やツールとして取り入れたい、もしくはプログラミングで何か面白いことがしたいと考えています。また、ブログ投稿のネタを用意するということを、コードを書いてプログラミングを継続的に行うための動機付けにしようと思いました。Kaggleへの挑戦や、一つの目標として文系の筆者がPyConのような情報交換会やカンファレンスにも参加して発表できれば良いなとも考えています。もちろん、このブログに書くスキルを研究や仕事につなげることができれば理想的だなと思います。

この記事について

ここでは、このブログの最初の記事として、チャレンジ開始以前の筆者の事前知識を示すため、プログラミング関連の学習歴を簡単にまとめます。筆者自身が何を勉強したか後で確認しやすいように、随時更新して書きとめておきます。無料の学習教材を利用して時間をかけない勉強法を行うことが多いです。

これまでの学習歴(随時更新)

大学院進学以前
  • 義務教育期間中にHTMLでウェブページなどを作成した経験があり、C, Java, JavaScript, PHPの基礎もふれたことがあるのでプログラミングに抵抗はないが、ある程度のスペックのパソコンとサーバー管理などが必要で面倒だと考えていた。

   

  • 高校・大学と文系を選択したが、高校時代の数学担当教員が「文系の範囲だけでは物足りないだろうし、将来何かの役に立てば」と、大学センターで十分な得点を取れるだけの数IIIと数C、旧課程では高校数学に含まれていた範囲の大学数学の初歩を、放課後や長期休業中に教えてくださった。(機械学習には数学の予備知識が必要だと思うので、この期間の学習効果は大きいのではないだろうか。先生ありがとう。)

  • 英語圏における大学院の出願要件を満たす英語力がある。(大学院修了後で考えると、英語での学位論文執筆や学会発表、英語のみの職場で仕事ができる程度の英語運用能力がある。)

大学院在学中
  • イングランドにある大学大学院の修士課程(文系)にて、量的分析についての学習の一環で、STATAの使い方などを勉強した際に、統計学関連の数学を一通り復習。
  • 社会科学分野の若手研究者向け研修に参加し、Rの研究への活用やプログラミングを中級レベル程度まで習得。(RStudioも好んで使っていた。)
大学院修了後 (特に2018年12月末頃から)
  • CourseraでStanford Universityが提供する人気コースのMachine Learningに聴講のみのAuditモードで登録して、機械学習の基礎を勉強(2018年12月末)。
  • 大学や研究者がYouTube上で公開している機械学習やプログラミング関連の動画を複数視聴(2019年1月中と3月中)。
  •  海外のあまり有名ではない全て無料のMOOCsで、復習と学習成果の確認を兼ねてPython 3, Ruby, Java, JavaScript, C, C++, C#, PHP, Swift 4, CSS, HTML, SQL, jQueryの各コースを受講し、修了(2019年2月上旬から中旬)。
  • Google Developer JapanのML Study Jams: Machine Learning初心者向けトレーニングに参加し、Google Cloud Platform(GCP)を利用して機械学習の基礎などを習得するQwiklabのコースを修了(2018年2月下旬)。
  • Courseraでdeeplearning.aiが提供するDeep Learning Specializationの5コース全てを受講登録から4日か5日で修了。学習成果の確認のため、できるだけ早く修了できるようチャレンジしました。より深めたい項目などはノートも取ったので、初学者の筆者にとっては大変良い勉強になりました(2019年4月中旬)。
  • ML Study Jams: Machine Learning中級者向けトレーニングに参加してQwiklabのコースを修了(2018年4月下旬)。
  • Anacondaで仮想環境の構築を試みも、パソコンのスペックがあまり高くないので、Minicondaでの環境に落ち着く(2018年4月下旬)。

このように筆者は主に英語での学習教材やオンラインコースを活用してきましたが、最近になって「世界最大級のオンライン学習プラットフォーム Udemy」のように日本語で無料もしくは低コストかつ高品質なコースを提供するサービスも増えてきたように思えます。日本語での学習をする場合、取り入れても良いかもしれません。

 免責事項

Coding Common Ravenは、記載されている情報の正確さについて可能な限り努力していますが、その背育成や適切性に問題がある場合、告知なしに情報を変更・削除することがございます。

当ブログの情報を用いて行う一切の行為、被った損害・損失に対しては、一切の責任を負いかねます。ご了承ください。

プライバシーポリシーについて

Coding Common Ravenは、「個人情報の保護に関する法律」に基づき、「個人情報保護の基本方針(プライバシーポリシー)」を定め個人情報の適切な管理・保護に努めます。

個人情報保護の基本方針(プライバシーポリシー)

1.個人情報の利用目的をできるだけ特定し、特定された利用目的の達成に必要な範囲を超えて利用することはありません。
2.個人情報を偽りその他不正な手段による取得しません。取得したときは、本人に速やかに利用目的を通知又は公表します。
3.個人情報を利用目的の範囲内で正確かつ最新の内容に保つように努めます。
4.個人情報の漏洩や滅失を防ぐために、必要かつ適切な安全管理措置を講じます。
5.あらかじめ本人の同意を得ないで第三者に個人情報を提供することはありません。ただし、法令に基づく場合、人の生命、身体又は財産の保護に必要な場合、公衆衛生・児童の健全育成に特に必要な場合、国等に協力する場合は除きます。

お問い合わせ

このブログの管理人(筆者)へのお問い合わせフォームはこちらです。
記事の訂正依頼、質問、お仕事の依頼、勧誘など、なんでも御用の方はこちらからお願いします。