从零实现深度学习框架——深入浅出PackedSequence

news2024/11/18 12:18:37

引言

本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。
💡系列文章完整目录: 👉点此👈
要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不适用外部框架的前提下,实现我们想要的模型。本系列文章的宗旨就是通过这样的过程,让大家切实掌握深度学习底层实现,而不是仅做一个调包侠。

当我们训练RNN时,如果想要进行批次化训练,就得需要截断和填充。
因为句子的长短不一,一般选择一个合适的长度来进行截断;而填充是在句子过短时,需要以 填充字符 填充,使得该批次内所有的句子长度相同。

PyTorch提供pack_padded_sequence可以压缩这些填充字符,加快RNN的计算效率。我们这里参考PyTorch也实现这些相关函数。

为什么要压缩填充

填充会带来两个问题:

  • 增加了计算复杂度。假设一个批次内有2个句子,长度分别为5和2。我们要保证批次内所有的句子长度相同,就需要把长度为2的句子填充为5。这样喂给RNN时,需要计算2 × 5 = 10 次,而实际真正需要的是5 + 2 = 7次。
  • 得到的结果可能不准确。我们知道RNN取的是最后一个时间步的隐藏状态做为输出,虽然一般是以0填充,权重乘以零不会影响最终的输出,但在Pytorch中还有偏差b bb,如果b ≠ 0 ,还是会影响到最后的输出。当然这个问题不大。主要是第1个问题。 毕竟批次大小很大的时候影响还是不小的。

如何压缩

假设一个批次有6个句子,我们将这些句子填充后如下所示。

image-20210717122749936

这里按句子长度逆序排序,pads就是填充。不同的颜色代表不同时间步的单词。

下面我们看pack_padded_sequence是如何压缩的。如下图所示:

image-20210717124043131

pack_padded_sequence根据时间步拉平了上面排序后的句子,在每个时间步维护一个数值,代表当前时间步内有效的批大小。比如上图右边黄色区域的时间步内,只有3个输入是有效的,其他都是填充。因此说该时间步内的批大小为3。

该方法会返回一个PackedSequence对象,其中包含 data保存拉平的数据 和 batch_sizes保存时间步相应的批大小,比如上面就是tensor([6,6,5,4,3,3,2,2,1])

这个PackedSequence对象可以传递给RNN,它会返回一个新的PackedSequence对象,接着我们可以用pad_packed_sequence方法把返回的PackedSequence还原成我们想要的形式。

实例说明

该样例由参考1中的例子改造而来。

下面我们结合一个实例来说明。

假设有一些单词序列如下:

 sentences = ['crazy', 'complicate', 'medium', 'hard', 'rookie']

我们首先转换成数值并填充使长度一致:

import metagrad.module as nn
from metagrad.tensor import Tensor
from metagrad.utils import pack_padded_sequence, pad_packed_sequence, pad_sequence

sentences = ['crazy', 'complicate', 'medium', 'hard', 'rookie']
# 词典
vocab = ['<pad>'] + sorted(set([char for sentence in sentences for char in sentence]))
# 转换成数值
vectorized = [Tensor([vocab.index(t) for t in seq]) for seq in sentences]
# 每个序列的长度
lengths = Tensor(list(map(len, vectorized)))
print(lengths)
Tensor([ 5 10  6  4  6], requires_grad=False)

假设这是一个批次的话,那么该批次最大长度为10,我们填充其他序列:

padded_seq = pad_sequence(vectorized)

print(padded_seq)
Tensor(
[[ 2 12  1 16 15  0  0  0  0  0] # crazy
 [ 2 10  9 11  8  6  2  1 13  4] # complicate
 [ 9  4  3  6 14  9  0  0  0  0] # medium
 [ 5  1 12  3  0  0  0  0  0  0] # hard
 [12 10 10  7  6  4  0  0  0  0]]# rookie
 ,requires_grad=False) 

可以看到,每个单词序列都被填充为长度10。此时的形状是(batch_size, seq_len) = (5, 10)

