I assume an epoch of 50,000 samples takes a long time to run. Choosing to calculate and print the average loss for groups 2000 batches is simply a way to get feedback on the training progress more often than once per epoch. Resetting running_loss to zero every now and then has no effect on the training.
for i, data in enumerate(trainloader, 0): restarts the trainloader iterator on each epoch. That is how python iterators work. Let’s take a simpler example for data in trainloader: python starts by calling trainloader.__iter__() to set up the iterator, this returns an object with a .next() method. Then python calls the .next() method of this object in order to get the first and subsequent values used by the for loop. enumerate just wraps the trainloader iterator in order to make it return a counter along with the value at each iteration.
If the batch number doesn’t divide the data set size, then I guess the last batch will simply be smaller than the others.