Python實(shí)戰(zhàn) | 基于 Flask 部署 Keras 深度學(xué)習(xí)模型

文 | 風(fēng)玲兒 出處 | 掘金
本文主要記錄在進(jìn)行
Flask部署過(guò)程中所使用的流程,遇到的問(wèn)題以及相應(yīng)的解決方案。
1、項(xiàng)目簡(jiǎn)介
該部分簡(jiǎn)要介紹一下前一段時(shí)間所做的工作:
基于深度學(xué)習(xí)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的圖像分類問(wèn)題 借助 flask 框架將其部署到 web 應(yīng)用中 并發(fā)要求較高
這是第一次進(jìn)行深度學(xué)習(xí)模型的 web 應(yīng)用部署,在整個(gè)過(guò)程中,進(jìn)一步折射出以前知識(shí)面之窄,在不斷的入坑、解坑中實(shí)現(xiàn)一版。
2、項(xiàng)目流程
這部分從項(xiàng)目實(shí)施的流程入手,記錄所做的工作及用到的工具。
2.1 圖像分類模型
1. 模型的選擇
需要進(jìn)行圖像分類,第一反應(yīng)是利用較為成熟與經(jīng)典的分類網(wǎng)絡(luò)結(jié)構(gòu),如 VGG 系列(VGG16, VGG19),ResNet 系列(如ResNet50),InceptionV3等。
考慮到是對(duì)未知類型的圖像進(jìn)行分類,且沒(méi)有直接可用的訓(xùn)練數(shù)據(jù),因此使用在Imagenet上訓(xùn)練好的預(yù)訓(xùn)練模型,基本滿足要求。
如果對(duì)性能(耗時(shí))要求較為嚴(yán)格,則建議使用深度較淺的網(wǎng)絡(luò)結(jié)構(gòu),如VGG16, MobileNet等。
其中,MobileNet網(wǎng)絡(luò)是為移動(dòng)端和嵌入式端深度學(xué)習(xí)應(yīng)用設(shè)計(jì)的網(wǎng)絡(luò),使得在 cpu 上也能達(dá)到理想的速度要求。是一種輕量級(jí)的深度網(wǎng)絡(luò)結(jié)構(gòu)。
MobileNet 由 Google 團(tuán)隊(duì)提出,發(fā)表于 CVPR-2017,論文標(biāo)題:《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》
2. 框架選擇
平時(shí)使用
Keras框架比較多,Keras底層庫(kù)使用Theano或Tensorflow,也稱為 Keras 的后端。Keras是在Tensorflow基礎(chǔ)上構(gòu)建的高層 API,比Tensorflow更容易上手。上述提到的分類網(wǎng)絡(luò),在
Keras中基本已經(jīng)實(shí)現(xiàn),Keras 中已經(jīng)實(shí)現(xiàn)的網(wǎng)絡(luò)結(jié)構(gòu)如下所示:
使用方便,直接導(dǎo)入即可,如下:

因此,選擇 Keras 作為深度學(xué)習(xí)框架。
3. 代碼示例
以Keras框架,VGG16網(wǎng)絡(luò)為例,進(jìn)行圖像分類。
from keras.models import Model
from keras.applications.vgg16 import VGG16, preprocess_input
import keras.backend.tensorflow_backend as KTF
import tensorflow as tf
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1" #使用GPU
# 按需占用GPU顯存
gpu_options = tf.GPUOptions(allow_growth=True)
sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options))
KTF.set_session(sess)
# 構(gòu)建model
base_model = VGG16(weights=‘imagenet’, include_top=True)
model = Model(inputs=base_model.input,
outputs=base_model.get_layer(layer).output) # 獲取指定層的輸出值,layer為層名
# 進(jìn)行預(yù)測(cè)
img = load_image(img_name, target_size=(224, 224)) # 加載圖片并resize成224x224
# 圖像預(yù)處理
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
feature = model.predict(x) # 提取特征
2.2 模型性能測(cè)試
將分類模型跑通后,我們需要測(cè)試他們的性能,如耗時(shí)、CPU 占用率、內(nèi)存占用以及 GPU 顯存占用率等。
1. 耗時(shí)
耗時(shí)是為了測(cè)試圖像進(jìn)行分類特征提取時(shí)所用的時(shí)間,包括圖像預(yù)處理時(shí)間和模型預(yù)測(cè)時(shí)間的總和。
# 使用python中的time模塊
import time
t0 = time.time()
....
圖像處理和特征提取
....
print(time.time()-t0) #耗時(shí),以秒為單位
2. GPU 顯存占用
使用英偉達(dá)命令行nvidia-smi可以查看顯存占用。
3. CPU, MEM 占用
使用top命令或htop命令查看 CPU 占用率以及內(nèi)存占用率。
內(nèi)存占用還可以使用free命令來(lái)查看:
free -h: 加上-h選項(xiàng),輸出結(jié)果較為友好,會(huì)給出合適單位需要持續(xù)觀察內(nèi)存狀況時(shí),可以使用
-s選項(xiàng)指定間隔的秒數(shù):free -h -s 3(每隔 3 秒更新一次,停止更新時(shí)按下Ctrl+c)
Ubuntu 16.04版本中默認(rèn)的free版本有 bug,使用-s選項(xiàng)時(shí)會(huì)報(bào)錯(cuò)。
根據(jù)以上三個(gè)測(cè)試結(jié)果適時(shí)調(diào)整所采用的網(wǎng)絡(luò)結(jié)構(gòu)及顯存占用選項(xiàng)。
命令具體含義可參考博文:
Linux 查看 CPU 和內(nèi)存使用情況[1]
2.3 Redis 使用
Redis=Remote DIctionary Server,是一個(gè)由 Salvatore Sanfilippo 寫的高性能的key-value存儲(chǔ)系統(tǒng)。Redis 是一個(gè)開(kāi)源的使用 ANSI C 語(yǔ)言編寫、遵守 BSD 協(xié)議、支持網(wǎng)絡(luò)、可基于內(nèi)存亦可持久化的日?qǐng)?zhí)行、key-value 數(shù)據(jù)庫(kù),并提供多種語(yǔ)言的 API。
Redis支持存儲(chǔ)的類型有string, list, set, zset和hash,在處理大規(guī)模數(shù)據(jù)讀寫的場(chǎng)景下運(yùn)用比較多。
1. 基本使用
安裝 redis
pip install redis
# 測(cè)試
import redis
基本介紹
redis.py提供了兩個(gè)類:Redis,StrictRedis用于實(shí)現(xiàn)Redis的命令 StrictRedis用于實(shí)現(xiàn)大部分官方命令,并使用官方的語(yǔ)法和命令 Redis是StrictRedis的子類,用于向前兼容redis.py 一般情況下我們就是用StrictRedis。
使用示例
# 1. 導(dǎo)入redis
from redis import StrictRedis
# 2. 連接數(shù)據(jù)庫(kù),指定host,端口號(hào),數(shù)據(jù)庫(kù)
r = StrictRedis(host=‘localhost’, port=6379, db=2)
# 3. 存儲(chǔ)到redis中
r.set('test1', 'value1') # 單個(gè)數(shù)據(jù)存儲(chǔ)
r.set('test2', 'value2')
# 4. 從redis中獲取值
r.get('test1')
# 5. 批量操作
r.mset(k1='v1', k2='v2')
r.mset({'k1':'v1', 'k2':'v2'})
r.mget('k1', 'k2')
r.mget(['k1', 'k2'])
2. Redis 存儲(chǔ)數(shù)組
Redis 是不可以直接存儲(chǔ)數(shù)組的,如果直接存儲(chǔ)數(shù)組類型的數(shù)值,則獲取后的數(shù)值類型發(fā)生變化,如下,存入 numpy 數(shù)組類型,獲取后的類型是bytes類型。
import numpy as np
from redis import StrictRedis
r = StrictRedis(host=‘localhost’, port=6379, db=2)
x1 = np.array(([0.2,0.1,0.6],[10.2,4.2,0.9]))
r.set('test1', x1)
>>> True
r.get('test1')
>>> b'[[ 0.2 0.1 0.6]\n [10.2 4.2 0.9]]'
type(r.get('test1')) #獲取后的數(shù)據(jù)類型
>>> <class 'bytes'>
為了保持?jǐn)?shù)據(jù)存儲(chǔ)前后類型一致,在存儲(chǔ)數(shù)組之前將其序列化,獲取數(shù)組的時(shí)候?qū)⑵浞葱蛄谢纯伞?/p>
借助于 python 的pickle模塊進(jìn)行序列化操作。
import pickle
r.set('test2', pickle.dumps(x1))
>>> True
pickle.loads(r.get('test2'))
>>> array([[ 0.2, 0.1, 0.6],
[10.2, 4.2, 0.9]])
這樣,就可以保持?jǐn)?shù)據(jù)存入前和取出后的類型一致。
2.4 web 開(kāi)發(fā)框架——Flask
之前學(xué)習(xí) python 語(yǔ)言,從來(lái)沒(méi)有關(guān)注過(guò)
Web開(kāi)發(fā)這一章節(jié),因?yàn)楣ぷ鲀?nèi)容并沒(méi)有涉及這一部分。如今需要重新看一下。
早期軟件主要運(yùn)行在桌面上,數(shù)據(jù)庫(kù)這樣的軟件運(yùn)行在服務(wù)器端,這種Client/Server模式簡(jiǎn)稱CS架構(gòu)。隨著互聯(lián)網(wǎng)的興起,CS架構(gòu)不適合Web,最大原因是 Web 應(yīng)用程序的修改和升級(jí)非常頻繁,CS架構(gòu)需要每個(gè)客戶端逐個(gè)升級(jí)桌面 App,因此,Browser/Server模式開(kāi)始流行,簡(jiǎn)稱BS架構(gòu)。
在BS架構(gòu)下,客戶端只需要瀏覽器,應(yīng)用程序的邏輯和數(shù)據(jù)存儲(chǔ)在服務(wù)器端,瀏覽器只需要請(qǐng)求服務(wù)器,獲取 Web 頁(yè)面,并把 Web 頁(yè)面展示給用戶即可。當(dāng)前,Web 頁(yè)面也具有極強(qiáng)的交互性。
Python 的誕生歷史比 Web 還要早,由于 Python 是一種解釋型的腳本語(yǔ)言,開(kāi)發(fā)效率高,所以非常適合用來(lái)做 Web 開(kāi)發(fā)。
Python 有上百個(gè)開(kāi)源的 Web 框架,比較熟知的有Flask, Django。接下來(lái)以Flask為例,介紹如何利用 Flask 進(jìn)行 web 部署。
關(guān)于 web 開(kāi)發(fā)框架的介紹,可以參考下面這篇博文:三個(gè)目前最火的 Python Web 開(kāi)發(fā)框架,你值得擁有![2]
有關(guān)Flask的具體用法可參考其他博文,這方面的資料比較全。下面主要以具體使用示例來(lái)說(shuō)明:
1. 安裝使用
安裝 Flask
pip install flask
import flask # 導(dǎo)入
flask.__version__ # 版本
>>> '1.1.1' #當(dāng)前版本一個(gè)簡(jiǎn)單的 Flask 示例
Flask 使用 Python 的裝飾器在內(nèi)部自動(dòng)的把
URL和函數(shù)給關(guān)聯(lián)起來(lái)。# hello.py
from flask import Flask, request
app = Flask(__name__) #創(chuàng)建Flask類的實(shí)例,第一個(gè)參數(shù)是模塊或者包的名稱
app.config['JSON_AS_ASCII']=False # 支持中文顯示
@app.route('/', methods=['GET', 'POST']) # 使用methods參數(shù)處理不同HTTP方法
def home():
return 'Hello, Flask'
if __name__ == '__main__':
app.run()運(yùn)行該文件,會(huì)提示
* Running on http://127.0.0.1:5000/,在瀏覽器中打開(kāi)此網(wǎng)址,會(huì)自動(dòng)調(diào)用home函數(shù),返回Hello, Flask,則在瀏覽器頁(yè)面上就會(huì)看到Hello, Flask字樣。app.run 的參數(shù)
app.run(host="0.0.0.0", port="5000", debug=True, processes=2, threaded=False)注意:絕對(duì)不能在生產(chǎn)環(huán)境中使用調(diào)試器
host設(shè)定為0.0.0.0,則可以讓服務(wù)器被公開(kāi)訪問(wèn)port:指定端口號(hào),默認(rèn)為5000debug:是否開(kāi)啟 debug 模型,如果你打開(kāi) 調(diào)試模式,那么服務(wù)器會(huì)在修改應(yīng)用代碼之后自動(dòng)重啟,并且當(dāng)應(yīng)用出錯(cuò)時(shí)還會(huì)提供一個(gè) 有用的調(diào)試器。processes:線程數(shù)量,默認(rèn)是1threaded:bool類型,是否開(kāi)啟多線程。注:當(dāng)開(kāi)啟多個(gè)進(jìn)程時(shí),不支持同時(shí)開(kāi)啟多線程。使用 route()裝飾器來(lái)告訴 Flask 觸發(fā)函數(shù)的 URL;函數(shù)名稱被用于生成相關(guān)聯(lián)的 URL。函數(shù)最后返回需要在用戶瀏覽器中顯示的信息。
2. Flask 響應(yīng)
視圖函數(shù)的返回值會(huì)自動(dòng)轉(zhuǎn)換為一個(gè)響應(yīng)對(duì)象。如果返回值是一個(gè)字符串,那么會(huì)被 轉(zhuǎn)換為一個(gè)包含作為響應(yīng)體的字符串、一個(gè) 200 OK 出錯(cuò)代碼 和一個(gè) text/html 類型的響應(yīng)對(duì)象。如果返回值是一個(gè)字典,那么會(huì)調(diào)用 jsonify() 來(lái)產(chǎn)生一個(gè)響應(yīng)。以下是轉(zhuǎn)換的規(guī)則:
如果視圖返回的是一個(gè)響應(yīng)對(duì)象,那么就直接返回它。 如果返回的是一個(gè)字符串,那么根據(jù)這個(gè)字符串和缺省參數(shù)生成一個(gè)用于返回的 響應(yīng)對(duì)象。 如果返回的是一個(gè)字典,那么調(diào)用 jsonify 創(chuàng)建一個(gè)響應(yīng)對(duì)象。 如果返回的是一個(gè)元組,那么元組中的項(xiàng)目可以提供額外的信息。元組中必須至少 包含一個(gè)項(xiàng)目,且項(xiàng)目應(yīng)當(dāng)由 (response, status) 、 (response, headers) 或者 (response, status, headers) 組成。status 的值會(huì)重載狀態(tài)代碼, headers 是一個(gè)由額外頭部值組成的列表 或字典。 如果以上都不是,那么 Flask 會(huì)假定返回值是一個(gè)有效的 WSGI 應(yīng)用并把它轉(zhuǎn)換為一個(gè)響應(yīng)對(duì)象。
JSON 格式的 API
JSON格式的響應(yīng)是常見(jiàn)的,用 Flask 寫這樣的 API 是很容易上手的。如果從視圖 返回一個(gè) dict ,那么它會(huì)被轉(zhuǎn)換為一個(gè) JSON 響應(yīng)。
@app.route("/me")
def me_api():
user = get_current_user()
return {
"username": user.username,
"theme": user.theme,
"image": url_for("user_image", filename=user.image),
}
如果 dict 還不能滿足需求,還需要?jiǎng)?chuàng)建其他類型的 JSON 格式響應(yīng),可以使用 jsonify() 函數(shù)。該函數(shù)會(huì)序列化任何支持的 JSON 數(shù)據(jù)類型。
@app.route("/users")
def users_api():
users = get_all_users()
return jsonify([user.to_json() for user in users])
3. 運(yùn)行開(kāi)發(fā)服務(wù)器
通過(guò)命令行使用開(kāi)發(fā)服務(wù)器
強(qiáng)烈推薦開(kāi)發(fā)時(shí)使用 flask 命令行腳本( 命令行接口 ),因?yàn)橛袕?qiáng)大的重載功能,提供了超好的重載體驗(yàn)?;居梅ㄈ缦?
$ export FLASK_APP=my_application
$ export FLASK_ENV=development
$ flask run這樣做開(kāi)始了開(kāi)發(fā)環(huán)境(包括交互調(diào)試器和重載器),并在
http://localhost:5000/提供服務(wù)。通過(guò)使用不同
run參數(shù)可以控制服務(wù)器的單獨(dú)功能。例如禁用重載器:$ flask run --no-reload通過(guò)代碼使用開(kāi)發(fā)服務(wù)器
另一種方法是通過(guò)
Flask.run()方法啟動(dòng)應(yīng)用,這樣立即運(yùn)行一個(gè)本地服務(wù)器,與使用flask腳本效果相同。示例:
if __name__ == '__main__':
app.run()通常情況下這樣做不錯(cuò),但是對(duì)于開(kāi)發(fā)就不行了。

