Tại sao một ReLU không thể học ReLU?


15

Khi theo dõi mạng lưới thần kinh của tôi, tôi thậm chí không thể học được khoảng cách Euclide, tôi đã đơn giản hóa hơn nữa và cố gắng huấn luyện một ReLU duy nhất (với trọng lượng ngẫu nhiên) thành một ReLU duy nhất. Đây là mạng đơn giản nhất có, và một nửa thời gian nó không hội tụ.

Nếu dự đoán ban đầu có cùng hướng với mục tiêu, nó sẽ học nhanh và hội tụ đúng trọng số 1:

hoạt hình của ReLU học ReLU

đường cong mất điểm cho thấy điểm hội tụ

Nếu dự đoán ban đầu là "ngược", nó sẽ bị kẹt ở mức 0 và không bao giờ đi qua vùng bị mất:

Hoạt hình của ReLU không học ReLU

đường cong mất của ReLU không học ReLU

Cận cảnh đường cong mất 0

Tôi không hiểu tại sao. Không nên giảm độ dốc dễ dàng theo đường cong mất đến cực tiểu toàn cầu?

Mã ví dụ:

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()

nhập mô tả hình ảnh ở đây

Điều tương tự cũng xảy ra nếu tôi thêm sai lệch: Hàm mất 2D rất đơn giản và đơn giản, nhưng nếu Relu bắt đầu lộn ngược, nó sẽ quay vòng và bị kẹt (điểm bắt đầu màu đỏ) và không theo độ dốc xuống mức tối thiểu (như nó không cho điểm bắt đầu màu xanh):

nhập mô tả hình ảnh ở đây

Điều tương tự cũng xảy ra nếu tôi thêm trọng lượng đầu ra và thiên vị, quá. (Nó sẽ lật từ trái sang phải hoặc từ trên xuống, nhưng không lật cả hai.)


3
@Sycorax Không phải đây không phải là một bản sao, nó hỏi về một vấn đề cụ thể, không phải lời khuyên chung chung. Tôi đã dành một lượng thời gian đáng kể để giảm điều này xuống một ví dụ Tối thiểu, Hoàn thành và Có thể kiểm chứng. Xin đừng xóa nó chỉ vì nó mơ hồ tương tự như một số câu hỏi quá rộng khác. Một trong những bước trong câu trả lời được chấp nhận cho câu hỏi đó là "Đầu tiên, xây dựng một mạng nhỏ với một lớp ẩn duy nhất và xác minh rằng nó hoạt động chính xác. Sau đó, tăng dần độ phức tạp của mô hình bổ sung và xác minh rằng mỗi mạng đó cũng hoạt động tốt." Đó chính xác là những gì tôi đang làm và nó không hoạt động.
endolith

2
Tôi thực sự thích "loạt" này trên NN được áp dụng cho các chức năng đơn giản: eats_popcorn_gif:
Cam.Davidson.Pilon

ReLU hoạt động như một bộ chỉnh lưu lý tưởng, ví dụ, một diode. Nó là đơn hướng. Nếu bạn muốn hướng chính xác, hãy cân nhắc sử dụng softplus, sau đó chuyển sang ReLU khi việc đào tạo là tích cực hoặc sử dụng một số biến thể khác như ELU.
Carl

Nói cách khác, ReLU dự kiến ​​sẽ vô dụng đối với , hãy nhìn vào việc học cho ; Nó bằng phẳng, nó không học. x<0x<0
Carl

1
x

Câu trả lời:


14

ww= =0w= =0w= =1w được khởi tạo là âm tính, có thể hội tụ thành một giải pháp tối ưu.

tối thiểuw,bf(x)-y22f(x)= =tối đa(0,wx+b)

và bạn đang sử dụng tối ưu hóa đơn hàng đầu tiên để làm như vậy. Một vấn đề với cách tiếp cận này là có độ dốcf

f'(x)= ={w,nếu x>00,nếu x<0

Khi bạn bắt đầu với , bạn sẽ phải di chuyển sang phía bên kia của để đến gần hơn với câu trả lời đúng, đó là . Điều này thật khó để làm, bởi vì khi bạn córất, rất nhỏ, độ dốc cũng sẽ trở nên nhỏ bé. Hơn nữa, bạn càng tiến gần đến 0 từ bên trái, tiến độ của bạn sẽ càng chậm!w<00w= =1|w|

Đây là lý do tại sao trong âm mưu của bạn cho các khởi tạo âm , tất cả các quỹ đạo của bạn bị đình trệ gần . Đây cũng là những gì hoạt hình thứ hai của bạn đang hiển thị.w(0)<0w(Tôi)= =0

Điều này có liên quan đến hiện tượng Relu sắp chết; để thảo luận, hãy xem mạng ReLU của tôi không khởi chạy được

Một cách tiếp cận có thể thành công hơn là sử dụng một phi tuyến khác như relu bị rò rỉ, không có vấn đề gọi là "độ dốc biến mất". Hàm Relu bị rò rỉ là

g(x)= ={x,nếu x>0cx,nếu không thì
trong đó là hằng số sao cholà nhỏ và tích cực. Lý do mà công việc này là đạo hàm không 0 "ở bên trái."c|c|

g'(x)= ={1,nếu x>0c,nếu x<0

Đặt là relu thông thường. Hầu hết mọi người chọn là một cái gì đó như hoặc . Tôi chưa thấy được sử dụng, mặc dù tôi rất muốn xem một nghiên cứu về tác dụng gì, nếu có, nó có trên các mạng như vậy. (Lưu ý rằng với điều này làm giảm chức năng nhận dạng; đối với , các tác phẩm của nhiều lớp như vậy có thể gây ra độ dốc nổ vì độ dốc trở nên lớn hơn trong các lớp liên tiếp.)c= =0c0,10,3c<0c= =1,|c|>1

Sửa đổi một chút mã của OP cung cấp một minh chứng rằng vấn đề nằm ở sự lựa chọn chức năng kích hoạt. Mã này khởi tạo là âm và sử dụng thay cho thông thường . Mất mát nhanh chóng giảm xuống một giá trị nhỏ và trọng lượng chính xác di chuyển đến , đó là tối ưu.wLeakyReLUReLUw= =1

LeakyReLU khắc phục sự cố

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()

Một lớp phức tạp khác phát sinh từ thực tế là chúng ta không di chuyển vô cùng, mà thay vào đó là rất nhiều "bước nhảy", và những bước nhảy này đưa chúng ta từ lần lặp này sang lần lặp tiếp theo. Điều này có nghĩa là có một số trường hợp các giá trị âm ban đầu của sẽ không bị kẹt; những trường hợp này phát sinh đối với các kết hợp cụ thể của và kích thước bước xuống dốc đủ lớn để "nhảy" qua gradient biến mất.w w(0)

Tôi đã chơi xung quanh với mã này một số và tôi thấy rằng việc để khởi tạo ở và thay đổi trình tối ưu hóa từ SGD sang Adam, Adam + AMSGrad hoặc SGD + không giúp được gì. Hơn nữa, việc thay đổi từ SGD sang Adam thực sự làm chậm tiến độ ngoài việc không giúp khắc phục độ dốc biến mất trong vấn đề này.w(0)= =-10

Mặt khác, nếu bạn thay đổi khởi tạo thành thay đổi trình tối ưu hóa thành Adam (kích thước bước 0,01), thì bạn thực sự có thể khắc phục độ dốc biến mất. Nó cũng hoạt động nếu bạn sử dụng và SGD với động lượng (kích thước bước 0,01). Nó thậm chí hoạt động nếu bạn sử dụng vanilla SGD (kích thước bước 0,01) và .w(0)= =-1 w(0)= =-1w(0)= =-1

Các mã có liên quan là dưới đây; sử dụng opt_sgdhoặc opt_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)

Tôi đã thấy vấn đề tương tự với LeakyReLU, ELU, SELU khi tôi có trọng lượng và độ lệch đầu ra, nhưng tôi không chắc liệu tôi đã thử những thứ đó mà không có đầu ra. Tôi sẽ kiểm tra
endolith

1
(Vâng, bạn đúng khi LeakyReLU và ELU hoạt động tốt trong ví dụ này)
endolith

2
Ồ, tôi hiểu rồi Nó đang thực hiện giảm độ dốc của hàm mất, chỉ là hàm mất trở nên phẳng (0 gradient) ở 0 khi tiếp cận từ phía âm, do đó, độ dốc giảm dần bị kẹt ở đó. Bây giờ nó có vẻ rõ ràng. : D
endolith

2
Chính xác. Lưu ý cách các âm mưu thua lỗ của bạn so với có "kink" gần 0: đó là bởi vì ở bên trái của 0, độ dốc của tổn thất biến mất về 0 (tuy nhiên, đây là một giải pháp tối ưu vì tổn thất cao hơn ở đó cho ). Hơn nữa, biểu đồ này cho thấy hàm mất là không lồi (bạn có thể vẽ một đường thẳng vượt qua đường mất ở 3 vị trí trở lên), do đó báo hiệu rằng chúng ta nên thận trọng khi sử dụng các trình tối ưu hóa cục bộ như SGD. ww= =0
Sycorax nói phục hồi Monica

2
Khi sử dụng kích hoạt relu, ngay cả SGD không có động lượng cũng có thể đi qua môi nếu kích thước bước đủ lớn cho bất kỳ giá trị cụ thể nào của . w(Tôi)
Sycorax nói Phục hồi lại
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.