機械学習のデータセットを正規化する方法

本日は、機械学習におけるデータセット作成の注意点をお伝えします。

結論からいうと、正規化しましょう。ということです。
ありのままの数字では、学習する上で処理しづらいとうことです。

自分もまだまだ分かっていないところだらけですが
機械学習の根本の概念として、ベクトル距離というのがあります。

データセットを作る際には、あらゆるデータ(画像、動画、音、テキスト…)を数値化していきます。 ただ、これは実際の数値に意味があるのではなくて、数値感の距離を測っているのです。実際の数値は学習していく上では、重要な数値ではないのです。

なので、データ間での、距離を正しくしましょう。というのが、データセットの正規化です。極端にいうと、データを [ 0 ~ 1 ] のデータに変換しましょうというこです。

正規化のやり方は色々とあるようです。下記を参考にしました。

https://sinyblog.com/deaplearning/preprocessing_002/#i-2

画像データを正規化。Numpyの高次元配列のデータ型を変更

画像読み込むと、0~255階調のデータになっているのを、0~1のデータへ正規化していきます。

まずは、Numpyのデータ型の確認方法です。
dtype属性で確認します。
trainingDataというndarray型の配列の場合

trainingData.dtype

unit8

など表示されます。

0~1のデータは、floatデータです。
floatデータ型に変換しつつ、0~1に変換します。

astype メソッドを使います。
配列の中のデータを丸っとキャストすることが出来ます。

trainingData.astype(np.float32) / 255

これで、0~255階調のデータになっているのを、0~1のデータへ正規化できます。

GANでデータセットを作るために、Numpyのreshapeを使う方法

画像pngデータを活用して、データセットを作るために必要だったreshapeの使い方を紹介したいと思います。(違うやり方もあると思います。)

以前、reshapeの基本的な使い方は下記で記載しました。

本記事の操作としては、下記記事で作成したデータセットを基に話を進めていきます。

画像データは、1つの画像に対して、( 350 × 350 ) の配列に格納された画像データが生成出来ました。

しかし、機械学習をしていく際に、
1つの画像が1つのndarray型に入っている必要があります。

(もしかしたら、行と列が別々の配列でも良い可能性がありますが、今のところ学習させる際には、1つの画像が、1つの配列に入っている方がススメやすいと思います。)

もう少し説明します。
上記で作ったデータを見ていきます。

training_data.shape

(3, 350, 350)

3枚の画像が、350×350のサイズデータとして存在している。ということですね。

機械学習していく際には、( 画像個数, X )といったデータ型にする必要があります。
ここで Numpy reshapeを使います。
※X = 1次元の画像データ

batchdata = training_data.reshape(3,122500)
#122500 = 350 × 350
batchdata.shape

(3, 122500)

こういったカタチで、機械学習する際に必要なデータ型になりました。

このデータって、変換されてしまったら画像データとして存在しないのでは?と思うかもしれません。確認してみましょう。

一度、reshapeして、変換したデータを、元に戻してみます。

batchReverse = batchdata.reshape(3,350, 350)

for p in batchReverse:
  plt.imshow(p, cmap="gray")
  plt.show()

実行してみると、元の画像が表示されます。

ここで、疑問なのは、データの変換される順番とかが決まっているのか?
ということです。
いま試してみたのは、同じ操作をもとに戻しただけですが、これが複雑な操作の後に、戻そうと思った際に、どうなるのか。疑問は深まるばかりです。

Google Colaboratory で 画像データを読み込んでデータセットを作る方法

Google Colaboratory便利ですね。
Google Colaboratoryで、データセットを作成したく、色々と試してみました。

正直、これで良いのかまだ、不明なところがあるのですが、ひとまず、紹介です。
やることをざっくりと分けると、下記フローでした。

