Kaggle首战记录(4)-English Language Learning-baseline的数据增强

数据增强后是否真的有用还有待商榷,但确实增加了0.01个点。另外还学会了nlp处理的很多库。

前言

个人认为数据增强有几个要点:1.真的增强了;2.不要改变数据分布。

数据增强一般是在自变量做手脚,也就是说评分不变,如何去改输入。

NLP数据增强可以看看这篇:NLP中简单的数据增强方法 - 简书 (jianshu.com),文中提到数据增强要做到:

(1)增加的数据要保证和原数据一致的语义信息。
(2)增加的数据需要多样化。

本题是长文本,数据增强其实有点困难,增删改词都行,但是变化少了——没多大用,变化大了——会影响评分。而且我们baseline是六个维度在一起的,适合词级别的增强不一定适合段落级别。并且,NLP那些回译、同义词等的改变,比较适合情感分类这种比较宏观的,不太适合这种需要注意到微观的文章评分。

因此我的解决办法如下:

1.在句号、逗号后随机增删空格、换行符(这对tokenizer有影响,算是一种噪声)

2.查找错误单词,改正一半的单词,并把同样数量的正确单词破坏

3.在to_tensor的时候,对于大于等于三个子句的文本,以大小为2的滑动窗口,1为步长滑动。

代码

对前3700个数据做数据增强,剩下的是验证集(切忌全部增强再划分,否则验证集和训练集有相似的,很难公正地检测性能)

主代码

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
for i in range(3700):
text_ = data['full_text'][i]
err_list = get_error_words(text_) #得到文本中的错误单词,和改正后的单词,当然这种检测是纯传统的、统计式的

for _ in range(2): #一个数据生成两个增强数据
text = text_
rx = r"\.(?=\S)"
text = re.sub(rx, ". ", text)
rx = r"\,(?=\S)"
text = re.sub(rx, ", ", text) #先把逗号,句号后的空格这些规范好
if len(err_list) > 1:
choice_num = int(p * (len(err_list) + 1)) #p设置为0.5
need_to_correct = random.sample(err_list, choice_num) #随机选择一半
for word_tuple in need_to_correct:
text = text.replace(word_tuple[0], word_tuple[1], 1) #改正
spl = list(text.split()) #以空格分词
d = random.sample(range(len(spl)), choice_num) #随机取文中的单词破坏
for k in d:
choosepool = list(edits1(spl[k])) #edits1为编辑距离为1的单词
spl[k] = random.choice(choosepool)
text = ' '.join(spl)

text = correct_text(text) #随机加空格和换行
data.loc[len(data)] = data.loc[i]
data['full_text'][len(data)-1] = text
if i % 10 == 0:
print(i)

各函数实现

导入这个库,检查单词是否正确的。

1
from enchant.checker import SpellChecker

先来看编辑距离为1的函数,这是魔改网上看到的一个函数得到的。我自己加了一个0.5编辑距离,也就是有单引号和没单引号之间的距离视为更小,因为Cheker库得到的修改单词不太智能,是返回一个列表,我需要自己在这个列表中选择更好的单词来修改。

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
def edits1(word, typ = "nohalf"):
"""
返回跟输入单词是1距离的单词
"""
# 26个英文字母 ord():获取'a'的码 chr():通过码还原对应的字符
t = [chr(ord('a') + i) for i in range(26)]
t.extend([chr(ord('A') + i) for i in range(26)])
alphabet = ''.join(t)
if typ == 'half':
alphabet = ''.join([chr(ord("'"))])
def splits(word):
"""
分割单词 以cta为例: ("","cat") ("c","at") ("ca","") ("cat","")
"""
return [(word[:i], word[i:])
for i in range(len(word) + 1)]

# 分割好的单词
pairs = splits(word)


deletes = []
transposes = []

if typ != 'half':
# 删除某个字符
deletes = [a + b[1:] for (a, b) in pairs if b]
# 两个字符换位置
transposes = [a + b[1] + b[0] + b[2:] for (a, b) in pairs if len(b) > 1]
# 替换某个字符
replaces = [a + c + b[1:] for (a, b) in pairs for c in alphabet if b]
# 插入某个字符
inserts = [a + c + b for (a, b) in pairs for c in alphabet]
# 返回集合
return set(deletes + transposes + replaces + inserts)

这里更倾向于选择编辑距离为1的单词,如果没有再随便选。而如果有相差一个单引号的更好。

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
import contextlib
def get_error_words(sen):
"""
返回错误单词的二元组的列表:(错误, 正确)
"""
chkr = SpellChecker("en_US") #引入语料库
chkr.set_text(sen) #检查单词
err_list = []
for err in chkr:
correct_list = chkr.suggest(err.word)
a = edits1(err.word, 'half') #相差一个单引号
b = edits1(err.word) #编辑距离为1
edits1_list = []
perfect = False
for word in correct_list:
if word in a:
err_list.append((err.word, word))
perfect = True
break
if word in b:
edits1_list.append(word)
if not perfect:
if edits1_list:
err_list.append((err.word, edits1_list[0]))
else:
with contextlib.suppress(Exception):
err_list.append((err.word, correct_list[0]))
return err_list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def correct(word):
a = random.uniform(0, 1)
if a <= 0.15:
return word[0] #15%的概率消除空格
elif 0.15 < a <= 0.25:
return word + '\n\n' #10%的概率增加换行
else:
return word #其余不变
def correct_match(match):
word = match.group()
return correct(word)
def correct_text(text):
"""
对符号后的空格进行是否删除
"""
return re.sub('[,.?!] ', correct_match, text)
1
data.to_csv("newtrain.csv", index=False)

最后再储存起来,得到了11000条数据。

结果

训练效果是有提升的,原来的baseline训练的时候也最高到0.48。

在CPU上也达到第6名(比前几天高我的两位哥们高了,但是杀出来几个更牛的)


Kaggle首战记录(4)-English Language Learning-baseline的数据增强
https://bebr2.com/2022/09/12/Kaggle首战记录(4)-English Language Learning-baseline的数据增强/
作者
BeBr2
发布于
2022年9月12日
许可协议