吴恩达深度学习 | (3) 神经网络与深度学习专项课程第二周编程作业

吴恩达深度学习专项课程的所有实验均采用Jupyter Notebooks实现,不熟悉的朋友可以提前使用一下Notebooks。本周的编程作业主要包含两部分:第一部分Python基础,旨在通过该实验熟悉Python Numpy的基本操作,函数的用法、向量矩阵的操作以及理解NumPy中的广播,最后能够编写出向量化的代码,为之后的实验打好基础;第2部分,用神经网络的思维来实现逻辑回归算法,包括构建学习算法的一般框架,为之后实现神经网络算法做准备。

目录

一、实验1:Python基础

1.用numpy构建基础函数

2.向量化

二、实验2:用神经网络的思维实现逻辑回归

1.导入必要的包

2.数据集预处理

3.学习算法的一般结构

4.构建学习算法的各个部分

5.把上述实现的函数汇总到一个模型中

6.进一步分析

7.用你自己的图片进行测试

8.实验总结和其他相关尝试

 


一、实验1:Python基础

1.用numpy构建基础函数

  • sigmoid函数

首先使用math.exp()实现sigmoid函数,然后再用np.exp()实现,感受后者的优越性。

math.exp():

import math

def basic_sigmoid(x):  #使用math.exp() 参数必须是标量 函数的输入输出都是标量
    s=1/(1+math.exp(-x))
    return s
basic_sigmoid(3)

x=[1,2,3]
basic_sigmoid(x)  #当输入不是标量时 会报错

np.exp():

import numpy as np

x=np.array([1,2,3])  #声明一个一维数组
print(np.exp(x))  #相当于对数组中的每个元素都取以e为底的指数
#数组加一个标量  相当于数组中每个元素都加上该标量
x=np.array([1,2,3])
print(x+3)

def sigmoid(x): #使用np.exp()实现sigmoid函数 输入可以是标量、向量、矩阵、高维张量
    s=1/(1+np.exp(-x))
    return s   #s 与 x 同维
x=np.array([1,2,3])
sigmoid(x)

  • 计算sigmoid 梯度

计算sigmoid函数的导数:

def sigmoid_derivative(x):
    s=1/(1+np.exp(-x))
    ds=s*(1-s)
    return ds

x=np.array([1,2,3])
print(sigmoid_derivative(x))

  • 数组变形

两个常用的函数:

X.shape 返回矩阵/向量X的大小; X.reshape() 把矩阵/向量X转变为其他形状

比如,在计算机中图片用3维数组来存储,接下来你需要做的是把它转换为一个列向量:

def image2vector(image):
    #image 三维数组  (length,height,depth)
    #返回一个列向量v  (length*height*depth,1)
    v = image.reshape(image.shape[0]*image.shape[1]*image.shape[2],1)
    #v = image.reshape(-1,1)
    return v #列向量 2维数组 (m,1)

#注意NumPy中最好用2维数组来声明向量  向量是特殊的矩阵(m,1)或(1,n) 不要用一维数组来声明向量
image = np.array([[[ 0.67826139,  0.29380381],
        [ 0.90714982,  0.52835647],
        [ 0.4215251 ,  0.45017551]],

       [[ 0.92814219,  0.96677647],
        [ 0.85304703,  0.52351845],
        [ 0.19981397,  0.27417313]],

       [[ 0.60659855,  0.00533165],
        [ 0.10820313,  0.49978937],
        [ 0.34144279,  0.94630077]]])
print ("image2vector(image) = " + str(image2vector(image)))

  • 对矩阵中的行进行规范化

矩阵中的每个行向量除以它的模长,返回规范化后的矩阵,如:

def normalizeRows(x):
    x_norm=np.linalg.norm(x,axis=1,keepdims=True)  #axis=1 对每一个行向量求模长
    #keepdims保持形状 用2维数组表示向量 避免不必要的麻烦
    x=x/x_norm   #每一个行向量除以它的模长 进行规范化 广播
    return x

x = np.array([
    [0, 3, 4],
    [1, 6, 4]])
print("normalizeRows(x) = " + str(normalizeRows(x)))

  •  广播和softmax函数

接下来用NumPy实现softmax函数,你可以把softmax函数看作是一个规范化的函数: 

def softmax(x):
    x_exp=np.exp(x)
    x_sum=np.sum(x_exp,axis=1) #对每一行求和  返回一个一维数组
    x_sum=x_sum.reshape(x_exp.shape[0],1) #将一维数组转换为列向量(2维数组)
    #x_sum=x_sum.reshape(-1,1)
    s=x_exp/x_sum   #广播
    return s

x = np.array([
    [9, 2, 5, 0, 0],
    [7, 5, 0, 0 ,0]])
print("softmax(x) = " + str(softmax(x)))

2.向量化

在深度学习中,应尽可能避免显式的for循环,使用向量化编程,提高运行效率:

非向量化和向量化对比:

#非向量化和向量化对比
#非向量化实现
import time

x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### 向量内积 ###
tic = time.process_time()
dot = 0
for i in range(len(x1)):
    dot+= x1[i]*x2[i]
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 向量外积 ###
tic = time.process_time()
outer = np.zeros((len(x1),len(x2))) # we create a len(x1)*len(x2) matrix with only zeros
for i in range(len(x1)):
    for j in range(len(x2)):
        outer[i,j] = x1[i]*x2[j]
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 向量乘法 对应位置元素相乘 ###
tic = time.process_time()
mul = np.zeros(len(x1))
for i in range(len(x1)):
    mul[i] = x1[i]*x2[i]
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 矩阵与向量相乘 ###
W = np.random.rand(3,len(x1)) # Random 3*len(x1) numpy array
tic = time.process_time()
gdot = np.zeros(W.shape[0])
for i in range(W.shape[0]):
    for j in range(len(x1)):
        gdot[i] += W[i,j]*x1[j]
toc = time.process_time()
print ("gdot = " + str(gdot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

 

#向量化实现
x1 = [9, 2, 5, 0, 0, 7, 5, 0, 0, 0, 9, 2, 5, 0, 0]
x2 = [9, 2, 2, 9, 0, 9, 2, 5, 0, 0, 9, 2, 5, 0, 0]

### 向量内积 ###
tic = time.process_time()
dot = np.dot(x1,x2)
toc = time.process_time()
print ("dot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 向量外积 ###
tic = time.process_time()
outer = np.outer(x1,x2)
toc = time.process_time()
print ("outer = " + str(outer) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 向量乘法 对应位置元素相乘 ###
tic = time.process_time()
mul = np.multiply(x1,x2)
toc = time.process_time()
print ("elementwise multiplication = " + str(mul) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

### 矩阵与向量乘法 ###
tic = time.process_time()
dot = np.dot(W,x1)
toc = time.process_time()
print ("gdot = " + str(dot) + "\n ----- Computation time = " + str(1000*(toc - tic)) + "ms")

 

  • 实现L1和L2损失函数

def L1(yhat,y):
    loss=np.sum(np.abs(yhat-y))
    return loss

yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L1 = " + str(L1(yhat,y)))

'''
def L2(yhat,y):
    loss=(yhat-y).dot(yhat-y) #向量内积 用一维数组表示向量有时也是可以的,但是最好用2维数组表示
    return loss
'''

def L2(yhat,y):
    yhat = yhat.reshape(-1,1)
    y = y.reshape(-1,1)
    loss = (yhat-y).T.dot(yhat-y)
    return loss[0][0]

yhat = np.array([.9, 0.2, 0.1, .4, .9])
y = np.array([1, 0, 0, 1, 1])
print("L2 = " + str(L2(yhat,y)))

二、实验2:用神经网络的思维实现逻辑回归

本次实验利用逻辑回归算法构建一个分类器(2分类),判断一张图片是否是关于猫的图片。

  • 尽可能不要在代码中使用显式循环,使用向量化编程。

通过本次实验你将学到如下技能

  • 构建学习算法的一般结构,包括:
    • 参数初始化
    • 计算算法的代价函数和梯度
    • 使用优化算法求解参数(梯度下降法)
  • 按照正确的顺序把以上三个函数放在一个主模型函数中

1.导入必要的包

import numpy as np
import matplotlib.pyplot as plt
import h5py  #用于与存储在H5文件中的数据集交互
#scipy和PIL 用于最后用你自己的图片测试你的模型
import scipy  
from PIL import Image
from scipy import ndimage
from lr_utils import load_dataset #该函数在lr_utils.py文件中

%matplotlib inline

2.数据集预处理

给定的数据集(data.h5)包含:

一个训练集,包含m_train张标记图片,y=1猫图,y=0非猫图

一个测试集,包含m_test张标记图片

 每张图片都是彩色图,形状(num_px(height),num_px(width),3),height=width,3代表RGB颜色通道。

  • 加载原始数据集
# 加载数据
#_orig代表原始数据 接下来会对数据进行预处理 得到train_set_x
#train_set_x_orig/test_set_x_orig是一个四维数组 (mtrain/mtest,height,width,3)
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()

load_dataset()函数:

def load_dataset():
    train_dataset = h5py.File('./datasets/train_catvnoncat.h5', "r")
    train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
    train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels

    test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r")
    test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
    test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels

    classes = np.array(test_dataset["list_classes"][:]) # the list of classes
    
    #转变为行向量 用2维数组表示
    train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
    test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
    
    return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes

 

  • 可视化数据集中的一张图片
#可视化其中的一张图片
index=25
plt.imshow(train_set_x_orig[index]) #每行代表一个样本/图片
print ("y = " + str(train_set_y[:, index]) + ", it's a '" + classes[np.squeeze(train_set_y[:, index])].decode("utf-8") +  "' picture.")

  • 查看原始数据集的相关信息
m_train = train_set_x_orig.shape[0] #训练集图片数
m_test = test_set_x_orig.shape[0]   #测试集图片数
num_px = train_set_x_orig[0].shape[0] #每张图片height/width

print ("Number of training examples: m_train = " + str(m_train))
print ("Number of testing examples: m_test = " + str(m_test))
print ("Height/Width of each image: num_px = " + str(num_px))
print ("Each image is of size: (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("train_set_x shape: " + str(train_set_x_orig.shape)) #四维数组
print ("train_set_y shape: " + str(train_set_y.shape)) #2维数组 表示的行向量
print ("test_set_x shape: " + str(test_set_x_orig.shape))
print ("test_set_y shape: " + str(test_set_y.shape))

 

  • 对原始数据集进行预处理

原始数据集转型:

#对训练集和测试集进行转型

train_set_x_flatten = train_set_x_orig.reshape(train_set_x_orig.shape[0], -1).T
test_set_x_flatten = test_set_x_orig.reshape(test_set_x_orig.shape[0], -1).T

print ("train_set_x_flatten shape: " + str(train_set_x_flatten.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x_flatten shape: " + str(test_set_x_flatten.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
print ("sanity check after reshaping: " + str(train_set_x_flatten[0:5,0]))

对转型后的数据集进行标准化:

#图片特征向量中的每个值0-255   接下来标准化数据集 转换为0-1之间
train_set_x = train_set_x_flatten/255 #训练集
test_set_x = test_set_x_flatten/255  #测试集

3.学习算法的一般结构

用神经网络的思维构建逻辑回归算法,下面这幅图解释为什么逻辑回归可以看作一个简单的神经网络:

  • 逻辑回归算法的数学表达式

  • 关键步骤

1)初始化模型参数

2)最小化代价学习模型参数

3)用学好的参数在测试集上进行预测

4)分析结果并得出结论

4.构建学习算法的各个部分

构建一个神经网络的主要步骤:

  1. 定义网络结构(输入特征的维数等)
  2. 初始化模型参数
  3. 迭代:
    • 前向传播计算当前的损失
    • 反向传播计算当前的梯度(最终输出对模型参数求偏导)
    • 用梯度下降法更新参数

分别构建上述3部分,最后把他们汇总到一个函数中,我们称为模型model().

  • sigmoid函数
def sigmoid(z):
    '''
    计算sigmoid(z)
    
    参数:
    z -- 可以是标量、向量、矩阵等任意形式的数组
    
    返回:
    s -- sigmoid(z)
    '''
    s=1/(1+np.exp(-z))
    return s

print ("sigmoid([0, 2]) = " + str(sigmoid(np.array([0,2]))))

  • 初始化参数
def initialize_with_zeros(dim):
    '''
    初始化参数w为(dim,1)的零列向量  初始化b=0
    
    参数:
    dim -- 输入特征数量
    
    返回:
    w -- 初始化的权重参数(dim,1)
    b -- 初始化的偏置参数 b=0
    '''
    
    w=np.zeros((dim,1))
    #w = np.zeros(dim).reshape(dim,1)
    #w = np.zeros(dim).reshape(-1,1)
    #w = np.zeros(dim).reshape((dim,1))
    b=0
    
    #断言
    assert(w.shape==(dim,1))
    assert(isinstance(b,float) or isinstance(b,int))
    
    return w,b

dim = 2
w, b = initialize_with_zeros(dim)
print ("w = " + str(w))
print ("b = " + str(b))

  • 前向/反向传播

前向传播计算代价,反向传播计算梯度。

def propagate(w,b,X,Y):
    '''
    计算代价和梯度
    
    参数:
    w -- 权重参数 (num_px*num_px*3,1)
    b -- 偏置参数 标量
    X -- 训练集特征矩阵  (num_px*num_px*3,mtrain) 每一列为一张图片的特征向量
    Y -- 训练集标签 行向量(1,mtrain) 0非猫 1猫
    
    返回:
    cost -- 逻辑回归算法的代价
    dw -- 代价对w的梯度 与w同维
    db -- 代价对b的梯度 与b同维
    '''
    
    m=X.shape[1] #训练样本数
    A=sigmoid(np.dot(w.T,X)+b) #(1,m)
    cost=-np.mean(Y*np.log(A)+(1-Y)*np.log(1-A))
    
    dw=np.dot(X,(A-Y).T)/m #对m个样本产生的梯度求均值
    db=np.mean(A-Y)
    
    assert(dw.shape==w.shape)
    assert(db.dtype==float)
    cost=np.squeeze(cost)
    assert(cost.shape==())
    
    grads={'dw':dw,'db':db}
    
    return grads,cost

w, b, X, Y = np.array([[1],[2]]), 2, np.array([[1,2],[3,4]]), np.array([[1,0]])
grads, cost = propagate(w, b, X, Y)
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))
print ("cost = " + str(cost))

 

  • 优化

使用梯度下降法最小化代价求解最优的参
数。

def optimize(w,b,X,Y,num_iterations,learning_rate,print_cost=False):
    '''
    使用梯度下降法优化参数w,b
    
    参数:
    w -- 权重参数 (num_px*num_px*3,1)
    b -- 偏置参数
    X -- 训练集特征矩阵  (num_px*num_px*3,mtrain) 每一列为一张图片的特征向量
    Y -- 训练集标签 行向量(1,mtrain) 0非猫 1猫
    num_iterations -- 梯度下降法迭代次数
    learing_rate -- 学习率取值
    print_cost:为True时,每100次迭代打印一次cost
    
    返回:
    params -- 字典形式返回最优的参数w,b
    grads -- 字典形式返回最后的梯度
    costs -- 用列表存储每100次迭代后的cost  用于绘制cost随迭代次数的变化曲线
    '''
    
    costs=[]
    for i in range(num_iterations):
        
        grads,cost=propagate(w,b,X,Y)
        dw=grads['dw']
        db=grads['db']
        
        w=w-learning_rate*dw
        b=b-learning_rate*db
        
        if i%100==0:
            costs.append(cost)
        if print_cost and i%100==0:
            print ("Cost after iteration %i: %f" %(i, cost))
    params={'w':w,'b':b}
    grads={'dw':dw,'db':db}
    
    return params,grads,costs

params, grads, costs = optimize(w, b, X, Y, num_iterations= 100, learning_rate = 0.009, print_cost = False)

print ("w = " + str(params["w"]))
print ("b = " + str(params["b"]))
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))

  • 预测

