中移集成-首届OneCity编程大赛复盘

简介: 中移集成-首届OneCity编程大赛复盘

1 竞赛背景


本届OneCity编程大赛主题围绕智慧城市OneCity赋能智慧政务,实现政务数据管理更智慧化、智能化展开。政务数据智能算法包括分类与标签提取,根据政府表格文件标题与内容,按照一定的原则将杂乱无章的文件自动映射到具体的类目上,加速数据归档的智能化与高效化。本次比赛旨在通过抽取政务表格文件中的关键信息,来实现表格数据自动化分类的目标。优胜者还可获得价值万元的苹果电脑,华为手机等丰厚大奖,欢迎大家踊跃参与!


PC端详情页:https://www.dcjingsai.com/v2/cmptDetail.html?id=457


2 任务


比赛任务本质上其实是文本分类,同时稍微对数据预处理要求高些,尤其能够正确读出每个表格的内容。


选手需要建立模型,针对政务表格文件实现自动化分类。允许使用一些常见的开源预训练模型,如bert。


数据智能分类按照行业领域,将政务数据分为以下20个基础大类,分别是:生态环境、资源能源、信息产业、医疗卫生、文化休闲、财税金融、经济管理、教育科技、交通运输、工业、农业畜牧业、政法监察、城乡建设、商业贸易、旅游服务、气象水文测绘地震地理、外交外事、文秘行政、民政社区、劳动人事。


3 数据


备注:报名参赛或加入队伍后,可获取数据下载权限。

数据是从政府开放数据平台收集的真实数据,共有9万多个表格文件,包括xls、xlsx、csv三种格式,其中csv文件编码格式统一为utf-8。

文件被分为三个部分,训练集、测试集1和测试集2。其中训练集(6万个文件,含标签)和测试集1(8000个文件,不含标签)于初赛阶段开放给选手下载,测试集2(不含标签)于复赛阶段开放给选手下载。

  • 注意1:有些文件的内容为空。
  • 注意2:有些文件的扩展名与文件格式不匹配,比如有些扩展名为xls的文件实际上是csv文件,有些扩展名为csv的文件其实是html文件。
  • 注意3:在复赛阶段,有约50%的文件名会被更名为纯数字(这些文件的内容非空),请选手注意调整模型。


4【硬件资源】


CPU: Intel(R) Xeon(R) Gold 5118 CPU @ 2.30GHz

GPU:Tesla V100


评分标准


采用分类的准确率Accuracy。示例代码如下:

from sklearn.metrics import accuracy_score
y_true = [’工业’,’文秘行政’,’劳动人事’,’信息产业’,’医疗卫生’,’文化休闲’,’旅游服务’]
y_pred = [’工业’,’ 工业’,’ 工业’,’信息产业’,’ 信息产业’,’ 信息产业’,’ 信息产业’]
score = accuracy_score(y_true, y_pred)


5 初赛top2思路


5.1 数据预处理


对于这部分,初赛期间下了不少功夫,我的目标就是能够正确读出每个表格的内容:包括表头(columns),表格单元内容(content),以及表格名称(sheet_names).


基本思路: 对表格文本数据进行提取,文本由三部分组成:文件名+表格名字+表头列名

文件名:直接用文件名,利用baseline代码可以达到一个不错的基线成绩,0.977+

表格名字:有的xls内容包含多个表格,可以利用这部分表格的名字提供额外的信息,后来发现这部分文本缺陷比较大,主要是test,少部分有中文文本,另外就是为空

表头列名:这部分信息作用比较大,因为这些数据其实从政府职能网站爬去下来,数据比较脏,但是网站的html或者格局是固定的,所以表头可以提供比较有效的信息

三段文本拼接方式不同,效果也会不同


数据预处理代码如下:

import multiprocessing as mp
import re
import warnings
import pandas as pd
from pandarallel import pandarallel
warnings.simplefilter('ignore')
pandarallel.initialize(progress_bar=False, nb_workers=16)
def clean_text(x):
    text = x.replace('train/', '').replace('.xls', '').replace('.csv',
                                                               '').replace('_', ' ').replace('test1/', '')
    return text
def process_text(text):
    r1 = '[a-zA-Z0-9’!"#$%&\'())*+-./:;,<=>?@。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
    text = re.sub(r1, '', text)
    text = text.replace('NaN', '').replace('\n', '')
    text = text.replace("\\", "")
    # print(text)
    text = "".join(text.split())
    return text
