从Eliza到ChatGPT,对话系统发展简史✨

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Eliza到ChatGPT,对话系统发展简史✨相关的知识,希望对你有一定的参考价值。

最近,由人工智能实验室OpenAI发布的对话式大型语言模型ChatGPT在各大中外媒体平台掀起了一阵狂热之风。短短4天时间,其用户量到达百万级,用户之多导致服务器一度爆满。

继 AI 绘画之后,由 OpenAI 上线的 ChatGPT 成了新的流量收割机,也引发了网友的一系列“花式整活”。ChatGPT的功能是如此强大,因此连马斯克都认为“我们离强大到危险的 AI 不远了”。


认识它不妨先用用,咋直接问问ChatGPT​​↓​

从Eliza到ChatGPT,对话系统发展简史✨_数据

从Eliza到ChatGPT,对话系统发展简史✨_数据_02

哗哗两下就给你列出1 2 3 4 5.....,不过咱先刨开这个内容质量不说,你就光看它这个文字能力,你问它什么,它都能给你对答如流,还说的有模有样的,反正是把我给震撼到了。

又拿美国医学牌照,又参加司法考试,又能写小说、编代码、查资料,你就感觉只要能用文字表达的事儿它全都能干...

你说这玩意儿,它怎么就横空出世了?
之前也有聊天机器人,怎么就感觉它就要颠覆世界了?让资本圈,好家伙 那个兴奋。那它又有什么问题呢?巨头们又如何应对?它到底会让谁失业呢?

今天咱们就一起把这些碎片的信息都给串起来,一起来聊一聊,关于ChatGPT你需要知道的那些事儿!!


这个聊天机器人儿,咱们得追溯到1950年,那时候号称计算机科学之父人工智能之父的艾伦-图灵,他发表了一篇具有划时代意义的论文,他提出了一个很有哲理的叫做模仿游戏,也就是说咱们大名鼎鼎的那个图灵测试,就是说当你在不面对面的时候跟人家文字聊天,你能不能准确的判断出来对方是一个人,还是一个机器人。如果你要是很难分辨出来,那就一定程度上可以说这个机器它是智能的。

你看这图灵测试,是不是又简单、又易懂、又具体而且还挺有意思的,所以呢就吸引了很多计算机方面的科学家来向它发起冲击

从Eliza到ChatGPT,对话系统发展简史✨_数据_03

不过最开始的时候,都是一些非常简单的指令,它就是通过一些语言技巧就是小聪明,来尽量让你感觉到你好像是在跟一个人对话。

就比如说1966年的时候,MIT实验室里就发明出来一个聊天机器人叫Eliza,是世界上第一个真正意义上的聊天机器人。

这开发者就很聪明,他给Eliza的设定是个心理治疗师,你看这种治疗师,一般不都是少说话,多倾听,所以呢它就可以主动的问人家说 “你有没有什么想法”之类的,人家就哗啦哗啦说一大堆,然后它又问说 “你昨天休息的怎么样”,人家又说一大堆,它少说就少错,所以就真的让人误以为它在倾听,然后跟你沟通,而其实它背后就是一些非常简单的​​if...else...​​的代码。

在自然语言理解技术尚未真正取得突破性进展时,Eliza的出现确实是个神奇的事情。

我们甚至还能找到github:​​codeanticode/eliza: The classic Eliza psychologist program, implemented as a Processing library. (github.com)​

然后到了30年之后的1995年Eliza又出来一个后辈叫ALICE,它就进化的已经很强大了,虽然跟ChatGPT还没法比,但就很日常的一些对话,它已经都可以应付了。不过本质上,不管是这个Eliza还是ALICE,它的原理都是基于一个叫做​​Pattern Matching​​,就是模式匹配。

用非技术人员的话解释模式匹配

谈话人的语序,它会在对方的言语中进行关键词扫描,为其中的某些“关键词” 匹配上合适的“对应词” , 然后再返回给谈话人。

在数据库中,关键词被按照日常使用中的频率划分为不同的等级。基于模式匹配的机器人会逐一在自己的脚本库里检索,找到对应的回答。如果遇到完全陌生的话,它就做出通用的回答。

听到一个关键词,它就会调取一个已经预设好的预案,就比如说它听见你好,你就问人家说 吃了吗;它听见妈妈,它就说跟我说说你的家庭,类似这种。

其实即使是在现在,一些购物网站,银行什么的机器人儿,它基于的还是这种​​Pattern Matching​​的模式。就比如说你跟它聊天,你一说到退货,它就给你发个退货流程,或者你说个地址,它就给你发一个路线位置,这种匹配模式。它虽然称不上是非常的智能,但确确实实减少了很多那种大量人力机械性的重复回答。但咱就从智能的角度讲你说这种限定规则的机器人儿,就算你的规则写的再复杂,预设再,也不可能穷尽所有的答案,它更不可能去创造新的答案。

所以,你要真的想通过那个图灵测试,想要变成真正的智能,单凭这种模式匹配,是不可能实现的,于是就出现了语言学习里面的一个新的流派,这个也是人工智能里边非常重要的一部分,就是机器学习

机器学习

顾名思义,它的基本理念就是让机器去学习,就是说我不给你人为规定,一些规则和回答了,就给你一大堆现成的例子,让你自己去学习,找规律。听着是不是就感觉厉害多了

从Eliza到ChatGPT,对话系统发展简史✨_语言模型_04

基于这个理念就到2001年,就有了一个叫做SmartChild更聪明小孩,这么个机器人就活出圈了。

那为什么火呢?

首先它用了一些机器学习里边当时比较先进的模型来让聊天变得更自然,而且2000年那会儿,不是兴起了一大批聊天软件AOLYahoo...那个SmartChild就把这些平台横扫了一遍,就让全世界好几亿人都可以跟它对话,不管你问它什么,你甭管它答得怎么样,它总能跟你聊上几句,可以算是ChatGPT的大前辈了。你说这么好玩的东西,那立马就风靡全球,吸引了超过3000万的用户跟它对话聊天,它每天光接收的信息就要超过10亿条。直到2007年,它被一家巨头公司收购了,就是微软。但是这个SmartChild离通过图灵测试还很远,你跟它聊两句就知道它是个机器人了。

人工神经网络Artificial Neural Networks

到了2010年的时候,机器学习里面的这个领域就开始闪光了。你看我们人的大脑,其实是靠超过100亿个神经元,通过网状连接来判断和传递信息的。虽然这每个神经元都很简单,但是它们组合起来就可以判断非常复杂的信息。所以这个人工神经网络其实就是想模拟人脑的这种形式。

输入信息之后,就会经过若干个隐藏神经节点的判断,就跟神经元似的,然后给你输出结果。

其实这个神经网的思想早就有了,但是神经网络需要大量的数据和强大的算力,而这些在之前是不具备的。到了2010年代,神经网络才开始应用起来。人脸识别、声音识别、自动驾驶包括前几年下围棋打败柯洁那个AlphaGo,都是用它训练出来的。

这个神经网络在这些领域都可以大展宏图,但是到了文字领域它发展就不太顺。因为这个机器学习它一般都是用一种叫做循环神经网络RNN来处理文字的。它主要的方式就是按顺序一个词一个词的看,那问题就是它没办法同时进行大量的学习,而且这句子也不能太长,要不然学到后面前面的又忘了。后来,谷歌提出来了一个新的学习框架​​Transformer​

Transformer

Transformer 是一种用于自然语言处理和其他序列到序列学习任务的深度学习模型,由 Vaswani 等人在 2017 年提出。它是一种基于自注意力机制的神经网络,相比于传统的递归神经网络(RNN)或卷积神经网络(CNN),Transformer 能够更好地处理长序列数据。

Transformer 主要由编码器和解码器两部分组成,每个部分包含多个相同的层。编码器将输入序列编码成一组隐藏状态,而解码器将这些隐藏状态作为上下文信息,并生成输出序列。Transformer 中的自注意力机制可以使模型更好地理解序列中不同位置的相关性,从而在处理序列数据时表现出色。

从Eliza到ChatGPT,对话系统发展简史✨_机器学习_05

通过训练,它可以让机器同时学习大量的文字。现在很多自然语言处理模型其实都是建立在它的基础架构之上的。

2015年包括马斯克、彼得.蒂尔在内...几个大佬一起注资了十亿美金成立了一家非盈利组织叫​​OpenAI​​,也就是ChatGPT的母公司,来进行AI方面的研究。但是后来因为马斯克因为他自己的特斯拉也需要大量的投入研究,为了避免特斯拉和OpenAI引起利益冲突后来就跑路了,哥不玩了。

那剩下的大牛们也确实很牛,在谷歌推出Transformer后,他们就立马在这个基础上研究学习,2018年发表了一篇论文介绍了一个新的语言学习模型 - ​​Chat Generative Pre-trained Transformer​​,就是​​GPT​​,之前的语言学习模型,它基本都是需要人去监督,或者人为给它设定一些标签,但是GPT就基本不怎么需要了,你就把一堆数据放进去,它就哗哗一顿给你整明白了。

从17年Transformer出来以后,基于 Transformer 的预训练语言模型发展如火如荼。Transformer可以提取上下文化动态embedding,也可以做文本生成。

于是OpenAI那些人成功拿着Transformer做出了语言模型,那个时候GPT刚出来,惊为天人,在GPT之前,虽然图像领域已经有用ImageNet做的预训练模型了,但是NLP领域是没有像ImageNet那样大的有标签数据集的,因此迟迟没有出现预训练模型。但是GPT用无标签数据,做出来了预训练模型+微调的范式,所以说是惊为天人。

然后呢BERT就出来了,用更大的数据集更大的模型训练了语言模型。并且还在论文中写的“我们做了两个模型,一个bert base,一个bert large,做bert base的原因。就是要和GPT比较。

这话一出来你能忍吗。所以GPT啪就做了一个GPT-2。用更更大的数据集做出来一个更更大的语言模型。

