tensorflow:批标准化(Bacth Normalization,BN)

批标准化(Bactch Normalization,BN)是为了克服神经网络加深导致难以训练而诞生的,随着神经网络深度加深,训练起来就会越来越困难,收敛速度回很慢,常常会导致梯度弥散问题(Vanishing Gradient Problem)。


统计机器学习中有一个经典的假设:Source Domain 和 Target Domain的数据分布是一致的也就是说,训练数据和测试数据是满足相同分布的。这是通过训练数据获得的模型能够在测试集上获得好的效果的一个基本保障。


Convariate Shift是指训练集的样本数据和目标样本集分布不一致时,训练得到的模型无法很好的Generalization。它是分布不一致假设之下的一个分支问题,也就是指Sorce Domain和Target Domain的条件概率一致的,但是其边缘概率不同。的确,对于神经网络的各层输出,在经过了层内操作后,各层输出分布就会与对应的输入信号分布不同,而且差异会随着网络深度增大而加大了,但每一层所指向的Label仍然是不变的。


解决办法一般是根据训练样本和目标样本的比例对训练样本做一个矫正。所以,通过引入Bactch Normalization来标准化某些层或者所有层的输入,从而固定每层输入信息的均值和方差


方法Bactch Normalization一般用在非线性映射(激活函数)之前,对x=Wu+b做标准化,是结果(输出信号各个维度)的均值为0,方差为1。让每一层的输入有一个稳定的分布会有利于网络的训练。


优点:Bactch Normalization通过标准化让激活函数分布在线性区间,结果就是加大了梯度,让模型更大胆的进行梯度下降,具有如下优点:

  • 加大搜索的步长,加快收敛的速度;
  • 更容易跳出局部最小值;
  • 破坏原来的数据分布,一定程度上缓解了过拟合;

因此,在遇到神经网络收敛速度很慢或梯度爆炸(Gradient Explore)等无法训练的情况系啊,都可以尝试用Bactch Normalization来解决。

梯度爆炸:梯度非常大,链式求导后乘积就变得很大,使权重变得非常大,产生指数级爆炸。

在GANs中我们充分利用bactch normalization的优点,加快模型的收敛速度。


batch normalization源码位于:

C:\Anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\python\ops\nn_impl.py

调用方法:tf.nn.batch_normalization()

[python] view plain copy
 print?
  1. def batch_normalization(x,  
  2.                         mean,  
  3.                         variance,  
  4.                         offset,  
  5.                         scale,  
  6.                         variance_epsilon,  
  7.                         name=None):  
  8.   r"""Batch normalization. 
  9.  
  10.   As described in http://arxiv.org/abs/1502.03167. 
  11.   Normalizes a tensor by `mean` and `variance`, and applies (optionally) a 
  12.   `scale` \\(\gamma\\) to it, as well as an `offset` \\(\beta\\): 
  13.  
  14.   \\(\frac{\gamma(x-\mu)}{\sigma}+\beta\\) 
  15.  
  16.   `mean`, `variance`, `offset` and `scale` are all expected to be of one of two 
  17.   shapes: 
  18.  
  19.     * In all generality, they can have the same number of dimensions as the 
  20.       input `x`, with identical sizes as `x` for the dimensions that are not 
  21.       normalized over (the 'depth' dimension(s)), and dimension 1 for the 
  22.       others which are being normalized over. 
  23.       `mean` and `variance` in this case would typically be the outputs of 
  24.       `tf.nn.moments(..., keep_dims=True)` during training, or running averages 
  25.       thereof during inference. 
  26.     * In the common case where the 'depth' dimension is the last dimension in 
  27.       the input tensor `x`, they may be one dimensional tensors of the same 
  28.       size as the 'depth' dimension. 
  29.       This is the case for example for the common `[batch, depth]` layout of 
  30.       fully-connected layers, and `[batch, height, width, depth]` for 
  31.       convolutions. 
  32.       `mean` and `variance` in this case would typically be the outputs of 
  33.       `tf.nn.moments(..., keep_dims=False)` during training, or running averages 
  34.       thereof during inference. 
  35.  
  36.   Args: 
  37.     x: Input `Tensor` of arbitrary dimensionality. 
  38.     mean: A mean `Tensor`. 
  39.     variance: A variance `Tensor`. 
  40.     offset: An offset `Tensor`, often denoted \\(\beta\\) in equations, or 
  41.       None. If present, will be added to the normalized tensor. 
  42.     scale: A scale `Tensor`, often denoted \\(\gamma\\) in equations, or 
  43.       `None`. If present, the scale is applied to the normalized tensor. 
  44.     variance_epsilon: A small float number to avoid dividing by 0. 
  45.     name: A name for this operation (optional). 
  46.  
  47.   Returns: 
  48.     the normalized, scaled, offset tensor. 
  49.   """  
  50.   with ops.name_scope(name, "batchnorm", [x, mean, variance, scale, offset]):  
  51.     inv = math_ops.rsqrt(variance + variance_epsilon)  
  52.     if scale is not None:  
  53.       inv *= scale  
  54.     return x * inv + (offset - mean * inv  
  55.                       if offset is not None else -mean * inv)  

C:\Anaconda3\envs\tensorflow\Lib\site-packages\tensorflow\contrib\layers\python\layers\layers.py

调用方法:tf.contrib.layers.batch_norm

[python] view plain copy
 print?
  1. def batch_norm(inputs,  
  2.                decay=0.999,  
  3.                center=True,  
  4.                scale=False,  
  5.                epsilon=0.001,  
  6.                activation_fn=None,  
  7.                param_initializers=None,  
  8.                param_regularizers=None,  
  9.                updates_collections=ops.GraphKeys.UPDATE_OPS,  
  10.                is_training=True,  
  11.                reuse=None,  
  12.                variables_collections=None,  
  13.                outputs_collections=None,  
  14.                trainable=True,  
  15.                batch_weights=None,  
  16.                fused=False,  
  17.                data_format=DATA_FORMAT_NHWC,  
  18.                zero_debias_moving_mean=False,  
  19.                scope=None,  
  20.                renorm=False,  
  21.                renorm_clipping=None,  
  22.                renorm_decay=0.99):  
  23.   """Adds a Batch Normalization layer from http://arxiv.org/abs/1502.03167. 
  24.  
  25.     "Batch Normalization: Accelerating Deep Network Training by Reducing 
  26.     Internal Covariate Shift" 
  27.  
  28.     Sergey Ioffe, Christian Szegedy 
  29.  
  30.   Can be used as a normalizer function for conv2d and fully_connected. 
  31.  
  32.   Note: When is_training is True the moving_mean and moving_variance need to be 
  33.   updated, by default the update_ops are placed in `tf.GraphKeys.UPDATE_OPS` so 
  34.   they need to be added as a dependency to the `train_op`, example: 
  35.  
  36.     update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS) 
  37.     with tf.control_dependencies(update_ops): 
  38.       train_op = optimizer.minimize(loss) 
  39.  
  40.   One can set updates_collections=None to force the updates in place, but that 
  41.   can have speed penalty, especially in distributed settings. 
  42.  
  43.   Args: 
  44.     inputs: A tensor with 2 or more dimensions, where the first dimension has 
  45.       `batch_size`. The normalization is over all but the last dimension if 
  46.       `data_format` is `NHWC` and the second dimension if `data_format` is 
  47.       `NCHW`. 
  48.     decay: Decay for the moving average. Reasonable values for `decay` are close 
  49.       to 1.0, typically in the multiple-nines range: 0.999, 0.99, 0.9, etc. 
  50.       Lower `decay` value (recommend trying `decay`=0.9) if model experiences 
  51.       reasonably good training performance but poor validation and/or test 
  52.       performance. Try zero_debias_moving_mean=True for improved stability. 
  53.     center: If True, add offset of `beta` to normalized tensor. If False, `beta` 
  54.       is ignored. 
  55.     scale: If True, multiply by `gamma`. If False, `gamma` is 
  56.       not used. When the next layer is linear (also e.g. `nn.relu`), this can be 
  57.       disabled since the scaling can be done by the next layer. 
  58.     epsilon: Small float added to variance to avoid dividing by zero. 
  59.     activation_fn: Activation function, default set to None to skip it and 
  60.       maintain a linear activation. 
  61.     param_initializers: Optional initializers for beta, gamma, moving mean and 
  62.       moving variance. 
  63.     param_regularizers: Optional regularizer for beta and gamma. 
  64.     updates_collections: Collections to collect the update ops for computation. 
  65.       The updates_ops need to be executed with the train_op. 
  66.       If None, a control dependency would be added to make sure the updates are 
  67.       computed in place. 
  68.     is_training: Whether or not the layer is in training mode. In training mode 
  69.       it would accumulate the statistics of the moments into `moving_mean` and 
  70.       `moving_variance` using an exponential moving average with the given 
  71.       `decay`. When it is not in training mode then it would use the values of 
  72.       the `moving_mean` and the `moving_variance`. 
  73.     reuse: Whether or not the layer and its variables should be reused. To be 
  74.       able to reuse the layer scope must be given. 
  75.     variables_collections: Optional collections for the variables. 
  76.     outputs_collections: Collections to add the outputs. 
  77.     trainable: If `True` also add variables to the graph collection 
  78.       `GraphKeys.TRAINABLE_VARIABLES` (see `tf.Variable`). 
  79.     batch_weights: An optional tensor of shape `[batch_size]`, 
  80.       containing a frequency weight for each batch item. If present, 
  81.       then the batch normalization uses weighted mean and 
  82.       variance. (This can be used to correct for bias in training 
  83.       example selection.) 
  84.     fused:  Use nn.fused_batch_norm if True, nn.batch_normalization otherwise. 
  85.     data_format: A string. `NHWC` (default) and `NCHW` are supported. 
  86.     zero_debias_moving_mean: Use zero_debias for moving_mean. It creates a new 
  87.       pair of variables 'moving_mean/biased' and 'moving_mean/local_step'. 
  88.     scope: Optional scope for `variable_scope`. 
  89.     renorm: Whether to use Batch Renormalization 
  90.       (https://arxiv.org/abs/1702.03275). This adds extra variables during 
  91.       training. The inference is the same for either value of this parameter. 
  92.     renorm_clipping: A dictionary that may map keys 'rmax', 'rmin', 'dmax' to 
  93.       scalar `Tensors` used to clip the renorm correction. The correction 
  94.       `(r, d)` is used as `corrected_value = normalized_value * r + d`, with 
  95.       `r` clipped to [rmin, rmax], and `d` to [-dmax, dmax]. Missing rmax, rmin, 
  96.       dmax are set to inf, 0, inf, respectively. 
  97.     renorm_decay: Momentum used to update the moving means and standard 
  98.       deviations with renorm. Unlike `momentum`, this affects training 
  99.       and should be neither too small (which would add noise) nor too large 
  100.       (which would give stale estimates). Note that `decay` is still applied 
  101.       to get the means and variances for inference. 
  102.  
  103.   Returns: 
  104.     A `Tensor` representing the output of the operation. 
  105.  
  106.   Raises: 
  107.     ValueError: If `batch_weights` is not None and `fused` is True. 
  108.     ValueError: If `param_regularizers` is not None and `fused` is True. 
  109.     ValueError: If `data_format` is neither `NHWC` nor `NCHW`. 
  110.     ValueError: If the rank of `inputs` is undefined. 
  111.     ValueError: If rank or channels dimension of `inputs` is undefined. 
  112.   """  
  113.   if fused:  
  114.     if batch_weights is not None:  
  115.       raise ValueError('Weighted mean and variance is not currently '  
  116.                        'supported for fused batch norm.')  
  117.     if param_regularizers is not None:  
  118.       raise ValueError('Regularizers are not currently '  
  119.                        'supported for fused batch norm.')  
  120.     if renorm:  
  121.       raise ValueError('Renorm is not supported for fused batch norm.')  
  122.     return _fused_batch_norm(  
  123.         inputs,  
  124.         decay=decay,  
  125.         center=center,  
  126.         scale=scale,  
  127.         epsilon=epsilon,  
  128.         activation_fn=activation_fn,  
  129.         param_initializers=param_initializers,  
  130.         updates_collections=updates_collections,  
  131.         is_training=is_training,  
  132.         reuse=reuse,  
  133.         variables_collections=variables_collections,  
  134.         outputs_collections=outputs_collections,  
  135.         trainable=trainable,  
  136.         data_format=data_format,  
  137.         zero_debias_moving_mean=zero_debias_moving_mean,  
  138.         scope=scope)  
  139.   
  140.   if data_format not in (DATA_FORMAT_NCHW, DATA_FORMAT_NHWC):  
  141.     raise ValueError('data_format has to be either NCHW or NHWC.')  
  142.   
  143.   layer_variable_getter = _build_variable_getter()  
  144.   with variable_scope.variable_scope(  
  145.       scope, 'BatchNorm', [inputs], reuse=reuse,  
  146.       custom_getter=layer_variable_getter) as sc:  
  147.     inputs = ops.convert_to_tensor(inputs)  
  148.   
  149.     # Determine whether we can use the core layer class.  
  150.     if (batch_weights is None and  
  151.         updates_collections is ops.GraphKeys.UPDATE_OPS and  
  152.         not zero_debias_moving_mean):  
  153.       # Use the core layer class.  
  154.       axis = 1 if data_format == DATA_FORMAT_NCHW else -1  
  155.       if not param_initializers:  
  156.         param_initializers = {}  
  157.       beta_initializer = param_initializers.get('beta',  
  158.                                                 init_ops.zeros_initializer())  
  159.       gamma_initializer = param_initializers.get('gamma',  
  160.                                                  init_ops.ones_initializer())  
  161.       moving_mean_initializer = param_initializers.get(  
  162.           'moving_mean', init_ops.zeros_initializer())  
  163.       moving_variance_initializer = param_initializers.get(  
  164.           'moving_variance', init_ops.ones_initializer())  
  165.       if not param_regularizers:  
  166.         param_regularizers = {}  
  167.       beta_regularizer = param_regularizers.get('beta')  
  168.       gamma_regularizer = param_regularizers.get('gamma')  
  169.       layer = normalization_layers.BatchNormalization(  
  170.           axis=axis,  
  171.           momentum=decay,  
  172.           epsilon=epsilon,  
  173.           center=center,  
  174.           scale=scale,  
  175.           beta_initializer=beta_initializer,  
  176.           gamma_initializer=gamma_initializer,  
  177.           moving_mean_initializer=moving_mean_initializer,  
  178.           moving_variance_initializer=moving_variance_initializer,  
  179.           beta_regularizer=beta_regularizer,  
  180.           gamma_regularizer=gamma_regularizer,  
  181.           trainable=trainable,  
  182.           renorm=renorm,  
  183.           renorm_clipping=renorm_clipping,  
  184.           renorm_momentum=renorm_decay,  
  185.           name=sc.name,  
  186.           _scope=sc,  
  187.           _reuse=reuse)  
  188.       outputs = layer.apply(inputs, training=is_training)  
  189.   
  190.       # Add variables to collections.  
  191.       _add_variable_to_collections(  
  192.           layer.moving_mean, variables_collections, 'moving_mean')  
  193.       _add_variable_to_collections(  
  194.           layer.moving_variance, variables_collections, 'moving_variance')  
  195.       if layer.beta:  
  196.         _add_variable_to_collections(layer.beta, variables_collections, 'beta')  
  197.       if layer.gamma:  
  198.         _add_variable_to_collections(  
  199.             layer.gamma, variables_collections, 'gamma')  
  200.   
  201.       if activation_fn is not None:  
  202.         outputs = activation_fn(outputs)  
  203.       return utils.collect_named_outputs(outputs_collections,  
  204.                                          sc.original_name_scope, outputs)  
  205.   
  206.     # Not supported by layer class: batch_weights argument,  
  207.     # and custom updates_collections. In that case, use the legacy BN  
  208.     # implementation.  
  209.     # Custom updates collections are not supported because the update logic  
  210.     # is different in this case, in particular w.r.t. "forced updates" and  
  211.     # update op reuse.  
  212.     if renorm:  
  213.       raise ValueError('renorm is not supported with batch_weights, '  
  214.                        'updates_collections or zero_debias_moving_mean')  
  215.     inputs_shape = inputs.get_shape()  
  216.     inputs_rank = inputs_shape.ndims  
  217.     if inputs_rank is None:  
  218.       raise ValueError('Inputs %s has undefined rank.' % inputs.name)  
  219.     dtype = inputs.dtype.base_dtype  
  220.     if batch_weights is not None:  
  221.       batch_weights = ops.convert_to_tensor(batch_weights)  
  222.       inputs_shape[0:1].assert_is_compatible_with(batch_weights.get_shape())  
  223.       # Reshape batch weight values so they broadcast across inputs.  
  224.       nshape = [-1] + [1 for _ in range(inputs_rank - 1)]  
  225.       batch_weights = array_ops.reshape(batch_weights, nshape)  
  226.   
  227.     if data_format == DATA_FORMAT_NCHW:  
  228.       moments_axes = [0] + list(range(2, inputs_rank))  
  229.       params_shape = inputs_shape[1:2]  
  230.       # For NCHW format, rather than relying on implicit broadcasting, we  
  231.       # explicitly reshape the params to params_shape_broadcast when computing  
  232.       # the moments and the batch normalization.  
  233.       params_shape_broadcast = list(  
  234.           [1, inputs_shape[1].value] + [1 for _ in range(2, inputs_rank)])  
  235.     else:  
  236.       moments_axes = list(range(inputs_rank - 1))  
  237.       params_shape = inputs_shape[-1:]  
  238.       params_shape_broadcast = None  
  239.     if not params_shape.is_fully_defined():  
  240.       raise ValueError('Inputs %s has undefined channels dimension %s.' % (  
  241.           inputs.name, params_shape))  
  242.   
  243.     # Allocate parameters for the beta and gamma of the normalization.  
  244.     beta, gamma = NoneNone  
  245.     if not param_initializers:  
  246.       param_initializers = {}  
  247.     if center:  
  248.       beta_collections = utils.get_variable_collections(variables_collections,  
  249.                                                         'beta')  
  250.       beta_initializer = param_initializers.get('beta',  
  251.                                                 init_ops.zeros_initializer())  
  252.       beta = variables.model_variable('beta',  
  253.                                       shape=params_shape,  
  254.                                       dtype=dtype,  
  255.                                       initializer=beta_initializer,  
  256.                                       collections=beta_collections,  
  257.                                       trainable=trainable)  
  258.     if scale:  
  259.       gamma_collections = utils.get_variable_collections(variables_collections,  
  260.                                                          'gamma')  
  261.       gamma_initializer = param_initializers.get('gamma',  
  262.                                                  init_ops.ones_initializer())  
  263.       gamma = variables.model_variable('gamma',  
  264.                                        shape=params_shape,  
  265.                                        dtype=dtype,  
  266.                                        initializer=gamma_initializer,  
  267.                                        collections=gamma_collections,  
  268.                                        trainable=trainable)  
  269.   
  270.     # Create moving_mean and moving_variance variables and add them to the  
  271.     # appropriate collections. We disable variable partitioning while creating  
  272.     # them, because assign_moving_average is not yet supported for partitioned  
  273.     # variables.  
  274.     partitioner = variable_scope.get_variable_scope().partitioner  
  275.     try:  
  276.       variable_scope.get_variable_scope().set_partitioner(None)  
  277.       moving_mean_collections = utils.get_variable_collections(  
  278.           variables_collections, 'moving_mean')  
  279.       moving_mean_initializer = param_initializers.get(  
  280.           'moving_mean', init_ops.zeros_initializer())  
  281.       moving_mean = variables.model_variable(  
  282.           'moving_mean',  
  283.           shape=params_shape,  
  284.           dtype=dtype,  
  285.           initializer=moving_mean_initializer,  
  286.           trainable=False,  
  287.           collections=moving_mean_collections)  
  288.       moving_variance_collections = utils.get_variable_collections(  
  289.           variables_collections, 'moving_variance')  
  290.       moving_variance_initializer = param_initializers.get(  
  291.           'moving_variance', init_ops.ones_initializer())  
  292.       moving_variance = variables.model_variable(  
  293.           'moving_variance',  
  294.           shape=params_shape,  
  295.           dtype=dtype,  
  296.           initializer=moving_variance_initializer,  
  297.           trainable=False,  
  298.           collections=moving_variance_collections)  
  299.     finally:  
  300.       variable_scope.get_variable_scope().set_partitioner(partitioner)  
  301.   
  302.     # If `is_training` doesn't have a constant value, because it is a `Tensor`,  
  303.     # a `Variable` or `Placeholder` then is_training_value will be None and  
  304.     # `needs_moments` will be true.  
  305.     is_training_value = utils.constant_value(is_training)  
  306.     need_moments = is_training_value is None or is_training_value  
  307.     if need_moments:  
  308.       # Calculate the moments based on the individual batch.  
  309.       if batch_weights is None:  
  310.         if data_format == DATA_FORMAT_NCHW:  
  311.           mean, variance = nn.moments(inputs, moments_axes, keep_dims=True)  
  312.           mean = array_ops.reshape(mean, [-1])  
  313.           variance = array_ops.reshape(variance, [-1])  
  314.         else:  
  315.           mean, variance = nn.moments(inputs, moments_axes)  
  316.       else:  
  317.         if data_format == DATA_FORMAT_NCHW:  
  318.           mean, variance = nn.weighted_moments(inputs, moments_axes,  
  319.                                                batch_weights, keep_dims=True)  
  320.           mean = array_ops.reshape(mean, [-1])  
  321.           variance = array_ops.reshape(variance, [-1])  
  322.         else:  
  323.           mean, variance = nn.weighted_moments(inputs, moments_axes,  
  324.                                                batch_weights)  
  325.   
  326.       moving_vars_fn = lambda: (moving_mean, moving_variance)  
  327.       if updates_collections is None:  
  328.         def _force_updates():  
  329.           """Internal function forces updates moving_vars if is_training."""  
  330.           update_moving_mean = moving_averages.assign_moving_average(  
  331.               moving_mean, mean, decay, zero_debias=zero_debias_moving_mean)  
  332.           update_moving_variance = moving_averages.assign_moving_average(  
  333.               moving_variance, variance, decay, zero_debias=False)  
  334.           with ops.control_dependencies([update_moving_mean,  
  335.                                          update_moving_variance]):  
  336.             return array_ops.identity(mean), array_ops.identity(variance)  
  337.         mean, variance = utils.smart_cond(is_training,  
  338.                                           _force_updates,  
  339.                                           moving_vars_fn)  
  340.       else:  
  341.         def _delay_updates():  
  342.           """Internal function that delay updates moving_vars if is_training."""  
  343.           update_moving_mean = moving_averages.assign_moving_average(  
  344.               moving_mean, mean, decay, zero_debias=zero_debias_moving_mean)  
  345.           update_moving_variance = moving_averages.assign_moving_average(  
  346.               moving_variance, variance, decay, zero_debias=False)  
  347.           return update_moving_mean, update_moving_variance  
  348.   
  349.         update_mean, update_variance = utils.smart_cond(is_training,  
  350.                                                         _delay_updates,  
  351.                                                         moving_vars_fn)  
  352.         ops.add_to_collections(updates_collections, update_mean)  
  353.         ops.add_to_collections(updates_collections, update_variance)  
  354.         # Use computed moments during training and moving_vars otherwise.  
  355.         vars_fn = lambda: (mean, variance)  
  356.         mean, variance = utils.smart_cond(is_training, vars_fn, moving_vars_fn)  
  357.     else:  
  358.       mean, variance = moving_mean, moving_variance  
  359.     if data_format == DATA_FORMAT_NCHW:  
  360.       mean = array_ops.reshape(mean, params_shape_broadcast)  
  361.       variance = array_ops.reshape(variance, params_shape_broadcast)  
  362.       beta = array_ops.reshape(beta, params_shape_broadcast)  
  363.       if gamma is not None:  
  364.         gamma = array_ops.reshape(gamma, params_shape_broadcast)  
  365.   
  366.     # Compute batch_normalization.  
  367.     outputs = nn.batch_normalization(inputs, mean, variance, beta, gamma,  
  368.                                      epsilon)  
  369.     outputs.set_shape(inputs_shape)  
  370.     if activation_fn is not None:  
  371.       outputs = activation_fn(outputs)  
  372.     return utils.collect_named_outputs(outputs_collections,  
  373.                                        sc.original_name_scope, outputs)  

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值