<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 1. Giới thiệu
Trong notebook này, mình sẽ trình bày cách giải quyết đề tài tuyển dụng của VinID. Mô hình CNN được sử dụng để phân loại 10 số viết tay trong bộ MNIST. Trong notebook này,bao gồm các phần sau
Step1:  2. Tiền xử lý
Đọc bộ dữ liệu MNIST, chúng ta chia bộ dữ liệu thành tập train/valid/test. Đồng thời chuẩn hóa dữ liệu về khoảng [0-1] để giúp tăng tốc quá trình hội tụ. Tập valid của chúng ta sẽ gồm 20% tập train.
Step2:  Kiểm tra phân bố của nhãn
Chúng ta thấy rằng số lượng mẫu dữ liệu cho mỗi nhãn tương đương nhau.
Step3:  Thử nhìn qua một số mẫu trong tập huấn luyện. Chúng ta thấy rằng hầu hết các ảnh đều rõ nét và tương đối dễ dàng để nhận dạng.
Step4:  Định nghĩa số epochs cần huấn luyện và bachsize
Step5:  3. Data Augmentation
Kĩ thuật data augmentation được sử dụng để phát sinh thêm những mẫu dữ liệu mới bằng cách áp dụng các kĩ thuật xử lý ảnh trên bức ảnh. Các phép biến đổi nhỏ này phải đảm bảo không làm thay đổi nhãn của bức ảnh. 
Một số kĩ thuật phổ biến của data augmentation như là
Step6:  4. Xây dưng mô hình CNN
CNN bao gồm tập hợp các lớp cơ bản bao gồm
Step7:  5. Hyper-params tunning
Chúng ta sử dụng Hyperas để tunning các tham số. Hyperas sẽ phát sinh bộ tham số dựa trên khai báo ở trên. Sau đó huấn luyện mô hình và đánh giá trên tập validation. Bộ tham số có độ chính xác cao nhất trên tập validation sẽ được ghi nhận lại. 
5.1 Khai báo không gian tìm kiếm siêu tham số
Có rất nhiều siêu tham số cần được tunning như
Step8:  5.2 Optimze để tìm bộ tham số tốt nhất
Hyperas sẽ phát sinh các bộ tham số giữ trên không gian tìm kiếm định nghĩa trước của chúng ta. Sau đó thư viện sẽ hỗ trợ quá trình tìm kiếm các tham số này đơn giản bằng một số API có sẵn.
Step9:  Chạy quá trình search tham số. Bộ siêu tham số tốt nhất sẽ được ghi nhận lại để chúng ta sử dụng trong mô hình cuối cùng.
Step10:  Huấn luyện lại mô hình với bộ tham số tốt nhất ở trên.
Step11:  Kết quả trên tập validation khá cao với acc > 99%
6. So sánh optimizers và loss
6.1 So sánh các optimzers
Mục tiêu của quá trình huấn luyện mô hình ML là giảm độ lỗi của hàm loss function được tính bằng sự khác biệt của giá trị mô hình dự đoán và giá trị thực tế. Để đạt được mục đích này chúng ta thường sử dụng gradient descent. Gradient descent sẽ cập nhật trọng số của mô hình ngược với chiều gradient để giảm độ lỗi của loss function. 
Chúng ta sử thường sử dụng 3 optimzer phổ biến sau là adam, sgd, rmsprop để cập nhật trọng số của mô hình. 
Stochastic Gradient Descent là một biến thể của Gradient Descent, yêu cầu chúng ta phải shuffle dự liệu trước khi huấn luyện. Trong khi đó RMSProp và Adam là 2 optimizer hướng đến việc điều chỉnh learning rate tự động theo quá trình học.
RMSprop (Root mean square propagation) được giới thiệu bởi Geoffrey Hinton. RMSProp giải quyết vấn đề giảm dần learning rate của Adagrad bằng cách chuẩn hóa learning với gradient gần với thời điểm cập nhật mà thôi. Để làm được điều này tác giả chia learning rate cho tổng bình phương gradient giảm dần. 
Adam là optimizer phổ biến nhất tại thời điểm hiện tại. Adam cũng tính learning riêng biệt cho từng tham số, tương tự như RMSProp và Adagrad. Adam chuẩn hóa learning của mỗi tham số bằng first và second order moment của gradient.
Step12:  Plot quá trình huấn luyện mô hình với 3 lọai optimizers khác nhau.
Step13:  6.2 So sánh các loss function
Trong bài toán phân loại nhiều lớp. Chúng ta thường sử dụng 2 loại loss function sau
Step14:  Plot quá trình huấn luyện mô hình với 2 loại loss function khác nhau.
Step16:  Chúng ta thấy rằng không có sự khác biệt rõ rằng về tốc độ hội tụ giữ 2 hàm loss function là cross-entropy và KL loss trong bài toán của chúng ta.
7. Đánh giá mô hình.
Chúng ta sẽ xem xét một số lỗi của mô hình dự huấn luyện được. Một số lỗi dễ dàng được phát hiện bằng confusion matrix thể hiện xác xuất/số ảnh bị phân loại nhầm thành lớp khác.
Step18:  Các giá trị trên đường chéo rất cao, chúng ta mô hình chúng ta có độ chính xác rất tốt.
Nhìn vào confusion matrix ở trên, chúng ta có một số nhận xét như sau
Step19:  Với các mẫu ảnh sai, chúng ta có thể thấy rằng những mẫu này rất khó nhận dạng nhầm lẫn sáng các lớp khác. ví dụ số 9 và 4 hay là 3 và 8
8. Kfold, Predict và submit kết quả
Chúng ta huấn luyện lại mô hình sử dụng kfold, kết quả cuối dự đoán cuối cùng là trung bình cộng sự đoán của các mô hình huấn luyện trên mỗi fold.
Chúng ta chọn nhãn là lớp được dự đoán có xác suất cao nhất mà mô hình nhận dạng được
<ASSISTANT_TASK:>
Python Code:
!pip install hyperas
# Basic compuational libaries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import seaborn as sns
%matplotlib inline
np.random.seed(2)
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import itertools
from sklearn.model_selection import KFold
from keras.utils.np_utils import to_categorical # convert to one-hot-encoding
from keras.layers import Dense, Dropout, Conv2D, GlobalAveragePooling2D, Flatten, GlobalMaxPooling2D
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D, BatchNormalization
from keras.models import Sequential
from keras.optimizers import RMSprop, Adam, SGD, Nadam
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import ReduceLROnPlateau, ModelCheckpoint, EarlyStopping
from keras import regularizers
# Import hyperopt for tunning hyper params
from hyperopt import hp, tpe, fmin
from hyperopt import space_eval
sns.set(style='white', context='notebook', palette='deep')
# Set the random seed
random_seed = 2
Explanation: 1. Giới thiệu
Trong notebook này, mình sẽ trình bày cách giải quyết đề tài tuyển dụng của VinID. Mô hình CNN được sử dụng để phân loại 10 số viết tay trong bộ MNIST. Trong notebook này,bao gồm các phần sau:
1. Giới thiệu
2. Tiền xử lý dữ liệu
2.1 Load dữ liệu
2.2 Kiểm tra missing value
2.3 Chuẩn hóa    
2.5 Label encoding
2.6 Xây dựng tập train/test
3. Data augmentation    
4. Xây dựng mô hình
4.1 Xây dựng mô hình
5. Tunning parameter    
5.1 Khai báo không gian tìm kiếm siêu tham số
5.2 Grid search
6. So sánh các optimizer và loss function    
6.1 So sánh optimizer
6.2 So sánh loss function
7. Đánh giá mô hình
7.1 Confusion matrix
8. Dự đoán
8.1 Predict and Submit results
Cài đặt thư viện hyperas để hỗ trỡ quá trình tunning siêu tham số. Hyperas cung cấp các api rất tiện lợi cho quá trình theo huấn luyện và theo dõi độ chính xác của model tại mỗi bộ tham số.
End of explanation
def data():
    # Load the data
    train = pd.read_csv("../input/digit-recognizer/train.csv")
    test = pd.read_csv("../input/digit-recognizer/test.csv")
    Y_train = train["label"]
    
    # Drop 'label' column
    X_train = train.drop(labels = ["label"],axis = 1) 
    
    # Normalize the data
    X_train = X_train / 255.0
    test = test / 255.0
    
    # Reshape image in 3 dimensions (height = 28px, width = 28px , canal = 1)
    X_train = X_train.values.reshape(-1,28,28,1)
    test = test.values.reshape(-1,28,28,1)
    
    # Encode labels to one hot vectors (ex : 2 -> [0,0,1,0,0,0,0,0,0,0])
    Y_train = to_categorical(Y_train, num_classes = 10)
    
    return X_train, Y_train, test
