Cifar10/Cifar100の画像データセットをC#で主成分分析

Visual Studio 2022で、WPF, Material Design In XAML Toolkit, MahApps.Metro, Prism, Accordを使ってCifar10/Cifar100の画像データセットを主成分分析するプログラムを作成しました。

主成分分析で得られた主成分ベクトルを各ベクトル要素の値が0から255となるRGB値に変換し、大きさ32 x 32 pixelのRGB画像として可視化しています。

Cifar10/Cifar100のtraining dataで計算した主成分ベクトルの線形結合でtraining dataとtest dataを表現するテストもしました。固有値が大きい順に並んだ主成分ベクトルをいくつ使用するかを選択し、選択した個数の主成分ベクトルの線形結合でtraining dataとtest dataの画像を表現しています。

1. ソースコードは下記のGitHubリポジトリに置きました。

https://github.com/fukagai-takuya/cifar10-cifar100-principal-component-analysis/

また、Windows 10とWindows 11用の実行ファイル(MSIXパッケージ)を下記のページに置きました。

# Visual Studio 2022でビルドして実行していたときには問題なく動作していたのですが、下記のリンク先のMSIXパッケージはプログラムが落ちる場合があることを確認しています。Cifar10またはCifar100のどちらかを読み込んだ後、Cifar10またはCifar100のもう一方を読み込もうとしたときにプログラムが落ちる場合があります。

## 補足(2024年1月20日):実行ファイルに変更はないのですが、2024年1月20日に最新のWindows Updateを適用したWindows 11 Homeバージョン23H2で何度か確認したところ、プログラムが落ちることはありませんでした。

https://www.leafwindow.com/softwares/msix/cifar10-cifar100-principal-component-analysis/

実行ファイルのインストール手順はこちらのページと同様です。

Windows 11 Version 22H2で動作確認しました。MSIXパッケージを作成するときのターゲットOSには添付の画像のようにWindows 10 Version 1803からWindows 11 Version 22H2までを指定したので、Windows 10 Version 1803以降のWindows OSなら動作するかと思います。

2. Cifar10/Cifar100データセットについて

Cifar10とCifar100は縦32 pixel、横32 pixelのRGB画像のデータセットです。データセット内の画像はCifar10, Cifar100とも全部で60,000枚あります。そのうち、50,000枚がニューラルネットワークの重みの学習等に用いられるtraining data、10,000枚が学習済みのニューラルネットワークによる画像識別能力の評価等に用いられるtest dataです。

Cifar10の60,000枚の画像にはairplane, automobile, bird, cat, deer, dog, frog, horse, ship, truckの10種類の乗り物と生き物のいずれかが写っています。画像データのラベルとしてこれらの種類の情報が付与されていて、ニューラルネットワークの学習とその識別能力の評価等に使用されます。10種類の乗り物と生き物の画像は6,000枚ずつ用意されています。

Cifar100の60,000枚の画像には600枚ずつ100種類の乗り物、生き物、家電等が写っています。Cifar100の画像にはこれら100種類のラベルが付与されています。また、100種類のラベルをグループ化したラベルも付与されています。例えば、beaver, dolphin, otter, seal, whaleにはaquatic mammalsという大まかな分類名も付与されています。

3. Cifar10/Cifar100データセット(Binary version)のファイル構成について

Cifar10/Cifar100データセットについては下記のページに説明があります。

https://www.cs.toronto.edu/~kriz/cifar.html

3.1. Cifar10データセットは、下記のリンクからダウンロードできる圧縮ファイルcifar-10-binary.tar.gz (Binary version)を展開して使用しました。

https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz

圧縮ファイルcifar-10-binary.tar.gzを下記のコマンドで展開すると、

$ tar xvzf cifar-10-binary.tar.gz

下記のファイルが存在することが確認できます。

$ ls cifar-10-batches-bin
batches.meta.txt  data_batch_2.bin  data_batch_4.bin  readme.html
data_batch_1.bin  data_batch_3.bin  data_batch_5.bin  test_batch.bin
2018年4月にリリースされたWindows10 April 2018 Update(バージョン1803)以降なら、コマンドプロンプトから下記のようにWindowsのtarコマンドで展開できます。

C:\dataset\tmp>where tar
C:\Windows\System32\tar.exe
C:\dataset\tmp>tar xvzf cifar-10-binary.tar.gz
x cifar-10-batches-bin/
x cifar-10-batches-bin/data_batch_1.bin
x cifar-10-batches-bin/batches.meta.txt
x cifar-10-batches-bin/data_batch_3.bin
x cifar-10-batches-bin/data_batch_4.bin
x cifar-10-batches-bin/test_batch.bin
x cifar-10-batches-bin/readme.html
x cifar-10-batches-bin/data_batch_5.bin
x cifar-10-batches-bin/data_batch_2.bin

