rm(list=ls(all=T))
options(digits=4, scipen=40)
library(dplyr)
library(keras)



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
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)
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_1 (Dense)                              (None, 512)                             401920         
____________________________________________________________________________________________________
dense_2 (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

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
  verbose=2
  )
2018-10-26 07:52:45.459740: I tensorflow/core/platform/cpu_feature_guard.cc:140] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2018-10-26 07:52:45.641383: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:898] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2018-10-26 07:52:45.641795: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1356] Found device 0 with properties: 
name: Tesla K80 major: 3 minor: 7 memoryClockRate(GHz): 0.8235
pciBusID: 0000:00:04.0
totalMemory: 11.17GiB freeMemory: 11.09GiB
2018-10-26 07:52:45.641841: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1435] Adding visible gpu devices: 0
2018-10-26 07:52:49.789244: I tensorflow/core/common_runtime/gpu/gpu_device.cc:923] Device interconnect StreamExecutor with strength 1 edge matrix:
2018-10-26 07:52:49.789317: I tensorflow/core/common_runtime/gpu/gpu_device.cc:929]      0 
2018-10-26 07:52:49.789332: I tensorflow/core/common_runtime/gpu/gpu_device.cc:942] 0:   N 
2018-10-26 07:52:49.795362: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1053] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 10750 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)
Epoch 1/10
 - 8s - loss: 0.2556 - acc: 0.9266
Epoch 2/10
 - 2s - loss: 0.1024 - acc: 0.9698
Epoch 3/10
 - 2s - loss: 0.0681 - acc: 0.9796
Epoch 4/10
 - 2s - loss: 0.0494 - acc: 0.9849
Epoch 5/10
 - 2s - loss: 0.0376 - acc: 0.9883
Epoch 6/10
 - 2s - loss: 0.0285 - acc: 0.9916
Epoch 7/10
 - 2s - loss: 0.0216 - acc: 0.9936
Epoch 8/10
 - 2s - loss: 0.0174 - acc: 0.9947
Epoch 9/10
 - 2s - loss: 0.0129 - acc: 0.9961
Epoch 10/10
 - 2s - loss: 0.0104 - acc: 0.9970
plot(fit1)

2.4 Validation & Predictione

# Evaluation
mlp %>% evaluate(test_images, test_labels, verbose=2)
$loss
[1] 0.0742

$acc
[1] 0.9818
# 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    0 1.0000    0 0.0000
 [2,]    0 0.0000    1 0.0000 0.0000    0    0 0.0000    0 0.0000
 [3,]    0 0.9999    0 0.0000 0.0000    0    0 0.0000    0 0.0000
 [4,]    1 0.0000    0 0.0000 0.0000    0    0 0.0000    0 0.0000
 [5,]    0 0.0000    0 0.0000 0.9953    0    0 0.0000    0 0.0047
 [6,]    0 0.9999    0 0.0000 0.0000    0    0 0.0001    0 0.0000
 [7,]    0 0.0000    0 0.0000 1.0000    0    0 0.0000    0 0.0000
 [8,]    0 0.0000    0 0.0004 0.0001    0    0 0.0000    0 0.9995
 [9,]    0 0.0000    0 0.0000 0.0000    1    0 0.0000    0 0.0000
[10,]    0 0.0000    0 0.0000 0.0000    0    0 0.0000    0 1.0000



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_1 (Conv2D)                            (None, 26, 26, 32)                      320            
____________________________________________________________________________________________________
max_pooling2d_1 (MaxPooling2D)               (None, 13, 13, 32)                      0              
____________________________________________________________________________________________________
conv2d_2 (Conv2D)                            (None, 11, 11, 64)                      18496          
____________________________________________________________________________________________________
max_pooling2d_2 (MaxPooling2D)               (None, 5, 5, 64)                        0              
____________________________________________________________________________________________________
conv2d_3 (Conv2D)                            (None, 3, 3, 64)                        36928          
____________________________________________________________________________________________________
flatten_1 (Flatten)                          (None, 576)                             0              
____________________________________________________________________________________________________
dense_3 (Dense)                              (None, 64)                              36928          
____________________________________________________________________________________________________
dense_4 (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

fit2 = cnn %>% fit(
  train_images, train_labels, 
  epochs = 5,        # 5 epochs
  batch_size=64,     # 64 samples per mini-batch
  verbose = 2
  )
Epoch 1/5
 - 12s - loss: 0.1775 - acc: 0.9443
Epoch 2/5
 - 7s - loss: 0.0481 - acc: 0.9850
Epoch 3/5
 - 7s - loss: 0.0326 - acc: 0.9893
Epoch 4/5
 - 8s - loss: 0.0245 - acc: 0.9923
Epoch 5/5
 - 7s - loss: 0.0200 - acc: 0.9937
plot(fit2)

3.3 Evaluation

cnn %>% evaluate(test_images, test_labels, verbose=2)
$loss
[1] 0.02787

$acc
[1] 0.9927





LS0tCnRpdGxlOiAiTU5JU1Q6IFNpbXBsZSBQYXR0ZXJuIFJlY29nbml0aW9uIgphdXRob3I6ICJ0b255Y2h1b0BtYWlsLm5zeXN1LmVkdS50dyIKZGF0ZTogImByIFN5cy50aW1lKClgIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgo8YnI+CmBgYHtyIHNldC1vcHRpb25zLCBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRX0KbGlicmFyeShrbml0cikKb3B0aW9ucyh3aWR0aD0xMDApCm9wdHNfY2h1bmskc2V0KGNvbW1lbnQgPSBOQSkKYGBgCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgY2FjaGU9RiwgZXJyb3I9Rn0Kcm0obGlzdD1scyhhbGw9VCkpCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj00MCkKbGlicmFyeShkcGx5cikKbGlicmFyeShrZXJhcykKYGBgCjxicj4KCi0gLSAtCgojIyMgMS4gUmVhZCAmIFByZXBhcmUgRGF0YQpSZWFkaW5nICYgRXhhbWluaW5nIGRhdGEgLi4uCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTZ9Cm1uaXN0ID0gZGF0YXNldF9tbmlzdCgpCgpwYXIobWZyb3cgPSBjKDYsIDgpLCBwdHkgPSAicyIsIG1hciA9IGMoMC41LCAwLjUsIDAsIDApKQpmb3IocCBpbiAxOjQ4KSBtbmlzdCR0cmFpbiR4W3AsLF0gJT4lIGFzLnJhc3RlcihtYXg9MjU1KSAlPiUgcGxvdApgYGAKClJlc2hhcGUgdGhlIGRhdGEgLi4uCmBgYHtyfQp0cmFpbl9pbWFnZXMgPSBhcnJheV9yZXNoYXBlKG1uaXN0JHRyYWluJHgsIGMoNjAwMDAsIDI4ICogMjgpKQp0cmFpbl9pbWFnZXMgPSB0cmFpbl9pbWFnZXMgLyAyNTUgICAgICAgICAgICAgICAgICAgICMgbm9ybWFsaXphdGlvbgp0ZXN0X2ltYWdlcyA9IGFycmF5X3Jlc2hhcGUobW5pc3QkdGVzdCR4LCBjKDEwMDAwLCAyOCAqIDI4KSkKdGVzdF9pbWFnZXMgPSBub3JtYWxpemF0aW9uPSB0ZXN0X2ltYWdlcyAvIDI1NSAgICAgICAjIG5vcm1hbGl6YXRpb24KdHJhaW5fbGFiZWxzID0gdG9fY2F0ZWdvcmljYWwobW5pc3QkdHJhaW4keSkKdGVzdF9sYWJlbHMgPSB0b19jYXRlZ29yaWNhbChtbmlzdCR0ZXN0JHkpCmBgYAo8YnI+CgotIC0gLQoKIyMjIDIuIFRyYWRpdGlvbmFsIE5ldXJhbCBOZXR3b3JrIChNTFApCgojIyMjIDIuMSBOZXR3cm9rIFBhcmFtZXRlcnMKYGBge3J9Cm1scCA9IGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA1MTIsICAgICAgICAgICAgICAgIyBudW1iZXIgb2YgcGVyY2VwdHJvbgogICAgICAgICAgICAgIGFjdGl2YXRpb24gPSAicmVsdSIsICAgICAgICMgYWN0aXZhdGlvbiBmdW5jdGlvbiAKICAgICAgICAgICAgICBpbnB1dF9zaGFwZSA9IGMoNzg0KSAgICAgICAjIGRpbWVuc2lvbnMgb2YgaW5wdXQgdGVuc29yCiAgICAgICAgICAgICAgKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMCwgICAgICAgICAgICAgICAgIyBvbmUgb3V0cHV0IG5ldXJvbiBwZXIgY2xhc3MKICAgICAgICAgICAgICBhY3RpdmF0aW9uID0gInNvZnRtYXgiICAgICAjIGFjdGl2YXRlIHRoZSBsYXJnZXN0IG9uZQogICAgICAgICAgICAgICkKCnN1bW1hcnkobWxwKSAgIyBzdW1tYXJ5IG9mIHRoZSBuZXR3b3JrIHNwZWMKYGBgCgpOby4gQ29lZmZpY2llbnRzIChgUGFyYW0gI2ApIGluIEVhY2ggTGF5ZXIKCisgYGRlbnNlXzE6ICgyOCAqIDI4ICsgMSkgKiA1MTIgPSA0MDEsOTIwYCAKKyBgZGVuc2VfMjogKDUxMiArIDEpICogMTAgPSA1LDEzMGAgCgo8YnI+CgojIyMjIDIuMiBUcmFpbmluZyBQYXJhbWV0ZXJzCmBgYHtyfQptbHAgJT4lIGNvbXBpbGUoICAgICAgICAgICAgICAgICAgICAgIyBzcGVjaWZ5CiAgb3B0aW1pemVyID0gInJtc3Byb3AiLCAgICAgICAgICAgICAgICMgb3B0aW1pemVyCiAgbG9zcyA9ICJjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHkiLCAgICMgbG9zcyBmdW5jdGlvbgogIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpICAgICAgICAgICAgICAjIGFjY3VyYWN5IG1ldHJpY2UgIAogICkKYGBgCgojIyMjIDIuMyBGaXR0aW5nIE1vZGVsCmBgYHtyfQpmaXQxID0gbWxwICU+JSBmaXQoCiAgdHJhaW5faW1hZ2VzLCAgICAgICAgICMgdHJhaW4gZGF0YQogIHRyYWluX2xhYmVscywgICAgICAgICAjIGxhYmVsIG9mIHRyYWluIGRhdGEKICBlcG9jaHM9MTAsICAgICAgICAgICAgIyBuby4gZXBvY2hzIOWFreiQrOWAi+Wtl+eci+WNgeasoSBlcG9jaHM9MTAKICBiYXRjaF9zaXplPTEyOCwgICAgICAgIyBuby4gaW5wdXQgcGVyIG1pbmktYmF0Y2ggYmF0Y2hfc2l6ZT3msbrlrprkuIDmrKHkuJ/lpJrlsJHos4fmlpkg6LaK5aSn6LeR6LaK5b+rCiAgdmVyYm9zZT0yCiAgKQpgYGAKCmBgYHtyfQpwbG90KGZpdDEpCmBgYAoKIyMjIyAyLjQgVmFsaWRhdGlvbiAmIFByZWRpY3Rpb25lCmBgYHtyfQojIEV2YWx1YXRpb24KbWxwICU+JSBldmFsdWF0ZSh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMsIHZlcmJvc2U9MikKYGBgCgpgYGB7cn0KIyBQcmVkaWN0aW9uIC0gQ2xhc3NlcwptbHAgJT4lIHByZWRpY3RfY2xhc3Nlcyh0ZXN0X2ltYWdlc1sxOjEwLF0pCmBgYAoKYGBge3J9CiMgUHJlZGljdGlvbiBQcm9iYWJpbGl0eQptbHAgJT4lIHByZWRpY3RfcHJvYmEodGVzdF9pbWFnZXNbMToxMCxdKSAlPiUgcm91bmQoNCkKYGBgCgo8YnI+CgotIC0gLQoKIyMjIDMuIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmsgKENOTikKCiMjIyMgMy4xIExvYWQgbmFkIFJlc2hhcGUKYGBge3J9CiMgbW5pc3QgPC0gZGF0YXNldF9tbmlzdCgpCiMgYyhjKHRyYWluX2ltYWdlcywgdHJhaW5fbGFiZWxzKSwgYyh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMpKSAlPC0lIG1uaXN0CiMgdHJhaW5faW1hZ2VzIDwtIGFycmF5X3Jlc2hhcGUodHJhaW5faW1hZ2VzLCBjKDYwMDAwLCAyOCwgMjgsIDEpKQojIHRyYWluX2ltYWdlcyA8LSB0cmFpbl9pbWFnZXMgLyAyNTUKIyB0ZXN0X2ltYWdlcyA8LSBhcnJheV9yZXNoYXBlKHRlc3RfaW1hZ2VzLCBjKDEwMDAwLCAyOCwgMjgsIDEpKQojIHRlc3RfaW1hZ2VzIDwtIHRlc3RfaW1hZ2VzIC8gMjU1CiMgdHJhaW5fbGFiZWxzIDwtIHRvX2NhdGVnb3JpY2FsKHRyYWluX2xhYmVscykKIyB0ZXN0X2xhYmVscyA8LSB0b19jYXRlZ29yaWNhbCh0ZXN0X2xhYmVscykKCnRyYWluX2ltYWdlcyA9IGFycmF5X3Jlc2hhcGUobW5pc3QkdHJhaW4keCwgYyg2MDAwMCwgMjgsIDI4LCAxKSkKdHJhaW5faW1hZ2VzID0gdHJhaW5faW1hZ2VzIC8gMjU1ICAgICAgICAgICAgICAgICAgICAjIG5vcm1hbGl6YXRpb24KdGVzdF9pbWFnZXMgPSBhcnJheV9yZXNoYXBlKG1uaXN0JHRlc3QkeCwgYygxMDAwMCwgMjgsIDI4LCAxKSkKdGVzdF9pbWFnZXMgPSBub3JtYWxpemF0aW9uPSB0ZXN0X2ltYWdlcyAvIDI1NSAgICAgICAjIG5vcm1hbGl6YXRpb24KdHJhaW5fbGFiZWxzID0gdG9fY2F0ZWdvcmljYWwobW5pc3QkdHJhaW4keSkKdGVzdF9sYWJlbHMgPSB0b19jYXRlZ29yaWNhbChtbmlzdCR0ZXN0JHkpCgpgYGAKCiMjIyMgMy4yIE1ldGhvZCBhbmQgVHJhaW5pbmcgUGFyYW1ldGVycwpgYGB7cn0KY25uIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gMzIsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICJyZWx1IiwKICAgICAgICAgICAgICAgIGlucHV0X3NoYXBlID0gYygyOCwgMjgsIDEpKSAlPiUgCiAgbGF5ZXJfbWF4X3Bvb2xpbmdfMmQocG9vbF9zaXplID0gYygyLCAyKSkgJT4lIAogIGxheWVyX2NvbnZfMmQoZmlsdGVycyA9IDY0LCBrZXJuZWxfc2l6ZSA9IGMoMywgMyksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9tYXhfcG9vbGluZ18yZChwb29sX3NpemUgPSBjKDIsIDIpKSAlPiUgCiAgbGF5ZXJfY29udl8yZChmaWx0ZXJzID0gNjQsIGtlcm5lbF9zaXplID0gYygzLCAzKSwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX2ZsYXR0ZW4oKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA2NCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAsIGFjdGl2YXRpb24gPSAic29mdG1heCIpCgpzdW1tYXJ5KGNubikKYGBgCgpOby4gQ29lZmZpY2llbnRzIChgUGFyYW0gI2ApIGluIEVhY2ggTGF5ZXIKCisgYGNvbnYyZF8xOiAoMyAqIDMgKiAgMSArIDEpICogMzIgPSAzMjBgIAorIGBjb252MmRfMjogKDMgKiAzICogMzIgKyAxKSAqIDY0ID0gMTgsNDk2YCAKKyBgY29udjJkXzM6ICgzICogMyAqIDY0ICsgMSkgKiA2NCA9IDM2LDkyOGAgCisgYGRlbnNlXzM6ICAoNTc2ICsgMSkgKiA2NCA9IDM2LDkyOGAgCisgYGRlbnNlXzQ6ICAoNjQgICsgMSkgKiAxMCA9IDY1MGAgCgoKYGBge3J9CmNubiAlPiUgY29tcGlsZSgKICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgbG9zcyA9ICJjYXRlZ29yaWNhbF9jcm9zc2VudHJvcHkiLAogIG1ldHJpY3MgPSBjKCJhY2N1cmFjeSIpKQpgYGAKCiMjIyMgMy4yIEZpdHRpbmcgTW9kZWwKYGBge3J9CmZpdDIgPSBjbm4gJT4lIGZpdCgKICB0cmFpbl9pbWFnZXMsIHRyYWluX2xhYmVscywgCiAgZXBvY2hzID0gNSwgICAgICAgICMgNSBlcG9jaHMKICBiYXRjaF9zaXplPTY0LCAgICAgIyA2NCBzYW1wbGVzIHBlciBtaW5pLWJhdGNoCiAgdmVyYm9zZSA9IDIKICApCmBgYAoKYGBge3J9CnBsb3QoZml0MikKYGBgCgoKIyMjIyAzLjMgRXZhbHVhdGlvbgpgYGB7cn0KY25uICU+JSBldmFsdWF0ZSh0ZXN0X2ltYWdlcywgdGVzdF9sYWJlbHMsIHZlcmJvc2U9MikKYGBgCgo8YnI+PGJyPjxicj48YnI+Cgo8c3R5bGU+Ci5jYXB0aW9uIHsKICBjb2xvcjogIzc3NzsKICBtYXJnaW4tdG9wOiAxMHB4Owp9CnAgY29kZSB7CiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7Cn0KcHJlIHsKICB3b3JkLWJyZWFrOiBub3JtYWw7CiAgd29yZC13cmFwOiBub3JtYWw7CiAgbGluZS1oZWlnaHQ6IDE7Cn0KcHJlIGNvZGUgewogIHdoaXRlLXNwYWNlOiBpbmhlcml0Owp9CnAsbGkgewogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoucnsKICBsaW5lLWhlaWdodDogMS4yOwp9CgoucWl6IHsKICBsaW5lLWhlaWdodDogMS43NTsKICBiYWNrZ3JvdW5kOiAjZjBmMGYwOwogIGJvcmRlci1sZWZ0OiAxMnB4IHNvbGlkICNjY2ZmY2M7CiAgcGFkZGluZzogNHB4OwogIHBhZGRpbmctbGVmdDogMTBweDsKICBjb2xvcjogIzAwOTkwMDsKfQoKdGl0bGV7CiAgY29sb3I6ICNjYzAwMDA7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmJvZHl7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmgxLGgyLGgzLGg0LGg1ewogIGNvbG9yOiAjMDA2NmZmOwogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoKaDN7CiAgY29sb3I6ICMwMDg4MDA7CiAgYmFja2dyb3VuZDogI2U2ZmZlNjsKICBsaW5lLWhlaWdodDogMjsKICBmb250LXdlaWdodDogYm9sZDsKfQoKaDV7CiAgY29sb3I6ICMwMDYwMDA7CiAgYmFja2dyb3VuZDogI2Y4ZjhmODsKICBsaW5lLWhlaWdodDogMS41OwogIGZvbnQtd2VpZ2h0OiBib2xkOwp9Cjwvc3R5bGU+CgoKCgo=