用户名:  密码:   
网站首页即时通讯活动公告最新消息科技前沿学人动向两岸三地人在海外历届活动关于我们联系我们申请加入
栏目导航 — 美国华裔教授专家网科技动向科技前沿
关键字  范围   
 
11 种经典时间序列预测方法!
来源:数据分析及应用 | 作者:花哥 | 2024/10/9 19:48:33 | 浏览:489 | 评论:0

本文我们深入探讨如何使用机器学习方法对 Python 的时间序列问题进行分类和预测。我们将深入研究一套经典的时间序列预测方法,您可以在探索机器学习方法之前测试这些方法。

虽然传统方法强调线性关系,但它们在许多情况下都是熟练且有效的。只要数据准备充分,技术设置好就会很好。

11 种经典时间序列预测方法!


一、 概述
本文演示了 11 种不同的经典时间序列预测方法,以及时间序列方法的对比及实践教程。
 

 自回归 (AR)

 移动平均线 (MA)

自回归移动平均线 (ARMA)

自回归综合移动平均线 (ARIMA)

季节性自回归综合移动平均线 (SARIMA)

具有外生回归的季节性自回归积分移动平均值 (SARIMAX)

向量自回归 (VAR)

向量自回归移动平均 (VARMA)

具有外生回归的向量自回归移动平均值 (VARMAX)

简单指数平滑 (SES)

Holt Winter 指数平滑 (HWES)


每种方法都以一致的方式呈现,包括:

描述。对该技术的简短而精确的描述。

Python 代码。一个简短的工作示例,用于在 Python 中拟合模型并进行预测。

更多信息。API 和算法的参考。

对于提供的每个代码示例,我们使用一个基本的说明性数据集。我们理解它可能并不总是适合重点方法,因此我们建议您用您的数据替换人为的数据集,以测试该方法。

请记住:定制每种方法都需要针对您的具体问题进行调整。在许多情况下,我已经在博客上提供了如何配置甚至网格搜索参数的示例,请尝试搜索功能。

如果本指南被证明是有益的,请在下面的评论部分分享您的想法。

 自回归 (AR)
自回归 (AR) 方法使用先前观测值的线性组合来预测序列中的后续值。

模型的符号涉及将模型 p 的顺序指定为 AR 函数的参数,例如 AR(p)。例如,AR(1) 是一个一阶自回归模型。

该方法最适合缺乏趋势和季节性分量的单变量时间序列。

 Python 代码


# AR example
from statsmodels.tsa.ar_model import AutoReg
from random import random
# contrived dataset
data = [x + random()for x in range(1, 100)]
# fit model
model = AutoReg(data, lags=1)
model_fit = model.fit()
# make prediction
yhat = model_fit.predict(len(data), len(data))
print(yhat)


 移动平均线 (MA)
移动平均 (MA) 方法模型将序列中的下一步预测为先前时间步长中平均过程残余误差的线性函数。

需要注意的是,移动平均线模型不同于计算时间序列的移动平均线。

模型的符号涉及将模型 q 的顺序指定为 MA 函数的参数,例如 MA(q)。例如,MA(1) 是一个一阶移动平均模型。

该方法适用于没有趋势和季节分量的单变量时间序列。

我们可以使用 ARIMA 类来创建 MA 模型并设置零阶 AR 模型。我们必须在 order 参数中指定 MA 模型的顺序。


 Python 代码
我们可以使用 ARIMA 类来创建 MA 模型并设置零阶 AR 模型。我们必须在 order 参数中指定 MA 模型的顺序。


# MA example

from statsmodels.tsa.arima.model import ARIMA

from random import random

# contrived dataset

data = [x + random()for x in range(1, 100)]

# fit model

model = ARIMA(data, order=(0, 0, 1))

model_fit = model.fit()

# make prediction

yhat = model_fit.predict(len(data), len(data))

print(yhat)


自回归移动平均线 (ARMA)
自回归移动平均 (ARMA) 方法模型基于过去观测值和过去残差的线性组合来预测序列中的下一步。

该方法结合了自回归 (AR) 和移动平均 (MA) 模型。

为了表示模型,符号涉及将 AR(p) 和 MA(q) 模型的顺序指定为 ARMA 函数的参数,例如 ARMA(p, q)。ARIMA 模型可用于开发 AR 或 MA 模型。

