动手实现条件随机场(下)

news2024/7/2 21:04:15

引言

本文基于PyTorch实现条件随机场,实现CRF层参考论文Neural Architectures for Named Entity Recognition中关于CRF层的描述。包含大量的图解和例子说明,看完一定能理解!

论文地址: https://arxiv.org/pdf/1603.01360.pdf

也可参考论文笔记: [论文笔记]Neural Architectures for Named Entity Recognition

虽然在尽量控制篇幅,但还是得分成上下两篇:
在这里插入图片描述


上一篇: 动手实现条件随机场(上)


实现维特比算法

def decode(
    self, emissions: torch.Tensor, mask: torch.ByteTensor = None
) -> list[list[int]]:
    """使用维特比算法找到最有可能的序列

    Args:
        emissions (torch.Tensor):  发射分数P 形状 (seq_len, batch_size, num_tags), 代表序列中每个单词产生每个标签的得分
        mask (torch.ByteTensor | None, optional): 表明哪些元素为填充符。  如果batch_first=False  形状 (seq_len, batch_size) ,否则 形状为 (batch_size, seq_len)
            默认为None,表示没有填充符。

    Returns:
        list[list[int]]: 批次内的最佳标签序列
    """
    if mask is None:
        mask = torch.ones(emissions.shape[:2], dtype=torch.uint8)

    if self.batch_first:
        # 转换为seq维度在前的形式
        emissions = emissions.permute(0, 1)
        mask = mask.permute(0, 1)

    return self._viterbi(emissions, mask)

首先提供一个对外调用的方法decode,可以接收批次数据,并且确保接收到的emissionsmask的维度是seq_len在前的。

我们重点来看下_viterbi方法的实现,即维特比算法。

总体来说维特比算法类似于上面配分函数的计算过程,不过维特比算法不是在每个时间步计算到达所有标签的累计得分,而是计算到达当前时间步某一标签的最佳上一时间步的标签。这里说的最佳是指,使得得分最大的标签。比如:

image-20230615173319046

图12

如图12所示,假设在第1个时间步上到第0个标签,即O标签的累计得分已知,那么维特比算法选择的就是最大得分,这里是5.5对应的那条路径,同时会记住该条路径。再加上当前时间步输入产生该标签的得分就得到了当前时间步标签O的累计得分。

关于维特比算法的详细介绍可以参考文章《统计学习方法》——隐马尔可夫模型,但这里也会通过图解来分析。

image-20230616150716875

图13

第一步是计算由start标签转移到第一个标签的转移得分+产生第一个标签的发射得分,如上图红框框出来的一列:

# 由start到当前时间步所有标签的转移得分 + 批次内所有当前时间步产生所有标签的发射得分
# (num_tags,) +  (batch_size, num_tags) -> (batch_size, num_tags)
# score 形状 (batch_size, num_tags) 保存了当前位置,到每个tag的最佳累计得分: 前一累计得分+转移得分+发射得分
score = self.start_transitions + emissions[0]

得到了到当前时间步所有标签的累计得分。假设当前时间步由start到O的转移得分是3.3,产生O的发射得分是2.2,那么累计得分score=5.5

接下来定义一个保存目前为止到当前时间步所有标签的最佳候选路径,具体地保存当当前时间步上所有标签的最佳路径的上一个标签。

# 保存了目前为止到当前时间步所有标签的最佳候选路径 最终有seq_len-1个(batch_size, num_tags)的Tensor
history: list[torch.Tensor] = []

但此时我们知道上一个标签一定是start标签,所以不用记录。

下面和上小节一样,进行广播后计算累计得分:

for i in range(1, seq_len):
    # broadcast_score: (batch_size, num_tags, 1) = (batch_size, pre_tag, current_tag)
    # 所有可能的当前标签current_tag广播
    broadcast_score = score.unsqueeze(2)
    # 广播成 (batch_size, 1, num_tags)
    # shape: (batch_size, 1, num_tags)
    broadcast_emissions = emissions[i].unsqueeze(1)
    # (batch_size, num_tags, num_tags) = (batch_size, num_tags, 1) + (num_tags, num_tags) + (batch_size, 1, num_tags)
    current_score = broadcast_score + self.transitions + broadcast_emissions

