新疆之行

本计划去年去新疆,不想因为疫情的关系错过了,今年算是把去年的计划做了个补完。由于错误的低估了新疆入冬的速度,未能走成独库公路。这是一次不完美,甚至是失败的旅行,不过依旧留下了一些美好。

赛里木湖

9月底的新疆早已入秋,草早已黄了,湖边的景致早已不如盛夏。湖很完美,比青海湖要漂亮。风很大,湖很蓝。浪花拍打着湖岸,然人觉赛里木湖更像是大海。

如果有机会再来赛湖,会选择在湖边住一晚。在湖边发发呆还是很惬意的。

夏塔

草地以及不远处的雪山,是我喜欢的画面。只是现在已经过了夏塔最漂亮的季节。出发前就一直很纠结要不要去夏塔。

由于规划不周,第一天车开到晚上11点才找到住处。第二天选择了去距离比较近的那拉提草原。

那拉提

也许是季节的原由,那拉提并没有给我很惊艳的感觉。独库路上风景要比那拉提漂亮。

巴音布鲁克

一望无际的大草原。如果夏季过来,应当会是遍地的野花吧。

独库公路

新疆之行最大的期待就是独库公路,不想独库公路瞬间入冬。独库封路,在山下等了一天,第二天依旧没有任何解封的迹象,只能绕道赛湖回乌鲁木齐。

第一天从上午11点一直等到下午5点,不慎将汽车电瓶耗尽,无法启动。非常感谢检查站的小哥帮忙找了个车接电,不然荒郊野外的还不知道修车厂来不来。

虽然只走了那拉提到巴音布鲁克这一小段独库公路,不过已经可以一窥独库公路的美了。

家里的小可爱

或许这次旅行并不能留下太多的记忆,但希望能成为他成长过程中美好经历的一部分。

2021年Python工具链

1. Python虚拟环境:Poetry

一个类似Pipenv的Python虚拟环境和依赖管理的工具,据称改善了一些Pipenv的问题。对我而言,主要看重了Poetry可以对Python库打包的功能。毕竟对我而言书写 setup.py 并不是一件很让人愉快的事情。

2. 代码静态扫描:Flake8

Flake8使用起来非常简单,不用这么配置就可以直接使用,之后检查过程中遇到自己不需要的规则,加个例外就好。

Flake8支持插件,通过添加插件还可以让Flake8功能变的更为强大。

3. 代码自动格式化:Black

写代码时,我个人会尽量遵守 PEP8 ,但难保团队中有些人代码写的有些随意。为保证编码风格的统一,在代码提交前统一由Black对代码镜像格式化。自动格式化之后的代码可能会少了那么一点个性,但为了统一还是值得的。

4. Import规则检查&格式化工具:isort

Black不会对Python 的 import 语句进行排序和分段,这个工作就交给isort来做了。

5. 类型检查:Mypy

长久以来Python作为脚本语言,程序里没有类型信息,很多本可在编译阶段发现的问题被保留到运行时。Python在3.5之后开始支持 Type Hint 了。利用Mypy可以利用这些类型信息对程序进行校验。

6. 单元测试:pytest

相比 unittest ,pytest使用上更为方便。更为重要的是pytest兼容 unittest,似乎没有什么理由来拒绝pytest。 

7. 测试覆盖率:Coverage.py

代码覆盖率测试工具好像也没有第二个选择。

8. pre-commit

git commit 时调用flake8进行代码检查,调用black对代码进行格式化等操作。利用pre-commit从源头上杜绝有人把不合格的代码提交到代码库。

9. Docker、Gitlab-CI、GitHub Action、Travis CI

CI服务可根据自己的实际情况进行选择

将OpenVINO预训练模型转为为ONNX,并使用TVM进行优化

OpenVINO是Intel推出的一款深度学习工具套件。OpenVINO带来大量的预训练模型,使用这些预训练模型可以快速的开发出自己的AI应用。

不过既然是Intel出的东西,自然少不了和Intel平台深度绑定。OpenVINO主要针对Intel的CPU进行优化。虽然也可以支持GPU,但支持的是Intel家的GPU。Intel家的GPU,应当不用报太多期待了。

