在Keras中導(dǎo)入測試數(shù)據(jù)的方法

在對Keras框架的學(xué)習(xí)中,一個很大的難點(diǎn)就是數(shù)據(jù)的導(dǎo)入,尤其是當(dāng)數(shù)據(jù)不能一次放入內(nèi)存的時候,應(yīng)該如何導(dǎo)入的問題。在Keras的官網(wǎng),沒有章節(jié)特意講這個內(nèi)容,而專門去找資料,也很難找到相關(guān)的內(nèi)容。絕大多數(shù)的教程都是直接使用的Keras自帶的數(shù)據(jù)集。為了處理大量數(shù)據(jù)的情況,我還特意研究了Python的多線程。后來我還知道了導(dǎo)入數(shù)據(jù)的時候的隨機(jī)性的重要性等各種問題。這篇文章算是一個總結(jié)。

如果看過我前面的文章Keras入門與LeNet的實(shí)現(xiàn),應(yīng)該知道Keras里面有很多經(jīng)典的數(shù)據(jù)集。當(dāng)我們研究自己的模型的時候,只需要拿出其中與我們要研究的問題類似的數(shù)據(jù)集進(jìn)行試驗(yàn)就行了。比如之前提到的手寫數(shù)字識別的經(jīng)典數(shù)據(jù)集mnist。就只需要一行代碼:

(X_train, y_train), (X_test, y_test) = mnist.load_data()

就可以成功導(dǎo)入了。在Keras官網(wǎng)上面有各個常用數(shù)據(jù)庫的導(dǎo)入方法,這使得使用這些經(jīng)典的數(shù)據(jù)庫特別簡單。但是我們使用Keras是不完全是為了研究自己的model,還可能是為了解決實(shí)際問題。這個時候,我們就要創(chuàng)造自己的數(shù)據(jù)集,并且把數(shù)據(jù)集運(yùn)用到自己的模型之中。

創(chuàng)造數(shù)據(jù)集是一件比較難的事情。尤其是要創(chuàng)造大量的、靠譜的數(shù)據(jù)集。通常來說,很多數(shù)據(jù)集只能通過大公司去收集,而不能自己創(chuàng)造。但是凡事總有例外。有的時候我們還是能夠自己創(chuàng)造數(shù)據(jù)集的。例如我們的OCR。我們只需要把字符進(jìn)行變形就可以生成我們的數(shù)據(jù)集了。(當(dāng)然,如果要考慮手寫的順序等問題,這就不夠了。)關(guān)于生成字符識別數(shù)據(jù)集,請參考我之前的文章。

接著,我來說明我查到的第一種運(yùn)用自己的數(shù)據(jù)集的方法。這種方法需要一次性地把數(shù)據(jù)放入內(nèi)存,因此數(shù)據(jù)量不能過大。

原理也很簡單??紤]到Keras的輸入數(shù)據(jù)是numpy、float類型,因此我們只需要把圖片讀入,然后轉(zhuǎn)成numpy就行了。

首先,我們的目錄結(jié)構(gòu)是這樣的:

./words
  ./0
    0.png
    1.png
    2.png
    ...
   /1
     0.png
     1.png
     2.png
      ...
   /2
   ...

先寫一個讀圖片的函數(shù),我們用這個函數(shù)把Image類型轉(zhuǎn)成numpy類型。

def read_image(imageName):
    im = Image.open(imageName).convert('L')
    data = np.array(im)
    return data

我們創(chuàng)造一個images的列表和labels的列表,用來存圖片和對應(yīng)的結(jié)果。接著,我們把圖片和它對應(yīng)的結(jié)果讀入:

# 讀取在words里面有幾個文件夾
text = os.listdir('./words')

# 把文件夾里面的圖片和其對應(yīng)的文件夾的名字也就是對應(yīng)的字
for textPath in text:
    for fn in os.listdir(os.path.join('words', textPath)):
        if fn.endswith('.png'):
            fd = os.path.join('./words', textPath, fn)
            images.append(read_image(fd))
            labels.append(textPath)

接著我們把剛剛得到的images和labels也變成numpy類型。當(dāng)然,labels首先要變成int類型。

X = np.array(images)
y = np.array(list(map(int, labels)))

最后,我們按三七分把這些分為訓(xùn)練集和測試集。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.30, random_state=30)

數(shù)據(jù)的導(dǎo)入工作就完成了。但是顯然,這樣做的話我們必須一次性把所有數(shù)據(jù)讀入內(nèi)存。當(dāng)我們的數(shù)據(jù)量特別大的時候,這肯定是行不通的。就算數(shù)據(jù)量不大,這樣也會浪費(fèi)很多時間在IO上面。我們的希望的是,在訓(xùn)練的時候拿數(shù)據(jù),一份一份地訓(xùn)練。