X, Y, X_test = data()
Explanation: 2. Tiền xử lý
Đọc bộ dữ liệu MNIST, chúng ta chia bộ dữ liệu thành tập train/valid/test. Đồng thời chuẩn hóa dữ liệu về khoảng [0-1] để giúp tăng tốc quá trình hội tụ. Tập valid của chúng ta sẽ gồm 20% tập train.
End of explanation
g = sns.countplot(np.argmax(Y, axis=1))
Explanation: Kiểm tra phân bố của nhãn
Chúng ta thấy rằng số lượng mẫu dữ liệu cho mỗi nhãn tương đương nhau.
End of explanation
for i in range(0, 9):
    plt.subplot(330 + (i+1))
    plt.imshow(X[i][:,:,0], cmap=plt.get_cmap('gray'))
    plt.title(np.argmax(Y[i]));
    plt.axis('off')
plt.tight_layout()    
Explanation: Thử nhìn qua một số mẫu trong tập huấn luyện. Chúng ta thấy rằng hầu hết các ảnh đều rõ nét và tương đối dễ dàng để nhận dạng.
End of explanation
epochs = 30 # Turn epochs to 30 to get 0.9967 accuracy
batch_size = 64
Explanation: Định nghĩa số epochs cần huấn luyện và bachsize
End of explanation
# With data augmentation to prevent overfitting (accuracy 0.99286)
train_aug = ImageDataGenerator(        
        rotation_range=10,  # randomly rotate images in the range (degrees, 0 to 180)
        zoom_range = 0.1, # Randomly zoom image 
        width_shift_range=0.1,  # randomly shift images horizontally (fraction of total width)
        height_shift_range=0.1,  # randomly shift images vertically (fraction of total height)
        )
