からっぽのしょこ

読んだら書く!書いたら読む!読書読読書読書♪同じ事は二度調べ(たく)ない

MNISTデータセットの読み込み【ゼロつく1のノート(Python)】

はじめに

 「Python」学習初手『ゼロから作るDeep Learning』民のためのPython攻略ノートです。『ゼロつく1』の学習の補助となるよう適宜解説を加えています。本と一緒に読んでください。

 本で必要となるPython文法や使用する関数について、そのルールや機能、扱い方を1つずつ確認していきます。

 この記事では、MNISTデータセット読み込み関数load_mist()の機能の確認と、load_mist()自体の読み込み方法を解説します。

【関連する記事】

www.anarchive-beta.com

【他の記事一覧】

www.anarchive-beta.com

【この節の内容】

・ファイルのインポートのためのライブラリ

 ファイルのインポートにはsysosのライブラリを用います。

# ファイル読み込み用ライブラリの読み込み
import sys
import os


 1行で同時に読み込むこともできます

# ファイル読み込み用ライブラリの読み込み(複数)
import sys, os


 一般的に言うフォルダのことをディレクトリと呼びます(これはデータを保存している場所はハード的にいうと保存している領域(directory)だからです)。フォルダの中にフォルダを作って管理しますよね。この中の中というのを階層と言います(ある領域の更に深い(下の)領域というイメージですね)。また階層の1つ上を親ディレクトリ(parent directory)、1つ下を子ディレクトリ(child directory)と表現します。
 そして、現在使用している領域のことをカレントディレクトリ(current directory:現行ディレクトリ)と言います。または、今作業している領域としてワーキングディレクトリ(working directory:作業ディレクトリ)とも言います(作業フォルダとか聞いたことありませんか)。

 その現在作業領域として使っているディレクトリを調べる関数がos.getcwd()です。引数を指定せずにそのまま実行します。

# カレントディレクトリを取得
os.getcwd()
'C:\\Users\\「ユーザーフォルダ」\\Documents\\・・・\\DL_from_Scratch'

 \(半角バックスラッシュあるいは半角円マーク)は、階層の区切りを意味します。右に行くほど階層が下がり(フォルダの中の中となり)ます。適当なファイルのプロパティを開くとそのファイルの保存場所がこの形式で書かれています。(\が1つか2つかの違いは正規表現で・・・)

 つまりこの実行結果例は「Cドライブ」の中の「ユーザーフォルダ」の中の・・・「DL_from_Scratch」という名前のフォルダで作業を行っているということです。(Jupyter Labを利用しているのであれば)今書いているスクリプトを保存している場所となっていると思います。

 このような形でファイルの位置を示すものをファイルパスと言います。

 本としては、3章をやっているのであればマスターデータの中の「03ch」フォルダにスクリプトを作っていることを想定しているようです。であれば(本の通り)sys.path.append(os.pardir)で親ディレクトリ(「deep-learning-from-scratch-master」フォルダ)を指定でき、importで「dataset」フォルダ内の「mnist.py」にアクセスできます。

 os.pardirは、カレントディレクトリの親ディレクトリのパス(の代わり)となります(つまり私が今このまま本の通り実行すると、「JupyterLab_Working」フォルダを指定することになります)。

 従って、本の通りの方法でばできないのであれば「deep-learning-from-scratch-master」フォルダを直接指定する必要があります。

 では実際にパスを指定します。

 「deep-learning-from-scratch-master」フォルダ内の(どれでもいいので)ファイルのプロパティを開いて、「場所」に書いてあるのがファイルパスになります。それをsys.path.append()の引数に指定します。ただし、\(バックスラッシュあるいは円マーク)を2つにする必要があります。

# 読み込みたいファイルのあるディレクトリを指定
sys.path.append('C:\\Users\\「ユーザーフォルダ」\\Documents\\・・・\\DL_from_Scratch\\deep-learning-from-scratch-master')

 (これでimportする際にファイルを検索するディレクトリ一覧に「dataset」を追加することができました。)

 これで、load_mnist()関数を読み込む準備が整いました。

