SVMのマージン最大化についてしつこく考えてみる

 SVMの説明というと、よく出てくるのはマージンの最大化である。しかし、実装を行う場合には、どちらかというと目的関数をどうやって最小化しようかな、というところの方が重要(注:主形式を勾配法で最適化する場合の話です)で、この間にある微妙なギャップを超えるのは微妙ながらも大変なような気がしている。このギャップをどうやったら埋められるのかというところを考えてみたい。考えながら書いてきちんと推敲しておりませんのでご注意ください。
 SVMってなに、という説明でよくあるパターンは、線形識別器(というか、SVM)の学習というのはパラメーターをいじって分離(超)平面をいい感じに引くことですよ、というところから始まり、いい感じってなんだろうか、マージンが最大化されるように引くといいっぽいよね、けど分離不可能な場合はマージンの値が負になることがあるよね、そこでソフトマージンというものを定義して、マージンが負になることにはペナルティを与えることにしましょう、という感じではなかろうか。
 この最後のソフトマージンのところで私はつまづくのである。ハードマージンのわかりやすさに安心したところに、不意討ちのようにソフトマージンが来るのだが、その難しさにダメージを受けたことに気づくことすら難しい。
 ハードマージンの場合は制約であったのに対して、ソフトマージンではペナルティという概念が入っており、根本的にマージンというものに対する考え方を変えないといけなくなっているように思う。少なくとも、念入りに解釈を行う必要はある。
 ソフトマージンの場合を考えると、ヒンジロスの和+正則化項という目的関数の方を天下り的に与え、それの解釈としてソフトマージンという概念を後付けした方がよいのではないかと思うのだが、どうだろうか。
 マージンの最大化という観点から離れると、別の説明方法も思いつく。SVMをFOBOSで最適化する場合とパーセプトロンとでは、パーセプトロンの場合はパラメーターの更新基準が

  if y w.cdot(x) > 0

 となっており、正解すればそれでよしとしているのに対し、SVMの場合は

  if y w.cdot(x) > 1

 という形になっているだけの違いしかない。(パーセプトロンにはそもそも正則化という概念はないけれど、普通に実装できるのでそれについては細かい話であるとしておく)
 しかしこの違いが性能の差を産む。パーセプトロンだと分類に成功していればそれでよしとするのに対し、SVMの場合は分類にある程度の余裕を持って成功する必要がある。余裕が足りなかった場合にはパラメーターが更新される。正のクラスと負のクラスの両方からこの押し合いが行われることでマージンが大きくなる(最大化されるかどうかは自明ではない)ことは、直感的に想像しやすい。
 しかし、SVMと言えばマージンの最大化とカーネルトリックの組み合わせな訳で、マージンの最大化についてはしっかりと理解しておきたいところである。その意味では、上の説明では不足がある。
 ここまで書いてきて、やはり一番大きな問題なのは、ソフトマージンという概念が難しいということであると感じた。
 ソフトマージン最大化というのは

  • 正しく判別している点については(ハード)マージンを最大化
  • 誤判定したデータについてはペナルティの最小化

 という2つの目的関数の和であり、どちらを重要視するかがCという定数によって決められる訳だが、ここがどうもまだ直感的に飲み込めない。
 極端な例を考えてみよう。Cをとても小さな値、例えば10^-8ぐらいに設定することを考えてみよう。これはつまりマージン最大化を重要視しないということであるが、マージンの最大化を重要視しないということは、ペナルティを最小化することに注力する、ということである。言い換えれば、経験損失の最小化をとにかく頑張ろう、という話である。逆に、マージンの最大化を非常に重要視する例を考えてみる。Cが10^8ぐらいの値であることを考えよう。この時、分類器はとにかくマージンを最大化することを目指す。経験損失が多少高くなろうが、知ったことではない。
 さらにここで、正則化という概念も混ざってくる。「言語処理のための機械学習入門」のP.134には「じつは、SVMのマージン最大化も正則化の一種である」とあるが、正則化とマージン最大化の間にはどういう関係があるのだろうか。包含関係なのだろうか。
 何がわからないかがよくわからなくなってきたので、今一度整理してみることにする。

  • ソフトマージンの最大化が理解しにくい
  • 正則化とマージン最大化の関係がわからない

 一番目は最初からの課題であり、ソフトマージンという概念を直感的にどう理解すればいいのか、というところでずっと悩んでいるわけだ。これについてはしかし、Cの値を極端に動かした際の挙動を想像することで、ある程度わかってきたように思う。ポイントは経験損失と期待損失の違いで、マージンの最大化というのは期待損失を減らす方向に効果がある。ハードマージンの場合も、マージンの最大化には期待損失を減らす効果があった(というか、期待損失を減らす効果しかない)のだが、ソフトマージンの場合は、期待損失の事を強く意識しないと訳が分からなくなる。
 二番目、正則化というのはパラメーターの複雑度に応じてペナルティをかける仕組みで、ソフトマージンSVMの目的関数においては|w|もしくは|w|^2の最小化の部分が正則化項に相当する。
 では、正則化項があればそれでいいのか。正則化項つきパーセプトロンはマージンを最大化するのか。先ほど述べたように、SVMの損失関数は、あきらかにパーセプトロンよりも性能が良さそうに見える。正則化が付いていれば、パーセプトロンもマージン最大化なのだろうか。
 マージン最大化はSVMの特権ではなく、MIRAとかAveraged Perceptronとか、ラージマージン分類器と呼ばれる分類器はいくつもあるわけであるのだが、単なる正則化項つきパーセプトロンをこの中に入れるというのは、感情的にはなんだか承服しがたい。どうも、自分の一番の悩みはここにありそうな気がしてきた。
 論理的には、

  • SVMのソフトマージン最大化において重要なのは正則化項であり、
  • とはいえ損失関数も重要であり、損失関数の違いが各分類アルゴリズムの性能の違いを生む

 という話だと思うのだが、その一方でラージマージン分類器は良いものだ、というこれまでの自分の中の知識が、「あれ、そうすると正則化項つきパーセプトロンはけっこういい性能出るはずなんじゃないの?」という疑問を投げかけてくる。それに対して、自分は明確な回答を用意することができない。
 さて、ここまで議論が整理できたのであれば、次は実際に試してみるしかありますまい。という訳で、次回に続く。

