訂閱
糾錯
加入自媒體

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

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

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

給照片貼標簽是一項重要的任務(wù),使用機器可以快速完成。

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

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

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

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

library(imager)       # 圖像處理

library(caret)        # 模型評估

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

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

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

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

數(shù)據(jù)集

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

與前一篇文章不同,在前一篇文章中,圖像像素數(shù)據(jù)已轉(zhuǎn)換為一個.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

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

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

首先,我們需要找到每個類別的父文件夾地址。

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/"

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

file_name <- 

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

   unlist()

我們可以在下面看到,總共有14034個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個顏色通道(對于紅色、綠色和藍色,也稱為RGB)。

dim(img)

#> [1] 150 150   1   3

現(xiàn)在,我們將構(gòu)建一個函數(shù)來獲取圖像的寬度和高度,并將該函數(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像素。在擬合到模型之前,所有這些圖像必須具有相同的大小。這一點至關(guān)重要,因為:

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

2. 如果圖像尺寸太高,訓(xùn)練模型可能會花費太長時間,并且

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

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

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

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

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

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

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

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

4. 隨機水平移動總寬度的0.1

5. 隨機水平移動總高度的0.1

我們不使用垂直翻轉(zhuǎn),因為在我們的例子中,它們可以改變圖像的含義。

可以使用image_data_generator函數(shù)完成此數(shù)據(jù)擴充。將生成器保存到名為train_data_gen的對象。請注意,train_data_gen僅在訓(xùn)練時應(yīng)用,我們在預(yù)測時不使用它。

在train_data_gen中,我們還執(zhí)行標準化以減少照明差異的影響。此外,CNN模型在[0..1]數(shù)據(jù)上的收斂速度快于[0..255]。為此,只需將每個像素值除以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像素作為輸入圖像的形狀,因為150像素是所有圖像中最常見的寬度和高度(再次查看EDA),并將大小設(shè)置為目標大小。

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

target_size <- c(150, 150)

batch_size <- 32

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

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

train_image_array_gen <- flow_images_from_directory(

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

 target_size = target_size,   # 圖像維度的目標

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

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

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

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


# 用于驗證數(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

接下來,我們將看到目標變量中標簽的比例,以檢查類的不平衡性。

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

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

幸運的是,如上所述,所有的類都是相對平衡的!

建模

首先,讓我們保存我們使用的訓(xùn)練和驗證圖像的數(shù)量。除了訓(xùn)練數(shù)據(jù)之外,我們還需要不同的數(shù)據(jù)進行驗證,因為我們不希望我們的模型只擅長于預(yù)測它看到的圖像,還可以推廣到看不見的圖像。這種對看不見圖像的需求正是我們還必須在驗證數(shù)據(jù)集上查看模型性能的原因。

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

train_samples <- train_image_array_gen$n

valid_samples <- val_image_array_gen$n

train_samples

#> [1] 14034

valid_samples

#> [1] 3000

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

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

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

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

3. 平坦層

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

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

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

在輸出層,我們使用softmax激活函數(shù),因為這是一個多類分類問題。最后,我們需要指定CNN輸入層所需的圖像大小。如前所述,我們將使用一個150×150像素的圖像大小和3個RGB通道,存儲在target_size中。

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

# 設(shè)置初始隨機權(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)建完成后,我們對模型進行編譯和訓(xùn)練。

我們使用分類交叉熵作為損失函數(shù),因為這也是一個多類分類問題。我們使用默認學(xué)習(xí)率為0.001的adam優(yōu)化器,因為adam是最有效的優(yōu)化器之一。

為了簡單起見,我們還使用準確率作為衡量標準。更重要的是,由于我們不喜歡一個類別高于其他類別,而且每個類別都是平衡的,因此與精確性、敏感性或特異性相比,準確率更受青睞。我們將對模型進行10個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,
   

   # 驗證數(shù)據(jù)

   validation_data = val_image_array_gen,

   validation_steps = as.integer(valid_samples / batch_size)

 )

plot(history)

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

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

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

然后,我們將每個圖像轉(zhuǎn)換為一個數(shù)組。不要忘記對像素值進行標準化,也就是說,將它們除以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

接下來,預(yù)測:

pred_test <- predict_classes(model, test_x)

head(pred_test)

#> [1] 4 0 0 0 4 3

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

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ū)分每個類別。驗證數(shù)據(jù)集的準確率為74%。有106個街道圖像預(yù)測為建筑物,占所有街道圖像的20%以上。這是有道理的,因為在許多街道圖像中,建筑物也存在。

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

更深的CNN

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

1. 塊1:2個卷積層和1個最大池層

2. 塊2:1個卷積層和1個最大池層

3. 塊3:1個卷積層和1個最大池層

4. 塊4:1個卷積層和1個最大池層

5. 平坦層

6. 一個致密層

7. 輸出層

tensorflow::tf$random$set_seed(RS)

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

 # 第一個卷積層

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

 # 第二個卷積層

 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)) %>%
 

 # 第三個卷積層

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

 # 最大池層

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

 # 第四個卷積層

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

# 最大池層

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

 # 第五個卷積層

 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)于早期模型,因為模型更復(fù)雜,因此能夠捕獲更多的特征。我們在驗證數(shù)據(jù)集上獲得了85%的準確率。雖然對街道圖像的預(yù)測已經(jīng)有所改善,但對冰川圖像的預(yù)測仍在進行中。

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

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

為了便于學(xué)習(xí),在本節(jié)中,我們將使用VGG16模型,因為它是所有模型中最簡單的模型之一,它只包括我們前面介紹的卷積層、最大池層和密集層。這個過程被稱為遷移學(xué)習(xí),它將預(yù)訓(xùn)練好的模型的知識轉(zhuǎn)移到解決我們的問題上。

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

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

# 加載沒有頂層的原始模型

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進行訓(xùn)練和預(yù)測,但同樣,為了學(xué)習(xí),讓我們自己從頭開始創(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

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

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

因此,隱喻是:

我們站在巨人的肩膀上。

也就是說,讓我們將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

請注意,層19–21仍然具有隨機權(quán)重,因為它們是由我們創(chuàng)建的,并且不是來自原始模型。我們只需要凍結(jié)所有層以便單獨訓(xùn)練這些層。

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

為了訓(xùn)練這些預(yù)測層,我們只需使用前面的設(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)在,對模型進行微調(diào)。要做到這一點,我們應(yīng)該對優(yōu)化器應(yīng)用較低的學(xué)習(xí)率,以便建立的預(yù)訓(xùn)練權(quán)重不會混亂。我們將使用0.00001的學(xué)習(xí)率。

此外,為了節(jié)省時間,我們只對模型進行4個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

模型在驗證數(shù)據(jù)集上的準確率為92%!盡管如此,仍然存在一些錯誤分類,因為沒有一個模型是完美的。以下是預(yù)測的摘要:

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

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

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

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

結(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個類別的圖像分類:“建筑物”、“森林”、“冰川”、“山”、“!焙汀敖值馈薄

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

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

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

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權(quán)或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內(nèi)容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

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

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