Chainer - Core Components



Chainer is a versatile deep learning framework designed to facilitate the development and training of neural networks with ease. The core components of Chainer provide a robust foundation for building complex models and performing efficient computations.

In chainer the core component the Chain class is used for managing network layers and parameters such as Links and Functions for defining and applying model operations and the Variable class for handling data and gradients.

Additionally the Chainer incorporates powerful Optimizers for updating model parameters, utilities for managing xDataset and DataLoader and a dynamic Computational Graph that supports flexible model architectures. Together all these components enable streamlined model creation, training and optimization by making Chainer a comprehensive tool for deep learning tasks.

Here are the different core components of the Chainer Framework −

Variables

In Chainer the Variable class is a fundamental building block that represents data and its associated gradients during the training of neural networks. A Variable encapsulates not only the data such as inputs, outputs or intermediate computations but also the information required for automatic differentiation which is crucial for backpropagation.

Key Features of Variable

Below are the key features of the variables in the Chainer Framework −

  • Data Storage: A Variable holds the data in the form of a multi-dimensional array which is typically a NumPy or CuPy array, depending on whether computations are performed on the CPU or GPU. The data stored in a Variable can be input data, output predictions or any intermediate values computed during the forward pass of the network.
  • Gradient Storage: During backpropagation the Chainer computes the gradients of the loss function with respect to each Variable. These gradients are stored within the Variable itself. The grad attribute of a Variable contains the gradient data which is used to update the model parameters during training.
  • Automatic Differentiation: Chainer automatically constructs a computational graph as operations are applied to Variable objects. This graph tracks the sequence of operations and dependencies between variables by enabling efficient calculation of gradients during the backward pass. The backward method can be called on a Variable to trigger the computation of gradients throughout the network.
  • Device Flexibility: Variable supports both CPU by using NumPy and GPU by using CuPy arrays by making it easy to move computations between devices. Operations on Variable automatically adapt to the device where the data resides.

Example

Following example shows how to use Chainer's Variable class to perform basic operations and calculate gradients via backward propagation −

import chainer
import numpy as np

# Create a Variable with data
x = chainer.Variable(np.array([1.0, 2.0, 3.0], dtype=np.float32))

# Perform operations on Variable
y = x ** 2 + 2 * x + 1

# Print the result
print("Result:", y.data)  # Output: [4. 9. 16.]

# Assume y is a loss and perform backward propagation
y.grad = np.ones_like(y.data)  # Set gradient of y to 1 for backward pass
y.backward()  # Compute gradients

# Print the gradient of x
print("Gradient of x:", x.grad)  # Output: [4. 6. 8.]

Here is the output of the chainer's variable class −

Result: [ 4.  9. 16.]
Gradient of x: [4. 6. 8.]

Functions

In Chainer Functions are operations that are applied to data within a neural network. These functions are essential building blocks that perform mathematical operations, activation functions, loss computations and other transformations on the data as it flows through the network.

Chainer provides a wide range of predefined functions in the chainer.functions module by enabling users to easily build and customize neural networks.

Key functions in Chainer

Activation Functions: These functions in neural networks introduce non-linearity to the model by enabling it to learn complex patterns in the data. They are applied to the output of each layer to determine the final output of the network. Following are the activation functions in chainer −

  • ReLU (Rectified Linear Unit): The ReLU outputs are given as input directly if it's positive otherwise it outputs zero. It's widely used in neural networks because it helps mitigate the vanishing gradient problem and is computationally efficient by making it effective for training deep models. The formula for ReLU is given as −

    $$ReLU(x) = max(\theta, x)$$

    The function of ReLU in chainer.functions module is given as F.relu(x).

  • sigmoid: This function maps the input to a value between 0 and 1 by making it ideal for binary classification tasks. It provides a smooth gradient which helps in gradient-based optimization but can suffer from the vanishing gradient problem in deep networks. The formula for sigmoid is given as −

    $$Sigmoid(x)=\frac{1}{1+e^{-x}}$$

    The function for Sigmoid in chainer.functionsmodule is given as F.sigmoid(x)

  • Tanh (Hyperbolic Tangent): This function in Chainer is employed as an activation function in neural networks. It transforms the input to a value between -1 and 1 by resulting in a zero-centered output. This characteristic can be beneficial during training as it helps to address issues related to non-centered data which potentially improving the convergence of the model. The formula for Tanh is given as −

    $$Tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}$$

    We have the function F.tanh(x) in chainer.functions module for calculating the Tanh in chainer.

  • Leaky ReLU: This is also called as Leaky Rectified Linear Unit function in neural networks is a variant of the standard ReLU activation function. Unlike ReLU which outputs zero for negative input where as Leaky ReLU permits a small, non-zero gradient for negative inputs.

    This adjustment helps prevent the "dying ReLU" problem where neurons become inactive and cease to learn by ensuring that all neurons continue to contribute to the model's learning process. The formula for Leaky ReLU is given as −

    $$Leaky Relu(x) = max(\alpha x, x)$$

    Where, $\alpha$ is a small constant. The chainer.functions module has the function F.leaky_relu(x) to calculate Leaky ReLu in chainer.

  • Softmax: This is an activation function typically employed in the output layer of neural networks especially for multi-class classification tasks. It transforms a vector of raw prediction scores (logits) into a probability distribution where each probability is proportional to the exponential of the corresponding input value.

    The probabilities in the output vector sum to 1 by making Softmax ideal for representing the likelihood of each class in a classification problem. The formula for Softmax is given as −

    $$Softmax(x_{i})=\frac{e^{x_{i}}}{\sum_{j} e^{x_{j}}}$$

    The chainer.functions module has the function F.softmax(x) to calculate Softmax in chainer.

Example

Here's an example which shows how to use various activation functions in Chainer within a simple neural network −

import chainer
import chainer.links as L
import chainer.functions as F
import numpy as np

# Define a simple neural network using Chainer's Chain class
class SimpleNN(chainer.Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
        # Define layers: two linear layers
        self.l1 = L.Linear(4, 3)  # Input layer with 4 features, hidden layer with 3 units
        self.l2 = L.Linear(3, 2)  # Hidden layer with 3 units, output layer with 2 units
      
   def __call__(self, x):
      # Forward pass using different activation functions
      
      # Apply ReLU activation after the first layer
      h = F.relu(self.l1(x))
      
      # Apply Sigmoid activation after the second layer
      y = F.sigmoid(self.l2(h))
      
      return y
      
# Create a sample input data with 4 features
x = np.array([[0.5, -1.2, 3.3, 0.7]], dtype=np.float32)

# Convert input to Chainer's Variable
x_var = chainer.Variable(x)

# Instantiate the neural network
model = SimpleNN()

# Perform a forward pass
output = model(x_var)

# Print the output
print("Network output after applying ReLU and Sigmoid activations:", output.data)

Here is the output of the Activation functions used in simple neural networks −

Network output after applying ReLU and Sigmoid activations: [[0.20396319 0.7766712 ]]

Chain and ChainList

In Chainer the Chain and ChainList are fundamental classes that facilitate the organization and management of layers and parameters within a neural network. Both Chain and ChainList are derived from chainer.Link the base class responsible for defining model parameters. However they serve different purposes and are used in distinct scenarios. Let's see in detail about the chain and chainlist as follows −

Chain

The Chain class is designed to represent a neural network or a module within a network as a collection of links (layers). When using Chain we can define the network structure by explicitly specifying each layer as an instance variable. This approach is beneficial for networks with a fixed architecture.

We can use Chain when we have a well-defined, fixed network architecture where we want to directly access and organize each layer or component of the model.

Following are the key features of Chain Class −

  • Named Components: Layers or links added to a Chain are accessible by name by making it straightforward to reference specific parts of the network.
  • Static Architecture: The structure of a Chain is usually defined at initialization and doesn't change dynamically during training or inference.

Example

Following is the example which shows the usage of the Chain class in the Chainer Framework −

import chainer
import chainer.links as L
import chainer.functions as F

# Define a simple neural network using Chain
class SimpleChain(chainer.Chain):
   def __init__(self):
      super(SimpleChain, self).__init__()
      with self.init_scope():
        self.l1 = L.Linear(4, 3)  # Linear layer with 4 inputs and 3 outputs
        self.l2 = L.Linear(3, 2)  # Linear layer with 3 inputs and 2 outputs
      
   def forward(self, x):
      h = F.relu(self.l1(x))  # Apply ReLU after the first layer
      y = self.l2(h)        # No activation after the second layer
      return y
      
# Instantiate the model
model = SimpleChain()
print(model)

Below is the output of the above example −

SimpleChain(
  (l1): Linear(in_size=4, out_size=3, nobias=False),
  (l2): Linear(in_size=3, out_size=2, nobias=False),
)

ChainList

The ChainList class is similar to Chain but instead of defining each layer as an instance variable we can store them in a list-like structure. ChainList is useful when the number of layers or components may vary or when the architecture is dynamic.

We can use the ChainList when we have a model with a variable number of layers or when the network structure can change dynamically. It's also useful for architectures like recurrent networks where the same type of layer is used multiple times.

Following are the key features of ChainList −

  • Unordered Components: Layers or links added to a ChainList are accessed by their index rather than by name.
  • Flexible Architecture: It is more suitable for cases where the network's structure might change or where layers are handled in a loop or list.

Example

Here is the example which shows how to use the ChainList class in the Chainer Framework

import chainer
import chainer.links as L
import chainer.functions as F

# Define a neural network using ChainList
class SimpleChainList(chainer.ChainList):
   def __init__(self):
      super(SimpleChainList, self).__init__(
         L.Linear(4, 3),  # Linear layer with 4 inputs and 3 outputs
         L.Linear(3, 2)   # Linear layer with 3 inputs and 2 outputs
      )

   def forward(self, x):
      h = F.relu(self[0](x))  # Apply ReLU after the first layer
      y = self[1](h)        # No activation after the second layer
      return y

# Instantiate the model
model = SimpleChainList()
print(model)

Below is the output of using the ChainList class in Chainer Framework −

SimpleChainList(
  (0): Linear(in_size=4, out_size=3, nobias=False),
  (1): Linear(in_size=3, out_size=2, nobias=False),
)

Optimizers

In Chainer optimizers plays a crucial role in training neural networks by adjusting the model's parameters such as weights and biases which are used to minimize the loss function.

During training, after the gradients of the loss function with respect to the parameters are calculated through back-propagation the optimizers use these gradients to update the parameters in a way that gradually reduces the loss.

Chainer offers a variety of built-in optimizers in which each employing different strategies for parameter updates to suit different types of models and tasks. Following are the key optimizers in Chainer −

SGD (Stochastic Gradient Descent)

The most basic optimizer is SGD updates in which each parameter in the direction of its negative gradient and scaled by a learning rate. It's simple but can be slow to converge.

Often these can be used in simpler or smaller models or as a baseline to compare with more complex optimizers.

The function in chainer to calculate SGD is given as chainer.optimizers.SGD

Example

Here's a simple example of using Stochastic Gradient Descent (SGD) in Chainer to train a basic neural network. We'll use a small dataset which define a neural network model and then apply the SGD optimizer to update the model's parameters during training −

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
import numpy as np
from chainer import Variable
from chainer import optimizers

class SimpleNN(Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
       self.fc1 = L.Linear(None, 100)  # Fully connected layer with 100 units
       self.fc2 = L.Linear(100, 10)   # Output layer with 10 units (e.g., for 10 classes)

   def forward(self, x):
      h = F.relu(self.fc1(x))  # Apply ReLU activation function
      return self.fc2(h)     # Output layer

# Dummy data: 5 samples, each with 50 features
x_data = np.random.rand(5, 50).astype(np.float32)
# Dummy labels: 5 samples, each with 10 classes (one-hot encoded)
y_data = np.random.randint(0, 10, 5).astype(np.int32)

# Convert to Chainer variables
x = Variable(x_data)
y = Variable(y_data)

# Initialize the model
model = SimpleNN()

# Set up SGD optimizer with a learning rate of 0.01
optimizer = optimizers.SGD(lr=0.01)
optimizer.setup(model)
def loss_func(predictions, targets):
   return F.softmax_cross_entropy(predictions, targets)
# Training loop
for epoch in range(10):  # Number of epochs
   # Zero the gradients
   model.cleargrads()
   
   # Forward pass
   predictions = model(x)
   
   # Calculate loss
   loss = loss_func(predictions, y)
   
   # Backward pass
   loss.backward()
   
   # Update parameters
   optimizer.update()
   
   # Print loss
   print(f'Epoch {epoch + 1}, Loss: {loss.data}')

Following is the output of the SGD optimizer −

Epoch 1, Loss: 2.3100974559783936
Epoch 2, Loss: 2.233552932739258
Epoch 3, Loss: 2.1598660945892334
Epoch 4, Loss: 2.0888497829437256
Epoch 5, Loss: 2.020642042160034
Epoch 6, Loss: 1.9552147388458252
Epoch 7, Loss: 1.8926388025283813
Epoch 8, Loss: 1.8325523138046265
Epoch 9, Loss: 1.7749309539794922
Epoch 10, Loss: 1.7194255590438843

Momentum SGD

The Momentum SGDis an extension of SGD that includes momentum which helps to accelerate gradients vectors in the right directions by leading to faster converging. It accumulates a velocity vector in the direction of the gradient.

This is suitable for models where vanilla SGD struggles to converge. We have the function called chainer.optimizers.MomentumSGD to perform the Momentum SGD optimization.

Momentum Term: Adds a fraction of the previous gradient update to the current update. This helps to accelerate gradients vectors in the right directions and dampen oscillations.

Formula: The update rule for parameters with momentum is given as −

$$v_{t} = \beta v_{t-1} + (1 - \beta) \nabla L(\theta)$$ $$\theta = \theta-\alpha v_{t}$$

Where −

  • $v_{t}$ is the velocity (or accumulated gradient)
  • $\beta$ is the momentum coefficient (typically around 0.9)
  • $\alpha$ is the learning rate
  • $\nabla L(\theta)$ is the gradient of the loss function with respect to the parameters.

Example

Here's a basic example of how to use the Momentum SGD optimizer with a simple neural network in Chainer −

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
from chainer import optimizers
import numpy as np
from chainer import Variable

class SimpleNN(Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
       self.fc1 = L.Linear(None, 100)  # Fully connected layer with 100 units
       self.fc2 = L.Linear(100, 10)   # Output layer with 10 units (e.g., for 10 classes)

   def forward(self, x):
      h = F.relu(self.fc1(x))  # Apply ReLU activation function
      return self.fc2(h)     # Output layer


# Dummy data: 5 samples, each with 50 features
x_data = np.random.rand(5, 50).astype(np.float32)
# Dummy labels: 5 samples, each with 10 classes (one-hot encoded)
y_data = np.random.randint(0, 10, 5).astype(np.int32)

# Convert to Chainer variables
x = Variable(x_data)
y = Variable(y_data)


# Initialize the model
model = SimpleNN()

# Set up Momentum SGD optimizer with a learning rate of 0.01 and momentum of 0.9
optimizer = optimizers.MomentumSGD(lr=0.01, momentum=0.9)
optimizer.setup(model)
def loss_func(predictions, targets):
   return F.softmax_cross_entropy(predictions, targets)
# Training loop
for epoch in range(10):  # Number of epochs
   # Zero the gradients
   model.cleargrads()
   
   # Forward pass
   predictions = model(x)
   
   # Calculate loss
   loss = loss_func(predictions, y)
   
   # Backward pass
   loss.backward()
   
   # Update parameters
   optimizer.update()
   
   # Print loss
   print(f'Epoch {epoch + 1}, Loss: {loss.data}')

Following is the output of the Momentum SGD optimizer −

Epoch 1, Loss: 2.4459869861602783
Epoch 2, Loss: 2.4109833240509033
Epoch 3, Loss: 2.346194267272949
Epoch 4, Loss: 2.25825572013855
Epoch 5, Loss: 2.153470754623413
Epoch 6, Loss: 2.0379838943481445
Epoch 7, Loss: 1.9174035787582397
Epoch 8, Loss: 1.7961997985839844
Epoch 9, Loss: 1.677260398864746
Epoch 10, Loss: 1.5634090900421143

Adam

Adam optimizer combines the advantages of two other extensions of SGD namely AdaGrad, which works well with sparse gradients and RMSProp, which works well in non-stationary settings. Adam maintains a moving average of both the gradients and their squares and updates the parameters based on these averages.

This is often used as the default optimizer due to its robustness and efficiency across a wide range of tasks and models. In chainer we have the function chainer.optimizers.Adam to perform Adam optimization.

Following of the Key features of the Adam optimizer −

  • Adaptive Learning Rates: Adam dynamically adjusts the learning rates for each parameter, making it effective across various tasks.
  • Moments of Gradients: It calculates the first moment (mean) and second moment (uncentered variance) of gradients to improve optimization.
  • Bias Correction: Adam uses bias-correction to address the bias introduced during initialization, particularly early in training.
  • Formula: The formula for Adam optimization is given as − $$m_t = \beta_1 m_{t-1} + (1 - \beta_1) \nabla L(\theta)$$ $$v_t = \beta_2 v_{t-1} + (1 - \beta_2) (\nabla L(\theta))^2$$ $$\hat{m}_t = \frac{m_t}{1 - \beta_1^t}$$ $$\hat{v}_t = \frac{v_t}{1 - \beta_2^t}$$ $$\theta = \theta - \frac{\alpha\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$$

    Where, $\alpha$ is the learning rate $\beta 1$ and $\beta 2$ are the decay rates for the moving averages of the gradient and its square, typically 0.9 and 0.999 respectively, ${m_t}$ and ${v_t}$ are the first and second moment estimates and $\epsilon$ is small constant added for numerical stability.

Example

Following is the example which shows how to use the Adam Optimizer in chainer with a neural network −

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
from chainer import optimizers
import numpy as np
from chainer import Variable

class SimpleNN(Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
       self.fc1 = L.Linear(None, 100)  # Fully connected layer with 100 units
       self.fc2 = L.Linear(100, 10)   # Output layer with 10 units (e.g., for 10 classes)

   def forward(self, x):
      h = F.relu(self.fc1(x))  # Apply ReLU activation function
      return self.fc2(h)     # Output layer

# Dummy data: 5 samples, each with 50 features
x_data = np.random.rand(5, 50).astype(np.float32)
# Dummy labels: 5 samples, each with 10 classes (one-hot encoded)
y_data = np.random.randint(0, 10, 5).astype(np.int32)

# Convert to Chainer variables
x = Variable(x_data)
y = Variable(y_data)

# Initialize the model
model = SimpleNN()

# Set up Adam optimizer with default parameters
optimizer = optimizers.Adam()
optimizer.setup(model)
def loss_func(predictions, targets):
   return F.softmax_cross_entropy(predictions, targets)
# Training loop
for epoch in range(10):  # Number of epochs
   # Zero the gradients
   model.cleargrads()
   
   # Forward pass
   predictions = model(x)
   
   # Calculate loss
   loss = loss_func(predictions, y)
   
   # Backward pass
   loss.backward()
   
   # Update parameters
   optimizer.update()
   
   # Print loss
   print(f'Epoch {epoch + 1}, Loss: {loss.data}')

Here is the output of applying the Adam optimizer to a neural network −

Epoch 1, Loss: 2.4677982330322266
Epoch 2, Loss: 2.365001678466797
Epoch 3, Loss: 2.2655398845672607
Epoch 4, Loss: 2.1715924739837646
Epoch 5, Loss: 2.082294464111328
Epoch 6, Loss: 1.9973262548446655
Epoch 7, Loss: 1.9164447784423828
Epoch 8, Loss: 1.8396313190460205
Epoch 9, Loss: 1.7676666975021362
Epoch 10, Loss: 1.7006778717041016

AdaGrad

AdaGrad is also known as Adaptive Gradient Algorithm which is an optimization algorithm that adjusts the learning rate for each parameter based on the accumulated gradient history during training. It is particularly effective for sparse data and scenarios where features vary in frequency or importance.

This is suitable for problems with sparse data and for dealing with models where some parameters require more adjustment than others. The function chainer.optimizers.AdaGrad is used to perfrom AdaGrad optimization in Chainer.

Following are the key features of the AdaGrad Optimizer −

  • Adaptive Learning Rates: AdaGrad adjusts the learning rate for each parameter individually based on the cumulative sum of squared gradients. This results in larger updates for infrequent parameters and smaller updates for frequent ones.
  • No Need for Learning Rate Tuning: AdaGrad automatically scales the learning rate which often removing the necessity for manual tuning.

Formula: The formula for AdaGrad is given as follows −

$$g_t = \nabla L(\theta)$$ $$G_t = G_{t-1} +{g_t}^2$$ $$\theta = \theta - \frac{\alpha}{\sqrt{G_t} + \epsilon} g_t$$

Where −

  • $g_t$ is the gradient at time step $t$.
  • $G_t$ is the accumulated sum of the squared gradients up to time $t$.
  • $\alpha$ is the global learning rate.
  • $\epsilon$ is a small constant added to prevent division by zero.

Example

Here's an example of how to use the AdaGrad optimizer in Chainer with a simple neural network −

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
from chainer import optimizers
import numpy as np
from chainer import Variable

class SimpleNN(Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
       self.fc1 = L.Linear(None, 100)  # Fully connected layer with 100 units
       self.fc2 = L.Linear(100, 10)   # Output layer with 10 units (e.g., for 10 classes)

   def forward(self, x):
      h = F.relu(self.fc1(x))  # Apply ReLU activation function
      return self.fc2(h)     # Output layer


# Dummy data: 5 samples, each with 50 features
x_data = np.random.rand(5, 50).astype(np.float32)
# Dummy labels: 5 samples, each with 10 classes (one-hot encoded)
y_data = np.random.randint(0, 10, 5).astype(np.int32)

# Convert to Chainer variables
x = Variable(x_data)
y = Variable(y_data)


# Initialize the model
model = SimpleNN()

# Set up AdaGrad optimizer with a learning rate of 0.01
optimizer = optimizers.AdaGrad(lr=0.01)
optimizer.setup(model)
def loss_func(predictions, targets):
   return F.softmax_cross_entropy(predictions, targets)
# Training loop
for epoch in range(10):  # Number of epochs
   # Zero the gradients
   model.cleargrads()
   
   # Forward pass
   predictions = model(x)
   
   # Calculate loss
   loss = loss_func(predictions, y)
   
   # Backward pass
   loss.backward()
   
   # Update parameters
   optimizer.update()
   
   # Print loss
   print(f'Epoch {epoch + 1}, Loss: {loss.data}')

Here is the output of applying the AdaGrad optimizer to a neural network −

Epoch 1, Loss: 2.2596702575683594
Epoch 2, Loss: 1.7732301950454712
Epoch 3, Loss: 1.4647505283355713
Epoch 4, Loss: 1.2398217916488647
Epoch 5, Loss: 1.0716438293457031
Epoch 6, Loss: 0.9412426352500916
Epoch 7, Loss: 0.8350374102592468
Epoch 8, Loss: 0.7446572780609131
Epoch 9, Loss: 0.6654194593429565
Epoch 10, Loss: 0.59764164686203

RMSProp

RMSProp optimizer is improved upon AdaGrad by introducing a decay factor to the sum of squared gradients by preventing the learning rate from shrinking too much. It's particularly effective in recurrent neural networks or models that require quick adaptation to varying gradient scales.

In Chainer to perform RMSProp optimizer we have the function chainer.optimizers.RMSprop.

Following are the key features of RMSProp optimizer −

  • Decay Factor: RMSProp introduces a decay factor to the accumulated sum of squared gradients by preventing the learning rate from becoming too small and allowing for a more stable convergence.
  • Adaptive Learning Rate: Like AdaGrad the RMSProp optimizer adapts the learning rate for each parameter individually based on the gradient history but it avoids the diminishing learning rate problem by limiting the accumulation of past squared gradients.

Formula: The formula for RMSProp optimizer is given as −

$$g_t = \nabla L(\theta)$$ $$E[g^2]_t = \gamma E[g^2]_{t-1} + (1 - \gamma){g_t}^2$$ $$\theta = \theta - \frac{\alpha}{\sqrt{E[g^2]_t} + \epsilon} g_t$$

Where −

  • $g_t$ is the gradient at time step $t$.
  • $E[g_2]$ is the moving average of the squared gradients.
  • $\gamma$ is the decay factor which is typically around 0.9.
  • $\alpha$ is the global learning rate.
  • $\epsilon$ is a small constant added to prevent division by zero.

Example

Below is the example which shows how we can use the RMSProp optimizer in Chainer with a simple neural network −

import chainer
import chainer.functions as F
import chainer.links as L
from chainer import Chain
import numpy as np
from chainer import Variable
from chainer import optimizers

class SimpleNN(Chain):
   def __init__(self):
      super(SimpleNN, self).__init__()
      with self.init_scope():
       self.fc1 = L.Linear(None, 100)  # Fully connected layer with 100 units
       self.fc2 = L.Linear(100, 10)   # Output layer with 10 units (e.g., for 10 classes)

   def forward(self, x):
      h = F.relu(self.fc1(x))  # Apply ReLU activation function
      return self.fc2(h)     # Output layer

# Dummy data: 5 samples, each with 50 features
x_data = np.random.rand(5, 50).astype(np.float32)
# Dummy labels: 5 samples, each with 10 classes (one-hot encoded)
y_data = np.random.randint(0, 10, 5).astype(np.int32)

# Convert to Chainer variables
x = Variable(x_data)
y = Variable(y_data)

# Initialize the model
model = SimpleNN()

# Set up RMSProp optimizer with a learning rate of 0.01 and decay factor of 0.9
optimizer = optimizers.RMSprop(lr=0.01, alpha=0.9)
optimizer.setup(model)
def loss_func(predictions, targets):
   return F.softmax_cross_entropy(predictions, targets)
# Training loop
for epoch in range(10):  # Number of epochs
   # Zero the gradients
   model.cleargrads()
   
   # Forward pass
   predictions = model(x)
   
   # Calculate loss
   loss = loss_func(predictions, y)
   
   # Backward pass
   loss.backward()
   
   # Update parameters
   optimizer.update()
   
   # Print loss
   print(f'Epoch {epoch + 1}, Loss: {loss.data}')

Following is the output of the above example of using the RMSProp optimization −

Epoch 1, Loss: 2.3203792572021484
Epoch 2, Loss: 1.1593462228775024
Epoch 3, Loss: 1.2626817226409912
Epoch 4, Loss: 0.6015896201133728
Epoch 5, Loss: 0.3906801640987396
Epoch 6, Loss: 0.28964582085609436
Epoch 7, Loss: 0.21569299697875977
Epoch 8, Loss: 0.15832018852233887
Epoch 9, Loss: 0.12146510928869247
Epoch 10, Loss: 0.09462013095617294

Datasets and Iterators in Chainer

In Chainer handling data efficiently is crucial for training neural networks. To facilitate this the chainer framework provides two essential components namely,Datasets and Iterators. These components help in managing data by ensuring that it is fed into the model in a structured and efficient manner.

Datasets

A dataset in Chainer is a collection of data samples that can be fed into a neural network for training, validation or testing. Chainer provides a Dataset class that can be extended to create custom datasets as well as several built-in dataset classes for common tasks.

Types of Datasets in Chainer

Chainer provides several types of datasets to handle various data formats and structures. These datasets can be broadly categorized into built-in datasets, custom datasets and dataset transformations.

Built-in Datasets

Chainer comes with a few popular datasets that are commonly used for benchmarking and experimentation. These datasets are readily available and can be loaded easily using built-in functions.

Following is the code to get the list of all available datasets in Chainer −

import chainer.datasets as datasets

# Get all attributes in the datasets module
all_datasets = [attr for attr in dir(datasets) if attr.startswith('get_')]

# Print the available datasets
print("Built-in datasets available in Chainer:")
for dataset in all_datasets:
   print(f"- {dataset}")

Here is the output which displays all the built-in datasets in Chainer Framework −

Built-in datasets available in Chainer:
- get_cifar10
- get_cifar100
- get_cross_validation_datasets
- get_cross_validation_datasets_random
- get_fashion_mnist
- get_fashion_mnist_labels
- get_kuzushiji_mnist
- get_kuzushiji_mnist_labels
- get_mnist
- get_ptb_words
- get_ptb_words_vocabulary
- get_svhn

Custom Datasets

When working with custom data we can create our own datasets by subclassing chainer.dataset.DatasetMixin. This allows us to define how data should be loaded and returned.

Here is the example of creating the custom datasets using chainer.dataset.DatasetMixin and printing the first row in it −

import chainer
import numpy as np

class MyDataset(chainer.dataset.DatasetMixin):
   def __init__(self, data, labels):
      self.data = data
      self.labels = labels

   def __len__(self):
      return len(self.data)

   def get_example(self, i):
      return self.data[i], self.labels[i]

# Creating a custom dataset
data = np.random.rand(100, 3)
labels = np.random.randint(0, 2, 100)
dataset = MyDataset(data, labels)
print(dataset[0])

Here is the output of the custom dataset first row −

(array([0.82744124, 0.33828446, 0.06409377]), 0)

Preprocessed Datasets

Chainer provides tools to apply transformations to datasets such as scaling, normalization or data augmentation. These transformations can be applied on-the-fly using TransformDataset.

Here is the example of using the Preprocessed Datasets in chainer −

from chainer.datasets import TransformDataset

def transform(data):
   x, t = data
   x = x / 255.0  # Normalize input data
   return x, t

# Apply transformation to dataset
transformed_dataset = TransformDataset(dataset, transform)
print(transformed_dataset[0])

Below is the first row of the preprocessed Datasets with the help of TransformDataset() function −

(array([0.00324487, 0.00132661, 0.00025135]), 0)

Concatenated Datasets

ConcatDataset is used to concatenate multiple datasets into a single dataset. This is useful when we have data spread across different sources. Here is the example of using the concatenated Datasets in chainer Framework in which prints out each sample's data and label from the concatenated dataset. The combined dataset includes all samples from both dataset1 and dataset2 −

import numpy as np
from chainer.datasets import ConcatenatedDataset
from chainer.dataset import DatasetMixin

# Define a custom dataset class
class MyDataset(DatasetMixin):
   def __init__(self, data, labels):
      self.data = data
      self.labels = labels

   def __len__(self):
      return len(self.data)

   def get_example(self, i):
      return self.data[i], self.labels[i]

# Sample data arrays
data1 = np.random.rand(5, 3)  # 5 samples, 3 features each
labels1 = np.random.randint(0, 2, 5)  # Binary labels for data1

data2 = np.random.rand(5, 3)  # Another 5 samples, 3 features each
labels2 = np.random.randint(0, 2, 5)  # Binary labels for data2

# Create MyDataset instances
dataset1 = MyDataset(data1, labels1)
dataset2 = MyDataset(data2, labels2)

# Concatenate the datasets
combined_dataset = ConcatenatedDataset(dataset1, dataset2)

# Iterate over the combined dataset and print each example
for i in range(len(combined_dataset)):
   data, label = combined_dataset[i]
   print(f"Sample {i+1}: Data = {data}, Label = {label}")

Here is the output of the concatenated datasets in Chainer −

Sample 1: Data = [0.6153635  0.19185915 0.26029754], Label = 1
Sample 2: Data = [0.69201927 0.70393578 0.85382294], Label = 1
Sample 3: Data = [0.46647242 0.37787839 0.37249345], Label = 0
Sample 4: Data = [0.2975833  0.90399536 0.15978975], Label = 1
Sample 5: Data = [0.29939455 0.21290926 0.97327959], Label = 1
Sample 6: Data = [0.68297438 0.64874375 0.09129224], Label = 1
Sample 7: Data = [0.52026288 0.24197601 0.5239313 ], Label = 0
Sample 8: Data = [0.63250008 0.85023346 0.94985447], Label = 1
Sample 9: Data = [0.75183151 0.01774763 0.66343944], Label = 0
Sample 10: Data = [0.60212864 0.48215319 0.02736618], Label = 0

Tuple and Dict Datasets

Chainer provides special dataset classes called TupleDataset and DictDataset that allow us to manage multiple data sources conveniently. These classes are useful when we have more than one type of data such as features and labels or multiple feature sets that we want to handle together.

  • Tuple Datasets: This is used to combine multiple datasets or data arrays into a single dataset where each element is a tuple of corresponding elements from the original datasets.
  • Here is the example which shows how to use the Tuple Datasets in Neural networks −

import numpy as np
from chainer.datasets import TupleDataset

# Create two datasets (or data arrays)
data1 = np.random.rand(100, 3)  # 100 samples, 3 features each
data2 = np.random.rand(100, 5)  # 100 samples, 5 features each

# Create a TupleDataset combining both data arrays
tuple_dataset = TupleDataset(data1, data2)

# Accessing data from the TupleDataset
for i in range(5):
   print(f"Sample {i+1}: Data1 = {tuple_dataset[i][0]}, Data2 = {tuple_dataset[i][1]}")

Here is the output of the Tuple Datasets −

 
Sample 1: Data1 = [0.32992823 0.57362303 0.95586597], Data2 = [0.41455   0.52850591 0.55602243 0.36316931 0.93588697]
Sample 2: Data1 = [0.37731994 0.00452533 0.67853069], Data2 = [0.71637691 0.04191565 0.54027323 0.68738626 0.01887967]
Sample 3: Data1 = [0.85808665 0.15863516 0.51649116], Data2 = [0.9596284  0.12417238 0.22897152 0.63822924 0.99434029]
Sample 4: Data1 = [0.2477932  0.27937585 0.59660463], Data2 = [0.92666318 0.93611279 0.96622103 0.41834484 0.72602107]
Sample 5: Data1 = [0.71989544 0.46155552 0.31835487], Data2 = [0.27475741 0.33759694 0.22539997 0.40985004 0.00469414]
  • DictDataset: This also works similarly to TupleDataset but allow us to label each element with a key by making it easier to access and understand the data.
  • Here is the example which shows how to use the Dict Datasets in chainer −

    import numpy as np
    from chainer.datasets import DictDataset
    
    # Create two datasets (or data arrays)
    data1 = np.random.rand(100, 3)  # 100 samples, 3 features each
    labels = np.random.randint(0, 2, 100)  # Binary labels for each sample
    
    # Create a DictDataset
    dict_dataset = DictDataset(data=data1, label=labels)
    
    # Accessing data from the DictDataset
    for i in range(5):
       print(f"Sample {i+1}: Data = {dict_dataset[i]['data']}, Label = {dict_dataset[i]['label']}")
    

    Here is the output of the Tuple Datasets −

     
    Sample 1: Data = [0.09362018 0.33198328 0.11421714], Label = 1
    Sample 2: Data = [0.53655817 0.9115115  0.0192754 ], Label = 0
    Sample 3: Data = [0.48746879 0.18567869 0.88030764], Label = 0
    Sample 4: Data = [0.10720832 0.79523399 0.56056922], Label = 0
    Sample 5: Data = [0.76360577 0.69915416 0.64604595], Label = 1
    

    Iterators

    In Chainer iterators are crucial for managing data during the training of machine learning models. They break down large datasets into smaller chunks known as minibatches which can be processed incrementally. This approach enhances memory efficiency and speeds up the training process by allowing the model to update its parameters more frequently.

    Types of Iterators in Chainer

    Chainer provides various types of iterators to handle datasets during the training and evaluation of machine learning models. These iterators are designed to work with different scenarios and requirements such as handling large datasets, parallel data loading or ensuring data shuffling for better generalization.

    SerialIterator

    This is the most common iterator in Chainer. It iterates over a dataset in a serial (sequential) manner, providing minibatches of data. When the end of the dataset is reached, the iterator can either stop or start again from the beginning, depending on the repeat option. This is used in ideal for standard training where the order of data is preserved.

    Here is the example which shows how to use the SerialIterator in chainer −

    import chainer
    import numpy as np
    from chainer import datasets, iterators
    
    # Create a simple dataset (e.g., dummy data)
    x_data = np.random.rand(100, 2).astype(np.float32)  # 100 samples, 2 features each
    y_data = np.random.randint(0, 2, size=(100,)).astype(np.int32)  # 100 binary labels
    
    # Combine the features and labels into a Chainer dataset
    dataset = datasets.TupleDataset(x_data, y_data)
    
    # Initialize the SerialIterator
    iterator = iterators.SerialIterator(dataset, batch_size=10, repeat=True, shuffle=True)
    
    # Example of iterating over the dataset
    for epoch in range(2):  # Run for two epochs
       while True:
          batch = iterator.next()  # Get the next batch
          
          # Unpacking the batch manually
          x_batch = np.array([example[0] for example in batch])  # Extract x data
          y_batch = np.array([example[1] for example in batch])  # Extract y data
    
          print("X batch:", x_batch)
          print("Y batch:", y_batch)
    
          if iterator.is_new_epoch:  # Check if a new epoch has started
           print("End of epoch")
           break
    
    # Reset the iterator to the beginning of the dataset (optional)
    iterator.reset()
    

    Below is the output of the SerialIterator used in Chainer −

     
    X batch: [[0.00603645 0.13716008]
     [0.97394305 0.9035589 ]
     [0.93046355 0.63140464]
     [0.44332692 0.5307854 ]
     [0.48565307 0.845648  ]
     [0.98147005 0.47466147]
     [0.3036461  0.62494874]
     [0.31664708 0.7176309 ]
     [0.14955625 0.65800977]
     [0.72328717 0.33383074]]
    Y batch: [1 0 0 1 0 0 1 1 1 0]
    ----------------------------
    ----------------------------
    ----------------------------
    X batch: [[0.10038178 0.32700586]
     [0.4653218  0.11713986]
     [0.10589143 0.5662842 ]
     [0.9196327  0.08948212]
     [0.13177629 0.59920484]
     [0.46034923 0.8698121 ]
     [0.24727622 0.8066094 ]
     [0.01744546 0.88371164]
     [0.18966147 0.9189765 ]
     [0.06658458 0.02469426]]
    Y batch: [0 1 0 0 0 0 0 0 0 1]
    End of epoch
    

    MultiprocessIterator

    This iterator is designed to speed up data loading by using multiple processes. It is particularly useful when working with large datasets or when the preprocessing of data is time-consuming.

    Following is the example of using the Multiprocessor Iterator in chainer Framework −

    import chainer
    import numpy as np
    from chainer import datasets, iterators
    
    # Create a simple dataset (e.g., dummy data)
    x_data = np.random.rand(1000, 2).astype(np.float32)  # 1000 samples, 2 features each
    y_data = np.random.randint(0, 2, size=(1000,)).astype(np.int32)  # 1000 binary labels
    
    # Combine the features and labels into a Chainer dataset
    dataset = datasets.TupleDataset(x_data, y_data)
    
    # Initialize the MultiprocessIterator
    # n_processes: Number of worker processes to use
    iterator = iterators.MultiprocessIterator(dataset, batch_size=32, n_processes=4, repeat=True, shuffle=True)
    
    # Example of iterating over the dataset
    for epoch in range(2):  # Run for two epochs
       while True:
          batch = iterator.next()  # Get the next batch
          
          # Unpacking the batch manually
          x_batch = np.array([example[0] for example in batch])  # Extract x data
          y_batch = np.array([example[1] for example in batch])  # Extract y data
    
          print("X batch shape:", x_batch.shape)
          print("Y batch shape:", y_batch.shape)
    
          if iterator.is_new_epoch:  # Check if a new epoch has started
           print("End of epoch")
           break
    
    # Reset the iterator to the beginning of the dataset (optional)
    iterator.reset()
    

    Below is the output of the Multiprocessor Iterator −

    X batch shape: (32, 2)
    Y batch shape: (32,)
    X batch shape: (32, 2)
    Y batch shape: (32,)
    X batch shape: (32, 2)
    Y batch shape: (32,)
    ---------------------
    ---------------------
    X batch shape: (32, 2)
    Y batch shape: (32,)
    X batch shape: (32, 2)
    Y batch shape: (32,)
    End of epoch
    

    MultithreadIterator

    The MultithreadIterator is an iterator in Chainer designed for parallel data loading using multiple threads. This iterator is particularly useful when dealing with datasets that can benefit from concurrent data processing such as when data loading or preprocessing is the bottleneck in training.

    Unlike MultiprocessIterator which uses multiple processes where MultithreadIterator uses threads by making it more suitable for scenarios where shared memory access or lightweight parallelism is required.

    Following is the example of using the MultithreadIterator in chainer Framework −

    import numpy as np
    from chainer.datasets import TupleDataset
    from chainer.iterators import MultithreadIterator
    
    # Create sample datasets
    data1 = np.random.rand(100, 3)  # 100 samples, 3 features each
    data2 = np.random.rand(100, 5)  # 100 samples, 5 features each
    
    # Create a TupleDataset
    dataset = TupleDataset(data1, data2)
    
    # Create a MultithreadIterator with 4 threads and a batch size of 10
    iterator = MultithreadIterator(dataset, batch_size=10, n_threads=4, repeat=False, shuffle=True)
    
    # Iterate over the dataset
    for batch in iterator:
       # Unpack each tuple in the batch
       data_batch_1 = np.array([item[0] for item in batch])  # Extract the first element from each tuple
       data_batch_2 = np.array([item[1] for item in batch])  # Extract the second element from each tuple
    
       print("Data batch 1:", data_batch_1)
       print("Data batch 2:", data_batch_2)
    

    Below is the output of the Multithread Iterator −

    Data batch 1: [[0.38723876 0.66585393 0.74603754]
     [0.136392   0.23425485 0.6053701 ]
     [0.99668734 0.13096871 0.13114792]
     [0.32277508 0.3718192  0.42083016]
     [0.93408236 0.59433832 0.23590596]
     [0.16351005 0.82340571 0.08372471]
     [0.78469682 0.81117013 0.41653794]
     [0.32369538 0.77524528 0.10378537]
     [0.21678887 0.8905319  0.88525376]
     [0.41348068 0.43437296 0.90430938]]
    ---------------------
    ---------------------
    Data batch 2: [[0.20541319 0.69626397 0.81508325 0.49767042 0.92252953]
     [0.12794664 0.33955336 0.81339754 0.54042266 0.44137714]
     [0.52487615 0.59930116 0.96334436 0.61622956 0.34192033]
     [0.93474439 0.37455884 0.94954379 0.73027705 0.24333167]
     [0.24805745 0.80921792 0.91316062 0.59701139 0.25295744]
     [0.27026875 0.67836862 0.16911597 0.50452568 0.86257208]
     [0.81722752 0.41361153 0.43188091 0.98313524 0.28605503]
     [0.50885091 0.80546812 0.89346966 0.63828489 0.8231125 ]
     [0.78996715 0.05338346 0.16573956 0.89421364 0.54267903]
     [0.05804313 0.5613496  0.09146587 0.79961318 0.02466306]]
    

    ShuffleOrderSampler

    The ShuffleOrderSampler is a component in Chainer that is used to randomize the order of indices in a dataset. It ensures that each epoch of training sees the data in a different order which helps in reducing overfitting and improving the generalization of the model.

    import numpy as np
    from chainer.datasets import TupleDataset
    from chainer.iterators import SerialIterator, ShuffleOrderSampler
    
    # Create sample datasets
    data = np.random.rand(100, 3)  # 100 samples, 3 features each
    labels = np.random.randint(0, 2, size=100)  # 100 binary labels
    
    # Create a TupleDataset
    dataset = TupleDataset(data, labels)
    
    # Initialize ShuffleOrderSampler
    sampler = ShuffleOrderSampler()
    
    # Create a SerialIterator with the ShuffleOrderSampler
    iterator = SerialIterator(dataset, batch_size=10, repeat=False, order_sampler=sampler)
    
    # Iterate over the dataset
    for batch in iterator:
       # Since the batch contains tuples, we extract data and labels separately
       data_batch, label_batch = zip(*batch)
       print("Data batch:", np.array(data_batch))
       print("Label batch:", np.array(label_batch))
    

    Below is the output of applying the ShuffleOrderSampler Iterator in Chainer −

    Data batch: [[0.93062607 0.68334939 0.73764239]
     [0.87416648 0.50679946 0.17060853]
     [0.19647824 0.2195698  0.5010152 ]
     [0.28589369 0.08394862 0.28748563]
     [0.55498598 0.73032299 0.01946458]
     [0.68907645 0.8920713  0.7224627 ]
     [0.36771187 0.91855943 0.87878009]
     [0.14039665 0.88076789 0.76606626]
     [0.84889666 0.57975573 0.70021538]
     [0.45484641 0.17291856 0.42353947]]
    Label batch: [0 1 1 0 1 0 1 1 0 0]
    -------------------------------------
    -------------------------------------
    Data batch: [[0.0692231  0.24701816 0.24603659]
     [0.72014948 0.67211487 0.45648504]
     [0.8625562  0.45570299 0.58156546]
     [0.60350332 0.81757841 0.30411054]
     [0.93224841 0.3055118  0.07809648]
     [0.16425884 0.69060297 0.36452719]
     [0.79252781 0.35895253 0.26741555]
     [0.27568602 0.38510119 0.36718876]
     [0.58806512 0.35221788 0.08439596]
     [0.13015496 0.81817428 0.86631724]]
    Label batch: [0 0 1 0 1 0 1 0 0 1]
    

    Training Loops

    Training loops are the core mechanism in machine learning through which a model learns from data. They involve a repetitive process of feeding data into a model, calculating the error (loss), adjusting the model's parameters to reduce that error and then repeating the process until the model performs well enough on the task. Training loops are fundamental to training neural networks and other machine learning models.

    Key Components in Training Loops

    • Model: The neural network or machine learning model that you want to train.
    • Loss Function: This is a function that measures how well the model's predictions match the actual data for example mean squared error, cross-entropy.
    • Optimizer: An algorithm used to update the model's parameters based on the computed gradients e.g., SGD, Adam.
    • Data: The dataset used for training typically divided into minibatches for efficient processing.

    Why Training Loops are Important?

    Training loops are fundamental in deep learning and machine learning for several reasons which are as mentioned as follows −

    • Efficiency: They allow models to be trained on large datasets by processing data in small chunks i.e. minibatches.
    • Iterative Improvement: By repeatedly adjusting the model's parameters, the training loop enables the model to learn and improve its accuracy over time.
    • Flexibility: Training loops can be customized to include additional features like learning rate schedules, early stopping or monitoring metrics.

    Key Steps in a Training Loop

    Following are the steps to be followed in Training Loops −

    • Forward Pass: Here first the input data is fed into the model and then the model processes the data through its layers to produce an output (prediction).
    • Loss Calculation: The output is compared to the actual target values using a loss function. The loss function computes the error or difference between the predicted output and the actual target.
    • Backward Pass (Backpropagation): The gradients of the loss with respect to each of the model's parameters (weights) are calculated. These gradients indicate how much each parameter contributed to the error.
    • Parameter Update: Here the model's parameters are updated using an optimization algorithm such as SGD, Adam, etc. The parameters are adjusted in a way that minimizes the loss.
    • Repeat: The process is repeated for multiple iterations (epochs) where the model sees the data multiple times. The goal is for the model to learn and improve its predictions by gradually reducing the loss.

    Example

    In Chainer training loops are used to iterate through the dataset, compute the loss and update the model parameters. Below is an example demonstrating a basic training loop using Chainer. This example uses a simple feedforward neural network trained on the MNIST dataset.

    import chainer
    import chainer.functions as F
    import chainer.links as L
    from chainer import Chain, optimizers, training, serializers
    from chainer.datasets import TupleDataset
    from chainer.iterators import SerialIterator
    from chainer.training import extensions
    import numpy as np
    
    # Define the neural network model
    class SimpleNN(Chain):
       def __init__(self):
          super(SimpleNN, self).__init__()
          with self.init_scope():
           self.l1 = L.Linear(3, 5)  # Input layer to hidden layer
           self.l2 = L.Linear(5, 2)  # Hidden layer to output layer
    
       def forward(self, x):
          h = F.relu(self.l1(x))  # Apply ReLU activation
          y = self.l2(h)      # Output layer
          return y
    
       def __call__(self, x, t):
          y = self.forward(x)
          return F.softmax_cross_entropy(y, t)
    
    # Generate synthetic data
    data = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=np.float32)
    labels = np.array([0, 1, 0], dtype=np.int32)
    
    # Create a dataset and iterator
    dataset = TupleDataset(data, labels)
    iterator = SerialIterator(dataset, batch_size=1, shuffle=True)
    
    # Initialize the model, optimizer, and updater
    model = SimpleNN()
    optimizer = optimizers.Adam()
    optimizer.setup(model)
    
    # Set up the trainer
    updater = training.StandardUpdater(iterator, optimizer, device=-1)
    trainer = training.Trainer(updater, (10, 'epoch'), out='result')
    
    # Add extensions to monitor training
    trainer.extend(extensions.Evaluator(iterator, model, device=-1))
    trainer.extend(extensions.LogReport())
    trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'validation/main/loss']))
    trainer.extend(extensions.ProgressBar())
    
    # Start training
    trainer.run()
    

    Here is the outut of the training loop −

    epoch      main/loss   validation/main/loss
    
    Advertisements