深度學期快速入門

Lesson 2 人工智慧基礎介紹

林嶔 (Lin, Chin)

第一節:基本知識(1)

\[\hat{y} = f(x)\]

\[\hat{y} = f(x) = b_{0} + b_{1}x\]

第一節:基本知識(2)

\[loss = diff(y, \hat{y})\] - 以簡單線性迴歸的損失函數為例,所求的值為殘差平方和,可將此式改寫為:

\[loss = diff(y, \hat{y}) = \sum \limits_{i=1}^{n} \left(y_{i} - \hat{y_{i}}\right)^{2}\]

\[loss = diff(y, f(x))\]

\[loss = diff(y, f(x)) = \sum \limits_{i=1}^{n} \left(y_{i} - \left(b_{0} + b_{1}x_{1,i}\right)\right)^{2}\]

第一節:基本知識(3)

\[min(loss)\]

第二節:決策樹(1)

F01

F02

第二節:決策樹(2)

– 需要特別注意的是,對於資料科學實驗的流程,我們通常會把樣本分為3個部分,分別是:

  1. 訓練組(Training set, Development set):負責用來建構一個預測模型,我們之前學的三大流程就是用在這上面的,樣本可以隨意調整

  2. 驗證組(Validation set, Tuning set):不參與模型訓練,但會用來指導模型訓練,以及建構重要的資訊所用,樣本可以隨意調整

  3. 測試組(Testing set, Hold-out set, Validation set):最終模型會在上面運行一次(只能一次),已確定最終的準確度,原則上樣本選取必須符合未來使用條件

– 實務上,訓練組跟驗證組在不選擇模型的前提下可以合併,但測試組是必須的。

iris <- read.csv('data/iris.csv')

#Split data

set.seed(1)

Train.sample <- sample(1:150, 100, replace = FALSE)

Train.data <- iris[Train.sample,]
Test.data <- iris[-Train.sample,]

第二節:決策樹(3)

library(party)

tree.model <- ctree(formula = Species ~ ., data = Train.data)
tree.model
## 
##   Conditional inference tree with 3 terminal nodes
## 
## Response:  Species 
## Inputs:  Sepal.Length, Sepal.Width, Petal.Length, Petal.Width 
## Number of observations:  100 
## 
## 1) Petal.Length <= 1.9; criterion = 1, statistic = 92.735
##   2)*  weights = 32 
## 1) Petal.Length > 1.9
##   3) Petal.Width <= 1.6; criterion = 1, statistic = 48.709
##     4)*  weights = 35 
##   3) Petal.Width > 1.6
##     5)*  weights = 33
pred.y <- predict(tree.model, Test.data[,1:4])
table(pred.y, Test.data[,5])
##             
## pred.y       setosa versicolor virginica
##   setosa         18          0         0
##   versicolor      0         14         3
##   virginica       0          0        15
plot(tree.model)

第二節:決策樹(4)

dat <- read.csv("data/ECG_train.csv", header = TRUE, fileEncoding = 'CP950', stringsAsFactors = FALSE, na.strings = "")
  1. AMI:這是個類別變項描述心肌梗塞的狀態,包含STEMI、NSTEMI及not-AMI

  2. K:這是一個連續變項描述鉀離子的濃度

  3. LVD:這是一個二元類別變項:1代表left ventricular dysfunction,0則代表正常

  4. time與death:這組變項描述病患隔多久後死亡與否,這用來做存活分析之用

– 除了性別(GENDER)和年齡(AGE)外,心電圖的重要參數包含了8個連續變項特徵(Rate、PR、QRSd、QT、QTc、Axes_P、Axes_QRS、Axes_T)以及31個二元類別變項描述相對應的rhythm。

– rhythm依序為:abnormal T wave、atrial fibrillation、atrial flutter、atrial premature complex、complete AV block、complete left bundle branch block、complete right bundle branch block、first degree AV block、incomplete left bundle branch block、incomplete right bundle branch block、ischemia/infarction、junctional rhythm、left anterior fascicular block、left atrial enlargement、left axis deviation、left posterior fascicular block、left ventricular hypertrophy、low QRS voltage、pacemaker rhythm、prolonged QT interval、right atrial enlargement、right ventricular hypertrophy、second degree AV block、sinus bradycardia、sinus pause、sinus rhythm、sinus tachycardia、supraventricular tachycardia、ventricular premature complex、ventricular tachycardia、Wolff-Parkinson-White syndrome

– 在大部分的狀態下,我們會使用後面的幾個變項去預測前面的4組變項。

第二節:決策樹(5)

– 這裡給一些範例語法,讓大家知道怎樣用在不同的依變項屬性上。

  1. 這是連續變項的範例:
subdat <- dat[!(dat[,'K'] %in% NA) & !(dat[,'Rate'] %in% NA) & !(dat[,'AGE'] %in% NA), c('K', 'Rate', 'AGE')]

tree.model <- ctree(formula = K ~ ., data = subdat)
plot(tree.model)

  1. 這是二元分類的範例:
subdat <- dat[!(dat[,'LVD'] %in% NA) & !(dat[,'GENDER'] %in% NA) & !(dat[,'Rate'] %in% NA), c('LVD', 'GENDER', 'Rate')]
subdat[,'GENDER'] <- as.factor(subdat[,'GENDER'])
subdat[,'LVD'] <- as.factor(subdat[,'LVD'])

tree.model <- ctree(formula = LVD ~ ., data = subdat)
plot(tree.model)

  1. 這是多分類任務的範例:
subdat <- dat[!(dat[,'AMI'] %in% NA) & !(dat[,'GENDER'] %in% NA) & !(dat[,'AGE'] %in% NA), c('AMI', 'GENDER', 'AGE')]
subdat[,'GENDER'] <- as.factor(subdat[,'GENDER'])
subdat[,'AMI'] <- as.factor(subdat[,'AMI'])

tree.model <- ctree(formula = AMI ~ ., data = subdat)
plot(tree.model)

第二節:決策樹(6)

  1. 參數【mincriterion】是顯著水準

  2. 參數【maxdepth】是樹的最大深度

  3. 參數【minsplit】是說樣本大於多少,才考慮繼續分類

  4. 參數【minbucket】是說每個分類至少要有幾個樣本

subdat <- dat[!(dat[,'LVD'] %in% NA) & !(dat[,'GENDER'] %in% NA) & !(dat[,'Rate'] %in% NA), c('LVD', 'GENDER', 'Rate')]
subdat[,'GENDER'] <- as.factor(subdat[,'GENDER'])
subdat[,'LVD'] <- as.factor(subdat[,'LVD'])

tree.model <- ctree(formula = LVD ~ ., data = subdat,
                    controls = ctree_control(mincriterion = 0.95, maxdepth = 2, minsplit = 20, minbucket = 7))
plot(tree.model)

– 如果你想獲得預測機率,可以用這種方式:

prob_list <- predict(tree.model, subdat, type = 'prob')
prob_y <- do.call('rbind', prob_list)[,1]

第三節:梯度提升機(1)

– 為什麼他這麼強呢?決策樹的缺點是模型都是建立在單一預測模的基礎上,容易因為樣本決定一個特定的極端條件。

F03

第三節:梯度提升機(2)

– 有趣的是,這樣模型的組合將可以讓我們擁有一個非線性模型。

  1. 先建立第一個模型:\(y = F_1(x) + \epsilon_1\)

  2. 根據第一個模型的殘差建立第二個模型:\(\epsilon_1 = F_2(x) + \epsilon_2\)

  3. 先將上面兩個式子合併,產生一個整合模型,並取得整合模型的殘差:\(y = F_1(x) + \eta F_2(x) + \tilde{\epsilon_2}\)

  4. 再根據這個殘差建立第三個模型:\(\tilde{\epsilon_2} = F_3(x) + \epsilon_3\)

  5. 再將三個式子合併,產生一個整合模型,並取得整合模型的殘差:\(y = F_1(x) + \eta F_2(x) + \eta F_3(x) + \tilde{\epsilon_3}\)

  6. 再根據這個殘差建立第四個模型:\(\tilde{\epsilon_3} = F_4(x) + \epsilon_4\)

  7. 再將三個式子合併,產生一個整合模型,並取得整合模型的殘差:\(y = F_1(x) + \eta F_2(x) + \eta F_3(x) + \eta F_4(x) + \tilde{\epsilon_4}\)

  8. 依此類推…

第三節:梯度提升機(3)

– 注意原始資料中存在「遺漏值」,一個比較簡單的方法是透過套件「mice」進行「多重插補法」

library(mice)

subdat <- dat[!(dat[,"LVD"] %in% NA), c(-1, -2, -4, -5)]

