統計学の検定で、有意水準といえば5%である。
有意水準とは、ある仮説の下でそれより低い確率でしか起こらないはずのことが観測されたら、それはたまたまでなく意味が有り、その仮説が誤っていると判断する、確率のしきい値のことである。
1%もよく使われるし、5%に限らないと教わるが、使われるのは圧倒的に5%=0.05である。
何故0.05かについては、筆者は寡聞にして、これまで根拠や由来を目にしたことが無かった。

数ヶ月前にある所で、この0.05は統計学の大家であるフィッシャーが決めたもので、その理由は、フィッシャーが30歳の頃に、研究者としてあと20年仕事して引退するとして、その内1年くらいは失敗するだろうと思ったから、というような話を聞いた。
その話に興味を持って、結構時間をかけてWeb検索してみたのだが、それに近い話は1つも見つからなかった。有名な話なら1つくらいは見つかりそうなものなので、この話が有名だという仮説は棄却するしかなさそうだ。

有意水準として0.05という数字を最初に記したのはフィッシャーだという話はすぐに大量に見つかった。
いくつか読んだので、筆者なりにまとめておく。

"On the Origins of the .05 Level of Statistical Significance"より:

Fisher's (1925) statement in his book, Statistical Methods for Research Workers, seems to be the first specific mention of the p=.05 level as determining statistical significance.
It is convenient to take this point as a limit in judging whether a deviation is to be considered significant or not. Deviations exceeding twice the standard deviation are thus formally regarded as significant. (p. 47)

参考リンク[2]からダウンロードできる"Statistical Methods for Research Workers"の5th editionではp.45だが、上記引用部の少し前から引用すると、

The value for which P=.05, or 1 in 20, is 1.96 or nearly 2; it is convenient to take this point as a limit in judging whether a deviation is to be considered significant or not. Deviations exceeding twice the standard deviation are thus formally regarded as significant.

となっており、これが歴史上初めて、有意水準を0.05とすると便利だと記したものということらしく、至る所に引用されている。
便利である理由は、平均μ、標準偏差σの正規分布に従う確率変数がμ±2σの範囲外の値を取る確率(つまり、平均から標準偏差の2倍以上外れる確率)がほぼ5%でわかりやすいから、と読み取れる。

In the 1926 article Fisher acknowledges that other levels may be used:
If one in twenty does not seem high enough odds, we may, if we prefer it, draw the line at one in fifty (the 2 per cent point), or one in a hundred (the 1 per cent point). Personally, the writer prefers to set a low standard of significance at the 5 per cent point, and ignore entirely all results which fail to reach this level. A scientific fact should be regarded as experimentally established only if a properly designed experiment rarely fails to give this level of significance. (p. 504)

この部分も多数引用されているが、5%にしても2%にしても1%にしても良いが、Fisher自身は5%を好む、とだけ書かれており、特別に理由があった訳ではないと読める。

そもそもFisher以前から大体5%くらいの値が使われていたことについて、以下のように書かれている。

With respect to the determination of a level of significance, Student's (1908) article, in which he published his derivation of the t test, stated that "three times the probable error in the normal curve, for most purposes, would be considered significant" (p. 13).

ここで"probable error"というのは、平均からの25%点や75%点までの距離であり、標準偏差が広く使われる前によく使われていた統計量で、値としては標準偏差の約2/3である。

In any case, it is clear that as early as 1908 X ± 3PE was accepted as a useful rule of thumb for rejecting differences occurring as the result of chance fluctuations.

Student(William Gosset)は正規分布において3PEが有意だと記しており、遅くとも1908年には3PEが有意水準として受け入れられていた、とある。

A fact that would have been no surprise to most of those reading his book (and which, indeed Fisher pointed out) is that "a deviation of three times the probable error is effectively equivalent to one of twice the standard error" (Fisher, 1925, pp. 47-48).
Fisher then cannot be credited with establishing the value of the significance level. What he can perhaps be credited with is the beginning of a trend to express a value in a distribution in terms of its own standard deviation instead of its probable error.

それを受けてFisherは3PE ≒ 2σだと書いており、Fisherは有意水準を0.05とした人というよりは、"probable error"の代わりに標準偏差を使い始めた人と言えるだろう、とある。

"Why P=0.05?"より:

The impact of Fisher's tables was profound. Through the 1960s, it was standard practice in many fields to report summaries with one star attached to indicate P 0.05 and two stars to indicate P 0.01, Occasionally, three starts were used to indicate P 0.001.

検定の計算表でp≦0.05, p≦0.01, p≦0.001をそれぞれ *, **, *** と表すのは、今でもよく使われるが、Fisherに由来するらしい。ここでも有意水準として0.05が出てくる。

For such procedures to be effective, it is essential ther be a tacit agreement among researchers to use them in the same way. Otherwise, individuals would modify the procedure to suit their own purposes until the procedure became valueless. As Bross (1971) remarks,
Anyone familiar with certain areas of the scientific literature will be well aware of the need for curtailing language-games. Thus if there were no 5% level firmly established, then some persons would stretch the level to 6% or 7% to prove their point. Soon others would be stretching to 10% and 15% and the jargon would become meaningless. Whereas nowadays a phrase such as statistically significant difference provides some assurance that the results are not merely a manifestation of sampling variation, the phrase would mean very little if everyone played language-games. To be sure, there are always a few folks who fiddle with significance levels--who will switch from two-tailed to one-tailed tests or from one significance test to another in an effort to get positive results. However such gamesmanship is severely frowned upon and is rarely practiced by persons who are native speakers of fact-limited scientific languages--it is the mark of an amateur.

Fisherは有意水準を5%に限らないとしたが、後世の人は、確立した5%という基準があることが無意味な言葉遊びを生じなくしている、その反面、5%にこだわる余りに両側検定でなく片側検定にしたり、5%以下を達成するために検定方法を変えたりするアマチュアが居る、と書いている。

"Fisher and the 5% Level"より:

Table VI gave only the P = .05 percent points for the distribution of z (the log of the F-statistic) by numerator df and denominator df, for df = 1, 2, 3, 4, 5, 6, 8, 12, 24, ∞. By the third edition (1930), he had added a table giving the 1% points and enlarged the range of denominator df considerably.

Fisherの"Statistical Methods for Research Workers"の初版には、F分布表はp=0.05のものしか記載されていなかったと書かれている。
参考リンク[2]からダウンロードできる5th editionの巻末のTable VIにはp=0.01の表も含まれているが、http://psychclassics.yorku.ca/にある"STATISTICAL METHODS FOR RESEARCH WORKERS"の初版のTABLE VIには確かにp=0.05の表しか無い。
なお、"Statistical Methods for Research Workers"のF分布表の数値は、普段我々が目にするF分布表の値の自然対数の1/2になっており、分散比との比較に使う値ではなく、標準偏差の比の対数との比較に使う値のようだ。

参考リンク
[1] Michael Cowles & Caroline Davis, "On the Origins of the .05 Level of Statistical Significance", 1982
[2] R. A. Fisher, "Statistical Methods for Research Workers" fifth edition, 1934
[3] Gerard E. Dallal, "Why P=0.05?"
[4] Stephen Stigler, "Fisher and the 5% Level"
[5] Lynn D. Torbeck, "On the Verge of Significance: Why 5%"
[6] Carl Anderson, "What's the significance of 0.05 significance?"
[7] 奥村 晴彦, http://oku.edu.mie-u.ac.jp/~okumura/stat/basics.html
 下の方に、Fisherが5%を好むとしたことについて言及あり
[8] Regarding p-values, why 1% and 5%? Why not 6% or 10%? - Cross Validated

続いてTomcatからMySQLが使えるよう、MySQL Connector/Jをインストールした。

■やったこと

  • aptitudeでlibmysql-javaをインストール
  • /var/lib/tomcat7/shared/に/usr/share/java/mysql-connector-java.jarへのシンボリックリンクを作成
    例:
    ln -s /usr/share/java/mysql-connector-java.jar /var/lib/tomcat7/shared/
  • /etc/tomcat7/catalina.propertiesの
    shared.loader=${catalina.home}/shared/classes,${catalina.home}/shared/*.jar
    の"home"を"base"に変更
  • Tomcatを再起動

特に何に使ってる訳でもないが、このブログの過去の記事で何か書いたのが残ってるし、Tomcatは筆者のお気に入りなので、Raspbianで作り直した自宅サーバーにもインストールしておくことにした。

▪️やったこと
  • Tomcatのインストール
    • aptitudeでtomcat7とtomcat7-examplesをインストール
    • http://localhost:8080/にアクセスしてServlets examplesが何らか動くことを確認
    • 昔作ったRandomTable Servletを/var/lib/tomcat7/webapps/servlet/に配置し、
      Tomcatを再起動し、
      http://localhost:8080/servlet/RandomTableにアクセスして動くことを確認
  • mod_jkのインストール
    • aptitudeでlibapache2-mod-jkをインストール
    • /etc/libapache2-mod-jk/workers.propertiesを書き換える
      workers.tomcat_home=/usr/share/tomcat7
      workers.java_home=/usr/lib/jvm/jdk-8-oracle-arm32-vfp-hflt
    • Tomcatはport 8009を待ち受ける設定に変更
      (上記ファイルにて"ajp13_worker"がport 8009に接続する設定になっている為)
      /etc/tomcat7/server.xml の Connector port="8009" ... の行のコメントアウトを外してTomcatを再起動
    • /servletへのアクセスをTomcatに転送する設定
      /etc/apache2/mods-available/jk.confの<IfModule jk_module>に
          <VirtualHost *:80>
              JkMount /servlet/* ajp13_worker
          </VirtualHost>
      
      または
          JkMount /servlet/* ajp13_worker
          JkMountCopy All
      
      を追加

Wolfram CDF Playerは期待外れだった

図書館からMathematicaの入門書を借りて、夏に買ったRaspberry Pi 2のMathematicaを少し使ってみた。

一昔前のワークステーション並みの速度は出ていると思うが、確かに遅い。
筆者はMathematicaは25年くらい前に大学にあったNeXTで触ったのが最初だが、その当時を思い出した。(実際には当時はもっと遅かったのだろうが)
入門書を読みながら書いてたら入力行が200くらいになったノートブックを丸ごと再計算させたら、2分かかった。
一番気になるのは、スクロール動作が遅いことで、スクロール操作を終えてから少し遅れてページが動くことがあるので、スクロールバーを操作して思った位置で止められなかったり、思った所にカーソルを当てられないことがあるのが地味にストレスになる。
まあ、筆者は主にMaximaを使っており、Mathematicaはたまにしか使わないし、本格的に使うこともまず無いので、これで満足である。

今日までずっと、Wolfram CDF Playerは前身のMathematica Playerとは違って、ノートブックの数式の評価(計算)ができるようになったものと勘違いしており、どんな数式でもRaspberry PiのMathematicaで書いてパソコンのCDF Playerで計算すれば良いと思っていたが、そうではないようだ。CDF Playerでできるのは、インタラクティブなUIを操作して変数の数値を変えて、UI上の評価結果を更新することだけらしい。
これを利用して、CDF Playerに任意の数式を評価させる手段を作成した人もいるようだが、決して便利ではない。
CDF Playerは、ほぼMathematicaそのものでプログラムサイズが巨大な割に、そのほとんどの機能が容易には使えなくされている、残念な代物のようだ。

さらに、Raspberry PiのMathematica(11.0.1.0)では再生できるのに、CDF Player(ver 11.1.1.0 for Mac)では再生できないアニメーションがあった。
Raspberry PiのMathematicaで

sol = DSolve[{y''[t] == -9.8, y'[0] == 10, y[0] == 0}, y, t];
x[t_] := 2.5 t;
Animate[Graphics[{Red, Disk[{x[t], First[y[t] /. sol]}, 0.5]},
Frame -> True, PlotRange -> {{0, 5}, {0, 5}},
GridLines -> Automatic], {t, 0, 2}, AnimationRunning -> False]

を実行すると、

このようなアニメーションが実行されるが、その状態をCDFとして保存してCDF Playerで開くと、グラフ部分がエラー表示になり、マウスカーソルを当てると
座標{$CellContext`x[0.], $CellContext`y[0.]}が数値のペア、Scaled形式、またはOffset形式ではありません。

というエラーメッセージが表示される。

また、CDF Playerで表示されるエラーメッセージには別バージョンもあり、

sol2 = DSolve[{y''[t] == -9.8, y'[0] == v0, y[0] == 0}, y, t];
Animate[Plot[First[y[t] /. sol2 /. v0 -> v], {t, 0, 2},
PlotRange -> {0, 5}], {v, 5, 10}, AnimationRunning -> False]

の出力

も同様にCDF Playerではエラーになり、
ReplaceAll: {sol2} is neither a list of replacement rules nor a valid dispatch table, and so cannot be used for replacing.

と表示される。

しかし、その下で

Show[Graphics[{Red, Disk[{x[t], First[y[0] /. sol]} /. t -> 0, 0.5]},
Frame -> True, PlotRange -> {{0, 5}, {0, 5}},
GridLines -> Automatic]]

Show[Plot[First[y[t] /. sol2 /. v0 -> 10], {t, 0, 2},
PlotRange -> {0, 5}]]

を実行して表示した、それぞれのアニメーションの1フレームは、CDF Playerでも正常に表示されるので、少なくともsol2は"a list of replacement rules"として機能しており、よくわからない。
Webで検索しても、同じようなエラーが発生するという情報すら見つからず、お手上げである。

無料だから文句は言えないが、インタラクティブなコンテンツを再生できるのが売りのCDF Playerなのに、よりによってアニメーションがエラーになるとは、残念である。

筆者は就職して約20年、一貫してソフトウェア開発の業務に関わってきた。研究や試作、性能改善などの為のものではなく、要求仕様に従って開発して最後ば残バグを0件にする、言わば品質の作り込みと品質確保を目的とするものである。
そのようなソフトウェア開発では、開発プロセスの定義が必須である。筆者も、ある時は実開発者として開発プロセスに従って作業し、ある時は外注管理担当として開発プロセスに則っているかを点検し、ある時はリーダーとしてプロセス改善に取り組み、ある時は間接業務担当として開発プロセスが崩壊してデスマーチとなる様子を眺めてきた。設計やソースコードよりも開発プロセスを最も意識して会社人生を過ごしてきたと言って過言ではない。

複数の開発プロセスを見てきて、用語の違いや揺れはかなりあったが、大体、ウォーターフォールモデル(及びスパイラルモデルの1サイクル)の開発フェーズは次のように分けられると理解してきた。

要件定義
ユーザー/顧客の言葉で、ソフトウェアに求められる要件を記述する
要求分析
ソフトウェアの仕様が一意に解釈される、完成したソフトウェアが要求を満たすかどうかを客観的に評価できる、要求仕様書を作成する
システム設計/方式設計
全体のモジュール分割、モジュールのI/F仕様(静的構造)やタスク分割(動的構造)、メモリ割り当てなど、ソフトウェア全体のトップレベルの基本設計を、プログラミング言語に依存しないレベルで行う
プログラム設計/モジュール設計/詳細設計
プログラミング言語に依存するレベルの設計やモジュールの内部設計を行う
実装
プログラムのコーディングを行う
単体テスト/モジュールテスト
モジュール内部の実装を踏まえた、網羅的なテストを行う
プログラム設計/モジュール設計/詳細設計に対応する
結合テスト
結合相手のモジュールがI/F仕様を満たしているかなどをテストする
システム設計/方式設計に対応する
システムテスト
ソフトウェア全体が要求仕様を満たしているかどうかをテストする

このように理解してきて、これまで実務上不都合はほとんど無かった。

たまに「要求分析」が「要件定義」より先になっている開発プロセスを見かけたが、たまたま、一般的な何かに基づいて作成されたように見えない、特定の会社が独自に考えたように見えるものばかりだったので、それを作った人の理解不足、きっとユーザーの「要求」を「分析」してシステムの「要件」を「定義」するという発想で字面だけで決めたものだろうと思っていた。

しかし、特にここ3年くらい、様々な企業が作成する資料で、「要求分析」が「要件定義」より先になっている開発プロセスをいくつも目にするようになって、気になるようになった。

そこで、ちょっとソフトウェア開発プロセスの「要件定義」と「要求分析」の使われ方について調べてみた。


[事例1]https://www.ipa.go.jp/files/000053941.pdf
IPA(情報処理推進機構)のサイトにある、この資料でも、「要求分析」→「要件定義」の順になっている。さらに「要求分析」→「要求定義」→「要件分析」→「要件定義」とも書かれていて、この一連の活動の成果物が要件定義書となっている。
要求分析は「顧客の要求」を分析するフェーズで、要件定義がシステムの要件を定義するフェーズという意図らしい。

[事例2]ESPR Ver.2.0
IPA/SECが作成した、ESPR(組込みソフトウェア向け開発プロセスガイド) Ver.2.0では「システム要求定義」「ソフトウェア要求定義」という言葉を使っている。開始条件として、それぞれ「製品企画として、エンドユーザーニーズが明確になっている」「製品仕様として、取説レベルの内容は決まっている前提になっている」とあり、出力はそれぞれシステム要求仕様書/ソフトウェア要求仕様書である。

[事例3]https://www.ogis-ri.co.jp/otc/hiroba/technical/RequirementsAnalysis/pdf/RequirementsAnalysis.pdf
オージス総研のサイトにある、この2007年くらいの記事では、「要求定義」の成果物が要求仕様書と書かれており、「要求定義」が要求分析を含むものと解釈できる。

[事例4]http://isw3.naist.jp/IS/TechReport/report/2011002.pdf
このレポートには「要求分析」の後に「要件定義」のように見える図があるが、本文は「要件定義」が「要件分析」/「要求分析」を含む前提で書かれている。

[事例5]JIS X 0160
ISO/IEC 15288の「利害関係者要件定義」→「要件分析」が、JIS X 0160では「利害関係者要求事項定義」→「システム要求事項分析」に対応すると書かれている。
「システム要求事項分析」の中に「要求事項の仕様化」があり、要求仕様書という言葉は使われていないが、次のフェーズが方式設計なので、このフェーズで要求仕様書が作成されるものと思われる。

[事例6]PMBOK(5th Edition)
"Collect Requirements"の次が"Define Scope"となっている。"Define Scope"において、specificationの類の単語は出て来ない。

[事例7]SWEBOK V3.0
"Requirements Elicitation"(要求抽出)
"Requirements Analysis"(要求分析)
"Requirements Specification"(要求仕様記述)
の順になっている。

[事例8]SDEM
よく知られた富士通のソフトウェア開発プロセスであるSDEMでは、「要件定義」という言葉が使われ、このフェーズが要求分析や要求仕様書作成を含むようである。


まず、「要求定義」や「要件分析」といった用語の揺れがあるが、筆者の理解では、開発プロセスの文脈において「要件」と「要求」の違いはほとんど無い。日本語的には、「要件」はシステムが満たすべきもので「要求」はユーザーが求めるものという印象があるが、元々はどちらも英語のrequirementを訳したものであり、「要件」=「要求仕様」だと思っている。「要件」は"requisite"だ、と書かれたのを何かで読んだことがあるが、筆者は英語の文献で"requisite"を目にしたことがない。
通常、"requirement"の訳は「要求」なので、表記の統一を目指せば「要件定義」は「要求定義」になるだろうが、筆者としては「要件定義」の方が日本語として自然だと思うし、Googleで調べても圧倒的に多い。

[事例1]では「要求分析」→「要求定義」→「要件分析」→「要件定義」となっているが、しかもこの一連のフェーズの成果物が要求仕様書の前段階の要件定義書だと、果たしてこれで要求分析と要件定義は明確に区別できるのだろうか?
筆者の感覚では、システムの動作仕様が一意に解釈される仕様書を作成するフェーズより前のフェーズはいくら分解しても無意味だと思う。そもそも分解したフェーズの完了基準を明確に一般化できないであろう。抽象レベルに差をつけるのであれば、結論ありきで逆算して段階的に抽象化する、無意味な工数を発生させるだけだと思う。

[事例6]も、日本語訳すれば"Collect Requirements"は「要求分析」、"Define Scope"は「要件定義」という感じなので、「要求分析」→「要件定義」であるが、同様に"Define Scope"のoutputが仕様書ではなく「要件定義書」という感じで、その先にも仕様を作成するフェーズが見当たらないので、そのままソフトウェアの開発プロセスとしては使えない。

[事例2][事例5]は要求仕様書を作成するフェーズの前にもユーザーの要求を扱うフェーズがあって、「要求定義」「要件分析」「システム要求事項分析」というフェーズで要求仕様書を作成している。

[事例3][事例4][事例8]はユーザーの要求を扱うフェーズが1つで、その大括りな「要求定義」「要件定義」というフェーズで要求分析を行い、要求仕様書を作成する。

[事例7]は要求仕様書を作成するフェーズの前に「要求分析」のフェーズがあるので、「要求分析」の成果物は、要求仕様書ではなく、要件定義書に相当するものだと思う。
ただ、少なくとも"Requirements Specification"を「要件定義」と訳す人はいないと思うので、[事例3][事例4][事例8]と同様、これらを含む大括りなフェーズの中に「要件分析」と「要求仕様書記述」があると解釈するのが妥当だろう。

こうして見ると、「要求分析」→「要件定義」の順の開発プロセスでは大体要求仕様作成、つまりソフトウェアの設計フェーズへのインプット作成まで行っていないので、何かソフトウェア開発とは異なるものを対象にしているのかも知れない。
もし「要件定義」という名前のフェーズで要求仕様書を作成するのであれば、名前からしておそらくそのフェーズで要求仕様書の前段階である要件定義書に相当するものも作成するだろうから、「要件定義」の前に似たような目的のフェーズは必要ないと思う。

筆者が冒頭で示したような、要件定義書作成フェーズと要求仕様書作成フェーズが大きく分かれているプロセスは、[事例2][事例5]のように近いものがいくつかあったが、要求仕様書を作成するフェーズの名称として「要求分析」が使われているケースは、上記の事例以外も含めて1つも見つからなかったので、「要件定義」→「要求分析」も一般的ではないようだ。筆者が最初の10年居た会社で外注管理の為に読みまくった開発プロセスには間違いなく「要求分析」と書かれていたので、個人的にショックである。そういえば、確か過去に要求仕様書作成フェーズの名前を「要求仕様」としていた開発プロセスがあり、当時はネーミングセンスが無いなあと思っていたが、今改めて考えると、よく体を表している名前だ。

最も一般的なのは、[事例3][事例4][事例7][事例8]のように、要求仕様書作成をゴールとする要件定義フェーズが要求分析を含む開発プロセスのようだ。確かに、要求仕様書作成に限らず、その前の要件定義書を作成する上でも要求分析と呼ぶべき作業は必ずあるので、不自然さが無く、これが妥当だと思った。

このweblogは9年もの間、玄箱HGの自宅サーバーで運用して来たが、昨年辺りからHDDがすごい音を出すようになり、そろそろハードウェア的に限界が来ていると思っていたのと、少し前にこの玄箱を木製の密閉式のサイドボードに入れるようにしてから、えらく高温になり、その内発火しそうなので、サーバーを引っ越すことにした。

当初、24h運転の自宅サーバーを設置した理由は、weblogを始めようと思ったら、プロバイダーの無料のホームページ設置スペースではMovableType等のCMSが使えなかったからであり、その後、Javaアプレットを公開したり、色々なCGIやJavaのサーブレットを動かして遊ぶようになって手放せなくなったが、現在はそういったことまでできる、自由が効く安価なレンタルサーバーがあるので、自宅サーバーにこだわる理由が無くなった。

しかし、今回はRaspberry Piを購入して、自宅サーバーの運用を継続することに決めた。
あの高価な、少し前まで40万超えだったMathematicaを無料で使えるということで、いつかRaspberry Piを買おうと思っていたのだが、さらに先月、Raspberry Piで個人的に思い入れのあるFreeBSDが動作することを知り、この夏休みにFreeBSDでサーバー構築すべく、Raspberry Piを購入したのである。

最新のRaspberry Pi 3はFreeBSDが未対応ということなので、Raspberry Pi 2を購入した。
microSDカードは、相性があるそうでよくわからなかったので、とりあえず日本製であれば何でも良いかと思って、Amazonで安かったTOSHIBAのMSDAR40N32Gを合わせて購入した。

とりあえず動かしてみようと、セットアップマニュアルに従って、microSDに"NOOBS"を焼き込み、HDMIケーブルでテレビと繋ぎ、電源端子にmicroUSBケーブルを挿してUSB給電可能な玄箱HGを繋ぐと、セットアップ画面が表示された。
OSの選択をしないと先に進まないようだったので、USBマウスを繋いでRaspbian OSを選択すると、1時間くらいしてOSのインストールが終わり、再起動するとRaspbianのデスクトップ画面が開いて、上部のMathematicaアイコンをクリックするとMathematicaが起動したので、とりあえず感動しておいた。

続いて、FreeBSD-11.1-RELEASE-arm-armv6-RPI2.img.xzをダウンロードして、このページに従ってmicroSDに焼き込んで起動すると、テレビにカラフルな四角形が表示されたのだが、SDカードのアクセスランプが点滅を繰り返すばかりで、そこから先に進まなかった。
何がおかしいのだろうと調べると、Raspberry Pi 2 v1.2ではSoCがBCM2836からBCM2837に変更され、ARMプロセッサーがCortex-A7からCortex-A53に変わったので、同じカーネルでは起動できなくなったらしい。
せっかくPi 3より少し割高だったPi 2を買ったのに、ガッカリである。

大量に出回っているPi 3用のFreeBSDはその内正式リリースされるだろうが、主に既にPi 2を使ってるシステムの為に販売されるPi 2 v1.2向けにもリリースされるとは限らないので、FreeBSDのリリースを待たず、Raspbianでサーバー構築することにした。

まだまだ不完全ながら、夏休みを丸々費やして、とりあえずweblogのデータを引っ越して最低限動作するようになったので、Webサーバーをこれに置き換えた。

以下、やったことを大まかに控えておく。

1. VNCの有効化
 最初からRealVNCサーバーがインストールされており、デスクトップの「設定」→「Raspberry Piの設定」→「インターフェース」で有効化するだけだった。

2. aptitudeでmovabletype, apache2, mysql-serverをインストール
 まずupdateする必要があった。
 aptのパッケージにmovabletype 5.2.7があったので、これまでマニュアルインストールして使っていた3.3.5から移行することにした。今はWordPressが圧倒的に人気で、MovableTypeはほとんど使われなくなっているそうだが、MovableTypeはHTMLを静的に生成できるので、依然としてスペックの低いマシンに向いていると思う。
 movabletypeのインストール中に、設定ファイルを自動生成するか何かのダイアログが出たが、自動設定しないを選択した。

3. Apache2の設定
 /etc/apache2/sites-enabled/000-default.conf のDocumentRootを変更
 /etc/apache2/mods-enabled/ にcgid.conf, cgid.loadのsymlink追加
 /etc/apache2/conf-enabled/serve-cgi-bin.conf にアクセス可能アドレス設定

4. MovableTypeの試運転
 mt.cgiにアクセスすると

You don't have permission to access /XXX/mt-wizard.cgi on this server.

 というエラーになるので、
 /etc/apache2/conf-enabled/movabletype-opensource.conf
 の
Require all denied

 の行をコメントアウトしてmt_wizard.cgiを有効にする必要があった。
 (/usr/share/doc/movabletype-opensource参照)

 それから、何故か/usr/lib/cgi-bin/movabletype/にthemesへのsymlinkが無かったのを修正した。

5. MovableTypeのデータベースの移行
 MySQLのデータベースをimportした後、
 https://www.movabletype.jp/documentation/mt5/upgrade/
 の「既存のデータベースを上書きする方式」に従ってアップグレードした。

6. BlogのDesign Themeを"Rainier"に変更
 デフォルトのデザインがもう1つな感じがしたので、定番の1つらしい"Rainier"をインストールした。
 MT5.2対応と書いてあるが、html_head.mtmlの <$mt:StatsSnippet$>がMT6.0以降にしか無いので、コメントアウトする必要があった。
 それから、本文と追記の境界がわからなかったので、間に線を引くようにした。

7. MT-Keystrokes v0.1.5の導入
 keystrokes.plを/usr/share/movabletype/plugins/にコピー
 keystrokes.plは以下の2行をコメントアウト
 ・"return 1 unless $MT::VERSION =~ m(^3\.);"の行
 ・"$eh->error("keystroke id: " . $obj->blog_id);"の行
 MovableTypeの管理画面でテンプレートのComment Formを以下のように変更
 ・<form>タグの直後に<$MTKeystrokes$>を追加
 ・<textarea>タグ内にonkeypress="keystrokes(this.form)"を追加
 ・<input type="submit" value="Submit">タグ(2ヶ所)内にonclick="keystrokes(this.form)"を追加

8. ローカルメールサーバー立ち上げ
 aptでmovabletypeをインストールした時にexim4がインストールされたので、SMTPサーバーはこれを使用した。
 dpkg-reconfigure exim4-configで設定画面を開き、適当に設定した。
 POP3サーバーはDovecotをインストールした。

9. 固定IP化
 /etc/dhcpcd.confの末尾に次の4行を挿入

interface eth0
static ip_address=192.168.0.XX/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1

 (man dhcpcd.confのstaticの所参照)

一昨日、野球のポジション当てゲームというのを知った。

http://heart-quake.com/article.php?p=527より:

1.藤山選手はサードと同じアパートに住んでいるということだ。
2.センターはライトより背は高いが、足はライトの方が早い。
3.鈴木選手の妹さんはセカンドと婚約中だそうで、どうも挙式は来春だそうだ。
4.キャッチャーの長男とサードの次女は同じ小学校の同級生だそうだ。
5.ショートとサードそれに桜井選手の3人はよく揃って競馬に行くそうだ。
6.ピッチャーはとても麻雀が強く、今月も梅田選手と菊池選手から5000円ずつまきあげたそうだ。
7.外野選手のうち一人はどうも木下選手か松村選手らしい。
8.小川選手はどうも奥さんとうまくいっていないようだ。近々離婚するのではないかとの噂がとんでいる。
9.選手達はよく揃ってゴルフに行くが、梅田・藤山・桜井の3選手はどうしてもキャッチャーとセカンドには勝てないようだ。
10.ピッチャーの奥さんはサードの妹さんだそうだ。
11.松村選手はキャッチャーと、又、桜井選手はピッチャーととても仲が良いようである。
12.選手たちのうちで、独身なのは、鈴木・梅田・山田の3選手、それにセンターとライトの5人である。
13.山田選手は桜井選手より背が高く、木下選手は桜井選手より背が低い。しかし、この3人はいずれもファーストより低い。

ここからは、問題文を一部改変しております。
製品をご購入いただくと正式な問題文が閲覧可能です。

14.選手たちのうちで酒を飲まないのは、XXX選手とXXX選手。それにショートの3人だけだそうだ。
15.バッテリーと内野の全員はXXX・XXX・XXXの3選手を除くとみんな外野の小川選手より背が低い。
16.鈴木選手は外野手のX人と一緒に麻雀をよくするそうだ。
こういう論理パズルで、色々制約のあるグループワークで解くもので、何らかの研修とかで使われるものらしい。

これが、色々制約のあるグループワークだから難しいが、一人でやったら簡単に解けるということだったので、上記XXXの部分を入手して、早速その日の夜にやってみたら、1時間くらいかけても解けなかった。

次の日の夜も、前日と同様、「藤山 not 5, 8 > 9, 鈴木 not 4, 4 single, ...」と紙に書き出すと、読み易い文字で書き直した為か、20分くらいで何らか解が出たのだが、解は複数あるような感じで、出した解は別途入手した答えと違った。

本当に解は1つしかないのだろうか?と気になって、今日、次のような簡単なプログラムを書いて探させてみたら、確かに解は1つしか無かった。

#!/opt/local/bin/python3
from itertools import permutations
names = ('藤山', '鈴木', '桜井', '梅田', '菊池', '木下', '松村', '小川', '山田')

def taller(name1, name2):
    dict1 = {'山田':3, '桜井':2, '木下':1}	#13
    if name1 in dict1 and name2 in dict1:
        if dict1[name1] > dict1[name2]: return True
    if name1 in ['XXX', 'XXX', 'XXX'] and name2 == '小川': return True	#15
    return False

def single(name):
    return name in ['鈴木', '梅田', '山田']	#12

def married(name):
    return name in ['小川']	#8

def judge(position):
    name_by_pos = {v:k for k,v in position.items()}

    if position['藤山'] == 5: return False	#1
    if taller(name_by_pos[9], name_by_pos[8]): return False	#2
    if position['鈴木'] == 4 or married(name_by_pos[4]): return False	#3
    if single(name_by_pos[2]) or single(name_by_pos[5]): return False	#4
    if position['桜井'] == 5 or position['桜井'] == 6: return False	#5
    if position['梅田'] == 1 or position['菊池'] == 1: return False	#6
    if (position['木下'] >= 7 and position['松村'] >= 7) \
       or (position['木下'] <= 6 and position['松村'] <= 6): return False	#7
    #8
    if position['梅田'] in (2,4) or position['藤山'] in (2,4) \
       or position['桜井'] in (2,4): return False	#9
    if single(name_by_pos[1]): return False	#10
    if position['松村'] == 2 or position['桜井'] == 1: return False	#11
    if position['鈴木'] >= 8 or position['梅田'] >= 8 or position['山田'] >= 8 \
       or married(name_by_pos[8]) or married(name_by_pos[9]): return False	#12
    if position['山田'] == 3 or position['桜井'] == 3 \
       or position['木下'] == 3: return False	#13
    if position['XXX'] == 6 or position['XXX'] == 6: return False	#14
    if position['XXX'] >= 7 or position['XXX'] >= 7 or position['XXX'] >= 7 \
       or position['小川'] <= 6: return False	#15
    if XXXXXXXXXXXXXXXXXXXXX: return False	#16
    return True

for p in permutations(range(9)):
    position = {names[i]: p[i]+1 for i in range(9)}
    if judge(position) == True:
        print(position)
(上記問題文で伏字にされている部分がわかる部分は伏字にしている)

プログラムのデバッグ中に、次のような表を作ってこれを埋めていく形で解き直したら、10分もかからずに解けた。問題を半分くらい覚えてしまったから早く解けたということもあるが、最初からこうやれば良かったと思った。

藤山 0 00
鈴木00 00
桜井00 000
梅田00 00
菊池0
木下 0 777
松村 0 777
小川 0 00
山田000 0
(1.〜13.まで読んでマークした状態、0は偽、7は7.参照の意)

筆者はこの所、4五歩早仕掛けを多用している。長らく将棋の勉強を怠っており、対四間飛車の急戦はこれしか思い出せないのである。しかも、四間飛車に対して急戦の構えを見せると△4三銀と上がる人が多いので、実現しやすく、便利である。
図1:4五歩早仕掛け

今月、久々に将棋を指す機会があり、久々の4五歩早仕掛けがクリーンヒットして安心し、その次の対局でもまた4五歩早仕掛けをしたら、4三の銀を5四〜6五〜7六と斜めにスルスルと動かされ、対処できずボロボロに負けてしまった。
図2:6九金型の4五歩早仕掛けに対する玉頭銀

筆者は昔からこの△5四銀が苦手なのであるが、寡聞にして定跡書で見たことが無かった為、この手は無い手で、必ず咎められる手だと思っていた。それで、△5四銀とされれば▲5五歩から殺しにかかるのだが、成功することは5回に1回もない。(しかも、銀を殺せても良くなるとは限らない。)

玉頭銀を殺すイメージ図
図3:歩を入手して▲7七歩とできれば銀が死ぬ

図4

図5:次に▲6六歩で銀が死ぬ

図6:このままなら次に▲7七歩で銀が死ぬ

今回も銀を殺せず、悪形だけが残り、総崩れしてしまった。
あまりにもひどかったので、一度きちんと調べておくことにした。


【結論】
  • この△5四銀〜△6五銀〜△7六銀と動く戦法には「玉頭銀」という名前が付いているらしい。
  • 玉頭銀は6九金型の4五歩早仕掛けに対して特に有効とされているらしい。
  • 4五歩早仕掛けの1手前の▲4六歩は△5四歩を待ってからにするべきらしい。
  • 従って、▲6八金直△5四歩の交換を入れてからにするべきらしい。

しかし、▲6八金直の次に△5四歩とされるとは限らない。
▲6八金直の直後に△5四銀と玉頭銀で来られれば▲5五歩△6五銀▲7五歩〜▲2六飛の筋があるし、▲3五歩△同歩▲4六銀が速いので、玉頭銀の牽制になっているとは思うが、後手には△1二香や△6四歩など、色々な手がある。△6四歩〜△5四銀〜△6三銀引と堅められることも多い。それに対して、▲6八金直は有効な待ちなのだろうか?

筆者は人生で▲6八金直にいい思い出が無い。△8四桂〜△7六桂の両取りを狙われるし、下段が空くし、5七銀の引き場所が無くなるし、▲5九香の頑張りがあまり利かなくなる。

▲6八金直が得な手でなく、次に△5四歩や△5四銀が期待できないなら、やっぱり▲6八金直と待つのではなく、さっさと▲4六歩と突きたい。それでもし△5四銀を食らっても、正確に指されれば少し悪くなる、という程度に済ませる方法は無いだろうか、と思って、懲りずに6九金型で玉頭銀を食らった場合の対策を探してみた。


△5四銀の後は(1)▲3七桂、(2)▲3八飛、(3)▲5五歩の3通りが多いようである。

(1)▲3七桂の後は、△6五銀▲4五歩△7六銀▲2四歩△同歩▲4四歩
図7
△同角▲同角△同飛▲7七歩△8七銀成
図8
▲同玉△8四飛▲8六銀△6四角▲9七角△9五歩▲同歩△9六歩▲同玉△8六角▲同角△8五銀
図9
と玉頭銀の恐ろしさを一方的に見せつけられるのが、代表的な進行である。

▲4四歩△4四同角には▲7七歩が正解だと思われる。▲4四歩に△3五歩でも▲7七歩と打つことになるらしい。(1)の▲3七桂は、銀を取れないのに形悪く▲7七歩を打つことになることが多く、いまいちである。

ただ、▲3七桂△6五銀▲4五歩△7六銀の後、▲4四歩でなく▲4四角とする手があるらしい。
△同角▲同歩△同飛なら▲6六角として、△8四飛と回るのを防げる。
図10


(2)▲3八飛は、△3二飛なら▲5五歩△6五銀▲3五歩△同歩▲同飛△7六銀▲2四歩△同歩▲5四歩(図6)のように銀を狙えそうだが、図6からでも簡単には銀が取れない。△4五歩でも助かりそうだし、△6四歩▲6六歩△6五歩▲8六歩△6六歩▲同銀△6四歩とやっても後手がなんとかなりそうである。

▲3八飛には△4五歩とする人が多いと思う。その後、▲3三角成△同桂▲8八角△4三金▲2八飛に△6四角(山崎六段)
図11
という手があり、これは先手こらえるのが大変そうである。△6四角に▲2四歩と攻め合って先手良しとする本もあるが、筆者には先手良しと理解できなかった。

▲3八飛には何と△4三銀と戻る手もあり、▲3五歩△同歩▲同飛△3二飛▲4五歩△同歩▲4四歩△2二角▲4五飛
図12
と自然に進めても、△3四銀(谷川-藤井)でうまくいかない。


やはり筆者としては、△5四銀には勝ち負けを度外視してでも(3)▲5五歩としたい。
▲5五歩の後は△6五銀▲3五歩△同歩▲3八飛
図13
とする。
そこで△4三金だと、▲3五飛△3四歩▲3六飛△7六銀▲4五歩△6五銀▲7五歩△4五歩▲8六飛(図5)のように銀挟みが成功する筋がある。

図13から△4五歩だと、▲3五飛△4六歩▲4五歩△3四歩▲同飛△4五飛▲3七桂△4四飛▲同飛△同角▲4一飛
図14
が一例で、ここで△3五角なら後手良しらしいが、アマ同士なら先手もやれそうではないだろうか。

図13から△4五歩▲3五飛△4六歩▲4五歩に△7六銀とした実戦例(森下-櫛田)もあり、▲4六銀△9五歩▲同歩△9八歩▲同香△9七歩▲同香△9六歩▲同香△8五銀
図15
と端から大暴れされたが、▲3二歩の垂らしから、と金を作って先手が勝っている。

図13から△7六銀だと、正確に指されると後手良しらしいが、▲3五飛△6五銀▲6六歩△7六銀▲5四歩△同歩▲9七角
図16
という指し方もあるかも知れない。△3二飛なら▲2四歩△同歩▲2二歩である。


1筋の突き合いが入っていると、6九金型でも玉頭銀に対して別の対策があるかも知れない。

1つには、△5四銀に対して、▲5五歩△6五銀▲3五歩△同歩▲3八飛△7六銀▲3五飛△6五銀に▲1五歩(升田-大山)という定跡がある。
図17
以下△同歩▲3四歩△2二角▲2四歩△4三飛▲5四歩△同銀▲3三歩成△同飛▲同飛成△同角▲2三歩成△5一角▲2八飛
図18
で先手優勢である。

もう1つは、△5四銀に対して、▲5五歩△6五銀▲3五歩△同歩▲3八飛△4五歩▲3五飛△4六歩の後、▲3七桂△7六銀▲4五桂が成立する。
図19
1筋の突き合いが入っていないと、ここで△1五角があり、▲5四歩△同歩▲1一角成としても△3四歩で後手良しになるが、図19だと△3四歩か△2二角しかなく、△3四歩なら▲3三桂成、△2二角なら▲2四歩でいずれも崩壊ではないだろうか。
 
問題はどこで1筋の突き合いを入れるかだが、▲5七銀左と上がる前に入れている実戦例が、上記の升田-大山戦を含め複数あるので、そのタイミングが良いと思う。ただ、後手が玉頭銀で来ない場合にどういう影響があるかは未確認である。

■参考文献
最強将棋21 四間飛車破り【急戦編】渡辺 明(著)

前回のFamily Out Problemの確率モデル(下図)を題材に、ナイーブベイズ分類器を使ってみる。


Family Out Problem

この問題において、family-out以外の変数の真偽値が与えられた時にfamily-out=TRUEかどうかを判定するよう、ナイーブベイズ分類器を学習させてみる。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import BernoulliNB
from sklearn.metrics import precision_recall_fscore_support
from sklearn.metrics import roc_curve, auc

def generate_sample():
    "Sample data generator of the Family Out problem"
    fo = np.random.binomial(1, 0.15)
    bp = np.random.binomial(1, 0.01)
    lo = np.random.binomial(1, (0.05, 0.6)[fo])
    do = np.random.binomial(1, ((0.3, 0.97), (0.9, 0.99))[fo][bp])
    hb = np.random.binomial(1, (0.01, 0.7)[do])
    return [fo, bp, lo, do, hb]

# Generate training data and test data
train_data = np.array([generate_sample() for _ in range(1000)])
test_data = np.array([generate_sample() for _ in range(100)])

X_train = train_data[:, 1:5]	# other than fo
y_train = train_data[:, 0]	# only fo
X_test = test_data[:, 1:5]	# other than fo
y_test = test_data[:, 0]	# only fo

# Train a Naive Bayes classifier
clf = BernoulliNB()
clf.fit(X_train, y_train)

# Evaluate with training data
y_pred = clf.predict(X_train)
metrics = precision_recall_fscore_support(y_train, y_pred)
print('Evaluation with training data')
print('Class FamilyOut=False: Precision={:.3f}, Recall={:.3f}, F-measure={:.3f}'.format(metrics[0][0], metrics[1][0], metrics[2][0]))
print('Class FamilyOut=True : Precision={:.3f}, Recall={:.3f}, F-measure={:.3f}'.format(metrics[0][1], metrics[1][1], metrics[2][1]))

# Evaluate with test data
y_pred = clf.predict(X_test)
metrics = precision_recall_fscore_support(y_test, y_pred)
print('Evaluation with test data')
print('Class FamilyOut=False: Precision={:.3f}, Recall={:.3f}, F-measure={:.3f}'.format(metrics[0][0], metrics[1][0], metrics[2][0]))
print('Class FamilyOut=True : Precision={:.3f}, Recall={:.3f}, F-measure={:.3f}'.format(metrics[0][1], metrics[1][1], metrics[2][1]))

# Draw ROC curve
y_train_post = clf.predict_proba(X_train)[:, 0]
y_test_post = clf.predict_proba(X_test)[:, 0]

fpr, tpr, thresholds = roc_curve(y_train, y_train_post, pos_label=0)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, 'k-', lw=2, label='ROC for training data (area = {:.2f})'.format(roc_auc))

fpr, tpr, thresholds = roc_curve(y_test, y_test_post, pos_label=0)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, 'k--', lw=2, label='ROC for test data (area = {:.2f})'.format(roc_auc))

plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC curve of BernoulliNB')
plt.legend(loc="lower right")

plt.show()

問題のモデルに従って学習用データとテストデータをランダムに生成し、sklearn.naive_bayes.BernoulliNBの分類器を学習させ、テストデータを分類させ、family-out=FALSEとfamily-out=TRUEのそれぞれについてPrecision, Recall, F値を計算している。
加えて、予測したfamily-out=FALSE/TRUEの確率から、ROC曲線とAUCを出力している。

出力例

Evaluation with training data
Class FamilyOut=False: Precision=0.936, Recall=0.983, F-measure=0.959
Class FamilyOut=True : Precision=0.847, Recall=0.589, F-measure=0.695
Evaluation with test data
Class FamilyOut=False: Precision=0.923, Recall=0.988, F-measure=0.955
Class FamilyOut=True : Precision=0.889, Recall=0.533, F-measure=0.667
ROC曲線
ROC curve
その時のtraining_dataとtest_data(.arff形式)
family_out_train.arff
family_out_test.arff

ナイーブベイズ分類器には、同じクラスのデータでは全ての特徴が独立に出現する、つまりbp,lo,do,hbに依存関係が無く、これらがTRUEになる確率はfoの値だけで決まるという仮定があるが、この問題ではbp,do,hbに依存関係があるので、ベイジアンネットワークの方がうまく学習できると考えられる。
なのでベイジアンネットワークで分類した場合の性能と比較したいが、scikit-learn(0.18)にベイジアンネットワークが無いので、代わりにWeka 3.8.1のBayesNetを用いて学習用データで学習させ、テストデータを分類した結果と比較してみる。

Wekaの操作手順

  1. Weka ExplorerのPreprocessタブでOpen file...ボタンを押し、family_out_train.arffを開く
  2. ClassifyタブでClassifierとしてBayesNetを選択する
  3. "BayesNet -D ..."とあるフィールドをクリックしてパラメーター設定画面を開き、searchAlgorithmのフィールドをクリックして、initAsNaiveBayes=False, maxNrOfParents=2に変更
  4. Test optionsのSupplied test setのボタンを押し、family_out_test.arffを開き、Classとしてfamily-outを選択
  5. クラスをfamily-outを選択し、Startボタンを押す
上記と同じデータを使った出力例
=== Run information ===

Scheme:       weka.classifiers.bayes.BayesNet -D -Q weka.classifiers.bayes.net.search.local.K2 -- -P 2 -N -S BAYES -E weka.classifiers.bayes.net.estimate.SimpleEstimator -- -A 0.5

=== Detailed Accuracy By Class ===

                 TP Rate  FP Rate  Precision  Recall   F-Measure  MCC      ROC Area  PRC Area  Class
                 0.988    0.467    0.923      0.988    0.955      0.651    0.889     0.967     0
                 0.533    0.012    0.889      0.533    0.667      0.651    0.889     0.648     1
Weighted Avg.    0.920    0.398    0.918      0.920    0.911      0.651    0.889     0.919     

=== Confusion Matrix ===

  a  b   <-- classified as
 84  1 |  a = 0
  7  8 |  b = 1

BernoulliNBのテストデータに対する出力と、Precision, Recall, F-measureが完全に一致している。

WekaのBayesNetのデフォルト設定では正しいネットワークが学習されなかったが、上記手順の3.のように設定を変えると、大体次のように正しいネットワークになった。

BernoulliNBの性能には試行毎にばらつきがあったが、1000回の平均を取ってみたのが次の値であり、上記の結果は特別に良い例ではない。

Average with training data
Class FamilyOut=False: Precision=0.925, Recall=0.982, F-measure=0.952
Class FamilyOut=True : Precision=0.844, Recall=0.545, F-measure=0.661
Average with test data
Class FamilyOut=False: Precision=0.925, Recall=0.982, F-measure=0.952
Class FamilyOut=True : Precision=0.842, Recall=0.544, F-measure=0.651
Average of AUC with training data=0.894
Average of AUC with test data=0.895
また、計10回、同じデータでWekaのBayesNetの性能と比較した所、Precision, Recall, F-measureについては、8回は一致していた。
ROC曲線のAUCはWekaのBayesNetの方が高いことが多かったが、BernoulliNBでも大体0.85-0.90の範囲であり、十分に高かった。
従って、この確率モデルに対して、ナイーブベイズ分類器は予測性能が十分に高いと言える。

なお、ここまでの結果では、学習データに対する各種性能値とテストデータに対する値にほぼ差が無いが、これは学習データのサンプル数が1000と十分に多く、偏りが無いからである。これを100にすると、次のように、テストデータに対する成績が少し下がるが、それでも、学習データだけに対して大幅に成績が良い「過学習(overfitting)」の状態と言える程ではない。

学習データ数が100の1000試行の平均

Average with training data
Class FamilyOut=False: Precision=0.931, Recall=0.963, F-measure=0.945
Class FamilyOut=True : Precision=0.793, Recall=0.582, F-measure=0.650
Average with test data
Class FamilyOut=False: Precision=0.926, Recall=0.955, F-measure=0.938
Class FamilyOut=True : Precision=0.767, Recall=0.564, F-measure=0.620
Average of AUC with training data=0.899
Average of AUC with test data=0.890

ベイジアンネットワークの復習をしていて、確率的グラフィカルモデル‐ベイジアンネットワークとその周辺‐(オペレーションズ・リサーチ 2013年4月号)という記事を見つけた。その中に、Family Out Problemという有名な例題(下図)の紹介があり、p.194に、

表1~3で与えられたCPTの値と式(11)を利用して丹念に計算することにより P(X1=1|X3=1,X5=1)=0.7577···という結論を得る.
と書かれていたので、これを自力で計算できたらベイジアンネットワークを理解できたことにしよう、と思って、丹念に計算してみたら、その値にならなかった。


Family Out Problem

ここでは、
X1: Family Out
X2: Bowel Problem
X3: Light On
X4: Dog Out
X5: Hear Bark
(それぞれ2値の確率変数)であり、それぞれの条件付き確率は次の通りである。
P(X1=1) = 0.15
P(X2=1) = 0.01
P(X3=1 | X1=0) = 0.05
P(X3=1 | X1=1) = 0.6
P(X4=1 | X1=0, X2=0) = 0.3
P(X4=1 | X1=0, X2=1) = 0.97
P(X4=1 | X1=1, X2=0) = 0.9
P(X4=1 | X1=1, X2=1) = 0.99
P(X5=1 | X4=0) = 0.01
P(X5=1 | X4=1) = 0.7

P(X_1=1 | X_3=1, X_5=1) = \frac{\sum_{X_2}\sum_{X_4} P(X_1=1, X_2, X_3=1, X_4, X_5=1)}{\sum_{X_1}\sum_{X_2}\sum_{X_4} P(X_1, X_2, X_3=1, X_4, X_5=1)}
なので、Pythonで

P00101 = 0.85 * 0.99 * 0.05 * 0.7 * 0.01
P00111 = 0.85 * 0.99 * 0.05 * 0.3 * 0.7
P01101 = 0.85 * 0.01 * 0.05 * 0.03 * 0.01
P01111 = 0.85 * 0.01 * 0.05 * 0.97 * 0.7
P10101 = 0.15 * 0.99 * 0.6 * 0.1 * 0.01
P10111 = 0.15 * 0.99 * 0.6 * 0.9 * 0.7
P11101 = 0.15 * 0.01 * 0.6 * 0.01 * 0.01
P11111 = 0.15 * 0.01 * 0.6 * 0.99 * 0.7

P35 = P00101 + P00111 + P01101 + P01111 + P10101 + P10111 + P11101 + P11111
P135 = P10101 + P10111 + P11101 + P11111
P1_35 = P135 / P35
print(P1_35)
とすると、0.8578...という数値になった。どこか読み間違えたかと思って、
def prob(x1, x2, x3, x4, x5):
    p = 1.0
    p *= (0.85, 0.15)[x1]
    p *= (0.99, 0.01)[x2]
    p *= ((0.95, 0.05), (0.4, 0.6))[x1][x3]
    p *= (((0.7, 0.3), (0.03, 0.97)), ((0.1, 0.9), (0.01, 0.99)))[x1][x2][x4]
    p *= ((0.99, 0.01), (0.3, 0.7))[x4][x5]
    return p

P135 = sum([prob(1, x2, 1, x4, 1)
             for x2 in range(2)
             for x4 in range(2)])
P35 =  sum([prob(x1, x2, 1, x4, 1)
             for x1 in range(2)
             for x2 in range(2)
             for x4 in range(2)])
P1_35 = P135 / P35
print(P1_35)
と書いてみたが、やはり0.8578...だった。

上記の記事内の条件付き確率表に誤記があり、0.7577...というのは元の確率表で計算された値か、とも思ったが、どの文書のFamily Out Problemを見ても確率は同じだった。

正解は何なのか、何らかのツールで確認しようと思って、Pythonのベイジアンネットワーク関連のツールを探したが、適当なものがなかなか見つからなかった。

scikit-learnには"Naive Bayes"のAPIはあるがベイジアンネットワークは見つからない。
PyMCBayesPyは、きっとうまく使えばこの計算ができるのだろうが、ネットワークを定義して、CPT(conditional probability table、条件付き確率表)とevidence(観測値)を与えて事後確率を計算する直接的なサンプルコードが見つからなかったので、諦めた。
PBNTにはそういうサンプルコードがあったので、使ってみたが、P(X1=1|X3=1,X5=1)=0.2018...という全然違う値が出力された。P(X5=1)=0.2831と、これは正しい値が出たので、ネットワークとCPTは合ってそうであり、事後確率を計算するにはengine.marginal()でなく別のメソッドを使わないといけないのかとも思ったが、よくわからなかった。

Pythonを諦めてツールを探すと、Wekaでできることがわかった。Wekaは機械学習の勉強をするなら必修らしく、過去にインストールしていたので、やってみた。
Wekaを起動して、Tools->Bayes net editorを開くと、GUIがバグだらけ(Version 3.8.1, WindowsとMacとで確認)で使いにくいが、ノードを追加して右クリックしながらネットワークを作成し、Tools->Layoutでノードの配置を修正し、CPTを設定し、さらに保存したXMLを書き換えて色々修正し、Tools->Show Marginsを選ぶと、次のように結合確率が表示される。

さらに、右クリック->Set evidenceで LightOn=True, HearBark=True と設定すると、次のように、各ノードの事後確率が表示される。

これによると、FamilyOut=Trueの事後確率はやはり0.8578...である。筆者は何か問題を読み間違えているのだろうか?