1. 程式人生 > >Udacity深度學習DeepLearning課程作業1—notMnist

Udacity深度學習DeepLearning課程作業1—notMnist

前言:Udacity上有一個免費的《深度學習》單項課程。雖然是免費的課程,但保持了Udacity一貫的水準。其課程主要是神經網路的實際應用。因此,該課程能較好的提升實際專案水平。但是,如果需要提升理論水平,則可以同步學習Coursera上hinton的《機器學習》課程,裡面有很深入和系統的講解了深度學習的所有基礎理論。

該課程的入門作業是notMnist字元的分類。其中,notMnist資料是一組按字元分類的影象資料夾,其共有A、B、C、D、E、F、G、H、I、J共10個資料夾。其中,每一張影象均維28*28。完成該作業能夠夯實兩個重要基礎:1是實現了一個分類專案的所有流程;2是實現對資料進行歸一化、隨機化和資料清洗。這是非常關鍵的預處理步驟。其具體的處理方法如下:

預處理方法
對於一組資料,第一步並不是將資料馬上用於訓練模型,而是要對資料進行預處理。預處理的步驟主要包括:
(1)歸一化
影象資料的取值範圍是[0,255],原始的樣本值一般都是過大的。因此,要使用如下公式對影象資料進行歸一化。
x= x128255

樣本數值較大還存在2個不利於學習的影響:1是引數訓練時存在多個樣本的求和,因此,存在資料的截斷以及大小資料相加帶來的偏差。2是樣本值過大影響到模型的初始化。模型的初始化時需要神經網路的分類儘可能的隨機,即logit值越小越好,這樣softmax的值才能趨向與均值。但是,如果樣本的值較大,即使引數w是隨機的小值,但由於x太大,導致logit的值較大,經由softmax計算後的結果趨向於1和0,影響初始化的隨機性。
(2)隨機化
由於最開始的樣本資料是以資料夾區分好類別的,但是,在訓練時,資料需要以隨機化的形式輸入,否則訓練難以穩定。
(3)資料清洗
由於訓練集、驗證集和測試集中可能有資料是重複的。如果重複得太多,那麼最後得分類結果得真實性會受到影響。因此,需要對3個樣本集進行資料得清洗,使其沒有相互沒有交集。

其作業是掛在jupyter上並由多個程式碼塊組成,包括從網頁上下載資料、解壓資料、資料預處理、模型訓練和測試等多個程式碼塊。下面就按照程式碼塊的順序進行講解和完成作業。

程式碼塊1-載入模組

from __future__ import print_function
import matplotlib.pyplot as plt#繪圖模組
import numpy as np#矩陣模組
import os
import sys
import tarfile#檔案解壓模組
from IPython.display import display, Image
from scipy import
ndimage from sklearn.linear_model import LogisticRegression#迴歸模組 from six.moves.urllib.request import urlretrieve#下載模組 from six.moves import cPickle as pickle#壓縮模組 # Config the matplotlib backend as plotting inline in IPython %matplotlib inline

程式碼塊2-下載檔案

注:由於該網路地址下載資料太慢,因此,建議不使用該函式進行資料下載。而是自己將資料下載到本地資料夾中。下載的網址如下:

http://yaroslavvb.com/upload/notMNIST/

程式碼塊3-解壓檔案並存儲解壓後的檔案地址

由於該程式碼塊的解壓速度太慢,因此,利用解壓工具解壓該檔案。並將解壓後的資料夾地址儲存起來,為後續的呼叫做好準備即可。下文程式碼刪除了作業中解壓部分的程式碼,而保留儲存資料夾路徑的程式碼。

num_classes = 10
np.random.seed(133)

#建立每一個類別的資料夾名
def maybe_extract(filename, force=False):
  root = os.path.splitext(os.path.splitext(filename)[0])[0]  # remove .tar.gz
  data_folders = [os.path.join(root, d) for d in sorted(os.listdir(root))
    if os.path.isdir(os.path.join(root, d))]
  if len(data_folders) != num_classes:
    raise Exception(
      'Expected %d folders, one per class. Found %d instead.' % (
        num_classes, len(data_folders)))
  return data_folders

#本地儲存notMnist資料的的資料夾
train_filename = 'C:\\ProgramInstall\\PythonCode\\notMNIST_large'  
test_filename = 'C:\\ProgramInstall\\PythonCode\\notMNIST_small'
train_folders = maybe_extract(train_filename)
test_folders = maybe_extract(test_filename)

問題1-顯示解壓後的影象

#Problem1: Display a sample of the images that we just download
nums_image_show = 2#顯示的影象張數
for index_class in range(num_classes):
    #i from 0 to 9
    imagename_list = os.listdir(train_folders[index_class])
    imagename_list_indice = imagename_list[0:nums_image_show]
    for index_image in range(nums_image_show):
        path = train_folders[index_class] +'\\' + imagename_list_indice[index_image]
        display(Image(filename = path))

其結果如下圖所示:
這裡寫圖片描述

程式碼塊4-載入和歸一化影象資料

該程式碼塊主要實現了3個功能:1是將本地硬碟中的每類影象資料夾中的影象資料讀到一個3維的dataset物件中,第1維是影象個數索引,其餘2維則是影象資料。其中主要是利用了scipy模組中的ndarray物件兌取硬碟中的影象資料。2是將讀取到的影象資料按照上文所述的公式進行了歸一化。3是將ndarray物件打包為pickle格式並存儲在工作目錄下,每個類別有一個.pickle檔案。並將打包後.pickle檔案的地址儲存為train_datasets和test_datasets返回。

注:將資料打包為.pickle檔案更便於資料的呼叫與處理。因為,影象的原始資料是使用迴圈打入到物件中的,如果每次使用影象資料均需要迴圈來載入,這樣加大了程式碼量。而對.pickle檔案只需要讀取一次即可,而無需使用迴圈。

問題2 顯示從pickle檔案中讀取的影象

#Problem2 Displaying a sample of the labels and images from the ndarray

# Config the matplotlib backend as plotting inline in IPython
%matplotlib inline
import matplotlib.pyplot as plt
def load_and_displayImage_from_pickle(data_filename_set,NumClass,NumImage):
    if(NumImage <= 0):
        print('NumImage <= 0')
        return
    plt.figure('subplot')
    for index,pickle_file in enumerate(data_filename_set):
        with open(pickle_file,'rb') as f:
            data = pickle.load(f)
            ImageList = data[0:NumImage,:,:]
            for i,Image in enumerate(ImageList):
                #NumClass代表類別,每個類別一行;NumImage代表每個類顯示的影象張數
                plt.subplot(NumClass, NumImage, index*NumImage+i+1)
                plt.imshow(Image)
            index = index+1        
#顯示10類,每類顯示5張圖片        
load_and_displayImage_from_pickle(train_datasets,10,5)    
load_and_displayImage_from_pickle(test_datasets,10,5) 

其結果如下圖所示:
這裡寫圖片描述

問題3-檢測資料是否平衡

資料是否平衡的意思是各類樣本的大小是否相當。

def show_sum_of_different_class(data_filename_set):
    plt.figure(1)
    #read .pickle file
    sumofdifferentclass = []
    for pickle_file in data_filename_set:
        with open(pickle_file,'rb') as f:
            data = pickle.load(f)
            print(len(data))
            sumofdifferentclass.append(len(data))

    #show the data
    x = range(10)
    plt.bar(x,sumofdifferentclass)    
    plt.show()

print('train_datasets:\n')    
show_sum_of_different_class(train_datasets)  
print('test_datasets:\n')    
show_sum_of_different_class(test_datasets) 

其結果如下圖所示:
這裡寫圖片描述

程式碼塊5-將不同類別的資料混合並將得到驗證集

該模組實現了2個功能:1是將不同類別的資料進行混合。之前是每個類別一個數據物件。現在,為了便於後續的訓練,需將不同類別的資料儲存為一個大的資料物件,即該物件同時包含A、B…J共個類別的樣本。2是從訓練集中提取一部分作為驗證集。

程式碼塊6-將混合後的資料進行隨機化

上一步只是將資料進行和混合並存儲為一個大的資料物件,此步則將混合後的資料物件中的資料進行了隨機化處理。只有隨機化後的資料訓練模型時才會有較為穩定的效果。

問題4 從驗證混合後的資料