這是一個很難解決的問題,在網(wǎng)上很難找到對應(yīng)的教程專門提到這一點(diǎn)。大神們都默認(rèn)這是一個很簡單的問題。但是對于新手來說,這卻是一個很難的問題。我甚至考慮過用多線程去寫。但是甚至讀入的數(shù)據(jù)的順序,都會影響最后的結(jié)果。如果我們一批一批地訓(xùn)練,就容易使得最后的結(jié)果偏向我們最后導(dǎo)入的數(shù)據(jù),從而過擬合。最后我在查閱了Keras的官方文檔,并且查看了很多相關(guān)的內(nèi)容之后,找到了解決方法。

原來,Keras的訓(xùn)練不僅僅有fit,還有fit_generator,也就是一個一個訓(xùn)練。fit_generator的API如下:

fit_generator(self, generator, steps_per_epoch, epochs=1, verbose=1, callbacks=None, validation_data=None, validation_steps=None, class_weight=None, max_q_size=10, workers=1, pickle_safe=False, initial_epoch=0)

文檔是這樣寫的:

函數(shù)的參數(shù)是:

  • generator:生成器函數(shù),生成器的輸出應(yīng)該為:
    • 一個形如(inputs,targets)的tuple
    • 一個形如(inputs, targets,sample_weight)的tuple。所有的返回值都應(yīng)該包含相同數(shù)目的樣本。生成器將無限在數(shù)據(jù)集上循環(huán)。每個epoch以經(jīng)過模型的樣本數(shù)達(dá)到samples_per_epoch時,記一個epoch結(jié)束
  • steps_per_epoch:整數(shù),當(dāng)生成器返回steps_per_epoch次數(shù)據(jù)時計一個epoch結(jié)束,執(zhí)行下一個epoch
  • epochs:整數(shù),數(shù)據(jù)迭代的輪數(shù)
  • verbose:日志顯示,0為不在標(biāo)準(zhǔn)輸出流輸出日志信息,1為輸出進(jìn)度條記錄,2為每個epoch輸出一行記錄
  • validation_data:具有以下三種形式之一
    • 生成驗(yàn)證集的生成器
    • 一個形如(inputs,targets)的tuple
    • 一個形如(inputs,targets,sample_weights)的tuple
  • validation_steps: 當(dāng)validation_data為生成器時,本參數(shù)指定驗(yàn)證集的生成器返回次數(shù)
  • class_weight:規(guī)定類別權(quán)重的字典,將類別映射為權(quán)重,常用于處理樣本不均衡問題。
  • sample_weight:權(quán)值的numpy array,用于在訓(xùn)練時調(diào)整損失函數(shù)(僅用于訓(xùn)練)??梢詡鬟f一個1D的與樣本等長的向量用于對樣本進(jìn)行1對1的加權(quán),或者在面對時序數(shù)據(jù)時,傳遞一個的形式為(samples,sequence_length)的矩陣來為每個時間步上的樣本賦不同的權(quán)。這種情況下請確定在編譯模型時添加了sample_weight_mode='temporal'。
  • workers:最大進(jìn)程數(shù)
  • max_q_size:生成器隊列的最大容量
  • pickle_safe: 若為真,則使用基于進(jìn)程的線程。由于該實(shí)現(xiàn)依賴多進(jìn)程,不能傳遞non picklable(無法被pickle序列化)的參數(shù)到生成器中,因?yàn)闊o法輕易將它們傳入子進(jìn)程中。
  • initial_epoch: 從該參數(shù)指定的epoch開始訓(xùn)練,在繼續(xù)之前的訓(xùn)練時有用。

這個里面,只有前面六個是比較重要的,其他的默認(rèn)就行了。甚至我們只需要前面三個就行了。steps_per_epoch和epochs都很好理解。這個generator,也就是生成器函數(shù),才是問題的關(guān)鍵。

接下來就非常簡單了。在keras系列︱利用fit_generator最小化顯存占用比率/數(shù)據(jù)Batch化這篇博客里面已經(jīng)講得很清楚了。

里面有個demo一般的生成器函數(shù):

def generate_batch_data_random(x, y, batch_size):
    """逐步提取batch數(shù)據(jù)到顯存,降低對顯存的占用"""
    ylen = len(y)
    loopcount = ylen // batch_size
    while (True):
        i = randint(0,loopcount)
        yield x[i * batch_size:(i + 1) * batch_size], y[i * batch_size:(i + 1) * batch_size]

這里應(yīng)該注意的有兩點(diǎn),第一點(diǎn)就是數(shù)據(jù)必須是要打亂的,沒有規(guī)律的。第二點(diǎn)就是最后用的是yield。這也是Python的一個高級特性了。簡單地說,這就是一個return。但是你每調(diào)用一次,它就返回一次,而不像其他函數(shù)一樣,return了就出去了。這樣就成為了一個生成器。具體可以看廖雪峰的Python教程

