MATH49111/69111 Project 2a: Neural NetworksR

Java Python MATH49111/69111

Project 2a: Neural Networks

1 Introduction

The idea that computers may be able to think or exhibit intelligence dates back to the dawn of the computing era.  Alan Turing argued in his seminal 1950 paper Computing Machinery and Intelligence that digital computers should be capable of thinking, and introduced the famous ‘Turing test’ to define what was meant by ‘thinking’ in this context:  the computer should be able to emulate a human mind.   He also suggested that a computer exhibiting human intelligence could not be programmed in the usual way (where the programmer has ‘a clear mental picture of the state of the machine at each moment in the computation’, as he put it) but would instead learn like a human, drawing inferences from examples.  Turing had previously identified that learning of this sort could occur in what he called an unorganised machine: a computational model of a brain consisting of many simple logical units connected together in a network to form a large and complex system.

Although a computer emulation of a complete human mind is still some way off, Turing’s idea that a network could be taught and learn was prescient.  Models of this sort are now known as Artificial Neural Networks  (ANNs),  and the process by which they learn from examples is known as supervised learning, part of the large field of machine learning.  In the last decade or so, ANNs have developed to become the best algorithms available for a range of ‘hard’ problems, including image recognition and classification, machine translation, text-to-speech, and playing of complex games such as Go.

In this project we will develop a simple neural network and train it to classify data into one  of two  classes,  a  so-called binary  classification problem.   (This  problem  is  discussed further by Higham and Higham (2019) whose notation we use in this project.)  Suppose we have a set of points in R2 , as shown in Fig.  1, acquired from some real-world measurements, with each point being of one of two classes, either a blue cross or a red circle:

Figure 1:  A set of data points in R2 , each of which is  of one of two  types .

This type of data could arise in many applications, for example the success or failure of an industrial chemical process as a function of ambient temperature and humidity,  or whether a customer has missed a loan payment as a function of their income and credit score.

Given a new point x ∈ R2 , we’d like to be able to predict from the existing data which of the two classes this new point is likely to be in.  Since our example data shows a clear pattern (red circles tend to lie at larger values of x1  than blue crosses), we could sketch a curve by eye (as in Fig.  2) that divides the plane into two, with the red circles on one side and the blue crosses on the other.

Figure 2: The plane is divided by a curve, with points on each side classified differently. Given a new point on the plane, we can now estimate which class it is in by seeing which side of the line it lies.

We could then classify any new point x ∈ R2   by seeing on which side of the curve it lies.  Our neural network will perform this classification for us by (1) learning from existing data where the boundary between the two classes of data point lies, and  (2) allowing us to evaluate the likely class of a new data point by using the boundary found in step  (1). Specifically, the network defines a function F (x), where the curve dividing our two types of point is the zero contour F (x) = 0.  Regions where the network outputs a positive value F (x) > 0 correspond to one class, and regions where it is negative to the other class.

Although this two-dimensional problem is relatively simple, the neural network developed extends naturally to much more difficult classification problems in higher dimensions (where x has hundreds or thousands of components), with more than two classes of points,  and where the boundaries between different regions are not as clear as in our example above. One example is character recognition, as used by banks and postal services to automatically read hand-written cheques and postcodes. In this case, a segmentation algorithm is applied to split the scanned text into lots of small images each containing a single digit.  Suppose each such small image has 25 × 25 = 625 greyscale pixels; the data in it can be represented by a vector x ∈ R625 .  A neural network of exactly the sort we develop can be used to classify a handwritten character encoded in the vector x into one of 36 classes (one for each of the characters 0–9 and A–Z) with very good accuracy.

2 Neural Networks: Theory

2.1 Overall setup

An artificial neural network consists of a set of neurons. A neuron is a simple scalar functional unit with a single input and a single output, modelled by an nonlinear activation function σ  :  R  →  R.   In  the feed-forward networks we will  study,  these  neurons  are  arranged  in a  series  of  layers,  with  the  outputs  of  neurons  in  one  layer  connected  to  the  inputs  of neurons in the next,  as shown in Fig.   3.   The networks in this project will also be fully connected, meaning that nodes in two adjacent layers and the connections between them form a complete bipartite graph (every neuron in one layer is connected to every neuron in the next).

