解决多个Keras Model实例同时加载,导致运行结果混乱的问题

最近做的一项任务,需要同时运行多个二分类模型(如果这些二分类结果稳定了,即可转为一个多标签分类模型)。

问题描述

假定分类模型分别为A和B
示例代码如下:

# 将kerasutil_A 与kerasutil_B 作为全局变量,可以一次加载多次应用,从而提升性能
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)

但是在运行过程中,发现莫名奇妙的问题。
比如有两个与类别B相关的句子:
sentence_B_1, Positive
sentence_B_2, Negative

在多个model实例加载完毕后,
如果按照sentence_B_1, sentence_B_2的顺序去跑,会得到sentence_B_1:Positive, sentence_B_2依然是Positive的结果;
如果按照sentence_B_2, sentence_B_1的顺序去跑,会得到sentence_B_2:Negative, sentence_B_1依然是Negative的结果;

但是如果只在一个进程加载Model B,则无论sentence_B_1, sentence_B_2按照什么顺序去跑,结果都是正确的。

事实上,KerasUtil中没有任何静态或类级别的变量,都是实例级别的变量(每个实例中的属性都是使用独立的内存地址):

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都是在实例化的时候,才去赋值,经过跟踪调试堆栈信息,也能够得出kerasutil_A与kerasutil_B中的属性都是独立,拥有各自特征的值。

这确实是太奇怪了!!

目前尚不能确定是否是底层的Tensorflow对同一个进程的多个模型的应用,导致出现类似问题。(模型内的张量出现混乱?)
不过事实证明,基于Tensorflow的Keras在多模型同时运行时,无论是单纯的执行predict方法(容易出现错误,需要clear session或者重新加载模型),还是执行某个模型的分类结果,都是诡异的。

比较消耗性能的解决方案

将模型的初始化与加载逻辑,放置在应用业务分类的函数内。
每次函数执行完毕,模型对象都会被释放。

def confirmwithmachinelearning(categoryid, sentencelist):
    # 用多个model时,出于性能考虑,之前是作为全局变量加载的
    # 之后发现同时run多个文档的的时候,结果居然与单独去跑某个文档的结果不一致
    # 猜测是否是底层的tensorflow的张量信息缺失或互相窜了
    # 目前这种方式是在函数内加载对应模型,虽然慢了几秒,但结果是准确的
    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)
    ...

经过验证,诡异的现象消失了,类别结果不会因为多模型加载而造成不同的结果。
附注:这种方式对于相对较小的模型,不会带来性能方面的明显损耗,但是对于较大的多个模型共用,建议API分离,才能通过全局变量一次加载多次应用的benefit

还有需要注意的地方:

就是模型加载的地方:
我们经常会这样写:

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下启动模型有关的API,非常顺畅。
但是发布到LINUX的Dev服务器,问题来了:
会出现这个问题,然后就崩了:
segmentation fault
经过搜索万能的谷歌,发现类似的问题,但没有解决方案:
Keras segmentation fault on load_model on Linux and not on Windows

不过因为有之前发布加载Keras模型API的经验,尝试将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~~~

新的解决方案

如果保存模型的权重,然后运行时初始化加载权重文件,貌似可以解决这个问题,而且可以在process初始化时,将多个model一次加载,多次应用。
具体代码:

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模型的业务应用部分代码

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)
        ...

你可能感兴趣的:(解决多个Keras Model实例同时加载,导致运行结果混乱的问题)