Click the above “Beginner’s Guide to Vision“, select to add “Star” or “Pin“
Heavyweight content delivered first-hand
Zhang Hao: Master’s student at the School of Computer Science, Nanjing University, focusing on computer vision and machine learning, particularly visual recognition and deep learning. Personal homepage:
http://lamda.nju.edu.cn/zhangh/
Original Zhihu link:
https://zhuanlan.zhihu.com/p/59205847?
This code is based on PyTorch version 1.0 and requires the following packages:
import collections
import os
import shutil
import tqdm
import numpy as np
import PIL.Image
import torch
import torchvision
Basic Configuration
Check PyTorch Version
torch.__version__ # PyTorch version
torch.version.cuda # Corresponding CUDA version
torch.backends.cudnn.version() # Corresponding cuDNN version
torch.cuda.get_device_name(0) # GPU type
Update PyTorch
PyTorch will be installed in the anaconda3/lib/python3.7/site-packages/torch/ directory.
conda update pytorch torchvision -c pytorch
Set Random Seed
torch.manual_seed(0)
torch.cuda.manual_seed_all(0)
Specify Program to Run on Specific GPU Card
Specify environment variable in the command line:
CUDA_VISIBLE_DEVICES=0,1 python train.py
Or specify in the code:
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
Check if CUDA is available:
torch.cuda.is_available()
Set cuDNN Benchmark Mode
Benchmark mode will improve computation speed, but due to randomness in computation, the results of each network feedforward may vary slightly.
torch.backends.cudnn.benchmark = True
If you want to avoid such result fluctuations, set:
torch.backends.cudnn.deterministic = True
Clear GPU Memory
Sometimes after terminating the run with Control-C, GPU memory is not released promptly and needs to be cleared manually. In PyTorch, you can:
torch.cuda.empty_cache()
Or in the command line, first use ps to find the PID of the program, and then use kill to terminate that process:
ps aux | grep python
kill -9 [pid]
Or directly reset the GPU that has not been cleared:
nvidia-smi --gpu-reset -i [gpu_id]
Tensor Processing
Basic Information of Tensor
tensor.type() # Data type
tensor.size() # Shape of the tensor. It is a subclass of Python tuple
tensor.dim() # Number of dimensions.
Data Type Conversion
# Set default tensor type. Float in PyTorch is much faster than double.
torch.set_default_tensor_type(torch.FloatTensor)
# Type conversions.
tensor = tensor.cuda()
tensor = tensor.cpu()
tensor = tensor.float()
tensor = tensor.long()
torch.Tensor and np.ndarray Conversion
# torch.Tensor -> np.ndarray.
ndarray = tensor.cpu().numpy()
# np.ndarray -> torch.Tensor.
tensor = torch.from_numpy(ndarray).float()
tensor = torch.from_numpy(ndarray.copy()).float() # If ndarray has negative stride
torch.Tensor and PIL.Image Conversion
In PyTorch, tensors default to N×D×H×W order and the data range is [0, 1], requiring transpose and normalization.
# torch.Tensor -> PIL.Image.
image = PIL.Image.fromarray(torch.clamp(tensor * 255, min=0, max=255
).byte().permute(1, 2, 0).cpu().numpy())
image = torchvision.transforms.functional.to_pil_image(tensor) # Equivalent way
# PIL.Image -> torch.Tensor.
tensor = torch.from_numpy(np.asarray(PIL.Image.open(path))
).permute(2, 0, 1).float() / 255
tensor = torchvision.transforms.functional.to_tensor(PIL.Image.open(path)) # Equivalent way
np.ndarray and PIL.Image Conversion
# np.ndarray -> PIL.Image.
image = PIL.Image.fromarray(ndarray.astype(np.uint8))
# PIL.Image -> np.ndarray.
darray = np.asarray(PIL.Image.open(path))
Extract Value from a Tensor Containing Only One Element
This is particularly useful for tracking the change of loss during training. Otherwise, this will accumulate the computation graph, causing GPU memory usage to increase.
value = tensor.item()
Tensor Reshaping
Tensor reshaping is often needed to input features from convolutional layers into fully connected layers. Compared to torch.view, torch.reshape can automatically handle cases where the input tensor is non-contiguous.
tensor = torch.reshape(tensor, shape)
Shuffle Order
tensor = tensor[torch.randperm(tensor.size(0))] # Shuffle the first dimension
Horizontal Flip
PyTorch does not support tensor[::-1] style negative stride operations; horizontal flipping can be achieved with tensor indexing.
# Assume tensor has shape N*D*H*W.
tensor = tensor[:, :, :, torch.arange(tensor.size(3) - 1, -1, -1).long()]
Copy Tensor
There are three ways to copy, corresponding to different needs.
# Operation | New/Shared memory | Still in computation graph |
tensor.clone() # | New | Yes |
tensor.detach() # | Shared | No |
tensor.detach().clone()() # | New | No |
Concatenate Tensors
Note that the difference between torch.cat and torch.stack is that torch.cat concatenates along the given dimension, while torch.stack adds a dimension. For example, when the parameters are three 10×5 tensors, the result of torch.cat is a 30×5 tensor, while the result of torch.stack is a 3×10×5 tensor.
tensor = torch.cat(list_of_tensors, dim=0)
tensor = torch.stack(list_of_tensors, dim=0)
Convert Integer Labels to One-Hot Encoding
Labels in PyTorch default to starting from 0.
N = tensor.size(0)
one_hot = torch.zeros(N, num_classes).long()
one_hot.scatter_(dim=1, index=torch.unsqueeze(tensor, dim=1), src=torch.ones(N, num_classes).long())
Get Non-Zero/Zero Elements
torch.nonzero(tensor) # Index of non-zero elements
torch.nonzero(tensor == 0) # Index of zero elements
torch.nonzero(tensor).size(0) # Number of non-zero elements
torch.nonzero(tensor == 0).size(0) # Number of zero elements
Tensor Expansion
# Expand tensor of shape 64*512 to shape 64*512*7*7.
torch.reshape(tensor, (64, 512, 1, 1)).expand(64, 512, 7, 7)
Matrix Multiplication
# Matrix multiplication: (m*n) * (n*p) -> (m*p).
result = torch.mm(tensor1, tensor2)
# Batch matrix multiplication: (b*m*n) * (b*n*p) -> (b*m*p).
result = torch.bmm(tensor1, tensor2)
# Element-wise multiplication.
result = tensor1 * tensor2
Calculate Pairwise Euclidean Distance Between Two Sets of Data
# X1 is of shape m*d.
X1 = torch.unsqueeze(X1, dim=1).expand(m, n, d)
# X2 is of shape n*d.
X2 = torch.unsqueeze(X2, dim=0).expand(m, n, d)
# dist is of shape m*n, where dist[i][j] = sqrt(|X1[i, :] - X[j, :]|^2)
dist = torch.sqrt(torch.sum((X1 - X2) ** 2, dim=2))
Model Definition
Convolutional Layer
The most commonly used convolutional layer configurations are:
conv = torch.nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=True)
conv = torch.nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=True)
If the convolutional layer configuration is complex and inconvenient to calculate the output size, you can use the following visualization tool for assistance:
Link: https://ezyang.github.io/convolution-visualizer/index.html
Global Average Pooling (GAP) Layer
gap = torch.nn.AdaptiveAvgPool2d(output_size=1)
Bilinear Pooling
X = torch.reshape(N, D, H * W) # Assume X has shape N*D*H*W
X = torch.bmm(X, torch.transpose(X, 1, 2)) / (H * W) # Bilinear pooling
assert X.size() == (N, D, D)
X = torch.reshape(X, (N, D * D))
X = torch.sign(X) * torch.sqrt(torch.abs(X) + 1e-5) # Signed-sqrt normalization
X = torch.nn.functional.normalize(X) # L2 normalization
Multi-GPU Synchronized Batch Normalization
When using torch.nn.DataParallel to run the code on multiple GPU cards, the default operation of PyTorch’s BN layer is to compute the mean and standard deviation independently on each card. Synchronized BN uses data from all cards to compute the mean and standard deviation of the BN layer, mitigating the issue of inaccurate mean and standard deviation estimation when the batch size is small, making it an effective performance enhancement technique in tasks like object detection.
Link: https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
Similar to BN Moving Average
To achieve operations similar to BN moving average, use in-place operations in the forward function to assign values to the moving average.
class BN(torch.nn.Module)
def __init__(self):
...
self.register_buffer('running_mean', torch.zeros(num_features))
def forward(self, X):
...
self.running_mean += momentum * (current - self.running_mean)
Calculate Total Number of Parameters in the Model
num_parameters = sum(torch.numel(parameter) for parameter in model.parameters())
Output Model Information Similar to Keras model.summary()
Link: https://github.com/sksq96/pytorch-summary
Model Weight Initialization
Note the difference between model.modules() and model.children(): model.modules() iterates through all sublayers of the model, while model.children() only iterates through one layer of the model.
# Common practice for initialization.
for layer in model.modules():
if isinstance(layer, torch.nn.Conv2d):
torch.nn.init.kaiming_normal_(layer.weight, mode='fan_out',
nonlinearity='relu')
if layer.bias is not None:
torch.nn.init.constant_(layer.bias, val=0.0)
elif isinstance(layer, torch.nn.BatchNorm2d):
torch.nn.init.constant_(layer.weight, val=1.0)
torch.nn.init.constant_(layer.bias, val=0.0)
elif isinstance(layer, torch.nn.Linear):
torch.nn.init.xavier_normal_(layer.weight)
if layer.bias is not None:
torch.nn.init.constant_(layer.bias, val=0.0)
# Initialization with given tensor.
layer.weight = torch.nn.Parameter(tensor)
Using Pre-trained Models for Certain Layers
Note that if the saved model is torch.nn.DataParallel, the current model also needs to be:
model.load_state_dict(torch.load('model,pth'), strict=False)
Loading a Model Saved on GPU to CPU
model.load_state_dict(torch.load('model,pth', map_location='cpu'))
Data Preparation, Feature Extraction, and Fine-tuning
Get Basic Information of Video Data
import cv2
video = cv2.VideoCapture(mp4_path)
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
num_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
fps = int(video.get(cv2.CAP_PROP_FPS))
video.release()
TSN Samples One Frame of Video per Segment
K = self._num_segments
if is_train:
if num_frames > K:
# Random index for each segment.
frame_indices = torch.randint(
high=num_frames // K, size=(K,), dtype=torch.long)
frame_indices += num_frames // K * torch.arange(K)
else:
frame_indices = torch.randint(
high=num_frames, size=(K - num_frames,), dtype=torch.long)
frame_indices = torch.sort(torch.cat((
torch.arange(num_frames), frame_indices)))[0]
else:
if num_frames > K:
# Middle index for each segment.
frame_indices = num_frames / K // 2
frame_indices += num_frames // K * torch.arange(K)
else:
frame_indices = torch.sort(torch.cat((
torch.arange(num_frames), torch.arange(K - num_frames))))[0]
assert frame_indices.size() == (K,)
return [frame_indices[i] for i in range(K)]
Extract Convolution Features from a Certain Layer of ImageNet Pre-trained Model
# VGG-16 relu5-3 feature.
model = torchvision.models.vgg16(pretrained=True).features[:-1]
# VGG-16 pool5 feature.
model = torchvision.models.vgg16(pretrained=True).features
# VGG-16 fc7 feature.
model = torchvision.models.vgg16(pretrained=True)
model.classifier = torch.nn.Sequential(*list(model.classifier.children())[:-3])
# ResNet GAP feature.
model = torchvision.models.resnet18(pretrained=True)
model = torch.nn.Sequential(collections.OrderedDict(
list(model.named_children())[:-1]))
with torch.no_grad():
model.eval()
conv_representation = model(image)
Extract Convolution Features from Multiple Layers of ImageNet Pre-trained Model
class FeatureExtractor(torch.nn.Module):
"""Helper class to extract several convolution features from the given
pre-trained model.
Attributes:
_model, torch.nn.Module.
_layers_to_extract, list<str> or set<str>
Example:
>>> model = torchvision.models.resnet152(pretrained=True)
>>> model = torch.nn.Sequential(collections.OrderedDict(
list(model.named_children())[:-1]))
>>> conv_representation = FeatureExtractor(
pretrained_model=model,
layers_to_extract={'layer1', 'layer2', 'layer3', 'layer4'})(image)
"""
def __init__(self, pretrained_model, layers_to_extract):
torch.nn.Module.__init__(self)
self._model = pretrained_model
self._model.eval()
self._layers_to_extract = set(layers_to_extract)
def forward(self, x):
with torch.no_grad():
conv_representation = []
for name, layer in self._model.named_children():
x = layer(x)
if name in self._layers_to_extract:
conv_representation.append(x)
return conv_representation
Other Pre-trained Models
Link: https://github.com/Cadene/pretrained-models.pytorch
Fine-tune Fully Connected Layer
model = torchvision.models.resnet18(pretrained=True)
for param in model.parameters():
param.requires_grad = False
model.fc = nn.Linear(512, 100) # Replace the last fc layer
optimizer = torch.optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9, weight_decay=1e-4)
Fine-tune Fully Connected Layer with Higher Learning Rate, Convolution Layer with Lower Learning Rate
model = torchvision.models.resnet18(pretrained=True)
finetuned_parameters = list(map(id, model.fc.parameters()))
conv_parameters = (p for p in model.parameters() if id(p) not in finetuned_parameters)
parameters = [{'params': conv_parameters, 'lr': 1e-3},
{'params': model.fc.parameters()}]
optimizer = torch.optim.SGD(parameters, lr=1e-2, momentum=0.9, weight_decay=1e-4)
Model Training
Common Training and Validation Data Preprocessing
Among them, the ToTensor operation will convert PIL.Image or np.ndarray with shape H×W×D and data range [0, 255] into torch.Tensor with shape D×H×W and data range [0.0, 1.0].
train_transform = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(size=224,
scale=(0.08, 1.0)),
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225)),
])
val_transform = torchvision.transforms.Compose([
torchvision.transforms.Resize(224),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(mean=(0.485, 0.456, 0.406),
std=(0.229, 0.224, 0.225)),
])
Basic Code Framework for Training
for t in epoch(80):
for images, labels in tqdm.tqdm(train_loader, desc='Epoch %3d' % (t + 1)):
images, labels = images.cuda(), labels.cuda()
scores = model(images)
loss = loss_function(scores, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Label Smoothing
for images, labels in train_loader:
images, labels = images.cuda(), labels.cuda()
N = labels.size(0)
# C is the number of classes.
smoothed_labels = torch.full(size=(N, C), fill_value=0.1 / (C - 1)).cuda()
smoothed_labels.scatter_(dim=1, index=torch.unsqueeze(labels, dim=1), value=0.9)
score = model(images)
log_prob = torch.nn.functional.log_softmax(score, dim=1)
loss = -torch.sum(log_prob * smoothed_labels) / N
optimizer.zero_grad()
loss.backward()
optimizer.step()
Mixup
beta_distribution = torch.distributions.beta.Beta(alpha, alpha)
for images, labels in train_loader:
images, labels = images.cuda(), labels.cuda()
# Mixup images.
lambda_ = beta_distribution.sample([]).item()
index = torch.randperm(images.size(0)).cuda()
mixed_images = lambda_ * images + (1 - lambda_) * images[index, :]
# Mixup loss.
scores = model(mixed_images)
loss = (lambda_ * loss_function(scores, labels)
+ (1 - lambda_) * loss_function(scores, labels[index]))
optimizer.zero_grad()
loss.backward()
optimizer.step()
L1 Regularization
l1_regularization = torch.nn.L1Loss(reduction='sum')
loss = ... # Standard cross-entropy loss
for param in model.parameters():
loss += torch.sum(torch.abs(param))
loss.backward()
Do Not Apply L2 Regularization/Weight Decay on Bias Terms
bias_list = (param for name, param in model.named_parameters() if name[-4:] == 'bias')
others_list = (param for name, param in model.named_parameters() if name[-4:] != 'bias')
parameters = [{'parameters': bias_list, 'weight_decay': 0},
{'parameters': others_list}]
optimizer = torch.optim.SGD(parameters, lr=1e-2, momentum=0.9, weight_decay=1e-4)
Gradient Clipping
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=20)
Calculate Accuracy of Softmax Output
score = model(images)
prediction = torch.argmax(score, dim=1)
num_correct = torch.sum(prediction == labels).item()
accuruacy = num_correct / labels.size(0)
Visualize the Computation Graph of Model Feedforward
Link: https://github.com/szagoruyko/pytorchviz
Visualize Learning Curves
There are two options: Facebook’s own Visdom and Tensorboard.
https://github.com/facebookresearch/visdom
https://github.com/lanpa/tensorboardX
# Example using Visdom.
vis = visdom.Visdom(env='Learning curve', use_incoming_socket=False)
assert self._visdom.check_connection()
self._visdom.close()
options = collections.namedtuple('Options', ['loss', 'acc', 'lr'])(
loss={'xlabel': 'Epoch', 'ylabel': 'Loss', 'showlegend': True},
acc={'xlabel': 'Epoch', 'ylabel': 'Accuracy', 'showlegend': True},
lr={'xlabel': 'Epoch', 'ylabel': 'Learning rate', 'showlegend': True})
for t in epoch(80):
tran(...)
val(...)
vis.line(X=torch.Tensor([t + 1]), Y=torch.Tensor([train_loss]),
name='train', win='Loss', update='append', opts=options.loss)
vis.line(X=torch.Tensor([t + 1]), Y=torch.Tensor([val_loss]),
name='val', win='Loss', update='append', opts=options.loss)
vis.line(X=torch.Tensor([t + 1]), Y=torch.Tensor([train_acc]),
name='train', win='Accuracy', update='append', opts=options.acc)
vis.line(X=torch.Tensor([t + 1]), Y=torch.Tensor([val_acc]),
name='val', win='Accuracy', update='append', opts=options.acc)
vis.line(X=torch.Tensor([t + 1]), Y=torch.Tensor([lr]),
win='Learning rate', update='append', opts=options.lr)
Get Current Learning Rate
# If there is one global learning rate (which is the common case).
lr = next(iter(optimizer.param_groups))['lr']
# If there are multiple learning rates for different layers.
all_lr = []
for param_group in optimizer.param_groups:
all_lr.append(param_group['lr'])
Learning Rate Decay
# Reduce learning rate when validation accuracy plateaus.
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', patience=5, verbose=True)
for t in range(0, 80):
train(...); val(...)
scheduler.step(val_acc)
# Cosine annealing learning rate.
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=80)
# Reduce learning rate by 10 at given epochs.
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[50, 70], gamma=0.1)
for t in range(0, 80):
scheduler.step()
train(...); val(...)
# Learning rate warmup by 10 epochs.
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda t: t / 10)
for t in range(0, 10):
scheduler.step()
train(...); val(...)
Save and Load Checkpoints
Note that to be able to resume training, we need to save both the model and optimizer states, as well as the current training epoch.
# Save checkpoint.
is_best = current_acc > best_acc
best_acc = max(best_acc, current_acc)
checkpoint = {
'best_acc': best_acc,
'epoch': t + 1,
'model': model.state_dict(),
'optimizer': optimizer.state_dict(),
}
model_path = os.path.join('model', 'checkpoint.pth.tar')
torch.save(checkpoint, model_path)
if is_best:
shutil.copy('checkpoint.pth.tar', model_path)
# Load checkpoint.
if resume:
model_path = os.path.join('model', 'checkpoint.pth.tar')
assert os.path.isfile(model_path)
checkpoint = torch.load(model_path)
best_acc = checkpoint['best_acc']
start_epoch = checkpoint['epoch']
model.load_state_dict(checkpoint['model'])
optimizer.load_state_dict(checkpoint['optimizer'])
print('Load checkpoint at epoch %d.' % start_epoch)
Calculate Accuracy, Precision, and Recall
# data['label'] and data['prediction'] are groundtruth label and prediction
# for each image, respectively.
accuracy = np.mean(data['label'] == data['prediction']) * 100
# Compute precision and recall for each class.
for c in range(len(num_classes)):
tp = np.dot((data['label'] == c).astype(int),
(data['prediction'] == c).astype(int))
tp_fp = np.sum(data['prediction'] == c)
tp_fn = np.sum(data['label'] == c)
precision = tp / tp_fp * 100
recall = tp / tp_fn * 100
Other PyTorch Considerations
Model Definition
-
It is recommended to use the torch.nn module to define layers with parameters and pooling layers, while activation functions should use torch.nn.functional directly. The difference between the torch.nn module and torch.nn.functional is that the torch.nn module calls torch.nn.functional at the bottom level during computation, but the torch.nn module includes the parameters of that layer and can handle both training and testing network states. When using torch.nn.functional, be aware of the network state, such as:
def forward(self, x):
...
x = torch.nn.functional.dropout(x, p=0.5, training=self.training)
-
Switch network states with model.train() and model.eval() before model(x).
-
Code blocks that do not require gradient calculation should be enclosed in with torch.no_grad(). The difference between model.eval() and torch.no_grad() is that model.eval() switches the network to the testing state, for example, BN and dropout use different computation methods during training and testing. torch.no_grad() disables the automatic differentiation mechanism of PyTorch tensors to reduce memory usage and speed up computation, and the results cannot perform loss.backward().
-
The input to torch.nn.CrossEntropyLoss does not need to go through Softmax. torch.nn.CrossEntropyLoss is equivalent to torch.nn.functional.log_softmax + torch.nn.NLLLoss.
-
Use optimizer.zero_grad() before loss.backward() to clear accumulated gradients. optimizer.zero_grad() and model.zero_grad() have the same effect.
PyTorch Performance and Debugging
-
In torch.utils.data.DataLoader, try to set pin_memory=True. For particularly small datasets like MNIST, setting pin_memory=False may actually be faster. The setting of num_workers needs to be found through experimentation for the fastest value.
-
Use del to promptly delete unnecessary intermediate variables, saving GPU memory.
-
Using in-place operations can save GPU memory, such as:
x = torch.nn.functional.relu(x, inplace=True)
-
Reduce data transfer between CPU and GPU. For example, if you want to know the loss and accuracy for each mini-batch in an epoch, accumulating them in GPU and transferring them back to CPU at the end of the epoch is faster than transferring each mini-batch individually.
-
Using half-precision floating points with half() can lead to some speed improvements, but specific efficiency depends on the GPU model. Be cautious of stability issues due to low numerical precision.
-
Frequently use assert tensor.size() == (N, D, H, W) as a debugging method to ensure the tensor dimensions match your expectations.
-
Avoid using one-dimensional tensors except for labels y; use n*1 two-dimensional tensors instead to avoid unexpected results from one-dimensional tensor calculations.
-
Profile the time spent in different parts of the code:
with torch.autograd.profiler.profile(enabled=True, use_cuda=False) as profile:
...
print(profile)
Or run in the command line:
python -m torch.utils.bottleneck main.py
Acknowledgments
Thanks to @someflow and @El tnoto for their corrections. Due to the author’s limited knowledge and time constraints, errors in the code are inevitable. Readers are encouraged to provide criticism and corrections.
References
-
PyTorch official code: pytorch/examples (https://link.zhihu.com/?target=https%3A//github.com/pytorch/examples)
-
PyTorch forum: PyTorch Forums (https://link.zhihu.com/?target=https%3A//discuss.pytorch.org/latest%3Forder%3Dviews)
-
PyTorch documentation: http://pytorch.org/docs/stable/index.html (https://link.zhihu.com/?target=http%3A//pytorch.org/docs/stable/index.html)
-
Other public implementations based on PyTorch cannot be listed one by one.
Discussion Group
Welcome to join the reader group of the public account to communicate with peers. Currently, there are WeChat groups for SLAM, 3D vision, sensors, autonomous driving, computational photography, detection, segmentation, recognition, medical imaging, GAN, algorithm competitions and more (will gradually subdivide in the future).Please scan the WeChat ID below to join the group, and note: “nickname + school/company + research direction”, for example: “Zhang San + Shanghai Jiao Tong University + Vision SLAM”. Please follow the format for notes, otherwise you will not be approved. Successful additions will be invited to relevant WeChat groups based on research direction. Please do not send ads in the group, otherwise you will be removed from the group, thank you for your understanding~