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

本文涉及的产品
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
函数计算FC,每月15万CU 3个月
简介: 随着 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

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
1天前
|
弹性计算 自然语言处理 搜索推荐
活动实践 | 基于函数计算部署GPT-Sovits模型实现语音生成
通过阿里云函数计算部署GPT-Sovits模型,可快速实现个性化声音的文本转语音服务。仅需少量声音样本,即可生成高度仿真的语音。用户无需关注服务器维护与环境配置,享受按量付费及弹性伸缩的优势,轻松部署并体验高质量的语音合成服务。
|
24天前
|
弹性计算 人工智能 自然语言处理
魔搭社区与函数计算:高效部署开源大模型的文本生成服务体验
在数字化时代,人工智能技术迅速发展,开源大模型成为重要成果。魔搭社区(ModelScope)作为开源大模型的聚集地,结合阿里云函数计算,提供了一种高效、便捷的部署方式。通过按需付费和弹性伸缩,开发者可以快速部署和使用大模型,享受云计算的便利。本文介绍了魔搭社区与函数计算的结合使用体验,包括环境准备、部署应用、体验使用和资源清理等步骤,并提出了改进建议。
|
23天前
|
机器学习/深度学习 数据采集 Python
从零到一:手把手教你完成机器学习项目,从数据预处理到模型部署全攻略
【10月更文挑战第25天】本文通过一个预测房价的案例,详细介绍了从数据预处理到模型部署的完整机器学习项目流程。涵盖数据清洗、特征选择与工程、模型训练与调优、以及使用Flask进行模型部署的步骤,帮助读者掌握机器学习的最佳实践。
63 1
|
28天前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
29天前
|
Serverless 数据安全/隐私保护 前端开发
大模型代码能力体验报告之贪吃蛇小游戏《一》:Claude.ai篇 - 生成、预览和快速部署的serverless一条龙
本文介绍了通过Claude.ai生成并优化Web版贪吃蛇游戏的过程,展示了其强大的代码生成功能及用户友好的界面设计。从初始版本的快速生成到根据用户反馈调整游戏速度,再到提供多种实用工具如文件管理、版本控制和一键部署,Claude.ai不仅是一个代码助手,更像是一个全面的serverless开发平台。文中还呼吁国内厂商关注此类技术的发展。
|
1月前
|
人工智能 弹性计算 自然语言处理
《触手可及,函数计算玩转 AI 大模型》解决方案体验与部署评测
在AI技术快速发展的背景下,大模型正推动各行业的智能化转型。企业为抓住机遇,纷纷寻求部署AI大模型以满足特定业务需求。阿里云函数计算凭借按量付费、卓越弹性和快速交付等优势,为企业提供了高效、安全的AI大模型部署方案。本文将详细介绍阿里云函数计算的技术解决方案及其在文生文、图像生成和语音生成等领域的应用实例,展示其在降低成本、提高效率和增强灵活性方面的显著优势。
|
1月前
|
弹性计算 Serverless API
海量大模型如何一键部署上云,函数计算 x ModelScope 社区给出答案
得益于阿里云函数计算的产品能力,魔搭 SwingDeploy 后的模型推理 API 服务默认具备极致弹性伸缩(缩零能力)、GPU 虚拟化(最小 1GB 显存粒度)、异步调用能力、按用付费、闲置计费等能力,这些能力帮助算法工程师大大加快了魔搭开源模型投入生产的生命周期。
|
2月前
|
存储 人工智能 弹性计算
函数计算部署 AI 大模型解决方案测评
函数计算部署 AI 大模型解决方案测评
|
3月前
|
消息中间件 JavaScript 中间件
函数计算产品使用问题否会自动进行打包部署
本文解答了五个关于阿里云函数计算的常见问题。包括:WebIDE编写的Node.js代码如何自动打包部署;如何为fc-stable-diffusion-plus开启API功能;如何在代码中主动结束实例并重启新实例处理触发器;如何在Koa中读取invoke事件消息;以及解决异步事件未触发的问题。提供了详细的解决方案和注意事项,帮助用户更好地理解和使用函数计算服务。[查看详情](https://developer.aliyun.com/ask/649609)
40 1
|
2月前
|
JSON Serverless 数据格式
体验函数计算一键部署 Flux 超写实文生图模型部署
体验函数计算一键部署 Flux 超写实文生图模型部署

热门文章

最新文章

相关产品

  • 函数计算