test_aug = ImageDataGenerator()
Explanation: 3. Data Augmentation
Kĩ thuật data augmentation được sử dụng để phát sinh thêm những mẫu dữ liệu mới bằng cách áp dụng các kĩ thuật xử lý ảnh trên bức ảnh. Các phép biến đổi nhỏ này phải đảm bảo không làm thay đổi nhãn của bức ảnh. 
Một số kĩ thuật phổ biến của data augmentation như là:
* Rotation: Xoay một góc nhỏ
* Translation: Tính tiến
* Brightness, Staturation: Thay đổi độ sáng, tương phản 
* Zoom: zoom to/nhỏ bức ảnh
* Elastic Distortion: biến dạng bức ảnh
* Flip: lật trái/phải/trên/dưới.
Ở dưới đây, chúng ta sẽ chọn xoay 1 góc trong 0-10 độ. Zoom ảnh 0.1 lần, tịnh tiến 0.1 lần mỗi chiều.
End of explanation
# Set the CNN model 
def train_model(train_generator, valid_generator, params):    
    model = Sequential()
    model.add(Conv2D(filters = params['conv1'], kernel_size = params['kernel_size_1'], padding = 'Same', 
                     activation ='relu', input_shape = (28,28,1)))
    model.add(BatchNormalization())
    model.add(Conv2D(filters = params['conv2'], kernel_size = params['kernel_size_2'], padding = 'Same', 
                     activation ='relu'))
    model.add(MaxPool2D(pool_size = params['pooling_size_1']))
    model.add(Dropout(params['dropout1']))
    model.add(BatchNormalization())
    model.add(Conv2D(filters = params['conv3'], kernel_size = params['kernel_size_3'], padding = 'Same', 
                     activation ='relu'))
    model.add(BatchNormalization())
    model.add(Conv2D(filters = params['conv4'], kernel_size = params['kernel_size_4'], padding = 'Same', 
                     activation ='relu'))
    model.add(MaxPool2D(pool_size = params['pooling_size_1'], strides=(2,2)))
    model.add(Dropout(params['dropout2']))
    model.add(Flatten())
    model.add(BatchNormalization())
    model.add(Dense(params['dense1'], activation = "relu"))
    model.add(Dropout(params['dropout3']))
    model.add(Dense(10, activation = "softmax"))
    
    if params['opt'] == 'rmsprop':
        opt = RMSprop()
    elif params['opt'] == 'sgd':
        opt = SGD()
    elif params['opt'] == 'nadam':
        opt = Nadam()
    else:
        opt = Adam()
    
    model.compile(loss=params['loss'], optimizer=opt, metrics=['acc'])
        
    reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=2, mode='auto', cooldown=2, min_lr=1e-7)
    early = EarlyStopping(monitor='val_loss', patience=3)
    
    callbacks_list = [reduce_lr, early]    
    
    history = model.fit_generator(train_generator,
                                  validation_data=valid_generator,
                                  steps_per_epoch=len(train_generator),
                                  validation_steps=len(valid_generator),
                                  callbacks=callbacks_list, epochs = epochs,
                                  verbose=2)
    
    score, acc = model.evaluate_generator(valid_generator, steps=len(valid_generator), verbose=0)
    
    return acc, model, history
