RumorLens是发表于CHIEA'22的交互式可视化分析系统,旨在助力社交媒体管理员高效分析验证谣言。其整合NLP与可视化技术,通过三个层次视图呈现信息:概述显示谣言时空分布,投影视图体现特征与相似性,传播视图以圆形设计展示动态传播细节,还介绍了数据处理及视图设计思路。
☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜
![[数据可视化]rumorlens: 社交媒体谣言的互动分析和验证 - php中文网](https://img.php.cn/upload/article/202507/28/2025072810014815292.jpg)
RumorLens 论文分享,发表于CHIEA'22(CCF-A)
论文网址
随着社会化媒体的发展,各种谣言很容易在互联网上传播,对社会造成严重的负面影响。因此,处理可疑谣言已经成为社交媒体平台的关键任务。但是,由于缺乏有效的工具,平台管理员往往难以有效地分析和验证来自社交媒体平台上大量信息的谣言。我们与社交媒体平台管理员紧密合作了四个月,总结了他们对谣言识别和分析的要求,并进一步提出了一个交互式可视化分析系统RumorLens,帮助他们高效应对谣言,深入了解谣言传播模式。RumorLens将自然语言处理(NLP)和其他数据处理技术与可视化技术相结合,以促进对可疑谣言的交互式分析和验证。
我们提出了协调良好的可视化方法,为用户提供可疑谣言的三个层次的细节:概述显示可疑谣言的空间分布和时间演化;投影视图利用基于隐喻的图示符来表示每个可疑谣言,并进一步使用户能够快速了解它们的总体特征和彼此之间的相似性;传播视图通过新颖的圆形可视化设计可视化可疑谣言的动态传播细节,并促进谣言的交互式分析和验证。
社交媒体在我们的日常生活中得到了广泛的应用,使得信息共享和交流变得非常方便。但是,它也提供了一种简单快捷的方法来产生和传播各种谣言。社交媒体服务提供商在过去几年中一直试图识别社交媒体平台上流传的谣言。
传统的谣言识别方法依赖于内容专家的个人经验,但处理海量信息却相当耗时费力。自动识别谣言的方法比手工方法更有效,但是大多数现有的自动化方法无法保证准确度,在实际应用中往往不够稳健。与这些自动方法相比,人类的经验可以提供更丰富的输出,包括决策的原因。
但是对于社交媒体管理者而言,他们更需要从内容、用户、话题、传播等方面深入了解可疑谣言的特点,使谣言验证更加扎实、可信。其中,十分重要的需求是追踪社交媒体上可疑谣言的动态传播细节。
为此,我们提出了一个交互式可视化分析系统RumorLens,帮助社交媒体平台的管理者有效地分析和验证可疑谣言,并深入洞察谣言的传播。我们开发了协调良好的可视化视图,为用户提供不同层次的可疑谣言细节:
Figure1: RumorLens,一个多层次的可视化分析系统,帮助用户以交互方式分析和验证社交媒体上的可疑谣言。A) 位置分布视图提供可疑谣言的空间分布摘要;B) 话题演化视图显示了不同话题的可疑谣言随着时间的推移而发生的变化;C) 特征投影图揭示了可疑谣言的总体特征和相互之间的相似性;D) 传播视图采用新颖的圆形可视化设计,可视化可疑谣言的动态传播细节;E) 帖子详细信息视图显示用户信息和帖子内容的详细信息。
新浪社区管理中心是负责收集、记录和处理新浪微博上发布的可疑谣言等非法信息的在线平台。据新浪微博报道,每年都有数以万计的各领域谣言需要及时处理,否则可能对公众和政府造成严重影响。我们从中收集了2019年12月27日至2020年12月14日期间的可疑谣言数据集。
可疑谣言的用户、内容、话题和传播等多个特性对于谣言分析和验证非常重要。因此,在推特原始数据的基础上,分别采用TF-IDF、情绪识别、话题分类、影响力计算进一步提取可疑谣言的关键词、情绪、话题和影响力。特别地,我们采用t-SNE来减少可疑谣言的维度并投影到2D平面图上以显示它们之间的关系。
import paddleimport paddlenlp as ppnlpfrom paddlenlp.data import Stack, Pad, Tupleimport paddle.nn.functional as Fimport numpy as npimport reimport randomimport pandas as pdfrom functools import partial # partial()函数可以用来固定某些参数值,并返回一个新的callable对象to_label = {'社会时事': '0', '母婴育儿': '1', '历史文化': '2', '常识': '3', '国际': '4', '军事': '5', '教育': '6', '娱乐': '7', '科技': '8', '情感': '9'}
content = pd.read_csv("final_all_texts_nonull.csv")["content"].values.tolist()
strlabel = pd.read_csv("final_all_texts_topic_withprob_merge_nonull.csv")["topic"].values.tolist()
int_label = []for i in range(len(content)):
content[i] = re.sub(u"\【.*?\】|\{.*?\}|\<.*?\>|\#.*?\#", "", content[i]).replace(" ", "")
int_label.append(to_label[strlabel[i]])
all_data = []for i in range(len(content)):
all_data.append([content[i], int_label[i]])
count = {'0': [0, []], '1': [0, []], '2': [0, []], '3': [0, []], '4': [0, []], '5': [0, []], '6': [0, []], '7': [0, []], '8': [0, []], '9': [0, []]}for i in range(len(int_label)):
count[int_label[i]][0] += 1
count[int_label[i]][1].append(i)
train = []
dev = []for i in count:
trainlen = count[i][0] // 10 * 7
for j in range(trainlen):
train.append(all_data[count[i][1][j]]) for j in range(count[i][0] - trainlen):
dev.append(all_data[count[i][1][j + trainlen]])
random.shuffle(train)
random.shuffle(dev)
trainls, devls = train[:], dev[:]
testlst = [] # 没有测试数据class SelfDefinedDataset(paddle.io.Dataset):
def __init__(self, data):
super(SelfDefinedDataset, self).__init__()
self.data = data def __getitem__(self, idx):
return self.data[idx] def __len__(self):
return len(self.data) def get_labels(self):
return ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
train_ds, dev_ds, test_ds = SelfDefinedDataset.get_datasets([trainls, devls, testlst])
label_list = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]print("训练集样本个数:{}".format(len(train_ds)))print("验证集样本个数:{}".format(len(dev_ds)))print("测试集样本个数:{}".format(len(test_ds)))
tokenizer = ppnlp.transformers.BertTokenizer.from_pretrained("bert-base-chinese")def convert_example(example, tokenizer, label_list, max_seq_length=256, is_test=False):
if is_test:
text = example else:
text, label = example # tokenizer.encode方法能够完成切分token,映射token ID以及拼接特殊token
encoded_inputs = tokenizer.encode(text=text, max_seq_len=max_seq_length)
input_ids = encoded_inputs["input_ids"]
segment_ids = encoded_inputs["segment_ids"] if not is_test:
label_map = {} for (i, l) in enumerate(label_list):
label_map[l] = i
label = label_map[label]
label = np.array([label], dtype="int64") return input_ids, segment_ids, label else: return input_ids, segment_ids# 数据迭代器构造方法def create_dataloader(dataset, trans_fn=None, mode='train', batch_size=1, use_gpu=True, pad_token_id=0,
batchify_fn=None):
if trans_fn:
dataset = dataset.apply(trans_fn, lazy=True) if mode == 'train' and use_gpu:
sampler = paddle.io.DistributedBatchSampler(dataset=dataset, batch_size=batch_size, shuffle=True) else:
shuffle = True if mode == 'train' else False # 如果不是训练集,则不打乱顺序
sampler = paddle.io.BatchSampler(dataset=dataset, batch_size=batch_size, shuffle=shuffle) # 生成一个取样器
dataloader = paddle.io.DataLoader(dataset, batch_sampler=sampler, return_list=True, collate_fn=batchify_fn) return dataloader# 使用partial()来固定convert_example函数的tokenizer, label_list, max_seq_length, is_test等参数值trans_fn = partial(convert_example, tokenizer=tokenizer, label_list=label_list, max_seq_length=128, is_test=False)
batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id),
Pad(axis=0, pad_val=tokenizer.pad_token_id),
Stack(dtype="int64")): [data for data in fn(samples)]# 训练集迭代器train_loader = create_dataloader(train_ds, mode='train', batch_size=64, batchify_fn=batchify_fn, trans_fn=trans_fn)# #验证集迭代器dev_loader = create_dataloader(dev_ds, mode='dev', batch_size=64, batchify_fn=batchify_fn, trans_fn=trans_fn)
model = ppnlp.transformers.BertForSequenceClassification.from_pretrained("bert-base-chinese", num_classes=10)#学习率learning_rate = 1e-5#训练轮次epochs = 100#学习率预热比率warmup_proption = 0.1#权重衰减系数weight_decay = 0.01num_training_steps = len(train_loader) * epochs
num_warmup_steps = int(warmup_proption * num_training_steps)def get_lr_factor(current_step):
if current_step < num_warmup_steps: return float(current_step) / float(max(1, num_warmup_steps)) else: return max(0.0, float(num_training_steps - current_step) / float(max(1, num_training_steps - num_warmup_steps)))#学习率调度器lr_scheduler = paddle.optimizer.lr.LambdaDecay(learning_rate, lr_lambda=lambda current_step: get_lr_factor(current_step))#优化器optimizer = paddle.optimizer.AdamW(
learning_rate=lr_scheduler,
parameters=model.parameters(),
weight_decay=weight_decay,
apply_decay_param_fun=lambda x: x in [
p.name for n, p in model.named_parameters() if not any(nd in n for nd in ["bias", "norm"])
])#损失函数criterion = paddle.nn.loss.CrossEntropyLoss()#评估函数metric = paddle.metric.Accuracy()#评估函数def evaluate(model, criterion, metric, data_loader):
model.eval()
metric.reset()
losses = [] for batch in data_loader:
input_ids, segment_ids, labels = batch
logits = model(input_ids, segment_ids)
loss = criterion(logits, labels)
losses.append(loss.numpy())
correct = metric.compute(logits, labels)
metric.update(correct)
accu = metric.accumulate() print("eval loss: %.5f, accu: %.5f" % (np.mean(losses), accu))
model.train()
metric.reset()#开始训练global_step = 0for epoch in range(1, epochs + 1): for step, batch in enumerate(train_loader, start=1): #从训练数据迭代器中取数据
input_ids, segment_ids, labels = batch
logits = model(input_ids, segment_ids)
loss = criterion(logits, labels) #计算损失
probs = F.softmax(logits, axis=1)
correct = metric.compute(probs, labels)
metric.update(correct)
acc = metric.accumulate()
global_step += 1
if global_step % 50 == 0 : print("global step %d, epoch: %d, batch: %d, loss: %.5f, acc: %.5f" % (global_step, epoch, step, loss, acc))
loss.backward()
optimizer.step()
lr_scheduler.step()
optimizer.clear_gradients()
evaluate(model, criterion, metric, dev_loader)
model.save_pretrained("model")
tokenizer.save_pretrained("tokenzier")def predict(model, data, tokenizer, label_map, batch_size=1):
examples = [] for text in data:
input_ids, segment_ids = convert_example(text, tokenizer, label_list=label_map.values(), max_seq_length=128, is_test=True)
examples.append((input_ids, segment_ids))
batchify_fn = lambda samples, fn=Tuple(Pad(axis=0, pad_val=tokenizer.pad_token_id), Pad(axis=0, pad_val=tokenizer.pad_token_id)): fn(samples)
batches = []
one_batch = [] for example in examples:
one_batch.append(example) if len(one_batch) == batch_size:
batches.append(one_batch)
one_batch = [] if one_batch:
batches.append(one_batch)
results = []
model.eval() for batch in batches:
input_ids, segment_ids = batchify_fn(batch)
input_ids = paddle.to_tensor(input_ids)
segment_ids = paddle.to_tensor(segment_ids)
logits = model(input_ids, segment_ids)
probs = F.softmax(logits, axis=1)
idx = paddle.argmax(probs, axis=1).numpy()
idx = idx.tolist()
labels = [label_map[i] for i in idx]
results.extend(labels) return results
data = [i[0] for i in dev]
label_map = {0: '0', 1: '1', 2:"2", 3:"3", 4:"4", 5:"5", 6:"6", 7:"7", 8:"8", 9:"9"}
predictions = predict(model, data, tokenizer, label_map, batch_size=32)from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerimport jiebaimport reimport jieba.analysedef participle(sentence):
stopwords = [line.strip() for line in open("cn_stopwords.txt", encoding='UTF-8').readlines()] for j in range(len(sentence)):
sentence[j] = re.sub(r"</?(.+?)>| | |
", "", sentence[j])
sentence[j] = re.sub(r"
", "", sentence[j])
sentence[j] = re.sub('[a-zA-Z0-9]', "", sentence[j])
sentence[j] = re.sub( "[
]"
, "", sentence[j])# seg = jieba.lcut(sentence[j], cut_all=False)# print(sentence[j])
seg = jieba.analyse.textrank(sentence[j], topK=None, withWeight=False, allowPOS=('ns', 'n', 'vn', 'nt', 'nz'), withFlag=False)
sentence[j] = ''
for word in seg: if word not in stopwords:
sentence[j] = sentence[j] + word + " "
for _ in range(sentence.count("")):
sentence.remove("") return sentencedef tfidf(sentence):
words = [] # step 1
vectoerizer = CountVectorizer(min_df=1, max_df=1.0, token_pattern='\b\w+\b') # step 2
vectoerizer.fit(sentence) # step 3
bag_of_words = vectoerizer.get_feature_names() # step 4
X = vectoerizer.transform(sentence) # step 1
tfidf_transformer = TfidfTransformer() # step 2
tfidf_transformer.fit(X.toarray()) # step 3
for idx, word in enumerate(vectoerizer.get_feature_names()):
words.append((word, tfidf_transformer.idf_[idx])) # step 4
# tfidf = tfidf_transformer.transform(X)
# print(tfidf.toarray())
words.sort(key=lambda word: word[1], reverse=True)
words = [i[0] for i in words] return words微博的转发文本中会附带有这一整条传播链上用户的名称,并且使用"//@"进行连接,但是每个人转发的时候会由于父级别转发用户的不一样导致从同一条微博产生的转发链会有分支,这时候分支节点就十分关键,节点用户信息的丢失会使得分支丢失,导致最终得到的传播链变小。而由于很多用户会被微博封禁或注销,这就导致很多用户数据消失了,此时传播链中的很多节点用户会出现丢失。
一开始,我以为简单的根据"//@"进行分割就可以得到传播链。但是,当我初步完成以后,对这一部分传播链进行深入分析的时候,我就发现由于节点用户数据的丢失使得传播链变短了,但是后续的数据又没有用到;因此,我摒弃了根据"//@"从根节点到子节点进行简单分割的方法,选择了从子节点往根节点反推,再从根节点向子节点验证的方法,最大程度地恢复了完整的传播链,减少了数据的误差,最好的效果是使得传播链从五到六级恢复到十五六级。
可疑谣言概述(图1(A)和(B))旨在为平台管理员提供所有可疑谣言的时空分布概述(R1)。可疑谣言概述由两个主要部分组成,一个是choropleth map,一个是line map。choropleth map(图1(A))显示了中国不同地区和海外的可疑谣言数量。line map(图1(B))显示了不同主题的可疑谣言随时间变化的数量。
在顶部的choropleth map中,有更多可疑谣言的位置以较暗的颜色显示,准确的数字可以通过悬停在上面观察到。单击地图时,会高亮显示并选择相应的位置。下面的line map显示了在choropleth map上选定的可疑谣言的主题演变,不同颜色的线代表不同的主题。一旦选择了某个主题,该主题下的可疑谣言关键词就会显示在相应的行上。还提供了时间轴,通过选择开始和结束时间进一步过滤可疑谣言。
Projection View(图1(C))旨在帮助平台管理员快速检查和定位最可疑的功能,这些功能可能属于谣言,以便进一步验证(R2)。 Projection View由表示投影到2D特征图上的过滤可疑谣言的图示符组成,图示符之间的距离表示可疑谣言消息之间的相似性。
为了便于目视检查和比较可疑特征,我们将每个可疑谣言编码为一个圆形符号,该符号由两部分组成:内圈和外圈,如图2(a)所示。内圈的颜色代表可疑谣言的主题,大小则表示其影响力。外部四个弧线分别显示了粉丝、关注者、发帖和用户信息完整性的数量,图示符如图2(B)所示。特别是,由于不同用户的粉丝和粉丝数量差异很大,为了便于比较,采用对数法进行计算。与thermograph shape(图2(C))相比,我们的图示符设计可以提供更简洁、紧凑的方式来显示每个可疑谣言。此外,在左侧缩略图的引导下,可以通过拖动和缩放来调整显示视图的位置和大小。一旦选中,图示符将被放大以突出显示与其他图示符的差异,以便近距离观察。
Figure 2: Glyph designs for features of each suspected rumor. (a) round glyph design; (b) arc glyph design; (c) thermography shape glyph design.
Propagation View(图1(D)和(E))提供了对可疑谣言消息如何在社交媒体上传播的详细理解,从而使平台管理员能够做出最终决策(R3)。这一点非常重要,因为专家经验和以往研究所提到的丰富的传播信息可以被展示和探索,以验证可疑谣言。该视图包含两个部分,一个新颖的圆形设计用于在顶部可视化可疑谣言传播,另一个表格在底部显示相应的内容详细信息。
我们提出了一种新颖的圆形设计,它由中心的原始tweet和周围的多层次转发组成。如图3所示,由圆形节点表示的原始tweet位于中心,大小表示其影响。在它的周围有几个宽度不同的同心圆环,每个圆环都表示同一层次上的转发。每个同心圆环都包含扇形指针,扇形指针由径向白线隔开,白线按日期顺时针排列。每个扇区都充满了由许多单元格编码的转发,这些单元格的大小表示包含的字数。此外,单元格的颜色表示转发的情绪是中性(淡黄色)、积极(绿色)还是消极(红色)。特别是,能够提供转发的粗略含义的关键字显示在一些大单元格中。
Figure 3: 传播视图的图示符设计。该图用于显示特定的编码和交互演示
Figure 4: 传播视图的交互设计。它允许用户通过左键和右键单击选择两个转发。详细视图将显示所选两个转发的详细信息,以进行详细比较
在传播视图中支持丰富的交互。首先,当鼠标悬停在相应的单元格上时,会出现一个面板来显示一些简短的用户信息,通过单击可以在图1(E)中显示更详细的信息,如用户属性、创建时间和完整内容。其次,还支持比较分析以便于仔细检查。通过连续单击两个转发单元格,可以并排查看和检查这两个单元格的详细信息,如图4所示。第三,在左上角有一个时间直方图,显示转发数量随时间的变化。平台管理员可以单击它并高亮显示当天在不同层次结构中转发的单元格。
在设计过程中,我们最初考虑使用节点链接图、树图、螺旋时间线或sunburst图来可视化社交媒体上的推特传播。但是,它们都不能从传播路径同时呈现转发层次结构和时间序列。此外,当大量数据需要可视化时,节点链接图的空间效率不够。相比之下,我们所提出的圆形设计能够显示可疑谣言的动态传播细节,以简洁的方式促进谣言的互动分析和验证。
我们提出了RumorLens,这是一个交互式可视分析系统,可以帮助社交媒体平台的管理员有效地处理可疑的谣言。此外,本文还提出了一种新颖的圆形 glyph 设计,以显示可疑谣言的动态传播细节,从而简化谣言的交互分析和验证。一位领域专家进行的案例研究为谣言在帮助用户分析和验证可疑谣言方面的有效性提供了支持。
但是,针对可疑谣言的互动分析和验证的谣言仍然需要进一步改进。首先,通过与领域专家合作,我们认识到用户信息对于谣言识别的重要性。例如,如果用户的帐户未定义,并且之前有几次已识别的传闻在推特上发布,则消息具有成为传闻的高风险。因此,有必要提供与用户相关的历史投诉的更多信息。其次,谣言可以通过各种特征进行识别,因此如何选择和评价其对谣言验证的影响仍然是一个有待解决的问题。
以上就是[数据可视化]RumorLens: 社交媒体谣言的互动分析和验证的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号