transformer之代码借鉴

我也不知道为什么要看别人写的代码,我并没有碰到问题,我只是觉得自己的知识和代码架构能力以及那种在码代码时候的一种直觉少了许多,所以要看一些别人的代码,在看的时候不能浅尝辄止,借鉴api,借鉴一些类库,借鉴一些架构的方式,看看别人的思路。

1.预处理

(1)文件读取

源文件为txt 文件,使用python内置的open 函数打开文件,然后读取,并进行划分为list.

 opt.src_data = open(opt.src_data).read().strip().split('\n')
(2)分词

使用spacy tokenize方法,并使用re表达式去除一些特殊的字符,然后运用列表推导式返回
如下所示:

return [tok.text for tok in self.nlp.tokenizer(sentence) if tok.text != " "]
(3)创建fields
  • 为什么fields 可以load weight
  • 为什么 SRCfields 没有soseos

使用了f-string,f‘hello world{var}'
使用了序列化来 加载 weightspickle.load函数

(4)创建dataset 迭代器
  • global修饰的变量 :全局变量,python 中若要修改肯定是修改局部变量,加上关键字,例global a = 1,就是修改全局变量
  • batch_size_fn 一种动态处理的东西,详看文档
  • opt checkpoint是什么
  • 在创建 field时,检查load_weight 是否为空,若不为空,就加载出来,在创建数据集的时候,检查load_weight是否为空,若为空就重新调用 build_vocab,并检查checkpoint值,若不为0 就dump field进去
    if opt.load_weights is None:
        SRC.build_vocab(train)
        TRG.build_vocab(train)
        if opt.checkpoint > 0:
            try:
                os.mkdir("weights")
            except:
                print("weights folder already exists, run program with -load_weights weights to load them")
                quit()
            pickle.dump(SRC, open('weights/SRC.pkl', 'wb'))
            pickle.dump(TRG, open('weights/TRG.pkl', 'wb'))

2.model 模型

1.Embedding Position-Encoding层
pe = torch.zeros(max_seq_len, d_model)
        for pos in range(max_seq_len):
            for i in range(0, d_model, 2):
                pe[pos, i] = \
                math.sin(pos / (10000 ** ((2 * i)/d_model)))
                pe[pos, i + 1] = \
                math.cos(pos / (10000 ** ((2 * (i + 1))/d_model)))
        pe = pe.unsqueeze(0)
        self.register_buffer('pe', pe)

pe = Variable(self.pe[:,:seq_len], requires_grad=False)
        if x.is_cuda:
            pe.cuda()
attention 计算
  • 可以用transpose方法进行 维度变换,没必要非得permute
scores = torch.matmul(q, k.transpose(-2, -1)) /  math.sqrt(d_k)
  • 关于view 的问题
    torch.view操作需要连续的tensor,torch.view 操作约定不改变数组本身,只是使用
    在使用transpose permute之后,只是改变了stride,并没有修改底层数组,如果执行t2.view(-1)要不报错,要不还是返回顺序的0-11.不是我们想要的

>>>t = torch.arange(12).reshape(3,4)
>>>t
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
>>>t.stride()
(4, 1)
>>>t2 = t.transpose(0,1)
>>>t2
tensor([[ 0,  4,  8],
        [ 1,  5,  9],
        [ 2,  6, 10],
        [ 3,  7, 11]])
>>>t2.stride()
(1, 4)
>>>t.data_ptr() == t2.data_ptr() # 底层数据是同一个一维数组
True

那么,这个时候,contiguous就派上用场啦!

>>>t3 = t2.contiguous()
>>>t3
tensor([[ 0,  4,  8],
        [ 1,  5,  9],
        [ 2,  6, 10],
        [ 3,  7, 11]])
>>>t3.data_ptr() == t2.data_ptr() # 底层数据不是同一个一维数组
False
layer Norm 实现
  • nn.parameter 官方文档介绍

Parameters are Tensor subclasses, that have a very special property when used with Module s - when they’re assigned as Module attributes they are automatically added to the list of its parameters, and will appear e.g. in parameters() iterator

我们注册这两个parameter

self.alpha = nn.Parameter(torch.ones(self.size))
self.bias = nn.Parameter(torch.zeros(self.size))
  • module 中什么样的东西会被学到呢
 norm = self.alpha * (x - x.mean(dim=-1, keepdim=True)) \
        / (x.std(dim=-1, keepdim=True) + self.eps) + self.bias
  • 预测的时候要不要做操作
模型初始化
 if opt.load_weights is not None:
        print("loading pretrained weights...")
        model.load_state_dict(torch.load(f'{opt.load_weights}/model_weights'))
    else:
        for p in model.parameters():
            if p.dim() > 1:
                nn.init.xavier_uniform_(p) 

3.argparse

  1. 位置参数
import argparse

