建立卷積神經(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ò)模型
請輸入評論內(nèi)容...
請輸入評論/評論長度6~500個字
最新活動更多
-
即日-11.13立即報名>>> 【在線會議】多物理場仿真助跑新能源汽車
-
11月28日立即報名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會議
-
12月19日立即報名>> 【線下會議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會
-
即日-12.26火熱報名中>> OFweek2024中國智造CIO在線峰會
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍皮書》
-
精彩回顧立即查看>> 【限時免費下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
推薦專題
-
5 夾縫中的文遠知行
- 高級軟件工程師 廣東省/深圳市
- 自動化高級工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市