Ruby – コンソール表示

puts

putsはオブジェクトの内容を表示する。文の実行ごとに改行する。

print

printはオブジェクトの内容を表示する。改行はしない。

p

pはオブジェクトの形式がわかるように表示する。文の実行ごとに改行する。

 

Python – 配列要素の重複数を制限する

概要

文字で表現するとわかり難いが、要するに次のようなことを想定している。たとえば次のような1次元の配列があるとする。

この配列には20個の要素があり、0~4の数値がそれぞれ5個、5個、2個、2個、4個、4個、順不同で含まれている。

この配列において、各数値の数を最大でも3個以内となるように切り落としたい、というのが目標。

たとえば、機械学習の教師データの数がターゲットごとにばらついている場合、各ターゲットのデータ数をある程度の数以下に抑えたいときが想定される。

上の例で仮に早く出現した準から3つまでを残して後は捨てるとすれば、以下のような配列になる。

内容:4 0 3 3 3 1 3 2 4 0 0 4 2 1 0 1 1 0 1 4
個数:1 1 1 2 3 1 4 1 2 2 3 3 2 2 4 3 4 5 5 4

なお、単に1つの配列の要素を切り落とすだけでなく、これと対応する配列が別にあって、その要素についても同時に切り落とすことも想定する。これは、機械学習のターゲット配列でデータを制限するのに、これに紐づけられた画像データなどを格納した配列も同時に操作するイメージ。

手順

ターゲットごとのインデックスの取得

targetsの20個のデータのうちid=0について考える。targetsの要素のうち値が0のものは5個あり、それらのインデックスは(1, 9, 10, 14, 17)。同様にid=1についても5個あり、インデックスは(5, 13, 15, 16, 18)。

このようにしてid=0~4についてインデックスを書き出すと以下の通りになる。

0:1, 9, 10, 14, 17
1:5, 13, 15, 16, 18
2:7, 12
3:2, 3, 4, 6
4:0, 8, 11, 19

各idに対応する配列はnumpy.where()関数を用いて以下のように得られる。

上の例では、ループのidを0~4と変化させていくのにrange(5)を使っている。ところが一般的には、番号が連続して存在しているとは限らず、またその上限もわからない。

そこで、targetsに出てくる要素を重なりなく、かつ全て使うためにnumpy.unique()関数を使っている。unique()関数は引数の配列の要素の重複を除き、昇順・辞書順に並べてくれる。この引数にtargetsを渡して、要素の重なりを除けば、targets中の要素を重なりなく1つずつ参照できる。

取り出す要素の制限

次に、すべてのターゲットのデータ数が3個以下になるようにすることを考える。

これらのデータで各idの個数を3個以下にするのに、出現順位の早いものから3個を選び出すことを考える。

0:1, 9, 10, 14, 17
1:5, 13, 15, 16, 18
2:7, 12
3:2, 3, 4, 6
4:0, 8, 11, 19

各配列の最初の3個を取り出すには、各idに対応する配列の先頭から3個目までをスライスで取り出せばよい。

これで値の最大3個までとするのに取り出すべきtargets中のインデックスが得られた。

要素の抽出

targets配列の要素の個数を制限するには、上で絞り込まれたインデックスに対応する要素を残し、それ以外の要素を切り捨てる。そのためには、残すべきインデックス位置の値がTrue、その他のインデックス位置の値がFalseであるbool配列をつくり、これをtargetsの引数とすればよい。

この配列を例えばmaskという名前とすると、targetsと同じサイズですべての要素がFalseである配列としてmaskを準備し、先ほどの切り落とすべきインデックスの位置のみTrueにするとよい。

以下では、まず全要素がFalsetargetsと同じサイズのbool配列を準備し、各idに対して3つ目までの要素の位置をTrue(1)としている。

ループの1回目で1、9、10番目がTrueになり、2回目で5、13、15番目がTrueに代わっていき、ループを重ねるごとに、取り出すべき要素の位置がTrueになっていることが確認できる。

なおbool配列の初期化では、Falseが数値の0と等価なため、numpy.zeros()関数を使っている。同じ理由で、numpy.where()Trueをセットするときに、数値の1をセットしている。

