github.com/matrixorigin/matrixone@v1.2.0/pkg/udf/pythonservice/demo/README_CN.md (about) 1 # MO Python UDF Demo 2 3 本文简述了 MO 中 python UDF 的使用方式,展示了 python UDF 的灵活与便捷。 4 5 ## 单机快速部署 6 7 1. 在部署之前,我们需要确保本地拥有 [python3](https://www.python.org/downloads/) 环境,并在 python 环境下安装 grpc 相关的 package: 8 9 ``` 10 pip install protobuf 11 pip install grpcio 12 ``` 13 14 2. 按照[快速开始](https://docs.matrixorigin.cn/1.0.0-rc1/MatrixOne/Get-Started/install-standalone-matrixone/)中的步骤完成相关安装,在启动 MO 之前,执行以下命令修改 mo_ctl 的配置: 15 16 ```shell 17 mo_ctl set_conf MO_CONF_FILE="${MO_PATH}/matrixone/etc/launch-with-python-udf-server/launch.toml" 18 ``` 19 20 3. 启动并连接 MO。 21 22 ## "Hello world" 23 24 我们从一个简单的函数开始说起:用 python UDF 实现两数之和。 25 26 在客户端中输入以下 SQL 来创建函数: 27 28 ```sql 29 create or replace function py_add(a int, b int) returns int language python as 30 $$ 31 def add(a, b): 32 return a + b 33 $$ 34 handler 'add'; 35 ``` 36 37 这段 SQL 定义了一个名为 py_add 的函数,接收两个类型为 `int` 的参数,其功能是返回两个参数的和,具体逻辑在 `as` 后的 python 代码中,`handler` 表示调用的 python 函数名称。 38 39 > 注意: 当前版本 matrixone 不会在创建 UDF 时检查 python 语法,用户需要自行保证 python 语法的正确性,否则在执行后续执行函数时会报错。 40 41 现在便可以使用该函数了: 42 43 ```sql 44 select py_add(1,2); 45 ``` 46 47 输出: 48 49 ``` 50 +--------------+ 51 | py_add(1, 2) | 52 +--------------+ 53 | 3 | 54 +--------------+ 55 ``` 56 57 当我们不再需要该函数时,可以将其删除: 58 59 ```sql 60 drop function py_add(int, int); 61 ``` 62 63 至此,我们已经掌握了 python UDF 的基本用法。 64 65 ## 进阶 66 67 本节主要介绍 python UDF 的一些进阶用法。 68 69 ### 导入式 python UDF 70 71 在上一节中,py_add 函数是直接写在 SQL 中的,python 代码写在 `as` 关键字之后,我们称这种形式为**嵌入式 UDF**。然而,如果函数逻辑十分复杂,直接写在 SQL 中并不是一个好的选择,这会让 SQL 语句膨胀而且不利于代码的维护。这时,我们可以选择另一种创建 python UDF 的方式:从外部文件中导入 python 代码,即**导入式 UDF**。支持导入的文件有两种:(1) 单个 python 文件,(2) wheel 包。我们仍旧以上一节的 py_add 函数为例,把 python 代码写到文件中。 72 73 ./add_func.py 代码如下: 74 75 ```python 76 def add(a, b): 77 return a + b 78 ``` 79 80 在客户端中输入以下 SQL 来创建函数(使用 `mysql` 命令行工具连接 matrixone 时,需要添加 `--local-infile` 允许工具读取本地的 Python 文件): 81 82 ```sql 83 create or replace function py_add(a int, b int) returns int language python import 84 './add_func.py' 85 handler 'add'; 86 ``` 87 88 其中,我们使用 `import` 关键字来导入当前工作目录下的 add_func.py 文件。 89 90 调用函数: 91 92 ```sql 93 select py_add(1,2); 94 ``` 95 96 仍旧可以得到与上一节相同的输出: 97 98 ``` 99 +--------------+ 100 | py_add(1, 2) | 101 +--------------+ 102 | 3 | 103 +--------------+ 104 ``` 105 106 导入 wheel 包与单个 python 文件具有相同的语法,这里不再赘述。在最后的案例中,我们将会以导入 wheel 包的形式来创建 python UDF。 107 108 ### Vector 选项 109 110 在某些场景下,我们会希望 python 函数一次性接收多个元组来提高运行效率。如在模型推理时,我们通常是以一个 batch 为单位进行的,这里的 batch 即为元组的 vector。而 vector 选项正是用来处理这种情况的。我们仍旧以 py_add 函数为例,展示 vector 选项的用法。 111 112 在客户端中输入以下 SQL 来创建函数: 113 114 ```sql 115 create or replace function py_add(a int, b int) returns int language python as 116 $$ 117 def add(a, b): # a, b are list 118 return [a[i] + b[i] for i in range(len(a))] 119 add.vector = True 120 $$ 121 handler 'add'; 122 ``` 123 124 其中,我们使用 `add.vector = True` 来标记 python 函数 add 接收两个 int 列表(vector)而不是 int 值。 125 126 调用函数: 127 128 ```sql 129 select py_add(1,2); 130 ``` 131 132 仍旧可以得到与上一节相同的输出: 133 134 ``` 135 +--------------+ 136 | py_add(1, 2) | 137 +--------------+ 138 | 3 | 139 +--------------+ 140 ``` 141 142 有了 vector 选项,我们就可以自由地选择函数的处理形式:一次一元组,或者一次多元组。 143 144 ### 类型映射 145 146 MO 类型与 python 类型的对应关系如下: 147 148 | MO 类型 | Python 类型 | 149 | -------------------------------------------------------- | --------------------------- | 150 | bool | bool | 151 | int8, int16, int32, int64, uint8, uint16, uint32, uint64 | int | 152 | float32, float64 | float | 153 | char, varchar, text, uuid | str | 154 | json | str, int, float, list, dict | 155 | time | datetime.timedelta | 156 | date | datetime.date | 157 | datetime, timestamp | datetime.datetime | 158 | decimal64, decimal128 | decimal.Decimal | 159 | binary, varbinary, blob | bytes | 160 161 ### 函数重载与匹配 162 163 MO UDF 目前不支持重载,函数名在一个 matrixone 集群要求是唯一的。 164 165 ## 案例:信用卡欺诈检测 166 167 本节以“信用卡欺诈检测”为例,讲述了 python UDF 在机器学习推理流水线中的应用。(相关代码详见 [github](https://github.com/matrixorigin/matrixone/tree/main/pkg/udf/pythonservice/demo)) 168 169 在本节中,我们需要确保本地 python 环境已安装了 numpy 和 scikit-learn。 170 171 ### 背景 172 173 信用卡公司需要识别欺诈交易,以防止客户的信用卡被他人恶意使用。(详见 kaggle [Credit Card Fraud Detection](https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud)) 174 175 ### 数据 176 177 数据集包含了2013年9月欧洲持卡人使用信用卡进行的交易记录。数据格式如下: 178 179 | 列名 | 类型 | 含义 | 180 | -------- | ------ | ----------------------------------------------- | 181 | Time | int | 此交易与数据集中的第一个交易之间经过的秒数 | 182 | V1 ~ V28 | double | 使用 PCA 提取的特征(以保护用户身份和敏感特征) | 183 | Amount | double | 交易金额 | 184 | Class | int | 1:欺诈交易,0:非欺诈交易 | 185 186 我们把数据按照8: 1: 1的比例划分为训练集、验证集和测试集。由于训练过程不是本文的重点,不在此处进行过多的介绍。我们把测试集当作生产过程中新出现的数据存储到 MO 中,DDL 如下: 187 188 ```sql 189 create table credit_card_transaction( 190 id int auto_increment primary key, 191 time int, 192 features json, -- 对应 V1 ~ V28,使用 json list 存储 193 amount decimal(20,2) 194 ); 195 ``` 196 197 ### 推理 198 199 训练好模型之后,我们就可以编写推理函数了。其核心代码如下: 200 201 ```python 202 import decimal 203 import os 204 from typing import List 205 206 import joblib 207 import numpy as np 208 209 # 模型路径 210 model_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'model_with_scaler') 211 212 213 def detect(featuresList: List[List[int]], amountList: List[decimal.Decimal]) -> List[bool]: 214 # 加载模型 215 model_with_scaler = joblib.load(model_path) 216 217 columns_features = np.array(featuresList) 218 column_amount = np.array(amountList, dtype='float').reshape(-1, 1) 219 # 对金额进行归一化 220 column_amount = model_with_scaler['amount_scaler'].transform(column_amount) 221 data = np.concatenate((columns_features, column_amount), axis=1) 222 # 模型推理 223 predictions = model_with_scaler['model'].predict(data) 224 return [pred == 1 for pred in predictions.tolist()] 225 226 # 开启 vector 选项 227 detect.vector = True 228 ``` 229 230 然后编写 setup.py 用于构建 wheel 包: 231 232 ```python 233 from setuptools import setup, find_packages 234 235 setup( 236 name="detect", 237 version="1.0.0", 238 packages=find_packages(), 239 package_data={ 240 'credit': ['model_with_scaler'] # 模型 241 }, 242 ) 243 ``` 244 245 整个项目的目录结构如下: 246 247 ``` 248 |-- demo/ 249 |-- credit/ 250 |-- __init__.py 251 |-- detection.py # 推理函数 252 |-- model_with_scaler # 模型 253 |-- setup.py 254 ``` 255 256 使用命令 `python setup.py bdist_wheel` 构建 wheel 包 detect-1.0.0-py3-none-any.whl。 257 258 创建函数: 259 260 ```sql 261 create or replace function py_detect(features json, amount decimal) returns bool language python import 262 'your_code_path/detect-1.0.0-py3-none-any.whl' -- 替换为 wheel 包所在目录 263 handler 'credit.detect'; -- credit module 下的 detect 函数 264 ``` 265 266 调用函数: 267 268 ```sql 269 select id, py_detect(features, amount) as is_fraud from credit_card_transaction limit 10; 270 ``` 271 272 输出: 273 274 ``` 275 +---------+----------+ 276 | id | is_fraud | 277 +---------+----------+ 278 | 1 | false | 279 | 2 | false | 280 | 3 | true | 281 | 4 | false | 282 | 5 | false | 283 | 6 | false | 284 | 7 | false | 285 | 8 | true | 286 | 9 | false | 287 | 10 | false | 288 +---------+----------+ 289 ``` 290 291 至此,我们已经在 MO 中完成了信用卡欺诈检测任务的推理。 292 293 通过该案例可以看出,我们可以方便地使用 python UDF 来处理 SQL 解决不了的任务。Python UDF 既扩展了 SQL 的语义,又避免了我们手动编写数据移动和转换的程序,极大地提高了开发效率。