เหตุใด ReLU เดียวจึงไม่สามารถเรียนรู้ ReLU ได้


15

จากการติดตามเครือข่ายประสาทของฉันไม่สามารถเรียนรู้ระยะทางแบบยุคลิดฉันได้ง่ายขึ้นและพยายามฝึกฝน ReLU เดี่ยว (ด้วยน้ำหนักสุ่ม) ไปยัง ReLU เดียว นี่คือเครือข่ายที่ง่ายที่สุดที่มีอยู่และยังครึ่งหนึ่งของเวลาที่มันไม่มาบรรจบกัน

หากการเดาเริ่มต้นอยู่ในทิศทางเดียวกับเป้าหมายมันจะเรียนรู้ได้อย่างรวดเร็วและแปลงเป็นน้ำหนักที่ถูกต้อง 1:

แอนิเมชั่นของ ReLU learning ReLU

เส้นโค้งการสูญเสียแสดงจุดบรรจบ

หากการเดาเริ่มต้นคือ "ถอยหลัง" มันจะติดอยู่ที่น้ำหนักเป็นศูนย์และไม่ผ่านไปยังพื้นที่ที่มีการสูญเสียต่ำกว่า:

ภาพเคลื่อนไหวของ ReLU ล้มเหลวในการเรียนรู้ ReLU

เส้นโค้งการสูญเสียของ ReLU ไม่สามารถเรียนรู้ ReLU

โคลสอัพของเส้นโค้งการสูญเสียที่ 0

ฉันไม่เข้าใจว่าทำไม โคตรลาดชันไม่ควรทำตามโค้งการสูญเสียไปสู่มินิมาระดับโลกอย่างง่ายดายหรือไม่?

รหัสตัวอย่าง:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, ReLU
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt

batch = 1000


def tests():
    while True:
        test = np.random.randn(batch)

        # Generate ReLU test case
        X = test
        Y = test.copy()
        Y[Y < 0] = 0

        yield X, Y


model = Sequential([Dense(1, input_dim=1, activation=None, use_bias=False)])
model.add(ReLU())
model.set_weights([[[-10]]])

model.compile(loss='mean_squared_error', optimizer='sgd')


class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.losses = []
        self.weights = []
        self.n = 0
        self.n += 1

    def on_epoch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))
        w = model.get_weights()
        self.weights.append([x.flatten()[0] for x in w])
        self.n += 1


history = LossHistory()

model.fit_generator(tests(), steps_per_epoch=100, epochs=20,
                    callbacks=[history])

fig, (ax1, ax2) = plt.subplots(2, 1, True, num='Learning')

ax1.set_title('ReLU learning ReLU')
ax1.semilogy(history.losses)
ax1.set_ylabel('Loss')
ax1.grid(True, which="both")
ax1.margins(0, 0.05)

ax2.plot(history.weights)
ax2.set_ylabel('Weight')
ax2.set_xlabel('Epoch')
ax2.grid(True, which="both")
ax2.margins(0, 0.05)

plt.tight_layout()
plt.show()

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งที่คล้ายกันเกิดขึ้นถ้าฉันเพิ่มอคติ: ฟังก์ชั่นการสูญเสีย 2D ราบรื่นและเรียบง่าย แต่ถ้า relu เริ่มกลับด้านมันจะวนไปมาและติดอยู่ (จุดเริ่มต้นสีแดง) และไม่ไล่ตามการไล่ระดับสีลงไปต่ำสุด ทำเพื่อจุดเริ่มต้นสีน้ำเงิน):

ป้อนคำอธิบายรูปภาพที่นี่

สิ่งที่คล้ายกันเกิดขึ้นถ้าฉันเพิ่มน้ำหนักและอคติด้วยเช่นกัน (มันจะพลิกจากซ้ายไปขวาหรือเลื่อนขึ้น - ลง แต่ไม่ใช่ทั้งคู่)


