pytorch

本文介绍了PyTorch的基本操作,包括矩阵创建、随机值生成、初始化、尺寸变换以及与Numpy的协同操作。接着,通过示例展示了线性回归的实现过程,包括模型训练、损失计算和反向传播。此外,还讨论了在GPU上运行模型以及模型的保存和加载。最后,提到了神经网络的简单应用,如气温预测和图像分类。
摘要由CSDN通过智能技术生成

PyTorch基础

import torch
torch.__version__ #return '1.13.1+cu116'

基本使用方法

矩阵

x = torch.empty(5, 3)
		tensor([[1.4586e-19, 1.1578e+27, 2.0780e-07],
		        [6.0542e+22, 7.8675e+34, 4.6894e+27],
		        [1.6217e-19, 1.4333e-19, 2.7530e+12],
		        [7.5338e+28, 8.1173e-10, 4.3861e-43],
		        [2.8912e-03, 4.3861e-43, 2.8912e-03]])
		        

随机值

x = torch.rand(5, 3)#5行三列的随机值
		tensor([[0.1511, 0.6433, 0.1245],
		        [0.8949, 0.8577, 0.3564],
		        [0.7810, 0.5037, 0.7101],
		        [0.1997, 0.4917, 0.1746],
		        [0.4288, 0.9921, 0.4862]])		

初始化一个全零的矩阵

x = torch.zeros(5, 3, dtype=torch.long)
		tensor([[0, 0, 0],
		        [0, 0, 0],
		        [0, 0, 0],
		        [0, 0, 0],
		        [0, 0, 0]])

直接传入数据

x = torch.tensor([5.5, 3])
tensor([5.5000, 3.0000])
x = x.new_ones(5, 3, dtype=torch.double)    
		tensor([[1., 1., 1.],
		        [1., 1., 1.],
		        [1., 1., 1.],
		        [1., 1., 1.],
		        [1., 1., 1.]], dtype=torch.float64)		
x = torch.randn_like(x, dtype=torch.float) #返回一个x大小相同的张量,其由均值为0、方差为1的标准正态分布填充
		tensor([[ 0.6811, -1.2104, -1.2676],
		        [-0.3295,  0.1155, -0.5736],
		        [-1.3656, -0.4973, -0.7043],
		        [-1.3670, -0.3296,  3.1743],
		        [ 1.3443,  0.3373,  0.6182]])   
		        

展示矩阵大小

x.size()
		torch.Size([5, 3])

基本计算方法

y = torch.rand(5, 3)#随机5行三列矩阵
		tensor([[0.0542, 0.9674, 0.5902],
		        [0.7749, 0.1682, 0.2871],
		        [0.1747, 0.3728, 0.2077],
		        [0.9092, 0.3087, 0.3981],
		        [0.4231, 0.8725, 0.6005]])
x		        
		tensor([[ 0.6811, -1.2104, -1.2676],
		        [-0.3295,  0.1155, -0.5736],
		        [-1.3656, -0.4973, -0.7043],
		        [-1.3670, -0.3296,  3.1743],
		        [ 1.3443,  0.3373,  0.6182]])	
x + y
		tensor([[ 0.7353, -0.2430, -0.6774],
		        [ 0.4454,  0.2837, -0.2865],
		        [-1.1908, -0.1245, -0.4967],
		        [-0.4578, -0.0209,  3.5723],
		        [ 1.7674,  1.2098,  1.2187]])	
torch.add(x, y)#一样的也是加法
		tensor([[ 0.7353, -0.2430, -0.6774],
		        [ 0.4454,  0.2837, -0.2865],
		        [-1.1908, -0.1245, -0.4967],
		        [-0.4578, -0.0209,  3.5723],
		        [ 1.7674,  1.2098,  1.2187]])			        	        	        

索引

x[:, 1]
		tensor([-1.2104,  0.1155, -0.4973, -0.3296,  0.3373])		

x.view() 类似于reshape(),重塑维度

x = torch.randn(4, 4)
		tensor([[ 0.1811, -1.4025, -1.2865, -1.6370],
		        [-0.2279,  1.0993, -0.4067, -0.2652],
		        [-0.5673,  0.2697,  1.8822, -1.3748],
		        [-0.3731, -0.9595,  1.8725, -0.8774]])
y = x.view(16)
		tensor([-0.3035, -2.5819,  1.2449, -0.3448,  1.0095, -0.1734,  1.5666,  0.5170,
		        -1.0587,  0.1241, -0.5550, -1.6905,  0.8625, -1.3681, -0.1491,  0.2202])	
z = x.view(-1, 8) #-1是值得注意的,x中总共16个元素,现在定义一行是8列(8个元素),16/8 = 2,所以是2行
		tensor([[-0.3035, -2.5819,  1.2449, -0.3448,  1.0095, -0.1734,  1.5666,  0.5170],
		        [-1.0587,  0.1241, -0.5550, -1.6905,  0.8625, -1.3681, -0.1491,  0.2202]])
print(x.size(), y.size(), z.size())
		torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

与Numpy的协同操作(互转)

a = torch.ones(5)
		tensor([1., 1., 1., 1., 1.])
b = a.numpy()
		array([1., 1., 1., 1., 1.], dtype=float32)

import numpy as np
a = np.ones(5)
		array([1., 1., 1., 1., 1.])
b = torch.from_numpy(a)
		tensor([1., 1., 1., 1., 1.], dtype=torch.float64)

autograb机制

需要求导的,可以手动定义:

x = torch.randn(3,4)#torch.randn:用来生成随机数字的tensor,这些随机数字满足标准正态分布(0~1)
		tensor([[-1.5885,  0.6992, -0.2198,  1.2736],
		        [ 0.6211, -0.3729,  0.1261,  1.4094],
		        [ 0.7418, -0.2801, -0.0672, -0.5614]])
x = torch.randn(3,4,requires_grad=True)
		tensor([[ 0.9318, -1.0761,  0.6794,  1.2261],
		        [-1.7192, -0.6009, -0.3852,  0.2492],
		        [-0.1853,  0.2066,  0.9497, -0.3329]], requires_grad=True)
#方法2
x = torch.randn(3,4)
x.requires_grad=True
		tensor([[-1.9635,  0.5769,  1.2705, -0.8758],
		        [ 1.2847, -1.0498, -0.3650, -0.5059],
		        [ 0.2780,  0.0816,  0.7754,  0.2048]], requires_grad=True)	   
b = torch.randn(3,4,requires_grad=True)
t = x + b
y = t.sum()
		tensor(4.4444, grad_fn=<SumBackward0>)
y.backward()
b.grad
		tensor([[1., 1., 1., 1.],
		        [1., 1., 1., 1.],
		        [1., 1., 1., 1.]])		
虽然没有指定t的requires_grad但是需要用到它,也会默认的
x.requires_grad, b.requires_grad, t.requires_grad#return (True, True, True)		        
     		        

在这里插入图片描述

#计算流程
x = torch.rand(1)
b = torch.rand(1, requires_grad = True)
w = torch.rand(1, requires_grad = True)
y = w * x 
z = y + b 
x.requires_grad, b.requires_grad, w.requires_grad, y.requires_grad#注意y也是需要的
		(False, True, True, True)
x.is_leaf, w.is_leaf, b.is_leaf, y.is_leaf, z.is_leaf
		(True, True, True, False, False)	
返向传播计算
z.backward(retain_graph=True)#如果不清空会累加起来
w.grad 累加后的结果
		tensor([1.6244])
b.grad
		tensor([2.])
	

做一个线性回归

构造一组输入数据X和其对应的标签y
x_values = [i for i in range(11)]
x_train = np.array(x_values, dtype=np.float32)
x_train = x_train.reshape(-1, 1)
x_train.shape #return (11, 1)

