Esta página foi traduzida pela API Cloud Translation.
Switch to English

Introdução ao TensorFlow Transform

Este guia apresenta os conceitos básicos do tf.Transform e como usá-los. Será:

  • Defina uma função de pré - processamento , uma descrição lógica do pipeline que transforma os dados brutos nos dados usados ​​para treinar um modelo de aprendizado de máquina.
  • Mostre a implementação do Apache Beam usada para transformar dados convertendo a função de pré - processamento em um pipeline do Beam .
  • Mostre exemplos adicionais de uso.

Definir uma função de pré-processamento

A função de pré-processamento é o conceito mais importante de tf.Transform . A função de pré-processamento é uma descrição lógica de uma transformação do conjunto de dados. A função de pré-processamento aceita e retorna um dicionário de tensores, onde um tensor significa Tensor ou SparseTensor . Existem dois tipos de funções usadas para definir a função de pré-processamento:

  1. Qualquer função que aceite e retorne tensores. Isso inclui operações TensorFlow no gráfico que transformam dados brutos em dados transformados.
  2. Qualquer um dos analisadores fornecidos pelo tf.Transform . Os analisadores também aceitam e retornam tensores, mas, diferentemente das funções do TensorFlow, eles não adicionam operações ao gráfico. Em vez disso, os analisadores fazem com que o tf.Transform calcule uma operação de passagem completa fora do TensorFlow. Eles usam os valores do tensor de entrada em todo o conjunto de dados para gerar um tensor constante que é retornado como saída. Por exemplo, tft.min calcula o mínimo de um tensor sobre o conjunto de dados. tf.Transform fornece um conjunto fixo de analisadores, mas isso será estendido em versões futuras.

Exemplo de função de pré-processamento

Ao combinar analisadores e funções regulares do TensorFlow, os usuários podem criar pipelines flexíveis para transformar dados. A seguinte função de pré-processamento transforma cada um dos três recursos de maneiras diferentes e combina dois dos recursos:

 import tensorflow as tf
import tensorflow_transform as tft
import tensorflow_transform.beam as tft_beam

def preprocessing_fn(inputs):
  x = inputs['x']
  y = inputs['y']
  s = inputs['s']
  x_centered = x - tft.mean(x)
  y_normalized = tft.scale_to_0_1(y)
  s_integerized = tft.compute_and_apply_vocabulary(s)
  x_centered_times_y_normalized = x_centered * y_normalized
  return {
      'x_centered': x_centered,
      'y_normalized': y_normalized,
      'x_centered_times_y_normalized': x_centered_times_y_normalized,
      's_integerized': s_integerized
  }
 

Aqui, x , y e s são Tensor s que representam recursos de entrada. O primeiro novo tensor criado, x_centered , é construído aplicando tft.mean em x e subtraindo-o de x . tft.mean(x) retorna um tensor que representa a média do tensor x . x_centered é o tensor x com a média subtraída.

O segundo novo tensor, y_normalized , é criado de maneira semelhante, mas usando o método de conveniência tft.scale_to_0_1 . Este método faz algo semelhante à computação x_centered em x_centered , ou seja, computando um máximo e um mínimo e usá-los para escalar y .

O tensor s_integerized mostra um exemplo de manipulação de string. Nesse caso, pegamos uma string e a mapeamos para um número inteiro. Isso usa a função de conveniência tft.compute_and_apply_vocabulary . Essa função usa um analisador para calcular os valores exclusivos obtidos pelas cadeias de entrada e, em seguida, usa as operações TensorFlow para converter as cadeias de entrada em índices na tabela de valores exclusivos.

A coluna final mostra que é possível usar as operações do TensorFlow para criar novos recursos combinando tensores.

A função de pré-processamento define um pipeline de operações em um conjunto de dados. Para aplicar o pipeline, contamos com uma implementação concreta da API tf.Transform . A implementação do Apache Beam fornece PTransform que aplica a função de pré-processamento do usuário aos dados. O fluxo de trabalho típico de um usuário tf.Transform uma função de pré-processamento e a incorpora em um pipeline de feixe maior, criando os dados para treinamento.

