Olá pessoal!
Vamos falar sobre, como você deve ter adivinhado, redes neurais e aprendizado de máquina. Pelo nome, fica claro o que será dito sobre as redes de densidade de mistura, e então apenas a MDN, não quero traduzir o nome e deixá-lo como está. Sim, sim, sim ... haverá um pouco de matemática chata e teoria das probabilidades, mas sem ela, infelizmente ou felizmente, cabe a você decidir o quão difícil é imaginar o mundo do aprendizado de máquina. Mas me apresso em tranquilizá-lo, será relativamente pequeno e não será muito difícil. De qualquer forma, você pode pular, mas basta olhar para uma pequena quantidade de código no Python e no PyTorch, isso mesmo, escreveremos a rede usando o PyTorch, além de vários gráficos com os resultados. Mas o mais importante é que haverá uma oportunidade de entender um pouco e entender o que são redes MD.
Bem, vamos começar!
Regressão
Para começar, vamos atualizar um pouco nosso conhecimento e relembrar brevemente, o que
é regressão linear .
Nós temos um vetor
X = \ {x_1, x_2, ..., x_n \}X = \ {x_1, x_2, ..., x_n \} precisamos prever o valor
Y , que de alguma forma depende
X usando algum modelo linear:
hatY=XT hat beta
Como função de erro, usaremos o erro ao quadrado:
SE( beta)= sumni=1(yi− hatyi)2= sumNi=1(yi−xTi hat beta)2
Esse problema pode ser resolvido diretamente pegando a derivada de SE e definindo seu valor como zero:
frac deltaSE( beta) delta beta=2XT( mathbfy−X beta)=0
Assim, simplesmente encontramos seu mínimo, e SE é uma função quadrática, o que significa que o mínimo sempre existirá. Depois disso, você já pode encontrar facilmente
beta :
hat beta=(XTX)−1XT mathbfy
Isso é tudo, o problema está resolvido. É aqui que terminamos de lembrar o que é regressão linear.
Obviamente, a dependência inerente à natureza da geração de dados pode ser diferente e, então, alguma não linearidade já deve ser adicionada ao nosso modelo. Resolver o problema de regressão diretamente para dados grandes e reais também é uma má ideia, pois existe uma matriz
XTX dimensões
n vezesn , e ainda é necessário encontrar sua matriz inversa, e muitas vezes acontece que essa matriz simplesmente não existe. Nesse caso, vários métodos baseados na descida do gradiente vêm em nosso auxílio. A não linearidade dos modelos pode ser implementada de várias maneiras, incluindo o uso de redes neurais.
Mas agora, não vamos falar sobre isso, mas sobre funções de erro. Qual é a diferença entre SE e Probabilidade de log quando os dados podem ter um relacionamento não linear?
Lidamos com o zoológico, a saber: OLS, LS, SE, MSE, RSSTudo isso é o mesmo em essência: RSS - soma dos quadrados residuais, OLS - mínimos quadrados ordinários, LS - mínimos quadrados, MSE - erro ao quadrado médio, SE - erro ao quadrado. Em diferentes fontes, você pode encontrar nomes diferentes. A essência disso é apenas uma:
desvio quadrático . Você pode se confundir, é claro, mas se acostuma rapidamente.
Vale ressaltar que MSE é o desvio padrão, um certo valor médio do erro para todo o conjunto de dados de treinamento. Na prática, o MSE é geralmente usado. A fórmula não é particularmente diferente:
MSE( beta)= frac1N sumni=1(yi− hatyi)2
N - o tamanho do conjunto de dados,
hatyi - previsão de modelo para
yi .
Pare com isso! Probabilidade? Isso é algo da teoria das probabilidades. Isso mesmo - é pura teoria das probabilidades. Mas como o desvio quadrático pode estar relacionado à função de probabilidade? E como acontece. Está relacionado à busca da máxima verossimilhança (máxima verossimilhança) e da distribuição normal, para ser mais preciso, com sua média
mu .
Para perceber que é assim, vejamos novamente a função de desvio quadrado:
RSS( beta)= sumni=1(yi− hatyi)2 qquad qquad(1)
Agora, suponha que a função de probabilidade tenha uma forma normal, ou seja, uma distribuição gaussiana ou normal:
L(X)=p(X| theta)= prodX mathcalN(xi; mu, sigma2)
Em geral, qual é a função de probabilidade e qual é o significado dela, não vou contar; você pode ler sobre isso em outro lugar; também deve se familiarizar com o conceito de probabilidade condicional, o teorema de Bayes e muito mais, para uma compreensão mais profunda. Tudo isso entra na pura teoria da probabilidade, estudada tanto na escola quanto na universidade.
Agora, lembrando a fórmula de distribuição normal, obtemos:
L(X; mu, sigma2)= prodX frac1 sqrt2 pi sigma2e− frac(xi− mu)22 sigma2 qquad qquad(2)
E se colocarmos o desvio padrão
sigma2=1 e remova todas as constantes da fórmula (2), apenas remova, não reduza, porque encontrar o mínimo da função não depende delas. Então vamos ver isso:
L(X; mu, sigma2) sim prodXe−(xi− mu)2
Ainda nada como? Não? Bem, e se pegarmos o logaritmo da função? No logaritmo, existem algumas vantagens: a multiplicação se transformará em uma soma, um grau em multiplicação e
loge=1 - para esta propriedade, vale esclarecer que estamos falando do logaritmo natural e, estritamente falando
lne=1 . E, em geral, o logaritmo de uma função não altera seu máximo, e esse é o recurso mais importante para nós. A conexão com o Log-Likelihood e Likelihood e por que isso será útil será descrita abaixo em uma pequena digressão. E o que fizemos: removemos todas as constantes e assumimos o logaritmo da função de probabilidade. Eles também removeram o sinal de menos, transformando a Probabilidade de log em Probabilidade de log negativo (NLL); a conexão entre eles também será descrita como um bônus. Como resultado, obtivemos a função NLL:
logL(X; mu,I2) sim sum(X− mu)2
Veja novamente a função RSS (1). Sim, eles são iguais! Exatamente! Também é visto que
mu= haty .
Se você usa a função de desvio padrão do MSE, obtemos disso:
nomedooperadorargminMSE( beta) sim nomedooperadorargmax mathbbEX simPdados logPmodelo(x; beta)
onde
mathbbE - expectativa matemática
beta - parâmetros do modelo, no futuro, iremos denotá-los como:
theta .
Conclusão: Se usarmos a família LS como função de erro na questão da regressão, resolveremos essencialmente o problema de encontrar a função de máxima verossimilhança no caso em que a distribuição é gaussiana. E o valor previsto
haty igual à média na distribuição normal. E agora sabemos como tudo isso está conectado, como a teoria das probabilidades está relacionada (com sua função de probabilidade e distribuição normal) e métodos de desvio padrão ou OLS. Mais detalhes sobre isso podem ser encontrados em [2].
E aqui está o bônus prometido. Como estamos falando das relações entre as várias funções de erro, consideraremos (não necessariamente para ser lido):
A relação entre entropia, verossimilhança, verossimilhança e verossimilhança negativasSuponha que tenhamos dados
X = \ {x_1, x_2, x_3, x_4, ... \}X = \ {x_1, x_2, x_3, x_4, ... \} , cada ponto pertence a uma classe específica, por exemplo
\ {x_1 \ rightarrow1, x_2 \ rightarrow2, x_3 \ rightarrow n, ... \}\ {x_1 \ rightarrow1, x_2 \ rightarrow2, x_3 \ rightarrow n, ... \} . Total lá
n enquanto a classe 1 ocorre
c1 vezes, classe 2 -
c2 horários e aula
n -
cn vezes. Sobre esses dados, treinamos algum modelo
theta . A função de probabilidade (Probabilidade) para ela será parecida com esta:
P(dados| theta)=P(0,1,...,n| theta)=P(0| theta)P(1| theta)...P(n| theta)
P(1| theta)P(2| theta)...P(n| theta)= prodc1 haty1 prodc2 haty2... prodcn hatyn= hatyc11 hatyc22... hatycnn
onde
P(n| theta)= hatyn - probabilidade prevista para a classe
n .
Pegamos o logaritmo da função de probabilidade e obtemos o Log-Likelihood:
logP(dados| theta)= log( hatyc11... hatycnn)=c1 log haty1+...+cn log hatyn= sumnici log hatyi
Probabilidade
haty em[0,1] está no intervalo de 0 a 1, com base na definição de probabilidade. Portanto, o logaritmo terá um valor negativo. E se multiplicarmos a probabilidade do log por -1, obtemos a função Probabilidade do log negativo (NLL):
NLL=− logP(dados| theta)=− sumnici log hatyi
Se dividirmos a NLL pelo número de pontos em
X ,
N=c1+c2+...+cn então temos:
− frac1N logP(dados| theta)=− sumni fracciN log hatyi
pode-se notar que a probabilidade real para a classe
n é igual a:
yn= fraccnN . A partir daqui, obtemos:
NLL=− sumniyi log hatyi
Agora, se você olhar para a definição de entropia cruzada
H(p,q)=− somap logq então temos:
NLL=H(yi, hatyi)
No caso em que temos apenas duas classes
n=2 (classificação binária), obtemos a fórmula para entropia cruzada binária (você também pode encontrar o conhecido nome Log-Loss):
H(y, haty)=−(y log haty+(1−y) log(1− haty))
De tudo isso, pode-se entender que, em alguns casos, minimizar a entropia cruzada é equivalente a minimizar a NLL ou encontrar a função de máxima verossimilhança (probabilidade) ou log-verossimilhança.
Um exemplo Considere uma classificação binária. Temos valores de classe:
y = np.array([0, 1, 1, 1, 1, 0, 1, 1]).astype(np.float32)
Probabilidade real
y para a classe 0 é igual

