机器学习管道实践 ML Pipeline:4. Flask部署

机器学习管道实践 ML Pipeline:4. Flask部署

我们将通过一系列文章学习机器学习管道(Machine Learning Pipeline)的一个实例。这一章节我们主要描述如何通过Flask进行简单的部署,以及如何通过使用python anywhere将我们上一章节的flask应用运行在云端。



(建议先从第一篇博客看起。)

0 搭建虚拟环境

首先,我们在Windows的平台下安装Anaconda3。具体的安装步骤此处略过,参见Anaconda的官方文档。

安装完后,新建虚拟环境。使用conda create -n your_env_name python=X.X(2.7、3.6等)命令创建python版本为X.X、名字为your_env_name的虚拟环境。

这里我输入了conda create -n mlAppFlaskMlopsEnv python=3.8

安装完默认的依赖后,我们进入虚拟环境:conda activate mlAppFlaskMlopsEnv。注意,如果需要退出,则输入conda deactivate。另外,如果Terminal没有成功切换到虚拟环境,可以尝试conda init powershell,然后重启terminal。

然后,我们在虚拟环境中下载好相关依赖:pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

1 Bootstrap

我们首先需要安装Bootstrap4。我们可以在官网直接下载。
当然,Bootstrap也支持在线引用,只是这里,我们选择了下载,然后放于static文件夹内。
另外,我们还需要下载jQuery,将相关的js文件放置于static/js文件夹下。

接下来,我们需要引用Bootestrap。在templates/index.html中,index.html是父模板,我们在head部分进行引入:

<head>
    <title>Image Processing Flask App running with Machine Learning</title>
    <!-- Bootstrap 4 -->
    <link rel="stylesheet" href="/static/css/bootstrap.css">
    <script src="/static/js/bootstrap.js"></script>
    <script src="/static/js/bootstrap.bundle.js"></script>
    <script src="/static/js/jquery-3.5.1.js"></script>
</head>

2 Navigation Bar

我们打开Bootstrap的官网(英文的),然后在documentation一栏中搜索navbar,你会看到对应的提示,在点进去之后的网址中,找到合适的例子,比如这个

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">Navbar</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarNav">
    <ul class="navbar-nav">
      <li class="nav-item active">
        <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Features</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#">Pricing</a>
      </li>
      <li class="nav-item">
        <a class="nav-link disabled" href="#">Disabled</a>
      </li>
    </ul>
  </div>
</nav>

它出来的效果就类似于

在这里插入图片描述

我们对它稍加改动,放进index.html中:

<nav class="navbar navbar-expand-lg navbar-dark" style="background-color:#2C3E50;">
<a class="navbar-brand" href="/">Image Classification App</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
    <div class="navbar-nav">
    <a class="nav-item nav-link" href="/">Home <span class="sr-only">(current)</span></a>
    <a class="nav-item nav-link" href="/about/">About</a>
    </div>
</div>
</nav>

3 模板继承

刚才我们对index.html进行了编写,这个文件其实就是一个父模板,那么,如果我们想新建新的文件,比如upload.html,如何对index.html进行继承呢?很简单,只需要在upload.html的第一行写好{% extends 'index.html' %}即可。

另外,在upload.html中需要改动的部分需要在index.html提前留位置,比如在index.html中先填上

{% block body %}
                
{% endblock  %}

中间可以什么都不加,然后在upload.html中也写上这两行,中间加上你想加的内容即可。

4 读取图片文件

接下来,我们希望可以增加一个功能,即读取图像文件。首先回到Bootstrap的官网,搜索Tabs,找到对应的例子。 Tab其实指的就是某个标签:

<ul class="nav nav-tabs">
  <li class="nav-item">
    <a class="nav-link active" href="#">Active</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" href="#">Link</a>
  </li>
  <li class="nav-item">
    <a class="nav-link disabled" href="#">Disabled</a>
  </li>
</ul>

我们稍作修改,加到upload.html中:

<ul class="nav nav-tabs">
  <li class="nav-item">
    <a class="nav-link active" id="uploadimg" href="#upload-image">Upload Image</a>
  </li>
</ul>

关于这个Tab的内容,实际上是由两部分组成:选择文件,以及提交。所以,就对应了两个按钮,一个type是file,一个type是submit。代码如下:

<div class="tab-content">
    <div class="tab-pane active" id="upload-image" role="tabpanel" aria-labelledby="uploadimg">
        <div class="row">
            <div class="col">
                <form action="#" method="POST" enctype="multipart/form-data">
                  <!-- file input -->
                  <p>
                      <input type="file" name="image_name" class="btn" style="background-color: #2471A3; color:azure" required>
                  </p>
                  <!-- Submit button -->
                  <p>
                      <input type="submit" value="submit" class="btn" style="background-color: #2471A3; color:azure">
                  </p>

                  <p align="center"> <strong>Intructions:</strong> <br>
                      Only upload the file with extention ".png", "jpg", ".jpeg"
                      
                  </p>
              
                </form>
            </div>
        </div>
    </div>
</div>

上面介绍了前端的写法,接下来我们介绍后端。

首先需要注意,当我们按下submit按钮后,会返回一个POST格式的URL,因为我们代码中的设定:<form action="#" method="POST" enctype="multipart/form-data">。所以,对应的后端代码中,我们需要增加if request.method == "POST":的设定。即,如果这个if返回是真的,那么说明我们按了按钮。我们可以接收到一个FileStorage对象,在我们加了这行代码之后:upload_file = request.files['image_name']。然后就简单了,不管是提取这个图片的名字,或者图片数据,或者是作为输入通过我们已经训练好的模型(pipeline_model函数)。详细代码如下:

@app.route('/',methods=['GET','POST'])
def index():
    if request.method == "POST":
        upload_file = request.files['image_name']       # i.e. <FileStorage: 'dog_test.jpg' ('image/jpeg')>
        filename = upload_file.filename 
        print('The filename that has been uploaded =',filename)
        # know the extension of filename
        # all only .jpg, .png, .jpeg, PNG
        ext = filename.split('.')[-1]
        print('The extension of the filename =',ext)
        if ext.lower() in ['png','jpg','jpeg']:
            # saving the image (with current working directory)
            path_save = os.path.join(UPLOAD_PATH,filename)  
            upload_file.save(path_save)
            print('File saved sucessfully')
            # send to pipeline model
            results = pipeline_model(path_save,scaler,model_sgd)
            hei = getheight(path_save)
            print(results)
            return render_template('upload.html',fileupload=True,extension=False,data=results,image_filename=filename,height=hei)
        else:
            print('Use only the extension with .jpg, .png, .jpeg')
            return render_template('upload.html',extension=True,fileupload=False)

5 Styling the Page

upload.html文件中,我们对每一个模块外面都加了一个container的class,比如:<div class="container" id="myrow">,然后我们给这个class赋了id值,因为在后面的style中需要使用。

如果我们要给这个class加style,在upload.html文件后面的style模块中写上:

<style>
    #myrow{
        background-color:#EBEDEF;
    }
</style>

效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u2xNntPb-1647609037083)(./img/flask_uploadcontainer.PNG)]

对于其他的模块也是一样的。

6 将图像识别的结果显示在网页

pipeline_model函数的输出是一个dict,包括了概率最高的前5个类:{'dog': 0.223, 'cow': 0.149, 'pigeon': 0.075, 'natural': 0.062, 'lion': 0.058}

现在,我们希望可以将这个结果,连带图片,呈现在网页端,如下图所示

在这里插入图片描述

首先,在flask_app.py中有这么一个返回:return render_template('upload.html',fileupload=True,extension=False,data=results,image_filename=filename,height=hei)。也就是说,当主页返回POST,并且有图片信息,并且这个图片信息经过了分类后,我们会将一些参数,如fileuploadextensiondata等传回upload.html中。

前端对应的代码:

{% if fileupload %}

<div class="container" id='myrow_result'>
    <div class="row">
        <div class="col col-8">
            <table>
                <tr>
                    <th>Label</th>
                    <th>Confidence Score</th>
                </tr>
                {% for name,score in data.items() %}
                    <tr>
                        <td>{{ name }}</td>
                        <td>{{ score }}</td>
                    </tr>
                {% endfor %}
            </table>
        </div>
        <div class="col col-4">
            <img src="/static/upload/{{ image_filename }}" alt="uploaded image" width="300" height="{{ height }}">

        </div>
    </div>
</div>

7 pythonanywhere的使用

登录pythonanywhere主页。(没有账号的话可以先注册一个免费的,注册完后如下图)

在这里插入图片描述

接下来,我们准备将flask相关的代码上传。需要注意的是,我们也需要把requirements.txt文件上传。

我们将requirements.txt放进2_flask_app文件夹中,然后做一个zip的压缩包。

然后,我们点击pythonanywhereFiles页面,点击upload a file按钮。

在这里插入图片描述

然后,我们回到pythonanywhereDashboard,点击$Bash。我们就能看到一个terminal的界面。输入ls,我们能看到刚放进去的zip文件了,解压缩:unzip XXX.zip。zip文件中的所有文件都会解压到当前目录下。

下一步,和我们本地的操作一样,我们需要在云端也把requirements都安装一下。所以,我们在terminal中进入2_flask_app文件夹,然后pip3 install -r requirements.txt

安装完后,我们回到pythonanywhereWeb界面,然后点击Add a new web app按钮,如下图

在这里插入图片描述

按照指示一步步走,然后你就能创建一个网站了。网站的命名为:[注册名].pythonanywhere.com。当你点进去的时候,发现显示的是Hello from Flask!。这是因为在我们创建这个网站的时候,系统会新建一个flask_app.py文件,里面都是空的。所以,我们接下来需要把我们的代码放进去。注意文件的路径。如果我们重新运行网页,但是报错,很可能是因为我们导入的model路径问题,就是如下这两行:

UPLOAD_PATH = os.path.join(BASE_PATH,'static/upload/')
MODEL_PATH = os.path.join(BASE_PATH,'static/models/')

具体的问题日志在pythonanywhereWeb页面中可以找到。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破浪会有时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值