搭建自己的AI模型应用网站:JavaScript + Flask-Python + ONNX

1. 前言

本文作者以一个前端新手视角,部署自己的神经网络模型作为后端,搭建自己的网站实现应用的实战经历。目前实现的网页应用有:

  • AI 语音服务主页
  • AI 语音识别
  • AI 语音合成
  • AI CP号码生成器

欢迎大家试用感受,本文将以博客基于GAN的序列号码预测中训练的pytorch模型为例,进行后端和前端搭建,并构建网站,以下是最终成果展示。
在这里插入图片描述
网址:http://www.funsound.cn:5002

2. 相关内容

相关知识点和工具语言如下,希望读者有一定的了解

  • 腾讯云服务器
  • Html + JavaScript 进行UI设计
  • pytorch 模型,onnx 模型导出
  • python flask 后端
  • 多进程服务实现并发访问

3. 后端工作

3.1 pytorch 模型转 onnx 模型

ONNX 模型是通用的NN格式,采用onnx格式将在服务器cpu推理上速度更快。

# 实例化生成器模型
generator = Generator(input_dim, output_dim)

# 加载训练好的生成器模型权重
generator.load_state_dict(torch.load('models/generator_model.pth'))
generator.eval()  # 设置生成器为评估模式

# 导出模型为 ONNX 格式
generator.export_onnx('models/generator_model.onnx', (batch_size, input_dim))

加载onnx模型进行推理

# 加载 ONNX 模型
ort_session = ort.InferenceSession('models/generator_model.onnx')
input_name = ort_session.get_inputs()[0].name
output_name = ort_session.get_outputs()[0].name
input_noise = np.random.randn(batch_size, input_dim).astype(np.float32)
generated_numbers = ort_session.run([output_name], {input_name: input_noise})[0]

基于onnx推理的CP号码生成算法封装成 【generator. LOTTO_GENERATOR】

3.2 多进程onnx服务

网站访问往往是一个多路并发访问场景,面对众多用户的请求,送入待处理,后端采用多进程进行调度。

if __name__ == "__main__":
    from generator import LOTTO_GENERATOR # 我们的gan网络生成算法

    # 初始化worker数量
    nj = 4
    backends = [LOTTO_GENERATOR() for _ in range(nj)]
    workers = init_workers(nj=nj, backends=backends)

    # 获取并打印所有worker的状态
    res = get_workers_state(workers)
    print(res)

    # 提交100个任务
    worker_dir = "demo"
    for _ in range(100):
        task_id = generate_random_string(length=11)  # 生成长度为11的随机字符串作为task_id
        task_dir = f"{worker_dir}/{task_id}"  # 任务目录
        task_inp = generate_random_number_string(length=8)  # 生成长度为8的随机数字字符串作为任务输入
        task_prgs = f'{task_dir}/progress.txt'  # 任务进度文件路径
        task_rst = f'{task_dir}/result.txt'  # 任务结果文件路径
        
        os.system(f'mkdir -p {task_dir}')  # 创建任务目录
        params = {
            'task_id': task_id,
            'task_inp': task_inp,
            'task_prgs': task_prgs,
            'task_rst': task_rst
        }
        submit_task(workers=workers, params=params)  # 提交任务
        time.sleep(0.01)  # 等待10毫秒后提交下一个任务

注意代码中多进程服务处理用户请求采用异步方式,用户提交任务后获取task_id, 主进程不会阻塞, 用户根据task_id来追踪自己的任务进度(task_prgs)和结果(task_rst)。

其中调度方式根据子进程的忙碌情况决定,选取最闲的子进程处理用户请求

def submit_task(workers, params: dict):
    # 找到任务最少的worker
    min_task_worker = min(workers, key=lambda worker: worker.queue.qsize() + worker.working.value)
    min_task_worker.queue.put(params)  # 将任务提交到最少任务的worker队列中
    print(f'assign the task to worker-{min_task_worker.wid}'

3.3 基于Flask搭建http访问接口

我们的后端代码如下,例如我们的ip 是 100.100.123,端口试用5002,则构建了以下http访问接口:
http一般格式: 【http://IP地址:端口/路由】

  • http://100.100.123:5002/ 主页
  • http://100.100.123:5002/lotto 提交任务 【输入:用户幸运数字,输出:task_id】
  • http://100.100.123:5002/get_worker_state 子进程负载状态 【输入:task_id,输出:负载状态】
  • http://100.100.123:5002/get_task_prgs 任务完成进度 【输入:task_id,输出:任务进度】
  • http://100.100.123:5002/get_task_rst 任务结果 【输入:task_id,输出:任务结果】
from flask import Flask, jsonify,render_template,request
from generator import LOTTO_GENERATOR
from workers import *
import datetime
import json 

def get_now_time():
    current_time = datetime.datetime.now()
    return current_time.strftime('%Y-%m-%d %H:%M:%S')

def task_log(text,log_file="TASK.LOG"):
    with open(log_file,'a+') as f:
        print(text,file=f)


app = Flask(__name__)
USER_DIR = "user_data"
TASK_MAP = {}

"""
主页
"""
@app.route('/')
def index():
    return render_template('index.html')


@app.route('/lotto', methods=['POST'])
def predict():
    # 获取客户端信息
    ip = request.remote_addr
    data = request.get_json()

    task_id = ip + "_" + generate_random_string(20)
    user_id = ip
    task_inp = data['luck_num'] # 8位数字字符串 或者 空字符串
    task_dir = "%s/%s/%s" % (USER_DIR, user_id, task_id)
    task_prgs = f'{task_dir}/progress.txt'  # 任务进度文件路径
    task_rst = f'{task_dir}/result.txt'  # 任务结果文件路径
    task_log(f"TIME:{get_now_time()}")
    task_log(f"TASK_ID:{task_id}")
    task_log("")

    # 生成临时文件
    if not os.path.exists(task_dir): os.makedirs(task_dir)
    with open(task_prgs,'wt') as f:
        json.dump([0.0,'start'],f,indent=4)
    TASK_MAP[task_id] = {'task_dir': task_dir,
                         'task_prgs': task_prgs,
                         'task_rst': task_rst, }
    
    # 提交任务
    params = {
            'task_id': task_id,
            'task_inp': task_inp,
            'task_prgs': task_prgs,
            'task_rst': task_rst
        }
    submit_task(workers=workers, params=params)  # 提交任务
    return task_id


"""
获得引擎状态
"""
@app.route('/get_worker_state', methods=['GET'])
def get_worker_state():
    ip = request.remote_addr
    res = {}
    for worker in workers:
        res[worker.wid] = worker.queue.qsize() + worker.working.value
    return res


"""
获得任务进度
"""
@app.route('/get_task_prgs', methods=['POST'])
def get_task_prgs():
    ip = request.remote_addr
    data = request.get_json()
    task_id = data['task_id']
    if task_id not in TASK_MAP:
        return [-1, 'no such task id']
    else:
        task_prgs = TASK_MAP[task_id]['task_prgs']
        with open(task_prgs, 'rt') as f:
            content = json.load(f)
        return content

"""
获得任务结果
"""
@app.route('/get_task_rst', methods=['POST'])
def get_task_rst():
    ip = request.remote_addr
    data = request.get_json()
    task_id = data['task_id']
    if task_id not in TASK_MAP:
        return {}
    else:
        task_rst = TASK_MAP[task_id]['task_rst']
        with open(task_rst, 'rt') as f:
            content = json.load(f)
        return content

if __name__ == '__main__':

    # 初始化worker数量
    nj = 4
    backends = [LOTTO_GENERATOR() for _ in range(nj)]
    workers = init_workers(nj=nj, backends=backends)
    
    app.run(host='0.0.0.0', port=5002)

这样后端就搭建起来啦,这里有4个onnx 模型在后台监听

3.4 python客户端测试

import requests
import time
import json

# 定义服务端地址
server_url = 'http://110.110.123:5002' # 你的服务器和端口
headers = {'Content-Type': 'application/json'}

# 检查服务器 Worker 状态
def check_worker_status():
    response = requests.get(f'{server_url}/get_worker_state')
    if response.status_code == 200:
        worker_status = response.json()
        idle_workers = [wid for wid, status in worker_status.items() if status == 0]
        if idle_workers:
            print("Idle workers available:", idle_workers)
            return True
        else:
            print("No idle workers available.")
            return False
    else:
        print("Failed to get worker status.")
        return False

# 提交任务
def submit_task(json_data):
    if not check_worker_status():
        print("No idle workers available. Task submission failed.")
        return None

    response = requests.post(f'{server_url}/lotto', json=json_data)
    if response.status_code == 200:
        task_id = response.text
        print(f"Task submitted successfully. Task ID: {task_id}")
        return task_id
    else:
        print("Failed to submit task.")
        return None

# 轮询任务进度
def poll_task_progress(task_id):
    while True:
        json_data = {'task_id':task_id}
        response = requests.post(f'{server_url}/get_task_prgs', json=json_data)
        if response.status_code == 200:
            progress, text = response.json()
            print(f"Progress: {progress}, Status: {text}")
            if progress == 1:
                print("Task completed successfully.")
                return True
            elif progress == -1:
                print(f"Task failed: {text}")
                return False
        else:
            print("Failed to get task progress.")
            return False
        time.sleep(3)  # 每3秒查询一次

# 获取任务结果
def get_task_result(task_id):
    json_data = {'task_id':task_id}
    response = requests.post(f'{server_url}/get_task_rst', json=json_data)
    if response.status_code == 200:
        result = response.json()
        print("Task result:", result)
        return result
    else:
        print("Failed to get task result.")
        return None


# 主函数
def main():
    json_data = {'luck_num':""}
    # json_data = {'luck_num':"12345678"}

    # 提交TTS任务
    task_id = submit_task(json_data)
    if not task_id:
        return
        
    # 轮询任务进度
    if poll_task_progress(task_id):
        # 获取任务结果
        result = get_task_result(task_id)

if __name__ == "__main__":
    main()

在这里插入图片描述

访问成功

4. 前端工作

4.1 JavaScript 访问 http 函数

JavaScript 调用 http端口如下:

<script>

        /* 提交任务 */
        function submitTask() {
            var button = document.querySelector("button");
            button.disabled = true;
            button.innerText = "正在生成...";

            var useLuckyNumber = document.getElementById("use_lucky_number").checked;
            var luckInput = document.getElementById("luck_input");
            var luckNum = useLuckyNumber ? luckInput.value : "";
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/lotto", true);
            xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    var taskId = xhr.responseText;
                    checkProgress(taskId);
                } else if (xhr.readyState == 4) {
                    button.disabled = false;
                    button.innerText = "生成";
                    alert("任务提交失败,请重试。");
                }
            };
            xhr.send(JSON.stringify({luck_num: luckNum}));
        }

        /* 检查任务进度 */
        function checkProgress(taskId) {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/get_task_prgs", true);
            xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    var response = JSON.parse(xhr.responseText);
                    var progress = response[0];
                    var status = response[1];
                    // document.getElementById("progress").innerText = "进度: " + progress + ", 状态: " + status;
                    if (progress == 1) {
                        getResult(taskId);
                    } else if (progress == -1) {
                        var button = document.querySelector("button");
                        button.disabled = false;
                        button.innerText = "生成";
                        alert("任务失败: " + status);
                    } else {
                        setTimeout(function() { checkProgress(taskId); }, 3000);
                    }
                }
            };
            xhr.send(JSON.stringify({task_id: taskId}));
        }

        /* 获取任务结果 */
        function getResult(taskId) {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/get_task_rst", true);
            xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    var response = JSON.parse(xhr.responseText);
                    displayResult(response);
                    var button = document.querySelector("button");
                    button.disabled = false;
                    button.innerText = "生成";
                }
            };
            xhr.send(JSON.stringify({task_id: taskId}));
        }

        /* 显示任务结果 */
        function displayResult(response) {
            var frontNumbers = response.front_numbers;
            var backNumbers = response.back_numbers;
            var resultContainer = document.getElementById("result");
            resultContainer.innerHTML = ""; // 清空之前的结果

            for (var i = 0; i < frontNumbers.length; i++) {
                var lotterySet = document.createElement("div");
                lotterySet.className = "lottery-set";
                
                frontNumbers[i].forEach(function(number) {
                    var numberBall = document.createElement("div");
                    numberBall.className = "number-ball front-ball";
                    numberBall.innerText = number;
                    lotterySet.appendChild(numberBall);
                });

                backNumbers[i].forEach(function(number) {
                    var numberBall = document.createElement("div");
                    numberBall.className = "number-ball back-ball";
                    numberBall.innerText = number;
                    lotterySet.appendChild(numberBall);
                });

                resultContainer.appendChild(lotterySet);
            }
        }
    </script>

