【图书推荐】《PyTorch深度学习与企业级项目实战》-CSDN博客
谣言检测系统项目背景
1938年10月30日的晚上,哥伦比亚广播公司照例安排了广播剧,当晚的节目是根据H·G·威尔斯《世界之战》改编的“火星人进攻地球”。为提升吸引力,制作团队选择以类纪实风格演绎这台节目,通过模拟新闻播报的形式推进剧情的发展。
虽然在节目播出前播音员已经强调了这是一部广播剧,但演员们的逼真表演还是让很多听众信以为真,许多人把节目内容当成了紧急插播的突发新闻。前一刻还在阖家欢乐享受晚餐的人们,下一刻竟认为世界末日即将到来。紧张恐惧的情绪在人群中蔓延,进而引发了全国性恐慌。人们涌上街头寻找避难所,横冲直撞的车辆把街道搅得更加混乱,教堂和车站成为人们寻求救赎和出路的目的地。
资料显示,当时美国的3200万个家庭中,约有2750万家购置了收音机。借由这一广泛而又便捷的媒介,美国约有170万人相信了“火星人进攻地球”的消息,其中包括28%的大学毕业生和35%的高收入人群。这荒诞的一幕在日后看来虽然可笑,但却向世人展示了在信息不对称的情况下,广泛传播的谬误能够取得怎样疯狂的效果。
时至今日,互联网的发展让信息触手可得,然而低门槛和高自由度的技术特征也使得有价值的内容与五花八门的谣言在互联网世界中泥沙俱下。日益猖獗的谣言正影响着人们的正常生活和社会的安定和谐。在信息传播愈加快捷便利的“自媒体”时代,谣言搭上了网络的快车,在速度、广度、力度方面都有了空前的扩展,谣言的包装手段也呈现多元和成熟的趋势。
一些造谣者通过大量的故事元素把耸人听闻的谣言传播出去。网络谣言的另一特征是擅长扯明星、蹭热点。图片、视频是更富感染力和欺骗性的造谣手段,近几年出现的图片或视频谣言,被造谣者换上文字说明和字幕,再将事件发生地更换为“本地模式”进行简单“包装”,很快在当地疯传。例如2017年8月中旬,网上广泛流传一段疑似郑州大学第二附属医院内狂犬病患者发病的视频,后经调查视频事发地在吉林一家医院,视频中的女子患有精神疾病也并非狂犬病。
社交媒体的发展在加速信息传播的同时,也带来了虚假谣言信息的泛滥,往往会引发诸多不安定因素,并对经济和社会产生巨大的影响。人们常说“流言止于智者”,要想不被网上的流言和谣言盅惑、伤害,首先需要对其进行科学甄别,而时下人工智能正在尝试担任这一角色。
15.4.2 谣言检测系统代码实战
传统的谣言检测模型一般根据谣言的内容、用户属性、传播方式人工地构造特征,而人工构建特征存在考虑片面、浪费人力等现象。本项目使用基于PyTorch+Transformer的谣言检测模型,将文本中的谣言事件进行连续向量化,通过一维卷积神经网络的学习训练来挖掘表示文本深层的特征,避免了特征构建的问题,并能发现那些不容易被人发现的特征,从而产生更好的效果。项目中使用的数据是微博头条新闻数据,数据集一共有3387条新闻数据,新闻的类型分为两类:“谣言新闻”和“真实新闻”。本项目所使用的数据是从新浪微博不实信息举报平台抓取的中文谣言数据,数据集中共包含1538条谣言和1849条非谣言。
在Transformer的编码器中,我们使用注意力机制来提取各个词的语义信息,这里需要引入不同词的位置信息,让注意力机制不仅考虑词之间的语义信息,还需要考虑不同词的上下文信息,Transformer中使用的是位置编码(Position Encoding),就是将每个词所在的位置形成一个嵌入向量,然后将这个向量与对应词的嵌入向量加和,然后“喂”进注意力机制网络中。因此,定义PositionEncoding类,直接拿过来用即可,只需要实例化这个类,然后传入我们的词嵌入向量即可。
定义Transformer网络结构:
(1)嵌入层:负责将我们的词形成连续型嵌入向量,用一个连续型向量来表示一个词。
(2)位置编码层:将位置信息添加到输入向量中。
(3)Transformer:利用Transformer来提取输入句子的语义信息。
(4)输出层:将Transformer的输出喂入,然后进行分类。
定义Transformer的编码器需要做两件事:第一是定义编码层,也就是每个块,需要传入每个块的一些超参数;第二是定义编码器,编码器是由多个编码层组成的,所以需要传入我们刚才定义的编码层,然后定义超参数层数即可。
项目完整代码如下:
#######################yaoyantest.py######################
import pickle
import numpy as np
import pandas as pd
import torch
import math
import torch.nn as nn
from keras.preprocessing.sequence import pad_sequences
from torch.utils.data import TensorDataset
from torch import optim
from torchnet import meter
from tqdm import tqdm
# 模型输入参数,需要自己根据需要调整
hidden_dim = 100 # 隐层大小
epochs = 20 # 迭代次数
batch_size = 32 # 每个批次样本大小
embedding_dim = 20 # 每个字形成的嵌入向量大小
output_dim = 2 # 输出维度,因为是二分类
lr = 0.003 # 学习率
device = 'cpu'
input_shape = 180 # 每句话的词的个数,如果不够,需要使用0进行填充
# 加载文本数据
def load_data(file_path, input_shape=20):
df = pd.read_csv(file_path, sep='\t')
# 标签及词汇表
labels, vocabulary = list(df['label'].unique()), list(df['text'].unique())
# 构造字符级别的特征
string = ''
for word in vocabulary:
string += word
# 所有的词汇表
vocabulary = set(string)
# word2idx 将字映射为索引
word_dictionary = {word: i + 1 for i, word in enumerate(vocabulary)}
with open('word_dict.pk', 'wb') as f:
pickle.dump(word_dictionary, f)
# idx2word 将索引映射为字
inverse_word_dictionary = {i + 1: word for i, word in enumerate(vocabulary)}
# label2idx 将正反面映射为0和1
label_dictionary = {label: i for i, label in enumerate(labels)}
with open('label_dict.pk', 'wb') as f:
pickle.dump(label_dictionary, f)
# idx2label 将0和1映射为正反面
output_dictionary = {i: labels for i, labels in enumerate(labels)}
# 训练数据中所有词的个数
vocab_size = len(word_dictionary.keys()) # 词汇表大小
# 标签类别,分别为正、反面
label_size = len(label_dictionary.keys()) # 标签类别数量
# 序列填充,按input_shape填充,长度不足的按0补充
# 将一句话映射成对应的索引 [0,24,63...]
x = [[word_dictionary[word] for word in sent] for sent in df['text']]
# 如果长度不够input_shape,使用0进行填充
x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0)
# 形成标签0和1
y = [[label_dictionary[sent]] for sent in df['label']]
y = np.array(y)
return x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout=0.1, max_len=128):
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
# 初始化Shape为(max_len, d_model)的PE (positional encoding)
pe = torch.zeros(max_len, d_model)
# 初始化一个tensor [[0, 1, 2, 3, ...]]
position = torch.arange(0, max_len).unsqueeze(1)
# 这里就是sin和cos括号中的内容,通过e和ln进行变换
div_term = torch.exp(
torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)
)
# 计算PE(pos, 2i)
pe[:, 0::2] = torch.sin(position * div_term)
# 计算PE(pos, 2i+1)
pe[:, 1::2] = torch.cos(position * div_term)
# 为了方便计算,在最外面在unsqueeze出一个batch
pe = pe.unsqueeze(0)
# 如果一个参数不参与梯度下降,但又希望保存model的时候,将其保存下来
# 这个时候就可以用register_buffer
self.register_buffer("pe", pe)
def forward(self, x):
# 将x和positional encoding相加
x = x + self.pe[:, : x.size(1)].requires_grad_(False)
return self.dropout(x)
class Transformer(nn.Module):
def __init__(self, vocab_size, embedding_dim, num_class, feedforward_dim=256,
num_head=2, num_layers=3, dropout=0.1,
max_len=128):
super(Transformer, self).__init__()
# 嵌入层
self.embedding = nn.Embedding(vocab_size, embedding_dim)
# 位置编码层
self.positional_encoding = PositionalEncoding(embedding_dim, dropout, max_len)
# 编码层
self.encoder_layer = nn.TransformerEncoderLayer(embedding_dim, num_head, feedforward_dim, dropout)
self.transformer = nn.TransformerEncoder(self.encoder_layer, num_layers)
# 输出层
self.fc = nn.Linear(embedding_dim, num_class)
def forward(self, x):
# 输入的数据维度为【批次,序列长度】
# transformer的输入维度为【序列长度,批次,嵌入向量维度】
x = x.transpose(0, 1)
# 将输入的数据进行词嵌入,得到数据的维度为【序列长度,批次,嵌入向量维度】
x = self.embedding(x)
# 维度为【序列长度,批次,嵌入向量维度】
x = self.positional_encoding(x)
# 维度为【序列长度,批次,嵌入向量维度】
x = self.transformer(x)
# 将每个词的输出向量取均值,维度为【批次,嵌入向量维度】
x = x.mean(axis=0)
# 进行分类,维度为【批次,分类数】
x = self.fc(x)
return x
# 1.获取训练数据
x_train, y_train, output_dictionary_train, vocab_size_train, label_size, \
inverse_word_dictionary_train = load_data(
"./rumor_data/train.tsv", input_shape)
x_test, y_test, output_dictionary_test, vocab_size_test, label_size,\
inverse_word_dictionary_test = load_data(
"./rumor_data/test.tsv", input_shape)
idx = 0
word_dictionary = {}
for k, v in inverse_word_dictionary_train.items():
word_dictionary[idx] = v
idx += 1
for k, v in inverse_word_dictionary_test.items():
word_dictionary[idx] = v
idx += 1
# 2.将numpy转成tensor
x_train = torch.from_numpy(x_train).to(torch.int32)
y_train = torch.from_numpy(y_train).to(torch.float32)
x_test = torch.from_numpy(x_test).to(torch.int32)
y_test = torch.from_numpy(y_test).to(torch.float32)
# 3.形成训练数据集
train_data = TensorDataset(x_train, y_train)
test_data = TensorDataset(x_test, y_test)
# 4.将数据加载成迭代器
train_loader = torch.utils.data.DataLoader(train_data,
batch_size,
True)
test_loader = torch.utils.data.DataLoader(test_data,
batch_size,
False)
# 5.模型训练
model = Transformer(len(word_dictionary), embedding_dim, output_dim)
Configimizer = optim.Adam(model.parameters(), lr=lr) # 优化器
criterion = nn.CrossEntropyLoss() # 多分类损失函数
model.to(device)
loss_meter = meter.AverageValueMeter()
best_acc = 0 # 保存最好准确率
best_model = None # 保存对应最好的准确率的模型参数
for epoch in range(epochs):
model.train() # 开启训练模式
epoch_acc = 0 # 每个epoch的准确率
epoch_acc_count = 0 # 每个epoch训练的样本数
train_count = 0 # 用于计算总的样本数,方便求准确率
loss_meter.reset()
train_bar = tqdm(train_loader) # 形成进度条
for data in train_bar:
x_train, y_train = data # 解包迭代器中的X和Y
x_input = x_train.long().contiguous()
x_input = x_input.to(device)
Configimizer.zero_grad()
# 形成预测结果
output_ = model(x_input)
# 计算损失
loss = criterion(output_, y_train.long().view(-1))
loss.backward()
Configimizer.step()
loss_meter.add(loss.item())
# 计算每个epoch正确的个数
epoch_acc_count += (output_.argmax(axis=1) == y_train.view(-1)).sum()
train_count += len(x_train)
# 每个epoch对应的准确率
epoch_acc = epoch_acc_count / train_count
# 打印信息
print("【EPOCH: 】%s" % str(epoch + 1))
print("训练损失为%s" % (str(loss_meter.mean)))
print("训练精度为%s" % (str(epoch_acc.item() * 100)[:5]) + '%')
# 保存模型及相关信息
if epoch_acc > best_acc:
best_acc = epoch_acc
best_model = model.state_dict()
# 在训练结束保存最优的模型参数
if epoch == epochs - 1:
# 保存模型
torch.save(best_model, './best_model.pkl')
word2idx = {}
for k, v in word_dictionary.items():
word2idx[v] = k
label_dict = {0: "非谣言", 1: "谣言"}
try:
input_shape = 180 # 序列长度,就是时间步大小,也就是这里的每句话中的词的个数
# 用于测试的话
sent = "凌晨的长春,丢失的孩子找到了,被偷走的车也找到了,只是偷车贼没找到," \
"看来,向雷锋同志学习50周年的今天,还是一个有效果的日子啊。"
# 将对应的字转换为相应的序号
x = [[word2idx[word] for word in sent]]
# 如果长度不够180,使用0进行填充
x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0)
x = torch.from_numpy(x)
# 加载模型
model_path = './best_model.pkl'
model = Transformer(len(word_dictionary), embedding_dim, output_dim)
model.load_state_dict(torch.load(model_path, 'cpu'))
# 模型预测,注意输入的数据第一个input_shape,就是180
y_pred = model(x.long())
print('输入语句: %s' % sent)
print('谣言检测结果: %s' % label_dict[y_pred.argmax().item()])
except KeyError as err:
print("您输入的句子有汉字不在词汇表中,请重新输入!")
print("不在词汇表中的单词为:%s." % err)
运行结果如下:
100%|██████████| 106/106 [00:30<00:00, 3.53it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】1
训练损失为0.7031196440165897
训练精度为52.11%
100%|██████████| 106/106 [00:38<00:00, 2.76it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】2
训练损失为0.6925747405807925
训练精度为54.82%
100%|██████████| 106/106 [00:47<00:00, 2.25it/s]
【EPOCH: 】3
训练损失为0.6917027954785329
训练精度为53.76%
100%|██████████| 106/106 [00:32<00:00, 3.31it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】4
训练损失为0.6912038483709659
训练精度为53.61%
100%|██████████| 106/106 [00:30<00:00, 3.45it/s]
【EPOCH: 】5
训练损失为0.6902508443256595
训练精度为53.76%
100%|██████████| 106/106 [00:29<00:00, 3.65it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】6
训练损失为0.689901611152685
训练精度为54.59%
100%|██████████| 106/106 [00:29<00:00, 3.55it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】7
训练损失为0.689611426501904
训练精度为54.59%
100%|██████████| 106/106 [00:29<00:00, 3.65it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】8
训练损失为0.6895414703297166
训练精度为54.59%
100%|██████████| 106/106 [00:37<00:00, 2.86it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】9
训练损失为0.6893950796352243
训练精度为54.59%
100%|██████████| 106/106 [00:28<00:00, 3.77it/s]
【EPOCH: 】10
训练损失为0.690319667447288
训练精度为54.59%
100%|██████████| 106/106 [00:29<00:00, 3.58it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】11
训练损失为0.652294947853628
训练精度为60.55%
100%|██████████| 106/106 [00:27<00:00, 3.85it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】12
训练损失为0.5072044069474597
训练精度为76.61%
100%|██████████| 106/106 [00:27<00:00, 3.88it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】13
训练损失为0.49225265729539797
训练精度为77.53%
100%|██████████| 106/106 [00:28<00:00, 3.78it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】14
训练损失为0.47202253538482614
训练精度为78.88%
100%|██████████| 106/106 [00:27<00:00, 3.87it/s]
【EPOCH: 】15
训练损失为0.47254926669147795
训练精度为78.09%
100%|██████████| 106/106 [00:33<00:00, 3.14it/s]
【EPOCH: 】16
训练损失为0.45091094990383895
训练精度为80.48%
100%|██████████| 106/106 [00:42<00:00, 2.52it/s]
【EPOCH: 】17
训练损失为0.45994811983041056
训练精度为79.98%
100%|██████████| 106/106 [00:33<00:00, 3.21it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】18
训练损失为0.4671612315863934
训练精度为79.74%
100%|██████████| 106/106 [00:31<00:00, 3.34it/s]
0%| | 0/106 [00:00<?, ?it/s]【EPOCH: 】19
训练损失为0.4289132805084282
训练精度为81.07%
100%|██████████| 106/106 [00:28<00:00, 3.69it/s]
【EPOCH: 】20
训练损失为0.4708527742691758
训练精度为78.68%
输入语句:凌晨的长春,丢失的孩子找到了,被偷走的车也找到了,只是偷车贼没找到,看来,向雷锋同志学习50周年的今天,还是一个有效果的日子啊。
谣言检测结果:非谣言。
《PyTorch深度学习与企业级项目实战(人工智能技术丛书)》(宋立桓,宋立林)【摘要 书评 试读】- 京东图书 (jd.com)