整个项目可以在AIStudio一键跑通:用Lisp的方言HY跑飞桨训练和推理 - 飞桨AI Studio星河社区
估计很多人都看过《黑客与画家》这本书,百度百科中的条目:《黑客与画家:硅谷创业之父paul graham文集》是硅谷创业之父paul graham 的文集,主要介绍黑客即优秀程序员的爱好和动机,讨论黑客成长、黑客对世界的贡献以及编程语言和黑客工作方法等所有对计算机时代感兴趣的人的一些话题。
Paul在他的著作中极力推崇Lisp语言,将其誉为最佳的编程语言。据悉很多读者在阅读后都深受其影响。现在,我们有了在星河社区亲自实践Lisp的机会!尽管我们可能还无法与大牛们并肩,但是我们可以阅读他们的书籍,倾听他们的见解,并跟随他们的实践来磨练我们的Lisp技能。Lisp是人工智能的开发语言,上一次人工智能浪潮中它曾风靡一时。虽然它在这次的AI浪潮中无声无息并未占据主导地位,但谁能预测Lisp不会在下一次AI浪潮中大放异彩呢?
Lisp(List Processing,列表处理)是一种具有悠久历史和独特魅力的编程语言,自上世纪50年代诞生以来,一直在计算机科学领域发挥着重要作用。Lisp以其简洁、优雅和强大的表达能力而著称,成为了函数式编程和人工智能领域的先锋。
Lisp的特点之一是其代码和数据结构的统一性,这使得Lisp具有极高的灵活性,可以轻松操作和处理复杂的数据结构。其独特的宏系统也为元编程提供了强大的工具,允许程序员创建自己的语言扩展和DSL(领域特定语言)。
它的优点和缺点都非常鲜明:
优点是使用前置运算符,语法非常简单
缺点是前置运算符是违反人类习惯的
另外因为Lisp语法非常简单,所以很多大牛都对Lisp进行了功能添加,新的语言被成为Lisp方言,这导致Lisp方言不计其数,这极大增加了初学者的学习成本。
比如我们学python、C语言,赋值语句都是x = 1
这样的,而Lisp的方言中,common Lisp是(defvar x 1)
scheme中是(define x 1)
hy中是(setv x 1)
, 这些都完全不挨着,可以说Lisp方言,除了使用前置运算符是一样的,其它完全可以没有任何一点关系!
Hy(或“Hylang”)是Lisp家族中的一种多范式通用编程语言,以膜翅目昆虫目命名,因为Paul Tagliamonte在创建该语言时正在研究群体行为。它被实现为Python的一种替代语法。与Python相比,Hy提供了各种额外的功能、概括和语法简化,正如Lisp所期望的那样。与其他Lisp相比,Hy提供了对Python内置库和第三方Python库的直接访问,同时允许您自由混合命令式、函数式和面向对象的编程风格。
Python程序员会注意到的关于Hy的第一件事是,它使用了Lisp的传统括号前缀语法来代替Python的类C中缀语法。例如,print("The answer is", 2 + object.method(arg))
可以在Hy中书写(print "The answer is" (+ 2 (.method object arg)))
。
pip安装HY库
!pip install hy -q
将x赋值1,然后再加上1,我们会看到输出结果为2
我们已经根据前面知道赋值语句是这样的(setv x 1)
,而对于前缀运算来说,常见的运算x= x+1
是这样的(+ x 1)
,另外需要注意:Lisp里面如果不用赋值语句的话,那么执行完结果是不会修改原变量的。比如下面的语句,运算结束后输出结果为2,但是这个2并不会赋值给x 。如果需要赋值,可以这样写(setv x 1) (setv x (+ x 1))
,因为需要显式赋值,所以Lisp代码相对其它编程语言会较少使用赋值(不需要的就不赋值了)。
import hy
hy.eval(hy.read_many("(setv x 1) (+ x 1)"))
2
将1赋值给x,然后返回x+1的值,果然是2,算对啦!
来个复杂的句子,导入飞桨库,创建一个随机的[2, 3]维度的矩阵,打印x的值,并打印x+1的值进行比较。
这段代码格式化为:
(import paddle)
(setv x (paddle.randn [2,3] ))
(print `x值:)
(print x)
(print `x+1值:)
(print (+ x 1))
hy.eval(hy.read_many("(import paddle) (setv x (paddle.randn [2,3] )) (print `x值:) (print x) (print `x+1值:) (print (+ x 1))"))
x值:
Tensor(shape=[23], dtype=float32, place=Place(cpu), stop_gradient=True,
[ 0.14839390, -0.73140484, 0.93431634, -1.44790399, 0.07094631,
0.39240602, -0.46890527, -0.45317811, 0.07772370, 1.42541373,
0.46317163, 0.77750325, 0.49435890, 0.63576186, 0.56791979,
1.36508441, -0.26096687, 1.33353806, -0.21448247, 1.14773476,
0.44072157, 1.07053936, 1.60521615])
x+1值:
Tensor(shape=[23], dtype=float32, place=Place(cpu), stop_gradient=True,
[1.14839387, 0.26859516, 1.93431640, -0.44790399, 1.07094634, 1.39240599,
0.53109473, 0.54682189, 1.07772374, 2.42541361, 1.46317160, 1.77750325,
1.49435890, 1.63576186, 1.56791973, 2.36508441, 0.73903310, 2.33353806,
0.78551751, 2.14773464, 1.44072151, 2.07053947, 2.60521603])
因为HY中使用了类似hy.eval(hy.read_many(runpaddle))
的语法,HY代码使用pyton字符串导入,因此没法语法高亮,我们先在Markdown里面看下加上语法高亮的HY代码:
(import paddle)
(import paddle.vision.transforms [ToTensor])
;(paddle.set_device "gpu")
(pys "train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())")
(setv test-dataset (paddle.vision.datasets.MNIST :mode 'test :transform (ToTensor)))
(setv lenet (paddle.vision.models.LeNet))
; Mnist inherits from paddle.nn.Layer which is a Net, and model includes training functionality
(setv model (paddle.Model lenet))
; Set the optimizer, loss, and metric needed to train the model
(model.prepare
(paddle.optimizer.Adam :learning_rate 0.001 :parameters (model.parameters))
(paddle.nn.CrossEntropyLoss)
(paddle.metric.Accuracy :topk [1 2])
)
; Start training
(model.fit train-dataset :epochs 2 :batch-size 64 :log-freq 400)
; Start evaluation
(model.evaluate test-dataset :log-freq 100 :batch-size 64)
这部分代码由飞桨python代码转为HY的,可以看到还是比较容易看的懂的,跟python代码可以说是每行都有对应。
导入飞桨直接用(import paddle)
,但是如果想导入子库,则from paddle.vision.transforms import ToTensor
要写成(import paddle.vision.transforms [ToTensor])
函数的传参,使用冒号+参数名+参数的格式,另外在Lisp中,单个引号可以理解为python中的一对引号。
HY(setv test-dataset (paddle.vision.datasets.MNIST :mode 'test :transform (ToTensor)))
对应python test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=ToTensor())
训练集较大,HY报错:struct.error, 因此训练集赋值没有用上面的代码,而是在HY中调用了python代码来实现的:(pys "train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())")
对应python train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())
比如这句话被注释掉了:;(paddle.set_device "gpu")
。归功于飞桨的自动识别cpu、GPU环境,我们HY代码也可以自如的在cpu和GPU环境下训练执行,而不用写set_device参数。
第一次执行的时候需要下载数据集,因此会多花几秒,第二次之后速度就跟python下比较接近了。
import hy
runpaddle = """
(import paddle)
(import paddle.vision.transforms [ToTensor])
;(paddle.set_device "gpu")
(pys "train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())")
(setv test-dataset (paddle.vision.datasets.MNIST :mode 'test :transform (ToTensor)))
(setv lenet (paddle.vision.models.LeNet))
; Mnist inherits from paddle.nn.Layer which is a Net, and model includes training functionality
(setv model (paddle.Model lenet))
; Set the optimizer, loss, and metric needed to train the model
(model.prepare
(paddle.optimizer.Adam :learning_rate 0.001 :parameters (model.parameters))
(paddle.nn.CrossEntropyLoss)
(paddle.metric.Accuracy :topk [1 2])
;(paddle.metric.Accuracy :topk (1 2))
)
; Start training
(model.fit train-dataset :epochs 2 :batch-size 64 :log-freq 400)
; Start evaluation
(model.evaluate test-dataset :log-freq 100 :batch-size 64)
"""
hy.eval(hy.read_many(runpaddle))
item 160/2421 [>.............................] - ETA: 1s - 651us/item
Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-images-idx3-ubyte.gz
Begin to download
item 8/8 [============================>.] - ETA: 0s - 4ms/item
Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/train-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/train-labels-idx1-ubyte.gz
Begin to download
Download finished
item 157/403 [==========>...................] - ETA: 0s - 880us/item
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-images-idx3-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-images-idx3-ubyte.gz
Begin to download
item 2/2 [===========================>..] - ETA: 0s - 2ms/item
Download finished
Cache file /home/aistudio/.cache/paddle/dataset/mnist/t10k-labels-idx1-ubyte.gz not found, downloading https://dataset.bj.bcebos.com/mnist/t10k-labels-idx1-ubyte.gz
Begin to download
Download finished
The loss value printed in the log is the current step, and the metric is the average value of previous steps.
Epoch 1/2
step 400/938 - loss: 0.0354 - acc_top1: 0.9104 - acc_top2: 0.9607 - 28ms/step
step 800/938 - loss: 0.0288 - acc_top1: 0.9402 - acc_top2: 0.9761 - 28ms/step
step 938/938 - loss: 0.0354 - acc_top1: 0.9453 - acc_top2: 0.9786 - 28ms/step
Epoch 2/2
step 400/938 - loss: 0.0481 - acc_top1: 0.9788 - acc_top2: 0.9946 - 28ms/step
step 800/938 - loss: 0.0330 - acc_top1: 0.9798 - acc_top2: 0.9953 - 27ms/step
step 938/938 - loss: 0.0192 - acc_top1: 0.9801 - acc_top2: 0.9954 - 27ms/step
Eval begin...
step 100/157 - loss: 0.0029 - acc_top1: 0.9834 - acc_top2: 0.9972 - 14ms/step
step 157/157 - loss: 3.2777e-04 - acc_top1: 0.9867 - acc_top2: 0.9976 - 14ms/step
Eval samples: 10000
{'loss': [0.00032777030719444156], 'acc_top1': 0.9867, 'acc_top2': 0.9976}
也可以在控制台直接使用命令行来执行HY的代码,到work目录查看runpaddle.hy文件,可以看到它是纯的Lisp代码,运行输出如下:
!hy work/runpaddle.hy
The loss value printed in the log is the current step, and the metric is the average value of previous steps.
Epoch 1/2
step 400/938 - loss: 0.0469 - acc_top1: 0.9047 - acc_top2: 0.9558 - 28ms/step
step 800/938 - loss: 0.0396 - acc_top1: 0.9349 - acc_top2: 0.9727 - 27ms/step
step 938/938 - loss: 0.0681 - acc_top1: 0.9406 - acc_top2: 0.9755 - 27ms/step
Epoch 2/2
step 400/938 - loss: 0.0872 - acc_top1: 0.9743 - acc_top2: 0.9931 - 28ms/step
step 800/938 - loss: 0.0932 - acc_top1: 0.9757 - acc_top2: 0.9938 - 27ms/step
step 938/938 - loss: 0.0316 - acc_top1: 0.9761 - acc_top2: 0.9940 - 27ms/step
Eval begin...
step 100/157 - loss: 0.0091 - acc_top1: 0.9761 - acc_top2: 0.9942 - 12ms/step
step 157/157 - loss: 0.0022 - acc_top1: 0.9812 - acc_top2: 0.9955 - 12ms/step
Eval samples: 10000
使用hy.eval(hy.read_many(runpaddle))
比HY控制台执行多了一行输出:{'loss': [0.00032777030719444156], 'acc_top1': 0.9867, 'acc_top2': 0.9976}
,这行输出就是hy.eval函数的返回值,是可以赋值给python变量的。
本身Lisp的速度是很快的,跟c语言接近,但是HY是编译成Pyhon伪代码进行执行的,所以没法比python快。
下面使用飞桨python代码训练,可以看到训练输出是一样的,训练时间也差不多,大约比Lisp快1秒。
import paddle
from paddle.vision.transforms import ToTensor
# paddle.set_device("gpu")
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=ToTensor())
lenet = paddle.vision.models.LeNet()
# Mnist继承paddle.nn.Layer属于Net,model包含了训练功能
model = paddle.Model(lenet)
# 设置训练模型所需的optimizer, loss, metric
model.prepare(
paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()),
paddle.nn.CrossEntropyLoss(),
paddle.metric.Accuracy(topk=(1, 2))
)
# 启动训练
model.fit(train_dataset, epochs=2, batch_size=64, log_freq=400)
# 启动评估
model.evaluate(test_dataset, log_freq=100, batch_size=64)
The loss value printed in the log is the current step, and the metric is the average value of previous steps.
Epoch 1/2
step 400/938 - loss: 0.0500 - acc_top1: 0.9109 - acc_top2: 0.9607 - 30ms/step
step 800/938 - loss: 0.0430 - acc_top1: 0.9389 - acc_top2: 0.9756 - 29ms/step
step 938/938 - loss: 0.0221 - acc_top1: 0.9442 - acc_top2: 0.9781 - 28ms/step
Epoch 2/2
step 400/938 - loss: 0.0440 - acc_top1: 0.9757 - acc_top2: 0.9936 - 27ms/step
step 800/938 - loss: 0.0332 - acc_top1: 0.9772 - acc_top2: 0.9947 - 28ms/step
step 938/938 - loss: 0.0349 - acc_top1: 0.9777 - acc_top2: 0.9949 - 28ms/step
Eval begin...
step 100/157 - loss: 0.0011 - acc_top1: 0.9812 - acc_top2: 0.9948 - 13ms/step
step 157/157 - loss: 0.0060 - acc_top1: 0.9858 - acc_top2: 0.9960 - 13ms/step
Eval samples: 10000
{'loss': [0.006007179617881775], 'acc_top1': 0.9858, 'acc_top2': 0.996}
通过上面的例子,我们对HY跑飞桨有了初步的了解,下面开始进阶到使用飞桨套件。
首先在PaddleClas repo官网找到python代码的示例:
import paddleclas
model = paddleclas.PaddleClas(model_name="person_exists")
result = model.predict(input_data="pulc_demo_imgs/person_exists/objects365_01780782.jpg")
print(next(result))
我们的任务就是将这段代码转为Lisp语法并使用HY来执行。首先需要:
因为HY底层还是调用了PaddleClas库,所以首先要进行库的安装。
当前版本的PaddleClas(大约截止到2023年的年底)通过PIP安装可能不太顺利,因此要用源码编译安装,同时要把requirements.txt文件中faiss-cpu去掉,否则会导致编译失败。
# 若不成功,这里可以多运行几次,也可以按照下面的提示,点击“快捷下载”来下载该套件。
# !git clone https://www.github.com/PaddlePaddle/PaddleClas.git
!git clone https://www.gitee.com/PaddlePaddle/PaddleClas.git
安装相关的库,faiss-cpu手工安装,并且pip使用work/requirements.txt
文件进行安装。
!pip install pip -U
!pip install faiss-cpu
!pip install -r work/requirements.txt
编译安装,还是使用了work/requirements.txt文件,以便编译通过。
# !tar -xzvf work/paddleclas.tar.gz
!cp work/requirements.txt PaddleClas/
!cd PaddleClas && python setup.py install
测试padddleclas库是否安装好,如果前面已经编译安装好,而这里导入的时候报错No module named 'paddleclas'
, 那么只需要重启内核即可。
import paddleclas
下面开始执行paddleclas模型任务,这是一个识别图片中有没有人的任务,代码量跟python一样多。当然有大神说Lisp代码量可以比Python 和C更精简,但是一味追求低代码量可读性就比较差了。
可以看到即使用cpu也可以在一秒内跑出结果,飞桨真棒!
import hy
runpaddleclas = """
(import paddleclas)
(setv model (paddleclas.PaddleClas :model_name "person_exists"))
;(setv result (model.predict :input_data "work/pulc_demo_imgs/person_exists/objects365_01780782.jpg"))
(setv result (.predict model :input_data "work/pulc_demo_imgs/person_exists/objects365_01780782.jpg"))
(next result)
"""
hy.eval(hy.read_many(runpaddleclas))
[2023/12/14 10:51:57] ppcls INFO: download https://paddleclas.bj.bcebos.com/models/PULC/inference/person_exists_infer.tar to /home/aistudio/.paddleclas/inference_model/PULC/person_exists/person_exists_infer.tar
100%|██████████| 7.29M/7.29M [00:00<00:00, 22.9MiB/s]
[2023/12/14 10:51:57] ppcls WARNING: The current running environment does not support the use of GPU. CPU has been used instead.
[{'class_ids': [0],
'scores': [0.9955421285703778],
'label_names': ['nobody'],
'filename': 'work/pulc_demo_imgs/person_exists/objects365_01780782.jpg'}]
‘label_names’: [‘nobody’],看来没有人。再来看个有人的:
import hy
runpaddleclas = """
(import paddleclas)
(setv model (paddleclas.PaddleClas :model_name "person_exists"))
(setv result (model.predict :input_data "work/pulc_demo_imgs/person_exists/objects365_02035329.jpg"))
;(setv result (.predict model :input_data "work/pulc_demo_imgs/person_exists/objects365_01780782.jpg"))
(next result)
"""
result = hy.eval(hy.read_many(runpaddleclas))
print(result)
[2023/12/14 11:32:28] ppcls WARNING: The current running environment does not support the use of GPU. CPU has been used instead.
[{'class_ids': [1], 'scores': [0.9999976], 'label_names': ['someone'], 'filename': 'work/pulc_demo_imgs/person_exists/objects365_02035329.jpg'}]
‘label_names’: [‘someone’],可见判断出里面有人了。
在python中使用hy.eval
执行的代码,输出可以赋值到一个python变量中,方便以后在python里面继续处理。比如上面就将输出赋值给变量result。可以看到result是一个list表。
print(type(result), result)
[{'class_ids': [1], 'scores': [0.9999976], 'label_names': ['someone'], 'filename': 'work/pulc_demo_imgs/person_exists/objects365_02035329.jpg'}]
‘filename’: ‘work/pulc_demo_imgs/person_exists/objects365_02035329.jpg’}]
Lisp确实非常有趣,强烈推荐大家尝试一下!
本项目仅仅完成了将飞桨Python代码转换为HY Lisp代码的工作,尚未涉及Lisp的一些强大功能,例如宏和编写“能够自我编写代码”的代码。如果有对此感兴趣的朋友,非常欢迎大家一起深入挖掘Lisp的潜力,我们可以共同交流学习,并不断提升自己的技能水平!
是train数据集解包报错,后来用调用python代码的方法解决,也就是不用上面那句,而是用下面那句:
;(setv train-dataset (paddle.vision.datasets.MNIST :mode 'test :transform (ToTensor)))
(pys "train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())")
不管是pip安装还是编译安装,都会失败。
解决方案是去掉requirements.txt文件中faiss-cpu,这样就能成功编译了。最后再手工pip install faiss-cpu即可。
让我们荡起双桨,在AI的海洋乘风破浪!
飞桨官网:https://www.paddlepaddle.org.cn
因为水平有限,难免有不足之处,还请大家多多帮助。
作者: 网名skywalk 或 天马行空,济宁市极快软件科技有限公司的AI架构师,百度飞桨PPDE。