FirefoxのGCまわり

なんとなくちょっとだけ読んでみた。Firefoxにおいて、JSが使うメモリはコンパートメント、チャンク、アリーナの順に分解される。コンパートメントがどうも一番大きな区切りらしく、通常はこの単位でGCが呼ばれる。コンパートメントはホスト単位で確保されるらしい。チャンクは特に特徴のない塊で、アリーナは型毎に作られる。

GCはFull GCとコンパートメントGCがあり、後者ではコンパートメント単位でGCが行われる。TriggerGCが呼ばれるとFull GCが実行され、TriggerCompartmentGCが呼ばれるとCompartment GCが実行される。TriggerGCを呼んでいるところはgrepした限りではAllocateArenaとTriggerCompartmentGCの2つだけで、AllocateArenaはPickChunkに失敗したときにだけ呼ぶ。TriggerCompartmentGC内では以下のような条件で呼び出している。

    if (rt->gcBytes > 8192 && rt->gcBytes >= 3 * (rt->gcTriggerBytes / 2)) {
        /* If we're using significantly more than our quota, do a full GC. */
        TriggerGC(rt);
        return;
    }

つまり、フルGCはメモリが確保できない時か、あらかじめ設定された以上の容量を使っていた場合に行われる。

一方、TriggerCompartmentGCはChunk::allocateArenaから呼ばれる。この際の条件は以下のようになっている。

    if (comp->gcBytes >= comp->gcTriggerBytes)
        TriggerCompartmentGC(comp);

gcBytesはArenaをallocateした分だけ増えるので、これもやはりあらかじめ設定されたいた以上の容量のメモリを使っていた場合にGCが呼ばれる、という条件である。

特にまとめとかはないが、GCが行われるのはメモリをあらたにallocateしようとした時である、と言えそうだ。

