# Neural Network
## Lời nói đầu
Mạng nơ-ron (Neural Network) là nền tảng của lĩnh vực Học Sâu (Deep Learning nổi tiếng), được sử dụng để giải quyết rất nhiều bài toán. Trong bài viết này, nhóm tác giả trình bày các nội dung chính như sau:
- Giới thiệu về bài toán phân lớp.
- Nhắc lại các phương pháp liên quan đến Neural Network trước đây (Logistic Regression, Softmax Regression).
- Giới thiệu mạng nơ-ron (Neural Network), cung cấp các điểm khác biệt so với các giải thuật trước đây.
## Giới thiệu
### Bài toán phân lớp
Trong các lĩnh vực liên quan đến Trí tuệ nhân tạo, ví dụ như Thị giác máy tính, hay Xử lý ngôn ngữ tự nhiên, thì bài toán phân lớp là một bài toán cơ bản nhất. Từ bài toán phân lớp, chúng ta có thể phát triển thành các bài toán nâng cao khác. Cụ thể, bài toán phân lớp được định nghĩa như sau:
- Đầu vào: một dạng dữ liệu (ảnh, văn bản).
- Đầu ra: loại/lớp của dữ liệu đó.
Ví dụ cụ thể một bài toán: Phân loại ảnh. Như vậy, ta sử dụng định nghĩa trên để xác định đầu vào - đầu ra bài toán:
- Đầu vào: một bức ảnh
- Đầu ra: loại/lớp của bức ảnh.

**Hình 1.** Minh họa đầu vào và đầu ra của bài toán phân lớp ảnh.
### Các phương pháp trước đây và hạn chế
#### Nhắc lại Logistic Regression
Để thực hiện bài toán phân lớp, thông thường chúng ta sẽ sử dụng phương pháp Logistic Regression. Cụ thể, nếu chúng ta có một điểm dữ liệu X = {x1, x2, x3, ..., xn}, mô hình Logistic được định nghĩa như sau:
$$f(x) = Sigmoid(w_0 + w_1 \times x_1 + w_2 \times x_2 + ... + w_n \times x_n)$$
Trong đó, $w_0 + w_1 \times x_1 + w_2 \times x_2 + ... + w_n \times x_n$ chính là một tổng tuyến tính; w0 là hệ số tự do, còn $w_0, w_1, ..., w_n$ là hệ số của mô hình (giống với Linear Regression); tuy nhiên Linear Regression sẽ trả về một số thực, để đưa về dạng phi tuyến chúng ta sử dụng hàm sigmoid. Hàm sigmoid được định nghĩa như sau:

**Hình 2.** Dạng hàm của hàm Signoid.
Trong bài toán phân lớp nhị phân (phân lớp 0,1), ta có thể xem đầu ra của Sigmoid là xác suất thuộc về lớp 1 (hoặc 0). Ta đặt một ngưỡng t nào đó, nếu xác suất này lớn hơn t, ta coi đó là lớp 1 (hoặc 0), ngược lại là 0 (hoặc 1).
Để huấn luyện mô hình Logistic Regression, ta cần so sánh giá trị dự đoán với giá trị thực tế. Thông thường chúng ta sử dụng hàm Binary Cross Entropy được định nghĩa như sau:
$$J(W)=-\sum_{i=1}^N(y_i\times\log{a_i} + (1-y_i)\times\log{(1-a_i)})$$
Trong đó, $y$ là giá trị thực tế của mẫu dữ liệu $X$; $a_i$ là giá trị dự đoán của mô hình Logistic Regression.
Điều chúng ta mong muốn là giá trị dự đoán gần với giá trị thực tế, do đó ta phải làm cho giá trị hàm L này càng nhỏ trong lúc huấn luyện. Ta thực hiện điều này bằng cách sử dụng Gradient Descent, cập nhật lại các trọng số $W={w_1, w_2, ..., w_n}$ dựa trên giá trị đạo hàm của hàm L đối với chúng bằng công thức sau:
$$w = w - \alpha\times\frac{dJ}{dw}$$
Như vậy, chúng ta sẽ tìm được bộ trọng số $W={w_1, w_2, ..., w_n}$ tốt nhất, mô hình Logistic Regression lúc này có khả năng dự đoán một mẫu/tập dữ liệu mới.
Tuy nhiên, ta có thể nhận thấy rằng nếu muốn phân lớp nhiều hơn 02 lớp, ví dụ là 03 lớp, chúng ta cần huấn luyện 3 mô hình Logistic Regression, tức là sẽ có 3 bộ trọng số ${W_1, W_2, W_3}$, với $W_i = {w_1, w_2, ..., w_n}$. Ví dụ ta có $C$ lớp, ta sẽ có $C$ bộ tham số $W$, kết quả của hàm Sigmoid ở từng bộ $W_i$ sẽ phản ánh giá trị như là một bài toán phân lớp nhị phân, nếu giá trị này lớn hơn ngưỡng $t$, thì dữ liệu đầu vào thuộc vào lớp $i$, ngược lại sẽ thuộc về $C-1$ lớp còn lại.

**Hình 3.** Ví dụ về Logistic Regression cho bài toán phân C lớp. [Machine learning cơ bản](https://machinelearningcoban.com/2017/02/17/softmax/)
Ta có một cách tiếp cận khác hiệu quả hơn, đó là sử dụng hàm Softmax, cụ thể sẽ được giới thiệu ở phân kế tiếp.
#### Nhắc lại Softmax Regression
Softmax Regression về cơ bản cũng giống như Logistic Regression, tuy nhiên thay vì dùng hàm Sigmoid ở mỗi bộ $W_i$, chúng ta sẽ sử dụng hàm Softmax. Ví dụ ta có bài toán phân lớp có $C$ lớp, mô hình Softmax Regression trở thành như sau:
$$z_i = w_{i0}+w_{i1}\times x_1+w_{i2}\times x_2 + ... + w_{in}\times x_n$$$$a_i = \mathrm{Softmax}(z_i)$$
Trong đó, $i \in \{1, 2, 3, ..., C\}$, $z_i$ là tổng tuyến tính của lớp thứ i, và công thức Softmax được định nghĩa như sau:
$$a_i = \frac{\mathrm{exp}(z_i)}{\sum^C_{j=1}\mathrm{exp}(z)j},\forall i=1,2...,C$$
Như vậy, $a_i$ chính là xác suất xảy ra lớp thứ $i$. Ta có $C$ lớp cần phân, như vậy ta có vector phân bố xác suất các lớp $A=(a_1, a_2, ..., a_C)$.
Dựa vào công thức Softmax, dễ dàng nhận thấy tổng các $a_i$ bằng 1.
Ta xem Hình 3 để hình dung rõ hơn Softmax Regression.

**Hình 4.** Minh họa Softmax Regression. Nguồn: [Machine learning cơ bản](https://machinelearningcoban.com/2017/02/17/softmax/)
Để phân loại nhiều lớp, chúng ta sử dụng hàm Cross Entropy thay vì Binary Cross Entropy. Hàm Cross Entropy được định nghĩa như sau:
$$J(W;x_i;y_i)=-\sum^{C}_{j=1}y_{ij}\times\log{(a_{ji})}$$
Trong đó $W$ là các trọng số, $x_i, y_i$ là điểm dữ liệu thứ $i$. $C$ là số lớp.
Cách huấn luyện và cập nhật trọng số tương tự như Logistic Regression.
Điểm khác biệt lớn nhất của Logistic Regression và Softmax Regression rất dễ nhận thấy: sử dụng 2 hàm phi tuyến khác nhau. Logistic Regression sử dụng Sigmoid, còn Softmax Regression sử dụng Softmax:
- Sigmoid sẽ trả về 1 giá trị xác suất one-to-rest: hoặc là dữ liệu thuộc về lớp $i$, hoặc là sẽ thuộc vào một trong các lớp còn lại, sử dụng hết $C$ mô hình để tìm ra lớp dự đoán của một mẫu dự đoán mới.
- Softmax tính xác suất của lớp i sử dụng thông tin từ các tổng tuyến tính thuộc các lớp khác. Ta có $C$ lớp, như vậy vector phân bố xác suất cuối cùng sẽ là $A=(a_1, a_2, ..., a_C)$, trong đó tổng các $a_i$ cộng lại bằng 1.
#### Hạn chế
Dễ dàng nhận ra Logisic hay Softmax Regression khá nông, thông tin chỉ được chuyển tiếp qua một lớp tuyến tính + phi tuyến, số trọng số $W$ ít, khiến mô hình không học được gì nhiều đối với dữ liệu có số đặc trưng lớn (ảnh, văn bản
## Neural Network
Để học được nhiều hơn, chúng ta có một lý thuyết mới: mạng nơ-ron nhân tạo. Đối với các dữ liệu có số đặc trưng lớn, ví dụ như ảnh. Một bức ảnh kích thước $28\times28$ thì chúng ta sẽ có $768$ đặc trưng cần học. Do đó, ta cần thêm các lớp tuyến tính + phi tuyến để có thể học được nhiều hơn. Đó chính là nội dung chính của Neural Network. Có thể hiểu đơn giản, Neural Network cũng tương tự Softmax Regression có nhiều lớp tuyến tính + phi tuyến hơn.
Một mạng nơ-ron nhân tạo bao gồm các thành phần chính:
- Lớp đầu vào
- Lớp ẩn: chứa các trọng số học, học các thông tin đặc trưng dữ liệu. Các node ở lớp ẩn chính là các tổng tuyến tính, với số node chúng ta sẽ xác định trước.
- Lớp đầu ra
Chúng ta xét một ví dụ mạng nơ-ron như sau (Được lấy từ ví dụ của Bài Neural Network ở sách Deep Learning cơ bản của tác giả Nguyễn Thanh Tuấn):

**Hình 5.** Một mạng Nơ-ron có 2 lớp ẩn, mỗi lớp ẩn có 3 node. Nguồn: [Deep Learning cơ bản](https://nttuan8.com/bai-3-neural-network/).
### Ý tưởng chính
Mạng nơ-ron có hai quá trình chính: Lan truyền tiến (Feed Forward) và Lan truyền ngược (Backpropagation). Quá trình lan truyền tiến là các bước tính tổng tuyến tính ở các node, sau đó giá trị tính được sẽ đi qua một hàm kích hoạt nào đó (ReLU, Sigmoid, etc.). Đối với bài toán phân lớp, lớp cuối cùng sẽ đi qua hàm kích hoạt Softmax để tính xác suất xảy ra các lớp. Lúc này xác suất dự đoán sẽ được so với nhãn bằng hàm Cross Entropy (tương tự Softmax Regression). Ta từ từ tính đạo hàm của hàm loss với các trọng số trong mạng, và cập nhật lại bằng Gradient Descent. Sau nhiều vòng lặp, mạng sẽ học được một bộ tham số được xem là tối ưu.
Tuy nhiên, trong ví dụ dưới đây, chúng ta chỉ xem xét một mạng nơ-ron đơn giản cho bài toán phân lớp nhị phân.
**Lưu ý:** giải thích phía bên dưới sẽ dựa vào Hình 4.
### Lan truyền tiến
Lưu ý kí hiệu:
- $z^{(i)}_j$: Tổng tuyến tính của node thứ $j$ tại lớp ẩn thứ $i$.
- $a^{(i)}_j$: Giá trị hàm kích hoạt tại node thứ $j$ tại lớp ẩn thứ $i$.
- $w^{i}_{lk}$: Trọng số của đầu vào thứ $l$ ở lớp $i-1$ được dùng để tính tổng tuyến tính của node thứ $k$ tại lớp $i$.
Có thể thấy rằng, mỗi node sẽ là kết quả của tổng tuyến tính toàn bộ các node trước đó, sau đó giá trị này được đi qua hàm kích hoạt. Ví dụ, xét mạng nơ-ron có 2 lớp ẩn, mỗi lớp 3 node như Hình 5, ta có $X=\{X_1, X_2\}$ là đầu vào của mạng, các node ở Hidden layer 1 sẽ được tính như sau:
$z^{(1)}_1=b_1^{(1)}+w_{11}^{(1)}\times x_1 + w^{(1)}_{21}\times x_2$
$a^{(1)}_1 = \sigma(z^{(1)}_1)$
$z^{(1)}_2=b_2^{(1)}+w_{12}^{(1)}\times x_1 + w^{(1)}_{22}\times x_2$
$a^{(1)}_2 = \sigma(z^{(1)}_2)$
$z^{(1)}_3=b_3^{(1)}+w_{13}^{(1)}\times x_1 + w^{(1)}_{23}\times x_2$
$a^{(1)}_3 = \sigma(z^{(1)}_3)$
Các node ở lớp ẩn thứ 2 sẽ nhận đầu vào là các node ở lớp ẩn phía trước:
$z^{(2)}_1=b_1^{(2)}+w_{11}^{(2)}\times a^{(1)}_1 + w^{(2)}_{21}\times a^{(1)}_2 + w^{(2)}_{31}\times a^{(1)}_3$
$a^{(2)}_1 = \sigma(z^{(2)}_1)$
$z^{(2)}_2=b_2^{(2)}+w_{12}^{(2)}\times a^{(1)}_1 + w^{(2)}_{22}\times a^{(1)}_2 + w^{(2)}_{32}\times a^{(1)}_3$
$a^{(2)}_2 = \sigma(z^{(2)}_2)$
$z^{(2)}_3=b_3^{(2)}+w_{13}^{(2)}\times a^{(1)}_1 + w^{(2)}_{23}\times a^{(1)}_2 + w^{(2)}_{33}\times a^{(1)}_3$
$a^{(2)}_3 = \sigma(z^{(2)}_3)$
Cuối cùng, chúng ta sẽ tính toán đầu ra bằng công thức:
$z^{(3)}_1=b_1^{(3)}+w_{11}^{(3)}\times a^{(2)}_1 + w^{(3)}_{21}\times a^{(2)}_2 + w^{(3)}_{31}\times a^{(2)}_3$
$\hat y = a^{(3)}_1 = \sigma(z^{(3)}_1)$
Trong trường hợp chúng ta đang giải quyết bài toán phân lớp nhị phân, hàm kích hoạt $\phi$ ở lớp cuối cùng có thể là `Sigmoid`. Còn đối với bài toán phân $C$ lớp, chúng ta sẽ dùng hàm `Softmax`.
Đến đây là đã hoàn thành xong quá trình lan truyền tiến. Để học được bộ trọng số tốt, chúng ta cần lan truyền ngược để cập nhật trọng số bằng Gradient Descent. Quá trình lan truyền ngược sẽ được giải thích ở phần bên dưới.
### Lan truyền ngược
#### Ý tưởng chính
Khi đã hiểu lan truyền tiến, chúng ta sẽ tìm hiểu cách làm thế nào một trọng số $w^{i}_{lk}$ được cập nhật.
Sau khi có được đầu ra $\hat y$, chúng ta sẽ tính toán sai số của nó với nhãn $y$ bằng hàm mất mát Binary Cross Entropy.
Hàm Binary Cross Entropy có dạng:
$$L = -y\times\log{\hat y}-(1-y)\times\log{(1-\hat y)}$$
Ý tưởng là chúng ta sẽ đi tính đạo hàm của hàm $L$ với các trọng số $w^{i}_{lk}$, sau đó cập nhật lại trọng số bằng công thức trong Gradient Descent. Ta sẽ cập nhật từ lớp ẩn cuối cùng đến lớp ẩn đầu tiên.
#### Cách thực hiện
Dễ nhận thấy, chúng ta cần phải đi cập nhật các trọng số ở lớp ẩn cuối cùng trước. Lớp ẩn cuối cùng bao gồm 3 tham số $w_{11}^{(3)}, w_{21}^{(3)}, w_{31}^{(3)}$. Để cập nhật 3 tham số này, ta sẽ lần lượt tính đạo hàm của hàm $L$ đối với 3 tham số này. Bây giờ nhóm sinh viên giả sử ta đi tính đạo hàm của hàm $L$ đối với $w_{11}^{(3)}$, ta tính như sau:
$$\frac{dL}{dw_{11}^{(3)}}=\frac{dL}{d\hat y} \times \frac{d\hat y}{dw_{11}^{(3)}}$$
Giải thích: Ta không thể tính trực tiếp đạo hàm của $L$ với $w_{11}^{(3)}$, vì thực tế hàm $L$ không nhận đối số trực tiếp là $w_{11}^{(3)}$, mà nó nhận đối số trực tiếp là $\hat y$, do đó ta cần dùng chain rule để tính đạo hàm của $L$ với $w_{11}^{(3)}$ thông qua đạo hàm của $L$ đối với $\hat y$ và đạo hàm của $\hat y$ đối với $w_{11}^{(3)}$
.
Đến đây thì dễ, ta lần lượt tính $\frac{dL}{d\hat y}$ và $\frac{d\hat y}{dw_{11}^{(3)}}$.
$$\frac{dL}{d\hat y} = -(\frac{y}{\hat y}-\frac{1-y}{1-\hat y})$$ (Ta dễ dàng tính được đạo hàm này)
Chúng ta lưu ý rằng, $\hat y$ là kết quả của một hàm kích hoạt $\sigma$ nào đó, tức là $w_{11}^{(3)}$ cũng không phải là đối số trực tiếp của $\hat y$, mà nó là đối số trực tiếp của một hàm kích hoạt nào đó. Do đó:
$$\frac{\hat y}{dw_{11}^{(3)}} = \frac{d\sigma(z^{(3)}_1)}{dz^{(3)}_1} \times \frac{dz^{(3)}_1}{dw_{11}^{(3)}}$$
Trong ví dụ này, ta xem hàm $\sigma$ là hàm Sigmoid, hàm Sigmoid được định nghĩa như sau:
$$\sigma = \frac{1}{1+e^{-x}}$$
Ta đó đạo hàm của x với hàm Sigmoid:
$$\frac{d\sigma(x)}{dx}=\sigma(x)\times(1-\sigma(x))$$
Như vậy:
$$\frac{d\sigma(z^{(3)}_1)}{dz^{(3)}_1} = \sigma{(z^{(3)}_1)} \times (1-\sigma{(z^{(3)}_1)}) =\hat y \times (1- \hat y)$$
Tính đạo hàm của $\frac{dz^{(3)}_1}{dw_{11}^{(3)}}$ cũng rất đơn giản:
$$\frac{dz^{(3)}_1}{dw_{11}^{(3)}} = \frac{d(b_1^{(3)}+w_{11}^{(3)}\times a^{(2)}_1 + w^{(3)}_{21}\times a^{(2)}_2 + w^{(3)}_{31}\times a^{(2)}_3)}{dw_{11}^{(3)}} = a_1^{(2)}$$
Cuối cùng, chúng ta tính được:
$$\frac{d\sigma(z^{(3)}_1)}{dz^{(3)}_1} \times \frac{dz^{(3)}_1}{dw_{11}^{(3)}} = a_1^{(2)} \times \hat y \times (1 - \hat y)$$
Có được đạo hàm, ta cập nhật lại $w^{(3)}_{11}$ theo công thức Gradient Descent:
$$w^{(3)}_{11} = w^{(3)}_{11} - \alpha \times \frac{dL}{dw^{(3)}_{11}}$$
Chúng ta cập nhật $w^{(3)}_{21}, w^{(3)}_{31}$ một cách tương tự.
Vừa rồi chúng ta cập nhật trọng số ở lớp cuối, như đã đề cập, lớp cuối có 3 trọng số. Ở lớp thứ hai, chúng ta sẽ có $9$ trọng số $w_{lk}^{(i)}$, với $i = 2$, $l$ và $k$ đều có giá trị $\{1,2,3\}$. Ví dụ để tính đạo hàm của hàm mất mát $L$ với tham số $w_{11}^{(2)}$, ta thực hiện:
$$\frac{dL}{dw^{(2)}_{11}}=\frac{dL}{d\hat y}\times \frac{d \hat y}{d w^{(2)}_{11}} = \frac{dL}{d\hat y} \times \frac{d \hat y}{d z^{(3)}_1} \times \frac{d z^{(3)}_1}{d a^{(2)}_1} \times \frac{d a^{(2)}_1}{d z^{(2)}_1} \times \frac{d z^{(2)}_1}{dw^{(2)}_{11}}$$
Giải thích: ta thấy $w^{(2)}_{11}$ là đối số trực tiếp của $z^{(2)}_1$, mà $z^{(2)}_1$ là đối số trực tiếp của $a^{(2)}_1 = \sigma{(z^{(2)}_1)}$. Tiếp tục quan sát, ta thấy $a^{(2)}_1$ chính là đối số trực tiếp của tổng tuyến tính $z^{(3)}_1$ , $z^{(2)}_1$ lại là đối số trực tiếp vào hàm kích hoạt sigmoid để cho ra đầu ra $\hat y$. Do đó, chúng ta sẽ sử dụng chain rule để tính đạo hàm của hàm mất mát $L$ đối với $w^{(2)}_{11}$ như công thức bên trên. Như vậy, ta sẽ cập nhật được trọng số $w^{(2)}_{11}$ bằng công thức Gradient Descent, chúng ta lần lượt lùi về các lớp phía trước để cập nhật trọng số như vậy.
Quá trình lan truyền tiến - lan truyền ngược sẽ diễn ra một cách xen kẽ và liên tục, đến khi dừng quá trình huấn luyện, chúng ta sẽ thu được một bộ trọng số đủ tốt để có thể dự đoán trên một mẫu dữ liệu mới.
## Thực hành
Sau khi đã tìm hiểu lý thuyết về Neural Network, chúng ta thực hành một bài tập nho nhỏ: **Phân lớp chữ số viết tay.**
Trong bài tập này, chúng ta sử dụng thư viện **Keras**.
### Load dữ liệu
Đầu tiên, chúng ta load bộ dữ liệu MNIST từ thư viện `keras.datasets`:
```[python3]
from keras.datasets import mnist
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print('X_train shape', X_train.shape, 'X_test shape', X_test.shape)
```
Output:
```
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
11501568/11490434 [==============================] - 0s 0us/step
X_train shape (60000, 28, 28) X_test shape (10000, 28, 28)
```
### Trực quan hóa
Chúng ta trực quan hóa một số mẫu trong bộ dữ liệu:
```[python3]
import matplotlib.pyplot as plt
import random
# Visualize data
plt.figure(figsize = (12,5))
for i in range(8):
ind = random.randint(0, len(X_train))
plt.subplot(240 + 1 + i)
plt.imshow(X_train[ind])
```
Đầu ra:

### Tiền xử lý dữ liệu
Chúng ta cần thực hiện các bước tiền xử lý:
- Đưa các giá trị pixel từ range [0, 255] về range [0, 1].
- Thực hiện one-hot-encoding cho nhãn dữ liệu.
```[python3]
from tensorflow.keras.utils import to_categorical # thư viện dùng để one-hot encoding
# chuyển từ 28x28 về vector 768
X_train = X_train.reshape(X_train.shape[0], -1)
# chuyển từ 28x28 về vector 768
X_test = X_test.reshape(X_test.shape[0], -1)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
# normalization
X_train = X_train/255.0
X_test_norm = X_test/255.0
# One-hot encoding label
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
```
### Khởi tạo mô hình
Chúng ta khởi tạo một class `CS431_model()`, chứa các phương thức cần thiết: `train()`, `build()`, `summary()`, `predict()`, `save()`, `load()`. Trong đó:
- `train()`: dùng để train mô hình
- `build()`: dùng để xây dựng mô hình
- `summary()`: dùng để xem kiến trúc mô hình
- `predict()`: dùng để dự đoán với tập dữ liệu test
- `save()`: lưu trọng số train được
- `load()`: load trọng số đã save lên
```[python3]
class CS431_model():
def __init__(self, num_epoch, batch_size, learning_rate, validation_split):
self.num_epoch = num_epoch # số vòng lặp trian
self.batch_size = batch_size # số batch size
self.learning_rate = learning_rate # learning rate
self.validation_split = validation_split # tỷ lệ val
return None
def build(self):
'''
Build model
'''
self.model = Sequential()
self.model.add(Dense(120, input_shape = (784,))) # lớp ẩn đầu tiên có 120 node, dùng hàm kích hoạt sigmoid
self.model.add(Dense(120, activation = 'sigmoid')) # lớp ẩn thứ hai có 120 node, dùng hàm kích hoạt sigmoid
self.model.add(Dense(84, activation = 'sigmoid')) # lớp ẩn thứ ba có 84 node, dùng hàm kích hoạt sigmoid
self.model.add(Dense(10, activation = 'softmax')) # lớp phân loại cuối cùng hàm softmax
opt = SGD(lr = 0.01) # bộ optimizer
# complie model để train, sử dụng hàm loss Categorical Cross Entropy
self.model.compile(loss = CategoricalCrossentropy(), optimizer = opt, metrics = ['accuracy'])
return None
def train(self, X_train, y_train):
# train mô hình
history = self.model.fit(X_train, y_train, validation_split=self.validation_split, epochs = self.num_epoch, \
batch_size = self.batch_size)
return None
def save(self):
# lưu mô hình
self.model.save_weights('CS431_model')
return None
def load(self):
# load mô hình
self.model.load_weights('CS431_model')
return None
def summary(self):
# xem kiến trúc + số tham số mô hình
self.model.summary()
return None
def predict(self, X_test):
# dự đoán
y_pred = self.model.predict(X_test) # (60k, 10)
y_pred = np.argmax(y_pred, axis=1) # (60k, 1)
return y_pred
```
### Tiến hành huấn luyện
Chúng ta sử dụng các lệnh sau để huấn luyện:
```[python3]
# khởi tạo mô hình
main_model = CS431_model(num_epoch=5, batch_size=64, learning_rate=0.01, validation_split=0.2)
main_model.build() # xây dựng mô hình
main_model.summary() # xem kiến trúc + số tham số
main_model.train(X_train, y_train) # huấn luyện
```
Output:
```
Model: "sequential_3"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_12 (Dense) (None, 120) 94200
dense_13 (Dense) (None, 120) 14520
dense_14 (Dense) (None, 84) 10164
dense_15 (Dense) (None, 10) 850
=================================================================
Total params: 119,734
Trainable params: 119,734
Non-trainable params: 0
_________________________________________________________________
/usr/local/lib/python3.7/dist-packages/keras/optimizer_v2/gradient_descent.py:102: UserWarning: The `lr` argument is deprecated, use `learning_rate` instead.
super(SGD, self).__init__(name, **kwargs)
Epoch 1/5
750/750 [==============================] - 2s 3ms/step - loss: 2.2641 - accuracy: 0.2451 - val_loss: 2.1970 - val_accuracy: 0.5391
Epoch 2/5
750/750 [==============================] - 2s 3ms/step - loss: 2.0936 - accuracy: 0.5117 - val_loss: 1.9378 - val_accuracy: 0.5877
Epoch 3/5
750/750 [==============================] - 2s 3ms/step - loss: 1.7070 - accuracy: 0.6006 - val_loss: 1.4497 - val_accuracy: 0.6284
Epoch 4/5
750/750 [==============================] - 2s 3ms/step - loss: 1.2757 - accuracy: 0.6681 - val_loss: 1.1001 - val_accuracy: 0.7069
Epoch 5/5
750/750 [==============================] - 2s 3ms/step - loss: 1.0173 - accuracy: 0.7242 - val_loss: 0.9044 - val_accuracy: 0.7628
```
Sau khi huấn luyện xong, ta save lại và predict trên tập test:
```[python3]
main_model.save()
y_pred = main_model.predict(X_test)
```
### Đánh giá mô hình
Chúng ta load lại dữ liệu để đánh giá:
```[python3]
# load lại dữ liệu
(X_train, y_train), (X_test, y_test) = mnist.load_data()
print('X_train shape', X_train.shape, 'X_test shape', X_test.shape)
```
Định nghĩa tên các lớp
```[python3]
target_names = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
```
Chúng ta dùng thư viện `classification_report` để đánh giá hiệu quả phân lớp mô hình:
```[python3]
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred, target_names=target_names))
```
Output:
```
precision recall f1-score support
0 0.74 0.98 0.84 980
1 0.82 0.98 0.90 1135
2 0.83 0.58 0.68 1032
3 0.59 0.90 0.71 1010
4 0.65 0.82 0.73 982
5 0.86 0.21 0.34 892
6 0.71 0.90 0.80 958
7 0.74 0.84 0.79 1028
8 0.84 0.51 0.64 974
9 0.71 0.45 0.55 1009
accuracy 0.73 10000
macro avg 0.75 0.72 0.70 10000
weighted avg 0.75 0.73 0.70 10000
```
Như vậy, độ chính xác là 73%. Mô hình chúng ta còn nhiều thứ cần phải cải thiện. Ta có thể tinh chỉnh thêm theo các hướng:
- Thay đổi hàm `Sigmoid` thành hàm `Tanh`.
- Thêm số node, thêm số lớp.
- Train thêm nhiều epoch.
- Sử dụng các kỹ thuật huấn luyện, ví dụ như `Early Stopping`.
- Thay đổi bộ optimizer khác: `Adam`, `AdamW`.
## Bài tập
Một số bài tập thực hành thêm:
**Bài tập 1:** Tinh chỉnh kiến trúc để đạt được độ chính xác cao hơn trên bộ MNIST (Thay đổi hàm kích hoạt, tăng số node, thêm số lớp, ...)
**Bài tập 2:** Sử dụng kiến trúc ở phần thực hành để huấn luyện thử và đánh giá trên tập Fashion MNIST.
**Bài tập 3:** Thử xây dựng các kiến trúc Neural Network khác (thay đổi số lớp, số node) để đạt hiệu quả cao trên bộ Fashion MNIST.
## Kết luận
Trong bài viết này, nhóm sinh viên đã:
- Nhắc lại bài toán phân lớp.
- Nhắc lại lý thuyết về Logistic Regression và Softmax Regression, nhắc lại sự khác biệt của hai mô hình này.
- Giới thiệu Neural Network, chỉ ra điểm giống và khác của Neural Network so với Logistic Regression và Softmax Regression, bên cạnh đó cung cấp lý thuyết về mạng nơ-ron.
- Chuẩn bị bài tập thực hành: phân lớp chữ số viết tay sử dụng bộ dữ liệu MNIST.
- Có bài tập củng cố.
## Nhóm sinh viên thực hiện
1. Bùi Cao Doanh - 19521366
2. Bùi Trần Ngọc Dũng - 19521385