気まぐれエンジニア日記

ひよっこエンジニアの雑記

そのアプリケーションから「ユーザの活動」は見えるか

今年もどうぞよろしくお願いします。くまちゃんです。
アドカレを書いて以来、書きたいことが増えたので、また書きます。

はじめに

多機能なアプリケーションで溢れかえる中で、いかに自分のアプリケーションを作っていくかについて、著者なりの考えを「モノ」と「ユーザの活動」に注目して書きます。

日頃使っているアプリケーションには様々な機能がついています。ものによってはその使い方を著した本が書店に並んでいるほどです。そういった超多機能なアプリケーションが身近にあると、自分がアプリケーションを作ろうと思ったときにも、様々な機能を作りたくなります。

けれども、機能を作りすぎるとかえって良くないということもしばしば言われます。使い方について書かれた本があるということは、裏を返せば、本を読まなければ使えないほどアプリケーションが複雑だということです。また、開発者からしても、使われない機能は保守コストを上げるだけとも言われます。

多機能であることが当たり前な一方で、機能のつくり過ぎは災いを招きかねない。こうした難しさの中で、著者がアプリケーションを作る際に心がけていることがあります。それは、アプリケーションが「モノ」中心になると使われにくい機能が増えてしまうから、「ユーザの活動」中心で考えるようにしようということです。

「モノ」中心のアプリケーション

アプリケーションの中には、様々なデータが存在します。そのデータの表現能力を高めることで様々なものを作れるようになったり、多様な表示ができるようになったりする方向性のアプリケーションのことを、ここでは「モノ」中心のアプリケーションと呼ぶことにします。

プレゼンテーション作成ソフトを例にとりましょう。プレゼンテーションには、聞いてくれる人に見せるための「発表資料」がモノとして登場します。多くのプレゼンテーション作成ソフトでは、その発表資料をリッチにするための様々な機能ついています。スタイリングを選べたり、動画や画像、図形を挿入できたり、グラフや表の種類やスタイリングを選択できたり、自由自在です。こうしたアプリケーションは非常に出来が良く、著者自身も頻繁にお世話になっています。

けれども、「こんな図形を使うことってあるんだろうか」あるいは「グラフや表の位置をピクセル単位で調整できる必要はないのに」とふと思う時があるのです。それは、「発表資料」というモノが中心になり、「発表資料」をリッチにしようとしすぎた結果だと著者は考えます。「フロントエンドはJSON色付け係だ」なんていう自虐ネタも、モノが前に出過ぎてしまったが故に生まれた言葉なのかもしれないと、そう思うのです。

「ユーザの活動」中心のアプリケーション

アプリケーションの見方を少し変えてみましょう。アプリケーションのユーザはそこにあるデータを見たり、作ったりすることを含めて、一連の活動を行なっています。その活動を体形的にサポートすることを目指すアプリケーションのことを、ここでは「ユーザの活動」中心のアプリケーションと呼ぶことにします。

先と同じように、プレゼンテーションを例にとりましょう。

プレゼンテーションをすることになったユーザは、どんな活動をするでしょうか。おそらく、いきなり発表資料を作ろうとする人は多くありません。むしろ「いきなり発表資料を作るな」と言われたことがある人もいるのではないかと思います。

著者自身の例を挙げておきましょう。何かの成果報告なら、まずは自分の成果を振り返ります。次に、発表資料のアウトラインを作ります。アウトラインができたら中身を埋めて資料をひとまず完成させます。資料ができたら練習をして、修正を加えます。そうして本番を迎え、フィードバックを受けます。その後、場合によっては、次のプレゼンテーションに向けての活動が始まります。

上記のような一連の活動がプレゼンテーションだと著者は思うのです。「発表資料」はこの活動の一部に登場するものにすぎず、「プレゼンテーション」それ自体ではないと思うのです。だからこそ、「こんな図形を使うことってあるんだろうか」あるいは「グラフや表の位置をピクセル単位で調整できる必要はないのに」と感じてしまうのです。

上手なプレゼンテーションができるようになる方が、発表資料がリッチになることよりも重要だと考えるアプリケーションがもっと増えて良い。そうなれば、使われなくなる機能はグッと減ると著者は考えます。なぜなら、それぞれの機能はプレゼンテーションという活動の上に置かれているからです。

「モノ」と「ユーザの活動」の関係性

ここまでのところで、「モノ」中心のアプリケーションと「ユーザの活動」中心のアプリケーションを対立させて話をしてきました。しかしながら、少し落ち着いて考えてみると、この2つは対立しているのではなく交差していることに気がつきます。

「モノ」中心のプレゼンテーション作成ソフトは、それが発表資料でさえあれば、どんなものでも対応してくれます。図形が入ろうが、動画が入ろうがグラフや表が入ろうが、自由自在です。しかしながら、資料作成以外のことは考えてくれません。それ以外のところは他のツールを使ってやれば良いという立場をとります。

一方、「ユーザの活動」中心のプレゼンテーション作成ソフトは、下準備、アウトライン作成、資料作成、練習、フィードバックという活動全体をサポートしてくれるモノになるでしょう。しかしながら、個々でできるものは決してリッチではありません。リッチなメディアは他のものを使って見せたり作ったりすれば良いという立場をとります。

これら2つは本来、互いに互いを補い合う存在のはずなのですが、多くのアプリケーションは「モノ」中心の考え方で作られています。だからこそ、著者自身は「ユーザの活動」を中心にアプリケーションを考えようと思っています。

アプリケーションの外まで、でも最低限に

「ユーザの活動」を中心にアプリケーションを考えるということをもう少し深掘りします。それは、アプリケーションの外にある活動まで考えること、それでいて活動の各部分において提供する機能は最低限にすることだと思っています。

ユーザの活動に目を向けてみると、「この作業は時間がない中でやらないといけない」だとか、「この時には他の人や別のアプリケーションと連携しながら何かをやる」だとか、アプリケーションの外に広がる感情や光景が浮かんできます。そうしたところも考慮して、うまく体験をデザインする必要があると考えています。

また、提供する機能が最低限であることも重要だと思っています。活動の各部分に対してあれこれ機能を提供しても、機能がユーザの活動のシナリオ、時間の流れの上に乗ってこなければ使われないと考えます。「その機能はいつどうやって使われることになるのか」を流れをもって説明できない機能は消すというのが著者のポリシーです。

おわりに

多機能なアプリケーションで溢れかえる中で、いかに自分のアプリケーションを作っていくかについて、著者なりの考えを書きました。

データの表現能力を高めることで様々なものを作れるようになったり、多様な表示ができるようになったりするアプリケーションのことを「モノ」中心のアプリケーションと名付けました。すでにあるアプリケーションの多くが「モノ」中心で作られており、そこにある機能にはリッチすぎると感じるものも少なくありません。

一方、ユーザが行なっている一連の活動を体形的にサポートすることを目指すアプリケーションのことを「ユーザの活動」中心のアプリケーションと名付けました。「ユーザの活動」中心のアプリケーションでは、それぞれの機能は活動の上に置かれるため、使われなくなる機能はグッと少なくなると考えています。

「モノ」中心のアプリケーションと「ユーザの活動」中心のアプリケーションの関係は、対立ではなく交差です。そして、多くのアプリケーションが「モノ」中心で作られているからこそ、著者はそれらとは別の軸を持った「ユーザの活動」中心のアプリケーションを考えるようにしています。その上で、「ユーザの活動」中心のアプリケーションを作るとは、アプリケーションの外にある活動まで見据えること、それでいて活動の各部分において提供する機能を最低限にすることだと考えています。

あれこれいろんなことはできなくても、ユーザの活動を最初から最後までサポートする。そんなアプリケーションがとても好きです。

思いを表現するということ

一度書き始めてみると、意外と書くことがあるものですね。くまちゃんです。

はじめに

これは 思いを持つということ、伝えるということ - 気まぐれエンジニア日記 の続編となる記事です。先の記事では、思いを持つ、他者の思いを伝えるということに注目しました。思いを持つとは、作るべきものを考え、多数の意見やアイデアから覚悟を持って選ぶことでした。その上で、著者なりのモノづくりとは、考え、覚悟を持って選び、表現するだと述べました。

この記事ではさらにその先、「思いを表現してモノを作るということ」について書こうと思います。「このアプリケーションはこういうものであれ、こういうことを理想とせよ。」という思いが固まった後、それをアプリケーションとして形にする際に著者が大切にしていることを整理します。

表現の制約を見つける

作ろうとするアプリケーションのあるべき姿を思いとして持ったとき、まず最初にやることは、思いをブレイクダウンして表現の制約に言い換えることだと考えています。

