<jupyter_start><jupyter_text>![Pytorch](https://miro.medium.com/max/1024/1*KKADWARPMxHb-WMxCgW_xA.png) PytorchAn open source machine learning framework that accelerates the path from research prototyping to production deployment.It is a deep learning framework and a scientific computing package.It has several components that you'd like to be aware of (from future perspective):* PyTorch is production ready. With **torchScript**, PyTorch provides ease-of-use and flexiblility in _eager_ mode, while seamlessly  transitioning to graph mode for speed, optimization, and functionality in C++ runtime environments.* **TorchServe** is an easy to use tool for deploying PyTorch models at scale. It is cloud and environment agnostic and supports features such as multi-model serving, logging, metrics and the creation of RESTful endpoints for application integration.* Optimized performance in both research and production by taking advantage of native support for asynchronous execution of collective operations and peer-to-peer communication that is accessible from Python and C++.* PyTorch supports an end-to-end workflow from Python to deployment on iOS and Android. It extends the PyTorch API to cover common preprocessing and integration tasks needed for incorporating ML in mobile applications.* An active community of researchers and developers have built a rich ecosystem of tools and libraries for extending PyTorch and supporting development in areas from computer vision to reinforcement learning.* Export models in the standard **ONNX** (Open Neural Network Exchange) format for direct access to ONNX-compatible platforms, runtimes, visualizers, and more.* The C++ frontend is a pure C++ interface to PyTorch that follows the design and architecture of the established Python frontend. It is intended to enable research in high performance, low latency and bare metal C++ applications.* PyTorch is well supported on major cloud platforms, providing frictionless development and easy scaling through prebuilt images, large scale training on GPUs, ability to run models in a production scale environment, and more.| **PyTorch**  | **TensorFlow**  | |---|---|| Dynamic Computation Graphs  | Had to integrate Keras to add Dynamic Graphs, else static graphs  || Have explicit GPU and CPU controls  | 2.0 has made this slighyl easy compared to earlier  || More Pythonic in nature  | Steep learning curve  || Good Documentation | Best possible documentation || External tool for visualization and logging | TensorBoard || Lighter to work with | Large deployment dependencies || _Debugging is difficult_ | _Better debugging capabilities_ |Lets check these sites- [1st](https://trends.google.com/trends/explore?date=today%205-y&q=%2Fg%2F11bwp1s2k3,%2Fg%2F11gd3905v1&hl=en-US)- [2nd](http://horace.io/pytorch-vs-tensorflow/)  Scientific ComputingThe scientific computing aspect of PyTorch is primarily a result of PyTorch's tensor Library and associated tensor operationsA tensor is an n-dimensional array. ![tensor](https://res.cloudinary.com/practicaldev/image/fetch/s--VaxrSdrA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bp6ux6ppf5t5amwkxklq.jpg) ![packages](https://lh5.googleusercontent.com/CQQ-OeIV6tQs-iVeHW7wX4CTyfz4m8N1SkZuaWDPWTKoSVtlhwOZaVOF3E_mrK1GVGNx55GJ34uVYbXtIiNuixcjt_kRdHizBZN6RtZj_84VCYapXcbgESAG-SLkEh4kuXbkSIzowrOLSZ1vLQ)![pack](https://manalelaidouni.github.io/assets/img/pexels/Pytorch-package-hierarchy.jpg) PyTorch Philosophy* Stay out of the way* Cater to the impatient* Promote linear code-flow* Full interop with the Python ecosystem* Be as fasst as anything else Debugging* PyTorch is Python Extension* You can use any Python Debugger* Even _print_ function works on PyTorch objects Why it got so popular?To solve deep neural networks, we need to calculate derivates, and to do this computationally, deep learning frameworks use what are called **computational graphs**. ![cgs](https://blog.paperspace.com/content/images/2019/03/full_graph.png)PyTorch introduced **Dynamic Computational Graphs**, a first in the industry. It means that the graph is generated on the fly as the operations occur in contrast to _static graphs_ that are fully determined before actual action occur. Experimentation, advanced models and quick tests need Dynamic Computation Graphs as a back-end feature.  PyTorch's Computational Graph![quick](https://www.miracleyoo.com/2019/12/11/Pytorch-Core-Code-Research/image-20191128215740348.png)  TensorsWe are interested in our data, but it is not stored at a location that is easy to work with (think of coordinate system that starts at 356, -1900, 300).![locatio](https://www.miracleyoo.com/2019/12/11/Pytorch-Core-Code-Research/image-20191128192741907.png)![tensors](https://www.miracleyoo.com/2019/12/11/Pytorch-Core-Code-Research/image-20191203093005864.png)  Let's do some coding now.  Pytorch's tensors are similar to Numpy's ndarrays<jupyter_code>import torch
import numpy as np
v1 = np.array(1.3)
v2 = np.array([1.,2.])
v3 = np.array([[1.,2.], 
               [2.,5.]])
print(f"{v1}, {v2}, {v3}")
v1 = torch.tensor(1.3)
v2 = torch.tensor([1.,2.])
v3 = torch.tensor([[1.,2.], [2.,5.]])
print(f"{v1}, {v2}, {v3}")<jupyter_output>1.2999999523162842, tensor([1., 2.]), tensor([[1., 2.],
        [2., 5.]])<jupyter_text>Creating a Tensor from a Numpy<jupyter_code>numpy_array = np.array([1,2,3])
numpy_array
t1 = torch.Tensor(numpy_array)
# Constructor - same as torch.FloatTensor
# uses a default float32 tensor, can be changed
# all other tensor inherits from this main tensor class

t2 = torch.tensor(numpy_array)
# Factory Function
# produces a new tensor with same dtype
# does not share underlying memory with numpy
# always copies the data  
# recommended to use

t3 = torch.as_tensor(numpy_array)
# Factory Function
# produces a new tensor with same dtype
# share underlying memory with numpy, changing one will change other
# can accept only any data type
# HAVE TO MANUALLY ADD requires_grad_() function, or does not support requires_grad flag. 
# recommended to use

t4 = torch.from_numpy(numpy_array)
# Factory Function
# produces a new tensor with same dtype
# share underlying memory with numpy, changing one will change other
# can accept only numpy arrays
numpy_array.dtype, t1.dtype, t2.dtype, t3.dtype, t4.dtype
t1, t2, t3, t4<jupyter_output><empty_output><jupyter_text>Sharing memory for performance: copy vs share| Share Data |Copy Data ||----|----||torch.as_tensor() | torch.tensor() ||torch.from_numpy() | torch.Tensor() |     > **Zero Memory Copy** ⇋ Very Efficient Something to keep in mind* Since numpy.array objects are allocated on CPU, the `as_tensor()` function must copy the data from the CPU to the GPU when a GPU is being used* The memory sharing of `as_tensor()` doesn't work with built-in Python data structure like list* The `as_tensor()` call requires developer knowledge of the sharing feature. This is necessary so we don't make an unwanted change in the underlying data without realising it* The `as_tensor()` performance improvement will be greater when there are a lot of back and forth operations between `numpy.array` objects and tensor objects.<jupyter_code>numpy_array *= 4
numpy_array
t1, t2, t3, t4
t1 *= 2
t1, numpy_array
t2 *= 2
t2, numpy_array
t3 *= 2
t3, numpy_array
t4
t4 *= 8
t3, t4, numpy_array
t4 *= 2
t1, t2, t3, t4, numpy_array
t5 = t4.cuda()

t5.device, t4.device 

t4 is on GPU & go and add t2 + t4 =?
t6 = t4 + t3
t6
t7 = t5 + t4
t7
t7 = t5 + t4.cuda()
t7
t4.device
torch.eye(2)
torch.zeros(2,4)
torch.zeros(2,2)
torch.ones(2,2)
torch.ones(2,2).shape
torch.rand(2, 2)
torch.rand(2, 2)
torch.manual_seed(0)
torch.rand(2, 2)
torch.rand(2, 2)
data = torch.tensor([[1, 2, 3], [4, 5, 6]])

data[0]
data[1, 1]
data[1, :2]
t = torch.tensor(data=[1, 2, 3], dtype=torch.float32, device='cpu', requires_grad = False)
t
t = torch.as_tensor(data=[1, 2, 3], dtype=torch.float32, device='cpu', requires_grad = False)
t
t = torch.as_tensor(data=[1, 2, 3], dtype=torch.float32, device='cpu')
t
t.requires_grad_()
import torch
torch.manual_seed(2)
torch.rand(2,)<jupyter_output><empty_output><jupyter_text>Autograd* Automatic differentiation for all operations on Tensors* The backward graph is automatically defined by the forward graph!<jupyter_code>x1 = torch.tensor(2, requires_grad=True, dtype=torch.float16)
x2 = torch.tensor(3, requires_grad=True, dtype=torch.float16)
x3 = torch.tensor(1, requires_grad=True, dtype=torch.float16)
x4 = torch.tensor(4, requires_grad=True, dtype=torch.float16)
z1 = x1*x2
z2 = x3*x4

f = z1 + z2
# f = 2*3 + 1*4
df_dx = torch.autograd.grad(outputs=f, inputs=[x1, x2, x3, x4])

print(f'gradient of x1 = {df_dx[0]}')
print(f'gradient of x2 = {df_dx[1]}')
print(f'gradient of x3 = {df_dx[2]}')
print(f'gradient of x4 = {df_dx[3]}')
x1 = torch.tensor(2, requires_grad=True, dtype=torch.float16)
x2 = torch.tensor(3, requires_grad=True, dtype=torch.float16)
x3 = torch.tensor(1, requires_grad=False, dtype=torch.float16)
x4 = torch.tensor(4, requires_grad=False, dtype=torch.float16)

z1 = x1*x2
z2 = x3*x4

f = z1 + z2
# f = 2*3 + 1*4
df_dx = torch.autograd.grad(outputs=f, inputs=[x1, x2])

print(f'gradient of x1 = {df_dx[0]}')
print(f'gradient of x2 = {df_dx[1]}')
# print(f'gradient of x3 = {df_dx[2]}')
# print(f'gradient of x4 = {df_dx[3]}')
x1 = torch.tensor(2, requires_grad=True, dtype=torch.float16)
x2 = torch.tensor(3, requires_grad=True, dtype=torch.float16)
x3 = torch.tensor(1, requires_grad=True, dtype=torch.float16)
x4 = torch.tensor(4, requires_grad=True, dtype=torch.float16)

z1 = x1 * x2
z2 = x3 * x4

f = z1 + z2

f.backward()<jupyter_output><empty_output><jupyter_text>Tensor Operation Types1. Reshaping Operations2. Element-wise Operations3. Reduction Operations4. Access Operations<jupyter_code>t = torch.tensor([
    [0,0,0,0],
    [1,1,1,1],
    [2,2,2,2],
    [3,3,3,3],
], dtype=torch.float16)
# use any
t.shape, t.size()
# rank of tensor 
len(t.shape)
len(t)
type(t.shape)
torch.tensor(t.shape).prod() # total number of elements 
t.numel()<jupyter_output><empty_output><jupyter_text>Why knowing these is important? When we reshape a tensor, the reshaping operation must account for all of numel (or total number of elements). We if we have 12 elements, we can only build 1x12, 12x1, 6x2, 2x6, 3x4, 4x3 reshaped tensors<jupyter_code>t.reshape(1,16)
t.reshape(2,8)
t.reshape(8,2)<jupyter_output><empty_output><jupyter_text>**Can we change the rank of a tensor?**> Indented blockOf course, but we need to take care of the original numel<jupyter_code>t.reshape(1,2,8)
len(t.reshape(1,2,8).shape)
t.reshape(2,2,4)
t.reshape(2,2,-1)
t.reshape(3,-1)
t.view(2,-1)<jupyter_output><empty_output><jupyter_text>**Squeezing a Tensor**Removes all the dimensions that have a length of 1**Unsqueezing a Tensor**Adds a dimension that has a length of 1.<jupyter_code>print(t.reshape(1, 16))
print(t.reshape(1, 16).shape)
print(t.reshape(1, 16).squeeze())
print(t.reshape(1, 16).squeeze().shape)
print(t.reshape(1, 16).unsqueeze(dim=0))
print(t.reshape(1, 16).unsqueeze(dim=0).shape)
print(t.reshape(1, 2, 8).squeeze())
print(t.reshape(1, 2, 8).squeeze().shape)<jupyter_output>tensor([[0., 0., 0., 0., 1., 1., 1., 1.],
        [2., 2., 2., 2., 3., 3., 3., 3.]], dtype=torch.float16)
torch.Size([2, 8])<jupyter_text>**Use cases?**Neural networks are always trained in a batch of samples. This is troubling because when we want to test 1 image, we do not have an array, we only have 1 image. Well we unsqueeze it to fake a batch.We use this function that is very common, called Flatten. This essentially create a new tensor that is only 1D. This is done to connect our data to next Fully Connected Layers. We use squeeze function for this. Let's implement such a function.<jupyter_code>t
def flatten(t):
  t = t.reshape(1, -1)
  t = t.squeeze()
  return t
flatten(t)
flatten(t).shape
t.flatten()
t1 = torch.tensor([
                   [1,1],
                   [2,2]
])

t2 = torch.tensor(
    [
     [3,3],
     [4,4]
    ]
)
t1,t2
torch.cat([t1,t2], dim=0)
torch.cat([t1,t2], dim=1)
img1 = torch.tensor([
  [1, 1, 1, 1],
  [1, 1, 1, 1],
  [1, 1, 1, 1],
  [1, 1, 1, 1]
])

img2 = torch.tensor([
  [2, 2, 2, 2],
  [2, 2, 2, 2],
  [2, 2, 2, 2],
  [2, 2, 2, 2]
])

img3 = torch.tensor([
  [3, 3, 3, 3],
  [3, 3, 3, 3],
  [3, 3, 3, 3],
  [3, 3, 3, 3]
])
batch = torch.stack([img1, img2, img3])
batch
batch.size()
batch = batch.reshape(3, 1, 4, 4) # (batch_size, num_ch, row, col)
batch
batch[0]
batch[1][0][0][1]
batch[0][0]
batch[0][0][0]
batch[0][0][0][3]
batch.flatten()
batch.reshape(1,-1)[0]
batch.reshape(-1)
batch.reshape(batch.numel())
batch.reshape(3,16) # we are mainting batch and flattening images only
batch.reshape(3,1,-1)
batch.reshape(1,3,16)
batch.shape
batch.flatten(start_dim=2)
batch.flatten(start_dim=2).shape<jupyter_output><empty_output><jupyter_text>Mathmetical opp<jupyter_code>t1 = torch.tensor([
    [1, 2],
    [3, 4]
], dtype=torch.float32)

t2 = torch.tensor([
    [5, 6],
    [7, 8]
], dtype=torch.float32)
t1+t2
t1-t2
t1+2
t2-2
print(t1.add(2))
print(t1.sub(2))
print(t1.mul(2))
print(t1.div(2))
t1.add(t2)
t1.mul(t2)
t = torch.tensor([
    [0, 5, 7],
    [6, 0, 7],
    [0, 8, 0]
], dtype=torch.float32)
t.eq(0)
t.eq(1)
t.ge(0)
t.le(10)
t.abs()
t.sqrt()
t.neg()<jupyter_output><empty_output><jupyter_text>Rduction Op<jupyter_code>t.sum()
t.prod()
t.std()
t.mean()
t = torch.tensor([
  [1, 1, 1, 1],
  [2, 2, 2, 2],
  [3, 3, 3, 3],
  [4, 4, 4, 4]
])
t.sum(dim=0)
t.sum(dim=1)
t.argmax(), t.max()
t.argmin(), t.min()<jupyter_output><empty_output><jupyter_text>PyTorch Main Working with the datasetWe will look at convolutional/image examples as they allow us to get a good intuition on axises and are more complex to work on just 1D data A common nn pipeline looks like this:1. Prepare the data2. Build the model3. Train the model4. Analyze the model<jupyter_code>import torch
import torchvision # provide access to datasets, models, transforms, utils, etc
import torchvision.transforms as transforms
from torch.utils.data import Dataset
import pandas as pd

class Animals(Dataset):
  def __init__(self, csv_file):
    self.data = pd.read_csv(csv_file)
  
  def __getitem__(self, index):
    image, label = self.data.iloc[index]

    return image, label
  
  def __len__(self):
    return len(self.data)
train_set = torchvision.datasets.FashionMNIST(
    root = "./data",
    train=True,
    download=True,
    transform = transforms.Compose(
        [
         transforms.ToTensor()
        ]
    )
)
test_set = torchvision.datasets.FashionMNIST(
    root = "./data",
    train=False,
    download=False,
    transform = transforms.Compose(
        [
         transforms.ToTensor()
        ]
    )
)
test_set
train_set
train_set[0]
len(train_set[0])
train_set[0][1]
train_set[2][1]
train_set[0][0].shape
next(iter(train_set))[1]
train_loader = torch.utils.data.DataLoader(
    train_set, batch_size=10, shuffle=True
)
test_loader = torch.utils.data.DataLoader(
    test_set, batch_size=10, shuffle=True
)
train_loader
next(iter(train_loader))[1]
import numpy as np
import matplotlib.pyplot as plt

torch.set_printoptions(linewidth=120)
len(train_set)
train_set.train_labels
len(train_set.train_labels)
train_set.train_labels.unique()
train_set.train_labels.bincount() #frequency of each label, we have balanced class here

for e in train_set:
  print(e)
  break
sample, label = next(iter(train_set))
sample
plt.imshow(sample.squeeze(), cmap='gray');
batch = next(iter(train_loader))

len(batch), type(batch)
images, labels = batch 
images.shape, labels.shape
grid = torchvision.utils.make_grid(images, nrows=5)
plt.figure(figsize=(8,8))
plt.imshow(np.transpose(grid, (1,2,0)));
print('labels:', labels)<jupyter_output>labels: tensor([9, 8, 9, 0, 8, 4, 8, 4, 4, 1])<jupyter_text>Let's build a networkBelow is the basic structure of a Model<jupyter_code>import torch.nn as nn

class Network(nn.Module):
  def __init__(self):
    super().__init__()
    self.layer = None
  
  def forward(self, t):
    t = self.layer(t)
    return t
class Network(nn.Module):
  def __init__(self):
    super().__init__()

    self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
    self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
    self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)
    self.fc2 = nn.Linear(in_features=120, out_features=60)
    self.out = nn.Linear(in_features=60, out_features=10)
  
  def forward(self, x):
    return t

network = Network()

print(network)
network.conv1
network.conv1.weight
network.fc2
network.fc2.weight.shape
network.parameters
for name, param in network.named_parameters():
  print(name, "\t\t",param.shape)
import torch.nn.functional as F

class Network(nn.Module):
    def __init__(self):
      super().__init__()
      self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
      self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)

      self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)
      self.fc2 = nn.Linear(in_features=120, out_features=60)
      self.out = nn.Linear(in_features=60, out_features=10)

    def forward(self, t):
      t = t  # input layer
      # 2 hidden layer conv 
      t = self.conv1(t)
      t = F.relu(t)
      t = F.max_pool2d(t, kernel_size=2, stride=2)

      # 3 hideen 
      t = self.conv2(t)
      t = F.relu(t)
      t = F.max_pool2d(t, kernel_size=2, stride=2)

      # 4 hidden layers
      t = t.reshape(-1, 12*4*4)
      
      t = self.fc1(t)
      t = F.relu(t)

      # (5) hidden linear layer
      t = self.fc2(t)
      t = F.relu(t)

      # (6) output layer
      t = self.out(t)
      #t = F.softmax(t, dim=1)
    
      return t
torch.set_grad_enabled(False)
image, label = next(iter(train_set))
image.shape
network = Network()
network(image)
pred = network(image.unsqueeze(0))
pred
pred.shape
label, pred.argmax(dim=1)
label, pred.argmax(dim=1).item()
F.softmax(pred, dim=1)
F.softmax(pred, dim=1).sum()
F.softmax(pred, dim=1).argmax(dim=1)
len(train_set[0])
train_set[0][1]
data_loader = torch.utils.data.DataLoader(
  train_set, batch_size=10, shuffle=True
)
batch = next(iter(data_loader))
len(batch)
type(batch)
batch[1]
images, labels = batch
images.shape, labels.shape
preds = network(images)
preds.shape
preds
labels
preds.argmax(dim=1)
preds.argmax(dim=1).eq(labels)
preds.argmax(dim=1).eq(labels).sum()
f"{preds.argmax(dim=1).eq(labels).sum() / 10*100}%"
def get_num_correct(preds, labels):
  return preds.argmax(dim=1).eq(labels).sum().item()
get_num_correct(preds, labels)<jupyter_output><empty_output><jupyter_text>TrainingDuring the entire training process, we do as many epochs as necessary to reach our desired level of accuracy. With this, we have the following steps:Get batch from the training set.Pass batch to network.Calculate the loss (difference between the predicted values and the true values).Calculate the gradient of the loss function w.r.t the network's weights.Update the weights using the gradients to reduce the loss.Repeat steps 1-5 until one epoch is completed.Repeat steps 1-6 for as many epochs required to reach the minimum loss.<jupyter_code>import torch.optim as optim

torch.set_grad_enabled(True) # remember we turned off the gradients?
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss
print(network.conv1.weight.grad)
loss.backward()
print(network.conv1.weight.grad)
print(network.conv1.weight.grad.shape)
optimizer = optim.Adam(network.parameters(), lr=0.01)

optimizer.step()
loss
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss
loss.item(), get_num_correct(preds, labels)
loss.backward()
optimizer.step()
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss.item(), get_num_correct(preds, labels)
loss.backward()
optimizer.step()
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss.item(), get_num_correct(preds, labels)
loss.backward()
optimizer.step()
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss.item(), get_num_correct(preds, labels)
loss.backward()
optimizer.step()
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss.item(), get_num_correct(preds, labels)
loss.backward()
optimizer.step()
preds = network(images)
loss = F.cross_entropy(preds, labels)
loss.item(), get_num_correct(preds, labels)
network = Network()

train_loader = torch.utils.data.DataLoader(train_set, batch_size=100)
optimizer = optim.Adam(network.parameters(), lr=0.01)

batch = next(iter(train_loader)) # Get Batch
images, labels = batch

preds = network(images) # Pass Batch
loss = F.cross_entropy(preds, labels) # Calculate Loss

loss.backward() # Calculate Gradients
optimizer.step() # Update Weights

print('loss1:', loss.item())
preds = network(images)
loss = F.cross_entropy(preds, labels)
print('loss2:', loss.item())
network = Network()

train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True)
optimizer = optim.Adam(network.parameters(), lr=0.01)

total_loss = 0
total_correct = 0

for batch in train_loader: # Get Batch
    images, labels = batch 

    preds = network(images) # Pass Batch
    loss = F.cross_entropy(preds, labels) # Calculate Loss

    optimizer.zero_grad()
    loss.backward() # Calculate Gradients
    optimizer.step() # Update Weights

    total_loss += loss.item()
    total_correct += get_num_correct(preds, labels)

print(
    "epoch:", 0, 
    "total_correct:", total_correct, 
    "loss:", total_loss
)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True)
optimizer = optim.Adam(network.parameters(), lr=0.01)

for epoch in range(10):

    total_loss = 0
    total_correct = 0

    for batch in train_loader: # Get Batch
        images, labels = batch 

        preds = network(images) # Pass Batch
        loss1 = F.cross_entropy(preds, labels) # Calculate Loss
        
        optimizer.zero_grad()
        loss.backward() # Calculate Gradients
        optimizer.step() # Update Weights

        total_loss += loss.item()
        total_correct += get_num_correct(preds, labels)

    print(
        "epoch", epoch, 
        "total_correct:", total_correct, 
        "loss:", total_loss
    )<jupyter_output>epoch 0 total_correct: 51072 loss: 190.69160869717598