不同的地方来了,计算当前时间步完累计得分current_score后,此时只需要记录最佳路径,比如,对于O来说,如图14,只需要记录此时间步到O标签最大转移得分(实际上我们已经累加上了当前标签的发射得分,但对于同一个标签来说,发射得分都是相同的,所以这里描述不冲突)对应的上一时间步的标签,这里上一时间步的标签正好也是O,整个可以用torch.max完成,它不仅会返回得分,还会返回对应的索引:

for i in range(1, seq_len):
    ...
    # 计算前一时间步到当前时间步的某个标签的最佳得分
    # best_score 形状 (batch_size, num_tags) indices 形状 (batch_size, num_tags)
    # indices 相当于 torch.argmax(current_score, dim=1) ,得到产生最大值对应的索引
    best_score, indices = torch.max(current_score, dim=1)

image-20230616151529584

图14

上图只画了对于标签O来说的最佳路径,实际上当前时间步的所有标签都会进行类似的计算。假设该步的计算结果如图15所示,每个标签上都只记录最佳路径:

image-20230616152219604

图15
for i in range(1, seq_len):
    ...
    best_score, indices = torch.max(current_score, dim=1)
    # 记录得到最佳得分的前一个索引
  	history.append(indices)

接下来当然是把最佳路径对应的前一个索引记录下来。如上面代码所示。

当这个for循环跑完之后,即已经累计到最后一个时间步上所有的标签上了,如下图16所示,下一步是加上转移到stop的转移得分,然后取到end标签的最大得分:

for i in range(1, seq_len):
    ...
    best_score, indices = torch.max(current_score, dim=1)
  	history.append(indices)
    
# 加上到end标签的转移得分 end_transitions本身保存的是所有的标签到end标签的得分
# score (batch_size, num_tags)
score += self.end_transitions
# 计算出最后一个时间步到end标签的最大得分 以及对应的索引(tag)
# best_score 形状(batch_size,)  indices 形状(batch_size,)
best_score, indices = torch.max(score, dim=1)

image-20230616152723340

图16

对于当前输入来说,此时已经知道最大得分是由哪个标签得到的,最后一步根据history中保存的信息回溯到第一个时间步即得到整条路径,再逆序变成正向路径:

# 序列最后有效标签的个数
seq_end_tags = mask.long().sum(dim=0) - 1
# 保存需要返回的结果
best_paths: list[list[int]] = []

# 因为批次内每个样本的最后一个有效标签可能不同,因此需要写成for循环
for i in range(batch_size):
    best_last_tag = indices[i]
    # 通过item()变成普通int
    this_path = [best_last_tag.item()]
    # history 有 seq_len-1个(batch_size, num_tags), 但是是顺序添加的,history[: seq_end_tags[i]]取有效的history,再逆序
    for hist in reversed(history[: seq_end_tags[i]]):
        # 先取批次内第i个样本的路径,再取到this_path[-1],即best_last_tag的最佳标签
        best_last_tag = hist[i][this_path[-1]]
        # 转换为int加入到最佳路径中
        this_path.append(best_last_tag.item())
    # 这里是通过回溯的方法添加的最佳路径,因此最后还需要reversed逆序,变回顺序的,存入best_tags列表
    this_path.reverse()
    best_paths.append(this_path)

return best_paths

注意由于可能存在填充,因此要考虑有效标签。seq_end_tags[i]保存了第i个输入有效标签的个数,只需要取history[: seq_end_tags[i]]即可,它返回了seq_end_tags[i](batch_size, num_tags)的Tensor列表。由于history是正序添加的,这里要取逆序。

this_path初始化为整个路径的最后一个标签,然后根据它回溯完整个路径。

最后返回的best_paths是一个列表,列表大小是批次大小,列表中的元素还是一个列表,表示该输入对应的最佳正序标签路径。

最后给出完整代码,有什么问题欢迎留言。

