隨著 Serverless 的流行缩挑,將應(yīng)用遷移到云上已經(jīng)成了一種必然的趨勢(shì)黔夭。
我們今天來看一下如何將機(jī)器學(xué)習(xí)應(yīng)用遷移到函數(shù)計(jì)算上帽哑。
1. 本地開發(fā)
首先我們看一下本地開發(fā)機(jī)器學(xué)習(xí)應(yīng)用的步驟。我們大概可以將本地開發(fā)概括為三個(gè)步驟坟比,分別是代碼編寫芦鳍,安裝依賴,運(yùn)行調(diào)試葛账。我們分別來看一下柠衅。
1.1 代碼編寫
假定我們的項(xiàng)目結(jié)構(gòu)為:
projectroot├──index.py├──model_data│? ├──checkpoint│? ├──model.data-00000-of-00001│? ├──model.index│? └──model.met└──pic└──e2.jpg
其中?index.py?存放了機(jī)器學(xué)習(xí)相關(guān)的代碼,model_data?存放了數(shù)據(jù)模型籍琳,pic 中存放了要進(jìn)行測(cè)試的圖片菲宴。
index.py 的內(nèi)容為(代碼參考了這篇文章):
# -*- coding:utf-8 -*-? importosimportsysimportcv2importnumpyasnpimporttensorflowastf? saver =NonedefreversePic(src):foriinrange(src.shape[0]):forjinrange(src.shape[1]):? ? ? ? ? ? src[i, j] =255- src[i, j]returnsrcdefmain():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 安裝依賴
在運(yùn)行應(yīng)用前,需要先安裝應(yīng)用依賴的模塊趋急,這里主要依賴了 opencv 以及 tensorflow喝峦,安裝方法很簡(jiǎn)單:
pip install opencv-python
pip install tensorflow
執(zhí)行完這兩條命令后,opencv-python?以及?tensorflow?就被安裝到系統(tǒng)目錄里呜达。Linux 下默認(rèn)為 /usr/local/lib/pythonX.Y/site-packages谣蠢。
1.3 運(yùn)行
運(yùn)行時(shí),python 會(huì)自動(dòng)在配置的路徑下查找相關(guān)模塊并進(jìn)行加載。
$ python index.py
the predict is 8
經(jīng)過這三個(gè)步驟眉踱,我們就完成了本地機(jī)器學(xué)習(xí)應(yīng)用的開發(fā)挤忙,我們接下來看下如何遷移應(yīng)用到函數(shù)計(jì)算。
2. 遷移函數(shù)計(jì)算
2.1 本地開發(fā)與函數(shù)計(jì)算開發(fā)對(duì)比
首先谈喳,我們需要做一些準(zhǔn)備工作册烈。讓我們來思考下函數(shù)計(jì)算應(yīng)用開發(fā)方式與本地應(yīng)用應(yīng)用的開發(fā)方式有什么不同呢?
代碼入口叁执。本地開發(fā)時(shí)茄厘,代碼可以省略 main 函數(shù)矮冬,也可以提供 main 函數(shù)作為程序入口谈宛,但在函數(shù)計(jì)算中,函數(shù)入口是固定的胎署,非 Http 觸發(fā)器的函數(shù)入口必須是一個(gè)包含了兩個(gè)參數(shù)的函數(shù)吆录,比如:def handler(event, context)。
模塊依賴琼牧。本地開發(fā)時(shí)恢筝,項(xiàng)目依賴的模塊通常會(huì)被安裝到系統(tǒng)的某個(gè)目錄。比如我們上面執(zhí)行的 pip install tensorflow巨坊。而對(duì)于函數(shù)計(jì)算撬槽,由于為了能夠最大限度的對(duì)應(yīng)用進(jìn)行優(yōu)化,開放給用戶的操作空間通常是比較小的趾撵。因此侄柔,對(duì)于函數(shù)計(jì)算,目前還無法做到安裝項(xiàng)目依賴到運(yùn)行環(huán)境占调。我們只能通過將自定義模塊一同打包的方式暂题。參考。
運(yùn)行究珊。本地開發(fā)時(shí)薪者,需要使用 python 命令或者 IDE 來運(yùn)行代碼。而在函數(shù)計(jì)算剿涮,我們需要首先部署應(yīng)用到函數(shù)計(jì)算言津,再通過觸發(fā)器或者控制臺(tái)手動(dòng)觸發(fā)執(zhí)行。
接下來我們針對(duì)這三點(diǎn)開發(fā)方式的不同對(duì)代碼進(jìn)行改造取试。
2.2 改造代碼
2.2.1 代碼入口改造
這個(gè)比較簡(jiǎn)單悬槽,只需要將
defmain():
修改為
defhandler(event, context):
并刪除下面代碼:
if__name__ =='__main__':? ? ? main()
2.2.2. 模塊依賴
這一塊稍微復(fù)雜些。不同的語言因?yàn)槟K加載機(jī)制的不同想括,這里的處理邏輯也會(huì)有差異陷谱。比如對(duì)于 java,無論是使用 maven,還是 gradle烟逊,都可以很容易的一鍵將代碼以及依賴打包成 jar渣窜。但遺憾的是 python 目前沒有這種機(jī)制。
我們先根據(jù)場(chǎng)景對(duì) python 依賴的模塊做個(gè)簡(jiǎn)單的分類宪躯。
應(yīng)用依賴:?對(duì)于本例中使用 pip 安裝的模塊乔宿,比如?pip install tensorflow,我們暫且將其稱為應(yīng)用依賴访雪。
系統(tǒng)依賴:?在某些場(chǎng)景下详瑞,python 安裝的庫(kù)僅僅是對(duì)底層 c、c++ 庫(kù)調(diào)用的封裝臣缀,例如使用 zbar 時(shí)坝橡,除了使用 pip install zbar,還要在系統(tǒng)中安裝相應(yīng)的庫(kù):apt-get install -y libzbar-dev精置。我們暫且把像 libzbar-dev 一樣需要使用系統(tǒng)軟件包管理器安裝的庫(kù)稱為系統(tǒng)依賴计寇。
資源依賴:?對(duì)于一些應(yīng)用,比如機(jī)器學(xué)習(xí)脂倦,啟動(dòng)后還需要加載數(shù)據(jù)模型番宁,數(shù)據(jù)模型需要在程序啟動(dòng)時(shí)準(zhǔn)備好,我們暫且將這種依賴稱為資源依賴赖阻。資源依賴比較特殊蝶押,它是我們的應(yīng)用邏輯所需要的,通常體積比較大火欧。
對(duì)于應(yīng)用依賴棋电,我們可以通過 pip 的?-t?參數(shù)改變其安裝位置,比如?pip install -t $(pwd) tensorflow布隔。并且可以通過?sys.path?改變加載行為离陶,使得可以從指定目錄加載模塊。
對(duì)于系統(tǒng)依賴衅檀,我們可以通過?apt-get?下載 deb 包招刨,再利用 deb 包安裝到指定目錄。
apt-get install -y-d-o=dir::cache=$(pwd) libzbar-devforfin$(ls $(pwd)/archives/*deb);dodpkg -x$f$(pwd);donerm -r archives
對(duì)于系統(tǒng)依賴包含的鏈接庫(kù)哀军,可以通過?LD_LIBRARY_PATH?變量改變其加載行為沉眶。
對(duì)于資源依賴,因?yàn)榭刂茩?quán)在我們的代碼里杉适,因此只需要改變代碼的處理邏輯就可以了谎倔。
根據(jù)上面的描述,我們可以整理成下面的表格:
類別定義安裝方法舉例指定位置安裝方法舉例影響加載的因素
應(yīng)用依賴pip 安裝的模塊pip install tensorflowpip install -t $(pwd) tensorflowsys.path
系統(tǒng)依賴系統(tǒng)軟件包管理器安裝的依賴apt-get install -y libzbar-devapt-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
資源依賴代碼依賴的資源猿推,比如數(shù)據(jù)模型\\由應(yīng)用代碼控制
2.2.3 下載依賴的邏輯
對(duì)于我們的 demo 應(yīng)用片习,存在兩種依賴捌肴,一種是應(yīng)用依賴,另一種是資源依賴藕咏。而需要我們特別處理的只有應(yīng)用依賴状知。我們需要在項(xiàng)目目錄下創(chuàng)建一個(gè)名為 applib 的目錄,并下載應(yīng)用依賴到該目錄孽查。這里需要注意的是如果引用的模塊使用 C / C++ / go 編譯出來的可執(zhí)行文件或者庫(kù)文件饥悴,那么推薦使用 fcli 的?sbox?進(jìn)行下載,使用方法為:
mkdir applib fcli shellsbox-dapplib -t python2.7pip install -t $(pwd) tensorflowpip install -t $(pwd) opencv-python
執(zhí)行完畢后盲再,就會(huì)發(fā)現(xiàn) applib 中就包含了項(xiàng)目所需要的應(yīng)用依賴西设。
2.2.4 打包依賴上傳到 OSS
機(jī)器學(xué)習(xí)的應(yīng)用依賴、資源依賴通常比較大答朋,會(huì)很容易超過函數(shù)計(jì)算對(duì)代碼包的限制(50M)贷揽。為了避開這個(gè)問題,我們需要將這些依賴上傳到 OSS:
cdapplib && zip -r applib.zip * && mv applib.zip ../ ;cd..
執(zhí)行完畢后绿映,項(xiàng)目會(huì)多出一個(gè)名為 applib.zip 的壓縮包擒滑,上傳到 oss 即可腐晾。
同樣的叉弦,對(duì)資源依賴進(jìn)行相同的操作:
cdmodel_data && zip -r model_data.zip * && mv model_data.zip ../ ;cd..
2.2.5 初始化依賴
這里我們提供一個(gè)模板代碼,負(fù)責(zé)在函數(shù)第一次啟動(dòng)時(shí)藻糖,從 OSS 下載資源到本地淹冰、解壓,并配置好相應(yīng)的環(huán)境變量巨柒。我們可以在項(xiàng)目中創(chuàng)建一個(gè)名為?loader.py?文件樱拴,內(nèi)容為:
# -*- coding:utf-8 -*-? importsysimportzipfileimportosimportoss2importimpimporttimeapp_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)inilized =Falsedefdownload_and_unzip_if_not_exist(objectKey, path, context):creds = context.credentialsif(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: '+ objectKeyprint'path: '+ pathprint'endpoint: '+ endpointprint'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)ifnotos.path.exists(path):? ? ? ? os.mkdir(path)print'before unzipping '+ objectKey +' ...'start_unzip_time = time.time()withzipfile.ZipFile(zipName,"r")asz:? ? ? ? z.extractall(path)print'unzipping done, used %s seconds...'% (time.time() - start_unzip_time)defhandler(event, context):globalinilizedifnotinilized:if(notlocal ):? ? ? ? ? ? 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)printsys.path? ? ? ? inilized =Truefile_handle, desc =None,Nonefn, modulePath, desc = imp.find_module('index')? ? mod = imp.load_module('index', fn, modulePath, desc)? ? request_handler = getattr(mod,'handler')returnrequest_handler(event, context)
這段代碼會(huì)首先讀取 AppLibObject 環(huán)境變量,用于從 OSS 下載應(yīng)用依賴洋满,并解壓到 AppLibDir 這個(gè)環(huán)境變量所代表的目錄晶乔。
其次會(huì)讀取 ModelObject 環(huán)境變量,用于從 OSS 下載資源依賴牺勾,并解壓到 ModelDir 這個(gè)環(huán)境變量所代表的目錄正罢。
最后,當(dāng)依賴準(zhǔn)備妥當(dāng)后驻民,會(huì)調(diào)用 index.py 中的 handler 函數(shù)翻具。
理論上,這個(gè)代碼可以用于其它任何需要下載應(yīng)用依賴回还、資源依賴的場(chǎng)景裆泳。而我們的 index.py 需要修改的,只有將原先的獲取模型依賴的固定的路徑柠硕,修改為利用 ModelDir 獲取路徑即可工禾。
2.3 本地運(yùn)行調(diào)試
代碼編寫完成后,我們的目錄結(jié)構(gòu)調(diào)整為:
projectroot├──code│? ├──index.py│? ├──loader.py│? └──pic│? ? ? └──e2.jpg├──applib│.? └── *└──model_data├──checkpoint├──model.data-00000-of-00001├──model.index└──model.met
我們本地運(yùn)行看下效果,這里我們借助于函數(shù)計(jì)算推出的?fc-dcoker?工具闻葵。
為了避免本地每次調(diào)試做無謂的下載糙捺,我們?nèi)∠虑桑瑢?yīng)用依賴笙隙、資源依賴掛載到?fc-docker?中洪灯,并開啟?local?的標(biāo)識(shí):
docker run --rm \-elocal=true\-eAppLibObject=applib.zip \-eAppLibDir=/tmp/applib \-eModelObject=model_data.zip \-eModelDir=/tmp/model \? ? -v $(pwd)/code:/code \? ? -v $(pwd)/applib:/tmp/applib \? ? -v $(pwd)/model_data:/tmp/model \? ? aliyunfc/runtime-python2.7 \? loader.handler
得到結(jié)果:
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 FMAFunctionCompute python runtime inited.FC Invoke Start RequestId: f3ea930e-d7e2-4173-9726-453cdd89f18clocalrunning: 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/modelthe predict is 8RequestId: f3ea930e-d7e2-4173-9726-453cdd89f18c? ? ? ? ? Billed Duration: 9954 ms? ? ? ? Memory Size: 1998 MB? ? ? Max Memory Used: 239 MB
2.4 部署
本地開發(fā)完成,接下來竟痰,我們就需要部署應(yīng)用到線上了签钩。這里我們借助函數(shù)計(jì)算推出的?Fun?工具。
Fun 工具使用步驟如下:
去?release?頁面對(duì)應(yīng)平臺(tái)的 binary 版本坏快,解壓就可以使用铅檩。或者使用?npm install @alicloud/fun -g?也可以直接使用莽鸿。
使用 fun config 配置 ak昧旨、region 等信息。
編寫 template.yml
fun deploy 部署
是的祥得,不需要登錄控制臺(tái)進(jìn)行繁瑣的配置兔沃,僅僅在項(xiàng)目下提供一個(gè) template.yml 即可:
ROSTemplateFormatVersion:'2015-09-01'Transform:'Aliyun::Serverless-2018-04-03'Resources:? tensorflow:# 服務(wù)名Type:'Aliyun::Serverless::Service'Properties:? ? ? Description:'tensorflow demo'Policies:? ? ? ? - AliyunOSSReadOnlyAccesstest:# 函數(shù)名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 bucketEndpoint:'https://oss-cn-shanghai-internal.aliyuncs.com'# 替換掉 OSS EndpointAppLibObject: applib.zip? ? ? ? ? AppLibDir: /tmp/applib? ? ? ? ? ModelObject: model_data.zip? ? ? ? ? ModelDir: /tmp/model
至此,我們的項(xiàng)目中又多了一個(gè) template.yml级及,結(jié)構(gòu)為
projectroot├──code│? ├──index.py│? ├──loader.py│? └──pic│? ? ? └──e2.jpg├──applib│? └── *├──template.yml└──model_data├──checkpoint├──model.data-00000-of-00001├──model.index└──model.met
通過這一個(gè) template.yml乒疏,執(zhí)行 fun deploy 后即可創(chuàng)建好相應(yīng)的服務(wù)、函數(shù)饮焦,并配置好函數(shù)的環(huán)境變量怕吴。
$ fun deployWaitingforservice tensorflow to be deployed...? ? ? ? Waitingforfunctiontestto be deployed...? ? ? ? ? ? ? ? Waitingforpackagingfunctiontestcode...? ? ? ? ? ? ? ? packagefunctiontestcodedonefunctiontestdeploy successservice tensorflow deploy success
即使修改了代碼,只要重復(fù)執(zhí)行 fun deploy 即可县踢。
接下來转绷,打開?https://fc.console.aliyun.com/?控制臺(tái),依次找到創(chuàng)建好的服務(wù)硼啤、函數(shù)议经,點(diǎn)擊執(zhí)行,即可得到與本地一致的輸出:
2.5 補(bǔ)充
在上面的例子中丙曙,我們只列出了應(yīng)用依賴爸业、資源依賴的情況拌牲。對(duì)于系統(tǒng)依賴的處理邏輯是比較簡(jiǎn)單的探橱,比如我們拿 zbar 舉例绕娘。除了需要在 applib 中通過 pip 安裝 zbar缴啡,還要在 code 目錄下新建一個(gè) lib 目錄纠修,并通過 sandbox 在這個(gè)目錄中執(zhí)行:
apt-get install -y-d-o=dir::cache=$(pwd) libzbar-devforfin$(ls $(pwd)/archives/*deb);dodpkg -x$f$(pwd);donerm -r archives
執(zhí)行完成后镊辕,目錄結(jié)構(gòu)變化為:
projectroot├── code│? ├── lib│? │? └── usr/lib│? │? ? ? └── *
就像上面提到的老厌,我們需要修改 LD_LIBRARY_PATH 來改變系統(tǒng)依賴的加載行為省咨,因此我們需要在 template.yaml 中的 EnvironmentVariables 下添加一行:
LD_LIBRARY_PATH: /code/lib/usr/lib:/code:/code/lib:/usr/local/lib
至此,就可以直接在 index.py 等文件中直接使用 zbar 了耸黑。
3. 總結(jié)
結(jié)果一番努力桃煎,我們終于將機(jī)器學(xué)習(xí)應(yīng)用上線到函數(shù)計(jì)算了〈罂回顧上面的所有操作可以發(fā)現(xiàn)为迈,其實(shí)大部分的改造工作都是可以通過工具解決的。無論是 template.yml缺菌,還是 loader.py 都是直接拿來就能用的葫辐。而真正需要開發(fā)者操作的也就只有下載依賴、修改對(duì)資源依賴的引用路徑了伴郁。
附:阿里云代金券1000元免費(fèi)領(lǐng)裙⒄健!領(lǐng)取地址:http://aliyun.jinre.com?
新老阿里云賬戶均可領(lǐng)群父怠剂陡!可用于購(gòu)買阿里云服務(wù)器ECS、云數(shù)據(jù)庫(kù)RDS狐胎、虛擬主機(jī)鸭栖、安騎士、DDoS高防IP等100多云計(jì)算產(chǎn)品顽爹。
? ? 代金券自領(lǐng)取之日起纤泵,有效期是30天,請(qǐng)及時(shí)使用镜粤,過30天后還可以重新領(lǐng)取。