Precisión de clasificación de clases múltiples por clase usando PyTorch — Visual Studio Magazine

Laboratorio de ciencia de datos

Precisión de clasificación de clases múltiples por clase usando PyTorch

Dr. de Microsoft Research. James McCaffrey: Cuando los datos de varias clases están sesgados hacia una o más clases, es muy importante analizar la precisión de clase.

Un problema de clasificación multiclase es aquel cuyo objetivo es predecir un valor discreto donde hay tres o más posibilidades. Por ejemplo, es posible que desee predecir la inclinación política de una persona (conservadora, moderada, liberal) en función de su sexo, edad, estado en el que vive e ingresos.

Un enfoque ingenuo para evaluar el rendimiento de un modelo multiclase entrenado es calcular la precisión del modelo en todos los datos de prueba. Pero suponga que sus datos están sesgados hacia una clase en particular. Por ejemplo, si la mayoría de los elementos de datos son de clase media (por ejemplo, 900 de 1000) y solo unos pocos son conservadores (por ejemplo, 40 de 1000) y la clase es liberal (60 de 1000), entonces el modelo predictivo para cualquier entrada dada, la clase promedio obtendrá un 90 por ciento de precisión.

Este artículo muestra cómo calcular la precisión de clasificación para una clase. Una buena manera de ver a dónde va este artículo es mirar una captura de pantalla del programa de demostración. Figura 1. La demostración comienza cargando un conjunto de datos de entrenamiento de 200 elementos y un conjunto de datos de prueba de 40 elementos.


Figura 1: Precisión multiclase por clase
[Click on image for larger view.] Figura 1: Precisión multiclase por clase

La precisión de clasificación general del modelo en los datos de prueba después del entrenamiento es del 75,00 por ciento. Luego, la demostración calcula la precisión para cada una de las tres clases: conservadora (54,55 por ciento), moderada (92,86 por ciento) y liberal (73,33 por ciento). Interpretar la precisión por clase es algo subjetivo, pero los resultados de la demostración son razonables.

Python y PyTorch deben estar instalados en su máquina para ejecutar la demostración. Las demostraciones se desarrollaron en Windows 10/11 utilizando la distribución Anaconda 2020.02 de 64 bits (que contiene Python 3.7.6) y PyTorch versión 1.12.1 para CPU instalada a través de pip. Las instrucciones de instalación detalladas paso a paso para esta configuración se pueden encontrar en las publicaciones de mi blog. aquí y aquí.

Puede encontrar el código fuente del programa de demostración completo aquí y también se adjunta al artículo como un archivo comprimido descargable.

Información
El formato de los datos de entrenamiento y prueba se ve así:

 1   0.24   1   0   0   0.2950   2
-1   0.39   0   0   1   0.5120   1
 1   0.63   0   1   0   0.7580   0
-1   0.36   1   0   0   0.4450   1
 1   0.27   0   1   0   0.2860   2
. . .

Cada línea de datos separados por tabuladores representa a un individuo. Los campos son sexo (masculino = -1, femenino = +1), edad (dividido por 100), estado (Michigan = 100, Nebraska = 010, Oklahoma = 001), ingreso (dividido por $100 000) y tipo político (conservador = 0, moderado = 1, liberal = 2).

Puede encontrar información completa sobre capacitación y pruebas aquí.

La demostración define la clase PeopleDataset Lista 1. Una instancia de un objeto PeopleDataset se puede pasar al DataLoader para entrenamiento o usarse directamente para calcular la precisión de la clasificación.

Listado 1: Definición de clase PeopleDataset

class PeopleDataset(T.utils.data.Dataset):
  def __init__(self, src_file):
    all_xy = np.loadtxt(src_file, usecols=range(0,7),
      delimiter="\t", comments="#", dtype=np.float32)
    tmp_x = all_xy[:,0:6]   # cols [0,6) = [0,5]
    tmp_y = all_xy[:,6]     # 1-D

    self.x_data = T.tensor(tmp_x, 
      dtype=T.float32).to(device)
    self.y_data = T.tensor(tmp_y,
      dtype=T.int64).to(device)  # 1-D

  def __len__(self):
    return len(self.x_data)

  def __getitem__(self, idx):
    preds = self.x_data[idx]
    trgts = self.y_data[idx] 
    return preds, trgts  # as a Tuple

Programa de demostración

Se presenta la estructura del programa de demostración.
Lista 2. Prefiero ingresar mis programas de Python con dos espacios en lugar de los cuatro más comunes. En Python, la barra invertida se usa para la continuación de línea.

Listado 2: Estructura general del programa de demostración

# people_politics.py
# predict politics type from sex, age, state, income
# PyTorch 1.12.1-CPU Anaconda3-2020.02  Python 3.7.6
# Windows 10/11 

# compute accuracy by class

import numpy as np
import torch as T
device = T.device('cpu')  # apply to Tensor or Module

# -----------------------------------------------------------

class PeopleDataset(T.utils.data.Dataset): . . . 

# -----------------------------------------------------------

class Net(T.nn.Module):
  def __init__(self):
    super(Net, self).__init__()
    self.hid1 = T.nn.Linear(6, 10)  # 6-(10-10)-3
    self.hid2 = T.nn.Linear(10, 10)
    self.oupt = T.nn.Linear(10, 3)

    T.nn.init.xavier_uniform_(self.hid1.weight)
    T.nn.init.zeros_(self.hid1.bias)
    T.nn.init.xavier_uniform_(self.hid2.weight)
    T.nn.init.zeros_(self.hid2.bias)
    T.nn.init.xavier_uniform_(self.oupt.weight)
    T.nn.init.zeros_(self.oupt.bias)

  def forward(self, x):
    z = T.tanh(self.hid1(x))
    z = T.tanh(self.hid2(z))
    z = T.log_softmax(self.oupt(z), dim=1)  # NLLLoss() 
    return z

# -----------------------------------------------------------

def accuracy(model, ds):
  # assumes model.eval()
  # item-by-item version
  n_correct = 0; n_wrong = 0
  for i in range(len(ds)):
    X = ds[i][0].reshape(1,-1)  # make it a batch
    Y = ds[i][1].reshape(1)  # 0 1 or 2, 1D
    with T.no_grad():
      oupt = model(X)  # logits form

    big_idx = T.argmax(oupt)  # 0 or 1 or 2
    if big_idx == Y:
      n_correct += 1
    else:
      n_wrong += 1

  acc = (n_correct * 1.0) / (n_correct + n_wrong)
  return acc

# -----------------------------------------------------------

def do_acc(model, dataset, n_classes):
  X = dataset[0:len(dataset)][0]  # all X values
  Y = dataset[0:len(dataset)][1]  # all Y values
  with T.no_grad():
    oupt = model(X)  #  [40,3]  all logits

  for c in range(n_classes):
    idxs = np.where(Y==c)  # indices where Y is c
    logits_c = oupt[idxs]  # logits corresponding to Y == c
    arg_maxs_c = T.argmax(logits_c, dim=1)  # predicted class
    num_correct = T.sum(arg_maxs_c == c)
    acc_c = num_correct.item() / len(arg_maxs_c)
    print("%0.4f " % acc_c)

# -----------------------------------------------------------

def main():
  # 0. get started
  print("Begin People predict politics type ")
  T.manual_seed(1)
  np.random.seed(1)
  
  # 1. create DataLoader objects
  print("Creating People Datasets ")

  train_file = ".\\Data\\people_train.txt"
  train_ds = PeopleDataset(train_file)  # 200 rows

  test_file = ".\\Data\\people_test.txt"
  test_ds = PeopleDataset(test_file)    # 40 rows

  bat_size = 10
  train_ldr = T.utils.data.DataLoader(train_ds,
    batch_size=bat_size, shuffle=True)

# -----------------------------------------------------------

  # 2. create network
  print("Creating 6-(10-10)-3 neural network ")
  net = Net().to(device)
  net.train()

# -----------------------------------------------------------

  # 3. train model
  max_epochs = 1000
  ep_log_interval = 100
  lrn_rate = 0.01

  loss_func = T.nn.NLLLoss()  # assumes log_softmax()
  optimizer = T.optim.SGD(net.parameters(), lr=lrn_rate)

  print("bat_size = %3d " % bat_size)
  print("loss = " + str(loss_func))
  print("optimizer = SGD")
  print("max_epochs = %3d " % max_epochs)
  print("lrn_rate = %0.3f " % lrn_rate)

  print("Starting training")
  for epoch in range(0, max_epochs):
    epoch_loss = 0  # for one full epoch

    for (batch_idx, batch) in enumerate(train_ldr):
      X = batch[0]  # inputs
      Y = batch[1]  # correct class/label/politics

      optimizer.zero_grad()
      oupt = net(X)
      loss_val = loss_func(oupt, Y)  # a tensor
      epoch_loss += loss_val.item()  # accumulate
      loss_val.backward()
      optimizer.step()

    if epoch % ep_log_interval == 0:
      print("epoch = %5d  |  loss = %10.4f" % \
        (epoch, epoch_loss))
  print("Training done ")

# -----------------------------------------------------------

  # 4. evaluate model accuracy
  print("Computing overall model accuracy")
  net.eval()
  acc_train = accuracy(net, train_ds)  # item-by-item
  print("Accuracy on training data = %0.4f" % acc_train)
  acc_test = accuracy(net, test_ds) 
  print("Accuracy on test data = %0.4f" % acc_test)

  print("Accuracy on test by class (fast technique): ")
  do_acc(net, test_ds, 3)

  # 5. make a prediction
  print("Predicting for M  30  oklahoma  $50,000: ")
  X = np.array([[-1, 0.30,  0,0,1,  0.5000]],
    dtype=np.float32)
  X = T.tensor(X, dtype=T.float32).to(device) 

  with T.no_grad():
    logits = net(X)  # do not sum to 1.0
  probs = T.exp(logits)  # sum to 1.0
  probs = probs.numpy()  # numpy vector prints better
  np.set_printoptions(precision=4, suppress=True)
  print(probs)

  # 6. save model (state_dict approach)
  print("Saving trained model state")
  # fn = ".\\Models\\people_model.pt"
  # T.save(net.state_dict(), fn)

  print("End People predict politics demo")

if __name__ == "__main__":
  main()  main()

Toda la lógica de control está en la función main() definida por el programa. El programa comienza con algunas declaraciones preparatorias:

import numpy as np
import torch as T
device = T.device('cpu')  # apply to Tensor or Module

def main():
  # 0. get started
  print("Begin People predict politics type ")
  T.manual_seed(1)
  np.random.seed(1)
. . .

Una vez que los datos se cargan en la memoria, se crea y entrena un clasificador multiclase. El entrenamiento se lleva a cabo utilizando técnicas estándar.

Evaluación de la precisión por clase
Se proporciona una función clave do_acc() que define la precisión por clase Lista 3. La función acepta un objeto Dataset que almacena los datos de entrenamiento o prueba.

Listado 3: Precisión por función de clase

def do_acc(model, dataset, n_classes):
  X = dataset[0:len(dataset)][0]  # all X values
  Y = dataset[0:len(dataset)][1]  # all Y values
  with T.no_grad():
    oupt = model(X)  #  [40,3]  all logits

  for c in range(n_classes):
    idxs = np.where(Y==c)  # indices where Y is c
    logits_c = oupt[idxs]  # logits corresponding to Y == c
    arg_maxs_c = T.argmax(logits_c, dim=1)  # predicted class
    num_correct = T.sum(arg_maxs_c == c)
    acc_c = num_correct.item() / len(arg_maxs_c)
    print("%0.4f " % acc_c)

Primero, todas las entradas se almacenan en X y todas las salidas objetivo se almacenan en Y. Las salidas tienen la forma de logits, donde la ubicación de la salida más grande corresponde a la clase (0, 1, 2). La salida de do_acc() es una instrucción print() que resume la clasificación de cada clase. En un escenario que no sea de demostración, es posible que desee devolver una matriz acc_c que contenga los resultados.

no envolver
Cuando los datos de varias clases están sesgados en una o más clases, es muy importante analizar la precisión de las clases. Esto significa que antes de comenzar a trabajar en un sistema de predicción multiclase, casi siempre es una buena idea analizar los datos de entrenamiento para determinar si hay datos sesgados por clases.

Sobre el Autor

El Dr. James McCaffrey trabaja para Microsoft Research en Redmond, Washington. Ha trabajado en varios productos de Microsoft, incluidos Azure y Bing. James puede ser contactado [email protected].

Leave a Reply

Your email address will not be published. Required fields are marked *