訂閱
糾錯(cuò)
加入自媒體

建立卷積神經(jīng)網(wǎng)絡(luò)模型

自從開(kāi)始在網(wǎng)上寫(xiě)作以來(lái),非常依賴(lài)Unsplash。這是一個(gè)創(chuàng)造高質(zhì)量圖像的地方。但是你知道Unsplash可以使用機(jī)器學(xué)習(xí)來(lái)幫助標(biāo)記照片嗎?

對(duì)于上傳到Unsplash[…]的每個(gè)圖像,我們通過(guò)一系列機(jī)器學(xué)習(xí)算法運(yùn)行圖像,以了解照片的內(nèi)容,消除了參與者手動(dòng)標(biāo)記照片的需要。

給照片貼標(biāo)簽是一項(xiàng)重要的任務(wù),使用機(jī)器可以快速完成。

因此,我們將建立一個(gè)模型,可以從圖像中提取信息,并提供正確的標(biāo)簽。我們將使用卷積神經(jīng)網(wǎng)絡(luò)(CNN)對(duì)圖像進(jìn)行分類(lèi)預(yù)測(cè),以確定圖像是否與“建筑物”、“森林”、“冰川”、“山脈”、“海洋”或“街道”有關(guān)。因此,這是一個(gè)圖像分類(lèi)問(wèn)題。

庫(kù)

除了我們通常在R中使用的循環(huán)庫(kù)之外,我們還將使用keras。Keras是一種高級(jí)神經(jīng)網(wǎng)絡(luò)API,旨在實(shí)現(xiàn)快速實(shí)驗(yàn)。

library(keras)        # 深度學(xué)習(xí)

library(tidyverse)    # 數(shù)據(jù)處理

library(imager)       # 圖像處理

library(caret)        # 模型評(píng)估

library(grid)         # 在網(wǎng)格中顯示圖像

library(gridExtra)    # 在網(wǎng)格中顯示圖像

RS <- 42              # 隨機(jī)狀態(tài)常數(shù)

請(qǐng)注意,我們創(chuàng)建了一個(gè)名為RS的變量,它只是一個(gè)數(shù)字,用于再現(xiàn)性。

數(shù)據(jù)集

數(shù)據(jù)由6種不同標(biāo)簽的圖像組成:“建筑物”、“森林”、“冰川”、“山脈”、“海洋”和“街道”。

與前一篇文章不同,在前一篇文章中,圖像像素?cái)?shù)據(jù)已轉(zhuǎn)換為一個(gè).csv文件,這次我們使用數(shù)據(jù)生成器直接讀取圖像。

為此,我們需要了解圖像文件夾結(jié)構(gòu),如下所示。

seg_train

└── seg_train

  ├── buildings

  ├── forest

  ├── glacier

  ├── mountain

  ├── sea

  └── street
   

seg_test

└── seg_test

  ├── buildings

  ├── forest

  ├── glacier

  ├── mountain

  ├── sea

  └── street

在每個(gè)建筑物、森林、冰川、山、海和街道子文件夾中,會(huì)保存相應(yīng)的圖像。顧名思義,我們將使用seg_train進(jìn)行模型訓(xùn)練,使用seg_test進(jìn)行模型驗(yàn)證。

探索性數(shù)據(jù)分析

首先,我們需要找到每個(gè)類(lèi)別的父文件夾地址。

folder_list <- list.files("seg_train/seg_train/")

folder_path <- paste0("seg_train/seg_train/", folder_list, "/")

folder_path

#> [1] "seg_train/seg_train/buildings/" "seg_train/seg_train/forest/"    "seg_train/seg_train/glacier/"   "seg_train/seg_train/mountain/" 

#> [5] "seg_train/seg_train/sea/"       "seg_train/seg_train/street/"

然后,列出每個(gè)父文件夾地址的所有seg_train圖像地址。

file_name <- 

   map(folder_path, function(x) paste0(x, list.files(x))) %>% 

   unlist()

我們可以在下面看到,總共有14034個(gè)seg_train圖像。

cat("Number of train images:", length(file_name))

#> Number of train images: 14034

讓我們看兩張訓(xùn)練的圖片。

set.seed(RS)

sample_image <- sample(file_name, 18)

img <- map(sample_image, load.image)

grobs <- lapply(img, rasterGrob)

grid.a(chǎn)rrange(grobs=grobs, ncol=6)

以第一張圖片為例。

img <- load.image(file_name[1])

img


#> Image. Width: 150 pix Height: 150 pix Depth: 1 Colour channels: 3

如下圖所示,該圖像的尺寸為150×150×1×3。這意味著該特定圖像具有150像素的寬度、150像素的高度、1像素的深度和3個(gè)顏色通道(對(duì)于紅色、綠色和藍(lán)色,也稱(chēng)為RGB)。

dim(img)

#> [1] 150 150   1   3

現(xiàn)在,我們將構(gòu)建一個(gè)函數(shù)來(lái)獲取圖像的寬度和高度,并將該函數(shù)應(yīng)用于所有圖像。

get_dim <- function(x){

 img <- load.image(x) 

 df_img <- data.frame(

   width = width(img),

   height = height(img),

   filename = x

 )

 return(df_img)

file_dim <- map_df(file_name, get_dim)

head(file_dim)


#>     width height                                filename

#> 1   150    150     seg_train/seg_train/buildings/0.jpg

#> 2   150    150 seg_train/seg_train/buildings/10006.jpg

#> 3   150    150  seg_train/seg_train/buildings/1001.jpg

#> 4   150    150 seg_train/seg_train/buildings/10014.jpg

#> 5   150    150 seg_train/seg_train/buildings/10018.jpg

#> 6   150    150 seg_train/seg_train/buildings/10029.jpg

我們得到了以下圖像的寬度和高度分布。

hist(file_dim$width, breaks = 20)

hist(file_dim$height, breaks = 20)

summary(file_dim)

#>          width                   height                 filename        

#>  Min.   :150      Min.   : 76.0      Length:14034      

#>  1st Qu.:150    1st Qu.:150.0    Class :character  

#>  Median :150     Median :150.0     Mode  :character  

#>  Mean   :150      Mean   :149.9                     

#>  3rd Qu.:150   3rd Qu.:150.0                     

#>  Max.   :150     Max.   :150.0

正如我們所看到的,數(shù)據(jù)集具有不同的圖像維度。所有寬度均為150像素。然而,最大和最小高度分別為150和76像素。在擬合到模型之前,所有這些圖像必須具有相同的大小。這一點(diǎn)至關(guān)重要,因?yàn)椋?/p>

