最近做的一項任務(wù)顽聂,需要同時運行多個二分類模型(如果這些二分類結(jié)果穩(wěn)定了,即可轉(zhuǎn)為一個多標簽分類模型)么伯。
問題描述
假定分類模型分別為A和B
示例代碼如下:
# 將kerasutil_A 與kerasutil_B 作為全局變量疟暖,可以一次加載多次應(yīng)用,從而提升性能
kerasutil_A = KerasUtil(rawtextpath=r'A.csv',
lstmmodelpath=r'A.h5',
modeltype='bilstm')
kerasutil_B = KerasUtil(rawtextpath=r'B.csv',
lstmmodelpath=r'B.h5',
modeltype='bilstm')
具體的使用方式為:
resultlist = []
if categoryid == 'A':
resultlist = kerasutil_A.predictcategory(sentence)
elif categoryid == 'B':
resultlist = kerasutil_B.predictcategory(sentence)
但是在運行過程中,發(fā)現(xiàn)莫名奇妙的問題俐巴。
比如有兩個與類別B相關(guān)的句子:
sentence_B_1, Positive
sentence_B_2, Negative
在多個model實例加載完畢后骨望,
如果按照sentence_B_1, sentence_B_2的順序去跑,會得到sentence_B_1:Positive, sentence_B_2依然是Positive的結(jié)果欣舵;
如果按照sentence_B_2, sentence_B_1的順序去跑擎鸠,會得到sentence_B_2:Negative, sentence_B_1依然是Negative的結(jié)果;
但是如果只在一個進程加載Model B缘圈,則無論sentence_B_1, sentence_B_2按照什么順序去跑劣光,結(jié)果都是正確的。
事實上糟把,KerasUtil中沒有任何靜態(tài)或類級別的變量绢涡,都是實例級別的變量(每個實例中的屬性都是使用獨立的內(nèi)存地址):
class KerasUtil:
def __init__(self, rawtextpath, lstmmodelpath, modeltype='lstm', multiplelabels=False):
self.max_fatures = 2000
self.modelrawtextpath = rawtextpath
self.modelpath = lstmmodelpath
self.modeltype = modeltype
self.multiplelabels = multiplelabels
self.data = None
self.lstmmodel = None
self.tokenizer = None
self.initialtokenizer()
data,lstmmodel, tokenizer都是在實例化的時候糊饱,才去賦值垂寥,經(jīng)過跟蹤調(diào)試堆棧信息,也能夠得出kerasutil_A與kerasutil_B中的屬性都是獨立另锋,擁有各自特征的值滞项。
這確實是太奇怪了!夭坪!
目前尚不能確定是否是底層的Tensorflow對同一個進程的多個模型的應(yīng)用文判,導(dǎo)致出現(xiàn)類似問題。(模型內(nèi)的張量出現(xiàn)混亂室梅?)
不過事實證明戏仓,基于Tensorflow的Keras在多模型同時運行時,無論是單純的執(zhí)行predict方法(容易出現(xiàn)錯誤亡鼠,需要clear session或者重新加載模型)赏殃,還是執(zhí)行某個模型的分類結(jié)果,都是詭異的间涵。
比較消耗性能的解決方案
將模型的初始化與加載邏輯仁热,放置在應(yīng)用業(yè)務(wù)分類的函數(shù)內(nèi)。
即每次函數(shù)執(zhí)行完畢勾哩,模型對象都會被釋放抗蠢。
def confirmwithmachinelearning(categoryid, sentencelist):
# 用多個model時,出于性能考慮思劳,之前是作為全局變量加載的
# 之后發(fā)現(xiàn)同時run多個文檔的的時候迅矛,結(jié)果居然與單獨去跑某個文檔的結(jié)果不一致
# 猜測是否是底層的tensorflow的張量信息缺失或互相竄了
# 目前這種方式是在函數(shù)內(nèi)加載對應(yīng)模型,雖然慢了幾秒潜叛,但結(jié)果是準確的
kerasutil = None
if categoryid == 'A':
kerasutil = KerasUtil(rawtextpath=r'A.csv',
lstmmodelpath=r'A.h5',
modeltype='bilstm')
if categoryid == 'B':
kerasutil = KerasUtil(rawtextpath=r'B.csv',
lstmmodelpath=r'B.h5',
modeltype='bilstm')
for sentence in sentencelist:
resultlist = kerasutil.predictcategory(sentence)
...
經(jīng)過驗證秽褒,詭異的現(xiàn)象消失了壶硅,類別結(jié)果不會因為多模型加載而造成不同的結(jié)果。
附注:這種方式對于相對較小的模型震嫉,不會帶來性能方面的明顯損耗森瘪,但是對于較大的多個模型共用,建議API分離票堵,才能通過全局變量一次加載多次應(yīng)用的benefit
還有需要注意的地方:
就是模型加載的地方:
我們經(jīng)常會這樣寫:
def initialforlstm(self):
if not os.path.exists(self.modelpath):
self.trainlstmmodel()
self.lstmmodel = load_model(self.modelpath)
elif self.lstmmodel is None:
try:
keras.backend.clear_session()
except Exception as e:
logger.info(e)
logger.info('Load model: {0}'.format(self.modelpath))
self.lstmmodel = load_model(self.modelpath)
在Windows下啟動模型有關(guān)的API扼睬,非常順暢。
但是發(fā)布到LINUX的Dev服務(wù)器悴势,問題來了:
會出現(xiàn)這個問題窗宇,然后就崩了:
segmentation fault
經(jīng)過搜索萬能的谷歌,發(fā)現(xiàn)類似的問題特纤,但沒有解決方案:
Keras segmentation fault on load_model on Linux and not on Windows
不過因為有之前發(fā)布加載Keras模型API的經(jīng)驗军俊,嘗試將clear_session的順序倒置一下(clear_session是必須要的,否則無法在Flask加載任何Keras的模型)
def initialforlstm(self):
if not os.path.exists(self.modelpath):
self.trainlstmmodel()
self.lstmmodel = load_model(self.modelpath)
elif self.lstmmodel is None:
logger.info('Load model: {0}'.format(self.modelpath))
try:
self.lstmmodel = load_model(self.modelpath)
except Exception as e:
logger.info(e)
keras.backend.clear_session()
self.lstmmodel = load_model(self.modelpath)
問題解決捧存,it's done~~~
新的解決方案
如果保存模型的權(quán)重粪躬,然后運行時初始化加載權(quán)重文件,貌似可以解決這個問題昔穴,而且可以在process初始化時镰官,將多個model一次加載,多次應(yīng)用吗货。
具體代碼:
kerasUtil
def initialforlstm(self):
modelfilename = os.path.splitext(os.path.basename(self.modelpath))
modelyamlpath = os.path.join(os.path.dirname(self.modelpath),
'{0}.yaml'.format(modelfilename[0]))
modelweightpath = os.path.join(os.path.dirname(self.modelpath),
'{0}_weight.h5'.format(modelfilename[0]))
if not os.path.exists(self.modelpath):
self.trainlstmmodel()
self.loadmodel(modelyamlpath, modelweightpath)
elif self.lstmmodel is None:
try:
self.loadmodel(modelyamlpath, modelweightpath)
except Exception as e:
logger.info(e)
keras.backend.clear_session()
self.loadmodel(modelyamlpath, modelweightpath)
def loadmodel(self, modelyamlpath, modelweightpath):
if not os.path.exists(modelyamlpath) or not os.path.exists(modelweightpath):
model = load_model(self.modelpath)
yaml_string = model.to_yaml()
open(modelyamlpath, 'w', encoding='utf-8').write(yaml_string)
model.save_weights(modelweightpath)
self.lstmmodel = model_from_yaml(open(modelyamlpath, encoding='utf-8').read())
self.lstmmodel.load_weights(modelweightpath)
else:
logger.info('Load model: {0}, {1}'.format(modelyamlpath, modelweightpath))
self.lstmmodel = model_from_yaml(open(modelyamlpath, encoding='utf-8').read())
self.lstmmodel.load_weights(modelweightpath)
使用Keras模型的業(yè)務(wù)應(yīng)用部分代碼
kerasutil_A = KerasUtil(rawtextpath=r'A.csv',
lstmmodelpath=r'A.h5',
modeltype='bilstm')
kerasutil_B = KerasUtil(rawtextpath=r'B.csv',
lstmmodelpath=r'B.h5',
modeltype='bilstm')
kerasutil_C = KerasUtil(rawtextpath=r'C.csv',
lstmmodelpath=r'C.h5',
modeltype='bilstm')
def confirmwithmachinelearning(categoryid, phrasewithkeylist):
for phrasewithkey in phrasewithkeylist:
sentence = phrasewithkey['sentence']
resultlist = []
if categoryid == 'A':
resultlist = kerasutil_A.predictcategory(sentence)
if categoryid == 'B':
resultlist = kerasutil_B.predictcategory(sentence)
if categoryid == 'C':
resultlist = kerasutil_C.predictcategory(sentence)
...