Lote

Os lotes são uma parte importante do TensorFlow. Como um dos objetivos do tf.Transform é fornecer um gráfico TensorFlow para pré-processamento que possa ser incorporado ao gráfico de veiculação (e, opcionalmente, ao gráfico de treinamento), o lote também é um conceito importante no tf.Transform .

Embora não seja óbvio no exemplo acima, a função de pré-processamento definida pelo usuário recebe tensores que representam lotes e não instâncias individuais, como acontece durante o treinamento e a veiculação no TensorFlow. Por outro lado, os analisadores executam um cálculo em todo o conjunto de dados que retorna um único valor e não um lote de valores. x é um Tensor com a forma de (batch_size,) , enquanto tft.mean(x) é um Tensor com a forma de () . A subtração x - tft.mean(x) transmite onde o valor de tft.mean(x) é subtraído de cada elemento do lote representado por x .

Implementação do Apache Beam

Enquanto a função de pré - processamento é planejada como uma descrição lógica de um pipeline de pré - processamento implementado em várias estruturas de processamento de dados, o tf.Transform fornece uma implementação canônica usada no Apache Beam. Esta implementação demonstra a funcionalidade necessária de uma implementação. Não há API formal para essa funcionalidade, portanto, cada implementação pode usar uma API idiomática para sua estrutura de processamento de dados específica.

A implementação do Apache Beam fornece dois PTransform s usados ​​para processar dados para uma função de pré-processamento. A seguir, mostra o uso do PTransform AnalyzeAndTransformDataset composto:

 raw_data = [
    {'x': 1, 'y': 1, 's': 'hello'},
    {'x': 2, 'y': 2, 's': 'world'},
    {'x': 3, 'y': 3, 's': 'hello'}
]

raw_data_metadata = ...
transformed_dataset, transform_fn = (
    (raw_data, raw_data_metadata) | tft_beam.AnalyzeAndTransformDataset(
        preprocessing_fn))
transformed_data, transformed_metadata = transformed_dataset
 

O conteúdo de transformed_data é mostrado abaixo e contém as colunas transformadas no mesmo formato que os dados brutos. Em particular, os valores de s_integerized são [0, 1, 0] - esses valores dependem de como as palavras hello e world foram mapeadas para números inteiros, o que é determinístico. Para a coluna x_centered , x_centered a média para que os valores da coluna x , que eram [1.0, 2.0, 3.0] , se tornassem [-1.0, 0.0, 1.0] . Da mesma forma, o restante das colunas corresponde aos valores esperados.

 [{u's_integerized': 0,
  u'x_centered': -1.0,
  u'x_centered_times_y_normalized': -0.0,
  u'y_normalized': 0.0},
 {u's_integerized': 1,
  u'x_centered': 0.0,
  u'x_centered_times_y_normalized': 0.0,
  u'y_normalized': 0.5},
 {u's_integerized': 0,
  u'x_centered': 1.0,
  u'x_centered_times_y_normalized': 1.0,
  u'y_normalized': 1.0}]
 

Os dados raw_data e os dados transformed_data são conjuntos de dados. As próximas duas seções mostram como a implementação do Beam representa conjuntos de dados e como ler e gravar dados no disco. O outro valor de retorno, transform_fn , representa a transformação aplicada aos dados, detalhados abaixo.

O AnalyzeAndTransformDataset é a composição das duas transformações fundamentais fornecidas pela implementação AnalyzeDataset e TransformDataset . Portanto, os dois trechos de código a seguir são equivalentes:

 transformed_data, transform_fn = (
    my_data | tft_beam.AnalyzeAndTransformDataset(preprocessing_fn))
 
 transform_fn = my_data | tft_beam.AnalyzeDataset(preprocessing_fn)
transformed_data = (my_data, transform_fn) | tft_beam.TransformDataset()
 