我们就得到了padded sequence,要打包成packed sequence,需要先按长度由大到小对上述批数据进行排序。

较新版本的pytorch打packed sequence时支持自动排序,即不需要显示地进行排序。我们这里第一版实现的还是需要预先排序的。

为了支持排序,我们需要实现一个新的操作sort

实现Sort Function

class Sort(Function):
    def forward(self, x: NdArray, axis=-1, descending=False) -> Tuple[NdArray, NdArray]:
        xp = get_array_module(x)
        # 在指定维度上对数组进行排序

        # sorted_x = xp.sort(x, axis=axis)
        sorted_indices = xp.argsort(x, axis=axis)
        sorted_x = xp.take_along_axis(x, sorted_indices, axis=axis)

        if descending:
            # 如果设置了descending为True,则按降序排序
            sorted_x = xp.flip(sorted_x, axis=axis)
            sorted_indices = xp.flip(sorted_indices, axis=axis)

        self.save_for_backward(axis, xp, sorted_indices)

        return sorted_x, sorted_indices

    def backward(self, *grad: NdArray) -> NdArray:
        axis, xp, indices = self.saved_tensors
        # forward返回了多个,只有非索引才有梯度
        grad = grad[0]
        # 逆转排序后的索引
        inverse_permutation = xp.argsort(indices, axis=axis)

        return xp.take_along_axis(grad, inverse_permutation, axis=axis)

内部调用numpy/cupy#argsort对输入x进行排序,返回排序后的索引,然后通过这个索引得到排序后的序列。这里支持指定维度axis和降序/升序descending。就像torch.sort一样。

在反向传播时,我们只取排序后的序列的grad,因为索引是没有梯度的。

这里关键的一步是逆转排序后的索引,通过argsort(indices, axis=axis)实现。indices是排序后的索引。然后根据逆转排序后的索引和维度从grad中取梯度。

并且添加了几个测试用例,具体可以看源码。

好了,实现了sort之后,我们就对上面的批数据进行排序。

sorted_lengths, sorted_indices = lengths.sort(0, descending=True)

padded_seq = padded_seq[sorted_indices]
print(padded_seq)

实际上是对长度Tensor进行排序,然后通过排序后的索引,得到排序后的批数据:

Tensor(
[[ 2 10  9 11  8  6  2  1 13  4] # complicate
 [12 10 10  7  6  4  0  0  0  0] # rookie
 [ 9  4  3  6 14  9  0  0  0  0] # medium
 [ 2 12  1 16 15  0  0  0  0  0] # crazy
 [ 5  1 12  3  0  0  0  0  0  0]]# hard
 , requires_grad=False)

每个单词都标出来了。这里要注意的是,同样长度的单词rookie和medium交换了顺序,因为numpy.argsort默认为quicksort,这是一种非稳定算法,感兴趣的可以看看 快速排序分析。

现在我们有了填充后的数据,并且也按照长度进行排序,接下来看如何压缩。

在压缩之前,我们先经过词嵌入层:

embed = nn.Embedding(len(vocab), 3)  # embedding_dim = 3
embedded_seq = embed(padded_seq)
print(embedded_seq)  # (batch_size, seq_len, embed_size)
Tensor(
[[[0.3222 0.2082 0.0166] # c
  [0.5821 0.8482 0.6586] # o
  [0.2423 0.8599 0.5947] # m
  [0.5781 0.4923 0.6869] # p
  [0.3805 0.0511 0.2411] # l
  [0.3645 0.778  0.3711] # i
  [0.3222 0.2082 0.0166] # c
  [0.3982 0.2133 0.7391] # a 
  [0.2787 0.4415 0.975 ] # t
  [0.6541 0.3558 0.5387]]# e

 [[0.1267 0.457  0.2262] # r
  [0.5821 0.8482 0.6586] # o
  [0.5821 0.8482 0.6586] # o
  [0.6753 0.5637 0.4325] # k
  [0.3645 0.778  0.3711] # i
  [0.6541 0.3558 0.5387] # e
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689]]# <pad>

 [[0.2423 0.8599 0.5947] # m
  [0.6541 0.3558 0.5387] # e
  [0.2034 0.0254 0.2949] # d
  [0.3645 0.778  0.3711] # i
  [0.4798 0.7325 0.7202] # u
  [0.2423 0.8599 0.5947] # m
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689]]# <pad>

 [[0.3222 0.2082 0.0166] # c
  [0.1267 0.457  0.2262] # r
  [0.3982 0.2133 0.7391] # a
  [0.1112 0.6079 0.877 ] # z
  [0.5113 0.9715 0.5163] # y
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689]]# <pad>

 [[0.7185 0.5206 0.25  ] # h
  [0.3982 0.2133 0.7391] # a
  [0.1267 0.457  0.2262] # r
  [0.2034 0.0254 0.2949] # d
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689] # <pad>
  [0.514  0.874  0.8689]]# <pad>
  ], requires_grad=True)

它的形状是(batch_size, seq_len, embed_size) = (5, 10, 3)

接下来就调用pack_padded_sequence方法,传入嵌入序列以及排序后的长度(转换为list)。且这里是批大小在前的形式:

result = pack_padded_sequence(embedded_seq, sorted_lengths.tolist(), batch_first=True)

它返回的resultPackedSequence对象,包含databatch_sizes

PackedSequence(data=Tensor(
[[0.3222 0.2082 0.0166] # c
 [0.1267 0.457  0.2262] # r
 [0.2423 0.8599 0.5947] # m
 [0.3222 0.2082 0.0166] # c
 [0.7185 0.5206 0.25  ] # h ----------------------  batch_size 5 
 [0.5821 0.8482 0.6586] # o
 [0.5821 0.8482 0.6586] # o
 [0.6541 0.3558 0.5387] # e
 [0.1267 0.457  0.2262] # r
 [0.3982 0.2133 0.7391] # a ----------------------  batch_size 5 
 [0.2423 0.8599 0.5947] # m
 [0.5821 0.8482 0.6586] # o
 [0.2034 0.0254 0.2949] # d
 [0.3982 0.2133 0.7391] # a
 [0.1267 0.457  0.2262] # r ----------------------  batch_size 5 
 [0.5781 0.4923 0.6869] # p
 [0.6753 0.5637 0.4325] # k
 [0.3645 0.778  0.3711] # i
 [0.1112 0.6079 0.877 ] # z
 [0.2034 0.0254 0.2949] # d ----------------------  batch_size 5 
 [0.3805 0.0511 0.2411] # l 
 [0.3645 0.778  0.3711] # i
 [0.4798 0.7325 0.7202] # u
 [0.5113 0.9715 0.5163] # y ----------------------  batch_size 4 
 [0.3645 0.778  0.3711] # i
 [0.6541 0.3558 0.5387] # e
 [0.2423 0.8599 0.5947] # m ----------------------  batch_size 3
 [0.3222 0.2082 0.0166] # c ----------------------  batch_size 1
 [0.3982 0.2133 0.7391] # a ----------------------  batch_size 1
 [0.2787 0.4415 0.975 ] # t ----------------------  batch_size 1
 [0.6541 0.3558 0.5387]]# e ----------------------  batch_size 1
,requires_grad=True), batch_sizes=[5, 5, 5, 5, 4, 3, 1, 1, 1, 1])

data的形状是(sum_seq_len,embedding_dim) = (31,3)

其中sum_seq_len是该批次内序列有效的长度之和。可以看到,经过压缩后的序列不再包含填充<pad>对应的嵌入。

可以看到共有10个时间步,每个时间步上有效的batch_size可能不一样,也可能一样。反映在batch_sizes字段。

上面标出了单词中每个字母,如果没理解可以结合前面的图片描述以及下面:

# c o m p l i c a t e   # complicate
# r o o k i e           # rookie
# m e d i u m           # medium
# c r a z y             # crazy
# h a r d			    # hard
# 5 5 5 5 4 3 1 1 1 1   # batch_sizes (sum = 31 [sum_seq_len])

比如c r m c h分别是排序后输入单词的第一个字母,即第一个时间步的输入。最短的单词长度为4,因此前面4步都有5个输入;在第5个时间步的(有效)输入只包含前4个单词;从第7个时间步开始到最后一个时间步,只有complicate的输入,对应cate

此时在回顾前文那句话: “pack_padded_sequence根据时间步拉平了上面排序后的句子,在每个时间步维护一个数值,代表当前时间步内有效的批大小。”应该就好理解了。

压缩后的输入就可以传给RNN进行更加高效地计算了。

不过,光理解原理还不够,我们还要实现它。

实现PackedSequence

class PackedSequence(NamedTuple):
    data: Tensor  # 包含packed sequence
    batch_sizes: List[int]  # 序列每个时间步的批大小

我们这里实现简单的PackedSequence,只包含两个属性,分别保存压缩后的变量和每个时间步的批大小。

实现pack_padded_sequence

def pack_padded_sequence(input: Tensor, lengths: List[int], batch_first: bool = False):
    """
    压缩填充后的序列,批次内序列需要先按照有效长度降序排序
    :param input: 输入序列  如果batch_first=True,形状为(batch_size, seq_len, embdding_size)
                          如果batch_first=False,形状为(seq_len, batch_size, embdding_size)
    :param lengths: 批次内每个序列的有效长度
    :param batch_first: 是否批大小维度在前
    :return:
    """
    if batch_first:
        # 转换成seq_len在前的形式
        input = input.transpose((1, 0, 2))

    steps = []
    # 每个step的批大小
    batch_sizes = []
    # 对长度进行逆序
    lengths_iter = reversed(lengths)
    # 当前长度
    current_length = next(lengths_iter)
    # 取出批大小
    batch_size = input.size(1)
    # lengths应该包含批大小个序列
    if len(lengths) != batch_size:
        raise ValueError("lengths array has incorrect size")
    # 现在是seq_len在前的形式,按seq_len维度取出每个句子,索引(step)从1开始
    for step, step_value in enumerate(input, 1):
        steps.append(step_value[:batch_size]) # 把step_value添加到steps,:batch_size取有效数据(不包括填充)
        batch_sizes.append(batch_size) # 记录该step有效的序列个数

        while step == current_length: # 表示此时长度为current_length的填完了
            try:
                new_length = next(lengths_iter) # 按照逆序取新的长度
            except StopIteration: # 遍历完lengths_iter
                current_length = None # 将current_length设为None
                break # 跳出while循环

            batch_size -= 1 # 但批大小减去1
            current_length = new_length # 新的长度赋值给current_length

        if current_length is None: # 表示此时已经遍历完了
            break # 可以跳出for循环

    return PackedSequence(F.cat(steps), batch_sizes)

参数就三个,填充后的序列、排序后的长度列表、是否batch_first。
比如上面那个例子中,排序后的长度列表就是 [10 6 6 5 4]

上面方法主要的过程为,将排序后的长度列表取逆序,依次遍历。比如,上面第一个是4,前4个时间步的批大小都是batch_size=5,将前4个时间步的 形状为(5,3)的有效输入存入steps ,此时hard处理完了;然后取出新的长度5,批大小也减1,变成了batch_size=4。将(4,3)(对应l,i,u,y)存入steps。此时满足while循环,说明crazy也处理完了;继续取出新长度6,批大小又减1,变成了batch_size=3,把(3,3)(对应 i,e,m)存入steps;接着再次取出新的长度(还是)6,批大小又减1,变成了batch_size=2,但发现长度6的已经处理完了;再次取出新的长度10,批大小又减1,变成了batch_size=1,最后依次处理complicate最后四个字母cate

最终返回PackedSequence对象,包含压缩后的输入和每个时间步内有效的批大小。

实现pad_packed_sequence

通常拿到PackedSequence对象后,我们就可以输入到RNN中进行运算了,然后在运算结果上调用pack_padded_sequence的逆操作pad_packed_sequence把它转换会原来的填充后的形式。

这中间可能会发现词嵌入维度转变成隐藏状态维度,但序列长度是不变的。

一般没有人直接压缩后就进行解压缩的,我们为了演示,马上进行解压缩。

output, input_sizes = pad_packed_sequence(result, batch_first=True)

print(output.shape)
print(input_sizes)
(5, 10, 3)
[10, 6, 6, 5, 4]

我们打印还原后的output

print(output)
Tensor(
[[[0.3222 0.2082 0.0166] # c
  [0.5821 0.8482 0.6586] # o
  [0.2423 0.8599 0.5947] # m
  [0.5781 0.4923 0.6869] # p
  [0.3805 0.0511 0.2411] # l
  [0.3645 0.778  0.3711] # i
  [0.3222 0.2082 0.0166] # c
  [0.3982 0.2133 0.7391] # a 
  [0.2787 0.4415 0.975 ] # t
  [0.6541 0.3558 0.5387]]# e
  
 [[0.1267 0.457  0.2262] # r
  [0.5821 0.8482 0.6586] # o
  [0.5821 0.8482 0.6586] # o
  [0.6753 0.5637 0.4325] # k
  [0.3645 0.778  0.3711] # i
  [0.6541 0.3558 0.5387] # e
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ]]# <pad>
  
 [[0.2423 0.8599 0.5947] # m
  [0.6541 0.3558 0.5387] # e
  [0.2034 0.0254 0.2949] # d
  [0.3645 0.778  0.3711] # i
  [0.4798 0.7325 0.7202] # u
  [0.2423 0.8599 0.5947] # m
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ]]# <pad>
  
 [[0.3222 0.2082 0.0166] # c
  [0.1267 0.457  0.2262] # r
  [0.3982 0.2133 0.7391] # a
  [0.1112 0.6079 0.877 ] # z
  [0.5113 0.9715 0.5163] # y
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ]]# <pad>
  
 [[0.7185 0.5206 0.25  ] # h
  [0.3982 0.2133 0.7391] # a
  [0.1267 0.457  0.2262] # r
  [0.2034 0.0254 0.2949] # d
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  [0.     0.     0.    ] # <pad>
  ]], requires_grad=False)

由于我们在压缩的时候没有记录<pad>对应的嵌入,实际上也没必要记录,这里默认用0.表示。

下面就来实现解压缩:

def pad_packed_sequence(sequence: PackedSequence, batch_first=False):
    """
    pack_padded_sequence的逆操作
    :param sequence: PackedSequence
    :param batch_first: 是否批大小维度在前
    :return:
    """
    # 取出data和batch_sizes
    var_data, batch_sizes = sequence
    # 0位置一定包含最大的批大小
    max_batch_size = batch_sizes[0]
    # 构建一个输出Tensor 形状为 (seq_len, batch_size, hidden_size?)
    output = Tensor.zeros((len(batch_sizes), max_batch_size, *var_data.shape[1:]))
    # 批次内实际的序列长度
    lengths = []
    # data的偏移量
    data_offset = 0
    # 前一个批大小
    prev_batch_size = batch_sizes[0]
    # 遍历batch_sizes,索引从0开始
    for i, batch_size in enumerate(batch_sizes):
        # 第i个位置(seq_len维度)取var_data从data_offset开始到第batch_size个
        output[i, :batch_size] = var_data[data_offset:data_offset + batch_size]
        # 偏移量加上实际取的batch_size
        data_offset += batch_size
        # 上一个batch_size 减去 当前batch_size
        dec = prev_batch_size - batch_size
        # 如果结果大于0
        if dec > 0:
            # 表示有dec个长度为i的序列
            lengths.extend((i,) * dec)
        # 把batch_size赋给prev_batch_size
        prev_batch_size = batch_size
    # 剩下batch_size个长度为i+1的序列
    lengths.extend((i + 1,) * batch_size)
    # 现在是从小到大的顺序,逆序成从大到小
    lengths.reverse()
    # 如果是batch_first,则转回batch_first的形式,因为在pack_padded_sequence中转了一次
    if batch_first:
        output = output.transpose((1, 0, 2))
    return output, lengths