Input layer      Layer 2          Layer 3     Output layer

Figure  3: Fully-connected network  topology  with  two  input  neurons,  two  hidden  layers  of four neurons each, and one output neuron.

The inputs to the neural network are specified at the first layer;  in our classification problem, this consists of the two components of the input vector x = (x1 , x2 ).  The output of the network is the output of the final layer of neurons; in our example this is just a single neuron, outputting a real number that we hope is close to either +1 or −1, depending on the class of the point.  In between are one or more hidden layers.  The network as a whole defines a nonlinear function, in this case R2  → R (two inputs, one output).

As noted above, there are two stages to using a neural network.

1.  The network is trained by specifying both the input to the network and the desired (target) output.  Through an iterative procedure (“learning”), parameters relating to the connections between neurons (known as the weights and biases, defined below) are modified so that the network reproduces the target output for a wide range of input data.  The overall structure of the network (the number of layers, and the number of neurons in each layer) is not changed in the training.

2.  The weights and biases are then fixed, and the output of the network is evaluated for arbitrary new input data.

We will consider initially the second of these steps,  assuming that the network has been pre-trained and that the weights and biases are known.

2.2 Evaluating the neural net output

Since each neuron is a simple scalar function  (R → R), the multiple inputs into a neuron from neurons in the previous layer must be combined into a single scalar input.  To describe how this is done we first establish some notation.  Let L represent the total number of layers in the network and nl  represent the number of neurons at layer l, for l = 1, 2, . . . , L.  The input to the jth neuron at layer l is denoted by zj[l]  and the output from the jth neuron at layer l by aj[l] .  The statement that each neuron is modelled by an activation function σ can then be written algebraically as

for l = 2, 3, . . . , L,    j = 1, . . . , nl ,                            (1)

where the subscript. on the activation function implies that different layers may use different activation functions (but all the neurons in a layer use the same one).  The statement that the inputs to the neural network are specified at the first layer can be written as

= xj ,    j = 1, . . . , n1 .

Note that the input to the network xj   determines the outputs aj([1])  of the neurons in the

first layer rather than their inputs, so  (1) is not evaluated for the first layer l  =  1.   The output of the network is the output of the final layer of neurons, aj[L] .  Consequently, the network has n1   (scalar) inputs and nL   (scalar) outputs.  The notation can be simplified by defining the neuron inputs and outputs as vectors z[l] , a[l]  ∈ Rnl , with components zj[l]  and aj[l]  respectively.  Then, defining the activation function to act componentwise, (1) becomes

a[l]  = σl  (z[l]) ,         for l = 2, 3, . . . , L.                                           (3)

The activation function is typically a nonlinear monotonically increasing function, but may have one of a number of different functional forms.  One common choice is the tanh function,

σ(z) = tanh(z).                                                                (4)

While (3) and (4) together define the behaviour of the individual neurons, the more sig- nificant part of a neural network is the set of connections between neurons, which determine how the input zj[l]   (of a neuron j in layer l) is determined from the outputs of the neurons in the previous layer.  Specifically, a neuron input zj[l]  is a biased linear combination of the outputs of the neurons in layer l − 1,

for l = 2, 3, . . . , L,    j = 1, . . . , nl ,           (5)

where wj[lk]   are the weights and bj[l]  are the biases at layer l.  These weights and biases are adjusted while the network is learning from the training data, but thereafter remain fixed. Defining b [l]  ∈ Rnl    as the vector of biases and W [l]  ∈ Rnl    × Rnl − 1    as the matrix of weights at layer l, with components bj[l]  and wj[lk]  respectively, we can write  (5) as

z[l]  = W [l]a[l−1]  + b[l] ,        for l = 2, 3, . . . , L.                                     (6)

Combining this with (3) and (2) we find

a[1]  = x,                                                                                                                              (7)

a[l]  = σl  (z[l]) = σl  (W [l]a[l−1]  + b[l])                    for    l = 2, 3, . . . , L.             (8)

Equations (7) and (8) together describe the feed-forward algorithm for evaluating the output of a neural network given an input vector x (and known weights W [l]  and biases b[l]).  First (7) is used to define a[1] , then  (8) is used for l = 2, 3, . . . , L to obtain the neuron outputs a[2] , a[3] , . . . , a[L]  in turn.  The last of these, a[L], is the output of the neural network.

2.3 Training the neural network

2.3.1 Gradient descent

The feed-forward algorithm outlined in the previous section allows the output of the neural network MATH49111/69111 Project 2a: Neural NetworksR  to be evaluated from its inputs, provided that the weights and biases at each layer of the network are known.  These parameters are determined by training the neural network against  measurements  or  other  data  for  which  both  the  input  and  target  output  of the network are known.

This training data consists of a set of N inputs to the network, x{i}, with N corresponding target outputs, denoted y{i}  (i = 1, . . . , N).  We can formalise the idea of how closely our network produces the target outputs y{i}  (when given the corresponding inputs x{i}) by defining a total cost function,

where ‘total cost’ refers to this cost being the sum of costs over all the N training data, and

the squared L2  norm Ⅱ · Ⅱ2(2) of a vector is the sum of its components squared,

xⅡ2(2)  = x1(2)  + x2(2)  + · · · + xn(2) .                                                    (10)

In the cost function (9), for each training datum i the norm is taken of the difference between the  target  network  output y{i}  and  the  actual  output  of the  neural  network a[L](x{i}), evaluated from the corresponding network input x{i}  by using the feed-forward algorithm. If the network reproduces exactly the target output for every piece of input data, the cost function is equal to its minimum possible value, zero.

For a given set of training data  (x{i} , y{i})   (i = 1, ..., N) , the cost C is a function of the parameters of the neural network, namely all the weights and biases for all of the layers. We could write this dependence explicitly by using  (8) recursively to express a[L](x{i}) in terms of a[1]  = x{i}  and the weights and biases of layers 2 to L,

The network sketched above with nl  = (2, 4, 4, 1) has in total 9 biases (one for each hidden and output neuron) and 28 weights  (one for each connection between a pair of neurons), making the cost C dependent on 37 parameters in total. For ease of notation we denote by p ∈ RM   the vector containing the weights and biases at each layer that parameterise the network, where M is the number of such parameters (in this case M = 37).  We can then write C = C(p) without needing to refer to the weights and biases explicitly.

The process of training the network is equivalent to minimising the difference between the desired  (target) and actual neural network outputs for the training data, or in other words, choosing p (and thus the weights and biases at each layer) so as to minimise the cost C(p), which is a nonlinear function of p.

We will solve this problem using an iterative method based on the classical technique of steepest descent.  Suppose we have a current vector of parameters p and initial cost C(p), and wish to generate a new vector of parameters p + δp such that the new cost C(p + δp) is minimised. What value should we choose for δp? For small δp we can use a Taylor series to expand the new cost

where pr   is the rth component of the parameter vector p, and terms of order  (δp)2   and higher have been neglected.  Equivalently, we can write this in vector form,

C(p + δp) ≈ C(p) + (∇C(p)) · δp,                                            (13)

where the gradient operator ∇ acts over each of the components of p (i.e.  over each weight and bias in the network),

To minimise the new weight C(p + δp) we wish to make the final term of (13) as negative as possible.  The Cauchy-Schwartz inequality states that

| (∇C(p)) · δp| ≤ ∥∇C(p)∥2 ∥δp∥2 ,                                            (15)

with equality only when ∇C(p) lies in the same direction as  (is a multiple of) δp.   This suggests that to minimise C(p + δp) we should choose δp in the direction of −∇C(p), and so we take

δp = −η∇C(p),                                                             (16)

where  the  positive  constant  η  is  the learning  rate.   The  training  of  the  neural  network therefore starts by choosing some initial parameters p for the network (we will choose these randomly), and repeatedly performing the iteration

p p + δp = p − η∇C(p).                                                  (17)

We would like this iteration to approach the global minimum of C(p) within a reasonable number of steps.  Unfortunately, minimisation of a nonlinear function in high dimensions (recall that even in our simple network p has 37 components) is a fundamentally difficult problem. For sufficiently small η, the Taylor series approximation (13) will be valid and each iteration of (17) will decrease the cost function.  However, for this to be true in general, η has be arbitrarily small. In practice, setting η to a very small value means that that p changes only very slightly at each iteration, and a very large number of steps is needed to converge to a minimum.  On the other hand, setting η too large means that the approximation (13) is rarely valid and the choice of step  (16) will not decrease the cost function as desired. Choosing an appropriate learning rate η is a balance between these two considerations.

Even if the iteration converges, it may converge to a local minimum of C, where ▽C = 0, but where C is nonetheless much larger than the global minimum cost minp (C(p)).  Finding the global minimum of an arbitrary nonlinear function in a high-dimensional space is near impossible.   We therefore abandon the goal of finding the global minimum of C(p),  and instead simply look for a value of p that has a cost C(p) less than some small threshold, τ , say.

2.3.2 Stochastic gradient descent

Each  step  of the  steepest  descent  iteration  (17)  requires  an  evaluation  of ▽C(p)  which contains the derivatives of the cost function with respect to each of the weights and biases in the network.  Recalling the definition of the cost function (9), we write

▽C (18)

where the contribution to the cost from each of the training data points i is

yⅡ (19)

Evaluation  of the  gradient  of the  cost  (18)  can  be  computationally  expensive,  since  the number of points N in the training data set may be large (many tens of thousands), and the number of elements in ▽C  (the number of parameters in the weights and biases) may be several million in a large neural network.  This means that it takes a long time to calculate each iteration of the steepest descent method (17).

A faster alternative is to instead calculate the increment δp at each iteration from the gradient of cost associated with a single randomly chosen training point,

p p + δp = p − η▽Cx{i} (p),                                               (20)

a technique called stochastic gradient descent, or simply stochastic gradient.  In stochastic gradient the reduction in the total cost function at  (9) is likely to be smaller than in the steepest descent method (17) – in fact many steps are likely to increase the total cost slightly – but since many more iterations can be performed in a given time, the convergence is often quicker.

There are several ways of choosing the training data point i to be used at each step, but we will use the simplest:  at each step randomly select  (with replacement) a training data point i independently of previous selections.

2.3.3 Evaluating ▽Cx{i} (p): Back-propagation

To use the stochastic gradient algorithm  (20) we must evaluate ▽Cx{i} (p), the derivative of the cost function associated with a single training data point with respect to each of the

network weights and biases,

for    l = 2, . . . , L,                                                (21)

for    l = 2, . . . , L.                                                (22)

The dependence of the cost Cx{i}   on the weights and biases seems rather complicated, so an obvious temptation is to evaluate the derivatives by finite-differencing.  This is possible, easy to implement and highly recommended for validation/debugging purposes (see below). However the approach is also extremely expensive and not viable for practical computations with big neural networks.

It turns out that the recursive nature of the network helps to simplify the analytical (and  efficient)  calculation  of the  required  derivatives  through  an  algorithm  called back- propagation.  Details can be found in the paper by Higham and Higham  (2019).  The key quantities required are known (for somewhat obscure reasons) as the errors, defined as

for 1 ≤ j ≤ nl

and    2 ≤ l ≤ L.               (23)

They represents the derivative of the cost for our chosen training point i with respect to the input z to the jth neuron at layer l.

Higham and Higham (2019) show that the errors can be computed by moving backwards through the network, starting with the output layer, l = L, for which

δ [L]  = σL(′)(z[L]) ◦ (a[L]  — y{i}) ,                                               (24)

where the operator ◦ is the componentwise (Hadamard) product, defined by

u v = (u1 v1 , u2 v2, . . . , un vn )    for u, v ∈ Rn.                               (25)

The Hadamard product can be avoided by writing equation  (24) using a product with a diagonal matrix,

Having established the errors for the output layer (l = L), we then move backwards through the network using the recurrence

δ [l]  = σl(′) (z[l]) ◦ (W[l+1])T δ [l+1]            for    l = L — 1 . . . , 2,                       (27)

which can again be written as

