Pytorch应用训练好的模型

这篇具有很好参考价值的文章主要介绍了Pytorch应用训练好的模型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1.保存训练好的模型:torch.save方法

保存训练好的模型有两种方式,第一种保存模型结构且保存模型参数,第一种方式存在一种陷阱,也就是每次加载模型都得把类定义,或者访问类所在的包。保存方式为:

torch.save(模型名, 以pth为后缀的文件)

第二种保存方式只保存模型参数,不保存模型结构,这样可以面对较大的网络模型,可以节省空间,是官方推荐的保存方式,具体为:

torch.save(模型名.state_dict(), 以pth为后缀的文件)

第一个参数,模型名.state_dict()意为只取模型的参数,且以字典方式存储;第二个参数存储模型的地址,一般都用以pth结尾的文件。
代码如下:

import torch
import torchvision
from torch import nn

vgg16 = torchvision.models.vgg16(pretrained=False)
# 保存方式1,模型结构+模型参数
torch.save(vgg16, "vgg16_method1.pth")

# 保存方式2,模型参数(官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")

# 陷阱
class test_Model(nn.Module):
    def __init__(self):
        super(test_Model, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3)

    def forward(self, x):
        x = self.conv1(x)
        return x

test_model = test_Model()
torch.save(test_model, "test_model_method1.pth")

2.加载之前保存的模型:torch.load方法

对应两种不同的保存模型的方式,也相应地有两种加载模型的方式,第一种方式为:

读取出的模型名 = torch.load(之前保存的模型文件名)

第二种方式为:

读取出的模型名 = torchvision.models.vgg16(pretrained=False)
读取出的模型名.load_state_dict(torch.load(之前保存的模型文件名))

因为第二种方法没有保存模型结构,所以我们要先设计一个模型结构,本例中用的是直接下载VGG16模型结构;然后第二条语句用于将保存的参数值传入到模型结构中。代码如下:

import torch
from model_save import *
import torchvision
from torch import nn

# 方式1-》保存方式1,加载模型
model = torch.load("vgg16_method1.pth")
# print(model)

# 方式2,加载模型
vgg16 = torchvision.models.vgg16(pretrained=False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
# 只读取参数
# model = torch.load("vgg16_method2.pth")
# print(vgg16)

# 方式1,陷阱
# class test_Model(nn.Module):
#     def __init__(self):
#         super(test_Model, self).__init__()
#         self.conv1 = nn.Conv2d(3, 64, kernel_size=3)
#
#     def forward(self, x):
#         x = self.conv1(x)
#         return x
model = torch.load('test_model_method1.pth')
print(model)

3.对于分类问题的补充

对于图像分类、目标检测或是语义分割等各种问题而言,单靠loss曲线还无法完全表现出模型的优劣,而在图像分类问题中,我们常用acc,即在测试集上的正确率来表示模型的训练情况及优劣。

在分类问题中,例如CIFAR10中,某张图片进入模型后的输出结果是形如([0.2, 0.1, 0.4, 0.6, 0.4, 0.4, 0.7, 0.2, 0.5, 0.1]),而targets是类似(5)这样的某个整型数字,表明该图片属于第5类。那么如和将输出结果与targets之间进行计算,产生正确率呢,我们采用argmax函数,该函数会找到数组中最大值并输出最大值的序号,那么如此一来,将输出结果的最大值序号和targets相比较,如果一致则说明该图像识别正确。代码如下:

import torch

output = torch.tensor([[0.2, 0.3],
                      [0.1, 0.5]])
# 纵向比较
print(output.argmax(0))
# 横向比较
print(output.argmax(1))

pred = output.argmax(1)
targets = torch.tensor([0, 1])
print(pred == targets)
# 输出正确的个数
print((pred == targets).sum())

注:因在验证过程中,因为不是一张图片,而是batch_size张图片验证,那么output应是二维数组,第二维的数目是batchsize个,此例中设为2个,相应的targets应是一个一维数组,数组中的每个数表示每张图对应的正确类别是哪类。另外,argmax(0)表示传入的数据纵向进行比较,在本例中是0.2和0.1比,0.3和0.5比;而argmax(1)表示传入的数据横向进行比较,在本例中是0.2和0.3比,0.1和0.5比,毫无疑问,运用在计算准确率中,我们应该使用的是argmax(1),那么记录所有output和targets相吻合的项的和除以总项数即可获得该batch_size的准确率。

4.CPU训练完整代码

CPU训练就是之前介绍的搭建模型和应用模型相结合,内容都是之前所讲,完整代码如下:

import torchvision
from torch.utils.tensorboard import SummaryWriter

from model import *
# 准备数据集
from torch import nn
from torch.utils.data import DataLoader

train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
tudui = Tudui()

# 损失函数
loss_fn = nn.CrossEntropyLoss()

# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    # 这句话在训练开始时调用,在有Dropout层和层起作用,在本例中可不写,但建议写,较为规范
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    # 这句话在测试开始时调用,在有Dropout层和层起作用,在本例中可不写,但建议写,较为规范
    tudui.eval()
    total_test_loss = 0
    # 整体正确个数
    total_accuracy = 0
    # 设置测试模型不改变梯度
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            # 计算正确个数
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    #此处用的模型保存方式为方式一,建议使用方式二
    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

5.GPU训练方法一

GPU训练大部分代码与CPU训练一致,调用GPU的方法有两种,第一种方式是在网络模型、损失函数、训练输入数据、测试输入数据四处加入自己 = 自己.cuda即可,即在不同的地方加入以下四句语句:

if torch.cuda.is_available():
    tudui = tudui.cuda()
if torch.cuda.is_available():
    loss_fn = loss_fn.cuda()
if torch.cuda.is_available():
	imgs = imgs.cuda()
	targets = targets.cuda()
        if torch.cuda.is_available():
            imgs = imgs.cuda()
            targets = targets.cuda()
        if torch.cuda.is_available():
            imgs = imgs.cuda()
            targets = targets.cuda()

因此第一种GPU训练的完整代码为:

import time

import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

# from model import *
# 准备数据集
from torch import nn
from torch.utils.data import DataLoader

train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
tudui = Tudui()
if torch.cuda.is_available():
    print("成功调用GPU")
    tudui = tudui.cuda()

# 损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():
    loss_fn = loss_fn.cuda()
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")
# 记录最初时间
start_time = time.time()

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        if torch.cuda.is_available():
            imgs = imgs.cuda()
            targets = targets.cuda()
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            # 记录结束时间
            end_time = time.time()
            print("每100次训练花费时间为:{}秒".format(end_time - start_time))
            # 重置开始时间
            start_time = time.time()
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            if torch.cuda.is_available():
                imgs = imgs.cuda()
                targets = targets.cuda()
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

6.GPU训练方法二

GPU第二种训练方式是先定义一个训练的设备,语句为device = torch.device(“cuda”),如果有多张显卡则语句为device = torch.device(“cuda:0”)代表用第0张显卡,要调用其它显卡则将0改成其它数字,然后将训练方法一中的.cuda改为.to(device),例如:

if torch.cuda.is_available():
    tudui = tudui.cuda()

则完整代码为:

import torch
import torchvision
from torch.utils.tensorboard import SummaryWriter

# from model import *
# 准备数据集
from torch import nn
from torch.utils.data import DataLoader

# 定义训练的设备
device = torch.device("cuda")

train_data = torchvision.datasets.CIFAR10(root="../data", train=True, transform=torchvision.transforms.ToTensor(),
                                          download=True)
test_data = torchvision.datasets.CIFAR10(root="../data", train=False, transform=torchvision.transforms.ToTensor(),
                                         download=True)

# length 长度
train_data_size = len(train_data)
test_data_size = len(test_data)
# 如果train_data_size=10, 训练数据集的长度为:10
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))


# 利用 DataLoader 来加载数据集
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 创建网络模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x
tudui = Tudui()
tudui = tudui.to(device)

# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
# 优化器
# learning_rate = 0.01
# 1e-2=1 x (10)^(-2) = 1 /100 = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10

# 添加tensorboard
writer = SummaryWriter("../logs_train")

for i in range(epoch):
    print("-------第 {} 轮训练开始-------".format(i+1))

    # 训练步骤开始
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        # 优化器优化模型
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{}, Loss: {}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    # 测试步骤开始
    tudui.eval()
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("整体测试集上的Loss: {}".format(total_test_loss))
    print("整体测试集上的正确率: {}".format(total_accuracy/test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy/test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")

writer.close()

7.GPU训练过程的细节优化

首先,对于网络模型和损失函数,不需要对其重新赋值,也就是说以下代码:

tudui = tudui.to(device)
loss_fn = loss_fn.to(device)
tudui = tudui.cuda()
loss_fn = loss_fn.cuda()

可以直接写为:

tudui.to(device)
loss_fn.to(device)
tudui.cuda()
loss_fn.cuda()

但imgs和targets不能省略前面的赋值。

其次,在方法二中可能出现没有GPU或是GPU未能成功调用,所以可以将代码:

device = torch.device("cuda")

改为:

device = torch.device("cuda" if torch.cuda.isavailable() else "CPU")

如此一来,如果无法调用GPU则会调用CPU训练,而不会报错。

8.验证模型

在训练并测试完之后,如果想要验证模型,类似与测试集的操作,可对某张特定图片或某些图片进行验证,观察其输出是否正确,代码如下:

import torch
import torchvision
from PIL import Image
from torch import nn

image_path = "../imgs/airplane.png"
image = Image.open(image_path)
print(image)
# 该语句是因为不是所有类型的图片都是三通道的,通过这句代码可以将所有类型的通篇都转化成3通道
image = image.convert('RGB')
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
                                            torchvision.transforms.ToTensor()])

image = transform(image)
print(image.shape)

class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(64*4*4, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x

# map_location属性是在,当保存的模型是GPU训练的,而现在我们要用CPU验证时需要添加这样一句话
model = torch.load("tudui_29_gpu.pth", map_location=torch.device('cpu'))
print(model)
image = torch.reshape(image, (1, 3, 32, 32))
model.eval()
with torch.no_grad():
    output = model(image)
print(output)

print(output.argmax(1))

注:因为训练是我们采用是第一种方式保存模型,所有加载模型时我们需要将模型的类再写一次,或者调用定义了模型类的文件;另外注意image = image.convert(‘RGB’)将各种不同的图片转化为三通道;最后注意如果保存的模型是GPU训练的,而现在我们要用CPU验证时需要添加这样一句话model = torch.load(“tudui_29_gpu.pth”, map_location=torch.device(‘cpu’))。文章来源地址https://www.toymoban.com/news/detail-403704.html

到了这里,关于Pytorch应用训练好的模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 浅谈一谈pytorch中模型的几种保存方式、以及如何从中止的地方继续开始训练;

    一、本文总共介绍3中pytorch模型的保存方式:1.保存整个模型;2.只保存模型参数;3.保存模型参数、优化器、学习率、epoch和其它的所有命令行相关参数以方便从上次中止训练的地方重新启动训练过程。 1.保存整个模型。这种保存方式最简单,保存内容包括模型结构、模型参数

    2024年01月17日
    浏览(32)
  • 【pytorch】使用训练好后的模型权重,在验证集上输出分类的混淆矩阵并保存错误图片

    在机器学习领域,混淆矩阵是一个非常有用的指标,它可以帮助我们更好地理解模型在验证集上的表现。本文介绍了如何使用pytorch框架,利用训练好后的模型权重,在验证集上输出分类的混淆矩阵,并保存错误图片的方法。 首先,我们需要准备一个pytorch框架的模型,并将模

    2024年02月13日
    浏览(28)
  • pytorch nn.Embedding 读取gensim训练好的词/字向量(有例子)

      *也许看了上面你依然会一脸懵(别着急,下面给你举个例子)

    2024年02月07日
    浏览(30)
  • Tensorflow调用训练好的yolov5模型进行推理

    conda search找找当前源下的CUDA与cuDNN有没有我们要的版本: Onnx(Open Neural Network Exchange)是一种开放的深度学习模型交换格式,用于在不同的深度学习框架之间共享模型。它提供了一个中间格式,可以将模型从一个框架转换为另一个框架。 Tensorflow是一个广泛使用的深度学习框

    2024年02月11日
    浏览(33)
  • 【VSCode部署模型】导出TensorFlow2.X训练好的模型信息

    参考tensorflow2.0 C++加载python训练保存的pb模型 经过模型训练及保存,我们得到 “OptimalModelDataSet2” 文件夹,模型的保存方法( .h5 或 .pb 文件),参考【Visual Studio Code】c/c++部署tensorflow训练的模型 其中“OptimalModelDataSet2”文件夹保存着训练好的模型数据 \\\"saved_model.pb\\\"

    2024年02月15日
    浏览(27)
  • 人工智能在教育上的应用1-基于pytorch框架下模型训练,用于数学题目图形的智能分类

    大家好,今天给大家介绍一下人工智能在教育上的应用1-基于pytorch框架下模型训练,用于数学题目图形的智能分类,本文将利用CNN算法对数学题目中的图形进行自动分类和识别。这种应用可以帮助学生更好地理解和解决与数学相关的问题。基于CNN的数学题目图形智能分类功能

    2024年02月16日
    浏览(31)
  • 使用训练好的YOLOV5模型在已有XML标注文件中添加新类别

            训练完一个YOLOV5模型后,可以使用模型快速在已有XML标注文件中添加新类别,下面是在已有XML标注文件中添加新类别的具体脚本:  以上代码需要修改run函数中的:weights为yolov5模型路径,source为图片数据和xml标注文件所在文件夹,修改的xml也在source路径下。亲测

    2024年02月15日
    浏览(27)
  • 【pytorch】Vision Transformer实现图像分类+可视化+训练数据保存

    Transformer的核心是 “自注意力” 机制。 论文地址:https://arxiv.org/pdf/2010.11929.pdf 自注意力(self-attention) 相比 卷积神经网络 和 循环神经网络 同时具有并行计算和最短的最大路径⻓度这两个优势。因此,使用自注意力来设计深度架构是很有吸引力的。对比之前仍然依赖循环神

    2023年04月08日
    浏览(31)
  • 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存(二)

    【如何训练一个中英翻译模型】LSTM机器翻译seq2seq字符编码(一) 【如何训练一个中英翻译模型】LSTM机器翻译模型训练与保存(二) 【如何训练一个中英翻译模型】LSTM机器翻译模型部署(三) 【如何训练一个中英翻译模型】LSTM机器翻译模型部署之onnx(python)(四) 基于

    2024年02月15日
    浏览(26)
  • Tensorflow实现训练数据的加载—模型搭建训练保存—模型调用和加载全流程

     将tensorflow的训练数据数组(矩阵)保存为.npy的数据格式。为后续的模型训练提供便捷的方法。例如如下:   加载.npy训练数据和测试数组(矩阵),加载后需要调整数据的形状以满足设计模型的输入输出需求,不然无法训练模型。 这里可以采用自定义层和tensorflow的API搭建

    2024年02月05日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包