, para a classe 1 é igual

. Suponha que tenhamos um classificador binário que prediz a probabilidade da classe 0
haty para cada exemplo, respectivamente, para a classe 1, a probabilidade é
(1− haty) . Vamos traçar os valores da função Log-Loss para diferentes previsões
haty :
No gráfico, você pode ver que o mínimo da função Log-Loss corresponde ao ponto 0,75, ou seja, se nosso modelo "aprendeu" completamente a distribuição dos dados de origem, haty=y . Regressão de rede neural
Então chegamos a uma prática mais interessante. Vamos ver como você pode resolver o problema de regressão usando redes neurais (redes neurais). Implementaremos tudo na linguagem de programação Python. Para criar uma rede, usamos a biblioteca de aprendizado profundo do PyTorch.
Geração de dados de origem
Dados de entrada
mathbfX in mathbbRN gerar usando uma distribuição uniforme, leve o intervalo de -15 a 15,
mathbfX emU[−15,15] . Pontos
mathbfY obtemos usando a equação:
mathbfY=0,5 mathbfX+8 sin(0,3 mathbfX)+ruído qquad qquad(3)
onde
noise É um vetor de ruído de dimensão
N obtido usando a distribuição normal com parâmetros:
mu=0, sigma2=1 .
O gráfico dos dados recebidos.Construção de rede
Crie uma rede neural de avanço de alimentação regular ou FFNN.
Edifício FFNN class Net(nn.Module): def __init__(self, input_dim=IN_DIM, out_dim=OUT_DIM, layer_size=40): super(Net, self).__init__() self.fc = nn.Linear(input_dim, layer_size) self.logit = nn.Linear(layer_size, out_dim) def forward(self, x): x = F.tanh(self.fc(x))
Nossa rede consiste em uma camada oculta com uma dimensão de 40 neurônios e com uma função de ativação - tangente hiperbólica:
tanhx= fracex−e−xex+e−x qquad qquad(4)
A camada de saída é uma transformação linear normal sem uma função de ativação.
Aprendendo e obtendo resultados
Como otimizador, usaremos o AdamOptimizer. O número de épocas de estudo = 2000, a taxa de aprendizagem (taxa de aprendizagem ou lr) = 0,1.
Treinamento FFNN def train(net, x_train, y_train, x_test, y_test, epoches=2000, lr=0.1): criterion = nn.MSELoss() optimizer = optim.Adam(net.parameters(), lr=lr) N_EPOCHES = epoches BS = 1500 n_batches = int(np.ceil(x_train.shape[0] / BS)) train_losses = [] test_losses = [] for i in range(N_EPOCHES): for bi in range(n_batches): x_batch, y_batch = fetch_batch(x_train, y_train, bi, BS) x_train_var = Variable(torch.from_numpy(x_batch)) y_train_var = Variable(torch.from_numpy(y_batch)) optimizer.zero_grad() outputs = net(x_train_var) loss = criterion(outputs, y_train_var) loss.backward() optimizer.step() with torch.no_grad(): x_test_var = Variable(torch.from_numpy(x_test)) y_test_var = Variable(torch.from_numpy(y_test)) outputs = net(x_test_var) test_loss = criterion(outputs, y_test_var) test_losses.append(test_loss.item()) train_losses.append(loss.item()) if i%100 == 0: sys.stdout.write('\r Iter: %d, test loss: %.5f, train loss: %.5f' %(i, test_loss.item(), loss.item())) sys.stdout.flush() return train_losses, test_losses net = Net() train_losses, test_losses = train(net, x_train, y_train, x_test, y_test)
Agora vamos ver os resultados da aprendizagem.
Gráfico dos valores da função MSE, dependendo da iteração do treinamento; gráfico de valores para dados de treinamento e dados de teste.Resultados reais e previstos em dados de teste.Dados invertidos
Nós complicamos a tarefa e invertemos os dados.
Inversão de dados x_train_inv = y_train y_train_inv = x_train x_test_inv = y_train y_test_inv = x_train
Gráfico de dados invertidos.Para previsão
mathbf hatY vamos usar a rede de distribuição direta da seção anterior e ver como ela lida com isso.
inv_train_losses, inv_test_losses = train(net, x_train_inv, y_train_inv, x_test_inv, y_test_inv)
Gráfico dos valores da função MSE, dependendo da iteração do treinamento; gráfico dos valores dos dados de treinamento e de teste.Resultados reais e previstos em dados de teste.Como você pode ver nos gráficos acima, nossa rede não lidou com esses dados, simplesmente não é capaz de prever. E tudo isso aconteceu porque em um problema tão invertido por um ponto
x pode corresponder a vários pontos
y . Você pergunta, e o barulho? Ele também criou uma situação em que, por um
x poderia obter alguns valores
y . Sim está certo. Mas o ponto principal é que, apesar do barulho, era tudo uma distribuição definida. E como nosso modelo previu essencialmente
p(y|x) , e no caso de MSE, era o valor médio para a distribuição normal (por que é descrito na primeira parte do artigo), depois lidou bem com a tarefa "direta". Caso contrário, obteremos várias distribuições diferentes para um
x e, portanto, não podemos obter um bom resultado com apenas uma distribuição normal.
Rede de densidade de mistura
A diversão começa! O que é rede de densidade de mistura (a seguir denominada rede MDN ou MD)? Em geral, este é um determinado modelo capaz de simular várias distribuições ao mesmo tempo:
p( mathbfy| mathbfx; theta)= sumKk pik( mathbfx) mathcalN( mathbfy; muk( mathbfx), sigma2( mathbfx)) qquad qquad(5)
Que fórmula estranha, você diz. Vamos descobrir. Nossa rede MD está aprendendo a modelar a média
mu e variação
sigma2 para
várias distribuições. Na fórmula (5)
pik( mathbfx) - os chamados fatores de significância de uma distribuição separada para cada ponto
xi in mathbfx , um determinado fator de mistura ou quanto cada uma das distribuições contribui para um determinado ponto. Total lá
K distribuições.
Mais algumas palavras sobre
pik( mathbfx) - de fato, isso também é uma distribuição e representa a probabilidade de que, por um ponto
xi in mathbfx será uma condição
k .
Ah, novamente, essa matemática, já vamos escrever algo. E assim, começaremos a realizar uma rede. Para a nossa rede, tomamos
K=30 .
self.fc = nn.Linear(input_dim, layer_size) self.fc2 = nn.Linear(layer_size, 50) self.pi = nn.Linear(layer_size, coefs) self.mu = nn.Linear(layer_size, out_dim*coefs)
Defina as camadas de saída para nossa rede:
x = F.relu(self.fc(x)) x = F.relu(self.fc2(x)) pi = F.softmax(self.pi(x), dim=1) sigma_sq = torch.exp(self.sigma_sq(x)) mu = self.mu(x)
Escrevemos a função de erro ou função de perda, fórmula (5):
def gaussian_pdf(x, mu, sigma_sq): return (1/torch.sqrt(2*np.pi*sigma_sq)) * torch.exp((-1/(2*sigma_sq)) * torch.norm((x-mu), 2, 1)**2) losses = Variable(torch.zeros(y.shape[0]))
Código de compilação MDN completo COEFS = 30 class MDN(nn.Module): def __init__(self, input_dim=IN_DIM, out_dim=OUT_DIM, layer_size=50, coefs=COEFS): super(MDN, self).__init__() self.fc = nn.Linear(input_dim, layer_size) self.fc2 = nn.Linear(layer_size, 50) self.pi = nn.Linear(layer_size, coefs) self.mu = nn.Linear(layer_size, out_dim*coefs)
Nossa rede MD está pronta para funcionar. Quase pronto. Resta treiná-la e analisar os resultados.
Treinamento MDN def train_mdn(net, x_train, y_train, x_test, y_test, epoches=1000): optimizer = optim.Adam(net.parameters(), lr=0.01) N_EPOCHES = epoches BS = 1500 n_batches = int(np.ceil(x_train.shape[0] / BS)) train_losses = [] test_losses = [] for i in range(N_EPOCHES): for bi in range(n_batches): x_batch, y_batch = fetch_batch(x_train, y_train, bi, BS) x_train_var = Variable(torch.from_numpy(x_batch)) y_train_var = Variable(torch.from_numpy(y_batch)) optimizer.zero_grad() pi, mu, sigma_sq = net(x_train_var) loss = loss_fn(y_train_var, pi, mu, sigma_sq) loss.backward() optimizer.step() with torch.no_grad(): if i%10 == 0: x_test_var = Variable(torch.from_numpy(x_test)) y_test_var = Variable(torch.from_numpy(y_test)) pi, mu, sigma_sq = net(x_test_var) test_loss = loss_fn(y_test_var, pi, mu, sigma_sq) train_losses.append(loss.item()) test_losses.append(test_loss.item()) sys.stdout.write('\r Iter: %d, test loss: %.5f, train loss: %.5f' %(i, test_loss.item(), loss.item())) sys.stdout.flush() return train_losses, test_losses mdn_net = MDN() mdn_train_losses, mdn_test_losses = train_mdn(mdn_net, x_train_inv, y_train_inv, x_test_inv, y_test_inv)

