查看原文
其他

搜狐新闻客户端Android端侧双擎Hybrid AI框架探索

搜狐新闻 邓松岳 搜狐技术产品 2021-11-19

  

本文字数:2917

预计阅读时间:24分钟



前言


人工智能,深度学习,机器学习,当今已经很广泛的应用到了手机端APP。无论是各类修图软件上的各种抠图美化,实时变装。还是社交软件的图片自动匹配文案。抑或是输入法程序和微信上的语音文字互转。以及各类消费应用上的人工智能客服,高效收集和处理客户的反馈。


——这些都用到了端侧人工智能机器学习的解决方案。



而把机器学习的结果,部署到手机端,实现端侧软件的AI化,传统解决方案有三种。让我们用一个小小的例子来看看这三种方案:

假如我们要实现一个最简单的AI功能:根据图片生成对应文案。如下图所示,当用户从图库中选择了一张电动跑车的图片,并准备发布一个原创内容feed。程序自动的识别到图片中主体构图是一辆跑车,并且扫描后台,发现有当前正在热议的社交话题,非常贴切这个图片,于是提醒用户,是否需要带上话题:

这个简单功能,为用户发的内容带来了更多的曝光机会,同时为话题运营也创造了更多的内容,一举两得。


那么,怎样实现类似的功能呢?


实现这个功能,传统的三种方案是这样的:


• 方案1.首先,客户端采集压缩图片,做预处理。然后上传图片到云端存储,云存储再利用AI服务器算力,使用物体识别,物体分类等成熟的模型识别图片中的物体,再匹配合适的文案。最后下发到客户端。

• 方案2.客户端自己训练实现一个端侧模型,大多利用类似Tensor Flow Lite,Paddle Paddle之类的支持Arm架构 cpu芯片的端侧AI框架,使用内置到应用内部的模型。需要识别物体时,由App加载AI引擎,引擎再加载预先训练好的模型,模型推演出结果以后,在手机端完成识别和展示。



• 方案3.客户端在不同系统的手机上,利用手机系统内置的AI能力,如华为手机上的EMUI内置的HiAI,调用系统接口。通常内置一些AI能力的手机,都有系统级的最优算力方案,可以调度NPU,而不是使用CPU或者GPU,可以更快速的利用系统算力完成识别,再在应用端做结果展示。


这三种方案各有缺点。
 
第一种,可以适配各种高低端机型,但是需要使用用户流量上传大图片,因为需要大量流量传递,所以如果需要摄像头数据逐帧处理并回显,这种方案是无法实现的,并且上传成功以后,识别也需要耗费云端的算力资源,并且还有一定的隐私风险。
 
第二种,一般情况可以适配大部分机型,但是会使用手机的cpu或者gpu资源,增加功耗,并且内置模型会增加客户端安装包体积。最重要的一点是,它对开发者有很高的要求,需要开发者即是人工智能算法工程师又是客户端前端工程师。
 
而第三种,虽然可以很好的调度npu,拥有更好的性能,并且对客户端开发者更友好,但是可用机型很少,比如HiAI,只能用在华为的部分手机上,并且不同ROM版本还有不同的AI能力。



优点整合的Hybrid AI Engine


在这个大前提下,搜狐新闻hybrid双擎AI客户端引擎框架应运而生。框架把常用的AI功能作为独立的可扩展模块,提供给上层应用开发者。利用Tensor Flow Lite和HiAI双引擎,做出决策,再由Hybrid AI Engine由速度优先,结果优先,性能优先几种模式做决策整合纠偏,最终提供给应用层推理的结果。


架构思路如下图所示

应用层和AI算法层之间,加入了一层框架适配层,应用开发者,只需要使用我们提供的AI能力接口,不需要关心这些能力,是云端实现还是客户端实现或者是手机系统实现。也不需要去专门设计训练对应的模型。更不需要考虑模型碎片化的问题,适配各种机型乃至处理器。极大的解放了应用开发者的手脚,让应用开发者专注与应用层面的创新,算法工程师专注于算法模型的训练和优化。

Talk is cheap, show me the code. 实战演示


光说不练假把式,让我们回到最初提出的例子,来实现一下识图功能。


实际的应用开者,把双擎Hybrid AI引擎编译集成到app端以后,只需要再加入下面几行代码,就可以实现从图片Bitmap中获取对应文案的功能:

AIHelperFactory aiHelperFactory =
        AIHelperFactory.getInstance(context);
aiHelperFactory.init(AIHelperFactory.AI_TOOL_RECOGNIZER);
……
if (AIHelperFactory.getInstance(context).isReady(AIHelperFactory.AI_TOOL_RECOGNIZER)) {
    discription = AIHelperFactory.getInstance(context).getRecognizer().recognize(recognizeBitmap);
}
……     AIHelperFactory.getInstance(context).release(AIHelperFactory.AI_TOOL_RECOGNIZER);

