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)