Neural network: Backpropagation với softmax và cross-entropy

Entry này chỉ để ghi lại vài công thức cập nhật trọng số cho mạng neural network, dùng trong thuật toán Backpropagation. Những công thức này dùng trong một bài tập lập trình gần đây. Chi tiết về neural network, backpropagation và các khái niệm khác trong bài này có thể sẽ trở lại sau, nếu có dịp.

1. Kiến trúc mạng

Mạng neuron trong bài này có:

  • 3 lớp: input, hidden, output
  • Input layer có 256 logistic units
  • Hidden layer có m logistic units
  • Output layer dùng hàm soft-max với 10 neurons
  • Hàm cost function là cross-entropy.
  • Dùng L2-regularization

Mục tiêu ban đầu của mạng này là nhận dạng chữ số viết tay trong MNIST, do đó ta tạm thời cố định số neurons trong lớp input và output lần lượt là 256 và 10. Đương nhiên mọi khai triển dưới đây đều có thể được tổng quát hóa với số neurons bất kì.

Mạng neural network dùng trong bài.

Nhắc lại về soft-max và cross-entropy: với neuron thứ i trong output layer, ta có:

\displaystyle{ y_i = \frac{e^{z_i}}{\sum_{j\in \text{group}}e^{z_j}}}

Trong đó z_i là phần logit (tổng input) của mỗi neuron. Ở đây thay vì dùng hàm logistics như bình thường, ta dùng hàm soft-max để biểu diễn posterior distribution của toàn bộ output layer. Ta sẽ trở lại với cách làm này khi có dịp.

Khi dùng soft-max cho output layer thì hàm cost function thường được dùng là hàm cross-entropy:

\displaystyle C = -\sum_j t_j \log y_j

Trong đó y_j là output của neuron thứ j theo công thức trên, và t_j là giá trị target của neuron j khi huấn luyện. Ta dùng hàm này, thay vì hàm squared error như bình thường vì với cross-entropy và soft-max thì đạo hàm của C theo y_i có dạng rất đẹp:

{\displaystyle \frac{\partial C}{\partial y_i} = -\frac{t_i}{y_i}}

Đạo hàm này có vài tính chất rất đẹp so với hàm logistic và squared error, đặc biệt là trong backward phase của backpropagation. Ta sẽ trở lại khi có dịp.

2. Maths

Trong phần này ta sẽ khai triển công thức cập nhật cho các trọng số trong mạng neuron trên. Muốn vậy ta sẽ tính đạo hàm của hàm lỗi theo các trọng số.

Xét 1 training case, hàm lỗi cho mạng neuron này có dạng sau:

E=C + E_{w}=-\sum_{j}t_{j}\log y_{j}+\frac{\lambda}{2}\sum_{i,j}w_{ij}^{2}      (1)

Trong đó w_{ij} là trọng số từ neuron i đến neuron j. Ta không quan tâm đến trọng số này thuộc lớp nào vì w_{ij} xuất hiện trong phần L2-regularization. Ta có:

{\displaystyle \frac{\partial E}{\partial w_{ij}}={\displaystyle \frac{\partial C}{\partial w_{ij}}+\frac{\lambda}{2}w_{ij}}}      (2)

Giờ tới phần lằng nhằng là tính đạo hàm của C theo các trọng số. Trước tiên, giống như trong hình, ta cần định nghĩa một số kí hiệu:

  • x_i: giá trị input của neuron thứ i trong input layer. Thực tế đây là component thứ i trong training vector.
  • W_{ji}^{(hx)}: trọng số của liên kết từ neuron thứ i của input layer đến neuron thứ j của hidden layer.
  • u_i: logit của hidden unit thứ i.
  • h_i: giá trị của hidden unit thứ i sau khi áp dụng hàm logistic.
  • W_{ji}^{(yh)}: trọng số của liên kết từ hidden unit thứ i đến output unit thứ j.
  • z_i: logit của output unit thứ i.
  • y_i: giá trị của output unit thứ i sau khi áp dụng hàm soft-max.

Và ta có công thức tính của các đại lượng trên như sau:

\begin{alignedat}{1}u_{j} & =\sum_{i\in\text{input}}W_{ji}^{\left(hx\right)}x_{i}\\h_{j} & =\frac{1}{1+e^{-u_{j}}}\\z_{j} & =\sum_{i\in\text{hidden}}W_{ji}^{\left(yh\right)}h_{i}\\y_{i} & =\frac{e^{z_{i}}}{{\displaystyle \sum_{j\in\text{output}}e^{z_{j}}}}\\C & =-\sum_{j\in\text{output}}t_{j}\log y_{j}\end{alignedat}        (3)

Ta lần lượt tính đạo hàm. Truớc tiên là đạo hàm của y_j theo z_i:

\frac{\partial y_{j}}{\partial z_{i}}=\begin{cases}  y_{i}\left(1-y_{i}\right) & \text{if }i=j\\  -y_{i}y_{j} & \text{if }i\neq j  \end{cases}

Trong công thức y_j, ta thấy y_j phụ thuộc vào tất cả các z_i trong cùng nhóm soft-max, do đó cần xét 2 trường hợp khi i=jy\neq j. Mặc dù vậy kết quả đạo hàm cũng khá gọn gàng.

Tiếp theo là đạo hàm của C theo z_i:

\begin{aligned}\frac{\partial C}{\partial z_{i}} & =\sum_{j\in\text{output}}\frac{\partial C}{\partial y_{j}}\frac{\partial y_{j}}{\partial z_{i}}\\  & =\sum_{j\neq i}\frac{\partial C}{\partial y_{j}}\frac{\partial y_{j}}{\partial z_{i}}+\frac{\partial C}{\partial y_{i}}\frac{\partial y_{i}}{\partial z_{i}}\\  & =\sum_{j\neq i}\frac{-t_{j}}{y_{j}}\left(-y_{i}y_{j}\right)+\frac{-t_{i}}{y_{i}}y_{i}\left(1-y_{i}\right)\\  & =y_{i}\sum_{j\neq i}t_{j}+y_{i}t_{i}-t_{i}\\  & =y_{i}\sum_{j}t_{j}-t_{i}\\  & =y_{i}-t_{i}  \end{aligned}

t_i là giá trị của các neuron trong nhóm soft-max, mà với 1 training case thì chỉ có một t_i = 1, và  với mọi j\neq i thì t_j=0 nên \sum_{j}t_{j}=1, dẫn tới kết quả rất đẹp như trên.

Còn lại dễ dàng ta có:

{\displaystyle \frac{\partial z_{i}}{\partial W_{ij}^{(yh)}}=h_{j}}

{\displaystyle \frac{\partial z_{i}}{\partial h_{j}}=W_{ij}^{(yh)}}

{\displaystyle \frac{\partial h_{j}}{\partial u_{j}}=\frac{e^{-u_{j}}}{\left(1-e^{-u_{j}}\right)^{2}}=h_{j}\left(1-h_{j}\right)}

{\displaystyle \frac{\partial u_{j}}{\partial W_{jk}^{(hx)}}=x_{k}}

Áp dụng chain rule, ta có công thức đạo hàm của hàm cost function C theo các trọng số W_{ij}^{(yh)} và W_{jk}^{(hx)} như sau:

{\displaystyle \frac{\partial C}{\partial W_{ij}^{(yh)}}=\frac{\partial C}{\partial z_{i}}\frac{\partial z_{i}}{\partial W_{ij}^{(yh)}}=\left(y_{i}-t_{i}\right)h_{j}}      (4)

{\displaystyle \frac{\partial C}{\partial W_{jk}^{(hx)}}=\frac{\partial C}{\partial z_{i}}\frac{\partial z_{i}}{\partial h_{j}}\frac{\partial h_{j}}{\partial u_{j}}\frac{\partial u_{j}}{\partial W_{jk}^{(hx)}}=\left(y_{i}-t_{i}\right)W_{ij}^{(yh)}h_{j}\left(1-h_{j}\right)x_{k}}       (5)

Thay kết quả này vào công thức (2), ta sẽ được đạo hàm của hàm lỗi đối với từng trọng số.

3. Cài đặt

