基于Transformer的诗句生成

在这里插入图片描述

前言

相关介绍

Transformer是一个用来替代RNN和CNN的新的网络结构,它能够直接获取全局的信息,而不像RNN需要逐步递归才能获得全局信息,也不像CNN只能获取局部信息。Transformer本质上就是一个Attention结构,并且其能够进行并行运算,要比RNN快上很多倍。

Transformer

Transformer是一种基于自注意力机制的深度学习模型,它在自然语言处理领域取得了巨大的成功。

一、基本原理与结构

Transformer的核心是自注意力机制,它允许模型在处理每个输入元素时,能够关注到序列中的所有其他元素,从而捕捉到长距离依赖关系。这种机制使得Transformer在处理自然语言任务时,能够更准确地理解文本的含义和上下文。

Transformer的结构包括两个主要部分:编码器和解码器。编码器由多个相同的层组成,每个层又包括一个多头自注意力子层和一个全连接层。解码器也由多个相同的层组成,但还包括一个额外的多头自注意力子层,用于捕捉编码器输出的信息。解码器的最后一层是一个全连接层,用于将解码器的输出转换成一个特定任务所需的形式。

二、关键技术
  1. 位置编码:为了使模型能够理解输入序列中的位置信息,Transformer在输入序列中添加了位置编码。这种编码可以使得模型更好地理解输入序列中的词语顺序和语义信息。
  2. 自注意力机制:这是Transformer的核心技术之一,它可以自动地捕捉输入序列中的长距离依赖关系。在自注意力机制中,每个词语都可以通过与其他词语的交互来获得一个新的表示形式。
  3. 残差连接和层归一化:为了缓解深度神经网络中的梯度消失问题,Transformer在模型的每个层之后添加了残差连接,并进行了层归一化。
三、应用领域

Transformer在自然语言处理领域取得了广泛的应用,包括但不限于:

  1. 语言建模:根据上下文预测下一个词语。
  2. 机器翻译:将一种语言翻译成另一种语言。
  3. 文本分类:将给定的文本分为不同的类别。
  4. 问答系统:自动地回答问题。

此外,Transformer还被应用于图像处理、时间序列分析等其他领域的序列建模任务。

四、优缺点

优点

  1. 长距离依赖关系建模:通过自注意力机制,Transformer能够有效捕捉长距离依赖关系,适用于处理长序列和涉及远距离语义关系的任务。
  2. 并行计算能力:多头注意力机制的并行计算特性极大提高了训练和推理效率,支持更大规模的模型和更长序列的处理。
  3. 通用性:Transformer的架构不仅适用于自然语言处理领域,还适用于图像处理、时间序列分析等其他领域的序列建模任务。

缺点

  1. 高计算成本:复杂的模型结构导致训练和推理过程中需要大量计算资源,尤其是在处理大规模数据集时。
  2. 优化难度:模型的复杂性和超参数数量增加了解决方案的难度,需要仔细调整学习率、批量大小等参数以获得最佳性能。
  3. 对长文本处理挑战:在处理长文本时,可能因位置编码和注意力机制的限制而受到内存限制和效率影响。
  4. 对特定任务需要大量数据:对于某些复杂语义关系和细粒度分类任务,Transformer可能需要大量标注数据才能发挥最佳性能。

综上所述,Transformer作为一种重要的深度学习模型,在自然语言处理领域取得了显著的成功。然而,它也存在一些挑战和限制,需要在实际应用中根据具体任务和数据特点进行选择和优化。

Transformer应用:诗句生成

基于Transformer的诗句生成是自然语言处理领域中的一个创新应用,它利用Transformer模型的强大能力,实现了自动化、高质量的诗歌创作。

  1. 模型背景

    • Transformer模型是近年来自然语言处理领域的重大突破,它通过自注意力机制,能够高效捕捉序列中不同位置之间的依赖关系,实现上下文感知。
    • 在诗歌生成任务中,Transformer模型能够利用大规模古诗词数据集进行训练,学习诗歌的韵律结构、文学风格等特征,从而生成符合传统格式和要求的诗句。
  2. 模型架构

    • 基于Transformer的诗句生成模型通常包括嵌入层、多头自注意力层、前馈神经网络层等关键组件。
    • 嵌入层将输入的词语转化为向量表示,捕捉词与词之间的语义关联。
    • 多头自注意力层用于捕捉词语间的依赖关系,特别是长距离依赖,这是Transformer模型的核心机制。
    • 前馈神经网络层则对注意力层的输出进行进一步处理,生成最终的诗句。
  3. 训练过程

    • 数据集准备:收集大规模的古诗词数据集,进行预处理,如分词、编码、归一化等步骤。
    • 模型训练:利用预处理后的数据集对Transformer模型进行训练,通过优化损失函数来提高模型的生成能力。
    • 微调与优化:根据具体任务需求,对模型进行微调,以进一步提高生成诗句的质量和文学性。
  4. 应用场景

    • 基于Transformer的诗句生成模型可以应用于多种场景,如自动化诗歌创作、诗歌风格转换、诗歌推荐等。
    • 通过输入关键词或主题,模型可以生成相关的诗句作品,为用户提供便捷的创作体验。
优缺点
  1. 优点

    • 高效性:Transformer模型采用并行计算方式,提高了处理速度,适用于大规模数据集的训练和应用。
    • 上下文感知:通过自注意力机制,Transformer模型能够捕捉序列中不同位置之间的依赖关系,实现上下文感知,从而生成更符合逻辑和语境的诗句。
    • 泛化能力强:通过大规模的无监督预训练和微调过程,Transformer模型可以学习到丰富的语言知识和模式,提高了模型的泛化能力。
  2. 缺点

    • 数据要求高:Transformer模型需要大量的数据和计算资源来进行预训练和微调。在诗歌生成任务中,需要收集大规模的古诗词数据集,并进行精细的预处理工作。
    • 解释性差:由于Transformer模型的内部结构比较复杂,难以解释和理解其生成诗句的具体过程和依据。这在一定程度上限制了模型在某些领域的应用。
    • 长距离依赖关系捕捉能力有限:虽然Transformer模型能够捕捉长距离依赖关系,但随着序列长度的增加,模型的性能可能会受到一定影响。在诗歌生成任务中,这可能导致生成的诗句在逻辑和语境上存在一定的不连贯性。

综上所述,基于Transformer的诗句生成模型具有高效性、上下文感知和泛化能力强等优点,但也存在数据要求高、解释性差和长距离依赖关系捕捉能力有限等缺点。在应用该模型时,需要根据具体任务需求和数据特点进行选择和优化。

前提条件

实验环境

Package                       Version
----------------------------- ------------
matplotlib                    3.3.4
numpy                         1.19.5
Pillow                        8.4.0
pip                           21.2.2
protobuf                      3.19.6
requests                      2.27.1
scikit-learn                  0.24.2
scipy                         1.5.4
sentencepiece                 0.1.91
setuptools                    58.0.4
threadpoolctl                 3.1.0
thulac                        0.2.2
tokenizers                    0.9.3
torch                         1.9.1+cu111
torchaudio                    0.9.1
torchvision                   0.10.1+cu111
tornado                       6.1
tqdm                          4.64.1
traitlets                     4.3.3
transformers                  3.5.1
urllib3                       1.26.20

基于Transformer的诗句生成

准备数据集

在这里插入图片描述

  • data_splited++.jl:该文件是预处理的诗句文本数据集文件。
  • id2w++.json:该文件是ID到词的映射文件。
  • w2id++.json:该文件是词到ID的映射文件。

读取数据集

import json
from tqdm import tqdm
import torch
import time
with open('w2id++.json', 'r') as f:
    w2id = json.load(f)
with open('id2w++.json', 'r') as f:
    id2w = json.load(f)
    
data_list = []
with open('data_splited++.jl', 'r') as f:
    for l in f:
        data_list.append(json.loads(l))

分割数据集

# 根据词数分割数据
dlx = [[] for _ in range(5)]
for d in data_list:
    dlx[len(d[0]) - 5].append(d)

设置相关参数

batch_size = 128
# data_workers = 4 # windows下不设置为0,会报页面错误
data_workers = 0
learning_rate = 0.0001
gradient_accumulation_steps = 1
max_train_epochs = 1
warmup_proportion = 0.05
weight_decay=0.01
max_grad_norm=1.0
cur_time = time.strftime("%Y-%m-%d_%H:%M:%S")
device = torch.device('cuda')

这些参数通常用于配置深度学习模型的训练过程,特别是在使用PyTorch这样的深度学习框架时。下面是对每个参数的解释:

  1. batch_size = 128:

    • 批大小(Batch Size)是指在模型训练过程中,一次迭代(iteration)所使用的数据样本数量。这里设置为128,意味着每次更新模型参数前,会使用128个样本来计算损失和梯度。较大的批大小可以加速训练,但也可能增加内存消耗并影响模型的泛化能力。
  2. data_workers = 0:

    • 数据加载工作线程数(Data Workers)是指用于并行加载数据的线程数量。设置为0意味着数据加载将在主线程上同步进行,这可能会降低数据加载的速度。通常,增加工作线程数可以加速数据加载过程,但过多的线程可能会增加系统开销。
  3. learning_rate = 0.0001:

    • 学习率(Learning Rate)是控制模型参数更新幅度的超参数。较小的学习率意味着参数更新的步长较小,训练过程可能更稳定但收敛速度较慢;较大的学习率可能导致训练过程不稳定甚至发散。这里设置为0.0001是一个相对较小的值,适用于一些精细调整的场景。
  4. gradient_accumulation_steps = 1:

    • 梯度累积步数(Gradient Accumulation Steps)是指在更新模型参数前,累积梯量的次数。设置为1意味着每次迭代都会立即更新模型参数。在内存有限但希望使用较大批大小进行训练时,可以通过增加梯度累积步数来模拟较大的批大小。
  5. max_train_epochs = 30:

    • 最大训练轮数(Max Training Epochs)是指整个训练数据集被遍历的次数。一个epoch等于整个数据集通过模型一次。这里设置为30,意味着整个数据集将被遍历30次。
  6. warmup_proportion = 0.05:

    • 预热比例(Warmup Proportion)是指在训练初期,学习率逐渐增加所占整个训练过程的比例。预热可以帮助模型在训练初期更稳定地更新参数,避免由于初始学习率过高而导致的训练不稳定。这里设置为0.05,意味着在前5%的训练轮数中,学习率会逐渐增加。
  7. weight_decay = 0.01:

    • 权重衰减(Weight Decay)是一种正则化技术,用于防止模型过拟合。它通过向损失函数添加一个与模型参数平方成正比的项来实现,鼓励模型参数保持较小值。这里设置为0.01。
  8. max_grad_norm = 1.0:

    • 最大梯度范数(Max Grad Norm)是梯度裁剪(Gradient Clipping)的一种形式,用于控制梯度的最大值。如果梯度的范数超过这个值,梯度将被缩放以确保其范数不超过这个值。这有助于防止梯度爆炸问题。这里设置为1.0。
  9. cur_time = time.strftime(“%Y-%m-%d_%H:%M:%S”):

    • 这行代码用于获取当前时间,并将其格式化为字符串,通常用于生成具有时间戳的文件名或日志,以便记录训练过程。
  10. device = torch.device(‘cuda’):

    • 这行代码指定了模型和数据应该在哪种设备上运行。'cuda'表示使用NVIDIA的CUDA技术来加速计算,通常是在具有NVIDIA GPU的计算机上。如果系统中没有可用的CUDA设备,PyTorch将回退到CPU。

这些参数共同决定了模型训练的具体配置,包括训练速度、模型性能以及训练过程中的稳定性等。

创建自己DataSet对象

class MyDataSet(torch.utils.data.Dataset):
    def __init__(self, examples):
        self.examples = examples
    def __len__(self):
        return len(self.examples)
    def __getitem__(self, index):
        example = self.examples[index]
        s1 = example[0]
        s2 = example[1]
        return s1, s2, index
