CNTK - เครือข่ายประสาทที่เกิดซ้ำ

ตอนนี้ให้เราเข้าใจวิธีสร้าง Recurrent Neural Network (RNN) ใน CNTK

บทนำ

เราได้เรียนรู้วิธีการจัดประเภทภาพด้วยโครงข่ายประสาทเทียมและเป็นงานที่โดดเด่นอย่างหนึ่งในการเรียนรู้เชิงลึก แต่อีกพื้นที่หนึ่งที่เครือข่ายประสาทเทียมมีความสามารถและมีงานวิจัยเกิดขึ้นมากมายคือ Recurrent Neural Networks (RNN) ที่นี่เราจะได้ทราบว่า RNN คืออะไรและสามารถใช้ในสถานการณ์ที่เราต้องจัดการกับข้อมูลอนุกรมเวลาได้อย่างไร

Recurrent Neural Network คืออะไร?

เครือข่ายประสาทที่เกิดซ้ำ (RNNs) อาจถูกกำหนดให้เป็น NN สายพันธุ์พิเศษที่สามารถให้เหตุผลได้ตลอดเวลา RNN ส่วนใหญ่จะใช้ในสถานการณ์ที่เราต้องจัดการกับค่าที่เปลี่ยนแปลงตลอดเวลานั่นคือข้อมูลอนุกรมเวลา เพื่อที่จะเข้าใจมันในทางที่ดีขึ้นเรามาเปรียบเทียบกันเล็กน้อยระหว่างเครือข่ายประสาทปกติและเครือข่ายประสาทที่เกิดซ้ำ -

  • ดังที่เราทราบดีว่าในโครงข่ายประสาทเทียมปกติเราสามารถป้อนข้อมูลได้เพียงรายการเดียว ซึ่งจะ จำกัด ให้ผลลัพธ์ในการทำนายเพียงครั้งเดียว เพื่อเป็นตัวอย่างเราสามารถแปลงานข้อความได้โดยใช้โครงข่ายประสาทเทียมปกติ

  • ในทางกลับกันในเครือข่ายประสาทที่เกิดซ้ำเราสามารถจัดเตรียมลำดับของตัวอย่างที่ทำให้เกิดการคาดคะเนเพียงครั้งเดียว กล่าวอีกนัยหนึ่งคือการใช้ RNNs เราสามารถทำนายลำดับเอาต์พุตตามลำดับอินพุต ตัวอย่างเช่นมีการทดลองกับ RNN ในงานแปลที่ประสบความสำเร็จค่อนข้างน้อย

การใช้โครงข่ายประสาทที่เกิดซ้ำ

RNN สามารถใช้ได้หลายวิธี บางส่วนมีดังนี้ -

การคาดการณ์ผลลัพธ์เดียว

ก่อนที่จะลงลึกในขั้นตอนต่างๆว่า RNN สามารถทำนายผลลัพธ์เดียวตามลำดับได้อย่างไรมาดูกันว่า RNN พื้นฐานมีลักษณะอย่างไร

ดังที่เราทำได้ในแผนภาพด้านบน RNN มีการเชื่อมต่อแบบย้อนกลับไปยังอินพุตและเมื่อใดก็ตามที่เราป้อนลำดับของค่ามันจะประมวลผลแต่ละองค์ประกอบตามลำดับเป็นขั้นตอนเวลา

ยิ่งไปกว่านั้นเนื่องจากการเชื่อมต่อแบบย้อนกลับ RNN สามารถรวมเอาท์พุทที่สร้างขึ้นกับอินพุตสำหรับองค์ประกอบถัดไปในลำดับ ด้วยวิธีนี้ RNN จะสร้างหน่วยความจำในลำดับทั้งหมดซึ่งสามารถใช้ในการทำนายได้

ในการทำนายด้วย RNN เราสามารถดำเนินการตามขั้นตอนต่อไปนี้

  • ขั้นแรกในการสร้างสถานะที่ซ่อนเริ่มต้นเราต้องป้อนองค์ประกอบแรกของลำดับการป้อนข้อมูล

  • หลังจากนั้นในการสร้างสถานะที่ซ่อนไว้ที่อัปเดตเราจำเป็นต้องใช้สถานะที่ซ่อนเริ่มต้นและรวมเข้ากับองค์ประกอบที่สองในลำดับอินพุต

  • ในที่สุดเพื่อสร้างสถานะสุดท้ายที่ซ่อนอยู่และเพื่อทำนายผลลัพธ์สำหรับ RNN เราจำเป็นต้องใช้องค์ประกอบสุดท้ายในลำดับอินพุต