日本語は不自由だ

 こないだ、「難しい」を敬体にするときに、「難しいです」はなんだか恥ずかしいし、「困難です」は実現可能性の低さが「難しい」よりもかなり低い気がしてしまうし、「難しゅうございます」は丁寧すぎるし、どうすればいいんだ、という話をTwitter書いた
 メールであれば「難しいです」一択なんだけど、多少かしこまった、後に残る文章を書くのであれば、おそらく私は「困難です」を選ぶと思う。だけど、実際のところ、「困難です」では自分が「難しい」を敬体にしたい、と考えた場合のニュアンスが実現されていないように感じる。
 この日記はしばらく書いていないと文体を忘れてしまって敬体で書いてしまうこともよくあるけれど、基本的には常態で書くことにしている。その理由としては、敬体を使うと形容詞をうまく丁寧に表現できない、という点が大きい。だが、敬体で書かなければいけない場面というのも世の中には沢山あり、とても悩ましい。
 日本語でまともに見られる文章を書こうとすると守らないといけないルールがいくつかあるが、守ろうとすると文を書くのが非常に難しい。

  • 同じ語尾が連続しないこと。「〜だと思います。〜だと思います。」みたいなのはダメ。
  • そもそも、同じ言葉を意味なく多用しないこと。例えば、一度「しかし」を使ったら、段落が変わるぐらいまでは使わない。
  • 否定形の接続語を連続させない。

 などなど。もっとも、これは自分が文章を書いているうちに、なんとなくこういうことをしたら妙な文になると感じたというだけの知見であるので、一般的なものではないかもしれない。日本語はかっちりとした正書法がないので、明確に間違いであるともなかなか言えないだろうけれど。
 こういったルールを守って文章を書いていると、穴だらけの道で穴を避けながら自動車を運転しているような気分になる。ホントはもっとスピードが出せるはずなのに、変な制約が付いているせいでスピードを出すことができないようなイメージ。
 こういうルールはたぶん、どういう文章を読んで育ってきたかによっても変わってくるはずで、例えば「全然大丈夫です」みたいな表現はこれからの世代にはオーケーになっていくのかもしれない。しかし、文章を書く上で普遍的なルールというのもあるような気がしている。例えば、英語であっても同じ言葉を多用することは望ましいことではないように感じる。どこまでが変えられる(意味のない)ルールで、どこからが普遍的な変えられないルールであるのか、という境目には興味がある。また、普遍的なルールはどういう理由で普遍的であるのかを知りたい。違う言語で同じルールが存在するということはなにか偶然ではない理由があってもおかしくなさそうであり、その理由が知りたい。

TIMEDOMAIN lightを買った

 これまで、スピーカーはBOSEのMediaMateを使っていたのだが、低音はボンボン出るけれど、どうも細かい音が聞こえない気がするので買い換えることにした。
 新しいスピーカーはいくつか調べた結果TIMEDOMAIN lightにした。TIMEDOMAIN理論というものに基づくらしいけれど、オーディオの世界の理論というのは正直なところどこまで信用していいものかわからないので、それなりの値段で評判がいい奴を適当に買うか、ぐらいの考えで購入した。
 数日使ってみて、MediaMateと比べると明らかに聞こえる音が増えたので、割と満足している。不満はなくはないが、オーディオの世界は上を見るとキリがないので、ここらへんで満足しておかなければなるまい。
 以下、良い点と悪い点を並べてみる。まずは良い点から。

  • 小さなボリュームで鳴っている楽器の音が聞き取れるようになった。自分はよくSonyのMDR-CD 900STを使っているのだが、それと比べて遜色ない…とまでは言えないけれどだいぶ近いレベルだ。
  • 小さいので机の上であまり邪魔にならない。PC用としては重要なポイントだと思う。
  • 20000円以下と、オーディオマニアじゃない人でも買える値段である。

 次に悪い点。

  • ケーブルがスピーカーと一体化しているのだけれど、あまりクオリティが高くない。シールドが甘いみたいで、液晶ディスプレイの近くにケーブルを置くとノイズがひどい。自分の場合はUSBオーディオを使っていたのでディスプレイからは迂回してケーブルをつなげるようにできたけれど、PCから直出ししてたらどうしようもなかった。こういう事を経験すると、高いケーブルを買い求めるオーディオマニアの気持ちはちょっとわかるなと感じる。もっとも、このスピーカーではケーブルを変えようがないのだが。
  • ノイズが大きくなると、単なるノイズではなく、ラジオか何かの音を拾っていることがわかる。つなげるタップを変えるとラジオの音は入らなくなるのだが、こういう事を経験すると、電力会社によって音質に違いが出る、みたいなオカルトの方向に走っていく人の気持ちはちょっとわかるなと感じる。
  • いろんなところで指摘されているけれど、低音の出が確かにあまりよくない。自分は低音はそんなに気にしない方だと思うけれど、それでもちょっと不足してるなぁと感じる。

 オーディオ用ではないとか言われることもあるようだが、モニター用ヘッドホンを使って満足しているようなタイプの人間なので、そこはあまり気にならない。いろんな音が聞こえたほうが楽しいじゃない、と思う。
 ごちゃごちゃ書いたけれど、PCに付属のスピーカーとかを使って音楽を聴いている人は、TIMEDOMAIN light(AA)とテックのTEU2(AA) あたりを買うだけで、全然違う世界が開けると思う。後者のUSBオーディオが必要かどうかはPC次第だけれど、少なくともThinkPad X31はあまり良い音ではなかった経験がある。PCで音を聴くなら買っておくと精神衛生上よろしかろう。

