すなぶろスマートフォン用の表示で見るすなぶろPythonistaを目指しているつもりが、Hyのおかげで無事脱線。Clojurian目指すことになりました。この広告は、90日以上更新していないブログに表示しています。前回はAIを作るにあたって、全体の骨組みとインターフェイスを作ってみました。結果として「○○ってなに?」としか発言しない、とてもAIとは呼べないものになっていますが、今回はもう少し反応を改善してみます。sandmark.hateblo.jp前回のおさらい:Responderクラス入力にもとづいて応答を返す思考エンジン。Unmoクラス人工無脳コア。思考エンジンResponderを持ち、ユーザーと思考エンジンを接続する。フロントエンドとりあえずコンソールアプリケーション。ユーザーからの入力を受け取り、Unmoオブジェクトへ渡し、応答結果を表示する。改善するといっても、どうやって行えばいいのでしょうか。インターフェイスがひとつのmain.pyに記述されているのもいただけません。ひとつひとつ見ていきましょう。今回のソースはsandmark/unmoに置いてあります。ファイルを分割する「応答のバリエーションを増やす」ということは、「Responderを増やす」ということに他なりません。Responderクラスの定義が長くなるということなので、ひとつのファイル(main.py)にいろいろなメソッドや関数が増えていくと管理が大変です。インターフェイスの3つのファイルに分割しましょう。短くなったmain.pyからご覧ください。response=response))importが追加され、UnmoとResponderの記述がなくなりました。Unmoというのは、「unmo.pyからUnmoという名前をロードする」という意味です。インターフェイスであるmain.pyからResponderを直接操作する必要はないので、人工無脳コアクラスのロードに留めています。definedというエラーが表示されます。「Unmoという定義されていない名前を使おうとしている」という内容なので、正しいエラーです。ここで知っておいて欲しいのが、「エラーが出たから一巻の終わりだ」という考え方は間違いだ、ということです。本当に怖ろしいのは「間違っているのにエラーが出ない。その結果バグに気づかず、動作がおかしいまま起動してしまうこと」です。おかしいコードに対しておかしいと言ってくれるエラーという存在は、むしろ親のように大切にすべきものです。話を戻して、次は新しく作ったunmo.pyの内容です。"""人工無脳コアクラス。"""文字列を受け取り、コアインスタンスの名前に設定する。Responderインスタンスを作成し、保持する。"""ユーザーからの入力を受け取り、Responderに処理させた結果を返す。self._responder.nameこちらも違いはわずかです。main.pyと違って、UnmoクラスはResponderクラスを扱わなければなりません。Responderとしてロードしています。最後にresponder.pyを見てみましょう。"""AIの応答を制御するクラス。"""文字列を受け取り、自身のnameに設定する。"""ユーザーからの入力(text)を受け取り、AIの応答を生成して返す。self._nameこのファイルからは何もインポートしていません。Responderクラスは他の機能を使わないので、何もロードする必要はありません。main.pyで以前と同じ動作をすることを確認してください。無事ファイルを分割できたところで、Responderを集中して拡張する準備ができました。「○○ってなに?」と返すResponderだけではなく、ランダムな文字列から選んで返すものを追加してみましょう。responder.pyを見てみます。"""AIの応答を制御する思考エンジンクラス。"""文字列を受け取り、自身のnameに設定する。"""ユーザーからの入力(text)を受け取り、AIの応答を生成して返す。"""AIの応答を制御する思考エンジンクラス。登録された文字列からランダムなものを返す。"""文字列を受け取り、自身のnameに設定する。"""ユーザーからの入力は受け取るが、使用せずにランダムな応答を返す。self._name新たにRandomResponderが追加されました。RandomResponder.RESPONSESには、3つの文字列を含むリストが定義されています。responseメソッドを呼び出したときに、このうちのどれかがランダムに選択されて返ってくるわけです。「リストからひとつをランダムに選択する」という機能は、Pythonにデフォルトで用意されています。randomモジュールに含まれるchoice関数で、ファイルの先頭でインポートし、responseメソッドの中で使っています。ユーザーの入力は一切無視する形になるので、responseメソッドの引数は「使用しない」ことを明示する_になっています。Unmoクラスは必ずresponse(text)の形式で呼び出すので、引数は受け取らなければなりません。動作確認Pythonシェルを起動します。r.response('こんにちは、トム。r.response('いいえ、それはトムではありません。r.response('それは花瓶です。')'きのう10円ひろった'もはや会話の体をなしていません。こちらの言うことにある程度の興味を示してくれるResponderのほうが、まだコミュニケーションのしがいがあったというものです。同じコードをまとめるそれにしても、__init__やnameメソッドは変わり映えしませんね。必要だから定義せざるを得ないのですが、仮に「nameメソッドはself._nameではなく'Hey!'という文字列にしたい」という意味不明で抗いがたい欲求に苛まれたときに困ります。ResponderとRandomResponderに定義されているnameメソッドを両方書き換えなければなりませんし、これからもResponderは増えていく予定ですから、同じコードが増えるばかりでメンテナンスが大変です。そこで、共通の機能はResponderにまとめてしまうことにします。「○○ってなに?」と返す思考エンジンには新たにWhatResponderという名前を与えましょう。新生responder.pyはこちらです。"""AIの応答を制御する思考エンジンの基底クラス。継承して使わなければならない。"""文字列を受け取り、自身のnameに設定する。"""AIの応答を制御する思考エンジンクラス。入力に対して疑問形で聞き返す。"""文字列textを受け取り、'{text}ってなに?'という形式で返す。"""AIの応答を制御する思考エンジンクラス。登録された文字列からランダムなものを返す。"""ユーザーからの入力は受け取るが、使用せずにランダムな応答を返す。choice(RandomResponder.RESPONSES)ぐっと短くなりました。WhatResponderとRandomResponderの間で同じ処理をしていた__init__とnameメソッドはResponderクラスにまとめられ、それぞれのクラスに同じコードを書かなくても済みます。これでコピペしたりする必要もありませんし、バグの発生確率もかなり減りました(専門用語でDRYとか『差分プログラミング』といいます)。RandomResponder(Responder)のようにResponderを指定することで組み込むことができます。これを継承と呼び、今回の場合は「ResponderクラスをWhatResponderとRandomResponderクラスが継承している」または「WhatResponderおよびRandomResponderは、親クラスとしてResponderを持つ」と表現します。__init__とnameメソッドは継承により省略できますが、responseメソッドはそうも行きません。いうより、子クラスで独自に定義してもらうものなので、親クラスであるResponderのresponseメソッドの定義が不可能なのです。pass今回は*args引数とpass文を使っています。*argsはいわば「引数はなんでもいい」ことを表すキーワードで、WhatResponderやRandomResponderでは引数が使われたり無視されたりするため、一定の形を持たないことを宣言しています(これが正しいのかどうか私には判断がつかないのでPythonistaからの意見募集中です)。pass文は「何もしない」ことを宣言するキーワードです。Responderは継承されることが前提の骨組みのようなクラスなので、実際に応答を考える必要はありません。いってメソッド定義に何も書かないわけにはいかないので、pass文を使って明示的に「何もしない」と宣言しています。Unmoクラスに反映するでは早速、RandomResponderを使うようにUnmoクラスを変更しましょう。unmo.pyはこんな感じになります。"""人工無脳コアクラス。"""文字列を受け取り、コアインスタンスの名前に設定する。Responderインスタンスを作成し、保持する。"""ユーザーからの入力を受け取り、Responderに処理させた結果を返す。self._responder.nameといってもちょっとしか変わっていません。インポートするものがRandomResponderになり、self._responderの中身がRandomResponder('Random')に変わっただけです。あれだけresponder.pyの中身をいじったのにunmo.pyの変更がこれだけで済むというのは大きな利点です。ファイルを分割したこともそうですが、「RandomResponderはあくまでもResponderの一種である」という保証があるため、Responderを扱う側(Unmo)のコードの変更は最小限で済みます。これがオブジェクト指向プログラミングを行うメリットのひとつであると言えます。オブジェクト指向プログラミングでなくとも、こういった『構造化』はデザインテクニックとして様々な言語で用いられています。Haskellであれば型システムを使って、Lispであればジェネリック関数を使って、C言語においては構造体を使うことで実現しています。オブジェクト指向プログラミングが必ずしも「ベストな答え」を提供するわけではなく、「ベターを目指すための手法のひとつ」に過ぎないことを忘れないでください。すれば、オブジェクト指向言語でなくともクリーンなコードを書くことができます。動かしてみるせっかく書き直したWhatResponderが使われていませんが、それは後の課題として、とりあえず動かしてみましょう。今回はどんなチグハグな会話になるのか楽しみです。改善(?)されたAIとの会話......会話になっているのでしょうか。今回のテーマは『改善』だったにも関わらず、こちらがかなり歩み寄らなければならない状態になっています。こんな調子で大丈夫でしょうか......。複数のResponderを定義することに成功したので、今度はこれらをプログラムの実行中に動的に切り替えてみたいと思います。コンパイルの必要がないとはいえ、いちいちソースを編集していたのでは手間がかかります。プログラミングの真髄は自動化なのです。「どういうときにどのResponderを使うか」を判断して......というのは難しいので後回しにしましょう。現時点のResponder達はそんな高尚なものを扱っても期待に応えてくれるとは思えません。ユーザーの入力を学習してファイルに保存する機能も実装します。『学習』というワードが入ると一気にAIっぽくなりますね。これまでの経験上、過度な期待はしないほうが良さそうですが。予想以上に記事が長くなったので次々回に持ち越しです。【ランクAよりもPython力をつけろ】編スマートPythonプログラミング、引き続き読んでます。ヶ月)実践CommonLispをちょっとだけ翻訳した人。AIブームもっかい来ないかなと思ってたら本当に来て引く。好きな言語はClojureです。Part2はてなブログをはじめよう!sandmarkさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。