・MNISTデータセット読み込み関数

 本で利用するために作成されたMNISTデータセット読み込み関数load_mnist()を確認しておきます。

・load_mnist()の読み込み

 「deep-learning-from-scratch-master」フォルダ(マスターデータ)のファイルパスを指定して、importload_mnist()を読み込みます。

# 読み込みたいファイルのあるディレクトリを指定
sys.path.append('C:\\Users\\「ユーザーフォルダ」\\Documents\\・・・\\DL_from_Scratch\\deep-learning-from-scratch-master')

# ファイルにアクセスして関数を読み込む
from dataset.mnist import load_mnist

 importによる読み込みは1章で取り上げましたが、fromを使う方法もあります。ライブラリに含まれる関数のみを読み込むときなどに使う方法です。(本の内容自体には深く知る必要はないため、ここでは取り上げません(何ならこの資料で微妙にごまかしていることの1つです(最後の最後に少し補足するつもりではいます))。)

 from dataset.mnistで「dataset」フォルダ内の「mnist.py」にアクセスします。そして、import load_mnistで「mnist.py」ファイル内で関数定義されているload_mnist()を読み込みます。

 ちなみに(ファイルパスの方ではなく)本のコード中で登場する\(バックスラッシュ)は、コードを改行しても処理が続いてることをPythonに認識させるための記号です。横に長いと視認性が悪くなるため(本的には紙面からはみ出るため)改行することがあります。ここでは(余計な情報が増えると脳の負担になるので)横に長くても改行せずに書きます。

 load_minit()でデータを取得するには次のように書きます。

# load_mnist()によるデータを取得
(x_train, t_train), (x_test, t_test) = load_mnist(flatten = True, normalize = False)


 変則的な受け取り方をする理由が気になったりしますか?一応load_minit()で取得できるデータの構造を確認しておきましょうか?正直しなくていいと思いますが・・・

# load_mnit()で取得できるデータ
load_mnist(flatten = True, normalize = False)
((array([[0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         ...,
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0]], dtype=uint8),
  array([5, 0, 4, ..., 5, 6, 8], dtype=uint8)),
 (array([[0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         ...,
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0],
         [0, 0, 0, ..., 0, 0, 0]], dtype=uint8),
  array([7, 2, 1, ..., 4, 5, 6], dtype=uint8)))

 データが入れ子になって出力されます。なので受け取る変数もこの構造に合わせる必要があります。

# 変数に同時に代入
a = 1
b = 2
print(a)
print(b)
1
2


# 複数の変数に同時に代入
a, b = 1, 2
print(a)
print(b)
1
2


c, d = 3, 4
print(a, b, c, d)
1 2 3 4


# 疑似再現
(a, b), (c, d) = (1, 2), (3, 4)
print(a, b, c, d)
1 2 3 4


# 失敗
x, y = (1, 2), (3, 4)
print(x, y)
(1, 2) (3, 4)

 まぁこんな感じです。タプル型のデータについては、本筋としては扱わないためこの資料では取り上げません。

・load_mnist()の引数

 load_mnist()の3つの引数についてそれぞれの機能を紹介します。必要になったタイミングで確認してください。

・normalize引数

 normalizeは、画像を構成するピクセル(画素)ごとに持つ値を0から1までの値に正規化するかを指定する引数です。

# データを取得
(x_train, _), (x_test, _) = load_mnist(normalize = False, flatten = True, one_hot_label = False)
print(x_train.shape) # 各次元の要素数を確認
print(x_train[0, 100:199]) # データを確認
(60000, 784)
[  0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   3  18
  18  18 126 136 175  26 166 255 247 127   0   0   0   0   0   0   0   0
   0   0   0   0  30  36  94 154 170 253 253 253 253 253 225 172 253 242
 195  64   0   0   0   0   0   0   0]

 Falseを指定すると、各要素(ピクセル)は0から255の値をとります。0から255の値が全256色に対応しています。例えば0なら真っ黒で255は真っ白を意味します。(256は2の8乗の値です。興味があれば8ビットカラーで検索してみてください。)