3.2. Cifar100データセットは、下記のリンクからダウンロードできる圧縮ファイルcifar-100-binary.tar.gz (Binary version)を展開して使用しました。

https://www.cs.toronto.edu/~kriz/cifar-100-binary.tar.gz

圧縮ファイルcifar-100-binary.tar.gzを下記のコマンドで展開すると、

$ tar xvzf cifar-100-binary.tar.gz

下記のファイルが存在することが確認できます。

$ ls cifar-100-binary
fine_label_names.txt  coarse_label_names.txt  train.bin  test.bin

4. Cifar10/Cifar100データセット(Binary version)のファイルの中身について

4.1. Cifar10のファイルの中身

下記のファイルのサイズは全て30,730,000 byteで10,000枚ずつtraining dataが格納さています。

$ ls -l cifar-10-batches-bin/data_batch_*
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/data_batch_1.bin
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/data_batch_2.bin
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/data_batch_3.bin
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/data_batch_4.bin
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/data_batch_5.bin

テストデータを格納したtest_batch.binもtraining data用のファイルと同様30,730,000 byteで、10,000枚のtest dataが格納されています。

$ ls -l cifar-10-batches-bin/test_batch.bin
-rw-r--r-- 1 fukagai 197610 30730000 Jun  5  2009 cifar-10-batches-bin/test_batch.bin

10,000枚の画像データの最初の1 byteは0から9のラベル番号、それに続く3072 byteが大きさ32 x 32 pixelのRGB画像のデータです。画像データは、赤色のデータ1024 byte (32 x 32 = 1024)、緑色のデータ1024 byte、青色のデータ1024 byteが順に並んだ3072 byte (1024 x 3 = 3072)のデータです。下の図のような順でデータが格納されています。

batches.meta.txtには、下記のように0から9のラベル番号に対応した名前が記載されています。ラベル番号の順に名前が記載されていて、ラベル番号0はairplane、1はautomobile、2はbirdです。

airplane
automobile
bird
cat
deer
dog
frog
horse
ship
truck

赤色のデータ1024 byte、緑色のデータ1024 byte、青色のデータ1024 byteには、32 x 32 pixelの画像データが1行目から順に並べられています。

下の図は赤色のデータの例になります。先頭の1 byte目のデータが画像の左上角のpixelの赤色成分の大きさで、最初の32 byteは画像の一番上のpixelの赤色成分の大きさになります。末尾の1024 byte目のデータは画像の右下角のpixelの赤色成分の大きさです。1 byteのデータなので大きさは0から255までの整数値になります。緑色と青色のデータについても同様です。

4.2. Cifar100のファイルの中身

Cifar10のデータと同様の構成ですが、50,000枚の画像からなるtraining dataが一つのファイルtrain.binにまとめられています。test.binは10,000枚の画像からなるtest dataを格納したファイルです。

$ ls -l cifar-100-binary
-rw-r--r-- 1 fukagai 197610       328 Nov  3 12:22 coarse_label_names.txt
-rw-r--r-- 1 fukagai 197610       725 Nov  3 12:22 fine_label_names.txt
-rw-r--r-- 1 fukagai 197610  30740000 Nov  3 12:22 test.bin
-rw-r--r-- 1 fukagai 197610 153700000 Nov  3 12:22 train.bin

各画像データの構成はCifar10とほぼ同じですが、Cifar100には下の図のようにラベル番号が2種類用意されています。1 byte目のCoarse Labelは0から19までのaquatic_mammals等の大まかな分類名のラベルです。2 byte目のFine Labelは0から99までのbeaver, dolphin, otter, seal, whale等の100種類のラベルです。

coarse_label_names.txtには、下記のように1 byte目の0から19のラベル番号に対応した名前が記載されています。ラベル番号の順に名前が記載されていて、ラベル番号0はaquatic_mammals、1はfish、2はflowersです。

aquatic_mammals
fish
flowers
food_containers
fruit_and_vegetables
...

fine_label_names.txtには、下記のように2 byte目の0から99のラベル番号に対応した名前が記載されています。ラベル番号の順に名前が記載されていて、ラベル番号0はapple、1はaquarium_fish、2はbabyです。

apple
aquarium_fish
baby
bear
beaver
...

5. アプリケーション起動後のCifar10/Cifar100データセットの読み取り方法とPCAフィルター等の表示

5.1. アプリケーション起動直後は次のような画面になっています。

5.2. Select Datasetボタンをクリックし、先ほど展開したCifar10のフォルダを選択します。

5.3. メッセージボックスが表示されます。

5.4. データセットフォルダ選択後、次のような画面になります。ここでRead Datasetボタンをクリックし、Cifar10のデータを読み込みます。

5.5. 展開したCifar10データセットのフォルダが選択されていたら、下のような画面になります。Cifar10データセットの画像データが表示されています。

5.6. 読み込むCifar10のデータセットがないフォルダを選択すると下のようなメッセージボックスが表示されます。この例ではCifar10データセットのフォルダではなくCifar100データセットを置いたフォルダを選択し、Cifar10データセットの読み取りに失敗しています。フォルダの場所と名前は任意です。

5.7. NextボタンとPreviousボタンをクリックするとCifar10の他の画像が表示されます。

5.8. Cifar10のtraining dataは50,000枚なので1ページに20×20ずつ表示した場合、全部で125ページになります。

5.9. Number of Picturesラジオボタンの選択を変えることで1ページに表示する画像の枚数を変更することができます。下の例は10×10を選択した例です。

5.10. Cifar10とCifar100の画像データは縦32 pixel、横32 pixelの小さな画像データなのでNumber of Picturesで1×1を選択し、1枚だけ表示すると、下の図のような少しぼやけた画像が表示されます。

5.11. 次に、Show PCA Filtersボタンをクリックし、PCAフィルターを表示します。

下記のリンクの実行ファイルをダウンロードして実行した場合は、すぐにPCAフィルターが表示されます。実行ファイルに計算済みのPCAフィルターが埋め込まれています。

https://www.leafwindow.com/softwares/msix/cifar10-cifar100-principal-component-analysis/

下記のリンクのソースコードをビルドして実行した場合、初めてShow PCA Filtersボタンをクリックしたときには下の図のような画面が表示されます。Show PCA Filtersボタンの色が薄くなり、PCAフィルターの計算が始まります。

https://github.com/fukagai-takuya/cifar10-cifar100-principal-component-analysis/

PCAフィルターの計算が完了したらShow PCA Filtersボタンの色が元に戻り、下記のようなメッセージボックスが表示されます。また、計算済みのPCAフィルターの画像が表示されます。ここで表示しているPCAフィルターの画像は主成分分析で得られた主成分ベクトルを各ベクトル要素の値が0から255のRGB値となるように変換し、大きさ32 x 32 pixelのRGB画像として可視化したものです。主成分ベクトルは第一主成分から順に並べています。主成分ベクトルは画像平面内をRGB値が緩やかに変化する低周波のフィルターから高周波のフィルターへと順に変化しています。

5.12. Cifar10の画像と同様、NextボタンとPreviousボタンをクリックすると他のPCAフィルターを見ることができます。ここで得られるPCAフィルターの画像は全部で3,072枚になります。

5.13. Cifar10の画像と同様、Number of Picturesラジオボタンの選択を変えることで一度に表示するPCAフィルター画像の枚数を変えることができます。

5.14. Show Data Imagesボタンをクリックした後、Number of Eigenvectors used to represent Cifar Imagesの選択を変更し、Cifar10の画像をPCAフィルターの重ね合わせで表現します。下の図の例では1,000枚のPCAフィルターの重ね合わせで表現しようとしています。

5.15. Cifar10の画像を1,000枚のPCAフィルターの重ね合わせで表現した結果の例です。この計算には少し時間を要するので、一度に表示する画像の枚数は3×3か1×1のみにしています。元の画像にない細かいドットが表示されていますが、元の画像に何が写っていたかだいたいわかる画像になっています。

5.16. ここで、Show Test Imagesボタンをクリックすると、主成分分析の計算には使用しなかったtest dataを1,000枚のPCAフィルターの重ね合わせで表現した画像が表示されます。主成分分析の計算に使用したtraining dataとほぼ同様の結果になっています。

5.17. 下の図は重ね合わせるPCAフィルターの枚数を300枚に変えて表示した結果です。1000枚のときに比べると何が写っているかわかりにくくなっています。

a. training dataを300枚のPCAフィルターの重ね合わせで表示したときの例

b. test dataを300枚のPCAフィルターの重ね合わせで表示したときの例

5.18. Datasetの選択をCifar100に変更するとCifar100のデータを対象として、同様の結果を表示することができます。

a. Cifar100に選択を切り替えた直後

b. Cifar100のデータを読み込んだ直後

c. PCAフィルターボタンをクリックしてPCAフィルターを表示した結果

6. Cifar10/Cifar100の画像を対象とした主成分分析

6.1. 下の図のような50,000行3,072列の行列を用意しました。各行はCifar10またはCifar100のtraining dataの画像データです。左上角のpixelから右下角のpixelまでのRGB各32 x 32 = 1024 byteのデータをB, G, Rの順に並べた3072 byteのデータが並んでいます。training dataは50,000枚あるため50,000行のデータになっています。

6.2. 主成分分析の計算をするため、上の図の行列データをbyte型ではなく下記のコードのようにdouble型の二次元配列に格納しました。

            double[,] X_input = new double[numberOfImages, ImageDataSize];
            for (int row = 0; row < numberOfImages; row++)
            {
                for (int column_i = 0; column_i < ImageAreaSize; column_i++)
                {
                    X_input[row, 3 * column_i] = (double) _dataImages[row].BlueChannelData[column_i]; // blue
                    X_input[row, 3 * column_i + 1] = (double) _dataImages[row].GreenChannelData[column_i]; // green
                    X_input[row, 3 * column_i + 2] = (double) _dataImages[row].RedChannelData[column_i]; // red
                }
            }

6.3. 主成分分析はC#の行列演算ライブラリを含むAccord.NETを使用し、下記のコードで計算しました。

        private double[] _vectorMean;
        private double[,] _matrixU;
        private double[] _vectorW;
        private double[,] _matrixV;

        private void CalculatePCA()
        {
            ...

            // column means
            _vectorMean = X_input.Mean(dimension: 0);

            X_input = X_input.Subtract(_vectorMean, dimension: (VectorType)0);
            double[,] X_cov = X_input.Transpose().Dot(X_input);

            X_input = null;
            GC.Collect();

            SingularValueDecomposition svd = new SingularValueDecomposition(X_cov);

            _matrixU = svd.LeftSingularVectors;
            _vectorW = svd.Diagonal;
            _matrixV = svd.RightSingularVectors;

            ...
        }

6.4. 上記の計算を数式で書くと下記のようになります。

6.4.1. まず、50,000枚のtraining dataの各pixelのRGB値の共分散行列$X_{cov}$を下記の式で計算します。得られる共分散行列$X_{cov}$は3,072行3,072列の行列になります。得られた結果全体を50,000または49,999で割っても割らなくても求めたい主成分ベクトルには影響しないため、共分散行列$X_{cov}$として下記の結果を使用します。共分散行列全体を定数倍してもしなくても今回計算したい主成分ベクトルの方向には影響しないためです。

\[X_{cov} = (X_{input} – X_{mean})^T (X_{input} – X_{mean})\]

上記の式で、$X_{input}$は50,000行3,072列の画像データ、$X_{mean}$は$X_{input}$の各列の平均値を格納した50,000行3,072列の平均値行列です。

$X_{mean}$は50,000枚の画像の各pixel位置のRGB値の平均値を格納した行列です。$X_{mean}$の各列には同じ値が格納されています。$X_{mean}$の1列目には左上角の青色成分(B)の平均値が格納されています。同じ値が1行目から50,000行目まで並んでいます。Cifar10とCifar100のtraining dataの各pixelのRGB成分の平均値は120から140前後でした。

$(X_{input} – X_{mean})^T$は平均値を引いて転置した行列で3,072行50,000列の行列です。上記の計算をして得られる3,072行3,072列の共分散行列$X_{cov}$の対角成分は各pixel位置のRGB値の分散です。対角成分以外は3,072のRGB値から2か所取り出して計算した画像データの共分散になります。

6.4.2. 次に$X_{cov}$を下記の式のように特異値分解します。

\[X_{cov} = UWV\]

上記の式の$U$, $W$, $V$は全て3,072行3,072列の行列で、$W$は対角行列、$U$と$V$は直交行列です。共分散行列の特異値分解では$V=U^T$となり、$UV$は単位行列です。

Accord.NETの特異値分解のクラスSingularValueDecompositionを使用すると、対角行列$W$には対角成分に大きな値から降順に数値が並んだ結果が得られます。先のコードの_vectorW = svd.Diagonalでは、対角行列の対角成分のみ3,072要素のベクトルとして取得しています。

また、_matrixV = svd.RightSingularVectorsで得られるのは上記の式の$V$の転置行列です。確認のため下記の計算をし、単位行列が得られるのを確認しています。

double[,] UV = _matrixU.Transpose().Dot(_matrixV);
3,072行3,072列の共分散行列の特異値分解(Singular Value Decomposition)の計算に時間がかかり、下記のスペックの私のノートパソコンでは2時間ほどの計算時間を要しました。

プロセッサ Intel(R) Core(TM) i3-8145U CPU @ 2.10GHz 2.30 GHz
実装 RAM 8.00 GB (7.75 GB 使用可能)
システムの種類 64 ビット オペレーティング システム、x64 ベース プロセッサ
エディション Windows 11 Home
バージョン 22H2
OS ビルド 22621.819
行列計算パッケージ Accord.Math 3.8.0