ด้วยวิธีนี้ด้วยความช่วยเหลือของการเชื่อมต่อแบบย้อนกลับนี้เราสามารถสอน RNN ให้จดจำรูปแบบที่เกิดขึ้นเมื่อเวลาผ่านไป

การทำนายลำดับ

โมเดลพื้นฐานที่กล่าวถึงข้างต้นของ RNN สามารถขยายไปยังกรณีการใช้งานอื่น ๆ ได้เช่นกัน ตัวอย่างเช่นเราสามารถใช้เพื่อทำนายลำดับของค่าตามอินพุตเดียว ในสถานการณ์นี้เพื่อทำการทำนายด้วย RNN เราสามารถทำตามขั้นตอนต่อไปนี้ -

  • ขั้นแรกในการสร้างสถานะที่ซ่อนอยู่เริ่มต้นและทำนายองค์ประกอบแรกในลำดับเอาต์พุตเราต้องป้อนตัวอย่างอินพุตเข้าไปในโครงข่ายประสาทเทียม

  • หลังจากนั้นในการสร้างสถานะที่ซ่อนไว้ที่อัปเดตและองค์ประกอบที่สองในลำดับเอาต์พุตเราจำเป็นต้องรวมสถานะที่ซ่อนไว้เริ่มต้นกับตัวอย่างเดียวกัน

  • ในที่สุดเพื่ออัปเดตสถานะที่ซ่อนอีกครั้งและทำนายองค์ประกอบสุดท้ายในลำดับเอาต์พุตเราจะป้อนตัวอย่างอีกครั้ง

ลำดับการทำนาย

ดังที่เราได้เห็นวิธีการทำนายค่าเดียวตามลำดับและวิธีทำนายลำดับตามค่าเดียว ตอนนี้เรามาดูกันว่าเราสามารถทำนายลำดับของลำดับได้อย่างไร ในสถานการณ์นี้เพื่อทำการทำนายด้วย RNN เราสามารถทำตามขั้นตอนต่อไปนี้ -

  • ขั้นแรกในการสร้างสถานะที่ซ่อนไว้เริ่มต้นและทำนายองค์ประกอบแรกในลำดับเอาต์พุตเราจำเป็นต้องใช้องค์ประกอบแรกในลำดับอินพุต

  • หลังจากนั้นในการอัปเดตสถานะที่ซ่อนอยู่และทำนายองค์ประกอบที่สองในลำดับเอาต์พุตเราจำเป็นต้องใช้สถานะที่ซ่อนอยู่เริ่มต้น

  • ในที่สุดในการทำนายองค์ประกอบสุดท้ายในลำดับเอาต์พุตเราจำเป็นต้องใช้สถานะที่ซ่อนอยู่ที่อัปเดตและองค์ประกอบสุดท้ายในลำดับอินพุต

การทำงานของ RNN

เพื่อให้เข้าใจการทำงานของเครือข่ายประสาทที่เกิดซ้ำ (RNN) ก่อนอื่นเราต้องเข้าใจว่าเลเยอร์ที่เกิดซ้ำในเครือข่ายทำงานอย่างไร ก่อนอื่นเรามาดูกันว่า e สามารถทำนายผลลัพธ์ด้วยเลเยอร์ซ้ำมาตรฐานได้อย่างไร

การคาดการณ์ผลลัพธ์ด้วยเลเยอร์ RNN มาตรฐาน

ดังที่เราได้กล่าวไปก่อนหน้านี้ว่าเลเยอร์พื้นฐานใน RNN นั้นค่อนข้างแตกต่างจากเลเยอร์ปกติในโครงข่ายประสาทเทียม ในส่วนก่อนหน้านี้เราได้แสดงให้เห็นในแผนภาพสถาปัตยกรรมพื้นฐานของ RNN ในการอัปเดตสถานะที่ซ่อนอยู่สำหรับลำดับขั้นตอนในครั้งแรกเราสามารถใช้สูตรต่อไปนี้ -

