Introdução
Regressão paramétrica é um tipo de modelagem cuja característica distintiva é a ausência (completa ou quase) de conhecimento a priori sobre a forma da função que está sendo estimada. Assim, o conjunto de formas que a função pode assumir é abrangente. Como consequência, haverá um número elevado de parâmetros estimados que não possuem uma interpretação física isolada.
As redes neurais para aprendizado supervisionado do tipo RBF (Radial Basis Function Neural Network) são um exemplo de modelo não-paramétrico baseado na teoria de aproximação de funções, que trata o problema genérico de aproximar uma função \(y(x)\) por uma função de aproximação \(f(w, x)\) dado um número fixo de parâmetros \(w\).
Uma função de ativação de base radial é caracterizada por apresentar uma resposta que decresce (ou cresce) monotomicamente com a distância em relação a um ponto central. O centro e a taxa de decrescimento em cada diração são alguns dos parâmetros a serem definidos. Uma função de base radial típica é a função gaussiana, dada na forma:
\(h_j(x) = \exp(\dfrac{-(x-c_j)^2}{{r_j^2}})\)
As funções de base radial podem ser utilizadas como funções-base em qualquer tipo de modelo de regressão não-linear e como função de ativação de qualquer tipo de rede multicamada. As redes RBF sempre apresentam uma única camada intermediária, os neurônios de saída são sempre lineares e os neurônios da camada intermediária têm uma função de base radial como função de ativação.
Um exemplo visual da rede RBF é mostrada na figura abaixo.
Uma das abordagens para o treinamento das redes neurais RBF é a adaptação supervisionada dos pesos da camada de saída, empregando técnicas como pseudo-inversão.
No caso de algoritmos que se ocupam apenas com o ajuste dos pesos da camada de saída de uma rede RBF, é necessário estabelecer algum critério para a fixação dos centros. Uma alternativa é auto-organizar os centros, de acordo com a distribuição ds dados de entrada, por meio do algoritmo k-means.
Em suma, existem três tipos de parâmetros que precisam ser aprendidos por uma rede neural RBF:
* Os centros da função de ativação;
* As dispersões (taxa de decrescimento) da função de ativação;
* Os pesos da camada intermediária para a camada de output.
Código
Para este exercício, são usados os seguintes pacotes:
library(readxl)
library(GGally)
library(tidyverse)
A leitura do arquivo e a mudança da ordem das colunas é feita abaixo:
arquivo <- "Base Projeto Daemon - Quant 2018.xlsx"
df <- readxl::read_excel(arquivo, sheet = "Sheet1",
col_names = TRUE, range = "B5:H128")
df <- as.data.frame(df)
# mudar ordem das colunas
df <- df[, rev(1:ncol(df))]
knitr::kable(head(df))
| 0.0120016 |
0.2751218 |
0.2343089 |
0.2645995 |
0.0335986 |
0.0515247 |
1.393146 |
| 0.0347053 |
0.2907528 |
0.2066793 |
0.2863589 |
0.0263905 |
0.0358156 |
1.444002 |
| 0.0360863 |
0.2920406 |
0.1471003 |
0.2780337 |
0.0234379 |
0.0301764 |
1.457103 |
| 0.0184709 |
0.1712113 |
0.1472847 |
0.1994207 |
0.0245995 |
0.0459377 |
1.462573 |
| 0.0212941 |
0.1456406 |
0.1891565 |
0.1937349 |
0.0232357 |
0.0437853 |
1.467897 |
| 0.0168037 |
0.2222374 |
0.1856247 |
0.2385001 |
0.0278113 |
0.0452344 |
1.434446 |
O objetivo deste trabalho é modelar a variável de frequência cardíaca em função de todas as outras variáveis do conjunto de dados.
Modelagem
No código abaixo, é criada uma função que realiza as seguintes tarefas:
* Extrai uma quantidade k de centros da matriz de input X. Por padrão, k corresponde à quantidade de colunas na matriz de input X;
* Aplica uma função de ativação gaussiana para obter a matriz \(\phi\), onde cada elemento da matriz N x k corresponde a \(\gamma \times h(\|x_i - t_i\|)\), onde \(\gamma\) corresponde à dispersão da função de ativação;
* Obtem pela técnica da matriz pseudo inversa o vetor de pesos, a partir da matriz \(\phi\) e do vetor de output (ou target) y;
* Retorna valores ajustados para o vetor de output y a partir de uma matriz de input X_test.
nrbf <- function(X, Y, X_test = X, k = ncol(X), gamma = 1.0, seed = 123, plot = TRUE){
library(corpcor)
library(neuralnet)
set.seed(seed)
#### Definição dos argumentos:
# X: Matriz de input
# Y: Matriz de output
# k: número de centros (polos)
# gamma: parametro de aprendizado
N <- nrow(X) # numero de observacoes
# criar funcao de ativacao gaussiana
ativ_gaussiana <- function(gamma, x, y){
# x e y sao vetores numericos
v <- as.matrix(x - y)
modulo <- norm(v, "F")
exp(-gamma * modulo^2)
}
# criar clusteres em matriz de input normalizada
cl <- kmeans(scale(X), centers = k)
cl_centros <- cl$centers # (Matriz de dimensões k x ncol(X))
# criar matriz vazia Phi para preencher posteriormente
matriz_phi <- matrix(NA_real_, nrow = N, ncol = k + 1)
#browser()
# iterar em cada linha
for (l in 1:N){
# Preencher a primeira coluna com 1 (coluna de bias)
matriz_phi[l, 1] <- 1
# iterar em cada coluna
for (c in 1:k){
# calcular modulo do vetor das diferencas
# preencher celula (com excecao da primeira coluna, que eh a de bias)
matriz_phi[l, c + 1] <- ativ_gaussiana(gamma = gamma, x = X[l, ], y = cl_centros[c, ])
}
}
w <- corpcor::pseudoinverse(t(matriz_phi) %*% matriz_phi) %*% t(matriz_phi) %*% Y
# obter previsoes para Y
# inicializar vetor de previsoes a partir do bias do vetor de pesos
prev <- rep(w[1], N)
for (n in 1:N){
for (j in 1:nrow(cl_centros)){
prev[n] <- prev[n] + w[j + 1] * ativ_gaussiana(gamma = gamma, x = X_test[n, ], y = cl_centros[j, ])
}
}
# plotar rede neural
if (plot){
# combinar input e target em um dataframe
df_nnet <- data.frame(input, target = target)
model_formula <- paste0("target ~ ", paste0(colnames(input), collapse = " + "))
nn <- neuralnet(model_formula, data = df_nnet, hidden = k)
plot(nn, information = FALSE, show.weights = FALSE)
}
list(weights=w, centers=cl_centros, gamma=gamma, prev = prev)
}
Após criar a função, o código abaixo mostra como é simples ajustar o modelo à matriz de input deste exercício:
input <- as.matrix(df[, -7]) # a ultima coluna corresponde ao vetor y que se deseja prever
target <- as.matrix(df[, 7])
modelo <- nrbf(input, target)

