【深度学习Tensrflow(14)】模拟生成时间序列&非深度学习方法预测

学习自中国大学MOOC TensorFlow学习课程

一、目的

时间序列是数据科学中最常用的技术之一,它具有广泛的应用——天气预报、销售预测、趋势分析等。本部分主要介绍时间序列的生成,并主要使用RNN、双向LSTM对时间序列进行预测。

二、模拟生成一个时间序列

时序信号 = 周期信号 + 趋势信号 + 随机信号(随机噪声)

2.1 趋势线

#生成时间序列数据D:
#绘制趋势线函数
def plot_series(time, series): 
    plt.figure(figsize=(10, 6))
    plt.plot(time, series)
    plt.xlabel("time")
    plt.ylabel("value")
    plt.grid(True)
    plt.show()
    
#生成趋势线
def trend(time, slope=0):
    return slope * time  #slope为斜率

time = np.arange(4 * 365 + 1, dtype='float32')
baseline = 10
series = trend(time, 0.1)
plot_series(time, series)

2.2 周期信号

# 生成季节性的时间序列
# 分段函数
def seasonal_pattern(season_time):
    return np.where(season_time < 0.4,
                    np.cos(season_time * 2 * np.pi),
                    1 / np.exp(3 * season_time))

#振幅amplitude,相位phase
def seasonality(time, period, amplitude=1, phase=0):
    season_time = ((time + phase) % period) / period
    return amplitude * seasonal_pattern(season_time)

baseline = 10
amplitude = 40
series = seasonality(time, period=365, amplitude=amplitude)
plot_series(time, series)

2.3 周期信号加上趋势线信号

slope = 0.05
series = baseline + trend(time, slope) + seasonality(time, period=365, amplitude=amplitude)
plot_series(time, series)

2.4 加入白噪声

#叠加白噪音
def noise(time, noise_level=1):
    return np.random.randn(len(time)) * noise_level
noise_level = 15
noisy_series = series + noise(time, noise_level)
plot_series(time, noisy_series)

增加噪声水平

趋势变得更加不明显了

noise_level = 40
noisy_series = series + noise(time, noise_level)
plot_series(time, noisy_series)

三、噪声平滑处理(非深度学习的方法)

3.1 平滑噪音的方法1

用加权和来替代原本的取值

两个权重:

  • rho1 = 0.5
  • rho2 = -0.1
def autocorrelation(time, amplitude):
    rho1 = 0.5
    rho2 = -0.1
    #生成一个正态分布的
    ar = np.random.randn(len(time) + 50)
    ar[:50] = 100
    for step in range(50, len(time) + 50):
        ar[step] += rho1 * ar[step - 50] #该位置点的加上(前面50个位置的点*权重rho1 的值)
        ar[step] += rho2 * ar[step - 33]  #该位置点的加上(前面33个位置的点*权重rho2的值)
    return ar[50:] * amplitude  #再乘强度amplitude,使得这个信号和原来的信号振幅尽量一样


series = autocorrelation(time, 10)
plot_series(time[:200], series[:200])

3.1 平滑噪音的方法2

某个时间点的取值 = 前一个点的值 + 前一个点的值*rho权重

#平滑噪音方法2
def autocorrelation(time, amplitude):
    rho = 0.8
    ar = np.random.randn(len(time) + 1)
    for step in range(1, len(time) + 1):
        ar[step] += rho * ar[step - 1]
    return ar[1:] * amplitude

series = autocorrelation(time, 10)
plot_series(time[:200], series[:200])

测试

生成噪声
series = noise(time)
plot_series(time[:200], series[:200])
将平滑函数曲线和趋势线进行叠加
series = autocorrelation(time, 10) + trend(time, 2)
plot_series(time[:200], series[:200])
再加上周期信号
series = autocorrelation(time, 10) + seasonality(time, period=50, amplitude=150) + trend(time, 2)
plot_series(time[:200], series[:200])

四、时间序列的非深度学习的预测方法

try:
  # %tensorflow_version only exists in Colab.
  %tensorflow_version 2.x
except Exception:
  pass

import tensorflow as tf
print(tf.__version__)

2.4.0

The next code block will set up the time series with seasonality, trend and a bit of noise.

# 生成有季节成分和趋势成分的时间序列
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras

def plot_series(time, series, format="-", start=0, end=None):
    plt.plot(time[start:end], series[start:end], format)
    plt.xlabel("Time")
    plt.ylabel("Value")
    plt.grid(True)

def trend(time, slope=0):
    return slope * time

def seasonal_pattern(season_time):
    """Just an arbitrary pattern, you can change it if you wish"""
    return np.where(season_time < 0.4,
                    np.cos(season_time * 2 * np.pi),
                    1 / np.exp(3 * season_time))

def seasonality(time, period, amplitude=1, phase=0):
    """Repeats the same pattern at each period"""
    season_time = ((time + phase) % period) / period
    return amplitude * seasonal_pattern(season_time)

def noise(time, noise_level=1, seed=None):
    rnd = np.random.RandomState(seed)
    return rnd.randn(len(time)) * noise_level

time = np.arange(4 * 365 + 1, dtype="float32")   #1461
baseline = 10
series = trend(time, 0.1)  
baseline = 10
amplitude = 40
slope = 0.05
noise_level = 5

# Create the series
series = baseline + trend(time, slope) + seasonality(time, period=365, amplitude=amplitude)
# Update with noise
series += noise(time, noise_level, seed=42)

plt.figure(figsize=(10, 6))
plot_series(time, series)
plt.show()

Now that we have the time series, let's split it so we can start forecasting

将序列前1000个切分作为训练集,后1001~1600作为测试集

#前100个时序为训练集,后面的为测试集
split_time = 1000
time_train = time[:split_time]
x_train = series[:split_time]
time_valid = time[split_time:]
x_valid = series[split_time:]
plt.figure(figsize=(10, 6))
plot_series(time_train, x_train)
plt.show()

plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid)
plt.show()

4.1 Naive Forecast 朴素预测法预测时间序列

# 使用朴素预测法进行预测
#即取上一个时间点的值,作为下一个时间点的预测值,
naive_forecast = series[split_time - 1:-1]

plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid)
plot_series(time_valid, naive_forecast) #相当于平移了一个单位


Let's zoom in on the start of the validation period:

#对图进行扩展
plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid, start=0, end=150)
plot_series(time_valid, naive_forecast, start=1, end=151) #在垂直和横向方向改动比例

You can see that the naive forecast lags 1 step behind the time series.

Now let's compute the mean squared error and the mean absolute error between the forecasts and the predictions in the validation period:

# 计算误差
tf.compat.v1.enable_eager_execution()
print(keras.metrics.mean_squared_error(x_valid, naive_forecast).numpy()) #均方误差
print(keras.metrics.mean_absolute_error(x_valid, naive_forecast).numpy()) #平均绝对误差

61.827538
5.9379086

That's our baseline, now let's try a moving average:

4.2 使用移动平均法进行预测

移动平均法是用最近一段区间内的时刻值的均值来预测未来时刻的值。

def moving_average_forecast(series, window_size):
  """Forecasts the mean of the last few values.
     If window_size=1, then this is equivalent to naive forecast"""
  forecast = []
  #窗口每次往后移动一个步长
  for time in range(len(series) - window_size): 
    forecast.append(series[time:time + window_size].mean()) #计算窗口里包含的序列值得平均值,作为这个时间点的取值
  return np.array(forecast)

#从分割点前30个时间(训练集)值的平均值开始,作为预测值
moving_avg = moving_average_forecast(series, 30)[split_time - 30:]

plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid)
plot_series(time_valid, moving_avg)
# 计算误差
print(keras.metrics.mean_squared_error(x_valid, moving_avg).numpy())
print(keras.metrics.mean_absolute_error(x_valid, moving_avg).numpy())

106.674576
7.142419

移动平均时,周期信号波动很大,如果窗口比较大,用它的平均值来预测某一个时刻的值,误差会非常大

问题根源是,用移动平均做预测去预测周期性信号误差会非常大

That's worse than naive forecast! The moving average does not anticipate trend or seasonality, so let's try to remove them by using differencing. Since the seasonality period is 365 days, we will subtract the value at time t – 365 from the value at time t.

解决方法:把正态分布的随机信号,从周期性信号中提取出来,只针对误差做移动平均的预测,周期性信号不需要做平均;

然后分别对这两个信号做预测并且相加,得到新的预测值。

让校验集的每一个时刻都减掉同一个周期,也就是减去周期365,只保留随机误差信号

优化

去除趋势或周期性

# 去除趋势或周期性
diff_series = (series[365:] - series[:-365]) #提取出随机误差信号
diff_time = time[365:]
plt.figure(figsize=(10, 6))
plot_series(diff_time, diff_series)
plt.show()

Great, the trend and seasonality seem to be gone, so now we can use the moving average:

#只对随机误差做移动平均
#时间窗放大了些,是50个时刻
diff_moving_avg = moving_average_forecast(diff_series, 50)[split_time - 365 - 50:] 
plt.figure(figsize=(10, 6))
plot_series(time_valid, diff_series[split_time - 365:])
plot_series(time_valid, diff_moving_avg)
plt.show()

Now let's bring back the trend and seasonality by adding the past values from t – 365:

计算周期信号的预测值 = 用上一个周期的周期值
预测值 = 用上一个周期的周期值 + 平滑后的误差值

diff_moving_avg_plus_past = series[split_time - 365:-365] + diff_moving_avg  
plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid)
plot_series(time_valid, diff_moving_avg_plus_past)
plt.show()
# 计算误差
print(keras.metrics.mean_squared_error(x_valid, diff_moving_avg_plus_past).numpy())
print(keras.metrics.mean_absolute_error(x_valid, diff_moving_avg_plus_past).numpy())

52.973656
5.8393106

进一步优化

Better than naive forecast, good. However the forecasts look a bit too random, because we're just adding past values, which were noisy. Let's use a moving averaging on past values to remove some of the noise:

对原本的周期信号进行移动平均 但是窗口不能太大

# 使用0均法来消除部分噪声
# 对上一个周期进行移动平均,但是窗口不能太大
diff_moving_avg_plus_smooth_past = moving_average_forecast(series[split_time - 370:-360], 10) + diff_moving_avg
plt.figure(figsize=(10, 6))
plot_series(time_valid, x_valid)
plot_series(time_valid, diff_moving_avg_plus_smooth_past)
plt.show()
print(keras.metrics.mean_squared_error(x_valid, diff_moving_avg_plus_smooth_past).numpy())
print(keras.metrics.mean_absolute_error(x_valid, diff_moving_avg_plus_smooth_past).numpy())

33.45226
4.569442
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容