(1):画像データ一覧をZipファイルに圧縮
(2):ZipファイルをGoogle Colaboratoryへアップロード
(3):Google Colaboratoryで、zipファイルを解凍
(4):Zipファイル内の画像データのファイルパス一覧を取得
(5):画像のFilePathを個別に取得
(6):画像データをNumPy配列ndarrayとして取得
(7):画像のリサイズ
(8):NumPy配列ndarrayとして、データセットとして保存

(1):画像データ一覧をZipファイルに圧縮

これは、普通にやってください。
※犬の画像を3枚取得して。ファイル名を(a.png, b.png, c.png)としました。
※Zipファイル名をdataset_vol2.zipとしました

(2):ZipファイルをGoogle Colaboratoryへアップロード

ZipファイルをGoogle Colaboratoryへアップロードします。
Colaboratoryファイルの、左部を開くとアップロードという箇所があるので、ここから、zipファイルをアップロードします。

(3):Google Colaboratoryで、zipファイルを解凍

Colaboratoryで、Zipファイルを解凍する際はZipファイルがある場所まで移動しましょう。
まずは、自分が何処の階層にいるのかを確認しましょう。

!ls

dataset_vol2.zip
sample_data

dataset_vol2.zip がある階層です。
それでは、解凍していきましょう。
解凍するコマンドは、!unzip です。

!unzip dataset_vol2.zip

Archive: dataset_vol2.zip
creating: dataset_vol2/
inflating: dataset_vol2/b.png
creating: __MACOSX/
creating: __MACOSX/dataset_vol2/
inflating: __MACOSX/dataset_vol2/._b.png
inflating: dataset_vol2/c.png
inflating: __MACOSX/dataset_vol2/._c.png
inflating: dataset_vol2/a.png
inflating: __MACOSX/dataset_vol2/._a.png

これで、Zipファイルが解凍されました。

(4):Zipファイル内の画像データのファイルパス一覧を取得

解凍したファイルの中へ移動していきましょう。

#ファイル移動
%cd dataset_vol2
!ls

a.png b.png c.png

(5):画像のFilePathを個別に取得

必要なライブラリをimportします。

import matplotlib.pyplot as plt
import os
import cv2
import glob
import numpy as np

フォルダの中のファイルPath一覧を取得する方法として、
Pythonのglobモジュールを活用しました。 詳しくは、下記を参考にしてます。
ワイルドカード(*)などを使って柔軟にFilePathを取得できるので、便利ですね。

Pythonで条件を満たすパスの一覧を再帰的に取得するglobの使い方
https://note.nkmk.me/python-glob-usage/

また、現在位置は、dataset_vol2の中にいるので、
このままでも問題ないですが、globを使う際には、取得したいファイルの中に移動してあげるのがいいでしょう。

そのために、コマンドじゃなく、ファイル階層を移動できる下記を使いました。
os.chdir(“Path名称”)

#階層移動
os.chdir("/content/dataset_vol2")
#階層内のPathを全取得
path = glob.glob("*")
print(path)

[‘a.png’, ‘c.png’, ‘b.png’]

もちろん、今回はファイル名を(a.png, b.png, c.png)にしたので、
当たり前の結果ですが、データ個数が多くなれば必要な作業です。

(6):画像データをNumPy配列ndarrayとして取得
(7):画像のリサイズ
(8):NumPy配列ndarrayとして、データセットとして保存

ここからは、一気にデータセット作成までいきましょう。

#データセットを格納する変数
training_data = []
#リサイズ後のサイズ
IMG_SIZE = 350

#個別のFilePathに対して処理していきます。
for p in path:
   pathEach = p
   print(pathEach)

  #NumPy配列ndarrayとして読み込まれ、ndarrayを画像として保存
   imageTTTT = cv2.imread(pathEach,cv2.IMREAD_GRAYSCALE)
  #画像のりサイズ
   img_resize_array = cv2.resize(imageTTTT, (IMG_SIZE, IMG_SIZE))
   print(imageTTTT)
   print(img_resize_array.dtype)
   training_data.append(img_resize_array)
   plt.imshow(img_resize_array, cmap="gray")
   plt.show()
   print("=============")