1. 擬合每個(gè)圖像像素值的模型的輸入層具有固定數(shù)量的神經(jīng)元,

2. 如果圖像尺寸太高,訓(xùn)練模型可能會(huì)花費(fèi)太長(zhǎng)時(shí)間,并且

3. 如果圖像尺寸太低,則會(huì)丟失太多信息。

數(shù)據(jù)預(yù)處理

神經(jīng)網(wǎng)絡(luò)模型可能出現(xiàn)的一個(gè)問(wèn)題是,它們傾向于存儲(chǔ)seg_train數(shù)據(jù)集中的圖像,因此當(dāng)新的seg_test數(shù)據(jù)集出現(xiàn)時(shí),它們無(wú)法識(shí)別它。

數(shù)據(jù)擴(kuò)充是解決這一問(wèn)題的眾多技術(shù)之一。對(duì)于給定的圖像,數(shù)據(jù)增強(qiáng)將稍微對(duì)其進(jìn)行變換,以創(chuàng)建一些新圖像。然后將這些新圖像擬合到模型中。

通過(guò)這種方式,模型知道原始圖像的許多版本,并且希望能夠理解圖像的含義,而不是記住它。我們將只使用一些簡(jiǎn)單的轉(zhuǎn)換,例如:

1. 隨機(jī)水平翻轉(zhuǎn)圖像

2. 隨機(jī)旋轉(zhuǎn)10度

3. 按系數(shù)0.1隨機(jī)縮放

4. 隨機(jī)水平移動(dòng)總寬度的0.1

5. 隨機(jī)水平移動(dòng)總高度的0.1

我們不使用垂直翻轉(zhuǎn),因?yàn)樵谖覀兊睦又,它們可以改變圖像的含義。

可以使用image_data_generator函數(shù)完成此數(shù)據(jù)擴(kuò)充。將生成器保存到名為train_data_gen的對(duì)象。請(qǐng)注意,train_data_gen僅在訓(xùn)練時(shí)應(yīng)用,我們?cè)陬A(yù)測(cè)時(shí)不使用它。

在train_data_gen中,我們還執(zhí)行標(biāo)準(zhǔn)化以減少照明差異的影響。此外,CNN模型在[0..1]數(shù)據(jù)上的收斂速度快于[0..255]。為此,只需將每個(gè)像素值除以255即可。

train_data_gen <- image_data_generator(

 rescale = 1/255,            # 縮放像素值

 horizontal_flip = T,        # 水平翻轉(zhuǎn)圖像

 vertical_flip = F,          # 垂直翻轉(zhuǎn)圖像

 rotation_range = 10,        # 將圖像從0旋轉(zhuǎn)到45度

 zoom_range = 0.1,           # 放大或縮小范圍

 width_shift_range = 0.1,    # 水平移位至寬度

 height_shift_range = 0.1,   # 水平移位到高度

我們將使用150×150像素作為輸入圖像的形狀,因?yàn)?50像素是所有圖像中最常見(jiàn)的寬度和高度(再次查看EDA),并將大小設(shè)置為目標(biāo)大小。

此外,我們將分批訓(xùn)練模型,每批32個(gè)觀(guān)察值。

target_size <- c(150, 150)

batch_size <- 32

現(xiàn)在,從各自的目錄中構(gòu)建生成器來(lái)生成訓(xùn)練和驗(yàn)證數(shù)據(jù)集。因?yàn)槲覀冇胁噬玆GB圖像,所以將顏色模式設(shè)置為“RGB”。最后,使用train_data_gen作為生成器并應(yīng)用先前創(chuàng)建的數(shù)據(jù)擴(kuò)充。

# 用于訓(xùn)練數(shù)據(jù)集

