Cài đặt Neural Network với Python

Neural Network

Neural Network

Trong bài viết này, tôi sẽ hướng dẫn mọi người cài đặt mô hình mạng nơ-ron đơn giản với ngôn ngữ lập trình Python. Tôi giả định mọi người đã biết sơ qua các khái niệm cơ bản về giải tích cũng như machine learning (thế nào là classification, regularization, tại sao sử dụng gradient descent để tối thiểu hóa độ lỗi, …).

Việc cài đặt lại Neural Network từ đầu sẽ giúp cho chúng ta hiểu được mô hình này hoạt động như thế nào. Từ đó, ta có thể mở rộng ra các mô hình tiên tiến hơn như Deep learning trong các bài viết sắp tới. Ngoài ra, khi áp dụng vào các dự án thực tế, ta có thể sử dụng các thư viện như Theano hay PyBrain để tăng tốc độ xử lý cũng như tiết kiệm thời gian cài đặt.

Cài đặt các thư viện cần thiết

Scikit-learn cần các package sau:

  • Python (>= 2.6 or >= 3.3),
  • NumPy (>= 1.6.1),
  • SciPy (>= 0.9).

Ta tiến hành cài đặt các package

sudo apt-get install python-dev
sudo pip install -U numpy
sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-notebook python-pandas python-sympy python-nose
sudo pip install -U scikit-learn

Phát sinh tập dữ liệu mẫu

Để phát sinh tập dữ liệu mẫu, ta có thể sử dụng hàm make_moons trong scikit-learn. Đây là một hàm đơn giản để phát sinh dữ liệu cho các tác vụ mô phỏng clustering hay classification.

import matplotlib.pyplot as plt
import numpy as np
import sklearn
import sklearn.datasets
import sklearn.linear_model
import matplotlib

# Generate a dataset and plot it
np.random.seed(0)
X, y = sklearn.datasets.make_moons(200, noise=0.20)
plt.scatter(X[:,0], X[:,1], s=40, c=y, cmap=plt.cm.Spectral)
plt.show()
Generate dataset

Generate dataset

Quan sát tập dữ liệu trên, ta giả sử các chấm điểm màu đỏ tượng trưng cho các bệnh nhân nữ, các chấm điểm màu xanh tượng trưng cho các bệnh nhân nam, hai trục tọa độ x, y tượng trưng cho các chỉ số chẩn đoán bệnh.

Nhiệm vụ của chúng ta là xây dựng một mô hình phân lớp nhị phân để dự đoán chính xác một mẫu dữ liệu mới thuộc về bệnh nhân nam hay bệnh nhân nữ khi cho trước các chỉ số chẩn đoán x, y.

Nếu sử dụng linear classifier như Logistic regression, ta có thể thấy mô hình này không thể nào hoàn toàn phân lớp chính xác tập dữ liệu đã cho chỉ bằng một đường thẳng. Để nâng độ chính xác cho mô hình dạng này, ta cần phải thực hiện lựa chọn và rút trích các feature thích hợp. Ngược lại, ta có thể thấy ngay ưu điểm của Neural Network. Ta không cần sử dụng đến kĩ thuật feature engineering. Các hidden layer trong Neural Network sẽ đóng vai trò là các feature mà mô hình này “tự học” được trong quá trình huấn luyện.

Logistic Regression

Logistic regression

Logistic regression

Để minh họa những điều nêu trên, ta sẽ cài đặt mô hình Logistic regression để phân lớp tập dữ liệu đã phát sinh. Đầu vào sẽ là cặp giá trị (x, y), đầu ra là lớp dự đoán (0 hoặc 1). Ta có thể sử dụng class LogisticRegression của scikit-learn để cài đặt.

def plot_decision_boundary(pred_func):
	# Set min and max values and give it some padding
	x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
	y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
	h = 0.01
	# Generate a grid of points with distance h between them
	xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
	# Predict the function value for the whole gid
	Z = pred_func(np.c_[xx.ravel(), yy.ravel()])
	Z = Z.reshape(xx.shape)
	# Plot the contour and training examples
	plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral)
	plt.scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.Spectral)

	# Train the logistic rgeression classifier
	clf = sklearn.linear_model.LogisticRegressionCV()
	clf.fit(X, y)

	# Plot the decision boundary
	plot_decision_boundary(lambda x: clf.predict(x))
	plt.title("Logistic Regression")
	plot.show()
Logistic Regression classifier

Logistic Regression classifier

Biểu đồ trên cho ta thấy đường biên phân lớp (decision boundary) của Logistic regression. Mô hình này mặc dù đã cố gắng phân lớp tốt nhất tập dữ liệu đã cho với một đường thẳng nhưng vẫn không thể nào nắm bắt (generalization/capture) được toàn bộ đặc điểm (moon shape) của tập dữ liệu đã phát sinh.

