<jupyter_start><jupyter_text>Lab 04: ResNetTrong bài thực hành này:- Cài đặt, train ResNet34 với data MNISTReference:- <NAME>, <NAME>, <NAME>, and <NAME>, “Deep residual learning for image recognition,” in Proceedings of the IEEE conference on computer vision and pattern recognition, 2016, pp. 770–778. https://arxiv.org/abs/1512.03385  1. Xây dựng ResNet34 bằng tf.keras.layersTrong phần này chúng ta sẽ xây dựng và huấn luyện model ResNet-34 trên dataset MNIST (ảnh được resize)<jupyter_code>#import thư viện cần thiết
## thư viện machine learning và hỗ trợ
import tensorflow as tf
from tensorflow import keras
import numpy as np

## thư viện để vẽ đồ thị
import matplotlib.pyplot as plt<jupyter_output><empty_output><jupyter_text>1.1 Residual BlockĐầu tiên, chúng ta sẽ định nghĩa lớp ResidualBlock, lớp này sẽ xây dựng 1 khối residual. Hình trên vẽ một khối Residual cơ bản.Các chú ý thêm:1. Theo paper, down sampling sẽ được thực hiện ở convolutional layer đầu tiên bằng cách cho strides = [2,2]2. Khi kích thước của input bằng kích thước của output, shorcut sẽ chính là input3. Khi kích thước của input khác kích thước của output, shorcut sẽ là 1 lớp convolution và 1 lớp batch normalization, lớp convolution có kernel_size là [1,1];strides và số filters được thiết lập để có cùng kích thước với output.<jupyter_code>## Import các layer cần thiết
from tensorflow.keras.layers import Input, Dense, Convolution2D, MaxPool2D, BatchNormalization, ReLU, GlobalAveragePooling2D

## Định nghĩa 1 Residual Block
class ResidualBlock(keras.layers.Layer):

    def __init__(self, n_filters=64, kernel_regularizer=None, down_sampling=False):
        ## Gọi hàm khởi tạo của keras.layers.Layer và lưu lại các thông số
        super(ResidualBlock, self).__init__()
        self.n_filters = n_filters
        self.down_sampling = down_sampling
        self.kernel_regularizer = kernel_regularizer
    
    ## override hàm này để có thể lưu file
    def get_config(self):
        ## lấy config của lớp cha
        config = super(ResidualBlock, self).get_config()
        ## thêm config của lớp này
        config.update({
            'n_filters': self.n_filters,
            "down_sampling": self.down_sampling,
        })
        return config
    
    def build(self, input_shape):
        
        ## Xác định xem input_shape có bằng output_shape không
        self.projection_shortcut = (int(input_shape[-1]) != self.n_filters) or self.down_sampling
        
        ## Nếu cần down sampling thì convolutional layer đầu tiên dùng strides=[2,2]
        first_strides = [1,1]
        if self.down_sampling:
            first_strides = [2,2]
        
        ##Khai báo các layer nhánh chính
        self.main_conv1 = Convolution2D(filters=self.n_filters,
                                         kernel_size=[3,3],
                                         strides=first_strides,
                                         padding='same',
                                         kernel_regularizer=self.kernel_regularizer,
                                         activation=None)
        self.main_batch1 = BatchNormalization()
        self.main_relu1 = ReLU()

        self.main_conv2 = Convolution2D(filters=self.n_filters,
                                              kernel_size=[3,3],
                                              strides=[1,1],
                                              padding='same',
                                              kernel_regularizer=self.kernel_regularizer,
                                              activation=None)
        self.main_batch2 = BatchNormalization()
        
        ## Khai báo các layer nhánh shortcut
        if self.projection_shortcut:
            self.shortcut_conv = Convolution2D(filters=self.n_filters,              ### N_FILETERS bằng N_FILTERS của output
                                                        kernel_size=[1,1],          ### KERNEL_SIZE = [1,1]
                                                        strides=first_strides,      ### STRIDES: giống STRIDES của conv đầu
                                                        padding='same',
                                                        kernel_regularizer=self.kernel_regularizer,
                                                        activation=None)
            self.shortcut_batch = BatchNormalization()

        self.main_relu2 = ReLU()

    def call(self, inputs):
        ## Thiết lập các input cho các layer đã khai báo
        main_conv1 = self.main_conv1(inputs)
        main_batch1 = self.main_batch1(main_conv1)
        main_relu1 = self.main_relu1(main_batch1)
        
        main_conv2 = self.main_conv2(main_relu1)
        main_batch2 = self.main_batch2(main_conv2)

        if self.projection_shortcut:
            shortcut_conv = self.shortcut_conv(inputs)
            shortcut_batch = self.shortcut_batch(shortcut_conv)
            
            ## Nếu input_shape != output_shape thì shortcut là convolutional layer
            shortcut = shortcut_batch
        else:
            ## Nếu input_shape == output_shape thì shortcut là inputs
            shortcut = inputs

        main_add = main_batch2 + shortcut

        main_relu2 = self.main_relu2(main_add)
        return main_relu2<jupyter_output><empty_output><jupyter_text>1.2 Resnet-34Hình trên vẽ các cấu trúc mạng ResNet có trong paper, ResNet-34 chính là cấu trúc có 34 layer.- Down sampling được thực hiện ở các lớp conv3_1, conv4_1, conv5_1<jupyter_code>from keras.regularizers import l2