transform_fn é uma função pura que representa uma operação aplicada a cada linha do conjunto de dados. Em particular, os valores do analisador já são calculados e tratados como constantes. No exemplo, o transform_fn contém como constantes a média da coluna x , o mínimo e o máximo da coluna y , e o vocabulário usado para mapear as seqüências de caracteres para números inteiros.

Um recurso importante do tf.Transform é que transform_fn representa um mapa sobre linhas - é uma função pura aplicada a cada linha separadamente. Todo o cálculo para agregar linhas é feito no AnalyzeDataset . Além disso, o transform_fn é representado como um Graph TensorFlow que pode ser incorporado ao gráfico de veiculação.

AnalyzeAndTransformDataset é fornecido para otimizações neste caso especial. Esse é o mesmo padrão usado no scikit-learn , fornecendo os métodos fit , transform e fit_transform .

Formatos e esquema de dados

Nos exemplos de código anteriores, o código que define raw_data_metadata é omitido. Os metadados contêm o esquema que define o layout dos dados para que sejam lidos e gravados em vários formatos. Mesmo o formato na memória mostrado na última seção não é autoexplicativo e requer que o esquema seja interpretado como tensor.

Aqui está a definição do esquema para os dados de exemplo:

 from tensorflow_transform.tf_metadata import dataset_metadata
from tensorflow_transform.tf_metadata import schema_utils

raw_data_metadata = dataset_metadata.DatasetMetadata(
      schema_utils.schema_from_feature_spec({
        's': tf.io.FixedLenFeature([], tf.string),
        'y': tf.io.FixedLenFeature([], tf.float32),
        'x': tf.io.FixedLenFeature([], tf.float32),
    }))
 

A classe dataset_schema.Schema contém as informações necessárias para analisar os dados do formato em disco ou na memória, em tensores. Geralmente, é construído chamando os valores schema_utils.schema_from_feature_spec com um recurso de mapeamento de dict para os valores tf.io.FixedLenFeature , tf.io.VarLenFeature e tf.io.SparseFeature . Consulte a documentação de tf.parse_example para obter mais detalhes.

Acima, usamos tf.io.FixedLenFeature para indicar que cada recurso contém um número fixo de valores, nesse caso, um único valor escalar. Como tf.Transform instâncias de lotes, o Tensor real que representa o recurso terá forma (None,) onde a dimensão desconhecida é a dimensão do lote.

Entrada e saída com Apache Beam

Até agora, o formato dos dados para os exemplos usava listas de dicionários. Essa é uma simplificação que se baseia na capacidade do Apache Beam de trabalhar com listas, bem como com sua principal representação de dados, a PCollection . Um PCollection é uma representação de dados que faz parte de um pipeline de feixe. Um pipeline de feixe é formado aplicando vários PTransform s, incluindo AnalyzeDataset e TransformDataset , e executando o pipeline. Um PCollection não é criado na memória do binário principal, mas é distribuído entre os trabalhadores (embora esta seção use o modo de execução na memória).

O exemplo a seguir requer a leitura e gravação de dados no disco e a representação de dados como um PCollection (não uma lista), consulte: census_example.py . Abaixo, mostramos como baixar os dados e executar este exemplo. O conjunto de dados "Census Income" é fornecido pelo UCI Machine Learning Repository . Este conjunto de dados contém dados categóricos e numéricos.

Os dados estão no formato CSV, aqui estão as duas primeiras linhas:

 39, State-gov, 77516, Bachelors, 13, Never-married, Adm-clerical, Not-in-family, White, Male, 2174, 0, 40, United-States, <=50K
50, Self-emp-not-inc, 83311, Bachelors, 13, Married-civ-spouse, Exec-managerial, Husband, White, Male, 0, 0, 13, United-States, <=50K
 

As colunas do conjunto de dados são categóricas ou numéricas. Como existem muitas colunas, um Schema é gerado (semelhante ao exemplo anterior) fazendo um loop por todas as colunas de cada tipo. Este conjunto de dados descreve um problema de classificação: prever a última coluna em que o indivíduo ganha mais ou menos que 50 mil por ano. No entanto, da perspectiva de tf.Transform , esse rótulo é apenas outra coluna categórica.