TIMEDOMAIN light
TIMEDOMAIN light
posted with amazlet at 11.04.16
TIMEDOMAIN
売り上げランキング: 3669

NLP2011に参加してきました

 もう一ヶ月以上も前の話になりますが、2011年の言語処理学会年次大会で、かな漢字変換について発表をしてきました。
 日本語入力についてのテーマセッションが用意されているということで、発表申し込みをしようかどうか迷った挙句、仕事が忙しいからあきらめたのですが、〆切前日にG社の方から「明日が〆切なのでよろしく」的なメールが来て、そんなに申し込みが少ないなら出すか…ということで発表してきました。蓋を開けてみたら2セッション分の発表があって割と大人気だった訳ですが、こんな機会がないとなかなか会わないような人にたくさん会えたので、結果的には行ってきて良かったです。
 発表に関しては会社ブログの方に既に資料を上げたので、こちらでは実装の細かいところの話をちょっと書いてみたいと思います。
 資料にも書きましたが、構造化SVMをFOBOSで最適化する場合、

  • 正解パスへはペナルティを与えつつ現在のパラメーターで変換してみる
  • 変換できたら何もしない、変換できなかったらパラメーターを更新する

 という手順になります。学習の速度のことを考えると、実際に変換してみるというところがボトルネックで、つまり、秒間に何文ぐらい変換できるか、というのが非常に重要な数字になってきます。結論から書くと、今回の実装ではいろいろ頑張りすぎたせいで、850文/secぐらいの速度で変換できるようになりました。Sandy Bridgeなら1000文/secぐらいはいけると思います。京大コーパスには結構長い文も含まれていますから、これはなかなかの速度だと考えています。
 変換の際にはビタビアルゴリズムを使うことになりますが、その中で、バイグラムコストを求めてくるところが計算量的には一番重いところです。プロファイルをとってみると一発でボトルネックなのがわかるぐらいで、だいたい8割以上の時間がここに費やされます。今回は単語バイグラム素性を使ったので、特に問題でした。
 バイグラムコストを求めるとは言うものの、実際にはなにかその場で計算をする訳ではなく、データ構造に突っ込んでおいたコストの値を取ってくるだけなので、問題としてはどういうデータ構造が速いのか、というものになります。
 単語バイグラムを素性として使う場合、バイグラムの時点で既に素性空間が広くなりすぎるので、二次元の配列で持つというのは現実的ではありません。いや、最近のハイスペックマシンだと結構現実的だったりしますが、今回はそこまでメモリが潤沢には使えませんでした。
 そうなると、残りの選択肢としては、ハッシュテーブルに突っ込むか、もしくは動的ダブルアレイに突っ込むか、私の技術力的にはこの2つに絞られます。
 動的ダブルアレイはバグったときにデバッグが大変(過去に体験して非常に辛いことがありました…)なので、〆切までのスケジュールがタイトな今回はハッシュテーブルを使うことにしました。
 ハッシュテーブルは、まずはglib付属のg_hash_tableを使っていたのですが、あんまり速くないし改造も面倒な感じだったので、他のものをいくつか当たってみることにしました。
 次にあたったのはC++のtr/unordred_mapでしたが、これはglibより遅く、論外でした。stringじゃなくてchar*を使えばよかったのかもしれませんが、そうするとメモリ管理がまたずいぶんと面倒になってしまうので、それ以上の改良は断念しました。
 次に、libghthashというハッシュテーブルを使ってみました。これはハッシュ関数が取り替えられたりできるし、glibと違って改造しやすかったので、こちらを使うことにしました。
 いろいろ試行錯誤をしたのですが、最終的には以下のような改造をしたlibghthashを使っています。

  • ハッシュ関数はone at a time hashをベースに、削っても大丈夫そうなところを削ってみたオリジナルの適当関数を採用
  • ハッシュテーブルから値を取り出すところは、すべての関数をヘッダファイルに移してインライン化
  • テーブルの空き領域を増やして、探索失敗を高速化

 ハッシュ関数はFNVとかmurmurハッシュとか試してみましたが、最終的には適当な奴が一番早い、という結果に落ち着きました。あまりシンプルなハッシュ関数にし過ぎると衝突確率が上がって却って遅くなるといったトレードオフあり、興味深い体験でした。
 インライン化は非常に重要で、これで20〜30%ぐらいは速度が稼げます。二重ループの中で呼び出すので仕方ないですが、抽象化と高速化は時に両立しない、というやや残念な結果になってしまいました。
 その他、ハッシュテーブルを2重にするといった実験もしてみましたが、あまり良い結果を得られませんでした。もっとも、これに関してはあまり追求していないので、もしかしたらうまく高速できたかもしれません。
 また、姑息な高速化として、IDが小さい部分に関しては二次元配列に値を保存するという工夫を入れたところ、数百MBのメモリと引換に、そこそこ高速化ができました。10%ぐらいだったかな?
 論文にはさらっと「学習時間は8分30秒程度であった」と書いてありますが、その影にはこのような苦労があったわけです。しかし、学習が10分程度で終わるのであれば、そもそもここまでシビアに高速化を行う必要はありません。特に改良をせずとも、当初のナイーブな実装で、おそらく30分以内ぐらいに学習は終わっていたでしょう。
 これにはおまぬけな事情があります。実は、バグによって変換時間がすごく長くなっており、その本質に気づかなかったせいで、ある意味では無駄な努力を高速化につぎ込んでいたのです。ビタビアルゴリズムではゴールまでたどり着く経路が一つ以上は必要ですから、グラフの構築時に入力文字そのままのノードをオンザフライで追加していたのですが、関数呼び出しのフラグを間違えていて、このオンザフライのノードを辞書にまで追加してしまっていたのでした。しかも辞書エントリに重複が許されていたものですからひらがなが辞書に大量に追加され、結果として辞書まわりの操作コストすべてが急上昇していたのでした。このバグの効果は劇的で、コーパスに対して1回の学習ループを回すために3日間以上の時間が必要でした。変換結果そのものには影響がでないので、どこにバグがあるのかがわかりづらく、厄介でした。
 その他、実装でつまづいたを細々と点を挙げておきます。
 今回は、素性を抽象化して扱いたかったので、ハッシュテーブルのキーとしては64bitの整数を使うことにし、その上位4bitを素性の種別として使い、残りの60bitを素性IDとして使うことにしました。これは最初に実装する方法としては、あまり良いやり方ではありませんでした。結局、この素性の抽象化の部分ではバグはなかったのですが、うまく動かないときにはこの抽象化の部分も疑わざるを得ず、しかも正しく動いていることを確認するのが難しいので、無駄に手間を取られてしまいました。後知恵ですが、まずはもっと分かりやすく、素性は分かりやすい文字列で表現すべきでした。そちらで正しく動くことが確認できてから、高速化実装を作ったほうが、結果的には早く実装を終わらせられたでしょう。
 また、メモリ管理が非常に面倒でした。グラフ構築時にひらがなの経路を追加していたわけですが、このために辞書が返すメモリ領域がフリーしてはいけないものとフリーしないといけないものの2種類があり、原理的にはその2つはコードパスで区別でき、うまく処理できるはずだったのですが、ダブルポインタ、トリプルポインタが乱舞するようになると自分の管理能力を超えてしまい、うまく動きませんでした。結局、変換時に一時的に確保するメモリは変換終了後に一気に開放できるようにバッファクラスを作って、メモリはそちらで管理するようにしたのですが、これはよく考えてみたらメモリプールそのもので、自分で作らずにaprとか使えばよかったなと、これも後知恵で思いました。
 高速化自体は楽しい作業ではありましたが、予稿提出の一週間前はまだまともな数字が出せていないという状態で、正直なところ気が気ではありませんでした。間に合って本当に良かった。

clangでソフトウェアをビルドしC++を知る

 clangというのはllvm向けのC/C++/Obj-Cのためのフロントエンドで、最近はGoogle ChromeとかFirefoxコンパイルできるレベルにまで成熟してきているらしい。
 いくつかのブログで紹介されているのを見ても、ふーん、ぐらいにしか思っていなかったのだが、あんな大規模なソフトウェアがコンパイルできるというのは、考えてみるとすごいことである。大事なことなので強調しておくが、すごいことである。十分に実用的なレベルに到達していることだ。ビルドも早いし生成されたコードもg++と同程度には速いというし、試してみる必要がある。
 という訳で、いくつか実際にソフトウェアをビルドしてみた。試してみた限りでは、

  • libstdc++のtr1/unordered_mapがビルドできない
  • C++のコーナーケースで、clangが許容しないものが多い

 といった問題があったが、割とどれもすんなりとコンパイルが通った。ビルドが通らなかったり、通ってもちゃんと動かなかったりする場合もあるが、私が遭遇した範囲ではどれもコード側の問題で、clangの問題ではなかった。
 ChromeFirefoxがビルドできるのだから当たり前とも言えるが、自分が書いたプログラムがいつもと違うコンパイラでビルドできると、驚いてしまう。clangはAppleがスポンサーについて開発が進んでいるプロジェクトなわけで、然るべき金額をどこかの会社が払って然るべき人間が開発を進めれば実用的なC++コンパイラは作れるのだ、という話でしかないのかもしれないが、それでも驚きである。
 clangはVisual C++やg++と比べてC++の規格への準拠度が高く、大きなソフトウェアになればなるほど、規格上は許されないコードが微妙に混入してしまっており、コンパイルできない場合が多い。以下では、clangを使ったときにハマるであろう典型的な例(というか、私が遭遇した例)をいくつか紹介したい。

