最详细NER实战讲解-bilstm+crf(2)数据预处理

目录

1.找到分隔符的index

2.过滤特殊字符串 同时也是将文本分割为长短句

3.处理长短句问题


1.找到分隔符的index

我们写个测试一下

输出值为

匹配到的:,有一个span 就是找到的标点符号的起始位置和结束位置

为了展示的更清楚一些span所表示的东西 我们再写个测试

 

 可以看到在第一个输出中span[0]就是,的下标 span[1]是后面的‘预’的下标

后面就表示为m.span[0]  (在循环中)



2.过滤特殊字符串 同时也是将文本分割为长短句

考虑多种不能作为分隔符的特殊情况

 为了判断标点符号前有\t换行符 是否应该断开我们写一个测试

if __name__ == '__main__':
    files = os.listdir(train_dir)
    files = set([file.split('.')[0] for file in files])
    for file in files:
        path=os.path.join(train_dir,file+'.txt')
        with open(path,'r',encoding='utf8') as f:
            text = f.read()
            pattern1 = '。|,|,|;|;|\.|\?'
            for m in re.finditer(pattern1, text):
                idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
                if text[idx - 1] == '\n':
                    print(file, text[idx-10:idx+10])  # 如果前面是换行符 就打印出当前文章的file 然后打印出前后的内容 idx+-10

然后就要找到文档中找到对应的文段 判断能不能分 后面也可以依照这个是否断开看对整个模型的性能有没有提升

在我们查看相关文档后判断为 不作为分割点 所以直接continue就行了

同样的 我们继续找特殊情况

files = os.listdir(train_dir)
    files = set([file.split('.')[0] for file in files])
    for file in files:
        path=os.path.join(train_dir,file+'.txt')
        with open(path,'r',encoding='utf8') as f:
            text = f.read()
            pattern1 = '。|,|,|;|;|\.|\?'
            for m in re.finditer(pattern1, text):
                idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
                if text[idx-1].isdigit() and text[idx+1].isdigit() :
                    print(file, text[idx-20:idx+20]) 

如果标点符号前后都是数字 再输出一下

 很容易看出 这种情况也不应该split

同样的 数字加空格加数字的类型 也不能断开

完整过滤特殊字符代码:

def split_text(text):
    split_index = []
    pattern1 = '。|,|,|;|;|\.|\?'
    for m in re.finditer(pattern1, text):
        idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
        if text[idx-1]=='\n':
            continue
        if text[idx-1].isdigit() and text[idx+1].isdigit():  # 如果标点符号的前后都是数字的话 怎么办
            continue
        if text[idx-1].isdigit() and text[idx+1].isspace() and text[idx+2].isdigit():
            continue
        if text[idx-1].islower() and text[idx+1].islower():
            continue
        if text[idx-1].islower() and text[idx+1].isdigit():
            continue
        if text[idx-1].isupper() and text[idx+1].isdigit():
            continue
        if text[idx - 1].isdigit() and text[idx + 1].islower():
            continue
        if text[idx - 1].isdigit() and text[idx + 1].isupper():
            continue
        if text[idx+1] in set('.。;;,,'):
            continue
        if text[idx-1].isspace() and text[idx-2].isspace() and text[idx-3]=='C':
            continue
        if text[idx-1].isspace() and text[idx-2]=='C':
            continue
        if text[idx-1].isupper() and text[idx+1].isupper() :#前大些后大写
            continue
        if text[idx]=='.' and text[idx+1:idx+4]=='com':#域名
            continue
        split_index.append(idx+1)
# 还有存在(7) 一、这种情况
    pattern2='\([一二三四五六七八九零十]\)|[一二三四五六七八九零十]、|'
    pattern2 += '注:|附录 |表 \d|Tab \d+|\[摘要\]|\[提要\]|表\d[^。,,;]+?\n|图 \d|Fig \d|'
    pattern2 += '\[Abstract\]|\[Summary\]|前  言|【摘要】|【关键词】|结    果|讨    论|'
    pattern2 += 'and |or |with |by |because of |as well as '
    # 如果是pattern1 应该在后面切 如果是pattern2 应该在前面切
    for m in re.finditer(pattern2, text):
        idx = m.span()[0]
        if (text[idx:idx+2] in ['or','by'] or text[idx:idx+3]=='and' or text[idx:idx+4]=='with')\
            and (text[idx-1].islower() or text[idx-1].isupper()):
            continue
        split_index.append(idx)

pattern3 = '\n\d\.'
    for m in re.finditer(pattern2, text):
        idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
        # if text[idx+1] in set('.。;;,,'):
        #     print(file, text[idx-20:idx+20])  # 如果前面是换行符 就打印出当前文章的file 然后打印出前后的内容 idx+-10
        if ischinese(text[idx + 3]):
            split_index.append(idx+1)  # 作为切分点

    #再处理一下带括号的
    for m in re.finditer('\n\(\d\)', text):  # 换行 正括号 数字 反括号
        idx = m.span()[0]
        split_index.append(idx+1)  # 匹配到的是/n所以要+1
    split_index = list(sorted(set([0, len(text)] + split_index)))
        # split_index有大有小 要排序 且要把0,1放进去

    for i in range(len(split_index)-1):
        print(i, '||||', text[split_index[i] : split_index[i+1]])

还要判断一个字是不是中文

def ischinese(char):
    # 判断一个数是不是中文  如果在这个范围内的话就是中文
    if '\u4e00' <= char <= '\u9fff':
        return True
    return False


3.处理长短句问题

最长的句子有256个字 太长的句子我们的循环神经网络抓不到信息 LSTM并不能处理长文本 抓不到那么长的信息 LSTM差不多能抓到前面十几个字 RNN顶多就三四个字 所以我们要将文本尽量的断成短句子 但是断成短句子又有一个问题 可能让一些句子断掉了 这就涉及到我们后面需要做的事情:样本增强 就是把句子拆成一个一个短句以后 我们把连续的几个短句组成一个新句子 所以我们现在要做的就是尽量的把句子拆短 nlp中的文本增强方法 只有复制 在我们发现某一类特殊样本训练效果非常差时 我们可以多复制一些 让这些样本多训练几次

完整data_process代码

import os
import re
train_dir='datas/ruijin_round1_train2_20181022'
def get_entities(dir):
    """
    返回实体类别统计字典
    :param dir: 文件目录
    :return:
    """
    entities={}#用来存储实体名
    files=os.listdir(dir)
    files=list(set([file.split('.')[0] for file in files]))
    for file in files:
        path=os.path.join(dir,file+'.ann')
        with open(path,'r',encoding='utf8') as f:
            for line in f.readlines():
                name=line.split('\t')[1].split(' ')[0]
                if name in entities:
                    entities[name]+=1
                else:
                    entities[name]=1
    return entities

def get_labelencoder(entities):
    """
    功能是得到标签和下标的映射
    :param entities:
    :return:
    """
    entities = sorted(entities.items(), key=lambda x: x[1], reverse=True)
    entities = [x[0] for x in entities]
    id2label=[]
    id2label.append('O')
    for entity in entities:
        id2label.append('B-'+entity)
        id2label.append('I-'+entity)
    label2id={id2label[i]:i for i in range(len(id2label))}
    return id2label,label2id


def ischinese(char):
    # 判断一个数是不是中文  如果在这个范围内的话就是中文
    if '\u4e00' <= char <= '\u9fff':
        return True
    return False

def split_text(text):
    split_index = []
    pattern1 = '。|,|,|;|;|\.|\?'
    for m in re.finditer(pattern1, text):
        idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
        if text[idx-1]=='\n':
            continue
        if text[idx-1].isdigit() and text[idx+1].isdigit():  # 如果标点符号的前后都是数字的话 怎么办
            continue
        if text[idx-1].isdigit() and text[idx+1].isspace() and text[idx+2].isdigit():
            continue
        if text[idx-1].islower() and text[idx+1].islower():
            continue
        if text[idx-1].islower() and text[idx+1].isdigit():
            continue
        if text[idx-1].isupper() and text[idx+1].isdigit():
            continue
        if text[idx - 1].isdigit() and text[idx + 1].islower():
            continue
        if text[idx - 1].isdigit() and text[idx + 1].isupper():
            continue
        if text[idx+1] in set('.。;;,,'):
            continue
        if text[idx-1].isspace() and text[idx-2].isspace() and text[idx-3]=='C':
            continue
        if text[idx-1].isspace() and text[idx-2]=='C':
            continue
        if text[idx-1].isupper() and text[idx+1].isupper() :#前大些后大写
            continue
        if text[idx]=='.' and text[idx+1:idx+4]=='com':#域名
            continue
        split_index.append(idx+1)
# 还有存在(7) 一、这种情况
    pattern2='\([一二三四五六七八九零十]\)|[一二三四五六七八九零十]、|'
    pattern2 += '注:|附录 |表 \d|Tab \d+|\[摘要\]|\[提要\]|表\d[^。,,;]+?\n|图 \d|Fig \d|'
    pattern2 += '\[Abstract\]|\[Summary\]|前  言|【摘要】|【关键词】|结    果|讨    论|'
    pattern2 += 'and |or |with |by |because of |as well as '
    # 如果是pattern1 应该在后面切 如果是pattern2 应该在前面切
    for m in re.finditer(pattern2, text):
        idx = m.span()[0]
        if (text[idx:idx+2] in ['or','by'] or text[idx:idx+3]=='and' or text[idx:idx+4]=='with')\
            and (text[idx-1].islower() or text[idx-1].isupper()):
            continue
        split_index.append(idx)

    pattern3 = '\n\d\.'
    for m in re.finditer(pattern2, text):
        idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
        # if text[idx+1] in set('.。;;,,'):
        #     print(file, text[idx-20:idx+20])  # 如果前面是换行符 就打印出当前文章的file 然后打印出前后的内容 idx+-10
        if ischinese(text[idx + 3]):
            split_index.append(idx+1)  # 作为切分点

    #再处理一下带括号的
    for m in re.finditer('\n\(\d\)', text):  # 换行 正括号 数字 反括号
        idx = m.span()[0]
        split_index.append(idx+1)  # 匹配到的是/n所以要+1
    split_index = list(sorted(set([0, len(text)] + split_index)))
        # split_index有大有小 要排序 且要把0,1放进去
    other_index = []
    for i in range(len(split_index)-1):
        begin = split_index[i]
        end = split_index[i+1]
        if text[begin] in '一二三四五六七八九零十' or text[begin] =='('and text[begin+1] in '一二三四五六七八九零十':
            for j in range(begin, end):
                if text[j] =='\n':
                    other_index.append(j+1)
    split_index += other_index
    split_index = list(sorted(set([0, len(text)] + split_index)))

    other_index=[]
    for i in range(len(split_index)-1):
        b = split_index[i]
        e = split_index[i+1]
        if e-b>150:  # 句子超过150就能拆分
            for j in range(b, e):
                if (j+1-other_index[-1]) > 15: #保证句子长度在15以上
                    if text[j] =='\n':
                        other_index.append(j+1)
                    if text[j] ==' ' and text[j-1].isnumeric() and text[j+1].isnumeric():
                        other_index.append(j+1)
    split_index += other_index
    split_index = list(sorted(set([0, len(text)] + split_index)))

    # 如果前面是个空格
    for i in range(1, len(split_index)-1):
        idx = split_index[i]
        while idx>split_index[i-1] -1 and text[idx-1].isspace():
            idx -= 1
        split_index[i] = idx
    split_index = list(sorted(set([0, len(text)] + split_index)))


    # 处理短句子
    temp_idx = []
    i = 0
    while i < len(split_index)-1: # 0 10 30 45
        b = split_index[i]
        e = split_index[i+1]

        num_ch=0
        num_en=0
        if e - b < 15:
            for ch in text[b:e]:
                if ischinese(ch):
                    num_ch += 1
                elif ch.islower() or ch.isupper():
                    num_en += 1
                if num_ch + 0.5 * num_en > 5:  # 如果汉字加英文超过5个  则单独成为句子
                    temp_idx.append(b)
                    i += 1
                    break
            if num_ch + 0.5 * num_en <= 5:  # 如果汉字加英文不到5个  和后面一个句子合并
                temp_idx.append(b)
                i += 2
        else:
            temp_idx.append(b)
            i += 1
    split_index = list(sorted(set([0, len(text)] + temp_idx)))
    result = []
    for i in range(len(split_index) - 1):
        result.append(text[split_index[i]:split_index[i + 1]])

    # 做一个检查
    s = ''
    for r in result:
        s += r
    assert len(s) == len(text)
    return result

    # lens=[split_index[i+1]-split_index[i] for i in range(len(split_index)-1)][:-1]
    # print(max(lens),min(lens))
    # for i in range(len(split_index)-1):
    #     print(i,'||||',text[split_index[i]:split_index[i+1]])


# lens = [split_index[i+1] - split_index[i] for i in range(len(split_index)-1)]  # 每一句话的终止位置减去每一句话的起始位置
    # print(max(lens), min(lens))
    #
    # for i in range(len(split_index)-1):
    #     print(i, '||||', text[split_index[i] : split_index[i+1]])







if __name__ == '__main__':
    # with open('./datas/ruijin_round1_train2_20181022/0.txt') as f:
    #     text = f.read()
    #     pattern = '。|,|,|;|;|\.|\?'
    #     for m in re.finditer(pattern,text):
    #         #print(m)
    #         start = m.span()[0] -5
    #         end = m.span()[1] + 5
    #         print('**********', text[start:end])
    #         print(text[start+5])
    files = os.listdir(train_dir)
    files = list(set([file.split('.')[0] for file in files]))
    # for file in files:
    #     path=os.path.join(train_dir,file+'.txt')
    #     with open(path,'r',encoding='utf8') as f:
    #         text = f.read()
    #         pattern2 = '\n\(\d\)'
    #         for m in re.finditer(pattern2, text):
    #             idx = m.span()[0]  # 就是标点符号的下标 然后根据各个情况判断是否要断开 就是根据标点符号后面的内容决定的
    #             # if text[idx+1] in set('.。;;,,'):
    #             #     print(file, text[idx-20:idx+20])  # 如果前面是换行符 就打印出当前文章的file 然后打印出前后的内容 idx+-10
    #             # if ischinese(text[idx+4]):
    #             print(file+'||||',text[idx-40:idx+40])
    path=os.path.join(train_dir,files[0]+'.txt')
    with open(path, 'r', encoding='utf8') as f:
        text = f.read()
        split_text(text)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lyttonkeepgoing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值