俺人〜OREGIN〜俺、バカだから人工知能に代わりに頑張ってもらうまでのお話

俺って、おバカさんなので、とっても優秀な人工知能を作って代わりに頑張ってもらうことにしました。世界の端っこでおバカな俺が夢の達成に向けてチマチマ頑張る、そんな小さなお話です。現在はG検定に合格し、E資格取得とKaggle参画に向けて、Pythonや機械学習の勉強中です。まだまだ道半ばですが、お時間がありましたら見て行ってください。

MNISTのデータをCSVに変換する(Pythonによるスクレイピング&機械学習テクニック)

今回は、前回ダウンロードしてきたMNISTのデータをCSVに変換しました。

今までバイナリデータをちゃんと扱ったことがなかったので、とても勉強になりました。

 Pythonによるスクレイピング&機械学習開発テクニック増補改訂 Scrapy、BeautifulSoup、scik [ クジラ飛行机 ]の第4章を参考にさせていただきながら、取り組んでいます。

 MNISTのデータのダウンロードまでは、前回の記事をご参照ください。

oregin-ai.hatenablog.com

では、振り返っていきたいと思います。

MNISTのデータをCSVに変換する

1.MNISTのデータ構造

MNISTからダウンロードしてきたデータは、以下のような構造になっています。

バイナリデータとなっているので、数値で扱うためには、後述のstructモジュールのunpack()メソッドを使って、整数値などに変換してから扱う必要があります。

ラベルファイル (「train-labels-idx1-ubyte」「t10k-labels-idx1-ubyte」)

  • 先頭4バイト マジックナンバー(今回は使いません)
  • 次の4バイト ラベルの個数(4バイトの整数)
  • 次の1バイト 1つ目のデータラベル(「0」〜「9」)
  • 次の1バイト 2つ目のデータラベル(「0」〜「9」)
  • ......

画像ファイル(「train-images-idx3-ubyte」「t10k-images-idx3-ubyte」)

  • 先頭4バイト マジックナンバー(今回は使いません)
  • 次の4バイト 画像の個数(4バイトの整数)
  • 次の4バイト 画像のピクセル行数(MNISTの場合28ピクセル)
  • 次の4バイト 画像のピクセル列数(MNISTの場合28ピクセル)
  • 次の1バイト 1枚目の画像の(0,0)の座標のデータ
  • 次の1バイト 1枚目の画像の(0,1)の座標のデータ
  • ..........

MNIST公式のWebサイトは以下の通りです。

MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

2.全体像

コード全体は以下の通りで、「mnist-to-csv.py」に保存しました。

今回は、「to_csv」という関数を定義して、最後にその関数を使って、CSVに変換するという構造になっています。

import struct

def to_csv(name, maxdata):
    #(1)ラベルファイルとイメージファイルを開く
    lbl_f = open('./mnist/'+name+'-labels-idx1-ubyte', 'rb')
    img_f = open('./mnist/'+name+'-images-idx3-ubyte', 'rb')
    csv_f = open('./mnist/'+name+'.csv', 'w', encoding='utf-8')


    #(2)ヘッダー情報を読み込む
    mag, lbl_count = struct.unpack('>II', lbl_f.read(8))
    mag, img_count = struct.unpack('>II', img_f.read(8))
    rows, cols = struct.unpack('>II', img_f.read(8))
    pixels = rows * cols


    #(3) 画像データを読み込んでCSVで保存
    res = []
    for idx in range(lbl_count):
        if idx > maxdata: break
        label = struct.unpack('B', lbl_f.read(1))[0]
        bdata = img_f.read(pixels)
        sdata = list(map(lambda n: str(n), bdata))
        csv_f.write(str(label)+',')
        csv_f.write(','.join(sdata)+'\r\n')

        #(4) うまく取り出せたかどうかPGMで保存して確認
        if idx < 10:
            s = 'P2 28 28 255\n'
            s += ' '.join(sdata)
            iname = './mnist/{0}-{1}-{2}.pgm'.format(name,idx,label)
            with open(iname, 'w', encoding='utf-8') as f:
                f.write(s)
    csv_f.close()
    lbl_f.close()
    img_f.close()

 

#(5) 出力件数を指定して出力
to_csv('train', 1000)
to_csv('t10k',500)
print('OK')

では、コードを順番に見ていきます。

3.ラベルファイルとイメージファイルを開く

まず、「to_csv」の関数の中身を見ていきます。

最初に、ダウンロードしたMNISTのラベルファイルと、イメージファイルを開きます。

このファイルは、バイナリファイルの読み込みなので、'rb'で開きます。

    lbl_f = open('./mnist/'+name+'-labels-idx1-ubyte', 'rb')
    img_f = open('./mnist/'+name+'-images-idx3-ubyte', 'rb')

また、CSVで保存するファイルを開きます。

今度は、テキストファイルの書き込みなので、'w'で開きます。また、エンコーディングには、'utf-8'を指定します。

    csv_f = open('./mnist/'+name+'.csv', 'w', encoding='utf-8')

4.ヘッダー情報を読み込む

structモジュールのunpack()メソッドを使って、バイナリデータを読めるデータに変換します。

