継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

PythonでスクレイピングとMNISTを使って機械学習を実践する

タダです。

あけましておめでとうございます! 新年一発目のブログを書いて行きます。

実は、id:kakku22さんのブログメンティーに選んでいただきまして、良いプレッシャーをいただきつつブログを書いています。

さて、昨年から機械学習に興味が出てきて、その技術について学んでいます。 その中でデータ収集するための手段としてスクレイピング技術を勉強しているので、今回はPythonスクレイピングをサンプルデータで実際に動かしてみます。

教本としてこちらの本を使わせてもらっています。

www.socym.co.jp

TL;DR

ソースコード載せたりしている関係で冗長になっているので、以下の2つのことを書いています。

スクレイピング機械学習

スクレイピングは、Webサイトから任意の情報を抽出、データ構造の解析することができます。

機械学習を行う為には、大量のデータを収集する必要があります。

そのための手段として、スクレイピング技術を扱うことができればデータの収集がしやすくなれると思い勉強しています。

スクレイピングの技術要素

勉強していて出てきたPythonスクレイピングするために必要な技術要素を紹介します。 なお、今回はスクレイピングしたデータから機械学習を行うための手順を書きたいので、下記の技術の詳細な説明は割愛します。

  • urllib : HTTPやFTPを利用してデータをダウンロードするためのライブラリ
    • urllib.request : ファイルをダウンロードするためのモジュール
  • requests : クッキーを利用したWebアクセスが簡単に行えるモジュール
  • BeautifulSoup : HTMLやXMLの解析を行うためのライブラリ
  • Selenium : JavaScriptなどWebブラウザを使わないと正しく動作しないサイトへの遠隔操作するためのツール。主にWebアプリのテストで使う。
  • PhantomJS : コマンドラインから使えるWebブラウザー(PhantomJSの開発は終了して、リポジトリアーカイブ化されてるため注意)

参考 : PhantomJSの開発が終了しリポジトリがアーカイブ化された - JSer.info

これらの技術を使ってWebからデータを収集します。

実際にPythonスクレイピングして機械学習を行う

それでは、実際にPythonスクレイピングして機械学習していきます。

今回は、機械学習のためのフレームワークである「scikit-learn」を使うので環境にインストールしておきます。

pip install -U scikit-learn scipy matplotlib scikit-image

また、機械学習の対象はMNISTの手書き数字データになります。

MNISTのデータは機械学習の練習によく使われるデータなので、利用することにしています。

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

さて、大まかに作業の順番ですが、以下の通りです。

  1. MNISTから学習用データとテスト用データをダウンロードする
  2. MNISTの学習用データをCSVに変換する
  3. 画像データを学習させる

1. MNISTから学習用データとテスト用データをダウンロードする

MNISTのサイトから以下の4つファイルを「mnist」ディレクトリにダウンロードします。

  • train-images-idx3-ubyte.gz : 学習用画像データ
  • train-labels-idx1-ubyte.gz : 学習用ラベルデータ
  • t10k-images-idx3-ubyte.gz : テスト用画像データ
  • t10k-labels-idx1-ubyte.gz : テスト用ラベルデータ
# coding:utf-8
import requests
import gzip, os, os.path

savepath = "./mnist"
baseurl = "http://yann.lecun.com/exdb/mnist"
files = [
    "train-images-idx3-ubyte.gz",
    "train-labels-idx1-ubyte.gz",
    "t10k-images-idx3-ubyte.gz",
    "t10k-labels-idx1-ubyte.gz"
]

# ディレクトリの作成
if not os.path.exists(savepath): os.mkdir(savepath)

# ダウンロード
for file in files :
    url = baseurl + "/" + file
    res = requests.get(url)
    location = savepath + "/" + file
    print("download:", url)
    if not os.path.exists(location):
        with open(location, "wb") as f:
            f.write(res.content)
            
for file in files:
    gz_file = savepath + "/" + file
    raw_file = savepath + "/" + file.replace(".gz", "")
    print("gzip:" + file)
    with gzip.open(gz_file, "rt") as fp:
        body = fp.read()
        with open(raw_file, "wb") as w:
            w.write(body)

2.MNISTの学習用データをCSVに変換する

バイナリデータのままだとPythonで扱いづらいため、CSVファイルへの変換を行います。

CSVファイルへの変換とあわせて、画像データから最初の10件だけデータを取得しています。

# coding:utf-8
import struct

def to_csv(name, maxdata):
    # ラベルファイルとイメージファイルを開く
    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")
    # ヘッダ情報を読む
    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
    # 画像データを読んで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")
        # 試しに10件だけ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()

# 出力件数を指定 --- (※4)
to_csv("train", 1000)
to_csv("t10k", 500)

3.画像データを学習させる

書籍では、下記のようなモジュールの読み込ませ方が紹介されていました。

from sklearn import cross_validation

ただ、scikit-learnの0.20バージョンからcross_validationモジュールはなくなり、代わりにmodel_selectionモジュールが使われるようになりました。

# coding:utf-8
from sklearn.model_selection import cross_val_score # ここの書き方注意
from sklearn import svm, metrics

# CSVファイルを読んで学習用データに整形
def load_csv(fname):
    labels = []
    images = []
    with open(fname, "r") as f:
        for line in f:
            cols = line.split(",")
            if len(cols) < 2: continue
            labels.append(int(cols.pop(0)))
            vals = list(map(lambda n: int(n) / 256, cols))
            images.append(vals)
    return {"labels":labels, "images":images}

data = load_csv("./mnist/train.csv")
test = load_csv("./mnist/t10k.csv")

# 学習
clf = svm.SVC()
clf.fit(data["images"], data["labels"])

# 予測
predict = clf.predict(test["images"])

# 精度の確認
ac_score = metrics.accuracy_score(test["labels"], predict)
cl_report = metrics.classification_report(test["labels"], predict)
print("正解率=", ac_score)
print("レポート=")
print(cl_report)

このプログラムの実行結果が下記です。

正解率が78.6%です。何もチューニングしていないためまずまずな精度かと言えます。

正解率= 0.7884231536926147
レポート=
              precision    recall  f1-score   support

           0       0.87      0.93      0.90        42
           1       0.81      1.00      0.89        67
           2       0.84      0.69      0.76        55
           3       0.87      0.57      0.68        46
           4       0.76      0.75      0.75        55
           5       0.63      0.80      0.71        50
           6       0.97      0.67      0.79        43
           7       0.74      0.86      0.79        49
           8       0.91      0.72      0.81        40
           9       0.71      0.81      0.76        54

   micro avg       0.79      0.79      0.79       501
   macro avg       0.81      0.78      0.78       501
weighted avg       0.80      0.79      0.79       501

まとめ

Pythonスクレイピングしたデータを使ってscikit-learnで機械学習をしてみました。

サンプルデータなのでチューニングはしていませんが、今度は公開されているAPIからデータを取得して実践的な使い方を記事にしたいと思います。