Pytorch实验过程记录

最近开始练手pytorch,和张梅山老师讨论(基本是请教)了一下,给了我一个关系抽取方面的idea。基于本身对什么都感兴趣但是又不动手(眼高手低),所以干脆直接用这个开始练手把所有知道的model和一些技巧都练习一遍也是不错的选择。相当于正式开始真正动手做实验了吧。
动手之后发现还是有很多问题的,现在记录一下遇到的:

数据预处理与加载

第一步就是将数据处理为方便模型读取操作的格式了,包括文件读写、正则抽取、等。通过阅读源码这里主要用到了两个东西,第一个是torchtext,第二个是通过自定义DataLoader的collate_fn将特定格式的数据以想要的格式加载的pytorch原生的框架中。

  • torchtext

torchtext主要是在之前做seq2seq中用到的,这里贴一下主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def load_dataset(batch_size):
def unicodeToAscii(s):
return ''.join(
c for c in unicodedata.normalize('NFD', s)
if unicodedata.category(c) != 'Mn'
)

# Lowercase, trim, and remove non-letter characters
def normalizeString(s):
s = unicodeToAscii(s.lower().strip())
s = re.sub(r"([.!?])", r" \1", s)
s = re.sub(r"[^a-zA-Z.!?]+", r" ", s)
return s

def tokenize(text):
return normalizeString(text).split()

EN = Field(sequential=True, lower=True,
include_lengths=True, init_token='<sos>',
eos_token='<eos>')

FR = Field(sequential=True, lower=True,
include_lengths=True, init_token='<sos>',
eos_token='<eos>')
lines = open(file_path, encoding='utf-8'). \
read().strip().split('\n')
# pairs = [[normalizeString(s) for s in l.split('\t')] for l in lines]
pairs = [[s for s in l.split('\t')] for l in lines]
examples = []
fields = [('src', EN), ('trg', FR)]
for pair in pairs:
src_line, trg_line = pair[0].strip(), pair[1].strip()
if src_line != '' and trg_line != '':
examples.append(Example.fromlist([src_line, trg_line], fields))
print(len(examples))
tsdataset = Dataset(examples, fields)
EN.build_vocab(tsdataset.src, min_freq=5)
FR.build_vocab(tsdataset.trg, min_freq=5)
train_iter = BucketIterator(tsdataset, batch_size=batch_size,
sort_key=lambda ex: data.interleave_keys(len(ex.src), len(ex.trg)),
device=-1, repeat=False)
return train_iter, train_iter, train_iter, EN, FR

这里其实可以直接使用torchtext给的TranslationDataset,但是为了自己拓展方便,找到了构建Iterator所需要的参数,通过自定义dataset实现的。

  • DataLoader

通过dataloader加载数据的时候,为了自定义数据格式,需要自定义collate_fn传给dataloader,通过collate_fn接受dataset中getitem得到的数据,处理后给到dataloader,并在其内部调用_DataLoader中的—next方法中调用,源码如下:

1
2
3
4
5
6
7
8
class _DataLoaderIter(object):
def __next__(self):
if self.num_workers == 0: # same-process loading
indices = next(self.sample_iter) # may raise StopIteration
batch = self.collate_fn([self.dataset[i] for i in indices])
if self.pin_memory:
batch = pin_memory_batch(batch)
return batch

而此处我自定的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def convert_instance_to_idx_seq(word_insts, word2idx):
''' Mapping words to idx sequence. '''
return [[word2idx.get(w, Constants.UNK) for w in s] for s in word_insts]


def paired_collate_fn(insts):
text, e1, e2, tag = list(zip(*insts))
text = collate_fn(text)
e1 = collate_fn(e1)
e2 = collate_fn(e2)
tag = collate_tag(tag)

return (text, e1, e2, tag)


def collate_tag(insts):
insts = np.array([inst for inst in insts])
insts = torch.LongTensor(insts)
return insts


def collate_fn(insts):
''' Pad the instance to the max seq length in batch '''

max_len = max(len(inst) for inst in insts)
# max_len = 110
batch_text = np.array([inst + [Constants.PAD] * (max_len - len(inst))
for inst in insts])
batch_text = torch.LongTensor(batch_text)

return batch_text

training_iter = torch.utils.data.DataLoader(dataset=training_set,
batch_size=3,
num_workers=0,
collate_fn=paired_collate_fn)

之后就可以通过正常的iter获取定义的格式的数据了。

加载预训练的词向量

Nlp项目中经常用到这个,这次也算是认真实践了一把。之前实现了都没认真分析过,对词向量的词袋如何对应等问题都不甚清楚。

步骤大概是:

  1. 获取目标数据的词表。
  2. 从预训练的词向量中加载目标词表中词语的向量值,构造自己的嵌入矩阵。
  3. 对未收录词语构造初始化向量。

代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
  full_vocab = set(w for sent in word_insts for w in sent)
# emerge the pretrained embedding
glove = vocab.GloVe(name='6B', dim=100)
matrix_len = len(word2idx)
weights_matrix = np.zeros((matrix_len, emb_dim))
words_found = 0

for word, i in word2idx.items():
try:
weights_matrix[i] = glove[word]
words_found += 1
except KeyError:
weights_matrix[i] = np.random.normal(scale=0.6, size=(emb_dim,))

之后再把嵌入矩阵加载到embedding层就可以了:

1
2
3
4
5
6
if config.pretrained_embed_path:
data = torch.load(config.pretrained_embed_path)
weights_matrix = data['weights_matrix']
self.embeds.weight.data.copy_(torch.from_numpy(weights_matrix))
if not config.embed_requires_grad:
self.embeds.weight.requires_grad = False

视需要是否对嵌入层也进行优化