ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

库内机器学习全流程实战:从数据出库到模型入库

库内机器学习全流程实战:从数据出库到模型入库 做数据的人都知道ML项目最烦的不是调参而是数据来回搬家。以前做个客户流失预测光是把几千万行数据从数仓导到Python环境就得留出一个晚上的窗口导完还得做数据质量检查特征口径和数仓ETL对不上又得返工。后来我上手了“库内机器学习”这套玩法把“数据出库”这个老动作直接省掉训练完把“模型入库”当成一个正式的数据表对象来管理整条链路清爽非常多也踩了不少坑。这篇就聊聊我实际跑通的库内机器学习全流程给准备入坑的朋友做个参考。1. 为什么越来越多人把机器学习“关”在数据库里1.1 传统“数据出库再训练”的流程到底哪里疼先说老流程。大部分团队做机器学习数据流的典型路径是业务库/N数据仓库导出数据落到文件或中间表再加载进Python或者R环境然后做特征工程、训练模型、调参最后把模型部署成接口服务线上推理时又要去数据库捞数据。听上去很顺真正跑起来全是问题。第一个痛点是数据搬迁成本。我经历过一张几亿行的用户行为大表单表就快200GB导出来要半小时传到训练机又要十几分钟训练机内存不够还得做抽样。一次两次无所谓迭代到第十版特征时光等数据就耗掉了半天。第二个痛点是数据口径分裂。ETL阶段在SQL里算好的特征到了Python环境为了格式兼容往往要重写一遍逻辑两边字段类型、空值处理、时间窗口定义稍微不一致模型结果就漂了。排查起来特别费劲因为特征映射关系和代码散落在不同地方。第三个痛点是安全合规与时效性。敏感数据出库要审批出库以后还要脱敏、加密、登记用途。好不容易审批通过训练完发现数据更新了又得重新导出。这种模式在数据量小的时候能忍一旦规模化几乎所有时间都耗在“搬运”而不是“建模”上。我当时思考的改进方向很简单既然数据已经躺在数据库里特征也在SQL里算好了为什么不让算法直接跑在数据库里训练不出库预测不出库模型训练完直接作为一个库对象保存下来就是标题里说的“数据出库”到“模型入库”的转变。这不是什么黑科技核心就一个思路把算法逻辑推给数据而不是把数据拉给算法。1.2 库内机器学习的核心思路把算法推给数据库内机器学习in-database machine learning指的是在数据库管理系统内部完成特征处理、模型训练、评估和推理预测的全过程。它不是一个单一产品而是一种架构选择不同数据库的实现方式不同但共同点是数据不离开数据库的存储和执行环境算法以数据库函数、扩展或脚本的形式被调用。我把这条链路拆成三段来看数据出库这个动作被取消变成SQL查询和特征视图模型训练发生在数据库执行器内部把训练算法编译成数据库运算符或调用内置库函数模型入库则是指训练产物模型参数、元数据、评估指标作为表或对象形式持久化到数据库中方便后续查询、版本管理和批量预测。这套思路解决了前面说的三个痛点。数据不跨域流转敏感数据无需审批出库安全边界清晰特征逻辑统一到SQL视图不会出现Python重算一套导致的口径漂移模型训练完直接落成表register成一个版本预测时一条SQL调用就完成时效性大幅提升。不过库内机器学习也不是万能药。它适合数据量大、特征较规整、模型以线性模型/树模型/聚类为主、预测以批量或准实时为主的场景。真要做大规模深度学习、自然语言处理或者超深度融合模型还是得靠外部计算框架然后把最终训练任务和结果再同步回库。我的经验是库内机器学习适合做“基线模型”和“快速落地场景”这是它最大的价值。2. 选型三条主流路线的原理与对比选库内机器学习方案前先要理解市面上大体有三条路线。它们原理不同适合的人群和场景也不同。我一个个说顺便给一张选型对比表。2.1 路线一SQL声明式机器学习这条路线的代表是BigQuery ML、Redshift ML、Snowflake ML这类云数据库原生的机器学习能力。使用者不写Python训练代码而是把训练任务表达成一条SQL语句比如CREATE MODEL churn_model OPTIONS(model_typelogistic_reg) AS SELECT label, features FROM training_table。数据库收到这条语句后在内部把它翻译成分布式训练任务自动调用底层计算资源。这条路线最大的优点是入门门槛低。SQL写得好的人基本不需要额外学习算法工程技能直接复用数仓里的数据做训练。而且因为运行在云数据库的分布式执行引擎上数据量再大也能自动做并行化处理。缺点也很明显算法种类相对有限很多实现是个黑盒你想调整学习率、特征缩放方式或者自定义损失函数基本没有操作空间。另外如果用的是云服务成本控制要心里有数每次训练都会消耗计算费用。适合已经有云数仓、模型任务相对标准和轻量的团队。2.2 路线二数据库算法扩展库以MADlib/PostgreSQL为例这条路线的典型是PostgreSQL上的MADlib以及Greenplum的MADlib版本、Oracle的Machine Learning for SQL。它们以扩展包的形式给数据库装上算法库算法以UDF用户自定义函数的方式在数据库执行器内部运行。以MADlib为例它的训练函数底层并不是简单调用Python库而是通过C/C和可扩展SQL聚合机制实现了优化算法。比如逻辑回归训练函数实际是在数据库的聚合计算框架里迭代执行梯度下降更新每次迭代都会扫描一遍训练数据更新参数向量。因为是原生执行性能比逐行调用外部脚本高很多。我选择这条路线的主要原因是开源可控、算法覆盖面广、模型系数可以直接查表解释。MADlib包含的逻辑回归、线性回归、随机森林、K-means、关联规则等算法足够覆盖大部分业务场景。训练完成后模型参数会存放在一张普通表里你可以直接用SQL查看权重、截距、损失等细节这极大方便了后续审计和诊断。需要注意MADlib对版本匹配有要求不同的PostgreSQL版本需要搭配对应的MADlib安装包安装前最好先查官方兼容矩阵。另外训练函数对特征输入有严格约定比如逻辑回归要求特征以数组形式传给函数数组长度和类型不一致会直接报错。2.3 路线三嵌入式Python/外部语言第三种路线是在数据库里嵌入Python运行时比如PostgreSQL的PL/Python、SQL Server的ML Services、TiDB的Python UDF等。这种方案给DBA和数据科学家提供了很大的自由度你可以在数据库函数里直接写Python逻辑数据不用出库就能跑sklearn等外部库模型。这条路线适合需求特殊的场景。比如某个特征清洗逻辑特别复杂用SQL写很绕用Python函数来定义就特别自然。又比如样本量在几十万级别想在库内直接用sklearn的随机森林PL/Python可以直接加载模型并预测。但用PL/Python类方案有几个问题要提前想清楚。一个是资源隔离和安全性Python运行时跑在数据库进程里如果脚本有bug或者占用内存过大可能拖累整个数据库实例建议严格控制执行权限并设置内存上限。另一个是性能天花板PL/Python对每一行数据都会做一次上下文切换纯行级处理在超大表上会很慢。所以我的做法是用PL/Python做预处理和少量数据训练没问题但大规模批量训练还是优先用MADlib这类原生算法扩展。路线代表技术实现原理适合场景主要局限SQL声明式MLBigQuery ML / Redshift ML训练语句解析为分布式执行计划云数仓内标准模型快速上线算法受限、黑盒、成本依赖云计费算法扩展库PostgreSQL MADlibUDF和聚合框架内实现优化算法开源库内线性/树模型、可解释审计版本匹配繁琐、深度模型能力有限嵌入式PythonPL/Python / SQL Server ML Services数据库进程内运行Python运行时自定义清洗、中小样本复杂模型资源隔离风险、行级性能瓶颈3. 完整实操从数据准备到模型入库以PostgreSQLMADlib为例前面理论铺得差不多了下面进入正题。我用一个客户流失预测的例子把整条流程串起来环境怎么装、特征怎么在SQL里做、训练怎么跑、模型怎么入库、最后怎么批量预测。3.1 环境准备与数据说明我的实验环境是PostgreSQL 14 MADlib 1.20版本操作系统是CentOS 7。MADlib的安装有两种方式一种是用官方发布的安装包另一种是源码编译。装包比较省事流程是先装PostgreSQL和依赖库然后下载对应版本的MADlib rpm包用yum install安装最后在数据库里执行安装脚本。# 以rpm包安装MADlib为例版本号以官方为准 sudo yum install -y madlib-1.20-0.x86_64 # 连接数据库后在psql里执行 # 创建MADlib扩展的schema CREATE SCHEMA madlib; # 在数据库中安装MADlib函数需要超级用户 /usr/local/madlib/bin/madpack install -s madlib -p postgres -c 你的数据库连接串安装完成后可以用SELECT madlib.version();验证是否成功。我遇到过几次安装成功后调用函数报function not found的情况多半是schema搜索路径没有包含madlib需要在连接时把madlib加进search_path或者在调用函数前显式加上madlib.前缀。业务数据我建了两张表一张是user_use_log保存用户近30天的使用行为包含用户ID、活跃天数、日均通话时长、月消费金额、投诉次数、套餐类型等字段另一张是user_churn_label保存用户的流失标签1代表已流失0代表未流失。建模目标就是根据使用行为预测用户流失概率。-- 用户行为日志表示例 CREATE TABLE user_use_log ( user_id INT PRIMARY KEY, active_days INT, -- 近30天活跃天数 avg_call_minutes NUMERIC(10,2), -- 日均通话分钟数 monthly_fee NUMERIC(10,2), -- 月消费金额 complaint_cnt INT, -- 投诉次数 plan_type VARCHAR(20) -- 套餐类型basic/standard/premium ); -- 用户流失标签表示例 CREATE TABLE user_churn_label ( user_id INT PRIMARY KEY, churn_label SMALLINT -- 1流失 0留存 );正式建模前我先做了一次快速数据质量探查。主要看三点一是特征列有没有NULL二是标签分布是否失衡三是每个特征的取值范围是否合理。MADlib的逻辑回归不支持特征列里有NULL所以探查发现NULL的时候我会在后续特征视图里统一用COALESCE填充。标签列也不能是NULL否则训练过程会随机选取部分样本造成结果不一致。3.2 特征工程留在SQL里建特征视图这一步很关键。MADlib训练时接收的特征一般是一个数组数组的每个元素代表一个特征值。我习惯把所有特征加工逻辑统一写成一张视图这样训练时直接引用视图未来做预测时也复用同一张视图从根上规避口径漂移的问题。CREATE OR REPLACE VIEW churn_feature_view AS SELECT l.user_id, l.churn_label AS label, ARRAY[ u.active_days::FLOAT8, u.avg_call_minutes::FLOAT8, u.monthly_fee::FLOAT8, u.complaint_cnt::FLOAT8, CASE u.plan_type WHEN basic THEN 1.0 ELSE 0.0 END, -- 套餐类型做one-hot CASE u.plan_type WHEN standard THEN 1.0 ELSE 0.0 END, CASE u.plan_type WHEN premium THEN 1.0 ELSE 0.0 END ] AS features, l.churn_label AS churn_label FROM user_use_log u JOIN user_churn_label l ON u.user_id l.user_id WHERE u.user_id IS NOT NULL;这里有几个容易踩的细节。数据拼接数组时所有元素必须转成统一的FLOAT8类型不要把INT和NUMERIC混着一股脑塞进数组否则训练函数解析时容易报类型错误。文本类特征不能直接放进数组必须先做编码映射比如套餐类型拆成三个0/1列。实际项目中如果类别特别多拆出来的特征列就会膨胀这种情况建议先用频次编码或分箱降到可控维度。我做特征时还顺手处理了量纲问题。MADlib内部做逻辑回归时会做标准化吗分情况有些版本有内置标准化策略但为了保险我还是习惯手动把monthly_fee、avg_call_minutes这类绝对数值大的特征做一次分箱或取对数比如LN(monthly_fee 1)这样梯度下降能收敛得更快训练报“不收敛”的概率也会小很多。3.3 训练与评估一条SQL跑起来特征视图就绪后训练模型就一条SQL的事。MADlib训练逻辑回归的完整调用如下DROP TABLE IF EXISTS churn_model, churn_model_summary; SELECT madlib.logregr_train( churn_feature_view, -- 训练数据来源表或视图 churn_model, -- 输出模型表名 label, -- 标签列 features, -- 特征数组列 NULL, -- 分组列NULL表示不分组 100, -- 最大迭代次数 irls -- 优化器irls或cg/lbfgs );训练结束后数据库里会生成模型表churn_model和对应的summary表。最常用的是直接查看模型系数SELECT unnest(independent_var) AS feature_name, unnest(coef) AS coefficient, unnest(std_err) AS std_err, unnest(z_stats) AS z_stat, unnest(p_values) AS p_value FROM churn_model;看系数是判断模型是否合理的第一步。假如“月消费金额”对应系数是负的业务解释通常是高价值用户流失概率略低“投诉次数”系数为正符合直觉。如果某个特征的系数方向严重违背业务常识先别急着验收模型回去查一下特征加工逻辑多半是数据有漏。评估模型我通常用混淆矩阵和ROC。MADlib里直接调用内置函数即可-- 计算混淆矩阵需要设定一个阈值 SELECT * FROM madlib.confusion_matrix( churn_feature_view, churn_model, label, features ); -- 计算ROC得到AUC SELECT * FROM madlib.roc( churn_feature_view, churn_model, label, features );实操中对confusion_matrix的阈值我一般看业务侧更关注召回率还是精确率。比如流失预警场景流失用户占比较低漏召回一个高价值用户损失大那么阈值可以适当调低让模型多圈出一些疑似流失用户交由运营人工确认。这个决策不要拍脑袋要结合业务成本和模型AUC一起来看。3.4 模型入库与版本登记训练完的模型表已经存在于库里但从工程治理角度看这不算真正的“模型入库”。我的做法是额外建一张模型注册表把每次训练出的模型当作一个可追溯的版本对象来管理记清楚模型叫什么、版本号、训练数据范围、关键指标、特征列表、创建时间、状态等元信息。这样后续维护、回滚、对比实验都有据可查。CREATE TABLE IF NOT EXISTS model_registry ( model_id SERIAL PRIMARY KEY, model_name TEXT NOT NULL, model_version TEXT NOT NULL, train_table TEXT, feature_spec TEXT, auc NUMERIC(8,4), precision NUMERIC(8,4), recall NUMERIC(8,4), created_at TIMESTAMP DEFAULT now(), status TEXT DEFAULT active ); -- 每次训练完把模型登记入库 INSERT INTO model_registry ( model_name, model_version, train_table, feature_spec, auc, precision, recall ) VALUES ( churn_logreg, v1.0, churn_feature_view, active_days,avg_call_minutes,monthly_fee,complaint_cnt,plan_onehot, 0.812, 0.73, 0.65 );这一步就是标题里说的“模型入库”的核心动作。它有两个层次第一层是模型对象本身以表结构持久化在数据库里第二层是把模型的业务属性挂到统一的注册表里。有了注册表你就可以非常方便地回答“线上这个模型是什么版本”“它的AUC多少”“用的什么特征”这类问题。顺便提一下模型的备份和迁移。MADlib模型表实际就是普通的数据表里面存的是系数和配置所以用pg_dump就能把模型表备份出来在另一个库恢复后重新注册版本号即可。树模型如random_forest_train也类似模型表里保存的是训练好的模型结构。相对外部Python环境里pkl文件的管理这种纯数据库方式迁移起来简单很多。3.5 落地推理批量打分与在线服务模型入库后预测也尽量在库内完成形成闭环。批量打分场景直接调用预测函数一次性给全量用户算流失概率结果写入新表DROP TABLE IF EXISTS churn_prediction; CREATE TABLE churn_prediction AS SELECT u.user_id, p.prediction_prob FROM user_use_log u JOIN LATERAL ( SELECT m.coef[1] m.coef[2] * u.active_days m.coef[3] * u.avg_call_minutes m.coef[4] * u.monthly_fee m.coef[5] * u.complaint_cnt m.coef[6] * (CASE WHEN u.plan_type basic THEN 1 ELSE 0 END) m.coef[7] * (CASE WHEN u.plan_type standard THEN 1 ELSE 0 END) m.coef[8] * (CASE WHEN u.plan_type premium THEN 1 ELSE 0 END) -- 这里是对逻辑回归的手工推断实际可以调用madlib.logregr_predict ) AS p(prediction_prob) FROM user_use_log u JOIN user_use_log u ON u.user_id u.user_id;上面这段只是示意实际更推荐用madlib.logregr_predict函数直接对特征视图做预测避免手动展开系数出错SELECT u.user_id, p.prediction_prob FROM churn_feature_view c JOIN madlib.logregr_predict(churn_model, c.features) AS p ON true;需要注意的是预测用的特征视图必须和训练时保持完全一致列顺序一致、类型一致、编码一致。我踩过一次坑预测时给特征数组多加了一列系统没报错但预测结果全乱了排查到半夜才发现是特征顺序问题从那以后我规定训练和预测统一调用同一个特征视图。如果是线上实时评分数据库里起一个函数或者接口从请求参数拼好特征数组调用MADlib预测函数返回概率也能做到几十毫秒级别。但低延迟高并发场景我会把模型系数导出到应用层缓存直接用系数做线性运算避免每笔请求都查数据库。模型入库的价值在于统一版本管理和离线批量在线高并发还是得靠应用侧计算这里要权衡。4. 库内机器学习避坑指南常见问题与排查经验最后这部分专门写坑。库内机器学习虽然省了数据搬运但调试起来也有自己的脾气。我把遇到过的问题和排查思路整理成速查表给大家参考。4.1 训练阶段的问题速查表症状可能原因处理方式调用madlib.logregr_train报函数不存在schema搜索路径里没有madlib在连接串或会话里设置search_path或调用时加madlib.前缀训练报错特征数组包含NULL特征列有NULL值MADlib不支持在特征视图中用COALESCE填充或删掉NULL行特征数组包含text类型报错文本类特征没做编码用CASE WHEN或映射表把文本转成数值训练迭代多次仍不收敛特征量纲差异过大对高数值特征做取对数或标准化调整优化器为irls模型全预测为多数类标签类别不平衡对少数类做重采样或考虑其它算法如随机森林训练内存不足数据量太大或特征维度过高先对训练数据做随机采样或降维等方案稳定再全量训练我印象最深的是第一次跑训练报错信息特别短只说array contains NULL。当时训练数据上千万行不好定位是哪一列导致的后来我写了个SQL按列逐项统计NULL数量发现是投诉次数在大量用户上没有记录默认存了NULL用COALESCE(complaint_cnt, 0)一改就通过了。所以我的习惯是建特征视图后先做一次全字段NULL扫描不要等训练报错。4.2 模型入库与推理阶段的经验细节模型入库阶段的坑主要集中在版本覆盖和特征一致性上。MADlib训练时如果模型表名重复默认行为是覆盖写这会把之前跑好的模型参数冲掉。我刚开始做实验时调试不同特征组合都往同一个模型表里写结果想回退到上一版根本找不到。后来强制执行“一训一表一注册”的规范模型表名带上时间戳或实验编号同时每次训练完立刻往model_registry里插入一条新记录才避免了这个混乱。推理阶段的特征一致性也得专门说。训练时特征数组是8位预测时少了一位或多了一位系统不一定报错但结果会错得离谱。为了根治这类问题我现在统一用特征视图作为训练和预测的数据源并且把feature_spec字段记到注册表里。如果谁改了特征视图feature_spec对比能立刻发现异常。还有一个容易忽略的问题权限。MADlib的很多函数默认只有超级用户能调如果业务账号没权限训练或者预测时会报权限不足。建议给只读业务账号单独授madlibschema的USAGE权限和算法函数的EXECUTE权限。这个权限配置写进数据库初始化脚本里别每次都手动去改。4.3 我个人在库内ML操作中的一些固定习惯分享几个我坚持了很久的操作习惯希望对你有参考价值。第一特征全放在视图里视图就是特征血缘的记录。任何人想了解模型用了什么特征、怎么加工的直接看视图定义就行。即使以后模型升级也能追溯到旧版本用的特征版本。这个思路本质上就是给特征做了版本管理非常适合多人协作的团队。第二每次训练完成后立刻记录指标并对比上一版。我会写一个简单的评估SQL训练完自动计算AUC、精确率、召回率插入到model_registry里。版本越积越多以后横向对比非常直观哪次实验有效、哪次无效用SQL一查就知道省去了各种实验记录表的维护成本。第三库内机器学习我没有用它替代所有机器学习工作流。深度学习、大规模文本向量化、复杂CV任务仍然在外部框架里跑但库内轻量模型作为基线已经成为我的默认选项。因为它的迭代成本太低从写SQL到出模型系数可能十分钟就完成了。很多时候你只需要一个快速验证根本不需要启动一个分布式训练集群。这篇内容从“为什么做”讲到“怎么做”再到“坑怎么避”核心就是想表达一件事数据出库不是必然步骤模型入库也不只是一张表。把机器学习真正放进数据库里收获的是更短的上线周期、更清晰的口径管理和更可靠的数据安全边界。如果你正在被数据迁移和口径不一致反复折磨不妨找个业务场景用今天的流程试一试库内机器学习先从一张特征视图和一个最简单的逻辑回归开始跑通以后你会回来点赞的。
返回列表