18

从数据到模型,你可能需要1篇详实的pytorch踩坑指南

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MjM5ODkzMzMwMQ%3D%3D&%3Bmid=2650414629&%3Bidx=1&%3Bsn=fec4871fbce9fe90952095018171706a
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

mYbYbmy.jpg!web

原创 · 作者 | Giant

学校 | 浙江大学

研究方向 | 对话系统、text2sql

熟悉DL的朋友应该知道Tensorflow、Pytorch、Caffe这些成熟的框架,它们让广大AI爱好者站在巨人的肩膀上,避免了重复造轮子的工作。当你有一个好的想法,这些工具可以帮助你快速复现,将idea变现成代码、模型。

此前,我一直在用Tensorflow及其高级API-Keras框架,后者简洁明了的API风格能让一个复杂的模型简化到10行代码。最近,因项目需要接触了基于动态图的pyTorch框架,再一次验证了真香定律。类似python的语法,让开发者搭建一个深度学习模型就像写一个python函数那样简单。

类似其他框架,pytorch已经封装好了AlexNet、VGG等模型结构,但一个优秀的算法工程师肯定不满足于调用别人封装好的API;往往,开发者是评估了需求后实现自定义模型。

经过一些实际项目的锻炼,我大致总结了使用深度学习解决一个实际问题的步骤:

(1) 分析问题

这是什么问题,分类还是回归?传统ML方法能否解决?端到端还是多个子任务?等等

(2)准备数据

包含数据分析、清洗、归一、划分等步骤,通常会将数据封装成迭代器形式方便模型调用,节约资源

(3)模型设计

根据任务设计相应模型,包括损失函数、优化器的选择等

(4) 结果分析

观察模型训练结果,通过压力测试、badcase分析等决定模型是否work;可能会多次进行1-3步的迭代优化

对于复杂任务如文本生成、text2sql,需要对模型输出结果先进行解码、还原

(5)封装交付

随着业务迭代,可能会对模型多次训练、微调,或结构变动。

由于这是一篇guide兼踩坑指南,本文主要针对自己碰到过的问题进行总结,欢迎读者朋友们将自己遇到过的问题(附上解决方法就更好啦)留言,共同避坑。

准备篇

1.用好官方文档

对于pytorch还陌生的朋友,入门的好方法之一是直接看官方文档。从类、函数到具体对象,都有详尽清晰的介绍,同时提供了诸多示例。这一点个人认为比tf要略胜一筹。

yERZBbI.jpg!web

官网首页就是安装方法,根据python版本、安装包、OS的差异提供了不同路径,可谓考虑非常周到了。

在国内,有时因为网速原因通过官网下载会非常慢,可以找一些镜像资源快速安装。

# 豆瓣镜像快速安装 1.1.0 版本pytorch
pip install torch==1.1.0 -i https://pypi.douban.com/simple

对于开发人员最有用的应该是 Docs 页面,提供了pytorch各个模块的解释和示例,还有源码链接。相信你的问题80%都能在这儿解决。

RFb6Nry.jpg!web

最后,介绍一本pytorch官方推荐的入门书籍: 《Deep-Learning-with-PyTorch》 ,主要面向有python基础的同学,介绍如何从0用pytorch搭建一个深度学习项目(软件/硬件)。

2q6RbqY.jpg!web Content

2.学好 numpy

pytorch的基本数据类型 2QreYfY.png!web 能和numpy对象“无缝切换”。很多关于张量的操作也和numpy的方法基本一致,所以想学好pytorch,可以先复习numpy。掌握了基本的矩阵操作,学习pytorch就不难啦。

(观察当前张量的 shape 变化,可以帮助你更好地了解数据的变换过程和debug)

3.本文测试环境

本文的实验环境为:

pytorch-1.1.0
python-3.6

下面让我们愉快的正式开始。

数据篇

“数据决定了最终结果的上界,好的模型帮助你不断逼近这个上界"。

2014年深度学习重新绽放活力以来,基于神经网络的模型不断刷新着各个领域的任务排行榜,某些任务甚至超越了人类表现。这背后是两大重要能力的支撑:强大的计算能力和庞大的数据。

pytorch工具包中提供了很多和数据准备相关的工具,比如最常用的有这两个:

from torch.utils.data import DataLoader, Dataset

Dataset 是一个数据包装抽象类,我们往往希望加载自定义的数据,只需要继承该类,重写“__ getitem__ ”和“__ len_ _”两个方法即可。

ZfYzyar.jpg!web

例如,我想在类的初始化函数中对传入的文本分词,可能写成这样:

class MyData(Dataset):

    def __init__(self, texts, labels, is_train=True):
        self.texts = [jieba.lcut(t) for t in texts]
        self.labels = labels
        # 其他操作 ....

    def __getitem__(self, item):
        token_id = convert_tokens_to_ids(self.texts[item]) # 词 -> token_id
        label = self.labels[item]
        return torch.LongTensor(token_id), torch.LongTensor([label])

    def __len__(self):
        return len(self.texts)

然后我希望将数据封装成迭代器,每次访问数据时可以返回一个指定batch大小的批数据,不需要一次性把所有数据都load到内存以减少占用:

def get_dataloader(dataset, batch_size, shuffle=False, drop_last=False):
    data_iter = DataLoader(
        dataset=dataset,
        batch_size=batch_size,
        shuffle=shuffle,
        drop_last=drop_last
    )
    return data_iter

dataset = MyData(texts, labels)
dataloader = get_dataloader(dataset, batch_size=16) # 成功封装成迭代器

这样在进行训练或测试时,可以很方便的按batch调用数据。

def train():
    model.cuda()
    model.train()
    for epoch in range(10):
        for batch in dataloader:
            # 传入一个 batch 的数据
            model(batch, "train")
            pass

是不是很简单呢!

接下来是一些碰到过的坑:

1.GPU / CPU 张量转换

3EfqMb7.png!web 模块对tensor在CPU、GPU之前的切换提供了很好的支持。如果你的深度学习模型是在GPU环境下运行的(model.cuda()),则需要将数据转换到GPU上再喂入模型;CPU上的数据和GPU数据直接计算时会抛错。

2.数据填充对齐-pad

一般来说NLP模型的输入是词ID矩阵,形状为 [batch_size, seq_len]。原始文本长度seq_len很可能是参差不齐的,但是神经网络的输入需要一个规整的张量,所以需要通过裁剪(丢失信息较多)或填充的方式使得它们变成定长。

以下代码是针对一个list进行填充(好像用什么框架都需要这一步╮(╯▽╰)╭ ;填充值一般习惯性选“0”)

def pad(s_list, pad_value=0):
    '''s_list = [[1,2,3,1,0],[1,2,3,]]'''
    max_len = max(len(i) for i in s_list)
    s_list = [s + [0] * (max_len - len(s))
                if len(s) < max_len
                else s[:max_len]
                for s in s_list]
    return s_list

模型篇

1.模型自定义

pytorch提供了和Keras类似的序列化方式来定义模型,一个简单的CNN网络可以写成:

import torch.nn as nn
model = nn.Sequential(
    nn.Conv2d(1,20,5)
    nn.ReLU()
)

但是实际开发中,这样写基本没什么意义,我们需要的是根据具体任务定义自己的模型。这在pytorch中也是很容易的一件事。分2步: 继承Moulde类,重写init、forward函数

import torch.nn.functional as F
import torch.nn as nn

optimizer = Adam(lr=2e-5) # 优化器

class MyModel(nn.Module):
    def __init__(self, ):
        super(MyModel, self).__init__()
        self.bert = BertModel.from_pretrained('/chinese_bert_pytorch/', cache_dir=None)
        self.s_linear = torch.nn.Linear(768, 1)
    
    def forward(self, batch, task='train'):

        batch = [b.cuda() for b in batch] # if needs GPU

        if task == 'train':
            input, input_type, label = batch
            _, pooled = self.bert(input, input_type)
            out = self.s_linear(pooled) # 1.计算输出
            loss = F.binary_cross_entropy_with_logits(out, label).sum() # 2.计算loss
            optimzer.zero_grad() # 3.清空梯度
            loss.backward() # 4.反向传播计算参数梯度
            optimzier.step() # 5.根据梯度和优化策略,更新参数
            
        elif task == 'eval':
            input, input_type = batch
            pooled = self.bert(input, input_type)
            out = self.s_linear(pooled)
            out = torch.sigmoid(out)
            return out

