函数计算部署机器学习遇到的问题和解法

本文涉及的产品
简介: 随着 Serverless 的流行,将应用迁移到云上已经成了一种必然的趋势。我们今天来看一下如何将机器学习应用迁移到函数计算上。 1. 本地开发 首先我们看一下本地开发机器学习应用的步骤。我们大概可以将本地开发概括为三个步骤,分别是代码编写,安装依赖,运行调试。

随着 Serverless 的流行,将应用迁移到云上已经成了一种必然的趋势。
我们今天来看一下如何将机器学习应用迁移到函数计算上。

1. 本地开发

首先我们看一下本地开发机器学习应用的步骤。我们大概可以将本地开发概括为三个步骤,分别是代码编写,安装依赖,运行调试。我们分别来看一下。

1.1 代码编写

假定我们的项目结构为:

project root
├── index.py
├── model_data
│   ├── checkpoint
│   ├── model.data-00000-of-00001
│   ├── model.index
│   └── model.met
└── pic
    └── e2.jpg

其中 index.py 存放了机器学习相关的代码,model_data 存放了数据模型,pic 中存放了要进行测试的图片。

index.py 的内容为(代码参考了这篇文章):

# -*- coding:utf-8 -*-   
import os
import sys
import cv2  
import numpy as np
import tensorflow as tf  

saver = None

def reversePic(src):
    for i in range(src.shape[0]):
        for j in range(src.shape[1]):
            src[i, j] = 255 - src[i, j]
    return src 

def main():
    sess = tf.Session()  

    saver = tf.train.import_meta_graph('model_data/model.meta')
 
    saver.restore(sess, 'model_data/model')
    graph = tf.get_default_graph()
    
    input_x = sess.graph.get_tensor_by_name("Mul:0")
    y_conv2 = sess.graph.get_tensor_by_name("final_result:0")
    
    path="pic/e2.jpg"  
    im = cv2.imread(path, cv2.IMREAD_GRAYSCALE)

    im = reversePic(im)

    im = cv2.resize(im, (28, 28), interpolation=cv2.INTER_CUBIC)  

    x_img = np.reshape(im , [-1 , 784])  
    output = sess.run(y_conv2 , feed_dict={input_x:x_img})  
    print 'the predict is %d' % (np.argmax(output)) 

    sess.close()

if __name__ == '__main__':  
    main()  

1.2 安装依赖

在运行应用前,需要先安装应用依赖的模块,这里主要依赖了 opencv 以及 tensorflow,安装方法很简单:

pip install opencv-python
pip install tensorflow

执行完这两条命令后,opencv-python 以及 tensorflow 就被安装到系统目录里。Linux 下默认为 /usr/local/lib/pythonX.Y/site-packages。

1.3 运行

运行时,python 会自动在配置的路径下查找相关模块并进行加载。

$ python index.py
the predict is 8

经过这三个步骤,我们就完成了本地机器学习应用的开发,我们接下来看下如何迁移应用到函数计算。

2. 迁移函数计算

2.1 本地开发与函数计算开发对比

首先,我们需要做一些准备工作。让我们来思考下函数计算应用开发方式与本地应用应用的开发方式有什么不同呢?

  1. 代码入口。本地开发时,代码可以省略 main 函数,也可以提供 main 函数作为程序入口,但在函数计算中,函数入口是固定的,非 Http 触发器的函数入口必须是一个包含了两个参数的函数,比如:def handler(event, context)。
  2. 模块依赖。本地开发时,项目依赖的模块通常会被安装到系统的某个目录。比如我们上面执行的 pip install tensorflow。而对于函数计算,由于为了能够最大限度的对应用进行优化,开放给用户的操作空间通常是比较小的。因此,对于函数计算,目前还无法做到安装项目依赖到运行环境。我们只能通过将自定义模块一同打包的方式。参考
  3. 运行。本地开发时,需要使用 python 命令或者 IDE 来运行代码。而在函数计算,我们需要首先部署应用到函数计算,再通过触发器或者控制台手动触发执行。

接下来我们针对这三点开发方式的不同对代码进行改造。

2.2 改造代码

2.2.1 代码入口改造

这个比较简单,只需要将

def main():

修改为

def handler(event, context):

并删除下面代码:

if __name__ == '__main__':  
    main()  

2.2.2. 模块依赖

这一块稍微复杂些。不同的语言因为模块加载机制的不同,这里的处理逻辑也会有差异。比如对于 java,无论是使用 maven,还是 gradle,都可以很容易的一键将代码以及依赖打包成 jar。但遗憾的是 python 目前没有这种机制。

我们先根据场景对 python 依赖的模块做个简单的分类。

应用依赖: 对于本例中使用 pip 安装的模块,比如 pip install tensorflow,我们暂且将其称为应用依赖。
系统依赖: 在某些场景下,python 安装的库仅仅是对底层 c、c++ 库调用的封装,例如使用 zbar 时,除了使用 pip install zbar,还要在系统中安装相应的库:apt-get install -y libzbar-dev。我们暂且把像 libzbar-dev 一样需要使用系统软件包管理器安装的库称为系统依赖。
资源依赖: 对于一些应用,比如机器学习,启动后还需要加载数据模型,数据模型需要在程序启动时准备好,我们暂且将这种依赖称为资源依赖。资源依赖比较特殊,它是我们的应用逻辑所需要的,通常体积比较大。

对于应用依赖,我们可以通过 pip 的 -t 参数改变其安装位置,比如 pip install -t $(pwd) tensorflow。并且可以通过 sys.path 改变加载行为,使得可以从指定目录加载模块。

对于系统依赖,我们可以通过 apt-get 下载 deb 包,再利用 deb 包安装到指定目录。

