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近傍法

概要

“Pythonではじめる機械学習”の主成分分析(PCA)のところで比較として述べられている内容。

著名人の顔画像データ(LFW peopleデータセット)を学習して、画像がどの人物のものかを判定するため、k近傍法を適用している。

結果は0.2程度でそれほどよくない。

コードと実行結果

 

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次元配列で、インデックスが画像枚数、要素の値はそのインデックスの枚数の画像データがある人の数。

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データセットで取りそろえられた特徴量は、住居の価格以外の何かを特徴づける傾向が強いのかもしれない。

 

PCA – Breast cancerデータセット

概要

scikit-learnの主成分分析モデル(PCA)をBreast cancerデータセットに適用して、その挙動を確認する。

30個の特徴量(全て連続量)を持つ569個の腫瘍データについて、悪性(marignant)/良性(benign)がターゲットとして与えられている。PCAによって特徴量のみの分析で、少ない主成分によってある程度明確な分離が可能なことが示される。

手順

以下の手順・コードで計算した。

  1. パッケージをインポート
  2. Breast cancerデータセットを準備
  3. データセットをスケーリング
    • StandardScalerで特徴量データを標準化している
  4. PCAモデルのインスタンスを生成
    • 引数n_components=3で3つの主成分まで計算させている
  5. fit()メソッドによって、モデルにデータを学習させる
  6. 成分やその寄与率を確認
    • 主成分はPCA.comonents_を、寄与率はPCA.explained_variance_ratio_を確認
  7. transform()メソッドによって、主成分に沿ってデータを変換
  8. 3つの主成分について3次元可視化
  9. 2つの主成分について2次元可視化

主成分と寄与率

以下に主成分と寄与率を計算するまでのコードを示す。

寄与率は第1主成分が44%、第2主成分が19%、第3主成分が9%。第3成分まで3/4の情報を説明していることになる。

また、第1主成分は全ての特徴量がプラス方向で寄与している。

主成分をヒートマップで視覚化してみると、各主成分の符号や大きさが直感的に把握しやすくなるが、第2~第3主成分がmeanとworst系の特徴量が小さい方が影響が大きい点、3つの主成分についてerrorが大きいほど影響が大きい点など、意味づけは難しい。

可視化

3次元

3つの主成分について3次元で可視化してみると、悪性/良性がかなりはっきりと分離されている。

2次元

2つの主成分のみでも、悪性/良性がよく区分されている。

まとめ

Irisデータの場合と同じく、特徴量分析のみでクラスの別がよくあぶりだされている。