Merge branch '3.0' of https://github.com/taosdata/TDengine into feat/TS-4994-3.0
This commit is contained in:
commit
cafa960db8
169
Jenkinsfile2
169
Jenkinsfile2
|
@ -1,9 +1,11 @@
|
|||
import hudson.model.Result
|
||||
import hudson.model.*;
|
||||
import jenkins.model.CauseOfInterruption
|
||||
docs_only=0
|
||||
node {
|
||||
}
|
||||
file_zh_changed = ''
|
||||
file_en_changed = ''
|
||||
file_no_doc_changed = ''
|
||||
def abortPreviousBuilds() {
|
||||
def currentJobName = env.JOB_NAME
|
||||
def currentBuildNumber = env.BUILD_NUMBER.toInteger()
|
||||
|
@ -29,7 +31,7 @@ def abort_previous(){
|
|||
if (buildNumber > 1) milestone(buildNumber - 1)
|
||||
milestone(buildNumber)
|
||||
}
|
||||
def check_docs() {
|
||||
def check_docs(){
|
||||
if (env.CHANGE_URL =~ /\/TDengine\//) {
|
||||
sh '''
|
||||
hostname
|
||||
|
@ -40,39 +42,94 @@ def check_docs() {
|
|||
cd ${WKC}
|
||||
git reset --hard
|
||||
git clean -f
|
||||
rm -rf examples/rust/
|
||||
git remote prune origin
|
||||
git fetch
|
||||
'''
|
||||
script {
|
||||
sh '''
|
||||
cd ${WKC}
|
||||
git checkout ''' + env.CHANGE_TARGET + '''
|
||||
'''
|
||||
}
|
||||
sh '''
|
||||
cd ${WKC}
|
||||
git remote prune origin
|
||||
git checkout ''' + env.CHANGE_TARGET + '''
|
||||
git pull >/dev/null
|
||||
git fetch origin +refs/pull/${CHANGE_ID}/merge
|
||||
git checkout -qf FETCH_HEAD
|
||||
git checkout -qf FETCH_HEAD
|
||||
'''
|
||||
def file_changed = sh (
|
||||
|
||||
file_zh_changed = sh (
|
||||
script: '''
|
||||
cd ${WKC}
|
||||
git --no-pager diff --name-only FETCH_HEAD `git merge-base FETCH_HEAD ${CHANGE_TARGET}`|grep -v "^docs/en/"|grep -v "^docs/zh/" || :
|
||||
git --no-pager diff --name-only FETCH_HEAD `git merge-base FETCH_HEAD ${CHANGE_TARGET}`|grep "^docs/zh/" || :
|
||||
''',
|
||||
returnStdout: true
|
||||
)
|
||||
|
||||
file_en_changed = sh (
|
||||
script: '''
|
||||
cd ${WKC}
|
||||
git --no-pager diff --name-only FETCH_HEAD `git merge-base FETCH_HEAD ${CHANGE_TARGET}`|grep "^docs/en/" || :
|
||||
''',
|
||||
returnStdout: true
|
||||
)
|
||||
|
||||
file_no_doc_changed = sh (
|
||||
script: '''
|
||||
cd ${WKC}
|
||||
git --no-pager diff --name-only FETCH_HEAD `git merge-base FETCH_HEAD ${CHANGE_TARGET}`|grep -v "^docs/en/"|grep -v "^docs/zh/"|grep -v "*.md" || :
|
||||
''',
|
||||
returnStdout: true
|
||||
).trim()
|
||||
if (file_changed == '') {
|
||||
echo "docs PR"
|
||||
docs_only=1
|
||||
} else {
|
||||
echo file_changed
|
||||
}
|
||||
env.FILE_CHANGED = file_changed
|
||||
echo "file_zh_changed: ${file_zh_changed}"
|
||||
echo "file_en_changed: ${file_en_changed}"
|
||||
echo "file_no_doc_changed: ${file_no_doc_changed}"
|
||||
}
|
||||
}
|
||||
|
||||
def build_pre_docs(){
|
||||
if (env.CHANGE_URL =~ /\/TDengine\//) {
|
||||
sh '''
|
||||
hostname
|
||||
date
|
||||
env
|
||||
'''
|
||||
|
||||
sh '''
|
||||
cd ${DOC_WKC}/${td_repo}
|
||||
git reset --hard
|
||||
git clean -f
|
||||
git remote prune origin
|
||||
git fetch
|
||||
git checkout ''' + env.CHANGE_TARGET + '''
|
||||
git pull >/dev/null
|
||||
git fetch origin +refs/pull/${CHANGE_ID}/merge
|
||||
git checkout -qf FETCH_HEAD
|
||||
'''
|
||||
|
||||
sh '''
|
||||
cd ${DOC_WKC}/${tools_repo}
|
||||
git reset --hard
|
||||
git clean -f
|
||||
git fetch
|
||||
git remote prune origin
|
||||
git checkout ''' + env.CHANGE_TARGET + '''
|
||||
git pull >/dev/null
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
def build_zh_docs(){
|
||||
sh '''
|
||||
cd ${DOC_WKC}/${zh_doc_repo}
|
||||
# git pull
|
||||
yarn ass local
|
||||
yarn build
|
||||
'''
|
||||
}
|
||||
|
||||
def build_en_docs(){
|
||||
sh '''
|
||||
cd ${DOC_WKC}/${en_doc_repo}
|
||||
# git pull
|
||||
yarn ass local
|
||||
yarn build
|
||||
'''
|
||||
}
|
||||
|
||||
|
||||
def pre_test(){
|
||||
sh '''
|
||||
hostname
|
||||
|
@ -153,6 +210,7 @@ def pre_test(){
|
|||
'''
|
||||
return 1
|
||||
}
|
||||
|
||||
def pre_test_build_mac() {
|
||||
sh '''
|
||||
hostname
|
||||
|
@ -173,6 +231,7 @@ def pre_test_build_mac() {
|
|||
date
|
||||
'''
|
||||
}
|
||||
|
||||
def pre_test_win(){
|
||||
bat '''
|
||||
hostname
|
||||
|
@ -284,6 +343,7 @@ def pre_test_win(){
|
|||
git log -5
|
||||
'''
|
||||
}
|
||||
|
||||
def pre_test_build_win() {
|
||||
bat '''
|
||||
echo "building ..."
|
||||
|
@ -313,6 +373,7 @@ def pre_test_build_win() {
|
|||
'''
|
||||
return 1
|
||||
}
|
||||
|
||||
def run_win_ctest() {
|
||||
bat '''
|
||||
echo "windows ctest ..."
|
||||
|
@ -322,6 +383,7 @@ def run_win_ctest() {
|
|||
time /t
|
||||
'''
|
||||
}
|
||||
|
||||
def run_win_test() {
|
||||
bat '''
|
||||
echo "windows test ..."
|
||||
|
@ -344,28 +406,67 @@ pipeline {
|
|||
WK = '/var/lib/jenkins/workspace/TDinternal'
|
||||
WKC = '/var/lib/jenkins/workspace/TDinternal/community'
|
||||
WKPY = '/var/lib/jenkins/workspace/taos-connector-python'
|
||||
DOC_WKC = '/root/doc_ci_work'
|
||||
td_repo = 'TDengine'
|
||||
zh_doc_repo = 'docs.taosdata.com'
|
||||
en_doc_repo = 'docs.tdengine.com'
|
||||
tools_repo = 'taos-tools'
|
||||
}
|
||||
stages {
|
||||
stage('check') {
|
||||
stage ('check doc file changed') {
|
||||
agent{label " slave1_47 || slave1_48 || slave1_49 || slave1_50 || slave1_52 || slave1_59 || slave1_63 || worker03 || slave215 || slave217 || slave219 || Mac_catalina "}
|
||||
steps {
|
||||
check_docs()
|
||||
}
|
||||
}
|
||||
|
||||
stage ('pre for build docs') {
|
||||
when {
|
||||
allOf {
|
||||
not { expression { env.CHANGE_BRANCH =~ /docs\// }}
|
||||
}
|
||||
beforeAgent true
|
||||
expression { env.CHANGE_BRANCH =~ /(?i)doc.*/ || file_zh_changed != '' || file_en_changed != '' }
|
||||
}
|
||||
agent{label "doc_build_0_30"}
|
||||
steps {
|
||||
build_pre_docs()
|
||||
}
|
||||
}
|
||||
|
||||
stage('build Docs') {
|
||||
when {
|
||||
beforeAgent true
|
||||
expression { env.CHANGE_BRANCH =~ /(?i)doc.*/ || file_zh_changed != '' || file_en_changed != '' }
|
||||
}
|
||||
parallel {
|
||||
stage('check docs') {
|
||||
agent{label " slave1_47 || slave1_48 || slave1_49 || slave1_50 || slave1_52 || slave1_59 || slave1_63 || worker03 || slave215 || slave217 || slave219 || Mac_catalina "}
|
||||
stage('build zh docs') {
|
||||
agent{label "doc_build_0_30"}
|
||||
when {
|
||||
expression { file_zh_changed != '' }
|
||||
}
|
||||
steps {
|
||||
check_docs()
|
||||
build_zh_docs()
|
||||
}
|
||||
}
|
||||
stage('build en docs') {
|
||||
agent{label "doc_build_0_30"}
|
||||
when {
|
||||
expression { file_en_changed != '' }
|
||||
}
|
||||
steps {
|
||||
build_en_docs()
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
unsuccessful {
|
||||
error('build docs stage failed, terminating pipeline.')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('run test') {
|
||||
when {
|
||||
allOf {
|
||||
not { expression { env.CHANGE_BRANCH =~ /docs\// }}
|
||||
expression { docs_only == 0 }
|
||||
not { expression { file_no_doc_changed == '' }}
|
||||
}
|
||||
}
|
||||
parallel {
|
||||
|
@ -420,7 +521,7 @@ pipeline {
|
|||
script {
|
||||
sh '''
|
||||
mkdir -p ${WKDIR}/tmp/${BRANCH_NAME}_${BUILD_ID}
|
||||
echo "''' + env.FILE_CHANGED + '''" > ${WKDIR}/tmp/${BRANCH_NAME}_${BUILD_ID}/docs_changed.txt
|
||||
echo "''' + file_no_doc_changed + '''" > ${WKDIR}/tmp/${BRANCH_NAME}_${BUILD_ID}/docs_changed.txt
|
||||
'''
|
||||
sh '''
|
||||
cd ${WKC}/tests/parallel_test
|
||||
|
@ -570,4 +671,4 @@ pipeline {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,10 @@ sidebar_label: "安装部署"
|
|||
---
|
||||
|
||||
### 环境准备
|
||||
ANode 可以运行在 Linux/Windows/Mac 操作系统之上,要求部署 Anode 的节点安装有 3.10 及以上版本的Python环境,以及相应的 Python 包自动安装组件 Pip。
|
||||
为了使用 TDgpt 的高级时序数据分析功能功能,需要在 TDengine 集群中安装部署 AI node(Anode)。ANode 可以运行在 Linux/Windows/Mac 等操作系统之上。请确保安装部署 Anode之前,系统中已经具备 3.10 及以上版本的Python环境,以及相应的 Python 包自动安装组件 Pip,否则无法正常安装 Anode。
|
||||
|
||||
### 安装及卸载
|
||||
不同操作系统上安装及部署操作有差异,主要包括安装/卸载操作、安装路径、Anode服务的启停等几个方面。本小节以 Linux 系统为例,说明安装部署的整个流程。使用Linux环境下的安装包 TDengine-enterprise-anode-1.x.x.tar.gz 可进行 ANode 的安装部署工作,使用如下命令:
|
||||
不同操作系统上安装及部署操作有细微的差异,主要是安装/卸载操作、安装路径、Anode服务的启停等几个方面。下面将以 Linux 系统为例,说明安装部署的整个流程。使用 Linux 环境下的安装包 TDengine-enterprise-anode-1.x.x.tar.gz 可进行 ANode 的安装部署工作,使用如下命令:
|
||||
|
||||
```bash
|
||||
tar -xzvf TDengine-enterprise-anode-1.0.0.tar.gz
|
||||
|
@ -15,7 +15,7 @@ cd TDengine-enterprise-anode-1.0.0
|
|||
sudo ./install.sh
|
||||
```
|
||||
|
||||
在安装完成 ANode 之后,执行命令 `rmtaosanode` 即可。
|
||||
在安装完成 ANode 之后,执行命令 `rmtaosanode` 即可已经安装的 Anode。
|
||||
ANode 使用 Python 虚拟环境运行,避免影响安装环境中现有的 Python 库。安装后的默认 Python 虚拟环境目录位于 `/var/lib/taos/taosanode/venv/`。为了避免反复安装虚拟环境带来的开销,卸载 ANode 执行的命令 `rmtaosanode` 并不会自动删除该虚拟环境,如果您确认不需要 Python 的虚拟环境,手动删除即可。
|
||||
|
||||
### 启停服务
|
||||
|
@ -82,8 +82,9 @@ log-level = DEBUG
|
|||
|
||||
**提示**
|
||||
请勿设置 `daemonize` 参数,该参数会导致 uWSGI 与 systemctl 冲突,从而无法正常启动。
|
||||
该配置文件只包含了使用 Anode提供服务的最基础的配置参数,对于 uWSGI 的其他配置参数设置及其含义和说明请参考[uWSGIS官方文档](https://uwsgi-docs-zh.readthedocs.io/zh-cn/latest/Options.html)。
|
||||
对于 Anode 运行配置主要是以下几个:
|
||||
上面的示例配置文件 `taosanode.ini` 只包含了使用 Anode 提供服务的基础配置参数,对于 uWSGI 的其他配置参数设置及其含义和说明请参考 [uWSGIS官方文档](https://uwsgi-docs-zh.readthedocs.io/zh-cn/latest/Options.html)。
|
||||
|
||||
Anode 运行配置主要是以下:
|
||||
- app-log: Anode 服务运行产生的日志,用户可以调整其到需要的位置
|
||||
- model-dir: 采用算法针对已经存在的数据集的运行完成生成的模型存储位置
|
||||
- log-level: app-log文件的日志级别
|
||||
|
|
|
@ -3,13 +3,22 @@ title: "数据分析预处理"
|
|||
sidebar_label: "数据分析预处理"
|
||||
---
|
||||
|
||||
import activity from './pic/activity.png';
|
||||
import wndata from './pic/white-noise-data.png'
|
||||
|
||||
### 分析流程
|
||||
在针对时序数据进行高级分析之前,首先进行数据的白噪声检查(White Noise Data check, WND)。白噪声时序数据可以简单地认为是随机数构成的时序数据序列,这种类型的序列没有分析的价值,因此会直接返回空的结果。整体的流程如下图所示。
|
||||
<img src="./pic/activity.png" width="560" alt="流程图" />
|
||||
在针对时序数据进行高级分析之前,首先进行数据的白噪声检查(White Noise Data check, WND)。整体的流程如下图所示。
|
||||
|
||||
<img src={activity} width="560" alt="预处理流程" />
|
||||
|
||||
- 对于时间序列数据预测分析,首先进行白噪声检查,不是白噪声数据,进行数据重采样和时间戳对齐的预处理,预处理完成后进行数据预测分析。
|
||||
- 对于时间序列异常检测,首先进行白噪声检查,检查通过以后无后续的处理流程,直接进行异常检测分析。
|
||||
|
||||
### 白噪声检查
|
||||
白噪声检查采用 `Ljung-Box` 检验,`Ljung-Box` 统计量的计算过程需遍历整个输入时间序列。
|
||||
如果用户能够明确输入序列一定不是白噪声序列,那么可以通过增加参数 `wncheck=0` 要求分析平台忽略白噪声输入时间序列检查,从而节省 CPU 计算资源。
|
||||
|
||||
<img src={wndata} width="430" alt="white-noise-data"/>
|
||||
|
||||
白噪声时序数据可以简单地认为是随机数构成的时序数据序列(如上图所示),随机数的时间序列没有分析的价值,因此会直接返回空结果。白噪声检查采用 `Ljung-Box` 检验,`Ljung-Box` 统计量的计算过程需遍历整个输入序列。如果用户能够明确输入序列一定不是白噪声序列,那么可以通过增加参数 `wncheck=0` 要求分析平台忽略白噪声输入时间序列检查,从而节省计算资源。
|
||||
TDgpt 暂不提供独立的时间序列白噪声检测功能。
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ FROM foo
|
|||
ANOMALY_WINDOW(i32, "algo=iqr");
|
||||
```
|
||||
|
||||
如下图所示,Anode 将返回时序数据异常窗口 [10:51:30, 10:54:40]
|
||||
如下图所示,Anode 将返回时序数据异常窗口 [10:51:30, 10:53:40]
|
||||
|
||||
<img src="../pic/anomaly-detection.png" width="560" alt="异常检测" />
|
||||
|
||||
|
|
|
@ -1,4 +1,93 @@
|
|||
---
|
||||
title: "开发者指南"
|
||||
sidebar_label: "开发者指南"
|
||||
---
|
||||
title: "预测算法"
|
||||
sidebar_label: "预测算法"
|
||||
---
|
||||
|
||||
### 输入约定
|
||||
`execute` 是预测算法处理的核心方法。框架调用该方法之前,在对象属性参数 `self.list` 中已经设置完毕用于预测的历史时间序列数据。
|
||||
|
||||
### 输出约定及父类属性说明
|
||||
`execute` 方法执行完成后的返回一个如下字典对象, 预测返回结果如下:
|
||||
```python
|
||||
return {
|
||||
"mse": mse, # 预测算法的拟合数据最小均方误差(minimum squared error)
|
||||
"res": res # 结果数组 [时间戳数组, 预测结果数组, 预测结果执行区间下界数组,预测结果执行区间上界数组]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
预测算法的父类 `AbstractForecastService` 包含的对象属性如下:
|
||||
|
||||
|属性名称|说明|默认值|
|
||||
|---|---|---|
|
||||
|period|输入时间序列的周期性,多少个数据点表示一个完整的周期。如果没有周期性,设置为 0 即可| 0|
|
||||
|start_ts|预测结果的开始时间| 0|
|
||||
|time_step|预测结果的两个数据点之间时间间隔|0 |
|
||||
|fc_rows|预测结果的数量| 0 |
|
||||
|return_conf|预测结果中是否包含置信区间范围,如果不包含置信区间,那么上界和下界与自身相同| 1|
|
||||
|conf|置信区间分位数|95|
|
||||
|
||||
|
||||
|
||||
### 示例代码
|
||||
下面我们开发一个示例预测算法,对于任何输入的时间序列数据,固定返回值 1 作为预测结果。
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
from service import AbstractForecastService
|
||||
|
||||
# 算法实现类名称 需要以下划线 "_" 开始,并以 Service 结束
|
||||
class _MyForecastService(AbstractForecastService):
|
||||
""" 定义类,从 AbstractForecastService 继承并实现其定义的抽象方法 execute """
|
||||
|
||||
# 定义算法调用关键词,全小写ASCII码
|
||||
name = 'myfc'
|
||||
|
||||
# 该算法的描述信息(建议添加)
|
||||
desc = """return the forecast time series data"""
|
||||
|
||||
def __init__(self):
|
||||
"""类初始化方法"""
|
||||
super().__init__()
|
||||
|
||||
def execute(self):
|
||||
""" 算法逻辑的核心实现"""
|
||||
res = []
|
||||
|
||||
"""这个预测算法固定返回 1 作为预测值,预测值的数量是用户通过 self.fc_rows 指定"""
|
||||
ts_list = [self.start_ts + i * self.time_step for i in range(self.fc_rows)]
|
||||
res.app(ts_list) # 设置预测结果时间戳列
|
||||
|
||||
"""生成全部为 1 的预测结果 """
|
||||
res_list = [1] * self.fc_rows
|
||||
res.append(res_list)
|
||||
|
||||
"""检查用户输入,是否要求返回预测置信区间上下界"""
|
||||
if self.return_conf:
|
||||
"""对于没有计算预测置信区间上下界的算法,直接返回预测值作为上下界即可"""
|
||||
bound_list = [1] * self.fc_rows
|
||||
res.append(bound_list) # 预测结果置信区间下界
|
||||
res.append(bound_list) # 预测结果执行区间上界
|
||||
|
||||
"""返回结果"""
|
||||
return { "res": res, "mse": 0}
|
||||
|
||||
|
||||
def set_params(self, params):
|
||||
"""该算法无需任何输入参数,直接重载父类该函数,不处理算法参数设置逻辑"""
|
||||
pass
|
||||
```
|
||||
将该文件保存在 `./taosanalytics/algo/ad/` 目录下,然后重启 taosanode 服务。然后就可以通过 SQL 语句调用该检测算法。
|
||||
|
||||
```SQL
|
||||
--- 对 col 列进行异常检测,通过指定 algo 参数为 myad 来调用新添加的异常检测类
|
||||
SELECT COUNT(*) FROM foo ANOMALY_DETECTION(col, 'algo=myad')
|
||||
```
|
||||
|
||||
将该文件保存在 `./taosanalytics/algo/fc/` 目录下,然后重启 taosanode 服务。通过执行 `SHOW ANODES FULL` 能够看到新加入的算法,然后就可以通过 SQL 语句调用该预测算法。
|
||||
|
||||
```SQL
|
||||
--- 对 col 列进行异常检测,通过指定 algo 参数为 myfc 来调用新添加的预测类
|
||||
SELECT _flow, _fhigh, _frowts, FORECAST(col_name, "algo=myfc")
|
||||
FROM foo;
|
||||
```
|
||||
|
|
|
@ -1,4 +1,76 @@
|
|||
---
|
||||
title: "开发者指南"
|
||||
sidebar_label: "开发者指南"
|
||||
---
|
||||
title: "异常检测"
|
||||
sidebar_label: "异常检测"
|
||||
---
|
||||
|
||||
### 输入约定
|
||||
`execute` 是算法处理的核心方法。框架调用该方法之前,在对象属性参数 `self.list` 中已经设置完毕用于异常检测的时间序列数据。
|
||||
|
||||
### 输出约定
|
||||
`execute` 方法执行完成后的返回值是长度与 `self.list` 相同的数组,数组位置 -1 的标识异常值点。
|
||||
> 例如:对于输入测量值序列 [2, 2, 2, 2, 100], 假设 100 是异常点,那么方法返回的结果数组则为 [1, 1, 1, 1, -1]。
|
||||
|
||||
|
||||
### 示例代码
|
||||
下面我们开发一个示例异常检测算法,在异常检测中,将输入时间序列值的最后一个值设置为异常值,并返回结果。
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
from service import AbstractAnomalyDetectionService
|
||||
|
||||
# 算法实现类名称 需要以下划线 "_" 开始,并以 Service 结束
|
||||
class _MyAnomalyDetectionService(AbstractAnomalyDetectionService):
|
||||
""" 定义类,从 AbstractAnomalyDetectionService 继承,并实现 AbstractAnomalyDetectionService 类的抽象方法 """
|
||||
|
||||
# 定义算法调用关键词,全小写ASCII码
|
||||
name = 'myad'
|
||||
|
||||
# 该算法的描述信息(建议添加)
|
||||
desc = """return the last value as the anomaly data"""
|
||||
|
||||
def __init__(self):
|
||||
"""类初始化方法"""
|
||||
super().__init__()
|
||||
|
||||
def execute(self):
|
||||
""" 算法逻辑的核心实现"""
|
||||
|
||||
"""创建一个长度为 len(self.list),全部值为 1 的结果数组,然后将最后一个值设置为 -1,表示最后一个值是异常值"""
|
||||
res = [1] * len(self.list)
|
||||
res[-1] = -1
|
||||
|
||||
"""返回结果数组"""
|
||||
return res
|
||||
|
||||
|
||||
def set_params(self, params):
|
||||
"""该算法无需任何输入参数,直接重载父类该函数,不处理算法参数设置逻辑"""
|
||||
pass
|
||||
```
|
||||
|
||||
将该文件保存在 `./taosanalytics/algo/ad/` 目录下,然后重启 taosanode 服务。然后就可以通过 SQL 语句调用该检测算法。
|
||||
|
||||
```SQL
|
||||
--- 对 col 列进行异常检测,通过指定 algo 参数为 myad 来调用新添加的异常检测类
|
||||
SELECT COUNT(*) FROM foo ANOMALY_DETECTION(col, 'algo=myad')
|
||||
```
|
||||
|
||||
|
||||
### 单元测试
|
||||
|
||||
在测试目录`taosanalytics/test`中的 anomaly_test.py 中增加单元测试用例或添加新的测试文件。框架中使用了 Python Unit test 包。
|
||||
|
||||
```python
|
||||
def test_myad(self):
|
||||
""" 测试 _IqrService 类 """
|
||||
s = loader.get_service("myad")
|
||||
|
||||
# 设置需要进行检测的输入数据
|
||||
s.set_input_list(AnomalyDetectionTest.input_list)
|
||||
|
||||
r = s.execute()
|
||||
|
||||
# 最后一个点是异常点
|
||||
self.assertEqual(r[-1], -1)
|
||||
self.assertEqual(len(r), len(AnomalyDetectionTest.input_list))
|
||||
```
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
title: "算法开发者指南"
|
||||
sidebar_label: "算法开发者指南"
|
||||
---
|
||||
TDgpt 是一个可扩展的时序数据高级分析平台,用户仅按照简易的步骤就能将新分析算法添加到分析平台中。将开发完成的算法代码文件放入对应的目录文件夹,然后重启 Anode 即可完成扩展升级。Anode 启动后会自动加载特定目录的分析算法。用户可以直接使用 SQL 语句调用添加到 TDgpt 系统中的分析算法。得益于 TDgpt 与 taosd 的松散耦合关系,分析平台升级对 taosd 完全没有影响。应用系统也不需要做任何更改就能够完成分析功能和分析算法的升级。
|
||||
|
||||
本节说明如何将自己开发的预测算法和异常检测算法整合到 TDengine 分析平台,并能够通过 SQL 语句进行调用。
|
||||
这种方式能够按需扩展新分析算法,极大地拓展了 TDgpt 适应的范围,用户可以将契合业务场景开发的(预测、异常检测)分析算法嵌入到 TDgpt,并通过 SQL 语句进行调用。在不更改或更改非常少的应用系统代码的前提下,就能够快速完成分析功能的平滑升级。
|
||||
|
||||
本节说明如何将预测算法和异常检测算法添加到 TDengine 分析平台。
|
||||
|
||||
## 目录结构
|
||||
首先需要了解TDgpt的目录结构。其主体目录结构如下图:
|
||||
|
||||
```bash
|
||||
.
|
||||
|
@ -25,19 +29,20 @@ sidebar_label: "算法开发者指南"
|
|||
|
||||
|目录|说明|
|
||||
|---|---|
|
||||
|taos|Python 源代码目录,其下包含了算法具体保存目录 algo,放置杂项目录 misc,单元测试和集成测试目录 test。 algo 目录下 ad 放置异常检测算法代码,fc 放置预测算法代码|
|
||||
|taos|Python 源代码目录,其下包含了算法具体保存目录 algo,放置杂项目录 misc,单元测试和集成测试目录 test。 algo 目录下 ad 保存异常检测算法代码,fc 目录保存预测算法代码|
|
||||
|script|是安装脚本和发布脚本放置目录|
|
||||
|model|放置针对数据集完成的训练模型|
|
||||
|cfg|配置文件目录|
|
||||
|
||||
## 约定与限制
|
||||
|
||||
定义异常检测算法的 Python 代码文件需放在 /taos/algo/ad 目录中,预测算法 Python 代码文件需要放在 /taos/algo/fc 目录中,以确保系统启动的时候能够正常加载对应目录下的 Python 文件。
|
||||
- 异常检测算法的 Python 代码文件需放在 `./taos/algo/ad` 目录中
|
||||
- 预测算法 Python 代码文件需要放在 `./taos/algo/fc` 目录中
|
||||
|
||||
|
||||
### 类命名规范
|
||||
|
||||
算法类的名称需要以下划线开始,以 Service 结尾。例如:_KsigmaService 是 KSigma 异常检测算法的实现类。
|
||||
由于算法采用自动加载,因此其只识别按照特定命名方式的类。算法类的名称需要以下划线开始,以 Service 结尾。例如:_KsigmaService 是 KSigma 异常检测算法类。
|
||||
|
||||
### 类继承约定
|
||||
|
||||
|
@ -47,111 +52,14 @@ sidebar_label: "算法开发者指南"
|
|||
### 类属性初始化
|
||||
每个算法实现的类需要静态初始化两个类属性,分别是:
|
||||
|
||||
- `name`:触发调用的关键词,全小写英文字母
|
||||
- `name`:触发调用的关键词,全小写英文字母。该名称也是通过 `SHOW` 命令查看可用分析算法是显示的名称。
|
||||
- `desc`:算法的描述信息
|
||||
|
||||
### 核心方法输入与输出约定
|
||||
|
||||
`execute` 是算法处理的核心方法。调用该方法的时候,`self.list` 已经设置好输入数组。
|
||||
|
||||
异常检测输出结果
|
||||
|
||||
`execute` 的返回值是长度与 `self.list` 相同的数组,数组位置为 -1 的即为异常值点。例如:输入数组是 [2, 2, 2, 2, 100], 如果 100 是异常点,那么返回值是 [1, 1, 1, 1, -1]。
|
||||
|
||||
预测输出结果
|
||||
|
||||
对于预测算法,`AbstractForecastService` 的对象属性说明如下:
|
||||
|
||||
|属性名称|说明|默认值|
|
||||
|---|---|---|
|
||||
|period|输入时间序列的周期性,多少个数据点表示一个完整的周期。如果没有周期性,那么设置为 0 即可| 0|
|
||||
|start_ts|预测结果的开始时间| 0|
|
||||
|time_step|预测结果的两个数据点之间时间间隔|0 |
|
||||
|fc_rows|预测结果的数量| 0 |
|
||||
|return_conf|预测结果中是否包含置信区间范围,如果不包含置信区间,那么上界和下界与自身相同| 1|
|
||||
|conf|置信区间分位数 0.05|
|
||||
|
||||
|
||||
预测返回结果如下:
|
||||
```python
|
||||
return {
|
||||
"rows": self.fc_rows, # 预测数据行数
|
||||
"period": self.period, # 数据周期性,同输入
|
||||
"algo": "holtwinters", # 预测使用的算法
|
||||
"mse": mse, # 预测算法的 mse
|
||||
"res": res # 结果数组 [时间戳数组, 预测结果数组, 预测结果执行区间下界数组,预测结果执行区间上界数组]
|
||||
}
|
||||
```SQL
|
||||
--- algo 后面的参数 algo_name 即为类名称 `name`
|
||||
SELECT COUNT(*) FROM foo ANOMALY_DETECTION(col_name, 'algo=algo_name')
|
||||
```
|
||||
|
||||
|
||||
## 示例代码
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
from service import AbstractAnomalyDetectionService
|
||||
|
||||
# 算法实现类名称 需要以下划线 "_" 开始,并以 Service 结束,如下 _IqrService 是 IQR 异常检测算法的实现类。
|
||||
class _IqrService(AbstractAnomalyDetectionService):
|
||||
""" IQR algorithm 定义类,从 AbstractAnomalyDetectionService 继承,并实现 AbstractAnomalyDetectionService 类的抽象函数 """
|
||||
|
||||
# 定义算法调用关键词,全小写ASCII码(必须添加)
|
||||
name = 'iqr'
|
||||
|
||||
# 该算法的描述信息(建议添加)
|
||||
desc = """found the anomaly data according to the inter-quartile range"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def execute(self):
|
||||
""" execute 是算法实现逻辑的核心实现,直接修改该实现即可 """
|
||||
|
||||
# self.list 是输入数值列,list 类型,例如:[1,2,3,4,5]。设置 self.list 的方法在父类中已经进行了定义。实现自己的算法,修改该文件即可,以下代码使用自己的实现替换即可。
|
||||
#lower = np.quantile(self.list, 0.25)
|
||||
#upper = np.quantile(self.list, 0.75)
|
||||
|
||||
#min_val = lower - 1.5 * (upper - lower)
|
||||
#max_val = upper + 1.5 * (upper - lower)
|
||||
#threshold = [min_val, max_val]
|
||||
|
||||
# 返回值是与输入数值列长度相同的数据列,异常值对应位置是 -1。例如上述输入数据列,返回数值列是 [1, 1, 1, 1, -1],表示 [5] 是异常值。
|
||||
return [-1 if k < threshold[0] or k > threshold[1] else 1 for k in self.list]
|
||||
|
||||
|
||||
def set_params(self, params):
|
||||
"""该算法无需任何输入参数,直接重载父类该函数,不处理算法参数设置逻辑"""
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
## 单元测试
|
||||
|
||||
在测试文件目录中的 anomaly_test.py 中增加单元测试用例。
|
||||
|
||||
```python
|
||||
def test_iqr(self):
|
||||
""" 测试 _IqrService 类 """
|
||||
s = loader.get_service("iqr")
|
||||
|
||||
# 设置需要进行检测的输入数据
|
||||
s.set_input_list(AnomalyDetectionTest.input_list)
|
||||
|
||||
# 测试 set_params 的处理逻辑
|
||||
try:
|
||||
s.set_params({"k": 2})
|
||||
except ValueError as e:
|
||||
self.assertEqual(1, 0)
|
||||
|
||||
r = s.execute()
|
||||
|
||||
# 绘制异常检测结果
|
||||
draw_ad_results(AnomalyDetectionTest.input_list, r, "iqr")
|
||||
|
||||
# 检查结果
|
||||
self.assertEqual(r[-1], -1)
|
||||
self.assertEqual(len(r), len(AnomalyDetectionTest.input_list))
|
||||
```
|
||||
|
||||
|
||||
## 需要模型的算法
|
||||
|
||||
针对特定数据集,进行模型训练的算法,在训练完成后。需要将训练得到的模型保存在 model 目录中。需要注意的是,针对每个算法,需要建立独立的文件夹。例如 auto_encoder 的训练算法在 model 目录下建立 autoencoder 的目录,使用该算法针对不同数据集训练得到的模型,均需要放置在该目录下。
|
||||
|
|
|
@ -3,7 +3,7 @@ sidebar_label: TDgpt
|
|||
title: TDgpt
|
||||
---
|
||||
|
||||
import TDgpt from '../pic/data-analysis.png';
|
||||
import TDgpt from './pic/data-analysis.png';
|
||||
|
||||
## 概述
|
||||
|
||||
|
@ -16,7 +16,7 @@ TDgpt 运行在部署于 TDengine 集群中的 AI Node (ANode)中。每个 TDeng
|
|||
通过注册指令将 ANode 注册到 MNode 中以后,就加入到 TDengine 集群,并可被查询引擎动态调用执行。在查询处理过程中,查询引擎根据生成的物理执行计划,**按需**向 ANode 请求高级时序数据分析服务。用户可通过SQL语句与 ANode 节点交互,并使用其提供的全部分析服务。需要注意的是 ANode 不直接接受用户的数据分析请求。同时 ANode 提供高效的动态注册机制,其注册和卸载过程完全不影响 TDengine 集群的服务,只影响提供对应的查询服务能力。
|
||||
|
||||
TDgpt 提供的高级数据分析功能分为时序数据异常检测和时序数据预测。
|
||||
- 时序数据异常检测的结果采用异常窗口的形式提供,即分析系统自动将算法检测到的连续异常数据以时间窗口的形式返回,其使用方式与 TDengine 中其他类型的时间窗口(例如状态窗口、事件窗口)类似。特别地,可以将异常数据窗口视作为一种特殊的**状态窗口(State Window)**,因此状态窗口可使用的所有查询操作均可应用在异常窗口上。
|
||||
- 时序数据异常检测的结果采用异常窗口的形式提供,即分析系统自动将算法检测到的连续异常数据以时间窗口的形式返回,其使用方式与 TDengine 中其他类型的时间窗口(例如状态窗口、事件窗口)类似。特别地,可以将异常数据窗口视作为一种特殊的**事件窗口(Event Window)**,因此状态窗口可使用的所有查询操作均可应用在异常窗口上。
|
||||
- 时序数据预测是基于输入的时间序列数据,使用指定(或默认)预测算法给出输入时序数据后续时间序列的**预测**观测值数据。因此,不同于异常检测是以窗口的形式存在,时序数据预测在 TDengine 中是一个(不确定输出)函数。
|
||||
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 87 KiB |
Loading…
Reference in New Issue