train_image_array_gen <- flow_images_from_directory(

 directory = "seg_train/seg_train/",   # 數(shù)據(jù)文件夾

 target_size = target_size,   # 圖像維度的目標(biāo)

 color_mode = "rgb",          # 使用rgb顏色

 batch_size = batch_size ,    # 每個(gè)批次中的圖像數(shù)

 seed = RS,                   # 設(shè)置隨機(jī)種子

 generator = train_data_gen   # 數(shù)據(jù)增強(qiáng)


# 用于驗(yàn)證數(shù)據(jù)集

val_image_array_gen <- flow_images_from_directory(

 directory = "seg_test/seg_test/",

 target_size = target_size, 

 color_mode = "rgb", 

 batch_size = batch_size ,

 seed = RS,

 generator = train_data_gen

接下來(lái),我們將看到目標(biāo)變量中標(biāo)簽的比例,以檢查類(lèi)的不平衡性。

如果存在的話(huà),分類(lèi)器傾向于建立有偏見(jiàn)的學(xué)習(xí)模型,與多數(shù)類(lèi)相比,少數(shù)類(lèi)的預(yù)測(cè)準(zhǔn)確率較差。我們可以通過(guò)對(duì)訓(xùn)練數(shù)據(jù)集進(jìn)行上采樣或下采樣,以最簡(jiǎn)單的方式解決此問(wèn)題。

output_n <- n_distinct(train_image_array_gen$classes)

table("Frequency" = factor(train_image_array_gen$classes)) %>% 

 prop.table()
 

#> Frequency

#>                  0                  1                  2                  3                 4                5 

#> 0.1561208 0.1618213 0.1712983 0.1789939 0.1620351 0.1697307

幸運(yùn)的是,如上所述,所有的類(lèi)都是相對(duì)平衡的!

建模

首先,讓我們保存我們使用的訓(xùn)練和驗(yàn)證圖像的數(shù)量。除了訓(xùn)練數(shù)據(jù)之外,我們還需要不同的數(shù)據(jù)進(jìn)行驗(yàn)證,因?yàn)槲覀儾幌M覀兊哪P椭簧瞄L(zhǎng)于預(yù)測(cè)它看到的圖像,還可以推廣到看不見(jiàn)的圖像。這種對(duì)看不見(jiàn)圖像的需求正是我們還必須在驗(yàn)證數(shù)據(jù)集上查看模型性能的原因。

因此,我們可以在下面看到,我們有14034張圖像用于訓(xùn)練(如前所述),3000張圖像用于驗(yàn)證模型。

train_samples <- train_image_array_gen$n

valid_samples <- val_image_array_gen$n

train_samples

#> [1] 14034

valid_samples

#> [1] 3000

我們將從最簡(jiǎn)單的模型逐步構(gòu)建三個(gè)模型。

簡(jiǎn)單CNN此模型只有4個(gè)隱藏層,包括最大池和平坦層,以及1個(gè)輸出層,詳情如下:

1. 卷積層:濾波器16,核大小3×3,same填充,relu激活函數(shù)

2. 最大池層:池大小2×2

3. 平坦層

4. 密集層:16節(jié)點(diǎn),relu激活函數(shù)

5. 密集層(輸出):6個(gè)節(jié)點(diǎn),softmax激活函數(shù)

請(qǐng)注意,我們使用平坦層作為從網(wǎng)絡(luò)的卷積部分到密集部分的橋梁;旧,平坦層——顧名思義——將最后一個(gè)卷積層的維度展平為單個(gè)密集層。例如,假設(shè)我們有一個(gè)大小為(8,8,32)的卷積層。這里,32是濾波器的數(shù)量。平坦層將把這個(gè)張量重塑成2048大小的向量。

在輸出層,我們使用softmax激活函數(shù),因?yàn)檫@是一個(gè)多類(lèi)分類(lèi)問(wèn)題。最后,我們需要指定CNN輸入層所需的圖像大小。如前所述,我們將使用一個(gè)150×150像素的圖像大小和3個(gè)RGB通道,存儲(chǔ)在target_size中。

現(xiàn)在,我們準(zhǔn)備好了。

# 設(shè)置初始隨機(jī)權(quán)重

tensorflow::tf$random$set_seed(RS)


model <- keras_model_sequential(name = "simple_model") %>%
 

 # 卷積層

 layer_conv_2d(filters = 16,
               kernel_size = c(3,3),
               padding = "same",
               activation = "relu",
               input_shape = c(target_size, 3)
               ) %>%

 # 最大池層

 layer_max_pooling_2d(pool_size = c(2,2)) %>%
 

 # 平坦層

 layer_flatten() %>%
 

 # 全連接層

 layer_dense(units = 16,
             activation = "relu") %>%
 

# Output Layer

 layer_dense(units = output_n,
             activation = "softmax",
             name = "Output")
 

summary(model)

#> Model: "simple_model"

#> _________________________________________________________________

#> Layer (type)                                                  Output Shape                                           Param #              

#> =================================================================

#> conv2d (Conv2D)                                               (None, 150, 150, 16)                                   448                  

#> _________________________________________________________________

#> max_pooling2d (MaxPooling2D)                                  (None, 75, 75, 16)                                     0                    

#> _________________________________________________________________

#> flatten (Flatten)                                             (None, 90000)                                          0                    

#> _________________________________________________________________

#> dense (Dense)                                                 (None, 16)                                             1440016              

#> _________________________________________________________________

#> Output (Dense)                                                (None, 6)                                              102                  

#> =================================================================

#> Total params: 1,440,566

#> Trainable params: 1,440,566

#> Non-trainable params: 0

#> _________________________________________________________________

構(gòu)建完成后,我們對(duì)模型進(jìn)行編譯和訓(xùn)練。

我們使用分類(lèi)交叉熵作為損失函數(shù),因?yàn)檫@也是一個(gè)多類(lèi)分類(lèi)問(wèn)題。我們使用默認(rèn)學(xué)習(xí)率為0.001的adam優(yōu)化器,因?yàn)閍dam是最有效的優(yōu)化器之一。

為了簡(jiǎn)單起見(jiàn),我們還使用準(zhǔn)確率作為衡量標(biāo)準(zhǔn)。更重要的是,由于我們不喜歡一個(gè)類(lèi)別高于其他類(lèi)別,而且每個(gè)類(lèi)別都是平衡的,因此與精確性、敏感性或特異性相比,準(zhǔn)確率更受青睞。我們將對(duì)模型進(jìn)行10個(gè)epoch的訓(xùn)練。

model %>% 

 compile(

   loss = "categorical_crossentropy",

   optimizer = optimizer_adam(lr = 0.001),

   metrics = "accuracy"

 )

# 擬合數(shù)據(jù)

history <- model %>% 

 fit_generator(

   # 訓(xùn)練數(shù)據(jù)

   train_image_array_gen,
 

   # 訓(xùn)練epoch數(shù)

   steps_per_epoch = as.integer(train_samples / batch_size), 

   epochs = 10,
   

   # 驗(yàn)證數(shù)據(jù)

   validation_data = val_image_array_gen,

   validation_steps = as.integer(valid_samples / batch_size)

 )

plot(history)

從第十個(gè)epoch的最終訓(xùn)練和驗(yàn)證準(zhǔn)確率可以看出,它們具有相似的值,并且相對(duì)較高,這意味著沒(méi)有出現(xiàn)過(guò)擬合。

接下來(lái),我們將對(duì)驗(yàn)證數(shù)據(jù)集上的所有圖像進(jìn)行預(yù)測(cè)(而不是像在訓(xùn)練中那樣按批次進(jìn)行預(yù)測(cè))。首先,讓我們將每個(gè)圖像及其對(duì)應(yīng)類(lèi)的路徑制成表格。

