分分钟将机器学习包装成HTTP服务

随着人工智能的热度不断提高,越来越多的程序员开始进行相关的研发和应用,机器学习是人工智能的重要组成部分,也是大多数人学习人工智能的首选途径。本文从一个传统程序员角度来介绍机器学习,重实战而轻理论,因此阅读本文您需要有以下技能:

  1. 基本的编程能力(至少能看懂简单的python代码)
  2. 初中的数学能力(是的,不需要懂得算法、高数、统计等)

环境准备

  1. Linux环境,如果没有就快装个虚拟机吧。
  2. 在linux上安装sklearn,sklearn是机器学习的工具包框架,官网  和网站  有更多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的相关文档和下载,请浏览 

posted @   dyf029  阅读(61)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示