l2_regularizer_rate = 0.0001

## Tạo lớp input kích thước (None, 32, 32, 1)
inputs = keras.layers.Input(shape=(32,32,1))

### Block 1
conv1 = Convolution2D(filters=64,
                      kernel_size=[7,7],
                      strides=[2,2],
                      padding='same',
                      kernel_regularizer=l2(l2_regularizer_rate),
                      activation=None)(inputs)

batch1 = BatchNormalization()(conv1)        ## lớp tf.keras.layers.BatchNormalization()

relu1 = ReLU()(batch1)

maxpool1 = MaxPool2D(pool_size=[3,3],
                     strides=[2,2])(relu1)

### Block 2
res2_1 = ResidualBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(maxpool1)
res2_2 = ResidualBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_1)
res2_3 = ResidualBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_2)

### Block 3
res3_1 = ResidualBlock(n_filters=128,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_3)
res3_2 = ResidualBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_1)
res3_3 = ResidualBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_2)
res3_4 = ResidualBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_3)

### Block 4
res4_1 = ResidualBlock(n_filters=256,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_4)
res4_2 = ResidualBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_1)
res4_3 = ResidualBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_2)
res4_4 = ResidualBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_3)
res4_5 = ResidualBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_4)
res4_6 = ResidualBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_5)

### Block 5
res5_1 = ResidualBlock(n_filters=512,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_6)
res5_2 = ResidualBlock(n_filters=512,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res5_1)
res5_3 = ResidualBlock(n_filters=512,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res5_2)


### Block Output
avage_pool = GlobalAveragePooling2D()(res5_3)

softmax = Dense(units=10, activation='softmax')(avage_pool)

## Compile model
model = keras.models.Model(inputs=inputs, outputs=softmax)
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.0001),    ##tự khai báo Optimizer với learning rate 10^-4
             loss=tf.keras.losses.sparse_categorical_crossentropy,
             metrics=["accuracy"])
    

