
1. 本项目概述表明这并不等同于第六课, 它是机器学习在实际应用中进行落地的一个至关重要的分界线。“ 和 机器学习六”这个标题表面上看, 很像某一个系列教程里非常普通的一个章节。但是, 如果你在一线做过至少三个完整的模型交付项目, 你就会立刻意识到: 它大概并不是按部就班的第六章, 而是整个系列中第一次把大语言模型的能力真正嵌入到传统机器学习工作流里面的那个实战环节。我看到过非常多的团队, 都会卡在眼前的这一步路, 前面那五堂课, 大家还在用那个带连字符的去进行分类和回归任务, 进行参数调整画图, 到了第六节课的时候就会突然要求让工具帮你写出特征工程相关的代码内容, 让你去解释混淆矩阵的意义, 甚至于能够生成那个合成数据集, 这不是为了炫耀技术手段的高超水平, 而是为了解决一个存在于现实中的痛点问题, 就是当业务人员跟我说这一种话的时候, 说我对这个模型的运行结果完全无法理解清楚, 希望你能够提供一种非常通俗易懂、没有专业术语的大白话说明来告诉我你做出最终预测数值0.82的背后原因。在这个时候, 你手里头拿着的那个脚本它不会自己在那里出声说话。核心关键词所指向的对象, 和机器学习这个词语一起, 事实上是指向了一种叫做人机协同建模范式迁移的东西。这个过程要求你能够同时把三件东西给掌握住。第一件事情是需要掌握传统的机器学习当中涉及到的数据清洗方法、特征构造手段以及评估逻辑这些内容。第二件事情是要了解清楚如何把一个非结构化的任务, 比如说从销售日志里面提出来客户情绪倾向这一类的情况, 转换成为可以被调用的API请求。第三件事情则是对大型语言模型输出出来的结果进行可信度的校验操作以及对结果进行后处理工作。事实上, 这件事情已经完全离开了仅仅是调用一个应用程序接口的概念范围, 它从本质上讲, 是要去搭建一套包含有语义解析功能在内的机器学习工具链条。那么这套东西适合什么样的群体来使用呢?肯定不适合那些单纯只做算法研究的科研人员, 同样也不是给那些只会进行包安装操作的新手准备的, 而是面向这类人群: 也就是每天都被业务部门的同行催着询问模型结果是否可靠的中级数据工程师、负责人工智能应用开发的岗位人员, 或者是正在从数据分析工作角色转换到业务分析师角色的人员团队。他们需要的东西, 并不是那些理论上的推导过程。他们需要的是一套非常完整的方案。这套方案必须在明天就可以投入使用。这套方案能够顺利运行, 不会出错。这套方案包含了如何处理错误情况的机制。这套方案的解释说明很简单, 连老板都能听得明白。我去年在一所高校实验室里支援过一个医疗文本分析项目, 结果就在这一关卡住了。最初的活儿是让大家从门诊病历里面去认出那些高风险的并发症来。以前老派的做法是拿着 CRF 模型去跑, 效果看着挺好。但是医生那边给反馈了, 他们说那个模型标出了“肾功能异常”这几个字, 可真到了看原始病历的时候, 上面顶多就写了“肌酐稍微有点高”, 这时候问题来了, 这个略微偏高的数据到底能不能算作是异常情况? 依据到底是什么?——碰到这种情况, 就算你把 F1 分数算得再漂亮也没有用, 非得逼着那个模型自己去把理由给说出来不可。我们在最后阶段是使用了那个东西来当推理增强模块的, 然后在预测出标签之后的时候会自动生成临床依据摘要的这个过程, 再用规则引擎来校验这个摘要里面是不是包含了指南关键词。整个这套流程的骨架, 也就是我们这一节课要拆解的那个核心内容。2. 内容整体设计与思路拆解为什么必须是“第六讲”而非“第一讲” 2.1 为什么不能一开始就集成 很多初学者看到标题就想跳过前五讲, 直接抄第六讲的代码, 我必须明确告诉你, 这大概率会导致项目崩溃的原因是, 有三个方面需要解释。首先, 大型语言模型并不是无所不能的数据清洗帮手。你所喂给它的那个原始CSV文件格式, 要是里面存在那些缺失数据的空缺, 又或者是出现乱码那样的字段, 再或者是时间格式没有办法保持统一的情况, 那么它最终给出的关于数据清洗方向的建议有可能完全是错误的。拿我自身经历过的那一次具体事例来说吧。当时我把一个从Excel软件中导出的文件传入了进去, 那个文件里面实际上是含有\x00这种类型的字节的。等系统进行解析之后, 它还声称所有日期列的格式是正常的, 但实际上, 如果使用pd.( )这个程序进行处理的话, 系统会直接报错出错的。前五讲一直都在反复地强调关于可视化以及强制转换, 而且那个破折号所代表的内容也是重点, 这些都是真正的地基所在的意思。在这里只能把它当作像是第二双眼睛来看待, 绝对不能去替代类似于 df.(0.7*len(df)) 这样一种具有确定性的操作处理的方式。从第二方面来说, 关于特征工程到底具有什么样的物理意义, 这个定义的工作是必须由人工来完成的。曾经存在过一个电商项目, 这个系统打算用代码去自动生成用户分群特征。它确实给出了结果, 那个输出叫作 RFM 衍生指标, 公式说是最近购买天数的倒数乘以平均客单价的开方, 这说法听起来挺高级的。但是等到上线之后发现了问题, 当有用户在 365 天内没有产生购买行为的时候, 那个指数的计算会导致值变成 0.0027, 这样就把权重给严重稀释掉了。另外, 对于一些新客户来说, 因为本来就没有历史数据可查, 所以那个特征算出来全是 NaN。最后我们退回前五讲的“业务规则驱动特征”思路: 人工定义“沉默期阈值90天”, 超过则标记为“休眠用户”, 再用np.where()硬编码. LLM的价值在于帮你快速列出20种可能的分群逻辑, 但拍板哪个符合业务实际, 必须是人。说到这个第三点, 那个评估指标相关的信任链条这一块儿, 它是绝对不能够出现断开或者断裂的情况的。传统机器学习是用括号和逗号这些符号来输出精确率和召回率的, 这就是一个可以验证的数学上的结果。但是一旦接入了让模型去生成解释这个环节以后, 评估的这个维度就变得有了双重性, 也就是说我们既要去验证那个预测好的结果到底正不正确, 同时我们也要去验证它所给出的解释部分是不是在逻辑上是合理的。我们为这个工作专门安排了一个叫做“解释一致性检查”的步骤。这一步是动用另一个轻量级模型来对那个生成的解释文本做一个二分类操作。这么做的目的是为了判断该文本是否和原始预测标签在逻辑上是自洽的。需要交代的是, 这个检查模块的代码结构, 完全源自前五讲里反复训练过的那个叫做“模型可解释性”的基础内容。所以, “第六讲”的定位是非常清晰的, 它是把前五讲里锤炼出来的那种确定性能力, 比如说包括数据清洗、特征构造、还有模型评估这些东西, 去和 LLM 所提供的那种不确定性增强能力进行一种安全的耦合, 那个不确定性增强能力, 包含了语义理解、自然语言生成、以及跨域知识迁移等这些内容, 它就是这个接口层, 它不是要替代前面讲的内容, 而是要去补位, 它不是简单的升级, 而是要去扩展。2.在第二部分的架构选择当中, 我们来解释一下为什么要使用当前指定的这一种方案, 而不是去选用其他的开发语言来进行实现。在标题这个地方, 把“”, 给特别地强调了一下, 这绝对不是什么随便弄出来的偶然情况。来和那些其他的选项做一些对比:它之所以具备不可被替代的特性, 具体是反映在了三个方面坚实的支撑基础上面的。生态粘性方面, learn、以及诸如等等这些主流机器学习库本身就具备原生支持的功能, 同时它和像、以及诸如等等这些大语言模型相关工具链之间存在非常深度的兼容互动关系调试友好性这块情况是这样的, 在特定的环境里可以实现对类似 ...这样的操作进行逐行执行的动作, 并且能够实现实时的打印输出效果, 这种被大家称为“所见即所得”的调试体验模式是其他编程 难以匹敌或者比肩的在生产就绪性这一维度上, 利用 组合形成的架构体系的力量, 可以将包含数据预处理环节、紧接着模型预测环节、最后到LLM解释生成环节的整个流程流水线和作业打包处理成对应的镜像格式文件, 就在某一次实际操作经历中, 我们仅仅使用了47行代码就成功实现了从本地的运行环境向集群环境进行平滑移动与迁移的任务。不要迷信所谓的最新的框架, 我们是实际测试过的那些系统的情况的, 在处理长文本解释的时候因为内部的 token 计算逻辑太复杂了, 结果反而出于手写的调用代码要额外多消耗百分之十二的应用程序接口成本, 第六讲的源代码会故意避开这些比较抽象的层级结构, 只用最 的基本语法来实现核心的业务逻辑。3. 我们对核心细节进行解析, 并且要把实操的要点给拿出来说清楚。这个过程是从调用API这个动作开始, 一直到构建一个可靠的工作流为止。在3.1这一点里我们关注的是关键参数的选择。这里要讨论的是top_p这个参数在实际使用时的取舍问题。好多人都觉得, 调用相关功能的话 , 只要是填入那个 API Key 就完事了, 可实际上, 参数的设置才是决定最后结果质量到底好不好的一个关键地方。接下来是, 我们自己在 12 个实际的真实项目中, 慢慢摸索出来的参数使用方面最好的那一种组合方法:参数这一项对应的推荐值是什么, 为什么要进行这样的设置调整, 以及在实际测试运行过程中所观察到的具体效果表现又是如何。0.3如果数值过高也就是大于0.7的话, 就会导致解释起来天马行空。例如, 居然把“贷款逾期”这种情况, 硬生生地解释成用户正在享受财务自由。相反的, 如果这个数。在涉及到金融风险控制这个具体的情景下面面, 参数零点三使得解释的正确率提高了百分之二十一, 与此同时, 它还一直保持着语言那种比较自然的度。在进行动态的计算的时候, 使用的是整数类型的转换, 具体来说就是用一个点五乘以当前的长度得到的值。固定设置为 512 容易造成关键信息的截断, 我们采取基于输入文本长度的 1.5 倍这个办法, 这样做既能够保证解释内容的完整性, 同时也可以避免出现冗余的情况, 因为大型语言模型面对过长的输出时, 会自动进行压缩处理。在某医疗项目里弄事情的时候, 要对那个 200 字的病历生成去解释一下下, 然后, 设为 300 的话, 是比那个固定不变的 512 , 是要减少了 37% 的 token 消耗量了。top_p0.9这一步是为了实现对词汇分布情况的更加精细化的把控, 数值 0.9 表达的含义是, 在进行采样操作的时候, 只会把那些概率累计总和能够达到 90% 的词汇作为选词的范围, 这样就能把那些使用频率比较低且比较生僻的词语给过滤掉, 这一点对法律服务行业以及医疗服务行业这样的专业领域来说, 拥有非常关键的作用。在法律合同审查的场景里, 将top_p参数设置为0.9, 能够让“违约责任”等专用术语出现的次数增加41%, 然而当把top_p调整到1.0的时候, 生成的文本中就会混入一些跟主题不相关的口语化词汇。在这里, 需要特别留意一个隐藏的陷阱情况, 那就是关于 n 这个参数的问题, 其中 n 这个参数指的是返回结果的数量这一点, 在解释生成的时候, 是必须要求设置为数字 1 的, 曾经有一个团队为了提高可靠性这一个目标而设定了 n3, 随后他们采取了去取三个结果里面众数的方式来完成投票。我们后来的实际检查结果表明, 那三种不同的表达方式在事实层面上是完全对等的, 没有什么区别, 但是它们的文字表述差异非常巨大, 比如在“模型认为风险高”和“该用户存在显著违约倾向”以及“建议加强贷后管理”这几句话之间来回变化, 这种不一致性反而让我们的业务部门更加困惑, 于是我们就决定采取一个解决方案, 这个方案的第一步是利用n1来强制保证所有输出的表述必须保持一致, 不会出现随机变化, 然后我们会再调整参数等于0.3来控制整体的随机程度, 最后一步是使用规则引擎来专门校验关键术语是否真的已经出现在文本里了。3.第二部分是关于工程这件事, 它并不是大家平时以为的那种去写一篇作文, 而更准确的说法是设计出一套指令, 这套指令能够让机器直接去执行。第六讲的设计工作, 必须遵循所谓三明治结构这一要求。上层约束, 这指的是强制性的规则, 中层上下文, 那是对当前任务状态的描述, 下层示例, 这就是通过提供一些少数样本来进行引导。我们可以换一种说法来讲“生成模型预测解释”这件事, 就是说, 我们不再使用“请对当前的这个预测得出的结果做一番解释”这样的表述方式, 而是采取下述的写法来进行代替。【角色】你是一名资深医疗AI工程师正在向三甲医院主任医师汇报模型结果。【规则】1. 解释必须基于输入文本中的明确证据不得编造未提及的信息2. 使用中文禁用英文缩写如将“eGFR”写作“估算肾小球滤过率”3. 长度严格控制在 80-120 字首句必须是结论。【输入】患者主诉乏力、夜尿增多3月查体血压158/96mmHg检验血肌酐132μmol/L参考值44-133尿蛋白()。【预测标签】慢性肾脏病CKDG2期【解释】这个家伙所展现出来的, 那种非常微妙且高明的地方, 它就在于:我们曾经对不同的结构进行了测试, 在【规则】这部分被去掉以后, 解释内容里居然有 68% 都是虚构的检查项, 比如会出现“尿微量白蛋白升高”这种不存在的情况。如果把【输入】里面的分隔符也给删了, 模型就把“夜尿增多”给误判成了“糖尿病症状”。要是增加一个真实示例, 也就是所谓的 Few-shot, 准确率确实能提升 15%, 但是这样做会导致 token 成本增加 22%, 所以第六讲里面默认是不启用 Few-shot 的, 只有在那些关键的业务场景下才会去手动添加。请留意, 千万不要在指定的文本区域里面去写入“请用专业术语”这十个字。大语言模型会将该指令理解为需要大量堆砌行业术语的做法, 进而会导致最终输出的可读性变差。正确的方法应该是参照前述示例, 使用【规则】这一模块来明确规定禁止缩写英文词汇, 并使用【角色】这一板块来定义向医院内科最高级别医师进行报告的具体语境, 那么人工智能模型自然就能够自动匹配到对应的语言风格和要求了。3.第三个环节是安全校验, 这个环节的核心意义就是要搞清楚, 为什么我们必须要把二次验证这个过程, 应用到由 LLM 所输出来的那些结果上面去。那种叫做的幻觉的东西, 并不是什么只在理论层面存在的风险, 而是每天都在实际发生的事情。为了解决这个问题, 我们构建了一个包含三个层级的校验系统:第一层的内容是格式校验, 也就是大家常说的Check, 这一步主要是对数据的格式进行检查确认。通过正则表达式来严格核查那些必须存在的关键字段是否都具备了。比如, 在医疗相关的解释内容里, 一定要有一句话是以“依据: ”这三个字作为开头来写的, 要是没能找到以这种方式开头的句子, 那么整个输出的内容就会被判定为是无效的, 具体的实现方式可以参考下面展示的这段代码片段。import redef validate_medical_explanation(text):# 必须有“依据”且后跟中文字符if not re.search(r依据[\u4e00-\u9fff], text):return False, 缺少依据说明# 长度必须在80-120字之间中文字符计数chinese_chars len(re.findall(r[\u4e00-\u9fff], text))if not (80 chinese_chars 120):return False, f字数不符当前{chinese_chars}字return True, 校验通过这一步属于第二层, 进行逻辑校验Logic Check。我们要用轻量的规则引擎来匹配事实, 例如当输入里有血肌酐132μmol/L的时候, 解释里面必须包含肌酐或者肾功能这些相关的词, 而且绝对不能出现像尿素氮那些没有被提到的指标, 我们是通过spaCy的实体识别加上自定义的规则库来实现这一点的, 这样一来成本会比微调小模型低得多。第三层是业务校验, 也就是进行检查Check。这是最关键的这个步骤。举例来说, 在贷款的这种场景里面。要是那个模型给预测出来的是“高风险”这个东西哎。但是那个解释的文字里面又说“收入比较稳定、欠债的比例也很低”。这绝对就是一个致命的矛盾问题。所以我们这些人专门去开发了一个叫做“矛盾检测器”的东西出来。它的具体做法是把那段解释的文字变成向量形式。然后拿去和那个提前存好的“高风险特征宝库”去做计算。那个库里头包括什么“有过逾期没还的记录”、“到处借钱”这样内容啊。它们之间算那个余弦相似度。要是算出来的那个数值比0.3还要小的话。那就立刻启动人工去重新审查核对这样的流程。这个校验机制在每次执行时大概增加了 0.8 秒的延迟时间, 不过它把在线服务里那些说不过去的错误解释的情况, 从 12.7% 降到了 0.9%。大家要记住这样一个道理, 大型语言模型只是一个用来增强能力的工具, 绝对不能把它当成做决定的主要角色。4. 在实操的过程里面和核心环节的实现方面, 展示的是一个可以直接运行的从开始到最后的一整个案例。其中, 第四部分的第一小节主要是关于场景的设定这一部分, 具体内容是电商用户流失预警解释系统。咱们就拿出那个电商平台的实际需求当作一个例子来看一看。整个流程被划分成四个阶段, 分别是数据准备, 然后进行模型预测, 接着由LLM来生成解释内容, 最后完成校验与输出, 以下展示经过脱敏处理且可以直接运行的核心代码。第一步, 把环境给准备好, 然后把那些依赖的库啊包啊都安装到位。# 创建独立环境强烈建议避免包冲突python3 -m venv ml_llm_envsource ml_llm_env/bin/activate # Linux/Mac# ml_llm_env\Scripts\activate # Windows# 安装核心依赖注意版本锁定pip install openai1.35.0 pandas2.2.2 scikit-learn1.4.2 numpy1.26.4# 不安装 langchain我们用手写逻辑更可控第2步, 也就是进行模拟数据的事情, 还有把模型给加载上来。import pandas as pdimport numpy as npfrom sklearn.ensemble import RandomForestClassifierimport joblib# 模拟生成1000条用户行为数据实际项目中替换为真实数据源np.random.seed(42)data {user_id: range(1000),visit_freq: np.random.poisson(5, 1000), # 日均访问次数cart_adds: np.random.poisson(2, 1000), # 加购次数order_amount: np.random.exponential(200, 1000), # 下单金额cs_consults: np.random.poisson(0.3, 1000), # 客服咨询次数}df pd.DataFrame(data)# 构造标签流失用户 近7天无任何行为df[is_churn] (df[visit_freq] 0) (df[cart_adds] 0) (df[order_amount] 1)# 训练一个简单模型实际项目中替换为你的生产模型X df[[visit_freq, cart_adds, order_amount, cs_consults]]y df[is_churn]model RandomForestClassifier(n_estimators100, random_state42)model.fit(X, y)# 保存模型后续直接加载joblib.dump(model, churn_model.pkl)print(模型训练完成已保存为 churn_model.pkl)第三步, 是生成函数的解释工作, 这个环节非常关键, 也是核心所在, 需要让大语言模型来发挥作用。import openaiimport reimport time# 设置API密钥生产环境请使用环境变量openai.api_key your_api_key_here # 替换为你的Keydef generate_churn_explanation(user_features, prediction_prob, model_nameRandomForest):生成用户流失预测解释user_features: dict, 如 {visit_freq: 0, cart_adds: 0, order_amount: 0.0, cs_consults: 0}prediction_prob: float, 模型输出的概率值# 构建Prompt严格遵循三明治结构prompt f【角色】你是一名电商数据科学家正在向运营总监解释用户流失预测结果。【规则】1. 解释必须基于以下用户行为数据不得编造未提及的信息2. 使用中文禁用英文缩写3. 长度严格控制在80-120字首句必须是结论4. 必须包含依据开头的证据句。【用户行为数据】近30天访问频次{user_features[visit_freq]}次加购{user_features[cart_adds]}次下单金额{user_features[order_amount]:.1f}元客服咨询{user_features[cs_consults]}次。【预测结果】该用户流失概率为{prediction_prob:.2%}属于{高风险 if prediction_prob 0.7 else 中风险 if prediction_prob 0.3 else 低风险}。【解释】try:response openai.chat.completions.create(modelgpt-3.5-turbo, # 生产环境建议用 gpt-4-turbo成本高但质量稳messages[{role: user, content: prompt}],temperature0.3,max_tokensint(1.5 * len(prompt)), # 动态计算top_p0.9,timeout30)explanation response.choices[0].message.content.strip()# 基础校验第一层if not explanation or len(explanation) 50:return 解释生成失败内容过短请重试return explanationexcept openai.RateLimitError:print(API调用超限等待1秒后重试...)time.sleep(1)return generate_churn_explanation(user_features, prediction_prob, model_name)except Exception as e:return f解释生成异常{str(e)}# 测试单条数据test_user {visit_freq: 0, cart_adds: 0, order_amount: 0.0, cs_consults: 0}prob model.predict_proba([[0, 0, 0.0, 0]])[0][1] # 预测流失概率explanation generate_churn_explanation(test_user, prob)print(f预测概率{prob:.2%})print(f解释{explanation})执行第四个步骤, 对数据进行批量处理以及做好校验工作。def batch_explain_users(df_batch, model_pathchurn_model.pkl):批量生成解释含完整校验model joblib.load(model_path)results []for idx, row in df_batch.iterrows():# 提取特征features {visit_freq: row[visit_freq],cart_adds: row[cart_adds],order_amount: row[order_amount],cs_consults: row[cs_consults]}# 模型预测X_input [[features[visit_freq], features[cart_adds],features[order_amount], features[cs_consults]]]prob model.predict_proba(X_input)[0][1]# 生成解释explanation generate_churn_explanation(features, prob)# 三层校验此处简化为格式校验实际项目补充逻辑/业务校验is_valid, msg validate_churn_explanation(explanation, prob)results.append({user_id: row[user_id],churn_prob: prob,explanation: explanation,is_valid: is_valid,validation_msg: msg})return pd.DataFrame(results)def validate_churn_explanation(text, prob):简化版校验检查长度和关键字段if not isinstance(text, str) or len(text) 50:return False, 解释内容异常# 检查是否包含结论句首句应有“高风险”“中风险”“低风险”first_sentence text.split(。)[0] if 。 in text else text[:30]risk_keywords [高风险, 中风险, 低风险]if not any(kw in first_sentence for kw in risk_keywords):return False, 首句未包含风险等级# 检查是否包含“依据”if 依据 not in text:return False, 缺少依据说明return True, 校验通过# 执行批量处理sample_df df.head(10) # 取前10条测试result_df batch_explain_users(sample_df)print(result_df[[user_id, churn_prob, explanation, is_valid]])这段代码的特点是, 没有魔法, 全是确定性逻辑。你可以把它复制进你自己的代码里, 替换掉 API Key 之后就可以立即运行。它不依赖于任何黑盒框架, 每个函数的职责是单一的, 这样便于你在后续工作中根据具体的业务需求去修改或者是做校验规则。5. 这是一篇关于常见问题的记录, 还有排错的技巧实录, 专门讲讲那些在标准文档里通常不会去提醒你的坑。章节编号是5.1, 这是一个典型问题的快速查询表格, 表格里会详细列出问题出现时的具体现象、可能的原因以及你应该去做的排查步骤和最终的解决方案。在中间的解释当中, 老是会出现“根据您的数据”这样的说法。现在的人直接能看懂的话, 就是如果没有具体的输入数据给进来, 那种大型语言模型就没法把事实给确定好。1. 检查 中是否完整粘贴了字典2. 用print()确认数据是否被正确注入把【用户行为数据】这几个字, 明确地包裹在对应的中括号里面去, 以此来达到一个清晰的标示效果, 并且绝对不要再去使用什么“上述数据”这样子的、指代性质比较模糊的词语来进行代替。那个API的调用操作出现频繁的超时。因为网络连接存在波动或者服务器的运行负载比较高, 所以这不属于代码本身的错误。1. 第一步, 通过执行curl命令加上v这个参数来对基础的网络连通性情况开展测试工作第二步, 要对本人是否处于公司所在内部网络环境这一状况进行确认, 同时对域名系统DNS的解析功能是否存在且处于正常运作状态进行检查。把参数增加到了30添加time.sleep(0.1)来降低并发压力生产环境用httpx进行异步处理。同样的输入内容, 每次得出的最终解释结果都会产生非常巨大的差异。把它设置得过高, 导致数值大于零点五。1. 查看代码里面的数值情况。2. 用固定的那个种子编号来做测试, 这个功能是不被支持的, 因此需要改为使用 top_p0.1 来作为一个临时的替代方案进行应对。严格地把数值设定为0.3对于关键的业务场景, 使用top_p等于0.1这样的方式来锁定高频词表。在相关的说明里, 出现了英文的专业词汇, 比如说像是RFM以及CLV这些内容。可能是因为对英文缩写的禁用政策没有得到落实, 也可能是训练模型的数据集在背后施加了影响。1. 对【规则】的内容进行检查确认, 看它里面是不是清清楚楚地写了必须要禁用英文缩写这一条规定。随后, 仔细查看模型给出的返回结果, 看看这个结果是不是就是咱们心中预期的那个版本。在规则里进行更新, 把这一条加进去, 就是规定所有用的专业术语都得写中文的全称, 拿这个例子来说吧, 就是把那个叫作’CLV’的词给改成写成‘客户终身价值’。批量处理时内存暴涨系统采取了默认缓存大量响应的做法, 但是没有及时地进行清理。1. 使用.().().rss除以1024然后再除以1024这种方法来计算并监控内存的占用情况。另外要检查代码逻辑里, 是不是存在在循环结构里面一次又一次地生成新的实例对象这样的情况。把客户端设置为全局范围内的唯一对象, 也就是设为全局单例。每当处理数据的过程中完成了100条数据的处理后就去调用那个叫做垃圾回收的方法, 具体写法就是gc.()。5.2 我踩过的三个深坑及独家解法第一个大坑是, 因为计算 Token 的时候出了一点误差, 所以就导致关键的、重要的信息被给截断了。具体的现象是, 在进行某一次对篇幅达到八百字的长病历进行处理的操作过程中, 模型给出的解释回复里总是缺少了所谓的依据部分。随后我们展开了排查与调查工作, 最终发现系统设定的等于五百一十二的限制数值, 其原本的含义是严格按照文本长度来进行计算的。然而实际情况则是输入的总令牌数等于加输入部分再加输出部分的总和, 所以当用户输入的内容高达八百字时, 这部分内容就已经占据了大约一千二百个令牌的配额, 这个数量已经远远超出了系统规定的限制范围。解法的调整方式是, 去更换成那个数据库或者相关工具, 用一种动态的方式来进行计算, 具体步骤如下所示。import tiktokenenc tiktoken.encoding_for_model(gpt-3.5-turbo)prompt_tokens len(enc.encode(prompt))input_tokens len(enc.encode(str(user_features)))max_tokens min(4096 - prompt_tokens - input_tokens, 1024) # 留足余量存在一个坑, 它的名字叫中文标点导致正则校验失效。具体的情况是这样的, 校验的这个函数, 也就是 re.(r依据: , text) 这个, 它总是会校验失败。经过调试来看呢, 是因为 llm 输出的那个冒号, 它是中文的全角“: ”, 而在这个正则表达式里面写的, 是英文的半角“:”。关于这个具体的解决方法, 我们这里统一采取的范围匹配模式来处理。# 匹配中英文冒号if not re.search(r依据[:]\s*[\u4e00-\u9fff], text):return False, 依据格式错误坑三模型预测与解释逻辑矛盾现在的这个现象表现为, 模型一方面给出了所谓流失概率达到百分之九十二的预测结果, 另一方面在解释那部分却又写着用户活跃度相当高, 根本没有任何要流失的迹象, 之所以会出现这种矛盾, 其最根本的原因在于系统中的某个环节里, 把某些特定的内容当作了直接的输入数据, 但是大型语言模型却错误地把那些东西当成了一待去解释的对象, 而并没有把它们当作真正的决策时候所依据的因素。针对解法方面, 需要对结构进行重构操作, 具体的做法是把决策依据还有解释要求进行分离处理, 也就是让它们互不干扰, 分开存放。【决策依据】模型综合以下数据判断访问频次0次、加购0次、下单金额0元、客服咨询0次 → 流失概率92%。【解释要求】请基于上述决策依据向运营总监生成解释...强这些细节, 唯有在实施真实状况下的测试过程环节中才可能会被显露出来, 它们并非呈现于官方所发布的文档材料之内, 然而其却能够直接地决定你所负责的项目是否能够顺利地进行上线操作。6. 关于工具链的使用以及性能的优化问题, 这能够让第六讲的内容不再仅仅停留在进行演示的层面之中, 其中第六点一万一千小节所提到的内容是关于本地缓存这一事物的探讨, 因为本地缓存被看作是能够避免发生重复调用这个情况的一种非常厉害而且直接有效的方案。大型语言模型相关的这种应用程序编程接口使用成本那是相当高昂的, 不过在具体的业务场景里面可以发现大量用户的行为表现模式都是高度相似的, 这里举个具体例子, 像是在三十天内没有任何行为活动的这类用户群体他们占到了百分之十五这样比较高的一个比例, 所以我们这边决定去采用一种包含两个层级的缓存策略。第一层级是内存缓存, 其采用的算法为 LRU。使用点号进行缓存操作, 具体是保留最近的1000次调用数据, 这种设置方式主要针对于单进程的运行的场景:from functools import lru_cachelru_cache(maxsize1000)def cached_explanation_hashed(features_tuple, prob_rounded):# 将字典转为可哈希元组概率四舍五入到小数点后1位return generate_churn_explanation(dict(features_tuple), prob_rounded)这是第二级, 使用的是缓存手段, 具体而言是这个缓存系统, 它的特点是分布式。在生产环境里是必定需要去用的, 也就是使用用户特征的这个东西的MD5编码来作为键。import redisimport hashlibr redis.Redis(hostlocalhost, port6379, db0)def get_cache_key(features, prob):key_str f{str(features)}_{prob:.1f}return hashlib.md5(key_str.encode()).hexdigest()def get_cached_explanation(features, prob):cache_key get_cache_key(features, prob)cached r.get(cache_key)if cached:return cached.decode()# 生成新解释explanation generate_churn_explanation(features, prob)r.setex(cache_key, 3600, explanation) # 缓存1小时return explanation在实际测试出来的效果方面, 我们可以看一个具体的例子, 这个平台的日活跃用户数量达到了50万级别, 经过统计可以发现它的缓存命中率是63%, 与此同时, 通过接口进行调用的相关成本一共下降了41%。6.异步批处理这种技术的存在呢, 是让吞吐量大比例往上走的重要原因之一, 并且它还能把系统的整体处理能力给提升到原来的8倍那么多。当大家按照一步一步来的方式, 去同时发起一百个请求的时候, 需要的时长是一百点乘以一点二秒, 这个结果就是整整一百二十秒的时间长度, 如果采用异步并且让它并发执行的办法, 那么整体所需的时间就完全可以被压缩到十五秒以内去, 关于实现这个想法的核心部分的代码内容展示在下面。import asyncioimport httpxasync def async_explain_single(client, features, prob):prompt build_prompt(features, prob) # 复用之前的Prompt构建函数try:response await client.post(json{model: gpt-3.5-turbo,messages: [{role: user, content: prompt}],temperature: 0.3,max_tokens: 200},headers{Authorization: fBearer {openai.api_key}})return response.json()[choices][0][message][content].strip()except Exception as e:return fError: {e}async def async_batch_explain(users_list):async with httpx.AsyncClient(timeout30) as client:tasks [async_explain_single(client, u[features], u[prob])for u in users_list]return await asyncio.gather(*tasks)# 使用users_data [{features: {...}, prob: 0.92}, ...]results asyncio.run(async_batch_explain(users_data))请注意, 并发数千万不要超过20, 一旦超过就会触发速率限制这个东西, 我们是通过使用(15)这种方式来控制并发量的, 这种方法比那种无脑提高数量的做法要更加稳妥。6.第三步是关于对成本实施监控, 而监控的具体对象则是每一项解释工作所花费的费用。在那个函数最后的结束位置, 把成本统计的内容给它加进去。# 获取实际使用的token数需开启响应头response openai.chat.completions.create(...)prompt_tokens response.usage.prompt_tokenscompletion_tokens response.usage.completion_tokenstotal_tokens prompt_tokens completion_tokens# 按 GPT-3.5-turbo 价格计算$0.0015/1K prompt tokens, $0.002/1K completion tokenscost (prompt_tokens / 1000 * 0.0015) (completion_tokens / 1000 * 0.002)print(f本次调用消耗 {total_tokens} tokens成本 ${cost:.6f})我们给每一个业务方都提供了一个“解释成本仪表盘”, 当单次成本大于了0.01美元的时候, 就会自动发出告警信号, 这种做法反过来逼着团队去不断地进行优化, 还要去对逻辑进行校验。7. 后来的发展走向, 就是把内容从第六个演讲章节, 继续推进并落实到实际的工业生产环境中去。第六讲这个部分, 它可不是一个结束的节点, 它更是那种能在工业级别里用起来的AI技术的开始的地方, 我们这支队伍在今后的日子里头, 要把主要的精力放在往前推动这三条路线上面:关于私有化大型语言模型的替代方案, 我们将其定义为第一个方向。我们这样做, 并不是为了所谓的去美化, 而是为了解决数据合规和响应延迟的问题。我们已经做了验证。我们在四十个A10 GPU上面运行Qwen2-7B-模型的情况是。单次解释延迟一点八秒, 平均一点二秒。但是成本降低了百分之九十二。关键的地方在于使用LoRA微调。这样可以让模型学会严格遵循我们的三明治结构。代码已经开源在内部了, 欢迎交流。在方向二这个概念里面, 它主要是去解释一下那个生成过程还有模型训练它们两个之间存在的闭环关系。现在的解释被看作是事后诸葛亮, 后面的步骤要让模型参与到训练过程里面去。举个例子来讲, 当大语言模型的指出来购加次数为零是主要原因的时候, 我们就把那个特征在下一轮训练里的权重给动态地调高一点。这个操作需要去改造参数的代码部分, 现在已经是在测试环境里做过了验证的, 结果就是AUC提升了零点零二三。对于方向三来讲, 我们现在要进行的事情是多模态解释扩展。在文本解释的基础之上, 我们还需要去增加可视化的证据内容。举个例子来说, 如果遇到了流失用户这样的概念, 那么系统应该能够自动生成热力图, 通过该热力图来显示这些用户在接近过去的三十天内行为所呈现出来的衰减情况曲线, 并且还要使用箭头去标注出其中包含的关键性拐点问题。这种方案实施起来是需要进行集成的操作, 但是它的核心逻辑部分依然还是保持在第六讲的内容范围之内, 也就是说它属于工程层面的知识, 这里只需要将最终输出的格式从原始的纯文本形式修改成为 HTML 片段就行了。最后跟大家说一个小窍门, 就是在每一个新版本正式发布之前, 我们会使用对抗样本测试这种手段, 人工去制造出 5 条明显互相矛盾的数据信息, 好比说什么访问频次高达 100 次, 但是流失概率却达到了 99% 这样的情况, 然后去看模型的解释是否能够诚实地承认数据之间存在矛盾并且建议大家去核查一下原始的日志记录数据, 如果这个大语言模型开始胡编乱造一些理由出来, 这就说明要么是我们的相关流程需要改进, 要么是校验逻辑部分需要进行升级迭代。真正的那种专业情况, 它并不是体现为使着模型去做到永远都正确答案的样子, 反而是使得这模型能够有勇气去讲出那句不知道的话。我在实际的工程项目当中发现, 最为有效的学习模式并不是死板地去钻研理论内容, 而是应该携带一个明确的具体问题去执行代码编写操作。例如在当前时刻你就可以启动代码编辑器程序, 将文章里面的函数源代码复制粘贴到工作空间之中, 接着替换成你自己手里拥有的某一个分类分析模型工具, 然后运行程序以达成第一条解释说明的要求, 从而体验到那种突然恍然大悟的感觉。