Keras初探(二)——識(shí)別驗(yàn)證碼

繼上篇對于Keras的初步探討之后,我將給出一個(gè)例子講解如何利用Keras用于處理圖像分類問題居兆,今天我們先探討一下識(shí)別驗(yàn)證碼的問題覆山。

一、探討內(nèi)容

1泥栖、數(shù)據(jù)來源
2簇宽、模型搭建
3勋篓、優(yōu)化問題

二、數(shù)據(jù)來源

在本文中魏割,我打算對驗(yàn)證碼進(jìn)行識(shí)別譬嚣,有一個(gè)python包——captcha,利用它可生成驗(yàn)證碼钞它。當(dāng)然使用前需要

先導(dǎo)入相關(guān)packages拜银。
```import cv2
import numpy as np
from captcha.image import ImageCaptcha

這里可以設(shè)置驗(yàn)證碼的大小為28*28,字體大小24须揣。比如下面兩張圖片盐股,第一張是5钱豁,第二張是6耻卡。干擾相對較大。


image1_5.jpg
image2_6.jpg

下面給出完整代碼

import cv2
import numpy as np
from captcha.image import ImageCaptcha

def generate_captcha(text):
    
    capt= ImageCaptcha(width=28,height=28,font_sizes = [24])
    image = capt.generate_image(text)
    image = np.array(image,dtype=np.uint8)
    return image

if __name__ == '__main__':
    output_dir = './datasets/images/'
    for i in range(5000):
        label = np.random.randint(0,10)
        image = generate_captcha(str(label))
        image_name = 'image{}_{}.jpg'.format(i+1,label)
        output_path = output_dir +image_name
        cv2.imwrite(output_path,image)

保存文件為gendata.py牲尺,運(yùn)行文件后生成5000張驗(yàn)證碼圖片卵酪。這里只是實(shí)驗(yàn)性質(zhì),所以驗(yàn)證碼圖片數(shù)量較少谤碳,大家自己做實(shí)驗(yàn)的時(shí)候可以適當(dāng)增加一些圖片數(shù)量溃卡。

三、模型搭建

一開始我們可以搭建一個(gè)非常簡單的LeNet來進(jìn)行驗(yàn)證和測試蜒简。保存下列文件命名為lenet.py

# import the necessary packages
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras import backend as K
 
class LeNet:
    @staticmethod
    def build(width, height, depth, classes):
        # initialize the model
        model = Sequential()
        inputShape = (height, width, depth)
        # if we are using "channels last", update the input shape
        if K.image_data_format() == "channels_first":   #for tensorflow
            inputShape = (depth, height, width)
        # first set of CONV => RELU => POOL layers
        model.add(Conv2D(20, (5, 5),padding="same",input_shape=inputShape))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        #second set of CONV => RELU => POOL layers
        model.add(Conv2D(50, (5, 5), padding="same"))
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
        # first (and only) set of FC => RELU layers
        model.add(Flatten())
        model.add(Dense(500))
        model.add(Activation("relu"))

        # softmax classifier
        model.add(Dense(classes))
        model.add(Activation("softmax"))

        # return the constructed network architecture
        return model

接著我們加載數(shù)據(jù)瘸羡,每張圖片對應(yīng)的數(shù)字是放在文件名'_'之后。

def get_data(images_path):
    if not os.path.exists(images_path):
        raise ValueError('images_path is not exist.')

    images = []
    labels = []
    images_path = os.path.join(images_path,'*.jpg')
    count = 0
    for image_file in glob.glob(images_path):
        count +=1
        if count % 100 == 0:
            print('Load{} images .'.format(count))
        image = cv2.imread(image_file)
        image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (norm_size, norm_size))
        label = int(image_file.split('_')[-1].split('.')[0])
        images.append(image)
        labels.append(label)
    images = np.array(images)
    labels = np.array(labels)

    (trainX, testX, trainY, testY) = train_test_split(images,
            labels, test_size=0.25, random_state=42)

    # convert the labels from integers to vectors
    trainY = to_categorical(trainY, num_classes=CLASS_NUM)
    testY = to_categorical(testY, num_classes=CLASS_NUM)   
    return trainX,trainY,testX,testY

經(jīng)過處理我們得到訓(xùn)練集和測試集搓茬。我們先放出來完整代碼train.py犹赖,然后我們在代碼基礎(chǔ)上加以修改。運(yùn)行命令如下

python3 train.py -d images/ -m my.model

其中images/ 為驗(yàn)證碼存放目錄卷仑,my.model為模型保存位置峻村。

import matplotlib
matplotlib.use("Agg")
 
# import the necessary packages
import glob
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from sklearn.model_selection import train_test_split
from keras.preprocessing.image import img_to_array
from keras.utils import to_categorical
#from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import cv2
import os
import sys
sys.path.append('..')
from lenet import LeNet



def args_parse():
    # construct the argument parse and parse the arguments
    ap = argparse.ArgumentParser()
    ap.add_argument("-d", "--dataset", required=True,
        help="path to input dataset")
    ap.add_argument("-m", "--model", required=True,
        help="path to output model")
    ap.add_argument("-p", "--plot", type=str, default="plot.png",
        help="path to output accuracy/loss plot")
    args = vars(ap.parse_args()) 
    return args


args = args_parse()

# initialize the number of epochs to train for, initial learning rate,
# and batch size
EPOCHS = 200
INIT_LR = 1e-2
BS = 128
CLASS_NUM = 10
norm_size = 32
# initialize the data and labels

def get_data(images_path):
    if not os.path.exists(images_path):
        raise ValueError('images_path is not exist.')

    images = []
    labels = []
    images_path = os.path.join(images_path,'*.jpg')
    count = 0
    for image_file in glob.glob(images_path):
        count +=1
        if count % 100 == 0:
            print('Load{} images .'.format(count))
        image = cv2.imread(image_file)
        image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
        image = cv2.resize(image, (norm_size, norm_size))
        label = int(image_file.split('_')[-1].split('.')[0])
        images.append(image)
        labels.append(label)
    images = np.array(images)
    labels = np.array(labels)

    (trainX, testX, trainY, testY) = train_test_split(images,
            labels, test_size=0.25, random_state=42)

    # convert the labels from integers to vectors
    trainY = to_categorical(trainY, num_classes=CLASS_NUM)
    testY = to_categorical(testY, num_classes=CLASS_NUM)   
    return trainX,trainY,testX,testY

def train(aug,trainX,trainY,testX,testY,args):
    # initialize the model
    print("[INFO] compiling model...")
    model = LeNet.build(width=norm_size, height=norm_size, depth=3, classes=CLASS_NUM)
    opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
#    opt = Adam(lr=INIT_LR)
    model.compile(loss="categorical_crossentropy", optimizer=opt,
        metrics=["accuracy"])

    # train the network
    print("[INFO] training network...")
    H = model.fit_generator(aug.flow(trainX, trainY, batch_size=BS),
        validation_data=(testX, testY), steps_per_epoch=len(trainX) // BS,
        epochs=EPOCHS, verbose=1)

    # save the model to disk
    print("[INFO] serializing network...")
    model.save(args["model"])
    
    # plot the training loss and accuracy
    plt.style.use("ggplot")
    plt.figure()
    N = EPOCHS
    plt.plot(np.arange(0, N), H.history["loss"], label="train_loss")
    plt.plot(np.arange(0, N), H.history["val_loss"], label="val_loss")
    plt.plot(np.arange(0, N), H.history["acc"], label="train_acc")
    plt.plot(np.arange(0, N), H.history["val_acc"], label="val_acc")
    plt.title("Training Loss and Accuracy on Invoice classifier")
    plt.xlabel("Epoch #")
    plt.ylabel("Loss/Accuracy")
    plt.legend(loc="lower left")
    plt.savefig(args["plot"])
    
#python train.py --dataset ../../invoice_all/train  --model invoice.model
if __name__=='__main__':
    args = args_parse()
    file_path = args["dataset"]
    trainX,trainY,testX,testY = get_data(file_path)
    # construct the image generator for data augmentation
    aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,
        height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
        horizontal_flip=True, fill_mode="nearest")
    train(aug,trainX,trainY,testX,testY,args)

四、優(yōu)化模型

我們可以根據(jù)每次生成的圖片觀察訓(xùn)練效果锡凝,這張圖是已經(jīng)經(jīng)過若干次修改后的結(jié)果粘昨,正確率大概為0.80,從下圖可以看到val_loss的抖動(dòng)還是比較大窜锯,這是由于兩個(gè)原因:一是初始的學(xué)習(xí)率比較大,二是因?yàn)樵诒纠形也捎昧薲ropout张肾,而dropoutrate設(shè)置得太高了(0.25)所以我們需要修改。
plot.png

4.1 采用BatchNormalization

BatchNormalization()真是非常好用锚扎,把它放在卷積層和池化層之間能非常有效地提升性能吞瞪。

model.add(Conv2D(30, (2, 2), padding="same"))
        model.add(BatchNormalization())
        model.add(Activation("relu"))
        model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))

4.2 學(xué)習(xí)率衰減策略

其實(shí)我們在一開始嘗試的時(shí)候完全沒必要設(shè)置學(xué)習(xí)率衰減策略。我們大可以嘗試使用或大或小的學(xué)習(xí)率觀察結(jié)果工秩。隨后我們可以讓學(xué)習(xí)率隨輪數(shù)衰減尸饺,以達(dá)到微調(diào)的效果进统。

    opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
#    opt = Adam(lr=INIT_LR)
    model.compile(loss="categorical_crossentropy", optimizer=opt,
        metrics=["accuracy"])

4.3 dropout

使用了batchnormal再使用dropout效果可能不太明顯。我們可以在最后的全連接層處使用dropout浪听,在卷積層中間使用dropout會(huì)導(dǎo)致結(jié)果不可預(yù)測螟碎。

        model.add(Dense(200))
        model.add(Dropout(droprate))

4.4 數(shù)據(jù)擴(kuò)充

我們把圖片變形扭曲增加數(shù)據(jù)源

aug = ImageDataGenerator(rotation_range=30, width_shift_range=0.1,
        height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
        horizontal_flip=True, fill_mode="nearest")

現(xiàn)在的結(jié)果如下圖所示,由于訓(xùn)練輪數(shù)(200)不是特別多迹栓,所以效果還不是很好正確率大概在85%掉分。有興趣的朋友可以在此基礎(chǔ)上加以修改一下。

plot.png

完整代碼參見
code

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末克伊,一起剝皮案震驚了整個(gè)濱河市酥郭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愿吹,老刑警劉巖不从,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異犁跪,居然都是意外死亡椿息,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門坷衍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寝优,“玉大人,你說我怎么就攤上這事枫耳》Ψ” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵迁杨,是天一觀的道長钻心。 經(jīng)常有香客問我,道長仑最,這世上最難降的妖魔是什么扔役? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮警医,結(jié)果婚禮上亿胸,老公的妹妹穿的比我還像新娘。我一直安慰自己预皇,他們只是感情好侈玄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吟温,像睡著了一般序仙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲁豪,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天潘悼,我揣著相機(jī)與錄音律秃,去河邊找鬼。 笑死治唤,一個(gè)胖子當(dāng)著我的面吹牛棒动,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宾添,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼船惨,長吁一口氣:“原來是場噩夢啊……” “哼缕陕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起扛邑,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鹿榜,沒想到半個(gè)月后海雪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锦爵,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡舱殿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了险掀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沪袭。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖樟氢,靈堂內(nèi)的尸體忽然破棺而出冈绊,到底是詐尸還是另有隱情,我是刑警寧澤埠啃,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布死宣,位于F島的核電站,受9級(jí)特大地震影響碴开,放射性物質(zhì)發(fā)生泄漏毅该。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一潦牛、第九天 我趴在偏房一處隱蔽的房頂上張望眶掌。 院中可真熱鬧,春花似錦巴碗、人聲如沸朴爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽召噩。三九已至母赵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間具滴,已是汗流浹背市咽。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抵蚊,地道東北人施绎。 一個(gè)月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像贞绳,于是被迫代替她去往敵國和親谷醉。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容