BERT(Bidirectional Encoder Representations from Transformers):BERT 是由 Google 提出的预训练语言模型,通过在大规模无标注数据上进行预训练,可以在各种自然语言处理任务上进行微调,如文本分类、命名实体识别、问答系统等。

GPT(Generative Pre-trained Transformer):GPT 是由 OpenAI 提出的预训练语言模型,通过单向 Transformer 编码器来学习文本序列的表示,然后使用该表示来生成各种任务的文本,如对话系统、文本生成等。

从Eliza到ChatGPT,对话系统发展简史✨_机器学习_06

从那之后NLP领域的预训练模型就有着越做越大的趋势。各家都开始搞大的预训练模型。

这种机器学习,它主要就拼两件事,一个是模型另一个是参数量。模型定义了机器怎么学习,参数量就需要大量的计算,说白了就是要砸钱。OpenAI对自己的模型是非常有信心的,那下一步就需要钱!每进步一点都需要上升一个数量级的数据去支撑,这些都是需要真金白银去支持的。迫于资金压力,OpenAI在2019年从非盈利组织转成了一个叫收益封顶的盈利组织。就是说任何投资人的投资回报都不能超过100倍,超过100倍的部分投资人就拿不到回报了,就都归OpenAI自己了。这时候微软出手了,注资10亿美元。

所以微软就得到了OpenAI的技术和团队,当然这样的话那OpenAI的研究成果也就别想再公开了

从Eliza到ChatGPT,对话系统发展简史✨_机器学习_07

技术方面已经有了非常强的突破了,万事俱备,只差人和钱。这时ChatGPT登场

ChatGPT

从Eliza到ChatGPT,对话系统发展简史✨_机器学习_08

这个ChatGPT诞生的整个流程:

  1. 收集数据并进行有监督训练。
    数据标注人员提供有关输入提示分布上所需行为的对话,然后,使用有监督学习在此数据上微调预训练的 GPT-3 模型。我们将其称为GPT3.5。
  2. 收集比较数据并训练奖励建模。
    收集一组模型输出之间的比较数据,其中标注人员指出他们更喜欢给定输入的哪个输出。然后,训练一个奖励模型来预测人类首选输出。

奖励建模(RM)。从删除最后一个 unembedding 层的 SFT 模型开始,我们训练一个模型,输入一个提示和回答,输出一个标量奖励。

  1. 强化学习。
    使用PPO在环境中对有监督的微调之后的模型继续进行微调。环境是一个bandit环境,它会呈现一个随机的用户提示,模型要对提示文本进行响应。给定提示和回答,它生成由奖励模型确定的奖励并结束该episode。此外,在每个token处添加来自有监督的微调模型的KL惩罚,以减轻奖励模型的过度优化。价值函数从奖励初始化。我们将这些模型称为“PPO”。

ChatGPT之所以好用就是因为第三步这个人工反馈的强化学习。因为OpenAI后面在训练ChatGPT的时候遇到一个问题,就是它在训练的时候没有一个非常好的反馈机制,就是没有人告诉它你答成什么样是对的什么样是不对的。为了解决这个问题,OpenAI就在训练的时候加入了一个人工反馈的机制,就是你跟我聊天,我告诉你你聊的好还是不好。

加入了这个人工反馈的强化学习之后,它不管是训练的效率还是效果都得到了大大的提升,2022年3月推出了GPT-3.5,之后呢又对对话进行了优化,在2022年11月就推出了ChatGPT,就是我们文章开头看到的那家伙。

ChatGPT的出现确确实实已经颠覆了大多人对聊天机器人的认知,所以在短短的两个月内,ChatGPT的月活跃就突破了一亿人,扩张速度肯定是史上最快,各种数据怎么吹都不为过。

最后再聊聊大家问的最多的一个问题,这个东西发展的这么快,它会不会导致很多人失业呢?它会导致谁失业呢?

就这种技术革新它永远都是一把双刃剑,它可能会创造出来更多的工作,就失业率也不一定降,但是短期内,它肯定会导致一部分人失业。那如何尽可能的不让自己失业呢?

  1. 尽量避免一些重复性或者套路性的工作
  2. cv工程师
  3. 基本的财务报告、设计、法律建议

从Eliza到ChatGPT,对话系统发展简史✨_数据_09

和ChatGPT对话学习使用时序数据库

前言和小结

从昨天的文章来看【和ChatGPT对话】就让它帮我做完成绩数据分析【和ChatGPT对话】帮助新手批量提取图片文字信息到文件,ChatGPT 完成基本的数据处理业务已经在行了。那么继续往下,是不是能完成数据库的构建,开发持久的数据服务呢?本文选取时序数据库进行尝试。

时序数据库是当前物联网服务的热门数据库,其建表和查询逻辑和传统关系型数据库不同。从实际问答和编码来看,ChatGPT 也可以轻松搞定建表、写数据、查询、编码开发的任务。 其中比较有意思的是,如果是直接建表,那么优先使用中文字段名称;如果是要一次性编程,那么就会使用英文字段名称。翻译结果还是一目了然,虽然没有增加注释,但不影响阅读。