parser = argparse.ArgumentParser(description="argparser 测试");
parser.add_argument('integers', nargs="+", type=str,help="传入一个参数");
args = parser.parse_args()
print(args)
print(args.integers)

结果


C:\Users\qiuqiu\PycharmProjects\Transformer_low_level\copy_tfm>python test1.py -h
usage: test1.py [-h] integers [integers ...]

argparser 测试

positional arguments:
  integers    传入一个参数

optional arguments:
  -h, --help  show this help message and exit

C:\Users\qiuqiu\PycharmProjects\Transformer_low_level\copy_tfm>python test1.py 1 2 3 4
Namespace(integers=['1', '2', '3', '4'])
['1', '2', '3', '4']

2.位置参数对位置要求比较严格,所以有了可选参数

parser.add_argument("--family",type=str,help="姓")
parser.add_argument("--name", type=str,help="名")

args = parser.parse_args()
print(args)
#print(args.integers)
print(args.family + args.name)

结果

C:\Users\qiuqiu\PycharmProjects\Transformer_low_level\copy_tfm>python test1.py -h
usage: test1.py [-h] [--family FAMILY] [--name NAME]

argparser 测试

optional arguments:
  -h, --help       show this help message and exit
  --family FAMILY  姓
  --name NAME      名
C:\Users\qiuqiu\PycharmProjects\Transformer_low_level\copy_tfm>python test1.py -h
usage: test1.py [-h] [--family FAMILY] [--name NAME]

argparser 测试

optional arguments:
  -h, --help       show this help message and exit
  --family FAMILY  姓
  --name NAME      名


C:\Users\qiuqiu\PycharmProjects\Transformer_low_level\copy_tfm>python test1.py --family=--name=三
Namespace(family='张', name='三')
张三

3.另有 action default等参数另可探索
store_true,store_false 等,只要在命令行中加入了这个关键字(无需赋值),便会将相应参数变为 True False
4. 对于 args 来说,可以使用args.param (例如:args.device=1)添加新的属性,在被调用的函数中对args的修改也会直接修改args

4.模型优化

可以自己写函数,也可以使用pytorch中的lr_schedular
这篇博客很详细

5.预测和 beam search

准备自己先写出来,然后再去参考他的代码!!!

时间隔得有点久,这几日心情可以说是七上八下,说一句矫情的话:
如今初识这世间,万般流连

Beam search 代码可以借鉴的地方:

  • 首先弄明白beam search的思路:可以把他想象成一个树,每次都累加对数似然概率,另外对于预测出的end_token,我们可以紧接着预测下去,其实是不影响的,只需要指定一个最大步数就可以了(即最长序列)
  • 将beam_size的维度抽取为batch的那个维度,设置最长步数进行循环。

伪代码

# 维持三个变量,e_outputs,outputs,log_scores
# outputs 维度(beam_size,max_len) e_outputs(beam_size,seq_len,d_model)
outputs, e_outputs, log_scores = init_vars(src, model, SRC, TRG, opt)
#然后,求出累加的k个最好结果
outputs, log_scores = k_best_outputs(outputs, out, log_scores, i, opt.k)

进入k_best_outputs函数

# 求k个结果的:最坏的情况就是当前最好的结果是由一个beam产生
# 使用top_k函数,求出每一个beam的top_k结果
probs, ix = out[:, -1].data.topk(k)
# 然后累加到当前beam
    log_probs = torch.Tensor([math.log(p) \
    for p in  probs.data.view(-1)]).view(k, -1) \
    +log_scores.transpose(0,1)
 #这里有一点不对的事,没根据当前当前是否产生了end_token进行累加
 k_probs, k_ix = log_probs.view(-1).topk(k)
    
    row = k_ix // k
    col = k_ix % k

    outputs[:, :i] = outputs[row, :i]
    outputs[:, i] = ix[row, col]

    log_scores = k_probs.unsqueeze(0)
# 每一步判断一下是否产生end_token,然后根据end_token 及时更正log_score的更新。

6.可以借鉴的其它一些地方

1.到处可见的断言 assert,当后面的条件为错误的时候抛出一个异常,而不至于使程序崩溃掉。到处可见的 try except,例如打开文件时。
2.pickle
判断load_weights是否需要,使用pickle.load函数进行加载
pickle.load 函数,对于词汇,如果是一开始跑,要进行pickle.dump
3.get_model 函数
get_model中创建并初始化并加载模型,并将模型放在GPU上面
4.对于有多层相同的,进行深复制:

def get_clones(module, N):
    return nn.ModuleList([copy.deepcopy(module) for i in range(N)])

5.模型的架构分的很清楚:model layer sublayer embedding 各司其职,对于预测,也要写到不同的文件里,最好一个文件一个功能吧!

7.一些小细节:

只有TRG 需要init_tokeneos_token

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值