每行代码都有解释,实际上就是根据batch_sizes依次取出有效的输入填充到(seq_len, batch_size, hidden_size?)output。这里hidden_size多了个?表示也可能是embed_size

这样,我们实现了压缩和解压填充序列,下篇文章我们看如何应用到RNN中。

完整代码

本文对应的代码在 : github

参考

  1. Minimal tutorial on packing
  2. Pytorch中pack_padded_sequence和pad_packed_sequence的理解
  3. PyTorch源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/733953.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用RabbitMQ

使用RabbitMQ 1 Docker安装RabbitMQ 1.1 安装RabbitMQ # 下载含有管理页面的镜像 docker pull rabbitmq:3.8.8-management# 创建容器 # 5672&#xff1a;应用访问端口&#xff1b;15672&#xff1a;控制台Web端口号&#xff1b; docker run -itd \ --namemy-rabbitmq \ --re…

【Python】Python基础知识总结

&#x1f389;欢迎来到Python专栏~Python基础知识总结 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望…

5. 学成在线案例

1.典型的企业级网站 2.目的&#xff1a;整体感知企业级网站布局流程&#xff0c;复习以前的知识 5.1 准备素材和工具 1.学成在线PSD源文件 2.开发工具 PS(切图) / cutterman插件 vscode(代码) chrome(测试) 5.2 案例准备工作 采取结构与样式相分离思想&#xff1a; 1.创…

【备战秋招】每日一题:2022.11.3-华为机试-去除多余空格

为了更好的阅读体检&#xff0c;可以查看我的算法学习网 在线评测链接:P1058 题目描述 塔子哥最近接到导师的一个任务&#xff0c;需要他帮忙去除文本多余空格&#xff0c;但不去除配对单引号之间的多余空格。给出关键词的起始和结束下标&#xff0c;去除多余空格后刷新关键词…

豆瓣T250电影

爬取电影名字、年份、评分、评价人数 import requests import re import csv"""1、拿到页面源代码"""headers {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.…

IntelliJ IDEA安装Mybatis 插件Free Mybatis plugin

需求描述 在开发一些Mybatis的项目&#xff0c;经常需要写一个Mapper接口&#xff0c;在找代码过程&#xff0c;经常需要去找对应的xml文件&#xff0c;所以非常的不方便。自从有了免费的free-mybatis-plugin插件之后 &#xff0c;在可以实现在idea里一键跳转到对应的xml文件&…

CRC16_Verilog

CRC校验 CRC即循环冗余校验码&#xff08;Cyclic Redundancy Check&#xff09;&#xff1a;是数据通信领域中最常用的一种查错校验码&#xff0c;其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查&#xff08;CRC&#xff09;是一种数据传输检错功能&#xff0c;…

Mac系统远程连接Windows11

一、远程桌面连接Windows11 1、下载并安装Microsoft Remote Desktop for mac。&#xff08;Microsoft Remote Desktop for mac简介&#xff0c;下载链接&#xff09; 2、Windows11开启远程桌面。 3、为当前Windows11账号设置密码。 二、ssh连接Windows11 1、下载并安装OpenSS…

numpy与python版本不匹配-ImportError: Unable to import required dependencies: numpy

问题 你在运行python代码的时候&#xff0c;是否遇到过下面这种错误 ImportError: Unable to import required dependencies: numpy: IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!Importing the numpy C-extensions failed. This error can happen f…

【优选算法题练习】day2

文章目录 一、11. 盛最多水的容器1.题目简介2.解题思路3.代码4.运行结果 二、611. 有效三角形的个数1.题目简介2.解题思路3.代码4.运行结果 三、剑指 Offer 57. 和为s的两个数字1.题目简介2.解题思路3.代码4.运行结果 总结 一、11. 盛最多水的容器 1.题目简介 11. 盛最多水的…

笔试刷过的题---选择

1.若使求解TSP算法&#xff0c;则时间复杂度是&#xff08;&#xff09; 2.用1*3的瓷砖密铺3*20的地板有&#xff08;&#xff09;种方式 答&#xff1a;1278 3.可以用于路径规划的算法 有多种算法可以用于路径规划&#xff0c;以下是一些常见的算法&#xff1a; Dijkstra算…

在电脑上,一秒钟快速复制汇总上百成千个文件(夹》到指定文件夹中

在日常生活和工作中&#xff0c;我们经常需要对大量的文件进行重命名&#xff0c;以方便整理和管理。使用高效的文件批量改名软件可以极大地提高我们的工作效率。本文将介绍一款功能强大的文件批量改名软件&#xff0c;并演示如何使用它来实现快速的文件重命名。 该软件的名称…

Spring异常处理器

文章目录 1. 异常分析2. 异常处理器2.1 异常处理器核心2.2 异常处理顺序 3. 自定义异常 1. 异常分析 问题:   程序允许不免的在各层都可能会产生异常&#xff0c;我们该如何处理这些异常? 如果只是在方法里面单独使用 try… catch… 语句去一个一个的进行捕捉处理的话&#x…

【JUC并发编程】读写锁:ReadWriteLock

一、介绍 二、代码演示 1. 不使用读写锁 package readwritelock;import java.util.HashMap; import java.util.Map;/*** author swaggyhang* create 2023-07-09 11:16*/ public class Test01 {public static void main(String[] args) {MyCache myCache new MyCache();for (…

功夫这个词,西方语言中没有

功夫这个词&#xff0c;西方语言中没有 功夫一种意思是武侠片的武功之意 另一种意思就是【下功夫】 趣讲大白话&#xff1a;只要功夫深&#xff0c;铁棒磨成针 【趣讲信息科技220期】 #非著名IT人安志强的趣味笔记# **************************** 西方词语怎么翻译功夫的&#…

EtherNet/IP转CAN网关can协议分为几种

生产管理设备中&#xff0c;会有设备与其他设备的协议不同&#xff0c;数据无法互通&#xff0c;让你的工作陷入困境。这时&#xff0c;一款神奇的产品出现了——远创智控YC-EIP-CAN通讯网关&#xff01; 1, 这款通讯网关采用ETHERNET/IP从站功能&#xff0c;可以将各种CAN总…

什么事RPC并实现一个简单的RPC

1. 基本的RPC模型 主要介绍RPC是什么&#xff0c;基本的RPC代码&#xff0c;RPC与REST的区别&#xff0c;gRPC的使用 1.1 基本概念 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;简单的理解是一个节点请求另一个节点提供的服务本地过程调用&am…

管理类联考——逻辑——技巧篇——数字编码——公式

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

特斯拉12V低压系统存在问题:刹车失灵还能怪司机吗?

特斯拉汽车失控加速事件引发全球关注&#xff0c;美国NHTSA&#xff08;交通运输安全委员会&#xff09;和特斯拉之前将责任归咎于司机误操作。但一位研究人员提出新解释&#xff0c;并指出特斯拉的12V低压系统可能存在问题&#xff0c;特别是在高负载状态下。 此研究认为&…

git上传文件到Gitee报错“error: failed to push some refs to https://gitee.com/xxxx”

文章目录 前言一、创建项目仓库二、创建工作区三、配置 LFS四、上传镜像文件 前言 我要将一个 4.27 GB 的文件上传到 Gitee 上&#xff0c;但是出现了下面这样的报错 error: failed to push some refs to https://gitee.com/xxxx/centos.git 因此记录一下解决报错的方法。 一、…