O que é a ativação GELU?

18

Eu estava examinando um artigo do BERT que usa GELU (Gaussian Error Linear Unit), que afirma a equação como

GELU(x)=xP(Xx)=xΦ(x).
que se aproxima de
0.5x(1+tanh[2/π(x+0.044715x3)])
Você poderia simplificar a equação e explicar como ela foi aprovada.

thanatoz
fonte

Respostas:

18

Função GELU

Podemos expandir a distribuição cumulativa de N(0,1) , ou seja, Φ(x) , da seguinte maneira:

GELU(x):=xP(Xx)=xΦ(x)=0.5x(1+erf(x2))

Observe que isso é uma definição , não uma equação (ou uma relação). Os autores forneceram algumas justificativas para esta proposta, por exemplo, uma analogia estocástica , porém matematicamente, essa é apenas uma definição.

Aqui está o enredo de GELU:

Aproximação de Tanh

Para esse tipo de aproximação numérica, a idéia principal é encontrar uma função semelhante (principalmente baseada na experiência), parametrizá-la e ajustá-la a um conjunto de pontos da função original.

Sabendo que está muito próximo deerf(x)tanh(x)

e a primeira derivada de coincide com a de em , que é , passamos a ajustar (ou com mais termos) para um conjunto de pontos .erf(x2)tanh(2πx)x=02π

tanh(2π(x+ax2+bx3+cx4+dx5))
(xi,erf(xi2))

Eu ajustei essa função em 20 amostras entre ( usando este site ), e aqui estão os coeficientes:(1.5,1.5)

Ao definir , foi estimado em . Com mais amostras de uma faixa maior (esse local é permitido apenas 20), o coeficiente estará mais próximo do do papel . Finalmente chegamosa=c=d=0b0.04495641b0.044715

GELU(x)=xΦ(x)=0.5x(1+erf(x2))0.5x(1+tanh(2π(x+0.044715x3)))

com erro quadrático médio para .108x[10,10]

Observe que se não utilizássemos o relacionamento entre as primeiras derivadas, o termo teria sido incluído nos parâmetros da seguinte maneira que é menos bonito (menos analítico, mais numérico)!2π

0.5x(1+tanh(0.797885x+0.035677x3))

Utilizando a paridade

Conforme sugerido por @BookYourLuck , podemos utilizar a paridade de funções para restringir o espaço de polinômios em que pesquisamos. Ou seja, como é uma função ímpar, ou seja, e também é uma função ímpar, função polinomial dentro também deve ser ímpar (deve ter poderes ímpares de ) para ter erff(x)=f(x)tanhpol(x)tanhx

erf(x)tanh(pol(x))=tanh(pol(x))=tanh(pol(x))erf(x)

Anteriormente, tivemos a sorte de acabar com coeficientes (quase) zero para potências pares e ; no entanto, em geral, isso pode levar a aproximações de baixa qualidade que, por exemplo, têm um termo como que está sendo cancelado por termos extras (pares ou ímpares) em vez de simplesmente optar por .x2x40.23x20x2

Aproximação sigmóide

Uma relação semelhante ocorre entre e (sigmoide), proposta no artigo como outra aproximação, com erro quadrático médio para .erf(x)2(σ(x)12)104x[10,10]

Aqui está um código Python para gerar pontos de dados, ajustar as funções e calcular os erros quadráticos médios:

import math
import numpy as np
import scipy.optimize as optimize


def tahn(xs, a):
    return [math.tanh(math.sqrt(2 / math.pi) * (x + a * x**3)) for x in xs]


def sigmoid(xs, a):
    return [2 * (1 / (1 + math.exp(-a * x)) - 0.5) for x in xs]


print_points = 0
np.random.seed(123)
# xs = [-2, -1, -.9, -.7, 0.6, -.5, -.4, -.3, -0.2, -.1, 0,
#       .1, 0.2, .3, .4, .5, 0.6, .7, .9, 2]
# xs = np.concatenate((np.arange(-1, 1, 0.2), np.arange(-4, 4, 0.8)))
# xs = np.concatenate((np.arange(-2, 2, 0.5), np.arange(-8, 8, 1.6)))
xs = np.arange(-10, 10, 0.001)
erfs = np.array([math.erf(x/math.sqrt(2)) for x in xs])
ys = np.array([0.5 * x * (1 + math.erf(x/math.sqrt(2))) for x in xs])

# Fit tanh and sigmoid curves to erf points
tanh_popt, _ = optimize.curve_fit(tahn, xs, erfs)
print('Tanh fit: a=%5.5f' % tuple(tanh_popt))

sig_popt, _ = optimize.curve_fit(sigmoid, xs, erfs)
print('Sigmoid fit: a=%5.5f' % tuple(sig_popt))

# curves used in https://mycurvefit.com:
# 1. sinh(sqrt(2/3.141593)*(x+a*x^2+b*x^3+c*x^4+d*x^5))/cosh(sqrt(2/3.141593)*(x+a*x^2+b*x^3+c*x^4+d*x^5))
# 2. sinh(sqrt(2/3.141593)*(x+b*x^3))/cosh(sqrt(2/3.141593)*(x+b*x^3))
y_paper_tanh = np.array([0.5 * x * (1 + math.tanh(math.sqrt(2/math.pi)*(x + 0.044715 * x**3))) for x in xs])
tanh_error_paper = (np.square(ys - y_paper_tanh)).mean()
y_alt_tanh = np.array([0.5 * x * (1 + math.tanh(math.sqrt(2/math.pi)*(x + tanh_popt[0] * x**3))) for x in xs])
tanh_error_alt = (np.square(ys - y_alt_tanh)).mean()

# curve used in https://mycurvefit.com:
# 1. 2*(1/(1+2.718281828459^(-(a*x))) - 0.5)
y_paper_sigmoid = np.array([x * (1 / (1 + math.exp(-1.702 * x))) for x in xs])
sigmoid_error_paper = (np.square(ys - y_paper_sigmoid)).mean()
y_alt_sigmoid = np.array([x * (1 / (1 + math.exp(-sig_popt[0] * x))) for x in xs])
sigmoid_error_alt = (np.square(ys - y_alt_sigmoid)).mean()

print('Paper tanh error:', tanh_error_paper)
print('Alternative tanh error:', tanh_error_alt)
print('Paper sigmoid error:', sigmoid_error_paper)
print('Alternative sigmoid error:', sigmoid_error_alt)

if print_points == 1:
    print(len(xs))
    for x, erf in zip(xs, erfs):
        print(x, erf)

Resultado:

Tanh fit: a=0.04485
Sigmoid fit: a=1.70099
Paper tanh error: 2.4329173471294176e-08
Alternative tanh error: 2.698034519269613e-08
Paper sigmoid error: 5.6479106346814546e-05
Alternative sigmoid error: 5.704246564663601e-05
Esmailiano
fonte
2
Por que a aproximação é necessária? Eles não poderiam simplesmente usar a função erf?
SebiSebi
8

Observe primeiro que por paridade de . Precisamos mostrar que por .

Φ(x)=12erfc(x2)=12(1+erf(x2))
erf
erf(x2)tanh(2π(x+ax3))
a0.044715

Para grandes valores de , ambas as funções são delimitadas em . Para pequeno , a respectiva série Taylor lê e Substituindo, obtemos que e Equacionando o coeficiente para , encontramos próximo ao dox[1,1]x

tanh(x)=xx33+o(x3)
erf(x)=2π(xx33)+o(x3).
tanh(2π(x+ax3))=2π(x+(a23π)x3)+o(x3)
erf(x2)=2π(xx36)+o(x3).
x3
a0.04553992412
0.044715.

BookYourLuck
fonte