Explanation: 4. Xây dưng mô hình CNN
CNN bao gồm tập hợp các lớp cơ bản bao gồm: convolution layer + nonlinear layer, pooling layer, fully connected layer. Các lớp này liên kết với nhau theo một thứ tự nhất định. Thông thường, một ảnh sẽ được lan truyền qua tầng convolution layer + nonlinear layer đầu tiên, sau đó các giá trị tính toán được sẽ lan truyền qua pooling layer, bộ ba convolution layer + nonlinear layer + pooling layer có thể được lặp lại nhiều lần trong network. Và sau đó được lan truyền qua tầng fully connected layer và softmax để tính sác xuất ảnh đó chứa vật thế gì.
Định nghĩa mô hình
Chúng ta sử dụng Keras Sequential API để định nghĩa mô hình. Các layer được thêm vào rất dễ dàng và tương đối linh động. 
Đầu tiên chúng ta sử dụng layer Conv2D trên ảnh đầu vào. Conv2D bao gồm một tập các filters cần phải học. Mỗi filters sẽ trược qua toàn bộ bức ảnh để detect các đặt trưng trên bức ảnh đó. 
Pooling layer là tầng quan trọng và thường đứng sau tầng Conv. Tầng này có chức năng giảm chiều của feature maps trước đó. Đối với max-pooling, tầng này chỉ đơn giản chọn giá trị lớn nhất trong vùng có kích thước pooling_size x pooling_size (thường là 2x2). Tầng pooling này được sử dụng để giảm chi phí tính toán và giảm được overfit của mô hình. 
Đồng thời, Dropout cũng được sử dụng để hạn chế overfit. Dropout sẽ bỏ đi ngẫu nhiên các neuron bằng cách nhân với mask zeros, do đó, giúp mô hình học được những đặc trưng hữu ích. Dropout trong hầu hết các trường hợp đều giúp tăng độ chính xác và hạn chết overfit của mô hình. 
Ở tầng cuối cùng, chúng ta flatten feature matrix thành một vector, sau đó sử dụng các tầng fully connected layers để phân loại ảnh thành các lớp cho trước.
Để giúp mô hình hội tụ gần với gobal minima chúng ta sử dụng annealing learning rate. Learning sẽ được điều chỉnh nhỏ dần sau mỗi lần cập nhật nếu như sau một số bước nhất định mà loss của mô hình không giảm nữa. Để giảm thời gian tính toán, chúng ta có thể sử dụng learning ban đầu lớn, sau đó giảm dần để mô hình hội tụ nhanh hơn.
Ngoài ra, chúng ta sử dụng early stopping để hạn chế hiện tượng overfit của mô hình. early stopping sẽ dừng quá trình huấn luyện nếu như loss trên tập validation tăng dần trong khi trên tập lại giảm. 
Sử dụng hyperas để tunning siêu tham số
Trong quá trình định nghĩa mô hình, chúng ta sẽ lồng vào đó các đoạn mã để hỗ trợ quá trình search siêu tham số đã được định nghĩa ở trên. Chúng ta sẽ cần search các tham số như filter_size, pooling_size, dropout rate, dense size. Đồng thời chúng ta cũng thử việc điều chỉnh cả optimizer của mô hình.
End of explanation
#This is the space of hyperparameters that we will search
space = {
    'opt':hp.choice('opt', ['adam', 'sgd', 'rmsprop']),
    
    'conv1':hp.choice('conv1', [16, 32, 64, 128]),
    'conv2':hp.choice('conv2', [16, 32, 64, 128]),
    'kernel_size_1': hp.choice('kernel_size_1', [3, 5]),
    'kernel_size_2': hp.choice('kernel_size_2', [3, 5]),
    'dropout1': hp.choice('dropout1', [0, 0.25, 0.5]),
    'pooling_size_1': hp.choice('pooling_size_1', [2, 3]),
    
    'conv3':hp.choice('conv3', [32, 64, 128, 256, 512]),
    'conv4':hp.choice('conv4', [32, 64, 128, 256, 512]),
    'kernel_size_3': hp.choice('kernel_size_3', [3, 5]),
    'kernel_size_4': hp.choice('kernel_size_4', [3, 5]),
    'dropout2':hp.choice('dropout2', [0, 0.25, 0.5]),
    'pooling_size_2': hp.choice('pooling_size_2', [2, 3]),
    
    'dense1':hp.choice('dense1', [128, 256, 512, 1024]),
    'dropout3':hp.choice('dropout3', [0, 0.25, 0.5]),
    
    'loss': hp.choice('loss', ['categorical_crossentropy', 'kullback_leibler_divergence']),
}
Explanation: 5. Hyper-params tunning
Chúng ta sử dụng Hyperas để tunning các tham số. Hyperas sẽ phát sinh bộ tham số dựa trên khai báo ở trên. Sau đó huấn luyện mô hình và đánh giá trên tập validation. Bộ tham số có độ chính xác cao nhất trên tập validation sẽ được ghi nhận lại. 
5.1 Khai báo không gian tìm kiếm siêu tham số
Có rất nhiều siêu tham số cần được tunning như: kiến trúc mạng, số filter, kích thước mỗi filters, kích thước pooling, các cách khởi tạo, hàm kích hoạt, tỉ lệ dropout,... Trong phần này, chúng ta sẽ tập trung vào các tham số  như kích thước filter, số filters, pooling size.
Đầu tiên, chúng ta cần khai báo các siêu tham để hyperas có thể tìm kiếm trong tập đấy. Ở mỗi tầng conv, chúng ta sẽ tunning kích thước filter, filter size. Ở tầng pooling, kích thước pooling size sẽ được tunning. Đồng thời, tỉ lệ dropout ở tầng Dropout cũng được tunning. Số filters ở tầng conv thường từ 16 -> 1024, kích thước filter hay thường dùng nhất trong là 3 với 5. Còn tỉ lệ dropout nằm trong đoạn 0-1
End of explanation
X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size = 0.2, random_state=random_seed)
# only apply data augmentation with train data
train_gen = train_aug.flow(X_train, Y_train, batch_size=batch_size)
valid_gen = test_aug.flow(X_val, Y_val, batch_size=batch_size)
def optimize(params):
    acc, model, history = train_model(train_gen, valid_gen, params)
    
    return -acc
