记录一下,很久之前看的论文-基于RNN来从微博中检测谣言及其代码复现。

1 引言

现有传统谣言检测模型使用经典的机器学习算法,这些算法利用了根据帖子的内容、用户特征和扩散模式手工制作的各种特征,或者简单地利用使用正则表达式表达的模式来发现推特中的谣言(规则加词典)。

特征工程是至关重要的,但手工特征工程是繁琐复杂、有偏见和耗时费力的。例如,图1中的两个时间序列图描述了典型的谣言信号的浅层模式。虽然它们可以表明谣言和非谣言事件的时间特征(微博文本中关键词的时序变化),但这两种情况之间的差异对于特征工程来说既不明确,也不明显。

  另一方面,深度神经网络在许多机器学习问题上已经显示出了明显的优势。本文利用了循环神经网络RNN来进行有效的谣言检测。RNN适用于处理社交媒体中的文本(retweet)流的序列性质 。这是因为RNN可以捕获谣言传播的动态时序特性。

本文提出基于RNN的方法,将谣言检测视为一个序列分类问题。具体地,本文将社会上下文信息(源微博的转帖文本或相关帖子文本)建模为可变长度的时间序列,然后用RNN来学习捕获微博相关帖子的上下文特征随时间的变化。

2 模型

2.1 问题描述

  • 基于事件的谣言检测(单个微博帖子都很短,上下文非常有限。Claim通常与一些与Claim相关的帖子有关)

  • 事件集E={ E_i }, E_i= { (m_i,j,t_i,j) },事件Ei由时间戳 ti,j内的帖子 mi,j组成

  • 任务是判断每一个Event是谣言还是不是谣言

2.2 数据预处理-构造可变长度时间序列 

  将输入的序列中的post进行划分,从而将处理后的序列长度限定在在一定范围。

可将每个帖子建模作为一个输入实例,并构建一个序列长度等于帖子数的时间序列的用于RNN建模。然而,一个流行的事件可能会有成千上万个的帖子。我们只有一个输出单元(仅适用最终隐状态,有信息瓶颈问题)来指示在每个事件的最后一个时间步长中的类。通过大量的时间步长进行反向传播,而只有一个最后阶段的损失,计算代价高昂且无效的。(处理长序列时,RNN的BPTT存在的梯度消失问题会导致有偏的权重,即离Loss越远的时间步的梯度对参数的贡献越小,从而使其难以建模好长期依赖

  因此,为了妥善处理短时间内密集的帖子序列,本文将一批帖子构成一个时间间隔,并将它作为一个时间序列中的一个输入单元,然后使用RNN进行序列建模。简而言之,就是将原始的帖子序列按相对时间间隔划分成固定长度(例如k个)的子序列,其中子序列中帖子的数量不一定相同。

具体地,给定事件相关帖子的数据集,先将每条帖子视为输入实例,其序列长度等于帖子数量。进一步将帖子按照时间间隔进行批处理,视为时间序列中的单元,然后使用RNN序列进行建模,采用RNN序列的参考长度来构造时间序列。

动态时间序列算法:

    1. 将整个事件线均分为N个internal,形成初始集合U0;
    2. 遍历U0,删除没有包含帖子的internal,形成U1;
    3. 从U1中选出总时间跨度最长的连续internal,形成集合U2(找到一个最长的时间序列)
    4. 如果U2中internal的数量小于N且大于之前一轮,将internal减半,返回步骤1,继续分区(使最终internal数量接近N);
    5. 否则,返回该总时间跨度最长的连续internal集合U2。

       根据上述算法,其实现如下所示(针对常用的微博数据集,其每一个样本的原始信息存储在JSON文件中):

def load_rawdata(file_path):
""" json file, like a list of dict """
with open(file_path, encoding="utf-8") as f:
data = json.loads(f.read())
return data def GetContinueInterval(inter_index):
"""根据初步划分的间隔索引列表,得出最大连续间隔的索引"""
max_inters = []
temp_inters = [inter_index[0]]
for q in range(1, len(inter_index)):
if inter_index[q] - inter_index[q - 1] > 1:
if len(temp_inters) > len(max_inters):
max_inters = temp_inters
temp_inters = [inter_index[q]]
else:
temp_inters.append(inter_index[q]) if len(max_inters) == 0:
max_inters = temp_inters return max_inters def ConstructSeries(tweet_list, interval_num, time_interval):
"""基于相对时间间隔,按照时间戳对post序列进行划分
Params:
tweet_list (list), 由Post Index以及时间戳二元组构成的序列
interval_num (int), 依据基准序列长度N,计算出的当前序列的时间间隔数
time_interval (float), 单位时间间隔长度
Returns:
Output (list), 划分好的post batch,每一个batch包含的一个时间间隔内的post
inter_index (list), Interval的index列表
"""
# 遍历每一个间隔
tweet_index = 0
output, inter_index = [], []
start_time = tweet_list[0][1]
for inter in range(0, interval_num):
non_empty = 0
interval_post = [] # 存储当前间隔内的post
for q in range(tweet_index, len(tweet_list)):
if start_time <= tweet_list[q][1] < start_time + time_interval:
non_empty += 1
interval_post.append(tweet_list[q][0])
elif tweet_list[q][1] >= start_time + time_interval:
# 记录超出interval的tweet位置,下次可直接从此开始
tweet_index = q - 1
break if non_empty == 0:
output.append([]) # 空间隔不会记录其索引
else:
if tweet_list[-1][1] == start_time + time_interval:
interval_post.append(tweet_list[-1][0]) # add the last tweet inter_index.append(inter)
output.append(interval_post)
start_time = start_time + time_interval # 更新间隔开始时间 return output, inter_index

   以下代码为动态时间序列算法主函数,其中N为RNN的参考长度,即超参数:

def SplitSequence(weibo_id, N=50):
"""将source post对应的posts划分成不定长的post batch序列
Params:
weibo_id (str), source post对应的id,用于读取对应数据
N (int), 时间序列的基准time steps个数
Returns:
output (list), interval list, 每一个interval包含一定数量的post index
"""
# 不同时间间隔内的post数量不必相同)
path = "Weibo" + os_sep + "{}.json".format(weibo_id)
data = load_rawdata(data_path + path) # 基于weibo id加载包含转帖文本及时间戳的原始数据
tweet_list = [(idx, tweet["t"]) for idx, tweet in enumerate(data)]
total_timespan = tweet_list[-1][1] - tweet_list[0][1] # L(i)
time_interval = total_timespan / N # l k = 0
pre_max_inters = [] # U_(k_1)
while True:
# Spliting series by the current time interval
k += 1
interval_num = int(total_timespan / time_interval)
output, inter_index = ConstructSeries(tweet_list, interval_num, time_interval)
max_inters = GetContinueInterval(inter_index) # maximum continue interval index
if len(pre_max_inters) < len(max_inters) < N:
time_interval = int(time_interval * 0.5) # Shorten the intervals
pre_max_inters = max_inters
if time_interval == 0:
output = output[max_inters[0]:max_inters[-1] + 1]
break
else:
output = output[max_inters[0]:max_inters[-1] + 1]
break return output

2.3 模型结构(two-layer GRU)

    首先,将每一个post的tf-idf向量和一个词嵌入矩阵相乘,这等价于加权求和词向量。由于本文较老,词嵌入是基于监督信号从头开始学习的,而非使用word2vec或预训练的BERT。

  以下是加载数据的部分的代码。为了便于实现,这里并没有使用torch自带的dataset和dataloader,也没有没有对序列进行截断和填充。

class Data():
def __init__(self, text_data):
self.text_data = text_data def get_wordindices(self):
return [torch.from_numpy(inter_text) for inter_text in self.text_data] def load_data(ids):
""" 依据weibo的id,加载所有的结点特征
Params:
ids (list), 微博id list
Returns:
instance_list: a list of numpy ndarray, 每一个numpy ndarray是一个B by k的tf-idf矩阵
"""
instance_list = []
for weibo_id in tqdm(ids):
text_matrix = load_sptext(weibo_id).toarray() # 所有post的numpy tfidx矩阵
split_interval = SplitSequence(weibo_id)
text_data = [text_matrix[interval] for interval in split_interval]
instance_list.append(Data(text_data))
return instance_list

  模型代码:本文的模型对每一个时间间隔内的post的embedding直接使用了最大池化操作。

class GlobalMaxPool1d(nn.Module):
def __init__(self):
super(GlobalMaxPool1d, self).__init__() def forward(self, x):
return torch.max_pool1d(x, kernel_size=x.shape[2]) class GRU2_origin(nn.Module):
def __init__(self, dim_in, dim_word, dim_hid, dim_out):
"""
Detecting Rumors with Recurrent Neural Network-IJCAI16 :Params:
dim_in (int): post的初始输入特征维度 k
dim_word(int): word嵌入的维度
dim_hid (int): GRU hidden unit
dim_out (int): 模型最终的输出维度,用于分类
"""
super(GRU2_origin, self).__init__()
self.word_embeddings = nn.Parameter(nn.init.xavier_uniform_(
torch.zeros(dim_in, dim_word, dtype=torch.float, device=device), gain=np.sqrt(2.0)), requires_grad=True) # GRU for modeling the temporal dynamics
rnn_num_layers = 2
self.MaxPooling = GlobalMaxPool1d()
self.GRU = nn.GRU(dim_word, dim_hid, rnn_num_layers)
self.H0 = torch.zeros(rnn_num_layers, 1, dim_hid, device=device)
self.prediction_layer = nn.Linear(dim_hid, dim_out)
nn.init.xavier_normal_(self.prediction_layer.weight) def forward(self, text_data):
batch_posts = []
for idx in range(len(text_data)):
# words_indices is a sparse tf-idf vector with N * 5000 dimension
words_indices = text_data[idx].to(device)
tmp_posts = []
for i in range(words_indices.shape[0]):
word_indice = torch.nonzero(words_indices[i], as_tuple=True)[0]
if word_indice.shape[0] == 0:
word_indice = torch.tensor([0], dtype=torch.long).to(device) words = self.word_embeddings.index_select(0, word_indice) # select out embeddings
word_tensor = words_indices[i][word_indice].unsqueeze(dim=0) # select out weights
post_embedding = word_tensor.mm(words).squeeze(dim=1)
tmp_posts.append(post_embedding) # Interval中的post batch取平均 (矩阵乘法)
tmp_embeddings = torch.cat(tmp_posts, dim=0).unsqueeze(1)
batch_embedding = self.MaxPooling(tmp_embeddings.transpose(0, 2)) # transpose(0, 2)
batch_posts.append(batch_embedding.squeeze(1).transpose(0, 1)) x = torch.cat(batch_posts, dim=0)
gru_output, _ = self.GRU(x.unsqueeze(1), self.H0)
return self.prediction_layer(gru_output[-1]) # Using the last hidden vector of GRU

  后续的完整的数据加载、模型初始化、训练和评估,可自行添加。

3 实验

  模型训练设置:

  1. 使用TF-IDF来获取post的初始文本表示
  2. AdaGrad算法进行参数更新
  3. 根据经验,将词汇量大小为k=5000,待从头学的词嵌入维度为100,隐藏单元的尺寸为100,学习率为0.5

  实验结果:

4 总结

  这篇文章算是将深度学习用于虚假信息检测的开山之作,开始了利用深度网络来自动提取具备判别性的高阶特征的范式,后续很多文章都是在此基础上改进的。

  由于文章较老,所以在目前看,待改进的点其实挺多的。首先要注意,原始的TF-IDF特征一般不能在全局数据上提取(训练集、验证集和测试集,暂不考虑半监督的情况),相同的词的在验证集和测试集的TF-IDF特征和训练集取同样的值。而对于新出现的词,取默认值。推广到一般情况,如果提取特征时,不区分训练测试,或许使用了相应特征的对比方法取得的结果过于乐观,并不符合实际情况。

  此外,可以考虑文本特征的获取、序列的层次化建模、注意力机制、其他特征的使用(用户信息、传播结构特征)、外部知识的引入(知识图谱)、非线性传播结构的利用、多任务学习(结合立场分类)等等。

  值得注意的是,当算法实际应用时,并不是越复杂的模型的效果就越好,而且需要考虑实际的业务需求和数据。有时候,或许假设简单、模型结构简单的算法或许在大量人工特征的引入和大量数据的支持下,也能取得不错的效果,毕竟数据决定算法的上限。

Detecting Rumors from Microblogs with Recurrent Neural Networks(IJCAI-16)的更多相关文章

  1. Recurrent Neural Network(递归神经网络)

    递归神经网络(RNN),是两种人工神经网络的总称,一种是时间递归神经网络(recurrent neural network),另一种是结构递归神经网络(recursive neural network ...

  2. AntisymmetricRNN: A Dynamical System View on Recurrent Neural Networks(解析)

    原文链接:https://arxiv.org/abs/1902.09689 发表在:ICLR 2019 ------------------------------------------------ ...

  3. 循环神经网络(RNN, Recurrent Neural Networks)介绍(转载)

    循环神经网络(RNN, Recurrent Neural Networks)介绍    这篇文章很多内容是参考:http://www.wildml.com/2015/09/recurrent-neur ...

  4. Attention and Augmented Recurrent Neural Networks

    Attention and Augmented Recurrent Neural Networks CHRIS OLAHGoogle Brain SHAN CARTERGoogle Brain Sep ...

  5. cs231n spring 2017 lecture10 Recurrent Neural Networks 听课笔记

    (没太听明白,下次重新听一遍) 1. Recurrent Neural Networks

  6. 第十四章——循环神经网络(Recurrent Neural Networks)(第一部分)

    由于本章过长,分为两个部分,这是第一部分. 这几年提到RNN,一般指Recurrent Neural Networks,至于翻译成循环神经网络还是递归神经网络都可以.wiki上面把Recurrent ...

  7. 第十四章——循环神经网络(Recurrent Neural Networks)(第二部分)

    本章共两部分,这是第二部分: 第十四章--循环神经网络(Recurrent Neural Networks)(第一部分) 第十四章--循环神经网络(Recurrent Neural Networks) ...

  8. Pixel Recurrent Neural Networks翻译

    Pixel Recurrent Neural Networks 目前主要在用的文档存放: https://www.yuque.com/lart/papers/prnn github存档: https: ...

  9. 循环神经网络(Recurrent Neural Networks, RNN)介绍

    目录 1 什么是RNNs 2 RNNs能干什么 2.1 语言模型与文本生成Language Modeling and Generating Text 2.2 机器翻译Machine Translati ...

随机推荐

  1. 排序算法详解(java代码实现)

    ​ 排序算法大致分为内部排序和外部排序两种 内部排序:待排序的记录全部放到内存中进行排序,时间复杂度也就等于比较的次数 外部排序:数据量很大,内存无法容纳,需要对外存进行访问再排序,把若干段数据一次读 ...

  2. gcc和g++是什么,有什么区别?

    点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! 素材来源:C语言中文网 编辑整理:strongerHua ...

  3. 详解六大国产CPU处理器

    一个执着于技术的公众号 CPU作为计算机设备的运算和控制核心,负责指令读取.译码与执行,因研发门槛高.生态构建难,被认为是集成电路产业中的"珠穆朗玛峰". 纵观全球,Intel.A ...

  4. Java SPI 和 API,傻傻分不清?

    最近新写了一个中间件「运行时动态日志等级开关」,其中使用Java SPI机制实现了自定义配置中心,保证良好的扩展性. 项目地址,走过路过可以点个star :)https://github.com/sa ...

  5. Spring Ioc源码分析系列--前言

    Spring Ioc源码分析系列--前言 为什么要写这个系列文章 首先这是我个人很久之前的一个计划,拖了很久没有实施,现在算是填坑了.其次,作为一个Java开发者,Spring是绕不开的课题.在Spr ...

  6. MySQL字段约束条件、字段类型、存储引擎、配置文件

    字符编码与配置文件 # 查看MySQL默认字符编码 \s ''' 如果是5.X系列 显示的编码有很多种 Latin1 gbk 如果是8.X系列 显示的统一是utf8mb4 utf8mb4是utf8优化 ...

  7. .NET性能优化-使用ValueStringBuilder拼接字符串

    前言 这一次要和大家分享的一个Tips是在字符串拼接场景使用的,我们经常会遇到有很多短小的字符串需要拼接的场景,在这种场景下及其的不推荐使用String.Concat也就是使用+=运算符. 目前来说官 ...

  8. SSO 方案演进

    背景介绍 随着业务与技术的发展,现今比以往任何时候都更需要单点登录 SSO 身份验证. 现在几乎每个网站都需要某种形式的身份验证才能访问其功能和内容. 随着网站和服务数量的增加,集中登录系统已成为一种 ...

  9. 【拖拽可视化大屏】全流程讲解用python的pyecharts库实现拖拽可视化大屏的背后原理,简单粗暴!

    "整篇文章较长,干货很多!建议收藏后,分章节阅读." 一.设计方案 整体设计方案思维导图: 整篇文章,也将按照这个结构来讲解. 若有重点关注部分,可点击章节目录直接跳转! 二.项目 ...

  10. 力扣算法JS LC 59-螺旋矩阵2,LC 152-乘积最大子数组

    LC 59-螺旋矩阵2 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix . 示例 1: 输入:n = 3输出:[[1,2 ...