最後に、このbool配列をtargetsに適用して、取り出すべき要素の配列を得る。

他の配列の同時操作

mask配列は、targetsと同じサイズを持つ次元の配列に繰り返し適用できるので、たとえば機械学習でtargetsの各要素に紐づけられた画像データなどを格納した配列などについても、targetsと整合させながら必要な分だけ切出すことができる。

PCA – 次元削減と逆変換について

概要

主成分分析(PCA)において、次元削減により主成分の一部だけを残し、それを逆変換することを考える。

結論から言うと、以下の2つの操作は同じ結果をもたらす。

  • 全ての主成分を用いて変換し、削減する主成分に対応する元の特徴量を0とし、逆変換する
  • 削減する主成分より低次の主成分のみで変換し、それを逆変換する

簡単な例

全主成分を使った手順

概要

最初の例は、2次元のデータについて以下のような操作を行っている。

  1. 2つの主成分まで使ってデータを変換
  2. 第2主成分に対応する変換後のデータを0にする
  3. そのデータを逆変換する

元データの作成

まず元データを作成し、左上に散布図を描画。

元データは水平線にcosine状に正規分布するノイズを乗せ、それを45度回転させている。

また、適当な位置に特定の点を1つ定義している。

フィッティングと元データの変換

次に元データをPCAによって変換し、変換後の散布図を右上に描画。

斜めだった分布が、第1主成分がx軸と重るように変換されて水平になる。

第2主成分のデータの削除

変換後のデータにおいて、第2主成分に関する値を0とし、左下に散布図を描画。

第2成分に相当する垂直成分が0になる。

逆変換

第2主成分を0としたデータを逆変換して描画。

第1主成分に直角な第2主成分が0となり、全点が一直線上に並ぶ。

最初から主成分を限定する手順

概要

次の例は元の特徴量を操作せず、以下のような手順に寄っている。

  1. PCAのモデル生成時に、n_component=1とする
  2. そのPCAモデルで元データを変換する
  3. 変換したデータを逆変換する

まとめ

途中で表示させているXpのデータが、2つの手順で全く同じであることがわかる。

最初から主成分を限定することで、元の特徴量を意識せずに次元削減ができる。

 

LFWデータセット – k近傍法(PCA変換付き)

概要