y_values = [2*i + 1 for i in x_values]
y_train = np.array(y_values, dtype=np.float32)
y_train = y_train.reshape(-1, 1)
y_train.shape# (11, 1)

import torch
import torch.nn as nn
线性回归模型:其实线性回归就是一个不加激活函数的全连接层
class LinearRegressionModel(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(LinearRegressionModel, self).__init__()
        self.linear = nn.Linear(input_dim, output_dim)  

    def forward(self, x):#前向传播
        out = self.linear(x)
        return out
input_dim = 1
output_dim = 1

model = LinearRegressionModel(input_dim, output_dim)
		LinearRegressionModel(
		  (linear): Linear(in_features=1, out_features=1, bias=True)
		)
指定好参数和损失函数
epochs = 1000  #执行次数
learning_rate = 0.01 #准确率
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)#优化模型
criterion = nn.MSELoss()#绝对值损失函数是计算预测值与目标值的差的绝对值
训练模型
for epoch in range(epochs):
    epoch += 1
    # 注意转行成tensor
    inputs = torch.from_numpy(x_train)
    labels = torch.from_numpy(y_train)

    # 梯度要清零每一次迭代
    optimizer.zero_grad() 

    # 前向传播
    outputs = model(inputs)

    # 计算损失
    loss = criterion(outputs, labels)

    # 返向传播
    loss.backward()

    # 更新权重参数
    optimizer.step()
    if epoch % 50 == 0:
        print('epoch {}, loss {}'.format(epoch, loss.item()))
				epoch 50, loss 0.4448287785053253
				epoch 100, loss 0.25371354818344116
				epoch 150, loss 0.14470864832401276
				epoch 200, loss 0.08253632485866547
				epoch 250, loss 0.04707561805844307
				epoch 300, loss 0.026850251480937004
				epoch 350, loss 0.015314370393753052
				epoch 400, loss 0.008734731003642082
				epoch 450, loss 0.004981952253729105
				epoch 500, loss 0.002841521752998233
				epoch 550, loss 0.0016206930158659816
				epoch 600, loss 0.0009243797394447029
				epoch 650, loss 0.0005272324196994305
				epoch 700, loss 0.0003007081104442477
				epoch 750, loss 0.00017151293286588043
				epoch 800, loss 9.782632696442306e-05
				epoch 850, loss 5.579544449574314e-05
				epoch 900, loss 3.182474029017612e-05
				epoch 950, loss 1.8151076801586896e-05
				epoch 1000, loss 1.0352457138651516e-05        
测试模型预测结果
predicted = model(torch.from_numpy(x_train).requires_grad_()).data.numpy()
		array([[ 0.99918383],
		       [ 2.9993014 ],
		       [ 4.9994187 ],
		       [ 6.9995365 ],
		       [ 8.999654  ],
		       [10.999771  ],
		       [12.999889  ],
		       [15.000007  ],
		       [17.000124  ],
		       [19.000242  ],
		       [21.000359  ]], dtype=float32)
模型的保存与读取
torch.save(model.state_dict(), 'model.pkl')
model.load_state_dict(torch.load('model.pkl'))
		<All keys matched successfully>

使用GPU进行训练:只需要把数据和模型传入到cuda里面就可以了

import torch
import torch.nn as nn
import numpy as np


class LinearRegressionModel(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(LinearRegressionModel, self).__init__()
        self.linear = nn.Linear(input_dim, output_dim)  

    def forward(self, x):
        out = self.linear(x)
        return out

input_dim = 1
output_dim = 1

model = LinearRegressionModel(input_dim, output_dim)

######使用GPU还是CPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)


criterion = nn.MSELoss()


learning_rate = 0.01

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

epochs = 1000
for epoch in range(epochs):
    epoch += 1
    ####加上.to(device)
    inputs = torch.from_numpy(x_train).to(device)
    labels = torch.from_numpy(y_train).to(device)

    optimizer.zero_grad() 

    outputs = model(inputs)

    loss = criterion(outputs, labels)

    loss.backward()

    optimizer.step()

    if epoch % 50 == 0:
        print('epoch {}, loss {}'.format(epoch, loss.item()))
				epoch 50, loss 0.011100251227617264
				epoch 100, loss 0.006331132724881172
				epoch 150, loss 0.003611058695241809
				epoch 200, loss 0.0020596047397702932
				epoch 250, loss 0.0011747264070436358
				epoch 300, loss 0.0006700288504362106
				epoch 350, loss 0.00038215285167098045
				epoch 400, loss 0.00021796672081109136
				epoch 450, loss 0.00012431896175257862
				epoch 500, loss 7.090995495673269e-05
				epoch 550, loss 4.044298475491814e-05
				epoch 600, loss 2.3066799258231185e-05
				epoch 650, loss 1.3156819477444515e-05
				epoch 700, loss 7.503344477299834e-06
				epoch 750, loss 4.279831500753062e-06
				epoch 800, loss 2.4414177914877655e-06
				epoch 850, loss 1.3924694712841301e-06
				epoch 900, loss 7.945647553242452e-07
				epoch 950, loss 4.530382398115762e-07
				epoch 1000, loss 2.5830334493548435e-07        

Tensor常见的形式

0: scalar:通常就是一个数值
1: vector:例如: [-5., 2., 0.],在深度学习中通常指特征,例如词向量特征,某一维度特征等
2: matrix:一般计算的都是矩阵,通常都是多维的
3: n-dimensional tensor:


Scalar

x = tensor(42.)
		tensor(42.)
x.dim()
		0		

Vector

一维向量
v = tensor([1.5, -0.5, 3.0])
		tensor([ 1.5000, -0.5000,  3.0000])
v.dim()
		1
v.size()
		torch.Size([3])		

Matrix

M = tensor([[1., 2.], [3., 4.]])
	tensor([[1., 2.],
	        [3., 4.]])
M.matmul(M)#矩阵乘法
		tensor([[ 7., 10.],
		        [15., 22.]])	        

几种形状的Tensor

在这里插入图片描述

强大hub模块

GITHUB:https://github.com/pytorch/hub
模型:https://pytorch.org/hub/research-models

import torch
model = torch.hub.load('pytorch/vision:v0.4.2', 'deeplabv3_resnet101', pretrained=True)
model.eval()

torch.hub.list('pytorch/vision:v0.4.2')

# Download an example image from the pytorch website
import urllib
url, filename = ("https://github.com/pytorch/hub/raw/master/dog.jpg", "dog.jpg")
try: urllib.URLopener().retrieve(url, filename)
except: urllib.request.urlretrieve(url, filename)

# sample execution (requires torchvision)
from PIL import Image
from torchvision import transforms
input_image = Image.open(filename)
preprocess = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

input_tensor = preprocess(input_image)
input_batch = input_tensor.unsqueeze(0) # create a mini-batch as expected by the model

# move the input and model to GPU for speed if available
if torch.cuda.is_available():
    input_batch = input_batch.to('cuda')
    model.to('cuda')

with torch.no_grad():
    output = model(input_batch)['out'][0]
output_predictions = output.argmax(0)

# create a color pallette, selecting a color for each class
palette = torch.tensor([2 ** 25 - 1, 2 ** 15 - 1, 2 ** 21 - 1])
colors = torch.as_tensor([i for i in range(21)])[:, None] * palette
colors = (colors % 255).numpy().astype("uint8")

# plot the semantic segmentation predictions of 21 classes in each color
r = Image.fromarray(output_predictions.byte().cpu().numpy()).resize(input_image.size)
r.putpalette(colors)

import matplotlib.pyplot as plt
plt.imshow(r)
plt.show()

在这里插入图片描述

神经网络实战分类与回归任务

神经网络进行气温预测

import numpy as np
import pandas as pd 
import matplotlib.pyplot as plt
import torch
import torch.optim as optim
import warnings
warnings.filterwarnings("ignore")
%matplotlib inline
#(1)数据获取
features = pd.read_csv('temps.csv')
#看看数据长什么样子
features.head()

在这里插入图片描述

print('数据维度:', features.shape)#数据维度: (348, 9)
# 处理时间数据
import datetime
# 分别得到年,月,日
years = features['year']
months = features['month']
days = features['day']

# datetime格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]

dates[:5]
	[datetime.datetime(2016, 1, 1, 0, 0),
	 datetime.datetime(2016, 1, 2, 0, 0),
	 datetime.datetime(2016, 1, 3, 0, 0),
	 datetime.datetime(2016, 1, 4, 0, 0),
	 datetime.datetime(2016, 1, 5, 0, 0)]

# 准备画图
# 指定默认风格
plt.style.use('fivethirtyeight')

# 设置布局
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(nrows=2, ncols=2, figsize = (10,10))
fig.autofmt_xdate(rotation = 45)

# 标签值
ax1.plot(dates, features['actual'])
ax1.set_xlabel(''); ax1.set_ylabel('Temperature'); ax1.set_title('Max Temp')

# 昨天
ax2.plot(dates, features['temp_1'])
ax2.set_xlabel(''); ax2.set_ylabel('Temperature'); ax2.set_title('Previous Max Temp')

# 前天
ax3.plot(dates, features['temp_2'])
ax3.set_xlabel('Date'); ax3.set_ylabel('Temperature'); ax3.set_title('Two Days Prior Max Temp')

# 我的逗逼朋友
ax4.plot(dates, features['friend'])
ax4.set_xlabel('Date'); ax4.set_ylabel('Temperature'); ax4.set_title('Friend Estimate')

plt.tight_layout(pad=2)

在这里插入图片描述

# 独热编码  将字符串转化为特定的数字,数字编码
features = pd.get_dummies(features)
features.head(5)

在这里插入图片描述

# 标签
labels = np.array(features['actual'])

# 在特征中去掉标签
features= features.drop('actual', axis = 1)

# 名字单独保存一下,以备后患
feature_list = list(features.columns)

# 转换成合适的格式
features = np.array(features)
features.shape#(348, 14)

from sklearn import preprocessing
input_features = preprocessing.StandardScaler().fit_transform(features)
input_features[0]
		array([ 0.        , -1.5678393 , -1.65682171, -1.48452388, -1.49443549,
		       -1.3470703 , -1.98891668,  2.44131112, -0.40482045, -0.40961596,
		       -0.40482045, -0.40482045, -0.41913682, -0.40482045])		      

构建网络模型

x = torch.tensor(input_features, dtype = float)

y = torch.tensor(labels, dtype = float)

# 权重参数初始化
weights = torch.randn((14, 128), dtype = float, requires_grad = True) 
biases = torch.randn(128, dtype = float, requires_grad = True) 
weights2 = torch.randn((128, 1), dtype = float, requires_grad = True) 
biases2 = torch.randn(1, dtype = float, requires_grad = True) 

learning_rate = 0.001 
losses = []

for i in range(1000):
    # 计算隐层
    hidden = x.mm(weights) + biases
    # 加入激活函数
    hidden = torch.relu(hidden)
    # 预测结果
    predictions = hidden.mm(weights2) + biases2
    # 通计算损失
    loss = torch.mean((predictions - y) ** 2) 
    losses.append(loss.data.numpy())
    
    # 打印损失值
    if i % 100 == 0:
        print('loss:', loss)
    #返向传播计算
    loss.backward()
    
    #更新参数
    weights.data.add_(- learning_rate * weights.grad.data)  
    biases.data.add_(- learning_rate * biases.grad.data)
    weights2.data.add_(- learning_rate * weights2.grad.data)
    biases2.data.add_(- learning_rate * biases2.grad.data)
    
    # 每次迭代都得记得清空
    weights.grad.data.zero_()
    biases.grad.data.zero_()
    weights2.grad.data.zero_()
    biases2.grad.data.zero_()
		loss: tensor(8347.9924, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(152.3170, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(145.9625, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(143.9453, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(142.8161, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(142.0664, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(141.5386, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(141.1528, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(140.8618, dtype=torch.float64, grad_fn=<MeanBackward0>)
		loss: tensor(140.6318, dtype=torch.float64, grad_fn=<MeanBackward0>)
predictions.shape #torch.Size([348, 1])			

更简单的构建网络模型

input_size = input_features.shape[1]
hidden_size = 128
output_size = 1
batch_size = 16
my_nn = torch.nn.Sequential(
    torch.nn.Linear(input_size, hidden_size),
    torch.nn.Sigmoid(),
    torch.nn.Linear(hidden_size, output_size),
)
cost = torch.nn.MSELoss(reduction='mean')
optimizer = torch.optim.Adam(my_nn.parameters(), lr = 0.001)

# 训练网络
losses = []
for i in range(1000):
    batch_loss = []
    # MINI-Batch方法来进行训练
    for start in range(0, len(input_features), batch_size):
        end = start + batch_size if start + batch_size < len(input_features) else len(input_features)
        xx = torch.tensor(input_features[start:end], dtype = torch.float, requires_grad = True)
        yy = torch.tensor(labels[start:end], dtype = torch.float, requires_grad = True)
        prediction = my_nn(xx)
        loss = cost(prediction, yy)
        optimizer.zero_grad()
        loss.backward(retain_graph=True)
        optimizer.step()
        batch_loss.append(loss.data.numpy())
    
    # 打印损失
    if i % 100==0:
        losses.append(np.mean(batch_loss))
        print(i, np.mean(batch_loss))
				0 3950.7627
				100 37.9201
				200 35.654438
				300 35.278366
				400 35.116814
				500 34.986076
				600 34.868954
				700 34.75414
				800 34.637356
				900 34.516705
预测训练结果
x = torch.tensor(input_features, dtype = torch.float)
predict = my_nn(x).data.numpy()

# 转换日期格式
dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]
dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in dates]

# 创建一个表格来存日期和其对应的标签数值
true_data = pd.DataFrame(data = {'date': dates, 'actual': labels})

# 同理,再创建一个来存日期和其对应的模型预测值
months = features[:, feature_list.index('month')]
days = features[:, feature_list.index('day')]
years = features[:, feature_list.index('year')]

test_dates = [str(int(year)) + '-' + str(int(month)) + '-' + str(int(day)) for year, month, day in zip(years, months, days)]

test_dates = [datetime.datetime.strptime(date, '%Y-%m-%d') for date in test_dates]

predictions_data = pd.DataFrame(data = {'date': test_dates, 'prediction': predict.reshape(-1)}) 

# 真实值
plt.plot(true_data['date'], true_data['actual'], 'b-', label = 'actual')

# 预测值
plt.plot(predictions_data['date'], predictions_data['prediction'], 'ro', label = 'prediction')
plt.xticks(rotation = '60'); 
plt.legend()

# 图名
plt.xlabel('Date'); plt.ylabel('Maximum Temperature (F)'); plt.title('Actual and Predicted Values');

在这里插入图片描述

在这里插入图片描述

神经网络分类任务

Mnist分类任务

在这里插入图片描述

torch.nn.functional

在这里插入图片描述

创建一个model来更简化代码

在这里插入图片描述

使用TensorDataset和DataLoader来简化

在这里插入图片描述

卷积神经网络

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

残差网络 (ResNets)

在这里插入图片描述

卷积神经网络效果(conv) cnn

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值