Training Neural Network

Binary classification.jpg

backpropagation_algorithm

backpropagation_algorithm

ann_24_adjustments

ann_24_adjustments

Tiếp theo, ta sẽ xây dựng mô hình mạng nơ-ron với 3 layer: input layer, hidden layer, và output layer. Input layer có 2 node tương đương với 2 feature đầu vào x, y của tập dữ liệu. Output layer có 2 node tương đương với 2 class bệnh nhân nam (1) và bệnh nhân nữ (0).

Ta có thể điều chỉnh tuỳ ý số node ở hidden layer. Tuy nhiên, số node ở hidden layer càng nhiều, nghĩa là số chiều của feature càng lớn thì chi phí tính toán sẽ tăng lên, thời gian hội tụ của hàm gradient descent để tìm được thông số cho mô hình sẽ lâu hơn. Để chọn được số node ở hidden layer là một nghệ thuật và tuỳ theo bài toán cụ thể mà chúng ta đặt ra. Ta sẽ thực nghiệm với số node ở hidden layer lần lượt là 1, 2, 3, 4, 5, 20, 50 để theo dõi được hiệu suất của từng trường hợp.

Activation function là hàm nhận vector đầu vào, sau đó biến đổi để trả về vector đầu ra. Có nhiều hàm activation function như tanh, sigmoid function, hay ReLUs. Trong thực nghiệm này, ta sẽ dùng hàm tanh. Hàm này hữu dụng trong một vài trường hợp. Ví dụ, đạo hàm của tanh(x)1 - tanh(x)^2, như vậy ta chỉ cần tính tanh(x) là có thể sử dụng tiếp giá trị này cho đạo hàm bậc nhất.

Do ta cần giá trị đầu ra là xác suất trong khoảng [0, 1] nên ta sẽ sử dụng hàm softmax để làm activation function của output layer.

Neural Network dự đoán như thế nào

Mạng nơ-ron của chúng ta dự đoán dựa trên forward propagation là các phép nhân ma trận cùng với activation function để thu được kết quả đầu ra. Nếu input x là vector 2 chiều thì ta có thể tính kết quả dự đoán \hat{y}  bằng công thức sau

z_1 = xW_1 + b_1 \\  a_1 = tanh(z_1) \\  z_2 = a_1W_2 + b_2 \\  a_2 = \hat{y} = softmax(z_2)

Trong đó, z_i là input của layer thứ i, a_i là output của layer thứ i sau khi áp dụng activation function. W_1, b_1, W_2, b_2 là các thông số (parameters) cần tìm của mô hình mạng nơ-ron.

Huấn luyện để tìm các thông số cho mô hình

Huấn luyện để tìm các thông số cho mô hình tương đương với việc tìm các thông số W_1, b_1, W_2, b_2, sao cho độ lỗi của mô hình đạt được là thấp nhất. Ta gọi hàm độ lỗi của mô hình là loss function. Đối với softmax function, ta dùng cross-entropy loss (còn gọi là negative log likelihood).

Nếu ta có N dòng dữ liệu huấn luyện, và C nhóm phân lớp (trường hợp này là hai lớp nam, nữ), khi đó loss function giữa giá trị dự đoán \hat{y}y được tính như sau

L(y, \hat{y}) = -\frac{1}{N} \sum_{n \in N} \sum_{i \in C} y_{n, i} \log \hat{y}_{n, i}

Ý nghĩa công thức trên nghĩa là: lấy tổng trên toàn bộ tập huấn luyện và cộng dồn vào hàm loss nếu kết quả phân lớp sai. Độ dị biệt giữa hai giá trị y\hat{y} càng lớn thì độ lỗi càng cao. Mục tiêu của chúng ta là tối thiểu hóa hàm lỗi này. Ta có thể sử dụng phương pháp gradient descent để tối tiểu hóa hàm lỗi. Có hai loại gradient descent, một loại với fixed learning rate được gọi là batch gradient descent, loại còn lại có learning rate thay đổi theo quá trình huấn luyện được gọi là SGD (stochastic gradient descent) hay minibatch gradient descent.

Gradient descent cần các gradient là các vector có được bằng cách lấy đạo hàm của loss function theo từng thông số \frac{\partial L}{\partial W_1}, \frac{\partial L}{\partial b_1}, \frac{\partial L}{\partial W_2}, \frac{\partial L}{\partial b_2}. Để tính các gradient này, ta sử dụng thuật toán backpropagation (lan truyền ngược). Đây là cách hiệu quả để tính gradient khởi điểm từ output layer. Ta có thể tìm hiểu thêm về backpropagation ở một trong hai link sau: Calculus on Computational Graphs: BackpropagationIntuitive understanding of backpropagation.