Khi cài đặt thuật toán backpropagation, ta sẽ tận dụng các thư viện tính toán trên ma trận để tăng tốc. Muốn làm vậy ta cần phải vectorize các công thức đạo hàm (4)(5). Gọi:

  • n: số lượng sample trong tập huấn luyện
  • W^{(hx)}\in\mathbb{R}^{m\times 256}: ma trận trọng số giữa hidden layer và input layer
  • W^{(yh)}\in\mathbb{R}^{10\times m}: ma trận trọng số giữa output layer và hidden layer
  • X\in\mathbb{R}^{256\times n}: ma trận chứa n feature vector 256 chiều trong tập huấn luyện
  • U\in\mathbb{R}^{m\times n}: logit của m hidden unit cho n samples trong tập huấn luyện
  • H\in\mathbb{R}^{m\times n}: giá trị của m hidden unit cho n samples trong tập huấn luyện sau khi áp dụng hàm logistics
  • Z\in\mathbb{R}^{10\times n}: logit của 10 hidden unit cho n samples trong tập huấn luyện
  • Y\in\mathbb{R}^{m\times n}: giá trị của 10 hidden unit cho n samples trong tập huấn luyện sau khi áp dụng hàm soft-max
  • T\in\mathbb{R}^{10\times n}: target của n sample trong tập huấn luyện.

Trong ma trận T thì trong mỗi cột chỉ có 1 phần tử có giá trị bằng 1 tại vị trí tương ứng với class của sample đó. Chẳng hạn nếu 1 sample thuộc về lớp thứ 3 thì target của nó là \left[0,0, 1, 0, 0, 0, 0, 0, 0, 0\right]^\top.

Như vậy ta có các công thức đơn giản sau:

U = W^{(hx)}X

H = \sigma\left(U\right)

Z = W^{(yh)}H

Y = \text{soft-max}\left(Z\right)

{\displaystyle C = -\frac{1}{n}\sum_{i=1}^n\sum_{j\in\text{output}}T_{ji}\log Y_{ji}}

Trong công thức cuối cùng, giá trị của hàm lỗi là trung bình cộng giá trị lỗi của n sample trong tập huấn luyện. Các công thức này chỉ là phiên bản vectorized của các công thức trong (3). Các công thức đạo hàm tương ứng có thể viết ở dạng vectorized như sau:

\begin{aligned}\frac{\partial E}{\partial W^{(yh)}} & =\frac{\partial C}{\partial W^{(yh)}}+\frac{\partial E_{w}}{\partial W^{(yh)}}\\ & =\frac{1}{n}\left(Y-T\right)H^{\top}+\frac{\lambda}{2}W^{(yh)} \end{aligned}

và cho ma trận trọng số giữa hidden layer và input layer:

\begin{aligned}\frac{\partial E}{\partial W^{(hx)}} & =\frac{\partial C}{\partial W^{(hx)}}+\frac{\partial E_{w}}{\partial W^{(hx)}}\\ & =\frac{1}{n}\left(W^{(yh)^{\top}}\left(Y-T\right).*H.*\left(1-H\right)\right)X^{\top}+\frac{\lambda}{2}W^{(hx)}\end{aligned}

Ta có thể thấy sự tương ứng của các công thức này với (4)(5).

Một cài đặt cho matlab có thể như thế này:


function ret = d_loss_by_d_model(model, data, wd_coefficient)
 % model.input_to_hid is a matrix of size <number of hidden units> by <number of inputs i.e. 256>
 % model.hid_to_class is a matrix of size <number of classes i.e. 10> by <number of hidden units>
 % data.inputs is a matrix of size <number of inputs i.e. 256> by <number of data cases>
 % data.targets is a matrix of size <number of classes i.e. 10> by <number of data cases>

% The returned object is supposed to be exactly like parameter <model>, i.e. it has fields ret.input_to_hid and ret.hid_to_class. However, the contents of those matrices are gradients (d loss by d model parameter), instead of model parameters.

 % forward pass
 hid_input = model.input_to_hid * data.inputs; % input to the hidden units, i.e. before the logistic. size: <number of hidden units> by <number of data cases>
 hid_output = logistic(hid_input); % output of the hidden units, i.e. after the logistic. size: <number of hidden units> by <number of data cases>
 class_input = model.hid_to_class * hid_output; % input to the components of the softmax. size: <number of classes, i.e. 10> by <number of data cases>

 class_normalizer = log_sum_exp_over_rows(class_input); % log(sum(exp of class_input)) is what we subtract to get properly normalized log class probabilities. size: <1> by <number of data cases>
 log_class_prob = class_input - repmat(class_normalizer, [size(class_input, 1), 1]); % log of probability of each class. size: <number of classes, i.e. 10> by <number of data cases>
 class_prob = exp(log_class_prob); % probability of each class. Each column (i.e. each case) sums to 1. size: <number of classes, i.e. 10> by <number of data cases>

 num_cases = size(data.inputs, 2);

 % hidden to output
 output_delta = (class_prob - data.targets); % <number of classes i.e. 10> by <number of data cases>
 ret.hid_to_class = output_delta * hid_output'; % <number of classes i.e. 10> by <number of hidden units>
 ret.hid_to_class = ret.hid_to_class ./ num_cases + ...
 wd_coefficient * model.hid_to_class;

 % <number of hidden units> by <number of data cases>
 error_derivated = model.hid_to_class'*output_delta ...
 .* hid_output .* (1 - hid_output);
 ret.input_to_hid = error_derivated * data.inputs'; % <number of hidden units> by <number of inputs i.e. 256>
 ret.input_to_hid = ret.input_to_hid ./ num_cases + ...
 wd_coefficient * model.input_to_hid;

end

Tính được đạo hàm của hàm lỗi chỉ là bước đầu tiên trong quá trình huấn luyện neural network. Để cài đặt đầy đủ backpropagation, ta còn rất nhiều việc phải làm:

  • Quyết định chiến lược huấn luyện: full-batch, mini-batch hay online. Nếu là mini-batch thì kích thước mỗi batch là bao nhiêu v.v..
  • Quyết định số lượng hidden unit
  • Chọn các tham số optimization khác: số vòng lặp tối đa, learning rate, early stopping hay momentum, giá trị của \lambda v.v…

Tất cả các tham số này đều có vai trò quan trọng quyết định performance của mạng neuron. Do đó việc tinh chỉnh giá trị cho các meta-parameter này là cần thiết và thường khá mất thời gian. Ta sẽ trở lại với chúng khi có dịp.

Advertisements

13 comments

    1. Hi Đức Anh,

      Đúng vậy, lớp cuối dùng softmax vì output của nó có thể xem là posterior distribution của 10 class.
      Trong bài này sigmoid được dùng chủ yếu để minh họa công thức thôi. Có thể dùng các nonlinearities khác như tanh, ReLU…

  1. rất cảm ơn topic rất chi tiết của bạn. Mình cũng đang nghiên cứu cái này nhưng mà cho convolution neural network.

  2. Bạn cho mình hỏi là, nếu như có nhiều hidden layer thì phần tính E để cập nhật weight mình sẽ phải làm thế nào vì ở đây mình thấy phải chia ra hai đạo hàm cho từng connection giữa input với hidden, và hidden với output, vậy còn hidden với hidden thì sao bạn?

    1. Y hệt vậy thôi bạn. Nếu dưới layer hidden 1 còn có layer hidden 2 thì cứ khai triển công thức như bình thường, xem hidden 2 là “input”.
      Nói chung ý tưởng của toàn bộ thuật toán backpropagation là sử dụng chain rule để tính đạo hàm cho từng tham số, không có gì huyền bí cả.

  3. Cám ơn Pham Hoai Vu. Mình còn một thắc mắc cũng tại điểm đó là về công thức (5), phần W[i,j] đó là W[i,j] của Output layer hay là của Hidden Layer tiếp theo nếu như có nhiều Hidden Layer vậy bạn?

    1. Sẽ là layer tiếp theo nếu có nhiều hidden layer.
      Tuy nhiên khi đó công thức (5) sẽ có thêm nhiều thành phần hơn nữa.

  4. Anh có nói là “Vì t_i là giá trị của các neuron trong nhóm soft-max, mà với 1 training case thì chỉ có một t_i = 1, và với mọi j\neq i thì t_j=0 nên \sum_{j}t_{j}=1, dẫn tới kết quả rất đẹp như trên.”
    Chỗ này có vẻ không chính xác phải không ạ?Nếu dùng one-hot encode thì đầu ra t_i=1 nếu t_i có xác suất lớn nhất, còn các đầu ra khác bằng 0. Còn nếu chỉ dùng softmax thì đầu ra là các giá trị xác suất <1, nhưng tổng vẫn là 1.

    1. Ah t_i là target của unit thứ i trong nhóm softmax, còn y_i mới là output của unit i. Thông thường khi dùng softmax với cross entropy thì t_i dùng one-hot encoding, thành ra câu đó vẫn đúng.
      Sorry em, anh viết tiếng Việt thường không đọc lại và sửa, chỉ có publish thôi, nên có đôi chỗ hơi tối nghĩa.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s