4.2 制作网页index.html

注意到Flask提供了网页渲染功能,这样我们可以设计我们的主页

@app.route('/')
def index():
    return render_template('index.html')

把上述JS脚本放入index.html 就可以访问后端服务啦,具体html的UI显示,由于代码量很大这里不与展示了,感兴趣同学可以根据上述python客户端的访问逻辑试用GPT为你编写index.html,手机端访问效果如下:

在这里插入图片描述

5. 最后

上述是个人搭建自己网站部署AI应用的简单过程,完整源码后期整理上传,欢迎大家留言关注~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/713323.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大数据—“西游记“全集文本数据挖掘分析实战教程

项目背景介绍 四大名著&#xff0c;又称四大小说&#xff0c;是汉语文学中经典作品。这四部著作历久不衰&#xff0c;其中的故事、场景&#xff0c;已经深深地影响了国人的思想观念、价值取向。四部著作都有很高的艺术水平&#xff0c;细致的刻画和所蕴含的思想都为历代读者所…

MyBatis使用 PageHelper 分页查询插件的详细配置

1. MyBatis使用 PageHelper 分页查询插件的详细配置 文章目录 1. MyBatis使用 PageHelper 分页查询插件的详细配置2. 准备工作3. 使用传统的 limit 关键字进行分页4. PageHelper 插件&#xff08;配置步骤&#xff09;4.1 第一步&#xff1a;引入依赖4.2 第二步&#xff1a;在m…

