Estou tentando codificar a seguinte variante da função Bump , aplicada em componentes:
onde σ é treinável; mas não está funcionando (erros relatados abaixo).
Minha tentativa:
Aqui está o que eu codifiquei até agora (se ajudar). Suponha que eu tenha duas funções (por exemplo):
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
Relatório de erro:
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
Além disso, ele não parece ser aplicado em termos de componentes (além do problema não treinável). Qual poderia ser o problema?
python
tensorflow
machine-learning
keras
tf.keras
FlabbyTheKatsu
fonte
fonte
input
? é um escalar?Respostas:
Estou um pouco surpreso que ninguém tenha mencionado a principal (e única) razão para o aviso dado! Como parece, esse código deve implementar a variante generalizada da função Bump; no entanto, basta dar uma olhada nas funções implementadas novamente:
O erro é evidente: não há uso do peso treinável da camada nessas funções! Portanto, não é de surpreender que você receba a mensagem dizendo que não existe gradiente para isso: você não o está usando, portanto, não há gradiente para atualizá-lo! Pelo contrário, esta é exatamente a função Bump original (ou seja, sem peso treinável).
Mas, você pode dizer que: "pelo menos, usei o peso treinável na condição de
tf.cond
, então deve haver alguns gradientes ?!"; no entanto, não é assim e deixe-me esclarecer a confusão:Primeiro de tudo, como você também notou, estamos interessados no condicionamento por elementos. Então, em vez de
tf.cond
você precisar usartf.where
.O outro equívoco é afirmar que, uma vez que
tf.less
é usado como condição, e como não é diferenciável, ou seja, não possui gradiente em relação a suas entradas (o que é verdade: não há gradiente definido para uma função com saída booleana, que é real. entradas valiosas!), então isso resulta no aviso dado!relu(x) = 0 if x < 0 else x
. Se a derivada da condição, ou seja,x < 0
, é considerado / necessário, o que não existe, então não poderíamos usar ReLU em nossos modelos e treiná-los usando métodos de otimização baseados em gradiente!)(Nota: a partir daqui, eu me referiria e denotaria o valor do limiar como sigma , como na equação).
Tudo certo! Encontramos a razão por trás do erro na implementação. Podemos consertar isso? Claro! Aqui está a implementação de trabalho atualizada:
Alguns pontos em relação a esta implementação:
Nós substituímos
tf.cond
portf.where
para fazer o condicionamento por elementos.Além disso, como você pode ver, ao contrário de sua implementação que só verificado para um dos lados da desigualdade, estamos usando
tf.math.less
,tf.math.greater
e tambémtf.logical_and
para descobrir se os valores de entrada têm magnitudes inferior asigma
(alternativamente, poderíamos fazer isso usando apenastf.math.abs
etf.math.less
, sem diferença !). E vamos repetir: usar funções de saída booleana dessa maneira não causa problemas e não tem nada a ver com derivadas / gradientes.Também estamos usando uma restrição de não negatividade no valor sigma aprendido por camada. Por quê? Como os valores de sigma menores que zero não fazem sentido (ou seja, o intervalo
(-sigma, sigma)
é mal definido quando o sigma é negativo).E considerando o ponto anterior, tomamos o cuidado de inicializar o valor sigma corretamente (ou seja, para um pequeno valor não negativo).
E também, por favor, não faça coisas como
0.0 * inputs
! É redundante (e um pouco estranho) e é equivalente a0.0
; e ambos têm um gradiente de0.0
(wrtinputs
). A multiplicação de zero por um tensor não adiciona nada ou resolve qualquer problema existente, pelo menos não neste caso!Agora, vamos testá-lo para ver como funciona. Escrevemos algumas funções auxiliares para gerar dados de treinamento com base em um valor sigma fixo e também para criar um modelo que contém um único
BumpLayer
com formato de entrada de(1,)
. Vamos ver se ele pode aprender o valor sigma usado para gerar dados de treinamento:Sim, ele pode aprender o valor do sigma usado para gerar dados! Mas, é garantido que ele realmente funciona para todos os diferentes valores de dados de treinamento e inicialização do sigma? A resposta é não! Na verdade, é possível que você execute o código acima e obtenha
nan
o valor da sigma após o treinamento ouinf
o valor da perda! Então qual é o problema? Por que issonan
ouinf
valores podem ser produzidos? Vamos discutir abaixo ...Lidar com a estabilidade numérica
Uma das coisas importantes a considerar, ao construir um modelo de aprendizado de máquina e usar métodos de otimização baseados em gradiente para treiná-los, é a estabilidade numérica das operações e cálculos em um modelo. Quando valores extremamente grandes ou pequenos são gerados por uma operação ou seu gradiente, quase certamente isso atrapalha o processo de treinamento (por exemplo, essa é uma das razões por trás da normalização dos valores de pixel de imagem nas CNNs para evitar esse problema).
Então, vamos dar uma olhada nessa função de bump generalizada (e vamos descartar o limiar por enquanto). É óbvio que esta função possui singularidades (isto é, pontos em que a função ou seu gradiente não está definido) em
x^2 = sigma
(isto é, quandox = sqrt(sigma)
oux=-sqrt(sigma)
). O diagrama animado abaixo mostra a função bump (a linha vermelha sólida), sua derivada wrt sigma (a linha verde pontilhada) ex=sigma
andx=-sigma
lines (duas linhas verticais tracejadas em azul), quando o sigma começa do zero e é aumentado para 5:Como você pode ver, em torno da região das singularidades, a função não é bem-comportada para todos os valores de sigma, no sentido de que a função e sua derivada assumem valores extremamente grandes nessas regiões. Assim, dado um valor de entrada nessas regiões para um valor específico de sigma, seriam gerados valores explosivos de saída e gradiente, daí a questão do
inf
valor da perda.Além disso, há um comportamento problemático
tf.where
que causa a emissão denan
valores para a variável sigma na camada: surpreendentemente, se o valor produzido no ramo inativo detf.where
for extremamente grande ouinf
que, com a função bump, resulta em valores extremamente grandes ouinf
gradientes , então o gradiente detf.where
serianan
, apesar do fato deinf
estar no ramo inativo e nem ser selecionado (consulte esta edição do Github que discute exatamente isso) !!Portanto, existe alguma solução alternativa para esse comportamento de
tf.where
? Sim, na verdade, há um truque para resolver de alguma forma esse problema, explicado nesta resposta : basicamente podemos usar um adicionaltf.where
para impedir que a função seja aplicada nessas regiões. Em outras palavras, em vez de aplicarself.bump_function
em qualquer valor de entrada, filtramos os valores que NÃO estão no intervalo(-self.sigma, self.sigma)
(ou seja, o intervalo real em que a função deve ser aplicada) e, em vez disso, alimentamos a função com zero (que sempre produz valores seguros, ou seja, é igual aexp(-1)
):A aplicação dessa correção resolveria completamente a questão dos
nan
valores para o sigma. Vamos avaliá-lo nos valores dos dados de treinamento gerados com diferentes valores sigma e ver como ele funcionaria:Poderia aprender todos os valores sigma corretamente! Isso é bom. Essa solução funcionou! Embora exista uma ressalva: é garantido que funcione corretamente e aprenda qualquer valor sigma se os valores de entrada para essa camada forem maiores que -1 e menores que 1 (ou seja, este é o caso padrão de nossa
generate_data
função); caso contrário, ainda existe a questão doinf
valor da perda que pode ocorrer se os valores de entrada tiverem uma magnitude maior que 1 (consulte os pontos 1 e 2 abaixo).Aqui estão alguns pensamentos para os curiosos e a mente interessada:
Acabamos de mencionar que, se os valores de entrada para essa camada forem maiores que 1 ou menores que -1, isso poderá causar problemas. Você pode argumentar por que esse é o caso? (Dica: use o diagrama animado acima e considere os casos em que
sigma > 1
e o valor de entrada está entresqrt(sigma)
esigma
(ou entre-sigma
e-sqrt(sigma)
.)Você pode fornecer uma correção para o problema no ponto 1, ou seja, para que a camada funcione para todos os valores de entrada? (Dica: como a solução alternativa
tf.where
, pense em como você pode filtrar ainda mais os valores não seguros nos quais a função bump pode ser aplicada e produzir saída / gradiente explosivos.)No entanto, se você não está interessado em corrigir esse problema e gostaria de usar essa camada em um modelo como está agora, como garantir que os valores de entrada nessa camada estejam sempre entre -1 e 1? (Dica: como uma solução, existe uma função de ativação comumente usada que produz valores exatamente nesse intervalo e pode ser potencialmente usada como a função de ativação da camada anterior a essa camada.)
Se você der uma olhada no último trecho de código, verá que usamos
epochs=3 if s < 1 else (5 if s < 5 else 10)
. Por que é que? Por que grandes valores de sigma precisam de mais épocas para serem aprendidas? (Dica: novamente, use o diagrama animado e considere a derivada da função para valores de entrada entre -1 e 1 à medida que o valor sigma aumenta. Qual é a magnitude deles?)Você também precisa verificar os dados de treinamento gerados para qualquer
nan
,inf
ou extremamente grandes valores dey
e filtrá-los? (Dica: sim, sesigma > 1
e faixa de valores, ou seja,min_x
emax_x
, cair fora do(-1, 1)
!?!, Caso contrário, não que não é necessário Por que isso é deixado como um exercício)fonte
Infelizmente, nenhuma operação para verificar se
x
está dentro(-σ, σ)
será diferenciável e, portanto, σ não pode ser aprendido através de nenhum método de descida de gradiente. Especificamente, não é possível calcular os gradientes em relação a,self.threshold_level
porquetf.math.less
não é diferenciável em relação à condição.Em relação à condicional em termos de elementos, você pode usar tf.where para selecionar elementos de
f_True(input)
ou def_False(input)
acordo com os valores booleanos em termos de componentes da condição. Por exemplo:NOTA: Respondi com base no código fornecido, onde
self.threshold_level
não é usadof_True
nemf_False
. Seself.threshold_level
for usada nessas funções como na fórmula fornecida, a função será, naturalmente, diferenciável em relação aself.threshold_level
.Atualizado em 19/04/2020: obrigado hoje pelo esclarecimento .
fonte
tf.math.less
na condição e o fato de não ser diferenciável. A condição não precisa ser diferenciável para fazer esse trabalho. O erro está no fato de que o peso treinável não é usado para produzir a saída da camada (ou seja, não há vestígios dele na saída). Por favor, veja a primeira parte da minha resposta para saber mais sobre isso.Eu sugiro que você tente uma distribuição normal em vez de um solavanco. Nos meus testes aqui, essa função de bump não está se comportando bem (não consigo encontrar um bug, mas não o descarto, mas meu gráfico mostra dois solavancos muito acentuados, o que não é bom para redes)
Com uma distribuição normal, você obteria um solavanco regular e diferenciável cuja altura, largura e centro você pode controlar.
Então, você pode tentar esta função:
Experimente em algum gráfico e veja como ele se comporta.
Por esta:
fonte