rm(list=ls(all=T))
options(digits=4, scipen=40)
library(dplyr)
library(keras)   # 類似xgboost,randomforest的模型



1. Read & Prepare Data

Reading & Examining data …

mnist = dataset_mnist()
par(mfrow = c(6, 8), pty = "s", mar = c(0.5, 0.5, 0, 0))
for(p in 1:48) mnist$train$x[p,,] %>% as.raster(max=255) %>% plot

Reshape the data …

train_images = array_reshape(mnist$train$x, c(60000, 28 * 28))
train_images = train_images / 255                    # normalization #做神經網路的時候,值要介於在0~1之間的灰階數字(255)跑起來才快
test_images = array_reshape(mnist$test$x, c(10000, 28 * 28))
test_images = normalization= test_images / 255       # normalization
train_labels = to_categorical(mnist$train$y)         # 做一個類別模型(數字0~9的圖片分類)
test_labels = to_categorical(mnist$test$y)



2. Traditional Neural Network (MLP)

2.1 Netwrok Parameters

mlp = keras_model_sequential() %>% 
  layer_dense(units = 512,               # number of perceptron
              activation = "relu",       # activation function
              input_shape = c(784)       # dimensions of input tensor
              ) %>% 
  layer_dense(units = 10,                # one output neuron per class # 最後一層你有多少類別就有多少顆
              activation = "softmax"     # activate the largest one
              )
summary(mlp)  # summary of the network spec
_______________________________________________________________________________________
Layer (type)                           Output Shape                      Param #       
=======================================================================================
dense_13 (Dense)                       (None, 512)                       401920        
_______________________________________________________________________________________
dense_14 (Dense)                       (None, 10)                        5130          
=======================================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_______________________________________________________________________________________

No. Coefficients (Param #) in Each Layer

  • dense_1: (28 * 28 + 1) * 512 = 401,920
  • dense_2: (512 + 1) * 10 = 5,130


2.2 Training Parameters

# 照抄(此為default的寫法)
mlp %>% compile(                       # specify
  optimizer = "rmsprop",               # optimizer
  loss = "categorical_crossentropy",   # loss function
  metrics = c("accuracy")              # accuracy metrice  
  )

2.3 Fitting Model

fit1 = mlp %>% fit(
  train_images,         # train data
  train_labels,         # label of train data
  epochs=10,            # no. epochs # 做幾次訓練
  batch_size=128,       # no. input per mini-batch # batch_size大會跑比較快(但GPU就要更大), depends on 硬體大小
  verbose=2             # 每個epoch打一個進度
  )
Epoch 1/10
 - 2s - loss: 0.2554 - acc: 0.9260
Epoch 2/10
 - 2s - loss: 0.1022 - acc: 0.9695
Epoch 3/10
 - 2s - loss: 0.0680 - acc: 0.9800
Epoch 4/10
 - 2s - loss: 0.0488 - acc: 0.9852
Epoch 5/10
 - 2s - loss: 0.0374 - acc: 0.9887
Epoch 6/10
 - 2s - loss: 0.0284 - acc: 0.9911
Epoch 7/10
 - 2s - loss: 0.0218 - acc: 0.9935
Epoch 8/10
 - 2s - loss: 0.0172 - acc: 0.9948
Epoch 9/10
 - 2s - loss: 0.0128 - acc: 0.9964
Epoch 10/10
 - 2s - loss: 0.0102 - acc: 0.9970
plot(fit1) # 每多做一次(epoch),acc就會上升一點,loss就會下降一點

2.4 Validation & Predictione

# Evaluation
mlp %>% evaluate(test_images, test_labels, verbose=2) # 等同於predict(這裡寫法不一樣) # verbose不打也沒關係
$loss
[1] 0.07241

$acc
[1] 0.982
# 0.98還不夠好不夠深(神經網路應該要100)
# Prediction - Classes
mlp %>% predict_classes(test_images[1:10,])
 [1] 7 2 1 0 4 1 4 9 5 9
# Prediction Probability
mlp %>% predict_proba(test_images[1:10,]) %>% round(4)
      [,1]   [,2] [,3]   [,4]   [,5]   [,6]   [,7]   [,8] [,9]  [,10]
 [1,]    0 0.0000    0 0.0000 0.0000 0.0000 0.0000 1.0000    0 0.0000
 [2,]    0 0.0000    1 0.0000 0.0000 0.0000 0.0000 0.0000    0 0.0000
 [3,]    0 0.9995    0 0.0000 0.0000 0.0000 0.0000 0.0004    0 0.0000
 [4,]    1 0.0000    0 0.0000 0.0000 0.0000 0.0000 0.0000    0 0.0000
 [5,]    0 0.0000    0 0.0000 0.9991 0.0000 0.0000 0.0001    0 0.0008
 [6,]    0 0.9979    0 0.0000 0.0000 0.0000 0.0000 0.0021    0 0.0000
 [7,]    0 0.0000    0 0.0000 1.0000 0.0000 0.0000 0.0000    0 0.0000
 [8,]    0 0.0000    0 0.0004 0.0000 0.0000 0.0000 0.0000    0 0.9996
 [9,]    0 0.0000    0 0.0000 0.0000 0.9995 0.0005 0.0000    0 0.0000
[10,]    0 0.0000    0 0.0000 0.0000 0.0000 0.0000 0.0087    0 0.9913



3. Convolutional Neural Network (CNN)

3.1 Load nad Reshape

# mnist <- dataset_mnist()
# c(c(train_images, train_labels), c(test_images, test_labels)) %<-% mnist
# train_images <- array_reshape(train_images, c(60000, 28, 28, 1))
# train_images <- train_images / 255
# test_images <- array_reshape(test_images, c(10000, 28, 28, 1))
# test_images <- test_images / 255
# train_labels <- to_categorical(train_labels)
# test_labels <- to_categorical(test_labels)
train_images = array_reshape(mnist$train$x, c(60000, 28, 28, 1))
train_images = train_images / 255                    # normalization
test_images = array_reshape(mnist$test$x, c(10000, 28, 28, 1))
test_images = normalization= test_images / 255       # normalization
train_labels = to_categorical(mnist$train$y)
test_labels = to_categorical(mnist$test$y)

3.2 Method and Training Parameters

cnn <- keras_model_sequential() %>% 
  layer_conv_2d(filters = 32, kernel_size = c(3, 3), activation = "relu",
                input_shape = c(28, 28, 1)) %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>% 
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_max_pooling_2d(pool_size = c(2, 2)) %>% 
  layer_conv_2d(filters = 64, kernel_size = c(3, 3), activation = "relu") %>% 
  layer_flatten() %>% 
  layer_dense(units = 64, activation = "relu") %>% 
  layer_dense(units = 10, activation = "softmax")
summary(cnn)
_______________________________________________________________________________________
Layer (type)                           Output Shape                      Param #       
=======================================================================================
conv2d_10 (Conv2D)                     (None, 26, 26, 32)                320           
_______________________________________________________________________________________
max_pooling2d_7 (MaxPooling2D)         (None, 13, 13, 32)                0             
_______________________________________________________________________________________
conv2d_11 (Conv2D)                     (None, 11, 11, 64)                18496         
_______________________________________________________________________________________
max_pooling2d_8 (MaxPooling2D)         (None, 5, 5, 64)                  0             
_______________________________________________________________________________________
conv2d_12 (Conv2D)                     (None, 3, 3, 64)                  36928         
_______________________________________________________________________________________
flatten_4 (Flatten)                    (None, 576)                       0             
_______________________________________________________________________________________
dense_15 (Dense)                       (None, 64)                        36928         
_______________________________________________________________________________________
dense_16 (Dense)                       (None, 10)                        650           
=======================================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_______________________________________________________________________________________

No. Coefficients (Param #) in Each Layer

  • conv2d_1: (3 * 3 * 1 + 1) * 32 = 320
  • conv2d_2: (3 * 3 * 32 + 1) * 64 = 18,496
  • conv2d_3: (3 * 3 * 64 + 1) * 64 = 36,928
  • dense_3: (576 + 1) * 64 = 36,928
  • dense_4: (64 + 1) * 10 = 650
cnn %>% compile(
  optimizer = "rmsprop",
  loss = "categorical_crossentropy",
  metrics = c("accuracy"))

3.2 Fitting Model

# 這個階段才真正在做training
fit2 = cnn %>% fit(
  train_images, train_labels, 
  epochs = 5,        # 5 epochs # 用少一點的epoch來示範
  batch_size=64,     # 64 samples per mini-batch
  verbose = 2
  )
Epoch 1/5
 - 8s - loss: 0.1672 - acc: 0.9474
Epoch 2/5
 - 7s - loss: 0.0461 - acc: 0.9858
Epoch 3/5
 - 7s - loss: 0.0327 - acc: 0.9903
Epoch 4/5
 - 7s - loss: 0.0252 - acc: 0.9923
Epoch 5/5
 - 7s - loss: 0.0196 - acc: 0.9943
plot(fit2)

3.3 Evaluation

cnn %>% evaluate(test_images, test_labels, verbose=2) # 從98提高到99
$loss
[1] 0.02187

$acc
[1] 0.9929





LS0tCnRpdGxlOiAiTU5JU1Q6IFNpbXBsZSBQYXR0ZXJuIFJlY29nbml0aW9uIgphdXRob3I6ICJ0b255Y2h1b0BtYWlsLm5zeXN1LmVkdS50dyIKZGF0ZTogImByIFN5cy50aW1lKClgIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgo8YnI+CmBgYHtyIHNldC1vcHRpb25zLCBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRX0KbGlicmFyeShrbml0cikKb3B0aW9ucyh3aWR0aD0xMDApCm9wdHNfY2h1bmskc2V0KGNvbW1lbnQgPSBOQSkKYGBgCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgY2FjaGU9RiwgZXJyb3I9Rn0Kcm0obGlzdD1scyhhbGw9VCkpCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj00MCkKbGlicmFyeShkcGx5cikKbGlicmFyeShrZXJhcykgICAjIOmhnuS8vHhnYm9vc3QscmFuZG9tZm9yZXN055qE5qih5Z6LCmBgYAo8YnI+CgotIC0gLQoKIyMjIDEuIFJlYWQgJiBQcmVwYXJlIERhdGEKUmVhZGluZyAmIEV4YW1pbmluZyBkYXRhIC4uLgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQptbmlzdCA9IGRhdGFzZXRfbW5pc3QoKQoKcGFyKG1mcm93ID0gYyg2LCA4KSwgcHR5ID0gInMiLCBtYXIgPSBjKDAuNSwgMC41LCAwLCAwKSkKZm9yKHAgaW4gMTo0OCkgbW5pc3QkdHJhaW4keFtwLCxdICU+JSBhcy5yYXN0ZXIobWF4PTI1NSkgJT4lIHBsb3QKYGBgCgpSZXNoYXBlIHRoZSBkYXRhIC4uLgpgYGB7cn0KdHJhaW5faW1hZ2VzID0gYXJyYXlfcmVzaGFwZShtbmlzdCR0cmFpbiR4LCBjKDYwMDAwLCAyOCAqIDI4KSkKdHJhaW5faW1hZ2VzID0gdHJhaW5faW1hZ2VzIC8gMjU1ICAgICAgICAgICAgICAgICAgICAjIG5vcm1hbGl6YXRpb24gI+WBmuelnue2k+e2sui3r+eahOaZguWAme+8jOWAvOimgeS7i+aWvOWcqDB+MeS5i+mWk+eahOeBsOmajuaVuOWtlygyNTUp6LeR6LW35L6G5omN5b+rCnRlc3RfaW1hZ2VzID0gYXJyYXlfcmVzaGFwZShtbmlzdCR0ZXN0JHgsIGMoMTAwMDAsIDI4ICogMjgpKQp0ZXN0X2ltYWdlcyA9IG5vcm1hbGl6YXRpb249IHRlc3RfaW1hZ2VzIC8gMjU1ICAgICAgICMgbm9ybWFsaXphdGlvbgp0cmFpbl9sYWJlbHMgPSB0b19jYXRlZ29yaWNhbChtbmlzdCR0cmFpbiR5KSAgICAgICAgICMg5YGa5LiA5YCL6aGe5Yil5qih5Z6LKOaVuOWtlzB+OeeahOWclueJh+WIhumhnikKdGVzdF9sYWJlbHMgPSB0b19jYXRlZ29yaWNhbChtbmlzdCR0ZXN0JHkpCmBgYAo8YnI+CgotIC0gLQoKIyMjIDIuIFRyYWRpdGlvbmFsIE5ldXJhbCBOZXR3b3JrIChNTFApCgojIyMjIDIuMSBOZXR3cm9rIFBhcmFtZXRlcnMKYGBge3J9Cm1scCA9IGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA1MTIsICAgICAgICAgICAgICAgIyBudW1iZXIgb2YgcGVyY2VwdHJvbgogICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAicmVsdSIsICAgICAgICMgYWN0aXZhdGlvbiBmdW5jdGlvbgogICAgICAgICAgICAgIGlucHV0X3NoYXBlID0gYyg3ODQpICAgICAgICMgZGltZW5zaW9ucyBvZiBpbnB1dCB0ZW5zb3IKICAgICAgICAgICAgICApICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwLCAgICAgICAgICAgICAgICAjIG9uZSBvdXRwdXQgbmV1cm9uIHBlciBjbGFzcyAjIOacgOW+jOS4gOWxpOS9oOacieWkmuWwkemhnuWIpeWwseacieWkmuWwkemhhgogICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAic29mdG1heCIgICAgICMgYWN0aXZhdGUgdGhlIGxhcmdlc3Qgb25lCiAgICAgICAgICAgICAgKQoKc3VtbWFyeShtbHApICAjIHN1bW1hcnkgb2YgdGhlIG5ldHdvcmsgc3BlYwpgYGAKCk5vLiBDb2VmZmljaWVudHMgKGBQYXJhbSAjYCkgaW4gRWFjaCBMYXllcgoKKyBgZGVuc2VfMTogKDI4ICogMjggKyAxKSAqIDUxMiA9IDQwMSw5MjBgIAorIGBkZW5zZV8yOiAoNTEyICsgMSkgKiAxMCA9IDUsMTMwYCAKCjxicj4KCiMjIyMgMi4yIFRyYWluaW5nIFBhcmFtZXRlcnMKYGBge3J9IAojIOeFp+aKhCjmraTngrpkZWZhdWx055qE5a+r5rOVKQptbHAgJT4lIGNvbXBpbGUoICAgICAgICAgICAgICAgICAgICAgICAjIHNwZWNpZnkKICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsICAgICAgICAgICAgICAgIyBvcHRpbWl6ZXIKICBsb3NzID0gImNhdGVnb3JpY2FsX2Nyb3NzZW50cm9weSIsICAgIyBsb3NzIGZ1bmN0aW9uCiAgbWV0cmljcyA9IGMoImFjY3VyYWN5IikgICAgICAgICAgICAgICMgYWNjdXJhY3kgbWV0cmljZSAgCiAgKQpgYGAKCiMjIyMgMi4zIEZpdHRpbmcgTW9kZWwKYGBge3J9CmZpdDEgPSBtbHAgJT4lIGZpdCgKICB0cmFpbl9pbWFnZXMsICAgICAgICAgIyB0cmFpbiBkYXRhCiAgdHJhaW5fbGFiZWxzLCAgICAgICAgICMgbGFiZWwgb2YgdHJhaW4gZGF0YQogIGVwb2Nocz0xMCwgICAgICAgICAgICAjIG5vLiBlcG9jaHMgIyDlgZrlub7mrKHoqJPnt7QKICBiYXRjaF9zaXplPTEyOCwgICAgICAgIyBuby4gaW5wdXQgcGVyIG1pbmktYmF0Y2ggIyBiYXRjaF9zaXpl5aSn5pyD6LeR5q+U6LyD5b+rKOS9hkdQVeWwseimgeabtOWkpyksIGRlcGVuZHMgb24g56Gs6auU5aSn5bCPCiAgdmVyYm9zZT0yICAgICAgICAgICAgICMg5q+P5YCLZXBvY2jmiZPkuIDlgIvpgLLluqYKICApCmBgYAoKYGBge3J9CnBsb3QoZml0MSkgIyDmr4/lpJrlgZrkuIDmrKEoZXBvY2gpLGFjY+Wwseacg+S4iuWNh+S4gOm7nixsb3Nz5bCx5pyD5LiL6ZmN5LiA6bueCmBgYAoKIyMjIyAyLjQgVmFsaWRhdGlvbiAmIFByZWRpY3Rpb25lCmBgYHtyfQojIEV2YWx1YXRpb24KbWxwICU+JSBldmFsdWF0ZSh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMsIHZlcmJvc2U9MikgIyDnrYnlkIzmlrxwcmVkaWN0KOmAmeijoeWvq+azleS4jeS4gOaooykgIyB2ZXJib3Nl5LiN5omT5Lmf5rKS6Zec5L+CCiMgMC45OOmChOS4jeWkoOWlveS4jeWkoOa3sSjnpZ7ntpPntrLot6/mh4noqbLopoExMDApCmBgYAoKYGBge3J9CiMgUHJlZGljdGlvbiAtIENsYXNzZXMKbWxwICU+JSBwcmVkaWN0X2NsYXNzZXModGVzdF9pbWFnZXNbMToxMCxdKQpgYGAKCmBgYHtyfQojIFByZWRpY3Rpb24gUHJvYmFiaWxpdHkKbWxwICU+JSBwcmVkaWN0X3Byb2JhKHRlc3RfaW1hZ2VzWzE6MTAsXSkgJT4lIHJvdW5kKDQpCmBgYAoKPGJyPgoKLSAtIC0KCiMjIyAzLiBDb252b2x1dGlvbmFsIE5ldXJhbCBOZXR3b3JrIChDTk4pCgojIyMjIDMuMSBMb2FkIG5hZCBSZXNoYXBlCmBgYHtyfQojIG1uaXN0IDwtIGRhdGFzZXRfbW5pc3QoKQojIGMoYyh0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscyksIGModGVzdF9pbWFnZXMsIHRlc3RfbGFiZWxzKSkgJTwtJSBtbmlzdAojIHRyYWluX2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRyYWluX2ltYWdlcywgYyg2MDAwMCwgMjgsIDI4LCAxKSkKIyB0cmFpbl9pbWFnZXMgPC0gdHJhaW5faW1hZ2VzIC8gMjU1CiMgdGVzdF9pbWFnZXMgPC0gYXJyYXlfcmVzaGFwZSh0ZXN0X2ltYWdlcywgYygxMDAwMCwgMjgsIDI4LCAxKSkKIyB0ZXN0X2ltYWdlcyA8LSB0ZXN0X2ltYWdlcyAvIDI1NQojIHRyYWluX2xhYmVscyA8LSB0b19jYXRlZ29yaWNhbCh0cmFpbl9sYWJlbHMpCiMgdGVzdF9sYWJlbHMgPC0gdG9fY2F0ZWdvcmljYWwodGVzdF9sYWJlbHMpCgp0cmFpbl9pbWFnZXMgPSBhcnJheV9yZXNoYXBlKG1uaXN0JHRyYWluJHgsIGMoNjAwMDAsIDI4LCAyOCwgMSkpCnRyYWluX2ltYWdlcyA9IHRyYWluX2ltYWdlcyAvIDI1NSAgICAgICAgICAgICAgICAgICAgIyBub3JtYWxpemF0aW9uCnRlc3RfaW1hZ2VzID0gYXJyYXlfcmVzaGFwZShtbmlzdCR0ZXN0JHgsIGMoMTAwMDAsIDI4LCAyOCwgMSkpCnRlc3RfaW1hZ2VzID0gbm9ybWFsaXphdGlvbj0gdGVzdF9pbWFnZXMgLyAyNTUgICAgICAgIyBub3JtYWxpemF0aW9uCnRyYWluX2xhYmVscyA9IHRvX2NhdGVnb3JpY2FsKG1uaXN0JHRyYWluJHkpCnRlc3RfbGFiZWxzID0gdG9fY2F0ZWdvcmljYWwobW5pc3QkdGVzdCR5KQoKYGBgCgojIyMjIDMuMiBNZXRob2QgYW5kIFRyYWluaW5nIFBhcmFtZXRlcnMKYGBge3J9CmNubiA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDMyLCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIsCiAgICAgICAgICAgICAgICBpbnB1dF9zaGFwZSA9IGMoMjgsIDI4LCAxKSkgJT4lIAogIGxheWVyX21heF9wb29saW5nXzJkKHBvb2xfc2l6ZSA9IGMoMiwgMikpICU+JSAKICBsYXllcl9jb252XzJkKGZpbHRlcnMgPSA2NCwga2VybmVsX3NpemUgPSBjKDMsIDMpLCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUgCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9mbGF0dGVuKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gNjQsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwLCBhY3RpdmF0aW9uID0gInNvZnRtYXgiKQoKc3VtbWFyeShjbm4pCmBgYAoKTm8uIENvZWZmaWNpZW50cyAoYFBhcmFtICNgKSBpbiBFYWNoIExheWVyCgorIGBjb252MmRfMTogKDMgKiAzICogIDEgKyAxKSAqIDMyID0gMzIwYCAKKyBgY29udjJkXzI6ICgzICogMyAqIDMyICsgMSkgKiA2NCA9IDE4LDQ5NmAgCisgYGNvbnYyZF8zOiAoMyAqIDMgKiA2NCArIDEpICogNjQgPSAzNiw5MjhgIAorIGBkZW5zZV8zOiAgKDU3NiArIDEpICogNjQgPSAzNiw5MjhgIAorIGBkZW5zZV80OiAgKDY0ICArIDEpICogMTAgPSA2NTBgIAoKCmBgYHtyfQpjbm4gJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogIGxvc3MgPSAiY2F0ZWdvcmljYWxfY3Jvc3NlbnRyb3B5IiwKICBtZXRyaWNzID0gYygiYWNjdXJhY3kiKSkKYGBgCgojIyMjIDMuMiBGaXR0aW5nIE1vZGVsCmBgYHtyfQojIOmAmeWAi+majuauteaJjeecn+ato+WcqOWBmnRyYWluaW5nCmZpdDIgPSBjbm4gJT4lIGZpdCgKICB0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscywgCiAgZXBvY2hzID0gNSwgICAgICAgICMgNSBlcG9jaHMgIyDnlKjlsJHkuIDpu57nmoRlcG9jaOS+huekuuevhAogIGJhdGNoX3NpemU9NjQsICAgICAjIDY0IHNhbXBsZXMgcGVyIG1pbmktYmF0Y2gKICB2ZXJib3NlID0gMgogICkKYGBgCgpgYGB7cn0KcGxvdChmaXQyKQpgYGAKCgojIyMjIDMuMyBFdmFsdWF0aW9uCmBgYHtyfQpjbm4gJT4lIGV2YWx1YXRlKHRlc3RfaW1hZ2VzLCB0ZXN0X2xhYmVscywgdmVyYm9zZT0yKSAjIOW+njk45o+Q6auY5YiwOTkKYGBgCgo8YnI+PGJyPjxicj48YnI+Cgo8c3R5bGU+Ci5jYXB0aW9uIHsKICBjb2xvcjogIzc3NzsKICBtYXJnaW4tdG9wOiAxMHB4Owp9CnAgY29kZSB7CiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7Cn0KcHJlIHsKICB3b3JkLWJyZWFrOiBub3JtYWw7CiAgd29yZC13cmFwOiBub3JtYWw7CiAgbGluZS1oZWlnaHQ6IDE7Cn0KcHJlIGNvZGUgewogIHdoaXRlLXNwYWNlOiBpbmhlcml0Owp9CnAsbGkgewogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoucnsKICBsaW5lLWhlaWdodDogMS4yOwp9CgoucWl6IHsKICBsaW5lLWhlaWdodDogMS43NTsKICBiYWNrZ3JvdW5kOiAjZjBmMGYwOwogIGJvcmRlci1sZWZ0OiAxMnB4IHNvbGlkICNjY2ZmY2M7CiAgcGFkZGluZzogNHB4OwogIHBhZGRpbmctbGVmdDogMTBweDsKICBjb2xvcjogIzAwOTkwMDsKfQoKdGl0bGV7CiAgY29sb3I6ICNjYzAwMDA7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmJvZHl7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmgxLGgyLGgzLGg0LGg1ewogIGNvbG9yOiAjMDA2NmZmOwogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoKaDN7CiAgY29sb3I6ICMwMDg4MDA7CiAgYmFja2dyb3VuZDogI2U2ZmZlNjsKICBsaW5lLWhlaWdodDogMjsKICBmb250LXdlaWdodDogYm9sZDsKfQoKaDV7CiAgY29sb3I6ICMwMDYwMDA7CiAgYmFja2dyb3VuZDogI2Y4ZjhmODsKICBsaW5lLWhlaWdodDogMS41OwogIGZvbnQtd2VpZ2h0OiBib2xkOwp9Cjwvc3R5bGU+CgoKCgo=