subdat[,'LVD'] <- as.factor(subdat[,'LVD'])
subdat[,'GENDER'] <- as.factor(subdat[,'GENDER'])
for (i in 1:31) {subdat[,paste0('rhythm.', i)] <- as.factor(subdat[,paste0('rhythm.', i)])}

used_dat.x <- subdat[,-1]
mice_dat <- mice(used_dat.x, m = 1, maxit = 5, meth = 'cart', seed = 123, printFlag = FALSE)
impute_dat.x <- mice:::complete(mice_dat, action = 1)

set.seed(0)
all_idx <- 1:nrow(subdat)

train_idx <- sample(all_idx, nrow(subdat) * 0.6)
valid_idx <- sample(all_idx[!all_idx %in% train_idx], nrow(subdat) * 0.2)
test_idx <- all_idx[!all_idx %in% c(train_idx, valid_idx)]

train_X <- impute_dat.x[train_idx,]
valid_X <- impute_dat.x[valid_idx,]
test_X <- impute_dat.x[test_idx,]

train_Y <- subdat[train_idx,"LVD"]
valid_Y <- subdat[valid_idx,"LVD"]
test_Y <- subdat[test_idx,"LVD"]

第三節:梯度提升機(4)

– 這個套件實現的是「eXtreme Gradient Boosting」,跟傳統的梯度提升機不完全一樣,但原理是類似的。

– 他必須吃矩陣格式的資料,並且還要轉換成自己的格式。

library(xgboost)

train_X_mat <- model.matrix(~ ., data = train_X)
xgb.data_train <- xgb.DMatrix(data = train_X_mat[,-1], label = as.integer(train_Y) - 1L)

valid_X_mat <- model.matrix(~ ., data = valid_X)
xgb.data_valid <- xgb.DMatrix(data = valid_X_mat[,-1], label = as.integer(valid_Y) - 1L)

xgb_fit <-  xgb.train(data = xgb.data_train, watchlist = list(eval = xgb.data_valid),
                      early_stopping_rounds = 10, eval_metric = 'auc', verbose = FALSE,
                      max.depth = 5, eta = 0.3,
                      nthread = 2, nrounds = 100, objective = "binary:logistic")
library(pROC)

pred_valid <- predict(xgb_fit, valid_X_mat[,-1])
roc_valid <- roc(valid_Y ~ pred_valid)
plot(roc_valid)
text(0.5, 0.5, paste0('AUC = ', formatC(roc_valid[['auc']], 4, format = 'f')), col = 'red')

第三節:梯度提升機(5)

– 按照我們先前的了解,【eta】應該越小就需要越多的【nrounds】,我們來試試看

xgb_fit <-  xgb.train(data = xgb.data_train, watchlist = list(eval = xgb.data_valid),
                      early_stopping_rounds = 10, eval_metric = 'auc', verbose = FALSE,
                      max.depth = 5, eta = 0.1,
                      nthread = 2, nrounds = 300, objective = "binary:logistic")
library(pROC)

pred_valid <- predict(xgb_fit, valid_X_mat[,-1])
roc_valid <- roc(valid_Y ~ pred_valid)
plot(roc_valid)
text(0.5, 0.5, paste0('AUC = ', formatC(roc_valid[['auc']], 4, format = 'f')), col = 'red')

第三節:梯度提升機(6)

test_X_mat <- model.matrix(~ ., data = test_X)
pred_test <- predict(xgb_fit, test_X_mat[,-1])
roc_test <- roc(test_Y ~ pred_test)

plot(roc_test)
text(0.5, 0.5, paste0('AUC = ', formatC(roc_test[['auc']], 4, format = 'f')), col = 'red')

importance_matrix <- xgb.importance(model = xgb_fit)
xgb.plot.importance(importance_matrix = importance_matrix)

第四節:人工神經網路(1)

F04

– 神經細胞的構造如下,不論是何種神經元皆可分成:接收區、觸發區、傳導區和輸出區。

F05

F06

第四節:人工神經網路(2)

F07

\[ \begin{align} \mbox{weighted sum} & = w_{0} + w_{1}x_1 + w_{2}x_2 + \dots \\ \hat{y} & = step(\mbox{weighted sum}) \end{align} \]

第四節:人工神經網路(3)

F08

library(mxnet)
  1. 資料:對於不同的預測函數需要指定不同的結構,你只要記住你想要用的部分即可

  2. 模型結構:負責定義預測函數

第四節:人工神經網路(4)

– 在MxNet內,我們用最後一個維度描述樣本數:

TRAIN.X.array <- t(train_X_mat[,-1])
TRAIN.Y.array <- t(model.matrix(~ -1 + factor(train_Y)))

VALID.X.array <- t(valid_X_mat[,-1])
library(mxnet)

data = mx.symbol.Variable(name = 'data')
fc1 = mx.symbol.FullyConnected(data = data, num.hidden = 50, name = 'fc1')
act1 = mx.symbol.Activation(data = fc1, act.type = 'relu', name = 'act1')
fc2 = mx.symbol.FullyConnected(data = act1, num.hidden = 2, name = 'fc2')
out_layer = mx.symbol.SoftmaxOutput(data = fc2, name = 'out_layer')

第四節:人工神經網路(5)

my.eval.metric.mlogloss <- mx.metric.custom(
  name = "m-logloss", 
  function(real, pred) {
    real1 = as.numeric(as.array(real))
    pred1 = as.numeric(as.array(pred))
    pred1[pred1 <= 1e-6] = 1e-6
    pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
    return(-mean(real1 * log(pred1), na.rm = TRUE))
  }
)

mx.set.seed(0)

mlp_fit = mx.model.FeedForward.create(symbol = out_layer,
                                      X = TRAIN.X.array, y = TRAIN.Y.array,
                                      optimizer = "adam", learning.rate = 0.001, beta1 = 0.9, beta2 = 0.999,
                                      array.batch.size = 20, num.round = 20,
                                      ctx = mx.cpu(), 
                                      eval.metric = my.eval.metric.mlogloss)
library(pROC)

pred_valid <- predict(mlp_fit, VALID.X.array, array.layout = "colmajor")[2,]
roc_valid <- roc(valid_Y ~ pred_valid)
plot(roc_valid)
text(0.5, 0.5, paste0('AUC = ', formatC(roc_valid[['auc']], 4, format = 'f')), col = 'red')

第五節:卷積神經網路(1)

– 但回到我們的手寫數字分類問題,當我們看到這些手寫數字時,我們一眼就能認出他們了,但從「圖片」到「概念」的過程真的這麼簡單嗎?

– 現在我們面對的是視覺問題,看來除了模擬大腦思考運作的過程之外,我們還需要模擬眼睛的作用!

第五節:卷積神經網路(2)

F09

– 他們的研究發現,貓咪在受到不同形狀的圖像刺激時,感受野的腦部細胞會產生不同反應

F10

第五節:卷積神經網路(3)

– 卷積器模擬了感受野最初的細胞,他們負責用來辨認特定特徵,他們的數學模式如下:

F11

– 「特徵圖」的意義是什麼呢?卷積器就像是最初級的視覺細胞,他們專門辨認某一種簡單特徵,那這個「特徵圖」上面數字越大的,就代表那個地方越符合該細胞所負責的特徵。

F12

第五節:卷積神經網路(4)

F13

F14

第五節:卷積神經網路(5)

– 我們想像有一張人的圖片,假定第一個卷積器是辨認眼睛的特徵,第二個卷積器是在辨認鼻子的特徵,第三個卷積器是在辨認耳朵的特徵,第四個卷積器是在辨認手掌的特徵,第五個卷積器是在辨認手臂的特徵

– 第1.2.3張特徵圖中數值越高的地方,就分別代表眼睛、鼻子、耳朵最有可能在的位置,那將這3張特徵圖合在一起看再一次卷積,是否就能辨認出人臉的位置?

– 第4.5張特徵圖中數值越高的地方,就分別代表手掌、手臂最有可能在的位置,那將這2張特徵圖合在一起看再一次卷積,是否就能辨認出的位置?

– 第4.5張特徵圖對人臉辨識同樣能起到作用,因為人臉不包含手掌、手臂,因此如果有個卷積器想要辨認人臉,他必須對第1.2.3張特徵圖做正向加權,而對第4.5張特徵圖做負向加權

F15

第六節:利用卷積神經網路做手寫數字辨識(1)

F16

第六節:利用卷積神經網路做手寫數字辨識(2)

– 請在這裡下載MNIST的手寫數字資料,並讓我們了解一下這筆資料的結構

library(data.table)

mnist = fread("data/MNIST.csv", data.table = FALSE)
mnist = data.matrix(mnist)

n.sample = dim(mnist)[1]

X = mnist[,-1]
X = t(X)
dim(X) = c(28, 28, 1, dim(mnist)[1])
for (i in 1:dim(X)[4]) {
  X[,,,i] = t(X[,,,i] )
}

Y = array(t(model.matrix(~ -1 + factor(mnist[,1]))), dim = c(10, n.sample))
library(OpenImageR)

imageShow(X[,,,25])

Y[,25]
##  [1] 0 0 1 0 0 0 0 0 0 0

第六節:利用卷積神經網路做手寫數字辨識(3)

– 另外,我們需要對X進行標準化(平均值33.4,標準差78.7),有些人可以嘗試不先標準化的結果,你會發現根本無法訓練!

print(mean(X))
## [1] 33.40891
print(sd(X))
## [1] 78.67774
X = (X - mean(X))/sd(X)

set.seed(0)
Train.sample = sample(1:n.sample, n.sample*0.6, replace = FALSE)

Train.X = X[,,,Train.sample]
dim(Train.X) = c(28, 28, 1, n.sample * 0.6)
Train.Y = Y[,Train.sample]

Test.X = X[,,,-Train.sample]
dim(Test.X) = c(28, 28, 1, n.sample * 0.4)
Test.Y = Y[,-Train.sample]
fwrite(x = data.table(mnist[Train.sample,]),
       file = 'data/train_data.csv',
       col.names = FALSE, row.names = FALSE)

fwrite(x = data.table(mnist[-Train.sample,]),
       file = 'data/test_data.csv',
       col.names = FALSE, row.names = FALSE)

第六節:利用卷積神經網路做手寫數字辨識(4)

# input
data <- mx.symbol.Variable('data')

# first conv
conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=10, name = 'conv1')
relu1 <- mx.symbol.Activation(data=conv1, act_type="relu")
pool1 <- mx.symbol.Pooling(data=relu1, pool_type="max",
                          kernel=c(2,2), stride=c(2,2))

# second conv
conv2 <- mx.symbol.Convolution(data=pool1, kernel=c(5,5), num_filter=20, name = 'conv2')
relu2 <- mx.symbol.Activation(data=conv2, act_type="relu")
pool2 <- mx.symbol.Pooling(data=relu2, pool_type="max",
                          kernel=c(2,2), stride=c(2,2))

# first fullc
flatten <- mx.symbol.Flatten(data=pool2)
fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=150, name = 'fc1')
relu3 <- mx.symbol.Activation(data=fc1, act_type="relu")

# second fullc
fc2 <- mx.symbol.FullyConnected(data=relu3, num_hidden=10, name = 'fc2')

# Softmax
lenet <- mx.symbol.SoftmaxOutput(data = fc2, name = 'lenet')
  1. 原始圖片(28x28x1)要先經過10個5x5的「卷積器」(5x5x1x10)處理,將使圖片變成10張「一階特徵圖」(24x24x10)

  2. 接著這10張「一階特徵圖」(24x24x10)會經過ReLU,產生10張「轉換後的一階特徵圖」(24x24x10)

  3. 接著這10張「轉換後的一階特徵圖」(24x24x10)再經過2x2「池化器」(2x2)處理,將使圖片變成10張「降維後的一階特徵圖」(12x12x10)

– 第二層卷積組合

  1. 再將10張「降維後的一階特徵圖」(12x12x10)經過20個5x5的「卷積器」(5x5x10x20)處理,將使圖片變成20張「二階特徵圖」(8x8x20)

  2. 接著這20張「二階特徵圖」(8x8x20)會經過ReLU,產生20張「轉換後的二階特徵圖」(8x8x20)

  3. 接著這20張「轉換後的二階特徵圖」(8x8x20)再經過2x2「池化器」(2x2)處理,將使圖片變成20張「降維後的二階特徵圖」(4x4x20)

– 全連接層

  1. 將「降維後的二階特徵圖」(4x4x20)重新排列,壓製成「一階高級特徵」(320)

  2. 讓「一階高級特徵」(320)進入「隱藏層」,輸出「二階高級特徵」(150)

  3. 「二階高級特徵」(150)經過ReLU,輸出「轉換後的二階高級特徵」(150)

  4. 「轉換後的二階高級特徵」(150)進入「輸出層」,產生「原始輸出」(10)

  5. 「原始輸出」(10)經過Softmax函數轉換,判斷圖片是哪個類別

第六節:利用卷積神經網路做手寫數字辨識(5)

my.eval.metric.mlogloss <- mx.metric.custom(
  name = "m-logloss", 
  function(real, pred) {
    real1 = as.numeric(as.array(real))
    pred1 = as.numeric(as.array(pred))
    pred1[pred1 <= 1e-6] = 1e-6
    pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
    return(-mean(real1 * log(pred1), na.rm = TRUE))
  }
)

mx.set.seed(0)

lenet_model = mx.model.FeedForward.create(symbol = lenet,
                                          X = Train.X, y = Train.Y,
                                          optimizer = "sgd", learning.rate = 0.05, momentum = 0.9,
                                          array.batch.size = 100, num.round = 20,
                                          ctx = mx.gpu(1),
                                          eval.metric = my.eval.metric.mlogloss)
predict_Y = predict(lenet_model, Test.X)
predict_cat = max.col(t(predict_Y))
real_cat = max.col(t(Test.Y))

confusion_table = table(predict_cat, real_cat)
cat("Testing accuracy rate =", sum(diag(confusion_table))/sum(confusion_table))
## Testing accuracy rate = 0.983869
print(confusion_table)
##            real_cat
## predict_cat    1    2    3    4    5    6    7    8    9   10
##          1  1656    0    4    0    3    3    7    0    4   10
##          2     0 1833    3    1    3    0    1    5    4    5
##          3     1    3 1634    4    1    0    3   10    9    1
##          4     0    0    6 1716    0    2    1    3    3    8
##          5     0    8    5    0 1588    1    8    1    2   14
##          6     1    1    1   14    0 1536    6    0   16   10
##          7     5    1    0    0    0    1 1632    0    5    0
##          8     0    4    3    2    3    3    0 1731    0   16
##          9     0    1    0    4    1    4    3    1 1631    6
##          10    0    0    0    1    7    1    0    2    1 1572

– 我們可以把模型儲存/讀取模型:

#Save model
mx.model.save(lenet_model, "model/lenet", iteration = 0)

#Load model
lenet_model = mx.model.load("model/lenet", iteration = 0)

第七節:利用經典模型進行圖像預測(1)

– 她在2009年CVPR所釋出的資料可以說是這一波深度學習突破中最重要的基石!

F17

– 我們在這裡提供了一個已經預訓練好的模型列表

第七節:利用經典模型進行圖像預測(2)

library(OpenImageR)

img<- readImage('test.jpg') 
resized_img <- resizeImage(img, 224, 224, method = 'bilinear')

imageShow(resized_img)

第七節:利用經典模型進行圖像預測(3)

library(mxnet)

res_model <- mx.model.load("model/resnet-50", 0)

dim(resized_img) <- c(dim(resized_img), 1)
pred_prob  <- predict(res_model, resized_img)
which.max(pred_prob)
## [1] 3
synsets <- readLines('model/chinese synset.txt', encoding = 'UTF-8')

pred_prob <- as.numeric(pred_prob)
names(pred_prob) <- synsets
pred_prob <- sort(pred_prob, decreasing = TRUE)
pred_prob <- formatC(pred_prob, 4, format = 'f')

head(pred_prob, 5)
##             n01484850 大白鯊               n01491361 虎鯊 
##                     "0.9971"                     "0.0027" 
##             n01494475 鎚頭鯊 n02071294 殺人鯨,逆戟鯨,虎鯨 
##                     "0.0001"                     "0.0001" 
##               n02066245 灰鯨 
##                     "0.0000"

第八節:訓練一個圖像分類模型並分享他的預測功能(1)

– 稍微試一下他的能力,你可以隨便下載一張圖片,這跟剛剛的一樣

– 舉例來說,我們可以定義我們要整個resnet-50除了最後一個全連接層外的所有結構,只把最後一層的FC從分1000類轉變成分2類:

library(magrittr)
library(mxnet)

res_model <- mx.model.load("model/resnet-50", 0)

all_layers <- res_model$symbol$get.internals()
flatten0_output <- which(all_layers$outputs == 'flatten0_output') %>% all_layers$get.output()

fc1 <- mx.symbol.FullyConnected(data = flatten0_output, num_hidden = 2, name = 'fc1')
softmax <- mx.symbol.SoftmaxOutput(data = fc1, name = 'softmax')

第八節:訓練一個圖像分類模型並分享他的預測功能(2)

Train_img <- array(0, dim = c(224, 224, 3, 200))
Train.y <- array(0, dim = c(2, 200))

for (i in 1:100) {
  
  # Cat
  img <- readImage(paste0('Dogs vs. Cats/cat.', i, '.jpg'))
  resized_img <- resizeImage(img, 224, 224, method = 'bilinear')
  Train_img[,,,2*i-1] <- resized_img
  Train.y[1,2*i-1] <- 1
  
  # Dog
  img <- readImage(paste0('Dogs vs. Cats/dog.', i, '.jpg'))
  resized_img <- resizeImage(img, 224, 224, method = 'bilinear')
  Train_img[,,,2*i] <- resized_img
  Train.y[2,2*i] <- 1
  
}

第八節:訓練一個圖像分類模型並分享他的預測功能(3)

– 接著,我們在開始訓練之前需要取得模型權重參數,我們可以將最後一層以外的部分填入resnet-50的參數,並以這為基礎開始訓練任務:

new_arg <- mxnet:::mx.model.init.params(symbol = softmax,
                                        input.shape = list(data = c(224, 224, 3, 32)),
                                        output.shape = NULL,
                                        initializer = mxnet:::mx.init.uniform(0.01),
                                        ctx = mx.cpu())

for (i in 1:length(new_arg$arg.params)) {
  pos <- which(names(res_model$arg.params) == names(new_arg$arg.params)[i])
  if (length(pos) == 1) {
    if (all.equal(dim(res_model$arg.params[[pos]]), dim(new_arg$arg.params[[i]])) == TRUE) {
      new_arg$arg.params[[i]] <- res_model$arg.params[[pos]]
    }
  }
}

for (i in 1:length(new_arg$aux.params)) {
  pos <- which(names(res_model$aux.params) == names(new_arg$aux.params)[i])
  if (length(pos) == 1) {
    if (all.equal(dim(res_model$aux.params[[pos]]), dim(new_arg$aux.params[[i]])) == TRUE) {
      new_arg$aux.params[[i]] <- res_model$aux.params[[pos]]
    }
  }
}

第八節:訓練一個圖像分類模型並分享他的預測功能(4)

my.eval.metric.mlogloss <- mx.metric.custom(
  name = "m-logloss", 
  function(real, pred) {
    real1 = as.numeric(as.array(real))
    pred1 = as.numeric(as.array(pred))
    pred1[pred1 <= 1e-6] = 1e-6
    pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
    return(-mean(real1 * log(pred1), na.rm = TRUE))
  }
)

mx.set.seed(0)

my_model <- mx.model.FeedForward.create(symbol = softmax,
                                       X = Train_img, y = Train.y,
                                       optimizer = "sgd", learning.rate = 0.001, momentum = 0.9,
                                       array.batch.size = 20, num.round = 20,
                                       arg.params = new_arg$arg.params, aux.params = new_arg$aux.params,
                                       ctx = mx.gpu(1),
                                       eval.metric = my.eval.metric.mlogloss)

第八節:訓練一個圖像分類模型並分享他的預測功能(5)

img <- readImage('Dogs vs. Cats/test_cat.3.jpg')
resized_img <- resizeImage(img, 224, 224, method = 'bilinear')

imageShow(resized_img)

dim(resized_img) = c(224, 224, 3, 1)

pred_prob <- predict(my_model, resized_img)
pred_prob
##             [,1]
## [1,] 0.996811688
## [2,] 0.003188348

第八節:訓練一個圖像分類模型並分享他的預測功能(6)

img <- readImage('Dogs vs. Cats/test_dog.3.jpg')
resized_img <- resizeImage(img, 224, 224, method = 'bilinear')

imageShow(resized_img)

dim(resized_img) <- c(224, 224, 3, 1)

pred_prob <- predict(my_model, resized_img)
pred_prob
##            [,1]
## [1,] 0.05498257
## [2,] 0.94501746

結語

– 如果你想把自己訓練的模型放到剛剛的App內,還要記得改變chinese synset.txt檔案喔!

– 另外,深度學習的潛力可不僅僅只有圖像辨識任務,其他包含物件識別、物件分割、圖像生成,甚至是語言模型,都是可以做出來的,這也值得有興趣的人再進一步學習!