2.5 使用 Gunicorn
當(dāng)我們執(zhí)行上面的app.py時(shí),使用的flask自帶的服務(wù)器,完成了 web 服務(wù)的啟動(dòng)。在生產(chǎn)環(huán)境中,flask 自帶的服務(wù)器,無(wú)法滿足性能要求,我們這里采用Gunicorn做wsgi容器,來(lái)部署flask程序。
Gunicorn(綠色獨(dú)角獸)是一個(gè)Python WSGI UNIX HTTP服務(wù)器。從 Ruby 的獨(dú)角獸(Unicorn )項(xiàng)目移植。該Gunicorn服務(wù)器作為wsgi app的容器,能夠與各種 Web 框架兼容,實(shí)現(xiàn)非常簡(jiǎn)單,輕量級(jí)的資源消耗。Gunicorn 直接用命令啟動(dòng),不需要編寫配置文件,相對(duì) uWSGI 要容易很多。
web 開(kāi)發(fā)中,部署方式大致類似。
1. 安裝及使用
pip install gunicorn
如果想讓Gunicorn支持異步workers的話需要安裝以下三個(gè)包:
pip install gevent
pip install eventlet
pip install greenlet
指定進(jìn)程和端口號(hào),啟動(dòng)服務(wù)器:
gunicorn -w 4 -b 127.0.0.1:5001 運(yùn)行文件名稱:Flask程序?qū)嵗?/code>
以上述 hello.py 文件為例:
gunicorn -w 4 -b 127.0.0.1:5001 hello:app
參數(shù):-w: 表示進(jìn)程(worker)。-b:表示綁定 ip 地址和端口號(hào)(bind)
查看 gunicorn 的具體參數(shù),可執(zhí)行gunicorn -h 通常將配置參數(shù)寫入到配置文件中,如gunicorn_conf.py
重要參數(shù):
bind: 監(jiān)聽(tīng)地址和端口workers: worker 進(jìn)程的數(shù)量。建議值:2~4 x (NUM_CORES),缺省值是 1.worker_class:worker 進(jìn)程的工作方式。有:sync(缺省值),eventlet,gevent,gthread,tornadothreads:工作進(jìn)程中線程的數(shù)量。建議值:2~4 x (SUM_CORES),缺省值是 1.reload: 當(dāng)代碼有修改時(shí),自動(dòng)重啟 workers。適用于開(kāi)發(fā)環(huán)境,默認(rèn)為Falsedaemon:應(yīng)用是否以daemon方式運(yùn)行,是否以守護(hù)進(jìn)程啟動(dòng),默認(rèn)Falseaccesslog:訪問(wèn)日志文件路徑errorlog:錯(cuò)誤日志路徑loglevel:日志級(jí)別。debug, info, warning, error, critical.
一個(gè)參數(shù)配置示例:
# gunicorn_conf.py
bind: '0.0.0.0:5000' # 監(jiān)聽(tīng)地址和端口號(hào)
workers = 2 # 進(jìn)程數(shù)
worker_class = 'sync' #工作模式,可選sync, gevent, eventlet, gthread, tornado等
threads = 1 # 指定每個(gè)進(jìn)程的線程數(shù),默認(rèn)為1
worker_connections = 2000 # 最大客戶并發(fā)量
timeout = 30 # 超時(shí)時(shí)間,默認(rèn)30s
reload = True # 開(kāi)發(fā)模式,代碼更新時(shí)自動(dòng)重啟
daemon = False # 守護(hù)Gunicorn進(jìn)程,默認(rèn)False
accesslog = './logs/access.log' # 訪問(wèn)日志文件
errorlog = './logs/error.log'
loglevel = 'debug' # 日志輸出等級(jí),debug, info, warning, error, critical
調(diào)用命令:
gunicorn -c gunicorn_conf.py hello:app
參數(shù)配置文件示例可見(jiàn):gunicorn/example_config.py at master · benoitc/gunicorn[3]
3、代碼示例
#flask_feature.app
import numpy as np
from flask import Flask, jsonify
from keras.models import Model
from keras.applications.vgg16 import VGG16
from keras.backend.tensorflow_backend import set_session
app = Flask(__name__)
app.config['JSON_AS_ASCII']=False
@app.route("/", methods=["GET", "POST"])
def feature():
img_feature = extract()
return jsonify({'result':'true', 'msg':'成功'})
def extract(img_name):
# 圖像預(yù)處理
img = load_image(img_name, target_size=(feature_params["size"], feature_params["size"]))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
with graph.as_default():
set_session(sess)
res = model.predict(x)
return res
if __name__ == '__main__':
tf_config = some_custom_config
sess = tf.Session(config=tf_config)
set_session(sess)
base_model = VGG16(weights=model_weights, include_top=True)
model = Model(inputs=base_model.input,
outputs=base_model.get_layer(layer).output)
graph = tf.get_default_graph()
app.run()
使用gunicorn啟動(dòng)服務(wù)命令:
gunicorn -c gunicorn_conf.py flask_feature:app
4、遇到的問(wèn)題
在此記錄整個(gè)部署工作中遇到的問(wèn)題及對(duì)應(yīng)解決方法。
4.1 Flask 多線程與多進(jìn)程問(wèn)題
由于對(duì)算法的時(shí)間性能要求較高,因此嘗試使用 Flask 自帶的多線程與多進(jìn)程選項(xiàng)測(cè)試效果。在Flask的app.run()函數(shù)中,上面有介紹到processes參數(shù),用于指定開(kāi)啟的多進(jìn)程數(shù)量,threaded參數(shù)用于指定是否開(kāi)啟多線程。
flask 開(kāi)啟 debug 模式,啟動(dòng)服務(wù)時(shí),dubug 模式會(huì)開(kāi)啟一個(gè) tensorflow 的線程,導(dǎo)致調(diào)用 tensorflow 的時(shí)候,graph 產(chǎn)生了錯(cuò)位。
4.1 Flask 與 Keras 問(wèn)題
使用 Flask 啟動(dòng)服務(wù)的時(shí)候,將遇到的問(wèn)題及參考的資料記錄在此。
Q1:Tensor is not an element of this graph
錯(cuò)誤信息:
"Tensor Tensor(\"pooling/Mean:0\", shape=(?, 1280), dtype=float32) is not an element of this graph.",
描述:使用Keras中預(yù)訓(xùn)練模型進(jìn)行圖像分類特征提取的代碼可以正常跑通,當(dāng)通過(guò)Flask來(lái)啟動(dòng)服務(wù),訪問(wèn)預(yù)測(cè)函數(shù)時(shí),出現(xiàn)上述錯(cuò)誤。
原因:使用了動(dòng)態(tài)圖,即在做預(yù)測(cè)的時(shí)候,加載的graph并不是第一次初始化模型時(shí)候的Graph,所有里面并沒(méi)有模型里的參數(shù)和節(jié)點(diǎn)等信息。
有人給出如下解決方案:
import tensorflow as tf
global graph, model
graph = tf.get_default_graph()
#當(dāng)需要進(jìn)行預(yù)測(cè)的時(shí)候
with graph.as_default():
y = model.predict(x)
Q2:使用 Flask 啟動(dòng)服務(wù),加載兩次模型,占用兩份顯存
出現(xiàn)該問(wèn)題的原因是使用Flask啟動(dòng)服務(wù)的時(shí)候,開(kāi)啟了 debug 模式,即debug=True。dubug模式會(huì)開(kāi)啟一個(gè)tensorflow的線程,此時(shí)查看 GPU 顯存占用情況,會(huì)發(fā)現(xiàn)有兩個(gè)進(jìn)程都占用相同份的顯存。
關(guān)閉 debug 模型(debug=False)即可。
參考資料:
[1]:Keras + Flask 提供接口服務(wù)的坑~~~[4]
4.2 gunicorn 啟動(dòng)服務(wù)相關(guān)問(wèn)題
當(dāng)使用 gunicorn 啟動(dòng)服務(wù)的時(shí)候,遇到以下問(wèn)題:
Q1: Failed precondition
具體問(wèn)題:
2 root error(s) found.\n
(0) Failed precondition: Error while reading resource variable block5_conv2/kernel from Container: localhost. This could mean that the variable was uninitialized. Not found: Container localhost does not exist. (Could not find resource: localhost/block5_conv2/kernel)\n\t [[{{node block5_conv2/convolution/ReadVariableOp}}]]\n\t [[fc2/Relu/_7]]\n
(1) Failed precondition: Error while reading resource variable block5_conv2/kernel from Container: localhost. This could mean that the variable was uninitialized. Not found: Container localhost does not exist. (Could not find resource: localhost/block5_conv2/kernel)\n\t [[{{node block5_conv2/convolution/ReadVariableOp}}]]\n0 successful operations.\n0 derived errors ignored."
解決方法:
通過(guò)創(chuàng)建用于加載模型的會(huì)話的引用,然后在每個(gè)需要使用的請(qǐng)求中使用 keras 設(shè)置 session。具體如下:
from tensorflow.python.keras.backend import set_session
from tensorflow.python.keras.models import load_model
tf_config = some_custom_config
sess = tf.Session(config=tf_config)
graph = tf.get_default_graph()
# IMPORTANT: models have to be loaded AFTER SETTING THE SESSION for keras!
# Otherwise, their weights will be unavailable in the threads after the session there has been set
set_session(sess)
model = load_model(...)
# 在每一個(gè)request中:
global sess
global graph
with graph.as_default():
set_session(sess)
model.predict(...)
有網(wǎng)友分析原因:tensorflow的graph和session不是線程安全的,默認(rèn)每個(gè)線程創(chuàng)建一個(gè)新的session(不包含之前已經(jīng)加載的 weights, models 等)。因此,通過(guò)保存包含所有模型的全局會(huì)話并將其設(shè)置為在每個(gè)線程中由keras使用,可以解決問(wèn)題。
有網(wǎng)友提取一種改進(jìn)方式:
# on thread 1
session = tf.Session(graph=tf.Graph())
with session.graph.as_default():
k.backend.set_session(session)
model = k.models.load_model(filepath)
# on thread 2
with session.graph.as_default():
k.backend.set_session(session)
model.predict(x, **kwargs)
這里的新穎性允許(一次)加載多個(gè)模型并在多個(gè)線程中使用。默認(rèn)情況下,加載模型時(shí)使用“默認(rèn)”Session和“默認(rèn)”graph。但是在這里是創(chuàng)建新的。還要注意,Graph存儲(chǔ)在Session對(duì)象中,這樣更加方便。
測(cè)試了一下好像不行
Q2:無(wú)法啟動(dòng)服務(wù),CRITICAL WORKER TIMEOUT
當(dāng)使用 gunicorn 啟動(dòng) flask 服務(wù)時(shí),查看服務(wù)器狀態(tài)和日志文件發(fā)現(xiàn)一直在嘗試啟動(dòng),但是一直沒(méi)有成功。
CRITICAL WORKER TIMEOUT
這是 gunicorn 配置參數(shù)timeout導(dǎo)致的。默認(rèn)值為30s,即超過(guò) 30s,就會(huì) kill 掉進(jìn)程,然后重新啟動(dòng)restart。
當(dāng)啟動(dòng)服務(wù)進(jìn)行初始化的時(shí)間超過(guò) timeout 值時(shí),就會(huì)一直啟動(dòng),kill, restart。
可根據(jù)具體情況,適當(dāng)增加該值。
參考資料
Linux查看CPU和內(nèi)存使用情況: https://www.cnblogs.com/mengchunchen/p/9669704.html
[2]三個(gè)目前最火的Python Web開(kāi)發(fā)框架,你值得擁有!: https://yq.aliyun.com/articles/700673
[3]gunicorn/example_config.py at master · benoitc/gunicorn: https://github.com/benoitc/gunicorn/blob/master/examples/example_config.py
[4]1]:[Keras + Flask 提供接口服務(wù)的坑~~~: https://www.cnblogs.com/svenwu/p/10189557.html
[5]tensorflow - GCP ML-engine FailedPreconditionError (code: 2) - Stack Overflow: https://stackoverflow.com/questions/55632876/gcp-ml-engine-failedpreconditionerror-code-2