## In toàn bộ cấu trúc của model
print("Cấu trúc của model: ")
model.summary()<jupyter_output>Cấu trúc của model: 
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 32, 32, 1)]       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 16, 16, 64)        3200      
_________________________________________________________________
batch_normalization (BatchNo (None, 16, 16, 64)        256       
_________________________________________________________________
re_lu (ReLU)                 (None, 16, 16, 64)        0         
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 7, 7, 64)          0         
_________________________________________________________________
residual_block (ResidualBloc (None, 7, 7, 64)          74368     
________________________________________[...]<jupyter_text>1.3 Resize MNIST<jupyter_code># Tải dataset MNIST từ tensorflow
## MNIST là bài toán dự đoán một ảnh thể hiện ký tự số nào

## tải MNIST dataset từ keras
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
##resacle ảnh thành ảnh thực trong đoạn [0,1]
X_train, X_test = X_train/255.0, X_test/255.0

##in dataset
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
## import thư viện OpenCV trên python
#!pip3 install opencv-python

### Thử resize một ảnh
import cv2
resized_img = cv2.resize(X_train[0], dsize=(32,32))
print("Kích thước ảnh sau resize: ", resized_img.shape)
## Resize toàn bộ ảnh train tập train và test
X_train = np.array([cv2.resize(img, dsize=(32,32)) for img in X_train])
X_test = np.array([cv2.resize(img, dsize=(32,32)) for img in X_test])
print("Kích thước tập sau khi resize: ", X_train.shape, X_test.shape)

## In xem ảnh còn ổn không sau khi resize
plt.imshow(X_train[0])
plt.show()

## Reshape ảnh để phù hợp với input của model (thêm một trục)
X_train = np.expand_dims(X_train, axis=-1)
X_test = np.expand_dims(X_test, axis=-1)
print("Kích thước tập sau khi reshape: ", X_train.shape, X_test.shape)

plt.imshow(X_train[0,:,:,0])
plt.show()

#Tách một phần tập train thành tập valid
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.1)

## Reshape ảnh để phù hợp với input của model (thêm một trục)<jupyter_output>Kích thước tập sau khi resize:  (60000, 32, 32) (10000, 32, 32)<jupyter_text>1.4 Train<jupyter_code># Checkpoint Callback
mc = keras.callbacks.ModelCheckpoint(filepath="resnet34_mnist.h5", 
                                     monitor='val_loss',
                                     mode='min', 
                                     verbose=0, 
                                     save_best_only=True)

## Train  ## Khuyến cáo chạy COLAB (hoặc tương tự)
history = model.fit(X_train, y_train,
                    batch_size=100,
                    epochs=10,
                    validation_data=(X_valid, y_valid),
                    callbacks=[mc])                    


## Đánh giá model trên tập test
valid_loss, valid_acc = model.evaluate(X_valid, y_valid)
test_loss, test_acc = model.evaluate(X_test, y_test)
print("Valid: loss {} acc {} -- Test: loss {} valid {}".format(valid_loss, valid_acc, test_loss, test_acc))

## Load lại model tốt nhất đã lưu
print("best model: ")
model.load_weights("resnet34_mnist.h5")
valid_loss, valid_acc = model.evaluate(X_valid, y_valid)
test_loss, test_acc = model.evaluate(X_test, y_test)
print("Valid: loss {} acc {} -- Test: loss {} valid {}".format(valid_loss, valid_acc, test_loss, test_acc))<jupyter_output>Epoch 1/10
540/540 [==============================] - 106s 134ms/step - loss: 0.8311 - accuracy: 0.9246 - val_loss: 1.6694 - val_accuracy: 0.6605
Epoch 2/10
540/540 [==============================] - 72s 133ms/step - loss: 0.6078 - accuracy: 0.9777 - val_loss: 0.6189 - val_accuracy: 0.9732
Epoch 3/10
540/540 [==============================] - 72s 132ms/step - loss: 0.5740 - accuracy: 0.9862 - val_loss: 0.6171 - val_accuracy: 0.9727
Epoch 4/10
540/540 [==============================] - 71s 131ms/step - loss: 0.5596 - accuracy: 0.9883 - val_loss: 0.5822 - val_accuracy: 0.9838
Epoch 5/10
540/540 [==============================] - 72s 133ms/step - loss: 0.5416 - accuracy: 0.9907 - val_loss: 0.6430 - val_accuracy: 0.9640
Epoch 6/10
540/540 [==============================] - 72s 133ms/step - loss: 0.5227 - accuracy: 0.9930 - val_loss: 0.5804 - val_accuracy: 0.9777
Epoch 7/10
540/540 [==============================] - 71s 131ms/step - loss: 0.5124 - accuracy: 0.9920 - val_loss: 0.5794 - val_a[...]<jupyter_text>Bài tập- Xây dựng và huấn luyện ResNet50 (được mô tả cùng ResNet30 trong phần ResNet30) như trong paper với MNIST bằng thư viện keras.layers.Gợi ý:- tf.keras có cài sẵn cấu trúc ResNet50, có thể dùng để tham khảo.- ResNet50 sử dụng một residual block khác gọi là Bottleneck Residual Block được định nghĩa như hình dưới- Down sampling sẽ được thực hiện ở conv3_1, conv4_1, conv5_1- Khi kích thước của input bằng kích thước của output, shorcut sẽ chính là input- Khi kích thước của input khác kích thước của output, shorcut sẽ là 1 lớp convolution và 1 lớp batch normalization, lớp convolution có kernel_size là [1,1];strides và số filters được thiết lập để có cùng kích thước với output.<jupyter_code>#import thư viện cần thiết
## thư viện machine learning và hỗ trợ
import tensorflow as tf
from tensorflow import keras
import numpy as np

