ロジスティック回帰による手書き文字の識別

###はじめに

以下のページを参考に手書き文字の識別プログラムをPythonで書いてみた。
http://gihyo.jp/dev/serial/01/machine-learning/0018
https://qiita.com/Ugo-Nama/items/04814a13c9ea84978a4c

以下で配布されているMNISTデータセットを使います。
http://yann.lecun.com/exdb/mnist/

視覚化するとこんな感じ。28×28の画像60,000枚が教師データ、10,000枚がテストデータ
![mnist_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/54f044a2-8ed9-4b7b-7117-8f3a79e9b57e.png)

###ソースコード
データのロード用クラス

```python:mnist.py
import struct
import os
import numpy as np

class MNIST:

	def __init__( self, pathMNIST = '.' ):

		fnLabelL = os.path.join( pathMNIST, 'train-labels-idx1-ubyte' )
		fnLabelT = os.path.join( pathMNIST, 't10k-labels-idx1-ubyte' )
		self.fnLabel = { 'L': fnLabelL, 'T': fnLabelT }
		fnImageL = os.path.join( pathMNIST, 'train-images-idx3-ubyte' )
		fnImageT = os.path.join( pathMNIST, 't10k-images-idx3-ubyte' )
		self.fnImage = { 'L': fnImageL, 'T': fnImageT }
		self.nrow = 28
		self.ncol = 28
		self.nclass = 10
	def getLabel( self, LT ):
		return _readLabel( self.fnLabel[LT] )
	def getImage( self, LT ):
		return _readImage( self.fnImage[LT] )

def _readLabel( fnLabel ):

	f = open( fnLabel, 'r' )
	header = f.read( 8 )
	mn, num = struct.unpack( '>2i', header ) # MSB first (bigendian)
	assert mn == 2049

	label = np.array( struct.unpack( '>%dB' % num, f.read() ), dtype = int )
	f.close()
	return label

def _readImage( fnImage ):
	f = open( fnImage, 'r' )

	header = f.read( 16 )
	mn, num, nrow, ncol = struct.unpack( '>4i', header ) # MSB first (bigendian)
	assert mn == 2051

	npixel = ncol * nrow
	pixel = np.empty( ( num, npixel ) )
	for i in range( num ):
		buf = struct.unpack( '>%dB' % npixel, f.read( npixel ) )
		pixel[i, :] = np.asarray( buf )

	f.close()

	return pixel

if __name__ == '__main__':
	mnist = MNIST( pathMNIST = './mnist' )

	print '# MNIST training data'
	dat = mnist.getImage( 'L' )
	lab = mnist.getLabel( 'L' )
	print dat.shape, dat.dtype, lab.shape

	print '# MNIST test data'
	dat = mnist.getImage( 'T' )
	lab = mnist.getLabel( 'T' )
	print dat.shape, dat.dtype, lab.shape
```

実際に学習->識別させてみる。

```python:logstic.py
# -*- coding: utf-8 -*-
import numpy as np
import scipy.misc as sp
import mnist

if __name__ == "__main__":

	#MNISTデータセットの読み込み
	mni = mnist.MNIST(pathMNIST = "./mnist")
	X = mni.getImage('L')
	lab = mni.getLabel('L')
	testimg = mni.getImage('T')
	testlabel = mni.getLabel('T')

	K = 10				#class num
	D = X.shape[1]		#学習データ数(60000)
	N = X.shape[0]		#1データのバイト数(784)

	# 教師データ準備
	t = np.zeros((K,N), dtype = bool)
	for i in range(K): t[i,:] = i == lab

	w = np.zeros((K, D+1))     #重み
	wsum = np.zeros((K, D+1))
	z = np.zeros((K, N))	   #出力（どのクラスに分類されるか）

	w[:] = 0.001               #重みの初期値
	eta = 0.001 / D / N        #学習率

	print 'epoch,Learn(%),Test(%)'
	for i in range(10000):
		#重みと入力の内積を計算
		y = w[:, 0, np.newaxis] + np.dot(w[:,1:], X.T)

		#ソフトマックス関数にて確率を算出
		z = np.exp(y - sp.logsumexp(y,axis = 0))

		#教師データとの誤差を算出
		tmp = z - t

		#パラメタを更新
		wsum[:,0] = np.sum(tmp, axis = 1)
		wsum[:,1:] = np.dot(tmp, X)

		#教師データの誤答率を計算
		lmis = np.sum(lab != np.argmax(z,axis = 0))

		#テストデータの誤答率を計算
		y = w[:, 0, np.newaxis] + np.dot(w[:,1:], testimg.T)
		z = np.exp(y - sp.logsumexp(y,axis = 0))
		tmis = np.sum(testlabel != np.argmax(z,axis = 0))

		#重みを更新
		w -= eta * wsum

		print '%d,%2.5f,%2.5f' %(i , lmis * 100.0 / len(t[0,:]), tmis*100.0 / len(testlabel))

```
###学習させてみる

単純パーセプトロンでもテストデータの識別率90%ぐらいまではいけるみたい。
学習回数,教師データの誤識別率,テストデータの誤識別率
<img width="694" alt="logistic_test1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/69af8bf6-4346-8e96-024c-42e02c41ecac.png">
<img width="537" alt="スクリーンショット 2019-07-07 1.31.18.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/4038b706-6a4e-0f4b-c051-016c7d51ca70.png">

グラフにするとこんな感じ。
教師データよりテストデータの方が識別率がよい？なぜ・・・
<img width="834" alt="スクリーンショット 2019-07-07 1.51.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/abbc401f-1b7c-ce1b-f4e3-da4f64d43010.png">

多層パーセプトロンではどこまで識別率が上がるか試してみたい。