【论文复现】NEP-model假新闻检查:放在环境下?只需计算余弦距离


原文地址:Zoom Out and Observe: News Environment Perception for Fake News Detection
原文代码:github

整体框架原理

在这里插入图片描述
在这里插入图片描述

construction

1.怎么才能构建一个合理有效的新闻环境?应该包含主流视角和新闻分布情况,大家看什么,大家关心什么
2.如何构建新闻环境来方便后续对流行度和新颖度的评价
3.宏观环境/微观环境(是宏观环境的子集,在里面找到与帖子最相近的新闻条目)
在这里插入图片描述
在这里插入图片描述

perception ( popularity& novelty)

1.如何把新闻帖子和新闻环境连接起来,来评估新颖度和流行度
2.流行度和新颖度本质上是相似度的一个变化。如果一个帖子与新闻环境中的非常多的条目都是非常像的,那么可以说明他们共享的事件或该话题是比较流行的;如果大家都是比较像的情况下,那么这个帖子说的事情和已有的新闻条目不太一样,那么这个帖子就显得比较新颖,容易被人看到。(把一对多的关系转成一对一的相似度计算,之后再总结起来)
popularity如果与热门新闻相关,捏造的帖子更有可能像病毒一样传播,从而获得更大的影响力。因此,假新闻创作者可能会在撰写假新闻时考虑如何追逐热点事件。
通过计算 k ( p , E m a c ) k(p, Emac) k(p,Emac),得到了 p p p M A C R O E N V MACROENV MACROENV之间作为受欢迎程度感知的相似度的软分布。
一、流行度引导的宏观环境的感知
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

拓展:高斯核池化——变长?固定维数?
当处理不同长度的相似性列表时,使用高斯核池化可以将这些变长列表转化为固定维度的特征向量,从而在后续的任务中更方便地处理和传递信息。
举个栗子:假设p1:相似性列表为[0.9, 0.6],p2:相似性列表为[0.7, 0.8, 0.5],p3:相似性列表为[0.4, 0.3, 0.6],为什么长度不相等?因为每个帖子对应的宏观环境新闻数量是不同的。下面使用高斯核池化来处理这些相似性列表。
1.选择高斯核:我们假设使用一个高斯核,其标准差为σ=0.1。
2.计算高斯核值:对于每个元素pi,我们分别计算其与其他元素之间的相似性与高斯核的距离,并使用高斯核函数计算相应的高斯核值。
对于p1: K ( p 1 , E n v ) = exp ⁡ ( − ( 0.9 − 0.6 ) 2 2 ⋅ 0. 1 2 ) + exp ⁡ ( − ( 0.6 − 0.9 ) 2 2 ⋅ 0. 1 2 ) ≈ 1.98 K(p1, Env) = \exp\left(-\frac{(0.9 - 0.6)^2}{2 \cdot 0.1^2}\right) + \exp\left(-\frac{(0.6 - 0.9)^2}{2 \cdot 0.1^2}\right) \approx 1.98 K(p1,Env)=exp(20.12(0.90.6)2)+exp(20.12(0.60.9)2)1.98
对于p2: K ( p 2 , E n v ) = e x p ( − ( 0.7 − 0.8 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.8 − 0.7 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.5 − 0.6 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.6 − 0.5 ) 2 2 ⋅ 0. 1 2 ) ≈ 3.96 K(p2, Env) = exp\left(-\frac{(0.7 - 0.8)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.8 - 0.7)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.5 - 0.6)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.6 - 0.5)^2}{2 \cdot 0.1^2}\right) \approx 3.96 K(p2,Env)=exp(20.12(0.70.8)2)+exp(20.12(0.80.7)2)+exp(20.12(0.50.6)2)+exp(20.12(0.60.5)2)3.96
对于p3: K ( p 3 , E n v ) = e x p ( − ( 0.4 − 0.3 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.3 − 0.4 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.6 − 0.6 ) 2 2 ⋅ 0. 1 2 ) + e x p ( − ( 0.6 − 0.6 ) 2 2 ⋅ 0. 1 2 ) ≈ 1.98 K(p3, Env) = exp\left(-\frac{(0.4 - 0.3)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.3 - 0.4)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.6 - 0.6)^2}{2 \cdot 0.1^2}\right) + exp\left(-\frac{(0.6 - 0.6)^2}{2 \cdot 0.1^2}\right) \approx 1.98 K(p3,Env)=exp(20.12(0.40.3)2)+exp(20.12(0.30.4)2)+exp(20.12(0.60.6)2)+exp(20.12(0.60.6)2)1.98
通过高斯核池化,我们得到了固定维度的特征向量 K ( p 1 , E n v ) ≈ 1.98 K(p1, Env) ≈ 1.98 K(p1,Env)1.98 K ( p 2 , E n v ) ≈ 3.96 K(p2, Env) ≈ 3.96 K(p2,Env)3.96 K ( p 3 , E n v ) ≈ 1.98 K(p3, Env) ≈ 1.98 K(p3,Env)1.98。现在,我们可以在后续的任务中使用这些特征向量,而不需要考虑原始相似性列表的不同长度问题。

二、新颖度引导的微观环境的感知
在这里插入图片描述
新颖度是一定要通过比较得到的
在这里插入图片描述
在这里插入图片描述

prediction

在这里插入图片描述
门融合,由它自己控制门融合的程度。最后把融合后的向量拼接起来放到一个真假分类器中。
门控机制的目标是生成一个权重向量,用来控制如何融合宏观环境和微观环境的特征。
在这里插入图片描述

#使用门控机制进行融合
elif self.args.strategy_of_fusion == 'gate':
	#门控机制要求两个输入维度相等
	assert args.macro_env_output_dim == args.micro_env_output_dim
	self.W_gate = nn.Linear(
    	self.fake_news_detector.last_output + args.macro_env_output_dim, args.macro_env_output_dim)

拓展:关于门控机制和自注意力机制的异同
相同:都是用在神经网络中实现信息的融合和调控
不同1:门控机制主要用于融合不同来源或不同特征的信息,如融合多个模态的数据(图像、文本、音频等)或不同层级的特征。自注意力机制主要用于在同一序列中不同位置的信息之间进行交互和融合,特别适用于处理自然语言处理(NLP)任务中的文本序列。
不同2:门控机制允许网络自动学习权重,以灵活地调节每个融合部分的重要性。门控机制通过引入门控单元,使得网络可以根据任务的需要动态地控制信息的流动和融合。自注意力机制通过计算不同位置之间的注意力权重,对序列中的每个位置进行信息的加权融合。每个位置的输出是通过考虑其他位置的信息加权求和得到的,注意力权重由模型学习得到。

举个栗子
在多模态数据融合的场景中,每个数据来源(如图像、文本、音频等)可能对最终任务的影响程度不同,因此需要根据实际情况对这些数据进行加权融合。门控机制通过引入门控单元,使得模型可以自动学习每个数据来源的权重,从而决定每个数据在融合过程中的贡献。

举个例子,在多模态情感分类任务中,模型可能同时接收图像和文本作为输入,用于判断输入内容的情感倾向。对于某些样本,图像可能对情感分类更加重要,而对于其他样本,文本可能更具信息量。门控机制可以自适应地决定在每个样本中使用图像和文本的权重,从而更好地捕捉不同模态数据之间的关联性,提高分类准确性。

import torch
import torch.nn as nn
import torch.optim as optim

class MultiModalFusionModel(nn.Module):
    def __init__(self, image_input_dim, text_input_dim, hidden_dim, output_dim):
        super(MultiModalFusionModel, self).__init__()
        
        self.image_encoder = nn.Linear(image_input_dim, hidden_dim)
        self.text_encoder = nn.Linear(text_input_dim, hidden_dim)
        
        self.gate_controller = nn.Linear(hidden_dim * 2, hidden_dim)
        
        self.classifier = nn.Linear(hidden_dim, output_dim)
        
    def forward(self, image_features, text_features):

        image_hidden = torch.relu(self.image_encoder(image_features))
        text_hidden = torch.relu(self.text_encoder(text_features))
        
        combined_features = torch.cat((image_hidden, text_hidden), dim=1)
        
        gate_values = torch.sigmoid(self.gate_controller(combined_features))
        
        fused_features = gate_values * image_hidden + (1 - gate_values) * text_hidden
        
        output = self.classifier(fused_features)
        return output