Áp dụng backpropagation ta có các đại lượng

\delta_3 = y - \hat{y}\\  \delta_2 = (1 - tanh^2 z_1) \circ\delta_3W^T_2\\  \frac{\partial L}{\partial W_2} = a^T_1\delta_3\\  \frac{\partial L}{\partial b_2} = \delta_3\\  \frac{\partial L}{\partial W_1} = x^T\delta_2\\  \frac{\partial L}{\partial b_1} = \delta_2

Tiến hành cài đặt

Đầu tiên, ta sẽ khai báo một số biến cho gradient descent

num_examples = len(X) # training set size
nn_input_dim = 2 # input layer dimensionality
nn_output_dim = 2 # output layer dimensionality

# Gradient descent parameters (I picked these by hand)
epsilon = 0.01 # learning rate for gradient descent
reg_lambda = 0.01 # regularization strength

Tiếp theo, ta sẽ định nghĩa loss function để đánh giá mô hình mạng nơ-ron.

# Helper function to evaluate the total loss on the dataset
def calculate_loss(model):
	W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
	# Forward propagation to calculate our predictions
	z1 = X.dot(W1) + b1
	a1 = np.tanh(z1)
	z2 = a1.dot(W2) + b2
	exp_scores = np.exp(z2)
	probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
	# Calculating the loss
	corect_logprobs = -np.log(probs[range(num_examples), y])
	data_loss = np.sum(corect_logprobs)
	# Add regulatization term to loss (optional)
	data_loss += reg_lambda/2 * (np.sum(np.square(W1)) + np.sum(np.square(W2)))
	return 1./num_examples * data_loss

Đồng thời, ta cũng định nghĩa hàm predict để dự đoán kết quả đầu ra

# Helper function to predict an output (0 or 1)
def predict(model, x):
	W1, b1, W2, b2 = model['W1'], model['b1'], model['W2'], model['b2']
	# Forward propagation
	z1 = x.dot(W1) + b1
	a1 = np.tanh(z1)
	z2 = a1.dot(W2) + b2
	exp_scores = np.exp(z2)
	probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
	return np.argmax(probs, axis=1)

Cuối cùng, ta tiến hành cài đặt hàm huấn luyện mô hình mạng nơ-ron. Trong thực nghiệm này, ta sử dụng batch gradient descent với backpropagation đã nêu ở trên

# This function learns parameters for the neural network and returns the model.
# - nn_hdim: Number of nodes in the hidden layer
# - num_passes: Number of passes through the training data for gradient descent
# - print_loss: If True, print the loss every 1000 iterations
def build_model(nn_hdim, num_passes=20000, print_loss=False):

	# Initialize the parameters to random values. We need to learn these.
	np.random.seed(0)
	W1 = np.random.randn(nn_input_dim, nn_hdim) / np.sqrt(nn_input_dim)
	b1 = np.zeros((1, nn_hdim))
	W2 = np.random.randn(nn_hdim, nn_output_dim) / np.sqrt(nn_hdim)
	b2 = np.zeros((1, nn_output_dim))

	# This is what we return at the end
	model = {}

	# Gradient descent. For each batch...
	for i in xrange(0, num_passes):

		# Forward propagation
		z1 = X.dot(W1) + b1
		a1 = np.tanh(z1)
		z2 = a1.dot(W2) + b2
		exp_scores = np.exp(z2)
		probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

		# Backpropagation
		delta3 = probs
		delta3[range(num_examples), y] -= 1
		dW2 = (a1.T).dot(delta3)
		db2 = np.sum(delta3, axis=0, keepdims=True)
		delta2 = delta3.dot(W2.T) * (1 - np.power(a1, 2))
		dW1 = np.dot(X.T, delta2)
		db1 = np.sum(delta2, axis=0)

		# Add regularization terms (b1 and b2 don't have regularization terms)
		dW2 += reg_lambda * W2
		dW1 += reg_lambda * W1

		# Gradient descent parameter update
		W1 += -epsilon * dW1
		b1 += -epsilon * db1
		W2 += -epsilon * dW2
		b2 += -epsilon * db2

		# Assign new parameters to the model
		model = { 'W1': W1, 'b1': b1, 'W2': W2, 'b2': b2}

		# Optionally print the loss.
		# This is expensive because it uses the whole dataset, so we don't want to do it too often.
		if print_loss and i % 1000 == 0:
			print "Loss after iteration %i: %f" % (i, calculate_loss(model))

	return model

Chạy thực nghiệm

