记又一次debug的血泪教训

这次的血泪教训就是,shuffle真的很重要!

训练模型发现一个问题:train集和dev集的performance差别很大。

我的心路历程:

  1. 怀疑自己的iterator写错了,将其换成dataloader之后,仍然有此问题。
  2. 有没有可能是模型的问题? 尝试换成以前写过的模型,但仍然有这个现象
  3. 难道是数据集的问题?我尝试print了几条数据,一切正常,也没有出现大量UNK。
  4. 是我训练方法出现问题?
    1. 尝试调整学习率。当调大学习率时,训练集的performance非常差,只有10%,而valid集有35%;当调小学习率时,训练集的performance有70%,而valid集仍然只有30%左右。
    2. 尝试不clip。训练集的performance变高了,但valid仍然很差。训练多几个epoch也是一样。
    3. 我把训练集的数据换成验证集的数据,用验证集来训练。神奇的是,居然不会过拟合。训练的performance甚至比验证的performance更差,但明明是同一个数据集训练的。
  5. 难道是我统计的问题?
    1. 会不会是我在统计loss和accuracy出现问题? 但肉眼debug了半天,完全没找到问题。
    2. 打印一下predict的值,发现一个batch内部预测的全部都是一样的label。是模型分类的问题?但不应该啊,明明check过模型了,应该不是模型的锅
    3. 嗯?难道是label的问题?统计一下label的分布,确实label类别不平衡,但也在合理范围内。
    4. 试试打印一下batch的label吧。 …这下发现,为什么batch内部的label全部都是同一类的?难怪predict时也是同一类。是不是数据本来就是按label顺序排的?

oh,不会是没有shuffle吧。check了一下代码,发现我的iterator是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def __iter__(self):

for i in range(0, self.data_len, self.batch_size):
start = i
end = min(self.data_len, i + self.batch_size)
sents = self.data['sents'][start:end]
labels = self.data['labels'][start:end]
batch = {'sents': sents, 'labels': labels}
batch = convert2tensor(batch)

yield batch

if self.shuffle:
start_state = random.getstate()
random.shuffle(self.data['sents'])
random.setstate(start_state)
random.shuffle(self.data['labels'])

有shuffle啊。等等,我好像是在iter完后再shuffle的。但这真的有问题吗?可能第一次没shuffle,但后面都shuffle了为啥performance还是没有上去?

反正找不到bug,那就试一下吧,我把shuffle代码放在前面。结果是,训练过程的结果变正常了…

想了一下原因。
第一,数据真的非常需要shuffle,一些数据集是按照label来排的,假如不shuffle直接按顺序喂数据会出现什么情况呢?模型一开始接触到的都是同一类的数据,那么就会疯狂overfit到该类的pattern,等到他有机会接触下一类后,可能已经陷入local minimum了,再怎么训都雷打不动了,这也就是为什么我在训练过程中当调高学习率时出现training set在10%而valid set在30%,这是因为模型已经无法拟合training set的其他类了;或者如果学习率小,那么还没overfit得很严重,在接触到下一类时,仍然会很快overfit到该类,在一个epoch后,模型会overfit到最后一个接触的类,此时做evaluation,当然结果好不到哪里去,但在training set上却还可以。

第二,为什么在第一个epoch后做shuffle,在后续的epoch模型仍然不正常呢?和上述的原因可能相同,因为overfit到一个特定类的pattern了,再也无法从local minimum出来了。假如学习率调的足够小,或许还有救,但一般我们用的都是Adam之类的,在训练过程中学习率可能越变越大,到最后可能还是没救。

所以啊,一定不要忘记shuffle!

最后贴上我在debug过程中的记录,来纪念这次的血泪教训。