河南省文化旅游发展相关统计数据(2014-2023年)

数据时间&#xff1a;2014-2023年&#xff0c;近10年 数据格式&#xff1a;excel 数据来源&#xff1a;中国旅游统计年鉴、河南省统计公报 数据内容&#xff1a;包括河南省近10年来游客量、旅游总收入、旅游景区数量&#xff08;包括A级&#xff09;、星级酒店数、旅行社数、公…

mongodb-java apispringboot整合mongodb

mongodb入门mongodb-java api的使用springboot整合mongodb评论 一 MongoDB 1.1 MongoDB简介 ​ MongoDB是一个基于分布式文件存储的数据库。由C语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 ​ MongoDB是一个介于关系数据库和非关系数据库之间的产品&…

双链表——AcWing.827双链表

双链表 定义 双链表是链表的一种&#xff0c;它的每个节点有两个指针&#xff0c;一个指向前一个节点&#xff0c;一个指向后一个节点。这样使得链表可以双向遍历。 运用情况 频繁进行前后双向遍历操作时非常有用&#xff0c;比如在一些需要来回移动处理数据的场景。可以方…

嵌入式学习——Linux高级编程复习(TCP编程)——day44

基于TCP聊天: clientA.c clientB.c socket socket connect bind listen acce…

2024年了! 为什么还在用串口服务器?

在数字化飞速发展的2024年&#xff0c;串口服务器这一看似古老的技术仍然在工业自动化、远程监控和数据通信等领域发挥着重要作用。本文将从串口服务器的定义、功能、优势和使用场景四个方面来探讨&#xff0c;为什么串口服务器在今天仍然被广泛使用。 1. 什么是串口服务器 串口…

基于51单片机的红外计数器-1602显示

一.硬件方案 本设计的主要原理为&#xff1a;红外发射管发射红外线&#xff0c;红外接收管接收红外线&#xff0c;并且接收管当有红外线照射的时候&#xff0c;电阻比较小&#xff0c;当无线外线照射的时候电阻比较大&#xff0c;这样就可以通过一个电压比较器和一个基准电压进…

线程池ThreadPoolExecutor使用指南

线程池ThreadPoolExecutor使用指南 &#x1f9d0;使用线程池的好处是什么&#xff1f; 统一管理&#xff0c;减少资源获取创建的开销&#xff0c;提高利用率。 &#x1f527;线程池的参数 ​ThreadPoolExecutor​ 3 个最重要的参数&#xff1a; ​corePoolSize​ : 任务队列…

Linux系统编程:基础IO

目录 1.C语言文件回顾 2.系统文件I/O 2.1 系统接口介绍 2.2 文件描述符fd 2.3 重定向 2.4 理解缓冲区 2.5 理解文件系统 1.C语言文件回顾 在学习系统文件的操作之前&#xff0c;还记得C语言是如何进行对文件的操作的吗&#xff1f;下面看C语言接口&…