def predict(w,b,X):
    '''
    使用学好的参数w,b预测新样本的类别0/1
    
    参数:
    w -- 学习的权重参数 (num_px*num_px*3,1)
    b -- 学习的偏置参数 标量
    X -- 测试集 (num_px*num_px*3,mtest) 每一列是新样本的特征向量
    
    返回:
    Y_prediction -- 预测的新样本类别 0/1
    '''
    m=X.shape[1]
    Y_prediction=np.zeros((1,m)) #2维数组表示行向量
    w=w.reshape(X.shape[0],1) #(dim,1)
    
    A=sigmoid(np.dot(w.T,X)+b) #(1,m)
    '''
    for i in range(A.shape[1]):
        Y_prediction[0][i]=np.rint(A[0][i])
    '''
    Y_prediction = np.rint(A)
    assert(Y_prediction.shape==(1,m))
    
    return Y_prediction

print ("predictions = " + str(predict(params["w"], params["b"], X)))
    

 

5.把上述实现的函数汇总到一个模型中

def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
    """
    构建完整的逻辑回归模型
    
    参数:
    X_train -- 训练集特征矩阵 (num_px * num_px * 3, m_train)每一列是样本的特征向量
    Y_train -- 训练集标签 (1,m_train) 行向量
    X_test -- 测试集特征矩阵 (num_px * num_px * 3, m_test)每一列是样本的特征向量
    Y_test -- 测试集标签 (1,m_test) 行向量
    num_iterations -- 梯度下降法迭代次数
    learing_rate -- 学习率取值
    print_cost:为True时,每100次迭代打印一次cost
    
    返回:
    d -- 模型相关信息 字典形式
    """
    
    #初始化模型参数为0
    w, b = initialize_with_zeros(X_train.shape[0])

    # 使用梯度下降法优化求解参数
    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost)
    
    # 得到最优的参数
    w = parameters["w"]
    b = parameters["b"]
    
    # 使用学好的参数 在测试集和训练集上进行预测
    Y_prediction_test = predict(w, b, X_test)
    Y_prediction_train = predict(w, b, X_train)

  

    # 打印训练集和测试集上的准确率
    print("train accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))
    print("test accuracy: {} %".format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100))

    #存储模型的相关信息
    d = {"costs": costs,
         "Y_prediction_test": Y_prediction_test, 
         "Y_prediction_train" : Y_prediction_train, 
         "w" : w, 
         "b" : b,
         "learning_rate" : learning_rate,
         "num_iterations": num_iterations}
    
    return d

d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)

模型在训练集上的准确率非常高,在测试集上的准确率不高,二者差距很大,说明模型出现了过拟合。在之后的学习中我们会学习一些解决过拟合的方法,和更高级的分类器。

  • 对测试集的某张图片进行预测
# 对测试集的某张图片进行预测
index = 1
plt.imshow(test_set_x[:,index].reshape((num_px, num_px, 3)))
#print ("y = " + str(test_set_y[:,index]) + ", it's a '" + classes[np.squeeze(d['Y_prediction_test'][:,index])].decode("utf-8") +  "' picture.")
print ("y = " + str(test_set_y[0,index]) + ", you predicted that it is a \"" + classes[int(np.squeeze(d["Y_prediction_test"][0,index]))].decode("utf-8") +  "\" picture.")

  • 绘制代价随梯度下降法迭代次数的变化曲线
#绘制代价随梯度下降迭代次数的变化曲线
costs = np.squeeze(d['costs'])
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(d["learning_rate"]))
plt.show()

可以看到代价随迭代次数在下降,说明参数在不断优化。

增加迭代次数,会发现训练集准确率会继续上升,而测试集准确率会下降,出现了过拟合。

6.进一步分析

  • 选择一个合适的学习率

学习率决定了更新参数的幅度。过大模型可能不会收敛,甚至会发散;过小模型收敛的非常缓慢,需要很多次迭代,才能收敛到最优值。可以广泛的尝试不同的学习率取值,找一个最好的。

