Long-Tail(长尾)问题的解决方案

本文详细探讨了长尾问题在图像识别中的挑战,特别是在数据分布不均衡时,提出多种解决方案,包括数据增强、重采样、重加权、学习策略和损失函数调整。重点介绍了BalancedGroup Softmax方法,通过分组和加权策略来平衡不同类别间的竞争,以提高尾部类别的性能。此外,还讨论了其他技术,如过采样、欠采样、阈值移动和迁移学习,以及各种训练技巧,如学习率预热和权重初始化策略。
摘要由CSDN通过智能技术生成

长尾问题

在实际的视觉相关问题中,数据都存在长尾分布:少量类别占据绝大多数样本,大量的类别仅有少量的样本,比如open-images,ImageNet等。
解决长尾问题嘚方案一般分为4种:

1,Re-sampling:主要是在训练集上实现样本平衡,如对tail中的类别样本进行过采样,或者对head类别样本进行欠采样;

2,Re-weighting:主要在训练loss中,给不同的类别的loss设置不同的权重,对tail类别loss设置更大的权重
3,Learning strategy(阶段训练):有专门为解决少样本问题涉及的学习方法可以借鉴,如:meta-learning、metric learning、transfer learing。另外,还可以调整训练策略,将训练过程分为两步:第一步不区分head样本和tail样本,对模型正常训练;第二步,设置小的学习率,对第一步的模型使用各种样本平衡的策略进行finetune。

4,综合使用以上策略

常见解决方法

1,数据增强

空间几何变换类

翻转:上下,左右
局部裁剪
旋转
缩放变形
仿射变换
同时对图片做裁剪,旋转,转换,模式调整等多重操作
视觉变换
对图像应用一个随机的四点透视变换
分段防射

噪声类

高斯噪声,
CoarseDropout
在面积大小可选定、位置随机的矩形区域上丢失信息实现转换,所有通道的信息丢失产生黑色矩形块,部分通道的信息丢失产生彩色噪声。
SimplexNoiseAlpha产生连续单一噪声的掩模后,将掩模与原图像混合
FrequencyNoiseAlpha在频域中用随机指数对噪声映射进行加权,再转换到空间域。在不同图像中,随着指数值逐渐增大,依次出现平滑的大斑点、多云模式、重复出现的小斑块。
2.2 模糊类减少各像素点值的差异实现图片模糊,实现像素的平滑化。高斯模糊
ElasticTransformation
根据扭曲场的平滑度与强度逐一地移动局部像素点实现模糊效果。
随机擦除法
对图片上随机选取一块区域,随机地擦除图像信息。
超像素法(Superpixels)
在最大分辨率处生成图像的若干个超像素,并将其调整到原始大小,再将原始图像中所有超像素区域按一定比例替换为超像素,其他区域不改变。
GrayScale将图像从RGB颜色空间转换为灰度空间,通过某一通道与原图像混合。

Pytorch上的transforms的二十二个方法

参考文献:https://blog.csdn.net/qq_41168327/article/details/104620934
1.裁剪——Crop
中心裁剪:transforms.CenterCrop
随机裁剪:transforms.RandomCrop
随机长宽比裁剪:transforms.RandomResizedCrop
上下左右中心裁剪:transforms.FiveCrop
上下左右中心裁剪后翻转,transforms.TenCrop
2,翻转和旋转——Flip and Rotation
依概率p水平翻转:transforms.RandomHorizontalFlip(p=0.5)
依概率p垂直翻转:transforms.RandomVerticalFlip(p=0.5)
随机旋转:transforms.RandomRotation
3,图像变换
resize:transforms.Resize
标准化:transforms.Normalize
转为tensor,并归一化至[0-1]:transforms.ToTensor
填充:transforms.Pad
修改亮度、对比度和饱和度:transforms.ColorJitter
转灰度图:transforms.Grayscale
线性变换:transforms.LinearTransformation()
仿射变换:transforms.RandomAffine
依概率p转为灰度图:transforms.RandomGrayscale
将数据转换为PILImage:transforms.ToPILImage
transforms.Lambda:Apply a user-defined lambda as a transform.
4,对transforms操作,使数据增强更灵活
transforms.RandomChoice(transforms):从给定的一系列transforms中选一个进行操作
transforms.RandomApply(transforms, p=0.5),给一个transform加上概率,依概率进行操作
transforms.RandomOrder,将transforms中的操作随机打乱

