數字營銷是指在數字平臺上推廣服務和產品。廣告技術(通常簡寫為“ad tech”)是指供應商、品牌及其代理機構使用數字技術來定位潛在客戶,提供個性化資訊和產品,並分析線上花費帶來的效果。
例如,贊助的故事在Facebook新聞傳播裡的傳播;在Instagram裡的故事量;在YouTube的影片內容開始前播放的廣告;由Outbrain支援的美國有線電視新聞網文章末尾的建議連結,所有這些都是實際使用廣告技術的案例。
在過去的一年裡,深度學習在數字營銷和廣告技術中得到了顯著地應用。
在這篇文章中,我們將深入探討一個流行的應用場景的一部分:挖掘網路名人認可的商品。在此過程中,我們將能瞭解深度學習架構的相對價值,進行實驗,理解資料量大小的影響,以及在缺乏足夠資料時如何增強資料等內容。
應用場景概述
在本文中,我們將看到如何建立一個深度學習分類器,該分類器可以根據帶有商標的圖片來預測該商品所對應的公司。本節概述了可以使用此模型的場景。
名人會認可一些產品。通常,他們會在社交媒體上釋出圖片來炫耀他們認可的品牌。典型帖子會包含一張圖片,其中有名人自己和他們寫的一些文字。相對應的,品牌的擁有者也渴望瞭解這些帖子的裡他們品牌的展現,並向可能受到影響的潛在客戶展示它們。
因此,這一廣告技術應用的工作流程如下:將大量的帖子輸入處理程式以找出名人、品牌和文字內容。然後,對於每個潛在客戶,機器學習模型會根據時間、地點、訊息、品牌以及客戶的偏好品牌和其他內容生成非常獨特的廣告。另外一個模型則進行目標客戶群的檢測。隨後進行目標廣告的傳送。
下圖顯示了這一工作流程:
名人品牌認可機器人的工作流程。圖片由Tuhin Sharma提供
如你所見,該系統由多個機器學習模型組成。
考慮一下上面所說的影像。這些照片可以是在任何情況下拍攝的。因此首要目標就是確定照片中的物體和名人。這可以透過物體檢測模型完成。然後,下一步是識別品牌(如果有的話)。而識別品牌最簡單的方法就是透過識別它的商標。
在本文中,我們將研究如何構建一個深度學習模型來透過影像中的商標識別品牌。後續的文章將討論構建機器人的其他部分(物體檢測、文字生成等)。
問題定義
本文中要解決的問題是:給定一張圖片,透過標識圖片裡的商標來預測圖片對應的公司(品牌)。
資料
要構建機器學習模型,獲取高質量資料集是必須的。在現實業務中,資料科學家會與品牌經理和代理商合作來獲得所有可能的商標。
為了本文的目的,我們將利用FlickrLogo資料集。該資料集有來自Flickr(一個流行的照片分享網站)的真實圖片。FlickrLogo頁面上有關於如何下載資料的說明。如果你想使用本文中的程式碼構建自己的模型,請自行下載資料。
模型
從商標標識別品牌是一個經典的計算機視覺問題。在過去的幾年中,深度學習已成為解決計算機視覺問題的最新技術。因此我們將為我們的場景構建深度學習模型。
軟體
在之前的文章中,我們談到了Apache MXNet的優點。我們還談到了Gluon這一基於MXNet的更簡單的介面。兩者都非常強大,並允許深度學習工程師快速嘗試各種模型架構。
現在讓我們來看看程式碼。
我們首先匯入構建模型所需的庫:
import mxnet as mx import cv2 from pathlib import Path import os from time import time import shutil import matplotlib.pyplot as plt %matplotlib inline
我們使用FlickrLogos資料集裡的FlickrLogos-32資料集。變數<flickrlogos-url>是這個資料集的URL。
%%capture !wget -nc <flickrlogos-url> # Replace with the URL to the dataset !unzip -n ./FlickrLogos-32_dataset_v2.zip
資料準備
接著是建立下述的資料集:
Train (訓練資料集)
Validation (驗證資料集)
Test (測試資料集)
FlickrLogos資料已經分好了訓練、驗證和測試資料集。下面是資料裡圖片的資訊。
訓練資料集包括32個類別,每個類別有10張圖片。
驗證資料集裡有3960張圖片,其中3000張沒有包含商標。
測試資料有3960張圖片。
所有的訓練資料圖片都包含有商標,但有些驗證和測試資料裡的圖片沒有包含商標。我們是希望構建一個有比較好泛化能力的模型。即我們的模型可以準確地預測它沒有見過的圖片(驗證和測試的圖片)。
為了讓我們的訓練更快速、準確,我們將把50%的沒有商標的圖片從驗證資料集移到訓練資料集。這樣我們製作出大小為1820的訓練資料集(在從驗證資料集新增1500個無商標影像之後),並將驗證資料集減少到2460張(在移出1500個無商標影像之後)。在現實生活中,我們應該嘗試使用不同的模型架構來選擇一個在實際驗證和測試資料集上表現良好的模型架構。
下一步,我們定義儲存資料的目錄。
data_directory = “./FlickrLogos-v2/”
現在定義訓練、測試和驗證資料列表的路徑。對於驗證目錄,我們定義兩個路徑:一個存放包含商標的圖片,另外一個用於沒有商標的圖片。
train_logos_list_filename = data_directory+”trainset.relpaths.txt” val_logos_list_filename = data_directory+”valset-logosonly.relpaths.txt” val_nonlogos_list_filename = data_directory+”valset-nologos.relpaths.txt” test_list_filename = data_directory+”testset.relpaths.txt”
讓我們從上面定義的列表裡面讀入訓練、測試和驗證(帶有商標和無商標的)資料檔名。
從FlickrLogo資料集讀入的列表已經被按照訓練、測試和驗證(包含和未包含商標)進行了分類。
# List of train images with open(train_logos_list_filename) as f: train_logos_filename = f.read().splitlines() # List of validation images without logos with open(val_nonlogos_list_filename) as f: val_nonlogos_filename = f.read().splitlines() # List of validation images with logos with open(val_logos_list_filename) as f: val_logos_filename = f.read().splitlines() # List of test images with open(test_list_filename) as f: test_filenames = f.read().splitlines()
現在讓我們把一些沒有商標的驗證圖片移動到訓練集裡面去。這樣就讓訓練資料裡包含了原來所有的圖片,外加上來自驗證資料裡50%的沒有商標的圖片。而驗證資料集現在只包含原有的所有帶有商標的圖片和剩下50%沒有商標的圖片。
train_filenames = train_logos_filename + val_nonlogos_filename[0:int(len(val_nonlogos_filename)/2)] val_filenames = val_logos_filename + val_nonlogos_filename[int(len(val_nonlogos_filename)/2):]
為了驗證我們的資料準備結果是對的,讓我們列印訓練、測試和驗證資料集裡的圖片數量。
print(“Number of Training Images : “,len(train_filenames)) print(“Number of Validation Images : “,len(val_filenames)) print(“Number of Testing Images : “,len(test_filenames))
資料準備過程的下一步是設定一種目錄結構來讓模型的訓練過程更容易一些。
我們需要目錄的結構和下圖裡的類似。
資料的目錄結構。圖片由Tuhin Sharma提供
下面這個函式能幫助我們建立這個目錄結構。
def prepare_datesets(base_directory,filenames,dest_folder_name): for filename in filenames: image_src_path = base_directory+filename image_dest_path = image_src_path.replace(‘classes/jpg’,dest_folder_name) dest_directory_path = Path(os.path.dirname(image_dest_path)) dest_directory_path.mkdir(parents=True,exist_ok=True) shutil.copy2(image_src_path, image_dest_path)
使用這個函式來建立訓練、驗證和測試目錄,並把圖片按照它們的相應的類別放到目錄裡面。
prepare_datesets(base_directory=data_directory,filenames=train_filenames,dest_folder_name=’train_data’) prepare_datesets(base_directory=data_directory,filenames=val_filenames,dest_folder_name=’val_data’) prepare_datesets(base_directory=data_directory,filenames=test_filenames,dest_folder_name=’test_data’)
接下來是定義模型所用的超引數。
我們將會有33個類別(32種商標和1個無商標)。這個資料量並不大,所以我們將只會使用一個GPU。我們將會訓練20個週期,並使用40作為訓練批次的大小。
batch_size = 40 num_classes = 33 num_epochs = 20 num_gpu = 1 ctx = [mx.gpu(i) for i in range(num_gpu)]
資料預處理
在圖片被匯入後,我們需要確保圖片的尺寸是一致的。我們會把圖片重縮放成224*224畫素大小。
我們有1820張訓練圖片,但並不算很多。有沒有一個好的辦法來獲得更多的資料?確實是有的。一張圖片在被翻轉後依然是表示相同的事物,至少商標還是一樣的。被隨機剪裁的商標還依然是同一個商標。
因此,我們沒有必要為訓練來找更多的圖片。而是把現有的圖片透過翻轉和剪下進行一定的變形來獲得更多的資料。這同時這還能幫助讓模型更加魯棒。
讓我們把50%的訓練資料上下翻轉,並把它們剪下成224*224畫素大小。
train_augs = [ mx.image.HorizontalFlipAug(.5), mx.image.RandomCropAug((224,224)) ]
對於驗證和測試資料,讓我們按中心點剪下圖片成224*224畫素大小。現在所有的訓練、測試和驗證資料集都是224*224畫素大了。
val_test_augs = [ mx.image.CenterCropAug((224,224)) ]
為了實現對於圖片的轉換,我們定義了transform函式。這個函式按照輸入的資料和增強的型別,對資料進行變換,以更新資料集。
def transform(data, label, augs): data = data.astype(‘float32’) for aug in augs: data = aug(data) # from (H x W x c) to (c x H x W) data = mx.nd.transpose(data, (2,0,1)) return data, mx.nd.array([label]).asscalar().astype(‘float32’)
Gluon庫有一個工具函式可以從檔案裡匯入圖片:
mx.gluon.data.vision.ImageFolderDataset
這個函式需要資料按照圖2所示的目錄結構來存放。
這個函式接收如下的引數:
資料儲存的根目錄路徑
一個是否需要把圖片轉換成灰度圖或是彩色圖(彩色是預設選項)的標記
一個函式來接收資料(圖片)和它的標籤,並將圖片轉換。
下面的程式碼展示了在匯入資料後如何對其進行轉換:
train_imgs = mx.gluon.data.vision.ImageFolderDataset( data_directory+’train_data’, transform=lambda X, y: transform(X, y, train_augs))
相同的,對於驗證和測試資料集,在匯入後也會進行相應的轉換。
val_imgs = mx.gluon.data.vision.ImageFolderDataset( data_directory+’val_data’, transform=lambda X, y: transform(X, y, val_test_augs)) test_imgs = mx.gluon.data.vision.ImageFolderDataset( data_directory+’test_data’, transform=lambda X, y: transform(X, y, val_test_augs))
DataLoader是一個內建的工具函式來從資料集裡匯入資料,並返回迷你批次的資料。在上述步驟裡,我們已經定義了訓練、驗證和測試資料集(train_imgs、val_imgs、test_imgs相應的)。
num_workers屬性讓我們可以指定為資料預處理所需的多程式工作器的個數。
train_data = mx.gluon.data.DataLoader(train_imgs, batch_size,num_workers=1, shuffle=True) val_data = mx.gluon.data.DataLoader(val_imgs, batch_size, num_workers=1) test_data = mx.gluon.data.DataLoader(test_imgs, batch_size, num_workers=1)
現在資料已經匯入了,來讓我們看一看吧。讓我們寫一個叫show_images的工具函式來以網格形式顯示圖片。
def show_images(imgs, nrows, ncols, figsize=None): “””plot a grid of images””” figsize = (ncols, nrows) _, figs = plt.subplots(nrows, ncols, figsize=figsize) for i in range(nrows): for j in range(ncols): figs[i][j].imshow(imgs[i*ncols+j].asnumpy()) figs[i][j].axes.get_xaxis().set_visible(False) figs[i][j].axes.get_yaxis().set_visible(False) plt.show()
現在,用4行8列的形式來展示前32張圖片。
for X, _ in train_data: # from (B x c x H x W) to (Bx H x W x c) X = X.transpose((0,2,3,1)).clip(0,255)/255 show_images(X, 4, 8) break
進行變形後的圖片的網格化展示。圖片由Tuhin Sharma提供
上面程式碼的執行結果如圖3所示。一些圖片看起來是含有商標的,不過也經常被切掉了一部分。
用於訓練的工具函式
本節內,我們會定義如下一些函式:
在當前處理的批次裡獲取資料
評估模型的準確度
訓練模型
給定URL,獲取圖片資料
對給定的圖片,預測圖片的標籤
第一個函式_get_batch會返回指定批次的資料和標籤。
def _get_batch(batch, ctx): “””return data and label on ctx””” data, label = batch return (mx.gluon.utils.split_and_load(data, ctx), mx.gluon.utils.split_and_load(label, ctx), data.shape[0])
函式evaluate_accuracy會返回模型的分類準確度。針對本文的目的,我們這裡選擇了一個簡單的準確度指標。在實際專案裡,準確度指標需要根據應用的需求來設定。
def evaluate_accuracy(data_iterator, net, ctx): acc = mx.nd.array([0]) n = 0. for batch in data_iterator: data, label, batch_size = _get_batch(batch, ctx) for X, y in zip(data, label): acc += mx.nd.sum(net(X).argmax(axis=1)==y).copyto(mx.cpu()) n += y.size acc.wait_to_read() return acc.asscalar() / n
下一個定義的函式是train函式。這是到目前為止我們要建立的最大的函式。
根據給出的模型、訓練、測試和驗證資料集,模型被按照指定的週期數訓練。在之前的一篇文章裡,我們對這個函式如何運作進行了更詳細的介紹。
一旦在驗證資料集上獲得了最佳的準確度,這個模型在此檢查點的結果會被存下來。在每個週期裡,在訓練、驗證和測試資料集上的準確度都會被列印出來。
def train(net, ctx, train_data, val_data, test_data, batch_size, num_epochs, model_prefix, hybridize=False, learning_rate=0.01, wd=0.001): net.collect_params().reset_ctx(ctx) if hybridize == True: net.hybridize() loss = mx.gluon.loss.SoftmaxCrossEntropyLoss() trainer = mx.gluon.Trainer(net.collect_params(), ‘sgd’, { ‘learning_rate’: learning_rate, ‘wd’: wd}) best_epoch = -1 best_acc = 0.0 if isinstance(ctx, mx.Context): ctx = [ctx] for epoch in range(num_epochs): train_loss, train_acc, n = 0.0, 0.0, 0.0 start = time() for i, batch in enumerate(train_data): data, label, batch_size = _get_batch(batch, ctx) losses = [] with mx.autograd.record(): outputs = [net(X) for X in data] losses = [loss(yhat, y) for yhat, y in zip(outputs, label)] for l in losses: l.backward() train_loss += sum([l.sum().asscalar() for l in losses]) trainer.step(batch_size) n += batch_size train_acc = evaluate_accuracy(train_data, net, ctx) val_acc = evaluate_accuracy(val_data, net, ctx) test_acc = evaluate_accuracy(test_data, net, ctx) print(“Epoch %d. Loss: %.3f, Train acc %.2f, Val acc %.2f, Test acc %.2f, Time %.1f sec” % ( epoch, train_loss/n, train_acc, val_acc, test_acc, time() – start )) if val_acc > best_acc: best_acc = val_acc if best_epoch!=-1: print(‘Deleting previous checkpoint…’) os.remove(model_prefix+’-%d.params’%(best_epoch)) best_epoch = epoch print(‘Best validation accuracy found. Checkpointing…’) net.collect_params().save(model_prefix+’-%d.params’%(epoch))
函式get_image會根據給定的URL返回一個圖片。這個函式可以用來測試模型的準確度。
def get_image(url, show=False): # download and show the image fname = mx.test_utils.download(url) img = cv2.cvtColor(cv2.imread(fname), cv2.COLOR_BGR2RGB) img = cv2.resize(img, (224, 224)) plt.imshow(img) return fname
最後一個工具函式是classify_logo。給定圖片和模型,這個函式將會返回此圖片的分類(在我們的場景裡就是品牌的名字)和此分類相應的機率。
def classify_logo(net, url): fname = get_image(url) with open(fname, ‘rb’) as f: img = mx.image.imdecode(f.read()) data, _ = transform(img, -1, val_test_augs) data = data.expand_dims(axis=0) out = net(data.as_in_context(ctx[0])) out = mx.nd.SoftmaxActivation(out) pred = int(mx.nd.argmax(out, axis=1).asscalar()) prob = out[0][pred].asscalar() label = train_imgs.synsets return ‘With prob=%f, %s’%(prob, label[pred])
模型
理解模型的架構是非常重要的。在我們之前的那篇文章裡,我們構建了一個多層感知機(MLP)。此架構如圖4所示。
多層感知機。圖片由Tuhin Sharma提供
此MLP模型的輸入層應該是怎麼樣的?我的資料圖片的尺寸是224 * 224畫素。
構建輸入層的最常見的方法就是把圖片打平,構建一個50176個(224 * 224)神經元的輸入層。這就形成了一個下圖所示的簡單的資料流。
扁平化輸入。圖片由Tuhin Sharma提供
但是當進行這樣的扁平化處理後,影像資料裡的很多空間資訊被丟失了。同時另外一個挑戰是相應的權重數量。如果第一個隱藏層有30個神經元,那麼這個模型的引數將會有50176 * 30再加上30個偏置量。因此,這看來不像是一個好的為影像建模的方法。
現在讓我們來討論一下一個更合適的架構:用於圖片分類的卷積神經網路(CNN)。
卷積神經網路(CNN)
CNN和MLP類似,因為它也是構建神經網路併為神經元學習權重。CNN和MLP的關鍵的區別是輸入資料是圖片。CNN允許我們在架構裡充分利用圖片的特性。
CNN有一些卷積層。這個詞彙“卷積”是來自影像處理領域,如圖6所述。它工作於一個較小的視窗,叫做“感知域”,而不是處理來自前一層的所有輸入。這種機制就可以讓模型學習區域性的特徵。
每個卷積層用一個小矩陣(叫卷積核)在進入本層的影像上面的一部分上移動。卷積會對卷積矩陣內的每個畫素進行修改,此運算可以幫助識別邊緣。圖6的左邊展示了一個圖片,中間是一個3×3的卷積核,而運用此卷積核對左邊圖片的左上角畫素計算的結果顯示在右邊圖裡。我們還能定義多個卷積核,來表示不同的特徵圖。
卷積層。圖片由Tuhin Sharma提供
在上圖的例子裡,輸入的圖片的尺寸是5×5,而卷積核的尺寸是3×3。卷積計算是兩個矩陣的元素與元素的乘積之和。例子裡卷積的輸出尺寸也是5×5。
為了理解這些,我們需要理解卷積層裡的兩個重要引數:步長(stride)和填充方法(padding)。
步長控制卷積核(過濾器)如何在圖片上移動。
下圖表明瞭卷積核從第一個畫素到第二個畫素的移動過程。
卷積核的移動。圖片由Tuhin Sharma提供
在上圖中,步長是1
當對一個5×5的圖片進行3×3的卷積計算後,我們將得到一個3×3的圖片。針對這一情況,我們會在圖片的邊緣進行填充。現在這個5×5的圖片被0所圍繞,如下圖所示。
用0填充邊緣。圖片由Tuhin Sharma提供
這樣,當我們用3×3卷積核計算時,將會獲得一個5×5的輸出。
因此對於上圖所示的計算,它的步長是1,且填充的尺寸也是1。
CNN比相應的MLP能極大地減少權重的數量。假設我們使用30個卷積核,每個是3×3。每個卷積核的引數是3×3=9,外加1個偏置量。這樣每個卷積核有10個權重,總共30個卷積核就是300個權重。而在前面的章節裡,MLP則是有150000個權重。
下一層一般典型地是一個子抽樣層。一旦我們識別了特徵,這一子抽樣層會簡化這個資訊。一個常用的方法是最大池化。它從卷積層輸出的區域性區域輸出最大值(見下圖)。這一層在保留了每個區域性區域的最大啟用特徵的同時,降低了輸出的尺寸。
最大池化。圖片由Tuhin Sharma提供
可以看到最大池化在保留了每個區域性區域的最大啟用特徵的同時,降低了輸出的尺寸。
想了解關於CNN的更多的資訊,一個好的資源是這本線上圖書《神經網路和深度學習》。另外一個好的資源是史丹佛大學的CNN課程。
現在我們已經對什麼是CNN有了基本的瞭解。為了這裡的問題,讓我們用gluon來實現它。
第一步是定義這個架構:
cnn_net = mx.gluon.nn.Sequential() with cnn_net.name_scope(): # First convolutional layer cnn_net.add(mx.gluon.nn.Conv2D(channels=96, kernel_size=11, strides=(4,4), activation=’relu’)) cnn_net.add(mx.gluon.nn.MaxPool2D(pool_size=3, strides=2)) # Second convolutional layer cnn_net.add(mx.gluon.nn.Conv2D(channels=192, kernel_size=5, activation=’relu’)) cnn_net.add(mx.gluon.nn.MaxPool2D(pool_size=3, strides=(2,2))) # Flatten and apply fullly connected layers cnn_net.add(mx.gluon.nn.Flatten()) cnn_net.add(mx.gluon.nn.Dense(4096, activation=”relu”)) cnn_net.add(mx.gluon.nn.Dense(num_classes))
在模型架構被定義好之後,讓我們初始化網路裡的權重。我們將使用Xavier初始器。
cnn_net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx)
權重初始化完後,我們可以訓練模型了。我們會呼叫之前定義的相同的train函式,並傳給它所需的引數。
train(cnn_net, ctx, train_data, val_data, test_data, batch_size, num_epochs,model_prefix=’cnn’) Epoch 0. Loss: 53.771, Train acc 0.77, Val acc 0.58, Test acc 0.72, Time 224.9 sec Best validation accuracy found. Checkpointing… Epoch 1. Loss: 3.417, Train acc 0.80, Val acc 0.60, Test acc 0.73, Time 222.7 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 2. Loss: 3.333, Train acc 0.81, Val acc 0.60, Test acc 0.74, Time 222.5 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 3. Loss: 3.227, Train acc 0.82, Val acc 0.61, Test acc 0.75, Time 222.4 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 4. Loss: 3.079, Train acc 0.82, Val acc 0.61, Test acc 0.75, Time 222.0 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 5. Loss: 2.850, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 222.7 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 6. Loss: 2.488, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 222.1 sec Epoch 7. Loss: 1.943, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.3 sec Epoch 8. Loss: 1.395, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 223.6 sec Epoch 9. Loss: 1.146, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 222.5 sec Epoch 10. Loss: 1.089, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.5 sec Epoch 11. Loss: 1.078, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 220.7 sec Epoch 12. Loss: 1.078, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.1 sec Epoch 13. Loss: 1.075, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.3 sec Epoch 14. Loss: 1.076, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.3 sec Epoch 15. Loss: 1.076, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 220.4 sec Epoch 16. Loss: 1.075, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.3 sec Epoch 17. Loss: 1.074, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.8 sec Epoch 18. Loss: 1.074, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 221.8 sec Epoch 19. Loss: 1.073, Train acc 0.82, Val acc 0.61, Test acc 0.76, Time 220.9 sec
我們讓模型執行20個週期。典型的情況是,我們會訓練非常多的週期,並選擇驗證準確度最高的那個模型。在上面執行了20個週期後,我們可以在日誌裡看到驗證準確度最高的是在週期5。
在此週期之後,模型看起來並沒有學到更多。有可能網路已經飽和,學習速度變慢了。我們下一節裡會試一個更好的方法,但還是先讓我們看看現在這個模型的表現如何。
讓我們把最佳驗證準確度的模型引數匯入,然後分配給我們的模型:
cnn_net.collect_params().load(‘cnn-%d.params’%(5),ctx)
現在讓我們看看這個模型在新資料上的表現。我們會從網上獲取一個容易識別的圖片(見下圖),並看看模型是否能準確地識別。
img_url = “http://sophieswift.com/wp-content/uploads/2017/09/pleasing-ideas-bmw-cake-and-satisfying-some-bmw-themed-cakes-crustncakes-delicious-cakes-128×128.jpg” classify_logo(cnn_net, img_url) ‘With prob=0.081522, no-logo’
BMW的商標。圖片由Tuhin Sharma提供
模型的預測結果很糟糕。它認為這個圖片裡沒有包含商標的機率是8%。預測的結果是錯的,且機率非常低。
讓我們再試一張圖片來看看準確率是否有改善。
img_url = “https://dtgxwmigmg3gc.cloudfront.net/files/59cdcd6f52ba0b36b5024500-icon-256×256.png” classify_logo(cnn_net, img_url) ‘With prob=0.075301, no-logo’
Foster的商標。圖片由Tuhin Sharma提供
又一次,模型的預測結果是錯的,且機率很低。
我們沒有太多的資料,而如上所見,模型訓練得已經飽和。我們可以繼續試驗更多的模型架構,但是我們沒法克服小資料集的問題,因為可訓練的引數遠遠大於訓練圖片的數量。那我們如何克服這個問題?在沒有太多資料的情況下不能使用深度學習嗎?
對此的答案就是遷移學習。下面接著討論。
遷移學習
想想這個比喻。你想學一門新的外語,怎麼進行哪?
例如,你可以進行一個對話。導師:你好嗎?你:我很好,你怎麼樣?
你也能試著對新的外語做一樣的事。
因為你的英語很熟練,你可以不用從零開始學一門新的語言(即使看起來你是這樣做的)。你對一門語言已經有了心智圖,你可以試著在新的語言裡找到相應的詞彙。因此在新的語言裡,你的詞彙表可能依然有限,但憑藉你對於英語對話結構的知識,你依然能用新語言進行對話。
遷移學習的工作機制也是一樣的。高準確度模型是在海量資料集上訓練出來的。一個常見的資料集是ImageNet資料集。它有超過一百萬張圖片。全球的研究人員已經使用這個資料構建了很多不同的前沿的模型。這些模型(包括模型的架構和訓練好的權重)都在網路上可以獲得。
透過這些預先訓練好的模型,我們將再次對於我們的問題來訓練這個模型。事實上,這種情況是非常普通的。幾乎總是這樣的,大家首先構建的能解決計算機視覺問題的模型都是使用了一個預先訓練好的模型。
在諸如我們的例子的很多場景裡,如果受限於資料,這可能是所有人都能做的。
一個典型的方法是保持模型的前面層不變,只訓練最後一層。如果資料量非常有限,只需要把分類層再訓練就可以了。如果資料量相對充足,可以把最後的幾層都再訓練一下。
這是有效的,因為卷積神經網路在每個連續層學習更高層次的表示。它在許多早期階段所做的學習在所有影像分類問題中都是共同的。
現在讓我們使用一個預先訓練的模型來做商標檢測。
MXNet有一個模型庫,裡面有很多預先訓練好的模型。
我們會使用一個流行的預先訓練的模型,它叫resnet。這篇論文裡提供了這個模型架構的很多細節內容。一個簡單一些的解釋可以在這篇文章裡找到。
讓我們先下載這個預訓練過的模型:
from mxnet.gluon.model_zoo import vision as models pretrained_net = models.resnet18_v2(pretrained=True)
因為我們的資料很少,所以我們將只訓練最後的輸出層。我們先隨機初始化輸出層的權重。
finetune_net = models.resnet18_v2(classes=num_classes) finetune_net.features = pretrained_net.features finetune_net.output.initialize(mx.init.Xavier(magnitude=2.24))
現在我們呼叫之前一樣的訓練函式:
train(finetune_net, ctx, train_data, val_data, test_data, batch_size, num_epochs,model_prefix=’ft’,hybridize = True) Epoch 0. Loss: 1.107, Train acc 0.83, Val acc 0.62, Test acc 0.76, Time 246.1 sec Best validation accuracy found. Checkpointing… Epoch 1. Loss: 0.811, Train acc 0.85, Val acc 0.62, Test acc 0.77, Time 243.7 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 2. Loss: 0.722, Train acc 0.86, Val acc 0.64, Test acc 0.78, Time 245.3 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 3. Loss: 0.660, Train acc 0.87, Val acc 0.66, Test acc 0.79, Time 243.4 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 4. Loss: 0.541, Train acc 0.88, Val acc 0.67, Test acc 0.80, Time 244.5 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 5. Loss: 0.528, Train acc 0.89, Val acc 0.68, Test acc 0.80, Time 243.4 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 6. Loss: 0.490, Train acc 0.90, Val acc 0.68, Test acc 0.81, Time 243.2 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 7. Loss: 0.453, Train acc 0.91, Val acc 0.71, Test acc 0.82, Time 243.6 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 8. Loss: 0.435, Train acc 0.92, Val acc 0.70, Test acc 0.82, Time 245.6 sec Epoch 9. Loss: 0.413, Train acc 0.92, Val acc 0.72, Test acc 0.82, Time 247.7 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 10. Loss: 0.392, Train acc 0.92, Val acc 0.72, Test acc 0.83, Time 245.3 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 11. Loss: 0.377, Train acc 0.92, Val acc 0.72, Test acc 0.83, Time 244.5 sec Epoch 12. Loss: 0.335, Train acc 0.93, Val acc 0.72, Test acc 0.84, Time 244.2 sec Epoch 13. Loss: 0.321, Train acc 0.94, Val acc 0.73, Test acc 0.84, Time 245.0 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 14. Loss: 0.305, Train acc 0.93, Val acc 0.73, Test acc 0.84, Time 243.4 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 15. Loss: 0.298, Train acc 0.93, Val acc 0.73, Test acc 0.84, Time 243.9 sec Epoch 16. Loss: 0.296, Train acc 0.94, Val acc 0.75, Test acc 0.84, Time 247.0 sec Deleting previous checkpoint… Best validation accuracy found. Checkpointing… Epoch 17. Loss: 0.274, Train acc 0.94, Val acc 0.74, Test acc 0.84, Time 245.1 sec Epoch 18. Loss: 0.292, Train acc 0.94, Val acc 0.74, Test acc 0.84, Time 243.9 sec Epoch 19. Loss: 0.306, Train acc 0.95, Val acc 0.73, Test acc 0.84, Time 244.8 sec
現在這個模型馬上就有了更好的準確度。典型的情況是,當資料比較少時,我們只訓練幾個週期,再選驗證準確度最高的那個週期的模型。
現在,週期16有最好的驗證準確度。因為訓練資料有限,再繼續訓練的話,模型就開始出現過擬合。我們可以看到在週期16後,隨著訓練準確度的增加,驗證準確度卻開始下降了。
讓我們匯入週期16相應的檢查點儲存的引數,並用它們作為最後的模型。
# The model’s parameters are now set to the values at the 16th epoch finetune_net.collect_params().load(‘ft-%d.params’%(16),ctx)
評估預測的結果
對於我們之前用於評估的相同的圖片,讓我們看看新模型的預測結果。
img_url = “http://sophieswift.com/wp-content/uploads/2017/09/pleasing-ideas-bmw-cake-and-satisfying-some-bmw-themed-cakes-crustncakes-delicious-cakes-128×128.jpg” classify_logo(finetune_net, img_url) ‘With prob=0.983476, bmw’
圖片由Tuhin Sharma提供
我們看到這個模型用98%的機率預測對了BMW這個商標。
現在讓我們試一試另外一個之前測試的圖片。
img_url = “https://dtgxwmigmg3gc.cloudfront.net/files/59cdcd6f52ba0b36b5024500-icon-256×256.png” classify_logo(finetune_net, img_url) ‘With prob=0.498218, fosters’
儘管預測的機率並不高,比50%略低。但Foster依然是所有商標裡機率最高的那個。
進一步改進這個模型
為了改進模型,我們需要改正一下我們構建訓練資料集的方式。每個商標都有10張訓練圖。但是,為了把一些驗證影像從驗證集裡轉到訓練集,我們將1500張影像作為無商標的例子移動到訓練集裡。這導致了顯著的資料偏差,這可不是一個好方法。以下是解決此問題的一些選項:
給交叉熵損失賦予一定的權重。
在訓練資料裡不包含無商標的圖片。從而訓練一個對不包含商標的測試和驗證圖片預測成所有商標都有很低的機率的模型。
但請記住,即使在用遷移學習和資料增強的情況下,我們也只有320個商標圖片。這對於想建立高度精確的深度學習模型而言還是太少了。
總結
在本文中,我們學習瞭如何使用MXNet構建影像識別模型。Gluon是進行快速原型試驗的理想選擇。使用雜交和符號匯出,把原型轉變成生產系統也非常容易。透過使用MXNet上大量預先訓練的模型,我們能夠在相當快的時間內獲得非常好的商標檢測模型。一個很好的學習這背後的理論的資源是史丹佛大學的CS231n課程。