一、赛题介绍
背景:云计算时代,Serverless软件架构可根据业务工作负载进行弹性资源调整,这种方式可以有效减少资源在空闲期的浪费以及在繁忙期的业务过载,同时给用户带来极致的性价比服务。在弹性资源调度的背后,对工作负载的预测是一个重要环节。如何快速感知业务的坡峰波谷,是一个实用的Serverless服务应该考虑的问题。
任务:传统的资源控制系统以阈值为决策依据,只关注当前监控点的取值,缺少对历史数据以及工作负载趋势的把控,不能提前做好资源的调整,具有很长的滞后性。近年来,随着企业不断上云,云环境的工作负载预测成为一个经典且极具挑战的难题。
1.1 数据说明
1. 数据背景
本次赛题数据来自华为云数据湖探索(Data Lake Insight,简称DLI),它是一个Serverless的弹性大 数据分析服务。对于用户来说,提交SQL/Spark/Flink 作业需要购买队列(Queue),并将作业指定到购买的队列中执行。队列(Queue)的概念可以认为是资源的容器,它在作业真实执行时是一个计算集群,队列存在不同的规格,单位是CU(计算单元Compute Unit),1CU等于1核4GB,即16CU的队列代表着总资源16核64GB的计算集群。数据每5分钟会进行一次采集,赛题假设集群内节点间的任务调度平均,数据中的CPU_USAGE是集群中各节点平均值。更多详情可访问
2. 训练集
选取了43个队列的性能采集数据作为训练数据,每个队列之间相互独立。
3. 测试集
对于每行测试数据,赛题会给定该队列在某时段的性能监控数据(比如9: 35– 10:00),希望参赛者可以预测该点之后的未来五个点的指标(10:00 – 10:25),详情可参看提交示例。
注意:不可使用测试数据的结果作为训练数据!
4. 字段说明:
训练集
字段 | 类型 | 说明 |
---|---|---|
QUEUE_ID | INT | 队列标识,每个ID代表一个唯一的队列 |
CU | INT | 队列规格,不同规格的资源大小不一样。1CU为1核4GB |
STATUS | STRING | 队列状态,当前队列的状态是否可用 |
QUEUE_TYPE | STRING | 队列类型,不同类型适用于不同的任务,常见的有通用队列(general)和SQL队列 |
PLATFORM | STRING | 队列平台,创建队列的机器平台 |
CPU_USAGE | INT | CPU使用率,集群中各机器节点的CPU平均使用率 |
MEM_USAGE | INT | 内存使用率,集群中各机器节点的内存平均使用率 |
LAUNCHING_JOB_NUMS | INT | 提交中的作业数,即正在等待执行的作业 |
RUNNING_JOB_NUMS | INT | 运行中的作业数 |
SUCCEED_JOB_NUMS | INT | 已完成的作业数 |
CANCELLED_JOB_NUMS | INT | 已取消的作业数 |
FAILED_JOB_NUMS | INT | 已失败的作业数 |
DOTTING_TIME | BIGINT | 采集时间,每5分钟进行一次采集 |
RESOURCE_TYPE | STRING | 资源类型,创建队列的机器类型 |
DISK_USAGE | INT | 磁盘使用 |
1.2 评测标准
本赛题在线评分采用绝对误差作为评估指标,首先单独计算每个测试用例的误差值:
上式中cpuUsage会被换算成百分比进行计算,比如预测值为89,计算时是0.89;当launching的最大值为0时,表示预测和实际提交的JOB数均为0,该项结果为0。之后会计算单个测试点的总误差:
二、思路
2.1 导入相关包
import os
import numpy as np
import pandas as pd
from tqdm import tqdm
from sklearn.model_selection import KFold
import matplotlib.pyplot as plt
import lightgbm as lgb
pd.options.display.max_columns = None # 展示所有列
2.2 加载数据
# 加载原始数据
train = pd.read_csv('train.csv')
test = pd.read_csv('evaluation_public.csv')
submit = pd.read_csv('submit_example.csv')
# 简单排序
train = train.sort_values(by=['QUEUE_ID', 'DOTTING_TIME']).reset_index(drop=True)
test = test.sort_values(by=['ID', 'DOTTING_TIME']).reset_index(drop=True)
数据探索与预处理
ID与QUEUE_ID
##test同个ID均为5条
# sum(test.ID.value_counts()!=5)
# ##对于test中的每个qid,train中均存在
# for qid in test.QUEUE_ID.unique():
# if qid not in train.QUEUE_ID.unique():
# print(qid)
##删除test中不存在的qid
df = train.copy()
train = pd.DataFrame()
for qid in test.QUEUE_ID.unique():
if len(train)==0:
train = df[df.QUEUE_ID==qid]
else:
train = pd.concat([train,df[df.QUEUE_ID==qid]],axis=0)
train.index = range(len(train))
STATUS,QUEUE_TYPE,PLATFORM,RESOURCE_TYPE
###对STATUS列进行数据对比
##STATUS列在test中只有单一值.
print(train.STATUS.unique(),test.STATUS.unique())
##有两个train_qid的STATUS不唯一。
for qid in test.QUEUE_ID.unique():
if train[train.QUEUE_ID==qid].STATUS.unique().all()!= 'available':
print(qid,train[train.QUEUE_ID==qid].STATUS.unique())
print(train[train.QUEUE_ID==qid].STATUS.value_counts())
look = train[train.QUEUE_ID==qid]
look.index = range(len(look))
print(look[look.STATUS!='available'].index,'\n')
##考虑直接将81221的前23条数据删去,将83609的前158条数据删去,并将该列删去
###对QUEUE_TYPE列进行数据对比
##QUEUE_TYPE列在test中只有sql和general.而train还包括spark
print(train.QUEUE_TYPE.unique(),test.QUEUE_TYPE.unique())
##同个qid的QUEUE_TYPE相同。且对于同个qid,为唯一值。
for qid in test.QUEUE_ID.unique():
if train[train.QUEUE_ID==qid].QUEUE_TYPE.unique()!= test[test.QUEUE_ID==qid].QUEUE_TYPE.unique():
print(qid)
##直接将QUEUE_TYPE删去。
###对PLATFORM列进行数据对比
##PLATFORM列在test中只有x86.而train还包括aarch64
print(train.PLATFORM.unique(),test.PLATFORM.unique())
##同个qid的PLATFORM相同且为唯一值
for qid in test.QUEUE_ID.unique():
if train[train.QUEUE_ID==qid].PLATFORM.unique()!= test[test.QUEUE_ID==qid].PLATFORM.unique():
print(qid)
##直接将PLATFORM列删去
###对RESOURCE_TYPE列进行数据对比
##RESOURCE_TYPE列在test中只有vm.而train还包括container和缺失值
print(train.RESOURCE_TYPE.unique(),test.RESOURCE_TYPE.unique(),'\n')
##同个qid的RESOURCE_TYPE相同。
for qid in test.QUEUE_ID.unique():
if train[train.QUEUE_ID==qid].RESOURCE_TYPE.unique().all()!= 'vm':
print(qid)
for qid in test.QUEUE_ID.unique():
if qid in train[train.RESOURCE_TYPE.isna()].QUEUE_ID.unique():
print(qid,' ',train[train.RESOURCE_TYPE.isna()].QUEUE_ID.value_counts()[qid],
' ',train[train.QUEUE_ID==qid].RESOURCE_TYPE.unique(),' ',len(train[train.QUEUE_ID==qid]),
' ',train[train.RESOURCE_TYPE.isna()].QUEUE_ID.value_counts()[qid]/len(train[train.QUEUE_ID==qid]))
####直接将RESOURCE_TYPE列删去
###删除STATUS为其他状态的
qid_81221 = train[train.QUEUE_ID==81221]
index = qid_81221.head(23).index.tolist()
train.drop(index=index,inplace=True)
train.index = range(len(train))
qid_83609 = train[train.QUEUE_ID==83609]
index = qid_83609.head(158).index.tolist()
train.drop(index=index,inplace=True)
train.index = range(len(train))
#(newcell)复制一个dotting_time入train方便后面做时间填充
timestamp=pd.DataFrame()
timestamp=train['DOTTING_TIME']
train=pd.concat([train,timestamp],axis=1)```
```python
train.columns=['QUEUE_ID', 'CU', 'STATUS', 'QUEUE_TYPE', 'PLATFORM', 'CPU_USAGE',
'MEM_USAGE', 'LAUNCHING_JOB_NUMS', 'RUNNING_JOB_NUMS',
'SUCCEED_JOB_NUMS', 'CANCELLED_JOB_NUMS', 'FAILED_JOB_NUMS',
'DOTTING_TIME', 'RESOURCE_TYPE', 'DISK_USAGE', 'Add_timestamp']
特征工程
def time_process(df): #时间差处理
df['DOTTING_TIME'] = pd.to_datetime(df['DOTTING_TIME']