δ [l]  =  (I σl(′) (z1[l]) σl(′) (z2[l])     . . . for  l = L — 1, . . . , 2.

\ σl(′) (zn[ll] )  ,

Note how this produces the errors in layer l from those already computed in layer l + 1.

The real punchline is that the derivatives (21) and (22) can be expressed in terms of the

the errors as

for

l = 2, . . . , L,                                        (29)

for l = 2, . . . , L.                 (30)

This is not only very neat (much neater than one may have expected, given the complicated structure of the network) but also much more efficient than the evaluation of the derivatives by finite-differencing         

<think>好的,我现在需要帮助用户解决他们遇到的JavaScript神经网络代码中的错误。用户提供的报错信息是TypeError: Cannot read properties of undefined (reading 'map'),发生在index.js的第39行,也就是前向传播过程中隐藏层输出的计算部分。我需要仔细分析这个问题,并给出正确的解决方案。 首先,报错信息显示hidden_input是undefined,导致无法调用map方法。这说明在调用map的时候,hidden_input变量没有被正确赋值或者初始化。那hidden_input是从哪里来的呢?通常在前向传播中,隐藏层的输入应该是权重矩阵与输入数据的乘积加上偏置。因此,我需要检查前向传播部分的代码,特别是隐藏层输入的计算是否正确。 用户提供的代码片段可能类似于以下结构: class NeuralNetwork { constructor(input_size, hidden_size, output_size) { this.weights1 = ...; this.weights2 = ...; this.bias1 = ...; this.bias2 = ...; } forward(input) { let hidden_input = math.multiply(this.weights1, input); hidden_input = math.add(hidden_input, this.bias1); let hidden_output = hidden_input.map(row => row.map(x => this.sigmoid(x))); // ...后续代码 } } 问题可能出在矩阵运算的结果没有被正确处理。例如,如果使用math.js库进行矩阵乘法,返回的可能是一个Matrix对象,而不是原生的JavaScript数组,这会导致map方法不可用。因此,需要将Matrix对象转换为数组,或者在计算时确保结果是一个数组。 另一个可能性是权重矩阵或输入数据的维度不匹配,导致矩阵乘法结果不正确,甚至未定义。例如,如果this.weights1的列数与输入数据的行数不匹配,矩阵乘法会失败,返回undefined或抛出错误,从而导致hidden_input为undefined。 此外,初始化权重和偏置时是否正确创建了矩阵结构也很重要。例如,如果权重矩阵未正确初始化,可能会导致后续计算出错。 针对这些可能性,我需要逐步排查: 1. 确保权重矩阵和输入数据的维度匹配。例如,输入数据应该是二维数组(矩阵),并且权重矩阵的列数等于输入的行数。 2. 检查是否在使用数学库(如math.js)时正确处理了矩阵运算的结果,可能需要使用库提供的方法来获取矩阵的数据,如math.multiply的结果是否为Matrix对象,是否需要调用toArray()方法转换为数组。 3. 确认隐藏层输入的计算是否正确,包括矩阵乘法和偏置的加法是否按正确的方式执行。 假设用户使用的是math.js库,矩阵相乘后返回的是Matrix对象,而直接对这个对象调用map会导致错误,因为原生的map方法不适用于Matrix对象。因此,正确的做法应该是将Matrix转换为数组,或者使用math.js自带的map方法。 例如,原代码可能错误地处理了矩阵运算的结果: let hidden_input = math.multiply(this.weights1, input); 这里math.multiply返回的是Matrix对象,而用户尝试调用hidden_input.map,但Matrix对象没有map方法,因此导致错误。正确的做法应该是将结果转换为数组: let hidden_input = math.multiply(this.weights1, input).toArray(); 或者在计算时使用库提供的函数来处理,比如使用math.map函数。 另外,还需要检查输入数据input的格式是否正确,是否是一个二维数组,并且维度与权重矩阵匹配。例如,输入数据可能应该是列向量,即[[x1], [x2], ..., [xn]],而权重矩阵的列数应等于输入数据的行数。 此外,初始化权重矩阵时,是否正确地创建了二维数组结构?例如,this.weights1应该是一个hidden_size行,input_size列的矩阵,而this.bias1应该是一个hidden_size行1列的矩阵。如果初始化不正确,也会导致后续计算错误。 综合这些可能性,修正后的代码应该包括: 1. 确保所有矩阵运算的结果转换为数组,或者在运算时使用正确的数据结构。 2. 检查权重和偏置的初始化是否正确。 3. 确保输入数据的维度和类型正确。 例如,修改前向传播中的hidden_input计算: forward(input) { // 确保输入是二维数组,例如列向量 let inputMatrix = math.matrix(input); let weights1Matrix = math.matrix(this.weights1); let hidden_input = math.multiply(weights1Matrix, inputMatrix); hidden_input = math.add(hidden_input, math.matrix(this.bias1)); // 转换为数组 hidden_input = hidden_input.toArray(); // 现在可以安全使用map let hidden_output = hidden_input.map(row => row.map(x => this.sigmoid(x))); // 后续处理... } 同时,检查构造函数中的权重初始化是否正确: constructor(input_size, hidden_size, output_size) { // 初始化权重矩阵和偏置,例如使用随机值 this.weights1 = math.random([hidden_size, input_size]); this.weights2 = math.random([output_size, hidden_size]); this.bias1 = math.random([hidden_size, 1]); this.bias2 = math.random([output_size, 1]); } 这样,hidden_input会被正确计算并转换为数组,从而避免.map的错误。 总结问题所在:原代码在处理矩阵运算后未正确转换结果类型,导致尝试在非数组对象上调用map方法,从而引发TypeError。修正方法包括正确使用数学库的API,转换矩阵为数组,并确保维度匹配。</think>让我们逐步分析并解决代码中的错误: **错误原因分析:** 1. `hidden_input`变量未定义导致`map()`错误 2. 矩阵维度不匹配或数学库使用不当 3. 权重/偏置初始化不正确 **修复后的神经网络代码:** ```javascript const math = require('mathjs'); class NeuralNetwork { constructor(inputSize, hiddenSize, outputSize) { // 正确初始化权重矩阵 (使用He初始化) this.weights1 = math.random([hiddenSize, inputSize], -1, 1) * math.sqrt(2/inputSize); this.weights2 = math.random([outputSize, hiddenSize], -1, 1) * math.sqrt(2/hiddenSize); // 正确初始化偏置向量 this.bias1 = math.zeros(hiddenSize, 1); this.bias2 = math.zeros(outputSize, 1); } sigmoid(x) { return 1 / (1 + math.exp(-x)); } forward(input) { // 转换为列向量 const inputVector = math.reshape(input, [input.length, 1]); // 隐藏层计算 const hiddenInput = math.add( math.multiply(this.weights1, inputVector), this.bias1 ); // 激活函数应用 const hiddenOutput = math.map(hiddenInput, x => this.sigmoid(x)); // 输出层计算 const outputInput = math.add( math.multiply(this.weights2, hiddenOutput), this.bias2 ); return math.map(outputInput, x => this.sigmoid(x)); } train(X, y, epochs=1000, lr=0.1) { for (let epoch = 0; epoch < epochs; epoch++) { X.forEach((sample, idx) => { // 前向传播 const inputVector = math.reshape(sample, [sample.length, 1]); const target = math.reshape(y[idx], [y[idx].length, 1]); // 完整前向传播流程 const hiddenInput = math.add( math.multiply(this.weights1, inputVector), this.bias1 ); const hiddenOutput = math.map(hiddenInput, x => this.sigmoid(x)); const outputInput = math.add( math.multiply(this.weights2, hiddenOutput), this.bias2 ); const output = math.map(outputInput, x => this.sigmoid(x)); // 反向传播计算梯度... // (此处应添加完整的反向传播逻辑) }); } } } // 测试示例 const nn = new NeuralNetwork(2, 4, 1); const X = [[0,0], [0,1], [1,0], [1,1]]; const y = [[0], [1], [1], [0]]; console.log("**** 开始训练 ****"); nn.train(X, y, 1000, 0.1); ``` **主要改进点:** 1. 使用`mathjs`库的矩阵操作方法代替原生数组操作 2. 确保所有中间结果保持矩阵格式 3. 使用`math.map()`代替原生`Array.map()` 4. 正确的权重初始化方法(He初始化) 5. 输入数据自动转换为列向量 6. 明确的矩阵维度管理 **使用注意事项:** 1. 需要先安装mathjs库:`npm install mathjs` 2. 输入数据应保持二维数组格式 3. 学习率(lr)建议设置在0.01-0.1之间 4. 建议添加反向传播完整实现 5. 建议添加预测函数和精度评估方法 如果需要补充反向传播的具体实现,可以进一步说明具体需求,我会提供完整实现方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值