It looks like the fundamental mistake I was making was failing to unsqueeze() the mini-batch I was training the network with. In this example, each mini-batch only had 1 example in it; adding a fake batch dimension (per http://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html 10) seems to do the trick!
    # Train network using target and predicted Q values
    final_input_tensor = torch.from_numpy(np.identity(16)[s:s+1]).type(torch.FloatTensor)
    final_input = Variable(final_input_tensor)
    final_input = final_input.unsqueeze(0)
    target_tensor = torch.from_numpy(targetQ).type(torch.FloatTensor)        
    target = Variable(target_tensor)     
  
    # Backprop
    optimizer.zero_grad()
    output = dqn(final_input)
    criterion = nn.MSELoss()
    loss = criterion(output, target)
    loss.backward()
    optimizer.step()