剩下的就很簡單了。我們甚至可以直接在這個生成器函數(shù)里面寫圖片生成的算法。當(dāng)然,考慮到IO操作肯定比直接生成要快,直接生成肯定是不可取的。

當(dāng)然,這不是最好的導(dǎo)入數(shù)據(jù)的方法。Keras還有更快的方法。在介紹這個方法之前,我需要先介紹Keras的圖像預(yù)處理的方法。

為了防止圖像的過擬合,Keras里面自帶了圖片生成器用來對圖像進(jìn)行一些簡單的操作,例如平移,旋轉(zhuǎn),縮放等等。這樣我們就可以在有限的數(shù)據(jù)集上面生成無限的訓(xùn)練樣本。這樣可以擴(kuò)大訓(xùn)練集的大小,防止圖像的過擬合。具體的內(nèi)容可以查看圖片生成器的文章。

關(guān)鍵問題不在于這個圖片生成,而是這個圖片生成器的方法里面提供了一個函數(shù)——flow_from_directory(directory)

這個函數(shù)的參數(shù)如下:

flow_from_directory(directory): 以文件夾路徑為參數(shù),生成經(jīng)過數(shù)據(jù)提升/歸一化后的數(shù)據(jù),在一個無限循環(huán)中無限產(chǎn)生batch數(shù)據(jù)

  • directory: 目標(biāo)文件夾路徑,對于每一個類,該文件夾都要包含一個子文件夾.子文件夾中任何JPG、PNG、BNP、PPM的圖片都會被生成器使用.詳情請查看此腳本
  • target_size: 整數(shù)tuple,默認(rèn)為(256, 256). 圖像將被resize成該尺寸
  • color_mode: 顏色模式,為"grayscale","rgb"之一,默認(rèn)為"rgb".代表這些圖片是否會被轉(zhuǎn)換為單通道或三通道的圖片.
  • classes: 可選參數(shù),為子文件夾的列表,如['dogs','cats']默認(rèn)為None. 若未提供,則該類別列表將從directory下的子文件夾名稱/結(jié)構(gòu)自動推斷。每一個子文件夾都會被認(rèn)為是一個新的類。(類別的順序?qū)凑兆帜副眄樞蛴成涞綐?biāo)簽值)。通過屬性class_indices可獲得文件夾名與類的序號的對應(yīng)字典。
  • class_mode: "categorical", "binary", "sparse"或None之一. 默認(rèn)為"categorical. 該參數(shù)決定了返回的標(biāo)簽數(shù)組的形式, "categorical"會返回2D的one-hot編碼標(biāo)簽,"binary"返回1D的二值標(biāo)簽."sparse"返回1D的整數(shù)標(biāo)簽,如果為None則不返回任何標(biāo)簽, 生成器將僅僅生成batch數(shù)據(jù), 這種情況在使用model.predict_generator()model.evaluate_generator()等函數(shù)時會用到.
  • batch_size: batch數(shù)據(jù)的大小,默認(rèn)32
  • shuffle: 是否打亂數(shù)據(jù),默認(rèn)為True
  • seed: 可選參數(shù),打亂數(shù)據(jù)和進(jìn)行變換時的隨機(jī)數(shù)種子
  • save_to_dir: None或字符串,該參數(shù)能讓你將提升后的圖片保存起來,用以可視化
  • save_prefix:字符串,保存提升后圖片時使用的前綴, 僅當(dāng)設(shè)置了save_to_dir時生效
  • save_format:"png"或"jpeg"之一,指定保存圖片的數(shù)據(jù)格式,默認(rèn)"jpeg"
  • flollow_links: 是否訪問子文件夾中的軟鏈接

這樣,我們導(dǎo)入數(shù)據(jù)就可以直接使用Keras自帶的導(dǎo)入數(shù)據(jù)的方法了,并且附帶了圖片的處理。

我們的代碼可以這樣寫:

datagen = ImageDataGenerator(...)

train_generator = datagen.flow_from_directory(  
    './words',
    target_size=(30, 30),
    color_mode='grayscale',
    batch_size=64)

model.fit_generator(train_generator, steps_per_epoch=500, epochs=50)

這樣我們就導(dǎo)入了數(shù)據(jù)了。自此Keras的簡單使用已經(jīng)不成問題。

最后,我們需要注意,最后的導(dǎo)入數(shù)據(jù)的時候,會自動搜索里面的文件夾,但是是按字典序排序的。這很自然。例如你的文件夾是分類問題,文件夾都是貓、狗、鼠這樣的漢字,它當(dāng)然得按字典序排序。但是如果是像我們用0、1、2、3…這樣的數(shù)字,就容易讓人崩潰。因此我們需要注意在生成文件夾的時候,前面補(bǔ)0,即000、001、003、…、999。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容