val_data <- data.frame(file_name = paste0("seg_test/seg

test/", val_image_array_gen$filenames)) %>% 

 mutate(class = str_extract(file_name, "buildings|forest|glacier|mountain|sea|street"))

head(val_data)

#>                                file_name     class

#> 1 seg_test/seg_test/buildings\20057.jpg buildings

#> 2 seg_test/seg_test/buildings\20060.jpg buildings

#> 3 seg_test/seg_test/buildings\20061.jpg buildings

#> 4 seg_test/seg_test/buildings\20064.jpg buildings

#> 5 seg_test/seg_test/buildings\20073.jpg buildings

#> 6 seg_test/seg_test/buildings\20074.jpg buildings

然后,我們將每個(gè)圖像轉(zhuǎn)換為一個(gè)數(shù)組。不要忘記對(duì)像素值進(jìn)行標(biāo)準(zhǔn)化,也就是說(shuō),將它們除以255。

image_prep <- function(x, target_size) {

 arrays <- lapply(x, function(path) {

   img <- image_load(

    path, 

     target_size = target_size, 

     grayscale = F

   )

   x <- image_to_array(img)

   x <- array_reshape(x, c(1, dim(x)))

   x <- x/255

 })

 do.call(abind::abind, c(arrays, list(along = 1)))



test_x <- image_prep(val_data$file_name, target_size)

dim(test_x)

#> [1] 3000  150  150    3

接下來(lái),預(yù)測(cè):

pred_test <- predict_classes(model, test_x)

head(pred_test)

#> [1] 4 0 0 0 4 3

現(xiàn)在,將每個(gè)預(yù)測(cè)解碼為相應(yīng)的類(lèi)。

decode <- function(x){

 case_when(

   x == 0 ~ "buildings",

   x == 1 ~ "forest",

   x == 2 ~ "glacier",

   x == 3 ~ "mountain",

   x == 4 ~ "sea",

   x == 5 ~ "street",

 )



pred_test <- sapply(pred_test, decode)

head(pred_test)

#> [1] "sea"       "buildings" "buildings" "buildings" "sea"       "mountain"

最后,分析混淆矩陣。

cm_simple <- confusionMatrix(as.factor(pred_test), as.factor(val_data$class))

acc_simple <- cm_simple$overall['Accuracy']

cm_simple

#> Confusion Matrix and Statistics

#> 

#>            Reference

#> Prediction  buildings forest glacier mountain sea street

#>   buildings       348     24      14       20  35    106

#>   forest            8    418       3        4   4     19

#>   glacier           7      5     357       53  38      5

#>   mountain         19      6      98      381  61      5

#>   sea              13      1      75       65 363      6

#>   street           42     20       6        2   9    360

#> 

#> Overall Statistics

#>                                                

#>                Accuracy : 0.7423               

#>                  95% CI : (0.7263, 0.7579)     

#>     No Information Rate : 0.1843               

#>     P-Value [Acc > NIR] : < 0.00000000000000022

#>                                                

#>                   Kappa : 0.6909               

#>                                                

#>  Mcnemar's Test P-Value : 0.0000000001327      

#> 

#> Statistics by Class:

#> 

#>                      Class: buildings Class: forest Class: glacier Class: mountain Class: sea Class: street

#> Sensitivity                    0.7963        0.8819         0.6456          0.7257     0.7118        0.7186

#> Specificity                    0.9224        0.9850         0.9559          0.9236     0.9357        0.9684

#> Pos Pred Value                 0.6362        0.9167         0.7677          0.6684     0.6941        0.8200

#> Neg Pred Value                 0.9637        0.9780         0.9227          0.9407     0.9407        0.9449

#> Prevalence                     0.1457        0.1580         0.1843          0.1750     0.1700        0.1670

#> Detection Rate                 0.1160        0.1393         0.1190          0.1270     0.1210        0.1200

#> Detection Prevalence           0.1823        0.1520         0.1550          0.1900     0.1743        0.1463

#> Balanced Accuracy              0.8593        0.9334         0.8007          0.8247     0.8238        0.8435

從混淆矩陣可以看出,模型很難區(qū)分每個(gè)類(lèi)別。驗(yàn)證數(shù)據(jù)集的準(zhǔn)確率為74%。有106個(gè)街道圖像預(yù)測(cè)為建筑物,占所有街道圖像的20%以上。這是有道理的,因?yàn)樵谠S多街道圖像中,建筑物也存在。

我們可以通過(guò)各種方式提高模型性能。但是現(xiàn)在,讓我們通過(guò)簡(jiǎn)單地改變架構(gòu)來(lái)改進(jìn)它。

更深的CNN

現(xiàn)在我們制作一個(gè)更深的CNN,有更多的卷積層。以下是體系結(jié)構(gòu):

1. 塊1:2個(gè)卷積層和1個(gè)最大池層

2. 塊2:1個(gè)卷積層和1個(gè)最大池層

3. 塊3:1個(gè)卷積層和1個(gè)最大池層

4. 塊4:1個(gè)卷積層和1個(gè)最大池層

5. 平坦層

6. 一個(gè)致密層

7. 輸出層

tensorflow::tf$random$set_seed(RS)

model_big <- keras_model_sequential(name = "model_big") %>%
 

 # 第一個(gè)卷積層

 layer_conv_2d(filters = 32,
               kernel_size = c(5,5), # 5 x 5 filters
               padding = "same",
               activation = "relu",
               input_shape = c(target_size, 3)
               ) %>%
 

 # 第二個(gè)卷積層

 layer_conv_2d(filters = 32,
               kernel_size = c(3,3), # 3 x 3 filters
               padding = "same",
               activation = "relu"
               ) %>%
 

 # 最大池層

 layer_max_pooling_2d(pool_size = c(2,2)) %>%
 

 # 第三個(gè)卷積層

 layer_conv_2d(filters = 64,
               kernel_size = c(3,3),
               padding = "same",
               activation = "relu"
               ) %>%

 # 最大池層

 layer_max_pooling_2d(pool_size = c(2,2)) %>%
 

 # 第四個(gè)卷積層

 layer_conv_2d(filters = 128,
               kernel_size = c(3,3),
               padding = "same",
               activation = "relu"
               ) %>%
 

# 最大池層

 layer_max_pooling_2d(pool_size = c(2,2)) %>%

 # 第五個(gè)卷積層

 layer_conv_2d(filters = 256,
               kernel_size = c(3,3),
               padding = "same",
               activation = "relu"
               ) %>%
 

 # 最大池層

 layer_max_pooling_2d(pool_size = c(2,2)) %>%
 

 # 平坦層

 layer_flatten() %>%
 

 # 密集層

 layer_dense(units = 64,
             activation = "relu") %>%
  

# 輸出層

 layer_dense(name = "Output",
             units = output_n,
             activation = "softmax")

summary(model_big)

#> Model: "model_big"

#> _________________________________________________________________

#> Layer (type)                                                  Output Shape                                           Param #              

#> =================================================================

#> conv2d_5 (Conv2D)                                             (None, 150, 150, 32)                                   2432                 

#> _________________________________________________________________

#> conv2d_4 (Conv2D)                                             (None, 150, 150, 32)                                   9248                 

#> _________________________________________________________________

#> max_pooling2d_4 (MaxPooling2D)                                (None, 75, 75, 32)                                     0                    

#> _________________________________________________________________

#> conv2d_3 (Conv2D)                                             (None, 75, 75, 64)                                     18496                

#> _________________________________________________________________

#> max_pooling2d_3 (MaxPooling2D)                                (None, 37, 37, 64)                                     0                    

#> _________________________________________________________________

#> conv2d_2 (Conv2D)                                             (None, 37, 37, 128)                                    73856                

#> _________________________________________________________________

#> max_pooling2d_2 (MaxPooling2D)                                (None, 18, 18, 128)                                    0                    

#> _________________________________________________________________

#> conv2d_1 (Conv2D)                                             (None, 18, 18, 256)                                    295168               

#> _________________________________________________________________

#> max_pooling2d_1 (MaxPooling2D)                                (None, 9, 9, 256)                                      0                    

#> _________________________________________________________________

#> flatten_1 (Flatten)                                           (None, 20736)                                          0                    

#> _________________________________________________________________

#> dense_1 (Dense)                                               (None, 64)                                             1327168              

#> _________________________________________________________________

#> Output (Dense)                                                (None, 6)                                              390                  

#> =================================================================

#> Total params: 1,726,758

#> Trainable params: 1,726,758

#> Non-trainable params: 0

#> _________________________________________________________________

其余部分與前面所做的相同。

model_big %>%

 compile(

   loss = "categorical_crossentropy",

   optimizer = optimizer_adam(lr = 0.001),

   metrics = "accuracy"

 )

history <- model_big %>%

 fit_generator(

   train_image_array_gen,

   steps_per_epoch = as.integer(train_samples / batch_size),

   epochs = 10,

   validation_data = val_image_array_gen,

   validation_steps = as.integer(valid_samples / batch_size)

 )
 

plot(history)

pred_test <- predict_classes(model_big, test_x)

pred_test <- sapply(pred_test, decode)

cm_big <- confusionMatrix(as.factor(pred_test), as.factor(val_data$class))

acc_big <- cm_big$overall['Accuracy']

cm_big


#> Confusion Matrix and Statistics

#> 

#>            Reference

#> Prediction  buildings forest glacier mountain sea street

#>   buildings       390      3      24       24  11     34

#>   forest            3    465      11        7   8     11

#>   glacier           2      0     367       35   9      1

#>   mountain          0      2      82      415  17      1

#>   sea               3      1      57       42 461      6

#>   street           39      3      12        2   4    448

#> 

#> Overall Statistics

#>                                                

#>                Accuracy : 0.8487               

#>                  95% CI : (0.8353, 0.8613)     

#>     No Information Rate : 0.1843               

#>     P-Value [Acc > NIR] : < 0.00000000000000022

#>                                                

#>                   Kappa : 0.8185               

#>                                                

#>  Mcnemar's Test P-Value : < 0.00000000000000022

#> 

#> Statistics by Class:

#> 

#>                      Class: buildings Class: forest Class: glacier Class: mountain Class: sea Class: street

#> Sensitivity                    0.8924        0.9810         0.6637          0.7905     0.9039        0.8942

#> Specificity                    0.9625        0.9842         0.9808          0.9588     0.9562        0.9760

#> Pos Pred Value                 0.8025        0.9208         0.8865          0.8027     0.8088        0.8819

#> Neg Pred Value                 0.9813        0.9964         0.9281          0.9557     0.9798        0.9787

#> Prevalence                     0.1457        0.1580         0.1843          0.1750     0.1700        0.1670

#> Detection Rate                 0.1300        0.1550         0.1223          0.1383     0.1537        0.1493

#> Detection Prevalence           0.1620        0.1683         0.1380          0.1723     0.1900        0.1693

#> Balanced Accuracy              0.9275        0.9826         0.8222          0.8746     0.9301        0.9351

這一結(jié)果總體上優(yōu)于早期模型,因?yàn)槟P透鼜?fù)雜,因此能夠捕獲更多的特征。我們?cè)隍?yàn)證數(shù)據(jù)集上獲得了85%的準(zhǔn)確率。雖然對(duì)街道圖像的預(yù)測(cè)已經(jīng)有所改善,但對(duì)冰川圖像的預(yù)測(cè)仍在進(jìn)行中。

