2024年1月

前言

各位同行有没有想过一件事,一个程序文件,比如
hello.go
是如何被编译器理解的,平常在编写程序时,IDE 又是如何提供代码提示的。在这奥妙无穷的背后,
AST(Abstract Syntax Tree)
抽象语法树功不可没,他站在每一行程序的身后,默默无闻的工作,为繁荣的互联网世界立下了汗马功劳。

AST 抽象语法树

AST 使用树状结构来表达编程语言的结构,树中的每一个节点都表示源码中的一个结构。听到这或许你的心里会咯噔一下,其实说通俗一点,在源代码解析后会得到一串数据,这个数据自然的呈现树状结构,它被称之为
CST(Concrete Syntax Tree)
具体语法树,在 CST 的基础上保留核心结构。忽略一些不重要的结构,比如标点符号,空白符,括号等,就得到了 AST。

如何生成 AST

生成 AST 大概需要两个步骤,词法分析
lexical analysis
和语法分析
syntactic analysis

词法分析 lexical analysis

lexical analysis 简称 lexer ,它表示字符串序列,也就是我们的源代码转化为 token 的过程,进行词法分析的工具叫做词法分析器(lexical analyzer,简称lexer),也叫扫描器(scanner)。Go 语言的
go/scanner
包提供词法分析。

func ScannerDemo() {
	// 源代码
	src := []byte(`
func demo() {
	fmt.Println("When you are old and gray and full of sleep")
}
`)

	// 初始化标记
	var s scanner.Scanner
	fset := token.NewFileSet()
	file := fset.AddFile("", fset.Base(), len(src))
	s.Init(file, src, nil, scanner.ScanComments)

	// Scan 进行扫码并打印出结果
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
	}
}

打印的结果我们接着往下看。

标记 token

标记(token)  是词法分析后留下的产物,是构成源代码的最小单位,但是这些 token 之间没有任何逻辑关系。以上述代码为例:

func demo() {
	fmt.Println("When you are old and gray and full of sleep")
}

经过词法分析后,会得到:

token literal(字面量,以string表示)
func "func"
IDENT "demo"
( ""
) ""
{ ""
IDENT "fmt"
. ""
IDENT "Println"
( ""
STRING "\"When you are old and gray and full of sleep\""
) ""
; "\n"
} ""
; "\n"