对不同的学习率选择,绘制不同的代价随迭代次数的变化曲线,观察此时训练集和测试集的准确率。

learning_rates = [0.01, 0.001, 0.0001] #不同的学习率选择
models = {} #不同的学习率产生不同的模型
for i in learning_rates:
    print ("learning rate is: " + str(i))
    models[str(i)] = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 1500, learning_rate = i, print_cost = False)
    print ('\n' + "-------------------------------------------------------" + '\n')

for i in learning_rates:
    plt.plot(np.squeeze(models[str(i)]["costs"]), label= str(models[str(i)]["learning_rate"]))

plt.ylabel('cost')
plt.xlabel('iterations')

legend = plt.legend(loc='upper center', shadow=True)
frame = legend.get_frame()
frame.set_facecolor('0.90')
plt.show()

  • 不同的学习率产生不同的代价和不同的预测结果
  • 学习率太大如上图中的0.01,曲线会出现波动,甚至会发散,但在上例中,0.01最后也能收敛,训练集上的代价非常低
  • 在训练集上代价低不见得是一个好的模型,此时需要检查是否出现过拟合,如果训练集准确率明显大于测试集,则出现了过拟合。
  • 选择一个最优的学习率来最小化代价求解参数;如果模型出现了过拟合,使用一些技术缓解过拟合(正则化等之后会讲)

7.用你自己的图片进行测试

#图片名称
my_image = "gargouille.jpg"   

# 图片相对路径
fname = "images/" + my_image
#读取图片 存储到数组中
image = np.array(ndimage.imread(fname, flatten=False))
#对图片进行转型 预处理
my_image = scipy.misc.imresize(image, size=(num_px,num_px)).reshape((1, num_px*num_px*3)).T
#使用学好的参数 预测图片类别
my_predicted_image = predict(d["w"], d["b"], my_image)

plt.imshow(image)
print("y = " + str(np.squeeze(my_predicted_image)) + ", your algorithm predicts a \"" + classes[int(np.squeeze(my_predicted_image)),].decode("utf-8") +  "\" picture.")

8.实验总结和其他相关尝试

 

  • 9
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值