浪潮信息打造业界首款50℃进液温度服务器 PUE逼近理论极限1.0!

在科技飞速发展的今天&#xff0c;浪潮信息以其前瞻性的技术创新思维&#xff0c;再次突破行业极限&#xff0c;推出业界首个支持50℃进液温度的浸没式液冷服务器NF5180G7。这一创新成果不仅展现了浪潮信息在液冷技术领域的深厚实力&#xff0c;更标志着服务器冷却技术的一次重…

【2024亲测无坑】在Centos.7虚拟机上安装Oracle 19C

目录 一、安装环境准备 1、linux虚拟机安装 2、虚拟机快照 3、空间检查&软件上传 二、Oracle软件安装 1.preinstall安装及其他配置准备 2.oracle安装 三、数据库实例的安装 1.netca——网络配置助手 2.dbca——数据库配置助手 四、ORACLE 19C 在linux centos 7上…

基于PPO的强化学习超级马里奥自动通关

目录 一、环境准备 二、训练思路 1.训练初期&#xff1a; 2.思路整理及改进&#xff1a; 思路一&#xff1a; 思路二&#xff1a; 思路三&#xff1a; 思路四&#xff1a; 3.训练效果&#xff1a; 三、结果分析 四、完整代码 训练代码&#xff1a; 测试代码&#…

MySQL 日志(二)

本篇将继续介绍MySQL日志的相关内容 目录 一、二进制日志 简介 注意事项 删除二进制日志 查看二进制日志 二进制日志的格式 二、服务器日志维护 一、二进制日志 简介 二进制日志中主要记录了MySQL的更改事件&#xff08;不包含SELECT和SHOW),例如&#xff1a;表的…

Base64编码的工作原理与实际应用

目录 前言 一、什么是Base64编码&#xff1f; 二、Base64编码的原理 三、Base64编码的应用场景 四、为什么要使用Base 64 五、Base64加密解密的实现 前言 当你需要将二进制数据转换为可传输和存储的文本格式时&#xff0c;Base64编码是一个常用的选择。在这篇博客中&#…

C++ 51 之 继承中的构造和析构

对象构造和析构的调用原则 继承中的构造和析构 子类对象在创建时会首先调用父类的构造函数父类构造函数执行完毕后&#xff0c;才会调用子类的构造函数当父类构造函数有参数时&#xff0c;需要在子类初始化列表(参数列表)中显示调用父类构造函数析构函数调用顺序和构造函数相…

可以用来制作硬模空心耳机壳的胶粘剂有哪些种类?

可以用来制作硬模空心耳机壳的胶粘剂有哪些种类&#xff1f; 制作耳机壳的胶粘剂有很多种类&#xff0c;常见的有环氧树脂胶水、UV树脂胶、快干胶、热熔胶等。 这些胶粘剂都有不同的特点和适用场景&#xff0c;可以根据自己的需求选择合适的类型。 例如&#xff1a; 环氧树脂…

Adobe设计替代软件精选列表

Adobe软件的替代列表&#xff0c;最初由 XdanielArt 收集&#xff0c;并由社区改进。您可以随意打开问题或拉出请求&#xff0c;或从数据中创建图像(以便于共享)。列表总是按照免费和开源选项的顺序排列&#xff0c;但根据您的用例&#xff0c;它可能不是最佳选择 替代因素 &am…

Python 潮流周刊#56:NumPy 2.0 里更快速的字符串函数

△△请给“Python猫”加星标 &#xff0c;以免错过文章推送 本周刊由 Python猫 出品&#xff0c;精心筛选国内外的 250 信息源&#xff0c;为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景&#xff1a;帮助所有读者精进 Python 技术&am…

【linux】认识“文件”的本质,理解“文件系统”的设计逻辑,体会linux优雅的设计理念

⭐⭐⭐个人主页⭐⭐⭐ ~~~~~~~~~~~~~~~~~~ C站最❤❤❤萌❤❤❤博主 ~~~~~~~~~~~~~~~~~~~ ​♥东洛的克莱斯韦克-CSDN博客♥ ~~~~~~~~~~~~~~~~~~~~ 嗷呜~ ✌✌✌✌ 萌妹统治世界~ &#x1f389;&#x1f389;&#x1f389;&#x1f389; ✈✈✈✈相关文章✈✈✈✈ &#x1f4a…