在 Go 语言中,如果 token 类型就是一个字面量,例如整型,字符串类型等,那么它的值就是相对应的值,比如上表的
STRING
;如果 token 是 Go 的关键词,那么它的值就是关键词,比如上表的
fun
;对于分号,它的值则是换行符;其他 token 类要么是不合法的,如果是合法的,则值为空字符串,比如上表的
{

语法分析 syntactic analysis

不具备逻辑关系的 token 经过语法分析(syntactic analysis,也叫 parsing)就可以得到具有逻辑关系的 CST 具体语法树,然后对 CST 进行分析提炼即可得到 AST 抽象语法树。完成语法分析的工具叫做语法分析器(parser)。Go 语言的
go/parser
提供语法分析。

func ParserDemo() {
	src := `
package main
`
	fset := token.NewFileSet()
	// 如果 src 为 nil,则使用第二个参数,它可以是一个 .go 文件地址
	f, err := parser.ParseFile(fset, "", src, 0)
	if err != nil {
		panic(err)
	}

	ast.Print(fset, f)
}

打印出来的 AST:

 0  *ast.File {
 1  .  Package: 2:1
 2  .  Name: *ast.Ident {
 3  .  .  NamePos: 2:9
 4  .  .  Name: "main"
 5  .  }
 6  .  FileStart: 1:1
 7  .  FileEnd: 2:14
 8  .  Scope: *ast.Scope {
 9  .  .  Objects: map[string]*ast.Object (len = 0) {}
10  .  }
11  }

它包含了源代码的结构信息,看起来像一个 JSON。

总结

源代码经过
词法分析
后得到 token(标记),token 经过
语法分析
得到 CST 具体语法树,在 CST 上创建 AST 抽象语法树。 来个图图或许更直观:

Go 的抽象语法树

这里我们以一个具体的例子来看:从 go 代码中提取所有结构体的名称。

// 源码
type A struct{}
type B struct{}
type C struct{}
func ExampleGetStructName() {
	fileSet := token.NewFileSet()
	node, err := parser.ParseFile(fileSet, "demo.go", nil, parser.ParseComments)
	if err != nil {
		return
	}

	ast.Inspect(node, func(n ast.Node) bool {
		if v, ok := n.(*ast.TypeSpec); ok {
			fmt.Println(v.Name.Name)
		}
		return true
	})
	// Output:
	// A
	// B
	// C
}

摘要:中科院自动化所蒲志强教授团队,提出一种基于关系图的深度强化学习方法,应用于多目标避碰包围问题(MECA),使用NOKOV度量动作捕捉系统获取多机器人位置信息,验证了方法的有效性和适应性。研究成果在2022年ICRA大会发表。

在多机器人系统的研究领域中,包围控制是一个重要的课题。其在民用和军事领域都有广泛的应用场景,包括协同护航、捕获敌方目标、侦察监视、无人水面舰艇巡逻狩猎等。

这些应用的核心问题是如何控制一个多机器人系统,涉及多目标分配,同时解决目标包围和避碰子问题。这是一个巨大的挑战,特别是对于分散的多机器人系统。

中科院自动化所蒲志强教授团队在2022年ICRA大会发表论文,提出了一种基于关系图的深度强化学习方法,对各种条件下的多目标避碰包围(MECA)问题具有良好的适应性。

定义任务

该研究定义了一个MECA任务,即在具有
L
个静态障碍物(黑色圆圈)的环境中,由
N
个机器人(绿色圆圈)组成的多机器人系统,协同包围
K (1 < K < N)
个静止或运动的目标(红色圆圈)。

所有机器人需要自动形成多组,包围所有目标,每组需要形成圆形队形,包围一个独立的目标,同时避免碰撞。这涉及到以下三个子问题:

1) 动态多目标分配与分组

2) 每组分别包围

3) 相互之间避免碰撞

分散式多机器人系统的MECA图解

方法框架

在MECA问题中,存在三种类型的实体,即机器人、目标和障碍物。不同的实体对机器人有不同的影响关系,例如避障、包围目标、与其他机器人合作等。

研究提出了一种基于机器人级和目标级关系图(RGs)的DRL分散方法,命名为MECA-DRL-RG方法。

具体而言:

  1. 利用图注意网络(GATs)对机器人级RGs进行建模和学习,该RGs由每个机器人与其他机器人、目标和障碍物之间的三个异构关系图组成。
  2. 利用GAT构建目标级RG,构建机器人与各目标之间的空间关系。目标的运动由目标级RG建模,并通过监督学习进行学习,以预测目标的轨迹。
  3. 此外,定义了一个知识嵌入式复合奖励函数,解决MECA中的多目标问题。采用基于集中式训练和去中心化执行框架的演员
    -
    评论家训练算法对策略网络进行训练。

MECA-DRL-RG方法的整体结构

实验验证

研究团队分别进行了仿真实验和真实环境实验。在真实实验中,情景设置为:6个机器人在有2个障碍物的环境中包围2个移动的目标。
机器人的位置和速度数据由NOKOV度量动作捕捉系统提供。

6个机器人在有2个障碍物的环境中包围2个移动目标

仿真实验和真实实验都验证了,相比于其他方法,MECA-DRL-RG方法使机器人能够从周围环境中,学习异构空间关系图,并预测目标的轨迹,从而促进每个机器人对其周围环境的理解和预测。证实了MECA-DRL-RG方法的有效性。