# データを取得
(x_train, _), (x_test, _) = load_mnist(normalize = True, flatten = True, one_hot_label = False)
print(x_train.shape) # 各次元の要素数を確認
print(x_train[0, 100:199]) # データを確認
(60000, 784)
[0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.01176471 0.07058824
 0.07058824 0.07058824 0.49411765 0.53333336 0.6862745  0.10196079
 0.6509804  1.         0.96862745 0.49803922 0.         0.
 0.         0.         0.         0.         0.         0.
 0.         0.         0.         0.         0.11764706 0.14117648
 0.36862746 0.6039216  0.6666667  0.99215686 0.99215686 0.99215686
 0.99215686 0.99215686 0.88235295 0.6745098  0.99215686 0.9490196
 0.7647059  0.2509804  0.         0.         0.         0.
 0.         0.         0.        ]

 Trueを指定すると、各要素を255で割った値が出力されます。最大値の255で割ることで、各要素が0から1の値となります。

・flatten引数

 flattenは、ピクセルデータを横一列に並べ替えるかを指定する引数です。

# データを取得
(x_train, _), (x_test, _) = load_mnist(normalize = False, flatten = False, one_hot_label = False)
print(x_train.shape) # 各次元の要素数を確認
print(x_train[0, 0, 0:9, 0:9]) # データを確認
(60000, 1, 28, 28)
[[  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0   0]
 [  0   0   0   0   0   0   0   0  30]
 [  0   0   0   0   0   0   0  49 238]
 [  0   0   0   0   0   0   0  18 219]]

 Falseを指定すると、$28 \times 28$の状態を維持して出力されます。この例では1枚の画像データ(の左上)から、要素を縦横10個(ピクセル)ずつ取り出しました。左上の辺りには文字(線)がかかっておらず0(黒)が多いのでしょう。

# データを取得
(x_train, _), (x_test, _) = load_mnist(normalize = False, flatten = True, one_hot_label = False)
print(x_train.shape) # 各次元の要素数を確認
print(x_train[0, 0:99]) # データを確認
(60000, 784)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

 Trueを指定すると、全て(784個)の要素(ピクセル)を横に並べた状態で出力されます。ちなみに一番上の行の要素に続けて次の行の要素が結合されています。

・one_hot_label引数

 one_hot_labelは、ラベル(正解)データを示す値の形式に影響する引数です。

# データを取得
(_, t_train), (_, t_test) = load_mnist(normalize = False, flatten = False, one_hot_label = False)
print(t_train.shape) # 各次元の要素数を確認
print(t_train[0:9]) # データを確認
(60000,)
[5 0 4 1 9 2 1 3 1]

 Falseを指定すると、1つの画像データに1個の要素(ラベル)が対応し、0から9の値をとります。各要素の値が対応する画像に書かれている数字を意味します。
 この出力例からは、最初の画像は5が書かれており、次の画像は0が書かれていることが分かります。

# データを取得
(_, t_train), (_, t_test) = load_mnist(normalize = False, flatten = False, one_hot_label = True)
print(t_train.shape) # 各次元の要素数を確認
print(t_train[0:9, :]) # データを確認
(60000, 10)
[[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
 [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]]

 Trueを指定すると、1つの画像データに10個の要素(10個で1つのラベル情報)が対応します。10個の要素はそれぞれ0から9の数字に対応していて、画像に書かれている数字に対応する要素が1それ以外は0をとります。このような表現をone-hot表現と呼びます。
 この出力例からも(当然)、最初の画像には5が書かれており、次の画像は0が書かれていることが分かります。

参考文献

  • 斎藤康毅『ゼロから作るDeep Learning』オライリー・ジャパン,2016年.

おわりに

 読み返す度に付け足したいことが増えていく。良いこと?良くない?

 自分が最初に躓いたのがこの内容ですね。思い出深い、、まだ先月のことだけど。

 引数について書きましたが、それぞれどんな利用例があるのか追加したいですねぇ。

【元の記事】

www.anarchive-beta.com