clangの使い方

 clangはg++とコマンドラインオプションの互換性があるので、gcc,g++を呼び出しているところをclang, clang++を呼び出すように変えれば良い。
 だいたいのビルドシステムの場合、export CC=clang ; export CXX=clang++とした上でconfigureからやり直すとビルドできるようになるはずである。

constなオブジェクトのデフォルトコンストラクタはユーザーが定義する必要がある

 これはエラーメッセージを見ただけで修正ができる問題であるが、C++の規格上このような制限があるということ自体、知らない人も多いのではないか。私はもちろん知らなかった。

default initialization of an object of const type 'const foo' requires a user-provided default constructor

 規格の文言では、"if the object is of const-qualified type, the underlying class type shall have a user-declared default constructor." と書いてあるみたいだ。

親クラスがテンプレートクラスである場合、そのメソッドを呼ぶときに修飾が必要である

 これはclangのエラーメッセージが親切であるということをありがたく感じると同時に、C++の規格を理解することは(素人には)難しい、と思わされるエラーである。エラー自体は、以下のように非常にシンプルなのだが、その下の方に"note:"としてわざわざ注意書きをつけてくれる。

error: use of undeclared identifier 'foo'

 これに対して以下のようなnoteがつく。

note: must qualify identifier to find this declaration in dependent base class

 エラーメッセージだけでは途方にくれるが、このnoteのおかげでなにをやらないといけないかが分かりやすい。要するに、foo()と書いてあるところをthis->foo()にすればいい。もしくは呼びたいメソッドが定義されている親クラスの名前を明示してもよいが、親クラスが変わった際(ということはさすがに考えにくいが…)に変更しなくて済むので、thisを使った方が良いように思う。
 これは、C++でシンボルからその定義を探す際に、two phase name lookupという名前の解決方法が規格として定められているのが原因であるそうだ。これは以下のような仕組みである。

  • C++ではテンプレート中のシンボルの名前解決を行う2つのフェーズがある。テンプレートの宣言時にはnondependent nameを解決し、テンプレートがインスタンス化された時点でdependent nameを解決する。
  • dependent nameというのは、テンプレートパラメータそのものや、テンプレートパラメータを含む名前などである。Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookupのところにそのような記述がある。
  • テンプレートの宣言時に参照できるのは、nondependent nameだけである。foo()をそのまま呼びだそうとしても、親クラスのfooはdependent nameであるために参照できず、エラーになる。
  • foo()の親クラスの名前を指定して呼びだすと、当たり前ではあるがParent::foo()みたいな形になってdependent nameになるので、名前解決のタイミングがインスタンス化の時点になるので成功する。
  • thisを付けた場合は、this自体がdependent nameとして扱われる(thisはクラスの型情報を含んでいるのでdependentである)ので、上記と同様にインスタンス化の時点で名前解決が行われることになるのでよろしい。

 私が見つけられたわかりやすい情報源は、結局 Standard Features Missing From VC++ 7.1. Part III: Two-Phase Name Lookup だけであった。他のページではそもそもdependent nameが具体的にどのようなものなのかを書いているところがなかった。もっとも、分かりやすく説明しているページはきっと他にもあるだろうし、そもそも規格にはdependent nameの定義は絶対に書いてあるはずである。