ในสมการข้างต้นเราคำนวณสถานะใหม่ที่ซ่อนอยู่โดยการคำนวณผลิตภัณฑ์ดอทระหว่างสถานะที่ซ่อนอยู่เริ่มต้นและชุดของน้ำหนัก

ตอนนี้สำหรับขั้นตอนต่อไปสถานะที่ซ่อนอยู่สำหรับขั้นตอนเวลาปัจจุบันจะถูกใช้เป็นสถานะที่ซ่อนไว้เริ่มต้นสำหรับขั้นตอนถัดไปในลำดับ นั่นเป็นเหตุผลว่าทำไมในการอัปเดตสถานะที่ซ่อนอยู่ในขั้นตอนที่สองเราสามารถทำการคำนวณซ้ำในขั้นตอนแรกได้ดังนี้ -

จากนั้นเราสามารถทำซ้ำขั้นตอนการอัปเดตสถานะที่ซ่อนอยู่สำหรับขั้นตอนที่สามและขั้นสุดท้ายตามลำดับดังนี้ -

และเมื่อเราประมวลผลขั้นตอนข้างต้นตามลำดับทั้งหมดแล้วเราสามารถคำนวณผลลัพธ์ได้ดังนี้ -

สำหรับสูตรข้างต้นเราได้ใช้ชุดที่สามของน้ำหนักและสถานะที่ซ่อนอยู่จากขั้นตอนสุดท้าย

หน่วยกำเริบขั้นสูง

ปัญหาหลักของเลเยอร์ที่เกิดซ้ำขั้นพื้นฐานคือการหายไปจากปัญหาการไล่ระดับสีและด้วยเหตุนี้การเรียนรู้ความสัมพันธ์ในระยะยาวจึงไม่ดีนัก พูดง่ายๆว่าเลเยอร์กำเริบขั้นพื้นฐานไม่สามารถจัดการลำดับที่ยาวได้เป็นอย่างดี นั่นเป็นเหตุผลที่บางประเภทของเลเยอร์ที่เกิดซ้ำซึ่งเหมาะสำหรับการทำงานกับลำดับที่ยาวขึ้นมีดังนี้ -

หน่วยความจำระยะยาว (LSTM)

Hochreiter & Schmidhuber เครือข่ายหน่วยความจำระยะยาว (LSTM) มันแก้ปัญหาในการรับเลเยอร์ซ้ำขั้นพื้นฐานเพื่อจดจำสิ่งต่างๆเป็นเวลานาน สถาปัตยกรรมของ LSTM แสดงไว้ด้านบนในแผนภาพ อย่างที่เราเห็นว่ามันมีเซลล์ประสาทอินพุตเซลล์ความจำและเซลล์ประสาทเอาท์พุต เพื่อต่อสู้กับปัญหาการไล่ระดับสีที่หายไปเครือข่ายหน่วยความจำระยะยาวจะใช้เซลล์หน่วยความจำที่ชัดเจน (เก็บค่าก่อนหน้านี้) และประตูต่อไปนี้ -

  • Forget gate- ตามความหมายของชื่อจะบอกให้เซลล์หน่วยความจำลืมค่าก่อนหน้านี้ เซลล์หน่วยความจำจะเก็บค่าไว้จนกว่าเกตคือ 'forget gate' จะบอกให้ลืม

  • Input gate- ตามความหมายของชื่อมันจะเพิ่มสิ่งใหม่ ๆ ให้กับเซลล์

  • Output gate- ตามความหมายของชื่อประตูเอาต์พุตจะตัดสินใจว่าเมื่อใดที่จะส่งต่อเวกเตอร์จากเซลล์ไปยังสถานะที่ซ่อนอยู่ถัดไป

Gated Recurrent Units (GRU)

Gradient recurrent units(GRUs) เป็นเครือข่าย LSTM ที่มีความแตกต่างกันเล็กน้อย มีประตูน้อยกว่าหนึ่งประตูและมีสายแตกต่างจาก LSTM เล็กน้อย สถาปัตยกรรมของมันแสดงอยู่ในแผนภาพด้านบน มีเซลล์ประสาทอินพุตเซลล์หน่วยความจำที่มีรั้วรอบขอบชิดและเซลล์ประสาทที่ส่งออก เครือข่าย Gated Recurrent Units มีสองประตูดังต่อไปนี้ -

  • Update gate- กำหนดสองสิ่งต่อไปนี้

    • ข้อมูลใดที่ควรเก็บไว้จากสถานะล่าสุด

    • ควรปล่อยให้ข้อมูลจำนวนเท่าใดจากเลเยอร์ก่อนหน้านี้

  • Reset gate- การทำงานของ reset gate เหมือนกับของ forget gate ของเครือข่าย LSTMs ข้อแตกต่างเพียงอย่างเดียวคือตั้งอยู่แตกต่างกันเล็กน้อย

ตรงกันข้ามกับเครือข่ายหน่วยความจำระยะสั้นเครือข่าย Gated Recurrent Unit นั้นเร็วกว่าและทำงานง่ายกว่าเล็กน้อย

การสร้างโครงสร้าง RNN

ก่อนที่เราจะเริ่มทำการทำนายเกี่ยวกับผลลัพธ์จากแหล่งข้อมูลใด ๆ ของเราเราต้องสร้าง RNN ก่อนและการสร้าง RNN นั้นค่อนข้างเหมือนกับที่เราสร้างเครือข่ายประสาทเทียมในส่วนก่อนหน้านี้ ต่อไปนี้เป็นรหัสสำหรับสร้างหนึ่ง

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

การจับคู่หลายชั้น

นอกจากนี้เรายังสามารถซ้อนเลเยอร์ที่เกิดซ้ำหลาย ๆ ชั้นใน CNTK ตัวอย่างเช่นเราสามารถใช้ชุดค่าผสมต่อไปนี้

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)

ดังที่เราเห็นในโค้ดด้านบนเรามีสองวิธีต่อไปนี้ที่เราสามารถสร้างแบบจำลอง RNN ใน CNTK -

  • ขั้นแรกหากเราต้องการเพียงผลลัพธ์สุดท้ายของเลเยอร์ที่เกิดซ้ำเราสามารถใช้ไฟล์ Fold เลเยอร์ร่วมกับเลเยอร์ที่เกิดซ้ำเช่น GRU, LSTM หรือแม้แต่ RNNStep

  • ประการที่สองเรายังสามารถใช้ไฟล์ Recurrence บล็อก.

การฝึกอบรม RNN ด้วยข้อมูลอนุกรมเวลา

เมื่อเราสร้างแบบจำลองแล้วมาดูกันว่าเราสามารถฝึก RNN ใน 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)

ตอนนี้ในการโหลดข้อมูลลงในกระบวนการฝึกอบรมเราต้องทำการ deserialize ลำดับจากชุดไฟล์ CTF รหัสต่อไปนี้มีนามสกุลcreate_datasource ฟังก์ชันซึ่งเป็นฟังก์ชันยูทิลิตี้ที่มีประโยชน์ในการสร้างทั้งแหล่งข้อมูลการฝึกอบรมและการทดสอบ

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.

ตอนนี้เมื่อเราตั้งค่าแหล่งข้อมูลโมเดลและฟังก์ชันการสูญเสียแล้วเราสามารถเริ่มกระบวนการฝึกอบรมได้ มันค่อนข้างคล้ายกับที่เราทำในส่วนก่อนหน้านี้กับโครงข่ายใยประสาทพื้นฐาน

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
)

เราจะได้ผลลัพธ์ที่คล้ายกันดังนี้ -

เอาต์พุต

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
[…]

กำลังตรวจสอบโมเดล

ที่จริงแล้วการคาดเดาด้วย RNN นั้นค่อนข้างคล้ายกับการคาดเดาด้วยโมเดล CNK อื่น ๆ ข้อแตกต่างเพียงอย่างเดียวคือเราต้องจัดเตรียมลำดับมากกว่าตัวอย่างเดียว

ตอนนี้เมื่อ RNN ของเราเสร็จสิ้นด้วยการฝึกอบรมในที่สุดเราสามารถตรวจสอบความถูกต้องของโมเดลได้โดยการทดสอบโดยใช้ลำดับตัวอย่างสองสามลำดับดังนี้ -

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

เอาต์พุต

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