该方法适用于没有趋势和季节分量的单变量时间序列。

 Python 代码


# ARMA example

from statsmodels.tsa.arima.model import ARIMA

from random import random

# contrived dataset

data = [random()for x in range(1, 100)]

# fit model

model = ARIMA(data, order=(2, 0, 1))

model_fit = model.fit()

# make prediction

yhat = model_fit.predict(len(data), len(data))

print(yhat)


 更多信息
维基百科上的自回归-移动平均模型

自回归综合移动平均线 (ARIMA)
自回归综合移动平均 (ARIMA) 方法模型将序列中的下一步预测为先前时间步长的差分观测值和残差误差的线性函数。

该方法集成了自回归 (AR) 和移动平均 (MA) 模型的原理以及序列的差分预处理步骤,使序列静止,称为积分 (I)。

模型的符号涉及将 AR(p)、I(d) 和 MA(q) 模型的顺序指定为 ARIMA 函数的参数,例如 ARIMA(p, d, q)。ARIMA 模型还可用于开发 AR、MA 和 ARMA 模型。

ARIMA 方法最适合表现出趋势但缺乏季节性变化的单变量时间序列。

 Python 代码


# ARIMA example

from statsmodels.tsa.arima.model import ARIMA

from random import random

# contrived dataset

data = [x + random()for x in range(1, 100)]

# fit model

model = ARIMA(data, order=(1, 1, 1))

model_fit = model.fit()

# make prediction

yhat = model_fit.predict(len(data), len(data), typ='levels')

print(yhat)


 更多信息
维基百科上的自回归综合移动平均线


季节性自回归综合移动平均线 (SARIMA)
季节性自回归综合移动平均 (SARIMA) 方法基于差异观测值、误差、差异季节性观测值和先前时间步长的季节性误差的线性混合,对序列中的下一步进行建模。

SARIMA 增强了 ARIMA 模型,使其能够在季节性水平上执行相同的自回归、差分和移动平均建模。

该模型的符号涉及指定 AR(p)、I(d) 和 MA(q) 模型的顺序作为 ARIMA 函数的参数,以及季节性水平的 AR(P)、I(D)、MA(Q) 和 m 参数,例如 SARIMA(p, d, q)(P, D, Q)m,其中“m”是每个季节(季节周期)的时间步长数。SARIMA 模型可用于开发 AR、MA、ARMA 和 ARIMA 模型。

该方法适用于具有趋势和/或季节性分量的单变量时间序列。


 Python 代码


# SARIMA example

from statsmodels.tsa.statespace.sarimax import SARIMAX

from random import random

# contrived dataset

data = [x + random()for x in range(1, 100)]

# fit model

model = SARIMAX(data, order=(1, 1, 1), seasonal_order=(0, 0, 0, 0))

model_fit = model.fit(disp=False)

# make prediction

yhat = model_fit.predict(len(data), len(data))

print(yhat)


 更多信息
statsmodels.tsa.statespace.sarimax.SARIMAX API 接口

statsmodels.tsa.statespace.sarimax.SARIMAXResults API

维基百科上的自回归综合移动平均线


具有外生回归的季节性自回归积分移动平均值 (SARIMAX)
具有外生回归的季节性自回归综合移动平均值 (SARIMAX) 是 SARIMA 模型的扩展,其中还包括外生变量的建模。

外生变量也称为协变量,可以将其视为具有与原始序列相同的时间步长的观测值的并行输入序列。初级序列可以称为内源性数据,以将其与外源序列进行对比。外生变量的观测值在每个时间步直接包含在模型中,并且以与主要内生序列相同的方式建模(例如,作为 AR、MA 等过程)。

SARIMAX 方法还可用于对具有外生变量(如 ARX、MAX、ARMAX 和 ARIMAX)的归入模型进行建模。

该方法适用于具有趋势和/或季节成分以及外生变量的单变量时间序列。

 Python 代码


# SARIMAX example

from statsmodels.tsa.statespace.sarimax import SARIMAX

from random import random

# contrived dataset

data1 = [x + random()for x in range(1, 100)]

data2 = [x + random()for x in range(101, 200)]

# fit model

model = SARIMAX(data1, exog=data2, order=(1, 1, 1), seasonal_order=(0, 0, 0, 0))

model_fit = model.fit(disp=False)

# make prediction

exog2 = [200 + random()]

yhat = model_fit.predict(len(data1), len(data1), exog=[exog2])

print(yhat)


 更多信息
statsmodels.tsa.statespace.sarimax.SARIMAX API 接口

statsmodels.tsa.statespace.sarimax.SARIMAXResults API

维基百科上的自回归综合移动平均线

向量自回归 (VAR)
向量自回归 (VAR) 方法使用 AR 模型方法对每个时间序列中的下一步进行建模。从本质上讲,它扩展了 AR 模型以迎合多个并行时间序列,例如多变量时间序列。

模型的符号涉及将 AR(p) 模型的顺序指定为 VAR 函数的参数,例如 VAR(p)。

该方法适用于没有趋势和季节分量的多变量时间序列。

 Python 代码

# VAR example
from statsmodels.tsa.vector_ar.var_model import VAR
from random import random
# contrived dataset with dependency
data = list()
for i in range(100):
    v1 = i + random()
    v2 = v1 + random()
    row = [v1, v2]
    data.append(row)
# fit model
model = VAR(data)
model_fit = model.fit()
# make prediction
yhat = model_fit.forecast(model_fit.y, steps=1)
print(yhat)


 更多信息
statsmodels.tsa.vector_ar.var_model。VAR API

statsmodels.tsa.vector_ar.var_model。VARResults API

维基百科上的向量自回归

向量自回归移动平均 (VARMA)
向量自回归移动平均 (VARMA) 方法利用 ARMA 模型方法对多个时间序列中即将到来的值进行建模。它是ARMA对多个并行时间序列的推广,例如多变量时间序列。

模型的符号涉及将 AR(p) 和 MA(q) 模型的顺序指定为 VARMA 函数的参数,例如 VARMA(p, q)。VARMA 模型还可用于开发 VAR 或 VMA 模型。

该方法适用于没有趋势和季节分量的多变量时间序列。


 Python 代码
 更多信息
statsmodels.tsa.statespace.varmax.VARMAX 接口

statsmodels.tsa.statespace.varmax.VARMAXResults

维基百科上的向量自回归

具有外生回归的向量自回归移动平均值 (VARMAX)
具有外生回归的向量自回归移动平均值 (VARMAX) 扩展了 VARMA 模型的功能,其中还包括外生变量的建模。它是 ARMAX 方法的多变量版本。

外生变量,也称为协变量,可以认为是与原始序列的时间步长对齐的并行输入序列。主要系列被称为内源性数据,以将其与外源序列进行对比。外生变量的观测值在每个时间步直接包含在模型中,并且以与主要内生序列相同的方式建模(例如,作为 AR、MA 等过程)。

VARMAX 方法还可用于对具有外生变量(如 VARX 和 VMAX)的归和模型进行建模。

该方法适用于无趋势的多变量时间序列和具有外生变量的季节分量。

 Python 代码


# VARMAX example

from statsmodels.tsa.statespace.varmax import VARMAX

from random import random

# contrived dataset with dependency

data = list()

for i in range(100):

    v1 = random()

    v2 = v1 + random()

    row = [v1, v2]

    data.append(row)

data_exog = [x + random()for x in range(100)]

# fit model

model = VARMAX(data, exog=data_exog, order=(1, 1))

model_fit = model.fit(disp=False)

# make prediction

data_exog2 = [[100]]

yhat = model_fit.forecast(exog=data_exog2)

print(yhat)


 更多信息
statsmodels.tsa.statespace.varmax.VARMAX 接口

statsmodels.tsa.statespace.varmax.VARMAXResults

维基百科上的向量自回归

简单指数平滑 (SES)
简单指数平滑 (SES) 方法将下一个时间步长建模为先前时间步长观测值的指数加权线性函数。

该方法适用于没有趋势和季节分量的单变量时间序列。

 Python 代码


# SES example

from statsmodels.tsa.holtwinters import SimpleExpSmoothing

from random import random

# contrived dataset

data = [x + random()for x in range(1, 100)]

# fit model

model = SimpleExpSmoothing(data)

model_fit = model.fit()

# make prediction

yhat = model_fit.predict(len(data), len(data))

print(yhat)


 更多信息
statsmodels.tsa.holtwinters.SimpleExpSmoothing API 接口

statsmodels.tsa.holtwinters.HoltWinters结果 API

维基百科上的指数平滑

Holt Winter 指数平滑 (HWES)
Holt Winter's Exponential Smoothing (HWES) 也称为三重指数平滑方法,将下一个时间步长建模为先前时间步长观测值的指数加权线性函数,同时考虑趋势和季节性。

该方法适用于具有趋势和/或季节性分量的单变量时间序列。

 Python 代码


# HWES example

from statsmodels.tsa.holtwinters import ExponentialSmoothing

from random import random

# contrived dataset

data = [x + random()for x in range(1, 100)]

# fit model

model = ExponentialSmoothing(data)

model_fit = model.fit()

# make prediction

yhat = model_fit.predict(len(data), len(data))

print(yhat)


二、时间序列方法对比及实践
在这里,我们将看看时间序列预测的例子,以及如何建立ARMA、ARIMA和SARIMA模型,对比特币(BTC)的未来价格进行时间序列预测。

读取和显示 BTC 时间序列数据
我们将首先使用 Pandas 数据读取器读取 BTC 的历史价格。让我们在终端中使用一个简单的 pip 命令来安装它:
pip install pandas-datareader
让我们打开一个 Python 脚本,并从 Pandas 库中导入数据读取器:
import pandas_datareader.data as web
import datetime
我们还导入 Pandas 库本身,并放宽对列和行的显示限制:
import pandas as pd
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
现在,我们可以导入日期时间库,这将允许我们定义数据提取的开始和结束日期:
import datetime
现在我们已经掌握了提取比特币价格时间序列数据所需的一切,让我们收集数据。
import pandas_datareader as web

btc = web.get_data_yahoo(['BTC-USD'], start=datetime.datetime(2018, 1, 1), end=datetime.datetime(2020, 12, 2))['Close']

print(btc.head())
11 种经典时间序列预测方法!

我们看到我们的数据框包含许多列。让我们来看看这些列中的每一个的含义。
日期:这是我们时间序列中的索引,用于指定与价格关联的日期。
收盘价:当天购买BTC的最后价格。
开盘价:当天购买BTC的第一个价格。
最高价:当天购买BTC的最高价格。
最低价:当天购买BTC的最低价格。
交易量:当天的总交易次数。
调整收盘价:根据股息和股票分割调整后的收盘价。
我们将使用收盘价作为预测模型。具体来说,我们将使用历史收盘价 BTC 来预测未来的 BTC 价格。
让我们将收盘价 BTC 数据写入 csv 文件。这样,我们就可以避免使用 Pandas 数据读取器重复提取数据。
btc.to_csv("btc.csv")
现在,让我们阅读 csv 文件并显示前五行:
btc = pd.read_csv("btc.csv")
print(btc.head())
11 种经典时间序列预测方法!

为了使用统计库提供的模型,我们需要将日期列设置为数据框索引。我们还应该使用 to_datetime 方法格式化该日期:
btc.index = pd.to_datetime(btc['Date'], format='%Y-%m-%d')
让我们显示我们的数据框:
del btc['Date']
11 种经典时间序列预测方法!

让我们绘制我们的时间序列数据。为此,让我们导入数据可视化库 Seaborn 和 Matplotlib:
import matplotlib.pyplot as plt
import seaborn as sns
让我们使用 Seaborn 格式化可视化:
sns.set()
并使用 Matplotlib 标记 y 轴和 x 轴。我们还将在 x 轴上旋转日期,以便它们更易于阅读:
plt.ylabel('BTC Price')
plt.xlabel('Date')
plt.xticks(rotation=45)
最后,使用 Matplotlib 生成我们的图:
plt.plot(btc.index, btc['BTC-USD'], )
11 种经典时间序列预测方法!

现在我们可以继续构建我们的第一个时间序列模型,即自回归移动平均线。
 
拆分数据进行训练和测试
模型构建的一个重要部分是拆分我们的数据以进行训练和测试,这可确保构建一个可以在训练数据之外泛化的模型,并且性能和输出具有统计意义。
我们将拆分数据,使 2020 年 11 月之前的所有内容都作为训练数据,而 2020 年之后的所有内容都将成为测试数据:
train = btc[btc.index < pd.to_datetime("2020-11-01", format='%Y-%m-%d')]
test = btc[btc.index > pd.to_datetime("2020-11-01", format='%Y-%m-%d')]

plt.plot(train, color = "black")
plt.plot(test, color = "red")
plt.ylabel('BTC Price')
plt.xlabel('Date')
plt.xticks(rotation=45)
plt.title("Train/Test split for BTC Data")
plt.show()
11 种经典时间序列预测方法!

 
自回归移动平均线 (ARMA)
ARMA 中的术语“自回归”意味着模型使用过去的值来预测未来的值。具体而言,预测值是过去值的加权线性组合。这种类型的回归方法类似于线性回归,不同之处在于此处的特征输入是历史值。
移动平均是指由白噪声项的加权线性组合表示的预测,其中白噪声是随机信号。这里的想法是,ARMA使用过去值和白噪声的组合来预测未来的值。自回归对市场参与者的行为进行建模,例如买卖BTC。白噪声模型震撼了战争、经济衰退和政治事件等事件。
我们可以使用 SARIMAX 软件包定义 ARMA 模型:
from statsmodels.tsa.statespace.sarimax import SARIMAX
 让我们定义我们的输入:
y = train['BTC-USD']
然后让我们定义我们的模型。为了定义一个带有 SARIMAX 类的 ARMA 模型,我们传入了 (1, 0 ,1) 的阶数参数。Alpha 对应于我们预测的显著性水平。通常,我们选择 alpha = 0.05。在这里,ARIMA 算法计算预测周围的上限和下限,因此实际值有 5% 的可能性超出上限和下限。这意味着有 95% 的置信度认为实际值将介于我们预测的上限和下限之间。
ARMAmodel = SARIMAX(y, order =(1, 0, 1))
然后,我们可以拟合我们的模型:
ARMAmodel = ARMAmodel.fit()
生成我们的预测:
y_pred = ARMAmodel.get_forecast(len(test.index))
y_pred_df = y_pred.conf_int(alpha = 0.05)
y_pred_df["Predictions"] = ARMAmodel.predict(start = y_pred_df.index[0], end = y_pred_df.index[-1])
y_pred_df.index = test.index
y_pred_out = y_pred_df["Predictions"]
并绘制结果:
plt.plot(y_pred_out, color='green', label = 'Predictions')
plt.legend()
11 种经典时间序列预测方法!

我们还可以使用均方根误差来评估性能:
import numpy as np
from sklearn.metrics import mean_squared_error

arma_rmse = np.sqrt(mean_squared_error(test["BTC-USD"].values, y_pred_df["Predictions"]))
print("RMSE:",arma_rmse)
11 种经典时间序列预测方法!

RMSE相当高,我们可以在检查地块时猜到这一点。不幸的是,当价格实际上涨时,该模型预测价格会下降。同样,ARMA 的局限性在于它无法用于非平稳时间序列,并且无法捕获季节性。让我们看看我们是否可以使用 ARIMA 模型提高性能。
 
自回归综合移动平均线 (ARIMA)
让我们从统计库导入 ARIMA 包:
from statsmodels.tsa.arima.model import ARIMA
ARIMA 任务有三个参数。第一个参数对应于滞后(过去的值),第二个参数对应于差分(这就是使非平稳数据平稳的原因),最后一个参数对应于白噪声(用于模拟冲击事件)。
让我们定义一个带有阶参数 (2,2,2) 的 ARIMA 模型:
ARIMAmodel = ARIMA(y, order =(2, 2, 2))
ARIMAmodel = ARIMAmodel.fit()

y_pred = ARIMAmodel.get_forecast(len(test.index))
y_pred_df = y_pred.conf_int(alpha = 0.05)
y_pred_df["Predictions"] = ARIMAmodel.predict(start = y_pred_df.index[0], end = y_pred_df.index[-1])
y_pred_df.index = test.index
y_pred_out = y_pred_df["Predictions"]
plt.plot(y_pred_out, color='Yellow', label = 'ARIMA Predictions')
plt.legend()


import numpy as np
from sklearn.metrics import mean_squared_error

arma_rmse = np.sqrt(mean_squared_error(test["BTC-USD"].values, y_pred_df["Predictions"]))
print("RMSE:",arma_rmse)
11 种经典时间序列预测方法!

我们看到 ARIMA 预测(黄色)位于 ARMA 预测之上。让我们尝试将差异参数增加到 ARIMA (2,3,2):
11 种经典时间序列预测方法!

我们看到这有助于捕捉价格上涨的方向。让我们尝试使用 ARIMA(5,4,2) 进一步使用参数:
11 种经典时间序列预测方法!

我们的 RMSE 为 793,比 ARMA 好。另一种方法是根据时间特征(如周、月和年)训练线性回归模型。这种方法是有限的,因为它不能像 ARIMA 方法那样捕获自回归和移动平均特征。此外,ARIMA 根据去趋势滞后目标值训练回归器,而不是线性回归等自变量。话虽如此,ARIMA的表现可能会优于在独立时间变量上训练的线性回归模型。
最后,让我们看看包含季节性的SARIMA是否会进一步提高性能。
 
 季节性 ARIMA (SARIMA)
季节性 ARIMA 捕捉历史价值、冲击事件和季节性。我们可以使用 SARIMAX 类定义一个 SARIMA 模型:
SARIMAXmodel = SARIMAX(y, order =(5, 4, 2), seasonal_order=(2,2,2,12))
SARIMAXmodel = SARIMAXmodel.fit()

y_pred = SARIMAXmodel.get_forecast(len(test.index))
y_pred_df = y_pred.conf_int(alpha = 0.05)
y_pred_df["Predictions"] = SARIMAXmodel.predict(start = y_pred_df.index[0], end = y_pred_df.index[-1])
y_pred_df.index = test.index
y_pred_out = y_pred_df["Predictions"]
plt.plot(y_pred_out, color='Blue', label = 'SARIMA Predictions')
plt.legend()
11 种经典时间序列预测方法!

在这里,我们的 RMSE 为 966,比 ARIMA 略差。这可能是由于缺乏超参数优化。如果我们对 SARIMA 模型的参数进行调整,我们应该能够进一步提高性能。
我鼓励您尝试使用超参数,看看是否可以构建一个性能优于 ARIMA 的 SARIMA 模型。此外,您可以采用网格搜索等方法通过算法找到每个模型的最佳参数。

 总结
在这篇文章中,你发现了一套经典的时间序列预测方法,你可以在时间序列数据集上测试和调整这些方法。这些方法专为各种时序数据集而设计,可用于在各种方案和行业中实现它们。无论您是在处理股票市场趋势、天气预报还是销售预测,这些方法都可以提供有价值的预测。

相关栏目:『科技前沿
工信部:未来产业六大方向聚焦人形机器人、脑机接口、量子科技等领域 2024-11-06 [288]
Gartner 公布2025年十大战略技术趋势 2024-10-31 [444]
这样图解Transformer应该没人看不懂了吧——Transformer工作原理 2024-10-16 [813]
Nature:智能体涌现出语言 2024-10-16 [789]
50个顶级ChatGPT论文指令 2024-10-10 [1003]
推荐五种简单有效的数据可视化方式 2024-10-10 [932]
这么有深度的文章是ChatGPT写的? 2024-10-10 [929]
讲透一个强大的算法模型,CNN!! 2024-10-10 [922]
人类与 AI 协同的三种模式 2024-10-10 [555]
美国宇航局(NASA)航天器发现木卫二咸海中可能存在生命! 2024-10-09 [418]
相关栏目更多文章
最新图文:
:北京452万人将从北京迁至雄安(附部分央企名单) :《2019全球肿瘤趋势报告》 :阿尔茨海默病预防与干预核心讯息图解 :引力波天文台或有助搜寻暗物质粒子 :Sail Through the Mist - SoCal Innovation Forum 2019(10/5) 游天龙:《唐人街》是如何炼成的:UCLA社会学教授周敏的学术之路 :“为什么海外华人那么爱国,但是让他回国却不愿意?...“ :学术出版巨头Elsevier 彻查433名审稿人“强迫引用”黑幕
更多最新图文
更多《即时通讯》>>
 
打印本文章
 
您的名字:
电子邮件:
留言内容:
注意: 留言内容不要超过4000字,否则会被截断。
未 审 核:  是
  
关于我们联系我们申请加入后台管理设为主页加入收藏
美国华裔教授专家网版权所有,谢绝拷贝。如欲选登或发表,请与美国华裔教授专家网联系。
Copyright © 2024 ScholarsUpdate.com. All Rights Reserved.