Libtorch系列教程1:一个丝滑的C++ Tensor库
系列教程列表:
1. 概述
Libtorch是Pytorch的C++接口,实现了在C++中进行网络训练、网络推理的功能。
除此之外,由于Libtorch中的大部份接口都是与Pytorch一致的,所以Libtorch还是一个很强大的张量库,有着类似Pytorch的清晰接口,这在C++中很难得的。如果你用过C++ Tensor库,就会发现写法比较复杂,学习成本。因为强类型的限制和通用容器类型的缺失,C++相比Python天然更复杂,库设计者因为语言使用习惯,以及为了性能等因素,设计的接口一般都是高效但难用的。而Libtorch采用了与Pytorch类似的函数接口,如果你使用过Pytorch的话,使用Libtorch学习成本很低,后面会看到具体的例子。
另一个问题是,很多Python库中基础的操作,例如numpy.einsum
函数,在C++中没有合适的替代,看看这些搜索你就知道了。Libtorch解决了这个问题,Pytorch中有的它都有,所以在C++中可以简单地用torch::einsum
来使用einsum函数,简直是C++开发者的福音。
此外Libtorch 是支持GPU的,主要用于模型的推理过程,但我猜测使用GPU的话,Libtorch的Tensor操作在速度上相比别的C++ Tensor 库可能有优势,具体速度需要测试对比。当然使用C++代码的话速度不是瓶颈,本身CPU代码就够快了。
Libtorch另一个优势是编译简单,只要你安装了Pytorch,Libtorch就可以直接使用,省去了复杂的安装和配置,一分钟内就能跑起来一个简单的的示例程序。
总结来说,Libtorch有以下很吸引人的特性:
- 强大如Numpy和Pytorch的C++ Tensor库,写法优雅丝滑,并且是支持GPU的。
- 可以训练神经网络
- 可以推理神经网络模型,用在C++环境的模型部署场景
- 编译简单
由于Pytorch开发团队是以Python优先的思路来进行Pytorch的开发的,因此我感觉Libtorch的重视程度不是很高,文档和教程也比较少,官网的示例也几乎没有,因此写一个比较完善的教程是比较有意义的。
这个系列文章中,我会对Libtorch 的Tensor库和推理神经网络过程进行介绍,因为这些内容在实际对于用Libtorch来进行网络训练的部分进行跳过,因为这部分使用的场景不是很多(用Python训练网络比C++香多了)。
本篇以Mac下的操作为例,对Libtorch的安装和简单使用进行介绍,后续内容近期会更新,敬请关注。
nanoGPT + 鲁迅
1. 起因
今晚看到了Simon Willison 的只使用自己的博客内容来训练nanoGPT的实验,觉得挺有意思,突发奇想,能不能在鲁迅的文集上训练一个nanoGPT,然后生成很具辨识度的鲁迅风格的文字呢?由于nanoGPT结构简单,鲁迅的文集在GitHub上可以下载到,因此通过简单的代码修改加实验,就得到一个在鲁迅作品上训练的GPT2模型(无别的语料库的预训练),简单测试下,以“故乡”开头,让模型生成鲁迅风格的文字:
1 | 故乡,债是佩服的。 |
还算有鲁迅文字的风格,但逻辑一窍不通,整体还是难让人满意,不知道是GPT2能力的问题还是我实验设置的问题。 Anyway,这里共享一下我实验的流程,有兴趣的朋友可以参考,进行改进。本文涉及的代码修改代码已经提交到这个仓库了,可以参考,文末会附上更多例子。
关于 np.float 被删除的问题
1. 概述
在Numpy 1.24版本中,删除了像np.float
、np.int
这样的 Python 内置类型的 alias,因此以后在代码中使用这些类型会报错AttributeError: module 'numpy' has no attribute 'float'
, 涉及的类型包括:
numpy.bool
numpy.int
numpy.float
numpy.complex
numpy.object
numpy.str
numpy.long
numpy.unicode
那该怎么解决这个错误呢?
TL;DR
- 对于在标量上的操作,直接使用Python内置类型替换
1
2
3
4
5foo = np.random.rand(10)
# 原先用法,注意foo[0]是一个标量
bar = np.float(foo[0])
# 新用法
bar = float(foo[0]) - 对于在
np.ndarray
上的操作,使用np.float64
或np.float32
来替代,具体选择哪个需要自己根据情况来确定,不同类型精度会有不同,下面举两个例子:1
2
3
4
5
6
7
8
9# 原先用法
foo = np.random.rand(10, dtype=np.float)
# 新用法
foo = np.random.rand(10, dtype=np.float32)
# 原先用法
foo = np.random.rand(10).astype(np.float)
# 新用法
foo = np.random.rand(10).astype(np.float32)
这里列出来了删除类型在标量和np.ndarray
上的替代,方便查找
原先类型 | 标量替换类型 | np.ndarray 替换类型 |
---|---|---|
np.int | int | np.int32/np.int64 |
np.float | float | np.float32/np.float64 |
np.bool | bool | np.bool_ |
np.complex | complex | np.complex128 |
np.object | object | - |
np.str | str | np.str_ |
np.long | int | np.int32/np.int64 |
np.unicode | str | np.str_ |
详细说明参考NumPy 1.20.0 Release Notes。
下面详细说说事情的来龙去脉。
手机上看arxiv上论文的方法
有时候想要在手机上访问Arxiv上的论文,打开arxiv.com,发现体验比较差,没有响应式设计,需要不断移动页面才能读完一行文字,影响阅读。偶然发现了arxiv-vanity这个网站,发现能很好的满足手机上看arxiv论文的需求,收藏了。
Linux小技巧:使用find命令来删除空文件
python 多个with 语句一起使用
在读《流畅的Python》时,偶然看到下面的语句:
1 | with urlopen(URL) as remote, open(JSON, 'wb') as local: |
突然才发现,原来多个with语句可以写到一起!
ffmpeg抽取高清图像帧
使用ffmpeg可以方便地从视频中抽取图像帧:
1 | ffmpeg -i /path/to/video.mp4 image-folder/%06d.jpg |
但实际测试发现,抽取的图像帧比较模糊,有明显的块效应。
搜索时有人说可以加-q:v 1 -qmin 1 -qmax 1
来提高图像质量
1 | ffmpeg -i /path/to/video.mp4 -q:v 1 -qmin 1 -qmax 1 image-folder/%06d.jpg |
测试发现确实有一些提升,但还是能看到明显的模糊。
最后发现,把抽取的图像格式从.jpg
修改为.png
,结果就是高清且无块效应的了:
1 | ffmpeg -i /path/to/video.mp4 image-folder/%06d.png |
另外PNG格式的图像存储大小要大一些,但不会太大,还是能接受的。
原来 git stash 应该这么用
概述
前段时间突然发现,我之前对git stash
的使用都是错误的。
具体说来,我是这么使用的:在远端有新的提交,需要git pull
来拉取合并时,发现本地有一些未提交的修改,功能也没实现,不适合做一次commit。这时候我执行git stash
隐藏本地的修改,然后执行git pull
来拉取远端的更新,在最新代码基础上重新实现stash的那些代码中的功能。
这里的问题是,重新实现stash代码中的那一步,其实完全可以用git stash pop
来替代,执行这个命令会在最新代码基础上作用stash的代码,不用再重新实现一遍了(不过这时可能会有代码冲突需要解决)。所以我之前是把git stash
当git checkout -- .
来用了,也就是抛弃了本地的代码更新,显然是有问题的。
正确流程基本上是这样:
1 | git stash # 或者 git stash push,效果一样 |
下面记录一下 git stash 提供的功能和一些参数。
2022年终总结
多年以后,想起2022,我会回忆起哪个画面?为了解答这个问题,我回想过去的这些年份,2021,2020,……2005,看能想到什么。除了有些年份里,我结婚,毕业,所以有记忆深刻的事件,大部分年份我甚至想不起任何事情。对于一个模糊的数字,在多年以后,我们确实难以将它和自己一天天度过的日常事情关联起来。虽然2022年发生了很多事情,但大部分还是会被遗忘,但我还是尽量想用详细的文字记录下来,这样当以后想会看那年我身上发生了什么的时候,我知道只要在浏览器里面输入vra.github.io/2022/12/31/summary-2022
,这些时刻都会清晰地浮现在我眼前。