Los cargadores de datos permiten cargar y administrar los datos de manera sencilla y eficiente. Tanto Tensorflow como Pytorch cuentan con métodos de cargadores de datos. En PyTorch, podemos usar el método Dataset para crear una clase que permita administrar los datos.
La clase MyDataset que presentamos aquí toma una dirección de un archivo .csv y carga los datos de este archivo (usando pandas). Permite entonces administrar los datos, accediendo a cada uno de ellos y separando los datos de sus clases.
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
class MyDataset(Dataset):
def __init__(self,file_name):
#Carga del csv
dataframe = pd.read_csv(file_name, index_col=0)
#Datos y clases
x = dataframe.iloc[:,:-1].values
y = dataframe.iloc[:,-1].values
self.x = torch.tensor(x, dtype=torch.float)
self.y = torch.tensor(y, dtype=torch.long)
#Tamaño de los datos
self.size = self.x.size()
def __len__(self):
#Regresa tamaño
return len(self.y)
def __getitem__(self,idx):
#Regresa un dato
return self.x[idx], self.y[idx]
Para cargar los datos, sólo llamamos a la clase MyDataset y le damos la dirección del archiv .csv que queremos que cargue. Esto genera un objeto que contenga la información de los datos.
#Carga de datos
data = MyDataset('dataset.csv')
Esta clase nos permite visualzar el tamaño de los datos, así como acceder a un dato que le indiquemos para que podamos ver ese dato y su clase.
#Imprime tamaño de datos
print(data.size)
#Regresa el dato indicado
print(data[0])
torch.Size([569, 30]) (tensor([1.7990e+01, 1.0380e+01, 1.2280e+02, 1.0010e+03, 1.1840e-01, 2.7760e-01, 3.0010e-01, 1.4710e-01, 2.4190e-01, 7.8710e-02, 1.0950e+00, 9.0530e-01, 8.5890e+00, 1.5340e+02, 6.3990e-03, 4.9040e-02, 5.3730e-02, 1.5870e-02, 3.0030e-02, 6.1930e-03, 2.5380e+01, 1.7330e+01, 1.8460e+02, 2.0190e+03, 1.6220e-01, 6.6560e-01, 7.1190e-01, 2.6540e-01, 4.6010e-01, 1.1890e-01]), tensor(0))
Los cargadores de datos nos permiten trabajar con mini-lotes (mini-batches) que son de suma importancia en el aprendizaje con redes neuronales, pues permiten un aprendizaje más eficiente. En PyTorch, podemos usar DataLoader para administrar los mini-lotes en los datos. De esta forma, podemos administrar el tamaño de los datos e indicar que estos mini-lotes se definan de manera aleatoria en cada época. Necesitamos, pues indicar el tamaño del lote; shuffle=True indica que en cada época, los lotes serán aleatorizados.
El método DataLoader permite manejar la clase Dataset por medio de la propiedad dataset, la cual nos permite hacer lo mismos procesos de la clase MyDataset.
#Cargador de datos con mini-lotes
loader = DataLoader(data, batch_size=64, shuffle=True)
print(loader.dataset[0])
(tensor([1.7990e+01, 1.0380e+01, 1.2280e+02, 1.0010e+03, 1.1840e-01, 2.7760e-01, 3.0010e-01, 1.4710e-01, 2.4190e-01, 7.8710e-02, 1.0950e+00, 9.0530e-01, 8.5890e+00, 1.5340e+02, 6.3990e-03, 4.9040e-02, 5.3730e-02, 1.5870e-02, 3.0030e-02, 6.1930e-03, 2.5380e+01, 1.7330e+01, 1.8460e+02, 2.0190e+03, 1.6220e-01, 6.6560e-01, 7.1190e-01, 2.6540e-01, 4.6010e-01, 1.1890e-01]), tensor(0))
La administración de mini-lotes permite que el modelo de aprendizaje vea sólo $n$ (el tamaño del lote) ejemplos en cada actualización de los parámetros de aprendizaje. Esto mejora el proceso de aprendizaje en redes neuronales, así como su eficiencia, pues procesar mini-lotes tomará menos tiempo que procesar dato por dato.
En cada época, se verán todos los lotes, hasta abarcar el total de datos. Podemos ver aquí el tamaó de lotes de los datos que hemos cargado.
for x_batch, y_batch in loader:
print('Tamaño de lote x e y: {}, {}'.format(x_batch.size(),y_batch.size()))
Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([64, 30]), torch.Size([64]) Tamaño de lote x e y: torch.Size([57, 30]), torch.Size([57])
Podemos utilizar la administración de lostes que nos permite el cargador de datos para entrenar más efiecientemente una red neuronal.
%%time
import torch.nn as nn
from tqdm import tqdm
#Arquitectura de red
model = nn.Sequential(nn.Linear(data.size[1], 60), nn.Tanh(), nn.Linear(60, 2), nn.Softmax(1))
risk = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
#Entrenamiento con mini-lotes
for t in tqdm(range(0, 1000)):
for x_batch, y_batch in loader:
y_pred = model(x_batch)
optimizer.zero_grad()
loss = risk(y_pred, y_batch)
loss.backward()
optimizer.step()
100%|██████████| 1000/1000 [00:10<00:00, 99.15it/s]
CPU times: user 10.1 s, sys: 39.9 ms, total: 10.1 s Wall time: 10.1 s
Usar un tamaño de lotes de mayor tamaño ahorará tiempo en el procesamiento sin sacrificar la calidad del aprendizaje. Si usamos un lote de tamaño 1, que equivale a procesar ejemplo por ejemplo, el tiempo del entrenamiento será mayor. Si usamos un lote del tamaño del número de datos, que equivale a aprender con todos los datos, el proceso será más rápido, pero muchas veces el aprendizaje será deficiente. Determinar el tamaño óptimo de los lotes es también parte importante del modelo de aprendizaje.
%%time
#Cargador de datos con mini-lotes
loader = DataLoader(data, batch_size=1, shuffle=True)
#Arquitectura de red
model = nn.Sequential(nn.Linear(data.size[1], 60), nn.Tanh(), nn.Linear(60, 2), nn.Softmax(1))
risk = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
#Entrenamiento con mini-lotes
for t in tqdm(range(0, 1000)):
for x_batch, y_batch in loader:
y_pred = model(x_batch)
optimizer.zero_grad()
loss = risk(y_pred, y_batch)
loss.backward()
optimizer.step()
100%|██████████| 1000/1000 [06:32<00:00, 2.55it/s]
CPU times: user 6min 32s, sys: 576 ms, total: 6min 32s Wall time: 6min 32s