A partir de hoje, os maiores sucessos do Deep Studying ocorreram no campo da aprendizagem supervisionada, exigindo muitos dados de treinamento anotados. No entanto, os dados não são (normalmente) com anotações ou etiquetas. Também, aprendizado não supervisionado é atraente por causa da analogia à cognição humana.
Neste weblog até agora, vimos duas principais arquiteturas para o aprendizado não supervisionado: AutoEncoders variacionais e Redes adversárias generativas. Menos conhecido, mas atraente por razões conceituais e de desempenho são Fluxos normalizando (Jimenez Rezende e Mohamed 2015). Neste e no próximo publish, apresentaremos fluxos, concentrando -se em como implementá -los usando Probabilidade do tensorflow (TFP).
Em contraste com Postagens anteriores envolvendo TFP que acessou sua funcionalidade usando baixo nível $
-Syntax, agora fazemos uso de TFProbabilityum invólucro no estilo de keras
Assim, tensorflow
e tfdatasets
. Uma nota sobre este pacote: ele ainda está sob desenvolvimento pesado e a API pode mudar. Até o momento em que este artigo foi escrito, os invólucros ainda não existem para todos os módulos TFP, mas toda a funcionalidade TFP está disponível usando $
-Syntax Se necessário.
Estimativa de densidade e amostragem
De volta à aprendizagem não supervisionada e, especificamente, pensando em autoencoders variacionais, quais são as principais coisas que eles nos dão? Uma coisa que raramente está faltando nos artigos sobre métodos generativos são fotos de rostos de aparência tremendous actual (ou quartos ou animais …). Tão evidentemente amostragem (ou: geração) é uma parte importante. Se pudermos provar de um modelo e obter entidades reais, isso significa que o modelo aprendeu algo sobre como as coisas são distribuídas no mundo: ele aprendeu um distribuição. No caso de autoencoders variacionais, há mais: as entidades devem ser determinadas por um conjunto de fatores latentes distintos e distintos (esperançosamente!). Mas essa não é a suposição no caso de normalizar os fluxos, por isso não vamos elaborar isso aqui.
Como recapitulação, como amostramos de um VAE? Nós desenhamos de (z )a variável latente e execute a rede de decodificadores nela. O resultado deve – esperamos – parece que vem da distribuição empírica de dados. Não deve, no entanto, olhar exatamente Como qualquer um dos itens usados para treinar o VAE, ou não aprendemos nada útil.
A segunda coisa que podemos obter de um VAE é uma avaliação da plausibilidade dos dados individuais, a ser usada, por exemplo, na detecção de anomalia. Aqui a “plausibilidade” é vaga de propósito: com VAE, não temos meios de calcular uma densidade actual sob o posterior.
E se quisermos ou precisarmos, ambos: geração de amostras e estimativa de densidade? É aqui que Fluxos normalizando entre.
Fluxos normalizando
UM fluxo é uma sequência de mapeamentos diferenciáveis e invertíveis de dados para uma distribuição “agradável”, algo de que podemos facilmente amostrar e usar para calcular uma densidade. Vamos tomar como exemplo a maneira canônica de gerar amostras de alguma distribuição, digamos, o exponencial.
Começamos pedindo ao nosso gerador de números aleatórios um número entre 0 e 1:
Este número que tratamos como vindo de um Distribuição cumulativa de probabilidade (CDF) – de um exponencial CDF, para ser preciso. Agora que temos um valor do CDF, tudo o que precisamos fazer é mapear isso “de volta” para um valor. Esse mapeamento CDF -> worth
Estamos procurando é apenas o inverso do CDF de uma distribuição exponencial, sendo o CDF
(F (x) = 1 – e^{ – lambda x} )
O inverso então é
(F^{ -1} (u) = – frac {1} { lambda} ln (1 -u) )
o que significa que podemos fazer nossa amostra exponencial fazendo
lambda <- 0.5 # choose some lambda
x <- -1/lambda * log(1-u)
Vemos o CDF é realmente um fluxo (ou um bloco de construção, se imaginarmos a maioria dos fluxos como compreendendo várias transformações), pois
- Ele mapeia os dados para uma distribuição uniforme entre 0 e 1, permitindo avaliar a probabilidade de dados.
- Por outro lado, ele mapeia uma probabilidade para um valor actual, permitindo gerar amostras.
A partir deste exemplo, vemos por que um fluxo deve ser invertível, mas ainda não vemos por que deveria ser diferenciável. Isso ficará claro em breve, mas primeiro vamos dar uma olhada em como os fluxos estão disponíveis em tfprobability
.
Bijetores
TFP vem com um tesouro de transformações, chamado bijectors
variando de cálculos simples como Expeneção para os mais complexos como o Transformação discreta de cosseno.
Para começar, vamos usar tfprobability
Para gerar amostras a partir da distribuição regular. Há um bijetor tfb_normal_cdf()
que leva dados de entrada para o intervalo ((0,1) ). Sua transformação inversa produz uma variável aleatória com a distribuição regular padrão:
Por outro lado, podemos usar esse bijetor para determinar a probabilidade (log) de uma amostra da distribuição regular. Vamos verificar contra um uso direto de tfd_normal
no distributions
módulo:
x <- 2.01
d_n <- tfd_normal(loc = 0, scale = 1)
d_n %>% tfd_log_prob(x) %>% as.numeric() # -2.938989
Para obter a mesma probabilidade de log do bijetor, adicionamos dois componentes:
- Em primeiro lugar, executamos a amostra através do
ahead
Transformação e Calcule a probabilidade de log sob a distribuição uniforme. - Em segundo lugar, como estamos usando a distribuição uniforme para determinar a probabilidade de uma amostra regular, precisamos rastrear como a probabilidade muda sob essa transformação. Isso é feito ligando
tfb_forward_log_det_jacobian
(a ser elaborado mais abaixo).
b <- tfb_normal_cdf()
d_u <- tfd_uniform()
l <- d_u %>% tfd_log_prob(b %>% tfb_forward(x))
j <- b %>% tfb_forward_log_det_jacobian(x, event_ndims = 0)
(l + j) %>% as.numeric() # -2.938989
Por que isso funciona? Vamos obter alguns antecedentes.
A massa de probabilidade é conservada
Os fluxos são baseados no princípio de que, sob transformação, a massa de probabilidade é conservada. Digamos que temos um fluxo de (x ) para (z ):
(z = f (x) )
Suponha que provemos de (z ) e então, calcule a transformação inversa para obter (x ). Nós sabemos a probabilidade de (z ). Qual é a probabilidade de que (x )a amostra transformada, está entre (x_0 ) e (x_0 + dx )?
Essa probabilidade é (p (x) dx )a densidade vezes o comprimento do intervalo. Isso tem que igualar a probabilidade de que (z ) mentiras entre (f (x) ) e (f (x + dx) ). Esse novo intervalo tem comprimento (f ‘(x) dx )então:
(p (x) dx = p (z) f ‘(x) dx )
Ou equivalentemente
(p (x) = p (z) * dz/dx )
Assim, a probabilidade de amostra (p (x) ) é determinado pela probabilidade base (p (z) ) da distribuição transformada, multiplicada por quanto o fluxo estende o espaço.
O mesmo acontece em dimensões mais altas: novamente, o fluxo é sobre a mudança no quantity de probabilidade entre o (z ) e (y ) espaços:
(p (x) = p (z) frac {vol (dz)} {vol (dx)} )
Em dimensões mais altas, o jacobiano substitui o derivado. Então, a mudança no quantity é capturada pelo valor absoluto de seu determinante:
(p ( mathbf {x}) = p (f ( mathbf {x})) bigg | det frac { parcial f ({ mathbf {x})}} { parcial { mathbf {x}}}}
Na prática, trabalhamos com probabilidades de log, então
(log p ( mathbf {x}) = log p (f ( mathbf {x})) + log bigg | det frac { parcial f ({ mathbf {x}}} { parcial { mathbf {x}}}}} { }
Vamos ver isso com outro bijector
exemplo, tfb_affine_scalar
. Abaixo, construímos um mini-fluxo que mapeia alguns escolhidos arbitrários (x ) valores para dobrar seu valor (scale = 2
):
x <- c(0, 0.5, 1)
b <- tfb_affine_scalar(shift = 0, scale = 2)
Para comparar densidades sob o fluxo, escolhemos a distribuição regular e examinamos as densidades de log:
d_n <- tfd_normal(loc = 0, scale = 1)
d_n %>% tfd_log_prob(x) %>% as.numeric() # -0.9189385 -1.0439385 -1.4189385
Agora aplique o fluxo e calcule as novas densidades de log como uma soma das densidades de log do correspondente (x ) valores e o determinante do log do jacobiano:
z <- b %>% tfb_forward(x)
(d_n %>% tfd_log_prob(b %>% tfb_inverse(z))) +
(b %>% tfb_inverse_log_det_jacobian(z, event_ndims = 0)) %>%
as.numeric() # -1.6120857 -1.7370857 -2.1120858
Vemos que, à medida que os valores são esticados no espaço (multiplicamos por 2), as densidades individuais de log diminuem. Podemos verificar se a probabilidade cumulativa permanece a mesma usando tfd_transformed_distribution()
:
d_t <- tfd_transformed_distribution(distribution = d_n, bijector = b)
d_n %>% tfd_cdf(x) %>% as.numeric() # 0.5000000 0.6914625 0.8413447
d_t %>% tfd_cdf(y) %>% as.numeric() # 0.5000000 0.6914625 0.8413447
Até agora, os fluxos que vimos eram estáticos – como isso se encaixa na estrutura das redes neurais?
Treinando um fluxo
Dado que os fluxos são bidirecionais, há duas maneiras de pensar neles. Acima, enfatizamos principalmente o mapeamento inverso: queremos uma distribuição simples de que possamos provar e que podemos usar para calcular uma densidade. Nessa linha, os fluxos às vezes são chamados de “mapeamentos de dados para ruído” – barulho sendo principalmente um gaussiano isotrópico. No entanto, na prática, ainda não temos esse “ruído”, apenas temos dados. Então, na prática, temos que aprender um fluxo que faz esse mapeamento. Fazemos isso usando bijectors
com parâmetros treináveis. Vamos ver um exemplo muito simples aqui e deixar o “mundo actual fluxos” para o próximo publish.
O exemplo é baseado na parte 1 de Introdução de Eric Jang aos fluxos normalizando. A principal diferença (além da simplificação para mostrar o padrão básico) é que estamos usando a execução ansiosa.
Começamos de um gaussiano bidimensional e isotrópico e queremos modelar dados que também são normais, mas com uma média de 1 e uma variação de 2 (em ambas as dimensões).
library(tensorflow)
library(tfprobability)
tfe_enable_eager_execution(device_policy = "silent")
library(tfdatasets)
# the place we begin from
base_dist <- tfd_multivariate_normal_diag(loc = c(0, 0))
# the place we wish to go
target_dist <- tfd_multivariate_normal_diag(loc = c(1, 1), scale_identity_multiplier = 2)
# create coaching information from the goal distribution
target_samples <- target_dist %>% tfd_sample(1000) %>% tf$forged(tf$float32)
batch_size <- 100
dataset <- tensor_slices_dataset(target_samples) %>%
dataset_shuffle(buffer_size = dim(target_samples)(1)) %>%
dataset_batch(batch_size)
Agora, construiremos uma pequena rede neural, composta por uma transformação afim e uma não linearidade. Para o primeiro, podemos usar tfb_affine
o parente multidimensional de tfb_affine_scalar
. Quanto às não linearidades, atualmente o TFP vem com tfb_sigmoid
e tfb_tanh
mas podemos construir nosso próprio relu parametrizado usando tfb_inline
:
# alpha is a learnable parameter
bijector_leaky_relu <- operate(alpha) {
tfb_inline(
# ahead rework leaves optimistic values untouched and scales unfavourable ones by alpha
forward_fn = operate(x)
tf$the place(tf$greater_equal(x, 0), x, alpha * x),
# inverse rework leaves optimistic values untouched and scales unfavourable ones by 1/alpha
inverse_fn = operate(y)
tf$the place(tf$greater_equal(y, 0), y, 1/alpha * y),
# quantity change is 0 when optimistic and 1/alpha when unfavourable
inverse_log_det_jacobian_fn = operate(y) {
I <- tf$ones_like(y)
J_inv <- tf$the place(tf$greater_equal(y, 0), I, 1/alpha * I)
log_abs_det_J_inv <- tf$log(tf$abs(J_inv))
tf$reduce_sum(log_abs_det_J_inv, axis = 1L)
},
forward_min_event_ndims = 1
)
}
Defina as variáveis aprendidas para o afine e as camadas pré -prelum:
d <- 2 # dimensionality
r <- 2 # rank of replace
# shift of affine bijector
shift <- tf$get_variable("shift", d)
# scale of affine bijector
L <- tf$get_variable('L', c(d * (d + 1) / 2))
# rank-r replace
V <- tf$get_variable("V", c(d, r))
# scaling issue of parameterized relu
alpha <- tf$abs(tf$get_variable('alpha', checklist())) + 0.01
Com a execução ansiosa, as variáveis devem ser usadas dentro da função de perda, e é onde definimos os bijetores. Nosso pequeno fluxo agora é um tfb_chain
de bijetores, e nós o envolvemos em um TransformedDistribution (tfd_transformed_distribution
) que vincula distribuições de origem e destino.
loss <- operate() {
affine <- tfb_affine(
scale_tril = tfb_fill_triangular() %>% tfb_forward(L),
scale_perturb_factor = V,
shift = shift
)
lrelu <- bijector_leaky_relu(alpha = alpha)
circulate <- checklist(lrelu, affine) %>% tfb_chain()
dist <- tfd_transformed_distribution(distribution = base_dist,
bijector = circulate)
l <- -tf$reduce_mean(dist$log_prob(batch))
# hold monitor of progress
print(spherical(as.numeric(l), 2))
l
}
Agora podemos realmente executar o treinamento!
optimizer <- tf$prepare$AdamOptimizer(1e-4)
n_epochs <- 100
for (i in 1:n_epochs) {
iter <- make_iterator_one_shot(dataset)
until_out_of_range({
batch <- iterator_get_next(iter)
optimizer$decrease(loss)
})
}
Os resultados diferirão dependendo da inicialização aleatória, mas você deve ver um progresso constante (se lento). Usando bijetores, na verdade treinamos e definimos um pouco de rede neural.
Panorama
Sem dúvida, esse fluxo é muito simples para modelar dados complexos, mas é instrutivo ter visto os princípios básicos antes de se aprofundar em fluxos mais complexos. No próximo publish, vamos checar Fluxos autoregressivosnovamente usando TFP e tfprobability
.
Jimenez Rezende, Danilo e Shakir Mohamed. 2015. “Inferência variacional de normalizar fluxos”. ARXIV E-PRINTSMaio, arxiv: 1505.05770. https://arxiv.org/abs/1505.05770.