# visualizar resultados do modelo
modelo$weights
[,1]
[1,] 0.4299523943
[2,] -7643.2332381372
[3,] 4.9617618704
[4,] -1.8867322271
[5,] 69.6572399773
[6,] 12.6253134136
[7,] 0.1175875511
modelo$centers
nuVHF nuHF nuLF a1 RMSSD SDNN
1 0.5837561752 0.6350442465 -0.66467743516 1.4381165768 2.2253072683 2.1569218102
2 0.4543580595 1.4390765402 -0.72753512269 0.4754167243 0.4489823963 0.2195427664
3 -0.6797732920 -0.8771208817 -0.36038859061 -1.0627150483 -0.6210457527 -0.2637736570
4 1.9941167775 -0.2407254829 0.87993133176 -0.1132062724 -0.3152785222 -0.5166736536
5 -0.5319770927 -0.8128502961 1.74751201173 -0.9378453344 -0.5700864149 -0.3475751648
6 -0.5577582063 -0.2105481031 -0.05084140639 0.2103505167 -0.4882146668 -0.5692230101
Análise dos resultados do modelo
O código abaixo mostra a qualidade do ajuste do modelo:
prev <- modelo$prev
plot(target)
lines(prev, col = "blue", lty = 2)

O gráfico acima mostra que o ajuste obtido é relativamente bom, mas falhou em ajustar os pontos mais à esquerda no gráfico. A função abaixo calcula o MAPE (Mean Absolute Percentage Error) do modelo de função de base radial.
# obter acuracia
mape <- function(real, previsto){
erro <- real - previsto
mean(abs(erro)/real)
}
mape(target, prev)
[1] 0.07508241996
Portanto, o modelo apresenta um erro de 7,5%.
Otimização do número de polos (centros)
É possível com certa facilidade obter a qualidade de ajuste do modelo NRBF para diferentes valores do número de polos (k) usados no modelo para descobrir qual número maximiza a acurácia do modelo.
O código abaixo obtem o MAPE do modelo NRBF para valores de K de 1 a 60. Os resultados são mostrados no gráfico abaixo, no qual a linha vertical corresponde ao MAPE do modelo de regressão linear múltipla.
# simular valores de k para observar se o rmse cai
vetor_k <- 1:60
# criar dataframe para armazenar resultados do rmse
df_mape <- data.frame(k = vetor_k, mape = rep(NA, length(vetor_k)))
for (i in 1:length(vetor_k)){
prev_loop <- nrbf(input, target, k = vetor_k[i], plot = FALSE)$prev
df_mape[i, 2] <- mape(target, prev_loop)
}
# analisar resultados visualmente
ggplot(df_mape, aes(x = k, y = 100 * mape)) +
geom_point() +
scale_x_continuous(breaks = seq(0, 60, 5)) +
scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
geom_hline(yintercept = 100 * mape_lin, linetype = "dashed") +
labs(x = "Quantidade de polos", y = "MAPE (%)",
title = "Relação entre quantidade de polos e o erro do modelo \n de função de base radial")

Uma informação muito útil extraída do gráfico é que a partir de \(k = 20\) o modelo NRBF se torna melhor que o de regressão linear múltipla. O erro do modelo é reduzido sensivelmente de \(k = 30\) para \(k = 31\) e se estabiliza desde então, o que nos leva a acreditar que este seja o número ideal de polos para o modelo.
O gráfico abaixo mostra a qualidade do ajuste do modelo NRBF com 31 polos no conjunto de dados do exercício.
# obter modelo com 31 clusteres
melhor_modelo <- nrbf(input, target, k = 31, plot = TRUE)

plot(target)
lines(melhor_modelo$prev, col = "blue", lty = 2)

LS0tCnRpdGxlOiAiQXBsaWNhw6fDo28gZGUgTmV0d29yayBSYWRpYWwgQmFzZSBGdW5jdGlvbiBwYXJhIG1vZGVsYWdlbSBkZSBmcmVxdcOqbmNpYSBjYXJkw61hY2EiCmF1dGhvcjogIlNpbGxhcyBUZWl4ZWlyYSBHb256YWdhIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKIyBEZXNjcmnDp8OjbyBkbyBkZXNhZmlvCgpDb25zdHJ1aXIgdW0gYWxnb3JpdG1vIGVtIGxpbmd1YWdlbSBSLCB1dGlsaXphbmRvIE5SQkYgKE5ldHdvcmsgUmFkaWFsIEJhc2UgRnVuY2lvbikgcGFyYSBtb2RlbGFyIG8gY29tcG9ydGFtZW50byBkZSB1bSBmZW7DtG1lbm8gZGVzY3JpdG8gbmEgYmFzZSBkZSBkYWRvcyBubyBhcnF1aXZvIEFuZXhvLiAKRXNwZWNpZmljYW1lbnRlIHBhcmEgYSBhcGxpY2HDp8OjbyBkbyBOUkJGLCB1c2U6ICAKMS4gRnVuw6fDo28gcmFkaWFsIEdhdXNzaWFuYSBjb21vIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo28gIAoyLiBUw6ljbmljYSBkYSBtYXRyaXogcHNldWRvLWludmVyc2EsIHBhcmEgcmVzb2x1w6fDo28gZG8gc2lzdGVtYSBkZSBjb2VmaWNpZW50ZXMgZGFzIGZ1bsOnw7VlcyAgCjMuIE3DqXRvZG8gZGUgSy1tZWFucyBwYXJhIHNlbGXDp8OjbyBkb3MgcG9sb3MgIAo0LiBOw7ptZXJvIGRlIHBvbG9zIGlndWFsIG91IG1haW9yIMOgIHF1YW50aWRhZGUgZGUgdmFyacOhdmVpcyAoNikgIAoKQ29tbyByZXN1bHRhZG8sIGVzcGVyYW1vcyByZWNlYmVyIHVtIGPDs2RpZ28gZW0gUiBjb250ZW5kbzogIAoxLiBPIGFsZ29yaXRtbyBkZSBtb2RlbGFnZW0gIAoyLiBSb3RpbmFzIHBhcmEgYSB2aXN1YWxpemHDp8OjbyBkYSBhcGxpY2HDp8OjbyBkbyBhbGdvcml0bW8gIAozLiBBbsOhbGlzZSBkZSBhc3NlcnRpdmlkYWRlIGUgZXJybyAgCjQuIEFwbGljYcOnw6NvIGRvIGPDs2RpZ28gcGFyYSBvcyBkYWRvcyBkbyBhcnF1aXZvIEFuZXhvICAgCgojIEludHJvZHXDp8OjbwoKUmVncmVzc8OjbyBwYXJhbcOpdHJpY2Egw6kgdW0gdGlwbyBkZSBtb2RlbGFnZW0gY3VqYSBjYXJhY3RlcsOtc3RpY2EgZGlzdGludGl2YSDDqSBhIGF1c8OqbmNpYSAoY29tcGxldGEgb3UgcXVhc2UpIGRlIGNvbmhlY2ltZW50byBhIHByaW9yaSBzb2JyZSBhIGZvcm1hIGRhIGZ1bsOnw6NvIHF1ZSBlc3TDoSBzZW5kbyBlc3RpbWFkYS4gQXNzaW0sIG8gY29uanVudG8gZGUgZm9ybWFzIHF1ZSBhIGZ1bsOnw6NvIHBvZGUgYXNzdW1pciDDqSBhYnJhbmdlbnRlLiBDb21vIGNvbnNlcXXDqm5jaWEsIGhhdmVyw6EgdW0gbsO6bWVybyBlbGV2YWRvIGRlIHBhcsOibWV0cm9zIGVzdGltYWRvcyBxdWUgbsOjbyBwb3NzdWVtIHVtYSBpbnRlcnByZXRhw6fDo28gZsOtc2ljYSBpc29sYWRhLgoKQXMgcmVkZXMgbmV1cmFpcyBwYXJhIGFwcmVuZGl6YWRvIHN1cGVydmlzaW9uYWRvIGRvIHRpcG8gKipSQkYqKiAoKlJhZGlhbCBCYXNpcyBGdW5jdGlvbiBOZXVyYWwgTmV0d29yayopIHPDo28gdW0gZXhlbXBsbyBkZSBtb2RlbG8gbsOjby1wYXJhbcOpdHJpY28gYmFzZWFkbyBuYSB0ZW9yaWEgZGUgYXByb3hpbWHDp8OjbyBkZSBmdW7Dp8O1ZXMsIHF1ZSB0cmF0YSBvIHByb2JsZW1hIGdlbsOpcmljbyBkZSBhcHJveGltYXIgdW1hIGZ1bsOnw6NvICR5KHgpJCBwb3IgdW1hIGZ1bsOnw6NvIGRlIGFwcm94aW1hw6fDo28gJGYodywgeCkkIGRhZG8gdW0gbsO6bWVybyBmaXhvIGRlIHBhcsOibWV0cm9zICR3JC4KClVtYSBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvIGRlIGJhc2UgcmFkaWFsIMOpIGNhcmFjdGVyaXphZGEgcG9yIGFwcmVzZW50YXIgdW1hIHJlc3Bvc3RhIHF1ZSBkZWNyZXNjZSAob3UgY3Jlc2NlKSBtb25vdG9taWNhbWVudGUgY29tIGEgZGlzdMOibmNpYSBlbSByZWxhw6fDo28gYSB1bSBwb250byBjZW50cmFsLiBPIGNlbnRybyBlIGEgdGF4YSBkZSBkZWNyZXNjaW1lbnRvIGVtIGNhZGEgZGlyYcOnw6NvIHPDo28gYWxndW5zIGRvcyBwYXLDom1ldHJvcyBhIHNlcmVtIGRlZmluaWRvcy4gVW1hIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsIHTDrXBpY2Egw6kgYSBmdW7Dp8OjbyBnYXVzc2lhbmEsIGRhZGEgbmEgZm9ybWE6CgokaF9qKHgpID0gXGV4cChcZGZyYWN7LSh4LWNfaileMn17e3Jfal4yfX0pJCAgCgpBcyBmdW7Dp8O1ZXMgZGUgYmFzZSByYWRpYWwgcG9kZW0gc2VyIHV0aWxpemFkYXMgY29tbyBmdW7Dp8O1ZXMtYmFzZSBlbSBxdWFscXVlciB0aXBvIGRlIG1vZGVsbyBkZSByZWdyZXNzw6NvIG7Do28tbGluZWFyIGUgY29tbyBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvIGRlIHF1YWxxdWVyIHRpcG8gZGUgcmVkZSBtdWx0aWNhbWFkYS4gQXMgcmVkZXMgUkJGIHNlbXByZSBhcHJlc2VudGFtIHVtYSDDum5pY2EgY2FtYWRhIGludGVybWVkacOhcmlhLCBvcyBuZXVyw7RuaW9zIGRlIHNhw61kYSBzw6NvIHNlbXByZSBsaW5lYXJlcyBlIG9zIG5ldXLDtG5pb3MgZGEgY2FtYWRhIGludGVybWVkacOhcmlhIHTDqm0gdW1hIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsIGNvbW8gZnVuw6fDo28gZGUgYXRpdmHDp8Ojby4gIAoKVW0gZXhlbXBsbyB2aXN1YWwgZGEgcmVkZSBSQkYgw6kgbW9zdHJhZGEgbmEgZmlndXJhIGFiYWl4by4KCiFbXShodHRwczovL2kuaW1ndXIuY29tLzI3UFhiWlouanBnKQoKVW1hIGRhcyBhYm9yZGFnZW5zIHBhcmEgbyB0cmVpbmFtZW50byBkYXMgcmVkZXMgbmV1cmFpcyBSQkYgw6kgYSBhZGFwdGHDp8OjbyBzdXBlcnZpc2lvbmFkYSBkb3MgcGVzb3MgZGEgY2FtYWRhIGRlIHNhw61kYSwgZW1wcmVnYW5kbyB0w6ljbmljYXMgY29tbyBwc2V1ZG8taW52ZXJzw6NvLgoKTm8gY2FzbyBkZSBhbGdvcml0bW9zIHF1ZSBzZSBvY3VwYW0gYXBlbmFzIGNvbSBvIGFqdXN0ZSBkb3MgcGVzb3MgZGEgY2FtYWRhIGRlIHNhw61kYSBkZSB1bWEgcmVkZSBSQkYsIMOpIG5lY2Vzc8OhcmlvIGVzdGFiZWxlY2VyIGFsZ3VtIGNyaXTDqXJpbyBwYXJhIGEgZml4YcOnw6NvIGRvcyBjZW50cm9zLiBVbWEgYWx0ZXJuYXRpdmEgw6kgYXV0by1vcmdhbml6YXIgb3MgY2VudHJvcywgZGUgYWNvcmRvIGNvbSBhIGRpc3RyaWJ1acOnw6NvIGRzIGRhZG9zIGRlIGVudHJhZGEsIHBvciBtZWlvIGRvIGFsZ29yaXRtbyAqay1tZWFucyouCgpFbSBzdW1hLCBleGlzdGVtIHRyw6pzIHRpcG9zIGRlIHBhcsOibWV0cm9zIHF1ZSBwcmVjaXNhbSBzZXIgYXByZW5kaWRvcyBwb3IgdW1hIHJlZGUgbmV1cmFsIFJCRjogIAoqIE9zIGNlbnRyb3MgZGEgZnVuw6fDo28gZGUgYXRpdmHDp8OjbzsgIAoqIEFzIGRpc3BlcnPDtWVzICh0YXhhIGRlIGRlY3Jlc2NpbWVudG8pIGRhIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo287ICAKKiBPcyBwZXNvcyBkYSBjYW1hZGEgaW50ZXJtZWRpw6FyaWEgcGFyYSBhIGNhbWFkYSBkZSBvdXRwdXQuICAKCiMgQ8OzZGlnbwoKUGFyYSBlc3RlIGV4ZXJjw61jaW8sIHPDo28gdXNhZG9zIG9zIHNlZ3VpbnRlcyBwYWNvdGVzOgoKYGBge3J9CmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKQSBsZWl0dXJhIGRvIGFycXVpdm8gZSBhIG11ZGFuw6dhIGRhIG9yZGVtIGRhcyBjb2x1bmFzIMOpIGZlaXRhIGFiYWl4bzoKCmBgYHtyfQphcnF1aXZvIDwtICJCYXNlIFByb2pldG8gRGFlbW9uIC0gUXVhbnQgMjAxOC54bHN4IgpkZiA8LSByZWFkeGw6OnJlYWRfZXhjZWwoYXJxdWl2bywgc2hlZXQgPSAiU2hlZXQxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF9uYW1lcyA9IFRSVUUsIHJhbmdlID0gIkI1OkgxMjgiKQpkZiA8LSBhcy5kYXRhLmZyYW1lKGRmKQojIG11ZGFyIG9yZGVtIGRhcyBjb2x1bmFzCmRmIDwtIGRmWywgcmV2KDE6bmNvbChkZikpXQprbml0cjo6a2FibGUoaGVhZChkZikpCmBgYAoKTyBvYmpldGl2byBkZXN0ZSB0cmFiYWxobyDDqSBtb2RlbGFyIGEgdmFyacOhdmVsIGRlIGZyZXF1w6puY2lhIGNhcmTDrWFjYSBlbSBmdW7Dp8OjbyBkZSB0b2RhcyBhcyBvdXRyYXMgdmFyacOhdmVpcyBkbyBjb25qdW50byBkZSBkYWRvcy4gCgojIyBNb2RlbGFnZW0KCk5vIGPDs2RpZ28gYWJhaXhvLCDDqSBjcmlhZGEgdW1hIGZ1bsOnw6NvIHF1ZSByZWFsaXphIGFzIHNlZ3VpbnRlcyB0YXJlZmFzOiAgCiogRXh0cmFpIHVtYSBxdWFudGlkYWRlIGBrYCBkZSBjZW50cm9zIGRhIG1hdHJpeiBkZSBpbnB1dCBgWGAuIFBvciBwYWRyw6NvLCBga2AgY29ycmVzcG9uZGUgw6AgcXVhbnRpZGFkZSBkZSBjb2x1bmFzIG5hIG1hdHJpeiBkZSBpbnB1dCBgWGA7ICAKKiBBcGxpY2EgdW1hIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo28gZ2F1c3NpYW5hIHBhcmEgb2J0ZXIgYSBtYXRyaXogJFxwaGkkLCBvbmRlIGNhZGEgZWxlbWVudG8gZGEgbWF0cml6IE4geCBrIGNvcnJlc3BvbmRlIGEgJFxnYW1tYSBcdGltZXMgaChcfHhfaSAtIHRfaVx8KSQsIG9uZGUgJFxnYW1tYSQgY29ycmVzcG9uZGUgw6AgZGlzcGVyc8OjbyBkYSBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvOyAgCiogT2J0ZW0gcGVsYSB0w6ljbmljYSBkYSBtYXRyaXogcHNldWRvIGludmVyc2EgbyB2ZXRvciBkZSBwZXNvcywgYSBwYXJ0aXIgZGEgbWF0cml6ICRccGhpJCBlIGRvIHZldG9yIGRlIG91dHB1dCAob3UgdGFyZ2V0KSBgeWA7ICAKKiBSZXRvcm5hIHZhbG9yZXMgYWp1c3RhZG9zIHBhcmEgbyB2ZXRvciBkZSBvdXRwdXQgYHlgIGEgcGFydGlyIGRlIHVtYSBtYXRyaXogZGUgaW5wdXQgYFhfdGVzdGAuICAKCgpgYGB7ciBkZWZpbmljYW8gZGEgZnVuY2FvfQpucmJmIDwtIGZ1bmN0aW9uKFgsIFksIFhfdGVzdCA9IFgsIGsgPSBuY29sKFgpLCBnYW1tYSA9IDEuMCwgc2VlZCA9IDEyMywgcGxvdCA9IFRSVUUpewogIGxpYnJhcnkoY29ycGNvcikKICBsaWJyYXJ5KG5ldXJhbG5ldCkKICBzZXQuc2VlZChzZWVkKQogICMjIyMgRGVmaW5pw6fDo28gZG9zIGFyZ3VtZW50b3M6CiAgIyBYOiBNYXRyaXogZGUgaW5wdXQgCiAgIyBZOiBNYXRyaXogZGUgb3V0cHV0CiAgIyBrOiBuw7ptZXJvIGRlIGNlbnRyb3MgKHBvbG9zKQogICMgZ2FtbWE6IHBhcmFtZXRybyBkZSBhcHJlbmRpemFkbwogIAogIE4gPC0gbnJvdyhYKSAjIG51bWVybyBkZSBvYnNlcnZhY29lcwoKICAjIGNyaWFyIGZ1bmNhbyBkZSBhdGl2YWNhbyBnYXVzc2lhbmEKICBhdGl2X2dhdXNzaWFuYSA8LSBmdW5jdGlvbihnYW1tYSwgeCwgeSl7CiAgICAjIHggZSB5IHNhbyB2ZXRvcmVzIG51bWVyaWNvcwogICAgCiAgICB2IDwtIGFzLm1hdHJpeCh4IC0geSkKICAgIG1vZHVsbyA8LSBub3JtKHYsICJGIikKICAgIGV4cCgtZ2FtbWEgKiBtb2R1bG9eMikKICB9CiAgCiAgICAKICAjIGNyaWFyIGNsdXN0ZXJlcyBlbSBtYXRyaXogZGUgaW5wdXQgbm9ybWFsaXphZGEKICBjbCA8LSBrbWVhbnMoc2NhbGUoWCksIGNlbnRlcnMgPSBrKQogIGNsX2NlbnRyb3MgPC0gY2wkY2VudGVycyAjIChNYXRyaXogZGUgZGltZW5zw7VlcyBrIHggbmNvbChYKSkKICAjIGNyaWFyIG1hdHJpeiB2YXppYSBQaGkgcGFyYSBwcmVlbmNoZXIgIHBvc3Rlcmlvcm1lbnRlCiAgbWF0cml6X3BoaSA8LSBtYXRyaXgoTkFfcmVhbF8sIG5yb3cgPSBOLCBuY29sID0gayArIDEpCiAgCiAgI2Jyb3dzZXIoKQogICMgaXRlcmFyIGVtIGNhZGEgbGluaGEKICBmb3IgKGwgaW4gMTpOKXsKICAgICMgUHJlZW5jaGVyIGEgcHJpbWVpcmEgY29sdW5hIGNvbSAxIChjb2x1bmEgZGUgYmlhcykKICAgIG1hdHJpel9waGlbbCwgMV0gPC0gMQogICAgIyBpdGVyYXIgZW0gY2FkYSBjb2x1bmEgCiAgICBmb3IgKGMgaW4gMTprKXsKICAgICAgIyBjYWxjdWxhciBtb2R1bG8gZG8gdmV0b3IgZGFzIGRpZmVyZW5jYXMKICAgICAgIyBwcmVlbmNoZXIgY2VsdWxhIChjb20gZXhjZWNhbyBkYSBwcmltZWlyYSBjb2x1bmEsIHF1ZSBlaCBhIGRlIGJpYXMpCiAgICAgIG1hdHJpel9waGlbbCwgYyArIDFdIDwtIGF0aXZfZ2F1c3NpYW5hKGdhbW1hID0gZ2FtbWEsIHggPSBYW2wsIF0sIHkgPSBjbF9jZW50cm9zW2MsIF0pCiAgICB9CiAgfQogIAogIHcgPC0gY29ycGNvcjo6cHNldWRvaW52ZXJzZSh0KG1hdHJpel9waGkpICUqJSBtYXRyaXpfcGhpKSAlKiUgdChtYXRyaXpfcGhpKSAlKiUgWSAKICAKICAjIG9idGVyIHByZXZpc29lcyBwYXJhIFkKICAjIGluaWNpYWxpemFyIHZldG9yIGRlIHByZXZpc29lcyBhIHBhcnRpciBkbyBiaWFzIGRvIHZldG9yIGRlIHBlc29zCiAgcHJldiA8LSByZXAod1sxXSwgTikKICBmb3IgKG4gaW4gMTpOKXsKICAgIGZvciAoaiBpbiAxOm5yb3coY2xfY2VudHJvcykpewogICAgICBwcmV2W25dIDwtIHByZXZbbl0gKyB3W2ogKyAxXSAqIGF0aXZfZ2F1c3NpYW5hKGdhbW1hID0gZ2FtbWEsIHggPSBYX3Rlc3RbbiwgXSwgeSA9IGNsX2NlbnRyb3NbaiwgXSkKICAgIH0KICB9CiAgCiAgIyBwbG90YXIgcmVkZSBuZXVyYWwKICBpZiAocGxvdCl7CiAgICAjIGNvbWJpbmFyIGlucHV0IGUgdGFyZ2V0IGVtIHVtIGRhdGFmcmFtZQogICAgZGZfbm5ldCA8LSBkYXRhLmZyYW1lKGlucHV0LCB0YXJnZXQgPSB0YXJnZXQpCiAgICBtb2RlbF9mb3JtdWxhIDwtIHBhc3RlMCgidGFyZ2V0IH4gIiwgcGFzdGUwKGNvbG5hbWVzKGlucHV0KSwgY29sbGFwc2UgPSAiICsgIikpCiAgICBubiA8LSBuZXVyYWxuZXQobW9kZWxfZm9ybXVsYSwgZGF0YSA9IGRmX25uZXQsIGhpZGRlbiA9IGspCiAgICBwbG90KG5uLCBpbmZvcm1hdGlvbiA9IEZBTFNFLCBzaG93LndlaWdodHMgPSBGQUxTRSkKICB9CiAgCiAgbGlzdCh3ZWlnaHRzPXcsIGNlbnRlcnM9Y2xfY2VudHJvcywgZ2FtbWE9Z2FtbWEsIHByZXYgPSBwcmV2KSAgCn0KCgpgYGAKCkFww7NzIGNyaWFyIGEgZnVuw6fDo28sIG8gY8OzZGlnbyBhYmFpeG8gbW9zdHJhIGNvbW8gw6kgc2ltcGxlcyBhanVzdGFyIG8gbW9kZWxvIMOgIG1hdHJpeiBkZSBpbnB1dCBkZXN0ZSBleGVyY8OtY2lvOgoKYGBge3Igb2J0ZXIgbW9kZWxvfQppbnB1dCA8LSBhcy5tYXRyaXgoZGZbLCAtN10pICMgYSB1bHRpbWEgY29sdW5hIGNvcnJlc3BvbmRlIGFvIHZldG9yIHkgcXVlIHNlIGRlc2VqYSBwcmV2ZXIKdGFyZ2V0IDwtIGFzLm1hdHJpeChkZlssIDddKQoKbW9kZWxvIDwtIG5yYmYoaW5wdXQsIHRhcmdldCkKCiMgdmlzdWFsaXphciByZXN1bHRhZG9zIGRvIG1vZGVsbwptb2RlbG8kd2VpZ2h0cwptb2RlbG8kY2VudGVycwpgYGAKCgojIyBBbsOhbGlzZSBkb3MgcmVzdWx0YWRvcyBkbyBtb2RlbG8KCk8gY8OzZGlnbyBhYmFpeG8gbW9zdHJhIGEgcXVhbGlkYWRlIGRvIGFqdXN0ZSBkbyBtb2RlbG86ICAKCmBgYHtyfQpwcmV2IDwtIG1vZGVsbyRwcmV2CgpwbG90KHRhcmdldCkKbGluZXMocHJldiwgY29sID0gImJsdWUiLCBsdHkgPSAyKQoKYGBgCgpPIGdyw6FmaWNvIGFjaW1hIG1vc3RyYSBxdWUgbyBhanVzdGUgb2J0aWRvIMOpIHJlbGF0aXZhbWVudGUgYm9tLCBtYXMgZmFsaG91IGVtIGFqdXN0YXIgb3MgcG9udG9zIG1haXMgw6AgZXNxdWVyZGEgbm8gZ3LDoWZpY28uIEEgZnVuw6fDo28gYWJhaXhvIGNhbGN1bGEgbyBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpIGRvIG1vZGVsbyBkZSBmdW7Dp8OjbyBkZSBiYXNlIHJhZGlhbC4KCmBgYHtyfQojIG9idGVyIGFjdXJhY2lhCm1hcGUgPC0gZnVuY3Rpb24ocmVhbCwgcHJldmlzdG8pewogIGVycm8gPC0gcmVhbCAtIHByZXZpc3RvCiAgbWVhbihhYnMoZXJybykvcmVhbCkKfQoKbWFwZSh0YXJnZXQsIHByZXYpCgpgYGAKClBvcnRhbnRvLCBvIG1vZGVsbyBhcHJlc2VudGEgdW0gZXJybyBkZSA3LDUlLgoKIyMgQ29tcGFyYcOnw6NvIGNvbSBvIG1vZGVsbyBkZSByZWdyZXNzw6NvIGxpbmVhciBtw7psdGlwbGEgKE1STE0pCgpPIG1vZGVsbyBkZSByZWdyZXNzw6NvIGxpbmVhciBtw7psdGlwbGEgYWJhaXhvIMOpIGNyaWFkbyBwYXJhIGNvbXBhcmFyIHN1YSBhY3Vyw6FjaWEgY29tIG8gbW9kZWxvIGRlIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsOgoKYGBge3J9CiMgcmVncmVzc2FvIG51bHRpcGxhIGxpbmVhcgptb2RlbG9fbGluIDwtIGxtKGZjIH4gLiwgZGF0YSA9IGRmKQpwcmV2X2xpbiA8LSBmaXR0ZWQobW9kZWxvX2xpbikKbWFwZV9saW4gPC0gbWFwZSh0YXJnZXQsIHByZXZfbGluKQptYXBlX2xpbgpgYGAKCk8gZXJybyBkbyBtb2RlbG8gZGUgcmVncmVzc8OjbyBtw7psdGlwbGEgw6kgZGUgNSUsIG8gcXVlIHNpZ25pZmljYSBxdWUgc3VhIHF1YWxpZGFkZSBkZSBhanVzdGUgw6kgc3VwZXJpb3IgYW8gbW9kZWxvIGRlIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsLiBPIGdyw6FmaWNvIGFiYWl4byBtb3N0cmEgZXNzYSBjb21wYXJhw6fDo28gdmlzdWFsbWVudGU6CgpgYGB7cn0KCnBsb3QodGFyZ2V0KQpsaW5lcyhwcmV2LCBjb2wgPSAiYmx1ZSIsIGx0eSA9IDIpCmxpbmVzKGZpdHRlZChtb2RlbG9fbGluKSwgY29sID0gInJlZCIsIGx0eSA9IDIpCmxlZ2VuZCgidG9wcmlnaHQiLCBsdHkgPSAxLCBjb2wgPSBjKCJibHVlIiwgInJlZCIpLAogICAgICAgbGVnZW5kID0gYygiTlJCRiIsICJNUkxNIikpCmBgYAoKIyMgT3RpbWl6YcOnw6NvIGRvIG7Dum1lcm8gZGUgcG9sb3MgKGNlbnRyb3MpCgrDiSBwb3Nzw612ZWwgY29tIGNlcnRhIGZhY2lsaWRhZGUgb2J0ZXIgYSBxdWFsaWRhZGUgZGUgYWp1c3RlIGRvIG1vZGVsbyBOUkJGIHBhcmEgZGlmZXJlbnRlcyB2YWxvcmVzIGRvIG7Dum1lcm8gZGUgcG9sb3MgKGBrYCkgdXNhZG9zIG5vIG1vZGVsbyBwYXJhIGRlc2NvYnJpciBxdWFsIG7Dum1lcm8gbWF4aW1pemEgYSBhY3Vyw6FjaWEgZG8gbW9kZWxvLgoKTyBjw7NkaWdvIGFiYWl4byBvYnRlbSBvIE1BUEUgZG8gbW9kZWxvIE5SQkYgcGFyYSB2YWxvcmVzIGRlIEsgZGUgMSBhIDYwLiBPcyByZXN1bHRhZG9zIHPDo28gbW9zdHJhZG9zIG5vIGdyw6FmaWNvIGFiYWl4bywgbm8gcXVhbCBhIGxpbmhhIHZlcnRpY2FsIGNvcnJlc3BvbmRlIGFvIE1BUEUgZG8gbW9kZWxvIGRlIHJlZ3Jlc3PDo28gbGluZWFyIG3Dumx0aXBsYS4KCmBgYHtyIHNpbXVsYXIga30KIyBzaW11bGFyIHZhbG9yZXMgZGUgayBwYXJhIG9ic2VydmFyIHNlIG8gcm1zZSBjYWkKdmV0b3JfayA8LSAxOjYwCgojIGNyaWFyIGRhdGFmcmFtZSBwYXJhIGFybWF6ZW5hciByZXN1bHRhZG9zIGRvIHJtc2UKZGZfbWFwZSA8LSBkYXRhLmZyYW1lKGsgPSB2ZXRvcl9rLCBtYXBlID0gcmVwKE5BLCBsZW5ndGgodmV0b3JfaykpKQoKZm9yIChpIGluIDE6bGVuZ3RoKHZldG9yX2spKXsKICBwcmV2X2xvb3AgPC0gbnJiZihpbnB1dCwgdGFyZ2V0LCBrID0gdmV0b3Jfa1tpXSwgcGxvdCA9IEZBTFNFKSRwcmV2CiAgZGZfbWFwZVtpLCAyXSA8LSBtYXBlKHRhcmdldCwgcHJldl9sb29wKQp9CgojIGFuYWxpc2FyIHJlc3VsdGFkb3MgdmlzdWFsbWVudGUKZ2dwbG90KGRmX21hcGUsIGFlcyh4ID0gaywgeSA9IDEwMCAqIG1hcGUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCA1KSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3MobiA9IDEwKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEwMCAqIG1hcGVfbGluLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArIAogIGxhYnMoeCA9ICJRdWFudGlkYWRlIGRlIHBvbG9zIiwgeSA9ICJNQVBFICglKSIsCiAgICAgICB0aXRsZSA9ICJSZWxhw6fDo28gZW50cmUgcXVhbnRpZGFkZSBkZSBwb2xvcyBlIG8gZXJybyBkbyBtb2RlbG8gXG4gZGUgZnVuw6fDo28gZGUgYmFzZSByYWRpYWwiKQoKCgoKYGBgCgpVbWEgaW5mb3JtYcOnw6NvIG11aXRvIMO6dGlsIGV4dHJhw61kYSBkbyBncsOhZmljbyDDqSBxdWUgYSBwYXJ0aXIgZGUgJGsgPSAyMCQgbyBtb2RlbG8gTlJCRiBzZSB0b3JuYSBtZWxob3IgcXVlIG8gZGUgcmVncmVzc8OjbyBsaW5lYXIgbcO6bHRpcGxhLiBPIGVycm8gZG8gbW9kZWxvIMOpIHJlZHV6aWRvIHNlbnNpdmVsbWVudGUgZGUgJGsgPSAzMCQgcGFyYSAkayA9IDMxJCBlIHNlIGVzdGFiaWxpemEgZGVzZGUgZW50w6NvLCBvIHF1ZSBub3MgbGV2YSBhIGFjcmVkaXRhciBxdWUgZXN0ZSBzZWphIG8gbsO6bWVybyBpZGVhbCBkZSBwb2xvcyBwYXJhIG8gbW9kZWxvLgoKTyBncsOhZmljbyBhYmFpeG8gbW9zdHJhIGEgcXVhbGlkYWRlIGRvIGFqdXN0ZSBkbyBtb2RlbG8gTlJCRiBjb20gMzEgcG9sb3Mgbm8gY29uanVudG8gZGUgZGFkb3MgZG8gZXhlcmPDrWNpby4KCmBgYHtyIHBsb3QgbWVsaG9yIG1vZGVsb30KIyBvYnRlciBtb2RlbG8gY29tIDMxIGNsdXN0ZXJlcwptZWxob3JfbW9kZWxvIDwtIG5yYmYoaW5wdXQsIHRhcmdldCwgayA9IDMxLCBwbG90ID0gVFJVRSkKcGxvdCh0YXJnZXQpCmxpbmVzKG1lbGhvcl9tb2RlbG8kcHJldiwgY29sID0gImJsdWUiLCBsdHkgPSAyKQoKYGBgCgoKIyBSZWZlcsOqbmNpYXMKCltOb3RhcyBkZSBhdWxhIGRvcyBwcm9mZXNzb3JlcyBGZXJuYW5kbyBKLiBWb24gWnViZW4gJiBMZXZ5IEJvY2NhdG9dKGZ0cDovL2Z0cC5kY2EuZmVlLnVuaWNhbXAuYnIvcHViL2RvY3Mvdm9uenViZW4vaWEzNTNfMDUvdG9waWNvOF8wNS5wZGYpICAKCgpbRXhwbGljYcOnw6NvIHRlw7NyaWNhIGRlIE5SQkYgY29tIGFwbGljYcOnw6NvIGRlIGPDs2RpZ28gUl0oaHR0cDovL3d3dy5kaS5mYy51bC5wdC9+anBuL3IvcmJmL3JiZi5odG1sKQoK