Primeira vez aqui? Seja bem vindo e cheque o FAQ!
x

Como implementar a técnica de inicialização de uma rede neural usando autoencoders no Python?

+1 voto
490 visitas
perguntada Jul 8, 2016 em Aprendizagem de Máquinas por Lyw (21 pontos)  

Implemente um perceptron multicamada e aplique para aprender a seguinte função:

f (x, y) = [y ∗ exp(sin(x)) + u, sin(x) ∗ cos(y) + v , sin(x) + cos(y) + w]

Use o python para criar sua rede neural e considere o procedimento de inicialização usando autoencoders. Compare a habilidade de generalização da rede considerando esse procedimento e também sem esse procedimento. Detalhe todo o procedimento usado e documente bem a sua solução.

Compartilhe

1 Resposta

0 votos
respondida Jul 8, 2016 por Lyw (21 pontos)  

Criei a rede neural usando o PyBrain. Infelizmente, o PyBrain não tem a técnica de inicialização com autoencoders implementada (até onde eu pude ver). Então, implementei essa parte diretamente.

Na primeira parte eu repeti o procedimento feito em http://prorum.com/index.php/2616/implementar-perceptron-multicamadas-aprender-funcao-python para fins de comparação. A diferença é a função nn_connections, que serve apenas como auxiliar na análise. Sua utilidade será destacada adiante:

def nn_connections(n):
    """
    Funcao que recebe uma rede neural e imprime todas as conexoes identificadas

    """
    for mod in n.modules:
        for conn in n.connections[mod]:
            print conn
            for cc in range(len(conn.params)):
                print conn.whichBuffers(cc), conn.params[cc]

Em seguida, programei o autoencoder que replica a camada de entrada. O procedimento é normal, com exceção de que a rede tem o mesmo número de entradas e de saídas e que é treinada com os dados de entrada. Variei uns parâmetros de aprendizagem aqui para fins de regularização. Os comentários no código dão mais detalhes:

    ###########################################################################
# Autoencoder: as linhas seguintes calculam os parâmetros do autoencoder  #
# da camada de entrada                                                    #
###########################################################################

# inicia-se a rede com os 12 neuronios da camada intermediaria e o mesmo
# numero de neuronios na entrada e na saida, para reproduzir as entradas
rn_in = buildNetwork(dimensaoDaEntrada, dimensaoDaCamadaEscondida,
                     dimensaoDaEntrada, bias=True, hiddenclass=TanhLayer)

dados_in = SupervisedDataSet(dimensaoDaEntrada, dimensaoDaEntrada)
dados_in.setField('input', dados['input'])
dados_in.setField('target', dados['input'])

# A tecnica como apresentada em Quoc V. Le A Tutorial on Deep Learning
# Part 2: Autoencoders, Convolutional Neural Networks and Recurrent Neural
# Networks nao e exatamente adequada para o caso. Como existem mais
# neuronios na camada intermediaria q na entrada, corre-se o risco da
# representacao dos dados ficar ruim, com concentracao de pesos em poucos
# neuronios. Eu alterei o weightdecay e a learningrate para aliviar o
# problema, e funcionou relativamente bem. Porem, o desempenho da rede com
# os autoencoders nao se mostrou sistematicamente superior ao caso controle
# na primeira parte. Talvez seja mais adequado usar a tecnica de sparse
# autoencoders, que visa minimizar exatamente este problema. Ou combinar
# a tecnica dos autoencoders com a tecnica de dropout.
treinadorSupervisionado_in = BackpropTrainer(rn_in, dados_in,
                                             weightdecay=0.01,
                                             learningrate=0.001)

# Grafico dos erros durante aprendizagem
fig_in = plt.figure()
ax_in = fig_in.add_subplot(111)
ax_in.axis([-50, numeroDeAcessos * numeroDeEpocasPorAcesso+50, 0.00001, 4])
ax_in.set_yscale('log')
fig_in.hold()
meansq_in = ModuleValidator()
erro_in = meansq_in.MSE(treinadorSupervisionado_in.module, dados_in)
print erro_in
ax_in.plot([0], [erro_in], 'bo')

tempoPausa = 1
for i in range(numeroDeAcessos):
    treinadorSupervisionado_in.trainEpochs(numeroDeEpocasPorAcesso)
    meansq_in = ModuleValidator()
    erro_in = meansq_in.MSE(treinadorSupervisionado_in.module, dados_in)
    print erro_in
    ax_in.plot([numeroDeEpocasPorAcesso * (i + 1)], [erro_in], 'bo')
    plt.pause(tempoPausa)

inputOutputRede_in = np.array([rn_in.activate(datax) for datax,
                              _ in dados_in])

# Grafico scatter plot comparando as entradas com as saidas da rede
# treinada. O ideal e que sejam o mais proximos possivel.
fig_in2 = plt.figure()
ax_in2 = fig_in2.add_subplot(111)
ax_in2.scatter(dados['input'][:, 0], dados['input'][:, 1], c='b')
ax_in2.scatter(inputOutputRede_in[:, 0], inputOutputRede_in[:, 1],
               c='r', marker="+")

Este código retorna o gráfico da evolução dos erros:

A imagem será apresentada aqui.

Note que o erro é pequeno, como esperado. O próximo gráfico compara as entradas com as saídas geradas pelo autoencoder treinado:

A imagem será apresentada aqui.

Note que os pontos são próximos, mas não exatamente iguais. O código seguinte treina as conexões de saída. Tem um fato crucial aqui: como eu coleto o sinal gerado pela camada escondida do primeiro autoencoder pra alimentar a rede, temos um perceptron de camada única com este sinal nas entradas, os dados de saída na saída e neurônios lineares. É de se esperar que sua capacidade de aprendizagem seja ruim, e de fato, é:

    ###########################################################################
# Autoencoder: as linhas seguintes calculam os parâmetros do autoencoder  #
# da camada de saida                                                      #
###########################################################################

# Aqui inicio um perceptron de camada unica com as entradas na dimensao
# da camada escondida e a saida desejada. Este treinamento e ruim, porque
# esta rede e linear e nao tem camada escondida
dados_hidden = SupervisedDataSet(dimensaoDaCamadaEscondida,
                                 dimensaoDaSaida)

# Este loop coleta os sinais da camada escondida gerados pela ativacao
# do autoencoder da camada de entrada com os dados de entrada. Estes dados
# 'artificiais' serao a entrada para o treinamento do autoencoder de saida
for i in range(dados_in['input'].shape[0]):
    rn_in.activate(dados_in['input'][i, :])
    dados_hidden.addSample(rn_in['hidden0'].outputbuffer,
                           dados['target'][i, :])

rn_hidden = buildNetwork(dimensaoDaCamadaEscondida,
                         dimensaoDaSaida, bias=True)

# Aqui novamente tentei melhorar a aprendizagem mudando alguns parametros
# do algoritmo de backpropagation
treinadorSupervisionado_hidden = BackpropTrainer(rn_hidden, dados_hidden,
                                                 weightdecay=0.02,
                                                 learningrate=0.0005)

# Plot com erros durante aprendizagem
fig_hidden = plt.figure()
ax_hidden = fig_hidden.add_subplot(111)
ax_hidden.axis([-50, numeroDeAcessos * numeroDeEpocasPorAcesso+50,
                0.00001, 4])
ax_hidden.set_yscale('log')
fig_hidden.hold()
meansq_hidden = ModuleValidator()
erro_hidden = meansq_hidden.MSE(treinadorSupervisionado_hidden.module,
                                dados_hidden)
print erro_hidden
ax_hidden.plot([0], [erro_hidden], 'bo')

tempoPausa = 1
for i in range(numeroDeAcessos):
    treinadorSupervisionado_hidden.trainEpochs(numeroDeEpocasPorAcesso)
    meansq_hidden = ModuleValidator()
    erro_hidden = meansq_hidden.MSE(treinadorSupervisionado_hidden.module,
                                    dados_hidden)
    print erro_hidden
    ax_hidden.plot([numeroDeEpocasPorAcesso * (i + 1)],
                   [erro_hidden], 'bo')
    plt.pause(tempoPausa)

Finalmente, eu junto as peças. Coleto os pesos da entrada do primeiro autoencoder junto com os pesos da camada se saída do segundo autoencoder e junto numa rede igual à original que vai iniciar a aprendizagem a partir dos pesos calculados pelo processo de autoencoding. Há uma questão técnica aqui envolvendo as limitações do PyBrain. Detalhes nos comentários do código:

    ###############################################################################
# As linhas seguintes refazem a aprendizagem a partir dos pesos calculados    #
# com auxilio dos autoencoders. Aqui eu junto as pecas, colocando os pesos    #
# calculados nas duas secoes anteriores em uma rede neural com a estrutura    #
# inicial para que a inicializacao da rede se de com os pesos calculados      #
###############################################################################

rn_autoencoded = buildNetwork(dimensaoDaEntrada, dimensaoDaCamadaEscondida,
                              dimensaoDaSaida, bias=True,
                              hiddenclass=TanhLayer)

# Estes dois loops coletam os parametros de interesse nos autoencoders
for mod in rn_in.modules:
    for conn in rn_in.connections[mod]:
        if conn.inmod.name == 'in' and conn.outmod.name == 'hidden0':
            in_to_hidden = conn.params
        elif conn.inmod.name == 'bias' and conn.outmod.name == 'hidden0':
            bias_to_hidden = conn.params

for mod in rn_hidden.modules:
    for conn in rn_hidden.connections[mod]:
        if conn.inmod.name == 'in' and conn.outmod.name == 'out':
            hidden_to_out = conn.params
        elif conn.inmod.name == 'bias' and conn.outmod.name == 'out':
            bias_to_out = conn.params

# Esta parte do codigo foi a mais complicada, porque o PyBrain nao permite
# que eu use o metodo de uso interno _setParameters para alterar os 
# parametros da rede modulo a modulo. Apenas e possivel aterar todos os
# pesos de uma vez.
# O problema e que os modulos formam um set, e nao podem ser indexados.
# Deste modo, a ordem dos parametro e meio arbitraria. Fiz varios testes e,
# no caso apresentado, a ordem sempre foi a utilizada na atribuicao abaixo.
# Porem, nao consegui descobrir o porque. Repeti varias vezes e a ordem nao
# se alterou, mas nao sei se em outra maquina isto pode acontecer.
# Pesquisando na internet, vi varios usuarios com o mesmo problema e
# ninguem apresentou uma solucao satisfatoria. Se o desempenho desta rede
# com os autoencoders for muito ruim, recomendo verificar se os parametros
# forma passados da forma correta. Para tanto, disponibilizei a funcao
# nn_connections definida no inicio do codigo. Ela recebe uma rede neural
# e imprime todas as conexoes.
autoencodedParameters = list(bias_to_out) + list(bias_to_hidden) +\
                        list(in_to_hidden) + list(hidden_to_out)

# Aqui os parametros dos autoencoders sao copiados para a rede. Considere
# o comentario anterior.
rn_autoencoded._setParameters(np.array(autoencodedParameters))

treinadorSupervisionado_autoencoded = BackpropTrainer(rn_autoencoded,
                                                      dados,
                                                      learningrate=0.005)

# Plot dos erros durante a aprendizagem
fig_autoencoded = plt.figure()
ax_autoencoded = fig_autoencoded.add_subplot(111)
ax_autoencoded.axis([-50, numeroDeAcessos * numeroDeEpocasPorAcesso+50,
                     0.00001, 4])
ax_autoencoded.set_yscale('log')
fig_autoencoded.hold()
meansq_autoencoded = ModuleValidator()
erro_autoencoded = meansq_autoencoded.MSE(treinadorSupervisionado_autoencoded.module,
                                          dados)
print erro_autoencoded
ax_autoencoded.plot([0], [erro_autoencoded], 'bo')

tempoPausa = 1
for i in range(numeroDeAcessos):
    treinadorSupervisionado_autoencoded.trainEpochs(numeroDeEpocasPorAcesso)
    meansq_autoencoded = ModuleValidator()
    erro_autoencoded = meansq_autoencoded.MSE(treinadorSupervisionado_autoencoded.module,
                                              dados)
    print erro_autoencoded
    ax_autoencoded.plot([numeroDeEpocasPorAcesso * (i + 1)],
                        [erro_autoencoded], 'bo')
    plt.pause(tempoPausa)

inputOutputRede_autoencoded = np.array([rn_autoencoded.activate(datax)
                                       for datax, _ in dados])

# Os graficos abaixo mostram os dados em azul, os resultados da rede sobre
# as entradas apos o terinamento em vermelho e tambem as superficies
# que representam a funcao alvo na saida sem ruido para comparacao
fig1_autoencoded = plt.figure()
ax1_autoencoded = fig1_autoencoded.gca(projection='3d')
plt.hold(True)
x_surf = np.arange(0, 2 * math.pi, 0.05)
y_surf = np.arange(0, 2 * math.pi, 0.05)
x_surf, y_surf = np.meshgrid(x_surf, y_surf)
z_surf = y_surf * np.exp(np.sin(x_surf))
ax1_autoencoded.plot_surface(x_surf, y_surf, z_surf,
                             cmap=cm.hot, alpha=0.2)
ax1_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        dados['target'][:, 0], c='b')
ax1_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        inputOutputRede_autoencoded[:, 0], c='r')
plt.show()

fig2_autoencoded = plt.figure()
ax2_autoencoded = fig2_autoencoded.gca(projection='3d')
plt.hold(True)
x_surf = np.arange(0, 2 * math.pi, 0.05)
y_surf = np.arange(0, 2 * math.pi, 0.05)
x_surf, y_surf = np.meshgrid(x_surf, y_surf)
z_surf = np.sin(x_surf) * np.cos(y_surf)
ax2_autoencoded.plot_surface(x_surf, y_surf, z_surf, cmap=cm.hot, alpha=0.2)
ax2_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        dados['target'][:, 1], c='b')
ax2_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        inputOutputRede_autoencoded[:, 1], c='r')
plt.show()

fig3_autoencoded = plt.figure()
ax3_autoencoded = fig3_autoencoded.gca(projection='3d')
plt.hold(True)
x_surf = np.arange(0, 2 * math.pi, 0.05)
y_surf = np.arange(0, 2 * math.pi, 0.05)
x_surf, y_surf = np.meshgrid(x_surf, y_surf)
z_surf = np.sin(x_surf) + np.cos(y_surf)
ax3_autoencoded.plot_surface(x_surf, y_surf, z_surf,
                             cmap=cm.hot, alpha=0.2)
ax3_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        dados['target'][:, 2], c='b')
ax3_autoencoded.scatter(dados['input'][:, 0], dados['input'][:, 1],
                        inputOutputRede_autoencoded[:, 2], c='r')
plt.show()

Este código gera os gráficos dos erros e das componentes 1, 2 e 3, respectivamente. Valem os mesmos comentários feitos em http://prorum.com/index.php/2616/implementar-perceptron-multicamadas-aprender-funcao-python:

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

Note que a melhoria foi desprezível em comparação ao procedimento comum. Os erros calculados mostram isso:

print "Erro modelo convencional: " + str(np.round(erro, 4))
print "Erro modelo autoencoded: " + str(np.round(erro_autoencoded, 4))

Erro modelo convencional: 0.1898
Erro modelo autoencoded: 0.1697

Isto se deve ao seguinte: foram necessários muitos neurônios na camada intermediária para aproximar a função. Com isso, o primeiro autoencoder sofre um grande risco de overfitting, ou de apenas poucos neurônios efetivamente aprenderem. Tentei contornar isso mudando parâmetros de aprendizagem, mas o desempenho não ficou muito melhor que sem a técnica. No caso em tela, o adequado seria usar sparse autoencoders, que visam exatamente evitar este problema. Há também o autoencoder de saída que, como só tem uma camada e neurônios lineares, não contribui muito para a rede final.

...