Junho de 2018

Volume 33 Número 6

Execução de teste — Regressão Neural Usando CNTK

Por James McCaffrey | De 2018 junho

Código disponível para download em msdn.com/magazine/0518magcode.

O objetivo de um problema de regressão é fazer uma previsão em que o valor para prever é um único valor numérico. Por exemplo, você pode

você deseja prever a altura de uma pessoa com base em seu peso, idade e sexo. Existem muitas técnicas que podem ser usadas para resolver um problema de regressão. Neste artigo, explicarei como usar a biblioteca CNTK para criar um modelo de regressão de rede neural.

A melhor maneira de saber o rumo que este artigo tomará é examinar o programa de demonstração na Figura 1. O programa de demonstração cria um modelo de regressão para o parâmetro de comparação de conjunto de dados Hydrodynamics Yacht bem conhecidos. O objetivo é prever uma medida de resistência um envoltório yacht, com base em variáveis de indicador seis: centro do buoyancy de convexa, coeficiente prismático, taxa de deslocamento de comprimento, taxa de transmissão draught, taxa de transmissão de tamanho e número de Froude.

Figura 1 regressão usando uma rede Neural CNTK

O programa de demonstração cria uma rede neural com duas camadas ocultas, cada um deles tem cinco nós de processamento. Depois de treinamento, o modelo é usado para fazer previsões para os dois itens de dados. O primeiro item tem valores de previsão (0,52, 0.79, 0,55, 0.41, 0.65, 0,00). A resistência convexa previsto é 0.0078 e a resistência real é 0,0030. O segundo item tem valores de indicador (1,00 1,00, 0,55, 0.56, 0.46, 1,00). A resistência convexa previsto é 0.8125 e a resistência real é 0.8250. O modelo parece ser bastante preciso.

Este artigo pressupõe tiver intermediário ou melhor habilidades de programação, mas não suponha que você sabe muito sobre CNTK ou redes neurais. A demonstração é codificada com Python, o idioma padrão para o aprendizado de máquina, mas mesmo se você não souber o Python deve ser capaz de acompanhar com facilidade. O código-fonte do programa de demonstração é apresentado por completo neste artigo. O arquivo de dados de envoltória yacht usado pelo programa de demonstração pode ser encontrado em bit.ly/2Ibsm5De também está disponível no download que acompanha este artigo.

Compreendendo os dados

Ao criar um modelo de aprendizado de máquina, a preparação de dados é quase sempre a parte mais demorada do projeto. O conjunto de dados bruto tem 308 itens e se parece com:

-2.3 0.568 4.78 3.99 3.17 0.125 0.11
-2.3 0.568 4.78 3.99 3.17 0.150 0.27
...
-5.0 0.530 4.78 3.75 3.15 0.125 0.09
...
-2.3 0.600 4.34 4.23 2.73 0.450 46.66

O arquivo é delimitada por espaço. Os primeiro de seis valores são os valores de previsão (geralmente chamados de recursos na terminologia de aprendizado de máquina). O último valor em cada linha é a "resistência residuary por peso da unidade de deslocamento".

Como há mais de uma variável de indicador, não é possível mostrar o conjunto completo de dados em um gráfico. Mas você pode obter uma ideia aproximada da estrutura dos dados examinando o gráfico de Figura 2. O gráfico plota apenas os valores de indicador prismático coeficiente e a resistência convexa. Você pode ver que os valores de coeficiente prismático, por si só, não fornecem informações suficientes para fazer uma previsão precisa de resistência convexa.

Figura 2 Yacht parcial convexa dados

Ao trabalhar com redes neurais, é geralmente necessário normalizar os dados para criar um modelo de previsão BOM. Usei a normalização Mín-Máx os seis valores de previsão e os valores de resistência convexa. Eu desconectada os dados brutos em uma planilha do Excel e, para cada coluna, eu computadas os valores máximo e mínimo. Em seguida, para cada coluna, eu substituído cada valor v com (v - min) / (max - min). Por exemplo, o valor mínimo coeficiente prismático é 0.53 e o valor máximo é 0,60. O primeiro valor na coluna é 0.568 e ele é normalizado para (0.568 - 0.53) / (0,60 - 0.53) = 0.038 / 0.07 = 0.5429.