“Pythonではじめる機械学習”の主成分分析(PCA)のところで、著名人の顔画像データ(LFW peopleデータセット)に対するk-近傍法の精度を確認している。

  • LFW peopleのデータを、最低20枚以上の画像がある人物で絞り込んで読み込み
  • 各人物の画像を最大でも50枚以内となるよう制限(配列要素数の制限手順についてはこちらを参照
  • 2063人の人物について、87×65ピクセルを1次元化した5,655個の数値配列を特徴量データ(X_people)、各画像の人物の番号を収めた配列(y_people)をターゲットデータとする
  • 画像データを訓練データとテストデータに分割
  • このデータセットをそのまま1-nnで予測したスコアは、0.2程度でそれほどよくない
  • 画像データに主成分分析(PCA)を用いて、100個の主成分で変換したデータについても1-nnを適用していて、この場合のスコアは0.31

コード例ではPCAインスタンス生成時の引数としてwhiten=Trueを指定しているが、これを指定しない場合、データ変換後のスコアは0.23でこうじょうしなかった。

なお、スコアが書籍掲載値と異なるが、画像データの内容も書籍と今回実行時で異なっている。

コードと実行結果

 

LFW peopleデータセット

概要

Scikit-learnから入手できるLFW peopleデータセットは、世界の著名人の顔画像データを集めたものである。

1人につき1枚~最大530枚の画像データが、それぞれの人に対して紐づけされている。

LFWは”Labeled Faces in the Wild”の略で、”in the Wild”には「出回っている」というニュアンスがあるらしい。

IrisBostonなどのデータと異なり、Scikit-learnをインストールした状態ではデータはローカルに格納されず、最初の読み込み時にデータがダウンロードされてローカルに格納される。1度ダウンロードされた後は、ローカルのデータが使われる。

【注意】

  • fetch_lfw_people()resize引数を変更すると、そのたびにデータのダウンロードが実行されるようなので、実行ごとの時間を節約したい場合はresizeの値を決めておくとよい

データの取得

データの読み込みは以下の手順による。

  • sklearn.datasets.fetch_lfw_peopleをインポートする
  • fetch_lfw_poeple()関数でBunchオブジェクトのデータセットを読み込む
    • fetch_lfw_peple()関数を最初に実行したときに、ローカルにデータが読み込まれる(これには数分程度かかる)
    • 一度読み込まれた後は、ローカル上のデータが使われる
    • ローカル上のデータの場所は、ログインしたユーザーのホームディレクトリー下、schikit_learn_dataディレクトリー

データ構造

データセットはBunchオブジェクトで、辞書型のkeyvalueで内容を取得できる。

内容は以下の通りで、

  • dataimagesは顔画像データを異なる形状の配列で格納したもの
  • targetは各顔画像の人物id
  • target_namesは各idに対する人物の名前
  • DESCRはデータに関する説明。

データの内容

target_names~ターゲットの人物

ターゲットとなる人の名前はtarget_namesに格納されていて、その数は20201122時点で5,749人分のユニークなデータ。

名前に対するインデックスがターゲットのidになる。

target~ターゲット数

ターゲットのidはtargetに1次元配列で格納されている。

1人のターゲットに複数枚の異なる顔画像が格納されているものもあり、targetデータに格納されたターゲットデータ全体は13,233個。

これらのidがターゲットとなる人の名前と顔画像データに結びついている。

images~顔画像のピクセルデータ

imagesには各顔画像のデータが1次元のピクセル値として格納されている。

配列のインデックスとtargetのインデックスが紐づいていて、targetの要素から顔画像の人物が特定できる。

このデータの構造は以下のとおりで、13,233個の画像データが62×47のグレイスケールの配列として保存されている。

3次元データの構造は以下の通り。

data~1次元の顔画像データ

dataには顔画像のピクセルデータが各画像ごとに1次元で格納されている。

imagesと同じく、各画像データと人物が紐づけられる。

このデータの構造は以下の通りで、13,233行のデータがあり、各行が2次元の配列を1次元にフラット化した形で格納されている(62×47=2914)。

2次元のデータ構造は以下の通り。

データの概要

顔画像データの確認

顔画像データの内容を確認してみる。ここでは、書籍”Pythonではじめる機械学習”の例に沿って、最低20枚以上の画像がある人物から最初の10人分を取り出して表示している。

人物の並びは原著どおりだが、それぞれの顔画像が異なっている。著書執筆後にデータが追加/更新されたようだ。

データの俯瞰

全体の画像データを、1人あたりの枚数ごとに集計してみる。

多くの人について顔画像が1つだけで、George Bush元大統領の顔画像が飛びぬけて多いようだ。

画像枚数ごとに人数を整理してみる。

そこで、顔画像の個数ごとに見た時の人数を確認してみる。

上の配列は0~530の531個の要素の1次元配列で、インデックスが画像枚数、要素の値はそのインデックスの枚数の画像データがある人の数。

顔画像が1枚の人数が4千人以上と、ほとんどの人物については顔画像が1枚しかない。そして画像枚数2枚以降の人物の数が減っていっている。

読み込みパラメーター

resize~画像のサイズ変更(再読み込みされる)

fetch_lfw_people()resize引数で、画像データのサイズを指定できる。

デフォルトは0.5でこの時のサイズは62×47、書籍”Pythonではじめる機械学習”ではresize=0.7を指定していて、この時のサイズは87×65になる。

自分のマシンでは、resize=1.0とするとメモリーの制約なのかエラーになった。

min_faces_per_person~1人あたりの最低画像数

分析の目的によって、1人あたりの画像が複数必要な場合に、最低限登録されている画像数を指定する。

ここで指定した数以上の画像が登録されている人物とその画像データのみ抽出される。

 

numpy.where – インデックスの検索

概要

numpy.where()関数の主な使い方は以下の通り。

  • 配列の要素のうち条件に合う要素のインデックスを取り出す
  • 配列の要素の条件によって、2つの配列のいずれかの要素を割り当てる

基本的な挙動

条件に応じた値の取り出し

以下のように、3項演算子と同じように使える。

True/Falseの代わりに数値でも可。

bool配列によるインデックスの取り出し

bool配列を引数に渡すと、True要素のインデックスの配列を返す。True/Falseの代わりに数値でも可。

bool配列と同じ形の2つの配列を引数に加えると、bool配列の要素のTrue/Falseに応じて、1つ目の配列/2つ目の配列の要素が取り出されて並べられた配列が返される。

上の例では、True(1)が0番目と3番目、False(0)が1番目と2番目にあるので、戻り値の配列の0番目と3番目には2つ目の配列の対応する要素、1番目と2番目には3つ目の配列の対応する要素があてられている。

利用法~条件に合う要素のインデックス

条件に合う要素が1つの場合

where()関数の引数として、配列の要素に関する条件式を与えると、条件に合致する要素のインデックスが得ることができる。

ただし戻り値はタプルで、かつ2次元のタプルの第1要素にndarrayとして納められている点に注意。

そのndarrayは1つの要素を持ち、その値が"FRA"のインデックスになっている。

インデックスの数値を取り出したいときは、このndarrayの要素を取り出す。

条件に合う要素が複数の場合

先の配列には"JPN"が3つ含まれている。このように条件に合致する要素が複数ある場合は、インデックスが配列で返される。

ただしこの場合も戻り値は2次元のタプルで、その第1要素に目的の配列が格納されている。

インデックスの配列を利用する場合は、タプルの先頭要素を取り出す。

条件に応じた配列の要素の選択

配列に対する条件式と、その条件の真偽に応じて選択される配列を引数に与える。文章にするとややこしいので、以下例示。

xの各要素が基数の場合はxから、偶数の場合はyから、同じ位置にある要素が取り出されて結果の配列にセットされる。

以下はもう一つの例。

 

この例では条件、真の場合、偽の場合に同じ配列を使っている。条件の配列の要素が偶数の場合は、その要素の1/2、奇数の場合はその要素から1を引いて1/2にした数値を持つ配列が返される。その結果、同じ数が2つずつ並ぶ配列が得られる。

 

numpy – bincount

概要

numpy.bincount()関数の仕様

  • 整数型の配列を引数にとる
  • 配列中、同じ値の要素の個数をカウントする
  • 0~要素の最大値を要素とし、各要素番号に対応する値の個数を要素とする配列を返す
  • 元のデータの要素ごとの重みを指定することができる

使い方

基本形

引数で与えた整数型配列中の同じ値をカウントして、各値ごとの個数を要素とする配列を返す。

上の結果の意味は、0が1個、1が2個、2が3個、3が2個。

値が飛んでいる場合

引数の配列中、0~最大値までの整数値に対する数をカウントする。値が存在しない場合の個数は0。

上の例では、0~5までの個数がカウントされ、0, 2, 4は配列中に存在しないので0となっている。

順不同

引数の配列中の要素は昇順である必要はない。

weightsの意味

引数にweightsを指定する場合。

  • データの配列と同じ要素数のweightsの配列を与える。
  • 要素をカウントの場合に1ずつ足すのではなく、各要素の位置に対応した重みが加算されていく

上の例では以下のように動作している。

  • 0は存在しないので0
  • 1は0番目に1つだけ存在し、その位置のweightsの値は0.1
  • 2は1番目と3番目に存在するので、weightsの第1要素0.2と第3要素0.4を加えて0.6
  • 3は存在しないので0
  • 4は2番目、4番目、5番目にあるので、weightsの第2要素0.3、第4要素0.5、第5要素0.6を加えて1.4

 

pyplot.imshow – 画像表示

概要

matplotlib.pyplot.imshow()は画像表示用のメソッドで、表示対象として、画像ファイルや画像情報を格納した配列を指定する。

pyplotやsubplotで直接実行するほか、Axesオブジェクトのメソッドとしても実行できる。

ピクセルデータのレンジのデフォルト設定と与えるデータのレンジによって予期しない結果になることもあり、vminvmaxを明示的に指定した方がよい。

画像ファイルの表示

以下のコードは、JPEGファイルを読み込んで表示する。

ここではpyplot.subplotのメソッドとしてimshow()を実行している。画像が1つの場合、pyplot.imshow()でもよい。

1つは画像ファイルをそのまま引数にし、もう1つは画像ファイルを配列の形にしてから引数に渡している。画像の配列の形については後述。

配列の画像表示

基本形

imshow()は配列を引数にとることができる。

以下の例では、カラーマップを指定して2×2=4要素の2次元配列を表示している。最小値0がカラーマップbwrの青に、最大値255が赤に対応し、その間の数値の大きさに応じたカラーマップ上の色が選択されている(デフォルトのcmapvirいdis)。

なお、この例ではpyplotから直接imshow()を実行している。

レンジ

imshow()に配列を渡して描画させるとき、数値のレンジに留意する必要がある。

デフォルトでは、imshow()は渡された配列の中の最小値と最大値をカラーマップの下限値と上限値に対応させ、線形にマッピングする。

なお、この例ではarray-likeとして2次元のリストを渡していて、Axesからimshow()を呼び出している。

4つの配列はそれぞれ最小値と最大値が異なり、かつその中央の値を持つ。値は異なるが全て最小値がカラーマップbwrの下限値に対応する青、最大値が上限値に対応する赤、中央値は白となっている(特段フランス国旗を意図したものではない)。

viminとvmax

imshow()の引数でvminvmaxを設定すると、配列の値に関わらず、vminvmiaxをカラーマップの下限値と上限値に対応させる。

以下の例では最小値0、最大値1の2要素の配列を、vminvmaxを変えてカラーマップbwrで描画させている。

左上はデフォルトなので、最小値0がカラーマップ下限値に対応した青に、最大値1が上限値に対応した赤になっている。

右上はvmin=0で配列の最小値0と同じだが、vmax=2としている。このため配列の0はカラーマップ下限の青で、配列の1はカラーマップ中央の白になっている。

左下はvmin=-1も設定されているので、配列の0、1はカラーマップの左から1/3、2/3に相当する色となっている。

右下はvminvmaxが配列の最小値と最大値の範囲より内側にある。このため、配列の最小値・最大値はそれぞれカラーマップの下限・上限に対応する青・赤となっている。

RGB

array-likeの次元が3次元になると、RGB/RGBA形式だと認識される。

[rows, cols, 3]
3次元目のサイズが3の時はRGB表現と認識される。1次元目と2次元目はそれぞれ画像の行数と列数とみなされ、3次元目は3つの列がR, G, Bの値に対応する。
[rows, cols, 4]
3次元目のサイズが4の時はRGBA表現と認識される。1次元目と2次元目はそれぞれ画像の行数と列数とみなされ、3次元目は3つの列がR, G, Bの値に対応し、4つ目の列が透明度に対応する。

R, G, B, Aの値は、配列のdtypeint形式の時には0~255、floatの時には0~1の範囲が想定される。

以下の例の内容。

  • 画像サイズを2行×4列として、R, G, Bごとに画像のピクセルデータを設定→shape=(3, 2, 4)
  • ピクセル並び替え後の配列を4つ準備
  • forループでピクセル並び替え
  • 画像表示とデータ内容の表示

3次元配列のピクセルの並び替えは、泥臭くforループで回しているが、もっとエレガントな方法があるかもしれない(もとから(3, rows, cols)の形にしてくれればよかったのに)。

 

imshow()に渡す配列のdtypeint型の時は、ピクセルデータのレンジが0~255になる。

  • 左上は元の配列のままR, G,Bが0か255なので、想定した組み合わせの色となっている
  • 右上は想定されているレンジに対して0.0~1.0の値を与えていることから、どのピクセルともR, G, Bが0か1(ほぼゼロ)となり黒くなっている(そのまま実行され、特にメッセ維持は出ない)

配列のdtypeがfloatの時は、ピクセルデータの想定レンジは0.0~1.0になる。

  • 左下は最小値0と最大255を与えているが、結果は左上と同じで、imshow()のデフォルトのレンジ0~255に変更されているようである(特にメッセージは出ない)
  • 右下は与えるピクセルデータを0.0~1.0としたところ、”入力データをクリップしている”というメッセージが出たが、レンジが修正されたらしく結果は意図通り

並べ替えた後の配列は、直感的にはわかりにくい形になっている。

グレースケール

グレースケールの場合は、cmap='gray'を指定する。vminvmaxは省略しても同じ結果となるが念のため。

 

PCA – Boston house-pricesデータセット

概要

scikit-learnの主成分分析モデル(PCA)をBiston housing pricesデータに適用して、その挙動を確認する。

主成分が適切に発見されてよい相関が得られることを期待したが、IrisデータBreast cancerデータの場合のようなクラス分類データにおける良好な結果は得られなかった。

ただし、Boston housing pricesデータはIrisやcancerのデータよりも複雑な社会行動に関するものであり、その指標も限定されていることから、これをもってPCAが回帰系のデータに不向きとまでは言い切れない。

なお、Boston housing pricesデータの特徴量には属性データ(カテゴリーデータ、クラスデータ)が含まれることから、DataFrameget_dummis()メソッドによるone-hot encodingを行っている。

計算の手順

  1. 必要なパッケージをインポート
  2. Boston housing pricesデータセットを準備
  3. データセットをスケーリング/エンコーディング
    1. 属性データの列を取り出して、get_dummiesでone-hot化
    2. StandardScalerで残りの特徴量データを標準化
    3. 上記2つを結合して前処理済みデータとして準備
  4. PCAモデルのインスタンスを生成
    • 引数n_components=2として、2つの特徴量について計算
  5. fit()メソッドにより、モデルにデータを学習させる
  6. 主成分やその寄与率を確認
    • 主成分はPCA.comonents_を、寄与率はPCA.explained_variance_ratio_を確認
  7. transform()メソッドによって、主成分に沿ってデータを変換
  8. 3つの主成分について3次元可視化
  9. 2つの主成分について2次元可視化

前処理

特徴量のうちの1つCHASについては、「チャールズ川に関するダミー変数(1:川沿い、0:それ以外)」~”Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)“となっていて、0か1の属性変数である。この変数をDataFrameget_dummies()メソッドでone-hot化する。

