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=
樣本數值較大還存在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)
其使用不同大小的訓練集測試的結果如下: