Thực hiện lớp chức năng Bump tổng quát có thể huấn luyện trong Keras / Tensorflow


8

Tôi đang cố mã hóa biến thể sau của hàm Bump , được áp dụng thành phần khôn ngoan:

phương trình hàm tổng quát,

trong đó có thể huấn luyện được; nhưng nó không hoạt động (lỗi được báo cáo dưới đây).


Nỗ lực của tôi:

Đây là những gì tôi đã mã hóa cho đến nay (nếu nó giúp). Giả sử tôi có hai chức năng (ví dụ):

  def f_True(x):
    # Compute Bump Function
    bump_value = 1-tf.math.pow(x,2)
    bump_value = -tf.math.pow(bump_value,-1)
    bump_value = tf.math.exp(bump_value)
    return(bump_value)

  def f_False(x):
    # Compute Bump Function
    x_out = 0*x
    return(x_out)

class trainable_bump_layer(tf.keras.layers.Layer):

    def __init__(self, *args, **kwargs):
        super(trainable_bump_layer, self).__init__(*args, **kwargs)

    def build(self, input_shape):
        self.threshold_level = self.add_weight(name='threshlevel',
                                    shape=[1],
                                    initializer='GlorotUniform',
                                    trainable=True)

    def call(self, input):
        # Determine Thresholding Logic
        The_Logic = tf.math.less(input,self.threshold_level)
        # Apply Logic
        output_step_3 = tf.cond(The_Logic, 
                                lambda: f_True(input),
                                lambda: f_False(input))
        return output_step_3

Báo cáo lỗi:

    Train on 100 samples
Epoch 1/10
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['reconfiguration_unit_steps_3_3/threshlevel:0'] when minimizing the loss.
 32/100 [========>.....................] - ETA: 3s

...

tensorflow:Gradients do not exist for variables 

Hơn nữa, nó dường như không được áp dụng thành phần khôn ngoan (bên cạnh vấn đề không thể đào tạo). Điều gì có thể là vấn đề?


kích thước của là inputgì? nó có phải là vô hướng không?
Vlad

1
Xin chào @ProbinglyAHuman, bạn có thể cung cấp mã có thể lặp lại tối thiểu cho kịch bản của mình và chỉ định chính xác nó không hoạt động như thế nào không?
TF_Support

1
@TF_Support Tôi đã thêm chi tiết về mục tiêu cũng như báo cáo lỗi ...
FlabbyTheKatsu

Là sigma đào tạo?
Daniel Möller

Bạn có thể chia sẻ biểu đồ về những gì bạn muốn và những gì trong biểu đồ này có thể thay đổi?
Daniel Möller

Câu trả lời:


2

Tôi hơi ngạc nhiên khi không ai đề cập đến lý do chính (và duy nhất) cho cảnh báo được đưa ra! Dường như, mã đó được cho là thực hiện biến thể tổng quát của hàm Bump; tuy nhiên, chỉ cần xem các chức năng được thực hiện lại:

def f_True(x):
    # Compute Bump Function
    bump_value = 1-tf.math.pow(x,2)
    bump_value = -tf.math.pow(bump_value,-1)
    bump_value = tf.math.exp(bump_value)
    return(bump_value)

def f_False(x):
    # Compute Bump Function
    x_out = 0*x
    return(x_out)

Lỗi là hiển nhiên: không có việc sử dụng trọng lượng có thể huấn luyện của lớp trong các chức năng này! Vì vậy, không có gì ngạc nhiên khi bạn nhận được thông báo nói rằng không có độ dốc nào tồn tại cho điều đó: bạn hoàn toàn không sử dụng nó, vì vậy không có độ dốc để cập nhật nó! Thay vào đó, đây chính xác là hàm Bump ban đầu (nghĩa là không có trọng lượng có thể huấn luyện).

Nhưng, bạn có thể nói rằng: "ít nhất, tôi đã sử dụng trọng lượng có thể luyện được trong điều kiện tf.cond, vì vậy phải có một số độ dốc?!"; tuy nhiên, nó không như vậy và để tôi giải tỏa sự nhầm lẫn:

  • Trước hết, như bạn đã nhận thấy, chúng tôi quan tâm đến điều hòa yếu tố khôn ngoan. Vì vậy, thay vì tf.condbạn cần sử dụng tf.where.

  • Một quan niệm sai lầm khác là cho rằng vì tf.lessnó được sử dụng như là điều kiện và vì nó không khác biệt, tức là nó không có độ dốc đối với các đầu vào của nó (điều này đúng: không có độ dốc xác định cho hàm với đầu ra boolean thực sự của nó đầu vào có giá trị!), sau đó dẫn đến cảnh báo nhất định!

    • Điều đó đơn giản là sai! Đạo hàm ở đây sẽ được lấy từ đầu ra của trọng lượng có thể luyện được của lớp , và điều kiện lựa chọn KHÔNG có trong đầu ra. Thay vào đó, nó chỉ là một tenxơ boolean xác định nhánh đầu ra được chọn. Đó là nó! Đạo hàm của điều kiện không được thực hiện và sẽ không bao giờ cần thiết. Vì vậy, đó không phải là lý do cho cảnh báo được đưa ra; lý do là duy nhất và duy nhất những gì tôi đã đề cập ở trên: không có sự đóng góp của trọng lượng có thể huấn luyện trong đầu ra của lớp. (Lưu ý: nếu quan điểm về điều kiện là một chút ngạc nhiên đối với bạn, thì hãy nghĩ về một ví dụ đơn giản: hàm ReLU, được định nghĩa là relu(x) = 0 if x < 0 else x. Nếu đạo hàm của điều kiện, nghĩa làx < 0, được coi là / cần thiết, không tồn tại, sau đó chúng tôi sẽ không thể sử dụng ReLU trong các mô hình của chúng tôi và huấn luyện chúng bằng các phương pháp tối ưu hóa dựa trên độ dốc!)

(Lưu ý: bắt đầu từ đây, tôi sẽ đề cập và biểu thị giá trị ngưỡng là sigma , giống như trong phương trình).

Được rồi! Chúng tôi tìm thấy lý do đằng sau lỗi trong việc thực hiện. Chúng ta có thể sửa cái này không? Tất nhiên! Dưới đây là cập nhật thực hiện công việc:

import tensorflow as tf
from tensorflow.keras.initializers import RandomUniform
from tensorflow.keras.constraints import NonNeg

class BumpLayer(tf.keras.layers.Layer):
    def __init__(self, *args, **kwargs):
        super(BumpLayer, self).__init__(*args, **kwargs)

    def build(self, input_shape):
        self.sigma = self.add_weight(
            name='sigma',
            shape=[1],
            initializer=RandomUniform(minval=0.0, maxval=0.1),
            trainable=True,
            constraint=tf.keras.constraints.NonNeg()
        )
        super().build(input_shape)

    def bump_function(self, x):
        return tf.math.exp(-self.sigma / (self.sigma - tf.math.pow(x, 2)))

    def call(self, inputs):
        greater = tf.math.greater(inputs, -self.sigma)
        less = tf.math.less(inputs, self.sigma)
        condition = tf.logical_and(greater, less)

        output = tf.where(
            condition, 
            self.bump_function(inputs),
            0.0
        )
        return output

Một vài điểm liên quan đến việc thực hiện này:

  • Chúng tôi đã thay thế tf.condbằng tf.wheređể làm điều hòa yếu tố khôn ngoan.

  • Hơn nữa, như bạn có thể nhìn thấy, không giống như thực hiện của bạn mà chỉ kiểm tra một mặt của sự bất bình đẳng, chúng tôi đang sử dụng tf.math.less, tf.math.greatervà cũng tf.logical_andđể tìm hiểu xem các giá trị đầu vào có độ lớn nhỏ hơn sigma(cách khác, chúng ta có thể làm được điều này chỉ sử dụng tf.math.abstf.math.less, không có sự khác biệt !). Và chúng ta hãy lặp lại nó: sử dụng các hàm đầu ra boolean theo cách này không gây ra bất kỳ vấn đề nào và không liên quan gì đến đạo hàm / độ dốc.

  • Chúng tôi cũng đang sử dụng một ràng buộc không phủ định đối với giá trị sigma được học theo lớp. Tại sao? Bởi vì giá trị sigma nhỏ hơn 0 không có nghĩa (nghĩa là phạm vi (-sigma, sigma)được xác định sai khi sigma âm).

  • Và xem xét điểm trước đó, chúng tôi chú ý khởi tạo giá trị sigma đúng cách (nghĩa là đến một giá trị không âm nhỏ).

  • Và ngoài ra, xin đừng làm những việc như 0.0 * inputs! Nó dư thừa (và hơi lạ) và nó tương đương với 0.0; và cả hai đều có độ dốc 0.0(wrt inputs). Nhân số không với một tenxơ không thêm bất cứ điều gì hoặc giải quyết bất kỳ vấn đề hiện có, ít nhất là không trong trường hợp này!

Bây giờ, hãy kiểm tra nó để xem nó hoạt động như thế nào. Chúng tôi viết một số hàm trợ giúp để tạo dữ liệu huấn luyện dựa trên giá trị sigma cố định và cũng để tạo một mô hình chứa một mẫu BumpLayercó hình dạng đầu vào (1,). Hãy xem liệu nó có thể tìm hiểu giá trị sigma được sử dụng để tạo dữ liệu đào tạo hay không:

import numpy as np

def generate_data(sigma, min_x=-1, max_x=1, shape=(100000,1)):
    assert sigma >= 0, 'Sigma should be non-negative!'
    x = np.random.uniform(min_x, max_x, size=shape)
    xp2 = np.power(x, 2)
    condition = np.logical_and(x < sigma, x > -sigma)
    y = np.where(condition, np.exp(-sigma / (sigma - xp2)), 0.0)
    dy = np.where(condition, xp2 * y / np.power((sigma - xp2), 2), 0)
    return x, y, dy

def make_model(input_shape=(1,)):
    model = tf.keras.Sequential()
    model.add(BumpLayer(input_shape=input_shape))

    model.compile(loss='mse', optimizer='adam')
    return model

# Generate training data using a fixed sigma value.
sigma = 0.5
x, y, _ = generate_data(sigma=sigma, min_x=-0.1, max_x=0.1)

model = make_model()

# Store initial value of sigma, so that it could be compared after training.
sigma_before = model.layers[0].get_weights()[0][0]

model.fit(x, y, epochs=5)

print('Sigma before training:', sigma_before)
print('Sigma after training:', model.layers[0].get_weights()[0][0])
print('Sigma used for generating data:', sigma)

# Sigma before training: 0.08271004
# Sigma after training: 0.5000002
# Sigma used for generating data: 0.5

Vâng, nó có thể tìm hiểu giá trị của sigma được sử dụng để tạo dữ liệu! Nhưng, nó có đảm bảo rằng nó thực sự hoạt động cho tất cả các giá trị khác nhau của dữ liệu đào tạo và khởi tạo sigma không? Câu trả lời là không! Trên thực tế, có thể bạn chạy mã ở trên và nhận nangiá trị của sigma sau khi đào tạo, hoặc inflà giá trị mất! Vậy vấn đề là gì? Tại sao điều này nanhoặc infgiá trị có thể được sản xuất? Hãy thảo luận về nó bên dưới ...


Xử lý sự ổn định số

Một trong những điều quan trọng cần xem xét, khi xây dựng mô hình học máy và sử dụng các phương pháp tối ưu hóa dựa trên độ dốc để huấn luyện chúng, là sự ổn định về số lượng của các hoạt động và tính toán trong một mô hình. Khi các giá trị cực lớn hoặc nhỏ được tạo bởi một thao tác hoặc độ dốc của nó, gần như chắc chắn nó sẽ phá vỡ quy trình đào tạo (ví dụ: đó là một trong những lý do đằng sau việc bình thường hóa giá trị pixel hình ảnh trong CNN để ngăn chặn vấn đề này).

Vì vậy, chúng ta hãy xem chức năng gập tổng quát này (và bây giờ hãy loại bỏ ngưỡng). Rõ ràng là hàm này có các điểm kỳ dị (tức là các điểm trong đó hàm hoặc độ dốc của hàm không được xác định) tại x^2 = sigma(tức là khi x = sqrt(sigma)hoặc x=-sqrt(sigma)). Biểu đồ hoạt hình dưới đây cho thấy hàm vết sưng (đường liền nét màu đỏ), sigma wrt phái sinh của nó (đường màu lục chấm) x=sigmax=-sigmađường (hai đường màu xanh nét đứt dọc), khi sigma bắt đầu từ 0 và tăng lên 5:

Hàm gập tổng quát khi sigma bắt đầu từ 0 và được tăng lên năm.

Như bạn có thể thấy, xung quanh khu vực của các điểm kỳ dị, hàm không hoạt động tốt đối với tất cả các giá trị của sigma, theo nghĩa là cả hàm và đạo hàm của nó đều có giá trị cực lớn tại các vùng đó. Vì vậy, với một giá trị đầu vào tại các khu vực đó cho một giá trị cụ thể của sigma, đầu ra bùng nổ và giá trị độ dốc sẽ được tạo ra, do đó vấn đề về infgiá trị mất mát.

Hơn nữa, có một hành vi có vấn đề tf.wheregây ra vấn đề về nangiá trị cho biến sigma trong lớp: đáng ngạc nhiên, nếu giá trị được tạo ra trong nhánh không hoạt động tf.wherelà rất lớn hoặc inf, với hàm bội thu dẫn đến infgiá trị cực lớn hoặc độ dốc , sau đó độ dốc của tf.wheresẽ là nan, mặc dù thực tế inflà nhánh không hoạt động và thậm chí không được chọn (xem vấn đề Github này thảo luận chính xác về điều này) !!

Vì vậy, có bất kỳ cách giải quyết cho hành vi này tf.where? Vâng, thực sự có một mẹo để giải quyết vấn đề này bằng cách nào đó được giải thích trong câu trả lời này : về cơ bản chúng ta có thể sử dụng một bổ sung tf.wheređể ngăn chặn chức năng được áp dụng trên các khu vực này. Nói cách khác, thay vì áp dụng self.bump_functioncho bất kỳ giá trị đầu vào nào, chúng tôi lọc các giá trị KHÔNG có trong phạm vi (-self.sigma, self.sigma)(nghĩa là phạm vi thực tế mà hàm nên được áp dụng) và thay vào đó cung cấp hàm bằng 0 (luôn tạo ra các giá trị an toàn, nghĩa là bằng exp(-1)):

     output = tf.where(
            condition, 
            self.bump_function(tf.where(condition, inputs, 0.0)),
            0.0
     )

Áp dụng sửa chữa này sẽ giải quyết hoàn toàn vấn đề nangiá trị cho sigma. Hãy đánh giá nó về các giá trị dữ liệu huấn luyện được tạo với các giá trị sigma khác nhau và xem cách nó sẽ thực hiện:

true_learned_sigma = []
for s in np.arange(0.1, 10.0, 0.1):
    model = make_model()
    x, y, dy = generate_data(sigma=s, shape=(100000,1))
    model.fit(x, y, epochs=3 if s < 1 else (5 if s < 5 else 10), verbose=False)
    sigma = model.layers[0].get_weights()[0][0]
    true_learned_sigma.append([s, sigma])
    print(s, sigma)

# Check if the learned values of sigma
# are actually close to true values of sigma, for all the experiments.
res = np.array(true_learned_sigma)
print(np.allclose(res[:,0], res[:,1], atol=1e-2))
# True

Nó có thể học tất cả các giá trị sigma chính xác! Điều đó thật tuyệt. Cách giải quyết đó có hiệu quả! Mặc dù, có một cảnh báo: điều này được đảm bảo để hoạt động đúng và tìm hiểu bất kỳ giá trị sigma nào nếu các giá trị đầu vào của lớp này lớn hơn -1 và nhỏ hơn 1 (nghĩa là đây là trường hợp mặc định của generate_datahàm của chúng tôi ); mặt khác, vẫn còn vấn đề về infgiá trị tổn thất có thể xảy ra nếu các giá trị đầu vào có cường độ lớn hơn 1 (xem điểm # 1 và # 2, bên dưới).


Dưới đây là một số thực phẩm cho suy nghĩ cho sự tò mò và tâm trí quan tâm:

  1. Nó chỉ được đề cập rằng nếu các giá trị đầu vào của lớp này lớn hơn 1 hoặc nhỏ hơn -1, thì nó có thể gây ra vấn đề. Bạn có thể tranh luận tại sao đây là trường hợp? (Gợi ý: sử dụng sơ đồ hoạt hình ở trên và xem xét các trường hợp trong đó sigma > 1và giá trị đầu vào nằm giữa sqrt(sigma)sigma(hoặc giữa -sigma-sqrt(sigma).)

  2. Bạn có thể cung cấp cách khắc phục sự cố ở điểm # 1, tức là lớp đó có thể hoạt động cho tất cả các giá trị đầu vào không? (Gợi ý: như cách giải quyết tf.where, hãy suy nghĩ về cách bạn có thể lọc thêm các giá trị không an toàn mà hàm bội có thể được áp dụng và tạo ra đầu ra / độ dốc phát nổ.)

  3. Tuy nhiên, nếu bạn không quan tâm đến việc khắc phục sự cố này và muốn sử dụng lớp này trong mô hình như hiện tại, thì làm thế nào để bạn đảm bảo rằng các giá trị đầu vào của lớp này luôn nằm trong khoảng -1 đến 1? (Gợi ý: là một giải pháp, có một hàm kích hoạt thường được sử dụng, tạo ra các giá trị chính xác trong phạm vi này và có thể được sử dụng làm hàm kích hoạt của lớp trước lớp này.)

  4. Nếu bạn xem đoạn mã cuối cùng, bạn sẽ thấy rằng chúng tôi đã sử dụng epochs=3 if s < 1 else (5 if s < 5 else 10). Tại sao vậy? Tại sao giá trị lớn của sigma cần nhiều kỷ nguyên hơn để được học? (Gợi ý: một lần nữa, sử dụng sơ đồ hoạt hình và xem xét đạo hàm của các giá trị đầu vào giữa -1 và 1 khi giá trị sigma tăng. Độ lớn của chúng là bao nhiêu?)

  5. Chúng ta cũng cần kiểm tra dữ liệu đào tạo được tạo ra cho bất kỳ nan, infhoặc các giá trị cực kỳ lớn yvà lọc chúng ra? (Gợi ý: có, nếu sigma > 1và phạm vi của các giá trị, nghĩa là min_xmax_xnằm ngoài (-1, 1); nếu không, không cần thiết! Tại sao điều đó? Còn lại như một bài tập!)


1
Làm tốt lắm. @ProbinglyAHuman đây sẽ là câu trả lời được chấp nhận.
rvinas

@hôm nay. Điều này thật tuyệt, tôi nghĩ, có thể là câu trả lời chi tiết / chính xác / riger nhất mà tôi từng thấy trên bất kỳ ngăn xếp nào. Cảm ơn rất nhiều!
FlabbyTheKatsu

4

Thật không may, không có thao tác nào để kiểm tra xem xbên trong (-σ, σ)có thể khác biệt hay không và do đó σ không thể học được thông qua bất kỳ phương pháp giảm độ dốc nào. Cụ thể, không thể tính toán độ dốc đối với self.threshold_leveltf.math.lesskhông khác biệt đối với điều kiện.

Về điều kiện phần tử khôn ngoan, thay vào đó, bạn có thể sử dụng tf.where để chọn các phần tử từ f_True(input)hoặc f_False(input)theo các giá trị boolean thành phần khôn ngoan của điều kiện. Ví dụ:

output_step_3 = tf.where(The_Logic, f_True(input), f_False(input))

LƯU Ý: Tôi đã trả lời dựa trên mã được cung cấp, nơi self.threshold_levelkhông được sử dụng trong f_Truecũng không f_False. Nếu self.threshold_levelđược sử dụng trong các hàm đó như trong công thức được cung cấp, thì dĩ nhiên, hàm sẽ khác biệt đối với self.threshold_level.

Cập nhật 19/04/2020: Cảm ơn bạn @today đã làm rõ .


Phải có một mẹo mã hóa để có thể luyện tập được ...
FlabbyTheKatsu

Tôi e rằng không có mẹo thực hiện phép thuật nào có thể thực hiện được nếu toán học không hoạt động ...
rvinas

"Thông báo lỗi cho biết chính xác điều này - không thể tính toán độ dốc đối với self.thr Ngưỡng_level vì tf.math.less không khác biệt đối với đầu vào của nó." -> Thông báo cảnh báo không liên quan gì đến việc sử dụng tf.math.lesstrong điều kiện và thực tế là nó không khác biệt. Điều kiện không cần phải khác biệt để làm cho công việc này. Lỗi nằm ở thực tế là trọng lượng có thể huấn luyện hoàn toàn không được sử dụng để tạo ra đầu ra của lớp (tức là không có dấu vết của nó trong đầu ra). Xin vui lòng xem phần đầu tiên của câu trả lời của tôi để biết thêm về điều này.
hôm nay ngày

Đồng ý, đó không phải là những gì thông điệp cảnh báo đang nói và tôi sẽ sửa chữa từ ngữ của mình. Tuy nhiên, điểm vẫn giữ nguyên - bạn không thể có thao tác để kiểm tra xem một biến có nằm trong một phạm vi cụ thể hay không và hy vọng nó có thể khác biệt đối với biến giới hạn hay không. Điều đó nói rằng, nếu biến này được sử dụng để tính toán đầu ra (mà tôi thậm chí không nhận thấy trong công thức, tôi phải thừa nhận), tất nhiên, nó sẽ có độ dốc.
rvinas

3

Tôi đề nghị bạn thử phân phối bình thường thay vì một vết sưng. Trong các thử nghiệm của tôi ở đây, chức năng gập này không hoạt động tốt (tôi không thể tìm thấy lỗi nhưng không loại bỏ nó, nhưng biểu đồ của tôi hiển thị hai lần rất sắc nét, không tốt cho mạng)

Với một phân phối bình thường, bạn sẽ có được một vết sưng thường xuyên và khác biệt mà chiều cao, chiều rộng và trung tâm bạn có thể kiểm soát.

Vì vậy, bạn có thể thử chức năng này:

y = a * exp ( - b * (x - c)²)

Hãy thử nó trong một số biểu đồ và xem cách nó hoạt động.

Đối với điều này:

class trainable_bump_layer(tf.keras.layers.Layer):

    def __init__(self, *args, **kwargs):
        super(trainable_bump_layer, self).__init__(*args, **kwargs)

    def build(self, input_shape):

        #suggested shape (has a different kernel for each input feature/channel)
        shape = tuple(1 for _ in input_shape[:-1]) + input_shape[-1:]

        #for your desired shape of only 1:
        shape = tuple(1 for _ in input_shape) #all ones

        #height
        self.kernel_a = self.add_weight(name='kernel_a ',
                                    shape=shape
                                    initializer='ones',
                                    trainable=True)

        #inverse width
        self.kernel_b = self.add_weight(name='kernel_b',
                                    shape=shape
                                    initializer='ones',
                                    trainable=True)

        #center
        self.kernel_c = self.add_weight(name='kernel_c',
                                    shape=shape
                                    initializer='zeros',
                                    trainable=True)

    def call(self, input):
        exp_arg = - self.kernel_b * K.square(input - self.kernel_c)
        return self.kernel_a * K.exp(exp_arg)
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.