2,过采样,欠采样

Pytorch上的过采样和欠采样
过采样:重复正比例数据,实际上没有为模型引入更多数据,过分强调正比例数据,会放大正比例噪音对模型的影响。
欠采样:丢弃大量数据,和过采样一样会存在过拟合的问题。但总的来肯定是利大于弊 pytorch的权重采样使用WeightedRandomSampler函数
代码示例:

import torch
from torch.utils.data import DataLoader,WeightedRandomSampler
from dataset import train_dataset
weights = torch.FloatTensor([1,2,2,4,4,1])#weights:指每一个类别在采样过程中得到权重大小(不要求综合为 1),权重越大的样本被选中的概率越大
train_sampler = WeightedRandomSampler(weights,len(train_dataset),replacement=True)#第二个参数是num_samples:共选取的样本总数,待选取得样本数目一般小于全部的样本数目;replacement :指定是否可以重复选取某一个样本,默认为 True,即允许在一个 epoch 中重复采样某一个数据。如果设为 False,则当某一类的样本被全部选取完,但其样本数目仍未达到 num_samples 时,sampler 将不会再从该类中选择数据,此时可能导致 weights 参数失效。
train_sampler = DataLoader(train_dataset,sampler=sampler)

函数加权:
就是在计算损失函数过程中,对每个类别的损失做加权,具体的方式如下:

weights = torch.FloatTensor([1,1,8,8,4])
criterion = nn.BCEWithLogitsLoss(pos_weight=weights).cuda()

在数据样本中的采样均衡,以Xgboost为例:
利用imblearn这个包对训练集进行处理

# 生成不平衡分类数据集
from collections import Counter
from sklearn.datasets import make_classification
from imblearn.over_sampling import RandomOverSampler
X, y = make_classification(n_samples=3000, n_features=2, n_informative=2,
                           n_redundant=0, n_repeated=0, n_classes=3,
                           n_clusters_per_class=1,
                           weights=[0.1, 0.05, 0.85],
                           class_sep=0.8, random_state=2018)
print(X)
print(Counter(y))#生成了一个类别个数为3的不均衡的样本集
#下面采用集中采样方法,降低样本不均衡带来的影响
# 使用RandomOverSampler从少数类的样本中进行随机采样来增加新的样本使各个分类均衡
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))
# SMOTE: 对于少数类样本a, 随机选择一个最近邻的样本b, 然后从a与b的连线上随机选取一个点c作为新的少数类样本,  属于新样本生成得方式增加少数样本类的个数
from imblearn.over_sampling import SMOTE
X_resampled_smote, y_resampled_smote = SMOTE().fit_sample(X, y)
print(sorted(Counter(y_resampled_smote).items()))
# ADASYN: 关注的是在那些基于K最近邻分类器被错误分类的原始样本附近生成新的少数类样本
from imblearn.over_sampling import ADASYN
X_resampled_adasyn, y_resampled_adasyn = ADASYN().fit_sample(X, y)
print(sorted(Counter(y_resampled_adasyn).items()))
# RandomUnderSampler函数是一种快速并十分简单的方式来平衡各个类别的数据: 随机选取数据的子集.
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(random_state=0)
X_resampled, y_resampled = rus.fit_sample(X, y)
print(sorted(Counter(y_resampled).items()))

下面是xgb.DMatrix设置每一个样本的权重,这样模型在计算损失的过程中都会结合每个样本的权重去计算。例如:样本1的权重为0.1,样本2的权重为0.5,样本3的权重为1.2,则如果这三个样本计算损失为: L o s s = 0.1 ∗ L o s s 1 + 0.5 ∗ L o s s 2 + 1 , 2 ∗ L o s s 3 Loss=0.1*Loss_1+0.5*Loss_2+1,2*Loss_3 Loss=0.1Loss1+0.5Loss2+1,2Loss3

import xgboost as xgb
import pandas as pd
import time
import numpy as np
dataset=pd.read_csv("mnist_train",header=None, nrows =5001)
train = dataset.iloc[0:5000,:784].values
labels = dataset.iloc[0:5000,784:785].values

params={
   
'booster':'gbtree',
# 这里手写数字是0-9,是一个多类的问题,因此采用了multisoft多分类器,
'objective': 'multi:softmax', 
'num_class':10, # 类数,与 multisoftmax 并用
'gamma':0.05,  # 在树的叶子节点下一个分区的最小损失,越大算法模型越保守 。[0:]
'max_depth':12, # 构建树的深度 [1:]
#'lambda':450,  # L2 正则项权重
'subsample':0.4, # 采样训练数据,设置为0.5,随机选择一般的数据实例 (0:1]
'colsample_bytree':0.7, # 构建树树时的采样比率 (0:1]
#'min_child_weight':12, # 节点的最少特征数
'silent':0 ,
'eta': 0.05, # 如同学习率http://localhost:8889/notebooks/Test.ipynb#
'seed':701,
'nthread':4,# cpu 线程数,根据自己U的个数适当调整
}

plst = list(params.items())

#Using 10000 rows for early stopping. 
offset = 4000  # 训练集中数据60000,划分50000用作训练,10000用作验证

num_rounds = 50 # 迭代你次数

# 定义每一个样本的权重
weight = []
for ele in labels[:offset]:
    if ele < 5:
        weight.append(0.1)
    else:
        weight.append(1.0)
# 划分训练集与验证集 
xgtrain = xgb.DMatrix(train[:offset,:], label=labels[:offset], weight=weight)
xgval = xgb.DMatrix(train[offset:,:])
y_label=labels[offset:]

# training model 
# early_stopping_rounds 当设置的迭代次数较大时,early_stopping_rounds 可在一定的迭代次数内准确率没有提升就停止训练
print("开始训练")
model = xgb.train(plst, xgtrain)
print("结束训练")
pred_val = model.predict(xgval)
print(pred_val)
print(accuracy_score(pred_val, y_label))
print(confusion_matrix(pred_val, y_label))

过采样和欠采样的中间方法:数据分布做平滑

重采样的权重使用函数进行拟合:
q i = p i α / ∑ N q_i=p^{\alpha}_i/\sum_{}^N qi=piα/N
其中, p i p_i pi是原始类样本占比, p i = n i / ∑ k = 1 N n k p_i=n_i/\sum_{k=1}^Nn_k pi=ni/k=1Nnk
,通过这种平滑抽样,抽样结果不会改变原始的各类别数据量大小的序关系,但是对类别数量过大的类数据量会相对减少,对类别数量过小的类数据量会相对增加。减少过拟合的可能性,也没有过度浪费数据。
参见论文:https://arxiv.org/pdf/1901.07291.pdf

阈值移动

就是对阈值进行调整。直接基于原始数据训练,进行预测时,用样例的真实观测几率来修正阈值。

数据不均衡,分类的会向样本大的位置偏置,所以预测时不按照置信度最大的分类,而是置信度大于样本占比就可以当做分为这一类,降低了样本少的分类置信度。

图像分类训练技巧包

论文:https://arxiv.org/pdf/1812.01187v2.pdf
这篇文章是亚马逊李沐团队的一篇技巧(tricks)文章,被CVPR2019收录了。虽然题目是讲的Image Classification,但是作者也说了,在目标检测,实例分类等问题上也是有一定的作用的。
技巧部分从以下几个部分展开:
efficient training 高效训练
在这里插入图片描述
蓝线代表常见的保持 Batch Size,逐步衰减学习率的方法;
红线代表与之相反的,保持学习率,相应的上升 Batch Size 的策略;
绿线模拟真实条件下,上升 Batch Size 达到显存上限的时候,再开始下降学习率的策略。
显然,增大 Batch Size 的方法中参数更新的次数远少于衰减学习率的策略。
但是一味的增大batch_size会造成一些缺点:1.模型收敛过慢。2.占用更大的显存。3.训练结果反而会比较小的Batch_size训练结果更差。那我们有没有什么办法在增大Batch_size的同时又避免这些缺点呢?方法如下:1.Large-batch training 大批量训练;2. Learning rate warmup 学习率预热
为了进行大Batch_size的训练,作者对比四种启发方法:

  1. Linear scaling learning rate 等比例增大学习率
    大批量会降低梯度中的噪声,因此我们可以提高学习率,以便进行调整。
    例如:作者按照何恺明的resnet论文中的内容,选择0.1作为Batch_size为256的初始学习率。当第b个batch时,学习率线性增加到0.1×b/256。

  2. Learning rate warmup 学习率预热
    在训练的开始时期,所有的参数都是一个随机值,这样离最终结果差的比较大。使用大的学习率会导致数值的不稳定。可以先采用一个手段使得训练过程稳定下来。这个手段就是“学习率预热”,那么什么是学习率预热?就是在训练最开始的时候,先使用一个小的学习率训练,当训练稳定下来后,再换回原来设定的学习率。前m个batches是用来warmup。

  3. Zero γ 零γ初始化
    这一技巧针对ResNet的网络结构提出来的。残差块的最后一层是BN层:具体操作如下:1.求均值。2.求方差。3.归一化。4.缩放和偏移。第4步将normalize后的数据再扩展和平移。是为了让神经网络自己去学着使用和修改这个扩展参数γ,和平移参数β, 这样神经网络就能自己慢慢琢磨出前面的normalization操作到底有没有起到优化的作用, 如果没有起到作用, 就使用γ和β来抵消一些normalization的操作
    其中,γ和β都是可训练的参数。通常的做法是在初始化时,将β设为0,但是作者提出在初始化时可以将γ也设为0,也就是上图中的block在初始化时输出为0。这样一来,输出就只有shortcut结构的输出了,也即输出等于输入。这样的好处:将所有残差块中的最后一个BN中的初始化设置成0,也即残差块的输出等于输入,相当于 模型的网络层数较少, 可以使得模型在初始化阶段更容易训练

  4. No bias decay 无偏置衰减
    Weight Decay是用来解决过拟合问题。 但是一般来说,会对可学习的参数如 weight 和 bias 都会做 decay,通常的做法是使用L2正则化来做。机智团队提出只对卷积层和全连接层的weight做L2中正则化,不对bias,BN层的γ和β进行正则化衰减。
    model tweaks 网络模型结构
    Training Refinements 训练过程优化
    Transfer Learing 迁移学习

解决方案二,BalancedGroup Softmax

代码开源:https://github.com/FishYuLi/BalancedGroupSoftmax
论文:https://arxiv.org/pdf/2006.10408.pdf
这种不平衡将使low-shot 类别(尾类)的分类分数比many-shot 类别(头部类)的分类分数小得多。在标准softmax函数之后,这种不平衡会被进一步放大,因此分类器错误地抑制了预测为low-shot 类别的proposal 。
长尾分布问题的一般解决方法
**Re-sampling:**主要是在训练集上实现样本平衡,如对tail中的类别样本进行过采样,或者对head类别样本进行欠采样。基于重采样的解决方案适用于检测框架,但可能会导致训练时间增加以及对tail类别的过度拟合风险。
**Re-weighting:**主要在训练loss中,给不同的类别的loss设置不同的权重,对tail类别loss设置更大的权重。但是这种方法对超参数选择非常敏感,并且由于难以处理特殊背景类(非常多的类别)而不适用于检测框架。
Learning strategy:有专门为解决少样本问题涉及的学习方法可以借鉴,如:meta-learning、metric learning、transfer learing。另外,还可以调整训练策略,将训练过程分为两步:第一步不区分head样本和tail样本,对模型正常训练;第二步,设置小的学习率,对第一步的模型使用各种样本平衡的策略进行finetune。
统计变换
数据分布的倾斜有很多负面的影响。我们可以使用特征工程技巧,利用统计或数学变换来减轻数据分布倾斜的影响。使原本密集的区间的值尽可能的分散,原本分散的区间的值尽量的聚合。
这些变换函数都属于幂变换函数簇,通常用来创建单调的数据变换。它们的主要作用在于它能帮助稳定方差,始终保持分布接近于正态分布并使得数据与分布的平均值无关。

  • Log变换
  • Box-Cox变换
    主要思想:
    将简单而有效的balanced group softmax(BAGS)模块引入到检测框架的分类head中。本文建议将训练实例数量相似的目标对象类别放在同一组中,并分别计算分组的softmax交叉熵损失。分别处理具有不同实例编号的类别可以有效地减轻head类对tail类的控制。但是,由于每次小组训练都缺乏不同的负样本,结果模型会有太多的误报。因此,BAGS还在每个组中添加了一个其他类别,并将背景类别作为一个单独的组引入,这可以通过减轻head类对tail类的压制来保持分类器的类别平衡,同时防止分类背景和其他类别的false positives。
    性能表现其tail类性能提升了9%-19%,整体mAP提升了约3%-6%。
    长尾数据集性能下降原因探索:
    当训练集遵循长尾分布时,当前表现良好的检测模型通常无法识别尾巴类别。本文通过对代表性示例(COCO和LVIS)进行对比实验,尝试研究从均衡数据集到长尾数据集这种性能下降的背后机制。
      通过所设计的对比实验发现(具体的实验细节可以参考论文原文),tail类的预测得分会先天性地低于head类,tail类的proposals 在softmax计算中与head类的proposals 竞争后,被选中的可能性会降低。这就解释了为什么目前的检测模型经常在tail类上失效。由于head类的训练实例远多于tail类的训练实例(例如,在某些极端情况下,10000:1),tail类的分类器权重更容易(频繁)被head类的权重所压制,导致训练后的weight norm不平衡。
      因此,可以看出为什么重采样方法能够在长尾目标分类和分割任务中的使得tail类受益。它只是在训练过程中增加了tail类proposals 的采样频率,从而可以平等地激活或抑制不同类别的权重,从而在一定程度上平衡tail类和head类。同样,损失重新加权方法也可以通过类似的方式生效。尽管重采样策略可以减轻数据不平衡的影响,但实际上会带来新的风险,例如过度拟合tail类和额外的计算开销。同时,损失重新加权对每个类别的损失加权设计很敏感,通常在不同的框架,backbone和数据集之间会有所不同,因此很难在实际应用中进行部署。而且,基于重新加权的方法不能很好地处理检测问题中的背景类。因此,本文提出了一种简单而有效的解决方案,无需繁重的超参数工程即可平衡分类器 weight norm。

Balanced Group Softmax

框架:
在这里插入图片描述
说明:训练:包含类似数量的训练实例的类被组合在一起。类others被添加到每个组中。G0为背景组。Softmax交叉熵(CE)损失分别应用于每组中。测试:利用新的预测z,将softmax应用于每一组,并将概率按其原始类别id (CID)排序,并与前景概率重新缩放,生成新的概率向量用于后续处理。
Group softmax
如前所述,权重规范与训练示例数的正相关关系会损害检测器的性能。为了解决这个问题,我们提出将类分成几个不连接的组,分别进行softmax操作,这样每组内只有训练实例数量相似的类才会竞争。通过这种方式,在训练过程中可以将包含显著不同实例数量的类与其他类隔离开来。头类不会实质上抑制尾类的分类器权重。
具体地说,我们将全部 C C C个类别根据训练样本数量把他们分成N组。我们将类别j分配给第 N j N_j Nj组。如果 s n t < = N ( j ) < s n h s_n^t<=N(j)<s_n^h snt<=N(j)<snh;在本文中将N设置为4,其中背景类别单独设为一组,因为太多,远远大于其他类,使用sigmoid cross损失函数,因为只包含一个预测。然后其他的类别使用softmax cross entropy loss。选择softmax的原因是softmax函数固有地拥有压制每个类的能力,并且不太可能产生大量的有效数量。在训练过程中,对一个真实标签为c的区域bk,两组会被激活,背景组合该类所在的组。
通过其他类来校准

