十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于注意力机制与图卷积神经网络的多任务谣言检测实践

基于注意力机制与图卷积神经网络的多任务谣言检测实践 简介基于注意力机制和图卷积神经网络的多任务谣言检测源码与数据集面向深度学习、自然语言处理方向的学生及研究者尤其适合作为毕业设计或期末大作业的参考实现也可用于相关课题的基线对比。这套实现源自2022届本科生毕业论文的后续优化在原始基线模型上改用BERT编码器获取词向量与句向量并接入双向图卷积网络或自注意力机制分别完成谣言检测与立场分类任务同时重构了数据集存储方式使目录更规整直观。压缩包共75个文件包含Python脚本、JSON数据文件、Jupyter Notebook示例、文本说明及Markdown文档等整体约18.67MB既能直接运行训练与评估也便于二次开发。目前已有364人学习下载对理解多任务谣言检测的模型搭建与数据预处理具有实际参考价值。1. 警惕“词在传播模型在睡觉”的谣言检测陷阱谣言检测跑通不难但跑出能写进论文的数字是另一回事。这份基于注意力机制和图卷积神经网络的多任务谣言检测源码包来自一篇2022届本科毕业论文的后续优化原论文的baseline跑在master分支主分支换成了BERT编码器再接BiGCN或Self-Attention同一套数据同时做谣言检测和立场分类两个任务。仓库里Python源码、PHEME和SemEval2017-Task8数据集、训练脚本、可视化notebook都是齐的不用自己凑数据。适合两类人一是在复现论文、想找一份能直接跑的图神经网络基线二是做多任务学习课程设计想参考真实训练流程而不是demo玩具。下面几章按数据、模型、训练、部署的顺序把MSA-BiGCN这条线完整拆一遍。2. MSA-BiGCN与BERT-BiGCN注意力机制和图卷积的两条实现路径打开仓库根目录最先注意到的是两套并行的实现MSABiGCN.py含ABGCN.py和BertBiGCN.py含BiGCN.py。它们共享utils.py里的数据读取和batch组织逻辑但模型结构差异很大理解这个差异是后续改代码的前提。2.1 BERT作为编码器词向量和句向量的来源原论文用的是GloVe或Word2Vec静态词向量主分支改成BERT后输入从input_idsattention_mask进入得到两个关键张量每个token的词向量序列last_hidden_state和整句话的句向量pooler_output。在rumorDataset.py里能看到数据集的每条记录已经提前用BERT的tokenizer切好max_len默认设为128超过直接截断不足用[PAD]补齐。# utils.py 中关于BERT输入的典型处理 def bert_encode(texts, tokenizer, max_len128): return tokenizer( texts, paddingTrue, truncationTrue, max_lengthmax_len, return_tensorspt, )逻辑说明paddingTrue保证一个batch内的样本长度一致truncationTrue把超长推文截到128。谣言文本大多在20~40个词128足够覆盖绝大多数情况如果做的是中文谣言数据建议把max_len提到256因为中文字符的信息密度低于英文单词。参数说明max_len决定GPU显存占用和训练速度。显存不够时先减batch size不要轻易减max_len因为截断会直接砍掉谣言事件里“转发链末端”的证据信息。选择BERT而不是继续用GloVe核心原因是谣言检测对上下文语义敏感——同一条推文前面加“据媒体报道”和加“我听说”立场完全相反静态向量捕捉不到这种差异。BERT的12层Transformer把每个词都放到整句话的上下文里重新编码词向量本身已经带了句法角色信息后续图卷积做邻居聚合时输入特征质量直接决定传播结构能被利用到什么程度。2.2 图卷积层BiGCN是怎么建图的BiGCN的核心思路是把一条谣言事件的所有相关推文看成节点节点之间的三类关系建边转发关系谁转发了谁、回复关系谁回复了谁、相似关系文本语义相似的推文互为邻居。图建好后每个节点的初始特征就是BERT输出的句向量然后过两层GCN每层都做邻居特征聚合。# BiGCN.py 中图卷积层的关键片段 class BiGCN(nn.Module): def __init__(self, in_dim, hidden_dim, out_dim, dropout0.5): super(BiGCN, self).__init__() self.conv1 GraphConv(in_dim, hidden_dim) self.conv2 GraphConv(hidden_dim, out_dim) self.dropout nn.Dropout(dropout) def forward(self, x, adj): x self.conv1(x, adj) x F.relu(x) x self.dropout(x) x self.conv2(x, adj) return x逻辑说明第一层conv1把BERT句向量通常是768维投影到隐藏层代码里hidden_dim默认128经过ReLU激活和Dropout后第二层conv2再投影到类别数谣言检测2类立场分类3类。adj是从数据集构建的邻接矩阵行归一化在GraphConv内部完成。参数说明dropout0.5是图网络里偏高的设置因为谣言事件图通常很小一个事件几十到几百个节点节点少意味着容易过拟合Dropout是成本最低的正则手段。如果你的场景是大型社交网络图可以把dropout降到0.3否则信息传递被断掉太多。2.3 注意力机制加在哪MSA-BiGCN的多头自注意力位置MSA-BiGCN不是简单在GCN后面接一个注意力层而是把多头自注意力放在BERT和图卷积之间。先用BERT得到句向量再过一层TransformerEncoderLayer做序列内的自注意力让模型在进入图结构之前先完成事件内部“跨推文”的语义交互。提示如果只听到“注意力机制”就默认是CBAM或SE那种通道注意力方向就偏了。这里做的是NLP里的Self-Attention作用对象是推文序列不是图像特征图。从MSABiGCN.py的实现看多头注意力头数num_heads默认是8和BERT的默认头数一致。这样设计的好处是图卷积只负责建模传播结构注意力只负责建模文本交互两个模块的职责边界清晰。改代码时如果想加大注意力容量优先加num_layersTransformer层数而不是num_heads因为后者超过16后收益很小显存却涨得很快。3. PHEME与SemEval2017从零构造Dataloader的数据整理思路仓库里datasets目录分三块PHEME谣言检测经典基准、PHEME-stance立场标注版本、semeval2017-task8SemEval2017的官方任务数据。旧repo把所有信息塞进一个JSON文件维护起来痛苦这次改成每个事件一个文件夹事件下的每条推文一个单独文件标注信息用字段名直接可读。3.1 目录结构切换从嵌套JSON到扁平化存储以PHEME为例典型目录长这样datasets/PHEME/ charliehebdo/ source-tweet.txt tweet_001/ tweet.json reply_001.json reply_002.json sydneysiege/ source-tweet.txt tweet_002/ tweet.jsonrumorDataset.py里的load_event函数负责遍历这个目录把每条推文的正文、转发/回复关系、标注结果抽出来组装成后续训练用的event_list。每个事件由一个source_tweet和若干replies组成二者构成图结构中的边。# rumorDataset.py 中解析PHEME目录的简化逻辑 def parse_pheme_event(event_path): source_tweet load_json(os.path.join(event_path, source-tweet.txt)) replies [] for sub_dir in os.listdir(event_path): if sub_dir.startswith(tweet_): tweet_data load_json(os.path.join(event_path, sub_dir, tweet.json)) replies.append(tweet_data) return {source: source_tweet, replies: replies, label: source_tweet[label]}逻辑说明label字段存在source-tweet的元数据里代表这个事件是谣言还是非谣言replies里的每条推文都有独立的in_reply_to字段用来重建传播树的父子关系。参数说明这里有个容易被忽略的点——tweet.json里存的是原始推文结构包含user_id、timestamp等噪声字段。第一次跑通前不建议清理这些字段因为后续如果要做用户特征比如粉丝数、注册时长还要回到这里取数据。3.2 构建邻接矩阵三种图关系的取舍在data.py里能看到build_graph函数它返回的adj矩阵是常规的numpy.ndarray形状为[num_nodes, num_nodes]。三种关系对应三种建边规则关系类型建边条件邻接矩阵权重转发边tweet.retweeted_status_id source_id1回复边tweet.in_reply_to_status_id target_id1相似边句向量余弦相似度 0.8相似度值相似边的加入是BiGCN能超过普通GCN的关键。BERT句向量本身已经编码了语义相似边把“说同一件事但没直接互动”的节点连起来相当于给图卷积提供了“软关系”通道。PHEME数据集平均每个事件只有约30条推文靠转发和回复建出的图非常稀疏不加相似边图卷积退化成只更新孤立节点等于白跑。3.3 多任务标签的对齐问题谣言检测的标签是二分类谣言/非谣言立场分类的标签是三分类支持/否定/观望两套标签存在同一批数据上。trainOnlyStance.py和trainOnlyRumor.py的存在就是为了先单独验证每个任务能不能收敛再合到一起训练。合训之前要确认两个任务的dataloader返回的batch里label_rumor和label_stance都能取到值缺一个就报KeyError。注意PHEME-stance和semeval2017-task8的立场标注口径不完全一致前者每一条回复都有立场标签后者只有部分子集有。用load_dataset.py切分数据时先打印label_distribution()统计一下类别数量防止某个类只有个位数样本导致训练直接崩。4. 多任务训练的梯度流动与损失函数设置在第二和第三章里我们把数据和模型都理清了接下来是最容易出问题的一步训练。train.py是整个仓库的主入口它把BERT微调、图卷积前向传播、两个损失函数反向传播串在一条链路里。多任务不等于多模型各训各的这里的核心机制是共享BERT主干分叉两个输出头。4.1 损失函数组合CrossEntropyLoss的加权策略# train.py 中多任务损失计算的核心逻辑 loss_rumor F.cross_entropy(logits_rumor, label_rumor) loss_stance F.cross_entropy(logits_stance, label_stance) loss_total alpha * loss_rumor (1 - alpha) * loss_stance loss_total.backward()逻辑说明alpha默认取0.6即谣言检测的损失权重更高。原因很简单——谣言检测是主任务立场分类是辅助任务辅助任务的作用是给BERT提供更多语义监督信号让句向量更快收敛到“有立场区分度”的表示空间而不是喧宾夺主。参数说明当你发现立场分类的accuracy很高但谣言检测的F1上不去时把alpha往0.7~0.8调反过来如果谣言检测很快过拟合训练集F195但验证集不到70把alpha降到0.5让立场任务充当正则项。4.2 优化器与学习率BERT微调的边界条件BERT部分的参数学习率不能和GCN部分一样大。仓库里的做法是给BERT单独设一个小的学习率# train.py 中分组设置学习率的典型写法 optimizer torch.optim.AdamW([ {params: bert.parameters(), lr: 2e-5}, {params: gcn.parameters(), lr: 1e-3}, {params: classifier.parameters(), lr: 1e-3}, ], weight_decay1e-5)逻辑说明BERT的预训练权重已经很好用2e-5的learning rate只做微调防止剧烈更新把预训练学到的语言知识冲掉而GCN和最后的分类头是从零训练需要更大学习率才能快速收敛。AdamW比Adam多了权重衰减的解耦对BERT这类大模型更稳。参数说明weight_decay1e-5是常规值不需要经常动。如果训练过程Loss曲线出现锯齿状震荡把GCN的学习率降到5e-4多半能缓解。Batch size设在32时SemEval2017训练集跑一个epoch大约3~5分钟单张V100总共20个epoch左右能收敛。4.2.1 训练过程中的Checkpoint策略getbash.py里已经写好了一段时间间隔保存一次model.pt的命令。建议保留这个逻辑因为多任务训练最怕训到第10个epoch才开始发散没有checkpoint就得重跑。保存时只存state_dict不要直接存整个model对象前者换环境加载更省事。4.2.2 常用实验参数速查回到训练本身这套代码里我比较常用的一组参数是参数默认值推荐调整区间调整依据batch_size3216~64显存不足优先减到16max_len128128~256中文数据调到256alpha0.60.5~0.8主任务收敛慢就调大dropout0.50.3~0.6小数据集调大num_heads84~16显存充足可试16这套参数在PHEME上跑出来的结果谣言检测F1通常在0.82~0.88之间浮动对比只用GCN不加注意力的baseline高出3到5个点差距主要来自BERT替换静态词向量带来的语义增益。4.3 多任务梯度冲突的观察方法训练时在train.py的backward之后手动打印两个任务的gradient norm能直观看到冲突如果loss_rumor的梯度范数一直比loss_stance大一个数量级说明主任务主导了共享层的更新辅助任务基本没起作用。处理办法是梯度裁剪和动态调整alpha。# 打印梯度范数的调试代码 total_norm_rumor 0.0 for p in model.bert.parameters(): if p.grad is not None: total_norm_rumor p.grad.norm().item() ** 2 print(fRumor grad norm: {total_norm_rumor ** 0.5:.4f})逻辑说明这段话挂在loss_total.backward()和optimizer.step()之间每个epoch打印一次。如果值一直往上涨说明训练不稳定配合clip_grad_norm_(model.parameters(), max_norm1.0)做全局梯度裁剪即可。5. 从trainOnly到完整训练如何区分是代码问题还是模型问题仓库里trainOnlyStance.py和trainOnlyRumor.py这两个文件一般不太会被当成核心模块但调试的时候它们比train.py有用得多。多任务训练失败的排查顺序永远先跑单任务再跑多任务。5.1 实验一只用立场分类分支# 运行单任务立场分类 python trainOnlyStance.py --only-stance --epochs 10跑通后观察验证集accuracy。SemEval2017的立场分类理论上在0.65以上算正常三类随机猜是0.33。如果训练5个epoch还停在0.4问题不出在模型结构而是标签对齐出错。5.2 实验二只用谣言检测分支# 运行单任务谣言检测 python trainOnlyRumor.py --only-rumor --epochs 10谣言检测二分类随机基线是0.5。PHEME上跑到0.8以上是及格线。如果单任务正常、多任务反而掉点再回train.py里调alpha。5.3 单任务与多任务的三个关键结论第一两个单任务都能收敛是多任务训练的前提任何一个单任务Loss不降多任务一定更差。第二多任务的最优epoch数往往比单任务长因为两个任务交替收敛Loss曲线会出现平台期不要在第5个epoch看到Loss不降就early stop再等3个epoch观察。第三立场分类的收敛速度普遍快于谣言检测分类头更简单、类别更分散如果谣言检测的F1开始下降但立场分类还在涨把alpha往0.7调强行压住立场任务的梯度贡献。这些结论在不同随机种子下跑过多次结果一致。验证方法也简单固定随机种子跑三次取平均如果标准差小于0.5个F1点说明实验设置可信。6. 部署到推理把训练好的模型接到新的推文流上训练完不是终点.ipynb里那些result和test文件才是把模型变成能用的工具的最后一公里。这里的部署不是指上线服务而是把model.pt加载回来对一条新推文或一个新事件做谣言预测并和真实标注对比。6.1 可复用的推理流程模板# 推理脚本 inference.py 的关键流程 def predict_event(event_texts, model, tokenizer, devicecpu): # 1. 用BERT tokenizer编码事件内所有推文 inputs tokenizer( event_texts, paddingTrue, truncationTrue, max_length128, return_tensorspt ).to(device) # 2. 构建事件图邻接矩阵这里用简化版只建相似边 adj build_similarity_graph(inputs[input_ids]) # 3. 模型前向推理 with torch.no_grad(): logits_rumor, logits_stance model(inputs, adj) rumor_prob torch.softmax(logits_rumor, dim-1) return rumor_prob.argmax(dim-1).item(), rumor_prob.max().item()代码说明build_similarity_graph里用BERT输出句向量算余弦相似度大于0.8的节点之间建边。推理阶段没有转发关系数据时只用相似边也能给出基本结果代价是F1会比完整图低2~3个百分点。6.2 部署时容易踩的坑注意加载model.pt时必须用和训练时完全相同的model_class MSABiGCN()或BertBiGCN()实例化再load_state_dict。直接torch.load(model.pt)在跨Python版本或跨PyTorch小版本时大概率报_pickle.UnpicklingError。建议保存时多存一个model_config.json记录模型参数hidden_dim、num_heads、max_len加载时先读配置再建模型这是最不踩坑的做法。最后一步验证用result.ipynb里的classification_report函数输出精确率、召回率、F1关注谣言类label1的召回率。二分类场景里模型倾向于把所有样本都判为非谣言来获得高准确率但召回率会掉到0.5以下。如果出现这种情况在推理时把rumor类的阈值从默认的0.5降到0.4通常是保召回最直接的手段。本文还有配套的精品资源点击获取
返回列表