一文了解:LLM Dropout

咱们来聊聊,啥样的预测模型才算是好模型?简单来说,就是那种在处理它从没见过的数据时,也能表现得特别棒的模型。老派的泛化理论告诉我们,要想让模型在训练集和测试集上都表现差不多,就得让模型简单点。这个简单,可以是参数少一点,还可以权重衰减,其实就是让参数的值别太大,这也是让模型保持简单性的一个好方法。还有一个概念就是平滑性,意思是说,模型对输入数据的小变化应该不那么敏感。比如在图像分类任务中,我们希望即使图像上有点随机噪声,模型也能正确地识别出来。

时间快进到1995年,有个叫克里斯托弗·毕晓普的大佬,他把这种想法变成了数学上的理论。他证明了,如果在训练时给输入数据加上点噪声,这实际上就相当于做了Tikhonov正则化。他的工作把函数需要平滑(也就是简单)的要求,和对输入扰动的抵抗力,这两者之间的数学关系给说清楚了。

然后到了2014年,Srivastava他们想出了一个绝妙的点子,就是怎么把毕晓普的想法用到神经网络的内部层。他们建议在训练的时候,在计算下一层之前,给网络的每一层都注入点噪声。他们发现,这样做可以让深层神经网络的输入输出映射更加平滑。

他们提出的这个点子,叫做Dropout,现在已经成了训练神经网络的标准操作了。这个操作之所以叫dropout,是因为在训练过程中,我们真的会"丢掉"一些神经元。每次迭代的时候,我们会随机把一些节点的输出置为零,这样下一层的计算就不会用到这些节点了。

直白一些,Dropout是深度神经网络里用来防止模型太复杂、记性太好的一种技巧。就像是,我们故意让网络忘记一些东西,这样它就不会把所有的训练数据都记得太死,从而在遇到新数据时能更好地适应。

简单来说,神经网络就像是一堆小团队,每个团队都忙着处理信息。但是,如果每个团队都只关注自己的工作,不跟别人合作,那整个网络处理信息的能力就会受到影响。所以,dropout就是在训练的时候,随机让一些团队暂时离线,这样剩下的团队就得学会在缺少某些信息的情况下工作。

为啥这么做呢?因为我们不想让模型只在训练数据上表现好,而是真的想让它在现实世界中也能派上用场。如果模型对训练数据记得太清楚,它可能就会忽略掉那些它没见过的新信息,这就是所谓的过拟合。Dropout就是通过让网络"忘记"一些信息,来防止这种情况发生的。

Dropout层的意思就是,在神经网络中故意忽略掉一些信息,好让模型不会过于依赖某些特定的数据点。

就好比我们的大脑,随时都在处理一大堆乱七八糟的信息,但不是所有的信息都是重要的。神经网络也是这样,它的每个单元都在忙忙碌碌地接收和发送信息,但这些信息里头,有的是关键的,有的可能就是干扰项。

数据科学家们用dropout这个技巧,就是想在模型学习的过程中,随机地让一些单元不工作,这样就能减少那些干扰项的影响。具体操作起来,他们会在神经网络的不同层用不同的策略来应用dropout:

  1. 输入层:这就像是神经网络的大门,原始数据就是从这儿进来的。如果有些数据跟我们要解决的问题没啥关系,就可以在这儿用dropout忽略掉。

  2. 中间层或隐藏层:这些层就像是神经网络的加工车间,数据进来后在这里经过一系列的处理。但是,这些处理的结果并不都是最终答案,很多都只是半成品。因为这些半成品里头可能包含了不少噪声,所以数据科学家们会在这些层用dropout来减少噪声。

  3. 输出层:这是神经网络给出最终答案的地方,这里的信息都是最重要的,所以通常不会用dropout。

总的来说,dropout就是数据科学家们用来提高模型泛化能力,避免过拟合的一种手段。通过dropout,可以让模型更加专注于学习那些真正重要的信息。