Explanation: 5.2 Optimze để tìm bộ tham số tốt nhất
Hyperas sẽ phát sinh các bộ tham số giữ trên không gian tìm kiếm định nghĩa trước của chúng ta. Sau đó thư viện sẽ hỗ trợ quá trình tìm kiếm các tham số này đơn giản bằng một số API có sẵn.
End of explanation
best = fmin(fn = optimize, space = space, 
            algo = tpe.suggest, max_evals = 50) # change to 50 to search more
best_params = space_eval(space, best)
print('best hyper params: \n', best_params)
Explanation: Chạy quá trình search tham số. Bộ siêu tham số tốt nhất sẽ được ghi nhận lại để chúng ta sử dụng trong mô hình cuối cùng.
End of explanation
acc, model, history = train_model(train_gen, valid_gen, best_params)
print("validation accuracy: {}".format(acc))
Explanation: Huấn luyện lại mô hình với bộ tham số tốt nhất ở trên.
End of explanation
optimizers = ['rmsprop', 'sgd', 'adam']
hists = []
params = best_params
for optimizer in optimizers:
    params['opt'] = optimizer
    print("Train with optimizer: {}".format(optimizer))
    _, _, history = train_model(train_gen, valid_gen, params)
    hists.append((optimizer, history))
Explanation: Kết quả trên tập validation khá cao với acc > 99%
6. So sánh optimizers và loss
6.1 So sánh các optimzers
Mục tiêu của quá trình huấn luyện mô hình ML là giảm độ lỗi của hàm loss function được tính bằng sự khác biệt của giá trị mô hình dự đoán và giá trị thực tế. Để đạt được mục đích này chúng ta thường sử dụng gradient descent. Gradient descent sẽ cập nhật trọng số của mô hình ngược với chiều gradient để giảm độ lỗi của loss function. 
Chúng ta sử thường sử dụng 3 optimzer phổ biến sau là adam, sgd, rmsprop để cập nhật trọng số của mô hình. 
Stochastic Gradient Descent là một biến thể của Gradient Descent, yêu cầu chúng ta phải shuffle dự liệu trước khi huấn luyện. Trong khi đó RMSProp và Adam là 2 optimizer hướng đến việc điều chỉnh learning rate tự động theo quá trình học.
RMSprop (Root mean square propagation) được giới thiệu bởi Geoffrey Hinton. RMSProp giải quyết vấn đề giảm dần learning rate của Adagrad bằng cách chuẩn hóa learning với gradient gần với thời điểm cập nhật mà thôi. Để làm được điều này tác giả chia learning rate cho tổng bình phương gradient giảm dần. 
Adam là optimizer phổ biến nhất tại thời điểm hiện tại. Adam cũng tính learning riêng biệt cho từng tham số, tương tự như RMSProp và Adagrad. Adam chuẩn hóa learning của mỗi tham số bằng first và second order moment của gradient.
End of explanation
for name, history in hists:
    plt.plot(history.history['val_acc'], label=name)