O gráfico dos valores da função de perda dependendo da iteração do treinamento; o gráfico de valores para dados de treinamento e dados de teste.Como nossa rede aprendeu os valores médios para várias distribuições, vejamos o seguinte:
pi, mu, sigma_sq = mdn_net(Variable(torch.from_numpy(x_test_inv)))
Gráfico para os dois valores médios mais prováveis para cada ponto (esquerda). Gráfico para os 4 valores médios mais prováveis para cada ponto (à direita).Gráfico para todos os valores médios para cada ponto.Para prever dados, selecionaremos aleatoriamente vários valores
mu e
sigma2 com base no valor
pik( mathbfx) . E então, com base neles, para gerar dados de destino
haty usando distribuição normal.
Previsão de resultado def rand_n_sample_cumulative(pi, mu, sigmasq, samples=10): n = pi.shape[0] out = Variable(torch.zeros(n, samples, OUT_DIM)) for i in range(n): for j in range(samples): u = np.random.uniform() prob_sum = 0 for k in range(COEFS): prob_sum += pi.data[i, k] if u < prob_sum: for od in range(OUT_DIM): sample = np.random.normal(mu.data[i, k*OUT_DIM+od], np.sqrt(sigmasq.data[i, k])) out[i, j, od] = sample break return out pi, mu, sigma_sq = mdn_net(Variable(torch.from_numpy(x_test_inv))) preds = rand_n_sample_cumulative(pi, mu, sigma_sq, samples=10)
Dados previstos para 10 valores selecionados aleatoriamente mu e sigma2 (esquerda) e para dois (direita).Pode-se ver pelas figuras que a MDN fez um excelente trabalho com a tarefa "inversa".
Usando dados mais complexos
Vamos ver como nossa rede MD lida com dados mais complexos, como dados em espiral. A equação da espiral hiperbólica nas coordenadas cartesianas:
x= rho cos phi qquad qquad qquad qquad qquad qquad(6)y= rho sin phi
Geração de dados em espiral N = 2000 x_train_compl = [] y_train_compl = [] x_test_compl = [] y_test_compl = [] noise_train = np.random.uniform(-1, 1, (N, IN_DIM)).astype(np.float32) noise_test = np.random.uniform(-1, 1, (N, IN_DIM)).astype(np.float32) for i, theta in enumerate(np.linspace(0, 5*np.pi, N).astype(np.float32)):
Gráfico de dados em espiral.Por diversão, vamos ver como uma rede Feed-Forward regular lidará com essa tarefa.
Como esperado, a rede Feed-Forward não pode resolver o problema de regressão para esses dados.Utilizamos a rede MD descrita e criada anteriormente para treinamento em dados em espiral.
A Rede de densidade da mistura fez um ótimo trabalho nessa situação.Conclusão
No início deste artigo, lembramos os princípios básicos da regressão linear. Vimos isso em comum entre encontrar a média para a distribuição normal e MSE. Desmontado como conectado NLL e entropia cruzada. E o mais importante, descobrimos o modelo MDN, que é capaz de aprender com os dados obtidos de uma distribuição mista. Espero que o artigo seja compreensível e interessante, apesar de haver um pouco de matemática.
O código completo pode ser visualizado no
GitHub .
Literatura
- Redes de densidade de mistura (Christopher M. Bishop, Grupo de Pesquisa em Computação Neural, Departamento de Ciência da Computação e Matemática Aplicada, Universidade de Aston, Birmingham) - o artigo descreve completamente a teoria das redes de MD.
- Mínimos quadrados e máxima verossimilhança (MROsborne)