epoch 1 total_correct: 51883 loss: 171.3865034878254
epoch 2 total_correct: 52412 loss: 160.21362775564194
epoch 3 total_correct: 52597 loss: 156.57048498094082
epoch 4 total_correct: 52770 loss: 151.87339685857296
epoch 5 total_correct: 52974 loss: 149.15490843355656
epoch 6 total_correct: 52985 loss: 148.6912507712841
epoch 7 total_correct: 53027 loss: 147.28827506303787
epoch 8 total_correct: 53079 loss: 146.6870104894042
epoch 9 total_correct: 53301 loss: 141.6432292163372<jupyter_text>Please cover this whole [PyTorch Series](https://www.youtube.com/playlist?list=PLZbbT5o_s2xrfNyHZsM6ufI0iZENK9xgG) (also the reference for our code) for even better understanding.  epochs = number of times ypu are apassing your datasetbatch = number of batchesiteration = number_of_datapoints / batch__size = 60000/128 = 469  Assignemnt - Write a neural network that can:  * take 2 inputs:  * an image from MNIST dataset, and  * a random number between 0 and 9- and gives three outputs:  * the **number** that was represented by the MNIST image, and  * the **sum** & **difference** of this number (predicted) with the random number that was generated and sent as the input to the network  * you can mix fully connected layers and convolution layers  * you can use one-hot encoding to represent the random number input as well as the **summed** and **subtracted** output. - Your code MUST be:  * well documented (via readme file on github and comments in the code)  * must mention the data representation  * must mention your data generation strategy  * must mention how you have combined the two inputs  * must mention how you are evaluating your results  * must mention "what" results you finally got and how did you evaluate your results  * must mention what loss function you picked and why!- Once done, upload the code with shot training logs in the readme file from colab to GitHub, and share the GitHub link (public repository)<jupyter_code><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12303125257689454, 'fraction_numerical': 0.04638410159149006, 'mean_word_length': 4.71109018130445, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 59, 'https://': 11, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '15', 'dir': 'jupyter-structured-clean-dedup', 'id': '55563', 'license': 'MIT', 'n_tokens_mistral': 9112, 'n_tokens_neox': 8511, 'n_words': 2486, 'path': 'Prasant_Kumar/Assignments/PytorchBasics.ipynb', 'repo_name': 'ks1320/Traffic-Surveillance-System', 'size': '24254'}