plt.legend(loc='best', shadow=True)
plt.tight_layout()
Explanation: Plot quá trình huấn luyện mô hình với 3 lọai optimizers khác nhau.
End of explanation
loss_functions = ['categorical_crossentropy', 'kullback_leibler_divergence']
hists = []
params = best_params
for loss_funct in loss_functions:
    params['loss'] = loss_funct
    print("Train with loss function : {}".format(loss_funct))
    _, _, history = train_model(train_gen, valid_gen, params)
    hists.append((loss_funct, history))
Explanation: 6.2 So sánh các loss function
Trong bài toán phân loại nhiều lớp. Chúng ta thường sử dụng 2 loại loss function sau:
* Cross entropy
* Kullback Leibler Divergence Loss
Cross entropy được sử dụng phổ biến nhất trong bài toán của chúng ta. Cross entropy loss có nền tảng toán học của maximun likelihood được tính bằng tổng của sự khác biệt giữ giá trị dự đoán và giá trị thực tế của dữ liệu. Cross entropy error tốt nhất khi có giá trị bằng 0.
KL loss (Kullback Leibler Divergence Loss) thể hiện sự khác biệt giữ 2 phân bố xác suất. KL loss bằng 0, chứng tỏ 2 phân bố này hoàn toàn giống nhau. 
Cross entropy cho bằng toán phân loại nhiều lớn tương đối giống với KL Loss về mặt toán học, nên có thể xem 2 độ lỗi này là một trong bài toán của chúng ta.
End of explanation
for name, history in hists:
    plt.plot(history.history['val_acc'], label=name)
plt.legend(loc='best', shadow=True)
plt.tight_layout()
Explanation: Plot quá trình huấn luyện mô hình với 2 loại loss function khác nhau.
End of explanation
def plot_confusion_matrix(cm, classes,
                          normalize=False,
                          title='Confusion matrix',
                          cmap=plt.cm.Blues):
    
    This function prints and plots the confusion matrix.
    Normalization can be applied by setting `normalize=True`.
    
    plt.imshow(cm, interpolation='nearest', cmap=cmap)
    plt.title(title)
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes, rotation=45)
    plt.yticks(tick_marks, classes)
    if normalize:
        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
    thresh = cm.max() / 2.
    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
        plt.text(j, i, cm[i, j],
                 horizontalalignment="center",
                 color="white" if cm[i, j] > thresh else "black")
    plt.tight_layout()
    plt.ylabel('True label')
    plt.xlabel('Predicted label')