'''Problem4 Convince yourself that the data is still good after shuffling!
'''
#data_set是資料集,NumImage是顯示的影象張數
def displayImage_from_dataset(data_set,NumImage):
    if(NumImage <= 0):
        print('NumImage <= 0')
        return
    plt.figure('subplot')
    ImageList = data_set[0:NumImage,:,:]
    for index,Image in enumerate(ImageList):
        #NumClass代表類別,每個類別一行;NumImage代表每個類顯示的影象張數
        plt.subplot(NumImage//5+1, 5, index+1)
        plt.imshow(Image)
        index = index+1    
    plt.show()
displayImage_from_dataset(train_dat```
set,50)   

其結果如下圖所示,下圖也表明影象資料確實是不同類別隨機分佈的。
這裡寫圖片描述

程式碼塊7-將不同的樣本及存為.pickle檔案

問題5-資料清洗

一般來說,訓練集、驗證集和測試集中會有資料的重合,但是,如果重合的資料太多則會影響到測試結果的準確程度。因此,需要對資料進行清洗,使彼此之間步存在交集。

注:ndarray資料無法使用set的方式來求取交集。但如果使用迴圈對比的方式在資料量大的情況下會非常慢,因此,下文的做法使先將資料雜湊化,再通過雜湊的鍵值來判斷資料是否相等。由於雜湊的鍵值是字串,因此比對起來效率會高很多。

#先使用hash
import hashlib

#使用sha的作用是將二維資料和雜湊值之間進行一一對應,這樣,通過比較雜湊值就能將二維陣列是否相等比較出來
def extract_overlap_hash_where(dataset_1,dataset_2):

    dataset_hash_1 = np.array([hashlib.sha256(img).hexdigest() for img in dataset_1])
    dataset_hash_2 = np.array([hashlib.sha256(img).hexdigest() for img in dataset_2])
    overlap = {}
    for i, hash1 in enumerate(dataset_hash_1):
        duplicates = np.where(dataset_hash_2 == hash1)
        if len(duplicates[0]):
            overlap[i] = duplicates[0]
    return overlap

#display the overlap
def display_overlap(overlap,source_dataset,target_dataset):
    overlap = {k: v for k,v in overlap.items() if len(v) >= 3}
    item = np.random.choice(list(overlap.keys()))
    imgs = np.concatenate(([source_dataset[item]],target_dataset[overlap[item][0:7]]))
    plt.suptitle(item)
    for i,img in enumerate(imgs):
        plt.subplot(2,4,i+1)
        plt.axis('off')
        plt.imshow(img)
    plt.show()

#資料清洗
def sanitize(dataset_1,dataset_2,labels_1):
    dataset_hash_1 = np.array([hashlib.sha256(img).hexdigest() for img in dataset_1])
    dataset_hash_2 = np.array([hashlib.sha256(img).hexdigest() for img in dataset_2])
    overlap = []
    for i,hash1 in enumerate(dataset_hash_1):
        duplictes = np.where(dataset_hash_2 == hash1)
        if len(duplictes[0]):
            overlap.append(i)
    return np.delete(dataset_1,overlap,0),np.delete(labels_1, overlap, None)


overlap_test_train = extract_overlap_hash_where(test_dataset,train_dataset)
print('Number of overlaps:', len(overlap_test_train.keys()))
display_overlap(overlap_test_train, test_dataset, train_dataset)

test_dataset_sanit,test_labels_sanit = sanitize(test_dataset,train_dataset,test_labels)
print('Overlapping images removed from test_dataset: ', len(test_dataset) - len(test_dataset_sanit))

valid_dataset_sanit, valid_labels_sanit = sanitize(valid_dataset, train_dataset, valid_labels)
print('Overlapping images removed from valid_dataset: ', len(valid_dataset) - len(valid_dataset_sanit))

print('Training:', train_dataset.shape, train_labels.shape)
print('Validation:', valid_labels_sanit.shape, valid_labels_sanit.shape)
print('Testing:', test_dataset_sanit.shape, test_labels_sanit.shape)

pickle_file_sanit = 'notMNIST_sanit.pickle'
try:
    f = open(pickle_file_sanit,'wb')
    save = {
        'train_dataset':train_dataset,
        'train_labels': train_labels,
        'valid_dataset': valid_dataset,
        'valid_labels': valid_labels,
        'test_dataset': test_dataset,
        'test_labels': test_labels,
    }
    pickle.dump(save,f,pickle.HIGHEST_PROTOCOL)
    f.close()
except Exception as e:
  print('Unable to save data to', pickle_file, ':', e)
  raise

statinfo = os.stat(pickle_file_sanit)
print('Compressed pickle size:', statinfo.st_size)

問題6-模型訓練

該模型是使用邏輯迴歸模型進行的訓練。

def train_and_predict(sample_size):
    regr = LogisticRegression()
    X_train = train_dataset[:sample_size].reshape(sample_size,784)
    y_train = train_labels[:sample_size]
    regr.fit(X_train,y_train)
    X_test = test_dataset.reshape(test_dataset.shape[0],28*28)
    y_test = test_labels

    pred_labels = regr.predict(X_test)
    print('Accuracy:', regr.score(X_test, y_test), 'when sample_size=', sample_size)

for sample_size in [50,100,1000,5000,len(train_dataset)]:
    train_and_predict(sample_size)

其使用不同大小的訓練集測試的結果如下:
這裡寫圖片描述