为了支持更丰富的硬件类型,可以将OpenVINO自带的预训练模型 转为ONNX格式,然后在做其他处理。

OpenVINO模型导出为ONNX

OpenVINO优化后的预训练模型无法直接转换为ONNX。不过好在Intel有提供模型的训练和导出工具,利用OpenVINO的训练工具导出ONNX

OpenVINO用于训练和导出的库为: https://github.com/openvinotoolkit/training_extensions

具体的操作方式参见项目的具体说明文档。

对照人脸检测的文档,导出人脸检测对应ONNX模型: https://github.com/openvinotoolkit/training_extensions/tree/develop/models/object_detection/model_templates/face-detection

注:导出目录里有 export/export/alt_ssd_export/ 两种模型。其中 export/alt_ssd_export/ 包含了OpenVINO特有的实现,在转换为其他推理引擎模型时会失败,因此后续工作使用 export/ 中的模型。

使用TVM对ONNX模型进行优化

针对TVM的VM进行优化

对于存在动态shape的模型,TVM无法进行编译。很不幸的是OpenVINO中物体检测相关的模型都存在动态shape。在TVM无法编译的情况下,可使用TVM的VM进行执行。

  • 注:
    • 关于VM的相关内容请阅读: https://tvm.apache.org/docs/dev/virtual_machine.html
    • TVM的文档比较欠缺(特别是VM相关的内容)。不过好在项目还在快速迭代过程中,提交的issue很快就可以得到回复。
    • 根据测试,使用VM模式,在CPU上TVM的速度甚至比用 ONNXRuntime 还要慢不少。不知道是否是跑在虚拟机上的关系。
import onnx
import time
import tvm
import numpy as np
import tvm.relay as relay
target = 'llvm -mcpu=skylake'
model_path = 'face-detection-0200.onnx'
onnx_model = onnx.load(model_path)
shape = [1,3,256,256]
input_name = "image"
shape_dict = {
        input_name: shape,
        }
mod, params = relay.frontend.from_onnx(onnx_model, shape_dict)
print(relay.transform.DynamicToStatic()(mod))
with tvm.transform.PassContext(opt_level=3):
    executable = relay.vm.compile(mod, target="llvm", target_host=None, params=params)
code, lib = executable.save()
with open("code.ro", "wb") as fo:
    fo.write(code)
lib.export_library("lib.so")

针对TVM进行编译和优化

如果你的模型可以正常编译,那就没必要采用VM模式了。直接编译理论上优化效果要好很多。这里采用的是TVM范例中给出的图片分类模型。

一个完整的模型优化和执行可以参考官方文档:Compiling and Optimizing a Model with the Python AutoScheduler

import onnx
import time
import tvm
import numpy as np
import tvm.relay as relay
target = 'llvm'
model_name = 'mobilenetv2'
model_path = f'{model_name}.onnx'
onnx_model = onnx.load(model_path)
mod, params = relay.frontend.from_onnx(onnx_model)
with relay.build_config(opt_level=3):
    graph, lib, params = relay.build(mod, target, params=params)
path_lib = f"./{model_name}.so"
lib.export_library(path_lib)
fo=open(f"./{model_name}.json","w")
fo.write(graph)
fo.close()
fo=open("./{model_name}.params","wb")
fo.write(relay.save_param_dict(params))
fo.close()

VM模式下加载和运行优化好的模型

加载前面导出的模型,并执行。


import onnx
import time
import tvm
import numpy as np
import tvm.relay as relay
def vmobj_to_array(o, dtype=np.float32):
    if isinstance(o, tvm.nd.NDArray):
        return [o.asnumpy()]
    elif isinstance(o, tvm.runtime.container.ADT):
        result = []
        for f in o:
            result.extend(vmobj_to_array(f, dtype))
        return result
    else:
        raise RuntimeError("Unknown object type: %s" % type(o))
shape = [1, 3, 224, 224]
model_path = 'face-detection-0200'
loaded_lib = tvm.runtime.load_module(f"{model_path}.tvm.so")
loaded_code = bytearray(open(f"{model_path}.tvm.code", "rb").read())
exe = tvm.runtime.vm.Executable.load_exec(loaded_code, loaded_lib)
ctx = tvm.cpu()
vm = tvm.runtime.vm.VirtualMachine(exe, ctx)
data = np.random.uniform(size=shape).astype("float32")
out = vm.run(data)
out = vmobj_to_array(out)
print(out)

Python多进程环境下日志模块导致死锁

近期公司的一个Python程序在启动新进程的时候总是会失败。在进程里可以看到对应的进程已经创建成功,但对应代码并未执行,且没有输入任何日志。

通过定位,发现问题源自Python的logging模块,在写文件模式下,logging模块是不支持多进程的。

问题产生原因

Python默认采用Fork方式创建新进程,在Fork新进程的时候会连同 也一同复制到新进程。

  1. 当主进程里有两个线程T1/T2,以及一个锁Lock1。
  2. 线程T2获取了锁Lock1,此时线程T1创建了一个新进程P2,此时Lock1被一同frok给了P2。
  3. P2执行时尝试等待Lock1解锁。由于线程T2不会被复制到P2,没有人给P2线程的Lock1解锁,导致P2死锁。

Python的logging模块在写文件时会加锁,由于锁被复制导致进程死锁。

注:由于创建新进程时锁会被复制,混用多进程和多线程时的加锁操作应当格外小心。

解决方案

根据Python的官方文档,logging模块不支持多进程模式下将日志保存到单一日志文件。多进程模式下日志保存方案,建议参考Python官方文档 Logging to a single file from multiple processes

参考资料:

TypeScript + React.FC + Hook

Vue.js的使用更接近传统的Web开发,入门门槛比较低。同时双向数据绑定等特性也让Vue.js更为平易近人。在我看来Vue.js为易用性做的妥协在成就了Vue.js的同时,也制约了Vue.js,让他无法变得“伟大”。

在Node.js、React、Vue.js出现后,整个前端的表现能力越来越强,同时也变的越来越复杂。传统依靠jQuery的开发模式已无法支持现在大型SPA应用的开发。相比Vue.js,React这种高度组件化开发框架才更能代表今后前端的发展方向。

之前也看过一些React的相关教程。我一方面认同React的组件开发理念,另一方面又被React繁琐的开发体验劝退(Ant Design Pro早期版本里的登录实现十分劝退)。

近期有机会实际使用了React一段时间。相比初次接触React,现在的TypeScript + React.FC + Hook似乎才是React的完全形态。

React的高度组件化,让代码结构很自然的变的清晰(当然,过细的拆分也让人头痛)。TypeScript让很多潜在错误可以在编译阶段被发现,而且编辑器也开始变的智能很多。Hook的引入,彻底释放了React.FC的能力。相比Class Components使用Function Components的代码实现要简洁很多。

企业信息化建设随想

信息化在一个企业中的地位,要不就应当很高,要不就可有可无,不存在中间状态。

企业信息化实际上是以信息化为手段,将企业的管理思想进行落地。企业信息化的过程应当伴随这企业管理流程的梳理和优化,企业的信息化部门在一定程度上是一个管理决策部门。如果做不这一点,信息化部门就完全沦为了后台职能部门,做些简单的系统运维工作。

一个优秀的IT经理应当熟悉公司的业务流程、熟悉当前最新的技术动态、还要有能力争取到足够的资源来推动信息化建设。

Carrot Box流程管理平台

django-lb-workflow 我开发的一个Django流程引擎APP。设计之初是以使用便捷性为目标,自带了完整的模板,希望可以方便的集成到已有系统。尽管已经将django-lb-workflow做到尽量的易用,但距离真正的开箱即用还有一段距离。

Carrot Box是一个完整的Django易用,带了权限管理、部门、角色等必要模块,真正的做到开箱即用。通过对Carrot Box的定制可以方便的改造为OA、工单系统、CRM等业务系统。