cv2.imreadを解説します。
OpenCVライブラリでは、様々なことができます。
imreadメソッドで、画像ファイルを読み込み、
NumPy配列ndarrayとして読み込まれ、ndarrayを画像として保存することができます。

ndarray型については、下記記事にも書きましたので参照下さい。
Numpyのarray・ndarrayって何。 簡単に説明します。
http://prglog.info/home/?p=29

imreadの詳細については、下記参考

▼Python, OpenCVで画像ファイルの読み込み、保存
https://note.nkmk.me/python-opencv-imread-imwrite/

print(training_data)

[array([[ 36, 36, 36, …, 191, 193, 193],
[ 36, 36, 36, …, 191, 192, 192],
[ 36, 36, 36, …, 190, 190, 190],
…,
[223, 221, 220, …, 225, 226, 228],
[225, 223, 222, …, 226, 228, 230],
[227, 225, 224, …, 228, 230, 232]], dtype=uint8), array([[ 84, 83, 82, …, 29, 27, 27],
[ 93, 92, 91, …, 34, 33, 32],
[102, 102, 101, …, 38, 37, 36],
…,
[126, 117, 110, …, 128, 133, 121],
[123, 117, 116, …, 134, 138, 127],
[119, 118, 122, …, 138, 142, 132]], dtype=uint8), array([[158, 158, 158, …, 142, 144, 147],
[158, 158, 158, …, 144, 146, 148],
[158, 158, 157, …, 146, 148, 149],
…,
[211, 208, 208, …, 212, 212, 211],
[219, 217, 217, …, 208, 207, 206],
[224, 223, 223, …, 205, 203, 203]], dtype=uint8)]

ここで、完了かなと思います。
ただ、他の記事を見てみると、ここからarrayメソッドを使って、ndarray型にデータを変換していたりします。

training_data = np.array(training_data)
print(training_data)

[[[ 36 36 36 … 191 193 193]
[ 36 36 36 … 191 192 192]
[ 36 36 36 … 190 190 190]

[223 221 220 … 225 226 228]
[225 223 222 … 226 228 230]
[227 225 224 … 228 230 232]]

[[ 84 83 82 … 29 27 27]
[ 93 92 91 … 34 33 32]
[102 102 101 … 38 37 36]

[126 117 110 … 128 133 121]
[123 117 116 … 134 138 127]
[119 118 122 … 138 142 132]]

[[158 158 158 … 142 144 147]
[158 158 158 … 144 146 148]
[158 158 157 … 146 148 149]

[211 208 208 … 212 212 211]
[219 217 217 … 208 207 206]
[224 223 223 … 205 203 203]]]

これで、ndarray型のデータが出来上がったということなのでしょうか。
ここは少し理解が浅いところですので、調べてみます。

Numpyのarray・ndarrayって何。 簡単に説明します。

機械学習で必ず使う、Numpy。
ndarray, arrayなど混合してしまったり、Pythonのlist [a, b, c, d, e]とかと違うのか。
などなど、よく分からないことが多いと思います。

正確なことは、下記に詳細が記載されております。
分かりますでしょうか? 私は、全くわかりませんでした。

■numpy.ndarray
https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html

■numpy.array
https://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html

簡単に言いますと
ndarray:
Numpyの高次元配列を扱うことが可能なクラス

array:
ndarray のクラス型をしたオブジェクトをreturnする、関数

ということです。
全く、性質が異なるものですね。

詳しいことは、下記のサイトに色々と載ってます。
是非、一度見てみるといいと思います。

http://www.kamishima.net/mlmpyja/nbayes1/ndarray.html

arrayで生成するndarrayのオブジェクトと、Python配列のListは違うものなのでしょうか。実験してみましょう。

#listを準備
z = [ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11]
#ndarray型のオブジェクト生成
nz = np.array(z)
print(nz)
print(z)

#print(nz)
[ 0 1 2 3 4 5 6 7 8 9 10 11]
#print(z)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