また、その他のデータについてはStandardScalerで標準化する。

  1. CHASのデータのみone-hot化
  2. CHASの列を除いたデータをStandardScalerで標準化
  3. 上記2つのデータをjoin()で結合

可視化

2次元

ここではまず、2次元可視化の結果を確認する。

クラス分類の場合は2次元で2つの主成分を確認できるが、回帰データの場合はターゲットの量を確認する必要があるため、グラフの軸を1つ消費する。このため、2次元による表現では1つの主成分による説明性を確認することになる。

  • 各点の色や大きさをターゲットの値によって変化させ、2つの軸を2つの主成分に割り当てる方法も考えられるが、直感的にとらえにくくなる。

この結果を見る限り、あまり美しい結果とはなっていない。データを俯瞰した際、各特徴量であまりいい説明ができなかったが、その中でもある程度関係がみられたMDEVやLSTATとの相関と変わらないくらい。

3次元

そこで3次元の可視化にして、2つの主成分による説明性を確認する。

これでもあまりいい結果にならない。ただしグラフを見ると、大きく2つの塊に分かれているように見える。ターゲットである住居価格とは別に、特徴量の組み合わせに隠れている、性質の違うグループがあるのかもしれない。

主成分と寄与率

2つの主成分と寄与率について表示させてみる。

寄与率は第1主成分が50%程度で低いため高い相関が出ないといえるかもしれない。だが、Breast cancerデータセットのクラス分類では第1主成分の寄与率が40%台だが、明確なクラス分類ができていた。やはり回帰系の問題にはPCAは不向きなのかもしれない。

主成分の要素について、先の散布図が第1主成分と負の相関があることから、第1主成分の各特徴量は価格低下に寄与するものはプラス、価格上昇に寄与するものはマイナスとなるはずである。

たとえばZNRMがマイナスなのは頷けるが、DISがマイナスなのは微妙。TAXPRATIOがプラスなのも逆のような気がする。

先にも書いたが、Boston housing pricesデータセットで取りそろえられた特徴量は、住居の価格以外の何かを特徴づける傾向が強いのかもしれない。