if __name__ == "__main__":
    image_features = torch.randn(10, 256) 
    text_features = torch.randn(10, 128)   
    
    image_input_dim = 256
    text_input_dim = 128
    hidden_dim = 64
    output_dim = 2 
    model = MultiModalFusionModel(image_input_dim, text_input_dim, hidden_dim, output_dim)
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    outputs = model(image_features, text_features)
    labels = torch.tensor([0, 1, 0, 1, 0, 1, 1, 0, 0, 1])  
    loss = criterion(outputs, labels)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

(输出的gate_values维度是(bs, hidden_dim),这里面bs=10.

代码实现

(代码结构)

  1. obtain the representation of posts and news environment items
    (1) prepare the SimCSE model
    ckpts/English( config.json/ pytorch_model.bin/ vocab.txt)
    (2) obtain the texts’ representation (get_repr.py)
  2. construct the macro&micro environment
  3. prediction

construction

在这里插入图片描述

class SimCSE(nn.Module):
    def __init__(self, pretrained, pool_type="cls", dropout_prob=0.3):
        super().__init__()
        conf = BertConfig.from_pretrained(pretrained)
        conf.attention_probs_dropout_prob = dropout_prob
        conf.hidden_dropout_prob = dropout_prob
        self.encoder = BertModel.from_pretrained(pretrained, config=conf)
        assert pool_type in [
            "cls", "pooler"], "invalid pool_type: %s" % pool_type
        self.pool_type = pool_type

    def forward(self, input_ids, attention_mask, token_type_ids):
        output = self.encoder(input_ids,
                              attention_mask=attention_mask,
                              token_type_ids=token_type_ids)
        if self.pool_type == "cls":
            output = output.last_hidden_state[:, 0]
        elif self.pool_type == "pooler":
            output = output.pooler_output
        return output
SimCSEBERT
SimCSE的架构相对简单,通常使用预训练的句子嵌入模型(如BERT)来获得句子表示,然后使用对比学习方法对句子表示进行微调。BERT采用Transformer编码器结构,包含多个Transformer编码层。它使用多头自注意力机制来捕捉文本中的上下文关系,并通过前馈神经网络进行特征提取。
SimCSE专注于句子级别的语义相似性任务,例如句子相似性判定、问答、信息检索等。BERT主要用于下游自然语言处理任务的微调,例如文本分类、命名实体识别、语义相似度等。
SimCSE使用有监督的句子对数据进行对比学习训练,利用两个句子之间的相似性标签来指导模型学习更好的句子表示。BERT使用大规模的无监督数据进行预训练,并通过MLM和NSP目标进行自监督训练。

拓展:SimCSE不像BERT有上亿个参数,据说单块GPU就可训练模型,所以要用的话可以试着自己训练一个SimCSE模型。

在这里插入图片描述

get_repr.py:输入texts(json文件);输出:tokens(pkl文件)

if __name__ == "__main__":
    parser = ArgumentParser(description='get_repr')
    parser.add_argument('--dataset', type=str)
    args = parser.parse_args()

    device = torch.device(
        "cuda:0") if torch.cuda.is_available() else torch.device("cpu")
    dataset = args.dataset
    SimCSE_ckpt = './train_SimCSE/ckpts/{}'.format(dataset)
    if dataset == 'Chinese':
        MAX_LEN = 256
    elif dataset == 'English':
        MAX_LEN = 128

    # --------------------- for news --------------------- #
    # 处理新闻(环境)数据集
    simcse = PTMEncode(
        fname='../../dataset/{}/news/news.json'.format(dataset),
        PTM_path=SimCSE_ckpt,
        batch_size=256,
        max_length=MAX_LEN,
        device=device)
    all_vecs = simcse.encode_news()
    # save all_vecs
    with open('./data/{}/news/all_vecs_origin.pkl'.format(dataset), 'wb') as f:
        pickle.dump(all_vecs, f)

    # --------------------- for post --------------------- #
    # 处理帖子数据集
    simcse = PTMEncode(
        fname='../../dataset/{}/post'.format(dataset),
        PTM_path=SimCSE_ckpt,
        batch_size=256,
        max_length=MAX_LEN,
        device=device)
    train_vecs, val_vecs, test_vecs = simcse.encode_post()
    for d in ['train', 'val', 'test']:
        with open('./data/{}/post/{}_vecs_origin.pkl'.format(dataset, d), 'wb') as f:
            pickle.dump(eval(d+'_vecs'), f)

(略讲)

class PTMEncode(object):
    def __init__(self,
                 fname,
                 PTM_path,
                 batch_size,
                 max_length,
                 device):
        self.fname = fname
        self.batch_size = batch_size
        self.max_length = max_length
        self.device = device
        self.tokenizer = BertTokenizer.from_pretrained(PTM_path)
        model = SimCSE(pretrained=PTM_path).to(device)
        self.model = model
        self.model.eval()
        self.id2text = None
        self.vecs = None
        self.ids = None
        self.index = None

    # 负责把输入进来的texts转成向量tokens
    def encode_batch(self, texts):
        text_encs = self.tokenizer(
            texts,
            add_special_tokens=True,
            max_length=self.max_length,
            truncation=True,
            padding='max_length',
            return_tensors="pt"
        )
        input_ids = text_encs["input_ids"].to(self.device)
        attention_mask = text_encs["attention_mask"].to(self.device)
        token_type_ids = text_encs["token_type_ids"].to(self.device)
        with torch.no_grad():
            output = self.model.forward(
                input_ids, attention_mask, token_type_ids)
        return output

    #这段代码的作用是将新闻内容进行批量编码,并返回编码结果的张量。它通过批次的方式处理文本数据,
    # 最后将所有的编码向量拼接成一个大的张量返回。在进行编码之前,还可以选择对编码向量进行 L2 归一化。
    def encode_news(self, L2_normalize=False):
        all_vecs = []
        data = json.load(open(self.fname, 'r'))
        file_len = len(data) # 获取数据的总长度,即新闻内容的数量
        content_lst = [d['content'] for d in data] # 从数据中提取每个新闻的内容,并存储在列表中
        num_batch = file_len // self.batch_size + 1 #批次的数量
        last_batch_pad = num_batch * self.batch_size - file_len #计算最后一个批次里需要填充的数量(如果数据不能完全分成整数倍的批次,需要进行填充)
        texts = [] #用于存储每个批次的文本内容
        for idx, line in tqdm(enumerate(content_lst), total=file_len):
            if not line.strip():#检查当前的内容是否为空,如果是空行,则打印错误信息并退出程序。
                print("Empty content, whose index is {}.".format(idx))
                exit()
            texts.append(line.strip()) # 将当前行的内容去除首尾空白字符后,添加到 texts 列表中。
            if idx == file_len-1: #如果当前行是最后一行
                # pad the last batch to batch_size
                for _ in range(last_batch_pad): #对于最后一个批次需要填充的数量
                    texts.append(line) # 将最后一行的内容重复添加到‘texts’列表中,进行填充
                assert len(texts) == self.batch_size # 断言当前 texts 列表的长度等于批次大小,确保填充后的长度正确。
                vecs = self.encode_batch(texts) #编码
                if L2_normalize:# 如果 L2_normalize 参数为 True,则对编码向量进行 L2 归一化
                    vecs = vecs / vecs.norm(dim=1, keepdim=True)
                all_vecs.append(vecs.cpu())
                texts = [] # 清空 texts 列表,准备处理下一个批次的文本内容。
            if len(texts) >= self.batch_size: #如果当前列表长度大于等于批次大小,
                vecs = self.encode_batch(texts)
                if L2_normalize:
                    vecs = vecs / vecs.norm(dim=1, keepdim=True)
                all_vecs.append(vecs.cpu())
                texts = []

        all_vecs = torch.cat(all_vecs, 0)
        all_vecs = all_vecs[:file_len, :]

        assert all_vecs.size()[0] == file_len

        return all_vecs

    def encode_post_a_dataset(self, datatype, L2_normalize=False):
        all_vecs = []
        data = json.load(
            open(os.path.join(self.fname, datatype + '.json'), 'r'))
        file_len = len(data)
        content_lst = [d['content'] for d in data]
        num_batch = file_len // self.batch_size + 1
        last_batch_pad = num_batch * self.batch_size - file_len
        texts = []
        for idx, line in tqdm(enumerate(content_lst), total=file_len):
            if not line.strip():
                print("Empty content, whose index is {}.".format(idx))
                exit()
            texts.append(line.strip())
            if idx == file_len-1:
                # pad the last batch to batch_size
                for _ in range(last_batch_pad):
                    texts.append(line)
                assert len(texts) == self.batch_size
                vecs = self.encode_batch(texts)
                if L2_normalize:
                    vecs = vecs / vecs.norm(dim=1, keepdim=True)
                all_vecs.append(vecs.cpu())
                texts = []
            if len(texts) >= self.batch_size:
                vecs = self.encode_batch(texts)
                if L2_normalize:
                    vecs = vecs / vecs.norm(dim=1, keepdim=True)
                all_vecs.append(vecs.cpu())
                texts = []

        all_vecs = torch.cat(all_vecs, 0)
        all_vecs = all_vecs[:file_len, :]

        assert all_vecs.size()[0] == file_len

        print('Finish encoding {}.json!'.format(datatype))

        return all_vecs

    def encode_post(self):
        train_vecs = self.encode_post_a_dataset(datatype='train')
        val_vecs = self.encode_post_a_dataset(datatype='val')
        test_vecs = self.encode_post_a_dataset(datatype='test')

        return train_vecs, val_vecs, test_vecs

get_env.py:输入:texts(json文件)、tokens(pkl文件);输出:
功能:计算每个帖子与过去一段时间内(宏观)的其他新闻的余弦相似度,并按照相似度进行排序(topk微观)

if __name__ == "__main__":

    parser = ArgumentParser(description='get_env')
    parser.add_argument('--dataset', type=str, default="Chinese")
    parser.add_argument('--macro_env_days', type=int, default=3)
    args = parser.parse_args()

    dataset = args.dataset
    days = args.macro_env_days

    # content, time, label, source
    train = json.load(
        open('../../dataset/{}/post/train.json'.format(dataset), 'r'))
    val = json.load(
        open('../../dataset/{}/post/val.json'.format(dataset), 'r'))
    test = json.load(
        open('../../dataset/{}/post/test.json'.format(dataset), 'r'))
    news = json.load(
        open('../../dataset/{}/news/news.json'.format(dataset), 'r'))

    train_vec = pickle.load(open(
        '../SimCSE/data/{}/post/train_vecs_origin.pkl'.format(dataset), 'rb'))
    val_vec = pickle.load(open(
        '../SimCSE/data/{}/post/val_vecs_origin.pkl'.format(dataset), 'rb'))
    test_vec = pickle.load(open(
        '../SimCSE/data/{}/post/test_vecs_origin.pkl'.format(dataset), 'rb'))
    news_vec = pickle.load(open(
        '../SimCSE/data/{}/news/all_vecs_origin.pkl'.format(dataset), 'rb'))

    # L2 normalization(确保在计算cosine时具有单位长度)
    train_vec = train_vec / train_vec.norm(dim=1, keepdim=True)
    val_vec = val_vec / val_vec.norm(dim=1, keepdim=True)
    test_vec = test_vec / test_vec.norm(dim=1, keepdim=True)
    news_vec = news_vec / news_vec.norm(dim=1, keepdim=True)

    #根据新闻中的日期构建一个字典,其中每个日期对应一个索引列表,用于存储在该日期发生的新闻在新闻数据中的索引。
    #这样可以通过日期来快速检索与特定日期相关的新闻。
    date2idx = {}# 用于存储日期和索引之间的映射关系
    for i in range(len(news)):
        date = news[i]['time'].split(' ')[0]#从新闻数据中获取日期,并仅保留日期部分(去除时间)
        if date in date2idx:#检查当前日期是否已经在 date2idx 字典中存在
            date2idx[date].append(i)#如果日期已存在于字典中,则将当前索引 i 添加到该日期对应的索引列表中
        else:#如果日期在字典中不存在,则创建一个空列表,并将当前索引 i 添加到该列表中,并将该日期与列表的映射关系添加到 date2idx 字典中。
            date2idx[date] = []
            date2idx[date].append(i)

    # 存储post里所有发布日期的唯一值
    all_post_dates = set()
    for dtst in [train, val, test]:
        for d in dtst:
            all_post_dates.add(d['time'].split(' ')[0])
    all_post_dates = sorted(list(all_post_dates))

    date2recent_news_idx = {}
    for d in all_post_dates: #对于排序后的日期列表中的每个日期 d 进行迭代。
        date2recent_news_idx[d] = fetch_past_k_days_unlabel_news(
            days, date2idx, d)# 获取给定日期 d 前 days 天内的未标记新闻索引,并将结果存储在 date2recent_news_idx 字典中。

    get_cosine(train, date2recent_news_idx, news_vec,
               train_vec, dataset, 'train_data', days)
    get_cosine(val, date2recent_news_idx, news_vec,
               val_vec, dataset, 'val_data', days)
    get_cosine(test, date2recent_news_idx, news_vec,
               test_vec, dataset, 'test_data', days)

(略讲)

# 此函数用于从date2idx字典中检索给定日期之前指定天数内的新闻文章索引。最终拿到k天前的所有新闻索引(存为列表)
def fetch_past_k_days_unlabel_news(k, date2idx, post_date):
    assert k >= 1 #checks if k is greater than or equal to 1. If it is not, an assertion error will be raised.
    all_idx = [] #存储提取的新闻索引
    post_date = datetime.datetime.strptime(post_date, "%Y-%m-%d")#将传入的 post_date 参数转换为日期时间对象,格式为 "%Y-%m-%d"(例如:"2023-07-09")
    for delta_date in range(1, k+1):#前1天到前k天
        target_date = (post_date + datetime.timedelta(
            days=-delta_date)).strftime("%Y-%m-%d")#计算目标日期,即当前日期向前偏移 delta_date 天得到的日期,并将其格式化为 "%Y-%m-%d" 格式的字符串。
        if target_date in date2idx:#检查目标日期是否存在于 date2idx 字典中
            idx = date2idx[target_date]#如果目标日期在字典中存在,则获取对应日期的新闻索引列表。
        else:
            idx = []#如果目标日期在字典中不存在,则将新闻索引列表设为空列表。
            if k == 1:
                print(
                    "Post at {} don't have news at yesterday.".format(post_date))
        all_idx.extend(idx)#将获取到的新闻索引列表扩展到 all_idx 列表中
    return all_idx

(略讲)

#计算指定天数内每篇文章与最近新闻的余弦相似度。最后,将排名后的相似度列表使用pickle保存到特定文件路径中。
def get_cosine(post, date2recent_news_idx, all_news_vec, post_vec, dataset, split_name, days):
    ranked_sims_in_n_days = [] #存储在指定天数内排名的相似度
    for i in tqdm(range(len(post))): #对于 post 列表中的每个索引 i 进行迭代,并利用 tqdm 函数显示循环进度条。
        date = post[i]['time'].split(" ")[0] #从 post 列表中获取索引为 i 的元素的日期,并仅保留日期部分(去除时间)
        news_idxs = date2recent_news_idx[date] #根据日期从 date2recent_news_idx 字典中获取与该日期相关的最近新闻的索引列表。
        news_vec = all_news_vec[news_idxs, :]# 根据新闻索引列表从 all_news_vec 中获取对应的新闻向量。
        cos_sims = torch.matmul(post_vec[i], news_vec.transpose(0, 1)).tolist() #计算 post_vec[i](表示当前文章的向量)与 news_vec(表示最近新闻的向量)之间的余弦相似度,并将结果转换为列表。
        tup = zip(news_idxs, cos_sims)#将新闻索引和相似度值进行配对,形成一个元组列表。
        tup = sorted(tup, key=lambda x: x[1], reverse=True)#根据相似度值对元组列表进行排序,按照降序排列。
        ranked_sims_in_n_days.append(tup)#将排名后的相似度元组列表添加到 ranked_sims_in_n_days 列表中。
    pickle.dump(ranked_sims_in_n_days, open(
        './data/{}/{}_{}d.pkl'.format(dataset, split_name, days), 'wb'))#将包含排名相似度的列表 ranked_sims_in_n_days 以二进制形式保存到指定的文件路径中。

生成的pkl文件:

# json文件
{
	"date": "2023-07-20",
	"news_indexes": [1,5,8,12],
	"cosine_sims": [0.98, 0.87, 0.92, 0.95]
}

表示在日期为2023年7月20日的帖子与新闻之间的相似度列表,每个date都有一个与之关联的列表(前几天)

perception

在这里插入图片描述

main.py

from ModelFramework import EnvEnhancedFramework

# 加载模型
    model = EnvEnhancedFramework(args)

ModelFramework.py
在这里插入图片描述

from NewsEnvExtraction import NewsEnvExtraction

class EnvEnhancedFramework(nn.Module):
    def __init__(self, args):
        super(EnvEnhancedFramework, self).__init__()

        self.args = args

        # === FEND ===
        if args.use_fake_news_detector:
            self.fake_news_detector = eval('{}(args)'.format(args.model))
            last_output = self.fake_news_detector.last_output
        else:
            last_output = 0

        # === Env ===
        if args.use_news_env:
        	# 这个模型用于提取新闻环境特征
            self.news_env_extractor = NewsEnvExtraction(args)

            #使用注意力机制进行融合
            if self.args.strategy_of_fusion == 'att':
                self.macro_multihead_attn = nn.MultiheadAttention(
                    args.multi_attention_dim, num_heads=8, dropout=0.5)
                self.micro_multihead_attn = nn.MultiheadAttention(
                    args.multi_attention_dim, num_heads=8, dropout=0.5)
            #使用门控机制进行融合
            elif self.args.strategy_of_fusion == 'gate':
                assert args.macro_env_output_dim == args.micro_env_output_dim
                self.W_gate = nn.Linear(
                    self.fake_news_detector.last_output + args.macro_env_output_dim, args.macro_env_output_dim)

        # === MLP layers ===
        # 根据融合策略的不同,构建不同的MLP层
        if args.use_news_env:
            if self.args.strategy_of_fusion == 'concat':
                last_output += args.macro_env_output_dim + args.micro_env_output_dim
            elif self.args.strategy_of_fusion == 'att':
                last_output += 2 * args.multi_attention_dim
            elif self.args.strategy_of_fusion == 'gate':
                last_output += args.macro_env_output_dim

        self.fcs = []
        for _ in range(args.num_mlp_layers - 1):
            curr_output = int(last_output / 2)
            self.fcs.append(nn.Linear(last_output, curr_output))
            last_output = curr_output
        self.fcs.append(nn.Linear(last_output, args.category_num))
        self.fcs = nn.ModuleList(self.fcs)

NewsEnvExtraction.py
在这里插入图片描述

class NewsEnvExtraction(nn.Module):
    def __init__(self, args) -> None:
        super(NewsEnvExtraction, self).__init__()
        self.args = args

        #高斯核函数的均值和标准差(转成张量)
        self.kernel_mu = self.tensorize(kernel_mu)
        self.kernel_sigma = self.tensorize(kernel_sigma)

        #获取宏观环境和微观环境的输出维度
        self.macro_env_output_dim = args.macro_env_output_dim
        self.micro_env_output_dim = args.micro_env_output_dim
        # self.news_env_output_dim = args.news_env_output_dim

        macro_output = 0
        # 使用新闻的语义信息
        if self.args.use_semantics_of_news_env:
            macro_output += 2 * args.bert_hidden_dim
        #使用新闻的相似度信息
        if self.args.use_similarity_of_news_env:
            macro_output += len(kernel_mu)

        self.macro_mlp = nn.Linear(macro_output, self.macro_env_output_dim)

        micro_output = 0
        if self.args.use_semantics_of_news_env:
            self.micro_sem_mlp = nn.Linear(
                2 * args.bert_hidden_dim, self.micro_env_output_dim)
            micro_output += self.micro_env_output_dim
        if self.args.use_similarity_of_news_env:
            self.micro_sim_mlp = nn.Linear(
                2 * len(kernel_mu), self.micro_env_output_dim)
            micro_output += self.micro_env_output_dim

        self.micro_mlp = nn.Linear(micro_output, self.micro_env_output_dim)

    def forward(self, post_idxs, dataset):
        """
        post_idxs: the indexes of posts
        dataset: an instance of DatasetLoader in `DatasetLoader.py`
        """
        # ------------------ Macro Env ------------------
        if not self.args.use_semantics_of_news_env:
            p = None
            avg_emac = None
        else:
            # (bs, 768)
            # 根据索引获取对应帖子的语义信息
            p = [dataset.SimCSERepr['post'][idx.item()] for idx in post_idxs]
            p = self.tensorize(p)

            # (bs, 768)
            # 获取每个帖子的宏观环境语义信息
            avg_emac = [dataset.MacroEnvAvg[idx.item()] for idx in post_idxs]
            avg_emac = self.tensorize(avg_emac)

        if not self.args.use_similarity_of_news_env:
            kernel_p_emac = None
        else:
            # p_emac_sims = [dataset.SimDict['p-mac'][idx.item()]
            #                for idx in post_idxs]
            # # (bs, #kernels)
            # kernel_p_emac = [self.gaussian_kernel_pooling(
            #     sims) for sims in p_emac_sims]
            # kernel_p_emac = self.tensorize(kernel_p_emac)

            # 获取对应帖子的相似性信息
            kernel_p_emac = [dataset.KernelFeatures['p-mac'][idx.item()]
                             for idx in post_idxs]
            kernel_p_emac = self.normalize(self.tensorize(kernel_p_emac))

        # 将p、avg_emac和kernel_p_emac三者中不为None的部分组合成列表vectors
        vectors = [x for x in [p, avg_emac, kernel_p_emac] if x is not None]

        # (bs, macro_env_output_dim)
        v_p_mac = torch.cat(vectors, dim=-1)
        v_p_mac = self.macro_mlp(v_p_mac)

        # ------------------ Micro Env ------------------
        if not self.args.use_semantics_of_news_env:
            usem = None
        else:
            # (bs, 768)
            avg_emic = [dataset.MicroEnvAvg[idx.item()] for idx in post_idxs]
            avg_emic = self.tensorize(avg_emic)

            # (bs, 2 * 768) -> (bs, micro_env_output_dim)
            usem = torch.cat([p, avg_emic], dim=1)
            usem = self.micro_sem_mlp(usem)

        if not self.args.use_similarity_of_news_env:
            usim = None
        else:
            # p_emic_sims = [dataset.SimDict['p-mic'][idx.item()]
            #                for idx in post_idxs]
            # # (bs, #kernels)
            # kernel_p_emic = [self.gaussian_kernel_pooling(
            #     sims) for sims in p_emic_sims]
            # kernel_p_emic = self.tensorize(kernel_p_emic)

            kernel_p_emic = [dataset.KernelFeatures['p-mic'][idx.item()]
                             for idx in post_idxs]
            kernel_p_emic = self.normalize(self.tensorize(kernel_p_emic))

            # avgmic_emic_sims = [dataset.SimDict['avgmic-mic'][idx.item()]
            #                     for idx in post_idxs]
            # # (bs, #kernels)
            # kernel_avgmic_emic = [self.gaussian_kernel_pooling(
            #     sims) for sims in avgmic_emic_sims]
            # kernel_avgmic_emic = self.tensorize(kernel_avgmic_emic)

            # KernelFeatures是在dataloader类中创建并存储的一个字典,p-mac, p-min, avgmic-mic
            kernel_avgmic_emic = [dataset.KernelFeatures['avgmic-mic'][idx.item()]
                                  for idx in post_idxs]
            kernel_avgmic_emic = self.normalize(
                self.tensorize(kernel_avgmic_emic))

            # Comparision
            # (bs, 2 * #kernels) -> (bs, micro_env_output_dim)
            usim = torch.cat([kernel_p_emic * kernel_avgmic_emic,
                              kernel_p_emic - kernel_avgmic_emic], dim=1)
            # print('usim: ', usim.shape, usim)
            usim = self.micro_sim_mlp(usim)

        vectors = [x for x in [usim, usem] if x is not None]

        # (bs, micro_env_output_dim)
        v_p_mic = torch.cat(vectors, dim=-1)
        # print('v_p_mic: ', v_p_mic.shape, v_p_mic)
        v_p_mic = self.micro_mlp(v_p_mic)
        # print('after micro_mlp, v_p_mic: ', v_p_mic)

        # # --------- Interaction (eg: Multi-head Attention) ---------

        # # Strategy1: Concat
        # out = torch.cat([v_p_mac, v_p_mic], dim=1)
        # out = self.mlp(out)

        # --------- In-batch Learning ---------
        h_mac = None
        h_mic = None

        if not self.args.use_macro_env:
            v_p_mac = torch.zeros_like(v_p_mac, device=self.args.device)
        if not self.args.use_micro_env:
            v_p_mic = torch.zeros_like(v_p_mic, device=self.args.device)

        return v_p_mac, v_p_mic, h_mac, h_mic

DatasetLoader.py

self.SimCSERepr = tmp_dict['SimCSERepr']
self.MacroEnvAvg = tmp_dict['MacroEnvAvg']
self.MicroEnvAvg = tmp_dict['MicroEnvAvg']
self.SimDict = tmp_dict['SimDict']
self.KernelFeatures = tmp_dict['KernelFeatures']

借鉴:公式、案例分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值