Carrot Box的主要特点:

  • 是一个完整的应用,可以直接跑起来,开箱即用。
  • 自带HR模块,支持部门、角色的定义。支持按照部门、角色设置权限。
  • 带了几个范例流程,方便熟悉系统。
  • 包含一个代码生成器的使用范例,用于熟悉如果快速的创建一个自定义流程。
  • simplewf模块使用范例,以纯配置的方式添加新流程。

Carrot Box范例站点

之前的django-lb-workflow范例站点已经切换到 Carrot Box

地址: http://wf.haoluobo.com/

管理员账号:admin 密码:password

切换为其他用户: http://wf.haoluobo.com/impersonate/search

退回管理员账号: http://wf.haoluobo.com/impersonate/stop

将Carrot Box跑起来:

make init-pyenv
make init
make run

JetBrains Quest 解谜过程

JetBrains的推广活动,解谜后可以获取三个月的免费订阅。由于是推广活动,所以解密过程不是非常难。真正让人头痛的是那糟糕的网速,不管挂不挂代理页面的打开都非常的慢。

题目一

48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f

解题

很明显字符串的ASCII码,使用python很容易进行解码

>>> s = "48 61 76 65 20 79 6f 75 20 73 65 65 6e 20 74 68 65 20 73 6f 75 72 63 65 20 63 6f 64 65 20 6f 66 20 74 68 65 20 4a 65 74 42 72 61 69 6e 73 20 77 65 62 73 69 74 65 3f"
>>> ''.join(chr(int(e, 16)) for e in s.split(' '))
'Have you seen the source code of the JetBrains website?'

题目二

查看首页源代码找到解谜线索

JetBrains has a lot of products, but there is one that looks like a joke on our Products page, you should start there... (hint: use Chrome Incognito mode)
It’s dangerous to go alone take this key: Good luck! == Jrrg#oxfn$

根据提示,到产品页面。其中名为“JK”的产品介绍是“dare to lean more”,点击该产品继续进行挑战。

注:之前都不知道JetBrains居然已经有这多的产品了。

题目三

补完 https://jb.gg/### 后面确实的三个数字。数字为500到5000的质数个数。

解题

到网上找了个求质数的函数,跑了一下,很快得到结果574

import math
def isprime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True
count = 0
for i in range(500, 5000):
    if isprime(i):
        count += 1
print(count)

题目四

打开上面得到的链接,其中有张图片。图片中的字符为“MPS-31816”

解题

这个题目最坑的在于这个图片实在是太大了,约有15M,死活打不开。

  1. 查看图片属性,用编辑器直接打开图片,都没有获取到有效的信息。
  2. 直接访问 https://www.jetbrains.com/MPS-31816 显示没有这个网页。
  3. 于是直接启用Google搜索 MPS-31816 在JetBrains 网站找到对应页面。注:后续会知道图片上的图标是JebBrains网站的问题区的Logo。

题目五

“The key is to think back to the beginning.” – The JetBrains Quest team
Qlfh$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dqg#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#sul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2

解题

很明显要用到之前的 take this key: Good luck! == Jrrg#oxfn$ 。我一开始将问题想的太复杂了,以为第一题里的内容是密钥,用xor进行解密。在网上找了个xor解密的函数,解出来一塌糊涂。

由回头仔细看了一下这所谓的密码,其实就是一个简单的字映射。