例を出しましょう。自分のお気に入りのテキストエディタを開発することを考えます。そのエディタは「文章を素早く編集できる」ことが理想であるとします。このとき「文章を素早く編集できる」ことはどのように言い換えられるでしょうか?例えば、以下のようなものが思いつきます。

  • 文章を素早く編集できる
    • キーボードから手を離さずに装飾を施せる
      • シンプルな記法を書くだけで装飾を施せる
      • キーボードショートカットで装飾を施せる
    • 文字を全部打たなくても書きたいことを書ける
      • 単語、短文の自動補完が出てくる
      • すでに書いた文章のコピペが推薦される
      • リンクを貼るだけでリンク先の文章が出てくる
    • 文章の推敲が支援される
      • 表記揺れの置き換えを一気にできる
      • 段落の入れ替えが簡単にできる

この段階では、すぐにプログラムにできそうなものも、どうやって作ればいいかがまだはっきりしないものもあると思います。でも、それで良いのです。重要なことは、アプリケーションに込めたい思いを分解して、具体的なイメージを描く上での制約として並べることです。アプリケーションの画面の見た目や持っている機能を考えるときに「この制約を満たしていないから、考え直さないとな」という試行錯誤を確実に行うことができます。

見たことがあるものをよく観察する

表現の制約をある程度列挙できたら、具体的なアプリケーションのイメージを描きます。画面の見た目を作ってみたり、持っている機能を整理したり、「なんかこれを作っていけば動きそう」なものがイメージできればゴールです。

ですが、全く何もないところから、具体的な画面、機能のイメージを描くことはとても難しいことです。そこで、すでにあるものをよく観察してみます。テキストエディタであれば、Word、Hack MD、VSCode、Notion、Scrapboxなんでも良いでしょう。どこかで見たことや使ったことがあるものは、何らか優れているところがあるから、目に触れるほど広まっているはずです。一方、自分で新しく作りたい物があるということは、見たことや使ったことがあるもののどれもが解決できていない課題やイケてないところがあるはずです。

この優れているところとイケてないところをしっかりと言語化して整理します。これをやることで、既存のものをベースにして具体的なアプリケーションのイメージを描きやすくなります。また、優れているところは取り入れるように、イケてないところはそうならないようにという形で、表現の制約にさらに磨きをかけることもできます。

ここで取り上げる既存のものは、「一生懸命調査をして探し出す」必要はあまりないと思っています。もちろん、最終的にアプリケーションが出来上がった後に、同じようなものがないかの調査を行うのは良いことです。ですが、ここでやりたいことは、具体的な画面、機能のイメージを描くための助けになるものを見つけることですから、今までに見たことがある、使ったことがあるというのが重要だと思っています。

「真新しい」と「よくある」を線引きする

具体的なアプリケーションのイメージが湧いてきたら、いよいよ作っていきます。この段階で大切にしていることは、「真新しい」と「よくある」を区分けすることです。

自分が時間をかけて新しく作るアプリケーションとなると「今までにないものを作りたい!」という気持ちが強くなるのは当然のことです。でも、このとき、どの部分を自分のアプリケーションにしかない「真新しい」特徴にして、どの部分を「よくある」ものに留めるのかの線引きをしっかりと行うことが重要だと思っています。

「真新しい」機能や操作、見た目ばかりでは、アプリケーションを使う人はその世界観についていくことができず、いいものとは感じてくれないでしょう。逆に「よくある」機能や操作が中心で「真新しい」部分が奥に隠れてしまっては、使う人のほとんどはその真新しい部分に気づくことはできず、「もうこれ〇〇っていうアプリとして世の中にあるよね」と言われてしまうでしょう。このアプリケーションだけの特徴として押し出す機能は一部のユーザがついてこられないリスクを覚悟で作り込み、そうでない部分は「よくある」ものに寄せて作ることが重要だと考えています。

先から例に挙げているテキストエディタを考えてみます。「シンプルな記法を書くだけで装飾を施せる」では、記法を突飛なものにすることは重要ではありません。独自の記法を一部取り入れるのは良いと思いますが、Markdown等の記法を真似た方が、Markdownを使ったことがある人がすぐに使えるという利点を得られます。逆に、「段落の入れ替えが簡単にできる」では、こういった機能を持ったエディタは多くないでしょうから、独自の特徴として押し出すのが良いです。機能をわかりやすい位置に配置したり、ドキュメントに特徴として書きます。

このバランスは非常に難しく、著者もまだまだ勉強中ですが、特徴として押し出す機能は全面に出すこと、普通でいいものをわざわざ奇抜に作らないことを強く意識しています。

フィードバックを得て改善する

アプリケーションがある程度動いて見えるようになったら、なるべくフィードバックを得ます。実際に使ってもらえることが一番ではありますが、難しければデモンストレーションを見てもらって意見を言ってもらうだけでも良いです。

自分では気づかなかったアプリケーションの良いところや足りていないことを気づくきっかけになります。作っている側は、根本にある思いの部分は徐々に当たり前になっていき、どうしても細部に目がいってしまうものです。他の人の目を通すことで、思いは揺らぎないものになっているか、思いを分解した表現の制約は適切だったかという根本を見直すことができます。根本に目を向けなければいけないのは、フィードバックの怖いところでもあるのですが、勇気を持ってフィードバックに望み、改善をしていくのも「考え、覚悟を持って選ぶ」のうちだと思っています。

フィードバックの中で自分が意識して作った部分にちゃんと他の人が気づいてくれたりすると、創作の意欲にもつながります。自分の思いを支持してくれる人もいると思いながらモノを作るのはとても楽しいです。

おわりに

この記事では、思いを持つということ、伝えるということ - 気まぐれエンジニア日記 の続編として「思いを表現してモノを作るということ」について書きました。

アプリケーションのあるべき姿を思いとして持ったとき、まずは思いをブレイクダウンして表現の制約を並べることから始めます。アプリケーションの画面や機能に対して、こんな表現でないといけないという制約を整理し、考えやすくします。

次に、すでにあるものを観察して、優れているところ、イケてないところを言語化します。具体的なものをベースにすることで、アプリケーションのイメージをより明確にします。また、優れている部分は取り入れるように、イケてない部分はそうならないように、表現の制約の磨き上げも行います。

具体的なアプリケーションのイメージが湧いてきたら、実際に作ります。作る上で重要なことは、「真新しい」と「よくある」を区分けすることです。特徴として押し出す機能は全面に出すこと、普通でいいものをわざわざ奇抜に作らないことを意識して、独自の特徴を十分に伝える一方で、ユーザを置いていかないように工夫します。

ある程度アプリケーションが動くようになったら、フィードバックを得ます。フィードバックには、アプリケーションの根本にあった思いを見直すきっかけとしての価値だけでなく、思いに共感してくれる他の人の存在に気づくきっかけとしての価値もあります。

以上が「考え、覚悟を持って選ぶ」の後の「表現する」において、著者が大切にしていることです。

思いを持つということ、伝えるということ

このブログを動かすのはいつぶりでしょうか、くまちゃんです。

これは 徒然 Advent Calendar 2022 - Adventar の23日目の記事です。

はじめに

4月から社会人、ソフトウェアエンジニア1年生になりました。研修やOJTも終わって、業務としてアプリケーション開発を本格的にやり始めています。優秀なチームメンバーに囲まれながら、少しでも貢献しようともがく毎日です。

そのかたわらで、学生時代にやっていた研究を趣味として続けています。研究といっても、自分の欲しいWebアプリケーションを作って修士論文にしちゃおうというものでした。趣味となった今では、自分の欲しいものをより良くするために、せっかく作ったものが死んでしまわないように、細々と活動を続けているという感じです。

ここでは、趣味として今も続く研究、そして新しく始まった業務の中で得た、「思いを持つということ、伝えるということ」について書こうと思います。思いを持ってモノを作るってどういうことなんだろう、思いを伝播させるってどういうことなんだろう、と考えて出した答えです。

思いを持つということ

何もないところから

学生時代に作っていたWebアプリケーションは、授業を受けて内容を整理する、理解するためのツールでした。「クソ真面目なテーマだな」と自分でも思いつつ、勉強をするってどういうことだったのかを学生最後に知りたかったというのもあり、時間をかけて取り組むことにしました。

著者自身の持ち込みで始めたことなので、正真正銘、何もないところからのスタートでした。初めのうちは、「こんな風に授業が整理できたらいいんじゃね?」ととりあえず画面の絵を描いてみて、とりあえず思うように作りました。何ヶ月かやると、なんか動いていそうなものができ、何もないところからモノが出来上がっていく過程を経験できました。でも、このとき、「思うように」作ってはいましたが、それは今考えれば「思い」ではありませんでした。

自分は何を作ったのか

自分の欲しいものを作るとはいえ、あくまで研究なので、成果発表会を乗り越える必要がありました。手元にあったのは「とりあえず思うように作ったもの」ですから、どう発表するのかに苦しむことになりました。どういうことを目指しているか、どんなことを重視して作ったのか、できたものはどんな特徴を持っているのか、といったことを自分の中から「掘り起こす」必要がありました。

自分は一体何を作ったと言えるのだろうか。

