36

还能这样?把 Python 自动翻译成 C++

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MjM5ODYwMjI2MA%3D%3D&%3Bmid=2649744940&%3Bidx=1&%3Bsn=7d4ff9ef5b7957a8ae54ba261f31f635
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

ARFBnqu.gif

作者: byronhe,腾讯 WXG 开发工程师

一、问题背景

随着深度学习的广泛应用,在搜索引擎/推荐系统/机器视觉等业务系统中,越来越多的深度学习模型部署到线上服务。

机器学习模型在离线训练时,一般要将输入的数据做特征工程预处理,再输入模型在 TensorFlow PyTorch 等框架上做训练。

1.常见的特征工程逻辑

常见的特征工程逻辑有:

  1. 分箱/分桶 离散化

  2. log/exp 对数/幂等 math numpy 常见数学运算

  3. 特征缩放/归一化/截断

  4. 交叉特征生成

  5. 分词匹配程度计算

  6. 字符串分隔匹配判断 tong

  7. 缺省值填充等

  8. 数据平滑

  9. onehot 编码,hash 编码等

这些特征工程代码,当然一般使用深度学习最主要的语言 python 实现。

二、业务痛点

离线训练完成,模型上线部署后,同样要 用 C++ 重新实现 这些 python 的特征工程逻辑代码。

我们发现, “用 C++ 重新实现” 这个步骤,给实际业务带来了大量的问题:

  1. 繁琐,费时费力,极容易出现 python 和 C++ 代码 不一致

  2. 不一致会直接影响模型在线上的效果,导致大盘业务指标不如预期,产生各种 bad case

  3. 不一致难以发现,无法测试,无法监控,经常要靠用户投诉反馈,甚至大盘数据异常才能发现

1. 业界方案

针对这些问题,我调研了这些业界方案:

《推荐系统中模型训练及使用流程的标准化》

https://www.infoq.cn/article/2E6LCqb1GeqFRAjkkjX3

《自主研发、不断总结经验,美团搜索推荐机器学习平台》

https://cloud.tencent.com/developer/article/1357309

《京东电商推荐系统实践》

https://www.infoq.cn/article/1OkKmb_gEYNR3YqC9RcW

“模型线上线下一致性问题对于模型效果非常重要,我们使用特征日志来实时记录特征,保证特征的一致性。这样离线处理的时候会把实时的用户反馈,和特征日志做一个结合生成训练样本,然后更新到模型训练平台上,平台更新之后在推送到线上,这样整个排序形成了一个闭环。”

总结起来,有几种思路:

  1. 在线特征存储起来给离线用

  2. 在线 C++ 代码编译成 so 导出给离线用

  3. 根据一份配置生成离线和在线代码

  4. 提取公共代码,加强代码复用,等软件工程手段,减少不一致

2. 自动翻译方案

(1) .已有方案的缺点

但这些思路都有各种缺点:

  1. 所有在线请求的所有特征,这个存储量数据量很大

  2. 算法改代码需要等待后台开发,降低了算法同学的工作效率

  3. 特征处理代码的复杂度转移到配置文件中,不一定能充分表达,而且配置格式增加学习成本

  4. 就这边真实离线特征处理代码来看,大部分代码都无法抽取出公共代码做复用。

(2). 翻译器

回到问题出发点考虑,显而易见,这个问题归根结底就是需要一个 “ python 到 c++ 的翻译器 ” 。

那其实 “翻译器 Transpiler ” ,和编译器解释器类似,也是个古老的热门话题了,比如 WebAssembly , CoffeeScriptBabel ,

Google Closure Compilerf2c

于是一番搜索,发现 python 到 C++ 的翻译器也不少,其中 Pythran 是新兴比较热门的开源项目。

