您现在的位置是:首页 >学无止境 >LSTM实现短时交通流量预测网站首页学无止境
LSTM实现短时交通流量预测
数据来源及web实现效果
这是我做的一个预测交通流量的项目,能够预测4个小时内的车流量变化,间接预测交通拥堵。完整项目代码请访问github个人仓库https://github.com/chen22676678/trafficForecas。
车流量数据来源(访问该链接需要梯子)https://webtris.highwaysengland.co.uk/。
该网站提供了英国高速(国道)各个监测点的每日交通流量数据,其中标准数据格式如下:
标准数据的采样频率为15min,字段有监测点、日期、时间、时间间隔、车辆长度(区别不同类型的车)、平均速度、车流量等。在我的预测模型中仅使用了车流和平均速度作为模型输入的特征数据。
模型预测的最终页面效果如下图:
项目结构
下图是我的代码项目基本架构:
图表及评估结果说明
以下是lstm模型及对比模型gru训练效果评估参数对比:
Metrics | MAE | MSE | RMSE | MAPE | R2 | VS |
---|---|---|---|---|---|---|
LSTM | 7.197 | 99.613 | 9.980 | 4.565 | 0.997 | 0.997 |
GRU | 14.670 | 514.314 | 22.678 | 7.813 | 0.985 | 0.986 |
下图是LSTM模型预测结果与真实数据拟合效果,时间范围为3天。LSTM模型在三天的时间范围内的预测车流量是能够拟合绝大部分的真实车流量数据,这与上文LSTM的MAPE、RMSE、R2等评价指标给出的值相符
下图是GRU模型预测结果与真实数据拟合效果。GRU模型与LSTM模型使用的参数相同,预测结果与真实数据拟合效果的时间范围也是3天。GRU模型在三天的时间范围内的预测车流量虽然也能够拟合绝大部分的真实车流量数据,但是也能明显看出该模型的问题,即对于真实车流量数据中极短时间内车流量出现较大变化时,模型的预测值与真实值相比出现较大误差。结合RMSE和R2指标来看,也说明了这点问题。
下图为两种模型的对比预测效果图,在四个小时内两种模型还比较准确,四小时后模型预测结果开始出现较大误差。
模型部分代码
模型训练
modelTrain.py部分代码如下:
# 数据归一化
def normalize_data(data, flow_scaler, avg_kph_scaler):
data["Flow"] = flow_scaler.transform(data["Flow"].values.reshape(-1, 1))
data["Avg kph"] = avg_kph_scaler.transform(data["Avg kph"].values.reshape(-1, 1))
return data
# 反向归一化
def denormalize_data(data, scaler):
data = scaler.inverse_transform(data.reshape(-1, 1)).reshape(1, -1)[0]
return data
# 添加时间滞后值以构建模型输入特征
def input_features(data):
lag = 48 # 1h->4;24h->96 控制时间步
# data = df.to_numpy()
# print(int(len(data) * 0.8)) #4684
train, test = [], []
for i in range(lag, len(data)):
if i < 14105: # 0.8->4703
train.append(data[i - lag: i + 1])
else:
test.append(data[i - lag: i + 1])
train = np.array(train)
test = np.array(test)
# 设置模型的输入输出
x_train = train[:, :-1, [0, 1]] # select Flow and Avg kph as input features
y_train = train[:, -1, [0]] # select Flow as output
x_test = test[:, :-1, [0, 1]] # select Flow and Avg kph as input features
y_test = test[:, -1, [0]] # select Flow as output
return x_train, y_train, x_test, y_test
# 构建模型
def build_LSTM():
model = Sequential()
model.add(Input(shape=(x_train.shape[1], x_train.shape[2])))
model.add(LSTM(units=256, activation='relu', kernel_regularizer=l2(0.0001), return_sequences=True))
model.add(Dropout(0.1))
model.add(Dense(units=64, activation='relu'))
model.add(Dropout(0.1))
model.add(LSTM(units=16))
model.add(Dense(1))
optimizer = Adam(learning_rate=0.0001)
model.compile(loss="mse", optimizer=optimizer, metrics=['mape'])
return model
def build_GRU():
model = Sequential()
model.add(Input(shape=(x_train.shape[1], x_train.shape[2])))
model.add(GRU(units=256, activation='relu', kernel_regularizer=l2(0.0001), return_sequences=True))
model.add(Dropout(0.1))
model.add(Dense(units=64, activation='relu'))
model.add(Dropout(0.1))
model.add(GRU(units=16))
model.add(Dense(1))
optimizer = Adam(learning_rate=0.0001)
model.compile(loss="mse", optimizer=optimizer, metrics=['mape'])
return model
def evaluate_models(y_true, y_pred):
y_true = [x for x in y_true if x > 0]
y_pred = [y_pred[i] for i in range(len(y_true)) if y_true[i] > 0]
# calculate the Mean Absolute Percentage Error
sums = 0 # initialize value
for i in range(len(y_pred)):
tmp = abs(y_true[i] - y_pred[i]) / y_true[i]
sums += tmp
mape = sums * (100 / len(y_pred))
# calculate variance score
vs = metrics.explained_variance_score(y_true, y_pred)
mae = metrics.mean_absolute_error(y_true, y_pred)
mse = metrics.mean_squared_error(y_true, y_pred)
r2 = metrics.r2_score(y_true, y_pred)
print('explained_variance_score:%f' % vs)
print('mape:%f%%' % mape)
print('mae:%f' % mae)
print('mse:%f' % mse)
print('rmse:%f' % math.sqrt(mse))
print('r2:%f' % r2)
flask框架
flask框架app.py
# 添加时间滞后值以构建模型输入特征
lag = 48 # 1h->4;24h->96 控制时间步
train, test = [], []
for i in range(lag, len(df)):
if i < 14109: # 0.8->4703
train.append(df[i - lag: i + 1])
else:
test.append(df[i - lag: i + 1])
test = np.array(test)
# 设置模型的输入
x_test = test[:, :-1, [0, 1]] # select Flow and Avg kph as input features
x_test = np.reshape(x_test, (x_test.shape[0], x_test.shape[1], 2))
# 加载模型
print("Loading LSTM keras model...")
model_lstm = load_model('../webapp/model/LSTM.h5')
print("LSTM model successfully loaded")
print("Loading GRU keras model...")
model_gru = load_model('../webapp/model/GRU.h5')
print("GRU model successfully loaded")
# 调用模型
predicted_lstm = model_lstm.predict(x_test)
predicted_gru = model_gru.predict(x_test)
# 反向归一化模型预测数据
predicted_lstm = flow_scaler.inverse_transform(predicted_lstm.reshape(-1, 1)).reshape(1, -1)[0]
predicted_gru = flow_scaler.inverse_transform(predicted_gru.reshape(-1, 1)).reshape(1, -1)[0]
# 前端请求
if flask.request.method == 'GET':
return flask.render_template('index.html')
if flask.request.method == 'POST':
# 接收文本框输入
date = flask.request.form['date']
time = flask.request.form['time']
col = date + " " + time
# 读取对应日期的真实数据,由于前面读入df后进行了归一化,所以真实数据也需要反向归一化
x = df.loc[col]
print(x)
print(x["Flow"])
true = int(flow_scaler.inverse_transform(x["Flow"].reshape(-1, 1)).reshape(1, -1)[0])
# 数据读入处理后索引变成了时间,所以需要计算时间步来提取模型的预测数据
index_time = x.name
pred_time = pd.Timestamp(index_time)
# 2019-05-20 00:00 起始为0,计算预测时间点与起始时间点的时间步之差,间隔为15min
time_point = datetime.strptime('2019-05-20 00:00', '%Y-%m-%d %H:%M')
time_step = int((pred_time - time_point).total_seconds() / 60 / 5) # 时间步之差,单位为5分钟
pred_LSTM = predicted_lstm[time_step].astype(int) # 4705-0
pred_GRU = predicted_gru[time_step].astype(int)
percentege = int((pred_LSTM / 1000) * 100)
print("预测时间:" + str(pred_time))
print("预测车流:" + str(pred_LSTM))
return flask.render_template('index.html', true_val=true, pred_val_LSTM=pred_LSTM,
date=date, time=time,
pred_val_GRU=pred_GRU, percentage=percentege)
index.html代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Road traffic forecast API</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
<style>
.g-progress {
width: 360px;
height: 30px;
border-radius: 5px;
background: linear-gradient(90deg, #00cc00 16.5%, #66ff66 16.5%, #66ff66 33%,
#ffcc00 33%, #ff9933 34%, #ff9933 50%, #ff3300 50%, transparent 0);
border: 3px solid #8f8f8f;
}
.form-control {
display: block;
width: 22%;
height: calc(1.5em + 0.75rem + 2px);
padding: 0.375rem 0.75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: 0.25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
</style>
</head>
<body>
<div class="jumbotron text-center">
<div class="col-4 mx-auto text-center">
<img src="{{url_for('static', filename='logo.png')}}" class="rounded-circle" width="214" height="100">
</div>
<br>
<h1>道路交通流量预测</h1>
<h4>道路交通流量预测模型效果演示界面 </h4>
</div>
<div class="container">
<div class="row">
<div class="col-sm-6">
<h4 style="text-align:center">LSTM static time step three-day graph</h4>
<img src="{{url_for('static', filename='LSTM_static_3Day.png')}}" class="img-thumbnail">
</div>
<div class="col-sm-6">
<h4 style="text-align:center">GRU static time step three-day graph</h4>
<img src="{{url_for('static', filename='GRU_static_3Day.png')}}" class="img-thumbnail">
</div>
</div>
<br>
<div class="row">
<div class="col-sm-6">
<h4 style="text-align:center">LSTM, GRU dynamic time step 12-hour graph</h4>
<img src="{{url_for('static', filename='LSTM_GRU_dynamic_12H.png')}}" class="img-thumbnail">
</div>
<div style="text-align:center;" class="col-sm-6">
<h4 style="text-align:center"><p>两种模型训练结果对比</p></h4><br>
<table style="text-align:center;" class="table table-striped table-hover">
<thead>
<tr>
<th>Metrics</th>
<th>MAE</th>
<th>MSE</th>
<th>RMSE</th>
<th>MAPE</th>
<th>R2</th>
<th>VS</th>
</tr>
</thead>
<tbody>
<tr>
<td>LSTM</td>
<td>7.197</td>
<td>99.613</td>
<td>9.980</td>
<td>4.565</td>
<td>0.997</td>
<td>0.997</td>
</tr>
<tr>
<td>GRU</td>
<td>14.670</td>
<td>514.314</td>
<td>22.678</td>
<td>7.813</td>
<td>0.985</td>
<td>0.986</td>
</tr>
</tbody>
</table>
</div>
</div>
<br>
<div class="row">
<div class="col-sm-6"><br>
<h5 style="text-align:center">Input data to test LSTM and GRU models.<p style="color:red;">Data available from 2019-05-20</p></h5><br>
<form action="{{ url_for('main') }}" method="POST" class="needs-validation" novalidate>
<div class="form-group">
<div class="result" align="center" style="display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
<div style="display: inline-block;font-size: 20px">Date:</div>
<input type="text" class="form-control" placeholder="yyyy-mm-dd" name="date" value="{{ date }}" required>
<div class="invalid-feedback">Please fill out this field.</div>
<div style="display: inline-block;font-size: 20px">Time:</div>
<input type="text" class="form-control" placeholder="hh:mm" name="time" value="{{ time }}" required>
<div class="invalid-feedback">Please fill out this field.</div>
<button type="submit" class="btn btn-outline-secondary">Predict</button><br>
</div>
</div>
</form><br>
</div><br>
<div class="col-sm-6">
<div class="result" align="center" style="display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
{% if true_val %}
True number<p style="font-size:40px">{{ true_val }}</p>
LSTM Predicted Number<p style="font-size:40px;color:green;">{{ pred_val_LSTM }}</p>
GRU Predicted Number<p style="font-size:40px;color:green;">{{ pred_val_GRU}}</p>
{% endif %}
</div>
<br>
<!-- 拥堵率:<p><div class="g-progress"></div></p><br>{{ percentage }}%-->
<div class="result" align="center" style="display: flex; flex-direction: row; justify-content: space-between; align-items: center;">
{% if percentage %}
<div style="display: inline-block;font-size: 25px">Flow/Capacity:</div>
<div class="g-progress" style="display: inline-block;"></div>
<div style="display: inline-block;font-size: 25px">{{ percentage }}%</div>
{% endif %}
</div>
</div><br>
</div>
</div>
</body>
<script>
var percent = {{ percentage }}; // 假设传入的百分比为50
// 更新渐变条形图的背景颜色
if (percent < 34) {
document.querySelector('.g-progress').style.background = "linear-gradient(90deg, #00cc00 " + percent + "%, transparent 0)";
} else if (percent < 66.7) {
document.querySelector('.g-progress').style.background = "linear-gradient(90deg, #00cc00 34%, #ffcc00 " + percent + "%, transparent 0)";
} else {
document.querySelector('.g-progress').style.background = "linear-gradient(90deg, #00cc00 34%, #ffcc00 66.7%, #ff3300 " + percent + "%, transparent 0)";
}
</script>
</html>