负采样策略

负采样策略主要来源于NLP中的word2vec中,为了简化训练的过程而提出。

参考

https://blog.csdn.net/ningyanggege/article/details/87869393
https://blog.csdn.net/Oscar6280868/article/details/105678035
https://blog.csdn.net/weixin_40248634/article/details/103040193
https://blog.csdn.net/qq_38890412/article/details/107657600
https://blog.csdn.net/devil_son1234/article/details/107303243

负采样

训练一个神经网络意味着要输入训练样本并且不断调整神经元的权重,从而不断提高对目标的准确预测。每当神经网络经过一个训练样本的训练,它的权重就会进行一次调整。

所以,词典的大小决定了我们的Skip-Gram神经网络将会拥有大规模的权重矩阵,所有的这些权重需要通过数以亿计的训练样本来进行调整,这是非常消耗计算资源的,并且实际中训练起来会非常慢。

负采样(negative sampling)解决了这个问题,它是用来提高训练速度并且改善所得到词向量的质量的一种方法。不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。

在这里插入图片描述
当我们用训练样本 ( input word: “fox”,output word: “quick”) 来训练我们的神经网络时,“ fox”和“quick”都是经过one-hot编码的。如果我们的词典大小为10000时,在输出层,我们期望对应“quick”单词的那个神经元结点输出1,其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为“negative” word。

当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)来更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

PS: 在论文中,作者指出对于小规模数据集,选择5-20个negative words会比较好,对于大规模数据集可以仅选择2-5个negative words。

我们使用“一元模型分布(unigram distribution)”来选择“negative words”。个单词被选作negative sample的概率跟它出现的频次有关,出现频次越高的单词越容易被选作negative words。

每个单词被选为“negative words”的概率计算公式:
p ( w i ) = f ( w i ) 3 4 ∑ j = 0 n ( f ( w j ) 3 4 ) p(w_i)=\frac{f(w_i)^{\frac{3}4}}{\sum_{j=0}^{n}({f(w_j)}^{\frac{3}4})} p(wi)=j=0n(f(wj)43)f(wi)43

其中 f ( w i ) f(w_i) f(wi)代表着单词出现的频次,而公式中开3/4的根号完全是基于经验的。

在代码负采样的代码实现中,unigram table有一个包含了一亿个元素的数组,这个数组是由词汇表中每个单词的索引号填充的,并且这个数组中有重复,也就是说有些单词会出现多次。那么每个单词的索引在这个数组中出现的次数该如何决定呢,有公式,也就是说计算出的负采样概率*1亿=单词在表中出现的次数。

有了这张表以后,每次去我们进行负采样时,只需要在0-1亿范围内生成一个随机数,然后选择表中索引号为这个随机数的那个单词作为我们的negative word即可。一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。

从上述表达式可以看出,负样本我们不需要取所有的都拿来训练,我们只需要每个中心词抽几个负样本就可以了,这样可以大大降低计算的复杂度。这就是word2vec训练过程中的Negative Sampling 负采样技巧,可以大大减小梯度下降的时间复杂度,这就有点像SGD随机梯度下降,就是随机一个样本进行梯度下降,大体的方向还是朝着最低点下降。

好的,我来为你解答。 首先,我们需要导入必要的库: ```python import torch import torch.nn as nn import torch.nn.functional as F from torch.autograd import Variable ``` 然后,我们定义模型。这里以一个简单的自编码器为例: ```python class AutoEncoder(nn.Module): def __init__(self, input_dim, hidden_dim): super(AutoEncoder, self).__init__() self.encoder = nn.Linear(input_dim, hidden_dim) self.decoder = nn.Linear(hidden_dim, input_dim) def forward(self, x): encoded = F.relu(self.encoder(x)) decoded = F.relu(self.decoder(encoded)) return decoded ``` 接着,我们定义自对抗负采样损失函数。这里以RotatE中的负采样策略为例: ```python class NegativeSampling(nn.Module): def __init__(self, num_entities, num_relations, embedding_dim, margin=1.0, corruption_rate=0.5): super(NegativeSampling, self).__init__() self.num_entities = num_entities self.num_relations = num_relations self.embedding_dim = embedding_dim self.margin = margin self.corruption_rate = corruption_rate self.entity_embeddings = nn.Embedding(num_entities, embedding_dim) self.relation_embeddings = nn.Embedding(num_relations, embedding_dim) self.entity_embeddings.weight.data.uniform_(-1, 1) self.relation_embeddings.weight.data.uniform_(-1, 1) def forward(self, positive_samples): batch_size, num_sampled = positive_samples.shape[0], 1 negative_samples = torch.randint(low=0, high=self.num_entities, size=(batch_size, num_sampled)).to(positive_samples.device) corrupted_samples = torch.where(torch.rand(batch_size, self.embedding_dim).to(positive_samples.device) < self.corruption_rate, negative_samples, positive_samples) positive_scores = self._calc_score(positive_samples) negative_scores = self._calc_score(corrupted_samples) loss = F.relu(self.margin - positive_scores + negative_scores).mean() return loss def _calc_score(self, samples): head, relation, tail = torch.chunk(samples, chunks=3, dim=1) head_emb = self.entity_embeddings(head).view(-1, self.embedding_dim) relation_emb = self.relation_embeddings(relation).view(-1, self.embedding_dim) tail_emb = self.entity_embeddings(tail).view(-1, self.embedding_dim) score = torch.sum(head_emb * relation_emb * tail_emb, dim=1) return score ``` 最后,我们将两个损失函数结合起来,定义整个模型: ```python class Model(nn.Module): def __init__(self, input_dim, hidden_dim, num_entities, num_relations, embedding_dim, margin=1.0, corruption_rate=0.5): super(Model, self).__init__() self.autoencoder = AutoEncoder(input_dim, hidden_dim) self.negsampling = NegativeSampling(num_entities, num_relations, embedding_dim, margin, corruption_rate) def forward(self, x, positive_samples): reconstructed = self.autoencoder(x) loss_ae = F.mse_loss(reconstructed, x) loss_ns = self.negsampling(positive_samples) return loss_ae, loss_ns ``` 其中,`input_dim`表示自编码器的输入维度,`hidden_dim`表示自编码器的隐藏层维度,`num_entities`表示实体总数,`num_relations`表示关系总数,`embedding_dim`表示实体和关系的嵌入维度,`margin`表示自对抗负采样损失函数中的margin,`corruption_rate`表示负采样时,对每个实体的每个维度进行替换的概率。 使用模型时,我们需要提供自编码器的输入`x`和正样本`positive_samples`,并计算出总的损失函数: ```python model = Model(input_dim=100, hidden_dim=50, num_entities=10000, num_relations=100, embedding_dim=50) x = torch.randn(32, 100) positive_samples = torch.randint(low=0, high=10000, size=(32, 3), dtype=torch.long) loss_ae, loss_ns = model(x, positive_samples) total_loss = loss_ae + loss_ns ``` 以上就是使用torch定义一个包含自编码器损失和自对抗负采样损失函数的模型的示例。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值