## thư viện để vẽ đồ thị
import matplotlib.pyplot as plt


## Import các layer cần thiết
from tensorflow.keras.layers import Input, Dense, Convolution2D, MaxPool2D, BatchNormalization, ReLU, GlobalAveragePooling2D

from keras.regularizers import l2
l2_regularizer_rate = 0.0001
## Định nghĩa 1 Residual Block cơ bản
class BasicBlock(keras.layers.Layer):
    def __init__(self, n_filters=64, kernel_regularizer=None, down_sampling=False):
        ## Gọi hàm khởi tạo của keras.layers.Layer và lưu lại các thông số
        super(BasicBlock, self).__init__()

        ## Số lượng filter
        self.n_filters = n_filters

        ## Down sampling
        self.down_sampling = down_sampling

        ## Kernel regularizer
        self.kernel_regularizer = kernel_regularizer

    ## override hàm này để có thể lưu file
    def get_config(self):
        ## lấy config của lớp cha
        config = super(ResidualBlock, self).get_config()


        ## thêm config của lớp này
        config.update({
            'n_filters': self.n_filters,
            "down_sampling": self.down_sampling,
        })
        return config
    
    def build(self, input_shape):
        ## Xác định xem input_shape có bằng output_shape không
        self.projection_shortcut = (int(input_shape[-1]) != self.n_filters) or self.down_sampling
        
        ## Nếu cần down sampling thì convolutional layer đầu tiên dùng strides=[2,2]
        first_strides = [1,1]
        if self.down_sampling:
            first_strides = [2,2]
        
        ##Khai báo các layer nhánh chính
        self.main_conv1 = Convolution2D(filters=self.n_filters,
                                         kernel_size=[3,3],
                                         strides=first_strides,
                                         padding='same',
                                         kernel_regularizer=self.kernel_regularizer,
                                         activation=None)
        self.main_batch1 = BatchNormalization()
        self.main_relu1 = ReLU()

        self.main_conv2 = Convolution2D(filters=self.n_filters,
                                              kernel_size=[3,3],
                                              strides=[1,1],
                                              padding='same',
                                              kernel_regularizer=self.kernel_regularizer,
                                              activation=None)
        self.main_batch2 = BatchNormalization()
        
        ## Khai báo các layer nhánh shortcut
        if self.projection_shortcut:
            self.shortcut_conv = Convolution2D(filters=self.n_filters,              ### N_FILETERS bằng N_FILTERS của output
                                                        kernel_size=[1,1],          ### KERNEL_SIZE = [1,1]
                                                        strides=first_strides,      ### STRIDES: giống STRIDES của conv đầu
                                                        padding='same',
                                                        kernel_regularizer=self.kernel_regularizer,
                                                        activation=None)
            self.shortcut_batch = BatchNormalization()

        self.main_relu2 = ReLU()

    def call(self, inputs):
        ## Thiết lập các input cho các layer đã khai báo
        ## Convolutional layer: 3x3, n_filters
        main_conv1 = self.main_conv1(inputs)
        ## Batch Normalization
        main_batch1 = self.main_batch1(main_conv1)
        ## ReLU
        main_relu1 = self.main_relu1(main_batch1)
        
        ## Convolutional layer: 3x3, n_filters
        main_conv2 = self.main_conv2(main_relu1)
        ## Batch Normalization
        main_batch2 = self.main_batch2(main_conv2)

        # Add shortcut
        if self.projection_shortcut:
            shortcut_conv = self.shortcut_conv(inputs)
            shortcut_batch = self.shortcut_batch(shortcut_conv)
            
            ## Nếu input_shape != output_shape thì shortcut là convolutional layer
            shortcut = shortcut_batch
        else:
            ## Nếu input_shape == output_shape thì shortcut là inputs
            shortcut = inputs

        main_add = main_batch2 + shortcut

        ## ReLU
        main_relu2 = self.main_relu2(main_add)
        return main_relu2
## Định nghĩa một Bottleneck Residual Block
class BottleneckBlock(keras.layers.Layer):
    def __init__(self, n_filters=64, kernel_regularizer=None, down_sampling=False):
        ## Gọi hàm khởi tạo của keras.layers.Layer và lưu lại các thông số
        super(BottleneckBlock, self).__init__()

        ## Số lượng filters
        self.n_filters = n_filters

        ## Số lượng bottleneck filters
        self.expansion = 4

        ## Down sampling
        self.down_sampling = down_sampling

        ## Kernel regularizer
        self.kernel_regularizer = kernel_regularizer
    
    ## Override hàm này để có thể lưu file
    def get_config(self):
        ## lấy config của lớp cha
        config = super(BottleneckBlock, self).get_config()


        ## thêm config của lớp này
        config.update({
            'n_filters': self.n_filters,
            "down_sampling": self.down_sampling,
        })
        return config

    def build(self, input_shape):
        ## Xác định xem input_shape có bằng output_shape không
        self.projection_shortcut = (int(input_shape[-1]) != self.n_filters) or self.down_sampling

        ## Nếu cần down sampling thì convolutional layer đầu tiên dùng strides=[2,2]
        first_strides = [1,1]
        if self.down_sampling:
            first_strides = [2,2]

        ## Khai báo các layer nhánh chính
        self.main_conv1 = Convolution2D(filters=self.n_filters,
                                         kernel_size=[1,1],
                                         strides=first_strides,
                                         padding='same',
                                         kernel_regularizer=self.kernel_regularizer,
                                         activation=None)
        self.main_batch1 = BatchNormalization()
        self.main_relu1 = ReLU()

        self.main_conv2 = Convolution2D(filters=self.n_filters,
                                              kernel_size=[3,3],
                                              strides=[1,1],
                                              padding='same',
                                              kernel_regularizer=self.kernel_regularizer,
                                              activation=None)
        self.main_batch2 = BatchNormalization()
        self.main_relu2 = ReLU()

        self.main_conv3 = Convolution2D(filters=self.n_filters * self.expansion,
                                         kernel_size=[1,1],
                                         strides=[1,1],
                                         padding='same',
                                         kernel_regularizer=self.kernel_regularizer,
                                         activation=None)
        self.main_batch3 = BatchNormalization()

        
        ## Khai báo các layer nhánh shortcut
        if self.projection_shortcut:
            self.shortcut_conv = Convolution2D(filters=self.n_filters,              ### N_FILETERS bằng N_FILTERS của output
                                                        kernel_size=[1,1],          ### KERNEL_SIZE = [1,1]
                                                        strides=first_strides,      ### STRIDES: giống STRIDES của conv đầu
                                                        padding='same',
                                                        kernel_regularizer=self.kernel_regularizer,
                                                        activation=None)
            self.shortcut_batch = BatchNormalization()

        self.main_relu3 = ReLU()

    def call(self, inputs):
        ## Thiết lập các input cho các layer đã khai báo
        ## Convolutional layer: 1x1, bottleneck_filters
        x = self.main_conv1(inputs)
        ## Batch Normalization
        x = self.main_batch1(x)
        ## ReLU
        x = self.main_relu1(x)
        
        ## Convolutional layer: 3x3, n_filters
        x = self.main_conv2(x)
        ## Batch Normalization
        x = self.main_batch2(x)
        ## ReLU
        x = self.main_relu2(x)

        ## Convolutional layer: 1x1, bottleneck_filters
        x = self.main_conv3(x)
        ## Batch Normalization
        x = self.main_batch3(x)

        # Add shortcut
        if self.projection_shortcut and self.down_sampling == [1, 1]:
            in_dim=int(inputs.get_shape().as_list()[-1])
            if (in_dim != self.n_filters):
                shortcut_conv = self.shortcut_conv(inputs)
                shortcut_batch = self.shortcut_batch(shortcut_conv)
                shortcut = shortcut_batch
                x = inputs + shortcut
            else:
                x = inputs + x

        ## ReLU
        x = self.main_relu3(x)
        return x
from keras.regularizers import l2
l2_regularizer_rate = 0.0001

## Tạo lớp input kích thước (None, 32, 32, 1)
inputs = keras.layers.Input(shape=(32,32,1))

### Block 1: 7x7, 64 filters, stride 2, 3x3 max pool, stride 2
conv1 = Convolution2D(filters=64,
                      kernel_size=[7,7],
                      strides=[2,2],
                      padding='same',
                      kernel_regularizer=l2(l2_regularizer_rate),
                      activation=None)(inputs)

batch1 = BatchNormalization()(conv1)        ## lớp tf.keras.layers.BatchNormalization()

relu1 = ReLU()(batch1)

maxpool1 = MaxPool2D(pool_size=[3,3],
                     strides=[2,2])(relu1)


### Block 2 x 3
res2_1 = BottleneckBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(maxpool1)
res2_2 = BottleneckBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_1)
res2_3 = BottleneckBlock(n_filters=64,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_2)

### Block 3 x 4
res3_1 = BottleneckBlock(n_filters=128,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res2_3)
res3_2 = BottleneckBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_1)
res3_3 = BottleneckBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_2)
res3_4 = BottleneckBlock(n_filters=128,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_3)

### Block 4 x 6
res4_1 = BottleneckBlock(n_filters=256,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res3_4)
res4_2 = BottleneckBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_1)
res4_3 = BottleneckBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_2)
res4_4 = BottleneckBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_3)
res4_5 = BottleneckBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_4)
res4_6 = BottleneckBlock(n_filters=256,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_5)


### Block 5 x 3
res5_1 = BottleneckBlock(n_filters=512,
                       down_sampling=True,
                       kernel_regularizer=l2(l2_regularizer_rate))(res4_6)
res5_2 = BottleneckBlock(n_filters=512,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res5_1)
res5_3 = BottleneckBlock(n_filters=512,
                       down_sampling=False,
                       kernel_regularizer=l2(l2_regularizer_rate))(res5_2)

### Block Output
avage_pool = GlobalAveragePooling2D()(res5_3)

softmax = Dense(units=10, activation='softmax')(avage_pool)

## Compile model
model = keras.models.Model(inputs=inputs, outputs=softmax)
## Tự khai báo Optimizer với learning rate 10^-4
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.0001),    
             loss=tf.keras.losses.sparse_categorical_crossentropy,
             metrics=["accuracy"])

## In toàn bộ cấu trúc của model
print("Cấu trúc của model: ")
model.summary()
# Tải dataset MNIST từ tensorflow
## MNIST là bài toán dự đoán một ảnh thể hiện ký tự số nào

## tải MNIST dataset từ keras
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
##resacle ảnh thành ảnh thực trong đoạn [0,1]
X_train, X_test = X_train/255.0, X_test/255.0

##in dataset
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
## import thư viện OpenCV trên python
#!pip3 install opencv-python

