Python笔记

来源:https://www.worthpen.top/blog?id=6566173f6aa58e39d930191b

目录

1 Colab

1.1 colab使用流程

(1) 将数据和参数模型压缩为zip上传至谷歌云盘对应的项目内, 将变动性较大的代码如网络, 主程序, trainer等直接上传至谷歌对应的项目内
(2) 将项目输出至colab的根目录
(3) 解压数据和参数模型,默认会解压至content
(4) 安装requirements的依赖库(发现版本不对则修改版本信息,并将其保存至本地)
(5) 根据电脑的核心数,配置训练参数,包括num_works、max_epoch、GPU或者TPU使用参数
(6) 开始训练,设置colab的自动点击
(7) 将log压缩并剪切至谷歌云盘
(8) 更多信息可查看: 知乎/优秀讲解/想免费用谷歌资源训练神经网络

1.2 ipynb文件调用.py

https://blog.csdn.net/qq1491599481/article/details/80025438

1.3 谷歌云盘复制共享文件至自己的云盘

https://blog.csdn.net/yanjiajun1/article/details/112000166

1.4 colab自动点击程序

function ClickConnect(){
console.log(“Working”);
document
.querySelector(“#top-toolbar > colab-connect-button”)
.shadowRoot
.querySelector(“#connect”)
.click()
}
var id=setInterval(ClickConnect,5*60000)
使用步骤:
1、按快捷键F12,并选择Console
2、复制粘贴上面的代码,并点击回车
要想停止,请在console中输入并回车运行以下代码:clearInterval(id)

1.5 colab的地址问题

colab中,python的import地址的当前地址为content,而命令行操作的当前地址为根目录(content的上一级)。.py文件内的地址的当前地址也为content。压缩和解压的当前目录为content

2 pytorch_lightening

2 .1基本教程

https://zhuanlan.zhihu.com/p/319810661

2.2 重载训练

https://zhuanlan.zhihu.com/p/370185203
注意: pl的重载训练会根据随机种子重新加载随机数, 导致第一次训练的数据序列和重载训练的数据序列相同

2.3 进度条

训练过程中的每个epoch的进度条的最大值包含训练的batch数和验证的batch数.

2.4 保存的模型文件

pl自动保存的ckpt文件的文件名中, epoch和step均是从0开始, 且step仅计算训练的batch数.

2.5 workers

不同线程的workers取数据的速度不一致, 会导致每次运行程序取数据的顺序不一致, 但是在用于训练时的数据顺序总是一致的. 此外, seed_everything中的workers=True参数会使pl给不同的workers设置差异较大的随机种子, 默认情况下torch会给不同线程设置原种子+ID的随机种子, 但是这实际上对随机性并无影响, 因为可能有BUG导致pl在训练阶段worker_init_fn不会赋值给训练集的dataloader(即使workers=True).
(1) Trainer.resume_from_checkpoint可以恢复完整的训练,而LightningModule.load_from_checkpoint()只恢复权重。
(2) pl不会参与对网络权重的初始化,网络权重的初始化在torch中。

2.6 模型保存和对ModelCheckpoint的理解

pl默认情况下具有自动保存机制, 通过trainer = Trainer(checkpoint_callback=False)也可以关闭自动保存.
自动保存主要通过回调函数ModelCheckpoint(Callback)来实现, 不同的回调函数产生不同的自动保存的行为, 主要有在训练某个步数后保存, 在验证后保存等. 如果未设置继承自ModelCheckpoint的Callback, 会默认添加ModelCheckpoint作为默认回调函数. 手动保存也可以通过self.trainer.save_checkpoint(model_path)实现.
ModelCheckpoint中的every_n_val_epochs和every_n_train_steps参数决定了检查点的周期, 两者是冲突的, 只需要设置一个, 前者是多少个验证epoch进行设置一个检查点, 后者时多少训练步设置一个检查点. 如果两者均为未设置,则默认every_n_val_epochs为1.
monitor, save_top_k, save_last参数决定了在当前检查点的保存行为:
1.如果monitor为None且save_top_k不为0, 则仅保存最后的模型参数, 该行为和行为2不会同时发生;
2.如果monitor不为None且save_top_k不为0,则保存最优的若干个模型参数到同一个文件夹, 该行为和行为1不会同时发生;
3.如果save_last为true,则仅保存最近的模型参数, 该行为与行为1, 行为2不冲突, 即可以在执行行为1或行为2的情况下, 同时保存最近的模型参数在另一个文件中.
当monitor不为None时, save_top_k为None时默认为1.

2.7 将损失记录在log中的说明

将验证集的loss记录在log中时, 记录的数据为一个epoch的均值, 多个batch记录一次.
将训练集的loss记录在log中时, 记录的数据为多个batch的均值, 多个batch记录一次.

2.8 batch_size对计算损失的影响

由于pl计算验证epoch的loss的方法为每个batch的loss求均值, 而每个batch内计算多个样本时同样会求均值, 这导致了两次求均值(官方的celoss也会出现该问题). 如果这时存在两个batch的size不同,则会导致每个loss的权重不相等, 导致求loss的错误. 这种情况常常出现, 因为数据量不能整除size, 所以基本上最后一个batch的size与前面的batch的size不同.
为了保证验证集计算loss的准确性, 该方法中 可以对于验证集的batch size进行了重新定义. 此外, 训练集的反向传播不受影响, 但训练集的loss记录会受影响. 然而, 由于batch_size对训练结果具有较大的影响, 因此, 忽略loss记录的影响, 不对训练集batch size进行重新定义.
此外, 对于某些loss计算方法(如dice-loss), 不同的batch size会出现不同的结果. 因此, 为了使验证和测试的loss结果与训练阶段的loss保持一致, 尽量使两者的batch size一致.

2.9 optimizer_idx

当train_step函数具有该参数时, 证明具有多个优化器, 这些优化器分多个step运行, 这些step获得的batch是相同的.

3 pytorch

3.1 world,rank理解

node:代表一个机器或者容器,其内可存在多个GPU;
rank:表示进程序号,用于进程间通信,可以用于表示进程的优先级。我们一般设置 rank=0 的主机为 master 节点。
local_rank:进程内 GPU 编号,非显式参数,由 torch.distributed.launch 内部指定。比方说, rank=3,local_rank=0 表示第 3 个进程内的第 1 块 GPU。
world_size:在pyorch实现的resnet网络中,表示node的数量。某些地方,表示所有的rank数量
group:进程组。一个group内具有多个进程,每个进程为rank X(如rank 0)表示。
上述概念在不同的库中均具有不同的定义,使用前一定仔细了解。

3.2 卷积的stride, padding等参数使用(1,)和1得到的结果不同

3.3 nn.CrossEntropyLoss()内自带softmax

3.4 每个batch的loss的计算为该batch所包含的样本的均值

3.5 一般情况默认的权重初始化采用kaiming分布

3.6 nn.Parameter()与requires_grad=True属性的区别

requires_grad=True只是将参数变成可训练的,并没有绑定在module的parameter列表中。

3.7 torch中的resize、numpy的resize和torchvision.transforms的resize的区别

torchvision的resize为放缩tensor,而torch和numpy的resize会重复性的扩展矩阵,具体的torch和numpy的逻辑还需进一步学习。

3.8 网络运行过程中的变量类型定义

GPU更擅长并行计算,而不擅长赋值、非并行计算、判断、拼接等操作。因此,对于这些操作,变量不需要定义为CUDA版本。并且,经过验证,纯CUDA变量运算速度更慢。

3.9 LambdaLR中lr_lambda的输入

输入为调用scheduler.step()的次数。如果scheduler.step()置于epoch循环中,输入为epoch数。如果scheduler.step()置于dataloader循环中,输入为step数。pytorch-lightning中,scheduler.step()默认置于epoch循环。

3.10 以同样的参数连续两次resize到同样尺寸不会改变tensor的值

3.11 浅拷贝和深拷贝

3.11.1 .Tensor、.tensor、.from_numpy、.as_tensor的区别

.Tensor和.tensor是深拷贝,在内存中创建一个额外的数据副本,不共享内存,所以不受数组改变的影响。.from_numpy和as_tensor是浅拷贝,在内存中共享数据。

3.11.2 赋值、clone()、detach()

torch中,赋值操作是浅拷贝。需要注意的是,在python中,赋值操作的对象其ID是相同的,但浅拷贝对象的ID不同。也就是说,不同的对象ID可能指向相同的内容。
clone()是深拷贝。但是梯度会回传到之前的变量。
tensor.detach()是浅拷贝,新的tensor会脱离计算图,计算完新tensor的梯度就到头了,不会继续回传。

3.12 .Tensor、.tensor的区别

Tensor是类,也是FloatTensor这一数据类型的别名。因此,Tensor创建的tensor都是float型。
tensor是函数,用来返回tensor实例。生成的tensor可以是float,也可以是int等。

4 显卡白嫖

白嫖步骤:
(1) 清空本地的requirements文件的版本
(2) 去掉不需要安装的库.
注意: 大多数预设的环境中已经安装了torch, torchaudio, torchvision.
(2) 若环境不稳定, 则安装后生成当前版本requirements文件.

5 动态导入库

https://blog.csdn.net/edward_zcl/article/details/88809212

6 python的可变对象

Python中一切皆为对象,因此,某些数据类型也是对象。这些数据类型分为:不可变对象(当该数据类型的对应变量的值发生了改变,它对应的内存地址也会发生改变)和可变对象(当该数据类型的对应变量的值发生了改变,它对应的内存地址不发生改变)。
因为Python中一切皆为对象,而对象的赋值是对象的引用,因此,一切赋值都是引用。当为可变对象是,改变数据会改变赋值前的数据;而当为不可变对象时,即使是引用,也不会改变赋值前的数据。
参数传递与赋值类似,Python中在进行传参的时候,参数传入的是引用。此时的数据改变的效果和赋值相同,特殊的是,可变对象的改变会影响函数外的数据。
详细见Python测试工程

7 Python方法重载

https://blog.csdn.net/ffantastic/article/details/82025130

8 python中矩阵切片的维数变化

https://blog.csdn.net/m0_37602827/article/details/90517374

9 Python中for循环变量作用域问题探讨

https://blog.csdn.net/Jerry_1126/article/details/80864473

10 Numpy中column_stack与row_stack的思考

https://blog.csdn.net/qq_34034762/article/details/79660386

11 类变量、全局变量

修改python import模块中的变量: https://www.cnblogs.com/xbit/p/10888110.html
python程序中用类变量代替global 定义全局变量: https://www.cnblogs.com/mangojun/p/10870357.html
python实现静态变量: https://blog.csdn.net/DawnRanger/article/details/78306878

12 import

导入模块:每个py文件称为一个模块。可以用import直接导入模块,导入的模块会将被导入的模块运行一遍。
导入函数或者类:只能使用from XXX.module import来导入模块中的函数和类,不能使用不带from的import导入函数或者类。
导入包:import也可以导入package,导入方法为import package。

13 with as关键字

详情见:https://zhuanlan.zhihu.com/p/74552877
当不存在as时,则__enter__无返回值。

14 参数类别及类型

和Dart语言类似,python也有位置参数和命名参数,此外还有可变长位置参数, 可变长命名参数等。
(1) 定义时,只有具有默认值和不具有默认值这两种概念,默认必须在非默认后
(2) 调用时,先约定需要传入的参数,然后就只有位置参数和命名参数两种,命名必须在位置之后
(3) 在调用时:
“ ** ” 可以将dict变量解析为命名参数
“ * ” 可以将tuple解析为位置参数
(4) 在定义时:
“ * ”会将所有剩余位置参数截取存入“ * ”内,因此,“ * ”只能放在所有位置参数后,命名参数前。即,“ * ”后为命名参数,不论其是否具有默认值。
“ ** ”会将剩余命名参数存入“ ** ” 内。由于其不截取,因此,可以放在任何地方。
函数参数中的冒号是参数的类型建议符,告诉程序员希望传入的实参的类型。函数后面跟着的箭头是函数返回值的类型建议符,用来说明该函数返回的值是什么类型。

15 删除, 移动, 复制等文件操作

https://blog.csdn.net/weixin_40002846/article/details/110681751

16 操作excel

需要安装openpyxl库

import openpyxl as xl

def xl_op():
# 写
dataset_xl = xl.Workbook(write_only=True)
dataset_sh = dataset_xl.create_sheet(‘dataset’, 0)
for row in range(self.x.shape[0]):
for col in range(self.x.shape[1]):
dataset_sh.cell(row + 1, col + 1).value = float(self.x[row, col])
dataset_sh.cell(row + 1, self.x.shape[1] + 1).value = float(self.y[row])
dataset_xl.save(dataset_path + ‘/dataset.xlsx’)
dataset_xl.close()
# 读
dataset_xl = xl.load_workbook(dataset_path + ‘/dataset_list.xlsx’, read_only=True)
dataset_sh = dataset_xl.get_sheet_by_name(‘dataset_list’)
temp = [[dataset_sh[row + 1][col].value for col in range(config[‘dim_in’] + 1)] for row in
range(config[‘dataset_len’])]
dataset_xl.close()

17 配置深度学习环境

(1) 禁用nouveau驱动
create /etc/modprobe.d/blacklist-nouveau.conf and 添加两行:
blacklist nouveau
options nouveau modeset=0
保存后:update-initramfs -u
重启后验证是否已经禁用:lsmod | grep nouveau
若无输出则已禁用
(2) 确定需要安装的所有版本
pytorch版本对应着cuda版本和python版本. 因此需要先确定pytorch版本, 再确定cuda版本和python版本.
python版本在pip install torch的网站确定, 可以看到文件名后对应着python版本. 仅此处可以看到, 其他地方不会看到, 也没有任何提示.
同时,如果一直驱动版本,可以在此处找到对应的torch版本。
pytorch建议版本:

torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio===0.10.0+cu113

(3) 删除驱动及cuda
删除说明的链接: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#removing-cuda-tk-and-driver
按照上述网站删除后还需要执行cuda的卸载程序,sudo /usr/local/cuda-11.4/bin/cuda-uninstaller
删除后需要重启
(4) 安装驱动
一般安装最新的驱动
○ 在英伟达的官网(网址:http://www.nvidia.cn/page/home.html)查找你自己电脑的显卡型号然后下载相应的驱动,下载后的run文件拷贝至home目录下。
○ 给驱动run文件赋予执行权限:sudo chmod a+x NVIDIA-Linux-x86_64-470.94.run
○ sudo ./NVIDIA-Linux-x86_64-470.94.run -no-x-check -no-nouveau-check -no-opengl-files
-no-x-check:安装驱动时关闭X服务
-no-nouveau-check:安装驱动时禁用nouveau
-no-opengl-files:只安装驱动文件,不安装OpenGL文件
○ 没有安装gcc、make
sudo apt-get install gcc
sudo apt-get install make
○ The distribution-provided pre-install script failed! Are you sure you want to continue?
选择 yes 继续。
Would you like to register the kernel module souces with DKMS? This will allow DKMS to automatically build a new module, if you install a different kernel later?
选择 No 继续。
某问题
选择 install without signing继续
Nvidia’s 32-bit compatibility libraries?
选择 No 继续。
Would you like to run the nvidia-xconfigutility to automatically update your x configuration so that the NVIDIA x driver will be used when you restart x? Any pre-existing x confile will be backed up.
选择 Yes 继续
○ 挂载Nvidia驱动:modprobe nvidia
○ 删除/etc/X11/xorg.conf, 不然只能有一个屏幕显示(通过sudo nvidia-xconfig命令可以恢复)
○ 重启
(5) cuda下载
官网: https://developer.nvidia.com/cuda-toolkit-archive
使用.run文件安装,安装时需要选不安装driver,并且安装时可以使用sudo命令直接运行,而不用sh命令
cuda建议版本: 11.3.1
安装完后需要配置cuda的环境变量:
echo ‘export PATH=/usr/local/cuda-11.3/bin/: P A T H ′ > >   / . b a s h r c e c h o ′ e x p o r t L D L I B R A R Y P A T H = / u s r / l o c a l / c u d a − 11.3 / l i b 64 : PATH'>>~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-11.3/lib64: PATH>> /.bashrcechoexportLDLIBRARYPATH=/usr/local/cuda11.3/lib64:LD_LIBRARY_PATH’>>~/.bashrc
source ~/.bashrc
(6) cudnn对应版本及其下载
https://developer.nvidia.com/cudnn
推荐版本:8.2.1
查询当前版本:sudo dpkg -l | grep cudnn
将解压包复制到cuda安装目录的同名文件夹下, 命令如下:
sudo dpkg -i libcudnn8_8.2.1.32-1+cuda11.3_amd64.deb
sudo dpkg -i libcudnn8-dev_8.2.1.32-1+cuda11.3_amd64.deb
sudo dpkg -i libcudnn8-samples_8.2.1.32-1+cuda11.3_amd64.deb
以上命令注意改版本号
(7) 安装python
python建议版本: py3.9
sudo apt-get install python3.9
(8) 安装pycharm
tar -zxvf pycharm-community-2021.3.tar.gz
sudo mkdir /opt/pycharm
sudo mv pycharm-community-2021.3/ /opt/pycharm/
sh /opt/pycharm/pycharm-2021.3/bin/pycharm.sh
(8) 安装torch
https://pytorch.org/
torch建议版本安装命令:

pip3 install torch==1.10.0+cu113 torchvision==0.11.1+cu113 torchaudio==0.10.0+cu113 -f https://download.pytorch.org/whl/cu113/torch_stable.html

(9) 安装其他依赖库
通过requirements.txt
pycharm的编译器的packages界面进行更新会默认使用国外的python库,这会导致安装依赖库的速度慢. 可以使用豆瓣源提高速度, 在pycharm的添加依赖库界面有manage repositories。
豆瓣源: https://pypi.douban.com/simple
默认源: https://pypi.python.org/simple
在pip的-i参数后加网址可以临时修改库的下载源
(10) 保存环境和恢复环境
pip:
注意: 保存环境后要删除某些库对应的版本, 否则不适配colab
pip freeze >requirements.txt
pip install -r requirements.txt
conda: https://www.cnblogs.com/maxiaodoubao/p/10605850.html

18 报错

18.1 Duplicate plugins for name projector

依赖库中由相同的名称的库, 可能是由于上个版本没删除干净, 在Lib/site-packages中找到并删除即可.

19 parser.add_argument的action='store_true’参数

https://blog.csdn.net/Arthur_Holmes/article/details/104228142

20 分布式训练

分布式训练分为数据并行和模型并行。模型并行需要重构代码,不推荐使用。
DP是在每个GPU上复制一份网络模型,每个GPU处理batch的一部分,集中到中心节点进行求导并广播结果到各个GPU。稳定性差,速度慢,不支持半精度训练,不支持多机。用了反而速度会降低. 有效batch size为batch_size。
DDP,在每个GPU上复制一份网络模型,每个GPU处理batch的一部分,各自进行求导,梯度进行分布式的平均。稳定性好,速度快,推荐使用,需要一个root package,无法在Jupyter Notebook中使用。有效batch size为batch_sizegpusnodes。
DDP_spawn,与DDP原理相同, 只不过在pl中使用不同的方式创建进程。在子模型上更新导致主流程上不会更新,num_workers需要为0,强制一切都是picklable,速度没有DDP快。有效batch size为batch_sizegpusnodes。
DDP2,同一机器上使用DP,跨机器使用DDP。扩大了求导的样本数,不是是个GPU独自求导,而是一个机器上所有数据进行求导。适用于某些特殊的需要更多样本进行求loss的情况,但使用DP带来了速度和稳定性降低,不支持半精度训练。有效batch size为batch_size*nodes。
最佳配置:DDP+半精度

不同分布式训练策略的训练速度比较

训练策略gpus数量训练时间
dp115-13-13-13-13
dp217-15-15-15-15
ddp(find_unused_parameters=True)115-13-13-12-12
ddp(find_unused_parameters=True)210-7-7-7-7
ddp(find_unused_parameters=False)114-12-13
ddp(find_unused_parameters=False)29-3
ddp_sharded(备注:出现警告UserWarning: Detected call of lr_scheduler.step() before optimizer.step().的情况下)116-17-16-15-15
ddp_sharded(备注:出现警告UserWarning: Detected call of lr_scheduler.step() before optimizer.step().的情况下)211-9-9-9-9
None114-12-12-12-12

21 减少训练内存

21.1 理论

21.1.1 重计算

在前向过程中只保存特定checkpoint的张量,未保存的张量在使用的时候重新计算得到。该方案对速度影响一般。

21.1.2 卸载

21.1.2.1 激活卸载

将前向过程中的张量保存到CPU的内存中。该方案对速度影响较大。

21.1.2.2 模型和参数卸载

该方案相较于激活卸载具有更明显的效果。

21.1.3 分片训练

不同等级的分片训练的示意图
不同等级的分片训练的示意图
分片技术只能分片参数、梯度、优化器状态,而不能分片batch及其激活函数输出。因此,对于大模型分片技术有效,而对小模型大输入效果不佳。分片技术也有说法叫内存共享技术。

21.2 实现方案

更多并行训练策略可查阅: https://pytorch-lightning.readthedocs.io/en/latest/advanced/model_parallel.html
讲解:https://zhuanlan.zhihu.com/p/485208899
目前使用较多的是fair-scale的ddp_sharding

21.2.1 DeepSpeed ZeRO

微软开发,有三个版本。不同版本对应不同的分片等级、是否卸载、是否重计算等。
目前存在的问题:与pytorch-lightning的适配较差

21.2.2 FairScale

facebook开发。也同样具有不同的版本。ddp_sharding为优化器状态和梯度的分片。也可以实现全分片、重计算等

21.2.3 torch官方

可以在lightning中找到全分片和卸载模型等功能的实现。无重计算和卸载激活检查点。

22 如何估计模型所需显存

在这里插入图片描述
所占内存示意图

22.1 理论计算

一个模型所占用的显存主要包含两部分: 模型自身的参数, 优化器参数, 模型每层的输入输出。
(1) 模型自身参数
模型自身的参数指的就是各个网络层的 Weight 和Bias,这部分显存在模型加载完成之后就会被占用, 注意到的是,有些层是有参数的,如CNN, RNN; 而有些层是无参数的, 如激活层, 池化层等。
(2) 优化器参数
优化器参数指的是模型在优化过程即反向传播中所产生的参数, 这部分参数主要指的就是 dw, 即梯度,在SGD中, 其大小与参数一样, 因此在优化期间, 模型的参数所占用的显存会翻倍。
值得注意的是,不同的优化器其所需保存的优化参数不同, 对于 Adam, 由于其还需要保存其余参数, 模型的参数量会在优化区间翻 4 倍。
(3) 模型每层的输入输出
输入数据所占用的显存: 这部分所占用的显存其实并不大,这是因为我们往往采用迭代器的方式读取数据,这意味着我们其实并不是一次性的将所有数据读入显存,而这保证每次输入所占用的显存与整个网络参数来比是微不足道的。
每层的输出: 在模型进行前向传播与反向传播时, 一个很重要的事情就是计算并保存每一层的输出以及其对应的梯度, 这意味着,这也占据了很大一部分显存。输出的显存占用与 batch size 成正比.
(4) 所有的显存占用计算
显存占用 = 模型自身参数 × n + batch size × 输出参数量 × 2 + 一个batch的输入数据(往往忽略)
其中,n是根据优化算法来定的,如果选用SGD, 则 n = 2, 如果选择Adam, 则 n = 4.

22.2 计算代码

使用torchsummay开源库。
安装:pip install torchsummay
代码:
from torchsummary import summary

model = resnet18()
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)
summary(model.to(device), input_size=(3, 224, 224), batch_size=8)

23 神经网络模型部署

保存为pl的.ckpt文件=>转换为.onnx格式=>转换为tensorflow格式=>转换为tflite格式

24 PIL、CV2、Torch读取图片的格式

opencv读取彩色图片:BGR,(h,w,c)。
例如:
cv2.imread(‘test.jpg’)

PIL.Image读取彩色图片:RGB, size:(w,h),转成numpy后变成(h,w,c)
例如:
image=Image.open(‘test.jpg’)
image = np.array(image,dtype=np.float32)

pytorch tensor matrix: (N, C, H, W)

25 将python3替换为python

https://blog.csdn.net/zyx_ly/article/details/89574445

26 python画图

https://blog.csdn.net/Gou_Hailong/article/details/120089602

27 如何获取数据直方图(柱状图)

import matplotlib.pyplot as plt

plt.hist(data, bins=10)
plt.show()
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BoilingHotPot

听说打赏我的人,都发顶会顶刊了

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

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

打赏作者

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

抵扣说明:

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

余额充值