¿Cómo obtener mini-lotes en pytorch de forma limpia y eficiente?

Estaba tratando de hacer una cosa simple que era entrenar un modelo lineal con Estocástica Gradiente Descenso (SGD) utilizando antorcha:

import numpy as np import torch from torch.autograd import Variable import pdb def get_batch2(X,Y,M,dtype): X,Y = X.data.numpy(), Y.data.numpy() N = len(Y) valid_indices = np.array( range(N) ) batch_indices = np.random.choice(valid_indices,size=M,replace=False) batch_xs = torch.FloatTensor(X[batch_indices,:]).type(dtype) batch_ys = torch.FloatTensor(Y[batch_indices]).type(dtype) return Variable(batch_xs, requires_grad=False), Variable(batch_ys, requires_grad=False) def poly_kernel_matrix( x,D ): N = len(x) Kern = np.zeros( (N,D+1) ) for n in range(N): for d in range(D+1): Kern[n,d] = x[n]**d; return Kern ## data params N=5 # data set size Degree=4 # number dimensions/features D_sgd = Degree+1 ## x_true = np.linspace(0,1,N) # the real data points y = np.sin(2*np.pi*x_true) y.shape = (N,1) ## TORCH dtype = torch.FloatTensor # dtype = torch.cuda.FloatTensor # Uncomment this to run on GPU X_mdl = poly_kernel_matrix( x_true,Degree ) X_mdl = Variable(torch.FloatTensor(X_mdl).type(dtype), requires_grad=False) y = Variable(torch.FloatTensor(y).type(dtype), requires_grad=False) ## SGD mdl w_init = torch.zeros(D_sgd,1).type(dtype) W = Variable(w_init, requires_grad=True) M = 5 # mini-batch size eta = 0.1 # step size for i in range(500): batch_xs, batch_ys = get_batch2(X_mdl,y,M,dtype) # Forward pass: compute predicted y using operations on Variables y_pred = batch_xs.mm(W) # Compute and print loss using operations on Variables. Now loss is a Variable of shape (1,) and loss.data is a Tensor of shape (1,); loss.data[0] is a scalar value holding the loss. loss = (1/N)*(y_pred - batch_ys).pow(2).sum() # Use autograd to compute the backward pass. Now w will have gradients loss.backward() # Update weights using gradient descent; w1.data are Tensors, # w.grad are Variables and w.grad.data are Tensors. W.data -= eta * W.grad.data # Manually zero the gradients after updating weights W.grad.data.zero_() # c_sgd = W.data.numpy() X_mdl = X_mdl.data.numpy() y = y.data.numpy() # Xc_pinv = np.dot(X_mdl,c_sgd) print('J(c_sgd) = ', (1/N)*(np.linalg.norm(y-Xc_pinv)**2) ) print('loss = ',loss.data[0]) 

el código funciona bien y, a pesar de que mi método get_batch2 parece realmente get_batch2 / ingenuo, probablemente sea porque soy nuevo en Pytorch, pero no he encontrado un buen lugar donde discutan cómo recuperar lotes de datos. Revisé sus tutoriales ( http://pytorch.org/tutorials/beginner/pytorch_with_examples.html ) y el conjunto de datos ( http://pytorch.org/tutorials/beginner/data_loading_tutorial.html ) sin suerte. Todos los tutoriales parecen suponer que uno ya tiene el lote y el tamaño del lote al principio y luego procede a entrenar con esos datos sin cambiarlos (consulte específicamente http://pytorch.org/tutorials/beginner/pytorch_with_examples.html# pytorch-variables-y-autograd ).

Entonces, mi pregunta es ¿realmente necesito volver a convertir mis datos en números para poder obtener una muestra aleatoria de ellos y luego volverlos a pytorch con Variable para poder entrenar en la memoria? ¿No hay manera de obtener mini-lotes con antorcha?

Miré algunas funciones que proporciona la antorcha pero sin suerte:

 #pdb.set_trace() #valid_indices = torch.arange(0,N).numpy() #valid_indices = np.array( range(N) ) #batch_indices = np.random.choice(valid_indices,size=M,replace=False) #indices = torch.LongTensor(batch_indices) #batch_xs, batch_ys = torch.index_select(X_mdl, 0, indices), torch.index_select(y, 0, indices) #batch_xs,batch_ys = torch.index_select(X_mdl, 0, indices), torch.index_select(y, 0, indices) 

A pesar de que el código que proporcioné funciona bien, me preocupa que no sea una implementación eficiente Y que si utilizara las GPU, se produciría una mayor desaceleración (porque supongo que se guardan las cosas en la memoria y luego se recuperan para ponerlas). La GPU así es tonta).


Implementé una nueva basada en la respuesta que sugería usar torch.index_select() :

 def get_batch2(X,Y,M): ''' get batch for pytorch model ''' # TODO fix and make it nicer, there is pytorch forum question #X,Y = X.data.numpy(), Y.data.numpy() X,Y = X, Y N = X.size()[0] batch_indices = torch.LongTensor( np.random.randint(0,N+1,size=M) ) pdb.set_trace() batch_xs = torch.index_select(X,0,batch_indices) batch_ys = torch.index_select(Y,0,batch_indices) return Variable(batch_xs, requires_grad=False), Variable(batch_ys, requires_grad=False) 

sin embargo, esto parece tener problemas porque no funciona si X,Y NO son variables … lo que es realmente extraño. Agregué esto al foro de pytorch: https://discuss.pytorch.org/t/how-to-get-mini-batches-in-pytorch-in-a-clean-and-efficient-way/10322

Ahora mismo con lo que estoy luchando es hacer que esto funcione para gpu. Mi versión más actual:

 def get_batch2(X,Y,M,dtype): ''' get batch for pytorch model ''' # TODO fix and make it nicer, there is pytorch forum question #X,Y = X.data.numpy(), Y.data.numpy() X,Y = X, Y N = X.size()[0] if dtype == torch.cuda.FloatTensor: batch_indices = torch.cuda.LongTensor( np.random.randint(0,N,size=M) )# without replacement else: batch_indices = torch.LongTensor( np.random.randint(0,N,size=M) ).type(dtype) # without replacement pdb.set_trace() batch_xs = torch.index_select(X,0,batch_indices) batch_ys = torch.index_select(Y,0,batch_indices) return Variable(batch_xs, requires_grad=False), Variable(batch_ys, requires_grad=False) 

el error:

 RuntimeError: tried to construct a tensor from a int sequence, but found an item of type numpy.int64 at index (0) 

No lo entiendo, realmente tengo que hacer:

 ints = [ random.randint(0,N) for ii range(M)] 

para obtener los enteros?

También sería ideal si los datos pudieran ser una variable. Parece que torch.index_select no funciona para datos de tipo Variable .

Esta lista de enteros todavía no funciona:

 TypeError: torch.addmm received an invalid combination of arguments - got (int, torch.cuda.FloatTensor, int, torch.cuda.FloatTensor, torch.FloatTensor, out=torch.cuda.FloatTensor), but expected one of: * (torch.cuda.FloatTensor source, torch.cuda.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (torch.cuda.FloatTensor source, torch.cuda.sparse.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (float beta, torch.cuda.FloatTensor source, torch.cuda.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (torch.cuda.FloatTensor source, float alpha, torch.cuda.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (float beta, torch.cuda.FloatTensor source, torch.cuda.sparse.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (torch.cuda.FloatTensor source, float alpha, torch.cuda.sparse.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) * (float beta, torch.cuda.FloatTensor source, float alpha, torch.cuda.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) didn't match because some of the arguments have invalid types: (int, torch.cuda.FloatTensor, int, torch.cuda.FloatTensor, torch.FloatTensor, out=torch.cuda.FloatTensor) * (float beta, torch.cuda.FloatTensor source, float alpha, torch.cuda.sparse.FloatTensor mat1, torch.cuda.FloatTensor mat2, *, torch.cuda.FloatTensor out) didn't match because some of the arguments have invalid types: (int, torch.cuda.FloatTensor, int, torch.cuda.FloatTensor, torch.FloatTensor, out=torch.cuda.FloatTensor) 

Utilizar cargadores de datos.

Conjunto de datos

Primero se define un conjunto de datos. Puede usar conjuntos de datos de paquetes en torchvision.datasets o usar la clase de conjunto de datos ImageFolder que sigue la estructura de Imagenet.

 trainset=torchvision.datasets.ImageFolder(root='/path/to/your/data/trn', transform=generic_transform) testset=torchvision.datasets.ImageFolder(root='/path/to/your/data/val', transform=generic_transform) 

Transforma

Las transformaciones son muy útiles para preprocesar los datos cargados sobre la marcha. Si está utilizando imágenes, tiene que usar la transformación ToTensor() para convertir las imágenes cargadas de PIL a torch.tensor . Más transformaciones se pueden empaquetar en una transformación de composición de la siguiente manera.

 generic_transform = transforms.Compose([ transforms.ToTensor(), transforms.ToPILImage(), #transforms.CenterCrop(size=128), transforms.Lambda(lambda x: myimresize(x, (128, 128))), transforms.ToTensor(), transforms.Normalize((0., 0., 0.), (6, 6, 6)) ]) 

Cargador de datos

Luego, define un cargador de datos que prepara el siguiente lote durante el entrenamiento. Puede establecer el número de subprocesos para la carga de datos.

 trainloader=torch.utils.data.DataLoader(trainset, batch_size=32, shuffle=True, num_workers=8) testloader=torch.utils.data.DataLoader(testset, batch_size=32, shuffle=False, num_workers=8) 

Para el entrenamiento, simplemente se enumera en el cargador de datos.

  for i, data in enumerate(trainloader, 0): inputs, labels = data inputs, labels = Variable(inputs.cuda()), Variable(labels.cuda()) # continue training... 

NumPy Stuff

Sí. Tienes que convertir torch.tensor a numpy usando el método .numpy() para trabajar en él. Si está utilizando CUDA, primero debe descargar los datos de GPU a CPU utilizando el método .cpu() antes de llamar a .numpy() . Personalmente, proveniente de los antecedentes de MATLAB, prefiero hacer la mayor parte del trabajo con el tensor de antorcha, luego convertir los datos a números solo para visualización. También tenga en cuenta que la antorcha almacena los datos en un modo de primer canal, mientras que el numpy y el PIL funcionan con el último canal. Esto significa que necesita usar np.rollaxis para mover el eje del canal al último. A continuación se muestra un código de ejemplo.

 np.rollaxis(make_grid(mynet.ftrextractor(inputs).data, nrow=8, padding=1).cpu().numpy(), 0, 3) 

Explotación florestal

El mejor método que encontré para visualizar los mapas de características es usar un tablero tensor. Un código está disponible en yunjey / pytorch-tutorial .

Si entiendo su código correctamente, su función get_batch2 parece estar tomando mini lotes aleatorios de su conjunto de datos sin rastrear qué índices ya ha utilizado en una época. El problema con esta implementación es que probablemente no hará uso de todos sus datos.

La forma en que normalmente hago el procesamiento por lotes es creando una permutación aleatoria de todos los vértices posibles usando torch.randperm(N) y torch.randperm(N) en lotes. Por ejemplo:

 n_epochs = 100 # or whatever batch_size = 128 # or whatever for epoch in range(n_epochs): # X is a torch Variable permutation = torch.randperm(X.size()[0]) for i in range(0,X.size()[0], batch_size): optimizer.zero_grad() indices = permutation[i:i+batch_size] batch_x, batch_y = X[indices], Y[indices] # in case you wanted a semi-full example outputs = model.forward(batch_x) loss = lossfunction(outputs,batch_y) loss.backward() optimizer.step() 

Si te gusta copiar y pegar, asegúrate de definir tu optimizador, modelo y función de pérdida en algún lugar antes del inicio del ciclo de la época.

Con respecto a su error, intente usar torch.from_numpy(np.random.randint(0,N,size=M)).long() lugar de torch.LongTensor(np.random.randint(0,N,size=M)) . No estoy seguro de si esto solucionará el error que está recibiendo, pero solucionará un error futuro.

No estoy seguro de lo que estabas tratando de hacer. Por lotes escritos no tendrías que convertir a numpy. Puedes usar index_select () , por ejemplo:

 for epoch in range(500): k=0 loss = 0 while k < X_mdl.size(0): random_batch = [0]*5 for i in range(k,k+M): random_batch[i] = np.random.choice(N-1) random_batch = torch.LongTensor(random_batch) batch_xs = X_mdl.index_select(0, random_batch) batch_ys = y.index_select(0, random_batch) # Forward pass: compute predicted y using operations on Variables y_pred = batch_xs.mul(W) # etc.. 

El rest del código también tendría que ser cambiado.


Supongo que le gustaría crear una función get_batch que concatene sus tensores X y tensores Y. Algo como:

 def make_batch(list_of_tensors): X, y = list_of_tensors[0] # may need to unsqueeze X and y to get right dimensions for i, (sample, label) in enumerate(list_of_tensors[1:]): X = torch.cat((X, sample), dim=0) y = torch.cat((y, label), dim=0) return X, y 

Luego, durante el entrenamiento, selecciona, por ejemplo, max_batch_size = 32, ejemplos a través del corte.

 for epoch: X, y = make_batch(list_of_tensors) X = Variable(X, requires_grad=False) y = Variable(y, requires_grad=False) k = 0 while k < X.size(0): inputs = X[k:k+max_batch_size,:] labels = y[k:k+max_batch_size,:] # some computation k+= max_batch_size 

Cree una clase que sea una subclase de torch.utils.data.Dataset y torch.utils.data.Dataset a torch.utils.data.Dataloader . A continuación se muestra un ejemplo para mi proyecto.

 class CandidateDataset(Dataset): def __init__(self, x, y): self.len = x.shape[0] if torch.cuda.is_available(): device = 'cuda' else: device = 'cpu' self.x_data = torch.as_tensor(x, device=device, dtype=torch.float) self.y_data = torch.as_tensor(y, device=device, dtype=torch.long) def __getitem__(self, index): return self.x_data[index], self.y_data[index] def __len__(self): return self.len def fit(self, candidate_count): feature_matrix = np.empty(shape=(candidate_count, 600)) target_matrix = np.empty(shape=(candidate_count, 1)) fill_matrices(feature_matrix, target_matrix) candidate_ds = CandidateDataset(feature_matrix, target_matrix) train_loader = DataLoader(dataset = candidate_ds, batch_size = self.BATCH_SIZE, shuffle = True) for epoch in range(self.N_EPOCHS): print('starting epoch ' + str(epoch)) for batch_idx, (inputs, labels) in enumerate(train_loader): print('starting batch ' + str(batch_idx) + ' epoch ' + str(epoch)) inputs, labels = Variable(inputs), Variable(labels) self.optimizer.zero_grad() inputs = inputs.view(1, inputs.size()[0], 600) # init hidden with number of rows in input y_pred = self.model(inputs, self.model.initHidden(inputs.size()[1])) labels.squeeze_() # labels should be tensor with batch_size rows. Column the index of the class (0 or 1) loss = self.loss_f(y_pred, labels) loss.backward() self.optimizer.step() print('done batch ' + str(batch_idx) + ' epoch ' + str(epoch)) 

Puedes usar torch.utils.data

Suponiendo que haya cargado los datos del directorio, en los arrays de tren y de prueba numpy, puede heredar de la clase torch.utils.data.Dataset para crear su objeto de conjunto de datos

 class MyDataset(Dataset): def __init__(self, x, y): super(MyDataset, self).__init__() assert x.shape[0] == y.shape[0] # assuming shape[0] = dataset size self.x = x self.y = y def __len__(self): return self.y.shape[0] def __getitem__(self, index): return self.x[index], self.y[index] 

Luego, crea tu objeto de conjunto de datos

 traindata = MyDataset(train_x, train_y) 

Finalmente, usa DataLoader para crear tus mini lotes

 trainloader = torch.utils.data.DataLoader(traindata, batch_size=64, shuffle=True)