def get_file_content_v1(filename):
    """
    直接将表格内容进行拼接
    """
    table_path = 'data/' + filename
    r1 = '[0-9’!"#$%&\'())*+-./:;,<=>?@。?★、…【】《》?“”‘’![\\]^_`{|}~]+'
    if filename.endswith('xls'):
        try:
            with open(table_path, 'r', encoding='utf-8') as f:
                text = "".join(f.read().split())
                text = re.sub(r1, '', text)
                print("读取xls方式[open]成功", table_path)
                return text[:300]
        except UnicodeDecodeError as e:
            try:
                df = pd.read_excel(table_path)
                print("读取xls方式[read_excel]成功", table_path)
                if len(df) == 0:
                    data = pd.DataFrame()
                    tmp_xls = pd.ExcelFile(table_path)
                    sheet_names = tmp_xls.sheet_names
                    for name in sheet_names:
                        d = tmp_xls.parse(name)
                        data = pd.concat([data, d])
                    text = data.to_string()
                    text = "".join(text.split())
                    text = re.sub(r1, '', text)
                    text = text.replace('NaN', '').replace('\n', '')
                    # print(text)
                    return text[:300]
                else:
                    text = df.to_string()
                    text = "".join(text.split())
                    text = re.sub(r1, '', text)
                    text = text.replace('NaN', '').replace('\n', '')
                    # print(text)
                    return text[:300]
            except Exception as e:
                try:
                    df = pd.read_html(table_path)
                    print("读取xls方式[read_html]成功", table_path)
                    text = df.to_string()
                    text = "".join(text.split())
                    text = re.sub(r1, '', text)
                    text = text.replace('NaN', '').replace('\n', '')
                    # print(text)
                    return text[:300]
                except Exception as e:
                    print(e)
                    print("读取xls失败", table_path)
                    return ''
    elif filename.endswith('csv'):
        try:
            df = pd.read_csv(table_path, error_bad_lines=False, warn_bad_lines=False, lineterminator='\n')
            text = df.to_string()
            text = "".join(text.split())
            text = re.sub(r1, '', text)
            text = text.replace('NaN', '').replace('\n', '')
            return text[:300]
        except Exception as e:
            return ''
def get_file_content_v2(filename):
    """
    按照表格 文件名 表格sheet文本 表格列名 文本进行拼接
    """
    table_path = 'data/' + filename
    if filename.endswith('xls'):
        try:
            data = pd.DataFrame()
            tmp_xls = pd.ExcelFile(table_path)
            sheet_names = tmp_xls.sheet_names
            # print("表格名字:",sheet_names," ".join(sheet_names))
            sheet_name_text = " ".join(sheet_names)
            col_names = []
            for name in sheet_names:
                d = tmp_xls.parse(name)
                try:
                    col_names.extend(d.columns.tolist())
                except Exception as e:
                    col_names.extend([])
                data = pd.concat([data, d])
            # print("表头名字", col_names)
            col_name_text = " ".join(col_names)
            table_content_text = data.to_string(header=False, show_dimensions=False, index=False, index_names=False,
                                                sparsify=False)
            print("处理成功", table_path)
            text = sheet_name_text + ' ' + col_name_text + ' ' + table_content_text
            text = " ".join(text.split())
            text = process_text(text)
            return text[:500]
        except Exception as e:
            # print(e, table_path)
            try:
                data = pd.read_csv(table_path, error_bad_lines=False, warn_bad_lines=False, lineterminator='\n')
                sheet_name_text = ''
                try:
                    col_name_text = " ".join(data.columns.tolist())
                except Exception as e:
                    col_name_text = ''
                table_content_text = data.to_string(header=False, show_dimensions=False, index=False, index_names=False,
                                                    sparsify=False)
                print("处理成功", table_path)
                text = sheet_name_text + ' ' + col_name_text + ' ' + table_content_text
                text = " ".join(text.split())
                text = process_text(text)
                return text[:500]
            except Exception as e:
                print(e, table_path)
                sheet_name_text = ''
                col_name_text = ''
                table_content_text = ''
                text = sheet_name_text + ' ' + col_name_text + ' ' + table_content_text
                text = " ".join(text.split())
                return text[:500]
    if filename.endswith('csv'):
        try:
            data = pd.read_csv(table_path, error_bad_lines=False, warn_bad_lines=False, lineterminator='\n')
            sheet_name_text = ''
            try:
                col_name_text = " ".join(data.columns.tolist())
            except:
                col_name_text = ''
            table_content_text = data.to_string(header=False, show_dimensions=False, index=False, index_names=False,
                                                sparsify=False)
            print("处理成功", table_path)
            text = sheet_name_text + ' ' + col_name_text + ' ' + table_content_text
            text = " ".join(text.split())
            text = process_text(text)
            return text[:500]
        except Exception as e:
            print(e, table_path)
            sheet_name_text = ''
            col_name_text = ''
            table_content_text = ''
            text = sheet_name_text + ' ' + col_name_text + ' ' + table_content_text
            return text