并且,无论机器人、障碍物或目标的数量增加,抑或是目标的移动速度加快,MECA-DRL-RG方法都表现出良好的性能,具有广泛的适应性。

MECA-DRL-RG方法训练曲线


参考文献:

T. Zhang, Z. Liu, Z. Pu and J. Yi, "Multi-Target Encirclement with Collision Avoidance via Deep Reinforcement Learning using Relational Graphs," 2022 International Conference on Robotics and Automation (ICRA), Philadelphia, PA, USA, 2022, pp. 8794-8800, doi: 10.1109/ICRA46639.2022.9812151.

近期,我一直在业余时间研究纯血鸿蒙(HarmonyOS)App的开发,所使用的IDE是华为官方的DevEco Studio 3.1.1。

随着使用时间的增长,我发现了几个特别容易被忽略,但又特别实用的功能,今天列举三个分享给大家。这三个实用功能在日常开发中的使用频率非常频繁,如果善加利用,将会极大地加速开发进程。

所以,本文的主题就是——IDE的使用技巧。好了,废话不多说,我们直奔主题吧!

预览视图可交互

预览(Previewer)视图不仅仅是给开发者“看”的,它其实是允许互动的!

比如下面这个动图,它是一个类似ChatGPT的简易Demo,当用户在底部输入框输入文字,并点击提交后,所输入的内容将显示到聊天内容区。具体实现代码可参考:
半小时实现GPT纯血鸿蒙版

仔细看!它是IDE里面的预览窗口,不是模拟器,更不是真机。

除了这种简单的交互,甚至它还可以实现页面跳转,感兴趣的朋友可以自行尝试。

“Quick Start”

如果我在此做一个调查:IDE的帮助(Help)菜单有多少人点过,其中的菜单项又有多少人真的点进去看过……将会有多少人能给出“是”的回答呢?

看到搜索栏下面的“Quick Start”菜单项了吗?不妨抱着好奇之心点选试试,你将会看到下图中的内容:

正如你所看的那样,如果你是一个新手,不用上网找各种资料,网络上有很多过期或是二手资料,很容易受其误导。直接把这里的“快速入门”文档阅读一遍,就能上手进行开发了。

它甚至还提供了示例代码、Codelabs实验环境。真是为“小白”或者“转行”开发者提供了足够的参考资料。

“API Reference”

和“Quick Start”类似,虽然掌握了一些开发技能,但在实际开发中,我们都必不可少地要去查API文档。而这个功能,则会帮上我们很大的忙。它提供了官方最完整也是最新的API用法,覆盖了ArkTS、ArkUI和JSUI。

用不到这些参考的时候,我们可以将其最小化。它们将被最小化到IDE右侧的边栏中,随时“听候调遣”。

除了上面提到的“Quick Start”和“API Reference”之外,菜单中还有FAQ项。打开之后,则是关于开发环境、IDE使用等等方面的常见问题解答,这里就不再贴图了。

总结

发现了吗?DevEco Studio实际上不只是一个传统意义上的IDE,它是一个融合了最新文档的集合。这些文档内容来自官方,是第一手的资料。它既可以帮助新手入门,也可以成为日常开发时的“僚机”,帮助开发者快速翻阅API文档,找到答案。

实际上,一个最为理想的情况是:开发者只需要打开UI设计稿和DevEco Studio,即可完成App的开发工作。而在这之前,至少还需要打开一个浏览器用来搜索相关的技术资料。

希望上述技巧能够帮到每一位开发者。

本文分享自华为云社区《
K8s 核心资源指标HPA性能优化之路
》,作者:可以交个朋友。

一 背景

以弹性指标为cpu、memory为例。在Kubernetes 1.7版本中引入了聚合层,允许第三方应用程序注册相关API接口到kube-apiserver上。其中
/apis/metrics.k8s.io/v1beta1
一般由metrics-server程序提供,以插件的形式安装在K8s集群中。相关流程如下:
image.png
纵观整个链路如何优化HPA的弹性速率呢?

