Ruby – クラス~initialize

基本

Rubyのクラスのコンストラクターはinitialize()

initialize()ではなくインスタンスメソッド内でプロパティーを定義することもできる。

ただしこの場合には、プロパティーが定義されるメソッドをプロパティーが参照される前に読んでおかなければならない(そうしないと演算などで未定義でエラーになる)。

なので、普通はインスタンス生成時に自動的に呼ばれるinitialize()内でプロパティーを定義するのが安全。

オーバーロード不可

異なる引数のコンストラクターを意図してオーバーロードすることはできない。

上記のコードの場合、後の方で定義したコンストラクターは使える。

引数のパターンを変えたいときは、デフォルト引数可変長引数を用いるとよい。

 

Ruby – 関数

関数の定義

関数はdef...endで定義する。その関数が呼び出されるより前に定義されていなければならない。

関数の戻り値

return文で関数の戻り値を指定できる。

再帰呼び出し

再帰呼び出しにも対応。

デフォルト引数

あまりよくない方法

引数のデフォルト値をPythonと同じように指定できる。

実行時に指定した引数は、デフォルト引数があっても引数の順に入れ替えられる。以下の例の8行目で2つの仮引数に対して1つの実引数を与えているが、その値は1つ目の引数に使われている。

以下の6行目で仮引数名yzを指定して実引数を渡しているが、それらの値はxyに与えられてしまう。これは以下の流れによるものと考えられる。

  • 実行時に与えた引数は、デフォルト引数の有無にかかわらず先頭から順番に与えられる
  • 実行時に指定した引数'y=7''z=8'はそれぞれ式として評価され、その値が7、8となる
  • この2つの値が仮引数の先頭から与えられx=7y=8となり、zはデフォルト値の3が使われる

妥当な方法~キーワード引数

仮引数名を指定して実引数を渡す場合は、以下のようにキーワード引数を使うとよい。ただしキーワード引数で仮引数を指定した場合、実行時には指定を省略してデフォルト値を使うか、仮引数を明示して引数を与える必要がある。

デフォルト値を指定しない仮引数と、デフォルト値を指定したキーワード引数は混在して利用可能。

可変長引数

引数の頭に'*'をつけると配列とみなされ、複数の引数を渡せる。

配列の要素を使った処理の例。

通常の引数と可変長の引数が混在する場合、少なくとも通常の引数に対応する引数を渡す必要がある。

可変長引数の内容は空でもよく、まず通常の引数が埋められた後、その数を超える引数が可変長引数となる。

 

Ruby – 条件分岐

if文

基本形

一般的なif文の書き方で条件が真のときにthen節が実行される。endで閉じる。

else

else節も同じ。

elsif

elseifではなくてelsif

thenの省略

if節やelsif節の後のthenは省略可能。

一行で書く

一行にまとめることは可能。ただしthenがないとエラーになる。

';'で区切ると正常に実行される。

値を返す

if文は条件に対応した節の値を持つ。

後置

実行文の後にif文を置くと、条件が真の場合に文が実行される。

unless文

基本

unlessは条件が真でなければ(偽のときに)then節が実行される。

else

unless文のelse節は条件が真の時(偽でないとき)に実行される。

一行で書く

if文と同じく一行にまとめられる。

後置

実行文の後にunless文を置くと、条件が偽の場合に文が実行される。

case文

基本

case文は複数の条件を順番に確認していく。

thenの省略

thenは省略できる。

値を返す

case文は真となった条件の節の値が戻り値となる。

 

Ruby – 繰り返し処理

繰り返し処理の種類

for~要素を取り出して処理する

Rangeオブジェクトを使った繰り返し処理。".."演算子は両端の値を含む。

"..."演算子は終了値を含まない。

配列の内容を一つずつ取り出す例。

times~回数を指定する処理

timesは繰り返し回数を指定して実行。

繰り返しのカウンターを指定できる。カウンターはゼロからスタートし、指定回数-1までカウントされる。

upto/downto~カウントアップ/ダウン

uptoは初期値から終了値までを含んでカウントアップ。downtoは逆にカウントダウン。カウンターを省略しても可。

step~刻み幅を指定

[初期値].step([終了値], [刻み幅])で実行。

終了値がちょうどでない場合はその手前まで実行される。

ただし浮動小数点の計算誤差があり得るので、ちょっと気持ち悪い。

each~要素を一つずつ取り出す

eachforと同じくコレクションの要素を順に取り出す。

hashはキーと値を取り出せる。

each_with_index~要素番号も取得

each_with_indexはコレクションから要素を取り出しながら、そのインデックスも得られる。Pythonのenumerateと同じ機能。

while~条件が真の間繰り返し

whileは条件が真の間ブロックを繰り返す。

whilebeginendブロックの後に置ける。上記の前置の場合は条件によってはブロックは一度も実行されないが、後置の場合は少なくとも1回は実行される。

until~条件が真になるまで繰り返し

untilwhileと逆で条件が偽の間繰り返し、真になったらループ終了。whileと同様、ブロックの前/後どちらに置いてもよい。

loop~無限ループ

loopは単にブロックを繰り返す。ループの終了判定が必要。

繰り返し制御

break~ループを終了

breakは実行中のループブロックを終了する。

next~残りを飛ばして続ける

nextはそれ以下の実行をせず、ループの次のステップに入る。他言語のcontinueと同じ。

以下の例ではn==2のときだけprintが実行されず、次のn=3の処理に入る。

redo~同じ条件で再実行

redoは現在のループと同じ条件で再度実行する(ループの次のステップではなく同じステップを再実行)。

この例では、n==2の時に"2 - C"を表示。その後n==3となってredoが実行され、a=="C"のままputsから実行、"3 - C"が表示されている。

次の例はすこしややこしい。

この例ではeachn=1とセットされて"1"が表示される。その後n==2となるのでredoが実行され、n==2のままprintが実行されて"2"が表示。その後n==3になるので次のeachに移り、n==2がセットされる。このn("2")を表示した後、n==3となるのでredoは実行されず・・・と続く。

 

Ruby – 変数と定数

変数の先頭はアルファベット小文字。任意のオブジェクト(の参照番地)を代入可能。

定数は先頭がアルファベット大文字。定数に値を代入しようとするとwarning。

 

Ruby – 演算子

算術演算子

単項演算子

算術演算子と括弧

除算は被除数・除数とも整数の時は小さい側の整数に丸められる(floor)。いずれかあるいは両方が実数の時は実数になる。

'%'で剰余を計算。

'**'でべき乗を計算。

文字列演算子

結合

'+'演算子は2つの文字列の結合結果を返し、元の文字列は保持される。

'<<'演算子は2つの文字列の結合結果を返し、1つ目の変数を結合結果で書き換える。

'*'演算子は文字列を繰り返して複製して結合。

ビット演算子

'~'は前値の否定演算子。

’&’’|’'^'はそれぞれAND、OR、XOR。

'<<''>>'は指定した数だけシフトする演算子。

数値比較演算子

数値の比較演算子は標準的な定義。

文字列比較演算子

論理演算子

'!'は前値の否定演算子。

'&&''||'は論理積、論理和。

'and''or'も論理積、論理和だが挙動が異なる。

andor演算子は他の殆どの演算・処理に対して優先度が低いため、演算子より前の項がまず実行されてから2項目との評価が行われるため。括弧で優先順位を明示すると&&||と同じ結果になる。

三項演算子

三項演算子の形。

使用例。

 

Ruby – 基本的なリテラル

数値

整数

基数を指定する場合。

先頭がゼロの場合は8進数と解釈される点に注意。8進数と解釈できない場合はエラーになる。

実数

小数点以下をゼロとすると実数のリテラルになるが、小数点のみを付けるとエラーになる。

0~1の小数点では、先頭のゼロは省略できない。

有理数

末尾に'r'を付けると有理数となり、分数形式で保持される。

複素数

'i'の直前に数値を並べると虚数となり、複素数形式で保存される。

文字列

ダブルクォートかシングルクォートで囲む。

ダブルクォートで囲むとバックスラッシュ記法は特殊文字として解釈されるが、シングルクォートで囲むとバックスラッシュが単なる文字として扱われる。

空白をはさんで並んだ文字列は結合される。

文字リテラル

'?'に続く一文字は文字列として扱われる。

ヒアドキュメント

'<<[識別子]'の次の行から'[識別子]'の直前の行までが複数行にわたる文字列として解釈される。

論理値

論理値のリテラルとしてtruefalseが定義されている。

 

PCR検査と条件付確率

概要

2020年9月時点で、COVID-19いわゆる新型コロナウイルス感染症は収束を見ていない。このコロナウイルスについて、PCR検査を増やすべきだ、いや効果はない/医療現場の負担が増えるだけ、といろいろな論が飛び交っている。

不思議なことに、多くの人の目に触れるマスメディアや一般的なネットのソースでは、殆どの場合定性的な、あるいはそれ以前の論拠もないようなものばかりが並んでいる。意図してネットで検索すると確率論を適用しているものも見られるが、残念ながらほとんどの人はこれらのコンテンツはその意義や存在さえ意識しないだろう。

新型コロナウイルスの場合、計算に必要なデータは未詳のようだが、少なくともベイズ理論を適用する教科書的なパターンの問題である。そこで、この機に学んだいくつかの用語や概念、計算過程などをまとめておく。

なお、難病検査を例にした条件付確率の考え方はこちらにまとめている。

基本のデータ

定義

鍵となる指標の定義を確認するとともに、その記号を以下のように定義する。

医学・疫学上の指標

陽性率/陰性率

以下は罹患している/罹患していないことがわかっている人の集合に対する割合。

感度(sensitivity):rSEN
真陽性率(true positive rate):rTPOS
罹患している人の検査結果が陽性と判定される割合。
特異度(specificity):rSPC
真陰性率(true negative rate):rTNEG
罹患していない人の検査結果が陰性と判定される割合。
偽陽性率(false positive rate):rFPOS
罹患していない人の検査結果が陽性と判定される割合。
偽陰性率(false negative rate):rFNEG
罹患している人の検査結果が陰性と判定される割合。

これらの関係を図にすると以下の通り。真/偽の陽性率/陰性率は罹患者数のみあるいは非罹患者数のみの集団に対して、検査の判定結果を集計して計算される。表から、検査判定の陽性/陰性の割合は問題にしていないことがわかる。

的中率

次に検査結果が判明している集団の実際の罹患状態に関する割合。

陽性的中率(Positive Predictive Value: PPV, PV+):rPPV
検査で陽性と判定された人のうち、実際に罹患者である割合。
陰性的中率(Negative Predictive Value: NPV, PV-):rNPV
検査で非陽性と判定された人のうち、実際に罹患していない者である割合。

これらは検査の判定結果が陽性あるいは陰性の集団における罹患/非罹患の人の割合で、罹患している(あるいはしていない)人の全体の割合は問題にしていない。

有病率/陽性率など

以下は対象となる集団全体における検査結果の割合や罹患状態の割合。

陽性率(positive rate):rpos
検査で陽性と判定された人の割合。
有病率(Prevalence / Prevalence rate):rPRV
ある時点における疾病者数の人口に対する割合。
罹患率(Incidence / Incidence rate):rINC
ある期間において、疾病リスクのある人が対象とする疾病に罹患する割合。

これらは対象エリアの全員に対して罹患している人の割合、検査結果が陽性の人の割合である。有病率と罹患率の違いは一般にはあまり伝えられていないようだ。図にすると以下のようになる。

補足:感度と特異度について

「感度(sensitivity)」が「罹患している」という帰無仮説が真なのに検査で陰性と判定してしまう誤り、いわば「第一種の過誤」を起こさない確率と言える。

また「特異度(specificity)」が「罹患していない」という仮説(上の帰無仮説の対立仮説)が真なのに検査で陽性と判定してしまう誤り、すなわち「第二種の過誤」を起こさない確率と言える。

補足:「特異度」という表現について

「特異度」の用語には違和感がある。日本語の「特異な」ならsinglularやpeculiarの方が近く、specificは「明確な」、「特定の」というのが相当する(specifyという動詞もその意味でつかわれる)。「特別な」というニュアンスもあるようだが、「特異」というのは言い過ぎではないか。

specificityは罹患していない人を検査で陰性と正しく判定する割合なので、罹患していない人が大勢だとすると、その明白な事実を支持する割合、「明白度」とすると、ちょっと意味が分からなくなる。あるいは、その人が罹患していないということを特定するなら、せめて「特定度」くらいか。

いずれにしても、「特異度」という表現が使われているのは、英語の単語のニュアンスからしても、そもそもの意味合いからしても気持ちが悪い。

ここで定義する記号

C / N
罹患している(contract)/罹患していない(non-contract)の人の集合
+ / −
検査結果が陽性/陰性の人の集合
N
検査人口
N+ / N
それぞれ検査結果が陽性/陰性の人口
NC / NN
検査を受けた人のうち、それぞれ罹患している(contract)/罹患していない(non-contract)人の人口
nC+ / nC , nN+ / nN
検査を受けた人のうち、それぞれ、罹患していてかつ検査結果が陽性(C+)あるいは陰性(C−)、罹患しておらずかつ検査結果が陽性(N+)あるいは陰性(N−)の人の人口

これらを表で整理すると以下の通り。

また、これらから計算される確率は以下の通り。

条件付確率による表示

感度/特異度(真陽性率/真陰性率)

感度や特異度は、ある人が罹患している/罹患していない場合に検査結果が陽性/陰性となる確率ともいえる。これを条件付確率で表示し、的中率による表現に変換すると以下のようになる。

(1)    \begin{align*} r_{SEN} = r_{TPOS} &= P(+|C) = \frac{P(C \cap +)}{P(C)} =\frac{P(C|+)P(+)}{P(C)} \\ &= \frac{r_{PPV} \cdot r_{POS}}{r_{PRV}}\\ r_{SPC} = r_{TNEG} &= P(-|N) = \frac{P(N \cap -)}{P(N)} = \frac{P(N|-)P(-)}{P(N)} \\ &= \frac{r_{NPV} \cdot (1 - r_{POS})}{1 - r_{PRV}} \end{align*}

偽陽性率/偽陰性率

偽陽性/偽陰性についても条件付確率で表し、これらを的中率による表現に変換すると以下のようになる。

(2)    \begin{align*} r_{FPOS} &= P(+|N) = \frac{P(N \cap +)}{P(N)} = \frac{P(N|+) P(+)}{P(N)}\\ &= \frac{(1 - r_{PPV}) r_{POS}}{1 - r_{PRV}} \\ r_{FNEG} &= P(-|C) = \frac{P(C \cap -)}{P(C)} = \frac{P(C|-) P(-)}{P(C)} \\ &= \frac{(1 - r_{NPV}) (1 - r_{POS})}{r_{PRV}} \end{align*}

たとえばここで真陽性率と偽陰性率を加えると、

(3)    \begin{align*} r_{TPOS} + r_{FNEG} &= \frac{r_{PPV} \cdot r_{POS}}{r_{PRV}} + \frac{(1 - r_{NPV}) (1 - r_{POS})}{r_{PRV}} \\ &= \frac{r_{PPV} \cdot r_{POS} + (1 - r_{NPV}) (1 - r_{POS})}{r_{PRV}} \end{align*}

ここで総数Nを仮定して分母分子にかければ、結果は1となることがわかる。

陽性的中率/陰性的中率

的中率は検査の判定陽性/陰性に対して、その対象者が実際に罹患/非罹患である割合を表す。これを条件付確率で表して陽性率や陰性率で表すと以下のようになる。

(4)    \begin{align*} r_{PPV} &= P(C|+) = \frac{P(C \cap +)}{P(+)} =\frac{P(+|C)P(C)}{P(+)}\\ &= \frac{r_{SEN} \cdot r_{PRV}}{r_{POS}} \\ r_{NPV} &= P(N|-) = \frac{P(N \cap -)}{P(-)} = \frac{P(-|N)P(N)}{P(-)} \\ &= \frac{r_{SPC} \cdot (1 - r_{PRV})}{1 - r_{POS}} \end{align*}

実際の使われ方

多くの場合、検査方法の精度を確認するには、「罹患している人と罹患していない人を連れてきて検査をしてみる」というのが妥当だろう。感染症などで疑いがある人が医療機関に集まるから、そういうところで臨床的に試すのだろう。場合によって臨床例が少ない場合は、とにかく検査方法を適用しながら、実際の罹患状態をトレースするという方法がとられるのかもしれないが。

ただCOVID-19のPCR検査の場合、データが公表されていないのかどうかわからないが、感度については適当に(あるいは他サイトで使われている値を持ってきて)設定したりしているものしか見当たらない。以下のような手順が見られる。

  1. 感度と特異度を適当に設定する。感度は70%、特異度は99%というのがほとんど。
  2. 対象エリアの陽性者数nと総人口Nを持ってくる。
  3. 陽性者数を感度で割って、罹患者数を算出。
  4. 罹患者数を総人口で割って有病率を算出。
  5. 総人口から有病人口を引いて非罹患者数を算出。
  6. 非罹患者数と特異度の定義から偽陽性者数を算出。
  7. 以上のパラメーターを使って陽性的中率を算出。

計算された陽性的中率がかなり低いと主張しているものや、オーソリティーがありそうなサイトでも例題みたいな数字を示して有病率10%くらいで設定したりしているものや、結構混乱しているようだ。

ここで陽性的中率を感度で微分して、感度を見てみる(ややこしい)。

(5)    \begin{align*} \frac{d r_{PPV}}{d r_{SEN}} = \frac{r_{PRV}}{r_{POS}} \\ \end{align*}

有病率が10000人に一人、陽性判定率が100人にひとりくらいなら、1%くらいで、感度(真陽性率)が数十%くらい違っても差は出なさそうだ。それよりも有病率がかなり低いと、いくら高感度の検査方法でも陽性的中率がかなり低くなりそうなことが式から読み取れる。

一方偽陽性率の方は、有病率や陽性的中率が結構低いとすると、陽性判定率の値が効いてくる。これは結構無視できないレベルのように思えるが。

ただ、共通して使えるデータが見当たらないので、数値の信憑性が揺らいでいるように思える。罹患・非罹患を計算するのに母数にエリアの全人口を使ったりするのは、検査をしていない人すべてを非罹患とみているので有病率を過少に見積もることになるだろう。

退院前の検査を含むかどうかなど定義の問題はあるが、そのようなフラグを立てたデータを集約するだけでも有益な情報になると思う。

 

罹患率と有病率

概要

2020年、COVID-19(新型コロナウイルス)の感染は続き、PCR検査が毎日のように各都道府県で行われている。これに伴って検査の信頼性がときどき話題になることがある(もっと真面目に議論されてもいいと思うが)。偽陽性/偽陰性などについて、一度Bayes理論などによって自分でトレースしてみようとしたときに、有病率と罹患率の違いがあることがわかってきたので、一度まとめてみる。

有病率

有病率を簡単に言うと、ある時点において、人口に対して疾病にかかっている人の割合、ということになる。Prevalence rateまたは単にPrevelanceと呼ばれることも多い。

より正確には、有病率rPは以下のように定義される。

(1)    \begin{equation*} r_P = \frac{N_C}{N_R}} \end{equation*}

ここでNCはある時点における疾病者数(C: Contract)、NRは当該疾病のリスクを有する母集団の人数(R: Risk)。

とはいっても、当該疾病リスクを有する人を除外することはなかなか難しいので、対象とするエリアの人口を近似値として用いるのが一般的らしい。

また、ある時期と言っても疾病者数や人口の調査にはそれなりの時間がかかるため、ある期間に調査した結果を「いついつ時点」としたその時点で扱われるだろう。

有病率は0~1の実数だが、通常は対人口千人あたりとか10万人あたりで示されることが多いとのことである。

ここでPrevalenceという言葉は、流行するとか行き渡るといった意味であり、ある疾病がある時点でどれだけ広がっているか、というイメージになるだろう。

罹患率/発生率

罹患率を簡単に言うと、ある期間において対象とする疾病リスクを有する者が罹患する割合、ということになる。有病率はIncidence rateまたは単にIncidenceと言われることが多い。本来の意味で言うと「発生率」の方がより妥当で、実際にその用語も使われている。ちなみにincidentは出来事、incidenceは(望ましくない事象の)発生率・頻度という意味。

より正確には、罹患率rIは以下のように定義される。

(2)    \begin{equation*} r_I = \frac{N_I}{N_R}} \end{equation*}

ここでNIはある期間内に新たに罹患した人の数(I: Incidence)、NRは観察期間内に当該疾病のリスクを有する集団の延べ人数(R: Risk)。

分子の方は新たに罹患した人の数なので、観察期間より前から罹患している人は含まれない。

有病率と罹患率(発生率)

有病率はある時点tにおける罹患者の数を表し、罹患率はある期間Δtにおける新たな罹患者数を表す。いわば有病率は罹患者の関数値、罹患率は罹患者数の変化率のうち発生による増加率を表すことになる(時間で除していないので罹患が広がるスピードの概念は含まれていない)。

また、罹患率以外に疾病が治癒する人、疾病により死亡する人、観察エリア外に出て行ってしまう人はいずれも負の変化率に相当するが、これらを含めた(あるいはこれらを総括した「罹患減少率」のような)パラメーターはあまり登場しない。

したがって、この2つのデータだけを見ていると、罹患者数が増大しているのに有病率が変化しなかったり減少する場合がある。

なお、有病率にしても罹患率にしても一定の期間で捕捉しなければならないが、一つ違う点は、対象となる期間より前から罹患していた人は罹患率の場合分母・分子の両方から除かれるという点である。一般に中止するエリアの人口に比べてそのような人の数は少ないので、近似的には既罹患者の分だけ率が小さくなる。

(3)    \begin{align*} \lim_{N \rightarrow \infty} \frac{N_C - N_{Cprev}}{N - N_{Cprev}} = \lim_{N \rightarrow \infty} \frac{\dfrac{N_C - N_{Cprev}}{N}}{1 - \dfrac{N_{Cprev}}{N}} = \frac{N_C - N_{Cprev}}{N} \end{align*}

どういうときにどちらを使うか

以上の点を踏まえると、有病率と罹患率はそれぞれ次のような場合に有用と思われる。

有病率

その時点時点での、疾病への対策を検討する場合。たとえば、

  • 疾病に対する薬品を準備するのにどの程度の総量が必要か検討する場合
  • 地域や国などの対策重点度を判断する場合

罹患率

疾病の発生状況を重視する場合。たとえば、

  • 感染症などの拡大期に、その危険性を判断する場合の一つの指標として
    • ここで「一つの指標」としたのは、その他に有病期間、致死率など「単に罹っただけ」で騒ぐのではない判断要素も必要と考えたため。
  • 疾病に対する予防法の効果を検証する場合

PCR検査?

2020年9月時点、COVID-19(新型コロナウイルス)の発症は続いており、PCR検査に関する議論も喧しい。個別の陽性反応に対してどのように対処するかという議論と別に、そもそもPCR検査の偽陽性/偽陰性、陽性/陰性的中率を議論するときは、有病率と罹患率のどちらを使うのが適切だろうか。

まず罹患率の方を考えてみる。