Após a normalização, inseri marcas | previsões e | resistência na planilha do Excel para os dados facilmente podem ser lido por um objeto do leitor de dados CNTK. Em seguida, eu exportou os dados como um arquivo delimitado por tabulação. Os dados resultantes é semelhante a:

|predictors  0.540000  0.542857 . . |resistance  0.001602
|predictors  0.540000  0.542857 . . |resistance  0.004166
...

Alternativas para normalização Mín-Máx incluem a normalização da pontuação z e normalização de ordem de magnitude.

O programa de demonstração

O programa de demonstração completo, com algumas pequenas edições para economizar espaço, é apresentado na Figura 3. Todas as verificações de erros normais foram removidas. Eu recuo dois caracteres de espaço em vez dos quatro usuais para economizar espaço e por preferência pessoal. Observe que o ' \' caractere é usado pelo Python de continuação de linha.

Figura 3 programa de demonstração de regressão

# hydro_reg.py
# CNTK 2.4 with Anaconda 4.1.1 (Python 3.5, NumPy 1.11.1)
# Predict yacht hull resistance based on six predictors

import numpy as np
import cntk as C

def create_reader(path, input_dim, output_dim, rnd_order,
  sweeps):
  x_strm = C.io.StreamDef(field='predictors',
    shape=input_dim, is_sparse=False)
  y_strm = C.io.StreamDef(field='resistance',
    shape=output_dim, is_sparse=False)
  streams = C.io.StreamDefs(x_src=x_strm, y_src=y_strm)
  deserial = C.io.CTFDeserializer(path, streams)
  mb_src = C.io.MinibatchSource(deserial,
    randomize=rnd_order, max_sweeps=sweeps)
  return mb_src

# ========================================================