if __name__ == '__main__':
    label_index = {
        '工业': 0,
        '文化休闲': 1,
        '教育科技': 2,
        '医疗卫生': 3,
        '文秘行政': 4,
        '生态环境': 5,
        '城乡建设': 6,
        '农业畜牧业': 7,
        '经济管理': 8,
        '交通运输': 9,
        '政法监察': 10,
        '财税金融': 11,
        '劳动人事': 12,
        '旅游服务': 13,
        '资源能源': 14,
        '商业贸易': 15,
        '气象水文测绘地震地理': 16,
        '民政社区': 17,
        '信息产业': 18,
        '外交外事': 19}
    train = pd.read_csv('data/answer_train.csv')
    test = pd.read_csv('data/submit_example_test1.csv')
    train['label'] = train['label'].map(label_index)
    with mp.Pool(8) as pool:
        train['content'] = pool.map(get_file_content_v1, train['filename'])
    # train['content'] = train['filename'].parallel_apply(get_file_content).values
    print("over")
    train['text'] = train['text'].astype(str) + ' ' + train['content'].astype(str)
    with mp.Pool(mp.cpu_count()) as pool:
        test['content'] = pool.map(get_file_content_v1, test['filename'])
    # test['content'] = test['filename'].parallel_apply(get_file_content).values
    test['text'] = test['text'].astype(str) + ' ' + test['content'].astype(str)
    train_df = train[['text', 'label']]
    test_df = test[['text', 'label']]
    train_df.to_csv('data/train_set_v3.csv', index=None)
    test_df.to_csv('data/test_set_v3.csv', index=None)


5.2 模型训练


思想很简单:多模型融合,分类问题本质就是要减少模型差异化

import re
import numpy as np
import pandas as pd
from simpletransformers.classification import ClassificationModel, ClassificationArgs
from sklearn.metrics import accuracy_score
def get_clean(text):
    r1 = '[a-zA-Z]+'
    text = re.sub(r1, '', text)
    text = text.replace('\\', '')
    return text[:300]
train_df = pd.read_csv('data/train_set_v3.csv')
test = pd.read_csv('data/test_set_v3.csv')
train_df['text'] = train_df['text'].apply(lambda x: get_clean(x))
test['text'] = test['text'].apply(lambda x: get_clean(x))
print(test['text'])
print(train_df.shape, test.shape)
print(train_df.head())
train_tmp = pd.read_csv('data/answer_train.csv')
train_df = train_df.sample(frac=1., random_state=1024)
eval_df = train_df[54000:]
train_df = train_df[:54000]
label_index_inverse = {
    0: '工业',
    1: '文化休闲',
    2: '教育科技',
    3: '医疗卫生',
    4: '文秘行政',
    5: '生态环境',
    6: '城乡建设',
    7: '农业畜牧业',
    8: '经济管理',
    9: '交通运输',
    10: '政法监察',
    11: '财税金融',
    12: '劳动人事',
    13: '旅游服务',
    14: '资源能源',
    15: '商业贸易',
    16: '气象水文测绘地震地理',
    17: '民政社区',
    18: '信息产业',
    19: '外交外事'}
models = [
    ('bert', 'hfl/chinese-roberta-wwm-ext'),
    ('xlnet', 'hfl/chinese-xlnet-base'),
    ('bert', 'schen/longformer-chinese-base-4096'),
    ('bert', 'voidful/albert_chinese_base'),
    ('bert', 'clue/roberta_chinese_base'),
    ('electra', 'hfl/chinese-electra-base-discriminator'),
]
for i in range(len(models)):
    print("training {}".format(models[i][1]))
    model_args = ClassificationArgs()
    model_args.max_seq_length = 150
    model_args.train_batch_size = 32
    model_args.num_train_epochs = 5
    model_args.fp16 = False
    model_args.evaluate_during_training = True
    model_args.overwrite_output_dir = True
    model_args.cache_dir = './caches'
    model_args.output_dir = './outputs'
    model_type = models[i][0]
    model_name = models[i][1]
    model = ClassificationModel(
        model_type,
        model_name,
        num_labels=len(label_index_inverse),
        args=model_args)
    model.train_model(train_df, eval_df=eval_df)
    result, _, _ = model.eval_model(eval_df, acc=accuracy_score)
    data = []
    for i, row in test.iterrows():
        data.append(row['text'])
    predictions, raw_outputs = model.predict(data)
    sub = pd.read_csv('data/submit_example_test1.csv')[['filename']]
    sub['label'] = predictions
    sub['label'] = sub['label'].map(label_index_inverse)
    print(sub.shape)
    print(sub.head(10))
    result_name = models[i][1].split('/')[1]
    np.save('result/{}_{}.npy'.format(i, result_name), raw_outputs)
    sub.to_csv('result/{}}_{}.csv'.format(i, result_name), index=False)


