Ruby – 配列メソッド

概要

配列オブジェクトのメソッドのには破壊系と非破壊系があり、注意を要する。

パラメーター系

size/length~要素数の取得

sizeメソッド、lengthメソッドとも配列の要素数を返すエイリアス。

sum~合計値の取得

sumメソッドは配列要素の合計値を返す。

要素抽出系

first/last~先頭要素/末尾要素の取得

firstメソッドは配列の先頭の要素、lastメソッドは末尾の要素を返す。

sample~ランダムな要素取得

sampleメソッドは配列からランダムに1つ要素を返す。引数を指定すると重複なしでその個数分のサンプル配列を返すが、引数が要素数を超えた場合は全要素がランダムに並べられた配列が返される。

破壊的メソッド

push/unshift~要素の追加

pushメソッドは配列の末尾に要素を追加し、unshiftメソッドは配列の先頭に要素を追加する。いずれも元の配列を変更する(要素の追加は<<演算子でもできる)。

pop/shift~要素の取出し

popメソッドは配列の末尾から要素を取り出し、shiftメソッドは配列の先頭から要素を取り出す。いずれも元の配列が変更され、取り出された要素が戻り値となる。

非破壊的メソッド

reverse~要素の順番の反転

reverseメソッドは、元の配列の要素の順番を反転した配列を新たに生成して返す。

sort~昇順ソート

sortメソッドは、元の配列を昇順でソートした配列を生成して返す。文字列オブジェクトの場合は辞書順で、大文字→小文字の順。

sort.reverse~降順ソート

sortメソッドとreverseメソッドの組み合わせで、降順にソートされた配列が生成されて返される。

uniq~重複要素の削除

uniqは配列中の重複した要素を削除して1つにし、重複のない配列とする。元の配列は変更されず、新たな配列が生成される。

shuffle~要素のシャッフル

shuffleメソッドは、元の配列の要素をランダムに並べ替えた配列を生成して返す。

文字列化・配列化

split~文字列の配列への分解

splitメソッドは、指定した文字列で元の文字列を区切って、それぞれが要素となる配列を生成する。

join~配列要素の文字列への結合

joinメソッドは、配列の各要素を指定した文字列でつないだ文字列を生成する。

 

Ruby – 文字列メソッド

length~文字列の長さ

半角も全角も1文字。

結合系

+~文字列の連結

+演算子は文字列同士を結合する。半角と全角の連結もok。

join~配列要素の文字列化

配列のメソッドだが、joinメソッドは配列要素を指定した文字列で結合して文字列にする。

分割系

split~文字列の配列への分割

引数で指定した文字列で元の文字列を分解して配列化。

2つ目の例から、引数で指定した文字列"--"が見つかるたびに、そこまでの文字列を要素として配列に加えていることがわかる。

削除系

delete~特定の文字の削除

引数で指定した文字列を削除。

strip~前後のスペースの削除

半角スペースを削除。全角スペースは削除されない

chop/chop!~末尾文字の削除

chop/chop!は末尾文字を削除する。

chomp/chomp!~末尾の改行文字の削除

chomp/chomp!は末尾文字を削除する。

変換系

upcase/downcase~大文字化/小文字化

アルファベットの大文字化/小文字化。

 

PCA – LFWデータセット

概要

Scikit-learnで提供されているLFW peopleデータセットを、主成分分析を使って分析する。

データの読み込みと確認

LFWデータセットは世界の著名人の顔画像を、その名前とそれに対応するクラスデータとともに格納したものである。

書籍”Pythonではじめる機械学習”に沿って、画像サイズを0.7にし、20枚以上の画像がある人物を抽出する。

画像の人物は書籍と同じだが顔画像は異なっている。書籍執筆後画像データが追加/変更されたものと思われる。

画像の枚数の絞り込み

元のコード

LFW peopleの画像データは、人物によって枚数にばらつきがある(特にGeorge Bushだけ500枚を超えている)。画像データの多寡によるばらつきを抑えるため、書籍では画像の数を50枚までとし、それ以上の画像は切り落としている。

このコードがちょっとわかり難かったので、別にこちらで整理している

k-近傍法との組み合わせによる精度の確認

書籍では、画像を50枚以下に制限したデータについて、k-近傍法(knn)を適用したときのスコア、元データを主成分分析によって変換した場合のknnのスコアを確認している。

その過程をトレースしてみた

  • 画像データを最近傍データ1つで判定する1-nnの実行結果は、スコアは0.23と低い
  • 元の画像データを100個の主成分で変換したデータに対しては、1-nnのスコアは0.31と若干向上
  • PCAインスタンス生成時にwhiten=Trueを指定しない場合、PCA変換後もスコアは向上しなかった

主成分の可視化

PCA.fit()を実行すると、PCA.components_に主成分が格納される。components_は2次元配列で、[主成分の数, 元の特徴量数]という形になっている。たとえば今回のデータの場合、主成分の数はn_componentsで指定した100、特徴量の数は画像のピクセル数87×65=5655となり、components_は100×5655の2次元配列になっている。

(1)    \begin{equation*} \tt{components_} = \left[ \begin{array}{ccc} (p_{0, 0} & \cdots & p_{0, 5654} ) \\ & \vdots &\\ (p_{99, 0} & \cdots & p_{99, 5654}) \end{array} \right] = \left[ \begin{array}{c} \boldsymbol{p}_0 \\ \vdots \\ \boldsymbol{p}_{99} \end{array} \right] \end{equation*}

components_に収められた主成分はそれぞれが画像データと同じサイズの配列なので、これらを画像として表示させてみる。

たとえばComponent-0は顔と背景のコントラスト、Component-2は顔の左右の明るさの差をコーディングしているように見える、と書籍では解説している。その他にも、Component-5は目の下の出っ張った部分、Component-11は鼻筋のあたりを表現しているかもしれないといった想像はできる。

上の画像は以下のコードで表示させたが、要点は以下の通り。

  • 最低20枚の画像を持つ人物のみ読み込んでいる
  • 画像の最大数を50枚以下に制限している
  • 訓練データとテストデータに分割し、訓練データを主成分分析にかけている
  • components_プロパティーの主成分配列のうち、15行分を取り出して表示させている
  • 表示にあたって、リニアな5655の要素を画像の形(87, 65)に変形している
  • components_の形状が、100行×5655の2次元配列であることを確認

次元圧縮された主成分からの復元

概要

主成分の意味の一つとして、元のデータは主成分の線形和で表せるという解釈がある。

(2)    \begin{equation*} \boldsymbol{x} = (x_0, ..., x_n) = a_0 \boldsymbol{p}_0 + a_1 \boldsymbol{p}_1 + a_2 \boldsymbol{p}_2 + \cdots \end{equation*}

LFWの顔画像データで考えると、components_に収められた主成分の重みによって、元のそれぞれの人物の画像を再現しようとすることになる。

そこで、限られた主成分だけを用いて元の顔画像を再現してみる。

顔画像の選定

まず、特に有名な人物の顔画像をいくつか表示させてみた。選んだ人物は、Arnold Schwarzenegger, Tiger Woods, Vladimir Putinの3人。

これらの画像から、一旦次元削減して復元する画像を選ぶ。Shwalzzeneggerは正面少し左向きの31番、Tiger Woodsは少し右側から撮った歯を出している683番、Putinは左を向いた顔をほぼ正面から撮った372番を選んだ。

次元削減後の逆変換

そして次元数を変化させながらPCAモデルに全データを学習させ、それらのモデルで3枚の画像を変形し、逆変換する。

10個の主成分では、3人とも似たような顔になっているが、30個になると顔の方向や葉を出しているかどうかといった特徴が表れ始めている。

70個から100個にかけて、ShwaltzeneggerとWoodsはかなり元の顔に近いが、Putinはあまり判然としない。前者2人が「濃い」顔立ちなのに比べると、Putinの顔立ちは平板だということだろうか。

この画像は、以下の手順で作成した。

  1. 20枚以上の画像を持つ人物を選び、画像の枚数を50枚以下に制限
  2. 3人の顔画像について、次元数を10、30、70、100と変化させて以下を実行
    1. 設定された次元数で全データを学習
    2. 学習済みモデルで各顔画像を変換(ここで次元が削減される)
    3. 設定された次元数で元の顔画像に逆変換

同一人物の画像

さらに、3人について1人ずつ、3枚の顔画像について同様のことを行った結果が以下の通り。

Shwalzeneggerの後半2枚は向きが逆だが口元などがよく似ていて、目元と口元の特徴が強調されている。1枚目の画像はこの2枚と特徴が違うが、主成分30個あたりではよく似た感じともいえる。

Tiger Woodsも、主成分30個のところで173と683の画像が似ている。だが、535については一貫して他の2つと異なっているように見える。個人の特徴よりも顔の表情に大きく引きずられているようだ。

Putinは60と372の画像が割に似ているが、239の画像はかなり異なり、コントラストが強調されているようだ。60や372では、そもそも顔画像が平板なせいなのか、主成分を増やしても明確な画像が得られていない(他の人物との区別も難しいのではないだろうか)。

第2主成分までによるクラスの分布

第1主成分と第2主成分だけを使って、各クラスの分布をみてみる。62人の人物の各画像データが1つの点に対応している。2つの主成分だけでは人物が明確なクラスターとしては認識し難い(というよりもクラスが多すぎて識別も難しい)。

試しに表示するクラスを5つに限定してみる。やはり2つの主成分では明確なクラスターは確認できない。先ほどの変換・逆変換の結果でも、主成分10個でも個々の顔の識別は困難だったので、2つの主成分では難しいのは自明だが。

以上の可視化のコードは以下の通り。

 

Ruby – コメント

1行コメント~#

文頭に'#'があると、以降行末までコメントになる。

複数行コメント~=begin/=end

=begin=endの間の行は埋め込みドキュメントとして実行時には無視される。=begin=endはそれぞれ行頭になければならない。

__END__の応用

__END__はコードの終端を示し、それ以降の記述は実行対象にならない。

 

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つずつ並ぶ配列が得られる。