dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

Lispの車窓から見た人工知能

はじめに

こんにちは。 機械学習エンジニアの辻です。

本記事はdely Advent Calendar 2018の22日目の記事です。

dely Advent Calendar 2018 - Adventar

dely Advent Calendar 2018 - Qiita

昨日は弊社のサーバサイド・エンジニアの山野井が「【Vue.js】算出プロパティの仕組みについて調べてみた」という記事を書きました!
とてもわかり易く解説しているので興味のある方は是非読んでみてください。

tech.dely.jp

さて本日は「Lispの車窓から見た人工知能」と題しまして、プログラミング言語Lispから見た人工知能の風景を眺めていきたいと思っています。ぼくはEmacs使いのLisperですが、Lispを書くのは自分用のスクリプトや、Emacs Lispの設定変更といったものだけで、ふだんの機械学習に関するプロダクションコードでは一切使っていません。ではなぜそんなLisp本職でないぼくが、Lispの歴史から人工知能の風景を眺めるかといいますと、実はLispと人工知能とはとても縁の深い関係にあるからなんです。昨今では、機械学習といえばPythonやC++を用いるのが主流となっていますが、黎明期には実はLispがその役割を担っていました(Lisp専用のハードウェアがふつうに販売されていたほどです)。その辺りの流れや様子などの風景を、象徴的な書籍などを少しずつかいつまみながら気楽にご紹介していけたらなぁと思っています。

目次

Lispと人工知能の父

f:id:long10:20181206102030p:plain

