大图推理依赖 CUDA 缓冲,无 GPU 时该流程无法直接运行;该库定位为补充而非替代 Catalyst、Ignite、fast.ai 等高层框架;README 未说明依赖版本约束,建议自行锁定 torch 版本。

pytorch-toolbelt
BloodAxeMIT长期未更新一套 PyTorch 扩展工具集,提供数据增强、损失函数、训练流水线等常用组件,帮你在做图像分割、检测和分类的原型研发与 Kaggle 比赛时少写重复代码。
PyTorch extensions for fast R&D prototyping and Kaggle farming
pip install pytorch_toolbeltREADME 未说明具体显存要求;大图推理示例使用 CUDA 张量缓冲,需要 NVIDIA GPU 和 CUDA 环境。
PyTorch 扩展工具库,帮你快速搭模型、跑 TTA 和大图推理,省去重复造轮子
一套 PyTorch 扩展工具集,提供数据增强、损失函数、训练流水线等常用组件,帮你在做图像分割、检测和分类的原型研发与 Kaggle 比赛时少写重复代码。
核心能力
- 提供编码器-解码器架构,可自由组合 U-Net、FPN 等模型
- 内置 CoordConv、SCSE、Hypercolumn、深度可分离卷积等模块
- 支持 GPU 上的 TTA,含分类水平翻转和分割 D4 增强
- 用切片加 CUDA 缓冲合并,在 GPU 上推理 5000x5000 大图
- 提供 Focal、Lovasz、Dice、Jaccard、Wing 等多种损失函数
容易踩的坑
- 0.8.0Pytorch Toolbelt 0.8.0最新正式版2024-11-21 · 2024-11-21 20:06 UTC
What's Changed
-
Feature/modules overhaul by @BloodAxe in https://github.com/BloodAxe/pytorch-toolbelt/pull/92
-
Feature/modules overhaul by @BloodAxe in https://github.com/BloodAxe/pytorch-toolbelt/pull/96
-
Release 0.7.0 by @BloodAxe in https://github.com/BloodAxe/pytorch-toolbelt/pull/97
-
Merge pull request #97 from BloodAxe/develop by @BloodAxe in https://github.com/BloodAxe/pytorch-toolbelt/pull/98
-
Reduced focal loss implementation is incorrect for binary case by @MrPrajwalB in https://github.com/BloodAxe/pytorch-toolbelt/pull/102
New Contributors
- @MrPrajwalB made their first contribution in https://github.com/BloodAxe/pytorch-toolbelt/pull/102
Full Changelog: https://github.com/BloodAxe/pytorch-toolbelt/compare/0.7.0...0.8.0
-
- 0.7.0Pytorch Toolbelt 0.7.02023-08-19 · 2023-08-19 14:26 UTC
查看更新说明
New stuff
-
All encoders & decoders & heads are now inherit from
HasOutputFeaturesSpecificationinterface to query number of output channels and strides this module outputs. -
New loss class
QualityFocalLossfrom https://arxiv.org/abs/2006.04388 -
New function
pad_tensor_to_size-A generic padding function for N-dimensional tensors [B,C, ...] shape. -
Added
DropPathlayer (aka DropConnect) -
Pretrained weights for SegFormer backbones
-
first_class_background_initfor initializing last output convolution/linear block to have zeros in weights and bias layer set to[logit(bg_prob), logit(1-bg_prob), ...] -
New function
instantiate_normalization_blockto create normalization layer by name. This is used in some decoder layers / heads. -
Improvements
-
Improve numeric accuracy of
focal_loss_with_logitsfunction by explicitly disabling AMP autocast for this function and casting preds & targets tofloat32. -
MultiscaleTTAnow allows setting interpolationmodeandalign_cornersfor resizing input and predictions. -
BinaryFocalLossnow has__repr__ -
name_for_stridenow acceptsstrideargument to be None. In this case the function is noop and returns input argumentname. -
RandomSubsetDatasetnow takes optionalweightsargument to select samples with given probability.
Bugfixes
- Implementation of
get_collate_fnforRandomSubsetDatasetis not correct and returns collate function instead of calling it
Breaking Changes
-
Signature of decoders changes to require first argument
input_specto be of typeFeatureMapsSpecification. -
Rewritten BiFPN decoder to support arbitrary number of input feature maps, user-defined normalization & activation & BiFPN block.
-
Rewritten UNetDecoder to allow setting upsample block as string type
-
WeightedLossandJointLossclasses has been removed. If your code was using these classes here they are - copy paste them to your project and live happily, but I strongly suggest to use modern deep learning frameworks that support defining losses from configuration files.
class WeightedLoss(_Loss): """Wrapper class around loss function that applies weighted with fixed factor. This class helps to balance multiple losses if they have different scales """ def __init__(self, loss, weight=1.0): super().__init__() self.loss = loss self.weight = weight def forward(self, *input): return self.loss(*input) * self.weight class JointLoss(_Loss): """ Wrap two loss functions into one. This class computes a weighted sum of two losses. """ def __init__(self, first: nn.Module, second: nn.Module, first_weight=1.0, second_weight=1.0): super().__init__() self.first = WeightedLoss(first, first_weight) self.second = WeightedLoss(second, second_weight) def forward(self, *input): return self.first(*input) + self.second(*input) -
- 0.6.2Pytorch Toolbelt 0.6.22022-12-25 · 2022-12-25 21:39 UTC
这个版本没有写更新说明。
- 0.6.1Pytorch Toolbelt 0.6.12022-10-25 · 2022-10-25 15:59 UTC
查看更新说明
Pytorch Toolbelt 0.6.1
- Fixes to CI actions
- Adding support for python 3.10
- Bugfix in DatasetMeanStdCalculator when mask argument was used
- 0.6.0PyTorch Toolbelt 0.6.02022-10-20 · 2022-10-20 21:13 UTC
查看更新说明
Breaking Changes
All catalyst-related callbacks are moved to fork of the Catalyst library
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Important Update

On February 24th, 2022, Russia declared war and invaded peaceful Ukraine. After the annexation of Crimea and the occupation of the Donbas region, Putin's regime decided to destroy Ukrainian nationality. Ukrainians show fierce resistance and demonstrate to the entire world what it's like to fight for the nation's independence.
Ukraine's government launched a website to help russian mothers, wives & sisters find their beloved ones killed or captured in Ukraine - https://200rf.com & https://t.me/rf200_now (Telegram channel). Our goal is to inform those still in Russia & Belarus, so they refuse to assault Ukraine.
Help us get maximum exposure to what is happening in Ukraine, violence, and inhuman acts of terror that the "Russian World" has brought to Ukraine. This is a comprehensive Wiki on how you can help end this war: https://how-to-help-ukraine-now.super.site/
Official channels
- Official account of the Parliament of Ukraine
- Ministry of Defence
- Office of the president
- Cabinet of Ministers of Ukraine
- Center of strategic communications
- Minister of Foreign Affairs of Ukraine
Glory to Ukraine!
Pytorch-toolbelt
A pytorch-toolbelt is a Python library with a set of bells and whistles for PyTorch for fast R&D prototyping and Kaggle farming:
What's inside
- Easy model building using flexible encoder-decoder architecture.
- Modules: CoordConv, SCSE, Hypercolumn, Depthwise separable convolution and more.
- GPU-friendly test-time augmentation TTA for segmentation and classification
- GPU-friendly inference on huge (5000x5000) images
- Every-day common routines (fix/restore random seed, filesystem utils, metrics)
- Losses: BinaryFocalLoss, Focal, ReducedFocal, Lovasz, Jaccard and Dice losses, Wing Loss and more.
- Extras for Catalyst library (Visualization of batch predictions, additional metrics)
Showcase: Catalyst, Albumentations, Pytorch Toolbelt example: Semantic Segmentation @ CamVid
Why
Honest answer is "I needed a convenient way to re-use code for my Kaggle career". During 2018 I achieved a Kaggle Master badge and this been a long path. Very often I found myself re-using most of the old pipelines over and over again. At some point it crystallized into this repository.
This lib is not meant to replace catalyst / ignite / fast.ai high-level frameworks. Instead it's designed to complement them.
Installation
pip install pytorch_toolbelt
How do I ...
Model creation
Create Encoder-Decoder U-Net model
Below a code snippet that creates vanilla U-Net model for binary segmentation.
By design, both encoder and decoder produces a list of tensors, from fine (high-resolution, indexed 0) to coarse (low-resolution) feature maps.
Access to all intermediate feature maps is beneficial if you want to apply deep supervision losses on them or encoder-decoder of object detection task,
where access to intermediate feature maps is necessary.
from torch import nn
from pytorch_toolbelt.modules import encoders as E
from pytorch_toolbelt.modules import decoders as D
class UNet(nn.Module):
def __init__(self, input_channels, num_classes):
super().__init__()
self.encoder = E.UnetEncoder(in_channels=input_channels, out_channels=32, growth_factor=2)
self.decoder = D.UNetDecoder(self.encoder.channels, decoder_features=32)
self.logits = nn.Conv2d(self.decoder.channels[0], num_classes, kernel_size=1)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return self.logits(x[0])
Create Encoder-Decoder FPN model with pretrained encoder
Similarly to previous example, you can change decoder to FPN with contatenation.
from torch import nn
from pytorch_toolbelt.modules import encoders as E
from pytorch_toolbelt.modules import decoders as D
class SEResNeXt50FPN(nn.Module):
def __init__(self, num_classes, fpn_channels):
super().__init__()
self.encoder = E.SEResNeXt50Encoder()
self.decoder = D.FPNCatDecoder(self.encoder.channels, fpn_channels)
self.logits = nn.Conv2d(self.decoder.channels[0], num_classes, kernel_size=1)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return self.logits(x[0])
Change number of input channels for the Encoder
All encoders from pytorch_toolbelt supports changing number of input channels. Simply call encoder.change_input_channels(num_channels) and first convolution layer will be changed.
Whenever possible, existing weights of convolutional layer will be re-used (in case new number of channels is greater than default, new weight tensor will be padded with randomly-initialized weigths).
Class method returns self, so this call can be chained.
from pytorch_toolbelt.modules import encoders as E
encoder = E.SEResnet101Encoder()
encoder = encoder.change_input_channels(6)
Misc
Count number of parameters in encoder/decoder and other modules
When designing a model and optimizing number of features in neural network, I found it's quite useful to print number of parameters in high-level blocks (like encoder and decoder).
Here is how to do it with pytorch_toolbelt:
from torch import nn
from pytorch_toolbelt.modules import encoders as E
from pytorch_toolbelt.modules import decoders as D
from pytorch_toolbelt.utils import count_parameters
class SEResNeXt50FPN(nn.Module):
def __init__(self, num_classes, fpn_channels):
super().__init__()
self.encoder = E.SEResNeXt50Encoder()
self.decoder = D.FPNCatDecoder(self.encoder.channels, fpn_channels)
self.logits = nn.Conv2d(self.decoder.channels[0], num_classes, kernel_size=1)
def forward(self, x):
x = self.encoder(x)
x = self.decoder(x)
return self.logits(x[0])
net = SEResNeXt50FPN(1, 128)
print(count_parameters(net))
# Prints {'total': 34232561, 'trainable': 34232561, 'encoder': 25510896, 'decoder': 8721536, 'logits': 129}
Compose multiple losses
There are multiple ways to combine multiple losses, and high-level DL frameworks like Catalyst offers way more flexible way to achieve this, but here's 100%-pure PyTorch implementation of mine:
from pytorch_toolbelt import losses as L
# Creates a loss function that is a weighted sum of focal loss
# and lovasz loss with weigths 1.0 and 0.5 accordingly.
loss = L.JointLoss(L.FocalLoss(), L.LovaszLoss(), 1.0, 0.5)
TTA / Inferencing
Apply Test-time augmentation (TTA) for the model
Test-time augmetnation (TTA) can be used in both training and testing phases.
from pytorch_toolbelt.inference import tta
model = UNet()
# Truly functional TTA for image classification using horizontal flips:
logits = tta.fliplr_image2label(model, input)
# Truly functional TTA for image segmentation using D4 augmentation:
logits = tta.d4_image2mask(model, input)
Inference on huge images:
Quite often, there is a need to perform image segmentation for enormously big image (5000px and more). There are a few problems with such a big pixel arrays:
- There are size limitations on maximum size of CUDA tensors (Concrete numbers depends on driver and GPU version)
- Heavy CNNs architectures may eat up all available GPU memory with ease when inferencing relatively small 1024x1024 images, leaving no room to bigger image resolution.
One of the solutions is to slice input image into tiles (optionally overlapping) and feed each through model and concatenate the results back. In this way you can guarantee upper limit of GPU ram usage, while keeping ability to process arbitrary-sized images on GPU.
import numpy as np
from torch.utils.data import DataLoader
import cv2
from pytorch_toolbelt.inference.tiles import ImageSlicer, CudaTileMerger
from pytorch_toolbelt.utils.torch_utils import tensor_from_rgb_image, to_numpy
image = cv2.imread('really_huge_image.jpg')
model = get_model(...)
# Cut large image into overlapping tiles
tiler = ImageSlicer(image.shape, tile_size=(512, 512), tile_step=(256, 256))
# HCW -> CHW. Optionally, do normalization here
tiles = [tensor_from_rgb_image(tile) for tile in tiler.split(image)]
# Allocate a CUDA buffer for holding entire mask
merger = CudaTileMerger(tiler.target_shape, 1, tiler.weight)
# Run predictions for tiles and accumulate them
for tiles_batch, coords_batch in DataLoader(list(zip(tiles, tiler.crops)), batch_size=8, pin_memory=True):
tiles_batch = tiles_batch.float().cuda()
pred_batch = model(tiles_batch)
merger.integrate_batch(pred_batch, coords_batch)
# Normalize accumulated mask and convert back to numpy
merged_mask = np.moveaxis(to_numpy(merger.merge()), 0, -1).astype(np.uint8)
merged_mask = tiler.crop_to_orignal_size(merged_mask)
Advanced examples
Citation
@misc{Khvedchenya_Eugene_2019_PyTorch_Toolbelt,
author = {Khvedchenya, Eugene},
title = {PyTorch Toolbelt},
year = {2019},
publisher = {GitHub},
journal = {GitHub repository},
howpublished = {\url{https://github.com/BloodAxe/pytorch-toolbelt}},
commit = {cc5e9973cdb0dcbf1c6b6e1401bf44b9c69e13f3}
}