apt-get install -y -d -o=dir::cache=$(pwd) libzbar-dev
for f in $(ls $(pwd)/archives/*deb); do dpkg -x $f $(pwd); done
rm -r archives

对于系统依赖包含的链接库,可以通过 LD_LIBRARY_PATH 变量改变其加载行为。

对于资源依赖,因为控制权在我们的代码里,因此只需要改变代码的处理逻辑就可以了。

根据上面的描述,我们可以整理成下面的表格:

类别 定义 安装方法举例 指定位置安装方法举例 影响加载的因素
应用依赖 pip 安装的模块 pip install tensorflow pip install -t $(pwd) tensorflow sys.path
系统依赖 系统软件包管理器安装的依赖 apt-get install -y libzbar-dev apt-get install -y -d -o=dir::cache=$(pwd) libzbar-dev

for f in $(ls $(pwd)/archives/*deb); do dpkg -x $f $(pwd); done
rm -r archives
LD_LIBRARY_PATH
资源依赖 代码依赖的资源,比如数据模型 \ \ 由应用代码控制

2.2.3 下载依赖的逻辑

对于我们的 demo 应用,存在两种依赖,一种是应用依赖,另一种是资源依赖。而需要我们特别处理的只有应用依赖。我们需要在项目目录下创建一个名为 applib 的目录,并下载应用依赖到该目录。这里需要注意的是如果引用的模块使用 C / C++ / go 编译出来的可执行文件或者库文件,那么推荐使用 fcli 的 sbox 进行下载,使用方法为:

mkdir applib 
fcli shell
sbox -d applib -t python2.7
pip install -t $(pwd) tensorflow
pip install -t $(pwd) opencv-python

执行完毕后,就会发现 applib 中就包含了项目所需要的应用依赖。

2.2.4 打包依赖上传到 OSS

机器学习的应用依赖、资源依赖通常比较大,会很容易超过函数计算对代码包的限制(50M)。为了避开这个问题,我们需要将这些依赖上传到 OSS:

cd applib && zip -r applib.zip * && mv applib.zip ../ ; cd ..

执行完毕后,项目会多出一个名为 applib.zip 的压缩包,上传到 oss 即可。

同样的,对资源依赖进行相同的操作:

cd model_data && zip -r model_data.zip * && mv model_data.zip ../ ; cd ..

2.2.5 初始化依赖

这里我们提供一个模板代码,负责在函数第一次启动时,从 OSS 下载资源到本地、解压,并配置好相应的环境变量。我们可以在项目中创建一个名为 loader.py 文件,内容为:

# -*- coding:utf-8 -*-   
import sys
import zipfile
import os
import oss2
import imp
import time

app_lib_object = os.environ['AppLibObject']
app_lib_dir = os.environ['AppLibDir']

model_object = os.environ['ModelObject']
model_dir = os.environ['ModelDir']

local = bool(os.getenv('local', ""))

print 'local running: ' + str(local)

initialized = False

def download_and_unzip_if_not_exist(objectKey, path, context):
    
    creds = context.credentials

    if (local):
        print 'thank you for running function in local!!!!!!'
        auth = oss2.Auth(creds.access_key_id,
                         creds.access_key_secret)
    else:
        auth = oss2.StsAuth(creds.access_key_id,
                            creds.access_key_secret,
                            creds.security_token)

    endpoint = os.environ['Endpoint']
    bucket = os.environ['Bucket']

    print 'objectKey: ' + objectKey
    print 'path: ' + path
    print 'endpoint: ' + endpoint
    print 'bucket: ' + bucket

    bucket = oss2.Bucket(auth, endpoint, bucket) 
    
    zipName = '/tmp/tmp.zip'

    print 'before downloading ' + objectKey + ' ...'
    start_download_time = time.time()
    bucket.get_object_to_file(objectKey, zipName)
    print 'after downloading, used %s seconds...' % (time.time() - start_download_time)

    if not os.path.exists(path):
        os.mkdir(path)

    print 'before unzipping ' + objectKey + ' ...'
    start_unzip_time = time.time()
    with zipfile.ZipFile(zipName, "r") as z:
        z.extractall(path)
    print 'unzipping done, used %s seconds...' % (time.time() - start_unzip_time)

def handler(event, context):
    global initialized
    if not initialized:
        if ( not local ):
            download_and_unzip_if_not_exist(app_lib_object, app_lib_dir, context) 
            download_and_unzip_if_not_exist(model_object, model_dir, context)
        sys.path.insert(1, app_lib_dir)
        print sys.path
        initialized = True

    file_handle, desc = None, None
    
    fn, modulePath, desc = imp.find_module('index')
    mod = imp.load_module('index', fn, modulePath, desc)

    request_handler = getattr(mod, 'handler')
    return request_handler(event, context)          

这段代码会首先读取 AppLibObject 环境变量,用于从 OSS 下载应用依赖,并解压到 AppLibDir 这个环境变量所代表的目录。

其次会读取 ModelObject 环境变量,用于从 OSS 下载资源依赖,并解压到 ModelDir 这个环境变量所代表的目录。

最后,当依赖准备妥当后,会调用 index.py 中的 handler 函数。

理论上,这个代码可以用于其它任何需要下载应用依赖、资源依赖的场景。而我们的 index.py 需要修改的,只有将原先的获取模型依赖的固定的路径,修改为利用 ModelDir 获取路径即可。

2.3 本地运行调试

代码编写完成后,我们的目录结构调整为:

project root
├── code
│   ├── index.py
│   ├── loader.py
│   └── pic
│       └── e2.jpg
├── applib
│.  └── *
└── model_data
    ├── checkpoint
    ├── model.data-00000-of-00001
    ├── model.index
    └── model.met

我们本地运行看下效果,这里我们借助于函数计算推出的 fc-dcoker 工具。

为了避免本地每次调试做无谓的下载,我们取下巧,将应用依赖、资源依赖挂载到 fc-docker 中,并开启 local 的标识:

docker run --rm \
    -e local=true \
    -e AppLibObject=applib.zip \
    -e AppLibDir=/tmp/applib \
    -e ModelObject=model_data.zip \
    -e ModelDir=/tmp/model \
    -v $(pwd)/code:/code \
    -v $(pwd)/applib:/tmp/applib \
    -v $(pwd)/model_data:/tmp/model \
    aliyunfc/runtime-python2.7 \
   --handler loader.handler

得到结果:

2018-08-28 17:44:16.043564: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supportsinstructions that this TensorFlow binary was not compiled to use: AVX2 FMA
FunctionCompute python runtime inited.
FC Invoke Start RequestId: f3ea930e-d7e2-4173-9726-453cdd89f18c
local running: True
['/code', '/tmp/applib', '/var/fc/runtime/python2.7/src', '/usr/local/lib/python27.zip', '/usr/local/lib/python2.7', '/usr/local/lib/python2.7/plat-linux2', '/usr/local/lib/python2.7/lib-tk', '/usr/local/lib/python2.7/lib-old', '/usr/local/lib/python2.7/lib-dynload', '/usr/local/lib/python2.7/site-packages']
2018-08-28T17:44:16.154Z f3ea930e-d7e2-4173-9726-453cdd89f18c [INFO] Restoring parameters from /tmp/model/model
the predict is 8

RequestId: f3ea930e-d7e2-4173-9726-453cdd89f18c          Billed Duration: 9954 ms        Memory Size: 1998 MB       Max Memory Used: 239 MB

2.4 部署

本地开发完成,接下来,我们就需要部署应用到线上了。这里我们借助函数计算推出的 Fun 工具。

Fun 工具使用步骤如下:

  1. release 页面对应平台的 binary 版本,解压就可以使用。或者使用 npm install @alicloud/fun -g 也可以直接使用。
  2. 使用 fun config 配置 ak、region 等信息。
  3. 编写 template.yml
  4. fun deploy 部署

是的,不需要登录控制台进行繁琐的配置,仅仅在项目下提供一个 template.yml 即可:

ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  tensorflow: # 服务名
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: 'tensorflow demo'
      Policies: 
        - AliyunOSSReadOnlyAccess
    test: # 函数名
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: utils.handler
        CodeUri: ./code/
        Description: 'tensorflow application!'
        Runtime: python2.7
        MemorySize: 1024
        Timeout: 300
        EnvironmentVariables:
          Bucket: just-fc-test # 替换为自己的 oss bucket
          Endpoint: 'https://oss-cn-shanghai-internal.aliyuncs.com' # 替换掉 OSS Endpoint
          AppLibObject: applib.zip
          AppLibDir: /tmp/applib
          ModelObject: model_data.zip
          ModelDir: /tmp/model

至此,我们的项目中又多了一个 template.yml,结构为

project root
├── code
│   ├── index.py
│   ├── loader.py
│   └── pic
│       └── e2.jpg
├── applib
│   └── *
├── template.yml
└── model_data
    ├── checkpoint
    ├── model.data-00000-of-00001
    ├── model.index
    └── model.met

通过这一个 template.yml,执行 fun deploy 后即可创建好相应的服务、函数,并配置好函数的环境变量。

$ fun deploy
Waiting for service tensorflow to be deployed...
        Waiting for function test to be deployed...
                Waiting for packaging function test code...
                package function test code done
        function test deploy success
service tensorflow deploy success

即使修改了代码,只要重复执行 fun deploy 即可。

接下来,打开 https://fc.console.aliyun.com/ 控制台,依次找到创建好的服务、函数,点击执行,即可得到与本地一致的输出:

image

2.5 补充

在上面的例子中,我们只列出了应用依赖、资源依赖的情况。对于系统依赖的处理逻辑是比较简单的,比如我们拿 zbar 举例。除了需要在 applib 中通过 pip 安装 zbar,还要在 code 目录下新建一个 lib 目录,并通过 sandbox 在这个目录中执行:

apt-get install -y -d -o=dir::cache=$(pwd) libzbar-dev
for f in $(ls $(pwd)/archives/*deb); do dpkg -x $f $(pwd); done
rm -r archives

执行完成后,目录结构变化为:

project root
├── code
│   ├── lib
│   │   └── usr/lib
│   │       └── *

就像上面提到的,我们需要修改 LD_LIBRARY_PATH 来改变系统依赖的加载行为,因此我们需要在 template.yaml 中的 EnvironmentVariables 下添加一行:

LD_LIBRARY_PATH: /code/lib/usr/lib:/code:/code/lib:/usr/local/lib

至此,就可以直接在 index.py 等文件中直接使用 zbar 了。

3. 总结

结果一番努力,我们终于将机器学习应用上线到函数计算了。回顾上面的所有操作可以发现,其实大部分的改造工作都是可以通过工具解决的。无论是 template.yml,还是 loader.py 都是直接拿来就能用的。而真正需要开发者操作的也就只有下载依赖、修改对资源依赖的引用路径了。

如果您在迁移过程中遇到了困难,请加入我们的钉钉群,同时,我们还会为您提供针对平台优化的 tensorflow 给您:

image

4. 代码链接

https://github.com/awesome-fc/tensorflow-fc-demo

5. 参考

函数计算使用自定义的模块
函数计算安装依赖库方法小结
Tensorflow MINIST数据模型的训练,保存,恢复和手写字体识别
fc-docker
Fun

相关实践学习
基于函数计算一键部署掌上游戏机
本场景介绍如何使用阿里云计算服务命令快速搭建一个掌上游戏机。
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
11天前
|
网络协议 Serverless 应用服务中间件
Serverless 应用引擎操作报错合集之阿里函数计算中在函数计算部署的stable diffusion应用无法访问如何解决
Serverless 应用引擎(SAE)是阿里云提供的Serverless PaaS平台,支持Spring Cloud、Dubbo、HSF等主流微服务框架,简化应用的部署、运维和弹性伸缩。在使用SAE过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
23 1
|
10天前
|
运维 Serverless 应用服务中间件
Serverless 应用引擎产品使用之在阿里云Serverless中函数计算FC nginx 部署上去之后放置静态页面如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
214 0
|
10天前
|
运维 监控 Serverless
Serverless 应用引擎产品使用之在使用阿里云函数计算部署网站时,网站打开不稳定如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
166 0
|
10天前
|
缓存 运维 Serverless
Serverless 应用引擎产品使用之阿里函数计算中。将本地电脑上的项目文件部署到阿里云函数计算(FC)上并实现对外提供API和WebUI如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
30 1
|
10天前
|
运维 NoSQL Java
Serverless 应用引擎产品使用之在函数计算上部署Java服务并访问阿里云MongoDB如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
12 0
|
10天前
|
缓存 运维 Serverless
Serverless 应用引擎产品使用之在阿里云函数计算中使用Docker进行部署函数如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
23 0
|
10天前
|
运维 IDE Serverless
Serverless 应用引擎产品使用之阿里函数计算中,阿里云容器镜像服务(Container Registry)中创建自定义镜像,然后将其部署到FC上如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
27 0
|
10天前
|
人工智能 运维 Serverless
Serverless 应用引擎产品使用之阿里函数计算中部署Stable Diffusion如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
22 0
|
10天前
|
机器学习/深度学习 存储 人工智能
人工智能平台PAI产品使用合集之是否可以在模型部署发布后以http接口形式提供给业务开发人员使用
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。
|
10天前
|
机器学习/深度学习 人工智能 运维
人工智能平台PAI产品使用合集之机器学习PAI可以通过再建一个done分区或者使用instance.status来进行部署吗
阿里云人工智能平台PAI是一个功能强大、易于使用的AI开发平台,旨在降低AI开发门槛,加速创新,助力企业和开发者高效构建、部署和管理人工智能应用。其中包含了一系列相互协同的产品与服务,共同构成一个完整的人工智能开发与应用生态系统。以下是对PAI产品使用合集的概述,涵盖数据处理、模型开发、训练加速、模型部署及管理等多个环节。

热门文章

最新文章

相关产品

  • 函数计算