AttributeError: Visualization Code on C1W1 Programming Assignment

The code crashes at the show_tensor_images line.
If I comment it out, it does not crash, but provides the wrong numbers for the losses.

Any idea how to debug this?

Thank you for your assistance and for this fantastic course!
### Visualization code ###
if cur_step % display_step == 0 and cur_step > 0:
print(f"Epoch {epoch}, step {cur_step}: Generator loss: {mean_generator_loss}, discriminator loss: {mean_discriminator_loss}")
fake_noise = get_noise(cur_batch_size, z_dim, device=‘cuda’)
fake = gen(fake_noise)
show_tensor_images(fake) ****
show_tensor_images(real)
mean_generator_loss = 0
mean_discriminator_loss = 0
cur_step += 1

—> 12 image_unflat = image_tensor.detach().gpu().view(-1, *size)

AttributeError: ‘Tensor’ object has no attribute ‘gpu’

That probably means you have hard-coded the device to “cuda” someplace in the logic for the fake images. Check that whole path: you should only use the device parameter that is passed in.

Actually you can see the bug right in the code that you show. The call to get_noise hard-codes the device to ‘cuda’. The point is that the code we write in the functions needs to be flexible enough to work on any device. To accomplish that, just use the device parameter that was passed in to the function.

Hey, thanks Paul.

I changed the device from ‘cuda’ to ‘cpu’. The final section now gives the following output:

HBox(children=(FloatProgress(value=0.0, max=469.0), HTML(value=’’)))

I’m still puzzled.

What else would be helpful for me to share?

Thank you again!

UNQ_C8 (UNIQUE CELL IDENTIFIER, DO NOT EDIT)

GRADED FUNCTION:

cur_step = 0
mean_generator_loss = 0
mean_discriminator_loss = 0
test_generator = True # Whether the generator should be tested
gen_loss = False
error = False
for epoch in range(n_epochs):

# Dataloader returns the batches
for real, _ in tqdm(dataloader):
    cur_batch_size = len(real)

    # Flatten the batch of real images from the dataset
    real = real.view(cur_batch_size, -1).to(device)

    ### Update discriminator ###
    # Zero out the gradients before backpropagation
    disc_opt.zero_grad()

    # Calculate discriminator loss
    disc_loss = get_disc_loss(gen, disc, criterion, real, cur_batch_size, z_dim, device)

    # Update gradients
    disc_loss.backward(retain_graph=True)

    # Update optimizer
    disc_opt.step()

    # For testing purposes, to keep track of the generator weights
    if test_generator:
        old_generator_weights = gen.gen[0][0].weight.detach().clone()

    ### Update generator ###
    #     Hint: This code will look a lot like the discriminator updates!
    #     These are the steps you will need to complete:
    #       1) Zero out the gradients.
    #       2) Calculate the generator loss, assigning it to gen_loss.
    #       3) Backprop through the generator: update the gradients and optimizer.
    #### START CODE HERE ####
    gen_opt.zero_grad()
    gen_loss = get_gen_loss(gen, disc, criterion, cur_batch_size, z_dim, device)
    gen_loss.backward()
    gen_opt.step()
    #### END CODE HERE ####

    # For testing purposes, to check that your code changes the generator weights
    if test_generator:
        try:
            assert lr > 0.0000002 or (gen.gen[0][0].weight.grad.abs().max() < 0.0005 and epoch == 0)
            assert torch.any(gen.gen[0][0].weight.detach().clone() != old_generator_weights)
        except:
            error = True
            print("Runtime tests have failed")

    # Keep track of the average discriminator loss
    mean_discriminator_loss += disc_loss.item() / display_step

    # Keep track of the average generator loss
    mean_generator_loss += gen_loss.item() / display_step

    ### Visualization code ###
    if cur_step % display_step == 0 and cur_step > 0:
        print(f"Epoch {epoch}, step {cur_step}: Generator loss: {mean_generator_loss}, discriminator loss: {mean_discriminator_loss}")
        fake_noise = get_noise(cur_batch_size, z_dim, device)
        fake = gen(fake_noise)
        show_tensor_images(fake)
        show_tensor_images(real)
        mean_generator_loss = 0
        mean_discriminator_loss = 0
    cur_step += 1

show_tensor_images is a helper function that you should not change. The implementation should look as follows:

def show_tensor_images(image_tensor, num_images=25, size=(1, 28, 28)):
    '''
    Function for visualizing images: Given a tensor of images, number of images, and
    size per image, plots and prints the images in a uniform grid.
    '''
    image_unflat = image_tensor.detach().cpu().view(-1, *size)
    image_grid = make_grid(image_unflat[:num_images], nrow=5)
    plt.imshow(image_grid.permute(1, 2, 0).squeeze())
    plt.show()

As you can see, the tensor will be copied to the host image_tensor.detach().cpu().view(-1, *size).

The line fake_noise = get_noise(cur_batch_size, z_dim, device=‘cuda’) was correct (but you should better use the device variable for this purpose), since you want to generate fake images from the noise on a GPU.

You missed the point: the solution is not to change the hard-coded setting to ‘cpu’. The point is not to hard-code it at all: you are given device as an argument to the function. Just use that and then your code is “general” and works in either case.