chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:19:01 +08:00
commit 3b90d1192f
2172 changed files with 594509 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
"""
---
title: Patches Are All You Need? (ConvMixer)
summary: >
A PyTorch implementation/tutorial of the paper
"Patches Are All You Need?"
---
# Patches Are All You Need? (ConvMixer)
This is a [PyTorch](https://pytorch.org) implementation of the paper
[Patches Are All You Need?](https://arxiv.org/abs/2201.09792).
![ConvMixer diagram from the paper](conv_mixer.png)
ConvMixer is Similar to [MLP-Mixer](../transformers/mlp_mixer/index.html).
MLP-Mixer separates mixing of spatial and channel dimensions, by applying an MLP across spatial dimension
and then an MLP across the channel dimension
(spatial MLP replaces the [ViT](../transformers/vit/index.html) attention
and channel MLP is the [FFN](../transformers/feed_forward.html) of ViT).
ConvMixer uses a $1 \times 1$ convolution for channel mixing and a
depth-wise convolution for spatial mixing.
Since it's a convolution instead of a full MLP across the space, it mixes only the nearby batches in
contrast to ViT or MLP-Mixer.
Also, the MLP-mixer uses MLPs of two layers for each mixing and ConvMixer uses a single layer for each mixing.
The paper recommends removing the residual connection across the channel mixing (point-wise convolution)
and having only a residual connection over the spatial mixing (depth-wise convolution).
They also use [Batch normalization](../normalization/batch_norm/index.html) instead
of [Layer normalization](../normalization/layer_norm/index.html).
Here's [an experiment](experiment.html) that trains ConvMixer on CIFAR-10.
"""
import torch
from torch import nn
from labml_nn.utils import clone_module_list
class ConvMixerLayer(nn.Module):
"""
<a id="ConvMixerLayer"></a>
## ConvMixer layer
This is a single ConvMixer layer. The model will have a series of these.
"""
def __init__(self, d_model: int, kernel_size: int):
"""
* `d_model` is the number of channels in patch embeddings, $h$
* `kernel_size` is the size of the kernel of spatial convolution, $k$
"""
super().__init__()
# Depth-wise convolution is separate convolution for each channel.
# We do this with a convolution layer with the number of groups equal to the number of channels.
# So that each channel is it's own group.
self.depth_wise_conv = nn.Conv2d(d_model, d_model,
kernel_size=kernel_size,
groups=d_model,
padding=(kernel_size - 1) // 2)
# Activation after depth-wise convolution
self.act1 = nn.GELU()
# Normalization after depth-wise convolution
self.norm1 = nn.BatchNorm2d(d_model)
# Point-wise convolution is a $1 \times 1$ convolution.
# i.e. a linear transformation of patch embeddings
self.point_wise_conv = nn.Conv2d(d_model, d_model, kernel_size=1)
# Activation after point-wise convolution
self.act2 = nn.GELU()
# Normalization after point-wise convolution
self.norm2 = nn.BatchNorm2d(d_model)
def forward(self, x: torch.Tensor):
# For the residual connection around the depth-wise convolution
residual = x
# Depth-wise convolution, activation and normalization
x = self.depth_wise_conv(x)
x = self.act1(x)
x = self.norm1(x)
# Add residual connection
x += residual
# Point-wise convolution, activation and normalization
x = self.point_wise_conv(x)
x = self.act2(x)
x = self.norm2(x)
#
return x
class PatchEmbeddings(nn.Module):
"""
<a id="PatchEmbeddings"></a>
## Get patch embeddings
This splits the image into patches of size $p \times p$ and gives an embedding for each patch.
"""
def __init__(self, d_model: int, patch_size: int, in_channels: int):
"""
* `d_model` is the number of channels in patch embeddings $h$
* `patch_size` is the size of the patch, $p$
* `in_channels` is the number of channels in the input image (3 for rgb)
"""
super().__init__()
# We create a convolution layer with a kernel size and and stride length equal to patch size.
# This is equivalent to splitting the image into patches and doing a linear
# transformation on each patch.
self.conv = nn.Conv2d(in_channels, d_model, kernel_size=patch_size, stride=patch_size)
# Activation function
self.act = nn.GELU()
# Batch normalization
self.norm = nn.BatchNorm2d(d_model)
def forward(self, x: torch.Tensor):
"""
* `x` is the input image of shape `[batch_size, channels, height, width]`
"""
# Apply convolution layer
x = self.conv(x)
# Activation and normalization
x = self.act(x)
x = self.norm(x)
#
return x
class ClassificationHead(nn.Module):
"""
<a id="ClassificationHead"></a>
## Classification Head
They do average pooling (taking the mean of all patch embeddings) and a final linear transformation
to predict the log-probabilities of the image classes.
"""
def __init__(self, d_model: int, n_classes: int):
"""
* `d_model` is the number of channels in patch embeddings, $h$
* `n_classes` is the number of classes in the classification task
"""
super().__init__()
# Average Pool
self.pool = nn.AdaptiveAvgPool2d((1, 1))
# Linear layer
self.linear = nn.Linear(d_model, n_classes)
def forward(self, x: torch.Tensor):
# Average pooling
x = self.pool(x)
# Get the embedding, `x` will have shape `[batch_size, d_model, 1, 1]`
x = x[:, :, 0, 0]
# Linear layer
x = self.linear(x)
#
return x
class ConvMixer(nn.Module):
"""
## ConvMixer
This combines the patch embeddings block, a number of ConvMixer layers and a classification head.
"""
def __init__(self, conv_mixer_layer: ConvMixerLayer, n_layers: int,
patch_emb: PatchEmbeddings,
classification: ClassificationHead):
"""
* `conv_mixer_layer` is a copy of a single [ConvMixer layer](#ConvMixerLayer).
We make copies of it to make ConvMixer with `n_layers`.
* `n_layers` is the number of ConvMixer layers (or depth), $d$.
* `patch_emb` is the [patch embeddings layer](#PatchEmbeddings).
* `classification` is the [classification head](#ClassificationHead).
"""
super().__init__()
# Patch embeddings
self.patch_emb = patch_emb
# Classification head
self.classification = classification
# Make copies of the [ConvMixer layer](#ConvMixerLayer)
self.conv_mixer_layers = clone_module_list(conv_mixer_layer, n_layers)
def forward(self, x: torch.Tensor):
"""
* `x` is the input image of shape `[batch_size, channels, height, width]`
"""
# Get patch embeddings. This gives a tensor of shape `[batch_size, d_model, height / patch_size, width / patch_size]`.
x = self.patch_emb(x)
# Pass through [ConvMixer layers](#ConvMixerLayer)
for layer in self.conv_mixer_layers:
x = layer(x)
# Classification head, to get logits
x = self.classification(x)
#
return x
+85
View File
@@ -0,0 +1,85 @@
"""
---
title: Train ConvMixer on CIFAR 10
summary: >
Train ConvMixer on CIFAR 10
---
# Train a [ConvMixer](index.html) on CIFAR 10
This script trains a ConvMixer on CIFAR 10 dataset.
This is not an attempt to reproduce the results of the paper.
The paper uses image augmentations
present in [PyTorch Image Models (timm)](https://github.com/rwightman/pytorch-image-models)
for training. We haven't done this for simplicity - which causes our validation accuracy to drop.
"""
from labml import experiment
from labml.configs import option
from labml_nn.experiments.cifar10 import CIFAR10Configs
class Configs(CIFAR10Configs):
"""
## Configurations
We use [`CIFAR10Configs`](../experiments/cifar10.html) which defines all the
dataset related configurations, optimizer, and a training loop.
"""
# Size of a patch, $p$
patch_size: int = 2
# Number of channels in patch embeddings, $h$
d_model: int = 256
# Number of [ConvMixer layers](#ConvMixerLayer) or depth, $d$
n_layers: int = 8
# Kernel size of the depth-wise convolution, $k$
kernel_size: int = 7
# Number of classes in the task
n_classes: int = 10
@option(Configs.model)
def _conv_mixer(c: Configs):
"""
### Create model
"""
from labml_nn.conv_mixer import ConvMixerLayer, ConvMixer, ClassificationHead, PatchEmbeddings
# Create ConvMixer
return ConvMixer(ConvMixerLayer(c.d_model, c.kernel_size), c.n_layers,
PatchEmbeddings(c.d_model, c.patch_size, 3),
ClassificationHead(c.d_model, c.n_classes)).to(c.device)
def main():
# Create experiment
experiment.create(name='ConvMixer', comment='cifar10')
# Create configurations
conf = Configs()
# Load configurations
experiment.configs(conf, {
# Optimizer
'optimizer.optimizer': 'Adam',
'optimizer.learning_rate': 2.5e-4,
# Training epochs and batch size
'epochs': 150,
'train_batch_size': 64,
# Simple image augmentations
'train_dataset': 'cifar10_train_augmented',
# Do not augment images for validation
'valid_dataset': 'cifar10_valid_no_augment',
})
# Set model for saving/loading
experiment.add_pytorch_models({'model': conf.model})
# Start the experiment and run the training loop
with experiment.start():
conf.run()
#
if __name__ == '__main__':
main()
+23
View File
@@ -0,0 +1,23 @@
# [Patches Are All You Need?](https://nn.labml.ai/conv_mixer/index.html)
This is a [PyTorch](https://pytorch.org) implementation of the paper
[Patches Are All You Need?](https://arxiv.org/abs/2201.09792).
ConvMixer is Similar to [MLP-Mixer](https://nn.labml.ai/transformers/mlp_mixer/index.html).
MLP-Mixer separates mixing of spatial and channel dimensions, by applying an MLP across spatial dimension
and then an MLP across the channel dimension
(spatial MLP replaces the [ViT](https://nn.labml.ai/transformers/vit/index.html) attention
and channel MLP is the [FFN](https://nn.labml.ai/transformers/feed_forward.html) of ViT).
ConvMixer uses a 1x1 convolution for channel mixing and a
depth-wise convolution for spatial mixing.
Since it's a convolution instead of a full MLP across the space, it mixes only the nearby batches in
contrast to ViT or MLP-Mixer.
Also, the MLP-mixer uses MLPs of two layers for each mixing and ConvMixer uses a single layer for each mixing.
The paper recommends removing the residual connection across the channel mixing (point-wise convolution)
and having only a residual connection over the spatial mixing (depth-wise convolution).
They also use [Batch normalization](https://nn.labml.ai/normalization/batch_norm/index.html) instead
of [Layer normalization](../normalization/layer_norm/index.html).
Here's [an experiment](https://nn.labml.ai/conv_mixer/experiment.html) that trains ConvMixer on CIFAR-10.