草庐IT

使用Flask服务部署算法模型

爱学习的小奶狗 2023-06-03 原文

1. 使用Flask服务部署算法

最近在公司快吧我恶心死了,好不容易把算法给弄完了,组长又让我把算法给部署到测试服务器上,这让我懵逼了,我刚开始工作没多久,也不知道怎么部署算法。组长就把接口的配置文件,还有让我使用Flask服务,好了就只剩我自己摸索了,话说真的不难,就是开始不会给我恶心到了。

解释说明: 当我们完成了一个算法模型后,我们对这个算法模型进行部署,总不能我们做完了算法,然后直接就打包扔给前后端了吧,人家也不懂啊,人家也不知道如何调用你这个算法啊,你得提供算法模型的接口。因此,为了让后端使用我们的代码,我们需要提供接口,那么这个接口是什么呢???这个接口是让后端输入算法模型的必要参数,算法通过接口获取到参数(数据)然后去调用相应的算法,得到结果后已后端要求的指定格式返回(一般为json格式)。

那么问题来了:我们该怎么样去写这个接口呢???往往后端[java,python等]和算法人员[python,c++等]使用编程语言不一样,而且做算法的也不能去跟后端说算法的内部代码怎么调用,简单的来说我们不能够使用一套代码直接调用。这个时候就想到了http网络的方式,后端通过网络请求的方式将必要的参数或数据发送给我们,我们使用一个服务获取到这个参数和数据,然后自己去调用算法,这就是接口。这个服务,我目前只用到了Flask服务(本人不是做后端,也不知道这里面有啥,只会简单的调用)。

那么这个服务这么编写呢??请看下面。

1.1 Flask服务的运行过程

第一步: 客户端通过浏览器发起HTTP请求。
第二步: Web服务器吧后端的请求交给Flask程序处理。
第三步:Flask处理完毕后,通过指定的方式或格式返回给浏览器。

1.2 Flask服务的一个helloworld

# 导入 Flask 类
from flask import Flask

# 创建了这个类的实例,参数是应用模块或者包的名称,它会指向程序所在的模块,但一般都是传递__name__。
app = Flask(__name__)


# 使用 route() 装饰器来告诉 Flask 触发函数的 URL
@app.route("/")
def hello():
    return "Hello World!"


if __name__ == "__main__":
    # 使用 run() 函数来运行本地服务器和我们的应用
    app.run()

当我们运行这个程序的时候,会出现以下内容:

解释说明: 它告诉了我们通过http://127.0.0.1:5000 这个网址去访问我们的服务,当我们访问后会出现以下内容:

因此我们可以看出,Flask服务主要的主代码主要包含一下内容:

  • 创建Flask实例
  • 使用@app.route(“/”) 装饰某一个函数,以此来获得我们访问这个服务时内部所需要做的事。
  • 运行这个代码:app.run()

题外话:

 在这其中,我们发现我们的访问地址默认是http://127.0.0.1:5000,
 	其中:http://127.0.0.1是网址
 	     5000 是端口号
 我们可以使用如下代码来指定的我们的网络地址和端口号:
 	app.config['HOST'] = '127.0.0.1'
    app.config['PORT'] = 5000
 然后再运行:
 app.run(app.config['HOST'], app.config['PORT'])

2. 使用Flask服务部署算法模型

解释说明:这里我做了一个目标检测的算法,我们通过Flask服务获取到算法的一些参数,调用算法进行计算,然后将结果返回给浏览器(在实际情况中是需要和后端交互的,一般是后端给我们返回的格式)主要过程如下:

  • 通过Flask服务获取到算法的一些参数,和需要检测的图片
  • 调用算法,进行检测计算
  • 返回json格式的算法结果

2.1 接收值和返回值的格式

解释说明: 这里我给出公司在进行算法测试时给的一部分服务的接收值格式和返回值格式。

解释说明:

从表中可以看出,在Flask的服务端,我们需要接受的参数有三个,分别为:
	
	imageType : 这个参数没什么多大的意义,只是告诉我们这个图片是属于检测哪种的图片。
	debug : 这个参数是告诉算法检测后需不需要吧图片保存下来,如果是0,则不需要保存,只需要返回检测后
			的结果图片,如果是1则需要保存。
	imageFile : 客户端传过来的需要检测的图片数据(网页传入当然是数据流啦)
	
返回参数:
	returnStatus : 返回的状态,为0时表示出现问题了,具体是哪些问题会在returnMessage中指出,为1时表示
	                成功,也会在returnMessage中写入成功的内容。
    余下参数不一一介绍了。

2.2 Flask步骤

2.1 Flask服务的过程代码

# -*- coding:utf-8 -*-
# coding=utf8
import logging
import cv2
import numpy as np
from flask import jsonify, Flask, request, Response, render_template
from werkzeug.utils import secure_filename
from werkzeug.exceptions import RequestEntityTooLarge

from detect import BaseDetector

app = Flask(__name__, template_folder='./')
app.config['DEBUG'] = False
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024
# app.config['DATA']='/data/hbpw_uploads'
app.config['DATA'] = '/data/shudian_uploads'

app.config['ALLOWED_EXTENSIONS'] = set(['png', 'PNG', 'jpg', 'JPG', 'jpeg', 'JPEG', 'bmp', 'BMP'])
app.config['JSON_AS_ASCII'] = False
app.config['HOST'] = '127.0.0.1'
app.config['PORT'] = 5000
app.config['JSON_AS_ASCII'] = False
app.config['JSONIFY_MIMETYPE'] = "application/json;charset=utf-8"
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
url = "http://" + app.config['HOST'] + ":" + str(app.config['PORT'])
net = BaseDetector()

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1] in app.config['ALLOWED_EXTENSIONS']

@app.route('/', methods=['GET'])
def index():
    return render_template('test.html')

if __name__ == '__main__':
    app.run(app.config['HOST'], app.config['PORT'], debug=False, threaded=True)

解释说明: 创建了Flask服务,并指定了服务内部的参数,如网络地址和端口号,注意这里的 debug和输入参数的debug是不一样的。当服务开启时,用户访问网址时,我们返回了一个网页test.html,网页地址的效果如下:

网页的源代码如下:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>接口测试</title>
    </head>
    <body align=center>
        <form action='/Upload' method='post' enctype='multipart/form-data'>
            <label>调试模式</label><input name="debug" value="1">
            <select name='imageType'>
                <option value='jcps'>基础设施破损</option>
            </select>
            <input type='file' name='imageFile'>
            <input type='submit' value='上传图像'>
        </form>
    </body>
</html>

解释说明: 我们可以通过选择图片上载图片至网页,点击上传图像将数据推送给Flask服务。
在Flask服务中写一个函数,接受这些数据,然后把需要的东西送入算法中,进行运算,返回给浏览器。
接收函数如下所示:

@app.route('/Upload', methods=['POST'])
def upload():
    try:
        upload_file = request.files['imageFile']
        img_type = request.values['imageType']
        debug = request.values['debug']
        if upload_file == None or img_type == None or debug == None:
            returnStatus = '0'
            returnMessage = '必要参数为空'
            json_result = {'returnStatus': returnStatus, 'returnMessage': returnMessage}
            return jsonify(returnResult=json_result)

        file_name = secure_filename(upload_file.filename)
        if allowed_file(file_name):
            img_stream = upload_file.stream.read()
            img_np_arr = np.frombuffer(img_stream, np.uint8)
            raw_img = cv2.imdecode(img_np_arr, cv2.IMREAD_COLOR + cv2.IMREAD_IGNORE_ORIENTATION)
            json_result = net.run(raw_img, file_name, img_type, app.config['DATA'], url, debug)
            # #json_result=JsonUTF8toUnicode(json_result)
            return jsonify(returnResult=json_result)
        else:
            returnStatus = '0'
            returnMessage = '上传图像格式不匹配'
            json_result = {'returnStatus': returnStatus, 'returnMessage': returnMessage}
            # json_result=JsonUTF8toUnicode(json_result)
            return jsonify(returnResult=json_result)

    except RequestEntityTooLarge as e:
        returnStatus = '0'
        allowedsize = app.config['MAX_CONTENT_LENGTH'] / 1024 / 1024
        returnMessage = '上传图像过大,图像应低于 ' + str(allowedsize) + ' M'
        json_result = {'returnStatus': returnStatus, 'returnMessage': returnMessage}
        return jsonify(returnResult=json_result)

解释说明:
我们采用如下方式接受参数:

upload_file = request.files['imageFile']      #图片数据流
img_type = request.values['imageType']   # 图片类型
debug = request.values['debug']               # debug模式

采用了以下代码调用了算法,至于算法的调用那就是我们的本行了,自行去编写,按照指定的格式返回值就行了。

json_result = net.run(raw_img, file_name, img_type, app.config['DATA'], url, debug) #调用算法

好吧 写的很烂,但这只是我记录这次算法部署的过程。后续还涉及到了算法镜像的打包,docker容器开机自启动步骤等,可以百度的。

有关使用Flask服务部署算法模型的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

随机推荐