แอปพลิเคชั่น Gradient Descent มากมายใน TensorFlow
เผยแพร่แล้ว: 2022-03-11TensorFlow ของ Google เป็นหนึ่งในเครื่องมือชั้นนำสำหรับการฝึกอบรมและปรับใช้โมเดลการเรียนรู้เชิงลึก มันสามารถเพิ่มประสิทธิภาพสถาปัตยกรรมเครือข่ายประสาทเทียมที่มีความซับซ้อนอย่างมากด้วยพารามิเตอร์นับร้อยล้าน และมาพร้อมกับเครื่องมือมากมายสำหรับการเร่งความเร็วฮาร์ดแวร์ การฝึกอบรมแบบกระจาย และเวิร์กโฟลว์การผลิต คุณลักษณะอันทรงพลังเหล่านี้อาจทำให้ดูน่ากลัวและไม่จำเป็นนอกขอบเขตของการเรียนรู้เชิงลึก
แต่ TensorFlow สามารถเข้าถึงได้และใช้งานได้สำหรับปัญหาที่ง่ายกว่าซึ่งไม่เกี่ยวข้องโดยตรงกับการฝึกโมเดลการเรียนรู้เชิงลึก ที่แกนหลัก TensorFlow เป็นเพียงไลบรารีที่ปรับให้เหมาะสมสำหรับการดำเนินการเทนเซอร์ (เวกเตอร์ เมทริกซ์ ฯลฯ) และการดำเนินการแคลคูลัสที่ใช้ในการทำการเกรเดียนต์โคตรบนลำดับการคำนวณโดยพลการ นักวิทยาศาสตร์ข้อมูลที่มีประสบการณ์จะรู้จัก "การไล่ระดับสีแบบค่อยเป็นค่อยไป" เป็นเครื่องมือพื้นฐานสำหรับคณิตศาสตร์เชิงคำนวณ แต่โดยปกติแล้วจะต้องใช้โค้ดและสมการเฉพาะแอปพลิเคชัน อย่างที่เราจะได้เห็นกัน นี่คือที่มาของสถาปัตยกรรม "การสร้างความแตกต่างอัตโนมัติ" ที่ทันสมัยของ TensorFlow
กรณีการใช้งาน TensorFlow
- ตัวอย่างที่ 1: การถดถอยเชิงเส้นพร้อมการไล่ระดับสีแบบไล่ระดับใน TensorFlow 2.0
- Gradient Descent คืออะไร?
- ตัวอย่างที่ 2: กระจายเวกเตอร์หน่วยสูงสุด
- ตัวอย่างที่ 3: การสร้างอินพุต AI ที่เป็นปฏิปักษ์
- ความคิดสุดท้าย: การเพิ่มประสิทธิภาพการไล่ระดับสี
- Gradient Descent ใน TensorFlow: จากการค้นหาค่าต่ำสุดไปจนถึงการโจมตีระบบ AI
ตัวอย่างที่ 1: การถดถอยเชิงเส้นพร้อมการไล่ระดับสีแบบไล่ระดับใน TensorFlow 2.0
ตัวอย่างที่ 1 สมุดบันทึก
ก่อนที่จะไปที่โค้ด TensorFlow คุณควรทำความคุ้นเคยกับการถดถอยแบบเกรเดียนท์และการถดถอยเชิงเส้น
Gradient Descent คืออะไร?
ในแง่ที่ง่ายที่สุด เป็นเทคนิคเชิงตัวเลขในการค้นหาอินพุตไปยังระบบสมการที่ลดเอาต์พุตให้เหลือน้อยที่สุด ในบริบทของการเรียนรู้ของเครื่อง ระบบสมการนั้นเป็น แบบจำลอง ของเรา อินพุตเป็น พารามิเตอร์ ที่ไม่รู้จักของแบบจำลอง และผลลัพธ์คือ ฟังก์ชันการสูญเสีย ที่จะย่อให้เล็กสุด ซึ่งแสดงถึงข้อผิดพลาดระหว่างแบบจำลองและข้อมูลของเรา สำหรับปัญหาบางอย่าง (เช่น การถดถอยเชิงเส้น) มีสมการเพื่อคำนวณพารามิเตอร์โดยตรงที่ลดข้อผิดพลาดของเราให้เหลือน้อยที่สุด แต่สำหรับการใช้งานจริงส่วนใหญ่ เราต้องการเทคนิคเชิงตัวเลข เช่น การไล่ระดับสีแบบลงล่างเพื่อให้ได้คำตอบที่น่าพอใจ
จุดที่สำคัญที่สุดของบทความนี้คือการที่การไล่ระดับสีโดยปกติต้องมีการจัดวางสมการของเราและใช้แคลคูลัสเพื่อให้ได้มาซึ่งความสัมพันธ์ระหว่างฟังก์ชันการสูญเสียและพารามิเตอร์ของเรา ด้วย TensorFlow (และเครื่องมือสร้างความแตกต่างอัตโนมัติที่ทันสมัย) แคลคูลัสจะได้รับการจัดการ ดังนั้นเราจึงสามารถมุ่งเน้นไปที่การออกแบบโซลูชัน และไม่ต้องใช้เวลากับการใช้งาน
นี่คือสิ่งที่ดูเหมือนกับปัญหาการถดถอยเชิงเส้นอย่างง่าย เรามีตัวอย่างความสูง (h) และน้ำหนัก (w) ของผู้ใหญ่เพศชาย 150 คน และเริ่มต้นด้วยการเดาความชันและค่าเบี่ยงเบนมาตรฐานของเส้นนี้ที่ไม่สมบูรณ์ หลังจากการไล่ระดับการไล่ระดับสีซ้ำประมาณ 15 ครั้ง เราก็ได้วิธีแก้ปัญหาที่ใกล้เคียงที่สุด
มาดูกันว่าเราสร้างโซลูชันข้างต้นโดยใช้ TensorFlow 2.0 ได้อย่างไร
สำหรับการถดถอยเชิงเส้น เราบอกว่าน้ำหนักสามารถทำนายได้ด้วยสมการเชิงเส้นของความสูง
เราต้องการค้นหาพารามิเตอร์ α และ β (ความชันและค่าสกัดกั้น) ที่ลดค่าคลาดเคลื่อนกำลังสองเฉลี่ย (การสูญเสีย) ระหว่างการคาดคะเนและค่าจริงให้เหลือน้อยที่สุด ดังนั้น ฟังก์ชันการสูญเสีย ของเรา (ในกรณีนี้ "ค่าเฉลี่ยกำลังสองข้อผิดพลาด" หรือ MSE) จะมีลักษณะดังนี้:
เราสามารถดูว่าค่าคลาดเคลื่อนกำลังสองเฉลี่ยค้นหาเส้นที่ไม่สมบูรณ์สองสามเส้นได้อย่างไร จากนั้นจึงใช้คำตอบที่แน่นอน (α=6.04, β=-230.5)
มานำแนวคิดนี้ไปปฏิบัติจริงกับ TensorFlow สิ่งแรกที่ต้องทำคือเขียนโค้ดฟังก์ชันการสูญเสียโดยใช้เทนเซอร์และฟังก์ชัน tf.*
def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse
นี้ดูค่อนข้างตรงไปตรงมา ตัวดำเนินการพีชคณิตมาตรฐานทั้งหมดมีเทนเซอร์มากเกินไป ดังนั้นเราต้องตรวจสอบให้แน่ใจว่าตัวแปรที่เรากำลังปรับให้เหมาะสมนั้นเป็นเทนเซอร์ และเราใช้เมธอด tf.*
สำหรับอย่างอื่น
จากนั้น สิ่งที่เราต้องทำคือใส่สิ่งนี้ลงในลูปการไล่ระดับสี:
def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]
ลองใช้เวลาสักครู่เพื่อชื่นชมความประณีตของสิ่งนี้ การไล่ระดับสีแบบไล่ระดับต้องใช้การคำนวณอนุพันธ์ของฟังก์ชันการสูญเสียที่สัมพันธ์กับตัวแปรทั้งหมดที่เรากำลังพยายามปรับให้เหมาะสม แคลคูลัสน่าจะเกี่ยวข้อง แต่จริงๆ แล้วเราไม่ได้ทำอะไรเลย ความมหัศจรรย์อยู่ในความจริงที่ว่า:
- TensorFlow สร้างกราฟการคำนวณของทุกการคำนวณที่ทำภายใต้
tf.GradientTape()
- TensorFlow รู้วิธีคำนวณอนุพันธ์ (การไล่ระดับสี) ของทุกการดำเนินการ เพื่อให้สามารถระบุได้ว่าตัวแปรใดๆ ในกราฟการคำนวณจะส่งผลต่อตัวแปรอื่นๆ อย่างไร
กระบวนการมีลักษณะอย่างไรจากจุดเริ่มต้นที่ต่างกัน
การไล่ระดับสีแบบไล่ระดับเข้าใกล้ MSE ที่เหมาะสมที่สุดอย่างน่าทึ่ง แต่จริงๆ แล้วมาบรรจบกันที่ความชันและการสกัดกั้นที่แตกต่างกันอย่างมากเมื่อเทียบกับค่าที่เหมาะสมที่สุดในทั้งสองตัวอย่าง ในบางกรณี นี่เป็นเพียงการเกรเดียนท์โคตรที่บรรจบกับค่าต่ำสุดในพื้นที่ ซึ่งเป็นความท้าทายโดยธรรมชาติของอัลกอริธึมการไล่ระดับสีแบบเกรเดียนท์ แต่การถดถอยเชิงเส้นสามารถพิสูจน์ได้ว่ามีขั้นต่ำเพียงหนึ่งเดียวทั่วโลก แล้วเราลงเอยด้วยความชันและจุดตัดที่ไม่ถูกต้องได้อย่างไร?
ในกรณีนี้ ปัญหาคือเราทำให้โค้ดง่ายเกินไปเพื่อการสาธิต เราไม่ได้ทำให้ข้อมูลของเราเป็นปกติ และพารามิเตอร์ความชันมีลักษณะที่แตกต่างจากพารามิเตอร์การสกัดกั้น การเปลี่ยนแปลงเล็กๆ น้อยๆ ในความชันสามารถทำให้เกิดการเปลี่ยนแปลงครั้งใหญ่ในการสูญเสีย ในขณะที่การเปลี่ยนแปลงเล็กๆ ในการสกัดกั้นมีผลน้อยมาก ความแตกต่างอย่างมากในสเกลของพารามิเตอร์ที่ฝึกได้นี้นำไปสู่ความชันที่ครอบงำการคำนวณแบบเกรเดียนต์ โดยที่พารามิเตอร์การสกัดกั้นเกือบจะถูกละเลย
ดังนั้นการไล่ระดับความชันจะพบความชันที่ดีที่สุดใกล้กับการเดาการสกัดกั้นเริ่มต้นอย่างมีประสิทธิภาพ และเนื่องจากข้อผิดพลาดนั้นใกล้เคียงกับค่าที่เหมาะสมที่สุด การไล่ระดับสีรอบๆ จึงมีขนาดเล็ก ดังนั้นการวนซ้ำแต่ละครั้งจะเคลื่อนที่เพียงเล็กน้อยเท่านั้น การปรับข้อมูลของเราให้เป็นมาตรฐานก่อนจะช่วยปรับปรุงปรากฏการณ์นี้ได้อย่างมาก แต่ก็ไม่ได้ขจัดออกไป
นี่เป็นตัวอย่างที่ค่อนข้างง่าย แต่เราจะเห็นในหัวข้อถัดไปว่าความสามารถ "การแยกอัตโนมัติ" นี้สามารถจัดการกับบางสิ่งที่ค่อนข้างซับซ้อนได้
ตัวอย่างที่ 2: กระจายเวกเตอร์หน่วยสูงสุด
ตัวอย่างที่ 2 สมุดบันทึก
ตัวอย่างต่อไปนี้อิงจากแบบฝึกหัดการเรียนรู้เชิงลึกที่สนุกสนานในหลักสูตรการเรียนรู้เชิงลึกที่ฉันเรียนเมื่อปีที่แล้ว
สาระสำคัญของปัญหาคือเรามี "เครื่องเข้ารหัสอัตโนมัติแบบแปรผัน" (VAE) ที่สามารถสร้างใบหน้าที่เหมือนจริงจากชุดตัวเลขที่แจกแจงแบบปกติ 32 หมายเลข ในการระบุตัวตนผู้ต้องสงสัย เราต้องการใช้ VAE เพื่อสร้างชุดใบหน้า (ตามทฤษฎี) ที่หลากหลายเพื่อให้พยานเลือก จากนั้นจำกัดการค้นหาโดยสร้างใบหน้าที่คล้ายกับใบหน้าที่ได้รับเลือกมากขึ้น สำหรับแบบฝึกหัดนี้ แนะนำให้สุ่มชุดเวกเตอร์เริ่มต้น แต่ฉันต้องการหาสถานะเริ่มต้นที่เหมาะสมที่สุด
เราสามารถอธิบายปัญหาได้ดังนี้: ให้พื้นที่ 32 มิติ หาชุดเวกเตอร์หน่วย X ที่กระจายออกจากกันมากที่สุด ในสองมิติ การคำนวณนี้เป็นเรื่องง่าย แต่สำหรับสามมิติ (หรือ 32 มิติ!) ไม่มีคำตอบที่ตรงไปตรงมา อย่างไรก็ตาม หากเราสามารถกำหนดฟังก์ชันการสูญเสียที่เหมาะสมซึ่งมีค่าน้อยที่สุดเมื่อเราบรรลุสถานะเป้าหมายแล้ว การไล่ระดับสีแบบไล่ระดับอาจช่วยให้เราไปถึงที่นั่นได้
เราจะเริ่มต้นด้วยชุดสุ่ม 20 เวกเตอร์ดังที่แสดงด้านบน และทดลองกับฟังก์ชันการสูญเสียที่แตกต่างกันสามฟังก์ชัน แต่ละอันมีความซับซ้อนเพิ่มขึ้น เพื่อแสดงความสามารถของ TensorFlow
อันดับแรก มากำหนดวงการฝึกของเรากันก่อน เราจะใส่ตรรกะ TensorFlow ทั้งหมดภายใต้ self.calc_loss()
จากนั้นเราสามารถแทนที่เมธอดนั้นสำหรับแต่ละเทคนิค โดยการรีไซเคิลลูปนี้
# Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients
เทคนิคแรกที่ต้องลองคือวิธีที่ง่ายที่สุด เรากำหนดเมทริกการแพร่กระจายที่เป็นมุมของเวกเตอร์ที่อยู่ใกล้กันมากที่สุด เราต้องการเพิ่มสเปรดให้สูงสุด แต่เป็นเรื่องปกติที่จะทำให้เป็นปัญหาในการลดขนาดให้น้อยที่สุด ดังนั้นเราจึงนำค่าลบของเมตริกสเปรดมาพิจารณา:
class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric
เวทย์มนตร์ Matplotlib บางอย่างจะทำให้เห็นภาพได้
นี่มันอืดๆ (จริงๆ นะ!) แต่ได้ผล เวกเตอร์เพียงสองตัวจาก 20 ตัวเท่านั้นที่ได้รับการอัปเดตในแต่ละครั้ง เพิ่มช่องว่างระหว่างพวกมันจนไม่อยู่ใกล้ที่สุดอีกต่อไป จากนั้นจึงสลับไปที่การเพิ่มมุมระหว่างเวกเตอร์ใหม่ที่อยู่ใกล้ที่สุดสองตัวใหม่ สิ่งสำคัญที่ควรสังเกตคือ มันใช้งาน ได้ เราเห็นว่า TensorFlow สามารถส่งการไล่ระดับสีผ่าน tf.reduce_min()
และเมธอด tf.acos()
เพื่อทำสิ่งที่ถูกต้อง
มาลองทำอะไรให้ละเอียดกว่านี้หน่อย เรารู้ว่าในคำตอบที่เหมาะสม เวกเตอร์ทั้งหมดควรมีมุมเท่ากันกับเพื่อนบ้านที่ใกล้ที่สุด ให้เพิ่ม “ความแปรปรวนของมุมต่ำสุด” ให้กับฟังก์ชันการสูญเสีย
class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric
เวกเตอร์ที่อยู่ทางเหนือโดดเดี่ยวนั้นตอนนี้รวมตัวกับมันอย่างรวดเร็ว เพราะมุมกับเพื่อนบ้านที่ใกล้ที่สุดนั้นใหญ่มากและทำให้ระยะความแปรปรวนเพิ่มขึ้นอย่างรวดเร็วซึ่งตอนนี้กำลังถูกย่อให้เล็กสุด แต่ท้ายที่สุดก็ยังคงขับเคลื่อนด้วยมุมต่ำสุดของโลกซึ่งยังคงช้ากว่าจะเพิ่มขึ้นเรื่อยๆ ไอเดียที่ฉันต้องปรับปรุง โดยทั่วไปแล้วจะใช้ได้ในกรณี 2 มิตินี้ แต่ไม่ใช่ในมิติที่สูงกว่า
แต่การเน้นที่คุณภาพของความพยายามทางคณิตศาสตร์มากเกินไปก็ขาดประเด็น ดูจำนวนการดำเนินการเทนเซอร์ที่เกี่ยวข้องกับการคำนวณค่าเฉลี่ยและความแปรปรวน และวิธีที่ TensorFlow ติดตามและแยกความแตกต่างของการคำนวณทั้งหมดสำหรับทุกองค์ประกอบในเมทริกซ์อินพุต และเราไม่ต้องคิดคำนวณเอง เราเพิ่งผสมคณิตศาสตร์ง่ายๆ เข้าด้วยกัน แล้ว TensorFlow ก็คำนวณแคลคูลัสให้เรา
สุดท้าย มาลองอีกสิ่งหนึ่ง: โซลูชันแบบใช้กำลัง ลองนึกภาพว่าเวกเตอร์ทุกตัวเป็นดาวเคราะห์ขนาดเล็กที่ผูกติดกับจุดศูนย์กลาง ดาวเคราะห์แต่ละดวงปล่อยพลังที่ขับไล่มันออกจากดาวเคราะห์ดวงอื่น ถ้าเราจะทำการจำลองทางฟิสิกส์ของแบบจำลองนี้ เราก็ควรจะได้คำตอบที่ต้องการ
สมมติฐานของฉันก็คือการไล่ระดับแบบไล่ระดับก็ควรได้ผลเช่นกัน ในการแก้ปัญหาที่เหมาะสม แรงสัมผัสบนดาวเคราะห์ทุกดวงจากดาวเคราะห์ดวงอื่นควรหักล้างกันจนกลายเป็นแรงสุทธิเป็นศูนย์ (ถ้าไม่ใช่ศูนย์ ดาวเคราะห์ก็จะเคลื่อนที่) ลองคำนวณขนาดแรงบนเวกเตอร์ทุกตัวแล้วใช้การไล่ระดับความชันเพื่อผลักมันเข้าหาศูนย์
อันดับแรก เราต้องกำหนดวิธีการที่คำนวณแรงโดยใช้วิธี tf.*
:
class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component
จากนั้น เรากำหนดฟังก์ชันการสูญเสียของเราโดยใช้ฟังก์ชันแรงด้านบน เราสะสมแรงสุทธิบนเวกเตอร์แต่ละตัวและคำนวณขนาดของมัน ในแนวทางที่เหมาะสมที่สุด แรงทั้งหมดควรตัดกันและเราควรมีแรงเป็นศูนย์

def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list))
โซลูชันไม่เพียงแค่ทำงานได้อย่างสวยงามเท่านั้น (นอกเหนือจากความโกลาหลในสองสามเฟรมแรก) แต่เครดิตที่แท้จริงไปที่ TensorFlow โซลูชันนี้เกี่ยวข้อง for
ลูปหลายตัว คำสั่ง if
และเว็บการคำนวณขนาดใหญ่ และ TensorFlow ติดตามการไล่ระดับได้สำเร็จผ่านทั้งหมดของเรา
ตัวอย่างที่ 3: การสร้างอินพุต AI ที่เป็นปฏิปักษ์
ตัวอย่างที่ 3 สมุดบันทึก
ณ จุดนี้ ผู้อ่านอาจคิดว่า "เฮ้ โพสต์นี้ไม่เกี่ยวกับการเรียนรู้อย่างลึกซึ้ง!" แต่ในทางเทคนิคแล้ว บทนำนี้หมายถึงการก้าวไปไกลกว่า " การฝึก โมเดลการเรียนรู้เชิงลึก" ในกรณีนี้ เราไม่ได้ ฝึก แต่ใช้ประโยชน์จากคุณสมบัติทางคณิตศาสตร์บางอย่างของเครือข่ายประสาทเทียมระดับลึกที่ได้รับการฝึกฝนมาล่วงหน้าเพื่อหลอกให้มันให้ผลลัพธ์ที่ไม่ถูกต้องแก่เรา กลายเป็นว่าง่ายและมีประสิทธิภาพมากกว่าที่จินตนาการไว้มาก และทั้งหมดที่ใช้ก็คือโค้ด TensorFlow 2.0 สั้นๆ อีกหยดหนึ่ง
เราเริ่มต้นด้วยการค้นหาตัวแยกประเภทภาพเพื่อโจมตี เราจะใช้หนึ่งในโซลูชั่นชั้นนำในการแข่งขัน Dogs vs. Cats Kaggle; โดยเฉพาะโซลูชันที่นำเสนอโดย Kaggler "uysimty" เครดิตทั้งหมดสำหรับการจัดหาโมเดล cat-vs-dog ที่มีประสิทธิภาพและจัดเตรียมเอกสารที่ยอดเยี่ยม นี่คือโมเดลอันทรงพลังที่ประกอบด้วยพารามิเตอร์ 13 ล้านตัวใน 18 เลเยอร์โครงข่ายประสาทเทียม (ผู้อ่านสามารถอ่านเพิ่มเติมได้ในสมุดบันทึกที่เกี่ยวข้อง)
โปรดทราบว่าเป้าหมายในที่นี้ไม่ได้เน้นย้ำถึงข้อบกพร่องใดๆ ในเครือข่ายนี้ แต่เพื่อแสดงให้เห็นว่า โครงข่ายประสาทเทียมมาตรฐานใดๆ ที่มีอินพุตจำนวนมากมีความเสี่ยงอย่างไร
ด้วยการดัดแปลงเล็กน้อย ฉันสามารถหาวิธีโหลดโมเดลและประมวลผลภาพล่วงหน้าเพื่อจัดประเภทตามนั้น
นี้ดูเหมือนลักษณนามที่แข็งแกร่งจริงๆ! การจำแนกประเภทตัวอย่างทั้งหมดถูกต้องและมีความมั่นใจสูงกว่า 95% มาโจมตีกันเถอะ!
เราต้องการสร้างภาพที่เห็นได้ชัดว่าเป็นแมว แต่ให้ผู้จำแนกประเภทตัดสินใจว่าเป็นสุนัขที่มีความมั่นใจสูง เราจะทำอย่างนั้นได้อย่างไร?
เริ่มจากรูปภาพแมวที่จัดประเภทอย่างถูกต้อง จากนั้นให้หาว่าการปรับเปลี่ยนเล็กๆ น้อยๆ ในแต่ละช่องสี (ค่า 0-255) ของพิกเซลอินพุตที่กำหนดส่งผลต่อเอาต์พุตตัวแยกประเภทสุดท้ายอย่างไร การปรับเปลี่ยนหนึ่งพิกเซลอาจจะไม่ช่วยอะไรมาก แต่บางทีการปรับแต่งแบบสะสมของค่า 128x128x3 = 49,152 พิกเซลทั้งหมดจะบรรลุเป้าหมายของเรา
เราจะรู้ได้อย่างไรว่าต้องผลักแต่ละพิกเซลไปทางไหน? ในระหว่างการฝึกอบรมโครงข่ายประสาทเทียมแบบปกติ เราพยายามลดการสูญเสียระหว่างป้ายกำกับเป้าหมายและป้ายกำกับที่คาดการณ์ไว้ โดยใช้การไล่ระดับสีใน TensorFlow เพื่ออัปเดตพารามิเตอร์ฟรีทั้งหมด 13 ล้านรายการพร้อมกัน ในกรณีนี้ เราจะปล่อยให้พารามิเตอร์ 13 ล้านคงที่และปรับค่าพิกเซลของอินพุตเอง
ฟังก์ชั่นการสูญเสียของเราคืออะไร? ก็แล้วแต่ภาพจะเหมือนแมวขนาดไหน! หากเราคำนวณอนุพันธ์ของค่า cat เทียบกับพิกเซลอินพุตแต่ละอัน เราทราบวิธีที่จะผลักดันแต่ละค่าแต่ละอันเพื่อลดความน่าจะเป็นของการจำแนกประเภท cat
def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad
Matplotlib magic อีกครั้งช่วยให้เห็นภาพผลลัพธ์
ว้าว! ในสายตามนุษย์ แต่ละภาพมีความเหมือนกัน แต่หลังจากการทำซ้ำสี่ครั้ง เราได้โน้มน้าวให้ลักษณนามว่านี่คือสุนัข ด้วยความมั่นใจ 99.4 เปอร์เซ็นต์!
ตรวจสอบให้แน่ใจว่านี่ไม่ใช่ความบังเอิญและทำงานในอีกทางหนึ่งด้วย
ความสำเร็จ! เดิมลักษณนามทำนายสิ่งนี้อย่างถูกต้องในฐานะสุนัขที่มีความมั่นใจ 98.4 เปอร์เซ็นต์ และตอนนี้เชื่อว่าเป็นแมวที่มีความมั่นใจ 99.8 เปอร์เซ็นต์
สุดท้ายนี้ เรามาดูตัวอย่างการแพตช์รูปภาพและดูว่ามีการเปลี่ยนแปลงอย่างไร
ตามที่คาดไว้ แพทช์สุดท้ายจะคล้ายกับต้นฉบับมาก โดยแต่ละพิกเซลจะเปลี่ยนเพียง -4 เป็น +4 ในค่าความเข้มของช่องสีแดง การเปลี่ยนแปลงนี้ไม่เพียงพอสำหรับมนุษย์ที่จะแยกแยะความแตกต่าง แต่เปลี่ยนผลลัพธ์ของตัวแยกประเภทโดยสิ้นเชิง
ความคิดสุดท้าย: การเพิ่มประสิทธิภาพการไล่ระดับสี
ในบทความนี้ เราได้พิจารณาการใช้การไล่ระดับสีด้วยตนเองกับพารามิเตอร์ที่ฝึกได้ของเรา เพื่อความง่ายและความโปร่งใส อย่างไรก็ตาม ในโลกแห่งความเป็นจริง นักวิทยาศาสตร์ด้านข้อมูลควรเริ่มใช้งาน เครื่องมือเพิ่มประสิทธิภาพ เพราะพวกเขามักจะมีประสิทธิภาพมากกว่ามาก โดยไม่ต้องเพิ่มโค้ดใด ๆ
มีเครื่องมือเพิ่มประสิทธิภาพที่ได้รับความนิยมมากมาย รวมถึง RMSprop, Adagrad และ Adadelta แต่ที่พบบ่อยที่สุดคือ Adam บางครั้งเรียกว่า "วิธีการปรับอัตราการเรียนรู้" เพราะพวกเขารักษาอัตราการเรียนรู้ที่แตกต่างกันแบบไดนามิกสำหรับแต่ละพารามิเตอร์ หลายคนใช้เงื่อนไขโมเมนตัมและประมาณอนุพันธ์อันดับสูงกว่า โดยมีเป้าหมายในการหลีกเลี่ยงค่าต่ำสุดในพื้นที่และบรรลุการบรรจบกันที่เร็วขึ้น
ในแอนิเมชั่นที่ยืมมาจาก Sebastian Ruder เราจะเห็นเส้นทางของเครื่องมือเพิ่มประสิทธิภาพต่างๆ ที่ลงมายังพื้นผิวที่สูญเสีย เทคนิคแบบแมนนวลที่เราได้แสดงให้เห็นนั้นเทียบได้กับ “SGD” มากที่สุด เครื่องมือเพิ่มประสิทธิภาพที่ทำงานได้ดีที่สุดจะไม่เหมือนกันในทุกพื้นผิวที่สูญเสีย อย่างไรก็ตาม เครื่องมือเพิ่มประสิทธิภาพขั้นสูงมัก จะ ทำงานได้ดีกว่าตัวที่ง่ายกว่า
อย่างไรก็ตาม การเป็นผู้เชี่ยวชาญด้านเครื่องมือเพิ่มประสิทธิภาพนั้นไม่ค่อยมีประโยชน์ แม้แต่กับผู้ที่ต้องการให้บริการพัฒนาปัญญาประดิษฐ์ เป็นการใช้เวลาของนักพัฒนามากขึ้นในการทำความคุ้นเคยกับคู่รัก เพียงเพื่อทำความเข้าใจว่าพวกเขาปรับปรุงการไล่ระดับการไล่ระดับสีใน TensorFlow ได้อย่างไร หลังจากนั้น พวกเขาสามารถใช้ Adam เป็นค่าเริ่มต้นและลองใช้อันอื่นก็ต่อเมื่อโมเดลของพวกเขาไม่ได้มาบรรจบกัน
สำหรับผู้อ่านที่สนใจจริงๆ ว่าเครื่องมือเพิ่มประสิทธิภาพเหล่านี้ทำงานอย่างไรและเพราะเหตุใด ภาพรวมของ Ruder ซึ่งมีภาพเคลื่อนไหวปรากฏขึ้น เป็นหนึ่งในแหล่งข้อมูลที่ดีที่สุดและละเอียดถี่ถ้วนที่สุดในหัวข้อนี้
มาอัปเดตโซลูชันการถดถอยเชิงเส้นของเราจากส่วนแรกเพื่อใช้เครื่องมือเพิ่มประสิทธิภาพ ต่อไปนี้เป็นรหัสการไล่ระดับสีดั้งเดิมโดยใช้การไล่ระดับสีแบบแมนนวล
# Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]
นี่คือรหัสเดียวกันโดยใช้เครื่องมือเพิ่มประสิทธิภาพแทน คุณจะเห็นว่ามันแทบไม่มีโค้ดพิเศษใดๆ (บรรทัดที่เปลี่ยนแปลงจะถูกเน้นด้วยสีน้ำเงิน):
# Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))
แค่นั้นแหละ! เรากำหนดเครื่องมือเพิ่มประสิทธิภาพ RMSprop
นอกลูปการไล่ระดับสี จากนั้นเราใช้เมธอด optimizer.apply_gradients()
หลังจากการคำนวณการไล่ระดับสีแต่ละครั้งเพื่ออัปเดตพารามิเตอร์ที่ฝึกได้ เครื่องมือเพิ่มประสิทธิภาพถูกกำหนดไว้นอกลูปเพราะจะติดตามการไล่ระดับสีในอดีตสำหรับการคำนวณเงื่อนไขพิเศษเช่นโมเมนตัมและอนุพันธ์อันดับสูงกว่า
มาดูกันว่ามันมีลักษณะอย่างไรกับเครื่องมือเพิ่มประสิทธิภาพ RMSprop
ดูดี! คราวนี้มาลองใช้กับ Adam Optimizer กัน
ห๊ะ นี่มันเกิดอะไรขึ้น? ดูเหมือนว่ากลไกโมเมนตัมในอดัมทำให้มันพุ่งเกินวิธีแก้ปัญหาที่เหมาะสมและย้อนกลับหลายครั้ง โดยปกติกลไกโมเมนตัมนี้จะช่วยในเรื่องการสูญเสียพื้นผิวที่ซับซ้อน แต่มันทำให้เราเจ็บปวดในกรณีง่ายๆ นี้ สิ่งนี้เน้นย้ำคำแนะนำในการเลือกเครื่องมือเพิ่มประสิทธิภาพหนึ่งในไฮเปอร์พารามิเตอร์เพื่อปรับแต่งเมื่อฝึกโมเดลของคุณ
ใครก็ตามที่ต้องการสำรวจการเรียนรู้เชิงลึกจะต้องการทำความคุ้นเคยกับรูปแบบนี้ เนื่องจากมีการใช้กันอย่างแพร่หลายในสถาปัตยกรรม TensorFlow แบบกำหนดเอง ซึ่งไม่จำเป็นต้องมีกลไกการสูญเสียที่ซับซ้อนซึ่งไม่ได้รวมไว้ในเวิร์กโฟลว์มาตรฐานอย่างง่ายดาย ในตัวอย่างการไล่ระดับสี TensorFlow อย่างง่ายนี้ มีเพียงพารามิเตอร์ที่ฝึกได้เพียงสองพารามิเตอร์ แต่จำเป็นเมื่อทำงานกับสถาปัตยกรรมที่มีพารามิเตอร์หลายร้อยล้านตัวเพื่อปรับให้เหมาะสม
Gradient Descent ใน TensorFlow: จากการค้นหาค่าต่ำสุดไปจนถึงการโจมตีระบบ AI
ตัวอย่างโค้ดและรูปภาพทั้งหมดสร้างจากโน้ตบุ๊กใน repo GitHub ที่เกี่ยวข้อง นอกจากนี้ยังมีข้อมูลสรุปของส่วนต่างๆ พร้อมลิงก์ไปยังสมุดบันทึกแต่ละรายการ สำหรับผู้อ่านที่ต้องการดูโค้ดทั้งหมด เพื่อลดความซับซ้อนของข้อความ เราจึงทิ้งรายละเอียดไว้มากมายซึ่งสามารถพบได้ในเอกสารประกอบแบบอินไลน์ที่กว้างขวาง
ฉันหวังว่าบทความนี้มีความเข้าใจอย่างลึกซึ้ง และทำให้คุณคิดเกี่ยวกับวิธีใช้การไล่ระดับการไล่ระดับสีใน TensorFlow แม้ว่าคุณจะไม่ได้ใช้มันด้วยตัวเอง หวังว่ามันจะทำให้ชัดเจนยิ่งขึ้นว่าสถาปัตยกรรมเครือข่ายนิวรัลสมัยใหม่ทั้งหมดทำงานอย่างไร—สร้างแบบจำลอง กำหนดฟังก์ชันการสูญเสีย และใช้การไล่ระดับสีแบบลงลึกเพื่อให้พอดีกับโมเดลกับชุดข้อมูลของคุณ
ในฐานะ Google Cloud Partner ผู้เชี่ยวชาญที่ผ่านการรับรองจาก Google ของ Toptal พร้อมให้บริการสำหรับบริษัทต่างๆ ที่ต้องการสำหรับโครงการที่สำคัญที่สุดของพวกเขา