基于文本挖掘的企业隐患排查质量分析模型
1 赛题名称
基于文本挖掘的企业隐患排查质量分析模型
2 赛题背景
企业自主填报安全生产隐患,对于将风险消除在事故萌芽阶段具有重要意义。企业在填报隐患时,往往存在不认真填报的情况,“虚报、假报”隐患内容,增大了企业监管的难度。采用大数据手段分析隐患内容,找出不切实履行主体责任的企业,向监管部门进行推送,实现精准执法,能够提高监管手段的有效性,增强企业安全责任意识。
3 赛题任务
本赛题提供企业填报隐患数据,参赛选手需通过智能化手段识别其中是否存在“虚报、假报”的情况。
看清赛题很关键,大家需要好好理解赛题目标之后,再去做题,可以避免很多弯路。
4 数据简介
本赛题数据集为脱敏后的企业填报自查隐患记录。
数据说明 训练集数据包含“【id、level_1(一级标准)、level_2(二级标准)、level_3(三级标准)、level_4(四级标准)、content(隐患内容)和label(标签)】”共7个字段。 其中“id”为主键,无业务意义;“一级标准、二级标准、三级标准、四级标准”为《深圳市安全隐患自查和巡查基本指引(2016年修订版)》规定的排查指引,一级标准对应不同隐患类型,二至四级标准是对一级标准的细化,企业自主上报隐患时,根据不同类型隐患的四级标准开展隐患自查工作;“隐患内容”为企业上报的具体隐患;“标签”标识的是该条隐患的合格性,“1”表示隐患填报不合格,“0”表示隐患填报合格。
预测结果文件results.csv
列名 | 说明 |
id | 企业号 |
label | 正负样本分类 |
- 文件名:results.csv,utf-8编码
- 参赛者以csv/json等文件格式,提交模型结果,平台进行在线评分,实时排名。
5 评测标准
本赛题采用F1 -score作为模型评判标准。
6 赛题解析笔记
1 导入工具包
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple transformers
# 导入transformers import transformers # from transformers import BertModel, BertTokenizer,BertConfig, AdamW, get_linear_schedule_with_warmup from transformers import AutoModel, AutoTokenizer,AutoConfig, AdamW, get_linear_schedule_with_warmup # 导入torch import torch from torch import nn, optim from torch.utils.data import Dataset, DataLoader import torch.nn.functional as F # 常用包 import re import numpy as np import pandas as pd import seaborn as sns from pylab import rcParams import matplotlib.pyplot as plt from matplotlib import rc from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix, classification_report from collections import defaultdict from textwrap import wrap %matplotlib inline %config InlineBackend.figure_format='retina' # 主题
sns.set(style='whitegrid', palette='muted', font_scale=1.2) HAPPY_COLORS_PALETTE = ["#01BEFE", "#FFDD00", "#FF7D00", "#FF006D", "#ADFF02", "#8F00FF"] sns.set_palette(sns.color_palette(HAPPY_COLORS_PALETTE)) rcParams['figure.figsize'] = 12, 8 # 固定随机种子 RANDOM_SEED = 42 np.random.seed(RANDOM_SEED) torch.manual_seed(RANDOM_SEED) device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") device
device(type='cuda', index=0)
torch.cuda.is_available()
True
2 加载数据
train=pd.read_csv('/home/mw/input/task026741/sub.csv') test=pd.read_csv('/home/mw/input/task026741/test.csv') sub=pd.read_csv('/home/mw/input/task026741/train.csv')
# train=pd.read_csv('data/02/train.csv') # test=pd.read_csv('data/02/test.csv') # sub=pd.read_csv('data/02/sub.csv') print("train.shape,test.shape,sub.shape",train.shape,test.shape,sub.shape)
train.shape,test.shape,sub.shape (12000, 7) (18000, 6) (18000, 2)
# 查看前三行 train.head(3) .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id | level_1 | level_2 | level_3 | level_4 | content | label | |
0 | 0 | 工业/危化品类(现场)—2016版 | (二)电气安全 | 6、移动用电产品、电动工具及照明 | 1、移动使用的用电产品和I类电动工具的绝缘线,必须采用三芯(单相)或四芯(三相)多股铜芯橡套软线。 | 使用移动手动电动工具,外接线绝缘皮破损,应停止使用. | 0 |
1 | 1 | 工业/危化品类(现场)—2016版 | (一)消防检查 | 1、防火巡查 | 3、消防设施、器材和消防安全标志是否在位、完整; | 一般 | 1 |
2 | 2 | 工业/危化品类(现场)—2016版 | (一)消防检查 | 2、防火检查 | 6、重点工种人员以及其他员工消防知识的掌握情况; | 消防知识要加强 | 0 |
2.1 查看缺失值
train.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 12000 entries, 0 to 11999 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 12000 non-null int64 1 level_1 12000 non-null object 2 level_2 12000 non-null object 3 level_3 12000 non-null object 4 level_4 12000 non-null object 5 content 11998 non-null object 6 label 12000 non-null int64 dtypes: int64(2), object(5) memory usage: 656.4+ KB
train[train['content'].isna()] # content 非常重要的字段 .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right;
id | level_1 | level_2 | level_3 | level_4 | content | label | |
6193 | 6193 | 工业/危化品类(现场)—2016版 | (一)消防检查 | 1、防火巡查 | 3、消防设施、器材和消防安全标志是否在位、完整; | NaN | 1 |
9248 | 9248 | 工业/危化品类(现场)—2016版 | (一)消防检查 | 1、防火巡查 | 4、常闭式防火门是否处于关闭状态,防火卷帘下是否堆放物品影响使用; | NaN | 1 |
test.info() # 4 条content为空
<class 'pandas.core.frame.DataFrame'> RangeIndex: 18000 entries, 0 to 17999 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 18000 non-null int64 1 level_1 18000 non-null object 2 level_2 18000 non-null object 3 level_3 18000 non-null object 4 level_4 18000 non-null object 5 content 17996 non-null object dtypes: int64(1), object(5) memory usage: 843.9+ KB
print("train null nums") print(train.shape[0]-train.count()) print("test null nums") print(test.shape[0]-test.count())
train null nums id 0 level_1 0 level_2 0 level_3 0 level_4 0 content 2 label 0 dtype: int64 test null nums id 0 level_1 0 level_2 0 level_3 0 level_4 0 content 4 dtype: int64
2.2 标签分布
tip:NLP所有任务,首先要看下答案或者标签的分布 分类任务,每个类别分布;回归任务,具体数值分布;NER任务,需要标注标签分布。。
train['label'].value_counts()
0 10712 1 1288 Name: label, dtype: int64
sns.countplot(train.label) plt.xlabel('label count')
Text(0.5, 0, 'label count')
1288/10712
0.12023898431665422
3 数据预处理
# 填充缺失值 train['content']=train['content'].fillna('空值') test['content']=test['content'].fillna('空值')
train['level_1']=train['level_1'].apply(lambda x:x.split('(')[0]) train['level_2']=train['level_2'].apply(lambda x:x.split(')')[-1]) train['level_3']=train['level_3'].apply(lambda x:re.split(r'[0-9]、',x)[-1]) train['level_4']=train['level_4'].apply(lambda x:re.split(r'[0-9]、',x)[-1]) test['level_1']=test['level_1'].apply(lambda x:x.split('(')[0]) test['level_2']=test['level_2'].apply(lambda x:x.split(')')[-1]) test['level_3']=test['level_3'].apply(lambda x:re.split(r'[0-9]、',x)[-1]) test['level_4']=test['level_4'].apply(lambda x:re.split(r'[0-9]、',x)[-1])
train .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id | level_1 | level_2 | level_3 | level_4 | content | label | |
0 | 0 | 工业/危化品类 | 电气安全 | 移动用电产品、电动工具及照明 | 移动使用的用电产品和I类电动工具的绝缘线,必须采用三芯(单相)或四芯(三相)多股铜芯橡套软线。 | 使用移动手动电动工具,外接线绝缘皮破损,应停止使用. | 0 |
1 | 1 | 工业/危化品类 | 消防检查 | 防火巡查 | 消防设施、器材和消防安全标志是否在位、完整; | 一般 | 1 |
2 | 2 | 工业/危化品类 | 消防检查 | 防火检查 | 重点工种人员以及其他员工消防知识的掌握情况; | 消防知识要加强 | 0 |
3 | 3 | 工业/危化品类 | 消防检查 | 防火巡查 | 消防设施、器材和消防安全标志是否在位、完整; | 消防通道有货物摆放 清理不及时 | 0 |
4 | 4 | 工业/危化品类 | 消防检查 | 防火巡查 | 常闭式防火门是否处于关闭状态,防火卷帘下是否堆放物品影响使用; | 防火门打开状态 | 0 |
... | ... | ... | ... | ... | ... | ... | ... |
11995 | 11995 | 商贸服务教文卫类 | 安全教育培训 | 员工安全教育 | 制定安全教育培训计划,确保全员参与培训,并建立安全培训档案。 | 个别员工对消防栓的使用不熟练\r\n | 0 |
11996 | 11996 | 工业/危化品类 | 电气安全 | 电气线路及电源插头插座 | 电源插座、电源插头应按规定正确接线。 | 化验室超净台照明电源线防护不足,且检测台金属架未安装漏电接地保护线。整改措施:更换照明灯为前... | 0 |
11997 | 11997 | 工业/危化品类 | 机械设备安全防护 | 人身防护 | 皮带轮、齿轮、凸轮、曲柄连杆机构等外露的转动和运动部件应有防护罩。 | 电箱、马达,没有防护罩,现在整改 | 0 |
11998 | 11998 | 工业/危化品类 | 作业环境 | 通风与照明 | 作业场所通风良好。 | D1部车间二楼配胶房排风扇未开启。 | 0 |
11999 | 11999 | 纯办公场所 | 消防安全 | 消防通道 | 疏散通道无占用、堵塞、封闭等现象。安全出口不得上锁。 | 已整改 | 1 |
12000 rows × 7 columns
# train['text']=train['content']+' '+train['level_1']+' '+train['level_2']+' '+train['level_3']+' '+train['level_4'] # test['text']=test['content']+' '+test['level_1']+' '+test['level_2']+' '+test['level_3']+' '+test['level_4'] train['text']=train['content']+'[SEP]'+train['level_1']+'[SEP]'+train['level_2']+'[SEP]'+train['level_3']+'[SEP]'+train['level_4'] test['text']=test['content']+'[SEP]'+test['level_1']+'[SEP]'+test['level_2']+'[SEP]'+test['level_3']+'[SEP]'+test['level_4']
train.head() .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id | level_1 | level_2 | level_3 | level_4 | content | label | text | |
0 | 0 | 工业/危化品类 | 电气安全 | 移动用电产品、电动工具及照明 | 移动使用的用电产品和I类电动工具的绝缘线,必须采用三芯(单相)或四芯(三相)多股铜芯橡套软线。 | 使用移动手动电动工具,外接线绝缘皮破损,应停止使用. | 0 | 使用移动手动电动工具,外接线绝缘皮破损,应停止使用.[SEP]工业/危化品类[SEP]电气安... |
1 | 1 | 工业/危化品类 | 消防检查 | 防火巡查 | 消防设施、器材和消防安全标志是否在位、完整; | 一般 | 1 | 一般[SEP]工业/危化品类[SEP]消防检查[SEP]防火巡查[SEP]消防设施、器材和消... |
2 | 2 | 工业/危化品类 | 消防检查 | 防火检查 | 重点工种人员以及其他员工消防知识的掌握情况; | 消防知识要加强 | 0 | 消防知识要加强[SEP]工业/危化品类[SEP]消防检查[SEP]防火检查[SEP]重点工种... |
3 | 3 | 工业/危化品类 | 消防检查 | 防火巡查 | 消防设施、器材和消防安全标志是否在位、完整; | 消防通道有货物摆放 清理不及时 | 0 | 消防通道有货物摆放 清理不及时[SEP]工业/危化品类[SEP]消防检查[SEP]防火巡查[... |
4 | 4 | 工业/危化品类 | 消防检查 | 防火巡查 | 常闭式防火门是否处于关闭状态,防火卷帘下是否堆放物品影响使用; | 防火门打开状态 | 0 | 防火门打开状态[SEP]工业/危化品类[SEP]消防检查[SEP]防火巡查[SEP]常闭式防... |
3.1 文本长度分布
train['text_len']=train['text'].map(len)
train['text'].map(len).describe()# 298-12=286
count 12000.000000 mean 80.439833 std 21.913662 min 43.000000 25% 66.000000 50% 75.000000 75% 92.000000 max 298.000000 Name: text, dtype: float64
test['text'].map(len).describe() # 520-12=518
count 18000.000000 mean 80.762611 std 22.719823 min 43.000000 25% 66.000000 50% 76.000000 75% 92.000000 max 520.000000 Name: text, dtype: float64
train['text_len'].plot(kind='kde')
<AxesSubplot:ylabel='Density'>
sum(train['text_len']>100) # text文本长度大于100的个数 sum(train['text_len']>200) # text文本长度大于200的个数
11
1878/len(train)
0.1565
4 认识Tokenizer
4.1 将文本映射为id表示
PRE_TRAINED_MODEL_NAME = 'bert-base-chinese' # PRE_TRAINED_MODEL_NAME = 'hfl/chinese-roberta-wwm-ext' # PRE_TRAINED_MODEL_NAME = 'hfl/chinese-roberta-wwm' # tokenizer = BertTokenizer.from_pretrained(PRE_TRAINED_MODEL_NAME) tokenizer = AutoTokenizer.from_pretrained(PRE_TRAINED_MODEL_NAME)
# tokenizer = BertTokenizer.from_pretrained('C:\\Users\\yanqiang\\Desktop\\bert-base-chinese')
tokenizer
PreTrainedTokenizerFast(name_or_path='bert-base-chinese', vocab_size=21128, model_max_len=512, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'})
- 可以看到
BertTokenizer
的词表大小为21128 - 特殊符号为
special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}
我们尝试使用BertTokenizer
进行分词
sample_txt = '今天早上9点半起床,我在学习预训练模型的使用.'
len(sample_txt)
23
tokens = tokenizer.tokenize(sample_txt) token_ids = tokenizer.convert_tokens_to_ids(tokens) print(f'文本为: {sample_txt}') print(f'分词的列表为: {tokens}') print(f'词对应的唯一id: {token_ids}')
文本为: 今天早上9点半起床,我在学习预训练模型的使用. 分词的列表为: ['今', '天', '早', '上', '9', '点', '半', '起', '床', ',', '我', '在', '学', '习', '预', '训', '练', '模', '型', '的', '使', '用', '.'] 词对应的唯一id: [791, 1921, 3193, 677, 130, 4157, 1288, 6629, 2414, 8024, 2769, 1762, 2110, 739, 7564, 6378, 5298, 3563, 1798, 4638, 886, 4500, 119]
4.2 特殊符号
tokenizer.sep_token, tokenizer.sep_token_id
('[SEP]', 102)
tokenizer.unk_token, tokenizer.unk_token_id
('[UNK]', 100)
tokenizer.pad_token, tokenizer.pad_token_id
('[PAD]', 0)
tokenizer.cls_token, tokenizer.cls_token_id
('[CLS]', 101)
tokenizer.mask_token, tokenizer.mask_token_id
('[MASK]', 103)
可以使用 encode_plus()
对句子进行分词,添加特殊符号
encoding=tokenizer.encode_plus( sample_txt, # sample_txt_another, max_length=32, add_special_tokens=True,# [CLS]和[SEP] return_token_type_ids=True, pad_to_max_length=True, return_attention_mask=True, return_tensors='pt',# Pytorch tensor张量 ) encoding.keys()
Truncation was not explicitly activated but `max_length` is provided a specific value, please use `truncation=True` to explicitly truncate examples to max length. Defaulting to 'longest_first' truncation strategy. If you encode pairs of sequences (GLUE-style) with the tokenizer you can select this strategy more precisely by providing a specific strategy to `truncation`. F:\ProgramData\Anaconda3\lib\site-packages\transformers\tokenization_utils_base.py:2271: FutureWarning: The `pad_to_max_length` argument is deprecated and will be removed in a future version, use `padding=True` or `padding='longest'` to pad to the longest sequence in the batch, or use `padding='max_length'` to pad to a max length. In this case, you can give a specific length with `max_length` (e.g. `max_length=45`) or leave max_length to None to pad to the maximal input size of the model (e.g. 512 for Bert). warnings.warn( dict_keys(['input_ids', 'token_type_ids', 'attention_mask'])
encoding
{'input_ids': tensor([[ 101, 791, 1921, 3193, 677, 130, 4157, 1288, 6629, 2414, 8024, 2769, 1762, 2110, 739, 7564, 6378, 5298, 3563, 1798, 4638, 886, 4500, 119, 102, 0, 0, 0, 0, 0, 0, 0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]])}
token ids的长度为32的张量
print(len(encoding['input_ids'][0]))
32
attention mask具有同样的长度
print(len(encoding['attention_mask'][0])) encoding['attention_mask']
32 tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0]])
我们将ids反转为词语,可以打印下每个字符是什么?
tokenizer.convert_ids_to_tokens(encoding['input_ids'][0])
['[CLS]', '今', '天', '早', '上', '9', '点', '半', '起', '床', ',', '我', '在', '学', '习', '预', '训', '练', '模', '型', '的', '使', '用', '.', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']
4.2 选取文本最大长度
token_lens = [] for txt in train.text: tokens = tokenizer.encode(txt, max_length=512) token_lens.append(len(tokens))
sns.distplot(token_lens) plt.xlim([0, 256]); plt.xlabel('Token count');
F:\ProgramData\Anaconda3\lib\site-packages\seaborn\distributions.py:2619: FutureWarning: `distplot` is a deprecated function and will be removed in a future version. Please adapt your code to use either `displot` (a figure-level function with similar flexibility) or `histplot` (an axes-level function for histograms). warnings.warn(msg, FutureWarning)
可以看到大多数文本的ids长度在100以内,我们设置最大长度为160
MAX_LEN = 160
5 构建数据集
5.1 自定义数据集
class EnterpriseDataset(Dataset): def __init__(self,texts,labels,tokenizer,max_len): self.texts=texts self.labels=labels self.tokenizer=tokenizer self.max_len=max_len def __len__(self): return len(self.texts) def __getitem__(self,item): """ item 为数据索引,迭代取第item条数据 """ text=str(self.texts[item]) label=self.labels[item] encoding=self.tokenizer.encode_plus( text, add_special_tokens=True, max_length=self.max_len, return_token_type_ids=True, pad_to_max_length=True, return_attention_mask=True, return_tensors='pt', ) # print(encoding['input_ids']) return { 'texts':text, 'input_ids':encoding['input_ids'].flatten(), 'attention_mask':encoding['attention_mask'].flatten(), # toeken_type_ids:0 'labels':torch.tensor(label,dtype=torch.long) }