### Thử resize một ảnh
import cv2
resized_img = cv2.resize(X_train[0], dsize=(32,32))
print("Kích thước ảnh sau resize: ", resized_img.shape)
## Resize toàn bộ ảnh train tập train và test
X_train = np.array([cv2.resize(img, dsize=(32,32)) for img in X_train])
X_test = np.array([cv2.resize(img, dsize=(32,32)) for img in X_test])
print("Kích thước tập sau khi resize: ", X_train.shape, X_test.shape)

## In xem ảnh còn ổn không sau khi resize
plt.imshow(X_train[0])
plt.show()

## Reshape ảnh để phù hợp với input của model (thêm một trục)
X_train = np.expand_dims(X_train, axis=-1)
X_test = np.expand_dims(X_test, axis=-1)
print("Kích thước tập sau khi reshape: ", X_train.shape, X_test.shape)

plt.imshow(X_train[0,:,:,0])
plt.show()

#Tách một phần tập train thành tập valid
from sklearn.model_selection import train_test_split
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.1)

## Reshape ảnh để phù hợp với input của model (thêm một trục)
# Checkpoint Callback
mc = keras.callbacks.ModelCheckpoint(filepath="resnet50_mnist.h5", 
                                     monitor='val_loss',
                                     mode='min', 
                                     verbose=0, 
                                     save_best_only=True)

## Train  ## Khuyến cáo chạy COLAB (hoặc tương tự)
history = model.fit(X_train, y_train,
                    batch_size=100,
                    epochs=20,
                    validation_data=(X_valid, y_valid),
                    callbacks=[mc])                    


## Đánh giá model trên tập test
valid_loss, valid_acc = model.evaluate(X_valid, y_valid)
test_loss, test_acc = model.evaluate(X_test, y_test)
print("Valid: loss {} acc {} -- Test: loss {} valid {}".format(valid_loss, valid_acc, test_loss, test_acc))

## Load lại model tốt nhất đã lưu
print("best model: ")
model.load_weights("resnet50_mnist.h5")
valid_loss, valid_acc = model.evaluate(X_valid, y_valid)
test_loss, test_acc = model.evaluate(X_test, y_test)
print("Valid: loss {} acc {} -- Test: loss {} valid {}".format(valid_loss, valid_acc, test_loss, test_acc))<jupyter_output>Epoch 1/20
540/540 [==============================] - 82s 137ms/step - loss: 3.7836 - accuracy: 0.1371 - val_loss: 3.8495 - val_accuracy: 0.1115
Epoch 2/20
540/540 [==============================] - 73s 135ms/step - loss: 3.6447 - accuracy: 0.1750 - val_loss: 3.6170 - val_accuracy: 0.1953
Epoch 3/20
540/540 [==============================] - 73s 135ms/step - loss: 3.5143 - accuracy: 0.2203 - val_loss: 3.5591 - val_accuracy: 0.2363
Epoch 4/20
540/540 [==============================] - 73s 135ms/step - loss: 3.3073 - accuracy: 0.2917 - val_loss: 3.1556 - val_accuracy: 0.3520
Epoch 5/20
540/540 [==============================] - 72s 134ms/step - loss: 2.9641 - accuracy: 0.4164 - val_loss: 2.7730 - val_accuracy: 0.4800
Epoch 6/20
540/540 [==============================] - 73s 135ms/step - loss: 2.5886 - accuracy: 0.5613 - val_loss: 2.3932 - val_accuracy: 0.6363
Epoch 7/20
540/540 [==============================] - 73s 136ms/step - loss: 2.2585 - accuracy: 0.6879 - val_loss: 2.0952 - val_ac[...]
{'fraction_non_alphanumeric': 0.10204343502389888, 'fraction_numerical': 0.040997016649023194, 'mean_word_length': 2.174541751527495, 'pattern_counts': {'":': 3, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 108, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '255978', 'license': 'MIT', 'n_tokens_mistral': 11080, 'n_tokens_neox': 10195, 'n_words': 2163, 'path': 'Lab04/Lab04.ipynb', 'repo_name': 'nhutnamhcmus/lab-deep-learning-solution', 'size': '31174'}