He entrenado una red neuronal completamente conectada con una capa oculta de 64 nodos. Estoy probando con el conjunto de datos Costos médicos. Con el modelo de precisión original, el error absoluto medio es 0.22063259780406952. Con un modelo cuantificado a float16 o integer quantization with float fallback, la diferencia entre el error original y el modelo de baja precisión nunca es superior a 0,1. Sin embargo, si lo hago full integer quantization, el error se dispara a cantidades irrazonables. En este caso particular, salta a casi 60. No tengo idea si esto es un error en TensorFlow, o si estoy usando las API incorrectamente o si este es un comportamiento razonable después de la cuantificación. Se agradece cualquier ayuda. El código que muestra la conversión y la inferencia se muestra a continuación:

  • Preprocesamiento
import math
import pathlib
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
import pandas as pd
from sklearn import preprocessing as pr
from sklearn.metrics import mean_absolute_error

url = 'insurance.csv'
column_names = ["age", "sex", "bmi", "children", "smoker", "region", "charges"]

dataset = pd.read_csv(url, names=column_names, header=0, na_values='?')

dataset = dataset.dropna()  # Drop rows with missing values
dataset['sex'] = dataset['sex'].map({'female': 2, 'male': 1})
dataset['smoker'] = dataset['smoker'].map({'yes': 1, 'no': 0})

dataset = pd.get_dummies(dataset, prefix='', prefix_sep='', columns=['region'])

# this is a trick to convert a dataframe to 2d array, scale it and
# convert back to dataframe
scaled_np = pr.StandardScaler().fit_transform(dataset.values)
dataset = pd.DataFrame(scaled_np, index=dataset.index, columns=dataset.columns)
  • División de entrenamiento y prueba
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)

train_features = train_dataset.copy()
test_features = test_dataset.copy()

train_labels = train_features.pop('charges')
test_labels = test_features.pop('charges')
  • Entrenamiento de modelo original
def build_and_compile_model():
    model = keras.Sequential([
        layers.Dense(64,
                     activation='relu',
                     input_shape=(len(dataset.columns) - 1, )),
        layers.Dense(1)
    ])

    model.compile(loss='mean_absolute_error',
                  optimizer=tf.keras.optimizers.Adam(0.001))
    return model


dnn_model = build_and_compile_model()
dnn_model.summary()

dnn_model.fit(train_features,
              train_labels,
              validation_split=0.2,
              verbose=0,
              epochs=100)

print("Original error = {}".format(
    dnn_model.evaluate(test_features, test_labels, verbose=0)))
  • Conversión a modelo de menor precisión
converter = tf.lite.TFLiteConverter.from_keras_model(dnn_model)
converter.optimizations = [tf.lite.Optimize.DEFAULT]

def representative_data_gen():
    for input_value in tf.data.Dataset.from_tensor_slices(
            train_features.astype('float32')).batch(1).take(100):
        yield [input_value]


converter.representative_dataset = representative_data_gen

# Full Integer Quantization
# Ensure that if any ops can't be quantized, the converter throws an error
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
# Set the input and output tensors to uint8 (APIs added in r2.3)
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8

tflite_model_quant = converter.convert()

dir_save = pathlib.Path(".")
file_save = dir_save / "model_16.tflite"
file_save.write_bytes(tflite_model_quant)
  • Instancia del modelo TFLite
interpreter = tf.lite.Interpreter(model_path=str(file_save))
interpreter.allocate_tensors()
  • Evaluar el modelo de menor precisión
def evaluate_model(interpreter, test_images, test_labels):
    input_details = interpreter.get_input_details()[0]
    input_index = interpreter.get_input_details()[0]["index"]
    output_index = interpreter.get_output_details()[0]["index"]

    # Run predictions on every image in the "test" dataset.
    prediction_digits = []
    for test_image in test_images:
        if input_details['dtype'] == np.uint8:
            input_scale, input_zero_point = input_details['quantization']
            test_image = test_image / input_scale + input_zero_point

        test_image = np.expand_dims(test_image,
                                    axis=0).astype(input_details['dtype'])
        interpreter.set_tensor(input_index, test_image)

        # Run inference.
        interpreter.invoke()

        output = interpreter.get_tensor(output_index)
        prediction_digits.append(output[0])


    filtered_labels, correct_digits = map(
        list,
        zip(*[(x, y) for x, y in zip(test_labels, prediction_digits)
              if not math.isnan(y)]))
    return mean_absolute_error(filtered_labels, correct_digits)

print(evaluate_model(interpreter, test_features[:].values, test_labels))
0
Samvid Mistry 20 oct. 2020 a las 17:12

1 respuesta

La mejor respuesta

Cuando realiza la cuantificación ( y el aprendizaje automático en general ), debe tener cuidado con el aspecto de sus datos. ¿Tendrá sentido aplicar un cierto nivel de cuantificación con los datos que tiene?

En el caso de un problema de regresión como el suyo, con la verdad del terreno en el rango [1121.8739;63770.42801], y algunos datos de entrada que también están en flotación, es probable que entrenar un modelo con esos datos y luego cuantificarlo en números enteros ganados no da buenos resultados.

Entrenó el modelo para generar valores en el rango [1121.8739;63770.42801], y después de la cuantificación en int8, podrá generar solo en el rango [-127;128], sin puntos decimales. Obviamente, cuando compare los resultados del modelo cuantificado con la verdad del terreno, el error saltará por las nubes.

¿Qué puede hacer si aún desea aplicar la cuantificación? Necesita cambiar sus datos en el dominio del conjunto cuantificado. En su caso, convierta sus datos float32 a int8 de una manera que todavía tengan sentido. Verá una gran caída en el rendimiento en un caso de uso real. Después de todo, con un problema de regresión, se cambia de un dominio de aproximadamente 25 millones posibles valores de salida (asumiendo una mantisa de 23 bits y 8 bits de exponentes, consulte Punto flotante de precisión simple y ¿Cuántos números de coma flotante hay en el intervalo [0,1]?) , a un dominio con 256 (2 ^ 8) salidas posibles.

Pero un enfoque realmente muy ingenuo podría ser aplicar la siguiente transformación:

def scale_down_data(data):
  max_value = data.max()
  min_value = data.min()
  # normalizing between -128 and 127
  scaled_down = 255*((data-min_value)/(max_value-min_value)) -128
  return scaled_down.astype(np.int8)

En la práctica, sería mejor observar la distribución de sus datos y hacer una transformación que le brinde más rango donde los datos son más densos. Tampoco desea limitar el rango de su regresión a los límites de su conjunto de entrenamiento. Y necesita hacer ese análisis para cada entrada o salida que no esté en el dominio cuantificado.

1
Lescurel 21 oct. 2020 a las 09:39