💡 如果你看完并理解了本文,你会惊醒地发现你理解了pytorch-crf包的源码,因为是大同小异的。

完整代码

# CRF Model
class CRF(nn.Module):
    """
    条件随机场的实现。
    使用前向-后向算法计算输入的对数似然。参考论文 Neural Architectures for Named Entity Recognition 。
    基于Python3.10.9和torch-1.13.1

    """

    def __init__(
        self,
        num_tags: int,
        batch_first: bool = False,
    ) -> None:
        """初始化CRF的参数

        Args:
            num_tags (int): 标签数量
            batch_first (bool, optional): 是否batch维度在前,默认为False
        """
        super().__init__()
        self.num_tags = num_tags
        self.batch_first = batch_first

        # 转移分数(A),表示两个标签之间转移的得分
        # transitions[i,j] 表示由第i个标签转移到第j个标签的得分(可以理解为可能性/置信度)
        self.transitions = nn.Parameter(torch.Tensor(num_tags, num_tags))
        # 新引入了两个状态:start和end
        # 从start状态开始转移的分数
        self.start_transitions = nn.Parameter(torch.Tensor(num_tags))
        # 转移到end状态的分数
        self.end_transitions = nn.Parameter(torch.Tensor(num_tags))

        self.reset_parameters()
        
            
    def __repr__(self):
        return f"CRF(num_tags={self.num_tags})"

    def reset_parameters(self) -> None:
        nn.init.uniform_(self.transitions, -0.1, 0.1)
        nn.init.uniform_(self.start_transitions, -0.1, 0.1)
        nn.init.uniform_(self.end_transitions, -0.1, 0.1)

    def forward(
        self,
        emissions: torch.Tensor,
        tags: torch.LongTensor,
        mask: torch.ByteTensor | None = None,
        reduction: str = 'sum'
    ) -> torch.Tensor:
        """计算给定的标签序列tags的负对数似然

        Args:
            emissions (torch.Tensor):  发射分数P 形状 (seq_len, batch_size, num_tags), 代表序列中每个单词产生每个标签的得分
            tags (torch.LongTensor): 标签序列 如果batch_first=False 形状 (seq_len, batch_size) ,否则 形状为 (batch_size, seq_len)
            mask (torch.ByteTensor | None, optional): 表明哪些元素为填充符,和tags的形状一致。  如果batch_first=False  形状 (seq_len, batch_size) ,否则 形状为 (batch_size, seq_len)
                默认为None,表示没有填充符。
            reduction (str): 汇聚函数: none|sum|mean|token_mean 。 none:不应用汇聚函数; 

        Returns:
            torch.Tensor: 输入tags的负对数似然
        """

        if mask is None:
            # mask 取值为0或1,这里1表示有效标签,默认都为有效标签
            mask = torch.ones_like(tags)

        if self.batch_first:
            # 转换为seq维度在前的形式
            emissions = emissions.permute(0, 1)
            tags = tags.permute(0, 1)
            mask = mask.permute(0, 1)

        # 计算标签序列tags的得分
        score = self._compute_score(emissions, tags, mask)
        # 计算划分函数 partition Z(x)
        partition = self._compute_partition(emissions, mask)

        return (partition - score).mean()

    def _compute_score(
        self,
        emissions: torch.Tensor,
        tags: torch.LongTensor,
        mask: torch.ByteTensor,
    ) -> torch.Tensor:
        """计算标签序列tags的得分


        Args:
            emissions (torch.Tensor): 发射分数P 形状 (seq_len, batch_size, num_tags)
            tags (torch.LongTensor): 标签序列 形状 (seq_len, batch_size)
            mask (torch.ByteTensor): 表明哪些元素为填充符 形状 (seq_len, batch_size)

        Returns:
            torch.Tensor: 批次内标签tags的得分, 形状(batch_size,)
        """

        seq_len, batch_size = tags.shape
        # first_tags (batch_size,)
        first_tags = tags[0]

        # 由start标签转移到批次内所有标签序列第一个标签的得分
        score = self.start_transitions[first_tags]
        # 加上 批次内第一个(index=0)发射得分,即批次内第0个输入产生批次内对应第0个标签的得分

        score += emissions[0, torch.arange(batch_size), first_tags]

        mask = mask.type_as(emissions)  # 类型保持一致
        # 这里的index从1开始,也就是第二个位置开始
        for i in range(1, seq_len):
            # 第i-1个标签转移到第i个标签的得分 + 第i个单词产生第i个标签的得分
            # 乘以mask[i]不需要计算填充单词的得分
            # score 形状(batch_size,)
            score += (
                self.transitions[tags[i - 1], tags[i]]
                + emissions[i, torch.arange(batch_size), tags[i]]
            ) * mask[i]

        # last_tags = tags[-1] × 这是错误的!,因为可能包含填充单词
        valid_last_idx = mask.long().sum(dim=0) - 1  # 有效的最后一个索引
        last_tags = tags[valid_last_idx, torch.arange(batch_size)]

        # 最后加上最后一个标签转移到end标签的转移得分
        score += self.end_transitions[last_tags]
        return score

    def _compute_partition(
        self, emissions: torch.Tensor, mask: torch.ByteTensor
    ) -> torch.Tensor:
        """利用CRF的前向算法计算partition的分数

        Args:
            emissions (torch.Tensor): 发射分数P 形状 (seq_len, batch_size, num_tags)
            mask (torch.ByteTensor): 表明哪些元素为填充符  (seq_len, batch_size)

        Returns:
            torch.Tensor: 批次内的partition分数 形状(batch_size,)
        """

        seq_len = emissions.shape[0]
        # score (batch_size, num_tags) 对于每个批次来说,第i个元素保存到目前为止以i结尾的所有可能序列的得分
        score = self.start_transitions.unsqueeze(0) + emissions[0]

        for i in range(1, seq_len):
            # broadcast_score: (batch_size, num_tags, 1) = (batch_size, pre_tag, current_tag)
            # 所有可能的当前标签current_tag广播
            broadcast_score = score.unsqueeze(2)
            # 广播成 (batch_size, 1, num_tags)
            # shape: (batch_size, 1, num_tags)
            broadcast_emissions = emissions[i].unsqueeze(1)
            # (batch_size, num_tags, num_tags) = (batch_size, num_tags, 1) + (num_tags, num_tags) + (batch_size, 1, num_tags)
            current_score = broadcast_score + self.transitions + broadcast_emissions
            # 在前一时间步标签上求和  -> (batch_size, num_tags)
            # 对于每个批次来说,第i个元素保存到目前为止以i结尾的所有可能标签序列的得分
            current_score = torch.logsumexp(current_score, dim=1)
            # mask[i].unsqueeze(1) -> (batch_size, 1)
            # 只有mask[i]是有效标签的current_score才将值设置到score中,否则保持原来的score
            score = torch.where(mask[i].bool().unsqueeze(1), current_score, score)

        # 加上到end标签的转移得分 end_transitions本身保存的是所有的标签到end标签的得分
        # score (batch_size, num_tags)
        score += self.end_transitions
        # 在所有的标签上求(logsumexp)和
        # return (batch_size,)
        return torch.logsumexp(score, dim=1)

    def decode(
        self, emissions: torch.Tensor, mask: torch.ByteTensor = None
    ) -> list[list[int]]:
        """使用维特比算法找到最有可能的序列

        Args:
            emissions (torch.Tensor):  发射分数P 形状 (seq_len, batch_size, num_tags), 代表序列中每个单词产生每个标签的得分
            mask (torch.ByteTensor | None, optional): 表明哪些元素为填充符。  如果batch_first=False  形状 (seq_len, batch_size) ,否则 形状为 (batch_size, seq_len)
                默认为None,表示没有填充符。

        Returns:
            list[list[int]]: 批次内的最佳标签序列
        """
        if mask is None:
            mask = torch.ones(emissions.shape[:2], dtype=torch.uint8)

        if self.batch_first:
            # 转换为seq维度在前的形式
            emissions = emissions.permute(0, 1)
            mask = mask.permute(0, 1)

        return self._viterbi(emissions, mask)

    def _viterbi(
        self, emissions: torch.Tensor, mask: torch.ByteTensor = None
    ) -> list[list[int]]:
        """维特比算法的实现

        Args:
            emissions (torch.Tensor): 发射分数P 形状 (seq_len, batch_size, num_tags)
            mask (torch.ByteTensor): 表明哪些元素为填充符 形状 (seq_len, batch_size)

        Returns:
            list[list[int]]: 批次内的最佳标签序列
        """
        seq_len, batch_size = mask.shape
        # 由start到当前时间步所有标签的转移得分 + 批次内所有当前时间步产生所有标签的发射得分
        # (num_tags,) +  (batch_size, num_tags) -> (batch_size, num_tags)
        # score 形状 (batch_size, num_tags) 保存了当前位置,到每个tag的最佳累计得分: 前一累计得分+转移得分+发射得分
        score = self.start_transitions + emissions[0]
        # 保存了目前为止到当前时间步所有标签的最佳候选路径 最终有seq_len-1个(batch_size, num_tags)的Tensor
        history: list[torch.Tensor] = []

        for i in range(1, seq_len):
            # broadcast_score: (batch_size, num_tags, 1) = (batch_size, pre_tag, current_tag)
            # 所有可能的当前标签current_tag广播
            broadcast_score = score.unsqueeze(2)
            # 广播成 (batch_size, 1, num_tags)
            # shape: (batch_size, 1, num_tags)
            broadcast_emissions = emissions[i].unsqueeze(1)
            # (batch_size, num_tags, num_tags) = (batch_size, num_tags, 1) + (num_tags, num_tags) + (batch_size, 1, num_tags)
            current_score = broadcast_score + self.transitions + broadcast_emissions
            # 计算前一时间步到当前时间步的某个标签的最佳得分
            # best_score 形状 (batch_size, num_tags) indices 形状 (batch_size, num_tags)
            # indices 相当于 torch.argmax(current_score, dim=1) ,得到产生最大值对应的索引
            best_score, indices = torch.max(current_score, dim=1)

            # mask[i].unsqueeze(1) -> (batch_size, 1)
            # 只有mask[i]是有效标签的best_score才将值设置到score中,否则保持原来的score
            score = torch.where(mask[i].bool().unsqueeze(1), best_score, score)
            # 记录得到最佳得分的前一个索引
            history.append(indices)

        # 加上到end标签的转移得分 end_transitions本身保存的是所有的标签到end标签的得分
        # score (batch_size, num_tags)
        score += self.end_transitions
        # 计算出最后一个时间步到end标签的最大得分 以及对应的索引(tag)
        # best_score 形状(batch_size,)  indices 形状(batch_size,)
        best_score, indices = torch.max(score, dim=1)
        # 序列最后有效标签的个数
        seq_end_tags = mask.long().sum(dim=0) - 1
        # 保存需要返回的结果
        best_paths: list[list[int]] = []

        # 因为批次内每个样本的最后一个有效标签可能不同,因此需要写成for循环
        for i in range(batch_size):
            best_last_tag = indices[i]
            # 通过item()变成普通int
            this_path = [best_last_tag.item()]
            # history 有 seq_len-1个(batch_size, num_tags), 但是是顺序添加的,history[: seq_end_tags[i]]取有效的history,再逆序
            for hist in reversed(history[: seq_end_tags[i]]):
                # 先取批次内第i个样本的路径,再取到this_path[-1],即best_last_tag的最佳标签
                best_last_tag = hist[i][this_path[-1]]
                # 转换为int加入到最佳路径中
                this_path.append(best_last_tag.item())
            # 这里是通过回溯的方法添加的最佳路径,因此最后还需要reversed逆序,变回顺序的,存入best_tags列表
            this_path.reverse()
            best_paths.append(this_path)

        return best_paths


参考

  1. 统计学习方法——条件随机场
  2. pytorch-crf
  3. ADVANCED: MAKING DYNAMIC DECISIONS AND THE BI-LSTM CR
  4. 《统计学习方法》——隐马尔可夫模型

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

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

相关文章

chatgpt赋能python:Python搜索算法:如何提高你的搜索体验

Python 搜索算法:如何提高你的搜索体验 在当今信息爆炸的时代,搜索已成为许多人获取信息的主要途径。而 Python 的搜索算法,也在此背景下日渐受到重视。本篇文章将深入探讨 Python 搜索算法,介绍以及如何使用它来提高你的搜索体验…

SpringBoot整合模板引擎Thymeleaf(5)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 概述 在本节教程中&#xff0c;我们在之前案例的基础上详细介绍利用Thymeleaf实现国际化。 项目结构 依赖文件 请在pom.xml文件中添加如下依赖。 <?xml version"…

Android 9 蓝牙协议初始化

先讲一下Application类的使用 要使用自定义的Application&#xff0c;首先就是要自己新建一个Application的子类&#xff0c;然后把它的名字写在manifest文件里面的application标签里的android:name属性就行&#xff0c;如我的Application子类名字是BaseApplication&#xff0c…

java入门3(程序流程结构)

目录 大部分和C语言一样 顺序结构 选择结构 简单if语句 ​ 多重if结构 嵌套if结构 Switch选择结构 if和Switch嵌套 循环结构 while循环 do while 语句 for循环 break和continue break 中断指令&#xff0c;结束所在层的循环 continue:中断指令 中断本轮的循环&…

chatgpt赋能python:Python收发短信:简单可靠的解决方案

Python收发短信&#xff1a;简单可靠的解决方案 如果您需要向客户发送定期提醒或通知的短信&#xff0c;则 Python 是一种简单易用的解决方案。在本文中&#xff0c;我们将介绍如何使用 Python 发送和接收短信&#xff0c;并探讨一些流行的短信 API。 什么是短信 API&#xf…

深入分析vfio-user设备实现原理 —— Client侧

文章目录 前言数据结构protocolVFIOUserHdrvfio_user_commandVFIOUserHdr flags 设备模型VFIODeviceVFIODevIOVFIOContIO VFIOPCIDeviceVFIOKernPCIDeviceVFIOUserPCIDevice proxyVFIOUserMsgVFIOProxy 流程详解消息发松流程DMA映射流程 前言 数据结构 protocol VFIO User P…

「解析」YOLOv4模型小结

Paper Yolo v4: https://arxiv.org/abs/2004.10934 Scaled-YOLOv4: Scaling Cross Stage Partial Network Source code:https://github.com/AlexeyAB/darknet Bag of Freebies(BoF) 只增加训练成本&#xff0c;但是能显著提高精度&#xff0c;并不影响推理速度&#xff1b;数据…

Kubernetes使用Istio

Kubernetes使用Istio 1、基本概念 1.1、流量方向 南北流量&#xff08;NORTH-SOURTH-TRAFFIC&#xff09;&#xff1a;客户端到服务器之间通信的流量 东西流量(EAST-WEST-TRAFFIC)&#xff1a;指的是服务器和服务器之间的流量 1.2、Service Mesh 2、安装Istio 2.1、下载 …

【编译、链接、装载九】静态链接

【编译和链接九】静态链接 一、demo二、空间与地址分配1、相似段合并 三、即虚拟地址VMA&#xff08;Virtual Memory Address&#xff09;四、重定位1、add调用2、printf调用——同add2、shared 五、重定位表六、符号解析七、c相关问题1、重复代码消除2、全局构造与析构3、C与A…

从创建到维护:掌握package.json的最佳实践

文章目录 I. 介绍什么是package.jsonpackage.json的作用npm与package.json的关系 II. 创建package.jsonnpm init自动生成package.jsonpackage.json各个字段的含义 III. dependencies和devDependenciesdependencies和devDependencies的区别安装依赖包安装依赖包的版本更新依赖包…

Flink 学习十 FlinkSQL

Flink 学习十 Flink SQL 1. FlinkSQL 基础概念 flink sql 基于flink core ,使用sql 语义方便快捷的进行结构化数据处理的上层库; 类似理解sparksql 和sparkcore , hive和mapreduce 1.1 工作流程 整体架构和工作流程 数据流,绑定元数据 schema ,注册成catalog 中的表 table …

【C语言复习】第七篇、关于C语言关键字的知识

目录 第一部分、常见关键字 1、数据类型关键字 2、流程控制类关键字 第二部分、常用的关键字 1、typedef&#xff08;类型重定义/类型重命名&#xff09; 2、static&#xff08;易混淆const&#xff09; 2.1、static修饰局部变量 2.2、static修饰全局变量 2.3、static修饰…

9.创建provider实例

创建provider网络 controller节点 创建一个provider 网络&#xff0c;网络类型为 external 对于 provider 网络来说&#xff0c;实例通过 2 层&#xff08;桥接网络&#xff09;连接到提供商网 络。 参数说明&#xff1a; --share&#xff1a; 允许所有项目都可以使用该网络…

深度学习-【图像分类】学习笔记8 ShuffleNet

文章目录 8.1 ShuffleNet v1 v2理论讲解ShuffleNet v1ShuffleNet v2 8.2 使用Pytorch搭建ShuffleNet 8.1 ShuffleNet v1 v2理论讲解 ShuffleNet v1 论文链接&#xff1a;https://readpaper.com/paper/2963125010 Channel shuffle 相关链接&#xff1a;深度学习-【图像分类】…

车载以太网 - 传输层 - TCP/IP

目录 一、传输层基础介绍 传输层主要包括两种协议 传输层端口号 二、UDP通信 UDP协议介绍 UDP 通信特点: UDP Segment结构 UDP通信过程 三、TCP通信 TCP通信特点: TCP Segment结构 一、传输层基础介绍 传输层的寻址方式&#xff1a;端口号 包括传输层的寻址方式&…

几个SQL的高级写法

一、ORDER BY FLELD() 自定义排序逻辑 MySql 中的排序 ORDER BY 除了可以用 ASC 和 DESC&#xff0c;还可以通过 ORDER BY FIELD(str,str1,...) 自定义字符串/数字来实现排序。这里用 order_diy 表举例&#xff0c;结构以及表数据展示&#xff1a; ORDER BY FIELD(str,str1,..…

chatgpt赋能python:Python支持跨平台软件开发

Python支持跨平台软件开发 作为一种高级编程语言&#xff0c;Python 以其丰富的库和跨平台支持而备受开发人员欢迎。Python 通过将应用程序的可移植性最大化&#xff0c;使得开发人员可以轻松地在不同的操作系统平台上构建和部署软件。 跨平台支持 Python 支持各种不同的操作…

三子棋都玩过吧,那C语言现造一个呢???

目录 前言 三子棋简介 棋盘介绍 规则介绍 程序设计 基本流程 游戏逻辑 菜单界面打印 创建棋盘并初始化 打印棋盘 玩家落子 电脑落子 判断胜负 1.判定是否和棋 2.判定胜负 代码总汇 ✅Game.h 头文件 ✅Game.c ✅Test.c 前言 &#x1f970;想必各位大佬们上学的…

编译安装以及升级Nginx

目录 一、前言 1、简介 2、 Nginx模块 3、与Apache的差异 4、优点 二、编译安装 1、关闭防火墙 2、安装依赖包 3、创建运行用户与组 4、编译安装 5、检测配置文件是否正确 6、添加系统服务 三、版本升级 四、总结 一、前言 1、简介 Nginx是一个高性能的HTTP和反…

chatgpt赋能python:Python散点图连线——一种美妙的可视化方法

Python散点图连线——一种美妙的可视化方法 散点图连线是一种常用的可视化方法&#xff0c;可以展示不同维度之间的关系和趋势。在Python中&#xff0c;我们可以使用多种库来绘制散点图连线&#xff0c;例如matplotlib、seaborn和plotly等。本文将介绍如何使用matplotlib和plo…