この方は言わずと知れたジョン・マッカーシー(John McCarthy)ですね。
人工知能(Artificial Intelligence、以下はAIと称します)という言葉の提唱者であると同時にLispの生みの親でもあります。つまり、その点においてはLispと人工知能は同じ親を持つ兄弟のような関係ともいえるわけです。
マッカーシーのAIに関する功績は偉大過ぎるほど偉大で、遡ること今からなんと60年以上も前、1956年にAIに関する世界初の国際会議(ダートマス会議)を主催されたのでした。実は「人工知能の父」と称されるあのマービン・ミンスキーもこの会議を機にAI研究者を志し、その3年後にはMITでマッカーシーを師事することになったのです。
また、AIにおいてしばしば議論の的となる「有限の情報処理能力しかないロボットには、現実に起こりうる問題全てに対処することができない」という、このフレーム問題についても、パトリック・ヘイズとともに最初に提唱されたのがやはりマッカーシーでした。
その一方で、コンピューターサイエンスに関するマッカーシーの功績も超絶偉大で、実はいまでは当たり前のように用いられている「ガベージコレクション」技法を発明されたのもマッカーシーなんですね。そして、タイムシェアリングシステム(メインフレームからエンジニアをはじめたぼくにとってはTSSはとても馴染みがありますが、一般的にはどうなんでしょうか?)の技術によって(水道や電力のように)コンピュータの能力や特定のアプリケーションを販売するビジネスモデルを生み出すかもしれないともマッカーシーは提唱されましたが、いかんせん当時のハードウェアもソフトウェアも通信技術も未熟であったために1970年代中ごろには消えてしまいました。しかし恐るべきことに、21世紀になってからこのTSSの考え方はアプリケーションサービスプロバイダにはじまり、グリッド・コンピューティングを経てクラウドコンピューティングへと昇華し再浮上してきたことを考えると、まぁなんとういう先見だろうとただただ驚愕してしまいます。
さて、Lispはと言いますと、マッカーシーが最初にLispを発表したのはダートマス会議から4年後(1960年)のことで、それはまさしくAIアプリケーションのための専用プログラミング言語としての誕生でした。
(出典:ジョン・マッカーシー - Wikipedia

Lispの概略

f:id:long10:20181206102931j:plain

ガイ・スティール(Guy L. Steele, Jr)

ここでもしかすると、Lispという言語をあまりよく知らないという方のためにほんの少しだけご紹介したいと思います。Lispは上述の通りマッカーシーによって1960年に生み出されたのですが、実装はというと、当時マッカーシーのもとで大学院生だったスティーブ・ラッセルがマッカーシーのLispに関する論文を読み、それを僅かな期間で機械語で実装してみせてマッカーシーを大変驚かせたという逸話が残っています。そしてこの時こそが、Lispインタプリタが誕生した瞬間だったのです。誕生から幾年月が流れ、1980年代から90年初頭には非常にたくさんのLisp方言が乱立することになりました。それはある意味ではLispという言語仕様の、その実装の容易さこそが仇となった結果と言えるのでした。LispにはLisp自体を拡張する(すなわちon Lispする)マクロという機能が備わっていて、そのマクロを用いることで既存のLispの文法構造自体を拡張することが可能であるため、利用者ごとにカスタマイズした方言を生み出すことができます。しかしそれでは管理が大変だということで、乱立するLisp方言たちを一つの言語に統合していかなければという動きがガイ・スティール(Guy L. Steele, Jr)を先頭として始まり、その結果として設計された新しい言語「Common Lisp」は、基本的にそれらの方言たちのスーパーセットであり、それらの方言たちを置き換え統合することになったのでした。そして試行錯誤の末の1994年、ついにANSIはCommon Lispの標準仕様「ANSI X3.226-1994 American National Standard for Programming Language Common LISP」を出版したのでした。ところが悲しいかな、その時には既にC言語など新たな言語の潮流に押し流され、Lispはもはや全盛期に比べると市場のほとんどが失われていたのでした。
(出典:LISP - Wikipedia

COMMON LISP 第2版

COMMON LISP 第2版

  • 作者:Guy L.Steele Jr.
  • 出版社/メーカー: 共立出版
  • 発売日: 1992/06/10
  • メディア: 単行本

言語の登場を簡易的に示した図 f:id:long10:20181211112944p:plain

図中の赤のやじるしは影響を与えた言語を示しています。

Lispの特徴

さらに続けまして、Lispの構文についても少しご紹介しておきたいと思います。
Lispという言語構文の特徴といえば何といってもカッコ、S式です。Lispはすべての構文をこのS式で記述できるので式と文とが区別されず、すべてのコードとデータは式としてシームレスに書き下すことができます。コンパイラによって式が評価されたとき、それは値(または値のリスト)として生成されます。ところで、S式はあまりにカッコを大量に使用するため、その見た目上読みづらい(他の言語構文の仕様と乖離があるため)という批判を受けることがままあります。「Lispは 『lots of irritating superfluous parentheses』(過剰でいらいらさせる大量の括弧)に由来する」というジョークもあるほどです。しかしこのS式によるすべてのコードとデータの同図像性(homoiconic)こそがLispの他言語にない能力を生み出す要因ともなっているわけです。特にLispの関数はそれ自身がリストとして書かれているのでデータとまったく同様に扱うことができます。
果たして、良いコードとは一体なにか、その答えは千差万別で、状況次第なわけですし、これという正解をぼくは知りません。可読性の対象とは人間なのか、否、機械なのか、それともまた他の何かなのか...まあその議論はここでは置いておきましょう。それでは、ほんの少しだけS式構文の例をCommon Lispでご紹介したいと思います。

Lispの構文的特徴としてはカッコもそうですが、前置記法が顕著なものとしてあります。こちらの例でカッコで閉じられた先頭のlistは文字列ではなくリストを返す関数です。

(list 1 2 "foo")
;; => (1 2 "foo")

そして、このようにカッコの前にシングルクォーテーションをつけても上のlist関数と同じ意味となりリストを返します。

'( 1 2 "foo")
;; => (1 2 "foo")

+オペレータを使うと、このように、後続の整数をすべて足し合わせることもできます。

(+ 1 2 3 4)
;; => 10

ラムダ式を記述する場合もこのようにlambdaと記述できて直感的に表現できます。

((lambda (x y) (+ x y)) 3 4)
;; => 7

また、関数を定義する場合はこのように、defunを用いて定義します。
再帰的に階乗を計算する場合はこのように定義します。

(defun factorial (n)
  (if (<= n 1)
    1
    (* n (factorial (- n 1)))))

(出典:LISP - Wikipedia

on Lisp

先ほど少し触れたマクロについても少しだけご紹介しておきたいと思います。
Lispといえばこの方、ポール・グレアムは著書「on Lisp」の前書きの中でこのようにおっしゃっています。

「プログラムをただLispで書くだけでなく、独自の言語をLispを基に(on Lisp)書くことが可能であり、プログラムはその言語で書くことができる。~中略~ Lispは開発当初から拡張可能なプログラミング言語となるよう設計されていた。言語そのものの大半がLispの関数の集合体であり、それらはユーザ自身の定義によるものと何も違わない。それどころかLispの関数はリストとして表現できるが、リストはLispのデータ構造なのだ。このことは、ユーザがLispコードを生成するLisp関数を書けるということだ。」

S式が同図像性(homoiconic)構文であるがゆえに、マクロを使って元の構文を自由に変更したり追加することが可能となります。ほんの少しだけマクロの例についてもCommon Lispを例に触れたいと思います。
マクロ定義はこのように記述します。

(defmacro macro-name (args1 args2)
  (list args1 args2))

この場合、呼び出す際にはこのように記述します。

(macro-name args1 args2)

あまり意味はないですが、ifを拡張してmy-ifを作る場合はこのようになります。

(defmacro my-if (condition then else)
  `(cond (,condition ,then)
         (t ,else)))

(defun fak (n)
  (my-if (= n 0) 1
         (* n (fak (- n 1)))))

リテラルデータをテーブルにputするのにdefmapマクロを作成する場合はこのようになります。

(defmacro defmap (name options &rest entries)
  (declare (ignore options))
  (let ((table (make-hash-table)))
    (dolist (entry entries)
      (setf (gethash (car entry) table) (cadr entry)))
    `(defvar ,name ',table)))

このように、マクロによってLispの構造をどんどん拡張することができます。しかし実際のところ関数で事足りる場合がほとんどです。マクロを使う場合として、ポール・グレアムはこのように示しています。
「ある関数が本当にマクロであるよりも関数であった方が良いというのは、 どうしたら分かるだろうか? マクロが必要な場合とそうでない場合の間には大抵明確な区別がある。基本的には関数を使うべきだ。関数で間に合う所にマクロを使うのはエレガントでない。マクロが何か特定の利益を与えるときのみそれを使うべきなのだ。それはいつだろうか? マクロで実現できることの大部分は関数ではできない。」つまり、関数を書いていて実現できないことをマクロで書くとそういうことですね。ボトムアップなコーディングを推奨されるLispにとってそれはある意味自然なことです。

On Lisp

On Lisp

こちらは、さらにマクロを過激に啓蒙する賛否ある書籍です。とても刺激的な内容になっています。

Let Over Lambda

Let Over Lambda

  • 作者:Doug Hoyte
  • 出版社/メーカー: Lulu.Com
  • 発売日: 2008/05/07
  • メディア: ペーパーバック

最初のAIの春(1956−1974)

ここでLispを一旦離れまして、車窓から見えるAIの風景へと目を移していきたいと思います。何と言っても、ダートマス会議後の数年はジョン・マッカーシーを中心としたAIに対する期待と発見の時代であり、新たな地平を疾走する機関車のような勢いでAIは発展を遂げていきました。しかし今から考えると、この時代に開発されたプログラムのほとんどは推論と探索に頼っていて、どんなに巨額を投じて開発した当時の最高峰のコンピュータだろうと、処理可能な計算量はごく僅かであり、非常に限定的な領域の問題しか解くことはできませんでした。しかし、それでも当時の人々にとって、見た目的にはまるで人間の様に振る舞う機械は非常に「驚異的」に写っていたことでしょう。

ELIZA

f:id:long10:20181210150717j:plain

そんな黎明期の象徴的な出来事として、ジョセフ・ワイゼンバウムは、ELIZAと呼ばれる単純な自然言語処理プログラムを公表されました。それはLispが誕生してわずか6年後の1966年のことでした。ELIZAは診療カウンセラーを装って人間と対話できるプログラムとして紹介され、あたかも人間の返答を理解したような対話をするために、今ではとても単純なパターン照合プログラムが適用されていました。つまり、ELIZAはいわゆる「チャットボット」の先駆けといえます。特に日本では人工無脳という名前で知られていて独自の進化を遂げました。

ちなみに、弊社では毎日こちらの哲学SlackBotからありがたいお言葉を頂いております。 tech.dely.jp

ワイゼンバウムは、ELIZAが多くの利用者や観衆に非常に重大な影響を及ぼしていく様子や、人々がELIZAに心を開く様子を見ていく中で徐々に不安に苛まれるようになりました。そして、あらためてAIという意味について冷静に考えはじめ、後にはAIに対する主要な批評家の1人に転じられることとなったのでした。
有名な1976年の書籍「コンピュータ・パワー―人工知能と人間の理性( Computer Power and Human Reason)」の中で、ワイゼンバウムはコンピュータ技術についてのアンビバレンスを示されています。それはすなわち、「AIは可能であろう。しかし、コンピュータは常に同情や知恵などの人間性を欠くため、人間は決してコンピュータに重要な決定を任せるべきでない」というものでした。
8章人工知能学にはこのような一節があります。「私の主張したいことは、知性についてあまりにも単純すぎる考え方が大衆ならびに科学会の思考を支配し、部分的にはこの考えがもとになって、人工知能学において誤った雄大なる幻想が育まれることになった、ということである。」このような警鐘を鳴らしています。
ちなみに、ワイゼンバウムがELIZAを実装された際のオリジナルはSLIPという独自の言語で実装されていたのですが、これを Bernie Cosell が後にLispに移植されました。(あとで紹介するピーター・ノーヴィグの「実用Common Lisp」にその実装が掲載されています)
(出典:ジョセフ・ワイゼンバウム - Wikipedia

コンピュータ・パワー―人工知能と人間の理性

コンピュータ・パワー―人工知能と人間の理性

余談ですが、Emacsで「M-x-doctor」としてみるとこのようなELIZAと謎の対話を始めることができます。

I am the psychotherapist.  Please, describe your problems.  Each time
you are finished talking, type RET twice.

me> Hi

ELIZA> How do you do?  What brings you to see me?

me> I am hungry

ELIZA> Why do you say you are hungry?

me> You've got to say what?

ELIZA> Is it because of your plans that you say I have got to say what?

ELIZA> What do you think?

SiriにELIZAのことを尋ねると...

f:id:long10:20181211115732p:plain:w250

お知り合いでした。

AIの最初の冬 (1974−1980)

1970年に入ると、それまで破竹の勢いで発展してきたAIでしたが、様々な批判に晒され、その結果として資金も縮小され発展に陰りが見え始めました。その要因として、ワイゼンバウムの警鐘も虚しく、当時のAI研究者たちの過度な期待によって、直面している問題の難しさに対して、正しく評価できなかったことが大きかったのでした。その期待ゆえに楽観的な彼らが予想する成果へのさらなる期待の高まりは、他の研究者や出資者の間で飽和していたにもかかわらず、実際の結果としてその期待に応えるどころか落胆の連鎖を生み、次第にAI研究への出資はほとんど無くなっていきました。今から考えると、1970年代前半のAIプログラムの能力は非常に限定的で、その実態を出資者から見れば、どのプログラムも単なる「おもちゃ」に過ぎませんでした。そしてさらには、その実用化に当たっては、ハード面でのメモリ容量や速度の不足といった性能の限界は深刻な課題でした。
また、AIそのものに対する他学界からの批判が激化し、一部の哲学者はAI研究者の楽観的な主張に強く反論し、ゲーデルの不完全性定理が形式体系(コンピュータプログラムなど)では、人間が真偽を判断できることも判断できない場合があることを示していると主張したのでした。

Gödel, Escher, Bach「GEB」 (1979)

f:id:long10:20181206114124j:plain:w300

さて、ゲーデルといえば、そんな最初の冬が終わりを告げる頃、ダグラス・ホフスタッター(Douglas Richard Hofstadter)によって執筆されたゲーデル・エッシャー・バッハ(Gödel, Escher, Bach)が出版されたのでした。この本は人工知能の問題に関する書籍としては世界中で一般の人々に読まれた最初の古典と言えるのではないでしょうか。当時、ホフスタッターはまだ20代後半という若さでしたが、人工知能問題を高エネルギー物理学、音楽、芸術、分子生物学、文学、といった多彩なテーマに絡めて非常に豊かに記述され当時大変な話題となりました。この本がきっかけになって、人工知能分野へ進むことを決めた学生も大勢いたと言われています。
未だファンの多い書籍であり、内容も多岐に渡るので概略をご紹介するのは恐れ多いことですが、気楽に、誤解を恐れず一ファンの個人的な感想として紹介させていただくと、この本のエッセンスは、バッハのカノンのパターンとエッシャーの『描く手』に現れていると言えるのではないかと思います。
それは、右手が左手を、左手が右手を描いているという極めて奇妙な絵です。(『描く手』の実物を、今年上野で「エッシャー展」が開かれましたので見て感激しました)

f:id:long10:20181206201324j:plain

この絵を「手」そのものの次元で観察したときには、どちらが描く方で、どちらが描かれている方なのか、判断することは不可能です。つまり、互いに描きあう手は自己言及を繰り返しその環の中で閉じているといえます。しかし、一方で観察者が「絵」としての次元で観察したときには、その背後の、右手、左手の両方の創造者である、エッシャー自身の描かれてない「描く手」がメタ的に控えています。そしてさらには、この絵を描くエッシャーを鏡に映したなら、エッシャーの絵をさらに鏡の向こうの環に閉じるという連続が無限に続いています(『3つの球体』の試み)。このように互いに描き合う「手」を起点として渦巻く無限ループからいったん離れ、自分のしていることをメタ的に眺めることができるのは、人と機械(AI、論理構造、システムetc...)の最大の違いであるという点にホフスタッターは言及するのでした。(こうして今「手」について記述しているぼく自身がしている行為も無限ループの一環に閉じているのかもしれません。)
この、人間と機械の違いというのは、「矛盾」の取り扱いに対して如実に現れ、人は自分の考えに矛盾した点を見つけても、全精神が崩壊したり、思考活動を停止してエラーを吐き出すようなことにはなりません。その代わりに、矛盾を引き起こしたと思われる信念や前提、推論方法を振り返り吟味しはじめます。すなわち、その中で矛盾が生じたと思われるシステムから外に出て、それをメタ的に修復しようと試みること、この「アナロジー」としてエッシャーのこの「描く手」やバッハのカノンのパターン、あるいは、特徴的な「アキレスと亀」の掛け合いを用いて議論が進行していきます。
ちなみに、書籍中の数ページではLispについて言及されている箇所もあって、「すべてのコンピュータ言語の中でもっとも重要でしかも魅力的なもののひとつ」として紹介されています。とくに面白いのが、『描く手』になぞらえて「自分自身の構造の中に入り込み、それを変更するように設計されたLispプログラムについても、これに比類できる両義性が生じうる。もしそれを「Lisp」そのものとして眺めれば、自分自身を変更しているといえるだろう。しかし、レベルを移して、LispプログラムをLispインタープリタのデータと見るなら、事実動いている唯一のプログラムはインタープリタであり、なされた変更は単にデータ片の変更のみでインタープリタ自身の変更は免れている」このように述べられています。

メタマジックゲームは、「サイエンティフィック・アメリカン」でホフスタッターが連載していたコラムを中心に一冊にまとめられた書籍でGEBよりも具体的なテーマを扱っています。17章ではまるっと1章「人工知能言語Lispの楽しみ」と題されたLispに関して記載されています。「私個人としては、setqや副作用のある関数を徹底的に嫌っているわけではない。関数型プログラミングのエレガンスも捨てがたいが、これだけで大きな人工知能プログラムを組むのはちょっと無理だと思う。」実に当時から関数型プログラミングの議論の論点は変わっていないみたいです。

メタマジック・ゲーム―科学と芸術のジグソーパズル

メタマジック・ゲーム―科学と芸術のジグソーパズル

こちらは今年翻訳が出版された書籍で、GEBから40年以上の年月を経た今、ホフスタッターの考える「人間の認知」についての考察がGEB同様様々な視点からつづられています。非常に興奮して一気に読み終えました。

わたしは不思議の環

わたしは不思議の環

2度目のAIの春 (1980–1987)

ゲーデル・エッシャー・バッハ(Gödel, Escher, Bach)の出版を皮切りにして(因果関係があるかどうかは言及しません)、1980年代に入ると再びAIが脚光を浴び始めます。この頃から、AIプログラムの一形態である「エキスパートシステム」が世界中の企業で採用されるようになり、その知識表現がAI研究の中心となっていきました。エキスパートシステムとは、特定領域の知識について質問に答えたり問題を解いたりするプログラムのことで、専門家の知識から抽出した論理的ルールを使用して解を導いていきます。エキスパートシステムはあえて扱う領域を狭くし(それによって1回目の冬の轍を踏まないよう常識的知識の問題を回避する)、単純な設計でプログラムを構築しやすくすると同時に運用中も修正が容易となりました。このエキスパートシステムは実用的なプログラムであり、それまでのAIが到達できていなかった段階にまで到達していくことが可能となったのでした。

Structure and Interpretation of Computer Programs「SICP」(1985)

f:id:long10:20181210145955j:plain:w250

この頃の書籍の代表としてはやはりこちらでしょう。
SICPこと『計算機プログラムの構造と解釈』(Structure and Interpretation of Computer Programs)が刊行されたのが1985年のことでした。この本の中で用いられている例にはすべてScheme(主要なLisp方言の一つ)が用いられていて、抽象化、再帰、インタプリタ、メタ言語的抽象といった計算機科学の概念の真髄が説明されています。
こちらも序文から当時の様子が伺える箇所を少し抜粋してみたいと思います。「Lispは人工知能のため、重要な応用分野でプログラミングの要求を支えてきた。これらの領域は重要であり続け、LispとFortranは少なくとも次の四半世紀では活発に使われるよう、そこでのプログラマは2つの言語に専念しよう。~中略~ これは人工知能の研究の準備に使われるほとんどの書籍と違いプログラミングの教科書であることに注意しよう。つまり、ソフトウェア工学と人工知能におけるプログラミングの重要な関心事が、検討するシステムが大きくなるにつれて合体する傾向なのである。これが人工知能以外でもLispへの関心が増しつつあることへの説明だ。」
最初の苦い冬の経験を経て、人工知能の研究がもたらした様々な課題への取り組みは実用的なプログラミングの問題解決に活用できる汎用的な成果であるということが窺い知れます。

AIとLispの冬 (1987−1993)

1980年代後半となり、再びAIに冬の時代が訪れます。そしてこの冬はAIとともにLispにとっても過酷な冬の時代でした(四半世紀で活発に使われることを望んでいたのですが)。
その最初の兆候となったのは、1987年にAI専用ハードウェアの市場が突然崩壊したことでした。その背景にはアップルやIBMのデスクトップコンピュータの性能が徐々に向上していったことがあり、当時非常に高価だったAI専用のLispマシンは性能的に凌駕され、Lispマシン5億ドルの市場があっという間に消えてしまったためでした。
それから1990年代初頭にかけて、AIには確率と決定理論がもたらされ大きな影響を受けることとなりました。ここで多くの実用化されたツールが、ベイジアンネットワーク、隠れマルコフモデル、情報理論、確率的モデリング、古典的最適化などを活用し始めることになりました。
またこの頃から、ニューラルネットワークや進化的アルゴリズムといった「計算知能」パラダイムのための正確な数学的記述も発展してきたのでした。この発展によりもたらされた再現性の恩恵によって、元来はAI研究者が開発したアルゴリズムだったプログラムなどが大規模システムの一部として使われ始めたのでした。
この流れによって、SICPでも述べられていた通り、実はそれまでAI研究を通じて様々な非常に難しい問題が解決されてきたという事実、そして、その解法は極めて実用的であったことが証明されはじめたのでした(例えば、データマイニング、産業用ロボット、物流、音声認識、銀行のソフトウェア、医療診断、Googleの検索エンジンなどがまさにその例です)。ところが残念なことに、こういった良い流れがありながら、それらの産業における実用的成功が、実はAIのおかげだという事実が世間的に知られることはほとんどありませんでした。それらの技術革新の多くは、達成と同時に計算機科学のありふれたアイテムの一つとして扱われたのです。一体なぜなのでしょうか?
ニック・ボストロムはこれを「AIの最先端の多くは、十分に実用的で一般的になったとたんAIとは呼ばれなくなり、一般のアプリケーションに浸透していく」と的確に説明されています。そういった背景にあって、1990年代の当時の多くのAI研究者は、意図的に自らの仕事をAIとは別の名前で呼んでいたのでした(例えば、インフォマティクス、知識ベース、認知システム、計算知能など)。その理由の一部には、確かに彼らが自分の研究をAIとは全く異なるものだと思っていたということもありますが、背景的にこのような新しい名前をつけることで資金提供を受けられるという面も少なからずあったのです。
とくに産業界では「最初のAIの冬」がもたらした失敗の影が依然として払拭されておらず、ニューヨークタイムズ紙は「無謀な夢を見る人とみなされることを恐れ、計算機科学者やソフトウェア工学者は人工知能という用語の使用を避けた」そのように評しました。

HAL 9000 はどこに?

f:id:long10:20181214100512p:plain

2001年宇宙の旅

さて話はころっと変わりますが、1968年、アーサー・C・クラークとスタンリー・キューブリックは、2001年(もうだいぶ昔に感じますが...)には人間並みか人間を越えた知性を持ったマシンが存在するだろうと想像していました。彼らが創造したHAL 9000は、当時(最初のAIブームの頃)のAI研究者が2001年には存在するだろうと予測していたものだったのです。(このエピソードだけでもだいぶ楽観的だなという感じがします..)これに対して、マービン・ミンスキーは、「そこで問題は、なぜ我々は2001年になってもHALを実現していないのかだ」と問題提起したのでした。

実用Common Lisp

f:id:long10:20181206114232j:plain:w250

1991年にピーター・ノーヴィグによって発表されたこちらの書籍は、人工知能とCommon Lispにおける古典と位置づけられている名著です。
この本が扱っているトピックは、人工知能(AI)、コンピュータプログラミング技術、プログラミング言語Common Lispの3つです。この本をていねいに読めば、AIに対する多くの疑問が解けると同時に、重要なAIの技法も理解できます。実例として、AIの研究で実際に使用されるテキストが多く含まれています。これらは、かつてのAI領域の重要な問題を解決へと導いた応用範囲の広い技法を使用したプログラムの一部でもあります。GPS問題、ELIZA、エキスパートシステムなどAIに2度目の春をもたらした当時の息吹を感じとることができます。

実用Common Lisp

実用Common Lisp

Deep Learningの芽生え

2度目の冬は果てしなく長く続きました。
そしてようやく2000年代となり、今ではよく知られている制限ボルツマンマシンやコントラスティブ・ダイバージェンスの提案が徐々に行われ始めました。これらの提案によって、あのディープラーニングの発明に向かう道筋がつくられていくことになっていくのでした。そしてついに、2006年にジェフリー・ヒントンによるオートエンコーダを利用したディープラーニングが発明されました。この発明は支持され、とくに人手を介さず特徴量を抽出できる点で、人間による知識表現の必要が無くなり、近年のAIにおける大きなブレイクスルーをもたらすこととなるのでした。そしてこの瞬間に、長らく暗黒時代を迎えていたコネクショニズムが突如として復活することとなりました。また同時に、人間が知識表現を行うことで生じていた記号設置問題も解決されたのでした。
ハード面でも、再び春の到来を待ちわびていたかのように、2010年頃にはインターネット上のデータ転送量の指数関数的な増大を受けて、ビッグデータという用語が誕生しそれを取り扱うハードウェアの発展への兆しを見せ始め、そしてまた後を追うようにして、2012年の物体の認識率を競うILSVRCにおける、GPU利用による大規模ディープラーニング(ジェフリー・ヒントンの率いる研究チームがAlex-netで出場した)の大幅な躍進があり、同年のGoogleによるディープラーニングを用いたYouTube画像からの猫の認識成功の発表など、世界各国において再び人工知能研究に注目がぐっと集まり始めたのでした。これ以降のディープラーニングの目覚ましい発展については記憶に新しいところです。

f:id:long10:20181215125517j:plain

こちらの図はガートナー社が今年発表した「先進テクノロジーハイプサイクル」です。ディープラーニングを見ると、過度な期待のピーク期にあるとされていて、いずれは幻滅期へと向かうとされています。しかし、AIは幾度の春と冬とを乗り越えた結果として現在に至り、轍を踏むことなく過度な期待を慎重に実用に転換してきました。「ディープラーニング活用の教科書」の中で、ディープラーニングは今後『ジェネラル・パーパス・テクノロジー(GPT)となっていく』という松尾先生の視点が示されています。GPTというのは、汎用的な目的に利用できる技術のことで、古くは車輪の開発や内燃機関の発明から、最近ではインターネットやナノテクノロジーといった汎用的な技術を指します。かつて2度目の冬では、あえてAI研究を隠していたわけですが、そういうネガティブな意味ではなく、ニック・ボストロムのいう「AIとは呼ばれなくなり、一般のアプリケーションに浸透していく」状態がごく自然な形で浸透して、この先の20年では春や冬という尺度ではないより高次元の汎用的な技術の一つとして扱われることで、これまで以上の驚くべき発展を遂げていくのではないでしょうか?

ディープラーニング活用の教科書

ディープラーニング活用の教科書

  • 作者:
  • 出版社/メーカー: 日経BP
  • 発売日: 2018/10/25
  • メディア: 単行本

まとめ・そしてHylangという選択

いかがでしたでしょうか?
「Lispの車窓から見た人工知能」ということで、現在における人工知能の源流から現在に至るまでの風景をLispというプログラミング言語の視点からご紹介してきました。Lispと人工知能がどちらもジョン・マッカーシーを父に持つ存在として誕生し、幾度となく春と冬の季節の移り変わりを経て、Lispマシン=AIと認知されていた頃に比べると、今ではLispは数あるプログラミング言語の一つとして、人工知能はGPTになりうる技術として、それぞれ別々の道を進むこととなりました(もちろんLispで機械学習のプログラミングができないという意味ではないです)。マッカーシーを始めとした多くの先人たちは、天国から今の様子をどんな思いで眺めていらっしゃることでしょうか。この先、HAL 9000が誕生する日は果たしてやってくるんでしょうか。今回こうしてLispの車窓から人工知能の風景を眺めてみたことで、ぼく自身も広義においてはAI分野を生業にしている端くれである以上、今後も近い場所でこの発展を眺めていけるように日々技術の研鑽や知識のアップデートに努めていきたいと改めて感じることができてとても有意義であったと思っています。

Hylang

f:id:long10:20181215160231p:plain

おまけですが、それでもS式を書きたい!そういう方のための選択肢としてHylangをご提案したいと思います。「Lisp and Python should love each other.」ということで、pythonの資産をすべからくS式で利用できるというプログラミング言語なっています。github.com

↓こちらから面白いreplが試せます。(powered by Symbolics, Inc.!!)

try-hylang

インストールはpipで普通にできます。

pip install hy

jupyter notebook のカーネルとしてhyを利用したい場合はcalysto_hyをインストールします。

pip3 install git+https://github.com/ekaschalk/jedhy.git
pip3 install git+https://github.com/Calysto/calysto_hy.git
python3 -m calysto_hy install

Common Lispの例で示した階乗計算も、hyではこんな感じで同じように書けます。

(defn fact [n]
  (if (= n 0)
    1
    (* n (fact (- n 1)))))

マクロの定義もこのように書けます。

(defmacro incf [var &optional [diff 1]]
  `(setv ~var (+ ~var ~diff)))

(defn plus [&rest args]
  (let ((sum 0))
    (for [i args] (incf sum i))
    sum))

letがないのでletをマクロで追加します。

(defmacro let [var-pairs &rest body]
  (setv var-names (list (map first  var-pairs))
        var-vals  (list (map second var-pairs)))
  `((fn [~@var-names] ~@body) ~@var-vals))

↓詳しくはチュートリアルを参照してください。

Tutorial — hy 0.18.0 documentation

では、簡単なMNISTをTensorflowでトレーニングするチュートリアル・プログラムをちょっとだけhyで示したいと思います。

まず、importから

(import [tensorflow.examples.tutorials.mnist [input_data]])
(import [tensorflow :as tf])

MNISTデータを取得します。

(setv mnist (input_data.read_data_sets "MNIST_data/" :one_hot True))

設定やデータ加工をもろもろおこないまして、

(setv sess (tf.InteractiveSession))
(setv x (tf.placeholder tf.float32 [None 784]))
(setv y_ (tf.placeholder tf.float32 [None 10]))
(setv W (tf.Variable (tf.zeros [784 10])))
(setv b (tf.Variable (tf.zeros 10)))
(sess.run (tf.global_variables_initializer))
(setv y (+ (tf.matmul x W) b))
(setv cross_entropy (tf.reduce_mean (tf.nn.softmax_cross_entropy_with_logits :labels y_ :logits y)))
(setv train_step (.minimize (tf.train.GradientDescentOptimizer 0.5) cross_entropy))
(for [_ (range 1000)]
     (setv batch (mnist.train.next_batch 100))
     (sess.run train_step :feed_dict {x (get batch 0) y_ (get batch 1)}))
(setv correct_prediction (tf.equal (tf.argmax y 1) (tf.argmax y_ 1)))
(setv accuracy (tf.reduce_mean (tf.cast correct_prediction tf.float32)))
;; (print (accuracy.eval :feed_dict {x mnist.test.images y_ mnist.test.labels}))

必要な関数をいくつか定義します。

(defn weight_variable [shape]
     (setv initial (tf.truncated_normal shape :stddev 0.1))
     (tf.Variable initial))

(defn bias_variable [shape]
     (setv initial (tf.constant 0.1 :shape shape))
     (tf.Variable initial))

(defn conv2d [x W]
     (tf.nn.conv2d x W :strides [1 1 1 1] :padding "SAME"))

(defn max_pool_2x2 [x]
     (tf.nn.max_pool x :ksize [1 2 2 1] :strides [1 2 2 1] :padding "SAME"))

関数を用いて変数にデータを入れていきます。

(setv W_conv1 (weight_variable [5 5 1 32]))
(setv b_conv1 (bias_variable [32]))

(setv x_image (tf.reshape x [-1 28 28 1]))
(setv h_conv1 (tf.nn.relu(+ (conv2d x_image W_conv1) b_conv1))) 
(setv h_pool1 (max_pool_2x2 h_conv1))

(setv W_conv2 (weight_variable [5 5 32 64]))
(setv b_conv2 (bias_variable [64]))

(setv h_conv2 (tf.nn.relu(+ (conv2d h_pool1 W_conv2) b_conv2))) 
(setv h_pool2 (max_pool_2x2 h_conv2))

(setv W_fc1 (weight_variable [(* 7 7 64) 1024]))
(setv b_fc1 (bias_variable [1024]))
(setv h_pool2_flat (tf.reshape h_pool2 [-1 (* 7 7 64)]))
(setv h_fc1 (tf.nn.relu (+ (tf.matmul h_pool2_flat W_fc1) b_fc1)))

(setv keep_prob (tf.placeholder tf.float32))
(setv h_fc1_drop (tf.nn.dropout h_fc1 keep_prob))

(setv W_fc2 (weight_variable [1024 10]))
(setv b_fc2 (bias_variable [10]))
(setv y_conv (+ (tf.matmul h_fc1_drop W_fc2) b_fc2))

(setv cross_entropy (tf.reduce_mean (tf.nn.softmax_cross_entropy_with_logits :labels y_ :logits y_conv)))
(setv train_step (.minimize (tf.train.AdamOptimizer 1e-4) cross_entropy))
(setv correct_prediction (tf.equal (tf.argmax y_conv 1) (tf.argmax y_ 1)))
(setv accuracy (tf.reduce_mean (tf.cast correct_prediction tf.float32)))

そして、最後にトレーニングを実行します。pythonで書く場合とほとんど同じですね。

(with (sess (tf.Session))
     (sess.run (tf.global_variables_initializer))
     (for [i (range 20000)]
         (setv batch (mnist.train.next_batch 50))
         (when (= (% i 100) 0)
             (setv train_accuracy (accuracy.eval :feed_dict {x (get batch 0) y_ (get batch 1) keep_prob 1.0}))
             (print (.format "step {0}, training accuracy {1:.2f}" i train_accuracy)))
         (train_step.run :feed_dict {x (get batch 0) y_ (get batch 1) keep_prob 0.5}))
     (print (.format "test accuracy {:.3f}"  (accuracy.eval :feed_dict {x mnist.test.images y_ mnist.test.labels keep_prob 1.0}))))

さいごに

明日は、お待ちかね弊社CTO大竹が「越境型スキルのすゝめ」というタイトルで投稿します!
お楽しみに!