Ta xem điều gì sẽ xảy ra nếu ta huấn luyện mạng nơ-ron với 3 node ở hidden layer.

# Build a model with a 3-dimensional hidden layer
model = build_model(3, print_loss=True)

# Plot the decision boundary
plot_decision_boundary(lambda x: predict(model, x))
plt.title("Decision Boundary for hidden layer size 3")
plt.show()
A network with a hidden layer of size 3

A network with a hidden layer of size 3

Ta có thể thấy mô hình mạng nơ-ron tìm được đường biên phân lớp khá hoàn chỉnh. Để quan sát những khác biệt khi thay đổi số lượng node ở hidden layer, ta tiến hành thực nghiệm với số lượng node lần lượt là 1, 2, 3, 4, 5, 20, 50.

plt.figure(figsize=(16, 32))
hidden_layer_dimensions = [1, 2, 3, 4, 5, 20, 50]
for i, nn_hdim in enumerate(hidden_layer_dimensions):
	plt.subplot(5, 2, i+1)
	plt.title('Hidden Layer size %d' % nn_hdim)
	model = build_model(nn_hdim)
	plot_decision_boundary(lambda x: predict(model, x))
	plt.show()
Varying the hidden layer size

Varying the hidden layer size

Ta có thể thấy rằng hidden layer với số chiều thấp hơn sẽ nắm bắt xu hướng chung của dữ liệu tốt hơn. Số chiều càng cao thì khả năng bị overfitting càng lớn. Nếu ta sử dụng phương pháp đánh giá mô hình bằng cách chia tập dữ liệu ban đầu thành tập huấn luyện và tập test, ta sẽ thấy mô hình với số node ở hidden layer ít hơn sẽ có điểm đánh giá cao hơn so với hidden layer có số node nhiều hơn. Trong thực tế, ta có thể tăng giá trị regularization để giảm overfitting. Tuy nhiên, việc lựa chọn số node đúng đắn sẽ tiết kiệm chi phí tính toán hơn.

Nguồn tham khảo:

Advertisements

17 thoughts on “Cài đặt Neural Network với Python

  1. Có hai loại gradient descent, một loại với fixed learning rate được gọi là batch gradient descent, loại còn lại có learning rate thay đổi theo quá trình huấn luyện được gọi là SGD (stochastic gradient descent) hay minibatch gradient descent.

    Em thấy đoạn này hình như có vấn đề vì em tưởng learning rate nó là do mình gán, chứ liên quan gì đến gradient descent ạ

    Số lượt thích

    • Gradient descent thuật toán có tham số alpha do mình tự gán để điều chỉnh trực tiếp tốc độ hội tụ của hàm tối ưu độ lỗi. Qua thực nghiệm, ta sẽ biết được tham số alpha nào thích hợp nhất đối với tập dữ liệu huấn luyện hiện tại của mình.
      Em có thể tham khảo thêm bài viết này: http://sebastianraschka.com/faq/docs/closed-form-vs-gd.html

      Số lượt thích

    • Cảm ơn anh Hồng về blog rất hữu ích.
      Chào bạn anhnamxtanh 🙂 mình thấy bạn đã hiểu sai về khái niệm BGD và SGD rồi.
      – Đầu tiên, khái niệm Batch Gradient Descent tức là mỗi bước bạn sẽ tính vector gradient dựa vào toàn bộ tập dữ liệu bạn có. Còn stochastic gradient descent là một kĩ thuật mà mỗi bước đi bạn không phải dựa vào toàn bộ tập mà bạn chỉ dựa vào một phần thôi. Người ta gọi là stochastic vì nó có tính nhiễu loạn trong đó (nhiễu loạn ở đây chỉ giá trị hàm chi phí không phải luôn giảm mà có thể tăng, có thể giảm, tuy nhiên nhìn chung là nó sẽ giảm sau khi lặp qua nhiều bước). Tất nhiên ở đây vector gradient chỉ giảm khi trong khoảng xấp xỉ của khai triển Taylor lúc xấp xỉ đạo hàm.
      -> Ý mình muốn nói là việc learning rate thay đổi không phải ý để nói về khái niệm BGD và SGD.

      – Còn việc chọn learning rate thì có một số lưu ý như sau:
      + Learning rate sẽ làm ảnh hưởng đến giá trị chi phí như thế nào: Quá lớn thì sao? (giá trị hàm chi phí sẽ lên xuống không theo qui luật) Quá nhỏ thì sao? (có thể vài năm hoặc vài chục năm mới đạt được kết quả hội tụ).
      + Tại sao learning rate có thể thay đổi: Một rule of thumb trong việc chọn máy học hiệu quả như const/(số lần lặp)

      Số lượt thích

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s