2022厦门大数据安全开放创新应用大赛食品安全专题-算法赛道Baseline
厦门大数据安全开放创新应用大赛-食品安全专题-食品安全信息抽取模型建立Baseline - 0.95+,采用规则匹配、TFIDF、TextRank、异常检测算法构建模型,经过适当调整可到0.96+
赛题介绍
赛事地址:
赛题说明:
“民以食为天,食以安为先”,食品安全就是民生。本赛题希望在现有的各类综合信息库中抽取与食品安全相关的信息,以助力相关部门监管高效精准。
赛题任务:
参赛者需要基于主办方提供的综合信息数据,对信息数据进行分类,通过模型建立、语义分析等方法筛选出食品安全相关的信息,输出属于食品安全相关的信息编号及信息名称,以助力相关部门监管高效精准。
针对食品及食品安全名词定义如下:
(1) 食品,指各种供人食用或者饮用的成品和原料以及按照传统既是食品又是中药材的物品,但是不包括以治疗为目的的物品。
(2) 食品安全,指食品无毒、无害,符合应当有的营养要求,对人体健康不造成任何急性、亚急性或者慢性危害。食品安全包括食品卫生、食品质量、食品营养等相关内容和食品(食物)种植、养殖、加工、包装、储藏、运输、销售、消费等各个环节。
提交格式要求
算法运行结果保存在文件名为result.txt,格式为 utf-8。
算法运行结果文件内容中,每个字段间以英文半角竖线 “|” 符 分隔。如下:
EVENT_ID|EVENT_TYPE|EVENT_NAME
Y000885|0|报警人报称衣服被施工的泥土搞脏了,现**工单位因赔偿问题有争执。
Y003481|1|请**官方展开卫生调查
输出结果字段注释
字段名 | 字段释意 | 备注 |
---|---|---|
EVENT_ID | 信息唯一编号 | {信息 ID_001,信息 ID_002,…} |
EVENT_TYPE | 是否属于食品安全相关信息 | {0:否;1:是} |
EVENT_NAME | 信息名称 | 信息唯一编号所对应的信息标题 |
Baseline思路
本赛题是基于无监督数据的二元文本分类问题,最大难点在于没有真实标签的无监督数据集如何建立有效的模型。
初步的思路是:规则匹配(与食品安全相关的文本)召回正例 + Sentnece Model提取正例文本特征 + 异常检测模型(基于One-Class SVM)通过学习正例在高维空间中获取一个可以拒绝负例(非食品安全相关)的决策边界,通过决策边界二分类结果。
- 1 规则匹配(参考Betterme大佬0.94的baseline)(召回高,精确稍低)
- 2 使用TFIDF筛选指定比例的前K个关键词,使用这些关键词进行规则匹配
- 2 使用TextRank筛选TopK个关键词,使用这些关键词进行规则匹配(召回低,精确高)
- 4 根据三种规则匹配方法判断标签的一致性生成训练集和测试集,当所有方法得到的标签一致则该文本可用于训练,否则存在不一致性用于测试集,后续借助模型判断其分类
- 3 遵循赛题构建分类模型,baseline使用异常检测模型做一个分类器,区别与常规需要二分类数据才能构建异常检测模型,本Baseline选择One-Class SVM分类器,OCSVM在训练时只需要一种标签的文本(即正例,比赛中是食品安全有关的文本)进行训练,其思想是利用正例数据在向量空间中拟合出一个封闭的边界,边界内的是正例,边界外的是负例(异常样本)。(平衡召回和精确)
其中存在两个问题:- 如何提取文本向量? 在paddlenlp中调用预训练模型获得文本的句向量,本baseline使用sim-bert。
- 没有真实标签如何判定分类器训练的好坏? 分类器的决策边界取决于正例的数量以及超参数的设置,超参数惩罚严格得到边界太小,召回率会降低,超参数惩罚宽松边界过大,精确率会降低,由于缺乏真实标签,目前暂未想到比较好验证方式。
- Baseline做法:由于TextRank匹配比规则匹配更严格,得到的正例是很少,默认超参数下分类器的边界太小,所以把规则匹配的结果(当前置信度最高,且可以不断优化规则提高精度)当做伪的真实标签,通过调参的方式尽量让分类器的结果在F1值上贴近规则匹配的结果,以达到慢慢扩大决策边界平衡召回率和精确率。当然,结果上可能很难超过规则匹配的分数,并且有些耍小聪明的感觉,要是官方没有明确说7000条数据中食品安全数据只有1500条,那1500的界限也很难估计出来的,所以这种方式仍然存在不合理性,仅当做一种验证方式。
- 4 使用训练好的OCSVM模型,推理所有数据,得到提交结果。
各Base效果(均为A榜提交结果):
方法 | 分数 |
---|---|
规则匹配(betterme) | 94.0610 |
规则匹配(betterme)+ OCSVM | 93.2805 |
规则匹配(扩增主题词) | 95.0985 |
TF-IDF规则匹配 | - |
TextRank规则匹配 | - |
规则匹配 + 一致性伪标签 + OCSVM | 95.3380 |
正则表达式 | 96.5010 |
正则表达式 + 一致性伪标签 + OCSVM | 96.8580 |
正则表达式 + 一致性伪标签 + Ernie3.0 Fine-tuning | 96.7430 |
后续可以尝试的优化:
- 优化句向量,使用更SOTA的句向量表征模型(…)
- 优化带边界的异常检测模型,使用更SOTA的带边界的异常检测模型(…)
- 优化正则表达式(…)
- 优化带噪声的标签,Learning with noisy label相关方向的方案也是不错参考(…)
- 优化伪标签,使用弱监督方法、半监督方法利用少量可靠的人工标注/筛选标签构建文本分类器(…)
- 使用文本匹配 or 文本排序 代替 文本分类(…)
- 试试Prompt方法,PET是一种将任务重新编写为完形填空问题的范式,在训练时同时利用了有标签数据和无标签的数据,比较贴合只有少量正确标注的场景(…)
优化方案仅简单描写新增思路,待赛题结束更新详细代码:
【2022.09.30】新增说明:
- 沿用betterme大佬的规则匹配,使用近义词查找、TF-IDF尽可扩大食品安全相关的主题词,提高规则匹配结果和TextRank的召回率和精确率,大约能有1个点左右的提升。
例如扩增下列词语:
卫生差、卫生不合格、腹泻、肚子不舒服、有烟头、有异物
- 替换更严格的正则表达式做规则匹配,大约有2.5个点左右的提升。
正则表达式构造样例如下:
pattern = '(食品|食物|环境|卫生|店铺|餐食|消防|许可证|健康证).*?(差|问题|隐患|不达标|不过关|不合理|不合格)|问题食品|食品质量|食品安全|有害食品|食品添加剂|强制购物|强制消费|消费维权|欺瞒消费'
df.loc[df["concat"].str.contains(pattern), 'EVENT_TYPE'] = 1
【2022.10.02】新增说明:
尝试伪标签 + Finetune
- 正则表达式 + 一致性标签 + Ernie3.0 Fine-tuning(常规二分类文本模型微调)初次尝试分数比正则表达式高,但比OCSVM分数低一点点,这并不能说明微调范式比OCSVM差,主要还是依赖伪标签的数据质量。
- Finetune代码带更新…
【2022.10.03】新增说明:
- 正则 is all you need ,替换更严格的正则表达式(再次提升1个点),重新训练OCSVM和Fine-tuning,随着数据质量的提升,OCSVM和Fine-tuning的模型也开始慢慢提升,Fine-tuning模型的效果明显提升(提1个点左右)
- OCSVM模型有一些效果,但效果不大,在正则表达式的基础上有微弱的提升。主要是因为OCSVM仅仅是在向量空间中寻找合适的边界,而句向量是直接调用模型获得的,在赛题数据上并没有进行相关任务训练(可以尝试增加SimCES等对比学习方法,优化句向量表征)
- 伪标签中的脏数据变少后,Fine-tuning模型效果提升明显
方法 | 分数 |
---|---|
正则表达式 | 97.1630 |
正则表达式 + 一致性伪标签 + OCSVM | 97.3160 |
正则表达式 + 一致性伪标签 + Ernie3.0 Fine-tuning | 98.2855 |
下面是完整代码
'''
食品以及食品安全的定义:
(1) 食品,指各种供人食用或者饮用的成品和原料以及按照传统既是食品又是中药材的物品,
但是不包括以治疗为目的的物品。
(2) 食品安全,指食品无毒、无害,符合应当有的营养要求,对人体健康不造成任何急性、亚急性或者慢性危害。
食品安全包括食品卫生、食品质量、食品营养等相关内容和
食品(食物)种植、养殖、加工、包装、储藏、运输、销售、消费等各个环节。
'''
'\n食品以及食品安全的定义:\n(1) 食品,指各种供人食用或者饮用的成品和原料以及按照传统既是食品又是中药材的物品,\n但是不包括以治疗为目的的物品。 \n(2) 食品安全,指食品无毒、无害,符合应当有的营养要求,对人体健康不造成任何急性、亚急性或者慢性危害。\n食品安全包括食品卫生、食品质量、食品营养等相关内容和\n食品(食物)种植、养殖、加工、包装、储藏、运输、销售、消费等各个环节。\n'
0 安装依赖
!pip install --upgrade pip
!pip install hyperopt
!pip install gensim==4.1.2
1 规则匹配
规则匹配的代码沿用了大佬的代码
在此感谢Betterme大佬开源超简洁的厦门大数据安全开放创新应用大赛-食品安全专题/baseline_0.94.py
# Betterme大佬的Baseline
import os
import pandas as pd
from tqdm import tqdm
# 读取数据
file_dir = r"./work/data/食品安全-算法分析题初赛A榜-综合信息数据.xls"
df = pd.read_excel(file_dir).fillna('')
df.rename(columns={'EVEN_ID': 'EVENT_ID'}, inplace=True)
df["concat"] = [str(row['EVENT_NAME'])+" "+str(row['CONTENT']) for idx,row in df.iterrows()]
df['EVENT_TYPE'] = 0
# 规则匹配
pattern = '食|餐|肉|饭|菜|面包|蛋糕'
df.loc[df["concat"].str.contains(pattern), 'EVENT_TYPE'] = 1
print(df['EVENT_TYPE'].value_counts())
os.makedirs('./work/prediction/Relu',exist_ok=True)
df[["EVENT_ID","EVENT_TYPE","EVENT_NAME"]].to_csv("./work/prediction/Relu/result1.txt",index=False,encoding="utf-8",sep="|")
0 5500
1 1500
Name: EVENT_TYPE, dtype: int64
# 扩增食品安全相关的主题词 规则匹配
import os
import pandas as pd
from tqdm import tqdm
# 读取数据
file_dir = r"./work/data/食品安全-算法分析题初赛A榜-综合信息数据.xls"
df = pd.read_excel(file_dir).fillna('')
df.rename(columns={'EVEN_ID': 'EVENT_ID'}, inplace=True)
df["concat"] = [str(row['EVENT_NAME'])+" "+str(row['CONTENT']) for idx,row in df.iterrows()]
df['EVENT_TYPE'] = 0
# 规则匹配
search_word = [
'食品', '食物', '餐饮', '肉类', '肉制品', '海鲜', '生鲜','小吃', '烧烤', '熟食', '罐头', '自助餐', '料理','食堂',
'饭堂', '菜市场', '大排档', '批发市场', '食用', '饮用', '就餐', '用餐','腹泻', '呕吐', '拉肚子', '恶心', '肚子疼',
'胃炎', '消化不良', '食欲不振', '反胃', '作呕', '肚子不舒服','蟑螂', '苍蝇', '蜘蛛', '寄生虫', '蛆', '蚊子', '毛发',
'异物', '虫子', '虫卵', '有烟头', '残留物', '沉淀物', '不新鲜', '过期','霉变', '发霉', '腐烂', '变质', '发黄', '变黑',
'发黑', '腐坏', '发臭', '变色', '变酸','发馊', '致癌物''未消毒', '未检疫', '没有检疫', '卫生不合格', '消防不合格', '卫生差',
'卫生问题' , '不卫生', '卫生环境差', '食品经营许可证','消费欺诈']
pattern = "|".join(search_word)
df.loc[df["concat"].str.contains(pattern), 'EVENT_TYPE'] = 1
print(df['EVENT_TYPE'].value_counts())
os.makedirs('./work/prediction/Relu',exist_ok=True)
df[["EVENT_ID","EVENT_TYPE","EVENT_NAME"]].to_csv("./work/prediction/Relu/result2.txt",index=False,encoding="utf-8",sep="|")
0 5484
1 1516
Name: EVENT_TYPE, dtype: int64
2 基于TF-IDF的规则匹配
from ast import literal_eval
# TF-IDF 预处理
file_dir = r"./work/cutword_data.csv"
# 借助百度的分词工具先对文本进行分词,第一次加载比较久,可以替换为速度较快的jieba分词
if os.path.exists(file_dir):
cut_df = pd.read_csv(file_dir)
else:
from paddlenlp import Taskflow
cut_df = df.copy()
seg_accurate = Taskflow("word_segmentation", mode="accurate")
cut_df["CUT_WORD"] = [seg_accurate(str(i)) for i in tqdm(cut_df["concat"])]
cut_df.to_csv(os.path.join(file_dir), index=False, encoding="utf-8")
# 去除停用词
stop_words = ['*',',', '。', '(',')','、',' ',':','《','》','DD',';','-','“','”','?','‘','’','…','【','】','.','!','·']
stop_words_files = [r"./work/stopwords/baidu_stopwords.txt",
r"./work/stopwords/hit_stopwords.txt",
r"./work/stopwords/scu_stopwords.txt",
]
for file_dir in stop_words_files:
with open(file_dir, 'r',encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
stop_words.append(line.strip())
stop_words = list(set(stop_words))
# 处理pandas读取list问题
if type(cut_df.CUT_WORD[0]) is str:
data = [[j for j in literal_eval(i) if j not in stop_words] for i in cut_df.CUT_WORD]
else:
data = [[j for j in i if j not in stop_words] for i in cut_df.CUT_WORD]
print("数据分词、清洗停用词完成")
# 计算TF-IDF
from gensim import corpora, models
# 每条文本的分词数量是大小不一的,设置比例参数rate控制召回前%多少的单词做匹配,默认是1.0
top_rate = 1.0
# 生成文档对应的字典和bow稀疏向量
dictionary = corpora.Dictionary(data)
corpus = [dictionary.doc2bow(text) for text in data] # 仍为list in list
tfidf_model = models.TfidfModel(corpus,dictionary=dictionary) # 建立TF-IDF模型
corpus_tfidf = tfidf_model[corpus] # 对所需文档计算TF-IDF结果
for ids,find in enumerate(corpus_tfidf):
find = sorted(find,key=(lambda x:x[1]),reverse=True)
tf_idf_word = [(dictionary[id], freq) for idx, (id, freq) in enumerate(find) if idx < int(len(find) * top_rate)]
cut_df.loc[ids,"TFIDF_WORD"] = " ".join([i for i,j in tf_idf_word])
# 匹配食品安全信息
cut_df['EVENT_TYPE'] = 0
cut_df.loc[cut_df["TFIDF_WORD"].str.contains("|".join(search_word)), 'EVENT_TYPE'] = 1
# 得到TF-IDF结果
df['TFIDF_EVENT_TYPE'] = cut_df['EVENT_TYPE']
print(df['TFIDF_EVENT_TYPE'].value_counts())
save_df = df[["EVENT_ID","TFIDF_EVENT_TYPE","EVENT_NAME"]]
save_df.columns = ["EVENT_ID","EVENT_TYPE","EVENT_NAME"]
save_df.to_csv("./work/prediction/Relu/result_TFIDF.txt",index=False,encoding="utf-8",sep="|")
数据分词、清洗停用词完成
0 5527
1 1473
Name: TFIDF_EVENT_TYPE, dtype: int64
3 基于TextRank的规则匹配
# TextRank
import jieba
import jieba.analyse as analyse
# copy一份dataframe
tr_df = df.copy()
# 停用词表
stopwords_files = [r"./work/stopwords/baidu_stopwords.txt",]
# 读取停用词
for i in stopwords_files:
analyse.set_stop_words(i)
# 提取TextRank关键词,筛选分数最高的前Top K个关键词
for ids,sentence in enumerate(tr_df["concat"]):
# TextRank关键词提取,词性筛选
keywords = analyse.textrank(sentence, topK=30, allowPOS=('n', 'nz', 'v', 'vd', 'vn', 'l', 'a', 'd'))
tr_df.loc[ids, "TEXTRANK_WORD"] = " ".join(keywords)
# TextRank关键词和pattern进行匹配,筛选出食品安全相关信息
tr_df['EVENT_TYPE'] = 0
tr_df.loc[tr_df["TEXTRANK_WORD"].str.contains(pattern), 'EVENT_TYPE'] = 1
# 得到TextRank结果
df['TEXTRANK_EVENT_TYPE'] = tr_df['EVENT_TYPE']
print(df['TEXTRANK_EVENT_TYPE'].value_counts())
# 保存结果
save_df = df[["EVENT_ID","TEXTRANK_EVENT_TYPE","EVENT_NAME"]]
save_df.columns = ["EVENT_ID","EVENT_TYPE","EVENT_NAME"]
save_df.to_csv("./work/prediction/Relu/result_TextRank.txt",index=False,encoding="utf-8",sep="|")
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 1.074 seconds.
Prefix dict has been built successfully.
0 5594
1 1406
Name: TEXTRANK_EVENT_TYPE, dtype: int64
4 通过标签一致性划分训练集和测试集
# 处理三种标签的一致性结果
res = df[['EVENT_TYPE','TFIDF_EVENT_TYPE', 'TEXTRANK_EVENT_TYPE']]
# 使用求和得到投票状态
df['EVENT_STATE'] = res.sum(axis=1).tolist()
# 只有当三种方法都得到相同结果,认为具有一致性(即分数为0和3的情况)标记为1,否则为0
df['Consistency'] = [1 if i in [0,3] else 0 for i in df['EVENT_STATE']]
# 根据一致性生成伪标签, 对于分数为0和3的保持标签不变用作训练集,其他分数赋予-1表示有歧义的测试集,交由模型判定
df['Pseudotag'] = [0 if i==0 else 1 if i==3 else -1 for i in df['EVENT_STATE']]
df.to_csv("./work/prediction/Relu/result_pseudo.txt",index=False,encoding="utf-8",sep="|")
print(df['Pseudotag'].value_counts())
0 5484
1 1405
-1 111
Name: Pseudotag, dtype: int64
# 读取伪标签数据
file_dir = r"./work/prediction/Relu/result_pseudo.txt"
df = pd.read_csv(file_dir,sep="|")
# 根据一致性划分训练集和数据集
train = df[df['Consistency']==1]
test = df[df['Consistency']==0]
train.reset_index(drop=True,inplace=True)
test.reset_index(drop=True,inplace=True)
print(train.shape,test.shape)
print(train['Pseudotag'].value_counts())
(6889, 10) (111, 10)
0 5484
1 1405
Name: Pseudotag, dtype: int64
5 句向量特征提取
# 句向量提取
import paddle
from functools import partial
from paddlenlp.data import Stack, Dict, Pad
from paddlenlp.datasets import load_dataset
from paddlenlp.transformers import BertModel, BertTokenizer
# 特征提取器
class Feature_extract(object):
# 初始化
def __init__(self,tokenizer,model,mode,batch_size=128):
self.tokenizer = tokenizer
self.model = model
self.mode = mode
self.batch_size = batch_size
# 以batch制作输入
def batch_tokens(self,sentences):
# 迭代数据函数
def read(sentences):
for sentence in sentences:
yield {"text":sentence}
# 编码数据函数
def convert_example(examples, tokenizer, max_seq_len=512):
tokenized_input = tokenizer(examples['text'],is_split_into_words=True,max_seq_len=max_seq_len)
return tokenized_input
# 制作batch数据
data_ds = load_dataset(read, sentences=sentences, lazy=False)
data_trans_func = partial(convert_example,tokenizer=self.tokenizer)
data_ds.map(data_trans_func, lazy=False)
data_batch_sampler = paddle.io.BatchSampler(data_ds, batch_size=self.batch_size, shuffle=False)
# 定义batchify_fn
batchify_fn = lambda samples, fn = Dict({
"input_ids": Pad(axis=0, pad_val=self.tokenizer.pad_token_id),
"token_type_ids": Pad(axis=0, pad_val=self.tokenizer.pad_token_type_id),
}): fn(samples)
# 制作dataloader
data_loader = paddle.io.DataLoader(
dataset=data_ds,
batch_sampler=data_batch_sampler,
collate_fn=batchify_fn,
return_list=True)
return data_loader
# 模型推理,获取特征
@paddle.no_grad()
def prepare_features(self,examples):
outputs = []
self.model.eval()
data_loader = self.batch_tokens(examples)
for batch in tqdm(data_loader):
output = self.model(input_ids=batch[0],token_type_ids=batch[1])
if self.mode == "cls":
output = output[1]
if self.mode == "last_avg":
output = output[0].mean(axis=1)
outputs.append(output)
outputs = paddle.concat(outputs,axis=0)
return outputs
# 特征提取函数
def get_emb(self,setences):
return self.prepare_features(setences)
# 超参数
max_seq_length = 256
mode = "cls" # cls | last_avg
# 特征提取并制作训练数据
# 使用simbert作为特征提取器的骨干模型
tokenizer = BertTokenizer.from_pretrained('simbert-base-chinese')
sim_model = BertModel.from_pretrained('simbert-base-chinese')
fe = Feature_extract(tokenizer,sim_model,mode=mode,batch_size=250)
# 创建训练集(只含正例),只使用TextRank筛选出的食品安全句子作为正例
pos_data = train[train['Pseudotag'] == 1]
pos_data.reset_index(drop=True,inplace=True)
# 正例 特征提取
train_pos_X = fe.get_emb(pos_data['concat'].str.replace("*",""))
print(train_pos_X.shape)
# 测试集 特征提取
test_X = fe.get_emb(test['concat'].str.replace("*",""))
print(test_X.shape)
# 全量数据 特征提取
train_X = fe.get_emb(df['concat'].str.replace("*", ""))
train_y = df['EVENT_TYPE']
[2022-10-03 14:33:30,957] [ INFO] - Already cached /home/aistudio/.paddlenlp/models/simbert-base-chinese/vocab.txt
[2022-10-03 14:33:30,980] [ INFO] - Already cached /home/aistudio/.paddlenlp/models/simbert-base-chinese/simbert-base-chinese-v1.pdparams
W1003 14:33:30.985726 12607 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1003 14:33:30.991935 12607 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
[2022-10-03 14:33:39,146] [ INFO] - Weights from pretrained model not used in BertModel: ['cls.predictions.decoder_bias', 'cls.predictions.transform.weight', 'cls.predictions.transform.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.LayerNorm.bias', 'cls.predictions.decoder_weight', 'cls.predictions.decoder.bias', 'cls.seq_relationship.weight', 'cls.seq_relationship.bias']
100%|██████████| 6/6 [00:13<00:00, 2.23s/it]
[1405, 768]
100%|██████████| 1/1 [00:01<00:00, 1.22s/it]
[111, 768]
100%|██████████| 28/28 [00:54<00:00, 2.29s/it]
6 训练OneClassSVM分类器
from sklearn import svm
from sklearn.metrics import f1_score
# 先测试一下OCSVM的效果,然后超参数搜索得到更好的分数
clf = svm.OneClassSVM(nu=0.03, kernel='rbf',gamma=0.03)
clf.fit(train_pos_X)
# 预测全量数据
y_pred_train = clf.predict(train_X)
y_pred_train = [0 if i==-1 else 1 for i in y_pred_train]
# 计算以规则匹配为伪标签的 f1分数
f1s = f1_score(y_true=train_y, y_pred=y_pred_train, average='binary')
df['PRE_EVENT_TYPE'] = y_pred_train
print('f1:',f1s)
print(df['PRE_EVENT_TYPE'].value_counts())
f1: 0.9194151649098945
0 5575
1 1425
Name: PRE_EVENT_TYPE, dtype: int64
7 超参数搜索
import numpy as np
import hyperopt
from hyperopt import hp, fmin, tpe, Trials, partial, STATUS_OK
from hyperopt.early_stop import no_progress_loss
from hyperopt import fmin, tpe, hp, Trials
def hyperopt_train_test(params):
clf = svm.OneClassSVM(kernel='rbf',nu=params['nu'],gamma=params['gamma'])
clf.fit(train_pos_X)
y_pred_train = clf.predict(train_X)
y_pred_train = [0 if i==-1 else 1 for i in y_pred_train]
f1s = f1_score(y_true=train_y, y_pred=y_pred_train, average='binary')
return f1s
space_params = {
'nu': hp.uniform('nu', 0.,0.1),
'gamma': hp.uniform('gamma', 0.,0.1),
}
def hyperopt_objective(params):
f1 = hyperopt_train_test(params)
return {'loss': -f1, 'status': STATUS_OK}
early_stop_fn = no_progress_loss(50)
trials = Trials()
search_best_params = fmin(hyperopt_objective,
space_params,
algo = tpe.suggest,
max_evals = 300,
trials = trials,
rstate= np.random.default_rng(42), # 固定随机数种子
verbose = True,
early_stop_fn = early_stop_fn)
print('best:',search_best_params)
39%|███▉ | 117/300 [06:56<04:08, 1.36s/trial, best loss: -0.9314079422382672]
best: {'gamma': 0.019149253346731697, 'nu': 0.014771601756989487}
8 最佳模型训练和预测
# 只预测不一致数据,然后和伪标签进行拼接
clf = svm.OneClassSVM(kernel='rbf', nu=search_best_params['nu'], gamma=search_best_params['gamma'])
clf.fit(train_pos_X)
y_pred_train = clf.predict(test_X)
y_pred_train = [0 if i==-1 else 1 for i in y_pred_train]
train_index = df[df['Consistency']==1].index
test_index = df[df['Consistency']==0].index
df.loc[train_index,'EVENT_TYPE'] = [int(i) for i in train['Pseudotag']]
df.loc[test_index,'EVENT_TYPE'] = [int(i) for i in y_pred_train]
df[["EVENT_ID","EVENT_TYPE","EVENT_NAME"]].to_csv("./work/prediction/OCSVM/result_few.txt",index=False,encoding="utf-8",sep="|")
print(df['EVENT_TYPE'].value_counts())
0 5526
1 1474
Name: EVENT_TYPE, dtype: int64
# 全量数据预测 - 使用最佳参数训练模型并得到结果
clf = svm.OneClassSVM(kernel='rbf', nu=search_best_params['nu'], gamma=search_best_params['gamma'])
clf.fit(train_pos_X)
y_pred_train = clf.predict(train_X)
y_pred_train = [0 if i==-1 else 1 for i in y_pred_train]
df['EVENT_TYPE'] = y_pred_train
print(df['EVENT_TYPE'].value_counts())
os.makedirs('./work/prediction/OCSVM',exist_ok=True)
df[["EVENT_ID","EVENT_TYPE","EVENT_NAME"]].to_csv("./work/prediction/OCSVM/result_all.txt",index=False,encoding="utf-8",sep="|")
print(df['EVENT_TYPE'].value_counts())
0 5469
1 1531
Name: EVENT_TYPE, dtype: int64
0 5469
1 1531
Name: EVENT_TYPE, dtype: int64
# # 初始化记录器,visualdl不知道什么原因一直在加载,只能手写一个UMAP 3D观察向量空间中样本的变化
# from visualdl import LogWriter
# with LogWriter(logdir='./log/token_hidi') as writer:
# writer.add_embeddings(tag='test', mat=[i for i in train_X.numpy()], metadata=df['EVENT_TYPE'].astype(str).tolist())
# import numpy as np
# np.save('paddle_trian_x.npy',train_X.numpy())
# np.save('paddle_trian_y.npy',df['EVENT_TYPE'].astype(str).tolist())
paddle自带的visualdl不知道什么原因一直加载不出来,只能手写一个UMAP3D观察向量空间中样本的分布
从图片中看,句向量在空间中的聚类效果很明显
其中蓝色是预测的非食安信息,红色的是食安信息
- 蓝色小团簇,非食安信息,以 转医院-无名氏-疾病类 为代表
- 蓝色大团簇,非食安信息
- 红色团簇,食安信息
总结
规则抽取 和 正则表达式在本次赛题中有这良好的基线分数。
思路上摒弃了常规的人工少量标注 + 文本分类,转而使用带边界的异常检测分类器来做文本分类,希望借助边界的力量,排除非食安信息。
带边界的异常检测分类器非常依赖高质量的正例和文本表示模型,通过提升规则匹配召回正例的精准度,可以提升分类器的分类效果,两者呈现正相关倾向。
换一种角度,这种带边界的异常检测分类器也可以用于判定规则抽取的质量。
作者:Armor
我在AI Studio上获得钻石等级,点亮8个徽章,来互关呀~ https://aistudio.baidu.com/aistudio/personalcenter/thirdview/392748
此文章为搬运
原项目链接
更多推荐
所有评论(0)