然而,我们发现上述组softmax设计存在以下问题。在测试期间,对于一个区域,所有组都将被用于预测,因为它是未知的。因此,每一组至少有一个类别将获得高预测分数,这将很难决定采取哪一组预测,导致大量错误的肯定。为了解决这个问题,我们在每一组中都安排了一些 others类别来校准各组之间的预测并抑制假阳性。此其他类别包含未包含在当前组中的类别,这些类别可以是其他组中的背景或前景类别。对于group_0,类别‘其他’也是代表前景类别,具体来说,对于一个提案区域具有真实类别标签 c c c,新的预测值 z z z应该是在 R ( c + 1 ) + ( N + 1 ) R^{(c+1)+(N+1)} R(c+1)+(N+1),类别j的置信度的计算方式为: p j = e ( z j ) / ∑ i < g n e ( z i ) p_j=e^(z_j)/\sum_i<g_n e^(z_i) pj=e(zj)/i<gne(zi),真实的标签应该重映射在每一个组中,在不包括c的组中,类别‘others’应该定义为真实类,所以最后得损失函数是 L k = − ∑ n = 0 N ∑ i < g n y i n l o g ( p i n ) L_k=-\sum_{n=0}^N\sum_{i<g_n}y_i^nlog(p_i^n) Lk=n=0Ni<gnyinlog(pin),其中, y n y^n yn p n p^n pn代表在组g_n中的标签和置信度。
在组中平衡训练样本
在上面的处理中,新添加的类别others将再次成为一个压倒性的例外,样本数量远远大于组内类别的样本数。为了平衡每组的训练样本数量,我们只抽取一定数量的‘others’类别进行训练,设置采样参数 β \beta β,用来控制。对于目标检测中的背景组,others都可以用,因为背景样本很多,对于其他的组, m n = β ∑ i N b a t c h ( i ) m_n=\beta\sum_{i}{N_{batch_(i)}} mn=βiNbatch(i), β \beta β是一个大于0的数,是一个超参,下面会实验它的影响。正常情况下,我们设置 β \beta β=8,
也就是说,在包含ground-truth类别的组中,将根据一小批K建议按比例抽样’others’实例。如果在一个组中激活了非正规类别,则不会激活所有其他实例。这个组被忽略。这样,每一组都能保持平衡,假阳性率低。加上其他类别,就比baseline提高了2.7%。
总结
在推理过程中,首先使用训练好的模型生成z,然后使用Eqn在各组中应用softmax。除G0外,其他所有节点都被忽略,所有类别的概率按原始类别id排序。 p 0 0 在 G 0 被 当 做 时 候 前 景 区 域 的 概 率 p_0^0在G_0被当做时候前景区域的概率 p00G0,最后,我们用 p j = p 0 0 ∗ p j p_j=p_0^0*p_j pj=p00pj所有正态类别的概率进行重新缩放,这个新的概率向量被用于后面的极大值抑制算法NMS产生最后得检测结果。从技术上讲,p不是一个真实的概率向量,因为它的总和不等于1。它扮演原始概率向量的角色,通过选择最终的盒子来引导模型。

实验

在LVIS的结果
由于其他类占主导地位,baseline模型忽略了大多数尾部类别。考虑其他模型由模型(1)初始化,并由另外12个epoch进一步进行微调。为了确保改进不是来自于更长的训练计划,我们训练模型(1)与另外12个epoch进行公平比较。对比模型(2)和模型(1),我们发现长时间训练主要对AP2有所改善,但AP1仍保持在0左右。也就是说,对于次数少于10次的低射击类别,长时间的训练很难提高成绩。只有AP2显著增加,而AP4减少2.5%,AP1仍然为0。这说明在训练实例过少的情况下,原有的softmax分类器不能很好地进行分类。
论文方法与其他长尾数据分布的方法比较:
我们的结果大大超过了所有其他方法。AP1增加11.3%,AP2增加10.3%,AP3和AP4几乎没有变化。该结果验证了所设计的均衡组softmax模块的有效性
更换backbone模型,基本mAP提高5.6%

模型分析

我们的方法可以很好的平衡分类效果吗?

Balanced-Meta Softmax

 BALMS提出 Meta Sampler来自动学习最优采样率以配合Balanced Softmax,避免过平衡问题。BALMS在长尾图像分类与长尾实例分割的共四个数据集上取得SOTA表现。这项研究也被收录为ECCV LVIS workshop的spotlight。
论文名称: Balanced Meta-Softmax for Long-Tailed Visual Recognition
论文连接https://papers.nips.cc/paper/2020/file/2ba61cc3a8f44143e1f2f13b2b729ab3-Paper.pdf

代码地址:https://github.com/jiawei-ren/BalancedMetaSoftmax
方法介绍:

  1. Balanced Softmax
    目的:避免样本不均匀带来的分类偏差,偏差将分类结果倾向于训练样本更多的类别。
    在这里插入图片描述

对Softmax进行改造:
原来softmax: e η j ∑ i = 1 k e η i \frac{e^{\eta_j}}{\sum_{i=1}^ke^{\eta_i}} i=1keηieη

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值