Dropout的实际例子和用场挺多的,我给你举两个例子来说明它是咋回事:

  1. 太空信号监听:想象一下,有个组织专门监控从宇宙深处传来的声音,他们想找的是那些重复的、有规律的信号,因为这可能意味着外星生命的存在。他们把收集到的声音信号送到神经网络里进行分析。在分析前,科学家们会先筛掉那些听起来乱七八糟、没啥规律的声音。同时,他们还会在神经网络的中间处理环节,故意忽略掉一部分数据,这样可以减少计算量,加快找出结果的速度。

  2. 生化公司研发新材料:再比如,有一家生化公司想研发一种全新的塑料。他们已经知道需要哪些基本元素来组成这种塑料,但具体的配方还不知道。为了节省研究时间和计算资源,他们用了一个神经网络来筛选全球的相关研究资料,但这个网络只关注那些直接和这种分子及其元素有关的研究,其他不相关的信息都会被自动过滤掉。这样做的好处是,他们的AI模型就不会出现过拟合,也就是不会因为数据太杂太乱而做出错误的预测。

最后来聊聊大型语言模型里怎么用dropout来帮忙模型学得更好?

首先,咱们得决定在哪里加dropout。通常是在词嵌入完了之后、每个处理层(比如Transformer或RNN)之后,有时候甚至在输出前也会加一点。

接下来,得挑个合适的dropout率,这个得靠试。一般这个率在0.1到0.5之间,每层可能还不一样,有时候中间那些层可能需要更大一点。

然后,得多做实验,用交叉验证来看看不同的dropout率哪个最好。这个过程可以用些自动化的工具来帮忙。

训练的时候,每次都得随机让一些神经元不工作,但测试的时候就全用上,这样模型就能好好表现了。

如果是特别大的模型,可能得多用点dropout,因为大模型容易记住太多东西,不太容易适应新情况。

dropout还可以和其他方法一起用,比如让权重小一点或者用更多的数据,这样模型能更好地适应各种情况。

训练的时候得看着点,比如损失是不是小了,准确率是不是高了,这些都是dropout有没有用的信号。

还可以用些工具来搞清楚dropout到底是咋帮忙的。

最后,训练和测试的时候dropout得保持一致,别一会儿用一会儿不用的。

用dropout就是为了让模型别太死记硬背,碰到新情况也能应对。这个得好好调调,确保它真能帮上忙,但又不会让模型学得太差。

接下来,我们分别用mxnet,pytorch 及 tensorflow 来简单以代码的方式来从头开始实现一下,注意不保证代码的完整性,只做参考。

要实现单层的dropout函数,我们必须根据层的维度从伯努利(二元)随机变量中抽取尽可能多的样本,其中随机变量以概率(1-p)取值(1)(保留)和概率(p)取值(0)(丢弃)。一种简单的实现方法是首先从均匀分布(U[0, 1])中抽取样本。然后我们可以保留对应样本大于(p)的节点,丢弃其余的。

在下面的代码中,我们实现了一个dropout_layer函数,它以概率dropout丢弃张量输入X中的元素,然后按照上述描述重新缩放剩余部分:将幸存者除以1.0-dropout

复制代码
from mxnet import autograd, gluon, init, np, npx
from mxnet.gluon import nn
from d2l import mxnet as d2l

npx.set_np()

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在这种情况下,所有元素都被丢弃
    if dropout == 1:
        return np.zeros_like(X)
    # 在这种情况下,所有元素都被保留
    if dropout == 0:
        return X
    mask = np.random.uniform(0, 1, X.shape) > dropout
    return mask.astype(np.float32) * X / (1.0 - dropout)

import torch
from torch import nn
from d2l import torch as d2l

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在这种情况下,所有元素都被丢弃
    if dropout == 1:
        return torch.zeros_like(X)
    # 在这种情况下,所有元素都被保留
    if dropout == 0:
        return X
    mask = (torch.rand(X.shape) > dropout).float()
    return mask * X / (1.0 - dropout)

import tensorflow as tf
from d2l import tensorflow as d2l

def dropout_layer(X, dropout):
    assert 0 <= dropout <= 1
    # 在这种情况下,所有元素都被丢弃
    if dropout == 1:
        return tf.zeros_like(X)
    # 在这种情况下,所有元素都被保留
    if dropout == 0:
        return X
    mask = tf.random.uniform(
        shape=tf.shape(X), minval=0, maxval=1) < 1 - dropout
    return tf.cast(mask, dtype=tf.float32) * X / (1.0 - dropout)

我们可以在一些示例上测试dropout_layer函数。在下面的代码行中,我们以概率0、0.5和1通过dropout操作传递我们的输入X

复制代码
X = np.arange(16).reshape(2, 8)
print(dropout_layer(X, 0))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1))

X= torch.arange(16, dtype = torch.float32).reshape((2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))

X = tf.reshape(tf.range(16, dtype=tf.float32), (2, 8))
print(X)
print(dropout_layer(X, 0.))
print(dropout_layer(X, 0.5))
print(dropout_layer(X, 1.))

接下来,定义模型参数,我们可以使用Fashion-MNIST数据集。下面定义了一个具有两个隐藏层,每个隐藏层包含256个单元的MLP。

复制代码
num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

W1 = np.random.normal(scale=0.01, size=(num_inputs, num_hiddens1))
b1 = np.zeros(num_hiddens1)
W2 = np.random.normal(scale=0.01, size=(num_hiddens1, num_hiddens2))
b2 = np.zeros(num_hiddens2)
W3 = np.random.normal(scale=0.01, size=(num_hiddens2, num_outputs))
b3 = np.zeros(num_outputs)

params = [W1, b1, W2, b2, W3, b3]
for param in params:
    param.attach_grad()

num_inputs, num_outputs, num_hiddens1, num_hiddens2 = 784, 10, 256, 256

num_outputs, num_hiddens1, num_hiddens2 = 10, 256, 256

这个时候,可以定义模型了。

下面的模型在每个隐藏层的输出(激活函数之后)应用dropout。我们可以为每层分别设置dropout概率。一个常见的趋势是在接近输入层的地方设置较低的dropout概率。下面我们将第一层和第二层的dropout概率分别设置为0.2和0.5。我们确保dropout只在训练期间激活。

复制代码
dropout1, dropout2 = 0.2, 0.5

def net(X):
    X = X.reshape(-1, num_inputs)
    H1 = npx.relu(np.dot(X, W1) + b1)
    # 仅在训练模型时使用dropout
    if autograd.is_training():
        # 在第一个全连接层之后添加一个dropout层
        H1 = dropout_layer(H1, dropout1)
    H2 = npx.relu(np.dot(H1, W2) + b2)
    if autograd.is_training():
        # 在第二个全连接层之后添加一个dropout层
        H2 = dropout_layer(H2, dropout2)
    return np.dot(H2, W3) + b3

dropout1, dropout2 = 0.2, 0.5

class Net(nn.Module):
    def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,
                 is_training = True):
        super(Net, self).__init__()
        self.num_inputs = num_inputs
        self.training = is_training
        self.lin1 = nn.Linear(num_inputs, num_hiddens1)
        self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)
        self.lin3 = nn.Linear(num_hiddens2, num_outputs)
        self.relu = nn.ReLU()

    def forward(self, X):
        H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))
        # 仅在训练模型时使用dropout
        if self.training == True:
            # 在第一个全连接层之后添加一个dropout层
            H1 = dropout_layer(H1, dropout1)
        H2 = self.relu(self.lin2(H1))
        if self.training == True:
            # 在第二个全连接层之后添加一个dropout层
            H2 = dropout_layer(H2, dropout2)
        out = self.lin3(H2)
        return out

net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)

dropout1, dropout2 = 0.2, 0.5

class Net(tf.keras.Model):
    def __init__(self, num_outputs, num_hiddens1, num_hiddens2):
        super().__init__()
        self.input_layer = tf.keras.layers.Flatten()
        self.hidden1 = tf.keras.layers.Dense(num_hiddens1, activation='relu')
        self.hidden2 = tf.keras.layers.Dense(num_hiddens2, activation='relu')
        self.output_layer = tf.keras.layers.Dense(num_outputs)

    def call(self, inputs, training=None):
        x = self.input_layer(inputs)
        x = self.hidden1(x)
        if training:
            x = dropout_layer(x, dropout1)
        x = self.hidden2(x)
        if training:
            x = dropout_layer(x, dropout2)
        x = self.output_layer(x)
        return x

net = Net(num_outputs, num_hiddens1, num_hiddens2)

最后,进行训练和测试。

复制代码
num_epochs, lr, batch_size = 10, 0.5, 256
loss = gluon.loss.SoftmaxCrossEntropyLoss()
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs,
              lambda batch_size: d2l.sgd(params, lr, batch_size))

num_epochs, lr, batch_size = 10, 0.5, 256
loss = nn.CrossEntropyLoss(reduction='none')
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = torch.optim.SGD(net.parameters(), lr=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

num_epochs, lr, batch_size = 10, 0.5, 256
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
trainer = tf.keras.optimizers.SGD(learning_rate=lr)
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

相关推荐
NAGNIP1 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab3 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab3 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP6 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年6 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼7 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS7 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区8 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈8 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang8 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx