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

Como usar a técnica de "dropout" em uma Rede Neural?

+1 voto
608 visitas
perguntada Jul 8, 2016 em Ciência da Computação por PRosa (61 pontos)  
editado Jul 8, 2016 por PRosa

De acordo com Srivastava et al. (2014) e Baldi e Sadowski (2014), dropout é um algoritmo relativamente novo para treinamento de redes neurais, que se fundamenta na eliminação aleatória de neurônios durante o processo de aprendizagem, para evitar a sobreadaptação aos dados (overfitting).

A principal motivação do algoritmo é dar maior robustez à rede para previsões fora da amostra, buscando capturar informações populacionais ao invés de características amostrais. Baldi e Sadowski (2014) reportam que a técnica de dropout tem alcançado o estado da arte de desempenho em muitas bases de dados de referência.

Goodfellow, Bengio e Courville (2016) definem regularização como qualquer modificação feita em um algoritmo de aprendizagem com o objetivo de reduzir o erro de generalização, mas não necessariamente o erro de treinamento (p.228), e acrescentam que o regularizador eficaz é aquele que faz um bom trade-off entre redução de variância e aumento de viés dos estimadores (p. 229). Conforme os autores, o desenvolvimento de estratégias de regularização tem sido um dos principais esforços de pesquisa na área de redes neurais (p.228), sendo o dropout uma das técnicas.

Na forma mais simples do algoritmo dropout, durante o treinamento, a cada inserção de um novo vetor de dados na rede, ocorre a eliminação temporária de neurônios e respectivas ligações, com a probabilidade p=0,50. Os neurônios que restarem após a eliminação são treinados por propagação reversa (backpropagation). Para a predição de Y, os pesos finais (hidden->output) são divididos por dois.

Referências

BALDI, P.; SADOWSKI, P. Understanding Dropout. Working Paper, 2014.

GOODFELLOW, I.; BENGIO, Y.; COURVILLE, A. Deep Learning. Acesso em http://www.deeplearningbook.org/ , 2016.

SRIVASTAVA, N.; HINTON, G.; KRIZHEVSKY, A.; SUTSKEVER, I.; SALAKHUTDINOV, R. Dropout: A Simple Way to Prevent Neural Networks from Overfitting. Journal of Machine Learning Research 15, 2014.

Compartilhe

2 Respostas

0 votos
respondida Jul 8, 2016 por PRosa (61 pontos)  
editado Jul 10, 2016 por PRosa

Considerando uma rede neural 2 x n x 3, em que f(x,y) = y.exp[sin(x)+u,sin(x)*cos(y)+v,sin(x)+cos(y)+ w], com ruídos u,v,w~N(0,0.09), busca-se nos testes realizados abaixo:

1) Treinar a rede, com função de ativação y=tanh(x) e arquitetura fully-connected entre os neurônios das três camadas, sem dropout, variando os hiperparâmetros.

Conforme Goodfellow, Bengio e Courville (2016), o projeto de camadas intermediárias (hidden units) é uma área extremamente ativa de pesquisa e não possui ainda princípios teóricos bem estabelecidos (p.190). O processo de escolha arquitetural consiste em tentativa e erro (p.191).

Quando se usa uma rede neural feedforward, toma-se como entrada x e produz-se uma saída estimada y, com o fluxo de informação passando através da camada intermediária (forward propagation). Calcula-se o custo a cada iteração (epoch) do treinamento, que é então utilizado pelo algoritmo de propagação reversa (backpropagation) para cálculo do gradiente descendente.

2) Utilizar a técnica de regularização por dropout para comparar os resultados.

Goodfellow, Bengio e Courville (2016) ponderam que embora o custo por iteração do dropout para um modelo específico seja insignificante, o custo para um sistema completo pode ser significante. Já que o dropout é uma técnica de regularização, o seu uso reduz a capacidade efetiva do modelo. Para lidar com isso, deve-se aumentar o tamanho do modelo.

Tipicamente, o erro na base de dados de validação é muito menor quando se usa dropout, mas tendo como contrapartida o custo de modelos maiores e muito mais iterações do algoritmo de treinamento. Quando a amostra de treinamento é muito pequena, o uso do dropout é menos efetivo. Redes bayesianas superam o dropout para n=5.000 (p. 265).

Testes:
1) Treinamento sem dropout
Foram treinadas diversas redes, variando-se a configuração da camada intermediária, conforme o gráfico abaixo. Para cada configuração, foram utilizados os hiperparâmetros learn-rate=0,005 ,0,0005 e 0,00005 para as épocas 1-3.000, 3.001-10.000 e 10.001-20.000, respectivamente. As amostras de treinamento e de validação foram de tamanhos 80 e 20 observações do processo gerados de dados especificado por f(x,y).

A imagem será apresentada aqui.

Como resultado, as três redes que apresentaram o menor custo (erro fora da amostra) foram as redes com 35, 36 e 33 neurônios.

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

Em seguida, a melhor rede foi treinada por mais 20.000 iterações, mantendo-se a última taxa de aprendizagem. Como resultado, o erro cai levemente, de 9,82 para 9,46.

A imagem será apresentada aqui.

Os gráficos abaixo mostram os sets de treinamento e de validação para as saídas da rede. Dada a relativa complexidade das saídas a serem estimadas, observa-se um bom grau de ajustamento do modelo, inclusive para os dados de validação.

A imagem será apresentada aqui.
A imagem será apresentada aqui.
A imagem será apresentada aqui.

2) Treinamento com dropout
Inicialmente, a técnica de dropout foi aplicada para a rede de melhor ajuste em 1), mantendo-se as amostras, com lr=0,0001 e p(dropout)=0,50. Observa-se um decaimento forte do erro nas primeiras épocas, mas com estabilização no patamar próximo a 500, até a última iteração.

A imagem será apresentada aqui.

O gráfico abaixo mostra o resultado do treinamento com 40.000 iterações adicionais.

A imagem será apresentada aqui.

Em seguida, foi utilizado p(dropout)=0,20 e lr=0,00005 , sem sucesso na tentativa de minimização do custo.

A imagem será apresentada aqui.

Finalmente, o modelo foi aumentado para 70 neurônios na camada intermediária, com p=0,50, 3 períodos de treinamento, amostra com n=5.000 e diminuição progressiva da taxa de aprendizagem para tentar o ajuste fino do modelo.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

A imagem será apresentada aqui.

Conclusão

Os testes realizados com a rede neural f(x,y) = y.exp[sin(x)+u,sin(x)cos(y)+v,sin(x)+cos(y)+ w] não evidenciaram a superioridade do treinamento com a regularização via *dropout.

O teste da rede integral demostrou uma boa adequação do modelo tanto para a base de treinamento quanto para a de validação, não implicando no sobreajuste dos parâmetros à amostra de treinamento.

Contudo, obteve-se convergência no modelo com dropout em apenas 300 iterações, com um nível de erro próximo do teste sem dropout. Mas, para essa convergência, foi necessário dobar a camada hidden e aumentar significativamente o tamanho da amostra.

Em situações muito mais complexas, como em Krizhevsky et al. (2012) com uma rede de 60 milhões de parâmetros e 650 mil neurônios para tratamento de imagens, a aplicação do dropout foi fundamental para a redução do overfitting.

Referência
KRIZHEVSKY, A.; SUTSKEVER, I.; HINTON, G. ImageNet Classification with Deep Convolutional Neural Networks. Working Paper, 2012.

(Código-fonte abaixo, por limitação de espaço)

0 votos
respondida Jul 8, 2016 por PRosa (61 pontos)  

(continuação da Resposta 1)

Código-fonte em Python, adaptado de Neural Network Dropout Training - James McCaffrey

import numpy as np
import matplotlib.pyplot as plt
import time

np.random.seed(10)

class NeuralNetwork():
    def __init__(self, numInput, numHidden, numOutput):

      self.numInput = numInput
      self.numHidden = numHidden
      self.numOutput = numOutput

      self.numWeights = (self.numInput * self.numHidden) + (self.numHidden * self.numOutput) 
      self.numWeights += (self.numHidden + self.numOutput)

      self.inputs = [0.0 for i in range(numInput)]
      self.ihWeights = [ [0.0 for i in range(numHidden)] for j in range(numInput)] 
      self.hBiases = [0.0 for i in range(numHidden)] 
      self.hOutputs =[0.0 for i in range(numHidden)]

      self.hoWeights = [ [0.0 for i in range(numOutput)] for j in range(numHidden)] 
      self.oBiases = [0.0 for i in range(numOutput)]
      self.outputs = [0.0 for i in range(numOutput)]

      self.InitializeWeights() 

    def InitializeWeights(self):

      initialWeights = []
      lo = -0.01
      hi = 0.01
      for i in range(self.numWeights): 
        initialWeights.append((hi - lo) * self.NextDouble() + lo)

      self.SetWeights(initialWeights)

    def SetWeights(self,weights):

      k = 0 
      for i in range(self.numInput):
        for j in range(self.numHidden):
          self.ihWeights[i][j] = weights[k]
          k+=1
      for i in range(self.numHidden):
        self.hBiases[i] = weights[k]
        k+=1
      for i in range(self.numHidden):
        for j in range(self.numOutput):
          self.hoWeights[i][j] = weights[k]
          k+=1
      for i in range(self.numOutput):
        self.oBiases[i] = weights[k]
        k+=1

    def UpdateWeights(self,tValues,learnRate,dropNodes):
      # update the weights and biases using back-propagation

      # back-prop related arrays. 
      hGrads = [0.0 for i in range(self.numHidden)] 
      oGrads = [0.0 for i in range(self.numOutput)]

      # 1. compute output gradients
      for k in range(self.numOutput):
        derivative = 1 
        oGrads[k] = derivative * (tValues[k] - self.outputs[k]); 

      # 2. compute hidden gradients
      for j in range(self.numHidden):
        if self.IsDropNode(j, dropNodes):
           continue
        derivative = (1 - self.hOutputs[j]) * (1 + self.hOutputs[j]); # derivative of tanh = (1 - y) * (1 + y)
        summ = 0.0
        for k in range(self.numOutput): # each hidden delta is the sum of numOutput terms
          x = oGrads[k] * self.hoWeights[j][k]
          summ += x
        hGrads[j] = derivative * summ

      # 3. update input-hidden weights and hidden biases
      for j in range(self.numHidden):
        if self.IsDropNode(j, dropNodes):
            continue
        for i in range(self.numInput):
          delta = learnRate * hGrads[j] * self.inputs[i] 
          self.ihWeights[i][j] += delta 
        biasDelta = learnRate * hGrads[j] 
        self.hBiases[j] += biasDelta

      # 4. update hidden-output weights and output biases
      for k in range(self.numOutput):
        for j in range(self.numHidden):
          if self.IsDropNode(j, dropNodes):
             continue;
          delta = learnRate * oGrads[k] * self.hOutputs[j]
          self.hoWeights[j][k] += delta
        biasDelta = learnRate * oGrads[k] * 1.0
        self.oBiases[k] += biasDelta

    def NextDouble(self):
        return np.random.uniform(low=-0.1, high=0.1)

    def NextInt(self,lower=0,higher=100):
        return np.random.randint(low=lower,high=higher)

    def NextUniform(self):
        return np.random.uniform()

    def Train(self, trainData, testData, maxEpochs, learnRate, withDropout):

      if withDropout:
          print "Dropout"

      epoch = 0
      totEpoch=0
      xValues = [0.0 for i in range(self.numInput)] # inputs
      tValues = [0.0 for i in range(self.numOutput)] # target values

      sequence = [i for i in range(len(trainData))]

      for indEpoch in range(len(maxEpochs)):
          lastCost = np.Inf

          if indEpoch==0:          
              theCost = np.empty([maxEpochs[indEpoch], 1])
              theEpoch = np.empty([maxEpochs[indEpoch]])
          else:
              theCost = np.empty([maxEpochs[indEpoch]-maxEpochs[indEpoch-1], 1])
              theEpoch = np.empty([maxEpochs[indEpoch]-maxEpochs[indEpoch-1]])

          idxTimes=0
          while (epoch < maxEpochs[indEpoch]):
              self.Shuffle(sequence) # visit each training data in random order

              if withDropout:          
                dropNodes = self.MakeDropNodes()
              else:
                dropNodes = []

              for i in range(len(trainData)):
                 idx = sequence[i]
                 xValues=trainData[idx][0:self.numInput]
                 tValues=trainData[idx][self.numInput:self.numInput+self.numOutput]

                 self.ComputeOutputs(xValues, dropNodes) 
                 self.UpdateWeights(tValues, learnRate[indEpoch], dropNodes)

              # Accuracy test
              if withDropout:
                  hoWeightsCopy = [row[:] for row in self.hoWeights] 
                  for j in range(self.numHidden):
                    for k in range(self.numOutput):
                      self.hoWeights[j][k] *= 0.5   # /= 2.0  

                  y_hat, _ = self.Accuracy(testData)
                  cost = self.calcCost(testData, y_hat) 
                  self.hoWeights=hoWeightsCopy[:]   # restore the training weights
              else:
                  y_hat, _ = self.Accuracy(trainData)
                  cost = self.calcCost(trainData, y_hat) 

              if (epoch+1)%50==0 or epoch+1==maxEpochs[indEpoch]:
                print "Epoch:", epoch+1, " Cost:", cost

              if cost > lastCost:
                 # break
                  pass
              else:
                  lastCost = cost

              theEpoch[idxTimes]=totEpoch
              theCost[idxTimes,0]=cost              
              idxTimes+=1
              epoch+=1
              totEpoch+=1

          # while

          fig=plt.figure(num=None, figsize=(6, 5), dpi=80, facecolor='w', edgecolor='k')

          ax = plt.subplot(111)
          ax.plot(theEpoch, theCost[:, 0]*1, linewidth=2)

          ax.set_ylabel('Erro')
          ax.set_xlabel('Epoca (lr=' + str(learnRate[indEpoch]) +')')
          ax.legend(loc='upper center', fancybox=True, shadow=True)
          strTitle = 'Neural Network ' + str(self.numInput) + 'x' + str(self.numHidden)+ "x" + str(self.numOutput)
          if withDropout:
              strTitle += ' (With Dropout)'
          ax.set_title(strTitle)
          plt.show()

      # for 

      # divide hidden-output weights by 2.0 to account for dropout
      if withDropout:          
          for j in range(self.numHidden):
            for k in range(self.numOutput):
              self.hoWeights[j][k] *= 0.5   # /= 2.0   

    def calcCost(self,trainData, y_hat):
        cost=0
        for i in range(len(trainData)):        
            for j in range(self.numOutput):
               cost += 0.5 * (trainData[i][self.numInput+j] - y_hat[i][j])**2 # 0.5 makes derivative nicer

        return cost

    def Shuffle(self,sequence):

      for i in range(len(sequence)):
        r = self.NextInt(i, len(sequence))
        tmp = sequence[r];
        sequence[r] = sequence[i]
        sequence[i] = tmp

    def MakeDropNodes(self):
      resultList = []
      for i in range(self.numHidden):
        p=self.NextUniform()
        if (p > 0.50): 
          resultList.append(i)

      if len(resultList) == 0:
        resultList.append(self.NextInt(0,self.numHidden))
      elif len(resultList) == self.numHidden:
        del resultList[self.NextInt(0,self.numHidden)]

      return resultList

    def IsDropNode(self,node,dropNodes):
      if len(dropNodes) == 0:
        return False
      else:
        for i in dropNodes:
            if i==node:
               return True
        return False

    def HyperTanFunction(self,x):
      if x < -20.0:
        return -1.0 # approximation is correct to 30 decimals
      elif x > 20.0:
          return 1.0
      else:
          return np.tanh(x)


    def ComputeOutputs(self,xValues,dropNodes=[]):

      hSums = [0.0 for i in range(self.numHidden)] 
      oSums = [0.0 for i in range(self.numOutput)] 

      self.inputs = xValues[:]

      for j in range(self.numHidden):  # each hidden node
        if self.IsDropNode(j, dropNodes):
            continue 
        for i in range(self.numInput):
          hSums[j] += self.inputs[i] * self.ihWeights[i][j]

        hSums[j] += self.hBiases[j] # add bias

        self.hOutputs[j] = self.HyperTanFunction(hSums[j]) # apply activation

      for k in range(self.numOutput):   # each output node
        for j in range(self.numHidden):
          if self.IsDropNode(j, dropNodes):
              continue # skip
          oSums[k] += self.hOutputs[j] * self.hoWeights[j][k]

        oSums[k] += self.oBiases[k] # add bias

      self.outputs=oSums[:]

      retResult = []
      retResult = self.outputs[:]

      return retResult

    def Accuracy(self,testData):
      numCorrect = 0
      numWrong = 0
      xValues = [] 
      tValues = [] 
      yValues = [] 
      y_hat = []

      for i in range(len(testData)):
        xValues=testData[i][0:self.numInput]
        tValues=testData[i][self.numInput:self.numInput+self.numOutput]
        yValues = self.ComputeOutputs(xValues) 
        y_hat.append(yValues)

        if yValues==tValues:
          numCorrect += 1
        else:
          numWrong += 1

      return (y_hat, (numCorrect * 1.0) / (numCorrect + numWrong) ) 

def getDataExercAula24(n,comRuido):
      # f(x,y) = y.exp[   sin(x)+u, sin(x) * cos(y) + v, sin(x)+cos(y) + w ]
      # u,v,w ~ N(0,0.09)
      data = [ [] for i in range(n)]

      for i in range(n):
         x=np.random.uniform(0,2*np.pi) 
         y=np.random.uniform(0,2*np.pi) 
         if comRuido:         
            u=np.random.normal(0, 0.09)
            v=np.random.normal(0, 0.09)
            w=np.random.normal(0, 0.09)            
         else:
            u,v,w=0,0,0

         data[i]=[x, y, y*np.exp(np.sin(x)+u), y*np.exp(np.sin(x)*np.cos(y)+v), y*np.exp(np.sin(x)+np.cos(y)+w)]
      return data

def graficoSaida(numSaida,rn,trainData,testData):

     fig1=plt.figure(num=None, figsize=(12, 4), dpi=80, facecolor='w', edgecolor='k')

     # TrainData   
     y_hat, trainAcc = rn.Accuracy(trainData)

     ax1=plt.subplot(121)
     ax1.axis([-0.5, 7, -0.5, 16])
     fig1.hold()
     for i in range(len(trainData)):
        ax1.plot(trainData[i][numSaida],trainData[i][numSaida+2],'bo',markersize=5,markeredgewidth=0)
        ax1.plot(trainData[i][numSaida],y_hat[i][numSaida],"ro",markersize=4, markeredgewidth=0)
     ax1.set_xlabel('x')
     if numSaida==0:
        ax1.set_ylabel('y * exp[sin(x)]')
     elif numSaida==1:
        ax1.set_ylabel('y * exp[sin(x)*cos(y)]')
     elif numSaida==2:
        ax1.set_ylabel('y * exp[sin(x)+cos(y)]')
        ax1.axis([-0.5, 10, -0.5, 22])

     ax1.set_title('Treinamento - Saida ' + str(numSaida+1))    

     # Validação
     y_hat, testAcc = rn.Accuracy(testData)

     ax2=plt.subplot(122)
     ax2.axis([-0.5, 7, -0.5, 16])
     fig1.hold()
     for i in range(len(testData)):
        ax2.plot(testData[i][numSaida],testData[i][numSaida+2],'bo',markersize=5,markeredgewidth=0)
        ax2.plot(testData[i][numSaida],y_hat[i][numSaida],"ro",markersize=4, markeredgewidth=0)
     ax2.set_title('Validacao')    
     if numSaida==2:
        ax2.axis([-0.5, 10, -0.5, 22])

def redeNeuralExercAula24(trainData, testData):

     numInput,numHidden,numOutput = 2,35,3
     nn = NeuralNetwork(numInput, numHidden, numOutput)

     maxEpochs = [3000 ,  10000 ,   20000]
     learnRate = [0.005, 0.0005 , 0.00005]

     nn.Train(trainData, testData, maxEpochs, learnRate, withDropout=True)

     print "Neural Network ", numInput, "x", numHidden, "x", numOutput
     print "Setting maxEpochs = ", maxEpochs, " learnRate = ", str (learnRate)

     return nn

if __name__ == "__main__":

    trainData = getDataExercAula24(80,comRuido=True)
    testData  = getDataExercAula24(20,comRuido=False)     

    rn=redeNeuralExercAula24(trainData, testData)

    graficoSaida(0,rn,trainData,testData)
    graficoSaida(1,rn,trainData,testData)
    graficoSaida(2,rn,trainData,testData)
...