def main():
  print("\nBegin yacht hull regression \n")
  print("Using CNTK version = " + \
    str(C.__version__) + "\n")
  input_dim = 6  # center of buoyancy, etc.
  hidden_dim = 5
  output_dim = 1  # residuary resistance
  train_file = ".\\Data\\hydro_data_cntk.txt"
  # data resembles:
  # |predictors 0.540  0.542 . . |resistance  0.001
  # |predictors 0.540  0.542 . . |resistance  0.004
  # 1. create neural network model
  X = C.ops.input_variable(input_dim, np.float32)
  Y = C.ops.input_variable(output_dim)
  print("Creating a 6-(5-5)-1 tanh regression NN for \
yacht hull dataset ")
  with C.layers.default_options():
    hLayer1 = C.layers.Dense(hidden_dim,
      activation=C.ops.tanh, name='hidLayer1')(X)
    hLayer2 = C.layers.Dense(hidden_dim,
      activation=C.ops.tanh, name='hidLayer2')(hLayer1)  
    oLayer = C.layers.Dense(output_dim,
      activation=None, name='outLayer')(hLayer2)
  model = C.ops.alias(oLayer)  # alias
  # 2. create learner and trainer
  print("Creating a squared error batch=11 Adam \
fixed LR=0.005 Trainer \n")
  tr_loss = C.squared_error(model, Y)
  max_iter = 50000
  batch_size = 11
  learn_rate = 0.005
  learner = C.adam(model.parameters, learn_rate, 0.99)
  trainer = C.Trainer(model, (tr_loss), [learner])
  # 3. create reader for train data
  rdr = create_reader(train_file, input_dim, output_dim,
    rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
  hydro_input_map = {
    X : rdr.streams.x_src,
    Y : rdr.streams.y_src
  }
  # 4. train
  print("Starting training \n")
  for i in range(0, max_iter):
    curr_batch = rdr.next_minibatch(batch_size,
      input_map=hydro_input_map)
    trainer.train_minibatch(curr_batch)
    if i % int(max_iter/10) == 0:
      mcee = trainer.previous_minibatch_loss_average
      print("batch %6d: mean squared error = %8.4f" % \
        (i, mcee))
  print("\nTraining complete")
  # (could save model to disk here)
  # 5. use trained model to make some predictions
  np.set_printoptions(precision=2, suppress=True)
  inpts = np.array(
    [[0.520000, 0.785714, 0.550000, 0.405512, \
      0.648352, 0.000000],
     [1.000000, 1.000000, 0.550000, 0.562992, \
      0.461538, 1.000000]],
    dtype=np.float32)
  actuals = np.array([0.003044, 0.825028],
    dtype=np.float32)
  for i in range(len(inpts)):
    print("\nInput: ", inpts[i])
    pred = model.eval(inpts[i])
    print("predicted resistance: %0.4f" % pred[0][0])
    print("actual resistance:    %0.4f" % actuals[i])
  print("\nEnd yacht hull regression ")

# ========================================================

if __name__ == "__main__":
  main()

A instalação do CNTK pode ser um pouco complicada. Primeiro, instale a distribuição Anaconda de Python, que contém o interpretador do Python necessário, pacotes necessários, como NumPy e SciPy, além de utilitários úteis como pip. Usei Anaconda3 4.1.1 64 bits, que tem o Python 3.5. Depois de instalar Anaconda, você instalar CNTK como um pacote do Python, não é um sistema autônomo, usando o utilitário de pip. De um shell comum, o comando que usei foi:

>pip install https://cntk.ai/PythonWheel/CPU-Only/cntk-2.4-cp35-cp35m-win_amd64.whl

A demonstração hydro_reg.py tem uma função auxiliar, create_reader. Você pode considerar create_reader como padrão para um problema de regressão CNTK. A única coisa que você precisará alterar na maioria dos cenários é os nomes de marca no arquivo de dados.

Toda a lógica de controle está em uma única função main. O código começa:

def main():
  print("Begin yacht hull regression \n")
  print("Using CNTK version = " + \
    str(C.__version__) + "\n")
  input_dim = 6  # center of buoyancy, etc.
  hidden_dim = 5
  output_dim = 1  # residuary resistance
  train_file = ".\\Data\\hydro_data_cntk.txt"
...

Como CNTK é pequena e em desenvolvimento contínuo, é uma boa ideia para exibir a versão que está sendo usado (2.4 neste caso). O número de nós de entrada é determinado pela estrutura do conjunto de dados. Para um problema de regressão, o número de nós de saída é sempre definido como 1. O número de camadas ocultas e o número de nós de processamento em cada camada oculta são parâmetros livres, eles devem ser determinados por tentativa e erro.

O programa de demonstração usa todos os 308 itens para treinamento. Uma abordagem alternativa é dividir o conjunto de dados em um conjunto de treinamento (normalmente 80 por cento dos dados) e um conjunto de teste (os 20 por cento restantes). Depois de treinamento, você pode calcular métricas de perda e a precisão do modelo no conjunto de dados de teste para verificar se as métricas são semelhantes aos dados de treinamento.

Criar o modelo de rede Neural

A demonstração define objetos CNTK para manter o indicador e valores de resistência convexa true:

X = C.ops.input_variable(input_dim, np.float32)
Y = C.ops.input_variable(output_dim)

CNTK usa valores de 32 bits por padrão porque a precisão de 64 bits raramente é necessário. O nome da função input_variable pode ser um pouco confuso se você estiver familiarizado com CNTK. Aqui, "input_" se refere ao fato de que os objetos de retorno armazenam valores que vêm dos dados de entrada (que correspondem à entrada e saída da rede neural).

A rede neural é criada com estas instruções:

print("Creating a 6-(5-5)-1 NN")
with C.layers.default_options():
  hLayer1 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer1')(X)
  hLayer2 = C.layers.Dense(hidden_dim,
    activation=C.ops.tanh, name='hidLayer2')(hLayer1)  
  oLayer = C.layers.Dense(output_dim,
    activation=None, name='outLayer')(hLayer2)
model = C.ops.alias(oLayer)  # alias

Há um pouco aqui. O Python "com" instrução pode ser usado para passar um conjunto de valores de parâmetros comuns para várias funções. Nesse caso, os valores de pesos e tendências de rede neural são inicializados usando valores padrão CNTK. Redes neurais são altamente confidenciais para valores iniciais de pesos e tendências, para fornecer valores padrão não é uma das primeiras etapas que tentar quando houver falha na rede neural saber mais, uma situação lamentavelmente comum.

A rede neural tem duas camadas ocultas. O objeto X como funciona a entrada para a primeira camada oculta; primeira camada oculta atuar como entrada para a segunda camada oculta; e o segundo oculto camada atua como entrada para a camada de saída.

As duas camadas ocultas usam a ativação do tanh (tangente hiperbólica). As duas alternativas principais são ativação logística sigmoide retificada linear unidades e (ReLU). A camada de saída usa "Nenhum" ativação, o que significa que os valores de nós de saída não são modificados. Este é o padrão de design a ser usado para um problema de regressão. Não usar nenhuma ativação às vezes é chamado usando a função de ativação de identidade porque a função de matemática de identidade é f = x, que não tem nenhum efeito.

O programa de demonstração cria um alias chamado "modelo" para a camada de saída. Essa técnica é opcional e é um pouco sutil. A ideia aqui é que uma rede neural é essencialmente uma função matemática complexa. Os nós de saída representam conceitualmente ambos uma camada da rede e o modelo de rede/como um todo.

Treinamento do modelo

A essência da funcionalidade CNTK é a capacidade para treinar um modelo de rede neural. Treinamento é preparado com estas instruções:

tr_loss = C.squared_error(model, Y)
max_iter = 50000
batch_size = 11
learn_rate = 0.005
learner = C.adam(model.parameters, learn_rate, 0.99)
trainer = C.Trainer(model, (tr_loss), [learner])

Uma função de perda (erro) é necessária para que o objeto de treinamento sabe como ajustar pesos e tendências para reduzir o erro. 2.4 CNTK tem nove funções de perda, mas o squared_error simples quase sempre é adequado para um problema de regressão. O número de iterações corresponde ao número de operações de atualização e deve ser determinado por tentativa e erro.

O objeto de aprendizagem exige um objeto de aprendiz. Você pode considerar um aprendiz como um algoritmo. CNTK oferece suporte a algoritmos de aprendizado de oito. Para problemas de regressão, recebo normalmente bons resultados usando descendente do gradiente estocástico básico ou mais sofisticado Adam ("estimativa impulso adaptável").

O tamanho do lote é usado por CNTK para determinar a frequência de executar atualizações de ajuste e o peso. A demonstração define o tamanho do lote para 11. Portanto, os 308 itens serão agrupados em 308 / 11 = 28 selecionadas aleatoriamente lotes. Cada lote é analisado e, em seguida, as atualizações são executadas. A taxa de aprendizagem controla a magnitude dos ajustes de peso e tendência. Determinar os valores adequados para o tamanho do lote, o número máximo de iterações e a taxa de aprendizagem são geralmente maiores desafios ao criar um modelo de previsão de rede neural.

A função de create_reader de chamadas a definido por programa de demonstração, também, criar um objeto do leitor. E um input_map é criado que diz ao leitor onde estão os valores de recurso e onde o valor-para-prever é:

rdr = create_reader(train_file, input_dim, output_dim,
  rnd_order=True, sweeps=C.io.INFINITELY_REPEAT)
hydro_input_map = {
  X : rdr.streams.x_src,
  Y : rdr.streams.y_src
}

O parâmetro rnd_order garante que os dados de itens serão processados de forma diferente em cada passagem, o que é importante para evitar treinamento de atrasando-out. O argumento INFINITELY_REPEAT permite treinamento em várias passagens por meio do conjunto de dados do item 308.

Após a preparação, o modelo é treinado da seguinte forma:

for i in range(0, max_iter):
  curr_batch = rdr.next_minibatch(batch_size,
    input_map=hydro_input_map)
  trainer.train_minibatch(curr_batch)
  if i % int(max_iter/10) == 0:
    mcee = trainer.previous_minibatch_loss_average
    print("batch %6d: mean squared error = %8.4f" % \
      (i, mcee))

A função next_minibatch recebe 11 itens dos dados. A função de treinamento usa o algoritmo de Adam para atualizar os pesos e tendências com base no erro ao quadrado entre valores de resistência convexa computada e resistência real. O erro ao quadrado no lote atual 11-item é exibido cada 50.000 / 10 = 5000 processa em lotes para que você pode monitorar visualmente o andamento de treinamento: Você deseja ver os valores de perda/erro geralmente diminuir.

Usando o modelo

Depois que o modelo foi treinado, o programa de demonstração faz algumas previsões. Primeiro, os valores de indicador de dois itens arbitrários do conjunto de dados normalizado selecionado (itens 99 e 238) e colocada em uma matriz de estilo da matriz de matrizes:

inpts = np.array(
  [[0.520000, 0.785714, 0.550000, 0.405512,
    0.648352, 0.000000],
   [1.000000, 1.000000, 0.550000, 0.562992,
    0.461538, 1.000000]],
  dtype=np.float32)

Em seguida, os valores de resistência normalizado convexa real correspondentes são colocados em uma matriz:

actuals = np.array([0.003044, 0.825028], dtype=np.float32)

Em seguida, os valores de previsão são usados para calcular os valores previstos usando a função model.eval e os valores previstos e reais são exibidos:

for i in range(len(inpts)):
  print("\nInput: ", inpts[i])
  pred = model.eval(inpts[i])
  print("predicted resistance: %0.4f" % pred[0][0])
  print("actual resistance:    %0.4f" % actuals[i])
print("End yacht hull regression ")

Observe que o valor de resistência convexa previsto é retornado como uma matriz de matriz de matrizes com um único valor. Portanto, o próprio valor é [0] [0] (linha 0, a coluna 0). Lidando com formas de CNTK vetores e matrizes é um desafio significativo de sintaxe. Ao trabalhar com CNTK passo muito tempo impressão objetos e exibindo sua forma, ao longo das linhas de print(something.shape).

Conclusão

Ao criar um modelo de regressão de rede neural, não há nenhuma métrica de exatidão predefinidos. Se você deseja computar a precisão de previsão, que você deve definir o que significa um valor previsto ser o mais próximo possível ao valor real correspondente para ser considerado correto. Normalmente, você deve especificar uma porcentagem/proporção, como 0,10 e avaliar um valor previsto como correto, se ele está dentro dessa porcentagem do valor real.

Como o modelo de demonstração funciona com dados normalizados, se você usar o modelo para fazer uma previsão novos, valores de indicador não vistos anteriormente, você precisa normalizá-los usando os mesmos valores mínimo e máximo que foram usados nos dados de treinamento. Da mesma forma, um valor de resistência convexa previsto, VP, é normalizado, será preciso desnormalizar computando pv * (max - min) + min.

O termo "regressão" pode ter vários significados diferentes. Este artigo o termo se refere a um cenário de problema em que o objetivo é prever um único valor numérico (resistência convexa). A técnica de regressão linear de estatísticas clássico é muito mais simples do que a regressão de rede neural, mas geralmente muito menos preciso. A regressão logística técnica de aprendizado de máquina prevê um único valor numérico entre 0,0 e 1,0, que é interpretado como uma probabilidade e, em seguida, usado para prever um valor categórico como "masculino" (p < 0,5) ou "feminino" (p > 0,5).


Dr. James McCaffreytrabalha para a Microsoft Research em Redmond, Washington. Ele trabalhou em vários produtos da Microsoft, incluindo Internet Explorer e Bing. Entre em contato com o Dr. McCaffrey pelo email jamccaff@microsoft.com.