于是一番尝试后,借助 pythran,我们实现了:

  1. 一条命令 全自动把 Python 翻译成等价 C++

  2. 严格等价保证改写,彻底消除不一致

  3. 完全去掉重新实现这块工作量,后台开发成本降到 0 ,彻底解放生产力

  4. 算法同学继续使用纯 python,开发效率无影响,** 无学习成本 **

  5. 并能推广到其他需要 python 改写成后台 C++ 代码 的业务场景,解放生产力

三、pythran 的使用流程

(1). 安装

一条命令安装:

pip3 install pythran

(2). 写 Python 代码

下面这个 python demo,是 pythran 官方 demo

import math
import numpy as np

def zero(n, m):
    return [[0]*n for col in range(m)]

#pythran export matrix_multiply(float list list, float list list)
def matrix_multiply(m0, m1):
    new_matrix = zero(len(m0),len(m1[0]))
    for i in range(len(m0)):
        for j in range(len(m1[0])):
            for k in range(len(m1)):
                new_matrix[i][j] += m0[i][k]*m1[k][j]
    return new_matrix

#pythran export arc_distance(float[], float[], float[], float[])
def arc_distance(theta_1, phi_1, theta_2, phi_2):
    """
    Calculates the pairwise arc distance
    between all points in vector a and b.
    """
    temp = (np.sin((theta_2-theta_1)/2)**2
           + np.cos(theta_1)*np.cos(theta_2) * np.sin((phi_2-phi_1)/2)**2)
    distance_matrix = 2 * np.arctan2(np.sqrt(temp), np.sqrt(1-temp))
    return distance_matrix


#pythran export dprod(int list, int list)
def dprod(l0,l1):
    """WoW, generator expression, zip and sum."""
    return sum(x * y for x, y in zip(l0, l1))


#pythran export get_age(int )
def get_age(age):
    if age <= 20:
        age_x = '0_20'
    elif age <= 25:
        age_x = '21_25'
    elif age <= 30:
        age_x = '26_30'
    elif age <= 35:
        age_x = '31_35'
    elif age <= 40:
        age_x = '36_40'
    elif age <= 45:
        age_x = '41_45'
    elif age <= 50:
        age_x = '46_50'
    else:
        age_x = '50+'
    return age_x

(3). Python 转成 C++

一条命令完成翻译

pythran -e demo.py -o  demo.hpp

(4). 写 C++ 代码调用

pythran/pythonic/ 目录下是 python 标准库的 C++ 等价实现,翻译出来的 C++ 代码需要 include 这些头文件

写个 C++ 代码调用

#include "demo.hpp"
#include "pythonic/numpy/random/rand.hpp"
#include <iostream>

using std::cout;
using std::endl;

int main() {
  pythonic::types::list<pythonic::types::list<double>> m0 = {{2.0, 3.0},
                                                             {4.0, 5.0}},
                                                       m1 = {{1.0, 2.0},
                                                             {3.0, 4.0}};
  cout << m0 << "*" << m1 << "\n=\n"
       << __pythran_demo::matrix_multiply()(m0, m1) << endl
       << endl;

  auto theta_1 = pythonic::numpy::random::rand(3),
       phi_1 = pythonic::numpy::random::rand(3),
       theta_2 = pythonic::numpy::random::rand(3),
       phi_2 = pythonic::numpy::random::rand(3);
  cout << "arc_distance " << theta_1 << "," << phi_1 << "," << theta_2 << ","
       << phi_2 << "\n=\n"
       << __pythran_demo::arc_distance()(theta_1, phi_1, theta_2, phi_2) << endl
       << endl;

  pythonic::types::list<int> l0 = {2, 3}, l1 = {4, 5};
  cout << "dprod " << l0 << "," << l1 << "\n=\n"
       << __pythran_demo::dprod()(l0, l1) << endl
       << endl;

  cout << "get_age 30 = " << __pythran_demo::get_age()(30) << endl << endl;

  return 0;
}

(5). 编译运行

g++ -g -std=c++11 main.cpp -fopenmp -march=native -DUSE_XSIMD -I /usr/local/lib/python3.6/site-packages/pythran/ -o pythran_demo

./pythran_demo

四、pythran 的功能与特性

(1). 介绍

按官方定义,Pythran 是一个 AOT (Ahead-Of-Time - 预先编译) 编译器。给科学计算的 python 加注解后,pythran 可以把 python 代码变成接口相同的原生 python 模块,大幅度提升性能。

并且 pythran 也可以利用 OpenMP 多核和 SIMD 指令集。

支持 python 3 和 Python 2.7 。

pythran 的 manual 挺详细:

https://pythran.readthedocs.io/en/latest/MANUAL.html

(2). 功能

pythran 并不支持完整的 python, 只支持 python 语言特性的一个子集:

  • polymorphic functions 多态函数(翻译成 C++ 的泛型模板函数)

  • lambda

  • list comprehension 列表推导式

  • map, reduce 等函数

  • dictionary, set, list 等数据结构

  • exceptions 异常

  • file handling 文件处理

  • 部分 numpy

不支持的功能:

  • classes 类

  • polymorphic variables 可变类型变量

(3). 支持的数据类型和函数

pythran export 可以导出函数和全局变量。

支持导出的数据类型,BNF 定义是:

    argument_type = basic_type
                  | (argument_type+)    # this is a tuple
                  | argument_type list    # this is a list
                  | argument_type set    # this is a set
                  | argument_type []+    # this is a ndarray, C-style
                  | argument_type [::]+    # this is a strided ndarray
                  | argument_type [:,...,:]+ # this is a ndarray, Cython style
                  | argument_type [:,...,3]+ # this is a ndarray, some dimension fixed
                  | argument_type:argument_type dict    # this is a dictionary

    basic_type = bool | byte | int | float | str | None | slice
               | uint8 | uint16 | uint32 | uint64 | uintp
               | int8 | int16 | int32 | int64 | intp
               | float32 | float64 | float128
               | complex64 | complex128 | complex256

可以看到基础类型相当全面,支持各种 整数,浮点数,字符串,复数

复合类型支持 tuple, list, set, dict, numpy.ndarray 等,

对应 C++ 代码的类型实现在 pythran/pythonic/include/types/ 下面,可以看到比如 dict 实际就是封装了一下 std::unordered_map

https://pythran.readthedocs.io/en/latest/SUPPORT.html

可以看到支持的 python 基础库,其中常用于机器学习的 numpy 支持算比较完善。

五、pythran 的基本原理

和常见的编译器/解释器类似, pythran 的架构是分成 3 层:

  1. python 代码解析成抽象语法树 AST 。用 python 标准库自带的的 ast 模块实现

  2. 代码优化。

    在 AST 上做优化,有多种 transformation pass,比如 deadcode_elimination 死代码消除,loop_full_unrolling 循环展开 等。还有 Function/Module/Node 级别的 Analysis,用来遍历 AST 供 transformation 利用。

  3. 后端,实现代码生成。目前有 2 个后端,Cxx / Python, Cxx 后端可以把 AST 转成 C++ 代码( Python 后端用来调试)。

目前看起来 ,pythran 还欠缺的:

  1. 字符串处理能力欠缺,缺少 str.encode()/str.decode() 对 utf8 的支持

  2. 缺少正则表达式 regex 支持

看文档要自己加也不麻烦,看业务需要可以加。

六、相关文章

《京东电商推荐系统实践》

https://www.infoq.cn/article/1OkKmb_gEYNR3YqC9RcW

《自主研发、不断总结经验,美团搜索推荐机器学习平台》

https://cloud.tencent.com/developer/article/1357309

《推荐系统中模型训练及使用流程的标准化》

https://www.infoq.cn/article/2E6LCqb1GeqFRAjkkjX3

numba

http://numba.pydata.org

rQjQf2Z.gif


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK