计算机网络(1) - IP地址

IP地址

IP地址(Internet Protocol Address)是用于标识互联网上每台设备的唯一地址。它允许设备之间进行通信和数据传输。IP地址由32位(IPv4)或128位(IPv6)的二进制数构成,通常以点分十进制(IPv4)或冒号分隔的十六进制(IPv6)的形式表示。

IPv4地址

IPv4地址由四组数字组成,每组数字的范围是0到255,例如:192.168.1.1。这四组数字分别代表了IP地址的四个八位段(octet),每八位段用一个点(.)分隔。

IP地址根据其结构被分为A、B、C、D和E五类,其中A、B、C类是最常见的用于公共网络的地址类别。D类用于多播,E类用于实验和未来使用。

A类地址:范围从0.0.0.0到127.255.255.255,默认子网掩码为255.0.0.0(/8)。
B类地址:范围从128.0.0.0到191.255.255.255,默认子网掩码为255.255.0.0(/16)。
C类地址:范围从192.0.0.0到223.255.255.255,默认子网掩码为255.255.255.0(/24)。
D类地址:范围从224.0.0.0到239.255.255.255,用于多播。
E类地址:范围从240.0.0.0到255.255.255.255,保留用于实验和未来使用。

IPv6地址

IPv6地址是为了解决IPv4地址耗尽问题而设计的下一代IP协议。IPv6地址由八组四个十六进制数构成,例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334。这些十六进制数用冒号(:)分隔。

算法(5)- 比特运算

比特运算

比特运算(Bitwise operations)是直接对整数的二进制位进行操作的运算。在Python中,比特运算符可以用来执行按位与(AND)、按位或(OR)、按位异或(XOR)、按位非(NOT)、左移(左移位)和右移(右移位)等操作。

Brian Kernighan算法

Brian Kernighan算法是一种用于高效计算一个整数中二进制表示下1的个数的算法。这个算法的核心思想是利用位运算来快速减少计数的过程。算法基于这样一个观察:对于任意一个非零整数n,n和n-1进行按位与操作(n & (n - 1))的结果会将n的二进制表示中最右边的一个1变为0。

位运算的技巧

通过与1进行按位与操作可以取得最右位并且判断一个数是奇数还是偶数。通过与(n-1)进行按位与操作可以清零最低位的1。对于十进制整数 x,我们可以用 x & (1 << k) 来判断 x 二进制表示的第 k 位(最低位为第 0 位)是否为 1。

参考文献

https://leetcode.cn/problems/counting-bits/solutions/627418/bi-te-wei-ji-shu-by-leetcode-solution-0t1i/?envType=study-plan-v2&envId=leetcode-75

https://leetcode.cn/problems/minimum-flips-to-make-a-or-b-equal-to-c/solutions/101777/huo-yun-suan-de-zui-xiao-fan-zhuan-ci-shu-by-lee-2/?envType=study-plan-v2&envId=leetcode-75

算法(4)- 前缀树

前缀树

前缀树(Trie树),也称为字典树或单词查找树,是一种树形数据结构,专门用于高效存储和检索字符串集合中的词项。

前缀树是一种多叉树结构,其中每个节点代表一个字符串前缀,从根节点到任一节点的路径上的字符序列构成该节点对应的前缀。

前缀树的应用

搜索引擎使用前缀树来快速检索和匹配用户输入的查询词。当用户输入一个查询词时,搜索引擎可以从前缀树的根节点开始,根据查询词中的字符逐个向下遍历节点,直到找到匹配的单词或其前缀。

搜索引擎可以利用前缀树提供自动补全建议和拼写检查。由于前缀树存储了大量的单词和它们的前缀,搜索引擎可以快速地根据用户已经输入的部分单词(前缀)给出完整的单词建议,或者指出拼写错误并提供正确的拼写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Trie:
def __init__(self):
self.children = [None] * 26
self.isEnd = False

def searchPrefix(self, prefix: str) -> "Trie":
node = self
for ch in prefix:
ch = ord(ch) - ord("a")
if not node.children[ch]:
return None
node = node.children[ch]
return node

def insert(self, word: str) -> None:
node = self
for ch in word:
ch = ord(ch) - ord("a")
if not node.children[ch]:
node.children[ch] = Trie()
node = node.children[ch]
node.isEnd = True

def search(self, word: str) -> bool:
node = self.searchPrefix(word)
return node is not None and node.isEnd

def startsWith(self, prefix: str) -> bool:
return self.searchPrefix(prefix) is not None

参考文献

链接:https://leetcode.cn/problems/implement-trie-prefix-tree/solutions/1/shi-xian-trie-qian-zhui-shu-by-leetcode-ti500/

算法(3)- 堆/优先级队列

堆是一种特殊的树形数据结构,通常使用数组来实现。堆具有以下特性:堆是一棵完全二叉树(Complete Binary Tree),即除了最后一层外,每一层都被填满,最后一层的节点都靠左排列。

最大堆(Max Heap):父节点的值总是大于或等于其子节点的值。

最小堆(Min Heap):父节点的值总是小于或等于其子节点的值。

优先级队列

优先级队列(Priority Queue)是一种特殊的队列,其中每个元素都有一个优先级。元素的出队顺序取决于其优先级,而不是进入队列的先后顺序。

深度学习(3) - 计算框架PyTorch

PyTorch

张量

张量是一种专门的数据结构,与数组和矩阵非常相似。张量类似于 NumPy 的 ndarrays,但张量可以在 GPU 或其他硬件加速器上运行,张量也针对自动微分进行了优化。

默认情况下,张量是在 CPU 上创建的。我们需要使用 .to 方法将张量显式地移动到 GPU(在检查 GPU 是否可用后)。请记住,跨设备复制大型张量在时间和内存方面可能代价高昂!

将结果存储到操作数中的操作称为就地操作。它们用 后缀表示。例如:x.copy(y)、x.t_() 将会改变 x。

数据加载

PyTorch 提供了两个数据基本类型:torch.utils.data.DataLoader 和 torch.utils.data.Dataset,它们使您可以使用预加载的数据集以及您自己的数据。

神经网络

torch.nn 命名空间提供了构建您自己的神经网络所需的所有构建块。PyTorch 中的每个模块都是 nn.Module 的子类。

自动微分

在训练神经网络时,最常用的算法是 反向传播。在此算法中,参数(模型权重)会根据损失函数相对于给定参数的 梯度 进行调整。

为了计算这些梯度,PyTorch 有一个内置的微分引擎,称为 torch.autograd。它支持任何计算图的梯度自动计算。当我们需要计算损失函数相对于这些变量的梯度,我们设置了这些张量的requiresgrad 属性。可以在创建张量时设置requires_grad的值,或者之后使用x.requires_grad(True)方法。要计算这些导数,我们调用loss.backward(),然后从w.grad和b.grad中检索值。

要阻止一个张量被跟踪历史,可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。为了防止跟踪历史记录(和使用内存),可以将代码块包装在 with torch.no_grad(): 中。

优化

在训练循环中,优化分三个步骤进行:
调用optimizer.zero_grad()重置模型参数的梯度。默认情况下,梯度会累加;为了防止重复计数,我们在每次迭代中显式地将其归零。

使用loss.backward()反向传播预测损失。PyTorch 将损失相对于每个参数的梯度存储起来。

在获得梯度后,我们调用optimizer.step()根据反向传播中收集的梯度调整参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
# Set the model to training mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.train()
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)

# Backpropagation
loss.backward()
optimizer.step()
optimizer.zero_grad()

if batch % 100 == 0:
loss, current = loss.item(), batch * batch_size + len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
# Set the model to evaluation mode - important for batch normalization and dropout layers
# Unnecessary in this situation but added for best practices
model.eval()
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0

# Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode
# also serves to reduce unnecessary gradient computations and memory usage for tensors with requires_grad=True
with torch.no_grad():
for X, y in dataloader:
pred = model(X)
test_loss += loss_fn(pred, y).item()
correct += (pred.argmax(1) == y).type(torch.float).sum().item()

test_loss /= num_batches
correct /= size
print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

保存和加载模型

保存和加载模型权重

PyTorch 模型将学习到的参数存储在内部状态字典中,称为 state_dict。这些可以通过 torch.save 方法持久化。

1
2
model = models.vgg16(weights='IMAGENET1K_V1')
torch.save(model.state_dict(), 'model_weights.pth')

要加载模型权重,您需要首先创建一个相同模型的实例,然后使用 load_state_dict() 方法加载参数。

1
2
3
model = models.vgg16() # we do not specify ``weights``, i.e. create untrained model
model.load_state_dict(torch.load('model_weights.pth', weights_only=True))
model.eval()

注意请务必在推理之前调用 model.eval() 方法,以将 dropout 和批归一化层设置为评估模式。如果不这样做,将产生不一致的推理结果。

保存和加载具有形状的模型

1
2
torch.save(model, 'model.pth')
model = torch.load('model.pth', weights_only=False),

参考文献

https://pytorch.ac.cn/tutorials/beginner/basics/tensor_tutorial.html

Recommender Systems

Recommender Systems

Collaborative Filtering

In a broad sense, it is the process of filtering for information or patterns using techniques involving collaboration among multiple users, agents, and data sources. Overall, CF techniques can be categorized into: memory-based CF, model-based CF, and their hybrid.

Matrix Factorization

Matrix factorization is a class of collaborative filtering models. Specifically, the model factorizes the user-item interaction matrix (e.g., rating matrix) into the product of two lower-rank matrices, capturing the low-rank structure of the user-item interactions.

Let $\mathbf{R} \in \mathbb{R}^{m \times n}$ denote the interaction matrix with m users and n items and the values of $\mathbf{R}$
represent explicit ratings. The user-item interaction will be factorized into a user latent matrix $\mathbf{P} \in \mathbb{R}^{m \times k}$ and an item latent matrix $\mathbf{Q} \in \mathbb{R}^{n \times k}$. For a given item
i, the elements of $\mathbf{q}_i$
measure the extent to which the item possesses those characteristics such as the genres and languages of a movie. For a given user u
, the elements of $\mathbf{p}_u$
measure the extent of interest the user has in items’ corresponding characteristics.
The predicted ratings can be estimated by

One major problem of this prediction rule is that users/items biases can not be modeled, so

Then, we train the matrix factorization model by minimizing the mean squared error between predicted rating scores and real rating scores.

where $\lambda$ denotes the regularization rate.

AutoRec

Although the matrix factorization model achieves decent performance on the rating prediction task, it is essentially a linear model. Thus, such models are not capable of capturing complex nonlinear and intricate relationships that may be predictive of users’ preferences.
AutoRec identifies collaborative filtering (CF) with an autoencoder architecture and aims to integrate nonlinear transformations into CF on the basis of explicit feedback. On the other hand, AutoRec differs from a traditional autoencoder: rather than learning the hidden representations, AutoRec focuses on learning/reconstructing the output layer. It uses a partially observed interaction matrix as input, aiming to reconstruct a completed rating matrix.

Let $\mathbf{R}_{*i}$
denote the $i^\textrm{th}$
column of the rating matrix, where unknown ratings are set to zeros by default.
The neural architecture is defined as:

The following objective function aims to minimize the reconstruction error:

where $| \cdot |_{\mathcal{O}}$
means only the contribution of observed ratings are considered, that is, only weights that are associated with observed inputs are updated during back-propagation.

Personalized Ranking

In general, personalized ranking models can be optimized with pointwise, pairwise or listwise approaches. Pointwise approaches considers a single interaction at a time and train a classifier or a regressor to predict individual preferences. Matrix factorization and AutoRec are optimized with pointwise objectives. Pairwise approaches consider a pair of items for each user and aim to approximate the optimal ordering for that pair. Listwise approaches approximate the ordering of the entire list of items, for example, direct optimizing the ranking measures such as Normalized Discounted Cumulative Gain (NDCG).

Bayesian Personalized Ranking Loss

Bayesian personalized ranking (BPR) is a pairwise personalized ranking loss that is derived from the maximum posterior estimator. The training data of BPR consists of both positive and negative pairs (missing values). It assumes that the user prefers the positive item over all other non-observed items. We can formulate the maximum posterior estimator to derive the generic optimization criterion for the personalized ranking task.

Where $\Theta$
represents the parameters of an arbitrary recommendation model,
$>u$ represents the desired personalized total ranking of all items for user.
$\hat{y}
{ui}$ and $\hat{y}_{uj}$
are the predicted scores of the user u to item i and j,respectively.

Hinge Loss

where m
is the safety margin size.

NeuMF

This model leverages the flexibility and non-linearity of neural networks to replace dot products of matrix factorization, aiming at enhancing the model expressiveness. In specific, this model is structured with two subnetworks including generalized matrix factorization (GMF) and MLP and models the interactions from two pathways instead of simple dot products. The outputs of these two networks are concatenated for the final prediction scores calculation.

Sequence-Aware Recommender Systems

Caser, short for convolutional sequence embedding recommendation model, adopts convolutional neural networks capture the dynamic pattern influences of users’ recent activities. The main component of Caser consists of a horizontal convolutional network and a vertical convolutional network, aiming to uncover the union-level and point-level sequence patterns, respectively. The goal of Caser is to recommend item by considering user general tastes as well as short-term intention.

Factorization Machines

The strengths of factorization machines over the linear regression and matrix factorization are: (1) it can model
$\chi$-way variable interactions, where $\chi$
is the number of polynomial order and is usually set to two. (2) A fast optimization algorithm associated with factorization machines can reduce the polynomial computation time to linear complexity, making it extremely efficient especially for high dimensional sparse inputs.

references

https://d2l.ai/chapter_recommender-systems/index.html

RL1

算法(2)--二叉树

二叉树

二叉树(Binary Tree)是一种常用的数据结构,在计算机科学中有广泛的应用。它是一种非线性数据结构,每个节点最多有两个子节点,通常这两个子节点被称为左子节点(left child)和右子节点(right child)。

有一些特别的二叉树,满二叉树就是每一层节点都是满的,除了叶子节点外,每个节点都有两个子节点。假设深度为 h,那么总节点数就是$2^h - 1$。 完全二叉树是指,二叉树的每一层的节点都紧凑靠左排列,且除了最后一层,其他每层都必须是满的,完全二叉树的特点:由于它的节点紧凑排列,如果从左到右从上到下对它的每个节点编号,那么父子节点的索引存在明显的规律。平衡二叉树中任何两个叶子节点的高度差不大于 1,二叉搜索树(Binary Search Tree,简称 BST)是一种很常见的二叉树,它的定义是:对于树中的每个节点,其左子树的每个节点的值都要小于这个节点的值,右子树的每个节点的值都要大于这个节点的值。

二叉树的递归/层序遍历

递归遍历

二叉树的递归遍历是一种常用的遍历方式,它利用递归来访问树中的所有节点。二叉树的递归遍历主要有三种方式:前序遍历(Preorder)、中序遍历(Inorder)和后序遍历(Postorder)。前序遍历的顺序是:先访问根节点,然后递归地遍历左子树,最后递归地遍历右子树。中序遍历的顺序是:递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。后序遍历的顺序是:递归地遍历左子树,然后递归地遍历右子树,最后访问根节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TreeNode:
def __init__(self, value=0, left=None, right=None):
self.value = value
self.left = left
self.right = right

def preorder_traversal(root):
if root is None:
return []
result = []
_preorder_helper(root, result)
return result

def _preorder_helper(node, result):
if node:
result.append(node.value) # 访问节点
_preorder_helper(node.left, result) # 遍历左子树
_preorder_helper(node.right, result) # 遍历右子树

层序遍历

二叉树的层序遍历,顾名思义,就是一层一层地遍历二叉树。这个遍历方式需要借助队列来实现。

参考文献

https://labuladong.online/algo/data-structure-basic/binary-tree-basic/#%E5%87%A0%E7%A7%8D%E5%B8%B8%E8%A7%81%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91

词嵌入

word2vec

word2vec工具包含两个模型,即跳元模型(skip-gram)和连续词袋(CBOW (Continuous Bag-of-Words)。Skip-gram是给定一个中心词,预测其周围的上下文词。CBOW是给定一段文本中的一个中心词周围的上下文词,预测中心词。

负采样

在标准的 softmax 方法中,我们有

这涉及到计算所有词汇表中词汇的条件概率, 在词汇表非常大的时候是非常耗时的。

负采样的核心思想是通过随机选择一部分非上下文词(负样本)来近似这个条件概率分布。具体来说,对于每个中心词和对应的上下文词,我们不仅更新这两个词的向量表示,而且还随机选择若干个不在上下文中的词作为“负样本”,并更新它们的向量表示。在负采样中,我们采用一个简化的逻辑回归目标函数来替代标准的 softmax 函数。对于每个训练样本,我们希望模型能够正确区分真正的上下文词和负样本词。因此,我们可以定义一个目标函数,它希望对于上下文词,有$\sigma(\u_o\v_i+b)=1$而对于每个负样本,有$\sigma(\u_o\v_i+b)=0$。 通过这种方式,负采样实际上是在模拟原始 softmax 的行为,但它只考虑了一小部分词汇,即一个正样本加上几个负样本。这意味着负采样通过一系列独立的二元分类任务来近似这个条件概率。

层次softmax

层次softmax 的核心思想是构建一个词汇的层次结构,通常是一棵哈夫曼树(Huffman Tree),并将分类问题转化为沿着树的路径进行的一系列二元分类问题。这样可以将原始的 softmax 层的计算复杂度从线性减少到对数级别。层次softmax 的目标是通过计算从根节点到词汇的叶子节点的路径概率来近似原条件概率。

GloVe

GloVe模型的主要特点是它试图捕捉单词之间的共现频率信息,即一个单词出现时另一个单词出现的概率。通过这种方式,模型可以学习到词汇语义以及词汇间的关系,比如同义词、反义词、以及类比关系等。

训练GloVe模型涉及到最小化词对共现概率预测值与实际共现概率之间的损失函数。

子词嵌入

fastText

在跳元模型和连续词袋模型中,同一词的不同变形形式直接由不同的向量表示,不需要共享参数。为了使用形态信息,fastText模型提出了一种子词嵌入方法,其中子词是一个字符n-gram。fastText可以被认为是子词级跳元模型,而非学习词级向量表示,其中每个中心词由其子词级向量之和表示。

字节对编码(Byte Pair Encoding)

字节对编码(Byte Pair Encoding,简称 BPE)是一种用于词汇归一化和文本压缩的技术,近年来,BPE 被重新引入到自然语言处理领域,特别是在机器翻译和语言建模中,作为一种生成子词单位的有效方法。BPE 的基本思想是不断地合并最常出现的相邻字符对,直到达到预定的词汇表大小。

ELMo

word2vec和GloVe都将相同的预训练向量分配给同一个词,而不考虑词的上下文,考虑到自然语言中丰富的多义现象和复杂的语义,上下文无关表示具有明显的局限性,同一个词可以根据上下文被赋予不同的表示。

ELMo(Embeddings from Language Models)是一种上下文敏感的词嵌入方法,使用双向 LSTM(长短期记忆网络)构建深度语言模型,这种模型可以捕获来自句子左右两边的信息。通过训练这种模型,可以得到一个对上下文敏感的词嵌入。

GPT

初代 GPT 基于 Transformer 架构,使用的是单向的 Transformer,这意味着它在生成文本时只能访问之前的位置信息,而不能访问当前位置之后的信息。通过自回归方式训练,即模型学习给定前面的文字后预测下一个文字。

BERT

ELMo对上下文进行双向编码,但使用特定于任务的架构;而GPT是任务无关的,但是从左到右编码上下文,BERT结合了这两个方面的优点。

BERT输入序列明确地表示单个文本和文本对。当输入为单个文本时,BERT输入序列是特殊类别词元“\”、文本序列的标记、以及特殊分隔词元“\”的连结。当输入为文本对时,BERT输入序列是“\”、第一个文本序列的标记、“\”、第二个文本序列标记、以及“\”的连结。

为了双向编码上下文以表示每个词元,BERT随机掩蔽词元并使用来自双向上下文的词元以自监督的方式预测掩蔽词元。此任务称为掩蔽语言模型。

尽管掩蔽语言建模能够编码双向上下文来表示单词,但它不能显式地建模文本对之间的逻辑关系。为了帮助理解两个文本序列之间的关系,BERT在预训练中考虑了一个二元分类任务——下一句预测。在为预训练生成句子对时,有一半的时间它们确实是标签为“真”的连续句子;在另一半的时间里,第二个句子是从语料库中随机抽取的,标记为“假”。

在预训练BERT时,最终的损失函数是掩蔽语言模型损失函数和下一句预测损失函数的线性组合。

参考文献

https://zh.d2l.ai/chapter_natural-language-processing-pretraining/word2vec.html

https://zh.d2l.ai/chapter_natural-language-processing-pretraining/subword-embedding.html

深度学习(2)-计算性能

命令式编程和符号式编程

命令式编程(Imperative Programming)和符号式编程(Symbolic Programming)是两种不同的编程范式。命令式编程描述了计算机应该执行的具体步骤来达到预期的结果。这种编程方式侧重于描述“怎么做”,而不是“做什么”。符号式编程侧重于表达计算的本质,而不是具体的执行步骤。程序表达的是问题的解决方案,而不是具体的操作细节,代码通常只在完全定义了过程之后才执行计算。

异步计算

异步计算(Asynchronous Computing)是一种编程和计算模式,它允许程序在等待某些操作完成的同时继续执行其他任务。与同步计算不同,异步计算可以提高程序的响应性和性能,尤其是在处理 I/O 操作、网络请求、长时间运行的任务等情况时。

GPU,TPU,NPU

GPU即Graphics Processing Unit,中文名为图形处理单元。需要注意的是,GPU没有独立工作的能力,必须由CPU进行控制调用才能工作,且GPU的功耗一般比较高。TPU即Tensor Processing Unit,中文名为张量处理器,是谷歌在2015年6月的IO开发者大会上推出了为优化自身的TensorFlow框架而设计打造的一款计算神经网络专用芯片。NPU即Neural-network Processing Unit,中文名为神经网络处理器,它采用“数据驱动并行计算”的架构,特别擅长处理视频、图像类的海量多媒体数据。

并行计算

CUDA是NVIDIA提供的一种GPU并行计算框架。对于GPU本身的编程,使用的是CUDA语言来实现的。在编写程序中,当我们使用了 .cuda() 时,其功能是让我们的模型或者数据从CPU迁移到GPU上(默认是0号GPU)当中,通过GPU开始计算。

当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0)。

PyTorch提供了两种多卡训练的方式,分别为DataParallel和DistributedDataParallel(以下我们分别简称为DP和DDP)。这两种方法中官方更推荐我们使用DDP,因为它的性能更好。DP只能在单机上使用,且DP是单进程多线程的实现方式,比DDP多进程多线程的方式会效率低一些。

首先我们来看单机多卡DP,通常使用一种叫做数据并行 (Data parallelism) 的策略,即将计算任务划分成多个子任务并在多个GPU卡上同时执行这些子任务。主要使用到了nn.DataParallel函数。

1
2
3
4
5
model = Net()
model.cuda() # 模型显示转移到CUDA上

if torch.cuda.device_count() > 1: # 含有多张GPU的卡
model = nn.DataParallel(model, device_ids=[0,1]) # 单机多卡DP训练

不过通过DP进行分布式多卡训练的方式容易造成负载不均衡,有可能第一块GPU显存占用更多,因为输出默认都会被gather到第一块GPU上。为此Pytorch也提供了torch.nn.parallel.DistributedDataParallel(DDP)方法来解决这个问题。

针对每个GPU,启动一个进程,然后这些进程在最开始的时候会保持一致(模型的初始化参数也一致,每个进程拥有自己的优化器),同时在更新模型的时候,梯度传播也是完全一致的,这样就可以保证任何一个GPU上面的模型参数就是完全一致的,所以这样就不会出现DataParallel那样显存不均衡的问题。

参考文献

https://zh.d2l.ai/chapter_computational-performance/index.html

https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E4%BA%8C%E7%AB%A0/2.3%20%E5%B9%B6%E8%A1%8C%E8%AE%A1%E7%AE%97%E7%AE%80%E4%BB%8B.html

https://datawhalechina.github.io/thorough-pytorch/%E7%AC%AC%E4%BA%8C%E7%AB%A0/2.4%20AI%E7%A1%AC%E4%BB%B6%E5%8A%A0%E9%80%9F%E8%AE%BE%E5%A4%87.html