このことを日々自問自答して、周りの力も借りながら、懸命に「自分が大事にしていること、その結果できたもの」を言語化しました。この中で、思いの種のようなものが、できていったような気がします。

自分は何を作るべきなのか

「自分はこんなことを重視して、こんな特徴を持ったものを作りました!」

やっとのことで発表が成り立つようになった頃、次の関門が顔をのぞかせます。

「こういう風な機能とかコンセプトみたいなことは考えないんですか?」

こういった質問や意見が寄せられるようになりました。質問や意見が全く間違っているということは少なく、どれも一理あるものでした。けれども、相反する意見や作っているものの根本から変わってしまうような意見も、少なくありません。そんなとき、指導教員がこんな言葉をかけてくれました。

聴衆の言っていることが常に正しいとは限らないんだよ。重要なことは、君がどういう立場を取るかを決めて、言い切ることなんだな。

自分は何を作るべきなのかを徹底的に考える。どういう意見やアイデアを取り入れるのか、捨てるのかを覚悟を持って選ぶ。

これが思いを持つということだと、少しずつ自分の中で思えるようになりました。

自己表現としての、モノづくり

このアプリケーションはこういうものであれ、こういうことを理想とせよ。

こうした思いをどっしりと構えることができれば、雑多な意見も怖くありませんでした。「いいですね、そのご意見いただきます」ということもあったし、「そういうことなら別で作るのがいいかなと思います。ここでは見送りますね」ということもありました。
こうしたことを繰り返すうち、「作っているアプリケーションが自分の思いを含んでいる」から、「自分の思いをアプリケーションとして表現する」に変化していきました。

考え、覚悟を持って選び、表現する。

著者なりのモノづくりを見つけることができました。

思いを伝えるということ

思いに心を寄せる

社会人になると、自分の欲しいものを作ることから、社会が必要としているものを作ることに変化しました。けれども、意外なことに、やることはあまり変わっていないように思います。「何を作ったと言えるのだろうか」、「何を作るべきなのだろうか」と問う相手を自分ではなく、他人にすれば良いからです。

自分が何を作ったと言えるのか、何を作るべきなのか、なかなか見つけられなかった経験が役に立ち、他の人の「どうなると便利なのだろう」という悩みにも、心を寄せることができるようになりました。「しんどいですよね」、「例えばこういう風になると便利ですか?」と言って、相手の思いを懸命に引き出しています。自分が欲しかったものができた時の感動を知っているから、他の人が欲しいものも全力で作るのです。

そして、楽しそうにモノを作っていると、案外他の人も思っていることをぶつけてくれます。「私はこう思う、こういうのが欲しい」と意志を示してくれるので、覚悟を持って選ぶことが伝播した感覚になります。

他者代弁としての、モノづくり

他の人が考え、覚悟を持って選べるように心をよせる。他の人が覚悟を持って選んだものを受け止め、表現する。

これが著者にとっての思いを伝えるということでした。自分の思いを他の人に伝えるプレゼンテーションではなく、他の人の思いを引き出してモノを作る他者代弁が、思いを伝播させることだと思ったのです。学生時代に、モノに思いをのせるのではなく、思いをモノを通じて表現しようと思ったからこそ出せた答えだと思っています。

おわりに

「思いを持つということ、伝えるということ」について書きました。

学生時代の経験から、思いを持ったモノづくりを、著者なりに見つけることができました。思いを持つとは、作るべきものを考え、多数の意見やアイデアから覚悟を持って選ぶことでした。モノづくりをする中で、モノに思いをのせるということから、思いをモノを通じて表現することへと変化していきました。

社会人になってからは、他の人の思いに心を寄せ、覚悟を持って選ぶ手助けをしています。そうして引き出せたことをアプリケーションで表現し、他者の思いを代弁できるように心がけています。この活動こそが思いを伝えることだと感じています。

自分で思いを持ちつづけること、他者の思いを伝えられるようになること、これを大切にしながら、これからもモノづくりをしていきます。

条件付き確率場(CRF)概説 CRF編

前回の記事でCRF以前の多クラス線形識別器まで話をしました. 今回はその続きということで, 本題であるCRFについてみていくことにします.
その前回の記事というのがこれです
kumachan-math.hatenablog.jp
これを前提に話を進めるのでまだの方は先にこっちから読むことをお勧めします.
前置きが長くても仕方ないので早速本題に行きましょう.

1. CRFは何をするか

多クラス線形識別においては, 1つの入力 xに対して, 1つの識別結果 yを与えるのでした. 具体的には, ソフトマックス関数によって定義される"条件付き確率"のようなもの
 \displaystyle p(y|x) := \frac{\exp(\textbf{v}^T\textbf{f}(x, y))}{\displaystyle \sum_{y' \in Y} \exp(\textbf{v}^T\textbf{f}(x, y'))}
を定義し, 正解ラベル y^*に対するこの値の積による尤度, あるいはその対数を取った対数尤度
 \displaystyle L(Y|X) := \prod_{x \in X} p(y^*|x) =  \prod_{x \in X} \frac{\exp(\textbf{v}^T\textbf{f}(x, y^*))}{\displaystyle \sum_{y' \in Y} \exp(\textbf{v}^T\textbf{f}(x, y'))}
 \displaystyle \log L(Y|X) = \log\left(\prod_{x \in X} p(y^*|x)\right) =  \sum_{x \in X} \log p(y^*|x) = \sum_{x \in X} \log\left(\frac{\exp(\textbf{v}^T\textbf{f}(x, y^*))}{\displaystyle \sum_{y' \in Y} \exp(\textbf{v}^T\textbf{f}(x, y'))}\right)

の最大化問題として定式化されるのでした. けれども, 例えば自然言語や音声データを対象として何らかの識別問題を解きたいと思った時には, 入力 xの識別結果が xの情報のみによって決まると考えるのは安易です. 自然言語であれば単語単体ではなく文脈を考慮したような識別ができた方がいいですし, 音声でも前後の発話からの抑揚の変化だったり速度の変化だったりを考慮できた方が良いですよね.
CRFは, 何らか順序を持ったデータをそれ単体で扱うのではなく系列として扱うということをします. 入力は今まで xだったものが順序を持った複数の入力の系列 \textbf{x} = x_1x_2, \cdots, x_Mになるし, 出力も \textbf{y} = y_1y_2, \cdots, y_Mになります.
問題は複雑になりましたが, 式の上ではあまり問題はあまり変わっていません. 最大化するべき対数尤度は次のようになります.
 \displaystyle \log L(\textbf{Y}|\textbf{X}) = \log\left(\prod_{\textbf{x} \in \textbf{X}} p(\textbf{y}^*|\textbf{x})\right) =  \sum_{\textbf{x} \in \textbf{X}} \log p(\textbf{y}^*|\textbf{x}) = \sum_{\textbf{x} \in \textbf{X}} \log\left(\frac{\exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}^*))}{\displaystyle \sum_{\textbf{y}' \in \textbf{Y}} \exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}'))}\right)

ここで, 出力に関しては \textbf{Y} = Y^Mですが, 入力は順序関係の束縛があるため真部分集合 \textbf{X} \subset X^Mです.
以上です. CRFってつまりはこれだけです. 数式の上では. 大事なことなのでもう一度言います.
数 式 の 上 で は
これで終わりです. つまり, まだまだ実際に動くシステムには遠いということです. 問題点を挙げましょう.

  •  \textbf{f}(\textbf{x}, \textbf{y}^*)ってさらっと書いてありますが, これはどうやって見つけるのでしょうか. 実験的に頑張るのでもいいかもしれませんが, もう少し理論に裏打ちされたセンスのある見つけ方というものはないのでしょうか
  • 式を複雑にするのは結構ですが, コンピュータに計算させるとなると計算時間の問題は常について回ります. どうにかして高速に計算できないでしょうか.

例えば, 尤度の式の中に出てくる
 \displaystyle Z_\textbf{x} = \sum_{\textbf{y}' \in \textbf{Y}} \exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}'))
とかは計算が大変そうです. 他にもパラメタ更新を勾配法で行うとき, その更新式の計算は一筋縄では行きそうにありません.

これらの点を順に見ていきましょう.

2. マルコフ性の仮定の導入とCRFの識別

今, 推定したいラベル列を \textbf{y} = y_1\cdots y_{m-1}y_my_{m+1}\cdots y_Mとし, これについて各 y_m y_{m-1}, x_mのみに依存して決まるという仮定をおきましょう. この仮定は問題によって変更をするところですが, ひとまず説明のためにこうしておきます. この仮定の下では次の3種類の特徴を得ることができます.

  •  y_m y_{m-1} x_mが全て関係する特徴
  •  y_m x_mのみが関係する特徴
  •  y_m y_{m-1}のみが関係する特徴

 y_{m-1}の値はすでに決まっていますから,  x_m y_{m-1}のみが関係する特徴は考える必要がありません. これらの特徴をいくつか定義することができれば, それらを各成分にもつベクトルを \textbf{x}のうちの位置 mにおける局所的な特徴ベクトルと考えることができます. この局所的な特徴ベクトルを \pi(y_m, y_{m-1}, x_m)と書くことにします.  \textbf{x}全体の特徴ベクトルを得るときには, 最も単純にはそれらを足し合わせれば良いですから.
 \displaystyle \textbf{f}(\textbf{x}, \textbf{y}) := \sum_{m = 1}^M \pi(y_m, y_{m-1}, x_m)