stdをつけないといけないところがg++より多い

 関わった中で出てきたエラーとして、

 error: use of undeclared identifier 'make_pair'

 というのがあった。g++だとこのままで通るのだが、clangだとstd::make_pairとしなければならない。undeclared identifierと言われたら、stdをつけないといけないか、もしくは一つ前の問題を疑うべきである。

デフォルト引数は、例え同じ値であってとしても一度しか定義してはならない

 プロトタイプ宣言と関数の宣言の二回でデフォルト引数を定義していると以下のようなコンパイルエラーになる。これもエラーメッセージからすぐにわかる。以下のようなエラーになった。

 error: redefinition of default argument

その他

 g++だと顕在化しなかったバグがclangによって実際に問題になったケースがあった。参照してはいけないオブジェクトを参照しちゃっていて、clangだと落ちるとか。ここら辺はたぶんケースバイケースで、最適化レベルによっても変わったりするだろうし、g++でバグが顕在化する場合もあるだろうから、複数のコンパイラで試すことが望ましいのだと思う。ちょっとの手間で試せるなら、複数のコンパイラでビルドしてユニットテストが通るかぐらいまでは確認しておくと、ソフトウェアの信頼性を上げられるのではないかと感じた。
 その他、現在のところ、wstringをリテラルで書くのが面倒であるという問題があった。gccだとUTF-8でL"あいうえお"とか書けるのだが、clangでは今のところそのような書き方はできない。

まとめ

 clangは実際にいろいろなソフトウェアをビルドできるし、ビルドできない場合にもclangではなくソフトウェア側の問題がほとんどであった。完成度はかなり高い。
 ビルド時間への影響の面では、自分が試した範囲だと、clangとg++では、clangの方がコンパイル速度は早いのだが、1〜2割ぐらいの差しかないようだ。どうも、デバッグビルドだとあんまり差がつかないみたい。もちろん、2割早くなれば嬉しいのは嬉しいのだが、乗り換えるための決定打になるほど速くはなかった。それよりは、エラーメッセージがg++より親切であるとか、規格への準拠度が高いので書いているコードのポータビリティを上げられる、といった点の方が嬉しさは大きい。