#print(nz)で出力した値は、ndarray型のオブジェクトです。
#print(z)で出力した値は、Pythonの配列 list、そのままです。

arrayの引数にデータ(z)を入れたことで、ndarray型のオブジェクトが出来上がりました。

確認してみましょう。
ndarray型のオブジェクトは、shape属性を持ちます。(配列の型を示す)

print(nz.shape)
print(z.shape)

#print(nz.shape)
(12,)
#print(z.shape)
AttributeError: ‘list’ object has no attribute ‘shape’

あたりまえですね。
Pythonの配列 list は、shape属性を持ち合わせて無いということです。

Tensorflow 1.x の Session をゆるく理解

Tensorflow 難しいですね。
Tensorflow 2.x がリリースされています。(現在は、2020年3月)
チュートリアル、記事など参考にしたいものが、Tensorflow 1.x のときに作られたものも多く、しばらく混乱しておりました。

Tensorflow 2.x の使い方から学んでも良いのかもしれませんが、
Tensorflow 1.x の歴史を学ぶことで、Tensorflowの考え方も理解できるかもしれません。

と、言うことで
Tensorflow 2.x では廃止されたTensorflow 1.x で使われていた Session について残しておきたいと思います。

Tensorflow 1.x  → Tensorflow 2.x へのUPDATEの詳しい変更点は下記を参考に頂けたらと思います。

ここで、不必要になった、Session。
これは、Tensorflow 1.x の概念を象徴する機能だったかなと思います。

Tensorflowは、下記 2段階のプロセスを辿ることで、より高速で強力な処理を可能にしてます。
( 1 ) 計算グラフ(フロー)を作成
( 2 ) 計算グラフの実行

詳細については下記を参考。
第1回 TensorFlowとは? 入門連載始動! データフローグラフ、事例、学び方

https://www.atmarkit.co.jp/ait/articles/1804/18/news142.html

で、ここでSessionが果たしていた機能は、
( 1 ) 計算グラフ(フロー)を作成 → ( 2 ) 計算グラフの実行
という実行をしますよ。という合図です。
(細かいことは置いておいて….)
この Session()が無ければ、作成した計算グラフを実行に移すことができません。

それでは、実際に
Sessionの使い方を見ていきましょう。

定数の処理を、Sessionを使って見ていきます。

基本的な流れは
( 1 ) 計算グラフ(フロー)を作成
( 2 ) 計算グラフの実行

# 必要なライブラリをインストール
import numpy as np
import tensorflow as tf
 # 定数を定義
const1 = tf.constant(10) 
const2 = tf.constant(20) 
const3 = const1 + const2
print(const1)
print(const2)
print(const3)

下記の結果がprintされます。
Const_x, add_x の項目を確認してみると、どちらも、値が 0 となってます。
(xの部分は連番で数字が加算されていく)

Tensor(“Const_0:0”, shape=(), dtype=int32)
Tensor(“Const_1:0”, shape=(), dtype=int32)
Tensor(“add_0:0”, shape=(), dtype=int32)

これは、値が定数( const )に代入されていない状態を意味してます。
ここまでの処理は
( 1 ) 計算グラフ(フロー)を作成 したに留まります。

本記事のメインテーマである、Sessionを使って
( 2 ) 計算グラフの実行 を進めていきます。

sessionの使い方は
Sessionを定義して、run()で実行です。

with tf.Session() as sess:
    result1 = sess.run(const1)
    print(result1)

10

run()で実行したことによって、何が起きたのでしょうか。
result2も追加してみましょう。

with tf.Session() as sess:
    result1 = sess.run(const1)
    result2 = sess.run(const2)
    print(result1)
    print(result2)

10
20

( 1 ) 計算グラフ(フロー)で、作成した計算グラフの定数が代入されています。
Sessionを定義して、run()することによって
計算グラフで、定義したものが実行されました。
(それまでは、型があるのみという、感じ。)

with tf.Session() as sess:
     result1 = sess.run(const1)
     result2 = sess.run(const2)
     result3 = sess.run(const3)
     print(result1)
     print(result2)
     print(result3)

10
20
30

const3 = const1 + const2

で定義した、グラフも実行されました。
さらに、const1 , const2 が既に実行されているので、下記も可能です。

with tf.Session() as sess:
     result1 = sess.run(const1)
     result2 = sess.run(const2)
     result3 = sess.run(const3)
     result4 = sess.run(const1 + const2)
     print(result1)
     print(result2)
     print(result3)
     print(result4)

10
20
30
30

ちなみに、これまでの処理をリセットした状態で、下記を実行してみましょう。

with tf.Session() as sess:
    #runせずに、const2をprint
    print(const2)

    #runした後に、const2をprint
    result2 = sess.run(const2)
    print(const2)

    #runした後に、result2をprint
    print(result2)

#runせずに、const2をprint
Tensor(“Const_1:0”, shape=(), dtype=int32)

#runした後に、const2をprint
Tensor(“Const_1:0”, shape=(), dtype=int32)

#runした後に、result2をprint
20

という結果となりました。
runによって、session 外部の const2に代入されたのではなく、
sessionの中で、constに代入している流れなのでしょうか。
(若干怪しい….)

Google Collaboratory で tensorflowのバージョンを変更する方法

非常に簡単です。
基本的には、Tensorflowのサイトに記載している通りです。

https://www.tensorflow.org/install/gpu

!pip install tensorflow==1.15

一つだけ注意する点は、
Collaboratory上で、pip する際には、 !pip とする必要があります。

バージョンが変更されたかを確認しましょう。

import tensorflow as tf
print(tf.__version__)

1.15.0

このように指定した、バージョンに変更されたら完了です。

一度、既存のバージョンで、import tensorflow as tfしてしまった場合には、
新しいCollaboratoryファイルでやり直すか、下記のように、uninstallした後に、指定のバージョンをinstallしましょう。

!pip uninstall -y tensorflow
!pip install tensorflow==1.15

tensorflowはバージョン違いで、エラーが起きるので、柔軟に使用しましょう。

Numpyでreshapeを簡単に使う方法

Numpyのメソッドであるreshapeを、簡単に使ってみたいと思います。

reshapeメソッドとは、
Numpyで扱う高次元配列のカタチを変形できるメソッドです。

reshapeを使う際の注意点など細かくあります。
詳細は下記を参考して下さい。

https://note.nkmk.me/python-numpy-reshape-usage/
https://www.sejuku.net/blog/69254#npreshape

難しいことは横においておいて、
とにかく、簡単に、reshapeを使って、感覚を掴んでみたいと思います。

まずは、データを用意します。

# numpyをimport
import numpy as np

# データを作成
a = np.arange(24)
print(a)
print(a.shape)

#データ内容
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
#データのカタチ
(24,)

reshapeを使って配列のカタチを変形します。
下記のように、4×6の配列に変形しました。

b = np.reshape(a, (4,6))
print(b)
print(b.shape)

#データ内容
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]]
#データのカタチ
(4, 6)

続いて、上記で変形したbを、再度変形します。
現状の配列のカタチを気にせず、新しいカタチを作ることが出来ました。

c = np.reshape(b, (3, 8))
print(c)
print(c.shape)

#データ内容
[[ 0 1 2 3 4 5 6 7]
[ 8 9 10 11 12 13 14 15]
[16 17 18 19 20 21 22 23]]
#データのカタチ
(3, 8)

更に、上記で変形したbを、再度変形します。
次元を1つ増やした配列が出来ました。

d = np.reshape(c, (3, 4, 2))
print(d)
print(d.shape)

#データ内容
[[[ 0 1] [ 2 3] [ 4 5] [ 6 7]]
[[ 8 9] [10 11] [12 13] [14 15]]
[[16 17] [18 19] [20 21] [22 23]]]
#データのカタチ
(3, 4, 2)