Q1 时序数据库有什么特点,比较流行的是哪些,目前发展势头最好的是哪个

时序数据库(Time Series Database,TSDB)是一种专门用于处理时间序列数据的数据库系统。时序数据是按时间顺序采集或生成的数据集合,例如传感器数据、日志数据、监控数据、金融数据等。

时序数据库的主要特点包括:

  1. 专注于处理时序数据:时序数据库专门针对时间序列数据设计和优化,可以高效地存储、查询和分析大规模的时序数据集。
  2. 支持高并发和大规模数据:时序数据库通常可以支持大规模的数据存储和高并发的数据读写操作,以满足大规模时序数据处理的需求。
  3. 支持复杂的查询和分析:时序数据库通常支持基于时间窗口、聚合函数、时间序列预测、异常检测等复杂的查询和分析操作。
  4. 具备高可用性和容错能力:时序数据库通常具备高可用性和容错能力,可以在节点故障、网络异常等情况下保证系统的正常运行。

目前比较流行的时序数据库包括:

  1. InfluxDB:一个开源的、分布式的时序数据库,采用Go语言编写,支持SQL查询和类似于HTTP的API接口。
  2. Prometheus:一个开源的、针对监控数据的时序数据库,采用Go语言编写,支持高效的指标存储和查询。
  3. OpenTSDB:一个开源的、基于Hadoop和HBase的时序数据库,支持大规模数据存储和高效的数据查询。
  4. TimescaleDB:一个开源的、基于PostgreSQL的时序数据库,支持SQL查询和与PostgreSQL兼容的API接口。

目前发展势头最好的时序数据库可能是InfluxDB。InfluxDB具有出色的性能和扩展性,支持大规模的数据存储和高效的查询操作,还具备强大的数据处理和分析能力。此外,InfluxDB的开发团队积极推进新功能的开发和优化,持续不断地为用户提供更好的体验。

Q2 现在使用 InfluxDB,创建一个交通路口抓拍车辆的数据表,记录城市名称,路口名称,车辆号牌,红绿灯状态,通行速度和时间戳,需要记录100个路口,按照路口比较繁忙考虑,后续需要对路口车辆分组统计,对城市分组统计,每分钟车辆数量统计

可以按照以下步骤在 InfluxDB 中创建交通路口抓拍车辆的数据表:

  1. 创建数据库:首先需要在 InfluxDB 中创建一个新的数据库,可以使用 InfluxDB CLI 或者 Web 界面进行创建。
CREATE DATABASE traffic_data
  1. 创建数据表:然后可以创建一个名为 intersection_traffic 的数据表,用于存储交通路口抓拍车辆的数据。
CREATE RETENTION POLICY "one_month" ON "traffic_data" DURATION 30d REPLICATION 1 DEFAULT
CREATE CONTINUOUS QUERY "cq_1_minute_intersection_traffic" ON "traffic_data" BEGIN SELECT count("车辆号牌") INTO "traffic_data"."one_month"."1_minute_intersection_traffic_count" FROM "intersection_traffic" GROUP BY time(1m), "城市名称", "路口名称" END
CREATE CONTINUOUS QUERY "cq_1_hour_intersection_traffic" ON "traffic_data" BEGIN SELECT count("车辆号牌") INTO "traffic_data"."one_month"."1_hour_intersection_traffic_count" FROM "intersection_traffic" GROUP BY time(1h), "城市名称", "路口名称" END
CREATE CONTINUOUS QUERY "cq_1_day_intersection_traffic" ON "traffic_data" BEGIN SELECT count("车辆号牌") INTO "traffic_data"."one_month"."1_day_intersection_traffic_count" FROM "intersection_traffic" GROUP BY time(1d), "城市名称", "路口名称" END

注意这里使用了 InfluxQL 语句来定义数据表的字段和标签,其中标签 城市名称 和 路口名称 可以用于后续的分组统计。

CREATE TABLE "intersection_traffic" (
    "城市名称"  TAG,
    "路口名称"  TAG,
    "车辆号牌"  STRING,
    "红绿灯状态"  STRING,
    "通行速度"  INTEGER,
    "时间戳"  TIME
) WITH (
    "measurement" = "intersection_traffic",
    "retention_policy" = "one_month"
)
  1. 插入数据:可以使用 InfluxDB CLI 或者 API 接口向数据表中插入数据,例如:
INSERT INTO "intersection_traffic" ("城市名称", "路口名称", "车辆号牌", "红绿灯状态", "通行速度", "时间戳")
VALUES (上海, 人民广场, 沪A00001, 绿灯, 50, 2023-02-16T10:00:00Z)
  1. 分组统计:可以使用 InfluxQL 语句对数据进行分组统计,例如:

对路口进行分组统计:

SELECT count("车辆号牌") FROM "intersection_traffic" GROUP BY "城市名称", "路口名称"

对城市进行分组统计:

SELECT count("车辆号牌") FROM "intersection_traffic" GROUP BY "城市名称"
  1. 每分钟车辆数量统计:为了每分钟车辆数量统计

可以使用 InfluxDB 的 Continuous Queries 功能,将数据按照每分钟进行聚合,并将结果存储在新的数据表中,可以使用以下 InfluxQL 语句创建每分钟车辆数量统计的 Continuous Query:

CREATE CONTINUOUS QUERY "cq_1_minute_intersection_traffic" ON "traffic_data"
BEGIN
  SELECT count("车辆号牌") INTO "traffic_data"."one_month"."1_minute_intersection_traffic_count"
  FROM "intersection_traffic"
  GROUP BY time(1m), "城市名称", "路口名称"
END

该语句将在 traffic_data 数据库中创建一个名为 1_minute_intersection_traffic_count 的数据表,用于存储每分钟车辆数量的统计结果,结果将按照 城市名称 和 路口名称 进行分组,存储时会以 UTC 时间为准。

查询每分钟车辆数量统计结果的示例 InfluxQL 语句:

SELECT COUNT("count") AS "车辆数量"
FROM "traffic_data"."one_month"."1_minute_intersection_traffic_count"
WHERE time >= now() - 30m AND "城市名称" = 上海 AND "路口名称" = 人民广场
GROUP BY time(1m)

该语句将返回最近 30 分钟内,上海人民广场这个路口每分钟的车辆数量统计结果。

Q3 在数据达到1亿条时,统计某个城市每个月的车辆数量,耗时大约秒?统计某一个路口每天的车辆数量,耗时大约多少秒?

InfluxDB 的查询性能受多个因素影响,如数据量、查询条件、索引和硬件配置等。因此,无法给出具体的耗时估计。但是,以下是一些可以提高查询性能的建议:

使用适当的时间范围进行查询:尽可能缩小查询的时间范围,可以减少需要扫描的数据量。

创建索引:在查询字段上创建索引,可以加快查询速度。可以在 InfluxDB 中创建索引,例如:

CREATE INDEX ON "traffic_data"."one_month"."1_minute_intersection_traffic_count"("城市名称")

该语句将在 1_minute_intersection_traffic_count 数据表中创建一个 城市名称 字段的索引。

使用合适的硬件配置:InfluxDB 的查询性能还受到硬件配置的影响,建议使用具有足够计算能力和存储容量的服务器或云服务。

基于以上建议,在数据量达到1亿条时,如果对某个城市每个月的车辆数量进行统计,可能需要几秒甚至几十秒的时间,而统计某一个路口每天的车辆数量可能需要更长的时间。然而,实际耗时取决于具体的查询条件、硬件配置和数据库性能。

Q4 最快能到多少?你可以租赁云服务器,给一个部署方案

InfluxDB 的查询性能取决于多个因素,如查询条件、数据量、索引和硬件配置等。在实际生产环境中,可以通过以下方式来提高 InfluxDB 的查询性能,从而缩短查询时间:

选择合适的硬件配置:建议选择 CPU、内存和磁盘容量都比较高的云服务器或物理服务器,并将 InfluxDB 部署在 SSD 存储设备上,以确保高速读写。

  1. 创建索引:在查询字段上创建索引,可以显著加速查询。因此,在设计数据表时,应根据实际需要创建索引。
  2. 选择适当的时间范围:尽量缩小查询时间范围,以减少需要扫描的数据量。
  3. 使用查询优化:InfluxDB 提供了一些查询优化功能,如 limit、time() 函数等,可以加速查询。

基于以上建议,如果使用高性能的硬件和合理的索引,可以在数百毫秒的时间内完成查询。但是,实际查询性能仍然受到多个因素的影响,例如查询的数据量和查询条件等。

针对本次问题,如果需要在云服务器上部署 InfluxDB,建议选择在亚洲地区较为靠近自己所在地的云服务商,例如阿里云、腾讯云等。在选择云服务器时,可以选择具有高性能 CPU、大内存和高速 SSD 存储的实例,并根据实际需要选择合适的存储容量。在 InfluxDB 的部署中,还需要考虑到监控、备份、故障恢复等方面的问题,以确保系统的高可用性和可靠性。

Q5 使用 go 实现刚才例子中数据的写入和查询,发布为 api

好的,这里提供一个使用 Golang 语言实现数据写入和查询的示例代码,并使用 Gin 框架将其发布为 API。

  1. 数据库连接 首先,需要引入 github.com/influxdata/influxdb-client-go 包,并创建一个 InfluxDB 客户端。
import (
    "time"
    "github.com/influxdata/influxdb-client-go/v2"
)

// InfluxDB 数据库连接信息
const (
    influxUrl = "http://localhost:8086"
    influxToken = "your_token"
    influxOrg = "your_org"
    influxBucket = "your_bucket"
)

// 创建 InfluxDB 客户端
func createInfluxClient() influxdb2.Client 
    client := influxdb2.NewClient(influxUrl, influxToken)
    return client

  1. 数据写入 在进行数据写入之前,需要先定义数据结构,这里我们定义一个名为 TrafficData 的结构体,包含城市名称、路口名称、车辆号牌、红绿灯状态、通行速度和时间戳。
// TrafficData 交通数据结构体
type TrafficData struct 
    CityName      string  `influx:"city_name"`
    Intersection  string  `influx:"intersection"`
    LicensePlate  string  `influx:"license_plate"`
    LightStatus   string  `influx:"light_status"`
    Speed         float64 `influx:"speed"`
    Timestamp     time.Time `influx:"time"`

然后,我们定义一个数据写入函数 writeData,接收一个 TrafficData 结构体的切片作为参数,将数据写入 InfluxDB 数据库中。

// 写入 TrafficData 数据
func writeData(data []TrafficData) error 
    client := createInfluxClient()
    defer client.Close()

    // 创建写入器
    writeAPI := client.WriteAPIBlocking(influxOrg, influxBucket)

    // 将 TrafficData 数据写入数据库
    for _, item := range data 
        p := influxdb2.NewPointWithMeasurement("traffic_data").
            AddTag("city_name", item.CityName).
            AddTag("intersection", item.Intersection).
            AddTag("license_plate", item.LicensePlate).
            AddTag("light_status", item.LightStatus).
            AddField("speed", item.Speed).
            SetTime(item.Timestamp)
        writeAPI.WritePoint(p)
    

    return nil

  1. 数据查询 接下来,我们定义一个数据查询函数 queryData,用于根据查询条件从 InfluxDB 数据库中查询数据。
// 根据查询条件查询 TrafficData 数据
// 根据查询条件查询 TrafficData 数据
func queryData(cityName string, startTime, endTime time.Time) ([]TrafficData, error) 
    client := createInfluxClient()
    defer client.Close()

    // 创建查询器
    queryAPI := client.QueryAPI(influxOrg)

    // 构建查询语句
    query := fmt.Sprintf(
        `from(bucket:"%s")
          |> range(start: %d, stop: %d)
          |> filter(fn: (r) => r._measurement == "traffic_data"`,
        influxBucket,
        startTime.UnixNano(),
        endTime.UnixNano(),
    )
    if cityName != "" 
        query += fmt.Sprintf(` and r.city_name == "%s"`, cityName)
    
    query += ")"

    // 执行查询
    result, err := queryAPI.Query(context.Background(), query)
    if err != nil 
        return nil, err
    

    // 处理查询结果
    var data []TrafficData
    for result.Next() 
        record := result.Record()
        fields := record.Values()
        timestamp := fields["_time"].(time.Time)
        city := fields["city_name"].(string)
        intersection := fields["intersection"].(string)
        plate := fields["license_plate"].(string)
        light := fields["light_status"].(string)
        speed := fields["speed"].(float64)
        data = append(data, TrafficData
            CityName:     city,
            Intersection: intersection,
            LicensePlate: plate,
            LightStatus:  light,
            Speed:        speed,
            Timestamp:    timestamp,
        )
    

    return data, nil

这个方法接受三个参数:cityName 表示要查询的城市名称,startTime 和 endTime 表示要查询的时间范围。它会根据这些条件构建查询语句,执行查询并将结果转换为 TrafficData 数组。如果查询出错,会返回一个错误。

  1. 主函数
func main() 
    // 创建 InfluxDB 客户端
    client := influxdb2.NewClient(influxUrl, influxToken)
    defer client.Close()

    // 创建 Gin 实例
    r := gin.Default()

    // 定义数据写入接口
    r.POST("/traffic", func(c *gin.Context) 
        // 解析请求参数
        var data []TrafficData
        if err := c.BindJSON(&data); err != nil 
            c.JSON(http.StatusBadRequest, gin.H"error": err.Error())
            return
        

        // 写入 TrafficData 数据
        if err := writeData(data); err != nil 
            c.JSON(http.StatusInternalServerError, gin.H"error": err.Error())
            return
        

        c.Status(http.StatusOK)
    )

    // 定义数据查询接口
    r.GET("/traffic", func(c *gin.Context) 
        // 解析请求参数
        cityName := c.Query("city_name")
        startTime, err := time.Parse(time.RFC3339, c.Query("start_time"))
        if err != nil 
            c.JSON(http.StatusBadRequest, gin.H"error": "invalid start_time")
            return
        
        endTime, err := time.Parse(time.RFC3339, c.Query("end_time"))
        if err != nil 
            c.JSON(http.StatusBadRequest, gin.H"error": "invalid end_time")
            return
        

        // 根据查询条件查询 TrafficData 数据
        result, err := queryData(cityName, startTime, endTime)
        if err != nil 
            c.JSON(http.StatusInternalServerError, gin.H"error": err.Error())
            return
        

        c.JSON(http.StatusOK, result)
    )

    // 启动 HTTP 服务
    if err := r.Run(":8080"); err != nil 
        log.Fatal(err)
    


Q6 你知道 tdengine 吗

是的,我了解 TDengine。TDengine是一个开源的时序数据库,最初由中国公司TD Tech开发并维护。它的特点是高性能、高可用性、高稳定性和高扩展性,适合处理大量的时序数据。TDengine支持SQL查询、数据备份和恢复、集群管理等功能,并提供了C、Java、Python等多种编程语言的API。在工业物联网、金融交易、能源管理等领域得到了广泛应用。

Q7 使用 tdegine 如何刚才例子中的数据表

使用TDengine创建类似的时序数据表,可以按照以下步骤操作:

  1. 安装TDengine:根据官方文档进行安装和配置。

  2. 创建数据库:登录TDengine客户端,并执行以下命令来创建数据库:

create database traffic_data;
  1. 创建数据表:执行以下命令来创建 TrafficData 表:
create table traffic_data (
  ts timestamp,
  city binary(20),
  intersection binary(20),
  license_plate binary(10),
  light_status binary(10),
  speed float
) tags (city, intersection) ;

这个命令创建了一个名为 traffic_data 的数据表,包含六个字段:ts、city、intersection、license_plate、light_status 和 speed。其中,ts 是时间戳字段,city 和 intersection 是标签,用于对数据进行分组。tags(city, intersection) 语句将这两个字段定义为标签。

  1. 插入数据:使用 insert 语句将数据插入 TrafficData 表。假设要插入一条记录,可以执行以下命令:
insert into traffic_data 
  using traffic_data.tags(city, intersection) 
  values(now, 上海, 南京路口, 沪A12345, 红灯, 50);

这个命令将一条数据插入 TrafficData 表中,同时指定了标签值。其中,now 表示当前时间戳,上海 和 南京路口 分别表示城市和路口名称,沪A12345 是车牌号码,红灯 是红绿灯状态,50 是通行速度。

  1. 查询数据:使用 select 语句查询 TrafficData 表中的数据。例如,可以执行以下命令来查询所有车牌号码以 沪A 开头的记录:
select * from traffic_data where license_plate like 沪A%;

这个命令将返回所有车牌号码以 沪A 开头的记录,包括时间戳、城市名称、路口名称、车牌号码、红绿灯状态和通行速度。

Q8 按天、周、日、分钟的路口车辆数量查询

要查询按天、周、月、分钟的路口车辆数量,可以使用TDengine的时间聚合函数,例如按天聚合使用 date_trunc 函数,按周聚合使用 date_trunc(week, ts) 函数,按月聚合使用 date_trunc(month, ts) 函数,按分钟聚合使用 date_trunc(minute, ts) 函数。下面是查询样例:

按天查询某路口的车辆数量:

select date_trunc(day, ts) as day, city, intersection, count(*) as total_count
from traffic_data
where city = 上海 and intersection = 南京路口
group by day, city, intersection
order by day;

按周查询某路口的车辆数量:

select date_trunc(week, ts) as week, city, intersection, count(*) as total_count
from traffic_data
where city = 上海 and intersection = 南京路口
group by week, city, intersection
order by week;

按月查询某路口的车辆数量:

select date_trunc(month, ts) as month, city, intersection, count(*) as total_count
from traffic_data
where city = 上海 and intersection = 南京路口
group by month, city, intersection
order by month;

按分钟查询某路口的车辆数量:

select date_trunc(minute, ts) as minute, city, intersection, count(*) as total_count
from traffic_data
where city = 上海 and intersection = 南京路口
group by minute, city, intersection
order by minute;

以上查询语句中,where 子句用于限定查询条件,group by 子句用于按时间聚合和按城市路口分组,order by 子句用于按时间排序。count(*) 函数用于计算车辆数量。这些语句将返回按天、周、月、分钟聚合的某个路口的车辆数量统计结果。

Q9 统计最近5分钟,最近1小时,最近24小时,最近48小时的车辆数量,统计5分钟内,30分钟内,1小时内最繁忙的20个路口

要查询最近5分钟、最近1小时、最近24小时、最近48小时的车辆数量,可以使用 now() 函数获取当前时间,然后减去对应时间间隔得到起始时间,再按时间进行分组聚合。下面是查询样例:

统计最近5分钟的车辆数量:

SELECT COUNT(*) FROM traffic 
WHERE time > NOW() - 5m
GROUP BY city, intersection

统计最近1小时的车辆数量:

SELECT COUNT(*) FROM traffic 
WHERE time > NOW() - 1h
GROUP BY city, intersection

统计最近24小时的车辆数量:

SELECT COUNT(*) FROM traffic 
WHERE time > NOW() - 24h
GROUP BY city, intersection

统计最近48小时的车辆数量:

SELECT COUNT(*) FROM traffic 
WHERE time > NOW() - 48h
GROUP BY city, intersection

统计5分钟内、30分钟内、1小时内最繁忙的20个路口,可以结合时间窗口函数和TDengine的TOP-K查询来实现。

统计5分钟内最繁忙的20个路口:

SELECT city, intersection, COUNT(*) FROM traffic 
WHERE time > NOW() - 5m
GROUP BY city, intersection
ORDER BY COUNT(*) DESC LIMIT 20

统计30分钟内最繁忙的20个路口:

SELECT city, intersection, COUNT(*) FROM traffic 
WHERE time > NOW() - 30m
GROUP BY city, intersection
ORDER BY COUNT(*) DESC LIMIT 20

统计1小时内最繁忙的20个路口:

SELECT city, intersection, COUNT(*) FROM traffic 
WHERE time > NOW() - 1h
GROUP BY city, intersection
ORDER BY COUNT(*) DESC LIMIT 20

需要注意的是,在进行TOP-K查询时,需要按照COUNT(*)的值进行降序排序,然后取前20个结果。如果在这些时间段内有相同数量的车辆通过某个路口,那么这些路口都会出现在TOP-K结果中。

Q10 优化统计5分钟内,30分钟内,1小时内最繁忙的20个路口

对于统计5分钟内、30分钟内、1小时内最繁忙的20个路口,可以通过TDengine的实时聚合功能来进行优化。TDengine支持在数据写入时实时计算聚合结果,然后将结果存储在聚合表中。这样,在进行聚合查询时,只需要从聚合表中读取结果,而不需要重新计算。

以下是对统计5分钟内、30分钟内、1小时内最繁忙的20个路口的优化:

创建聚合表:

CREATE TABLE traffic_agg
(city NCHAR(64), intersection NCHAR(64), timestamp TIMESTAMP, count INT, interval INT, PRIMARY KEY(city, intersection, timestamp, interval))

在写入数据时实时计算聚合结果,将结果存储到聚合表中。以下是写入数据时计算5分钟聚合结果的示例:

INSERT INTO traffic_agg(city, intersection, timestamp, count, interval)
SELECT city, intersection, FLOOR(time TO 5m), COUNT(*), 5 FROM traffic
GROUP BY city, intersection, FLOOR(time TO 5m)

在进行聚合查询时,只需要从聚合表中读取结果,而不需要重新计算。以下是从聚合表中读取5分钟聚合结果的示例:

SELECT city, intersection, count FROM traffic_agg 
WHERE timestamp > NOW() - 5m 
AND interval = 5
ORDER BY count DESC 
LIMIT 20

以上优化方案可以提高查询的效率,减少查询的响应时间。由于聚合表是在写入时计算聚合结果,因此需要根据实际业务需求,选择合适的聚合粒度和计算频率,以确保聚合表中的数据能够满足查询需求。

Q11 再介绍一下实时聚合计算,要怎么部署

实时聚合计算是指在数据写入时,对数据进行聚合计算,并将聚合结果实时存储到聚合表中,从而可以提高查询效率。TDengine提供了实时聚合计算功能,用户可以在创建表时,通过设置实时计算参数来开启实时聚合计算功能。

以下是在TDengine中如何开启实时聚合计算功能的步骤:

  1. 创建聚合表 首先需要创建聚合表,聚合表的定义和普通表类似,需要指定表名、列名、主键等信息。在创建表时,需要设置实时计算参数,包括计算粒度、计算函数、聚合表名称等。

例如,以下是创建一个traffic实时聚合表的示例:

CREATE TABLE traffic_agg
(city NCHAR(64), intersection NCHAR(64), timestamp TIMESTAMP, count INT, interval INT, 
PRIMARY KEY(city, intersection, timestamp, interval))
WITH 
      realtime=1, 
      source=traffic, 
      granularity=5m, 
      function=count(*), 
      filter=city=Shanghai

在上述代码中,通过设置realtime=1,开启了实时计算功能;通过设置source=traffic,指定了聚合表的数据源为traffic表;通过设置granularity=5m,指定了聚合粒度为5分钟;通过设置function=count(),指定了计算函数为count(),即统计每个5分钟内的记录数;通过设置filter=city=Shanghai,指定了计算的数据过滤条件,即只计算城市为Shanghai的数据。

  1. 写入数据 在写入数据时,TDengine会自动对数据进行实时聚合计算,并将聚合结果存储到聚合表中。以下是向traffic表中写入数据的示例:
INSERT INTO traffic(city, intersection, plate_number, light_status, speed, time) 
VALUES (Shanghai, Intersection1, 沪A88888, 1, 50, NOW())

在上述代码中,当写入一条数据时,TDengine会自动对该数据进行实时聚合计算,并将聚合结果存储到聚合表traffic_agg中。

  1. 查询聚合表 在查询聚合表时,可以直接从聚合表中读取聚合结果,而不需要重新计算。以下是查询聚合表traffic_agg中某个时间范围内的聚合结果的示例:
SELECT city, intersection, count FROM traffic_agg 
WHERE timestamp > 2023-02-16 00:00:00 AND timestamp <= 2023-02-16 00:05:00
AND interval = 300
ORDER BY count DESC 
LIMIT 20

在上述代码中,通过设置timestamp和interval条件,可以查询某个时间范围内的聚合结果;通过设置ORDER BY和LIMIT条件,可以按照聚合结果进行排序和分页。

以上是关于从Eliza到ChatGPT,对话系统发展简史✨的主要内容,如果未能解决你的问题,请参考以下文章

linux 的发展简史

ChatGPT - InstructGPT 论文简读

ChatGPT中文在线官网

RL极简入门:从MDPDP MC TC到Q函数策略学习PPO

数据库技术的发展简史

和ChatGPT对话学习使用时序数据库