# Predict the values from the validation dataset
Y_pred = model.predict(X_val)
# Convert predictions classes to one hot vectors 
Y_pred_classes = np.argmax(Y_pred,axis = 1) 
# Convert validation observations to one hot vectors
Y_true = np.argmax(Y_val,axis = 1) 
# compute the confusion matrix
confusion_mtx = confusion_matrix(Y_true, Y_pred_classes) 
# plot the confusion matrix
plot_confusion_matrix(confusion_mtx, classes = range(10)) 
Explanation: Chúng ta thấy rằng không có sự khác biệt rõ rằng về tốc độ hội tụ giữ 2 hàm loss function là cross-entropy và KL loss trong bài toán của chúng ta.
7. Đánh giá mô hình.
Chúng ta sẽ xem xét một số lỗi của mô hình dự huấn luyện được. Một số lỗi dễ dàng được phát hiện bằng confusion matrix thể hiện xác xuất/số ảnh bị phân loại nhầm thành lớp khác.
End of explanation
# Display some error results 
# Errors are difference between predicted labels and true labels
errors = (Y_pred_classes - Y_true != 0)
Y_pred_classes_errors = Y_pred_classes[errors]
Y_pred_errors = Y_pred[errors]
Y_true_errors = Y_true[errors]
X_val_errors = X_val[errors]
def display_errors(errors_index,img_errors,pred_errors, obs_errors):
     This function shows 6 images with their predicted and real labels
    n = 0
    nrows = 2
    ncols = 3
    fig, ax = plt.subplots(nrows,ncols,sharex=True,sharey=True)
    for row in range(nrows):
        for col in range(ncols):
            error = errors_index[n]
            ax[row,col].imshow((img_errors[error]).reshape((28,28)))
            ax[row,col].set_title("Predicted label :{}\nTrue label :{}".format(pred_errors[error],obs_errors[error]))
            n += 1
    fig.tight_layout()
    
# Probabilities of the wrong predicted numbers
Y_pred_errors_prob = np.max(Y_pred_errors,axis = 1)
# Predicted probabilities of the true values in the error set
true_prob_errors = np.diagonal(np.take(Y_pred_errors, Y_true_errors, axis=1))
# Difference between the probability of the predicted label and the true label
delta_pred_true_errors = Y_pred_errors_prob - true_prob_errors
# Sorted list of the delta prob errors
sorted_dela_errors = np.argsort(delta_pred_true_errors)
# Top 6 errors 
most_important_errors = sorted_dela_errors[-6:]
# Show the top 6 errors
display_errors(most_important_errors, X_val_errors, Y_pred_classes_errors, Y_true_errors)
Explanation: Các giá trị trên đường chéo rất cao, chúng ta mô hình chúng ta có độ chính xác rất tốt.
Nhìn vào confusion matrix ở trên, chúng ta có một số nhận xét như sau:
* Số 4 hay nhầm lẫn với số 9, bởi vì khi viết tay đường nét của 2 số này tương tự nhau khá nhiều
* số 3 và số 8, cũng hay bị tình trạng tương tự. 
Hiển thị một số trường hợp bị sai
Để có cái nhìn rõ hơn về một số mẫu bị sai, chúng ta quan sát top các mẫu có giá trị dự đoán khác nhất so với nhãn thật
End of explanation
kf = KFold(n_splits=5)
preds = []
for train_index, valid_index in kf.split(X):
    X_train, Y_train, X_val, Y_val = X[train_index], Y[train_index], X[valid_index], Y[valid_index]
    train_gen = train_aug.flow(X_train, Y_train, batch_size=batch_size)
    valid_gen = test_aug.flow(X_val, Y_val, batch_size=batch_size)
    acc, model, history = train_model(train_gen, valid_gen, best_params)
    
    pred = model.predict(X_test)
    preds.append(pred)
# predict results
results = np.mean(preds, axis=0)
# select the indix with the maximum probability
results = np.argmax(results,axis = 1)
results = pd.Series(results,name="Label")
submission = pd.concat([pd.Series(range(1,28001),name = "ImageId"),results],axis = 1)
submission.to_csv("cnn_mnist_datagen.csv",index=False)
Explanation: Với các mẫu ảnh sai, chúng ta có thể thấy rằng những mẫu này rất khó nhận dạng nhầm lẫn sáng các lớp khác. ví dụ số 9 và 4 hay là 3 và 8
8. Kfold, Predict và submit kết quả
Chúng ta huấn luyện lại mô hình sử dụng kfold, kết quả cuối dự đoán cuối cùng là trung bình cộng sự đoán của các mô hình huấn luyện trên mỗi fold.
Chúng ta chọn nhãn là lớp được dự đoán có xác suất cao nhất mà mô hình nhận dạng được
End of explanation
<END_TASK>