External Memory

プログラミング周辺知識の備忘録メイン

フォント数字を使って手書き数字を判別

手書き数字データとしては、MNISTという手ごろなものがあるが、
機械学習において、そういうものがなくても手書き数字を判別したい場合は、
学習するための手書き数字データを収集しなければいけない。

手持ちのデータが十分でない場合、例えば自分でデータを作製しなければいけない。
高度なデータ作製方法としてはGANを使ったようなものがあるようだが、
よくある手っ取り早い方法の一つとして、画像加工により増やす方法がある。

今回はWindowsに元から入っているフォントデータを加工して、学習データを増やしMNIST手書き数字を判別した。
Windowsの入っているフォントデータは太字や斜体なども含めれば100種類以上ある。
Wingdingsのような謎フォントは除外した。

画像の加工方法としては、

などがあるが、今回はpillowの画像加工を使ってアフィン変換と回転を組み合わせた。
アフィン変換で回転もできるが、回転角度が分かりやすくするため、操作を分離させた。
回転は右利きが多いことを考慮して回転角10度の範囲で時計回り寄りにした。


画像加工のプログラムはPythonで以下のように作成した。

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import numpy as np
import random

def transform_img(numlist,fontpath,size=(PIXEL,PIXEL),samples=1,offset=2,r=0.3):
    font = ImageFont.truetype(fontpath,int(size[0]*0.85))
    imglist = []
    randarr = np.random.rand(samples*50)
    labeldata = np.eye(10,dtype=np.float32)
    for i in range(samples):
        for num in numlist:       
            img = Image.new('1',size)
            draw = ImageDraw.Draw(img)
            num_imgsize = font.getsize(str(num))
            draw.text(((size[0]-num_imgsize[0])//2,(size[1]-num_imgsize[1])//2-offset),str(num),1,font=font)
            data = (randarr[(i*10+num)*5]*r+(1.0-r/2),(randarr[(i*10+num)*5+1]-0.5)*r,0,
                    (randarr[(i*10+num)*5+2]-0.5)*r,randarr[(i*10+num)*5+3]*r+(1.0-r/2),0)
            trans_img = img.transform(img.size,Image.AFFINE,data)
            rotate_img = trans_img.rotate(-randarr[(i*10+num)*5+4]*10+3)
            rotate_img.resize(size)         
            imglist.append((np.array(rotate_img).astype(np.float32).reshape(-1).tolist(),
                            labeldata[num].tolist()))
    return imglist

def load_imgarr(fontlist,samples):
    numlist = [i for i in range(10)]
    datalist =[]
    for font,offset in fontlist:
            datalist.extend(transform_img(numlist,fontpath="C:/Windows/Fonts/{}".format(font),
                              samples=samples,offset=offset,r=0.3))
    random.shuffle(datalist)
    return datalist

加工した画像データを計62500個作製して、TensorFlowで学習させた。
使ったモデルは全結合の隠れ層2層を挿んだ単純なFFNNとCNNである。


まずは、FFNNの結果から以下に示す。
画像加工なしデータで学習させたものとも比較した。
rはアフィン変換時の係数(ここでは変形率とする)である。rが大きいほど、大きく変形しやすくなる。
ドロップアウトのkeep率はFFNNで0.7、CNNで0.5に設定した。

units                   step   train_loss    train_acc    test_loss     test_acc
-----------------------------------------------------------------------------------
加工なし
784,784,784,10         30000       0.0000       1.0000      11.0097       0.5549
FFNN r=0.2
784,784,784,10         30000       0.0015       0.9998       1.9878       0.7423
FFNN r=0.3
784,784,784,10         30000       0.0046       0.9988       1.5088       0.7499
FFNN r=0.4
784,784,784,10         30000       0.0066       0.9982       1.3941       0.7520

CNN r=0.3
(conv+maxpool)*2,dense 20000                                 0.8949       0.8451

画像加工によって正答率は20%ほど向上しているものの、正答率自体はあまり高くなかった。フォント数字に対する正答率は過学習ぽく高く、変形率による正答率の変化が小さいことから、フォント数字がフォントの種類によって似たり寄ったりでそれほど大きく形状の差異がないことや、根本的に手書き数字全てを判別するほどのフォント数字そのものがなく、対MNIST用学習データになってないことが影響しているかもしれない。
改善にはもっとうまい加工方法があったのかもしれないし、手書き風フォントを収集するのもひとつの方法かもしれない。

学習用データがテストデータの分類目的として適当であるかどうかみたいな指標があったりするのだろうか。

ちなみに、手書き風フォント"Segoe Print"のみを加工してデータを5万まで増やして学習させると72-73%の正答率であった。


画像の加工方法は機械学習自体の本質的なものではないが、引き出しを増やすべきであろうと思う。
アルゴリズムのモデルを構築するのも重要どころではあるが難易度は高いし、
個人で十分なで適当なデータを収集するのもなかなか困難だからである。

画像の学習と同定においては、今回のような文字だけがあるケースよりも、
背景、対象の位置、視点、形状、色などの問題をクリアしなければならない点で考えることが多い。