帶預(yù)訓(xùn)練權(quán)重的CNN

實(shí)際上,研究人員已經(jīng)為圖像分類(lèi)問(wèn)題開(kāi)發(fā)了許多模型,從VGG模型系列到谷歌開(kāi)發(fā)的最新最先進(jìn)的EfficientNet。

為了便于學(xué)習(xí),在本節(jié)中,我們將使用VGG16模型,因?yàn)樗撬心P椭凶詈?jiǎn)單的模型之一,它只包括我們前面介紹的卷積層、最大池層和密集層。這個(gè)過(guò)程被稱(chēng)為遷移學(xué)習(xí),它將預(yù)訓(xùn)練好的模型的知識(shí)轉(zhuǎn)移到解決我們的問(wèn)題上。

最初的VGG16模型接受了1000個(gè)類(lèi)的訓(xùn)練。為了使其適合我們的問(wèn)題,我們將排除模型的頂層(密集層),并插入我們版本的預(yù)測(cè)層,其中包括一個(gè)全局平均池層(作為平坦層的替代)、一個(gè)具有64個(gè)節(jié)點(diǎn)的密集層和一個(gè)具有6個(gè)節(jié)點(diǎn)的輸出層(用于6個(gè)類(lèi))。

讓我們看看總體架構(gòu)。

# 加載沒(méi)有頂層的原始模型

input_tensor <- layer_input(shape = c(target_size, 3))

base_model <- application_vgg16(input_tensor = input_tensor,
                               weights = 'imagenet',
                               include_top = FALSE)

# 添加我們的自定義層

predictions <- base_model$output %>%

 layer_global_average_pooling_2d() %>%

 layer_dense(units = 64, activation = 'relu') %>%

 layer_dense(units = output_n, activation = 'softmax')

# 這是我們將要訓(xùn)練的模型

vgg16 <- keras_model(inputs = base_model$input, outputs = predictions)

summary(vgg16)

#> Model: "model"

#> _________________________________________________________________

#> Layer (type)                                                  Output Shape                                           Param #              

#> =================================================================

#> input_1 (InputLayer)                                          [(None, 150, 150, 3)]                                  0                    

#> _________________________________________________________________

#> block1_conv1 (Conv2D)                                         (None, 150, 150, 64)                                   1792                 

#> _________________________________________________________________

#> block1_conv2 (Conv2D)                                         (None, 150, 150, 64)                                   36928                

#> _________________________________________________________________

#> block1_pool (MaxPooling2D)                                    (None, 75, 75, 64)                                     0                    

#> _________________________________________________________________

#> block2_conv1 (Conv2D)                                         (None, 75, 75, 128)                                    73856                

#> _________________________________________________________________

#> block2_conv2 (Conv2D)                                         (None, 75, 75, 128)                                    147584               

#> _________________________________________________________________

#> block2_pool (MaxPooling2D)                                    (None, 37, 37, 128)                                    0                    

#> _________________________________________________________________

#> block3_conv1 (Conv2D)                                         (None, 37, 37, 256)                                    295168               

#> _________________________________________________________________

#> block3_conv2 (Conv2D)                                         (None, 37, 37, 256)                                    590080               

#> _________________________________________________________________

#> block3_conv3 (Conv2D)                                         (None, 37, 37, 256)                                    590080               

#> _________________________________________________________________

#> block3_pool (MaxPooling2D)                                    (None, 18, 18, 256)                                    0                    

#> _________________________________________________________________

#> block4_conv1 (Conv2D)                                         (None, 18, 18, 512)                                    1180160              

#> _________________________________________________________________

#> block4_conv2 (Conv2D)                                         (None, 18, 18, 512)                                    2359808              

#> _________________________________________________________________

#> block4_conv3 (Conv2D)                                         (None, 18, 18, 512)                                    2359808              

#> _________________________________________________________________

#> block4_pool (MaxPooling2D)                                    (None, 9, 9, 512)                                      0                    

#> _________________________________________________________________

#> block5_conv1 (Conv2D)                                         (None, 9, 9, 512)                                      2359808              

#> _________________________________________________________________

#> block5_conv2 (Conv2D)                                         (None, 9, 9, 512)                                      2359808              

#> _________________________________________________________________

#> block5_conv3 (Conv2D)                                         (None, 9, 9, 512)                                      2359808              

#> _________________________________________________________________

#> block5_pool (MaxPooling2D)                                    (None, 4, 4, 512)                                      0                    

#> _________________________________________________________________

#> global_average_pooling2d (GlobalAveragePooling2D)             (None, 512)                                            0                    

#> _________________________________________________________________

#> dense_3 (Dense)                                               (None, 64)                                             32832                

#> _________________________________________________________________

#> dense_2 (Dense)                                               (None, 6)                                              390                  

#> =================================================================

#> Total params: 14,747,910

#> Trainable params: 14,747,910

#> Non-trainable params: 0

#> _________________________________________________________________

我們可以直接使用vgg16進(jìn)行訓(xùn)練和預(yù)測(cè),但同樣,為了學(xué)習(xí),讓我們自己從頭開(kāi)始創(chuàng)建vgg16模型。

model_bigger <- keras_model_sequential(name = "model_bigger") %>%
 

 # 塊一

 layer_conv_2d(filters = 64,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               input_shape = c(94, 94, 3),
               name='block1_conv1') %>%
 

 layer_conv_2d(filters = 64,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block1_conv2') %>%
   

 layer_max_pooling_2d(pool_size = c(2, 2),
                      strides=c(2, 2),
                      name='block1_pool') %>%
 

 # 塊二

 layer_conv_2d(filters = 128,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block2_conv1') %>%
   

layer_conv_2d(filters = 128,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block2_conv2') %>%
   

 layer_max_pooling_2d(pool_size = c(2, 2),
                      strides=c(2, 2),
                      name='block2_pool') %>%
 

 # 塊三

 layer_conv_2d(filters = 256,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block3_conv1') %>%
 

 layer_conv_2d(filters = 256,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block3_conv2') %>%
   

 layer_conv_2d(filters = 256,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block3_conv3') %>%
   

 layer_max_pooling_2d(pool_size = c(2, 2),
                      strides=c(2, 2),
                      name='block3_pool') %>%
 

 # 塊四

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block4_conv1') %>%
 

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block4_conv2') %>%
   

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block4_conv3') %>%
 

 layer_max_pooling_2d(pool_size = c(2, 2),
                      strides=c(2, 2),
                      name='block4_pool') %>%
 

 # 塊五

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block5_conv1') %>%
   

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block5_conv2') %>%
   

 layer_conv_2d(filters = 512,
               kernel_size = c(3, 3),
               activation='relu',
               padding='same',
               name='block5_conv3') %>%
   

 layer_max_pooling_2d(pool_size = c(2, 2),
                      strides=c(2, 2),
                      name='block5_pool') %>%
 

 # 全連接層

 layer_global_average_pooling_2d() %>%

 layer_dense(units = 64, activation = 'relu') %>%

 layer_dense(units = output_n, activation = 'softmax')

model_bigger

#> Model

#> Model: "model_bigger"

#> _________________________________________________________________

#> Layer (type)                                                  Output Shape                                           Param #              

#> =================================================================

#> block1_conv1 (Conv2D)                                         (None, 94, 94, 64)                                     1792                 

#> _________________________________________________________________

#> block1_conv2 (Conv2D)                                         (None, 94, 94, 64)                                     36928                

#> _________________________________________________________________

#> block1_pool (MaxPooling2D)                                    (None, 47, 47, 64)                                     0                    

#> _________________________________________________________________

#> block2_conv1 (Conv2D)                                         (None, 47, 47, 128)                                    73856                

#> _________________________________________________________________

#> block2_conv2 (Conv2D)                                         (None, 47, 47, 128)                                    147584               

#> _________________________________________________________________

#> block2_pool (MaxPooling2D)                                    (None, 23, 23, 128)                                    0                    

#> _________________________________________________________________

#> block3_conv1 (Conv2D)                                         (None, 23, 23, 256)                                    295168               

#> _________________________________________________________________

#> block3_conv2 (Conv2D)                                         (None, 23, 23, 256)                                    590080               

#> _________________________________________________________________

#> block3_conv3 (Conv2D)                                         (None, 23, 23, 256)                                    590080               

#> _________________________________________________________________

#> block3_pool (MaxPooling2D)                                    (None, 11, 11, 256)                                    0                    

#> _________________________________________________________________

#> block4_conv1 (Conv2D)                                         (None, 11, 11, 512)                                    1180160              

#> _________________________________________________________________

#> block4_conv2 (Conv2D)                                         (None, 11, 11, 512)                                    2359808              

#> _________________________________________________________________

#> block4_conv3 (Conv2D)                                         (None, 11, 11, 512)                                    2359808              

#> _________________________________________________________________

#> block4_pool (MaxPooling2D)                                    (None, 5, 5, 512)                                      0                    

#> _________________________________________________________________

#> block5_conv1 (Conv2D)                                         (None, 5, 5, 512)                                      2359808              

#> _________________________________________________________________

#> block5_conv2 (Conv2D)                                         (None, 5, 5, 512)                                      2359808              

#> _________________________________________________________________

#> block5_conv3 (Conv2D)                                         (None, 5, 5, 512)                                      2359808              

#> _________________________________________________________________

#> block5_pool (MaxPooling2D)                                    (None, 2, 2, 512)                                      0                    

#> _________________________________________________________________

#> global_average_pooling2d_1 (GlobalAveragePooling2D)           (None, 512)                                            0                    

#> _________________________________________________________________

#> dense_5 (Dense)                                               (None, 64)                                             32832                

#> _________________________________________________________________

#> dense_4 (Dense)                                               (None, 6)                                              390                  

#> =================================================================

#> Total params: 14,747,910

#> Trainable params: 14,747,910

#> Non-trainable params: 0

#> _________________________________________________________________
請(qǐng)注意,model_bigger的每個(gè)層的參數(shù)數(shù)量與vgg16完全相同。

遷移學(xué)習(xí)的優(yōu)點(diǎn)是,我們不必從隨機(jī)權(quán)重開(kāi)始訓(xùn)練模型,而是從原始模型的預(yù)訓(xùn)練權(quán)重開(kāi)始。這些預(yù)訓(xùn)練好的權(quán)重已經(jīng)針對(duì)圖像分類(lèi)問(wèn)題進(jìn)行了優(yōu)化,我們只需對(duì)它們進(jìn)行微調(diào)以符合我們的目的。

因此,隱喻是:

我們站在巨人的肩膀上。

也就是說(shuō),讓我們將vgg16的所有權(quán)重分配給模型。

set_weights(model_bigger, get_weights(vgg16))

下面是我們的模型層的摘要:

layers <- model_bigger$layers

for (i in 1:length(layers))

 cat(i, layers[[i]]$name, "")
 

#> 1 block1_conv1 

#> 2 block1_conv2 

#> 3 block1_pool 

#> 4 block2_conv1 

#> 5 block2_conv2 

#> 6 block2_pool 

#> 7 block3_conv1 

#> 8 block3_conv2 

#> 9 block3_conv3 

#> 10 block3_pool 

#> 11 block4_conv1 

#> 12 block4_conv2 

#> 13 block4_conv3 

#> 14 block4_pool 

#> 15 block5_conv1 

#> 16 block5_conv2 

#> 17 block5_conv3 

#> 18 block5_pool 

#> 19 global_average_pooling2d_1 

#> 20 dense_5 

#> 21 dense_4

請(qǐng)注意,層19–21仍然具有隨機(jī)權(quán)重,因?yàn)樗鼈兪怯晌覀儎?chuàng)建的,并且不是來(lái)自原始模型。我們只需要凍結(jié)所有層以便單獨(dú)訓(xùn)練這些層。

freeze_weights(model_bigger, from = 1, to = 18)

為了訓(xùn)練這些預(yù)測(cè)層,我們只需使用前面的設(shè)置。

# 編譯模型

model_bigger %>% compile(loss = "categorical_crossentropy",
                        optimizer = optimizer_adam(lr = 0.001),
                        metrics = "accuracy")

history <- model_bigger %>%

 fit_generator(

 train_image_array_gen,

 steps_per_epoch = as.integer(train_samples / batch_size),

 epochs = 10,

 validation_data = val_image_array_gen,

 validation_steps = as.integer(valid_samples / batch_size)

現(xiàn)在,對(duì)模型進(jìn)行微調(diào)。要做到這一點(diǎn),我們應(yīng)該對(duì)優(yōu)化器應(yīng)用較低的學(xué)習(xí)率,以便建立的預(yù)訓(xùn)練權(quán)重不會(huì)混亂。我們將使用0.00001的學(xué)習(xí)率。

此外,為了節(jié)省時(shí)間,我們只對(duì)模型進(jìn)行4個(gè)epoch的訓(xùn)練。

在微調(diào)之前,不要忘記解凍要訓(xùn)練的層。在本例中,我們將解凍所有層

unfreeze_weights(model_bigger)

# 以低學(xué)習(xí)率重新編譯

model_bigger %>% compile(loss = "categorical_crossentropy",
                        optimizer = optimizer_adam(lr = 0.00001),
                        metrics = "accuracy")

history <- model_bigger %>%

 fit_generator(

 train_image_array_gen,

 steps_per_epoch = as.integer(train_samples / batch_size),

 epochs = 4,

 validation_data = val_image_array_gen,

 validation_steps = as.integer(valid_samples / batch_size)


plot(history)

pred_test <- predict_classes(model_bigger, test_x)

pred_test <- sapply(pred_test, decode)

cm_bigger <- confusionMatrix(as.factor(pred_test), as.factor(val_data$class))

acc_bigger <- cm_bigger$overall['Accuracy']

cm_bigger

#> Confusion Matrix and Statistics

#> 

#>            Reference

#> Prediction  buildings forest glacier mountain sea street

#>   buildings       396      0       2        1   2     13

#>   forest            1    469       2        2   4      0

#>   glacier           1      2     479       61   5      0

#>   mountain          0      0      50      452   4      0

#>   sea               1      1      16        7 492      2

#>   street           38      2       4        2   3    486

#> 

#> Overall Statistics

#>                                               

#>                Accuracy : 0.9247              

#>                  95% CI : (0.9146, 0.9339)    

#>     No Information Rate : 0.1843              

#>     P-Value [Acc > NIR] : < 0.0000000000000002

#>                                               

#>                   Kappa : 0.9095              

#>                                               

#>  Mcnemar's Test P-Value : 0.00281             

#> 

#> Statistics by Class:

#> 

#>                      Class: buildings Class: forest Class: glacier Class: mountain Class: sea Class: street

#> Sensitivity                    0.9062        0.9895         0.8662          0.8610     0.9647        0.9701

#> Specificity                    0.9930        0.9964         0.9718          0.9782     0.9892        0.9804

#> Pos Pred Value                 0.9565        0.9812         0.8741          0.8933     0.9480        0.9084

#> Neg Pred Value                 0.9841        0.9980         0.9698          0.9707     0.9927        0.9939

#> Prevalence                     0.1457        0.1580         0.1843          0.1750     0.1700        0.1670

#> Detection Rate                 0.1320        0.1563         0.1597          0.1507     0.1640        0.1620

#> Detection Prevalence           0.1380        0.1593         0.1827          0.1687     0.1730        0.1783

#> Balanced Accuracy              0.9496        0.9929         0.9190          0.9196     0.9769        0.9752

模型在驗(yàn)證數(shù)據(jù)集上的準(zhǔn)確率為92%!盡管如此,仍然存在一些錯(cuò)誤分類(lèi),因?yàn)闆](méi)有一個(gè)模型是完美的。以下是預(yù)測(cè)的摘要:

1. 有些建筑被錯(cuò)誤地預(yù)測(cè)為街道,反之亦然。同樣,這是由于一些包含街道的建筑物圖像混淆了模型。

2. 森林的預(yù)測(cè)幾乎是完美的。

3. 許多冰川被預(yù)測(cè)為山脈和海洋,也有許多山脈被預(yù)測(cè)為冰川。

4. 海洋預(yù)測(cè)良好。

結(jié)論

rbind(

 "Simple CNN" = acc_simple,

 "Deeper CNN" = acc_big,

 "Fine-tuned VGG16" = acc_bigger



#>                   Accuracy

#> Simple CNN       0.7423333

#> Deeper CNN       0.8486667

#> Fine-tuned VGG16 0.9246667

我們已經(jīng)成功地完成了6個(gè)類(lèi)別的圖像分類(lèi):“建筑物”、“森林”、“冰川”、“山”、“!焙汀敖值馈薄

由于圖像是非結(jié)構(gòu)化數(shù)據(jù),可以通過(guò)使用神經(jīng)網(wǎng)絡(luò)進(jìn)行機(jī)器學(xué)習(xí)來(lái)解決這一問(wèn)題,神經(jīng)網(wǎng)絡(luò)可以自動(dòng)進(jìn)行特征提取,而無(wú)需人工干預(yù)。

為了獲得更好的性能,我們使用卷積神經(jīng)網(wǎng)絡(luò)對(duì)密集層進(jìn)行連續(xù)預(yù)測(cè)。最后,我們使用VGG16模型進(jìn)行初始化權(quán)重,達(dá)到92%的準(zhǔn)確率。

       原文標(biāo)題 : 建立卷積神經(jīng)網(wǎng)絡(luò)模型

聲明: 本文由入駐維科號(hào)的作者撰寫(xiě),觀(guān)點(diǎn)僅代表作者本人,不代表OFweek立場(chǎng)。如有侵權(quán)或其他問(wèn)題,請(qǐng)聯(lián)系舉報(bào)。

發(fā)表評(píng)論

0條評(píng)論,0人參與

請(qǐng)輸入評(píng)論內(nèi)容...

請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字

您提交的評(píng)論過(guò)于頻繁,請(qǐng)輸入驗(yàn)證碼繼續(xù)

  • 看不清,點(diǎn)擊換一張  刷新

暫無(wú)評(píng)論

暫無(wú)評(píng)論

    掃碼關(guān)注公眾號(hào)
    OFweek人工智能網(wǎng)
    獲取更多精彩內(nèi)容
    文章糾錯(cuò)
    x
    *文字標(biāo)題:
    *糾錯(cuò)內(nèi)容:
    聯(lián)系郵箱:
    *驗(yàn) 證 碼:

    粵公網(wǎng)安備 44030502002758號(hào)