>>> s = "Qlfh$#Li#|rx#duh#uhdglqj#wklv#|rx#pxvw#kdyh#zrunhg#rxw#krz#wr#ghfu|sw#lw1#Wklv#lv#rxu#lvvxh#wudfnhu#ghvljqhg#iru#djloh#whdpv1#Lw#lv#iuhh#iru#xs#wr#6#xvhuv#lq#Forxg#dq
g#iru#43#xvhuv#lq#Vwdqgdorqh/#vr#li#|rx#zdqw#wr#jlyh#lw#d#jr#lq#|rxu#whdp#wkhq#zh#wrwdoo|#uhfrpphqg#lw1#|rx#kdyh#ilqlvkhg#wkh#iluvw#Txhvw/#qrz#lw“v#wlph#wr#uhghhp#|rxu#iluvw#s
ul}h1#Wkh#frgh#iru#wkh#iluvw#txhvw#lv#‟WkhGulyhWrGhyhors†1#Jr#wr#wkh#Txhvw#Sdjh#dqg#xvh#wkh#frgh#wr#fodlp#|rxu#sul}h1#kwwsv=22zzz1mhweudlqv1frp2surpr2txhvw2"
>>>
>>> c = ord('J') - ord('G')
>>> ''.join(chr(ord(e) - c) for e in s)
'Nice! If you are reading this you must have worked out how to decrypt it. This is our issue tracker designed for agile teams. It is free for up to 3 users in Cloud and for 10
 users in Standalone, so if you want to give it a go in your team then we totally recommend it. you have finished the first Quest, now it’s time to redeem your first prize. Th
e code for the first quest is “TheDriveToDevelop”. Go to the Quest Page and use the code to claim your prize. https://www.jetbrains.com/promo/quest/'

Go语言版 [Telegram Shell Bot]及Go的初体验

项目地址(Go): https://github.com/vicalloy/telegram-shell-bot-go

由于对Go不熟悉,因此之前版本的 Telegram Shell Bot 采用Python实现。在Python版发布后就开始尝试使用Go重新实现,毕竟之所以要做这东西也是想借个小程序体验一下Go语言。

我对Go语言的一些看法

随着微服务的兴起,相较以往服务的粒度可以拆分的更细。系统复杂度更多的转变为微服务构架设计的复杂度。服务拆分后每个具体的服务的复杂度将降低,系统对程序语言本身的构架能力的依赖会减少很多。同时在服务拆分后,各个服务组件可以灵活的采用适合的技术来完成工作。

Go语言本身极度精简(简陋),如果要象Java一样搭建一个单体的巨型项目会存在一定困难。Go的高性能,异步,加之足够简单,使Go在一些对性能要求较高,但业务复杂度相对较低的场合会非常适合。

Go可能永远不能变的同Java一样流行,但Go已经足够成功,值得去体验。

Go开发的相关资源

  • Tour of Go 官方的简明教程,可以快速浏览一遍了解Go的主要语言特性。代码编写过程中也可以作为参考手册。
  • Go标准类库 Go的标准类库。
  • Vim Go Vim的Go语言插件,支持语法检查、自动格式化、自动补全等特性。注:挺想试试GoLand,不过GoLand只有30天的试用期。
  • 在国内Go的官网访问不是特别方便,可以使用国内翻译的中文教程。Go标准类库(中文版) Tour of Go(中文版)

开发过程中的实际体验

Telegram Shell Bot Go 是我第一次使用Go写东西,由于只是一个小项目体验还不算特别深。Go语言中最精华的并发相关内容(goroutine / channel / select)并没有用到。

总的来说Go有着还不错的开发体验,但另一方便由于习惯了Python,Go又有不少让我用的不是特别舒服的地方。有人说Go是better C,我个人还是比较认可这个观点,相比其他语言Go很像C语言。

优点

  • Python使用缩进语法保证代码的整洁。Go则直接内置代码格式化工具来强制保证代码格式的一致性。
  • 之前听说Go的包管理很差劲。这次使用已经有go mod的支持,用下来体验还不错。
  • GC、性能好、编译速度快等。

不太习惯的地方(不一定是缺点)

  • 基础类型同Python相比差太多。Slice和Python里的List相对应,但功能方面差非常多。其中一个简单的删除对象的功能都需要自己实现。不清楚这是否是出于性能的考虑,需要让用户明确清楚相关操作的时间复杂度。但使用起来真的非常不方便。用了其他开发语言后,更让人怀念Python的列表推导。
  • Go在语法层面比较简单,相对较新的概念也就异步相关的内容(这部分Go设计的不错,实际上也并不难理解),真正难的还是适应Go的编程风格。Go的Telegram类库API风格同Python版有较大差别。我想API的风格差异更多的还是同语言本身风格差异相关的。要完全适应Go的编程风格可能需要先找几个代码写的比较好的项目先学习一下。