3
@Sycorax ไม่มีสิ่งนี้ซ้ำซ้อน แต่จะถามถึงปัญหาเฉพาะไม่ใช่คำแนะนำทั่วไป ฉันใช้เวลาเป็นจำนวนมากในการลดให้เหลือน้อยที่สุดตัวอย่างที่สมบูรณ์และตรวจสอบได้ โปรดอย่าลบเพียงเพราะมันคล้ายกับคำถามทั่ว ๆ ไป หนึ่งในขั้นตอนในคำตอบที่ยอมรับสำหรับคำถามนั้นคือ "ขั้นแรกสร้างเครือข่ายขนาดเล็กที่มีเลเยอร์ที่ซ่อนอยู่หนึ่งชั้นและตรวจสอบว่าทำงานได้อย่างถูกต้องจากนั้นเพิ่มความซับซ้อนของโมเดลเพิ่มเติมทีละส่วน นั่นคือสิ่งที่ฉันทำและมันไม่ทำงาน
endolith

2
ฉันสนุกกับ "ซีรี่ย์" นี้ใน NN ที่ใช้กับฟังก์ชั่นง่าย ๆ : eats_popcorn_gif:
Cam.Davidson.Pilon

ฟังก์ชั่นReLUเช่น rectifier ในอุดมคติเช่น diode มันเป็นทิศทางเดียว หากคุณต้องการทิศทางที่ถูกต้องให้พิจารณาใช้ softplus จากนั้นเปลี่ยนเป็น ReLU เมื่อการฝึกอบรมเป็นบวกหรือใช้ตัวแปรอื่น ๆ เช่น ELU
Carl

ในการพูดแบบนี้อีกวิธีหนึ่ง ReLU คาดว่าจะไร้ประโยชน์สำหรับดูที่การเรียนรู้สำหรับ ; มันแบนไม่เรียนรู้ x < 0x<0x<0
Carl

1
การไล่ระดับสีมีแนวโน้มที่จะเป็นศูนย์สำหรับต่ำกว่าศูนย์; มันแผงลอย x
คาร์ล

คำตอบ:


14

มีคำแนะนำในแปลงของคุณของการสูญเสียเป็นหน้าที่ของเป็นWแปลงเหล่านี้มี "kink" ใกล้ : นั่นเป็นเพราะด้านซ้ายของ 0 การไล่ระดับสีของการสูญเสียจะหายไปเป็น 0 (อย่างไรก็ตามเป็นทางออกที่ไม่ดีเพราะการสูญเสียสูงกว่าสำหรับ ) นอกจากนี้พล็อตนี้แสดงให้เห็นว่าฟังก์ชั่นการสูญเสียไม่ใช่แบบนูน (คุณสามารถวาดเส้นที่ข้ามเส้นโค้งการสูญเสียใน 3 ตำแหน่งหรือมากกว่า) เพื่อให้สัญญาณว่าเราควรระมัดระวังเมื่อใช้เครื่องมือเพิ่มประสิทธิภาพท้องถิ่นเช่น SGD การวิเคราะห์ต่อไปนี้แสดงให้เห็นว่าเมื่อถูกกำหนดค่าเริ่มต้นให้เป็นค่าลบก็เป็นไปได้ที่จะมาบรรจบกันเป็นวิธีแก้ปัญหาที่ไม่ดีww=0w = 0 w = 1 ww=0w=1w

ปัญหาการเหมาะสมคือ

minw,bf(x)y22f(x)=max(0,wx+b)

และคุณกำลังใช้การปรับให้เหมาะสมอันดับแรกเพื่อทำเช่นนั้น ปัญหาของวิธีนี้คือมีการไล่ระดับสีf

f(x)={w,if x>00,if x<0

เมื่อคุณเริ่มต้นด้วยคุณจะต้องย้ายไปที่ด้านอื่น ๆ ของจะมาใกล้ชิดกับคำตอบที่ถูกต้องซึ่งเป็น 1 สิ่งนี้ทำยากเพราะเมื่อคุณมีมากน้อยมากการไล่ระดับสีก็จะกลายเป็นขนาดเล็กเช่นกัน ยิ่งคุณเข้าใกล้มากถึง 0 จากด้านซ้ายความคืบหน้าของคุณก็จะช้าลงเท่านั้น!w<00w=1|w|

นี่คือเหตุผลที่ในแปลงของคุณสำหรับ initializations ที่เป็นลบ , ไบร์ทของคุณทุกแผงอยู่ใกล้กับ 0 นี่เป็นสิ่งที่แอนิเมชั่นที่สองของคุณแสดงw(0)<0w(i)=0

เรื่องนี้เกี่ยวข้องกับปรากฏการณ์ relu ที่กำลังจะตาย สำหรับการสนทนาดูที่เครือข่าย ReLU ของฉันไม่สามารถเปิดได้

วิธีที่อาจประสบความสำเร็จมากกว่านั้นก็คือการใช้ความไม่เชิงเส้นที่แตกต่างกันเช่น relu ที่รั่วซึ่งไม่มีปัญหาที่เรียกว่า "การไล่ระดับสีที่หายไป" ฟังก์ชั่น relu ที่รั่วไหลคือ

g(x)={x,if x>0cx,otherwise
โดยที่เป็นค่าคงที่ดังนั้นมีขนาดเล็กและเป็นบวก เหตุผลที่ใช้งานได้คืออนุพันธ์ไม่ใช่ 0 "ทางซ้าย"c|c|

g(x)={1,if x>0c,if x<0

การตั้งค่าเป็น relu สามัญ คนส่วนใหญ่เลือกจะเป็นสิ่งที่ชอบหรือ0.3ฉันไม่ได้เห็นการใช้ถึงแม้ว่าฉันสนใจที่จะดูการศึกษาว่ามีผลกระทบอย่างไรหากมีในเครือข่ายดังกล่าว (โปรดทราบว่าสำหรับสิ่งนี้จะลดไปที่ฟังก์ชันเอกลักษณ์สำหรับองค์ประกอบของเลเยอร์จำนวนมากอาจทำให้เกิดการไล่ระดับสีแบบระเบิดเนื่องจากการไล่ระดับสีมีขนาดใหญ่ขึ้นในชั้นที่ต่อเนื่องกัน)c=0c0.10.3c<0c=1,|c|>1

การแก้ไขโค้ด OP เล็กน้อยให้การสาธิตว่าปัญหาอยู่ที่ตัวเลือกของฟังก์ชั่นการเปิดใช้งาน รหัสนี้เริ่มต้นจะเป็นเชิงลบและใช้ในสถานที่ของธรรมดา การสูญเสียลดลงอย่างรวดเร็วเป็นค่าน้อยและน้ำหนักถูกต้องย้ายไปที่ซึ่งเป็นสิ่งที่ดีที่สุดww = 1LeakyReLUReLUw=1

LeakyReLU แก้ไขปัญหา

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, ReLU
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt

batch = 1000


def tests():
    while True:
        test = np.random.randn(batch)

        # Generate ReLU test case
        X = test
        Y = test.copy()
        Y[Y < 0] = 0

        yield X, Y


model = Sequential(
    [Dense(1, 
           input_dim=1, 
           activation=None, 
           use_bias=False)
    ])
model.add(keras.layers.LeakyReLU(alpha=0.3))
model.set_weights([[[-10]]])

model.compile(loss='mean_squared_error', optimizer='sgd')


class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.losses = []
        self.weights = []
        self.n = 0
        self.n += 1

    def on_epoch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))
        w = model.get_weights()
        self.weights.append([x.flatten()[0] for x in w])
        self.n += 1


history = LossHistory()

model.fit_generator(tests(), steps_per_epoch=100, epochs=20,
                    callbacks=[history])

fig, (ax1, ax2) = plt.subplots(2, 1, True, num='Learning')

ax1.set_title('LeakyReLU learning ReLU')
ax1.semilogy(history.losses)
ax1.set_ylabel('Loss')
ax1.grid(True, which="both")
ax1.margins(0, 0.05)

ax2.plot(history.weights)
ax2.set_ylabel('Weight')
ax2.set_xlabel('Epoch')
ax2.grid(True, which="both")
ax2.margins(0, 0.05)

plt.tight_layout()
plt.show()

ความซับซ้อนอีกชั้นเกิดขึ้นจากความจริงที่ว่าเราไม่ได้เคลื่อนไหวอย่างไร้ขอบเขต แต่ใน "การกระโดด" และการกระโดดเหล่านี้จะนำเราจากการทำซ้ำครั้งต่อไป ซึ่งหมายความว่ามีบางสถานการณ์ที่ค่าลบเริ่มต้นของจะไม่ติด; กรณีเหล่านี้เกิดขึ้นสำหรับการรวมกันโดยเฉพาะอย่างยิ่งของและขนาดการไล่ระดับสีที่ลาดลงขนาดใหญ่พอที่จะ "กระโดด" เหนือการไล่ระดับสีที่หายไปw w ( 0 )w(0)

ฉันได้เล่นกับโค้ดนี้บ้างแล้วและฉันพบว่าการปล่อยให้ค่าเริ่มต้นที่และการเปลี่ยนออพติไมเซอร์จาก SGD เป็น Adam, Adam + AMSGrad หรือ SGD + ไม่ช่วยอะไรเลย ยิ่งกว่านั้นการเปลี่ยนจาก SGD เป็น Adam ทำให้ความคืบหน้าช้าลงและไม่ช่วยในการแก้ไขปัญหานี้w(0)=10

ในทางกลับกันถ้าคุณเปลี่ยนค่าเริ่มต้นเป็นและเปลี่ยนเครื่องมือเพิ่มประสิทธิภาพเป็น Adam (ขนาดขั้นตอน 0.01) คุณสามารถเอาชนะการไล่ระดับสีที่หายไปได้จริง นอกจากนี้ยังใช้งานได้หากคุณใช้และ SGD พร้อมโมเมนตัม (ขนาดขั้นตอน 0.01) มันยังทำงานถ้าคุณใช้วานิลลา SGD (ขั้นตอนขนาด 0.01) และ 1w(0)=1 w ( 0 ) = - 1 w ( 0 ) = - 1w(0)=1w(0)=1

รหัสที่เกี่ยวข้องอยู่ด้านล่าง ใช้หรือopt_sgdopt_adam

opt_sgd = keras.optimizers.SGD(lr=1e-2, momentum=0.9)
opt_adam = keras.optimizers.Adam(lr=1e-2, amsgrad=True)
model.compile(loss='mean_squared_error', optimizer=opt_sgd)

ฉันเห็นปัญหาเดียวกันกับ LeakyReLU, ELU, SELU เมื่อฉันมีน้ำหนักและอคติออกมา แต่ฉันไม่แน่ใจว่าฉันลองใช้ที่ไม่มีผลลัพธ์หรือไม่ ฉันจะตรวจสอบ
endolith

1
(ใช่คุณพูดถูกว่า LeakyReLU และ ELU ทำงานได้ดีสำหรับตัวอย่างนี้)
endolith

2
อ้อ! ฉันเข้าใจแล้ว. มันจะทำโคตรลาดของฟังก์ชั่นการสูญเสียก็เพียงว่าฟังก์ชั่นการสูญเสียจะกลายเป็นแบน (0 ลาด) ที่ 0 เมื่อใกล้เข้ามาจากด้านลบเพื่อเชื้อสายไล่ระดับสีที่ได้รับการติดอยู่ที่นั่น ตอนนี้ดูเหมือนชัดเจน : D
endolith

2
เผง สังเกตุว่าพล็อตการสูญเสียของคุณเทียบกับมี "kink" ใกล้ 0: นั่นเป็นเพราะทางซ้ายของ 0 การไล่ระดับสีของการสูญเสียจะหายไปเป็น 0 (อย่างไรก็ตามนี่เป็นวิธีแก้ปัญหาที่ไม่ดี สำหรับ ) นอกจากนี้พล็อตนี้แสดงให้เห็นว่าฟังก์ชั่นการสูญเสียไม่ใช่แบบนูน (คุณสามารถวาดเส้นที่ข้ามเส้นโค้งการสูญเสียใน 3 ตำแหน่งหรือมากกว่า) เพื่อให้สัญญาณว่าเราควรระมัดระวังเมื่อใช้เครื่องมือเพิ่มประสิทธิภาพท้องถิ่นเช่น SGD w = 0ww=0
Sycorax พูดว่า Reinstate Monica

2
เมื่อใช้การเปิดใช้งาน Relu แม้ SGD โดยไม่ต้องโมเมนตัมสามารถไปกว่าริมฝีปากถ้าขนาดขั้นตอนคือขนาดใหญ่พอสำหรับสิ่งที่คุ้มค่าเฉพาะของ{(i)} w(i)
Sycorax พูดว่า Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.