5.3 模型融合


直接将不同模型的预测概率相加平均

import numpy as np
import pandas as pd
import os
label_index_inverse = {
    0: '工业',
    1: '文化休闲',
    2: '教育科技',
    3: '医疗卫生',
    4: '文秘行政',
    5: '生态环境',
    6: '城乡建设',
    7: '农业畜牧业',
    8: '经济管理',
    9: '交通运输',
    10: '政法监察',
    11: '财税金融',
    12: '劳动人事',
    13: '旅游服务',
    14: '资源能源',
    15: '商业贸易',
    16: '气象水文测绘地震地理',
    17: '民政社区',
    18: '信息产业',
    19: '外交外事'}
pred = None
for file in os.listdir('result/'):
    if file.endswith('.npy'):
        if pred is None:
            pred = np.load('result/{}'.format(file))
        else:
            pred += np.load('result/{}'.format(file))
predictions = np.argmax(pred, axis=1)
sub = pd.read_csv('data/submit_example_test1.csv')[['filename']]
sub['label'] = predictions
sub['label'] = sub['label'].map(label_index_inverse)
print(sub.head(10))
sub.to_csv('result/ensemble_mean.csv', index=False)


5.4 数据增强


对于样本少的类别,使用表格内容进行扩充数据,数据增强之后初赛线上效果稍微有些提升

import pandas as pd
from tqdm import tqdm
def aug_df(df=None, selected=[15, 16, 17, 18, 19], text_len=100):
    """
    工业            8542
    文化休闲          7408
    教育科技          7058
    医疗卫生          5750
    文秘行政          5439
    生态环境          4651
    城乡建设          3722
    农业畜牧业         2703
    经济管理          2516
    交通运输          2250
    政法监察          2159
    财税金融          1784
    劳动人事          1759
    旅游服务          1539
    资源能源          1209
    商业贸易           652
    气象水文测绘地震地理     375
    民政社区           349
    信息产业           108
    外交外事            27
                 label  label_n
    0           工业        0
    1         文化休闲        1
    2         教育科技        2
    3         医疗卫生        3
    4         文秘行政        4
    5         生态环境        5
    6         城乡建设        6
    7        农业畜牧业        7
    8         经济管理        8
    9         交通运输        9
    10        政法监察       10
    11        财税金融       11
    12        劳动人事       12
    13        旅游服务       13
    14        资源能源       14
    15        商业贸易       15
    16  气象水文测绘地震地理       16
    17        民政社区       17
    18        信息产业       18
    19        外交外事       19
    :param df:
    :return:
    """
    # 外交
    # tmp0 = df[df.label == 15]
    # tmp1 = df[df.label == 16]
    # tmp2 = df[df.label == 17]
    # tmp3 = df[df.label == 18]
    # tmp4 = df[df.label == 19]
    data_list = []
    data = df[df.label.isin(selected)]
    for index, row in tqdm(data.iterrows()):
        # print(index,row)
        text=row.text
        if len(text) > 300:
            text=text[:1500]
            # print("====" * 2000)
            filename = text.split(' ')[0]
            candidate_text = text.replace(filename, '')
            # print([filename, candidate_text])
            for text_index in range(0, len(candidate_text), text_len):
                # print(text_index)
                data_list.append([file_name+' '+candidate_text[text_index:text_index +70], row.label])
    result = pd.DataFrame(data_list, columns=['text', 'label'])
    result = pd.concat([df, result], axis=0)
    return result


初赛自己的思路其实蛮简单,无非数据预处理部分下了点功夫,我们通过复赛总结来看看可以学习到什么?


6 复赛总结-失败的原因


6.1 自身的原因:


由于复赛大量表格中没有文件名,导致初赛的基于title的融合方案效果有限,这个其实由于初赛前排之后没有花时间去尝试只基于content的实验,权重没有保存以及复赛基于simpletransfors训练卡顿等问题,最终崩盘。


6.2 对手的实力


非常感谢大佬分享的方案,我们在这篇文章中可以找到不少亮点:


15.png


文章链接:https://mp.weixin.qq.com/s/LgrnMtIvsUTLHzVeT3sx5g

同时通过对比「答案」,发现有文件名的部分,准确度已经相当高了,单折也只有 8 个错误,所以可以将精力放在如何提高无文件名模型的精度上。
因为训练集中的文件内容有很多重复的(但是文件名并不重复,甚至 label 也不同),所以仅使用文档内容进行训练时需要先去重处理,训练集经过清理后只剩下 20000+ 个样本
增大文本输入长度为 512,使用 sliding windows 对文本做切割成 192 seq len 的文本,训练及推断的结果使用投票机制,线上 0.933 (+0.002);
尝试不清理非中文字符,线下 acc 比只使用中文字符下降 0.01 左右,所以放弃了
文档内容模型增加一折(十折中的两折),线上 0.937 (+0.004);再增加两折(十折中的四折),线上 0.939 (+0.002)
最终我的提交是 十折中的两折全文本模型+十折仅文档内容模型+后处理,线上 0.93934,排名第六。


  • 第一名 @挥霍的人生

分为文件名模型和文档内容模型
花了很多时间在预处理
模型为 TextCNN,seq len 7000 (char 级别),没有清理非中文字符
TextCNN softmax 之前拼接了文本、文件的统计特征,如文本总长度、文件的大小(指占了磁盘多少的文件大小)
半监督,利用初赛模型去判定初赛测试集,做伪标签


  • 第二名 @第二次打比赛-小迷弟

利用规则直接找到测试集 17000+ 个样本的答案
剩下的 8000+ 个样本只采用仅文档内容模型
文本增强,训练集+初赛测试集+复赛测试集所有中文表头转化为拼音首字母,再找复赛测试集中的拼音首字母转化为中文,等
seqlen 192 的 BERT base


  • 第四名 亿万少年的梦

分为文件名模型和文档内容模型
BERT / TextCNN / TF-IDF 做 stacking


开源整理


7 总结


  • 对于NLP、ML等常规化比赛进行代码复盘总结,形成自己的一套体系:包括代码规范和思路,相比模型带来的收益,其中强调一点代码写得好真的可以节省很多时间,尤其对于复赛时间紧凑的比赛更为重要
  • 敢于尝试,要不断的尝试,尝试次数越多,你就能发现要优化的思路和方向。


相关文章
|
3月前
|
机器学习/深度学习 算法
机器学习 - [集成学习]Bagging算法的编程实现
机器学习 - [集成学习]Bagging算法的编程实现
32 1
|
11月前
|
机器学习/深度学习 算法 Python
机器学习 - [集成学习] - Bagging算法的编程实现
本文介绍集成学习中的 Bagging 算法,并使用 Python 语言手写实现
110 0
|
机器学习/深度学习 算法
m基于多集成BP神经网络的matlab仿真,神经网络通过编程实现不使用工具箱
m基于多集成BP神经网络的matlab仿真,神经网络通过编程实现不使用工具箱
233 0
m基于多集成BP神经网络的matlab仿真,神经网络通过编程实现不使用工具箱
|
前端开发 JavaScript .NET
【转】BarTender与ASP.NET的集成小结(条码标签打印编程)
话说自从上次发了篇NHibernate的资料后,好久没有写东西了,半年来一直在忙一个项目,做完项目后,发现很多东西虽然当时做了,懂了,但是很快就会模糊了,于是又再想起总结的重要性~~没啥地方好放资料的,放在博客园也是一个不错的选择~~   本人也是新手,写的不好的地方,请多原谅。
2460 0
|
17天前
|
消息中间件 Java Kafka
Springboot集成高低版本kafka
Springboot集成高低版本kafka
|
23天前
|
NoSQL Java Redis
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
256 0
|
28天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
401 0
|
1月前
|
NoSQL Java Redis
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
268 1
|
1月前
|
XML Java 关系型数据库
【SpringBoot系列】SpringBoot集成Fast Mybatis
【SpringBoot系列】SpringBoot集成Fast Mybatis
|
2月前
|
Java
【极问系列】springBoot集成elasticsearch出现Unable to parse response body for Response
【极问系列】springBoot集成elasticsearch出现Unable to parse response body for Response