Tạo cấu trúc RNN

Giới thiệu

Chúng tôi đã học cách phân loại hình ảnh bằng mạng lưới thần kinh và đó là một trong những công việc mang tính biểu tượng trong học sâu. Tuy nhiên, một lĩnh vực khác mà mạng thần kinh vượt trội và có nhiều nghiên cứu đang diễn ra là Mạng thần kinh tái phát (RNN). Ở đây, chúng ta sẽ biết RNN là gì và làm thế nào nó có thể được sử dụng trong các tình huống mà chúng ta cần xử lý dữ liệu chuỗi thời gian.

Mạng thần kinh tái phát là gì?

Mạng thần kinh tái phát (RNN) có thể được định nghĩa là loại NN đặc biệt có khả năng suy luận theo thời gian. RNN chủ yếu được sử dụng trong các tình huống, trong đó chúng ta cần xử lý các giá trị thay đổi theo thời gian, tức là dữ liệu chuỗi thời gian. Để hiểu nó theo cách tốt hơn, chúng ta hãy so sánh nhỏ giữa mạng nơ-ron thông thường và mạng nơ-ron tái phát –

  • Như chúng ta biết rằng, trong mạng nơ-ron thông thường, chúng ta chỉ có thể cung cấp một đầu vào. Điều này giới hạn nó chỉ dẫn đến một dự đoán. Để cho bạn một ví dụ, chúng tôi có thể thực hiện công việc dịch văn bản bằng cách sử dụng mạng nơ-ron thông thường.
  • Mặt khác, trong các mạng thần kinh tái diễn, chúng ta có thể cung cấp một chuỗi mẫu dẫn đến một dự đoán duy nhất. Nói cách khác, sử dụng RNN, chúng ta có thể dự đoán chuỗi đầu ra dựa trên chuỗi đầu vào. Ví dụ, đã có khá nhiều thử nghiệm thành công với RNN trong các tác vụ dịch thuật.

Công dụng của mạng thần kinh tái phát

RNN có thể được sử dụng theo nhiều cách. Một số trong số chúng như sau –

Dự đoán một đầu ra duy nhất

Trước khi đi sâu vào các bước, cách RNN có thể dự đoán một đầu ra dựa trên một chuỗi, hãy xem RNN cơ bản trông như thế nào

mạng thần kinh tái phát

Như chúng ta có thể làm trong sơ đồ trên, RNN chứa kết nối vòng lặp tới đầu vào và bất cứ khi nào chúng ta cung cấp một chuỗi các giá trị, nó sẽ xử lý từng phần tử trong chuỗi theo các bước thời gian.

Hơn nữa, nhờ kết nối vòng lặp, RNN có thể kết hợp đầu ra được tạo với đầu vào cho phần tử tiếp theo trong chuỗi. Bằng cách này, RNN sẽ xây dựng bộ nhớ trên toàn bộ chuỗi có thể được sử dụng để đưa ra dự đoán.

Để đưa ra dự đoán với RNN, chúng ta có thể thực hiện các bước sau−

  • Đầu tiên, để tạo trạng thái ẩn ban đầu, chúng ta cần cung cấp phần tử đầu tiên của chuỗi đầu vào.
  • Sau đó, để tạo trạng thái ẩn cập nhật, chúng ta cần lấy trạng thái ẩn ban đầu và kết hợp nó với phần tử thứ hai trong chuỗi đầu vào.
  • Cuối cùng, để tạo trạng thái ẩn cuối cùng và dự đoán đầu ra cho RNN, chúng ta cần lấy phần tử cuối cùng trong chuỗi đầu vào.

Bằng cách này, với sự trợ giúp của kết nối vòng lặp này, chúng ta có thể dạy RNN nhận biết các mẫu xảy ra theo thời gian.

Dự đoán trình tự

Mô hình cơ bản, đã thảo luận ở trên, của RNN cũng có thể được mở rộng sang các trường hợp sử dụng khác. Ví dụ: chúng ta có thể sử dụng nó để dự đoán một chuỗi giá trị dựa trên một đầu vào duy nhất. Trong trường hợp này, để đưa ra dự đoán với RNN, chúng ta có thể thực hiện các bước sau –

  • Đầu tiên, để tạo trạng thái ẩn ban đầu và dự đoán phần tử đầu tiên trong chuỗi đầu ra, chúng ta cần đưa mẫu đầu vào vào mạng nơ-ron.
  • Sau đó, để tạo trạng thái ẩn được cập nhật và phần tử thứ hai trong chuỗi đầu ra, chúng ta cần kết hợp trạng thái ẩn ban đầu với cùng một mẫu.
  • Cuối cùng, để cập nhật trạng thái ẩn một lần nữa và dự đoán phần tử cuối cùng trong chuỗi đầu ra, chúng tôi nạp mẫu vào một lần khác.

Dự đoán trình tự

Như chúng ta đã thấy cách dự đoán một giá trị dựa trên một chuỗi và cách dự đoán một chuỗi dựa trên một giá trị. Bây giờ hãy xem cách chúng ta có thể dự đoán trình tự cho các trình tự. Trong trường hợp này, để đưa ra dự đoán với RNN, chúng ta có thể thực hiện các bước sau –

  • Đầu tiên, để tạo trạng thái ẩn ban đầu và dự đoán phần tử đầu tiên trong chuỗi đầu ra, chúng ta cần lấy phần tử đầu tiên trong chuỗi đầu vào.
  • Sau đó, để cập nhật trạng thái ẩn và dự đoán phần tử thứ hai trong chuỗi đầu ra, chúng ta cần lấy trạng thái ẩn ban đầu.
  • Cuối cùng, để dự đoán phần tử cuối cùng trong chuỗi đầu ra, chúng ta cần lấy trạng thái ẩn được cập nhật và phần tử cuối cùng trong chuỗi đầu vào.

Hoạt động của RNN

Để hiểu hoạt động của mạng thần kinh tái phát (RNN), trước tiên chúng ta cần hiểu cách hoạt động của các lớp tái phát trong mạng. Vì vậy, trước tiên hãy thảo luận về cách e có thể dự đoán đầu ra bằng lớp lặp lại tiêu chuẩn.

Dự đoán đầu ra với lớp RNN tiêu chuẩn

Như chúng ta đã thảo luận trước đó, lớp cơ bản trong RNN khá khác với lớp thông thường trong mạng nơ-ron. Trong phần trước, chúng tôi cũng đã trình bày trên sơ đồ kiến ​​trúc cơ bản của RNN. Để cập nhật trạng thái ẩn cho trình tự bước vào lần đầu tiên, chúng ta có thể sử dụng công thức sau

Dự đoán đầu ra với lớp RNN tiêu chuẩn

Trong phương trình trên, chúng ta tính toán trạng thái ẩn mới bằng cách tính tích số chấm giữa trạng thái ẩn ban đầu và một tập hợp trọng số.

Bây giờ đối với bước tiếp theo, trạng thái ẩn cho bước thời gian hiện tại được sử dụng làm trạng thái ẩn ban đầu cho bước thời gian tiếp theo trong chuỗi. Đó là lý do tại sao, để cập nhật trạng thái ẩn cho bước lần thứ hai, chúng ta có thể lặp lại các phép tính được thực hiện ở bước lần đầu tiên như sau –

Hoạt động của RNN

Tiếp theo, chúng ta có thể lặp lại quá trình cập nhật trạng thái ẩn cho bước thứ ba và bước cuối cùng trong trình tự như dưới đây –

 lớp RNN tiêu chuẩn

Và khi chúng ta đã xử lý tất cả các bước trên theo trình tự, chúng ta có thể tính toán kết quả đầu ra như sau –

Đối với công thức trên, chúng tôi đã sử dụng bộ trọng số thứ ba và trạng thái ẩn từ bước thời gian cuối cùng.

Đơn vị định kỳ nâng cao

Vấn đề chính với lớp hồi quy cơ bản là vấn đề biến mất độ dốc và do đó, nó không tốt trong việc tìm hiểu các mối tương quan dài hạn. Nói một cách đơn giản, lớp lặp lại cơ bản không xử lý tốt các chuỗi dài. Đó là lý do tại sao một số loại lớp lặp lại khác phù hợp hơn nhiều để làm việc với các chuỗi dài hơn như sau:

Trí nhớ ngắn hạn dài hạn (LSTM)

Trí nhớ ngắn hạn dài hạn (LSTM)

Mạng bộ nhớ dài hạn (LSTM) được giới thiệu bởi Hochreiter & Schmidhuber. Nó giải quyết được vấn đề có được một lớp lặp lại cơ bản để ghi nhớ mọi thứ trong một thời gian dài. Kiến trúc của LSTM được đưa ra ở sơ đồ trên. Như chúng ta có thể thấy, nó có các nơ-ron đầu vào, các ô nhớ và các nơ-ron đầu ra. Để giải quyết vấn đề độ dốc biến mất, mạng bộ nhớ ngắn hạn dài sử dụng một ô nhớ rõ ràng (lưu trữ các giá trị trước đó) và các cổng sau –

  • Quên cổng− Đúng như tên gọi, nó báo cho ô nhớ quên các giá trị trước đó. Ô nhớ lưu trữ các giá trị cho đến khi cổng, tức là ‘quên cổng’ yêu cầu nó quên chúng.
  • Cổng đầu vào− Đúng như tên gọi, nó thêm nội dung mới vào ô.
  • Cổng đầu ra− Như tên ngụ ý, cổng đầu ra quyết định thời điểm chuyển dọc theo các vectơ từ ô sang trạng thái ẩn tiếp theo.

Đơn vị định kỳ có kiểm soát (GRU)

Đơn vị định kỳ có kiểm soát (GRU)

Đơn vị lặp lại theo độ dốc (GRU) là một biến thể nhỏ của mạng LSTM. Nó có ít cổng hơn và có dây hơi khác so với LSTM. Kiến trúc của nó được thể hiện trong sơ đồ trên. Nó có các nơ-ron đầu vào, các ô nhớ có cổng và các nơ-ron đầu ra. Mạng Đơn vị định kỳ có cổng có hai cổng sau −

  • Cổng cập nhật− Nó xác định hai điều sau đây−
    • Lượng thông tin nào nên được giữ lại từ trạng thái cuối cùng?
    • Lượng thông tin nào sẽ được đưa vào từ lớp trước?
  • Cổng đặt lại− Chức năng của cổng đặt lại rất giống với cổng quên của mạng LSTM. Sự khác biệt duy nhất là nó nằm ở vị trí hơi khác một chút.

Ngược lại với mạng bộ nhớ dài hạn, mạng Đơn vị định kỳ có cổng nhanh hơn và dễ chạy hơn một chút.

Tạo cấu trúc RNN

Trước khi có thể bắt đầu đưa ra dự đoán về đầu ra từ bất kỳ nguồn dữ liệu nào, trước tiên chúng ta cần xây dựng RNN và việc xây dựng RNN khá giống với cách chúng ta đã xây dựng mạng nơ-ron thông thường trong phần trước. Sau đây là mã để xây dựng một−

Đơn vị lặp lại theo độ dốc (GRU) là một biến thể nhỏ của mạng LSTM. Nó có ít cổng hơn và có dây hơi khác so với LSTM. Kiến trúc của nó được thể hiện trong sơ đồ trên. Nó có các nơ-ron đầu vào, các ô nhớ có cổng và các nơ-ron đầu ra. Mạng Đơn vị định kỳ có cổng có hai cổng sau −
•	Cổng cập nhật− Nó xác định hai điều sau đây−
o	Lượng thông tin nào nên được giữ lại từ trạng thái cuối cùng?
o	Lượng thông tin nào sẽ được đưa vào từ lớp trước?
•	Cổng đặt lại− Chức năng của cổng đặt lại rất giống với cổng quên của mạng LSTM. Sự khác biệt duy nhất là nó nằm ở vị trí hơi khác một chút.
Ngược lại với mạng bộ nhớ dài hạn, mạng Đơn vị định kỳ có cổng nhanh hơn và dễ chạy hơn một chút.
Tạo cấu trúc RNN
Trước khi có thể bắt đầu đưa ra dự đoán về đầu ra từ bất kỳ nguồn dữ liệu nào, trước tiên chúng ta cần xây dựng RNN và việc xây dựng RNN khá giống với cách chúng ta đã xây dựng mạng nơ-ron thông thường trong phần trước. Sau đây là mã để xây dựng một−
from cntk.losses import squared_error
from cntk.io import CTFDeserializer, MinibatchSource, INFINITELY_REPEAT, StreamDefs, StreamDef
from cntk.learners import adam
from cntk.logging import ProgressPrinter
from cntk.train import TestConfig
BATCH_SIZE = 14 * 10
EPOCH_SIZE = 12434
EPOCHS = 10

Đặt cược nhiều lớp

Chúng tôi cũng có thể xếp chồng nhiều lớp lặp lại trong CNTK. Ví dụ: chúng ta có thể sử dụng tổ hợp các lớp sau đây−

from cntk import sequence, default_options, input_variable
from cntk.layers import Recurrence, LSTM, Dropout, Dense, Sequential, Fold
features = sequence.input_variable(1)
with default_options(initial_state = 0.1):
   model = Sequential([
      Fold(LSTM(15)),
      Dense(1)
   ])(features)
target = input_variable(1, dynamic_axes=model.dynamic_axes)

Như chúng ta có thể thấy trong đoạn mã trên, chúng ta có hai cách sau để có thể lập mô hình RNN trong CNTK –

  • Đầu tiên, nếu chúng ta chỉ muốn đầu ra cuối cùng của lớp lặp lại, chúng ta có thể sử dụng lớp Fold kết hợp với lớp lặp lại, chẳng hạn như GRU, LSTM hoặc thậm chí RNNStep.
  • Thứ hai, như một cách khác, chúng ta cũng có thể sử dụng khối Recurrence.

Huấn luyện RNN với dữ liệu chuỗi thời gian

Sau khi xây dựng mô hình, hãy xem cách chúng tôi có thể huấn luyện RNN trong CNTK

from cntk import Function
@Function
def criterion_factory(z, t):
   loss = squared_error(z, t)
   metric = squared_error(z, t)
   return loss, metric
loss = criterion_factory(model, target)
learner = adam(model.parameters, lr=0.005, momentum=0.9)

Bây giờ để tải dữ liệu vào quá trình huấn luyện, chúng ta phải giải tuần tự hóa các chuỗi từ một tập hợp tệp CTF. Mã sau có hàm create_datasource, đây là một hàm tiện ích hữu ích để tạo cả nguồn dữ liệu huấn luyện và kiểm tra.

target_stream = StreamDef(field='target', shape=1, is_sparse=False)
features_stream = StreamDef(field='features', shape=1, is_sparse=False)
deserializer = CTFDeserializer(filename, StreamDefs(features=features_stream, target=target_stream))
   datasource = MinibatchSource(deserializer, randomize=True, max_sweeps=sweeps)
return datasource
train_datasource = create_datasource('Training data filename.ctf')#we need to provide the location of training file we created from our dataset.
test_datasource = create_datasource('Test filename.ctf', sweeps=1) #we need to provide the location of testing file we created from our dataset.

Bây giờ, khi chúng ta đã thiết lập nguồn dữ liệu, mô hình và hàm mất dữ liệu, chúng ta có thể bắt đầu quá trình đào tạo. Nó khá giống với những gì chúng ta đã làm trong các phần trước với mạng nơ-ron cơ bản.

progress_writer = ProgressPrinter(0)
test_config = TestConfig(test_datasource)
input_map = {
   features: train_datasource.streams.features,
   target: train_datasource.streams.target
}
history = loss.train(
   train_datasource,
   epoch_size=EPOCH_SIZE,
   parameter_learners=[learner],
   model_inputs_to_streams=input_map,
   callbacks=[progress_writer, test_config],
   minibatch_size=BATCH_SIZE,
   max_epochs=EPOCHS
)

Chúng ta sẽ nhận được kết quả tương tự như sau –

Đầu ra−

average  since  average  since  examples
loss      last  metric  last
------------------------------------------------------
Learning rate per minibatch: 0.005
0.4      0.4    0.4      0.4      19
0.4      0.4    0.4      0.4      59
0.452    0.495  0.452    0.495   129
[…]

Xác thực mô hình

Trên thực tế, việc đọc lại bằng RNN khá giống với việc đưa ra dự đoán với bất kỳ mô hình CNK nào khác. Sự khác biệt duy nhất là chúng tôi cần cung cấp trình tự thay vì các mẫu đơn lẻ. Bây giờ, vì RNN của chúng ta cuối cùng đã hoàn tất quá trình đào tạo, chúng ta có thể xác thực mô hình bằng cách kiểm tra nó bằng cách sử dụng một vài chuỗi mẫu như sau –

import pickle
with open('test_samples.pkl', 'rb') as test_file:
test_samples = pickle.load(test_file)
model(test_samples) * NORMALIZE

Đầu ra−

array([[ 8081.7905],
[16597.693 ],
[13335.17 ],
...,
[11275.804 ],
[15621.697 ],
[16875.555 ]], dtype=float32)

Trả lời