各都道府県でPCR検査が毎日行われているが、陽性率とのセットで考えるなら、PCR検査の結果もリアルタイムに近い形で(少なくともそういう思想で)発生者の全容を捉えるべきだろう。ところがその数は日単位で悉皆の調査というレベルには程遠い(日本に限らずこれはどこの国もそうだ)。一か月程度で各都道府県の全人口の検査ができるなら月単位での罹患者増加状況は捉えられるが、それでも実現性には難があり、スピードが遅すぎる。まして一週間単位で(しかも毎週?)そのようなことをしようとするのも無理だ。したがって、PCR検査に対して罹患率の考えを適用するのは難しいだろう。考えてみれば、罹患率の場合は既発生者が除かれるのだから、ヒアリングか何かで期間前に罹患していた人を除外しなければ分母と分子で意味が違ってしまう。

となると、PCR検査でいろいろとデータをいじるのは、有病率を使って、ということになる。

「期間前の既発生者も除かない」ということと、検査で陽性/陰性になった人の状況からその時点での全容を推測する、という点から見れば、Bayes理論で条件付確率を計算するときなどに用いるのは有病率だろう。

まとめ

罹患率と有病率の区別は、医学・疫学の世界ではあたりまえとされていることが、あまり伝わっていない例の一つだ。違いを説明しようとするとやっかいだが、そういうことを明快に伝えて、受け取る側も腹に落とすことができれば一番いいのだが。

これはIT業界、建設業界でも同じで、説明に関する意識の問題(その前段の必要性を感じるかどうかの問題)であるとともに、門外の専門家や一般の多くの人の側が理解しようとする文化も大切かもしれない。技術・政策や危機管理に関するコミュニケーションの問題(発する側、受ける側のどちらも)に通ずると思う。

 

scikit-learn – predict_proba

概要

decision_function()は各データが推測したクラスに属する確信度(confidence)を表すが、超平面のパラメータに依存し、そのレンジや値の大きさと確信度の関係が明確ではない。

これに対してpredict_probaは、それぞれのターゲットが予測されたクラスに属する確率を0~1の実数で表す。2クラス分類では、結果の配列の形状は(n_sumples, 2)となる。

predict_proba()の挙動

以下はmake_circles()で生成した2クラスのデータをGradient Boostingによって分類したときの確信度。各データに対応した2要素の配列の1つ目がクラス0(blue)、2つ目がクラス1(orange)に属する確率を表し、2つの和は1となる。なお16行目でsuppress=Trueとすることで、ndarrayの表示を常に固定小数点としている。

decision_function()との比較

先のコードに以下を続けて、predict_proba()による確率、予測されたクラス、decsion_function()の値と、各データの正解クラスを並べて表示する。予測されたクラスの方の確率が大きいこと、その予測結果とdecision_function()の符号が一致していることが確認できる。

このデータをクラス0(blue)に対する確率(prob0)でソートし、decision_function()との関係を見てみると、以下のことがわかる。

  • blueクラスの確率が高いとdecision_functionの確信度はマイナスで絶対値が大きくなり、orangeクラスの確率が高いと確信度はプラスで絶対値が大きくなる
  • blueクラスの確率とorangeクラスの確率が同程度の時、確信度の絶対値が同程度になり、符号が逆になる
  • 確率に対して確信度は線形ではない

クラス0(blue)に対する確率とdecision_function()の確信度の関係を図示すると以下のようになり、確率に対して確信度が必ずしも線形になっていないことがわかる。

コードはmatplotlib.pyplotをインポートした上で、以下を追加。

決定境界

以下は、predict_proba()で計算された確率を可視化したもので、decision_function()の場合に比べて、直感的にも分かりやすい分布となっている。

コンターに表す値として、30行目でpredict_proba()の結果の0列目、すなわちClass0の確率を取り出している。

3クラス以上の場合

3クラスのirisデータセットにGradientBoostingClassifierを適用し、predict_proba()の出力を見てみる。

このコードの出力結果は以下の通り。3つのクラスに対する確率が得られ、合計は1になる。こちらはdecision_function()が2クラスの時だけ配列が1次元となるのと違って、どのような場合でも行数×列数=データ数×クラス数の配列になる。

なお17行目で、argmaxを使って各データで確率が最大となるクラスを探している。