Use este esquema para ler os dados do arquivo CSV. A constante ordered_columns contém a lista de todas as colunas na ordem em que aparecem no arquivo CSV - necessário porque o esquema não contém essas informações. Algumas transformações extras do Beam foram removidas, pois já foram feitas ao ler o arquivo CSV. Cada linha CSV é convertida em uma instância no formato na memória.

Neste exemplo, permitimos que o recurso education-num da education-num esteja ausente. Isso significa que ele é representado como um tf.io.VarLenFeature no feature_spec e como um tf.SparseTensor no preprocessing_fn. Para lidar com o valor possivelmente ausente do recurso, preenchemos instâncias ausentes com um valor padrão, neste caso 0.

 converter = tft.coders.CsvCoder(ordered_columns, raw_data_schema)

raw_data = (
    p
    | 'ReadTrainData' >> textio.ReadFromText(train_data_file)
    | ...
    | 'DecodeTrainData' >> beam.Map(converter.decode))
 

O pré-processamento é semelhante ao exemplo anterior, exceto que a função de pré-processamento é gerada programaticamente em vez de especificar manualmente cada coluna. Na função de pré-processamento abaixo, NUMERICAL_COLUMNS e CATEGORICAL_COLUMNS são listas que contêm os nomes das colunas numéricas e categóricas:

 def preprocessing_fn(inputs):
  """Preprocess input columns into transformed columns."""
  # Since we are modifying some features and leaving others unchanged, we
  # start by setting `outputs` to a copy of `inputs.
  outputs = inputs.copy()

  # Scale numeric columns to have range [0, 1].
  for key in NUMERIC_FEATURE_KEYS:
    outputs[key] = tft.scale_to_0_1(outputs[key])

  for key in OPTIONAL_NUMERIC_FEATURE_KEYS:
    # This is a SparseTensor because it is optional. Here we fill in a default
    # value when it is missing.
      sparse = tf.sparse.SparseTensor(outputs[key].indices, outputs[key].values,
                                      [outputs[key].dense_shape[0], 1])
      dense = tf.sparse.to_dense(sp_input=sparse, default_value=0.)
    # Reshaping from a batch of vectors of size 1 to a batch to scalars.
    dense = tf.squeeze(dense, axis=1)
    outputs[key] = tft.scale_to_0_1(dense)

  # For all categorical columns except the label column, we generate a
  # vocabulary but do not modify the feature.  This vocabulary is instead
  # used in the trainer, by means of a feature column, to convert the feature
  # from a string to an integer id.
  for key in CATEGORICAL_FEATURE_KEYS:
    tft.vocabulary(inputs[key], vocab_filename=key)

  # For the label column we provide the mapping from string to index.
  initializer = tf.lookup.KeyValueTensorInitializer(
      keys=['>50K', '<=50K'],
      values=tf.cast(tf.range(2), tf.int64),
      key_dtype=tf.string,
      value_dtype=tf.int64)
  table = tf.lookup.StaticHashTable(initializer, default_value=-1)

  outputs[LABEL_KEY] = table.lookup(outputs[LABEL_KEY])

  return outputs
 

Uma diferença do exemplo anterior é que a coluna label especifica manualmente o mapeamento da string para um índice. Portanto, '>50' é mapeado para 0 e '<=50K' é mapeado para 1 porque é útil saber qual índice no modelo treinado corresponde a qual rótulo.

A variável raw_data representa um PCollection que contém dados no mesmo formato que a lista raw_data (do exemplo anterior), usando a mesma AnalyzeAndTransformDataset . O esquema é usado em dois locais: lendo os dados do arquivo CSV e como entrada para AnalyzeAndTransformDataset . O formato CSV e o formato na memória devem ser pareados com um esquema para interpretá-los como tensores.

O estágio final é gravar os dados transformados no disco e tem uma forma semelhante à leitura dos dados brutos. O esquema usado para fazer isso faz parte da saída de AnalyzeAndTransformDataset que infere um esquema para os dados de saída. O código para gravar no disco é mostrado abaixo. O esquema faz parte dos metadados, mas usa os dois de forma intercambiável na API tf.Transform (ou seja, passa os metadados para o ExampleProtoCoder ). Esteja ciente de que isso grava em um formato diferente. Em vez de textio.WriteToText , use o suporte TFRecord do TFRecord para o formato TFRecord e use um codificador para codificar os dados como Example protos. Esse é um formato melhor para usar no treinamento, conforme mostrado na próxima seção. transformed_eval_data_base fornece o nome do arquivo base para os shards individuais que são gravados.

 transformed_data | "WriteTrainData" >> tfrecordio.WriteToTFRecord(
    transformed_eval_data_base,
    coder=tft.coders.ExampleProtoCoder(transformed_metadata))
 

Além dos dados de treinamento, transform_fn também é gravado com os metadados:

 _ = (
    transform_fn
    | 'WriteTransformFn' >> tft_beam.WriteTransformFn(working_dir))
transformed_metadata | 'WriteMetadata' >> tft_beam.WriteMetadata(
    transformed_metadata_file, pipeline=p)
 

Execute o pipeline inteiro do Beam com p.run().wait_until_finish() . Até esse momento, o pipeline Beam representa um cálculo distribuído adiado. Ele fornece instruções para o que será feito, mas as instruções não foram executadas. Essa chamada final executa o pipeline especificado.

Faça o download do conjunto de dados do censo

Faça o download do conjunto de dados do censo usando os seguintes comandos do shell:

  wget https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data
  wget https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test

Ao executar o script census_example.py , transmita o diretório que contém esses dados como o primeiro argumento. O script cria um subdiretório temporário para adicionar os dados pré-processados.

Integre-se ao treinamento TensorFlow

A seção final de census_example.py mostra como os dados pré-processados ​​são usados ​​para treinar um modelo. Consulte a documentação dos Estimadores para obter detalhes. O primeiro passo é construir um Estimator que requer uma descrição das colunas pré-processadas. Cada coluna numérica é descrita como uma coluna real_valued_column que é um invólucro em torno de um vetor denso com um tamanho fixo ( 1 neste exemplo). Cada coluna categórica é mapeada da cadeia de caracteres para números inteiros e, em seguida, é passada para o indicator_column . tft.TFTransformOutput é usado para encontrar o caminho do arquivo de vocabulário para cada recurso categórico.

 real_valued_columns = [feature_column.real_valued_column(key)
                       for key in NUMERIC_FEATURE_KEYS]

one_hot_columns = [
    tf.feature_column.indicator_column(
        tf.feature_column.categorical_column_with_vocabulary_file(
            key=key,
            vocabulary_file=tf_transform_output.vocabulary_file_by_name(
                vocab_filename=key)))
    for key in CATEGORICAL_FEATURE_KEYS]

estimator = tf.estimator.LinearClassifier(real_valued_columns + one_hot_columns)
 

O próximo passo é criar um construtor para gerar a função de entrada para treinamento e avaliação. tf.Learn difere do treinamento usado pelo tf.Learn pois uma especificação de recurso não é necessária para analisar os dados transformados. Em vez disso, use os metadados para os dados transformados para gerar uma especificação de recurso.

 def _make_training_input_fn(tf_transform_output, transformed_examples,
                            batch_size):
  ...
  def input_fn():
    """Input function for training and eval."""
    dataset = tf.data.experimental.make_batched_features_dataset(
        ..., tf_transform_output.transformed_feature_spec(), ...)

    transformed_features = tf.compat.v1.data.make_one_shot_iterator(
        dataset).get_next()
    ...

  return input_fn
 

O código restante é o mesmo que usar a classe Estimator . O exemplo também contém código para exportar o modelo no formato SavedModel . O modelo exportado pode ser usado pelo Tensorflow Serving ou pelo Cloud ML Engine .