使い方は、unpack(フォーマット、バイナリデータ)となります。

フォーマットの'>II'は、'>'がビッグエンディアン(データの順番を変えずに読み込む)を表し、'II'が2つ連続したunsined int型(符号無4バイト整数型)を表しています。

全体としてlbl_f.read(8)で読み込んだ8バイトのデータを4バイト、4バイトに分けてそれぞれ、1つずつの整数値としてmag, lbl_countにそれぞれ格納します。

    mag, lbl_count = struct.unpack('>II', lbl_f.read(8))

同様に、img_fから、最初の8バイトを読み込んでmag, img_countにそれぞれ格納します。

    mag, img_count = struct.unpack('>II', img_f.read(8))

次の8バイトを読み込んで、前半4バイトを行数(rows)、後半4バイトを列数(cols)に読み込んで、掛けた値を画素数(pixels)に格納します。(MNISTのデータは、rows=28,cols=28のpixels=784のデータとなります。)

    rows, cols = struct.unpack('>II', img_f.read(8))
    pixels = rows * cols

5.画像データを読み込んでCSVで保存

ラベルの数だけ、繰り返します。

    for idx in range(lbl_count):

指定したデータの個数(maxdata)を上回ったら処理を終了します。

        if idx > maxdata: break

lbl_f.read(1)でラベルデータの1バイトを、'B'(整数型の1バイトデータ)としてlabelに格納します。

        label = struct.unpack('B', lbl_f.read(1))[0]

img_f.read(pixels)で画像データの画素数バイト(784バイト)を、bdataに格納します。

        bdata = img_f.read(pixels)

bdataを、map関数とlambda式を用いて1バイトづつ文字列に変換した後に、list関数で配列としてsdataに格納します。

        sdata = list(map(lambda n: str(n), bdata))

csv_fに出力する際は、先頭1文字目にラベルデータの数字とカンマを出力後、list関数で配列に格納された画像データの値をjoin関数でカンマ区切りにして出力し、最後に改行を出力します。

        csv_f.write(str(label)+',')
        csv_f.write(','.join(sdata)+'\r\n')

6.うまく取り出せたかどうかPGMで保存して確認

CSVで出力したデータが本当に画像データであるか可視化するためにPGM形式の画像ファイルとして保存してテストします。PGMはテキストファイルで画像を表現する形式です。 

全てのデータに実施する必要はないので、取り出した画像データの10個目までについて処理をします。

        if idx < 10:

 まず、画像のヘッダを定義します。'P2'は、グレースケールの画像を、'28 28'は縦28x横28の画像サイズを、'255'は255階調のグレースケールであることを表しています。

            s = 'P2 28 28 255\n'

あとは画像データ(sdata)をスペース区切りで追加します。

            s += ' '.join(sdata)

mnistフォルダの配下に、指定した名前と、インデックスと、「0」〜「9」のラベルを作ったファイルを作成して、開いていたファイルを全てクローズします。

            iname = './mnist/{0}-{1}-{2}.pgm'.format(name,idx,label)
            with open(iname, 'w', encoding='utf-8') as f:
                f.write(s)
    csv_f.close()
    lbl_f.close()
    img_f.close()

7.出力件数を指定して出力

最後にこれまで作ってきた「to_csv」関数を使って、訓練データ(train)を1,000個とテストデータ(t10k)を500個のデータを、それぞれCSVに変換します。

#(5) 出力件数を指定して出力
to_csv('train', 1000)
to_csv('t10k',500)
print('OK')

 

8.コマンドラインから実行してみる

先ほど作成したファイルを実行してみます。

python3 mnist-to-csv.py

以下の通り、MNISTのバイナリデータをCSVに変換し、PGMファイルも作成することができました。

f:id:kanriyou_h004:20200523131715p:plain

図1.mnist-to-csv.py実行結果

f:id:kanriyou_h004:20200523131950p:plain

図2.出力されたファイル

作成されたPGMファイルを開いてみると、手書きの数字(図3では、数字の7)の画像ファイルであることが確認できました。

f:id:kanriyou_h004:20200523132243p:plain

図3.画像ファイルを確認

 

今回は、MNISTの手書き数字画像のバイナリデータを、CSVに変換する処理を確認しました。これまで、バイナリデータをきちんと扱ったことがなかったので、とても勉強になりました。

引き続き、このCSVデータを使って、画像分類の機械学習を実施していきたいと思います。

 

今後も、Pythonによるスクレイピング&機械学習開発テクニック増補改訂 Scrapy、BeautifulSoup、scik [ クジラ飛行机 ]で、スクレイピング機械学習開発に取り組んでいきたいと思います。

 

 

 【過去記事】

2019年8月31日(土)にE資格を受験して、合格しました!

E資格対策として勉強の進め方や、参考書などをまとめました。

これから受験される方がいらっしゃいましたらご参考まで。

oregin-ai.hatenablog.com 

 

 2019年3月9日(土)にG検定を受験し、見事合格できました!

受験の体験記や勉強法などを別のブログにまとめました。

これから受験される方がいらっしゃいましたらご参考まで。

g-kentei.hatenablog.com

 【E資格対策に使った参考書】