def str2id(s):
    ids = []
    for ch in s:
        if ch in w2id:
            ids.append(w2id[ch])
        else:
            ids.append(0)
    return ids
def the_collate_fn(batch):
    s1x = []
    s2x = []
    for b in batch:
        s1 = str2id(b[0])
        s2 = str2id(b[1])
        s1x.append(s1)
        s2x.append(s2)
    indexs = [b[2] for b in batch]
    s1 = torch.LongTensor(s1x)
    s2 = torch.LongTensor(s2x)
    return s1, s2, indexs

dldx = []
for d in dlx:
    ds = MyDataSet(d)
    dld = torch.utils.data.DataLoader(
        ds,
        batch_size=batch_size,
        shuffle = True,
        num_workers=data_workers,
        collate_fn=the_collate_fn,
    )
    dldx.append(dld)

定义网络模型和损失函数

import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn import TransformerEncoder, TransformerEncoderLayer

class PositionalEncoding(nn.Module):

    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)

        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0).transpose(0, 1)
        self.register_buffer('pe', pe)

    def forward(self, x):
        x = x + self.pe[:x.size(0), :]
        return self.dropout(x)


class TransformerModel(nn.Module):

    def __init__(self, ntoken, ninp, nhead, nhid, nlayers, dropout=0.5):
        super(TransformerModel, self).__init__()
        self.pos_encoder = PositionalEncoding(ninp, dropout)
        encoder_layers = TransformerEncoderLayer(ninp, nhead, nhid, dropout)
        self.transformer_encoder = TransformerEncoder(encoder_layers, nlayers)
        self.encoder = nn.Embedding(ntoken, ninp)
        self.ninp = ninp
        self.decoder = nn.Linear(ninp, ntoken)
        self.init_weights()

    def init_weights(self):
        initrange = 0.1
        self.encoder.weight.data.uniform_(-initrange, initrange)
        self.decoder.bias.data.zero_()
        self.decoder.weight.data.uniform_(-initrange, initrange)

    def forward(self, s1, s2=None):
        batch_size, length = s1.shape[:2]
        s1 = self.encoder(s1) * math.sqrt(self.ninp)
        s1 = self.pos_encoder(s1)
        output = self.transformer_encoder(s1)
        output = self.decoder(output)
        output = F.log_softmax(output, dim=2)
        if s2 is not None:
            criterion = nn.NLLLoss()
            loss = criterion(output.view(batch_size*length, -1), s2.view(batch_size*length))
            return loss
        return output
ntokens = len(w2id)
emsize = 300 # embedding dimension
nhid = 256 # the dimension of the feedforward network model in nn.TransformerEncoder
nlayers = 4 # the number of nn.TransformerEncoderLayer in nn.TransformerEncoder
nhead = 4 # the number of heads in the multiheadattention models
dropout = 0.2 # the dropout value
model = TransformerModel(ntokens, emsize, nhead, nhid, nlayers, dropout).to(device)

这段代码是在初始化一个基于Transformer模型的实例,用于自然语言处理(NLP)或其他序列到序列的任务。下面是对各个参数的解释:

  1. ntokens (len(w2id)): 这个参数表示词汇表的大小,即模型能够处理的唯一单词或标记的数量。w2id很可能是一个字典,将每个单词映射到一个唯一的ID。因此,len(w2id)就是这个字典中键值对的数量,也就是词汇表的大小。

  2. emsize (300): 这是嵌入层的维度大小,也称为嵌入向量的大小。每个单词或标记都会被转换成一个固定大小的向量(这里是300维),这个向量能够捕捉到单词的语义信息。

  3. nhid (256): 这个参数指的是Transformer模型中前馈神经网络(Feedforward Neural Network)的隐藏层大小。在Transformer的每个编码器层和解码器层中,都有一个前馈神经网络,它的输入和输出大小由这个参数决定(虽然中间层可能会有更大的大小,但这里的256通常指的是输入和输出的维度)。

  4. nlayers (4): 这个参数指定了Transformer模型中编码器或解码器层的数量。在这个例子中,模型有4个Transformer层,每个层都包含多头自注意力机制和前馈神经网络。

  5. nhead (4): 这个参数指定了多头注意力机制中“头”的数量。在多头注意力中,模型会并行地学习输入序列的不同表示(每个头学习一种),然后将这些表示合并起来。在这个例子中,有4个头,意味着输入会被分割成4个部分,每个部分独立地通过一个注意力机制进行处理。

  6. dropout (0.2): 这个参数指定了在训练过程中应用dropout的比例。Dropout是一种正则化技术,通过在训练过程中随机丢弃网络的一部分连接,来防止模型过拟合。这里的0.2意味着有20%的连接会被随机丢弃。

  7. model: 这是通过提供的参数初始化的Transformer模型实例。.to(device)是一个PyTorch的方法,用于将模型移动到指定的设备上(如CPU或GPU),以便进行训练或推理。

总的来说,这些参数共同定义了Transformer模型的结构和大小,以及训练过程中使用的正则化技术。通过调整这些参数,可以影响模型的性能、训练速度和所需的计算资源。

定义优化器

from transformers import AdamW, get_linear_schedule_with_warmup

t_total = len(data_list) // gradient_accumulation_steps * max_train_epochs + 1
num_warmup_steps = int(warmup_proportion * t_total)

print('warmup steps : %d' % num_warmup_steps)

no_decay = ['bias', 'LayerNorm.weight'] # no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
param_optimizer = list(model.named_parameters())
optimizer_grouped_parameters = [
    {'params':[p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],'weight_decay': weight_decay},
    {'params':[p for n, p in param_optimizer if any(nd in n for nd in no_decay)],'weight_decay': 0.0}
]
optimizer = AdamW(optimizer_grouped_parameters, lr=learning_rate)
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=num_warmup_steps, num_training_steps=t_total)

训练模型

def t2s(t):
    l = t.cpu().tolist()
    r = [id2w[x] for x in l[0]]
    return ''.join(r)

def get_next(s):
    ids = torch.LongTensor(str2id(s))
    print(s)
    ids = ids.unsqueeze(0).to(device)
    with torch.no_grad():
        r = model(ids)
        r = r.argmax(dim=2)
        return t2s(r)
def print_cases():
    print(get_next('好好学习') + '\n')
    print(get_next('白日依山尽') + '\n')
    print(get_next('学而时习之') + '\n')
    print(get_next('人之初性本善') + '\n')

loss_list = []
for e in range(max_train_epochs):
    print(e)
    loss_sum = 0
    c = 0
    xxx = [x.__iter__() for x in dldx]
    j = 0
    for i in tqdm(range((len(data_list)//batch_size) + 5)):
        if len(xxx) == 0:
            break
        j = j % len(xxx)
        try:
            batch = xxx[j].__next__()
        except StopIteration:
            xxx.pop(j)
            continue
        j += 1
        s1, s2, index = batch
        s1 = s1.to(device)
        s2 = s2.to(device)
        loss = model(s1, s2)
        loss_sum += loss.item()
        c += 1
        loss.backward()
        optimizer.step()
        scheduler.step() 
        optimizer.zero_grad()
    print_cases()
    print(loss_sum / c)
    loss_list.append(loss_sum / c)
warmup steps : 3350217
0
100%|██████████████████████████████████████████████████████████████████████████████| 8729/8729 [03:14<00:00, 44.90it/s]
好好学习
疃如颐钞

白日依山尽
郯疚毳察葩

学而时习之
匙彻察谩堪

人之初性本善
轸麻胶邵洁廛

8.991864916192085

......

58
100%|██████████████████████████████████████████████████████████████████████████████| 8729/8729 [03:18<00:00, 43.99it/s]
好好学习
时时为迟

白日依山尽
不年无日多

学而时习之
此无日不之

人之初性本善
我之无心不不

6.59523827047266

查看损失变化

from matplotlib import pyplot as plt
plt.figure(figsize=(9,6))
plt.plot([i for i in range(len(loss_list))], loss_list)

在这里插入图片描述

保存模型

torch.save(model.state_dict(), 'transform_model_parameter.pkl')
torch.save(model, 'transform_model.pkl')

测试模型

model = torch.load('transform_model.pkl')
model.load_state_dict(torch.load('transform_model_parameter.pkl'))

print_cases()
好好学习
时时为迟

白日依山尽
不年无日多

学而时习之
此无日不之

人之初性本善
我之无心不不

参考文献

[1] Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Lukasz Kaiser, Illia Polosukhin. Attention Is All You Need. 2023.
[2] https://blog.csdn.net/m0_47779101/article/details/128087403
[3] https://zhuanlan.zhihu.com/p/338817680
[4] https://zhuanlan.zhihu.com/p/407012757
[5] https://blog.csdn.net/FriendshipTang/article/details/132032019
[6] 论文地址:https://arxiv.org/abs/1706.03762

<think>好的,我需要回答用户关于基于Transformer生成式模型实现和应用的问题。首先,用户提到了几个引用,我需要确保在回答中正确引用这些内容。 用户的需求是了解Transformer生成式模型的实现和应用,我需要先概述Transformer的基本结构,然后分步骤讲解实现的关键点,比如预训练、微调等。接着,应用场景部分要覆盖文本、图像和多模态领域,每个部分举例子,比如GPT、DALL-E和Flamingo,这里要特别注意引用[2]和[3]提到的内容。 然后,生成相关问题部分要围绕实现细节、应用场景、优化方法等方面提问。需要确保问题有针对性,能够引导用户深入思考。 同时,要遵循系统指令,数学公式用$...$,独立公式用$$...$$,并且正确使用中文。还要检查LaTeX语法是否正确,比如注意转义字符和符号的正确使用。 现在,我需要组织内容结构,确保每个部分清晰,引用自然插入。比如在讲应用场景时提到图像分类,引用[2]的例子,在多模态部分引用[3]的Flamingo模型。最后的问题部分要涵盖实现、应用、优化、多模态和资源工具等方面,可能需要参考引用[1]提到的工具和资源推荐。 检查是否有遗漏的引用或格式错误,确保回答符合用户的所有要求。</think>### 基于Transformer生成式模型实现与应用 #### 一、Transformer核心架构 Transformer模型的核心是自注意力机制(Self-Attention),通过并行计算和长距离依赖建模提升生成能力。其数学表达为: $$ \text{Attention}(Q,K,V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V $$ 其中$Q$(查询)、$K$(键)、$V$(值)是输入矩阵的线性变换,$d_k$为维度缩放因子[^2]。 #### 二、实现关键步骤 1. **预训练任务设计** - 文本生成:掩码语言建模(如BERT)或自回归建模(如GPT) - 多模态:联合训练图像-文本对(如Flamingo的跨模态注意力层)[^3] 2. **微调策略** - 领域适配:通过小样本学习调整模型参数 - 工具链示例: ```python from transformers import GPT2LMHeadModel, GPT2Tokenizer tokenizer = GPT2Tokenizer.from_pretrained('gpt2') model = GPT2LMHeadModel.from_pretrained('gpt2', pad_token_id=tokenizer.eos_token_id) ``` 3. **解码优化** - 束搜索(Beam Search):保留多个候选序列 - 温度采样(Temperature Scaling):控制生成多样性 #### 三、典型应用场景 1. **文本生成** - 代码生成:GitHub Copilot基于Codex模型 - 创意写作:GPT-3生成小说/诗歌 2. **图像生成** - DALL-E系列:通过离散编码将文本映射到图像空间 - 公式示例:图像分类任务中Transformer特征提取可提升准确率[^2] 3. **多模态生成** - Flamingo模型:处理视频-文本联合生成任务,在低数据量下仍保持高质量输出[^3] - 医疗领域:生成放射报告与影像的关联分析 #### 四、性能优化方向 1. **稀疏注意力** 将注意力矩阵计算复杂度从$O(n^2)$降至$O(n\sqrt{n})$ 2. **模型蒸馏** 使用教师-学生架构压缩模型规模 3. **硬件适配** 通过张量并行(Tensor Parallelism)提升训练效率
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FriendshipT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值