精华内容
下载资源
问答
  • view/reshape:大小不变的条件下,转变shapesqueeze/unsqueeze:减少/增加维度transpose/t/permute:转置,单次/多次交换expand/repeat:维度扩展view reshape在pytorch0.3的时候,默认是view .为了numpy一致0.4...

    引言

    本篇介绍tensor的维度变化。

    维度变化改变的是数据的理解方式!

    view/reshape:大小不变的条件下,转变shape

    squeeze/unsqueeze:减少/增加维度

    transpose/t/permute:转置,单次/多次交换

    expand/repeat:维度扩展

    view reshape

    在pytorch0.3的时候,默认是view .为了与numpy一致0.4以后增加了reshape。

    损失维度信息,如果不额外存储/记忆的话,恢复时会出现问题。

    执行view/reshape是有一定的物理意义的,不然不会这样做。

    保证tensor的size不变即可/numel()一致/元素个数不变。

    数据的存储/维度顺序非常非常非常重要

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    In[4]: a = torch.rand(4,1,28,28)

    In[5]: a.shape

    Out[5]: torch.Size([4, 1, 28, 28])

    In[6]: a.view(4,28*28) # 4, 1*28*28 将后面的进行合并/合并通道,长宽,忽略了通道信息,上下左右的空间信息。适合全连接层。

    Out[6]:

    tensor([[0.1483, 0.6321, 0.8864, ..., 0.0646, 0.4791, 0.0892],

    [0.5868, 0.5278, 0.8514, ..., 0.0682, 0.7815, 0.2724],

    [0.4945, 0.4957, 0.0047, ..., 0.4253, 0.4135, 0.1234],

    [0.0618, 0.4257, 0.1960, ..., 0.1377, 0.5776, 0.4071]])

    In[7]: a.view(4,28*28).shape

    Out[7]: torch.Size([4, 784])

    In[8]: a.view(4*28, 28).shape # 合并batch,channel,行合并 放在一起为N [N,28] 每个N,刚好有28个像素点,只关心一行数据

    Out[8]: torch.Size([112, 28])

    In[9]: a.view(4*1,28,28).shape# 4张叠起来了

    Out[9]: torch.Size([4, 28, 28])

    In[10]: b = a.view(4,784) # a原来的维度信息是[b,c,h,w],但a这样赋值后,它是恢复不到原来的

    In[11]: b.view(4,28,28,1) # logic Bug # 语法上没有问题,但逻辑上 [b h w c] 与以前是不对应的。

    a.view(4,783)

    RuntimeError: shape '[4, 783]' is invalid for input of size 3136

    squeeze 与 unsqueeze

    unsqueeze

    unsqueeze(index) 拉伸(增加一个维度) (增加一个组别)

    参数的范围是 [-a.dim()-1, a.dim()+1) 如下面例子中范围是[-5,5)

    -5 –> 0 … -1 –> 4 这样的话,0表示在前面插入,-1表示在后面插入,正负会有些混乱,所以推荐用正数。

    0与正数,就是在xxx前面插入。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    In[17]: a.shape

    Out[17]: torch.Size([4, 1, 28, 28])

    In[18]: a.unsqueeze(0).shape# 在0的前面插入一个维度

    Out[18]: torch.Size([1, 4, 1, 28, 28]) # 理解上就是在batch的基础上增加了组。

    In[19]: a.unsqueeze(-1).shape# 在-1之后插入一个维度

    Out[19]: torch.Size([4, 1, 28, 28, 1]) # 理解上可能增加一个方差之类的

    In[20]: a.unsqueeze(4).shape

    Out[20]: torch.Size([4, 1, 28, 28, 1])

    In[21]: a.unsqueeze(-4).shape

    Out[21]: torch.Size([4, 1, 1, 28, 28])

    In[22]: a.unsqueeze(-5).shape

    Out[22]: torch.Size([1, 4, 1, 28, 28])

    In[23]: a.unsqueeze(-6).shape

    IndexError: Dimension out of range (expected to be in range of [-5, 4], but got -6)

    In[24]: a = torch.tensor([1.2,2.3])

    In[27]: a.shape

    Out[27]: torch.Size([2])

    In[25]: a.unsqueeze(-1) # 维度变成 [2,1] 2行1列

    Out[25]:

    tensor([[1.2000],

    [2.3000]])

    In[26]: a.unsqueeze(0)

    Out[26]: tensor([[1.2000, 2.3000]]) # 维度变成 [1,2] 1行2列

    实际案例

    给一个bias(偏置),bias相当于给每个channel上的所有像素增加一个偏置

    为了做到 f+b 我们需要改变b的维度

    1

    2

    3

    4

    5

    In[28]: b = torch.rand(32)

    In[29]: f = torch.rand(4,32,14,14)

    In[30]: b = b.unsqueeze(1).unsqueeze(2).unsqueeze(0)

    In[31]: b.shape

    Out[31]: torch.Size([1, 32, 1, 1])

    后面进一步扩张到 [4,32,14,14]

    queeze

    squeeze(index) 当index对应的dim为1,就产生作用。

    不写参数,会挤压所有维度为1的。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    In[38]: b.shape

    Out[38]: torch.Size([1, 32, 1, 1])

    In[39]: b.squeeze().shape # 默认将所有维度为1的进行挤压 这32个channel,每个channel有一个值

    Out[39]: torch.Size([32])

    In[40]: b.squeeze(0).shape

    Out[40]: torch.Size([32, 1, 1])

    In[41]: b.squeeze(-1).shape

    Out[41]: torch.Size([1, 32, 1])

    In[42]: b.squeeze(1).shape

    Out[42]: torch.Size([1, 32, 1, 1])

    In[43]: b.squeeze(-4).shape

    Out[43]: torch.Size([32, 1, 1])

    expand / repeat

    Expand:broadcasting (推荐)

    只是改变了理解方式,并没有增加数据

    在需要的时候复制数据

    Reapeat:memory copied

    会实实在在的增加数据

    上面提到的b [1, 32, 1, 1] f[ 4, 32, 14, 14 ]

    目标是将b的维度变成与f相同的维度。

    expand

    扩展(expand)张量不会分配新的内存,只是在存在的张量上创建一个新的视图(view)

    1

    2

    3

    4

    5

    6

    7

    8

    9

    In[44]: a = torch.rand(4,32,14,14)

    In[45]: b.shape

    Out[45]: torch.Size([1, 32, 1, 1]) # 只有1-->N才是可行的, 3 -> N 是需要规则的

    In[46]: b.expand(4,32,14,14).shape

    Out[46]: torch.Size([4, 32, 14, 14])

    In[47]: b.expand(-1,32,-1,-1).shape# -1表示这个维度不变

    Out[47]: torch.Size([1, 32, 1, 1])

    In[48]: b.expand(-1,32,-1,-4).shape# -4这里是一个bug,没有意义,最新版已经修复了

    Out[48]: torch.Size([1, 32, 1, -4])

    repeat

    主动复制原来的。

    参数表示的是要拷贝的次数/是原来维度的倍数

    沿着特定的维度重复这个张量,和expand()不同的是,这个函数拷贝张量的数据。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    In[49]: b.shape

    Out[49]: torch.Size([1, 32, 1, 1])

    In[50]: b.repeat(4,32,1,1).shape

    Out[50]: torch.Size([4, 1024, 1, 1])

    In[51]: b.repeat(4,1,1,1).shape

    Out[51]: torch.Size([4, 32, 1, 1])

    In[52]: b.repeat(4,1,32,32)

    In[53]: b.repeat(4,1,32,32).shape

    Out[53]: torch.Size([4, 32, 32, 32])

    In[55]: b.repeat(4,1,14,14).shape# 这样就达到目标了

    Out[55]: torch.Size([4, 32, 14, 14])

    转置

    .t

    转置操作

    .t 只针对 2维矩阵

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    a = torch.randn(3,4)

    a.t().shape

    Out[58]: torch.Size([4, 3])

    In[60]: a

    Out[60]:

    tensor([[ 0.5629, -0.5085, -0.3371, 1.2387],

    [ 0.2142, -1.7846, 0.2297, 1.7797],

    [-0.3197, 0.6116, 0.3791, 0.9218]])

    In[61]: a.t()

    Out[61]:

    tensor([[ 0.5629, 0.2142, -0.3197],

    [-0.5085, -1.7846, 0.6116],

    [-0.3371, 0.2297, 0.3791],

    [ 1.2387, 1.7797, 0.9218]])

    b.t()

    RuntimeError: t() expects a tensor with <= 2 dimensions, but self is 4D

    transpose

    在结合view使用的时候,view会导致维度顺序关系变模糊,所以需要人为跟踪。

    错误的顺序,会导致数据污染

    一次只能两两交换

    contiguous

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    # 由于交换了1,3维度,就会变得不连续,所以需要用contiguous,来吧数据变得连续。

    In[17]: a1 = a.transpose(1,3).view(4,3*32*32).view(4,3,32,32)

    RuntimeError: invalid argument 2: view size is not compatible with input tensor's size and stride (at least one dimension spans across two contiguous subspaces). Call .contiguous() before .view()

    In[8]: a = torch.randn(4,3,32,32)

    In[9]: a.shape

    Out[9]: torch.Size([4, 3, 32, 32])

    In[10]: a1 = a.transpose(1,3).contiguous().view(4,3*32*32).view(4,3,32,32)

    #[b c h w] 交换1,3维度的数据 [b w h c],再把后面的三个连在一起,展开后变为 [b c w h] 导致和原来的顺序不同,造成数据污染!!!

    In[11]: a1.shape

    Out[11]: torch.Size([4, 3, 32, 32])

    In[12]: a2 = a.transpose(1,3).contiguous().view(4,3*32*32).view(4,32,32,3).transpose(1,3)

    # [b c h w] -> [b w h c] -> [b w h c] -> [b c h w] 和原来顺序相同。

    In[13]: a2.shape

    Out[13]: torch.Size([4, 3, 32, 32])

    # 验证向量一致性

    In[14]: torch.all(torch.eq(a,a1))

    Out[14]: tensor(0, dtype=torch.uint8)

    In[15]: torch.all(torch.eq(a,a2))

    Out[15]: tensor(1, dtype=torch.uint8)

    permute

    会打乱内存顺序,待补充!!!

    由于transpose一次只能两两交换,所以变换后在变回去至少需要两次操作,而permute一次就好。例如对于[b,h,w,c]

    [b,h,w,c]是numpy存储图片的格式,需要这一步才能导出numpy

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    In[18]: a = torch.rand(4,3,28,28)

    In[19]: a.transpose(1,3).shape# [b c h w] -> [b w h c] h与w的顺序发生了变换,导致图像发生了变化

    Out[19]: torch.Size([4, 28, 28, 3])

    In[20]: b = torch.rand(4,3,28,32)

    In[21]: b.transpose(1,3).shape

    Out[21]: torch.Size([4, 32, 28, 3])

    In[22]: b.transpose(1,3).transpose(1,2).shape

    Out[22]: torch.Size([4, 28, 32, 3]) # [b,h,w,c]是numpy存储图片的格式,需要这一步才能导出numpy

    In[23]: b.permute(0,2,3,1).shape

    Out[23]: torch.Size([4, 28, 32, 3])

    Broadcast

    自动扩展:

    维度扩展,自动调用expand

    without copying data ,不需要拷贝数据。

    核心思想

    在前面插入1维

    将size 1 扩展成相同 size 的维度

    例子:

    对于 feature maps : [4, 32, 14, 14],想给它添加一个偏置Bias

    Bias:[32] –> [32, 1 , 1] (这里是手动的) => [1, 32, 1, 1] => [4, 32, 14, 14]

    目标:当Bias和feature maps的size一样时,才能执行叠加操作!!!

    Why broadcasting?

    就像下图表示的一样:我们希望进行如下的几种计算,但需要满足数学上的约束(size相同),为了节省人们为满足数学上的约束而手动复制的过程,而产生的Broadcast,它节省了大量的内容消耗。

    第二行数据中 [3] => [1, 3] => [4, 3] (行复制了4次)

    第三行数据中

    [4,1] => [4, 3] (列复制了3次)

    [1,3] => [4, 3] (行复制了4次)

    broadcast = unsqueze(插入新维度) + expand(将1dim变成相同维度)

    例子:

    有这样的数据 [class, students, scores],具体是4个班,每个班32人,每人8门课程[4, 32, 8] 。

    考试不理想,对于这组数据我们需要为每一位同学的成绩加5分

    要求: [4, 32, 8] + [4, 32, 8]

    实际上:[4, 32, 8] + [5.0]

    操作上:[1] =>(unsqueeze) [1, 1, 1] =>(expand_as) [4, 32, 8],这样需要写3个接口。

    所以才会有 broadcast!!

    内存分析:

    [4, 32, 8] => 1024

    [5.0] => 1 如果是手动复制的话,内存消耗将变为原来的1024倍

    使用条件?

    A [ 大维度 —> 小维度 ]

    从最后一位(最小维度)开始匹配,如果维度上的size是0,1或相同,则满足条件,看下一个维度,直到都满足条件为止。

    如果当前维度是1,扩张到相同维度

    如果没有维度,插入一个维度并扩张到相同维度

    当最小维度不匹配的时候是没法使用broadcastiong,如共有8门课程,但只给了4门课程的变化,这样就会产生歧义。

    note:小维度指定,大维度随意

    小维度指定:假如英语考难了,只加英语成绩 [0 0 5 0 0 0 0 0]

    案例

    情况一

    A[4, 32, 14, 14]

    B[1, 32, 1, 1] => [4,,32, 14, 14]

    情况二

    A[4, 32, 14, 14]

    B[14, 14] => [1, 1, 14, 14] => [4, 32, 14, 14]

    情况三

    不符合条件

    A[4, 32, 14, 14]

    B[2, 32, 14, 14]

    理解这种行为

    小维度指定,大维度随意。小维度设定规则(加5分),大维度默认按照这个规则(通用)。

    维度为1才满足条件,是为了保证公平(统一的规则)

    常见使用情景

    A [4, 3, 32, 32] b,c,h,w

    +[32, 32] 叠加一个相同的feature map,做一些平移变换。相当于一个base(基底),

    +[3, 1, 1] 针对 RGB 进行不同的补充,如R 0.5 、G 0 、B 0.3

    +[1, 1, 1, 1] 对于所有的都加一个数值,抬高一下,如加0.5.

    展开全文
  • 内容包括有:Pytorch自带的分布式训练API,以及Nvidia的apex附带的API,最后再额外写一下Nvidia的DALI数据读取处理加速的方法。文末是一些查看后觉得有用的链接,可以先看看里面的内容,本文只是对其做一些补充。...

    最近尝试用Pytorch的分布式训练API进行一系列实验,踩了不少坑,也看了很多资料。在这里简单做一下总结。内容包括有:Pytorch自带的分布式训练API,以及Nvidia的apex附带的API,最后再额外写一下Nvidia的DALI数据读取与处理加速的方法。文末是一些查看后觉得有用的链接,可以先看看里面的内容,本文只是对其做一些补充。

    在一些大的视觉任务例如语义分割和目标检测中由于数据IO不是瓶颈,所以DALI其实不太需要,我是在ImageNet预训练阶段会用到DALI,提速确实明显。

    首先是Pytorch自带的分布式API:

    import argparse
    parse = argparse.ArgumentParser()
    
    # 在训练的开始要设置一个叫local_rank的参数到环境变量中,用argparse模块。
    # 当使用分布式训练时Pytorch会开多进程,这个参数负责表示具体进程。
    # 通常我们只需要其中一个进程输出结果就可以了。
    
    parse.add_argument('--local_rank', type=int, default=0) 
    args = parse.parse_args()
    local_rank = args.local_rank
    
    
    # 设置并初始化环境,后端通常用nccl就可以。
    
    torch.cuda.set_device(local_rank)
    torch.distributed.init_process_group(backend="nccl", init_method='env://')
    device = torch.device("cuda:{}".format(local_rank))
    
    # 加载数据集,注意加载如DataLoader时,要使用分布式sampler的API,然后把shuffle设置为False。
    # 具体对训练数据shuffle操作放到了每一轮中额外设置。
    # 开启pin_memory是方便后续做数据IO的加速,用到Pytorch自带的一些性质。
    # 通常不是数据IO不是瓶颈时用这个就足够
    traindataset = train_set()
    
    from torch.utils.data.distributed import DistributedSampler
    from torch.utils import data
    datasampler = DistributedSampler(dataset=traindataset)
    trainloader = data.DataLoader(traindataset, sampler=datasampler, batch_size=xxx, shuffle=False,
                                  pin_memory=True, num_workers=xxx)
    

    以上是环境初始化以及数据读取部分的设置,无论使用apex还是Pytorch自带的分布式API,上面都是必须做的,以下模型读取的话Pytorch的API和apex有所不同,主要是apex不仅可以分布式训练,还可以使用混合精度模式以及同步bn(Sync-BN)。参考代码:

    from apex.parallel import DistributedDataParallel as DDP
    from apex.parallel import convert_syncbn_model, SyncBatchNorm
    from apex.fp16_utils import *
    from apex import amp, optimizers
    from apex.multi_tensor_apply import multi_tensor_applier
    
    model = XXXNet()
    model.train()
    model.to(device) # device是上面设置好的device。
    
    # Sync-BatchNorm
    # 使用apex自带的同步BN层。
    # 调用函数后会自动遍历model的层,将Batchnorm层替换。
    model = convert_syncbn_model(model)
    optimizer = optim.SGD(model.parameters(), lr=.., decay=.., ...)
    
    # 用于记录训练过程中的loss变化。
    def reduce_tensor(tensor, world_size=1):
        dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
        tensor /= world_size
        return tensor
    
    # 通过调整下面的opt_level实现半精度训练。
    # opt_level选项有:'O0', 'O1', 'O2', 'O3'.
    # 其中'O0'是fp32常规训练,'O1'、'O2'是fp16训练,'O3'则可以用来推断但不适合拿来训练(不稳定)
    # 注意,当选用fp16模式进行训练时,keep_batchnorm默认是None,无需设置;
    # scale_loss是动态模式,可以设置也可以不设置。
    model, optimizer = amp.initialize(model, optimizer, opt_level=opt_level,
                                      keep_batchnorm_fp32=keep_bn_32, scale_loss=scale_loss)
    
    # Pytorch自带的分布式API
    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank])
    
    # Apex的分布式API
    model = DDP(model)
    
    # 训练过程中:
    while training:
        output = model(input)
        loss = criterion(output, target)
        
        # 方便记录损失下降
        log_loss = reduce_tensor(log_loss.clone().detach_())
        optimizer.zero_grad()
        # 假如设置了fp16训练模式,则要将反传过程替换为以下。
        with amp.scale_loss(loss, optimizer) as scaled_loss:
             scaled_loss.backward()
        optimizer.step()
    

    关于模型保存和重用:

    # Save checkpoint
    # 注意,在保存权重时要注意判断local_rank。
    # 因为是多进程任务,假如多个进程同时写同一个文件在同一个地址会损坏文件。
    # 通常是local_rank 为默认值时保存权重,由于多个进程之间的保持GPU通信,所以每个进程的权重都是一样的。
    if local_rank == 0:
    checkpoint = {
        'model': model.state_dict(),
        'optimizer': optimizer.state_dict(),
        'amp': amp.state_dict()
    }
    torch.save(checkpoint, 'amp_checkpoint.pt')
    ...
    
    
    # 模型重用,把权重放在‘cpu’模式上,然后读取,再设置分布式。
    net = XXXNet()
    net.train()
    resume = 'XXXNet.pth'
    checkpoint = torch.load(resume, map_location='cpu')  # CPU mode
    net.load_state_dict(checkpoint['model'])
    optimizer.load_state_dict(checkpoint['optimizer'])
    
    model, optimizer = amp.initialize(model, optimizer, opt_level=opt_level)
    amp.load_state_dict(checkpoint['amp'])
    

    注意,由于我自己还没有尝试过模型重用,所以具体流程还不是特别清楚,所以这里遇到什么问题可以交流下。

    最后一步就是开启训练:

    # 在终端输入:
    NCCL_DEBUG=INFO CUDA_VISIBLE_DEVICES=0,1 python -m torch.distributed.launch --nproc_per_node=2 train.py
    
    # NCCL_DEBUG=INFO 表示打印nccl初始化信息,可选,用于debug。
    # CUDA_VISIBLE_DEVICES=0,1 用来指定训练用GPU
    # python -m torch.distributed.launch 必须加,用来初始化local_rank到每一个进程
    # --nproc_per_node=2 通常一台机器上用多少GPU就设置多少,每个GPU在一个进程上效率较好。

    最后提一些遇到的比较常见的问题:

    1. apex编译出错:
      1. 有可能是Pytorch的cuda版本和nvcc(NVIDIA (R) Cuda compiler driver)版本不一致。查看nvcc:命令行输入nvcc --version;查看Pytorch的cuda:Python下输入torch.version.cuda 查看。不对应的话建议更换,一般需要cuda 9.2 及以上,Pytorch最好升到1.1以上。
      2. C++文件编译出错。准确来说是Python的C++编译文件,像我的话我的g++在anaconda路径下,和C++其他标准库路径不一致,要在Python代码(通常是setup.py文件中)的Extension部分添加extra_link_args=['-L/usr/lib/x86_64-linux-gnu/']。这个问题一般是出在各种涉及到C++编译例如目标检测的NMS或者ROI操作。之前编译目标检测包如mmdetection和Simpledet等时就是用这种解决办法,一试一个准。
      3. 其他问题应该google就可以搜得到。
    2. 数据加速:
      1. 使用训练小网络时,梯度更新都很快,数据IO的时间消耗是训练时长的瓶颈。特别是训练Mobilenet,Shufflenet,IGCV等小网络时,这时候就要通过一些手段来加速。
      2. 例子一:ImageNet预训练,可以使用Nvidia的DALI库。通过将预处理手段放在GPU上进行来加速数据IO,会占用一部分显存。值得注意的是,DALI支持Pytorch、Mxnet和tensorflow。但是Mxnet的话只能读取record文件,所以还是先将数据生成record再调用DALI,而Pytorch则可以直接调用。详细的API在文末[1]。
      3. 例子二:我们可以用data_prefetcher类来加速,通过预读文件到GPU上来加速,具体做法在[2]。值得注意的是,链接里的类定义了DataLoader的数据输出格式,并对输出的数据做归一化,注意是否需要去除。

    参考:

    1. DALI文档
    2. data_prefetcher类
    英伟达(NVIDIA)训练深度学习模型神器APEX使用指南blog.csdn.net英伟达APEX,多GPU分布式训练,同步Batchnorm,自动混合精度训练法宝指南blog.csdn.net陈瀚可:Pytorch 安装 APEX 疑难杂症解决方案zhuanlan.zhihu.com
    lbin:pytorch 1.0 分布式zhuanlan.zhihu.com
    尼古拉·瓦砾:【PyTorch】唯快不破:基于Apex的混合精度加速zhuanlan.zhihu.com
    3d94db1675d4803690525280a4304b26.png
    lbin:pytorch + apex 生活变得更美好zhuanlan.zhihu.com
    4f1ec834fabc155518780bb5b5ab7a5e.png
    尼古拉·瓦砾:【分布式训练】单机多卡的正确打开方式(三):PyTorchzhuanlan.zhihu.com
    MrTian:给训练踩踩油门 —— Pytorch 加速数据读取zhuanlan.zhihu.com
    19b2951e10c7e6e12403dfb908bbc35a.png
    展开全文
  • 一. BILSTM + CRF介绍1....1.1开始之前我们假设我们的数据集中有两类实体——人名和地名,之相对应在我们的训练数据集中,有五类标签:B-Person, I- Person,B-Organization,I-Organization假...

    一. BILSTM + CRF介绍

    1.介绍

    基于神经网络的方法,在命名实体识别任务中非常流行和普遍。 如果你不知道Bi-LSTM和CRF是什么,你只需要记住他们分别是命名实体识别模型中的两个层。

    1.1开始之前

    我们假设我们的数据集中有两类实体——人名和地名,与之相对应在我们的训练数据集中,有五类标签:

    B-Person, I- Person,B-Organization,I-Organization

    假设句子x由五个字符w1,w2,w3,w4,w5组成,其中【w1,w2】为人名类实体,【w3】为地名类实体,其他字符标签为“O”。

    1.2BiLSTM-CRF模型

    以下将给出模型的结构:

    第一,句子x中的每一个单元都代表着由字嵌入或词嵌入构成的向量。其中,字嵌入是随机初始化的,词嵌入是通过数据训练得到的。所有的嵌入在训练过程中都会调整到最优。

    第二,这些字或词嵌入为BiLSTM-CRF模型的输入,输出的是句子x中每个单元的标签。

    566c6faace64

    Bi-LSTM结构图

    尽管一般不需要详细了解BiLSTM层的原理,但是为了更容易知道CRF层的运行原理,我们需要知道BiLSTM的输出层。

    566c6faace64

    图2.Bi-LSTM标签预测原理图

    如上图所示,BiLSTM层的输出为每一个标签的预测分值,例如,对于单元w0,BiLSTM层输出的是

    1.5 (B-Person), 0.9 (I-Person), 0.1 (B-Organization), 0.08 (I-Organization) 0.05 (O).

    这些分值将作为CRF的输入。

    1.3 如果没有CRF层会怎样

    你也许已经发现了,即使没有CRF层,我们也可以训练一个BiLSTM命名实体识别模型,如图所示:

    566c6faace64

    图3.去除CRF的BiLSTM命名实体识别模型

    由于BiLSTM的输出为单元的每一个标签分值,我们可以挑选分值最高的一个作为该单元的标签。例如,对于单元w0,“B-Person”有最高分值—— 1.5,因此我们可以挑选“B-Person”作为w0的预测标签。同理,我们可以得到w1——“I-Person”,w2—— “O” ,w3——“B-Organization”,w4——“O”。

    虽然我们可以得到句子x中每个单元的正确标签,但是我们不能保证标签每次都是预测正确的。例如,图4.中的例子,标签序列是“I-Organization I-Person” and “B-Organization I-Person”,很显然这是错误的。

    566c6faace64

    在这里插入图片描述

    1.4 CRF层能从训练数据中获得约束性的规则

    CRF层可以为最后预测的标签添加一些约束来保证预测的标签是合法的。在训练数据训练过程中,这些约束可以通过CRF层自动学习到。

    这些约束可以是:

    I:句子中第一个词总是以标签“B-“ 或 “O”开始,而不是“I-”

    II:标签“B-label1 I-label2 I-label3 I-…”,label1, label2, label3应该属于同一类实体。例如,“B-Person I-Person” 是合法的序列, 但是“B-Person I-Organization” 是非法标签序列.

    III:标签序列“O I-label” is 非法的.实体标签的首个标签应该是 “B-“ ,而非 “I-“, 换句话说,有效的标签序列应该是“O B-label”。

    有了这些约束,标签序列预测中非法序列出现的概率将会大大降低。

    二. 标签的score和损失函数的定义

    Bi-LSTM layer的输出维度是tag size,这就相当于是每个词 wi 映射到tag的发射概率值,设Bi-LSTM的输出矩阵为P,其中Pi,j代表词wi映射到tagj的非归一化概率。对于CRF来说,我们假定存在一个转移矩阵A,则Ai,j代表tagi转移到tagj的转移概率。

    对于输入序列 X 对应的输出tag序列 y,定义分数为

    566c6faace64

    在这里插入图片描述

    利用Softmax函数,我们为每一个正确的tag序列y定义一个概率值(YX代表所有的tag序列,包括不可能出现的)

    566c6faace64

    在这里插入图片描述

    因而在训练中,我们只需要最大化似然概率p(y|X)即可,这里我们利用对数似然

    566c6faace64

    在这里插入图片描述

    所以我们将损失函数定义为-log(p(y|X)),就可以利用梯度下降法来进行网络的学习了。

    loss function:

    566c6faace64

    在这里插入图片描述

    在对损失函数进行计算的时候,S(X,y)的计算很简单,而

    566c6faace64

    在这里插入图片描述(下面记作logsumexp)的计算稍微复杂一些,因为需要计算每一条可能路径的分数。这里用一种简便的方法,对于到词wi+1的路径,可以先把到词wi的logsumexp计算出来,因为

    566c6faace64

    在这里插入图片描述

    因此先计算每一步的路径分数和直接计算全局分数相同,但这样可以大大减少计算的时间。

    三. 对于损失函数的详细解释

    这篇文章对于理解十分有用

    举例说 【我 爱 中国人民】对应标签【N V N】那这个标签就是一个完整的路径,也就对应一个Score值。

    接下来我想讲的是这个公式:

    566c6faace64

    在这里插入图片描述

    这个公式成立是很显然的,动笔算一算就知道了,代码里其实就是用了这个公式的原理。

    def _forward_alg(self, feats):

    # Do the forward algorithm to compute the partition function

    init_alphas = torch.full((1, self.tagset_size), -10000.)

    # START_TAG has all of the score.

    init_alphas[0][self.tag_to_ix[START_TAG]] = 0.

    # Wrap in a variable so that we will get automatic backprop

    forward_var = init_alphas

    # Iterate through the sentence

    for feat in feats:

    alphas_t = [] # The forward tensors at this timestep

    for next_tag in range(self.tagset_size):

    # broadcast the emission score: it is the same regardless of

    # the previous tag

    emit_score = feat[next_tag].view(

    1, -1).expand(1, self.tagset_size)

    # the ith entry of trans_score is the score of transitioning to

    # next_tag from i

    trans_score = self.transitions[next_tag].view(1, -1)

    # The ith entry of next_tag_var is the value for the

    # edge (i -> next_tag) before we do log-sum-exp

    next_tag_var = forward_var + trans_score + emit_score

    # The forward variable for this tag is log-sum-exp of all the

    # scores.

    alphas_t.append(log_sum_exp(next_tag_var).view(1))

    forward_var = torch.cat(alphas_t).view(1, -1)

    terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]

    alpha = log_sum_exp(terminal_var)

    return alpha

    我们看到有这么一段代码 :

    next_tag_var = forward_var + trans_score + emit_score

    我们主要就是来讲讲他。

    首先这个算法的思想是:假设我们要做一个词性标注的任务,对句子【我 爱 中华人民】,我们要对这个句子做

    566c6faace64

    在这里插入图片描述

    意思就是 对这个句子所有可能的标注,都算出来他们的Score,然后按照指数次幂加起来,再取对数。一般来说取所有可能的标注情况比较复杂,我们这里举例是长度为三,但是实际过程中,可能比这个要大得多,所以我们需要有一个简单高效得算法。也就是我们程序中得用得算法, 他是这么算得:

    先算出【我, 爱】可能标注得所有情况,取 log_sum_exp 然后加上 转换到【中国人民】得特征值 再加上【中国人民】对应得某个标签得特征值。其等价于【我,爱,中国人民】所有可能特征值指数次幂相加,然后取对数.

    接下来我们来验证一下是不是这样

    首先我们假设词性一共只有两种 名词N 和 动词 V

    那么【我,爱】得词性组合一共有四种 N + N,N + V, V + N, V + V

    那么【爱】标注为N时得log_sum_exp 为

    566c6faace64

    在这里插入图片描述

    【爱】 标注为 V时的 log_sum_exp为

    566c6faace64

    在这里插入图片描述

    我们的forward列表里就是存在着这两个值,即:

    566c6faace64

    在这里插入图片描述

    假设【中华人民】得词性为N,我们按照代码来写一下公式,在forward列表对应位置相加就是这样

    566c6faace64

    在这里插入图片描述

    566c6faace64

    在这里插入图片描述

    四. 代码块详细说明:

    先说明两个重要的矩阵:

    feats: 发射矩阵(emit score)是sentence 在embedding后,再经过LSTM后得到的矩阵(也就是LSTM的输出), 维度为11 * 5 (11为sentence 的length,5是标签数)。这个矩阵表示经过LSTM后sentence的每个word对应的每个labels的得分)。 表示发射概率。

    self.transitions:转移矩阵,维度为55,transitions[i][j]表示label j转移到label i的概率。transtion[i]维度为15,表示每个label转移到label i的概率。 表示概率矩阵

    1. def log_sum_exp(vec)

    # compute log sum exp in numerically stable way for the forward algorithm

    def log_sum_exp(vec): #vec是1*5, type是Variable

    max_score = vec[0, argmax(vec)]

    # max_score维度是1, max_score.view(1,-1)维度是1*1,

    # max_score.view(1, -1).expand(1, vec.size()[1])的维度是1*5

    max_score_broadcast = max_score.view(1, -1).expand(1, vec.size()[1])

    # 里面先做减法,减去最大值可以避免e的指数次,计算机上溢

    return max_score + \

    torch.log(torch.sum(torch.exp(vec - max_score_broadcast)))

    你可能会疑问return 的结果为什么先减去max_score.其实这是一个小技巧,因为一上来就做指数运算可能会引起计算结果溢出,先减去score,经过log_sum_exp后,再把max_score给加上。

    其实就等同于:

    return torch.log(torch.sum(torch.exp(vec)))

    2. def neg_log_likelihood(self, sentence, tags)

    如果你完整地把代码读完,你会发现neg_log_likelihood()这个函数是loss function.

    loss = model.neg_log_likelihood(sentence_in, targets)

    我们来分析一下neg_log_likelihood()函数代码:

    def neg_log_likelihood(self, sentence, tags):

    # feats: 11*5 经过了LSTM+Linear矩阵后的输出,之后作为CRF的输入。

    feats = self._get_lstm_features(sentence)

    forward_score = self._forward_alg(feats)

    gold_score = self._score_sentence(feats, tags)

    return forward_score - gold_score

    你在这里可能会有疑问:问什么forward_score - gold_score可以作为loss呢。

    这里,我们回顾一下我们在上文中说明的loss function函数公式:

    566c6faace64

    在这里插入图片描述

    你就会发现forward_score和gold_score分别表示上述等式右边的两项。

    3. def _forward_alg(self, feats):

    我们通过上一个函数的分析得知这个函数就是用来求forward_score的,也就是loss function等式右边的第一项:

    566c6faace64

    在这里插入图片描述

    # 预测序列的得分

    # 只是根据随机的transitions,前向传播算出的一个score

    #用到了动态规划的思想,但因为用的是随机的转移矩阵,算出的值很大score>20

    def _forward_alg(self, feats):

    # do the forward algorithm to compute the partition function

    init_alphas = torch.full((1, self.tagset_size), -10000.) # 1*5 而且全是-10000

    # START_TAG has all of the score

    # 因为start tag是4,所以tensor([[-10000., -10000., -10000., 0., -10000.]]),

    # 将start的值为零,表示开始进行网络的传播,

    init_alphas[0][self.tag_to_ix[START_TAG]] = 0

    # warp in a variable so that we will get automatic backprop

    forward_var = init_alphas # 初始状态的forward_var,随着step t变化

    # iterate through the sentence

    # 会迭代feats的行数次

    for feat in feats: #feat的维度是5 依次把每一行取出来~

    alphas_t = [] # the forward tensors at this timestep

    for next_tag in range(self.tagset_size): #next tag 就是简单 i,从0到len

    # broadcast the emission(发射) score:

    # it is the same regardless of the previous tag

    # 维度是1*5 LSTM生成的矩阵是emit score

    emit_score = feat[next_tag].view(

    1, -1).expand(1, self.tagset_size)

    # the i_th entry of trans_score is the score of transitioning

    # to next_tag from i

    trans_score = self.transitions[next_tag].view(1, -1) # 维度是1*5

    # The ith entry of next_tag_var is the value for the

    # edge (i -> next_tag) before we do log-sum-exp

    # 第一次迭代时理解:

    # trans_score所有其他标签到B标签的概率

    # 由lstm运行进入隐层再到输出层得到标签B的概率,emit_score维度是1*5,5个值是相同的

    next_tag_var = forward_var + trans_score + emit_score

    # The forward variable for this tag is log-sum-exp of all the scores

    alphas_t.append(log_sum_exp(next_tag_var).view(1))

    # 此时的alphas t 是一个长度为5,例如:

    # [tensor(0.8259), tensor(2.1739), tensor(1.3526), tensor(-9999.7168), tensor(-0.7102)]

    forward_var = torch.cat(alphas_t).view(1, -1) #到第(t-1)step时5个标签的各自分数

    # 最后只将最后一个单词的forward var与转移 stop tag的概率相加

    # tensor([[ 21.1036, 18.8673, 20.7906, -9982.2734, -9980.3135]])

    terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]

    alpha = log_sum_exp(terminal_var) # alpha是一个0维的tensor

    return alpha

    4. def _score_sentence(self, feats, tags)

    由2的函数分析,我们知道这个函数就是求gold_score,即loss function的第二项

    # 根据真实的标签算出的一个score,

    # 这与上面的def _forward_alg(self, feats)共同之处在于:

    # 两者都是用的随机转移矩阵算的score

    # 不同地方在于,上面那个函数算了一个最大可能路径,但实际上可能不是真实的各个标签转移的值

    # 例如:真实标签是N V V,但是因为transitions是随机的,所以上面的函数得到其实是N N N这样,

    # 两者之间的score就有了差距。而后来的反向传播,就能够更新transitions,使得转移矩阵逼近

    #真实的“转移矩阵”

    # 得到gold_seq tag的score 即根据真实的label 来计算一个score,

    # 但是因为转移矩阵是随机生成的,故算出来的score不是最理想的值

    def _score_sentence(self, feats, tags): #feats 11*5 tag 11 维

    # gives the score of a provied tag sequence

    score = torch.zeros(1)

    # 将START_TAG的标签3拼接到tag序列最前面,这样tag就是12个了

    tags = torch.cat([torch.tensor([self.tag_to_ix[START_TAG]], dtype=torch.long), tags])

    for i, feat in enumerate(feats):

    # self.transitions[tags[i + 1], tags[i]] 实际得到的是从标签i到标签i+1的转移概率

    # feat[tags[i+1]], feat是step i 的输出结果,有5个值,

    # 对应B, I, E, START_TAG, END_TAG, 取对应标签的值

    # transition【j,i】 就是从i ->j 的转移概率值

    score = score + \

    self.transitions[tags[i+1], tags[i]] + feat[tags[i + 1]]

    score = score + self.transitions[self.tag_to_ix[STOP_TAG], tags[-1]]

    return score

    5. def _viterbi_decode(self, feats):

    # 维特比解码, 实际上就是在预测的时候使用了, 输出得分与路径值

    # 预测序列的得分

    def _viterbi_decode(self, feats):

    backpointers = []

    # initialize the viterbi variables in long space

    init_vvars = torch.full((1, self.tagset_size), -10000.)

    init_vvars[0][self.tag_to_ix[START_TAG]] = 0

    # forward_var at step i holds the viterbi variables for step i-1

    forward_var = init_vvars

    for feat in feats:

    bptrs_t = [] # holds the backpointers for this step

    viterbivars_t = [] # holds the viterbi variables for this step

    for next_tag in range(self.tagset_size):

    # next-tag_var[i] holds the viterbi variable for tag i

    # at the previous step, plus the score of transitioning

    # from tag i to next_tag.

    # we don't include the emission scores here because the max

    # does not depend on them(we add them in below)

    # 其他标签(B,I,E,Start,End)到标签next_tag的概率

    next_tag_var = forward_var + self.transitions[next_tag]

    best_tag_id = argmax(next_tag_var)

    bptrs_t.append(best_tag_id)

    viterbivars_t.append(next_tag_var[0][best_tag_id].view(1))

    # now add in the emssion scores, and assign forward_var to the set

    # of viterbi variables we just computed

    # 从step0到step(i-1)时5个序列中每个序列的最大score

    forward_var = (torch.cat(viterbivars_t) + feat).view(1, -1)

    backpointers.append(bptrs_t) # bptrs_t有5个元素

    # transition to STOP_TAG

    # 其他标签到STOP_TAG的转移概率

    terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]

    best_tag_id = argmax(terminal_var)

    path_score = terminal_var[0][best_tag_id]

    # follow the back pointers to decode the best path

    best_path = [best_tag_id]

    for bptrs_t in reversed(backpointers):

    best_tag_id = bptrs_t[best_tag_id]

    best_path.append(best_tag_id)

    # pop off the start tag

    # we don't want to return that ti the caller

    start = best_path.pop()

    assert start == self.tag_to_ix[START_TAG] # Sanity check

    best_path.reverse() # 把从后向前的路径正过来

    return path_score, best_path

    如果对于该函数还没有太理解,可以参考这篇博客:

    总结

    以上就是我结合了几篇比较不错的博客后的总结,欢迎大家提问。

    展开全文
  • 点上方蓝字人工智能算法Python大数据获取更多干货在右上方···设为星标★,第一时间获取资源仅做学术分享,如有侵权,联系删除转载于 :奔腾的黑猫@知乎来源丨https://zhuanlan.zhihu.com/p/158643792在做毕设的...

    点上方蓝字人工智能算法与Python大数据获取更多干货

    在右上方 ··· 设为星标 ,第一时间获取资源

    仅做学术分享,如有侵权,联系删除

    转载于 :奔腾的黑猫@知乎

    来源丨https://zhuanlan.zhihu.com/p/158643792在做毕设的时候,需要实现一个PyTorch原生代码中没有的并行算子,所以用到了这部分的知识,再不总结就要忘光了= =本文内容主要是PyTorch的官方教程的各种传送门,这些官方教程写的都很好,以后就可以不用再浪费时间在百度上了。由于图神经网络计算框架PyG的代码实现也是采用了扩展的方法,因此也可以当成下面总结PyG源码文章的前导知识吧 。

    第一种情况:使用PyThon扩展PyTorch

    使用PyThon扩展PyTorch准确的来说是在PyTorch的Python前端实现自定义算子或者模型,不涉及底层C++的实现。这种扩展方式是所有扩展方式中最简单的,也是官方首先推荐的,这是因为PyTorch在NVIDIA cuDNN,Intel MKL或NNPACK之类的库的支持下已经对可能出现的CPU和GPU操作进行了高度优化,因此用Python扩展的代码通常足够快。比如要扩展一个新的PyThon算子(torch.nn)只需要继承torch.nn.Module并实现其forward方法即可。详细的过程请参考官方教程传送门:https://pytorch.org/docs/master/notes/extending.html

    第二种情况:使用pybind11构建共享库形式的C++和CUDA扩展

    但是如果我们想对代码进行进一步优化,比如对自己的算子添加并行的CUDA实现或者连接个OpenCV的库什么的,那么仅仅使用Python进行扩展就不能满足需求;其次如果我们想序列化模型,在一个没有Python环境的生产环境下部署,也需要我们使用C++重写算法;最后考虑到考虑到多线程执行和性能原因,一般Python代码也并不适合做部署。因此在对性能有要求或者需要序列化模型的场景下我们还是会用到C++扩展。下面我先把官方教程传送门放在这里:https://pytorch.org/tutorials/advanced/cpp_extension.html对于一种典型的扩展情况,比如我们要设计一个全新的C++底层算子,其过程其实就三步:第一步:使用C++编写算子的forward函数和backward函数第二步:将该算子的forward函数和backward函数使用**pybind11**绑定到python上第三步:使用setuptools/JIT/CMake编译打包C++工程为so文件注意到在第一步中,我们不仅仅要实现forward函数也要实现backward函数,这是因为在C++端PyTorch目前不支持自动根据forward函数推导出backward函数,所以我们必须要对自己算子的反向传播过程完全清楚。一个需要注意的地方是,你可以选择直接在C++中继承torch::autograd类进行扩展;也可以像官方教程中那样在C++代码中实现forward和backward的核心过程,而在python端继承PyTorch的torch.autograd.Function类。在C++端扩展forward函数和backward函数的需要注意以下规则:(1)首先无论是forward函数还是backward函数都需要声明为静态函数。(2)forward函数可以接受任意多的参数并且应该返回一个 variable list或者variable;forward函数需要将[torch::autograd::AutogradContext](https://link.zhihu.com/?target=https%3A//pytorch.org/cppdocs/api/structtorch_1_1autograd_1_1_autograd_context.html%23structtorch_1_1autograd_1_1_autograd_context) 作为自己的第一个参数。Variables可以被使用ctx->save_for_backward保存,而其他数据类型可以使用ctx->saved_data以<:string>pairs的形式保存在一个map中。(3)backward函数第一个参数同样需要为torch::autograd::AutogradContext,其余的参数是一个variable_list,包含的变量数量与forward输出的变量数量相等。它应该返回和forward输入一样多的变量。保存在forward中的Variable变量可以通过ctx->get_saved_variables而其他的数据类型可以通过ctx->saved_data获取。请注意,backward的输入参数是自动微分系统反传回来的参数梯度值,其需要和forward函数的返回值位置一一对应的;而backward的返回值是对各参数根据自动微分规则求导后的梯度值,其需要和forward函数的输入参数位置一一对应,对于不需要求导的参数也需要使用空Variable占位。
    // PyG的C++扩展就选择的是直接继承PyTorch的C++端的torch::autograd类进行扩展// 下面是PyG的一个ScatterSum算子的扩展示例// 不用纠结这个算子的具体内容,对扩展的算子的结构有一个大致了解即可class ScatterSum : public torch::autograd::Function {public:  // AutogradContext *ctx指针可以操作  static variable_list forward(AutogradContext *ctx, Variable src,                               Variable index, int64_t dim,                               torch::optional optional_out,                               torch::optional dim_size) {    dim = dim < 0 ? src.dim() + dim : dim;    ctx->saved_data["dim"] = dim;    ctx->saved_data["src_shape"] = src.sizes();    index = broadcast(index, src, dim);    auto result = scatter_fw(src, index, dim, optional_out, dim_size, "sum");    auto out = std::get<0>(result);    ctx->save_for_backward({index});    // 如果在扩展的C++代码中使用非Aten内建操作修改了tensor的值,需要对其进行脏标记    if (optional_out.has_value())      ctx->mark_dirty({optional_out.value()});      return {out};  } // grad_outs是out参数反传回来的梯度值  static variable_list backward(AutogradContext *ctx, variable_list grad_outs) {    auto grad_out = grad_outs[0];    auto saved = ctx->get_saved_variables();    auto index = saved[0];    auto dim = ctx->saved_data["dim"].toInt();    auto src_shape = list2vec(ctx->saved_data["src_shape"].toIntList());    auto grad_in = torch::gather(grad_out, dim, index, false);    // 不需要求导的参数需要空Variable占位    return {grad_in, Variable(), Variable(), Variable(), Variable()};  }};
    由于涉及到在C++环境下操作张量和反向传播等操作,因此我们需要对PyTorch的C++后端的库有所了解,主要就是Torch和Aten这两个库,下面我简要介绍一下这两兄弟。
    b4aeec9915f2685672870cbe3a274511.png
    其中Torch是PyTorch的C++底层实现(PS:其实是先有的Torch后有的PyTorch,从名字也能看出来),FB在编码PyTorch的时候就有意将PyTorch的接口和Torch的接口设计的十分类似,因此如果你对PyTorch很熟悉的话那么你也会很快的对Torch上手。Torch官方文档传送门:https://pytorch.org/cppdocs/frontend.html安装PyTorch的C++前端的官方教程:https://pytorch.org/cppdocs/installing.html而Aten是ATen从根本上讲是一个张量库,在PyTorch中几乎所有其他Python和C ++接口都在其上构建。它提供了一个核心Tensor类,在其上定义了数百种操作。这些操作大多数都具有CPU和GPU实现,Tensor该类将根据其类型向其动态调度。和Torch相比Aten更接近底层和核心逻辑。Aten源代码传送门:https://github.com/zdevito/ATen/tree/master/aten/srcgithub.com使用Aten声明和操作张量的教程:https://pytorch.org/cppdocs/notes/tensor_basics.html由于Pyorch的C++后端文档比较少,因此要多参考官方的例子,尝试去模仿官方教程的代码,同时可以通过Python前端的接口猜测后端接口的功能,如果没有文档了就读一读源码,还是有不少注释的,还能理解实现的逻辑。

    第三种情况:为TORCHSCRIPT添加C++和CUDA扩展

    首先简单解释一下TorchScript是什么,如果用官方的定义来说:“TorchScript是一种从PyTorch代码创建可序列化和可优化模型的方法。任何TorchScript程序都可以从一个Python进程中保存并可以在一个没有Python环境的进程中被加载。”通俗来说TorchScript就是一个序列化模型(即Inference)的工具,它可以让你的PyTorch代码方便的在生产环境中部署,同时在将PyTorch代码转化TorchScript代码时还会对你的模型进行一些性能上的优化。使用TorchScript完成模型的部署要比我们之前提到的使用C++重写要简单的多,因为是自动生成的。TorchScript包含两种序列化模型的方法:tracingscript,两种方法各有其适用场景,由于和本文关系不大就不详细展开了,具体的官方教程传送门在此:https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html但是,TorchScript只能自动化的构造PyTorch的原生代码,如果我们需要序列化自定义的C++扩展算子,则需要我们显式的将这些自定义算子注册到TorchScript中,所幸的是,这一过程其实非常简单,整个过程和第二小节中使用pybind11构建共享库的形式的C++和CUDA扩展十分类似。官方教程传送门如下:https://pytorch.org/tutorials/advanced/torch_script_custom_ops.html而对于自定义的C++类,如果要注册到TorchScript要稍微复杂一些,官方教程传送门如下:https://pytorch.org/tutorials/advanced/torch_script_custom_classes.html?highlight=registeroperators另外需要注意的是,如果想要编写能够被TorchScript编译器理解的代码,需要注意在C++自定义扩展算子参数中的数据类型,目前被TorchScript支持的参数数据类型有torch::Tensortorch::Scalar(标量类型),doubleint64_tstd::vector,而像float,int,short这些是不能作为自定义扩展算子的参数数据类型的。目前就先总结这么多吧~

    ------------------

    声明:本内容来源网络,版权属于原作者

    图片来源网络,不代表本公众号立场。如有侵权,联系删除

    AI博士私人微信,还有少量空位

    3169455e2f42272ead726b5aab121159.png

    7b3e991b14ca1c984af68a94a385fc8e.gif

    如何画出漂亮的深度学习模型图?

    如何画出漂亮的神经网络图?

    一文读懂深度学习中的各种卷积

    5fd3770c362785b65b13081c04625585.png

    点个在看支持一下吧75c884b65f4d18a1c9a94b9219e958f3.png75c884b65f4d18a1c9a94b9219e958f3.png

    展开全文
  • pytorch 有多种乘法运算,在这里做一次全面的总结。元素一一相乘该操作又称作 "哈达玛积", 简单来说就是 tensor 元素逐个相乘。这个操作,是通过 也就是常规的乘号操作符定义的操作结果。torch.mul 是等价的。import...
  • 从接口的角度来讲,对tensor的...为方便使用,对tensor的大部分操作同时支持这两类接口,在此不做具体区分,如torch.sum (torch.sum(a, b))tensor.sum (a.sum(b))功能等价。而从存储的角度来讲,对tensor的操作...
  • pytorch可以说是torch的python版,然后增加了很多新的特性 torch是火炬的意思 上面的对比图来源于官网,官方认为,这两者最大的区别就是Pytorch重新设计了model模型和intermediate中间变量的关系,在Pytorch中...
  • Pytorch torch.cat与torch.chunk

    千次阅读 2019-11-08 15:26:13
    部分内容摘自pytorch中文文档 torch.cat torch.cat(inputs, dimension=0) → Tensor ,在给定维度上对输入的张量序列seq 进行连接操作。 torch.cat()可以看做 torch.split() 和 torch.chunk()的反操作, cat() 函数...
  • 数据导入处理来自这里。在解决任何机器学习问题时,都需要在处理数据上花费大量的努力。PyTorch提供了很多工具来简化数据加载,希望使代码更具可读性。在本教程中,我们将学习如何从繁琐的数据中加载、预处理数据...
  • pytorch与torch的关系

    万次阅读 2018-09-23 22:15:41
    分享一篇关于pytorchtorch关系的文章,讲的很清楚。 本文转载至:https://m.oldpan.me/archives/pytorch-torch-relation, 对pytorch感兴趣的可以关注Oldpan博客微信公众号,干货多多,同步更新博客深度学习文章...
  • pytorch深度指南-torch与Tensor常用操作方法import torchtorch.Tensor会继承某些torch的某些数学运算,例如sort, min/max....不需要调用相应的torch.funciton进行处理,下文中如果是torch/Tensor即表示该函数可以直接...
  • 主要介绍了PyTorchtorch.tensor与torch.Tensor的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • Pytorch安装方法与Pytorch module ‘torch’ has no attribute 'FloatTensor’等错误出现的奇怪原因解决法 Pytorch安装方法(我将国内一些开源的安装链接附在最后供你们使用): ①Anaconda3使用Anaconda Prompt打出...
  • pytorch中文文档:torch.nn 在__init__()函数里定义,定义的是一个类: torch.nn.functional pytorch中文文档:torch.nn.functional 在__forward()__函数里定义,定义的是一个函数: 两者的区别: nn中是定义...
  • 9/6 Pytorch与Torch的关系

    2020-09-06 09:30:33
    pytorch可以说是torch的python版 Pytorch采用python语言接口来实现编程,而torch是采用lua语言,Lua相当于一个小型加强版的C,支持类和面向对象
  • 浅谈Pytorch与Torch的关系

    千次阅读 2019-12-25 11:10:06
    那么Pytorch是怎么来的,追根溯源,pytorch可以说是torch的python版,然后增加了很多新的特性,那么pytorchtorch的具体区别是什么,这篇文章大致对两者进行一下简要分析,有一个宏观的了解。 上面的对比图来源...
  • Pytorch torch.cat与torch.stack的区别

    千次阅读 2019-10-24 12:36:35
    torch.cat()函数可以将多个张量拼接成一个张量。torch.cat()有两个参数,第一个是要拼接的张量的列表或是元组;第二个参数是拼接的维度。 torch.cat()的示例如下图1所示 ...stackcat的区别在于,torch.stack()函...
  • 加入极市专业CV交流群,6000+来自腾讯,...点击文末“阅读原文”立刻申请入群~作者| 余霆嵩来源专栏 |PyTorch学习笔记已获作者原创授权,请勿二次转发本文截取自一个github上千星的火爆教程——《PyTorch 模型训...
  • 那么Pytorch是怎么来的,追根溯源,pytorch可以说是torch的python版,然后增加了很多新的特性,那么pytorchtorch的具体区别是什么,这篇文章大致对两者进行一下简要分析,有一个宏观的了解。上面的对比图来源于...
  • 专访Facebook研究员田渊栋和PyTorch作者Soumith 新智元 :田博士,关于PyTorch的发布,请问...新智元 :请问 PyTorch 除了是基于 Python,其它架构是否 Torch 一样? 田渊栋:基本C/C++这边都是用的torch原来的函...
  • PyTorchtorch.tensor与torch.Tensor的区别

    千次阅读 2020-05-17 18:08:27
    PyTorch最近几年可谓大火。相比于TensorFlow,PyTorch对于Python初学者更为友好,更易上手。 众所周知,numpy作为Python中数据分析的专业第三方库,比Python自带的Math库速度更快。同样的,在PyTorch中,有一个...
  • tensorflow与pytorch

    千次阅读 2019-04-22 16:57:33
    tensorflow中与pytorch同等作用的函数: tf.reshape(input, shape) -> input.view(shape) tf.minium(input, min) -> torch.clamp(input, max) tf.gather(input1, input2) -> input1[input2] tf....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,298
精华内容 919
关键字:

torch与pytorch