{"id":436,"date":"2022-08-01T10:50:38","date_gmt":"2022-08-01T09:50:38","guid":{"rendered":"https:\/\/wp.coventry.domains\/e2edu\/?page_id=436"},"modified":"2022-08-23T18:55:27","modified_gmt":"2022-08-23T17:55:27","slug":"pytorch","status":"publish","type":"page","link":"https:\/\/wp.coventry.domains\/e2edu\/pytorch\/","title":{"rendered":"PyTorch"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><a rel=\"noreferrer noopener\" href=\"https:\/\/pytorch.org\/\" target=\"_blank\">PyTorch <\/a>is a popular open source framework for developing, training, and deploying deep learning models. PyTorch is developed by <a rel=\"noreferrer noopener\" href=\"https:\/\/about.facebook.com\/\" target=\"_blank\">Meta <\/a>and is available for the three major operating systems (Linux, Windows, MacOS). <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pytorch is a <a rel=\"noreferrer noopener\" href=\"https:\/\/www.python.org\/\" target=\"_blank\">Python <\/a>wrapper for Torch. Torch is a library for conducting mathematical operations on tensors and offers strong GPU support. Torch employs a script language based on the Lua programming language that uses an underlying C implementation. The combination of Pytorch and Torch offers the flexibility and ease of development from Python and the performance of GPU-based parallel computation from Torch. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article introduces some of the basic programming principles of Pytorch. More information about Torch itself or about other language bindings for Torch can be found on the <a rel=\"noreferrer noopener\" href=\"https:\/\/pytorch.org\/docs\/stable\/torch.html\" target=\"_blank\">Pytorch website<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Tensors<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A tensor is a multi-dimensional matrix that contains elements of a single data type. <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"668\" src=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-1024x668.jpeg\" alt=\"\" class=\"wp-image-992\" srcset=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-1024x668.jpeg 1024w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-300x196.jpeg 300w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-768x501.jpeg 768w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-1536x1001.jpeg 1536w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors-788x514.jpeg 788w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/Tensors.jpeg 2000w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Tensors of Different Dimensions. \u00a9Sayak Paul<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">Tensors are used to store a variety of data such as the data neural networks operate on or the parameters of the networks. Tensors can be used in a very similar manner as Python <a rel=\"noreferrer noopener\" href=\"https:\/\/numpy.org\/\" target=\"_blank\">numpy arrays<\/a>. At the same time, Tensors surpass the capabilities of numpy arrays since they can run either on the CPU or CPU and keep track of gradients. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Tensor Creation<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Tensors can be created in a variety of ways. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The content and datatype of a tensor can be directly specified when creating it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# create a one dimensional tensor with 3 elements of datatype float64\nx = torch.tensor(&#091;1.3, 2.5, 1.0], dtype=torch.float64)\n\n# create a two dimensional tensor with 2x2 elements of datatype float32\nx = torch.tensor(&#091;&#091;1.1, 1.2],&#091;2.1, 2.2]], dtype=torch.float32)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Torch tensors can also be created from other torch tensors. For this, the clone function is provided. This function creates a new tensor with its own memory. If only a regular assignment operator is used, then the new tensor uses the same memory as the original tensor.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# copy a tensor from another tensor\nx = torch.tensor(&#091;1.3, 2.5, 1.0], dtype=torch.float64)\ny = torch.clone(x)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Alternatively,  torch tensors can be created from numpy arrays by using the &#8220;from_numpy&#8221; function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport numpy as np\n\n# copy a tensor from a numpy array\nx = np.array(&#091;1.3, 2.5, 1.0], dtype=np.float64)\ny = torch.from_numpy(x)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tensors whose elements all have a value of zero or one can be created directly from the corresponding &#8220;zeros&#8221; and &#8220;ones&#8221; functions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# create a one dimensional tensor with 3 zero values of datatype int32\nx = torch.zeros(&#091;3], dtype=torch.int32)\n\n# create a two dimensional tensor with 2x2 one values of datatype float32\nx = torch.ones(&#091;2, 2], dtype=torch.float32)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tensors can also be created with random values by using the &#8220;rand&#8221; function. The random values are from a uniform distribution in the interval from 0.0 to 1.0 (excluding 1.0). <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# create a two dimensional tensor with 5x3 random values of datatype float32\nx = torch.rand(&#091;5, 3], dtype=torch.float32)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When creating a tensor, this tensor initially resides in the memory of the CPU. To conduct tensor operations on the GPU, the tensor has to be moved to the GPU. The member function of the tensor class for moving a tensor from CPU memory to GPU memory (or vice versa) is &#8220;to&#8221;. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\nx = torch.rand(&#091;5, 3], dtype=torch.float32)\n\n# move tensor from cpu memory to gpu memory\nx = x.to(\"cuda\")\n\n# move tensor from gpu memory to cpu memory\nx = x.to(\"cpu\")\n\n# an alternative method for copying a tensor from gpu to cpu memory is to call the member function \"cpu\" on the tensor\nx = x.cpu()\n\n# when creating a new tensor, the move into gpu memory can be done right away.\nx = torch.rand(&#091;5, 3], dtype=torch.float32).to(\"cuda\")<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When transferring a tensor from GPU memory to CPU memory, attention has to be paid on whether the tensor forms part of a computational graph (more about this later on). If that is the case, then the tensor has to be first detached from the computational graph before it can be moved to CPU memory.  For this, the member function &#8220;detach&#8221; can be called on the tensor. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# assuming that the tensor x exists and has for instance been obtained as output from a model\n\nx = x.detach().cpu()<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Tensor Attributes<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Tensors possess a variety of attributes that can be accessed for inspection using the corresponding member variables. The tensor attributes that are commonly inspected are dtype, device, and shape. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\nx = torch.rand(&#091;5, 3], dtype=torch.float32)\n\n# the data type of the values in a tensor is stored in the member variable \"dtype\"\n\nprint(x.dtype)\n\n# the device memory in which the tensor is stored is stored in the member variable \"device\"\n\nprint(x.device)\n\n# one of the most frequently referred to attributes of a tensor is its shape. The shape of a tensor refers to the number of elements along each dimension. The shape can be obtained in the member variable \"shape\".\n\nprint(x.shape)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Casting Tensors<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Tensors containing elements of a data type can be casted into tensors containing elements of another data type. For this, the member function &#8220;type&#8221; can be used.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\nx = torch.tensor(&#091;5.3, 1.2], dtype=torch.float32)\n\n# cast tensor into a new tensor with a different data type for its elements\ny = x.type(torch.float64)<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Accessing Tensor Elements<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">The elements stored in a tensor can be accessed using standard square bracket notation and numerical indices. The calls for accessing the elements are identical to those used for numpy arrays. Accordingly, these calls provide the same level of flexibility for accessing ranges of elements or indexing backwards from the end of a tensor. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\n# create a 3x4 tensor with values that make it simple to verify that the correct elements are accessed\nx = torch.tensor(&#091;&#091;0.0, 0.1, 0.2, 0.3],&#091;1.0, 1.1, 1.2, 1.3],&#091;2.0, 2.1, 2.2, 2.3]], dtype=torch.float32)\n\n# access a scalar element by specifying all indices with indices being counted upwards from zero\nx&#091;1, 3]\n\n# access the same scalar element by specifying all indices with indices being counted downwards from the length along each tensor dimension\nx&#091;-2, -1]\n\n# access an entire row of values\nx&#091;1]\n\n# access an entire column of values\nx&#091;:,3]\n\n# access a sub-region of the tensor\nx&#091;1:,1:3]<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Tensor Mathematical Operations<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Basic mathematical operations such as addition, subtraction, multiplication and division are available for tensors. These operations can be conducted between tensors and scalars as well as tensors and tensors. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\nx = torch.tensor(&#091;1.2, 3.4, 6.1], dtype=torch.float32)\ny = torch.tensor(&#091;5.1, 0.3, 2.8], dtype=torch.float32)\n\n# mathematical operations between tensor and scalar. \nx + 3\nx - 10\nx * 0.3\nx \/ 2.1\n\n# mathematical operations between tensor and tensor. \nx + y\nx - y\nx * y\nx \/ y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Vector and matrix operations can also available for tensors. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\n\nv1 = torch.tensor(&#091;1., 0., 0.], dtype=torch.float64)\nv2 = torch.tensor(&#091;0., 1., 0.], dtype=torch.float64)    \nm1 = torch.tensor(&#091;&#091;0.0, 0.1],&#091;1.0, 1.1]], dtype=torch.float64) \nm2 = torch.tensor(&#091;&#091;3.0, 0.0],&#091;0.0, 3.0]], dtype=torch.float64) \n\ntorch.cross(v2, v1) # vector cross product\ntorch.dot(v2, v1) # vector dot product\ntorch.matmul(m1, m2) # matrix multiplication\ntorch.svd(m1) # singular value decomposition<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Other common mathematical operations include clamping, bitwise operations, comparisons, and reductions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport math\n\nx = torch.rand(&#091;2, 4], dtype=torch.float64)\n\n# absolute values, rounding, clamping\ntorch.abs(x) # absolute\ntorch.ceil(x) # round up\ntorch.floor(x) # round down\ntorch.clamp(x, -0.5, 0.5) # clamp\n\n# trigonometric functions\nangles = torch.tensor(&#091;0, math.pi \/ 4, math.pi \/ 2, 3 * math.pi \/ 4])\ntorch.sin(angles) # sine\ntorch.asin(torch.sin(angles)) # inverse sine\n\n# bitwise operations\nb = torch.tensor(&#091;1, 5, 11], dtype=torch.int32)\nc = torch.tensor(&#091;2, 7, 10], dtype=torch.int32)\ntorch.bitwise_xor(b, c) # bitwise xor\n\n# comparisons\ntorch.eq(b, c) # equality\n\n# reductions\ntorch.max(x) # maximum value\ntorch.mean(x) # mean value\ntorch.std(x) # standard deviation\ntorch.prod(x) # product<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Datasets<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Data used for training or inference is organised in datasets. PyTorch offers two classes for this. The Dataset class stores the data and the DataLoader class allows to iterate over the data. These classes offer functions for randomising data, for associating input data and labels, for splitting data into batches, and for pre-processing data. The basic Dataset class needs to be sub-classed to handle any actual data. Pytorch provides convenience classes for dealing with standard datasets and standard types of data (such as images and audio). The following section describes how to work with custom data that involves features and labels. The code example can be easily modified for data that doesn&#8217;t have labels. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When writing you own custom Dataset class, there are three functions that need to be implemented. The constructor &#8220;__init__&#8221; which takes as arguments at least the features and labels that will be stored by the dataset. The function &#8220;__len__&#8221; which returns the size of the dataset which corresponds to the number of data instances stored in it. The function &#8220;__getitem__&#8221; which returns a data instance. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nfrom torch.utils.data import Dataset, DataLoader\n\n# create to random tensors representing dummy data for the dataset\ndata_count = 100 # number of data instances\ndata_dim = 8 # number of features per instance\nlabel_count = 4 # number of class labels\n\ndummy_features = torch.rand(&#091;data_count, data_dim], dtype=torch.float32)\ndummy_labels = torch.randint(0, label_count, &#091;data_count], dtype=torch.int32)\n\n# Create class for a simple customised Dataset by subclassing Dataset\nclass CustomDataset(Dataset):\n    def __init__(self, features, labels):\n        self.features = features\n        self.labels = labels\n    def __len__(self):\n        return len(self.features)\n    def __getitem__(self, idx):\n        feature = self.features&#091;idx]\n        label = self.labels&#091;idx]\n        return feature, label\n\n# Create an instance of the customised dataset\ncustomDataset = CustomDataset(dummy_features, dummy_labels)\n\n#print length of dataset\nprint(len(customDataset))\n\n#iterate over all data instances\nfor instance in iter(customDataset):\n    print(instance)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To split a full dataset into two datasets, one for training and one for testing, the torch.utils.data.random_split can be used.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># continuation from previous code block\n\n# split full dataset into train and test dataset\n\ntrain_test_ratio = 0.8 # 80% of data goes into training set, 20% into test set\ntrain_size = int(len(customDataset) * train_test_ratio)\ntest_size = len(customDataset) - train_size \n\ntrain_dataset, test_dataset = torch.utils.data.random_split(customDataset , &#091;train_size, test_size])\n\nprint(\"train dataset size: \", len(train_dataset))\nprint(\"test dataset size: \", len(test_dataset))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">An instance of the DataLoader class can be created by passing an instance of the Dataset class to the DataLoader constructor. This is the only mandatory argument. Other typically used arguments include &#8220;batch_size&#8221; and &#8220;shuffle&#8221;. &#8220;Batch_size&#8221; specifies the number of data instances in a batch. &#8220;Shuffle&#8221; specifies if the data instances should be picked randomly from a Dataset. Once a Dataloader has been instantiated, it can be iterated over with each iteration returning a batch of training data. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># continuation from previous code block\n\n# instantiate DataLoaders\n\nbatch_size = 16\n\ntrain_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\nbatch = next(iter(train_dataloader))\n\nbatch_features = batch&#091;0]\nbatch_labels = batch&#091;1]\n\nprint(\"batch features shape \", batch_features.shape)\nprint(\"batch labels shape\", batch_labels.shape)\n\n# iterate over DataLoader for test dataset\n\nfor (idx, batch) in enumerate(test_dataloader):\n    print(\"batch \", idx, \" features: \", batch&#091;0])\n    print(\"batch \", idx, \" labels: \", batch&#091;1])\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Network Layers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PyTorch provides a large number of different layer types from which neural networks can be constructed. These layers are accessible via the torch.nn module. This module contains among others conventional neural network layers (torch.nn.Linear), convolution layers (e.g. torch.nn.Conv2d), and recurrent layers (e.g. torch.nn.LSTM). Activation functions are added to a network as separate layers (e.g. torch.nn.ReLU). <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A single conventional artificial neural network layer can be created as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport torch.nn as nn\n\n# create a single classical network layer\ninput_feature_count = 8\noutput_feature_count = 1\n\ndummy_layer = nn.Linear(input_feature_count, output_feature_count)\n\n# pass dummy data through layer\n\nbatch_size = 16\ndummy_input = torch.rand(&#091;batch_size, input_feature_count], dtype=torch.float32)\ndummy_output = dummy_layer(dummy_input)\n\nprint(\"dummy_input shape \", dummy_input.shape)\nprint(\"dummy_output shape \", dummy_output.shape)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A tensor that is passed into a classical artificial neural network layer needs to possess the following shape: batch size x feature count. The feature count needs to match the number of input features specified when creating the layer. The tensor that is output by the layer also has the shape: batch size x feature count. but this time the feature count matches the number of output features that has been specified when creating the layer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A single 2d convolution layer can be created as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport torch.nn as nn\n\n# create a single convolutional network layer\ninput_channel_count = 3 # number of channels in the input feature map\noutput_channel_count = 8 # number of channels in the output feature map\nkernel_size = 5 # 5 x 5 kernel\nstride = 2\npadding = 0\n\ndummy_layer = nn.Conv2d(input_channel_count, output_channel_count, kernel_size, stride, padding)\n\n# pass dummy data through layer\nbatch_size = 16\ninput_size = &#091;64, 64]\ndummy_input = torch.rand(&#091;batch_size, input_channel_count, input_size&#091;0], input_size&#091;1]], dtype=torch.float32)\ndummy_output = dummy_layer(dummy_input)\n\nprint(\"dummy_input shape \", dummy_input.shape)\nprint(\"dummy_output shape \", dummy_output.shape)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For convolution network layers, the shape of the input tensor is batch size x channel count x feature map height x feature map width. The channel count needs to match the number of input channels specified when creating the layer. The shape of the output tensor is also batch size x channel count x feature map height x feature map width. Here, the channel count corresponds to the number of output channels specified when creating the layer. Calculating the height and width of the output tensor is a bit more involved since it depends not only on the height and width of the input tensor but also on the size of the kernel, the stride, and the padding (and also dilation when used). The equations to calculate the output height and width are as follows:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"307\" src=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-1024x307.jpg\" alt=\"\" class=\"wp-image-1094\" srcset=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-1024x307.jpg 1024w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-300x90.jpg 300w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-768x230.jpg 768w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-1536x460.jpg 1536w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize-788x236.jpg 788w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/ConvolutionOutputFeatureMapSize.jpg 1538w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Equations for Calculating the Size of a Feature Map Output by a Convolution Layer. N stands for batch size, C for Channel Count, H for height, W for Width.<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">A single or multiple recurrent layers (LSTM) can be created as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport torch.nn as nn\n\n# create a LSTM network layer\ninput_feature_count = 8\nhidden_feature_count = 512 # number of features in the hidden state h\nlayer_count = 2 # number of recurrent layers\nbatch_first = True\n\ndummy_layer = nn.LSTM(input_feature_count, hidden_feature_count, layer_count, batch_first=batch_first)\n\n# pass dummy data through layer\nbatch_size = 16\nsequence_length = 64\ndummy_input = torch.rand(&#091;batch_size, sequence_length, input_feature_count], dtype=torch.float32)\ndummy_output, (hidden_state, cell_state) = dummy_layer(dummy_input)\n\nprint(\"dummy_input shape \", dummy_input.shape)\nprint(\"dummy_output shape \", dummy_output.shape)\nprint(\"hidden_state shape \", hidden_state.shape)\nprint(\"cell_state shape \", cell_state.shape)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The construction and use of LSTM layers differs significantly from conventional or convolution network layers. First, LSTM layers are typically not constructed one single layer after the other. Instead, the layer constructor takes as argument the number of layers. If this number is larger than one, the constructor automatically creates a stack of LSTM layers. LSTM layers return not only one but three output tensors. The first tensor represents the regular data output. The second tensor  contains the hidden states of the layers. The last tensor contains the cell states of the layers. When the batch_first constructor argument is set to False (which is the default value), then the input and output data possess the following shape: sequence length x batch size x feature dimension. If the batch_first argument is set to True, then shape of the input and output data is:  batch size x sequence length x feature dimension. The batch_first flag doesn&#8217;t affect the shape of the hidden states. These are always: layer count x batch size x hidden feature count. As a side note, another flag that can be specified when creating a stack of LSTM layers is &#8220;bidirectional&#8221;. The default value is False. If the flag is set to true, the LSTM operates in bidirectional mode which means that the sequence of data runs in both directions, backwards (future to past) and forward (past to future). If this is the case, then the shape of the hidden layer changes to:   layer count * 2 x batch size x hidden feature count.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Combining Multiple Network Layers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">PyTorch offers different options to combine multiple layers when processing input data. One option is to explicitly pass the output data of one layer as input into the next layer. While this option requires the largest amount of code writing, it also offers the greatest flexibility including for instance conditional passes of data through layers. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># the code assumes that the layers and the tensor containing input data already exist. \n# The variables for the layers are named layer0 to layerN and the variable for the input tensor is x. \n# Then an explicit forward pass of the input data can be specified as follows:\n\nx = layer0(x) # pass x into 1. layer and assign output tensor to x\nx = layer1(x) # pass x into 2. layer and assign output tensor to x\nx = layer2(x) # pass x into 3. layer and assign output tensor to x\n....\ny = layerN(x) # pass x into last layer and assign output tensor to y<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Another option is to use the class torch.nn.Sequential. This class serves as sequential container for layers. Layers that are added to an instance of this class are cascaded one after the other. When data is input input into a sequential container, then this data is internally forwarded as input into the first layer, then the output of the first layer is passed as input into the second layer, and so on. The output of the last layer is returned as output of the sequential container. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># the code assumes that the tensor containing input data already exist. \n\n# add all layers to a sequential container\n\nlayers = nn.Sequential(\n          nn.Conv2d(1,20,5),\n          nn.ReLU(),\n          nn.Conv2d(20,64,5),\n          nn.ReLU()\n        )\n\n# directly pass input data into the sequential container\n\ny = layers(x)\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A variation of using the  class torch.nn.Sequential is to combine it with an instance of an OrderedDict. An OrderedDict is, as the name implies, an ordered dictionary. To such a dictionary, the layers are added as values. The keys could then be used to name the layers. This approach offers two benefits. First, naming the layers makes it easier to identify layers for instance during debugging. Second, the layers can be added using procedures instead of hard-coding them. Such procedures offer the flexibility to compute the number and settings of layers automatically.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># the code assumes that the tensor containing input data already exist.\n\nfrom collections import OrderedDict\n\n# add all layers to a python list\n\nlayer_list = &#091;]\nlayer_list.append((\"conv1\", nn.Conv2d(1,20,5)))\nlayer_list.append((\"relu1\", nn.ReLU()))\nlayer_list.append((\"conv1\", nn.Conv2d(20,64,5)))\nlayer_list.append((\"relu1\", nn.ReLU()))\n\n# create sequential container from an OrderedDict\n\nlayers = nn.Sequential(OrderedDict(layer_list))\n\n# directly pass input data into the sequential container\n\ny = layers(x)<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Models<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In Pytorch, machine-learning models are created by subclassing the nn.Module class. Doing so involves overwriting the constructor and a forward function of the base class. Typically, the constructor is used to create all layers and the forward function is used to process data with a forward pass. Other than that, the model class inherits from the nn.Module base class functions to switch between training and evaluation mode, and to load previously stored weights. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import torch\nimport torch.nn as nn\nfrom collections import OrderedDict\n\n# declare model class by subclassing nn.Module\nclass Model(nn.Module):\n    def __init__(self):\n        super().__init__()\n        \n        layer_list = &#091;]\n        layer_list.append((\"conv1\", nn.Conv2d(1,20,5)))\n        layer_list.append((\"relu1\", nn.ReLU()))\n        layer_list.append((\"conv2\", nn.Conv2d(20,64,5)))\n        layer_list.append((\"relu2\", nn.ReLU()))\n        \n        self.layers = nn.Sequential(OrderedDict(layer_list))\n\n    def forward(self, x):\n        return self.layers(x)\n    \n# instantiate model\nmodel = Model()\n\n# print textual summary of model\nprint(model)\n\n# set model into evaluation mode\nmodel.eval()\n\n# create a test input tensor for the model\nbatch_size = 16\ninput_channel_count = 1\ninput_size = &#091;64, 64]\ndummy_input = torch.rand(&#091;batch_size, input_channel_count, input_size&#091;0], input_size&#091;1]], dtype=torch.float32)\nprint(\"dummy_input shape \", dummy_input.shape)\n\n# conduct a forward pass with the dummy input\ndummy_output = model(dummy_input)\n\n# verify shape of output tensor\nprint(\"dummy_output shape \", dummy_output.shape)\n\n# set model back into training mode\nmodel.train()\n\n# save model weights\ntorch.save(model.state_dict(), \"model_weights\")\n\n# load model weights\nmodel.load_state_dict(torch.load(\"model_weights\"))<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A brief note on the evaluation and training modes of models. Changing this mode only has an effect for models that contain certain types of layers such as <a href=\"https:\/\/pytorch.org\/docs\/stable\/generated\/torch.nn.Dropout.html\" target=\"_blank\" rel=\"noreferrer noopener\">Dropout <\/a>layers or <a href=\"https:\/\/pythonguides.com\/pytorch-batch-normalization\/\" target=\"_blank\" rel=\"noreferrer noopener\">BatchNormalisation <\/a>layers. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Apart from saving and loading model weights, a model can also be saved. Models can be saved in three different formats: in a directly serialised format using  Python&#8217;s <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.python.org\/3\/library\/pickle.html\" target=\"_blank\">pickle <\/a>module, in the ONNX (<a rel=\"noreferrer noopener\" href=\"https:\/\/onnx.ai\/\" target=\"_blank\">Open Neural Network eXchange<\/a>) format, or as <a rel=\"noreferrer noopener\" href=\"https:\/\/pytorch.org\/docs\/stable\/jit.html\" target=\"_blank\">TorchScript <\/a>code. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Using pickle is easy but comes with the drawback that the saved model is bound to specific classes and directory structures used when saving the model. Models that are saved in the ONNX format can be used by any of the <a rel=\"noreferrer noopener\" href=\"https:\/\/onnx.ai\/supported-tools.html#deployModel\" target=\"_blank\">runtimes <\/a>that support this format. Models that are saved as Torchscript Saving a model in ONNX format or as TorchScript requires an example of an input tensor that can be processed by the model. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># continuation from previous code block\n\n# save model using pickle\nmodel.eval()\ntorch.save(model, \"model.pth\")\nmodel.train()\n\n\n#load model using pickle\nmodel = torch.load(\"model.pth\")\nmodel.eval()\n\n# save model in ONNX format\nmodel.eval()\ntorch.onnx.export(model, dummy_input, \"model.onnx\")\nmodel.train()\n\n# save model as TorchScript\nmodel.eval()\nscript_module = torch.jit.trace(model, dummy_input)\nscript_module.save(\"model.pt\")\nmodel.train()<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Training an Model<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Training a model involves the following steps. <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>define a loss function<\/li><li>instantiate an optimisation algorithm<\/li><li>iterate through a dataset by conducting the following steps for each batch of data<ul><li>forward data through the model<\/li><li>compute the prediction error using the loss function<\/li><li>clear gradients for the model weights<\/li><li>conduct a back propagation step to calculate new gradients<\/li><li>update the model weights<\/li><\/ul><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The torch.optim module provides various optimisation algorithms such as Adam or RMSprop. A list of all available optimisation algorithms is available <a rel=\"noreferrer noopener\" href=\"https:\/\/pytorch.org\/docs\/stable\/optim.html#algorithms\" target=\"_blank\">here<\/a>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The torch.nn module provides various predefined loss functions such as Mean Square Error or CrossEntropyLoss. A list of all available loss functions is available <a rel=\"noreferrer noopener\" href=\"https:\/\/pytorch.org\/docs\/stable\/nn.html#loss-functions\" target=\"_blank\">here<\/a>. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># this code assumes that a model and a dataset \/ dataloader have already been created.\n\nlearning_rate = 1e-4\nepochs = 10\n\n# instantiate loss function\nloss_function = nn.CrossEntropyLoss()\n\n# instantiate optimiser\noptimiser = torch.optim.Adam(model.parameters(), lr=learning_rate)\n\n# iterate over all epochs\nfor epoch in range(epochs):\n    \n    # set model into training mode\n    model.train()\n    \n    train_loss_per_epoch = &#091;]\n    \n    # iterate over all batches in the train data set\n    for batch in train_dataloader:\n        batch_features = batch&#091;0].to(device)\n        batch_labels = batch&#091;1].to(device)\n        \n        # forward pass\n        pred_labels = model(batch_features)\n        \n        # calculate prediction error\n        loss = loss_function(pred_labels, batch_labels)\n        \n        # clear gradients\n        optimiser.zero_grad()\n        \n        # back propagation\n        loss.backward()\n        \n        # update model weights\n        optimiser.step()\n        \n        train_loss_per_epoch.append(loss.detach().cpu().numpy())\n        \n    train_loss_per_epoch = np.mean(np.array(train_loss_per_epoch))\n    \n    print (\"epoch {} : train loss: {:01.4f}\".format(epoch, train_loss_per_epoch)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">It is usually recommended to test a model that is being trained on data from a test dataset. These tests should be done in parallel to the training. In order to ensure that the data from the test dataset doesn&#8217;t affect the model weights, it is important to deactivate the calculation of gradients that would normally be automatically conducted by PyTorch. The gradient calculation can be temporarily deactivated by calling the &#8220;no_grad&#8221; function. Any operations that are executed while the &#8220;no_grad&#8221; function is active will not cause the gradients to change. Iterating over an entire test dataset can then be implemented for example as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># continuation from previous code block\n\n# iterate over all epochs\nfor epoch in range(epochs):\n\n    # code for iterating over all batches of the training dataset and updating model weights goes here\n\n    # set model into evaluation mode\n    model.eval()\n    \n    test_loss_per_epoch = &#091;]\n    \n    # iterate over all batches in the test data set\n    for batch in test_dataloader:\n        batch_features = batch&#091;0].to(device)\n        batch_labels = batch&#091;1].to(device)\n        \n        # deactivate gradient calculation\n        with torch.no_grad():\n            # forward pass\n            pred_labels = model(batch_features)\n        \n            # calculate prediction error\n            loss = loss_function(pred_labels, batch_labels)\n            \n        test_loss_per_epoch.append(loss.detach().cpu().numpy())\n    \n    test_loss_per_epoch = np.mean(np.array(test_loss_per_epoch))\n    \n    print (\"epoch {} : test loss: {:01.4f}\".format(epoch, test_loss_per_epoch)\n    \n\n    # set model into training mode\n    model.train() <\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Computational Graphs<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">A brief note is in place concerning the concept and principle of computational graphs. When processing tensors with a model or any other function, the processing operations take place within a computational graph. A computational graph is a tree structure whose nodes are tensor operations. These operations are connected to each other through directed links. A computation is conducted by passing tensors into the root nodes and then processing them consecutively by following the links. In the case of a forward pass, the links are followed in the direction from input layers to loss function. In case of backpropagation, the links are followed into the opposite direction.  <\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"876\" src=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph-1024x876.png\" alt=\"\" class=\"wp-image-1130\" srcset=\"https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph-1024x876.png 1024w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph-300x256.png 300w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph-768x657.png 768w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph-788x674.png 788w, https:\/\/wp.coventry.domains\/e2edu\/wp-content\/uploads\/sites\/3486\/2022\/08\/computational_graph.png 1262w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Example of a Computational Graph used for Forward- and Backpropagation. \u00a9pytorch.org<\/figcaption><\/figure>\n<\/div>\n\n\n<p class=\"wp-block-paragraph\">To retrieve tensors that have been created by a computational graph and access their internal values, these tensors have to be detached from the graph. The member function &#8220;detach&#8221; can be used for this. In case the detached tensor resides in GPU memory, then its content has to be copied into CPU memory before accessing it. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>PyTorch is a popular open source framework for developing, training, and deploying deep learning models. PyTorch is developed by Meta and is available for the three major operating systems (Linux, Windows, MacOS). Pytorch is a Python wrapper for Torch. Torch is a library for conducting mathematical operations on tensors and offers strong GPU support. Torch [&hellip;]<\/p>\n","protected":false},"author":2154,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","footnotes":""},"class_list":["post-436","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/pages\/436","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/users\/2154"}],"replies":[{"embeddable":true,"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/comments?post=436"}],"version-history":[{"count":130,"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/pages\/436\/revisions"}],"predecessor-version":[{"id":3139,"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/pages\/436\/revisions\/3139"}],"wp:attachment":[{"href":"https:\/\/wp.coventry.domains\/e2edu\/wp-json\/wp\/v2\/media?parent=436"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}