と定義すれば良さそうです.
まずは, 学習のステップは考えないで, 学習が終わった後の識別のステップを考えましょう.  \textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y})の最大化に注目します. 最大化における変数は他でもなく識別結果のラベル列 \textbf{y} = y_1\cdots y_{m-1}y_my_{m+1}\cdots y_Mです.
 \displaystyle \textbf{v}^T \textbf{f}(\textbf{x}, \textbf{y}) = \textbf{v}^T \sum_{m = 1}^M \pi(y_m, y_{m-1}, x_m) = \sum_{m = 1}^M \textbf{v}^T \pi(y_m, y_{m-1}, x_m)
ですから,
 \displaystyle r_\textbf{x}(m, y_m, y_{m-1}) := \textbf{v}^T \pi(y_m, y_{m-1}, x_m)
と定義すれば,
 \displaystyle \textbf{v}^T \textbf{f}(\textbf{x}, \textbf{y}) =  \sum_{m = 1}^M r_\textbf{x}(m, y_m, y_{m-1})
とかけます. 改めて問題を明示的に式で書けば,
 \displaystyle \textbf{y}^* = \arg\max_{\textbf{y} \in \textbf{Y}} \sum_{m = 1}^M r_\textbf{x}(m, y_m, y_{m-1})
です.

これ, どうやってときましょうか.  \textbf{y} \in \textbf{Y} = Y^Mは組合せ爆発の問題を抱えていて, 時間軸上順番を持った系列を考えており, 各 y_m Yの元それぞれを選択肢として持っていて,  y_1\cdots y_{m-1}でどんなラベル列を選んできたかが y_mの選び方を束縛しないわけです. こうきたら, 典型的な動的計画法...ですよね. DPと分かれば漸化式を書いてみましょう. 時間的な流れ(今まで変数を mと書きました)を一方に, それぞれの選択肢たるラベル Yの元(今まで変数を yと書きました)を他方の次元に持つようなDP行列 R(m, y)を考えれば良さそうです.
 \displaystyle R(1, y) =  r_\textbf{x}(1, y, \mathrm{BOS}), ~~~ R(m, y) = \max_{y' \in Y} \{R(m-1, y') +r_\textbf{x}(0, y, y')\}
ここで,  \mathrm{BOS}はBeginning Of Sequenceのことで, 系列の開始を表すダミーのラベルです. あとはこのDP行列を計算して, 最大成分のところからバックトラックすれば, 識別結果のラベル列 \textbf{y} = y_1\cdots y_Mが得られます. バックトラック...説明した方が良ければコメントでください.

3. 分配関数の計算

学習のステップにおいて最大化したい尤度の中に
 \displaystyle Z_\textbf{x} = \sum_{\textbf{y}' \in \textbf{Y}} \exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}'))
という式が出てきました. この関数のことは分配関数と呼んでいます. これを愚直に計算しようと思うと |Y|^M回もの内積計算が必要になります. つまりラベルの種類の数に関して指数オーダーです. 誰かさんの言葉を借りれば, 恥ずかしいほど遅いを通り越して論外です. いいですか, 論 of the 外です. もう少し効率の良い計算方法はないものだろうか, そう考えるのは自然なことです.
せっかく上で r_\textbf{x}(m, y_m, y_{m-1})を定義したので, それを用いて分配関数の定義を書き直してみましょう.
 \displaystyle Z_\textbf{x} = \sum_{\textbf{y}' \in \textbf{Y}} \exp(\sum_{m = 1}^M r_\textbf{x}(m, y'_m, y'_{m-1})) = \sum_{\textbf{y}' \in \textbf{Y}} \prod_{m = 1}^M \exp(r_\textbf{x}(m, y'_m, y'_{m-1}))
式の見た目が煩雑になるのが嫌なので,
  \displaystyle \hat{r}_\textbf{x}(m, y'_m, y'_{m-1}) :=  \exp(r_\textbf{x}(m, y'_m, y'_{m-1}))
と置きます. この(因数分解と展開の意味で)展開された式を因数分解してみようと思うと
 \displaystyle Z_\textbf{x} = \sum_{\textbf{y'} \in Y^M}^K \prod_{m = 1}^M \hat{r}_\textbf{x}(m, y'_m, y'_{m-1}) = \sum_{y'_1 \in Y} \hat{r}_\textbf{x}(1, y'_1, \mathrm{BOS}) \sum_{y'_2 \in Y} \hat{r}_\textbf{x}(2, y'_2, y'_1) \sum_{y'_3 \in Y} \cdots \sum_{y'_M \in Y} \hat{r}_\textbf{x}(M, y'_M, y'_{M-1})
となります. 少々煩雑ですが, ただの因数分解なので, ゆっくり式を追えば必ず理解できます(頑張って!). ここまでくれば, この計算を再帰的に行うことができると言うことに気がつくと思います.
 \displaystyle \zeta_\textbf{x}(m, y'_m) = \sum_{y'_1 \in Y} \hat{r}_\textbf{x}(1, y'_1, \mathrm{EOS}) \sum_{y'_2 \in Y} \cdots \sum_{y'_{m-1} \in Y} \hat{r}_\textbf{x}(m-1, y'_{m-1}, y'_{m-2}) \hat{r}_\textbf{x}(m, y'_m, y'_{m-1})
とおくと, これらは漸化式
 \displaystyle \zeta_\textbf{x}(1, y'_1) = \hat{r}_\textbf{x}(1, y'_1, \mathrm{EOS}), ~~ \zeta_\textbf{x}(m, y'_m) = \sum_{y'_{m-1}\in Y} \zeta_\textbf{x}(m-1, y'_{m-1})\hat{r}_\textbf{x}(m, y'_m, y'_{m-1})
を満たします. あとはこの漸化式に従って行列を埋めていけば,
 \displaystyle Z_\textbf{x} = \sum_{y'_M \in Y} \zeta_\textbf{x}(M, y'_M)
として分配関数を求めることができました. 組み合わせ爆発で困ったら, とりあえずDPを検討すればいいみたいですね.

次の章のために, もう一種類の漸化式を考えておきましょう.
 \displaystyle \eta_\textbf{x}(m, y'_{m-1}) :=\sum_{y'_m\in Y} \hat{r}_\textbf{x}(m, y'_m, y'_{m-1})\sum_{y'_{m+1}\in Y} \cdots  \sum_{y'_M\in Y} \hat{r}_\textbf{x}(M, y'_M, y'_{M-1})
とおくと, これらは漸化式
 \displaystyle \eta_\textbf{x}(M, y'_{M-1}) = \sum_{y'_M\in Y} \hat{r}_\textbf{x}(M, y'_M, y'_{M-1}), ~~ \eta_\textbf{x}(m, y'_{m-1}) = \sum_{y'_m\in Y} \hat{r}_\textbf{x}(m, y'_m, y'_{m-1})\eta_\textbf{x}(m+1, y'_m)
を満たして,
 \displaystyle  Z_\textbf{x} =  \eta_\textbf{x}(1, \mathrm{EOS})
となります.

4. CRFのオンライン学習

最尤推定によって学習を進めるにあたって, 謎の変数がまだ1つ残っています. というより, この謎の変数が学習によって調整をするものな訳ですけれどね. そう,今まで定ベクトルとか言ってだましだまし考えていた \textbf{v}です. この \textbf{v}をうまく決めましょう, ただし評価関数は対数尤度
 \displaystyle \log L(\textbf{Y}|\textbf{X}) = \sum_{\textbf{x} \in \textbf{X}}\log p(\textbf{y}^*|\textbf{x}) = \sum_{\textbf{x} \in \textbf{X}} \log\left(\frac{\exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}^*))}{Z_\textbf{x}(\textbf{v})}\right)
にしましょうというのがCRFの学習そのものです. オンライン学習で勾配法によって \textbf{v}を更新するときの更新式は \alpha_tを学習率として,
 \textbf{v} \leftarrow \textbf{v} - \alpha_t (\nabla l)( \textbf{v})
とかけます. ここに,  \textbf{x}, \textbf{y}^*を新しく追加したデータとして
 \displaystyle l(\textbf{v} | \textbf{x}, \textbf{y}^*) := \log p(\textbf{y}^*|\textbf{x}) = \log\left(\frac{\exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}^*))}{Z_\textbf{x}(\textbf{v})}\right) = \textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}^*) - \log Z_\textbf{x}(\textbf{v})
です. このとき,
 \displaystyle (\nabla l)( \textbf{v}) = \textbf{f}(\textbf{x}, \textbf{y}^*) - \frac{1}{Z_\textbf{x}(\textbf{v})}(\nabla Z_\textbf{x})( \textbf{v})
となりますが,
 \displaystyle (\nabla Z_\textbf{x})( \textbf{v}) = \sum_{\textbf{y}' \in \textbf{Y}}  \exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}'))\textbf{f}(\textbf{x}, \textbf{y}')
ですから,
 \displaystyle (\nabla l)( \textbf{v}) = \textbf{f}(\textbf{x}, \textbf{y}^*) - \frac{1}{Z_\textbf{x}(\textbf{v})}\sum_{\textbf{y}' \in \textbf{Y}}  \exp(\textbf{v}^T\textbf{f}(\textbf{x}, \textbf{y}'))\textbf{f}(\textbf{x}, \textbf{y}') =  \textbf{f}(\textbf{x}, \textbf{y}^*) - \sum_{\textbf{y}' \in \textbf{Y}}  p(\textbf{y}'|\textbf{x}) \textbf{f}(\textbf{x}, \textbf{y}')
と綺麗な形に整理されました.

今から, 第2項を \zeta_\textbf{x}(m, y'_m) \eta_\textbf{x}(m, y'_{m-1})を使って表現することを考えましょう. もしこれがうまくいけば, 分配関数の計算過程で出てきた副産物を使いまわすことができるので計算時間はグッと短縮されます. 第2項の総和では Y^Mの元全てについて考えるのですから, 局所的にある連続した2つのラベルを見たときには,  Y^2の全てについて考えていると言えます. 入力として \textbf{x}が与えられたとき位置 mにおいて, 直前のラベルが y'であって位置 mのラベルが yとなるソフトマックス関数の意味での確率を p(y, y'|\textbf{x}, m)と書くことにしましょう. すると, 上の第二項は \pi(y_m, y_{m-1}, x_m)を用いて,
 \displaystyle \sum_{\textbf{y}' \in \textbf{Y}}  p(\textbf{y}'|\textbf{x}) \textbf{f}(\textbf{x}, \textbf{y}') = \sum_{(y, y') \in Y^2} p(y, y'|\textbf{x}, m) \pi(y, y', x_m)
とかけます.  p(y, y'|\textbf{x}, m)は, 分散関数の値を分母に持って, その計算過程において, (m-1, y')と(m, y)を通る値の総和を分子に持つ比によって書けるので, ある位置 mより先の値の総和を計算している  \eta_\textbf{x}(m+1, y_{m-1})とその直前の位置 m-1までの値の総和を計算している \zeta_\textbf{x}(m-1, y')用いれば,
 \displaystyle p(y, y'|\textbf{x}, m) = \frac{\eta_\textbf{x}(m+1, y)\hat{r}_\textbf{x}(m, y, y')\zeta_\textbf{x}(m-1, y')}{Z_\textbf{x}}
となります. 晴れて目標達成です.

5. まとめ

CRF概説はここまでとなります. 色々数式をいじりましたが, お気持ちをまとめると次の二点です.

  • CRFは, 何らか順序を持ったデータをそれ単体で扱うのではなく系列として扱い, 入力系列 \textbf{x}から識別ラベルの系列 \textbf{y}を推定するための手法です.
  • ソフトマックス関数によって確率のようなものを定義して, それによる尤度の最大化問題として学習ステップを行い, 内積によるスコアの最大化問題として識別ステップをおこないます.

はー長かったですね. お疲れした〜!

条件付き確率場(CRF)概説 CRF以前編

みなさま, いかがお過ごしでしょうか. 筆者は春休みの研究がちょっと落ちつきまして新しいことをしようかなーと考えています. きっと春休みに研究なんぞしようかと考える学生はそうそういないかと思いますがね. 今日も平常運転, くまちゃんです.

さて, その新しいことをしようかなーの関係でCRFの勉強をしといたほうがいいかなということで, ちょっとドキュメントを読んでいます.
そのドキュメントというのはこちらです
条件付き確率場の理論と実践
今回は, 筆者の頭の整理にお付き合いいただく目的で, CRFについて簡単(図が出てこないので書くのは簡単)にまとめていきたいと思います. 今回は前編でCRFの直前のところについて書いていきます.

1. ロジスティック回帰

まずはデータの2クラス分類問題を考えてみましょう. 2クラス分類問題とは, 入力 xが与えられたとき, それが A,  B = A^cのどちらに属するかを推定する問題のことです. 簡単な例をあげると, 入力として英文中の英単語が与えられたとき, それが人の名前であるか人の名前でないかを推定する問題は2クラス分類問題になります. パターン認識の枠組みでは, 入力を特徴ベクトルという何らかの数字の並びにうまく変換して, 特徴ベクトルの格好から1つクラスを推定します.
今, 入力である英文中の英単語全体の集合をX, 特徴ベクトルをd次元の実数の組 \mathbb{R}^d, 出力をクラス全体の集合 Y = \{1, -1\}と書くことにしましょう. 2クラス分類問題ですから, 人の名前であることを1が出力されること, 人の名前でないことを-1が出力されることだとしても問題ないことに注意してください.
すると, 入力から特徴ベクトルを生成する関数(特徴抽出器と呼びます) \phiは, 次の写像です.
 \displaystyle  \phi: X \to \mathbb{R}^d
パターン認識ではこの特徴抽出器をいかに作るかが非常に重要, というか研究することは特徴抽出器の設計に他ならないのだけれど, ここでは研究者が非常に頑張って, うまく特徴抽出器が作れたとしましょう. その上で, どのような基準でクラスを決定するか, もっと言えば1, -1のどちらを出力するかについて考えていきます.

特徴ベクトルが与えられたときに, それが2つのクラスのうちのどちらかであるかを推定するには, 特徴ベクトル全体の空間 \mathbb{R}^dを2つのグループに分けてしまえば良いです. 一方のグループであればそれは一方のクラスの特徴ベクトルだと思うことができるからです. 特徴ベクトル全体の空間 \mathbb{R}^dを2つのグループに分ける方法として, 最も単純なのは1つ平面を決めてしまってそれを境界面にしてしまうことです. 今, ベクトル \textbf{w} \in \mathbb{R}^dを法線ベクトルにもつ境界面が識別平面を与えるとしましょう. そのとき, 識別の条件は次のように書くことができます.

 \displaystyle
y =
\begin{cases}
1 & \textbf{w}^ T \phi(x) + b > 0 \\ -1 & \textbf{w}^ T \phi(x) + b < 0 \\
\end{cases}
\Leftrightarrow
y(\textbf{w}^T\phi(x) + b) > 0

 \displaystyle
\left(\begin{matrix}
 \phi(x) \\
b
\end{matrix}\right)
\to
 \phi(x) 
~~~
\left(\begin{matrix}
 \textbf{w} \\
1
\end{matrix}\right)
\to
\textbf{w}
とベクトル拡張した形で書き直せば,  y\textbf{w}^T \phi(x) > 0とかけます. さて,  y\textbf{w}^T \phi(x)の部分をロジスティック関数
 \displaystyle \sigma (z) := \frac{1}{1+ \exp(-z)}
に代入してみましょう. ロジスティック関数は値域が (0, 1)であるような単調増加関数です. 入力が xであるときに, 出力が y \in Y = \{1, -1\}となる条件は y\textbf{w}^T \phi(x) > 0と書けますが, この値は大きい方が「安全に」識別できて優位と考えるべきでしょう. したがって,
 \displaystyle  \sigma (y\textbf{w}^T \phi(x)) = \frac{1}{1+ \exp(-y\textbf{w}^T \phi(x))}
 (0, 1)の値を取り, 大きい方が優位と考えることができます. したがって, 今この値は識別平面のパラメータを \textbf{w}とするときに xを入力として yを出力する"条件付き確率"のようなものだとみなすことができます. これは正確には確率ではないので「みなす」だけですが, 1つ評価軸ができたのは非常に大きいです. 確率だと思うことにしようと言いましたから, 記号も確率っぽくしてしまいましょう.
  \displaystyle  p(y|x) :=  \sigma (y\textbf{w}^T \phi(x)) = \frac{1}{1+ \exp(-y\textbf{w}^T \phi(x))}

2. ロジスティック回帰の学習

 xを入力として yを出力する"条件付き確率"のようなもの定義することができましたから, 入力 xと正解ラベル y^*が与えられたときには,  p(y^*|x)を最大化するように \textbf{w}を決めてやれば良いということになります. 入力データと正解ラベルの組みが大量に与えられたときには, この確率の積
 \displaystyle L(X, Y) := \prod_{i = 1}^N p(y_i|x_i)
を最大化するように \textbf{w}を決めてやれば良さそうです. この値は尤度と呼ばれ, 尤度の最大化によるパラメタ推定の手法を最尤推定と呼びます. 時間軸に沿った系列データとして入力データと正解ラベルの組み与えられる場合には, 尤度にはただの積以上の意味を見いだすことができるのですが, ここでは系列データに限らないのでただの積として捉えることにします. ここまでくれば, 識別平面のパラメタ \textbf{w}の決定問題は次の最適化問題として定式化されることになります.
 \displaystyle \textbf{w}^* = \arg\max_{\textbf{w} \in \mathbb{R}^d} L(X, Y)
ここから先の勾配法を用いて実際に最適解を求める手法については, 他の場所でも広く取り扱われていますから, ここで改めて深く言及する必要はないでしょう. 勾配法と言っても様々なアルゴリズムがありますが, それらについてより知識を得たい方は, 例えばこんなドキュメントがオススメです.
An overview of gradient descent optimization algorithms

3. 多クラス線形識別器

今までは入力データ xが与えられたときに出力を1と-1のどちらにするべきだろうかという2クラス分類問題を考えてきました. しかしながら, 残念なことに世の中の一般の問題は2つのうちのどちらであるかの識別だけでは解くことができないものがほとんどです.  K種類のラベルがあってそのうちのどれであるかを識別したいなんてことはしばしばありますし(例えば, 解決済みですが単語の品詞分類タスク), 2クラス分類問題だけを考えていたつもりでも, クラスAかクラスBか"そのどちらでもない"かが問題となることは珍しくありません. ここからは, 3クラス以上の分類タスクを考えることにしましょう.
先まで考えていたパラメタ \textbf{w}は, 識別平面の法線ベクトルの末尾に切片 bを連結したものとして捉えていました. ここでは, その捉え方を少し変えてみましょう.  \textbf{w}はクラスAの代表ベクトルであるという捉え方です. 実際,
 \displaystyle s(1 \cdot \textbf{w}^T \textbf{w}) =  \frac{1}{1+ \exp(-\textbf{w}^T \textbf{w})}
なるスコア値は, (少なくとも\textbf{w}と大きさが等しいベクトルの中では)もっとも値が大きくなりますから,  \textbf{w}自身は真っ先にクラスAに識別されるべきベクトルということになります. 同様に, クラスBの代表ベクトルは -\textbf{w}となります.
他クラス線形識別においては, この代表ベクトルの考え方を採用しましょう.  K個のクラスの代表ベクトルをそれぞれ \textbf{w}_1, \textbf{w}_2, \cdots, \textbf{w}_Kとします.  K-1個のクラスの識別ができれば,  K個目のクラスは"それ以外"だと思えばいいので, 実はこれらのベクトルは一次独立ではなくて
  \displaystyle \textbf{w}_1 + \textbf{w}_2 + \cdots + \textbf{w}_K = \textbf{0}
なる関係式が成り立つとしても良いです. そのため, 実質的なパラメータとしては1つ少ないです. けれども, 以降はこういった細かいことは特に気にしなくても問題ありません. 代表ベクトルが決まってしまえば, 入力が xであるときに, 出力が y \in Y = \{1, 2, \cdots, K\}となる条件は, 2クラス識別問題の時と同様に考えることができて,   \textbf{w}_{y'}^T\phi(x)がなるべく大きい方が優位ですから.
  \displaystyle y = \arg \max _{y'\in Y} \textbf{w}_{y'}^T\phi(x)
とかけます.
上の内積の格好で話を進めてもいいのですが, もう少し一般化をしてみましょう. 上の格好では, 出力である識別クラスの情報を \textbf{w}_yたちが持っていて, 入力の特徴ベクトルの情報を \phi(x)が持っているといったように, 入力と出力がベクトルとして完全に分離できるという制約をおいています. だけれども, この分離可能性の制約は必須ではありません. より複雑な識別をしたいと考えたときには, この制約は邪魔になってしまいます. そこで次のように考えましょう. 何らかの定ベクトル \textbf{v}をうまく決めておきます. そして, 入力 xとクラス yを引数にとって,  x yに分類しようと考えたときの特徴ベクトルを返す関数を \textbf{f}(x, y)を用意します. その上で,
  \displaystyle y = \arg \max _{y'\in Y} \textbf{v}^T \textbf{f}(x, y)
として識別結果を決めます.  \textbf{f}(x, y)が何者であるかはブラックボックスですが,  yが,  xが分類されるべきクラスであれば有意な値を多く持つベクトルとなるだろうし, そうでなければほとんどゼロベクトルに近いか識別に否定的なベクトルを返す関数であることが期待されます. この方法が先の \textbf{w}_yたちによるものの拡張であることを確認しておきましょう.
  \displaystyle \textbf{v} =
\left(\begin{matrix} \textbf{w}_1 \\ \textbf{w}_2 \\ \vdots \\ \textbf{w}_K \\ \end{matrix}\right)
~~~~~
\textbf{f}(x, y) = \left(\begin{matrix} \textbf{0} \\ \vdots \\ \textbf{0} \\ \phi(x) \\ \textbf{0} \\ \vdots \\ \textbf{0} \\ \end{matrix}\right)
< y番目
とおけば(これらは行列ではなくて d \times K次元の長い列ベクトルです),
  \displaystyle y = \arg \max _{y'\in Y} \textbf{v}^T \textbf{f}(x, y)

  \displaystyle y = \arg \max _{y'\in Y} \textbf{w}_{y'}^T\phi(x)
と等しくなります. すなわち,  \textbf{f}(x, y)による表現方法は \textbf{w}_yによる表現も含んでいることが確認できました.
次に, 2クラス分類問題の時と同様に xを入力として yを出力する"条件付き確率"のようなものを定義することを考えましょう. これが定義できれば, あとは最尤推定の問題に帰着されますから, 解くことができます. 2クラス分類問題の時にはロジスティック関数を用いましたが, 多クラスの場合にはそれを拡張したともみれるソフトマックス関数を用いてみましょう.
 \displaystyle p(y|x) := \frac{\exp(\textbf{v}^T \textbf{f}(x, y))}{\displaystyle \sum_{y' \in Y} \exp(\textbf{v}^T \textbf{f}(x, y'))}
あとは最尤推定の問題として最適化問題を解きます.


CRF以前編は以上です. 次の記事で本格的にCRFを扱います.
お疲れ様でした〜

「プログラミング」を教えない情報系学科の意義に関し, 一学生が考えること

はじめに

筆者も情報系学科の3年生となり, 自らの職業の決定というものが遠い将来というわけでもなくなってきました. 筆者自身は大学院への進学を決めましたが, 情報系学科の大学院進学率は機械系や物理・化学系と比べると低く, 学部4年で卒業・就職を考えている学生も少なくありません. 筆者の周りにも少なからずそんな学生がいます. 就活をする, あるいは就活をしようと考えている彼らから, よくこんなことを耳にします.

  • 大学の成績は就活においてほとんど役に立たない. バイトやインターン等の実務経験の有無ばかりが重視されてしまっている.
  • 就活に向けて開発の経験を積みたいのに, 課題が忙しくてほとんど時間が取れない.
  • 大学で教えることと実務でやることが乖離しすぎている. 大学では理論的なことばかり教えられて, プログラムの実装力を上げる機会が与えられない.
  • 扱うプログラミング言語も実務と講義で全く異なる. あんな言語使わないのに講義でなぜやるのか.

筆者は大学院に進むと決めたために, 就職が目の前に迫っているわけではないとはいえ, 2年後には同じような思いをすることは目に見えています. こうした大学と企業の思惑の差とも言えるものをどう考えるかということは到底他人事にはできません. 筆者は企業側の言い分を詳しく聞いたわけではないので, 企業がどう考えているだろうという想像はしないことにします. そうではなくて, 大学が, 自分たちが教えていることと実務との差に関してどう考えているんだろうか, 学生はそれをどう捉えるべきなんだろうかということを想像してみたいと思います.

大学の言い分

講義や学内の就活説明会, 研究室配属説明会で聞かされる, 大学が目指しているものを少し整理してみましょう. 著者の通う大学では, こんなようなことを教員から聞きました.

  • このご時世, すぐにコードが書けるようになるとか言って即戦力になる人をを育てるような目的で教えているところもありますが, 大学としてはそう言った小手先のテクニックではなくて, 長い目で見て役に立つ力を身につけてほしいと考えています.
  • 与えられた仕様をプログラムにするなんてことは誰だってできる. 今や小学生だってね, コードを書いて立派にものを作ったりしているわけだ. 大学を出た君たちがやることはそこじゃないだろうってことですよ.

こんなことを大学はいう一方, 企業は実務経験の有無を面接で聞いているらしく, そこに大きな差があるようです. そしてその差によるしわ寄せは, 他でもない大学と企業の狭間にいる我々学生にやってくるわけです. これはあんまりではないか, そう考えるのも不思議ではありません. 筆者だってそう思います. もっと大学で実務経験に近いことを教えてくれれば, もっと企業が大学のいう長い目で見て役立つことに目を向けてくれれば, そう思います.

だけれども, 大学の教員は決してバカではない, それどころか博士課程を出て研究を続け, 教員をやっているわけですから, 多くの学生より, 社会人より頭はいいはずです. そうであれば, こうした大学と企業の思惑の差に教員が全く気づいていないと考えるべきではないように思います. それでもなお, このような差を解消しようという方向にならないのか, 実装力の強化に特化したような講義を多く配置しないのか, そこを考えていく必要がありそうです.

4年間で理論から実務まで, それは無理難題

話は少し変わりますが, 情報科学と聞いたとき, どんなものを思い浮かべるでしょうか. ちょっと大学の講義を思い返してみました.
論理回路・電気回路・ハードウェアデバイス, コンピュータアーキテクチャ, コンパイラ, オペレーティングシステム, データベース, 情報理論と符号化, 通信技術, 信号処理, ネットワーク・Web, チューリングマシンと計算量, アルゴリズムとデータ構造, 手続き型パラダイム, オブジェクト指向パラダイム, 関数型パラダイム, ソフトウェア分析設計手法, 最適化・パターン認識
まだあるかもしれません. 忘れていたらその科目の先生ごめんなさい. 今パッと思いつくのはこんなもんです. いや, パッと思いつくだけでこれだけあるんです.

著者は大学に入って初めてプログラミングを経験しました. 大学1年からプログラミングを始めて, たった3年でこれだけのことを勉強してきたわけです. 逆に言えば, 大学はこれだけのことをプログラミングを始めて1, 2年の学生も多くいる中で教えているわけです. 冷静に考えてとんでもないことだと思いませんか. 例えば, 数学なんかは小学校の算数から入って, 少なくとも大学の教養まで(数学科の人はもっとですが), 13年かけて大学レベルまで持っていくわけです. それが情報科学になった途端, たった3, 4年で「大学レベル」と言えなければいけないわけです. 筆者が賛同するかは別としても, 大学が6年一貫教育, すなわち院進を前提としたような話し方をするのも, 無理はないのかもしれません.

学部の間にこれだけのことをやらなければいけない, しかも時間は3, 4年しかない. そうなったとき, 当然ながら全てを端から端まで教えることは不可能で, 何かしらの取捨選択が必要になるであろうことは想像に難くありません. だったら大学は企業の求める実務経験の方を教えてくれればいいじゃないか, なぜそちらの方を捨てて, 理論的な方ばかりやらされるんだ. せめて, 課題を減らすなり実務寄りの課題を出すなりしてくれればいいのに. 次にそんな声が聞こえてきそうです. この点を考えていきましょう.

大学は学生に「期待」している

なぜ大学は, 実務寄りのことはほとんどやらずに, 理論的なことばかりやるのでしょうか. しかも, 「就活に役立たない」課題がたっぷりと出るんでしょうか. そのせいでバイトやインターンにかけられる時間が減るのでしょうか. 「大学は研究機関であり, 職業訓練校ではないから」というド正論も一理ありますが, 大学と企業の思惑に差があることにおそらく気がついていながら, いつまでも意地を張っているというのは, 仮にも頭のいい教員たちの考えることだとは思えません. そこには何らかの意図があるのではないか, 著者はそう考えました. 著者の答えは1つです.
大学は学生に「期待」している
これには, 2つの意味があります.


1つ目. 大学で学ぶことがちゃんとわかってしまえば, いわゆる就活で求められるような実務経験とかいう「簡単」なものは, すぐに手に入れることができるはずだという期待です.

実務経験が「簡単」というと多方面から刺されそうなのでちゃんと言っておきましょう. 先ほど, 課題を減らすなり実務寄りの課題を出すなりしてくれればいいのに. 次にそんな声が聞こえてきそうだと言いました. では, この通りにして差し上げようではありませんか. すると, どんなことが起こるか考えてみましょう. 先も言ったように, 大学で教えられる期間はたった3, 4年しかありませんから, 取捨選択が必要です. 大学で実務経験に近いこともやりましょうとなれば, 自ずと今課題で出されているもの, 講義で扱っていることをやめる必要があります. やめた分はどうなるでしょうか. 簡単です. 学生が自力でやるしかありません. 今, 実務経験はバイトやインターン, あるいは個人的にプロダクトを作るなど大学の外で自力でやっているわけですから, それが一部逆転する. ただそれだけの話です.

さて質問です.

  • 理論的なことを大学で学び, 残った少ない時間で, 大学ではやりきれない自力で実務に近いことをやる
  • 実務経験を大学でも積めるようにし, 残りの時間で理論的なことのうち大学で扱えなくなったことを自力で勉強する

この2つではどちらが易しいか.

筆者は圧倒的に上をとります. そういうことじゃないですかね. 全てのものはトレードオフです. キャッシュのウェイ数を多くすれば, コンフリクトミスは起こりにくいけどマルチプレクサが増えるのでアクセス時間が大きくなるんです. 全部いいところだけなんてできないわけです.
そうであれば, 大学で理論寄りの根本的なことがわかって, それを元に自力で実務経験を積もう, バイトをしよう, インターンを受けてみようというのは, 少なくとも大学で勉強していることを自力でやろうと思うよりは「簡単」だから, 大学では理論的なことを中心に教えようと教員が考えるのは, 実に自然だと思います. 実務が絶対的に簡単だ, 誰でもできるといっているのはではありません. どれも難しいことなのだけれど, 大学やる理論的なことの方がより自力のハードルは高いはずだと考えているのではないでしょうか. 加えて, 大学に来られるような頭を持っている人たちならば, 例えば, 大学でCを勉強すれば, Javaを勉強すれば, バイトで使うPythonは, Rubyは, C#は, TypeScriptは, きっと自分で勉強できるはずだと, 「期待」しているのではないかと考えます.


2つ目. もう二十歳を超えた, あるいは目前にしたいい大人なんだし, それぞれのことにどれだけ時間を割くかというマネジメントは自力でできるはずだという期待です.

これは筆者の苦手なことです. 大学で課題が多いと嘆いている筆者を含める人たちですが, それは多分大学の課題に時間を割きすぎなんだと思います. テキトーにこなそうと思えばいくらでもテキトーにできる. それが大学の課題の持つ性質かと思います. レポートを書けとは言われても, 多くの場合, 字数指定がありません. 簡単に終わらせることもできるし, 大作にすることもできます. 問題を解く課題なら, 他人の答案を丸写ししてはダメですが, 誰かと協力して少しずつ解くことはできます. 全部きっちり自分でやって本当に理解することもできます. 単位をたくさん落としてしまうのは問題ですが, 半分以上90点以上の評価を取らなければ人権を失うとかそんなことはありません. 大学では適度な手抜きが許されていたのだと, 今は思います.
筆者は, レポートは大作を仕上げて, 問題を解く課題も全部自分でやっていたタイプです. その必要はなかったのかもしれません. 大学が求めること, 企業が面接で見ることを適切に見極めて, 何にどれだけ時間を割くか, それをよく考える必要があるのだな, あったのだなと反省しています. 「大学で頑張ったんだから面接で評価してくれよお」と企業にいうのもおかしいし, 「インターン頑張ってるんだから単位くれよお」と大学にいうのもおかしな話です. そのバランスくらい自分で考えられるはずだと大学は期待しているのだと考えます. これは自戒です.

筆者は大学院で何をするべきだろう

こうして考えた結果を一言で言うと, 筆者は残りの時間で大学のことに関して手を抜くことを覚えるのが必要なんではないかと思いました. これは, 卒業論文修士論文が紙ゴミになってもいいとかそう言うことではなく, それだけしか見えていない状況はやめようと考えたのです. インターンも応募しよう, バイトもしよう. 大学が全てなわけではない. それだけで企業は筆者を取ってくれるわけではないのだと少し考え直しました. 一方, 大学もうまくこき使おうと思います. 研究と銘打って欲しいものを作ります. それでいいのではないかと思いました. 少しずつ行動していければいいなと思います. 難しいことですけどね.

そんなわけで, なんで大学は実務に近いことを教えないんだ, 就活を邪魔しているんだ, と思ってしまうのは少し違うのではないかと, そういうお話でした.

ラムダ計算 #5 型検査・型推論演習

ラムダ計算セルフブログリレー5日目, 最終回です. 今日も平常運転, くまちゃんです. 4日目はこちら.
あれ?4日目をさっき書いたような気もするんですけど...まあいいでしょ笑笑
kumachan-math.hatenablog.jp

今日は例を使ってバリバリ証明図を書いていくぞー!おー٩( 'ω' )و

5.1 型検査

問題1.  \vdash \lambda x.x::\mathrm{nil}:\mathrm{int} \to \mathrm{list}(\mathrm{int})を示す証明図を書け.


\displaystyle \frac{
     \displaystyle \frac{
        \overline{x:\mathrm{int} \vdash x:\mathrm{int}}~~~\overline{x:\mathrm{int} \vdash \mathrm{nil}:\mathrm{list}(\mathrm{int})}
    }{
        x:\mathrm{int} \vdash x::\mathrm{nil}:\mathrm{list}(\mathrm{int})
    }
}{
    \vdash \lambda x.x::\mathrm{nil}:\mathrm{int} \to \mathrm{list}(\mathrm{int})
}

ラムダ抽象は, 引数を型環境の方に追い出して, その型環境のもとで返り値のラムダ式の型を規定する(abs)ということに注意しておきましょう.
また, 上に積まれる推論規則は下にあるものの計算環境を引き継ぐことにも注意しましょう.


問題2.  \vdash (\mathrm{let}~f =  \lambda x.x::\mathrm{nil}~\mathrm{in}~f~0):\mathrm{list}(\mathrm{int})を示す証明図を書け.

 \displaystyle \frac{
     \displaystyle \frac{
        問題1
    }{
         \vdash \lambda x.x::\mathrm{nil}:\mathrm{int} \to \mathrm{list}(\mathrm{int})
    }
    ~~~
     \displaystyle \frac{
         \overline{f:\mathrm{int} \to \mathrm{list}(\mathrm{int}) \vdash  f:\mathrm{int} \to \mathrm{list}(\mathrm{int})}
        ~~~
        \overline{f:\mathrm{int} \to \mathrm{list}(\mathrm{int}) \vdash 0:\mathrm{int}}
    }{
       f:\mathrm{int} \to \mathrm{list}(\mathrm{int}) \vdash f~0:\mathrm{int}
    }
}{
     \vdash (\mathrm{let}~f =  \lambda x.x::\mathrm{nil}~\mathrm{in}~f~0):\mathrm{list}(\mathrm{int})
}


問題3.   \vdash \lambda x. \lambda y. \lambda z. x(yz):(\tau_2 \to \tau_3) \to (\tau_1 \to \tau_2) \to \tau_1 \to \tau_3を示せ

 \displaystyle \frac{
    \displaystyle \frac{
         \displaystyle \frac{
             \displaystyle \frac{
                   \displaystyle \frac{}{x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2, z:\tau_1,  \vdash x: \tau_2 \to \tau_3}
                   ~~~  \displaystyle \frac{
                        \displaystyle \frac{}{x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2, z:\tau_1,  \vdash y:\tau_1\to \tau_2}
                       ~~~   \displaystyle \frac{}{x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2, z:\tau_1,  \vdash z:\tau_1}
                   }{
                       x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2, z:\tau_1,  \vdash yz: \tau_2
                   }
            }{
                 x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2, z:\tau_1,  \vdash x(yz): \tau_3
            }
        }{
            x: \tau_2 \to \tau_3, y: \tau_1 \to \tau_2 \vdash \lambda z. x(yz): \tau_1 \to \tau_3
        }
    }{
        x: \tau_2 \to \tau_3 \vdash \lambda y. \lambda z. x(yz): (\tau_1 \to \tau_2) \to \tau_1 \to \tau_3
    }
}{
     \vdash \lambda x. \lambda y. \lambda z. x(yz):(\tau_2 \to \tau_3) \to (\tau_1 \to \tau_2) \to \tau_1 \to \tau_3
}

うーんもう何もわからん. 多分あってると思うんですよ. 知らんけど. (abs)と(app)と(var)をたくさん使いました. それしか覚えてません.

5.2型推論

答えの型がわからず推論をしなければいけない場合には, 答えの型を変数でおいていって, 連立方程式のようなものを解くことで型を導きます.


問題4:  \lambda x.x::xはいかなる型も持たないことを示せ.

 \lambda x.x::x \tau型を持つと仮定して証明図を書くと矛盾することを示す.
 \displaystyle \frac{
     \displaystyle \frac{
        \displaystyle \frac{}{
            x: \tau_1 \vdash x: \tau_3
        }
        ~~~
         \displaystyle \frac{}{
            x: \tau_1 \vdash x: \mathrm{list}(\tau_3)
        }
    }{
        x: \tau_1 \vdash x::x:\tau_2
    }
}{
     \vdash \lambda x.x::x:\tau
}

これが正しい証明図であるためには,
 \displaystyle
\begin{cases}
\tau = \tau_1 \to \tau_2 \\
\tau_1 = \tau_3 \\
\tau_1 = \mathrm{list}(\tau_3) \\
\tau_2 = \mathrm{list}(\tau_3)
\end{cases}
でなければなければならないが, これらからは,  \tau_3 = \mathrm{list}(\tau_3)が導かれるため矛盾する.


問題5.  \lambda x.\lambda y.xの主型を推論せよ.
型変数をおいて証明図を書くと次のようになる.
 \displaystyle \frac{
    \displaystyle \frac{
        \displaystyle \frac{}{x: \tau_x, y: \tau_y \vdash x:\tau_2}
    }{
        x: \tau_x \vdash \lambda y.x: \tau_1
    }
}{
    \vdash \lambda x.\lambda y.x: \tau
}
これが正しい証明図であるためには,
 \displaystyle
\begin{cases}
\tau_2 = \tau_x \\
\tau_1 = \tau_y \to \tau_2 \\
\tau = \tau_x \to \tau_1 \\
\end{cases}
でなければならない.
 \displaystyle
\begin{cases}
\tau_2 = \tau_x \\
\tau_1 = \tau_y \to \tau_2 \\
\tau = \tau_x \to \tau_1 \\
\end{cases}

\Leftrightarrow
\begin{cases}
\tau_2 = \tau_x \\
\tau_1 = \tau_y \to  \tau_x \\
\tau = \tau_x \to \tau_1 \\
\end{cases}

\Leftrightarrow
\begin{cases}
\tau_2 = \tau_x \\
\tau_1 = \tau_y \to  \tau_x \\
\tau = \tau_x \to (\tau_y \to  \tau_x) \\
\end{cases}
よって, 答えは,  \tau = \tau_x \to (\tau_y \to  \tau_x) = \tau_x \to \tau_y \to  \tau_xである. ただし,  \tau_x,  \tau_yは任意の型.


問題6.  \lambda f.\lambda x.f(fx)の主型を推論せよ.
型変数をおいて証明図を書くと次のようになる.
 \displaystyle \frac{
    \displaystyle \frac{
        \displaystyle \frac{
             \displaystyle \frac{}{
                f: \tau_f, x: \tau_x \vdash f:\tau_3 \to \tau_2
            }~~~
            \displaystyle \frac{
                \displaystyle \frac{}{
                    f: \tau_f, x: \tau_x \vdash f:\tau_4 \to \tau_3
                }~~~
                \displaystyle \frac{}{
                    f: \tau_f, x: \tau_x \vdash x:\tau_4
                }
            }{
                f: \tau_f, x: \tau_x \vdash fx:\tau_3
            }
        }{
            f: \tau_f, x: \tau_x \vdash f(fx): \tau_2
        }
    }{
       f: \tau_f \vdash \lambda x.f(fx): \tau_1
    }
}{
    \vdash \lambda f.\lambda x.f(fx): \tau
}
これが正しい証明図であるためには,
 \displaystyle
\begin{cases}
\tau = \tau_f \to \tau_1 \\
\tau_1 = \tau_x \to \tau_2 \\
\tau_f = \tau_3 \to \tau_2 \\
\tau_f = \tau_4 \to \tau_3 \\
\tau_x = \tau_4 \\
\end{cases}
でなければならない.
 \displaystyle
\begin{cases}
\tau_f = \tau_3 \to \tau_2 \\
\tau_f = \tau_4 \to \tau_3 \\
\end{cases}
より,  \tau_3 = \tau_4, \tau_2 = \tau_3であるから整理すると.
 \displaystyle
\begin{cases}
\tau = \tau_f \to \tau_1 \\
\tau_1 = \tau_x \to \tau_2 \\
\tau_f = \tau_3 \to \tau_2 \\
\tau_f = \tau_4 \to \tau_3 \\
\tau_x = \tau_4 \\
\end{cases}

\Leftrightarrow
\begin{cases}
\tau = \tau_f \to \tau_1 \\
\tau_1 = \tau_x \to \tau_x \\
\tau_f = \tau_x \to \tau_x \\
\tau_2 = \tau_x \\
\tau_3 = \tau_x \\
\tau_4 = \tau_x \\
\end{cases}

\Leftrightarrow
\begin{cases}
\tau = (\tau_x \to \tau_x) \to (\tau_x \to \tau_x) \\
\tau_1 = \tau_x \to \tau_x \\
\tau_f = \tau_x \to \tau_x \\
\tau_2 = \tau_x \\
\tau_3 = \tau_x \\
\tau_4 = \tau_x \\
\end{cases}
 \tau = (\tau_x \to \tau_x) \to (\tau_x \to \tau_x) = (\tau_x \to \tau_x) \to \tau_x \to \tau_xである. ただし,  \tau_xは任意の型.


以上です. ラムダ計算セルフブログリレーもこれでおしまい(のはず)です. お疲れ様でしたー!