可以看到这个调用封装十分精简,中间的模型加载和推算已经对开发者完全透明,假如使用TensorFlowLite完成同样的工作,需要下面这段冗长的代码:

private void initModel(Context context) {
    final Context appContext = context.getApplicationContext();
    ModelControler.getInstance(appContext).prepareModels(TF_MODEL_NAME, new ModelControler.ModelDownloadListener() {
        @Override
        public void onModelReady() {
            String modelPath =
                    ModelControler.getInstance(appContext).getModelPath(TF_MODEL_NAME);
            recreateClassifier(appContext, modelPath);
        }

        @Override
        public void onModelDownloadFailed() {
            // TODO: retry?
        }
    });
}

public void recreateClassifier(Context context, String modelPath) {
    if (mClassifier != null) {
        LOGGER.d("Closing mClassifier.");
        mClassifier.close();
        mClassifier = null;
    }

    try {
        mClassifier =
                TFLiteObjectDetectionAPIModel.create(
                        modelPath + TF_OD_API_MODEL_FILE,
                        modelPath + TF_OD_API_LABELS_FILE,
                        mInputSize,
                        TF_OD_API_IS_QUANTIZED);
        mRecognizedBitmap =
                Bitmap.createBitmap(
                        mInputSize, mInputSize, Bitmap.Config.ARGB_8888);
    } catch (final IOException e) {
        e.printStackTrace();
    }
}

private List<Recognition> recognizeQualify(Bitmap sourceBitmap) {
    // NOTICE: this should be invoked in working thread
    if (mClassifier == null || mRecognizedBitmap == null) {
        return null;
    }

    Matrix frameToCropTransform =
            ImageUtils.getTransformationMatrix(
                    sourceBitmap.getWidth(),
                    sourceBitmap.getHeight(),
                    mInputSize,
                    mInputSize,
                    0,
                    true);

    Matrix cropToFrameTransform = new Matrix();
    frameToCropTransform.invert(cropToFrameTransform);
    final Canvas canvas = new Canvas(mRecognizedBitmap);
    canvas.drawBitmap(sourceBitmap, frameToCropTransform, null);

    List<Recognition> results = mClassifier.recognizeImage(mRecognizedBitmap);
    List<Recognition> qualifyResults = new ArrayList<>();
    if (results.size() > 0) {
        for (Recognition result : results) {
            if (result.getConfidence() >= MINIMUM_CONFIDENCE_TF_OD_API) {
                qualifyResults.add(result);
            }
        }
        return qualifyResults;
    } else {
        return null;
    }
}

@Override
public String recognize(Bitmap sourceBitmap) {
    List<Recognition> results = recognizeQualify(sourceBitmap);
    List<String> labels = new ArrayList<>();
    for (Recognition result : results) {
        labels.add(mTFTranslater.translate(result));
    }
    return mDescriptionCreator.getDescription(labels);
}

那么这精简调用的背后,详细是怎么实现的呢?


首先,根据需求初始化双擎AI框架,在收到客户端请求之后,会优先检查系统端有不有相近的系统级实现,如果有,会先调用系统接口,快速的返回结果。


接着会使用Tensor Flow Lite,加载对应功能的处理模型,使用客户端侧返回的结果,对系统返回结果进行补充或者纠偏。

public static AIHelperFactory getInstance(Context context) {
    if (sInstance != null) {
        return sInstance;
    }

    sInstance = new AIHelperFactory(context);
    return sInstance;
}

public void init(final int... tools) {
    if (HiAIUtils.isHuaweiAIAppInstalled(mContext)) {
        VisionBase.init(mContext, new ConnectionCallback() {
            @Override
            public void onServiceConnect() {
                for (int tool : tools) {
                    createTools(tool);
                }
            }

            @Override
            public void onServiceDisconnect() {
                release();
            }
        });
    } else {
        for (int tool : tools) {
            createTools(tool);
        }
    }
}

TFlite的模型还支持在线下载和更新。模型在线更新的机制,可以更好的缩窄应用场景,优化模型体积,同时更好的配合细化场景运营。


同样是“AI识图”的例子,用户不同时间上传同一张“山水”的风景图片,我们可以配合当前客户端正在运营的活动,配以不同的AI识图模型,给用户返回不同的结果,可以是#无人机大赛#相关的,也可以是#小长假旅游风景照#。做到差异化,精细化的为实际项目引流的作用。


接着,应用端只用直接调用初始化好的功能模块提供的识别图片方法,获取图片内容:

public Recognizer(Context context) {
    if (HiAIUtils.isHuaweiAIAppInstalled(context)) {
        mApi = new RecognizerHiAI(context);
    } else {
        mApi = new RecognizerTF(context);
    }
}

public String recognize(Bitmap bitmap) {
    if (mApi != null) {
        return mApi.recognizeTarget(bitmap);
    }
    return null;
}

从应用端只看到一个api的interface,实际的实现,可能是两种,HiAI或者Tensor Flow,各自实现了对应模块的功能接口:

public class RecognizerHiAI extends RecognizerApi {
……
@Override
protected String recognizeTarget(Bitmap sourceBitmap) {
    if (!isReady()) {
        return null;
    }
    VisionImage vi = VisionImage.fromBitmap(sourceBitmap);
    Scene sceneResult = new Scene(Scene.UNKNOWN);
    int result = mSceneDetector.detect(vi, sceneResult, null);
    if (result != 0) {
        Log.e(TAG, "detect failed:" + result);
        return null;
    }

    return mHiAITranslater.translate(sceneResult.getType());
}
……
}

public class RecognizerTF extends RecognizerApi {
……
private List<Recognition> recognizeQualify(Bitmap sourceBitmap) {
    // NOTICE: this should be invoked in working thread
    if (mClassifier == null || mRecognizedBitmap == null) {
        return null;
    }

    Matrix frameToCropTransform =
            ImageUtils.getTransformationMatrix(
                    sourceBitmap.getWidth(),
                    sourceBitmap.getHeight(),
                    mInputSize,
                    mInputSize,
                    0,
                    true);

    Matrix cropToFrameTransform = new Matrix();
    frameToCropTransform.invert(cropToFrameTransform);
    final Canvas canvas = new Canvas(mRecognizedBitmap);
    canvas.drawBitmap(sourceBitmap, frameToCropTransform, null);

    List<Recognition> results = mClassifier.recognizeImage(mRecognizedBitmap);
    List<Recognition> qualifyResults = new ArrayList<>();
    if (results.size() > 0) {
        for (Recognition result : results) {
            if (result.getConfidence() >= MINIMUM_CONFIDENCE_TF_OD_API) {
                qualifyResults.add(result);
            }
        }
        return qualifyResults;
    } else {
        return null;
    }
}

……
}

最后,识别结束,如果不需要再识别的话,可以调用释放资源接口。释放加载到内存的模型文件,完成识别。

public void release(final int... tools) {
    for (int toolId : tools) {
        if (toolId == AI_TOOL_RECOGNIZER || toolId == AI_TOOL_ALL) {
            if (mRecognizer != null) {
                mRecognizer.release();
                mRecognizer = null;
            }
        }

        if (toolId == AI_TOOL_ID_DETECTOR || toolId == AI_TOOL_ALL) {
            if (mIDCardDetector != null) {
                mIDCardDetector.release();
                mIDCardDetector = null;
            }
        }

        if (toolId == AI_TOOL_FACE_COMPARE || toolId == AI_TOOL_ALL) {
            if (mFaceCompare != null) {
                mFaceCompare.release();
                mFaceCompare = null;
            }
        }

 ………
 }
}


Last but not least未来展望


客户端架构选择使用了TFlite的模型,算法工程师可以方便的把自己各种架构的模型转换成Tensor Flow Lite兼容模式。这样算法和客户端可以做到完美的解耦合,让各个领域的专家从事他们所擅长的工作,提高效率,中间的自动适配和翻译工作交给框架来维护。


除了图片识别这类基础功能,双擎AI框架还已经实现了:人脸识别,人脸比较,证件识别,语音输入…并且,各个模块完全独立,可以实时扩展更新增加新的模块。基础SDK包只有100K左右的体积,其他模块都可以通过在线下载的方式支持。对于apk本身的负担极小。


Hybrid AI Engine是端侧AI方案的一次整合,目前还在beta版本迭代开发中,这是一个开发友好度很好的支持库,应用端工程师可以不用深入了解人工智能机器学习的相关知识,也可以通过我们提供好的接口,快速的开发炫目的让人眼前一亮的“智能”的功能。真正release 1.0版本之后,我们打算彻底开源整个项目,欢迎届时大家一起来开发更多的更有意思的扩展模块。




也许你还想看

(▼点击文章标题或封面查看)

RocketMQ中台化建设

2021-01-14

带你实现完整的视频弹幕系统

2021-01-07

llvm 编译器高级用法:第三方库插桩

2020-12-10

【文末有惊喜!】Hive SQL血缘关系解析与应用

2020-12-03

如何实现图片的扭曲效果,窗帘效果及仿真水波纹效果,修图技术之瘦身瘦脸效果的实现(android-drawBitmapMesh)

2020-11-26


加入搜狐技术作者天团

千元稿费等你来!

👈 戳这里!




: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存