通常,我们在 __init__ 函数中定义模型需要使用的层以及初始化等。 forward 函数中定义前向传播、反向传播(pytorch后端自动实现)、计算loss等过程。可以简单概括成5点:

1.计算模型输出 out

2.借损失函数计算和真实label之间的误差loss

3.清空梯度

4.反向传播计算梯度

5.更新参数

这样,我们就完成了对一个深度学习模型的训练、参数更新、预测过程。

这里介绍一个小 trick: 在forward中同时传入任务类型 task ,这样1份代码既可以做训练又可以做预测;因为预测时只进行了前向传播,所以通常将模型输出结果直接返回,再做后处理。

另外需要注意, 只有标量才能直接使用backward() ,如果是对一个batch_size计算loss,得到的不是标量,要先使用tensor.sum()转换成scalar。否则会报错:

RuntimeError: grad can be implicitly created only for scalar outputs

2.模型转换

前边提到,CPU上的数据不能和GPU上数据直接计算,模型也是如此。要用GPU时,先简单做一个转换。

model.cuda() # 将模型所有参数和缓存转至GPU
if task == 'train':
    model.train()
else:
    model.eval() # 冻结 dropout、BN 层,具体参考官方文档

3.避免OOM

训练过程中由于loss.backward() 会将计算图的隐藏变量梯度清除,从而释放空间;但是测试的时候没有这一机制,因此有可能随着测试的进行中间变量越来越多,导致out of memory的发生。

pytorch0.4.1以上可以使用 with torch.no_grad() 进行数值计算,不需要创建计算图;也就不会跟踪计算梯度,节省了内存/显存。

with torch.no_grad(): # 不进行梯度计算
    for batch in testloader:
       res = model(batch, task='eval')
       res = res.cpu() # 转换回CPU,节约显存
       pass

如果显存不够大支撑不了实验,一般有几种缓解方法:

1.加大显存

2.减小batch

3.使用一些策略及时释放显存

最后再次强调,学习pytorch的最好途径是阅读官方文档(中文翻译版亦可)。如果能跟着官方Doc学习,结合一些项目实战(NLP、CV等都可以),想必会有事半功倍的效果。

训练篇

1.损失函数

pytorch根据模型输出和真实label计算损失时,一般使用损失函数。对于常用的二元交叉熵损失函数 binary_cross_entropy_with_logits ,有2个注意点:

(1)计算损失时,input和target需要先转换成float类型

(2)reduction参数可以决定返回的loss是tensor还是一个整数

import torch.nn.functional as F
print(F.binary_cross_entropy_with_logits(torch.LongTensor([[1.2,3.1,2.2]]).float(), torch.LongTensor([[1,2,3]]).float(), reduction='none'))
print(F.binary_cross_entropy_with_logits(torch.LongTensor([[1.2,3.1,2.2]]).float(), torch.LongTensor([[1,2,3]]).float(), reduction='mean'))
print(F.binary_cross_entropy_with_logits(torch.LongTensor([[1.2,3.1,2.2]]).float(), torch.LongTensor([[1,2,3]]).float(), reduction='sum'))
# output
# tensor([[ 0.3133, -2.9514, -3.8731]])
# tensor(-6.5112)
# tensor(-6.5112)

2.Device-side assert triggered Error

报错输出的典型信息:

[RuntimeError: cuda runtime error (59) : device-side assert triggered at /opt/conda/condabld/pytorch_1503970438496/work/torch/lib/THC/generic/THCStorage.c:32]....

这个错误一般在model进行forward前向传播中碰到,典型原因是GPU tensor 下索引失败引起的异常,out-of-bounds 即在[0, x]下,索引为负,或者超过 x。

建议:检查targets有没有越界!比如输入数据到 qENVBjY.png!web 层,对应的索引范围应该是0-9,如果输入1-10就会报错。

3.zip argument #1 must support iteration rror

7fEF3eU.jpg!web

这个错误是我在使用GPU单机多卡训练时碰到的;多gpu训练时,服务器自动把你的batch_size分成n_gpu份,每个gpu跑一些数据, 最后再合起来。之所以出现这个bug是因为我在模型返回的时候(forward函数中),除了loss还返回了标量(这一批batch_size中正确预测的个数,int类型)。

所以多卡训练时应避免从训练过程中返回标量 ;其他统计指标可以在训练完一个epoch再进行。如果是单卡训练,则返回标量还是张量,都没有问题了。

Loss篇

1.使用Cross_entropy损失函数时出现 RuntimeError: multi-target not supported at …

输入的真实标签必须为0~n-1(sparse编码,非one-hot),而且必须为1维的,如果设置标签为[n x 1]维,也会出现以上错误。

cross_entropy官网函数定义:

# input (Tensor) – size = (N, C) where C = number of classes
# target (Tensor) – size = (N,) where each value is 0 <= targets[i] <= C - 1
torch.nn.functional.cross_entropy(input, target, ...)

小结

pytorch框架虽然好用,但只是干活的工具不是目的;同时Keras也有即插即用,部署方便等优点。综上,最理想的状态是各个工具都能灵活使用,且适当了解框架底层的架构、源码,可以按需debug、自定义模型和loss等等。

1.pyTorch官网

2.horace.io/pytorch-vs-te

3.[PyTorch]论文pytorch复现中遇到的BUG

本文由作者授权AINLP原创发布于公众号平台,欢迎投稿,AI、NLP均可。 原文链接,点击"阅读原文"直达:

https://zhuanlan.zhihu.com/p/149771904

推荐阅读

这个NLP工具,玩得根本停不下来

文本自动摘要任务的“不完全”心得总结番外篇——submodular函数优化

Node2Vec 论文+代码笔记

模型压缩实践收尾篇——模型蒸馏以及其他一些技巧实践小结

中文命名实体识别工具(NER)哪家强?

学自然语言处理,其实更应该学好英语

斯坦福大学NLP组Python深度学习自然语言处理工具Stanza试用

太赞了!Springer面向公众开放电子书籍,附65本数学、编程、机器学习、深度学习、数据挖掘、数据科学等书籍链接及打包下载

数学之美中盛赞的 Michael Collins 教授,他的NLP课程要不要收藏?

自动作诗机&藏头诗生成器:五言、七言、绝句、律诗全了

这门斯坦福大学自然语言处理经典入门课,我放到B站了

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLPer(id:ainlper),备注工作/研究方向+加群目的。

7z2URfZ.jpg!web

阅读至此了,点个在看吧 :point_down:

推荐阅读

征稿启示| 200元稿费+5000DBC(价值20个小时GPU算力)

文本自动摘要任务的“不完全”心得总结番外篇——submodular函数优化

Node2Vec 论文+代码笔记

模型压缩实践收尾篇——模型蒸馏以及其他一些技巧实践小结

中文命名实体识别工具(NER)哪家强?

学自然语言处理,其实更应该学好英语

斯坦福大学NLP组Python深度学习自然语言处理工具Stanza试用

太赞了!Springer面向公众开放电子书籍,附65本数学、编程、机器学习、深度学习、数据挖掘、数据科学等书籍链接及打包下载

数学之美中盛赞的 Michael Collins 教授,他的NLP课程要不要收藏?

自动作诗机&藏头诗生成器:五言、七言、绝句、律诗全了

这门斯坦福大学自然语言处理经典入门课,我放到B站了

关于AINLP

AINLP 是一个有趣有AI的自然语言处理社区,专注于 AI、NLP、机器学习、深度学习、推荐算法等相关技术的分享,主题包括文本摘要、智能问答、聊天机器人、机器翻译、自动生成、知识图谱、预训练模型、推荐系统、计算广告、招聘信息、求职经验分享等,欢迎关注!加技术交流群请添加AINLPer(id:ainlper),备注工作/研究方向+加群目的。

mYbYbmy.jpg!web

阅读至此了,点个在看吧 :point_down:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK