Um pouco mais de um ano atrás, em sua bela postagem de convidadoNick Strayer mostrou como classificar um conjunto de atividades diárias usando dados de giroscópio e acelerômetro gravados em smartphone. A precisão foi muito boa, mas Nick passou a inspecionar os resultados de classificação mais de perto. Havia atividades mais propensas a classificação incorreta do que outras? E quanto a esses resultados errôneos: a rede os relatou com igual ou menos confiança do que aqueles que estavam corretos?
Tecnicamente, quando falamos de confiança Dessa maneira, estamos nos referindo ao pontuação obtido para a classe “vencedora” após a ativação do softmax. Se essa pontuação vencedora for de 0,9, podemos dizer “a rede tem certeza de que é um pinguim do Gentoo”; Se for 0,2, concluímos “para a rede, nenhuma opção parecia apropriada, mas Cheetah parecia melhor”.
Esse uso de “confiança” é convincente, mas não tem nada a ver com confiança – ou credibilidade ou previsão, o que você tem – intervalos. O que realmente gostaríamos de poder fazer é colocar distribuições sobre os pesos da rede e fazê -lo Bayesiano. Usando TFProbabilityAs camadas compatíveis com as keras variacionais, isso é algo que realmente podemos fazer.
Adicionando estimativas de incerteza aos modelos Keras com probabatização mostra como usar uma camada densa variacional para obter estimativas de incerteza epistêmica. Neste put up, modificamos o convnet usado na postagem de Nick para ser variacional. Antes de começarmos, vamos resumir rapidamente a tarefa.
A tarefa
Para criar o Reconhecimento baseado em smartphone de atividades humanas e conjunto de dados de transições posturais (Reyes-Ortiz et al. 2016)os pesquisadores tiveram assuntos a pé, sentados, permanecem e transitam de uma dessas atividades para outra. Enquanto isso, dois tipos de sensores de smartphone foram usados para gravar dados de movimento: Acelerômetros medir a aceleração linear em três dimensões, enquanto Giroscópios são usados para rastrear a velocidade angular ao redor dos eixos de coordenadas. Aqui estão os respectivos dados de sensor bruto para seis tipos de atividades da postagem authentic de Nick:
Assim como Nick, vamos aumentar esses seis tipos de atividade e tentar inferi -los dos dados do sensor. É necessária algumas disputas de dados para colocar o conjunto de dados em um formulário com o qual podemos trabalhar; Aqui vamos desenvolver a postagem de Nick e começaremos efetivamente a partir dos dados bem pré-processados e divididos em conjuntos de treinamento e teste:
Observations: 289
Variables: 6
$ experiment 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 17, 18, 19, 2…
$ userId 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 7, 7, 9, 9, 10, 10, 11…
$ exercise 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7…
$ information (, , STAND_TO_SIT, STAND_TO_SIT, STAND_TO_SIT, STAND_TO_S…
$ observationId 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14, 17, 18, 19, 2…
Observations: 69
Variables: 6
$ experiment 11, 12, 15, 16, 32, 33, 42, 43, 52, 53, 56, 57, 11, …
$ userId 6, 6, 8, 8, 16, 16, 21, 21, 26, 26, 28, 28, 6, 6, 8,…
$ exercise 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8…
$ information (, , STAND_TO_SIT, STAND_TO_SIT, STAND_TO_SIT, STAND_TO_S…
$ observationId 11, 12, 15, 16, 31, 32, 41, 42, 51, 52, 55, 56, 71, …
O código necessário para chegar a este estágio (copiado da postagem de Nick) pode ser encontrado no apêndice na parte inferior desta página.
Pipeline de treinamento
O conjunto de dados em questão é pequeno o suficiente para caber na memória – mas o seu pode não ser, por isso não pode prejudicar o streaming em ação. Além disso, provavelmente é seguro dizer isso com o tensorflow 2.0, tfdatasets Os pipelines são o maneira de alimentar dados para um modelo.
Depois que o código listado no apêndice é executado, os dados do sensor podem ser encontrados em trainData$information
uma coluna de lista contendo information.body
s, onde cada linha corresponde a um ponto no tempo e cada coluna contém uma das medições. No entanto, nem todas as séries temporais (gravações) são do mesmo comprimento; Assim, seguimos a postagem authentic para encaixar todas as séries em comprimento pad_size
(= 338). A forma esperada dos lotes de treinamento será então (batch_size, pad_size, 6)
.
Inicialmente, criamos nosso conjunto de dados de treinamento:
train_x <- train_data$information %>%
map(as.matrix) %>%
pad_sequences(maxlen = pad_size, dtype = "float32") %>%
tensor_slices_dataset()
train_y <- train_data$exercise %>%
one_hot_classes() %>%
tensor_slices_dataset()
train_dataset <- zip_datasets(train_x, train_y)
train_dataset
Então embaralhe e lote:
n_train <- nrow(train_data)
# the best attainable batch measurement for this dataset
# chosen as a result of it yielded the perfect efficiency
# alternatively, experiment with e.g. completely different studying charges, ...
batch_size <- n_train
train_dataset <- train_dataset %>%
dataset_shuffle(n_train) %>%
dataset_batch(batch_size)
train_dataset
O mesmo para os dados do teste.
test_x <- test_data$information %>%
map(as.matrix) %>%
pad_sequences(maxlen = pad_size, dtype = "float32") %>%
tensor_slices_dataset()
test_y <- test_data$exercise %>%
one_hot_classes() %>%
tensor_slices_dataset()
n_test <- nrow(test_data)
test_dataset <- zip_datasets(test_x, test_y) %>%
dataset_batch(n_test)
Usando tfdatasets
Não significa que não podemos executar uma verificação rápida de sanidade em nossos dados:
first <- test_dataset %>%
reticulate::as_iterator() %>%
# get first batch (= complete check set, in our case)
reticulate::iter_next() %>%
# predictors solely
.((1)) %>%
# first merchandise in batch
.(1,,)
first
tf.Tensor(
(( 0. 0. 0. 0. 0. 0. )
( 0. 0. 0. 0. 0. 0. )
( 0. 0. 0. 0. 0. 0. )
...
( 1.00416672 0.2375 0.12916666 -0.40225476 -0.20463985 -0.14782938)
( 1.04166663 0.26944447 0.12777779 -0.26755899 -0.02779437 -0.1441642 )
( 1.0250001 0.27083334 0.15277778 -0.19639318 0.35094208 -0.16249016)),
form=(338, 6), dtype=float64)
Agora vamos construir a rede.
Uma convnet variacional
Construímos a arquitetura convolucional direta do put up de Nick, apenas fazendo pequenas modificações em tamanhos de kernel e números de filtros. Também jogamos fora todas as camadas de abandono; Nenhuma regularização adicional é necessária sobre os anteriores aplicados aos pesos.
Observe o seguinte sobre a rede “bayesificada”.
Cada camada é de natureza variacional, os convolucionais (camada_conv_1d_flipout), bem como as camadas densas (Layer_Dense_flipout).
Com camadas variacionais, podemos especificar a distribuição de peso anterior, bem como a forma do posterior; Aqui, os padrões são usados, resultando em um posterior padrão anterior e em um campo médio padrão.
Da mesma forma, o usuário pode influenciar a função de divergência usada para avaliar a incompatibilidade entre anterior e posterior; Nesse caso, na verdade executamos alguma ação: escalamos a divergência (padrão) KL pelo número de amostras no conjunto de treinamento.
Uma última coisa a observar é a camada de saída. É uma camada de distribuição, ou seja, uma camada envolvendo uma distribuição – onde o embrulho significa: treinar a rede é comercial como de costume, mas as previsões são distribuiçõesum para cada ponto de dados.
library(tfprobability)
num_classes <- 6
# scale the KL divergence by variety of coaching examples
n <- n_train %>% tf$forged(tf$float32)
kl_div <- perform(q, p, unused)
tfd_kl_divergence(q, p) / n
mannequin <- keras_model_sequential()
mannequin %>%
layer_conv_1d_flipout(
filters = 12,
kernel_size = 3,
activation = "relu",
kernel_divergence_fn = kl_div
) %>%
layer_conv_1d_flipout(
filters = 24,
kernel_size = 5,
activation = "relu",
kernel_divergence_fn = kl_div
) %>%
layer_conv_1d_flipout(
filters = 48,
kernel_size = 7,
activation = "relu",
kernel_divergence_fn = kl_div
) %>%
layer_global_average_pooling_1d() %>%
layer_dense_flipout(
models = 48,
activation = "relu",
kernel_divergence_fn = kl_div
) %>%
layer_dense_flipout(
num_classes,
kernel_divergence_fn = kl_div,
identify = "dense_output"
) %>%
layer_one_hot_categorical(event_size = num_classes)
Dizemos à rede para minimizar a probabilidade negativa do log.
nll <- perform(y, mannequin) - (mannequin %>% tfd_log_prob(y))
Isso se tornará parte da perda. A maneira como configuramos este exemplo, essa não é a parte mais substancial. Aqui, o que domina a perda é a soma das divergências de KL, adicionadas (automaticamente) a mannequin$losses
.
Em uma configuração como essa, é interessante monitorar ambas as partes da perda separadamente. Podemos fazer isso por meio de duas métricas:
# the KL a part of the loss
kl_part <- perform(y_true, y_pred) {
kl <- tf$reduce_sum(mannequin$losses)
kl
}
# the NLL half
nll_part <- perform(y_true, y_pred) {
cat_dist <- tfd_one_hot_categorical(logits = y_pred)
nll <- - (cat_dist %>% tfd_log_prob(y_true) %>% tf$reduce_mean())
nll
}
Treinamos um pouco mais do que Nick no put up authentic, permitindo a parada precoce.
mannequin %>% compile(
optimizer = "rmsprop",
loss = nll,
metrics = c("accuracy",
custom_metric("kl_part", kl_part),
custom_metric("nll_part", nll_part)),
experimental_run_tf_function = FALSE
)
train_history <- mannequin %>% match(
train_dataset,
epochs = 1000,
validation_data = test_dataset,
callbacks = listing(
callback_early_stopping(endurance = 10)
)
)
Embora a perda geral diminua linearmente (e provavelmente teria para muitas outras épocas), esse não é o caso da precisão da classificação ou da parte da NLL da perda:
A precisão closing não é tão alta quanto na configuração não variária, embora ainda não seja ruim para um problema de seis lessons. Vemos que, sem regularização adicional, há muito pouco ajuste para os dados de treinamento.
Agora, como obtemos previsões desse modelo?
Previsões probabilísticas
Embora não entremos nisso aqui, é bom saber que acessamos mais do que apenas as distribuições de saída; através deles kernel_posterior
Atributo, também podemos acessar as distribuições de peso posterior das camadas ocultas.
Dado o tamanho pequeno do conjunto de testes, calculamos todas as previsões de uma só vez. As previsões agora são distribuições categóricas, uma para cada amostra no lote:
test_data_all <- dataset_collect(test_dataset) %>% { .((1))((1))}
one_shot_preds <- mannequin(test_data_all)
one_shot_preds
tfp.distributions.OneHotCategorical(
"sequential_one_hot_categorical_OneHotCategorical_OneHotCategorical",
batch_shape=(69), event_shape=(6), dtype=float32)
Nós prefixamos essas previsões com one_shot
Para indicar sua natureza barulhenta: essas são previsões obtidas em uma única passagem pela rede, todos os pesos da camada sendo amostrados de seus respectivos posteriors.
A partir das distribuições previstas, calculamos a média e o desvio padrão POR (teste) amostra.
Os desvios padrão assim obtidos podem ser considerados para refletir o geral incerteza preditiva. Podemos estimar outro tipo de incerteza, chamado epistêmicafazendo vários passes pela rede e depois calculando – novamente, por amostra de teste – os desvios padrão dos meios previstos.
Juntando tudo, nós temos
# A tibble: 414 x 6
obs class imply sd mc_sd label
1 1 V1 0.945 0.227 0.0743 STAND_TO_SIT
2 1 V2 0.0534 0.225 0.0675 SIT_TO_STAND
3 1 V3 0.00114 0.0338 0.0346 SIT_TO_LIE
4 1 V4 0.00000238 0.00154 0.000336 LIE_TO_SIT
5 1 V5 0.0000132 0.00363 0.00164 STAND_TO_LIE
6 1 V6 0.0000305 0.00553 0.00398 LIE_TO_STAND
7 2 V1 0.993 0.0813 0.149 STAND_TO_SIT
8 2 V2 0.00153 0.0390 0.102 SIT_TO_STAND
9 2 V3 0.00476 0.0688 0.108 SIT_TO_LIE
10 2 V4 0.00000172 0.00131 0.000613 LIE_TO_SIT
# … with 404 extra rows
Comparando previsões com a verdade do fundamento:
# A tibble: 69 x 7
obs maxprob maxprob_sd maxprob_mc_sd predicted fact appropriate
1 1 0.945 0.227 0.0743 STAND_TO_SIT STAND_TO_SIT TRUE
2 2 0.993 0.0813 0.149 STAND_TO_SIT STAND_TO_SIT TRUE
3 3 0.733 0.443 0.131 STAND_TO_SIT STAND_TO_SIT TRUE
4 4 0.796 0.403 0.138 STAND_TO_SIT STAND_TO_SIT TRUE
5 5 0.843 0.364 0.358 SIT_TO_STAND STAND_TO_SIT FALSE
6 6 0.816 0.387 0.176 SIT_TO_STAND STAND_TO_SIT FALSE
7 7 0.600 0.490 0.370 STAND_TO_SIT STAND_TO_SIT TRUE
8 8 0.941 0.236 0.0851 STAND_TO_SIT STAND_TO_SIT TRUE
9 9 0.853 0.355 0.274 SIT_TO_STAND STAND_TO_SIT FALSE
10 10 0.961 0.195 0.195 STAND_TO_SIT STAND_TO_SIT TRUE
11 11 0.918 0.275 0.168 STAND_TO_SIT STAND_TO_SIT TRUE
12 12 0.957 0.203 0.150 STAND_TO_SIT STAND_TO_SIT TRUE
13 13 0.987 0.114 0.188 SIT_TO_STAND SIT_TO_STAND TRUE
14 14 0.974 0.160 0.248 SIT_TO_STAND SIT_TO_STAND TRUE
15 15 0.996 0.0657 0.0534 SIT_TO_STAND SIT_TO_STAND TRUE
16 16 0.886 0.318 0.0868 SIT_TO_STAND SIT_TO_STAND TRUE
17 17 0.773 0.419 0.173 SIT_TO_STAND SIT_TO_STAND TRUE
18 18 0.998 0.0444 0.222 SIT_TO_STAND SIT_TO_STAND TRUE
19 19 0.885 0.319 0.161 SIT_TO_STAND SIT_TO_STAND TRUE
20 20 0.930 0.255 0.271 SIT_TO_STAND SIT_TO_STAND TRUE
# … with 49 extra rows
Os desvios padrão são mais altos para classificações incorretas?
# A tibble: 2 x 5
appropriate depend avg_mean avg_sd avg_mc_sd
1 FALSE 19 0.775 0.380 0.237
2 TRUE 50 0.879 0.264 0.183
Eles são; embora talvez não na medida em que possamos desejar.
Com apenas seis lessons, também podemos inspecionar desvios padrão no nível de pares de previsão de previsão.
# A tibble: 14 x 7
# Teams: fact (6)
fact predicted cnt avg_mean avg_sd avg_mc_sd appropriate
1 SIT_TO_STAND SIT_TO_STAND 12 0.935 0.205 0.184 TRUE
2 STAND_TO_SIT STAND_TO_SIT 9 0.871 0.284 0.162 TRUE
3 LIE_TO_SIT LIE_TO_SIT 9 0.765 0.377 0.216 TRUE
4 SIT_TO_LIE SIT_TO_LIE 8 0.908 0.254 0.187 TRUE
5 STAND_TO_LIE STAND_TO_LIE 7 0.956 0.144 0.132 TRUE
6 LIE_TO_STAND LIE_TO_STAND 5 0.809 0.353 0.227 TRUE
7 SIT_TO_LIE STAND_TO_LIE 4 0.685 0.436 0.233 FALSE
8 LIE_TO_STAND SIT_TO_STAND 4 0.909 0.271 0.282 FALSE
9 STAND_TO_LIE SIT_TO_LIE 3 0.852 0.337 0.238 FALSE
10 STAND_TO_SIT SIT_TO_STAND 3 0.837 0.368 0.269 FALSE
11 LIE_TO_STAND LIE_TO_SIT 2 0.689 0.454 0.233 FALSE
12 LIE_TO_SIT STAND_TO_SIT 1 0.548 0.498 0.0805 FALSE
13 SIT_TO_STAND LIE_TO_STAND 1 0.530 0.499 0.134 FALSE
14 LIE_TO_SIT LIE_TO_STAND 1 0.824 0.381 0.231 FALSE
Novamente, vemos desvios padrão mais altos para previsões incorretas, mas não em alto grau.
Conclusão
Mostramos como construir, treinar e obter previsões de um convite totalmente variacional. Evidentemente, existem espaço para experimentação: existem implementações de camada alternativa; Um prior diferente poderia ser especificado; A divergência pode ser calculada de maneira diferente; e as opções usuais das opções de ajuste de hiperparâmetro da rede neural.
Então, há a questão das consequências (ou: tomada de decisão). O que vai acontecer em casos de alta incerteza, qual é o caso de alta incerteza? Naturalmente, perguntas como essas estão fora do escopo para este put up, mas de importância essencial em aplicativos do mundo actual. Obrigado pela leitura!
Apêndice
A ser executado antes de executar o código desta postagem. Copiado de Classificando a atividade física dos dados do smartphone.
library(keras)
library(tidyverse)
activity_labels <- learn.desk("information/activity_labels.txt",
col.names = c("quantity", "label"))
one_hot_to_label <- activity_labels %>%
mutate(quantity = quantity - 7) %>%
filter(quantity >= 0) %>%
mutate(class = paste0("V",quantity + 1)) %>%
choose(-quantity)
labels <- learn.desk(
"information/RawData/labels.txt",
col.names = c("experiment", "userId", "exercise", "startPos", "endPos")
)
dataFiles <- listing.recordsdata("information/RawData")
dataFiles %>% head()
fileInfo <- data_frame(
filePath = dataFiles
) %>%
filter(filePath != "labels.txt") %>%
separate(filePath, sep = '_',
into = c("kind", "experiment", "userId"),
take away = FALSE) %>%
mutate(
experiment = str_remove(experiment, "exp"),
userId = str_remove_all(userId, "consumer|.txt")
) %>%
unfold(kind, filePath)
# Learn contents of single file to a dataframe with accelerometer and gyro information.
readInData <- perform(experiment, userId){
genFilePath = perform(kind) {
paste0("information/RawData/", kind, "_exp",experiment, "_user", userId, ".txt")
}
bind_cols(
learn.desk(genFilePath("acc"), col.names = c("a_x", "a_y", "a_z")),
learn.desk(genFilePath("gyro"), col.names = c("g_x", "g_y", "g_z"))
)
}
# Operate to learn a given file and get the observations contained alongside
# with their lessons.
loadFileData <- perform(curExperiment, curUserId) {
# load sensor information from file into dataframe
allData <- readInData(curExperiment, curUserId)
extractObservation <- perform(startPos, endPos){
allData(startPos:endPos,)
}
# get statement places on this file from labels dataframe
dataLabels <- labels %>%
filter(userId == as.integer(curUserId),
experiment == as.integer(curExperiment))
# extract observations as dataframes and save as a column in dataframe.
dataLabels %>%
mutate(
information = map2(startPos, endPos, extractObservation)
) %>%
choose(-startPos, -endPos)
}
# scan via all experiment and userId combos and collect information right into a dataframe.
allObservations <- map2_df(fileInfo$experiment, fileInfo$userId, loadFileData) %>%
right_join(activityLabels, by = c("exercise" = "quantity")) %>%
rename(activityName = label)
write_rds(allObservations, "allObservations.rds")
allObservations <- readRDS("allObservations.rds")
desiredActivities <- c(
"STAND_TO_SIT", "SIT_TO_STAND", "SIT_TO_LIE",
"LIE_TO_SIT", "STAND_TO_LIE", "LIE_TO_STAND"
)
filteredObservations <- allObservations %>%
filter(activityName %in% desiredActivities) %>%
mutate(observationId = 1:n())
# get all customers
userIds <- allObservations$userId %>% distinctive()
# randomly select 24 (80% of 30 people) for coaching
set.seed(42) # seed for reproducibility
trainIds <- pattern(userIds, measurement = 24)
# set the remainder of the customers to the testing set
testIds <- setdiff(userIds,trainIds)
# filter information.
# be aware S.Ok.: renamed to train_data for consistency with
# variable naming used on this put up
train_data <- filteredObservations %>%
filter(userId %in% trainIds)
# be aware S.Ok.: renamed to test_data for consistency with
# variable naming used on this put up
test_data <- filteredObservations %>%
filter(userId %in% testIds)
# be aware S.Ok.: renamed to pad_size for consistency with
# variable naming used on this put up
pad_size <- trainData$information %>%
map_int(nrow) %>%
quantile(p = 0.98) %>%
ceiling()
# be aware S.Ok.: renamed to one_hot_classes for consistency with
# variable naming used on this put up
one_hot_classes <- . %>%
{. - 7} %>% # deliver integers right down to 0-6 from 7-12
to_categorical() # One-hot encode
Reyes-Ortiz, Jorge-L., Luca Oneto, Albert Samà, Xavier Parra e Davide Anguita. 2016. “Reconhecimento de atividades humanas consciente da transição usando smartphones”. Neurocomput. 171 (c): 754–67. https://doi.org/10.1016/j.neucom.2015.07.085.