二 关键时间点分析

首先对于HPA controller

Kubernetes 将HPA pod自动扩缩实现为一个间歇运行的控制回路,间隔由kube-controller-manager的 --horizontal-pod-autoscaler-sync-period参数设置,默认间隔为15s。

然后kubelet指标汇总。kubelet提供指标端点:10250/metrics/resource 15刷新一次指标数据。容器指标由cadvisor负责采集,cAdvisor已经集成到kubelet程序当中。

metrics-server默认抓取时间为60s一次,60s的时间间隔内,kubelet的指标已经刷新4轮了,HPAcontroller也运行4轮了。

所以我们优化的思路可以从metrics-server程序入手,可以将HPA弹性响应速率提高45s左右。kube-controller-manager的参数不宜修改,设置过短会对集群控制面造成压力,产生过载风险。kubelet刷新指标间隔15s 同样也是一个合理且推荐的数值。

三 优化方案

metrics-server启动参数
--metric-resolution
可用于设置指标保留的时间。默认是60s,我们可以将该值设置为15s 加快pod资源指标的获取速率。

未修改指标保存时间前,查看metrics-server日志可以发现平均60s抓取一次指标

image.png

修改metrics-server负载配置,添加启动参数

containers:-command:- /metrics-server- --metric-resolution=15s # 添加该参数,加快指标更新速率- --v=6# 可设置日志级别,可以通过日志查看进程工作信息- xxx 

查看metrics-server日志发现抓取指标间隔为15s

image.png

四 验证过程

主要是对比优化该参数后,HPA弹性的速率是否提高。

基于负载app01创建伸缩策略,以cpu指标为例,HPA配置清单如下:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app01
namespace: hu
spec:
scaleTargetRef:
apiVersion: apps
/v1
kind: Deployment
name: app01
minReplicas:
1maxReplicas:10metrics:-type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization:
50

4.1 优化前

对工作负载进行压测,探知HPAcontroller感知metrics的变化并观察HPA弹性的灵敏度

image.png

通过上图我们可以发现,HPA对指标变化感知滞后:

其中60s-90s区间内,指标数值一直为250%,未发生变化。实际情况是HPAcontroller已经运转三次,但是每次获取的指标均为250%。
4m-7m15s区间内,每隔60s HPA状态刷新一次,也就是说60s后 HPAcontroller才能感知下游服务的状态变化。

可能大家也会有疑问,在执行 kubectl get xxx -w 的时候,每隔15s也刷新了一次数据,那是因为有其它value值发生了变化,例如REPLICAS值发生了变化,所以被记录了下来,并不是因为指标刷新触发的。

4.2 优化后

对工作负载进行压测,探知HPAcontroller感知metrics的变化并观察HPA弹性的灵敏度

image.png

通过上图我们可以发现,HPA对指标变化的获取提升明显,几乎每隔15s指标都会变化一次,如果获取的指标满足扩容条件则会立马扩容。

其中 60s-90s 区间内,其它选项保持不变(MINPODS、MAXPODS、REPLICAS),指标每隔15s就会刷新一次。HPA会根据当前获取的指标结合特定算法进行扩缩的实例的判断。
其中2m15s-4m 区间内,其它选项保持不变,指标固定每隔15s会刷新一次。

可能大家会有疑问,明明指标已经达到扩容条件了,例如60s那个时间点,这就和HPA算法有关系了,如果存在未就绪状态或者不健康的pod,这些pod会被搁置掉不参与计算,所以75s那个时间,并未看到扩容行为。但是这些动作不与metrics指标层面冲突,metrics-server每隔15s上报一次指标,剩下具体的扩缩就是HPA controller该做的了。

点击关注,第一时间了解华为云新鲜技术~

本系列文章介绍基于OpenAI GPT API开发大模型应用的方法,适合从零开始,也适合查缺补漏。

本文首先介绍基于聊天API编程的方法。

环境搭建

很多机器学习框架和类库都是使用Python编写的,OpenAI提供的很多例子也是Python编写的,所以为了方便学习,我们这个教程也使用Python。

Python环境搭建

Python环境搭建有很多种方法,我们这里需要使用 Python 3.10 的环境,如果你已经具备或者自己很清楚怎么搭建,请跳过这个小节。

Anaconda安装

Anaconda 可以简单理解为一个软件包管理器,通过它我们可以方便的管理Python运行环境。

Anaconda 的官方下载地址是:https://www.anaconda.com/download,页面如下图所示:

请注意选择你使用的操作系统,整个安装包有800多M,下载时间取决于你的网路。

Anaconda 的安装比较简单,但是安装中间还需要下载很多程序,所以需要的时间可能会久一点;另外全部安装完毕后,可能会占用5G多的硬盘空间,需要提前预留好。

Anaconda 集成了一些方便的工具,安装完成后,我们可以使用 Anaconda Navigator 来启动它们。在Windows系统下,我们可以在开始菜单中找到这个 Anaconda Navigator,就是下面图片中的这个。

JupyterLab

启动 Anaconda Navigator 后,在右边的应用列表中找到 JupyterLab。

JupyterLab 是一个Web的交互式计算窗口,能在网页中运行Python程序,可以省掉很多麻烦。

启动后,窗口界面如下所示。左边是当前用户的根目录,右边有一些功能入口,我们先不管。

我们在左侧根目录下点击右键创建一个文件夹:gptdemo,名字可以随便起,后边我们的程序都放到这里边。

然后我们双击进入 gptdemo,再点击右键创建一个 Notebook,Notebook 可以记录文字、编写代码并执行。

在左侧文件夹中双击新创建的Notebook,它会在右侧编辑区打开,在工具栏的“Cell Type”中选择“Code”,然后点击工具栏前边的加号(+),Notebook中就会自动创建一个代码Cell,这种Cell既可以运行Python代码,也可以执行各种Shell指令。

安装OpenAI包

执行下边的命令,安装openai的python sdk。

pip install --upgrade openai httpx[socks]

这个只需要安装成功一次就行了。

下图是JupyterLab中的命令执行效果演示:

代码演练

下边进入本文的重点,运行一个GPT程序。

在这个程序中,我们还是让 GPT 扮演一个善于出题的小学数学老师。

可以先把下边的代码粘贴到你的开发环境中运行一下,后面我会解释各个参数。

注意替换 api_key,没有的可以去注册一个或者找人购入一个。

from openai import OpenAI

client = OpenAI(api_key='sk-xxx')
stream = client.chat.completions.create(
    messages=[{
        "role": "system", "content": "你是一名数学老师,从事小学数学教育30年,精通设计各种数学考试题"
    },{
        "role": "user", "content": "你是谁?请以json返回"
    }],
    model='gpt-3.5-turbo-1106',
    max_tokens:1024,
    #temperature=0.1,
    top_p=0.3,
    #frequency_penalty=0.5,
    presence_penalty=0.2,
    seed=12345,
    #stop='30年',
    response_format={ "type": "json_object" },
    n=1,
    stream=True
)

for chunk in stream:
    msg = chunk.choices[0].delta.content
    if msg is not None:
        print(msg, end='')

我们需要先创建一个客户端:client = OpenAI(api_key='sk-xxx'),注意替换其中的 api-key。

然后我们使用 client.chat.completions.create 来创建一个聊天 Completion。Completion这个单词怎么理解呢?这有点类似搜索框中的那种联想输入,我们输入部分字符,它就会生成一组补全的查询词语列表,这个技术叫:Auto Complete。

理解Token

因为很多参数都涉及到Token的概念,所以在开始介绍参数之前,我们先来理解下 Token 这个概念。

在大模型中,模型的输入和输出实际都是Token。Token不是完全对照到单词或者字符的,大模型处理数据时,文本会被切分成单个元素或标记,也就是Token,这些Token可能是单词、字符或单词的一部分。