参考文献

統計学を拓いた異才たちを読んだ

 2月ぐらいに、ナイチンゲール統計学を始めたとか始めないとかtwitterでなんか盛り上がっていた(らしい)ことがあって、そこで"統計学を拓いた異才たち"というタイトルの本がおすすめされていたので読んでみることにした。そもそも、統計学についてはあまり詳しい知識がなく、頻度論はフィッシャーとかネイマン・ピアソンとかよくわからん、ややこしいなぁ、ぐらいの認識しかなかったので、歴史の方向からも勉強を進めることで、もうちょっと詳しくなりたかったという思いもあった。
 本書には、カール・ピアソンから始まり、フィッシャー、ネイマン、エゴン・ピアソン、ウィルコクソン、コルモゴロフ、ゴセット(スチューデント)など、どこかで名前を聞いたことがあるような人々がズラズラ出てくる。彼ら彼女らがどのような問題にであい、どのようにして解決したかについての大まかな内容がたくさん描写されており、細かい部分については省略されている。
 カール・ピアソンは記述統計学と呼ばれる分野で貢献した人で、ピアソンの積率相関係数(単に相関係数というとこれを指すそうだ)やカイ二乗検定は彼の仕事である。フィッシャーは十分統計量や最尤法、フィッシャーの判別関数など、様々な手法、概念を提案した。カール・ピアソンはフィッシャーの仕事を尊重せず、この二人は仲が悪かった。ネイマンはエゴン・ピアソンとともに、仮説検定の考え方を確立した。エゴン・ピアソンはカール・ピアソンの息子であるが、ネイマン・ピアソン流、といった時のピアソンは息子の方である。父とは違いフィッシャーの仕事を尊敬していたが、フィッシャーからは父同様に攻撃されることとなった。これはフィッシャーの方の性格に問題があったふうに描写されている。
 本書の中に出てくる人々は統計学に貢献をして名を残した人ばかりなので頭のよい人ばかりなのであるが、その中でフィッシャーとコルモゴロフの扱いは別格であり、天才として扱われているように感じた。数学の歴史についての本を読んでいると、時代のところどころでポロッと天才が出てきて一気に議論を進める、というような場面が出てくる。場合によっては早すぎて同時代の人間には理解されないことすらあり、そんな場合の天才は不幸であるとしか言いようがないが、フィッシャー、コルモゴロフの場合は幸運にも周囲に理解され、存命中に大家として扱われていたようだ。
 他にもたくさんの人物が出てくるので、統計学をどのような人々がつくってきたのかということを知りたければ、一読する価値はあると感じた。ただ、縦書きであることからもわかる通り、統計学についての本ではなく、統計学に貢献した人々についての本であるので、トピックの配置は人物が中心になっており、今読み返しても、どこに何が書いてあったかを見つけるのはすごく大変だった。統計学について知りたいのであれば普通の教科書を読んだほうが良いだろう。教科書は定理と証明の繰り返しで分かりにくかったりするので、勉強する意味が分からなくなったときにはこういった本が役に立つだろう。1200円と文庫本にしてはちょっと高いが、教科書の副読本だと考えればほら、急に安く見えてきた。
 当初のナイチンゲールの疑問に戻ると、本書によれば、ナイチンゲール統計学を始めたというと言い過ぎではあるが、彼女は統計学を医学のために活用した最初期の人物の一人であり、パイチャートはナイチンゲールの発明である。また、ナイチンゲールの友人は敬愛する友人の名前を自分の子供につけ、その子供フローレンス・ナイチンゲール・デイヴィッドはなんやかやで統計学者になり、統計学の分野で大きな功績を残した。9冊の本と100編以上の論文を書いたというのだからすごい。

統計学を拓いた異才たち(日経ビジネス人文庫)
デイヴィッド・サルツブルグ
日本経済新聞出版社
売り上げランキング: 91127