Trong bài viết này, mình xin phép giới thiệu về Dropout (Bỏ học) trong mạng Neural, sau đó là mình sẽ có 1 số đoạn code để xem Dropout ảnh hưởng thế nào đến hiệu suất của mạng Neural.
1. Lý thuyết
1.1. Dropout trong mạng Neural là gì
Theo Wikipedia - Thuật ngữ 'Dropout' đề cập đến việc bỏ qua các đơn vị (units) ẩn và hiện trong 1 mạng Neural.
Hiểu 1 cách đơn giản thì Dropout là việc bỏ qua các đơn vị (tức là 1 nút mạng) trong quá trình đào tạo 1 cách ngẫu nhiên. Bằng việc bỏ qua này thì đơn vị đó sẽ không được xem xét trong quá trình forward và backward. Theo đó, p được gọi là xác suất giữ lại 1 nút mạng trong mỗi giai đoạn huấn luyện, vì thế xác suất nó bị loại bỏ là (1 - p).
1.2. Tại sao lại cần Dropout
Câu hỏi là: Tại sao phải tắt 1 số nút mạng theo đúng nghĩa đen trong quá trình huấn luyện ? Câu trả lời là: Tránh học tủ (Over-fitting)
Nếu 1 lớp fully connected có quá nhiều tham số và chiếm hầu hết tham số, các nút mạng trong lớp đó quá phụ thuộc lẫn nhau trong quá trình huấn luyện thì sẽ hạn chế sức mạnh của mỗi nút, dẫn đến việc kết hợp quá mức.
1.3. Các kỹ thuật khác
Nếu bạn muốn biết Dropout là gì, thì chỉ 2 phần lý thuyết phía trên là đủ. Ở phần này mình cũng giới thiệu 1 số kỹ thuật có cùng tác dụng với Dropout.
Trong Machine Learning, việc chính quy hóa (regularization) sẽ làm giảm over-fitting bằng cách thêm 1 khoảng giá trị 'phạt' vào hàm loss. Bằng cách thêm 1 giá trị như vậy, mô hình của bạn sẽ không học quá nhiều sự phụ thuộc giữa các trọng số. Chắc hẳn nhiều người đã biết đến Logistic Regression thì đều biết đến L1 (Laplacian) và L2 (Gaussian) là 2 kỹ thuật 'phạt'.
-
Quá trình training: Đối với mỗi lớp ẩn, mỗi example, mỗi vòng lặp, ta sẽ bỏ học 1 cách ngẫu nhiên với xác suất (1 - p) cho mỗi nút mạng.
-
Quá trình test: Sử dụng tất cả các kích hoạt, nhưng sẽ giảm đi 1 hệ số p (để tính cho các kích hoạt bị bỏ học).
1.4. Một số nhận xét
- Dropout sẽ được học thêm các tính năng mạnh mẽ hữu ích
- Nó gần như tăng gấp đôi số epochs cần thiết để hội tụ. Tuy nhiên, thời gian cho mỗi epoch là ít hơn.
- Ta có H đơn vị ẩn, với xác suất bỏ học cho mỗi đơn vị là (1 - p) thì ta có thể có 2^H mô hình có thể có. Nhưng trong giai đoạn test, tất cả các nút mạng phải được xét đến, và mỗi activation sẽ giảm đi 1 hệ số p.
2. Thực hành
Nói thì hơi khó hiểu, nên mình sẽ code 2 phần để xem Dropout là như thế nào.
Đặt vấn đề: Bạn đi xem 1 trận đấu bóng đá và bạn thử dự đoán xem thủ môn sút vào vị trí nào thì cầu thủ nhà đánh đầu được quả bóng.
Mình import các thư viện cần thiết
# import packages
import numpy as np
import matplotlib.pyplot as plt
from reg_utils import sigmoid, relu, plot_decision_boundary, initialize_parameters, load_2D_dataset, predict_dec
from reg_utils import compute_cost, predict, forward_propagation, backward_propagation, update_parameters
import sklearn
import sklearn.datasets
import scipy.io
from testCases import * %matplotlib inline
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
Visualize dữ liệu 1 chút
train_X, train_Y, test_X, test_Y = load_2D_dataset()
Ta được kết quả
Dấu chấm đỏ là cầu thủ nhà đã từng đánh đầu, chấm xanh là cầu thủ bạn đánh đầu. Việc chúng ta là dự đoán xem thủ môn nên sút bóng vào khu vực nào để cầu thủ nhà đánh đầu được. Nhìn có vẻ như chỉ cần kẻ 1 đường thẳng để phân chia 2 khu vực là được.
2.1. Mô hình không có chính quy hóa
def model(X, Y, learning_rate = 0.3, num_iterations = 30000, print_cost = True): """ Triển khai mạng với 3 layer: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID. Tham số: X -- Dữ liệu đầu vào, kích thước (input size, number of examples) Y -- 1 vector (1 là chấm xanh / 0 là chấm đỏ), kích thước (output size, number of examples) learning_rate -- Tỷ lệ học num_iterations -- Số epochs print_cost -- Nếu là True, in ra coss cho mỗi 10000 vòng lặp Returns: parameters -- Tham số học được, được dùng để dự đoán """ grads = {} costs = [] # to keep track of the cost m = X.shape[1] # number of examples layers_dims = [X.shape[0], 20, 3, 1] # Initialize parameters dictionary. parameters = initialize_parameters(layers_dims) # Loop (gradient descent) for i in range(0, num_iterations): # Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID. a3, cache = forward_propagation(X, parameters) # Cost function cost = compute_cost(a3, Y) grads = backward_propagation(X, Y, cache) # Update parameters. parameters = update_parameters(parameters, grads, learning_rate) # Print the loss every 10000 iterations if print_cost and i % 10000 == 0: print("Cost after iteration {}: {}".format(i, cost)) if print_cost and i % 1000 == 0: costs.append(cost) # plot the cost plt.plot(costs) plt.ylabel('cost') plt.xlabel('iterations (x1,000)') plt.title("Learning rate =" + str(learning_rate)) plt.show() return parameters
Hàm dự đoán
print("On the training set:")
predictions_train = predict(train_X, train_Y, parameters)
print("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)
Xem kết quả
Cost after iteration 0: 0.6557412523481002
Cost after iteration 10000: 0.16329987525724216
Cost after iteration 20000: 0.13851642423255986
...
On the training set:
Accuracy: 0.947867298578
On the test set:
Accuracy: 0.915
Có thể thấy độ chính xác ở tập training là 94% và tập test là 91% (khá cao). Ta sẽ visualize 1 chút
Khi không có chính quy hóa, ta thấy đường phân chia vẽ rất chi tiết, tức là nó đang over-fitting.
2.2. Mô hình chính quy hóa với Dropout
2.2.1. Quá trình Forward Propagation
def forward_propagation_with_dropout(X, parameters, keep_prob=0.5): """ Triển khai 3 layer: LINEAR -> RELU + DROPOUT -> LINEAR -> RELU + DROPOUT -> LINEAR -> SIGMOID. Arguments: X -- Dữ liệu đầu vào, kích thước (2, number of examples) parameters -- Các đối số chúng ta có "W1", "b1", "W2", "b2", "W3", "b3": W1 -- weight matrix of shape (20, 2) b1 -- bias vector of shape (20, 1) W2 -- weight matrix of shape (3, 20) b2 -- bias vector of shape (3, 1) W3 -- weight matrix of shape (1, 3) b3 -- bias vector of shape (1, 1) keep_prob - xác suất giữ lại 1 unit Returns: A3 -- giá trị đầu ra mô hình, kích thước (1,1) cache -- lưu các đối số để tính cho phần Backward Propagation """ np.random.seed(1) # retrieve parameters W1 = parameters["W1"] b1 = parameters["b1"] W2 = parameters["W2"] b2 = parameters["b2"] W3 = parameters["W3"] b3 = parameters["b3"] # LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID Z1 = np.dot(W1, X) + b1 A1 = relu(Z1) ### START CODE HERE ### (approx. 4 lines) # Steps 1-4 below correspond to the Steps 1-4 described above. D1 = np.random.rand(A1.shape[0], A1.shape[1]) # Step 1: khởi tạo ngẫu nhiên 1 ma trận kích thước bằng kích thước A1, giá trị (0, 1) D1 = D1 < keep_prob # Step 2: chuyển các giá trị về 0 hoặc 1, trả về 1 nếu giá trị đó nhỏ hơn keep_prob A1 = A1 * D1 # Step 3: giữ nguyên các phần tự trong A1 ứng với phần tử 1 của D1, và đổi thành 0 nếu vị trị trong D1 tương tứng là 0 A1 = A1 / keep_prob # Step 4: giảm đi 1 hệ số keep_prob, để tính cho các phần tử đã bỏ học. ### END CODE HERE ### Z2 = np.dot(W2, A1) + b2 A2 = relu(Z2) ### START CODE HERE ### (approx. 4 lines) D2 = np.random.rand(A2.shape[0], A2.shape[1]) D2 = D2 < keep_prob A2 = A2 * D2 A2 = A2 / keep_prob ### END CODE HERE ### Z3 = np.dot(W3, A2) + b3 A3 = sigmoid(Z3) cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) return A3, cache
2.2.2. Quá trình Backward Propagation
def backward_propagation_with_dropout(X, Y, cache, keep_prob): Các đối số: X -- Dữ liệu đầu vào, kích thước (2, number of examples) Y -- kích thước (output size, number of examples) cache -- lưu đầu ra của forward_propagation_with_dropout() keep_prob - như forward Returns: gradients -- Đạo hàm của tất cả các weight, activation """ m = X.shape[1] (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) = cache dZ3 = A3 - Y dW3 = 1. / m * np.dot(dZ3, A2.T) db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True) dA2 = np.dot(W3.T, dZ3) ### START CODE HERE ### (≈ 2 lines of code) dA2 = dA2 * D2 # Step 1: Áp dụng D2 để tắt các unit tương ứng với forward dA2 = dA2 / keep_prob # Step 2: Giảm giá trị 1 hệ số keep_prob ### END CODE HERE ### dZ2 = np.multiply(dA2, np.int64(A2 > 0)) dW2 = 1. / m * np.dot(dZ2, A1.T) db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True) dA1 = np.dot(W2.T, dZ2) ### START CODE HERE ### (≈ 2 lines of code) dA1 = dA1 * D1 dA1 = dA1 / keep_prob ### END CODE HERE ### dZ1 = np.multiply(dA1, np.int64(A1 > 0)) dW1 = 1. / m * np.dot(dZ1, X.T) db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True) gradients = {"dZ3": dZ3, "dW3": dW3, "db3": db3,"dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2, "dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1} return gradients
Sau khi có Forward và Backward, ta thay 2 hàm này vào hàm model của phần trước:
parameters = model(train_X, train_Y, keep_prob=0.86, learning_rate=0.3) print("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)
Kết quả:
Cost after iteration 10000: 0.06101698657490559
Cost after iteration 20000: 0.060582435798513114
...
On the train set:
Accuracy: 0.928909952607
On the test set:
Accuracy: 0.95
Ta thấy, độ chính xác trong tập test đã lên đến 95%, mặc dù tập training bị giảm. Thực hiện visualize:
plt.title("Model with dropout")
axes = plt.gca()
axes.set_xlim([-0.75, 0.40])
axes.set_ylim([-0.75, 0.65])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
Ta được
Ta thấy đường phân chia không quá chi tiết, nên đã tránh được over-fitting.
2.3. Chú ý
- Không dùng Dropout cho quá trình test
- Áp dụng Dropout cho cả quá trình Forward và Backward
- Giá trị kích hoạt phải giảm đi 1 hệ số keep_prob, tính cả cho những nút bỏ học.