大模型使用Token而不是直接使用单词或字符的原因主要是效率、灵活性和性能的问题。例如大词汇表会导致模型参数数量剧增,增加内存需求和计算成本,而使用子词可以降低词汇表的大小,特别是含有大量专有名词的语料库,同时子词还可以避免单个字符携带信息可能过少,导致需要处理更长序列才能理解文本的问题。另外子词对于一些语言还具备跨语言表示的能力,子词还可以让模型更好地学习和理解单词的形态变化和复杂的词形构造规则。

比如对于这个句子:I don't like cats.

其拆分后的Token序列可能是:["I", "do", "n't", "like", "cats", "."]

注意,不同的模型可能会采用不同的切分方法。

completion参数

然后我们看下这几个参数:

  • messages:聊天的上下文,里边可以包含多条消息。GPT会针对最后一条消息,结合上下文,生成文本内容。每条消息可以设定role、name、content。
    • role:就是会话中的角色,可以选择:system(系统)、assistant(GPT)、user(用户)
    • name:用来区分同一个角色中的不同人物。
    • content:具体角色发出的消息内容。
  • model:本次会话使用的GPT模型,最新的3.5模型是 gpt-3.5-turbo-1106,训练数据截止2021年9月份,上下文窗口的最大token数为16K;最新的4模型是gpt-4-1106-preview,训练数据截止2023年4月份,上下文窗口的最大token数为128K。
  • max_tokens:本次Completion允许生成的最大token数量,token数量和字符数量实际上不是对等的,不过也可以简单的认为就是字符数量。messages中输入的token数量和生成的token数量不能超过模型上下文窗口的最大token数量。
  • temperature:生成时对token进行采样的温度,取值范围为 0-2 的float,默认值为1。值越小输出越确定,值越大输出越随机,可能会跳出上下文约束,甚至输出不可读的乱七八糟字符。
  • top_p:temperature 的替代方法,称为核采样。取值范围为 0-1 的float,默认值为1。模型考虑具有top_p概率质量的标记的结果,比如 0.1 表示仅考虑概率最大的前10%的token。注意不要同时更改 temperature 和 top_p。
  • frequency_penalty:频率惩罚,用于降低生成重复token的可能性,它基于相关token出现的频率产生影响。取值范围 -2.0 到 2.0 ,默认值为0。一般限制重复时建议设置为0.1-1,强烈限制重复可设置为2,但是生成的质量可能会比较低,负值可用于增加重复。
  • presence_penalty:存在惩罚,也是用于降低生成重复token的可能性,和频率惩罚相比,它跟踪的是相关token有没有出现过至少一次。取值范围 -2.0 到 2.0,默认值为0。一般限制重复时建议设置为0.1-1,强烈限制重复可设置为2,但是生成的质量可能会比较低,负值可用于增加重复。
  • seed:这个参数是为了尽可能的提高输出的确定性。使用相同的种子和相同的其它参数,会尽可能的输出相同的结果。
  • stop:GPT生成文字时,遇到这些字符会停止继续生成。最多4个字符。
  • response_format:生成文本的格式。虽然我们也可以在聊天内容中直接要求以某种格式返回,但是这是没有保障的,也可能返回别的格式,但是如果再加上这个参数,就可以确保生成内容的格式了。
  • n:一次返回几条结果,默认为1。使用时建议设置 stream=false,可以从 choices 中获取多条结果。
  • stream:流式输出与否,一般都采用流式输出,看着比较像真人说话。

看下这个运行效果吧:


以上就是本文的主要内容,GPT编程是不是挺简单的?!

后续我还会继续分享图片、插件、语音等API的使用方法。

如需GPT API Key、全套学习资料、加入陪伴学习群,推荐加入小册:
大模型应用开发 | API 实操

关注萤火架构,加速技术提升!