Com a abundância de grandes bibliotecas, em r, para computação estatística, por que você estaria interessado na probabilidade de tensorflow (Tfppara abreviar)? Bem – vejamos uma lista de seus componentes:
- Distribuições e bijectos (os bijetores são mapas reversíveis, composíveis)
- Modelagem probabilística (Edward2 e camadas de rede probabilística)
- Inferência probabilística (through MCMC ou inferência variacional)
Agora think about tudo isso funcionando perfeitamente com a estrutura TensorFlow – Core, Keras, contribuiu com módulos – e também, distribuída e na GPU. O campo de possíveis aplicações é vasto – e diversificado demais para cobrir como um todo em uma postagem introdutória no weblog.
Em vez disso, nosso objetivo aqui é fornecer uma primeira introdução a Tfpconcentrando -se na aplicabilidade direta e interoperabilidade com o aprendizado profundo. Mostraremos rapidamente como começar com um dos blocos básicos de construção: distributions
. Em seguida, construiremos um autoencoder variacional semelhante ao de Aprendizagem de representação com MMD-VAE. Desta vez, porém, vamos usar Tfp Amostra das distribuições posteriores anteriores e aproximadas.
Consideraremos este submit como uma “prova sobre conceito” para usar Tfp Com Keras – de R – e planeje acompanhar exemplos mais elaborados da área de aprendizado de representação semi -supervisionado.
Para instalar Tfp junto com o tensorflow, basta anexar tensorflow-probability
para a lista padrão de pacotes extras:
library(tensorflow)
install_tensorflow(
extra_packages = c("keras", "tensorflow-hub", "tensorflow-probability"),
model = "1.12"
)
Agora para usar Tfptudo o que precisamos fazer é importá -lo e criar algumas alças úteis.
E aqui vamos nós, amostragem de uma distribuição regular padrão.
n <- tfd$Regular(loc = 0, scale = 1)
n$pattern(6L)
tf.Tensor(
"Normal_1/pattern/Reshape:0", form=(6,), dtype=float32
)
Agora isso é bom, mas é 2019, não queremos mais ter que criar uma sessão para avaliar esses tensores. No exemplo de autoencoder variacional abaixo, vamos ver como Tfp e tf Execução ansiosa são a combinação perfeita, então por que não começar a usá -lo agora.
Para usar a execução ansiosa, temos que executar as seguintes linhas em uma nova sessão (R):
… e importar Tfpo mesmo que acima.
tfp <- import("tensorflow_probability")
tfd <- tfp$distributions
Agora vamos olhar rapidamente para Tfp distribuições.
Usando distribuições
Aqui está esse padrão regular novamente.
n <- tfd$Regular(loc = 0, scale = 1)
As coisas comumente feitas com uma distribuição incluem amostragem:
# simply as in low-level tensorflow, we have to append L to point integer arguments
n$pattern(6L)
tf.Tensor(
(-0.34403768 -0.14122334 -1.3832929 1.618252 1.364448 -1.1299014 ),
form=(6,),
dtype=float32
)
Além de obter a probabilidade de log. Aqui fazemos isso simultaneamente para três valores.
tf.Tensor(
(-1.4189385 -0.9189385 -1.4189385), form=(3,), dtype=float32
)
Podemos fazer as mesmas coisas com muitas outras distribuições, por exemplo, o Bernoulli:
b <- tfd$Bernoulli(0.9)
b$pattern(10L)
tf.Tensor(
(1 1 1 0 1 1 0 1 0 1), form=(10,), dtype=int32
)
tf.Tensor(
(-1.2411538 -0.3411539 -1.2411538 -1.2411538), form=(4,), dtype=float32
)
Observe que, no último pedaço, estamos pedindo as probabilidades de log de quatro empates independentes.
Formas de lote e formas de eventos
Em Tfppodemos fazer o seguinte.
tfp.distributions.Regular(
"Regular/", batch_shape=(3,), event_shape=(), dtype=float32
)
Ao contrário da aparência, isso não é um regular multivariado. Conforme indicado por batch_shape=(3,)
este é um “lote” de distribuições univariadas independentes. O fato de que estes são univariados é visto em event_shape=()
: Cada um deles vive em unidimensional Espaço de evento.
Se, em vez disso, criarmos um único regular, bidimensional, normais multivariada:
tfp.distributions.MultivariateNormalDiag(
"MultivariateNormalDiag/", batch_shape=(), event_shape=(2,), dtype=float32
)
nós vemos batch_shape=(), event_shape=(2,)
como esperado.
Obviamente, podemos combinar os dois, criando lotes de distribuições multivariadas:
Este exemplo outline um lote de três distribuições normais bidimensionais multivariadas.
Convertendo entre formas de lote e formas de evento
Por mais estranho que pareça, surgem situações onde queremos transformar formas de distribuição entre esses tipos – na verdade, veremos esse caso muito em breve.
tfd$Unbiased
é usado para converter dimensões em batch_shape
para dimensões em event_shape
.
Aqui está um lote de três distribuições independentes de Bernoulli.
bs <- tfd$Bernoulli(probs=c(.3,.5,.7))
bs
tfp.distributions.Bernoulli(
"Bernoulli/", batch_shape=(3,), event_shape=(), dtype=int32
)
Podemos converter isso em um Bernoulli “tridimensional” digital como este:
b <- tfd$Unbiased(bs, reinterpreted_batch_ndims = 1L)
b
tfp.distributions.Unbiased(
"IndependentBernoulli/", batch_shape=(), event_shape=(3,), dtype=int32
)
Aqui reinterpreted_batch_ndims
diz Tfp Quantas das dimensões do lote estão sendo usadas para o espaço de eventos, começando a contar a partir do direito da lista de formas.
Com este entendimento básico de Tfp Distribuições, estamos prontos para vê -los usados em um VAE.
Vamos tirar a (não tão) arquitetura convolucional profunda de Aprendizagem de representação com MMD-VAE e uso distributions
Para probabilidades de amostragem e computação. Opcionalmente, nosso novo VAE será capaz de Aprenda a distribuição anterior.
Concretamente, a seguinte exposição consistirá em três partes. Primeiro, apresentamos o código comum aplicável a um VAE com um prior estático e um que aprende os parâmetros da distribuição anterior. Em seguida, temos o loop de treinamento para o primeiro (prior) VAE. Finalmente, discutimos o ciclo de treinamento e o modelo adicional envolvido no segundo VAE (aprendizado anterior).
Apresentar as duas versões um após o outro leva a duplicações de código, mas evita a dispersão confusa de ramificações if-else ao longo do código.
O segundo VAE está disponível como parte dos exemplos de Keras Portanto, você não precisa copiar trechos de código. O código também contém funcionalidade adicional não discutida e replicada aqui, como para salvar pesos do modelo.
Então, vamos começar com a parte comum.
Correndo o risco de nos repetir, aqui estão novamente as etapas preparatórias (incluindo algumas cargas adicionais da biblioteca).
Conjunto de dados
Para uma mudança de mnist e moda-mnist, usaremos o novo Kuzushiji-mnist(Clanuwat et al. 2018).

Como naquele outro submit, transmitimos os dados through tfdatasets:
buffer_size <- 60000
batch_size <- 256
batches_per_epoch <- buffer_size / batch_size
train_dataset <- tensor_slices_dataset(train_images) %>%
dataset_shuffle(buffer_size) %>%
dataset_batch(batch_size)
Agora vamos ver o que muda nos modelos de codificador e decodificador.
Codificador
O codificador difere do que tivemos sem Tfp na medida em que não retorna os meios e variações posteriores aproximados diretamente como tensores. Em vez disso, ele retorna um lote de distribuições normais multivariadas:
# you would possibly need to change this relying on the dataset
latent_dim <- 2
encoder_model <- operate(identify = NULL) {
keras_model_custom(identify = identify, operate(self) {
self$conv1 <-
layer_conv_2d(
filters = 32,
kernel_size = 3,
strides = 2,
activation = "relu"
)
self$conv2 <-
layer_conv_2d(
filters = 64,
kernel_size = 3,
strides = 2,
activation = "relu"
)
self$flatten <- layer_flatten()
self$dense <- layer_dense(items = 2 * latent_dim)
operate (x, masks = NULL) {
x <- x %>%
self$conv1() %>%
self$conv2() %>%
self$flatten() %>%
self$dense()
tfd$MultivariateNormalDiag(
loc = x(, 1:latent_dim),
scale_diag = tf$nn$softplus(x(, (latent_dim + 1):(2 * latent_dim)) + 1e-5)
)
}
})
}
Vamos tentar isso.
encoder <- encoder_model()
iter <- make_iterator_one_shot(train_dataset)
x <- iterator_get_next(iter)
approx_posterior <- encoder(x)
approx_posterior
tfp.distributions.MultivariateNormalDiag(
"MultivariateNormalDiag/", batch_shape=(256,), event_shape=(2,), dtype=float32
)
approx_posterior$pattern()
tf.Tensor(
(( 5.77791929e-01 -1.64988488e-02)
( 7.93901443e-01 -1.00042784e+00)
(-1.56279251e-01 -4.06365871e-01)
...
...
(-6.47531569e-01 2.10889503e-02)), form=(256, 2), dtype=float32)
Não sabemos sobre você, mas ainda gostamos da facilidade de inspecionar valores com Execução ansiosa – bastante.
Agora, no decodificador, que também retorna uma distribuição em vez de um tensor.
Decodificador
No decodificador, vemos por que as transformações entre a forma do lote e a forma do evento são úteis. A saída de self$deconv3
é quadridimensional. O que precisamos é de uma probabilidade para cada pixel. Anteriormente, isso period conseguido alimentando o tensor em uma camada densa e aplicando uma ativação sigmóide. Aqui, nós usamos tfd$Unbiased
Para transportar efetivamente o tensor em uma distribuição de probabilidade sobre imagens tridimensionais (largura, altura, canal (s)).
decoder_model <- operate(identify = NULL) {
keras_model_custom(identify = identify, operate(self) {
self$dense <- layer_dense(items = 7 * 7 * 32, activation = "relu")
self$reshape <- layer_reshape(target_shape = c(7, 7, 32))
self$deconv1 <-
layer_conv_2d_transpose(
filters = 64,
kernel_size = 3,
strides = 2,
padding = "similar",
activation = "relu"
)
self$deconv2 <-
layer_conv_2d_transpose(
filters = 32,
kernel_size = 3,
strides = 2,
padding = "similar",
activation = "relu"
)
self$deconv3 <-
layer_conv_2d_transpose(
filters = 1,
kernel_size = 3,
strides = 1,
padding = "similar"
)
operate (x, masks = NULL) {
x <- x %>%
self$dense() %>%
self$reshape() %>%
self$deconv1() %>%
self$deconv2() %>%
self$deconv3()
tfd$Unbiased(tfd$Bernoulli(logits = x),
reinterpreted_batch_ndims = 3L)
}
})
}
Vamos tentar isso também.
decoder <- decoder_model()
decoder_likelihood <- decoder(approx_posterior_sample)
tfp.distributions.Unbiased(
"IndependentBernoulli/", batch_shape=(256,), event_shape=(28, 28, 1), dtype=int32
)
Essa distribuição será usada para gerar as “reconstruções”, bem como determinar a loglikeliento das amostras originais.
Perda de KL e otimizador
Ambos os Vaes discutidos abaixo precisarão de um otimizador…
optimizer <- tf$practice$AdamOptimizer(1e-4)
… E ambos vão delegar para compute_kl_loss
Para calcular a parte KL da perda.
Essa função auxiliar simplesmente subtrai a probabilidade de log das amostras sob o prior de sua loglikeliodhity sob o posterior aproximado.
compute_kl_loss <- operate(
latent_prior,
approx_posterior,
approx_posterior_sample) {
kl_div <- approx_posterior$log_prob(approx_posterior_sample) -
latent_prior$log_prob(approx_posterior_sample)
avg_kl_div <- tf$reduce_mean(kl_div)
avg_kl_div
}
Agora que analisamos as partes comuns, discutimos primeiro como treinar um VAE com um prior estático.
Neste vae, nós usamos Tfp Para criar o ordinary prior dos Gaussianos Isotrópicos. Em seguida, amostrarmos diretamente dessa distribuição no ciclo de treinamento.
latent_prior <- tfd$MultivariateNormalDiag(
loc = tf$zeros(record(latent_dim)),
scale_identity_multiplier = 1
)
E aqui está o ciclo completo de treinamento. Vamos apontar o essential Tfp-Etapas relacionadas abaixo.
for (epoch in seq_len(num_epochs)) {
iter <- make_iterator_one_shot(train_dataset)
total_loss <- 0
total_loss_nll <- 0
total_loss_kl <- 0
until_out_of_range({
x <- iterator_get_next(iter)
with(tf$GradientTape(persistent = TRUE) %as% tape, {
approx_posterior <- encoder(x)
approx_posterior_sample <- approx_posterior$pattern()
decoder_likelihood <- decoder(approx_posterior_sample)
nll <- -decoder_likelihood$log_prob(x)
avg_nll <- tf$reduce_mean(nll)
kl_loss <- compute_kl_loss(
latent_prior,
approx_posterior,
approx_posterior_sample
)
loss <- kl_loss + avg_nll
})
total_loss <- total_loss + loss
total_loss_nll <- total_loss_nll + avg_nll
total_loss_kl <- total_loss_kl + kl_loss
encoder_gradients <- tape$gradient(loss, encoder$variables)
decoder_gradients <- tape$gradient(loss, decoder$variables)
optimizer$apply_gradients(purrr::transpose(record(
encoder_gradients, encoder$variables
)),
global_step = tf$practice$get_or_create_global_step())
optimizer$apply_gradients(purrr::transpose(record(
decoder_gradients, decoder$variables
)),
global_step = tf$practice$get_or_create_global_step())
})
cat(
glue(
"Losses (epoch): {epoch}:",
" {(as.numeric(total_loss_nll)/batches_per_epoch) %>% spherical(4)} nll",
" {(as.numeric(total_loss_kl)/batches_per_epoch) %>% spherical(4)} kl",
" {(as.numeric(total_loss)/batches_per_epoch) %>% spherical(4)} whole"
),
"n"
)
}
Acima, brincando com o codificador e o decodificador, já vimos como
approx_posterior <- encoder(x)
nos dá uma distribuição da qual podemos provar. Nós o usamos para obter amostras do posterior aproximado:
approx_posterior_sample <- approx_posterior$pattern()
Essas amostras, nós as levamos e as alimentamos ao decodificador, que nos dá-se-da-off-likelhoods para pixels de imagem.
decoder_likelihood <- decoder(approx_posterior_sample)
Agora, a perda consiste nos componentes elbo usuais: perda de reconstrução e divergência de KL. A perda de reconstrução que obtemos diretamente de Tfpusando a distribuição do decodificador aprendido para avaliar a probabilidade da entrada authentic.
nll <- -decoder_likelihood$log_prob(x)
avg_nll <- tf$reduce_mean(nll)
A perda de KL que obtemos de compute_kl_loss
a função ajudante que vimos acima:
kl_loss <- compute_kl_loss(
latent_prior,
approx_posterior,
approx_posterior_sample
)
Adicionamos os dois e chegamos à perda geral da VAE:
loss <- kl_loss + avg_nll
Além dessas mudanças devido ao uso Tfpo processo de treinamento é apenas o backprop regular, a maneira como parece usar Execução ansiosa.
Agora, vamos ver como, em vez de usar o gaussiano isotrópico padrão, poderíamos aprender uma mistura de gaussianos. A escolha do número de distribuições aqui é bastante arbitrária. Assim como em latent_dim
você pode querer experimentar e descobrir o que funciona melhor no seu conjunto de dados.
mixture_components <- 16
learnable_prior_model <- operate(identify = NULL, latent_dim, mixture_components) {
keras_model_custom(identify = identify, operate(self) {
self$loc <-
tf$get_variable(
identify = "loc",
form = record(mixture_components, latent_dim),
dtype = tf$float32
)
self$raw_scale_diag <- tf$get_variable(
identify = "raw_scale_diag",
form = c(mixture_components, latent_dim),
dtype = tf$float32
)
self$mixture_logits <-
tf$get_variable(
identify = "mixture_logits",
form = c(mixture_components),
dtype = tf$float32
)
operate (x, masks = NULL) {
tfd$MixtureSameFamily(
components_distribution = tfd$MultivariateNormalDiag(
loc = self$loc,
scale_diag = tf$nn$softplus(self$raw_scale_diag)
),
mixture_distribution = tfd$Categorical(logits = self$mixture_logits)
)
}
})
}
Em Tfp terminologia, components_distribution
é o tipo de distribuição subjacente e mixture_distribution
mantém as probabilidades que os componentes individuais são escolhidos.
Observe como self$loc
Assim, self$raw_scale_diag
e self$mixture_logits
são tensorflow Variables
e assim, persistente e atualizável por backprop.
Agora criamos o modelo.
latent_prior_model <- learnable_prior_model(
latent_dim = latent_dim,
mixture_components = mixture_components
)
Como obtemos uma distribuição anterior latente da qual podemos provar? Um pouco incomum, esse modelo será chamado sem uma entrada:
latent_prior <- latent_prior_model(NULL)
latent_prior
tfp.distributions.MixtureSameFamily(
"MixtureSameFamily/", batch_shape=(), event_shape=(2,), dtype=float32
)
Aqui agora está o loop completo de treinamento. Observe como temos um terceiro modelo para fazer o backprop.
for (epoch in seq_len(num_epochs)) {
iter <- make_iterator_one_shot(train_dataset)
total_loss <- 0
total_loss_nll <- 0
total_loss_kl <- 0
until_out_of_range({
x <- iterator_get_next(iter)
with(tf$GradientTape(persistent = TRUE) %as% tape, {
approx_posterior <- encoder(x)
approx_posterior_sample <- approx_posterior$pattern()
decoder_likelihood <- decoder(approx_posterior_sample)
nll <- -decoder_likelihood$log_prob(x)
avg_nll <- tf$reduce_mean(nll)
latent_prior <- latent_prior_model(NULL)
kl_loss <- compute_kl_loss(
latent_prior,
approx_posterior,
approx_posterior_sample
)
loss <- kl_loss + avg_nll
})
total_loss <- total_loss + loss
total_loss_nll <- total_loss_nll + avg_nll
total_loss_kl <- total_loss_kl + kl_loss
encoder_gradients <- tape$gradient(loss, encoder$variables)
decoder_gradients <- tape$gradient(loss, decoder$variables)
prior_gradients <-
tape$gradient(loss, latent_prior_model$variables)
optimizer$apply_gradients(purrr::transpose(record(
encoder_gradients, encoder$variables
)),
global_step = tf$practice$get_or_create_global_step())
optimizer$apply_gradients(purrr::transpose(record(
decoder_gradients, decoder$variables
)),
global_step = tf$practice$get_or_create_global_step())
optimizer$apply_gradients(purrr::transpose(record(
prior_gradients, latent_prior_model$variables
)),
global_step = tf$practice$get_or_create_global_step())
})
checkpoint$save(file_prefix = checkpoint_prefix)
cat(
glue(
"Losses (epoch): {epoch}:",
" {(as.numeric(total_loss_nll)/batches_per_epoch) %>% spherical(4)} nll",
" {(as.numeric(total_loss_kl)/batches_per_epoch) %>% spherical(4)} kl",
" {(as.numeric(total_loss)/batches_per_epoch) %>% spherical(4)} whole"
),
"n"
)
}
E é isso! Para nós, ambos os VAEs produziram resultados semelhantes e não experimentamos grandes diferenças ao experimentar a dimensionalidade latente e o número de distribuições de mistura. Mas, novamente, não gostaríamos de generalizar para outros conjuntos de dados, arquiteturas and so on.
Falando em resultados, como eles parecem? Aqui vemos cartas geradas após 40 épocas de treinamento. À esquerda estão letras aleatórias, à direita, a exibição common da grade VAE do espaço latente.
Felizmente, conseguimos mostrar que a probabilidade de tensorflow, a execução ansiosa e as keras são uma combinação atraente! Se você se relacionar Quantidade whole de código necessário Para a complexidade da tarefa, bem como a profundidade dos conceitos envolvidos, isso deve aparecer como uma implementação bastante concisa.
Em um futuro mais próximo, planejamos acompanhar as aplicações mais envolvidas da probabilidade de tensorflow, principalmente da área de aprendizado de representação. Fique atento!
Clanuwat, Tarin, Mikel Bober-Irizar, Asanobu Kitamoto, Alex Lamb, Kazuaki Yamamoto e David Ha. 2018. “Aprendizagem profunda para a literatura japonesa clássica”. 3 de dezembro de 2018. https://arxiv.org/abs/cs.cv/1812.01718.