分分钟将机器学习包装成HTTP服务
随着人工智能的热度不断提高,越来越多的程序员开始进行相关的研发和应用,机器学习是人工智能的重要组成部分,也是大多数人学习人工智能的首选途径。本文从一个传统程序员角度来介绍机器学习,重实战而轻理论,因此阅读本文您需要有以下技能:
- 基本的编程能力(至少能看懂简单的python代码)
- 初中的数学能力(是的,不需要懂得算法、高数、统计等)
环境准备
- Linux环境,如果没有就快装个虚拟机吧。
- 在linux上安装sklearn,sklearn是机器学习的工具包框架,官网 https://scikit-learn.org/ 和网站 https://www.sklearncn.cn/ 有更多sklearn的信息(包括如何安装)。
示例程序来自于 Module Proxy(v0.5)使用手册 ,同时Module Proxy也是将python函数包装成HTTP服务的工具。
机器学习小栗子
1 import json 2 import socket 3 import pickle 4 import pandas as pd 5 import numpy as np 6 from sklearn.linear_model import LinearRegression 7 8 # 模型训练函数 9 def training(dct): 10 df = pd.DataFrame(dct) #dict转Pandas DataFrame 11 #训练数据 12 X = df[['Fahrenheit']] 13 y = df['Celsius'] 14 #训练 15 model = LinearRegression() #使用线性回归算法 16 model.fit(X, y) #拟合 17 #存储训练模型 18 file_name = "/tmp/model" #文件路径 19 with open(file_name, 'wb') as file: 20 pickle.dump(model, file) #保存模型
假定有华氏度和摄氏度两组数据,上面的模型训练函数用来找出它们之间的转换规则。
华氏度 | -40 | 14 | 32 | 46 | 59 | 72 | 100 |
摄氏度 | -40 | -10 | 0 | 8 | 15 | 22 | 38 |
我们当然知道转换规则有个简单公式:华氏度 = 摄氏度 * 1.8 + 32
,但是机器并不知道,它甚至不知道这个函数的目的。机器只明白这里有两组数据,并根据一定的算法(代码中LinearRegression()表示使用了线性回归算法)匹配出之间的规律。就像我们在刚开始学习一次、二次函数时,老师在黑板上的平面坐标系中标点几组(x,y)坐标,根据这些标点勾画出直线或曲线,同样机器依据这个表格中的数据,华氏度(x),摄氏度(y)进行标点,在机器学习中称为拟合,形成最终的函数图像称为模型(model)。这里对拟合、模型的解释并不那么精确,但应该更容易去理解。训练出模型后把它存入文件/tmp/model以供使用,这样就可以根据模型输入一个华氏度计算出摄氏度,这个过程在机器学习中称为预测。
1 # 数据预测函数 2 def predict(dct): 3 #从文件中获取刚才训练的模型 4 file_name = "/tmp/model" 5 with open(file_name, 'rb') as file: 6 model=pickle.load(file) 7 #预测数据 8 df = pd.DataFrame(dct) 9 X = df[['Fahrenheit']] 10 #预测 11 y = model.predict(X) 12 #返回预测结果 13 df['Celsius'] = y 14 return df
现在已有了训练和预测两个函数,接下来运行一下,看看机器学习的效果。
1 if __name__ == "__main__": 2 dct = {"Fahrenheit":[-40, 14, 32, 46, 59, 72, 100],"Celsius":[-40, -10, 0, 8, 15, 22, 38]} #训练数据 3 training(dct) #调用训练函数 4 dct = {"Fahrenheit":[68]} #预测数据 5 ret = predict(dct) #调用预测函数并得到返回值 6 print(ret)
if __name__ == "__main__": 在python中是程序的开始,就像C语言或Java语言的main函数。这里把一组华氏度(Fahrenheit)和另一组摄氏度(Celsius)组成一个结构dct,传递给训练函数training,在training内部用华氏度和摄氏度构建训练集X和目标y,套用线性回归算法进行拟合,得到他们之间的规律model,并存储到文件中。
然后把华氏度68交给预测函数predict进行预测,predict内部加载刚才的model,利用model进行预测,并返回预测结果。
以上两个函数,既是机器学习最重要的两个环节:训练、预测。
可以把训练和预测看作是对数学函数 y = f(x) , 即y、x、函数规则三者间的关系描述:
训练: 通过y、x获得函数规则(model)
预测: 通过函数规则和x计算y
运行截图如下:
计算器验证下,机器学习预测的摄氏度还是比较精确的。上面这个小程序目前只能自己来使用,如果想让其他人也能用需要把它包装成服务。
包装成TCP Socket服务
修改成TCP Socket服务,其他人可以通过网络来调用:
1 if __name__ == "__main__": 2 sock = socket.socket() 3 sock.bind(('', 8888)) 4 sock.listen() 5 print("server start... listen port: 8888") 6 while True: 7 conn, addr = sock.accept() 8 # 接收数据 9 msg_bytes = conn.recv(4096) 10 msg = msg_bytes.decode('utf-8') 11 # 解析json 12 obj = json.loads(msg) #json to obj 13 method = obj["method"] #函数名 14 data = obj["data"] #函数参数 15 #调用函数 16 if method == "training": 17 training(data) 18 ret = "{\"status\":true}" 19 else: 20 dct = predict(data) 21 ret = dct.to_json() 22 #返回 23 ret_json_bytes = bytes(ret, encoding='utf-8') #str to bytes 24 conn.send(ret_json_bytes)
Socket服务侦听端口8888,使用者(TCP客户端)向8888端口发送请求数据,服务端根据数据内容调用训练或预测函数,再把结果返回给客户端。 要实现这些功能需要客户端和服务端事先设计出通信的数据格式,双方都按此格式发送和接收数据,这种格式有个专业名称:接口协议。在我们的小栗子中要求的协议必须是json数据格式,并且客户端请求的数据格式如下:
#请求训练的数据格式 {"method":"training","data":{"Fahrenheit":[-40, 14, 32, 46, 59, 72, 100],"Celsius":[-40, -10, 0, 8, 15, 22, 38]}} #请求预测的数据格式 {"method":"predict","data":{"Fahrenheit":[68, 97]}}
可以看到,数据被分成了两部分,method表示要调用哪个函数,data是给这个函数传递的参数。 现在服务和协议都设定好了,编写一个客户端程序来验证这一切,考虑到java使用较为广泛,测试客户端就用java编写了:
1 /** Example.java **/ 2 //训练请求json 3 String json = "{\"method\":\"training\",\"data\":{\"Fahrenheit\":[-40, 14, 32, 46, 59, 72, 100],\"Celsius\":[-40, -10, 0, 8, 15, 22, 38]}}"; 4 //预测请求json 5 //String json = "{\"method\":\"predict\",\"data\":{\"Fahrenheit\":[68, 97]}}"; 6 Socket socket = new Socket("127.0.0.1", 8888); //tcp连接服务端 7 OutputStream out = socket.getOutputStream(); 8 out.write(json.getBytes()); //发送 9 out.flush(); 10 byte[] recvBuf = new byte[4096]; //接收缓存 11 InputStream in = socket.getInputStream(); 12 in.read(recvBuf); //接收返回数据 13 System.out.println(new String(recvBuf).trim());
切换代码中的请求json(训练请求和预测请求json),编译后执行,java的运行截图秀一下:
改善TCP通信细节
细心观察在上面的Python和Java代码中,接收数据时都使用了4096的接收参数。TCP通讯时需要先把数据存储在缓存中,这里表示使用4096字节的接收缓存。 对于当前的小栗子这个缓存足够用了,但对于服务而言需要考虑稳定可靠,比如某个头脑发热的使用者一次性发来10万个华氏度数据来预测,4096字节肯定是不够用了,结果是数据接收出错或引起缓冲区溢出的安全问题。
解决这个问题常用的做法是在发送数据之前,先发送数据长度再发送数据; 接收端也先接收数据长度,再根据长度创建缓存来接收数据。那么数据长度的格式也就成为了协议的一部分,看起来是这个样子:
________50\r\n {"method":"predict","data":{"Fahrenheit":[68, 97]}
注意第一行'50'前的8个下划线表示空格,'50'后的'\r\n'表示回车换行。
协议中数据长度是这样定义的:数据长度占用10字节,文本格式,左补空格,右跟回车换行,一共12字节。
因此服务端相应修改:
1 ... 2 while True: 3 conn, addr = sock.accept() 4 # 先接收12字节长度单元 5 len_bytes = conn.recv(12) 6 msg_len = int(len_bytes) #数据长度 7 # 再接收数据 8 msg_bytes = conn.recv(msg_len) #按数据长度接收数据 9 ... 10 #返回 11 ret_json_bytes = bytes(ret, encoding='utf-8') #str to bytes 12 ret_json_len = len(ret_json_bytes) #数据长度 13 # 先发送12字节长度单元 14 conn.send( bytes('{:>10d}\r\n'.format(ret_json_len), encoding='utf-8') ) 15 # 再发送数据 16 conn.send(ret_json_bytes)
客户端也做相应修改:
1 Socket socket = new Socket("127.0.0.1", 8888); 2 OutputStream out = socket.getOutputStream(); 3 byte[] bytes = json.getBytes(); //str to bytes 4 String lenStr = String.format("%10d\r\n", bytes.length); //长度单元 5 out.write(lenStr.getBytes()); //先发送长度单元 6 out.write(bytes); //再发送数据 7 out.flush(); 8 9 InputStream in = socket.getInputStream(); 10 //接收长度单元 11 byte[] len_buf = new byte[12]; 12 in.read(len_buf); 13 lenStr = new String(len_buf).trim(); 14 int len = Integer.parseInt(lenStr); //数据长度 15 //接收数据 16 byte[] recvBuf = new byte[len]; //按长度创建缓存 17 in.read(recvBuf); //接收数据 18 System.out.println(new String(recvBuf).trim());
至此我们的机器学习TCP Socket服务大功告成,快点把服务IP告诉朋友们来换取一点点因满足感而带来的喜悦。稍等一下,朋友们听了后可能一脸懵逼:什么是TCP Socket,我不会啊!
突然发现了TCP Socket服务的使用门槛有点高,适用性有点不足。不必发愁,该 Module Proxy 出场了,分分钟把TCP Socket升级为HTTP/Restful服务。
HTTP/Restful服务
升级非常简单,编辑Module Proxy的配置文件,在[module]中的module_socket部分添加如下配置:
重启Module Proxy后,就可以使用Restful测试工具来调用机器学习服务。测试工具有很多,开发人员喜欢使用postman,我个人一般使用firefox的插件RESTCLient,效果如下:
Restful使用相对简单,但几个关键处还是要强调:
- 请求方式只能是POST,其他的如GET、HEAD、DELETE等Module Proxy并不支持。
- 注意地址中的结束'/'必须要有,如果输入
http://127.0.0.1/machine_learning
将无法让Module Proxy找到模块,也就不能完成到Socket服务的代理。 - 数据的字符集必须是utf-8,否则Module Proxy无法计算出准确的数据长度。
- 建议的数据格式是json,示例中的method、data结构也是推荐的。
关于Module Proxy
Module Proxy是一款开源的HTTP代理中间件,使用起了非常简单。它可作为三种类型的服务器来使用:
- HTTP静态网站服务器
- HTTP反向代理服务器
- Socket代理服务器
Socket代理转发是Module Proxy的独特功能,可以让后端编程从HTTP技术体系中抽身出来,这将带来两个重要的改变:
- 无HTTP编程经验的程序员,可以用传统TCP Socket方式编写HTTP后端服务。
- 因为不需要HTTP框架的实现,几乎所有的编程语言(支持TCP Socket即可)都可以用来编写HTTP后端服务程序。
Module Proxy的相关文档和下载,请浏览 http://gzmaike.com.cn
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构