Unsupervised contrastive learning is a technique for training deep neural networks without the need for labeled data. The goal is to learn representations of data that are useful for downstream tasks, such as image recognition or image retrieval.
In contrastive learning, the goal is to maximize the similarity between samples from the same class and minimize the similarity between samples from different classes. This is achieved by training a neural network to embed the input data into a high-dimensional space where similar samples are closer together and dissimilar samples are further apart.
PyTorch provides several tools for implementing unsupervised contrastive learning, such as the ‘torch.nn.Module‘ class for defining the architecture of the neural network and the ‘torch.optim‘ package for optimizing the network parameters. Additionally, PyTorch also provides several pre-trained models for unsupervised contrastive learning, such as SimCLR and SwAV.
For example, to implement SimCLR using PyTorch, we first define the encoder network as follows:
import torch.nn as nn
class Encoder(nn.Module):
def __init__(self):
super(Encoder, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True)
)
self.proj_head = nn.Sequential(
nn.Linear(512*4*4, 512),
nn.ReLU(inplace=True),
nn.Linear(512, 128)
)
def forward(self, x):
x = self.conv(x)
x = x.view(x.size(0), -1)
x = self.proj_head(x)
return x
In this example, we define a convolutional neural network with 5 convolutional layers, followed by a projection head that maps the output of the encoder to a lower-dimensional space. We then define the loss function as follows:
import torch.nn.functional as F
class ContrastiveLoss(nn.Module):
def __init__(self, temperature):
super(ContrastiveLoss, self).__init__()
self.temperature = temperature
def forward(self, z_i, z_j):
batch_size = z_i.shape[0]
z = torch.cat([z_i, z_j], dim=0)
sim_matrix = torch.exp(torch.mm(z, z.t().contiguous()) / self.temperature)
mask = torch.eye(batch_size, dtype=torch.bool).to(device)
sim_matrix = sim_matrix.masked_fill(mask, 0.0)
pos_samples = torch.diagonal(sim_matrix, offset=batch_size)
neg_samples = torch.exp(sim_matrix).sum(dim=1) / batch_size
loss = -torch.log(pos_samples / neg_samples).mean()
return loss
In this example, we define the contrastive loss function, which computes the similarity between pairs of samples and encourages the network to maximize the similarity between positive pairs and minimize the similarity between negative pairs. We use a temperature parameter to control the sharpness of the softmax distribution.
Finally, we can train the network using stochastic gradient descent:
encoder = Encoder().to(device)
optimizer = optim.Adam(encoder.parameters(), lr=1e-4)
for epoch in range(num_epochs):
for x in data_loader:
x_i = x.to(device)
x_j = torch.flip(x, dims=[3]).to(device)
z_i = encoder(x_i)
z_j = encoder(x_j)
loss = ContrastiveLoss(0.5)(z_i, z_j)
optimizer.zero_grad()
loss.backward()
optimizer.step()
In this example, we train the network using a data loader that loads pairs of images, and we flip the second image along the horizontal axis to create a negative pair. We then compute the embeddings for each image using the encoder, compute the contrastive loss, and update the network parameters using stochastic gradient descent. With this implementation, we can learn useful representations of our data without the need for labeled data.