第 10 篇:LLM 辅助数据分析——自动化 EDA、智能报告生成与自然语言查询
本文为「从零到落地:机器学习分析数据实战系列」第 10 篇,完整系列持续更新中。
前言
前 9 篇覆盖了从数据处理到模型部署的完整链路。从这篇开始进入高阶拓展板块——用大语言模型(LLM)让数据分析变得更高效、更智能、门槛更低。
回顾一下第 4 篇我们做 EDA(探索性数据分析)的流程:
- 手动调
df.describe() 看统计量
- 手动写 Matplotlib / Seaborn 画图
- 肉眼看图、读数字,总结出「扭矩和转速负相关」「故障样本只占 3.4%」等结论
- 把结论写成 Markdown 报告
整个过程花了大量时间在写代码画图 → 看图写文字上。而且写出来的报告质量完全取决于分析师的经验——新手可能漏掉关键发现,老手也可能因为疲劳而忽略细节。
如果有一个 AI 助手,能自动帮你完成这些工作呢?
这就是本篇的主题——用 LLM(大语言模型)辅助数据分析。我们会探索三个方向:
- 自动化 EDA——把数据丢给 LLM,让它自动生成完整的探索报告
- 自然语言查数据——运维人员不会写 Python?用中文问问题就能出分析结果
- 智能报告生成——模型预测结果 + LLM 自动生成可读的运维建议
本篇学完你将掌握:
- LLM 辅助数据分析的三种模式和各自适用场景
- 用 OpenAI API(或本地开源模型)构建 EDA 助手的完整代码
- Text-to-Pandas 的实现原理和安全性考量
- 如何构建一个能调用 Python 工具的 EDA Agent
- LLM 辅助 vs 手动 EDA 的效果对比
一、环境与工具准备
1.1 为什么现在才讲 LLM?
在前 9 篇里,我们故意没有引入 LLM。原因很简单:你必须先知道「正确答案」长什么样,才能判断 LLM 给的结果对不对。
如果你没手动做过 EDA,就不知道 LLM 生成的报告漏了哪些关键发现;如果你没手动训练过模型,就无法判断 LLM 给的特征重要性是否合理。LLM 是加速器,不是替代品。
现在你已经有了完整的基础,是时候用 LLM 来提效了。
1.2 LLM 选型
本篇提供两种方案,按你的条件选:
| 方案 |
适用场景 |
优点 |
缺点 |
| OpenAI API |
有 API Key,网络可访问 |
效果最好,代码最简单 |
需要付费(约 ¥0.1-0.5/次分析) |
| 本地开源模型(Ollama + Qwen) |
内网环境、数据敏感场景 |
免费、数据不出内网 |
需要 GPU 显存 ≥ 8GB |
1.3 环境安装
1 2 3 4 5 6 7 8 9 10 11 12
| conda activate ml-data
pip install openai==1.40.*
pip install langchain==0.2.* langchain-openai==0.1.* pip install tabulate==0.9.*
|
1.4 API Key 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import os
from openai import OpenAI client = OpenAI()
try: response = client.chat.completions.create( model='gpt-4o-mini', messages=[{'role': 'user', 'content': '你好,回复OK即可'}], max_tokens=5 ) print(f'API 连接成功: {response.choices[0].message.content}') except Exception as e: print(f'API 连接失败: {e}')
|
⚠️ 安全提醒:API Key 绝不能提交到 Git 仓库。用 .env 文件或环境变量管理,.env 文件加入 .gitignore。
1.5 加载数据
复用第 4-5 篇的 AI4I 2020 数据集和特征工程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import pandas as pd import numpy as np import warnings warnings.filterwarnings('ignore')
def load_and_prepare(filepath='ai4i2020.csv'): """加载 AI4I 2020 数据集并完成特征工程(复用第 4-5 篇代码)。""" df = pd.read_csv(filepath) df['Temperature_diff'] = df['Process temperature [K]'] - df['Air temperature [K]'] df['Power_approx'] = df['Rotational speed [rpm]'] * df['Torque [Nm]'] df['Speed_torque_ratio'] = df['Rotational speed [rpm]'] / (df['Torque [Nm]'] + 1e-8) bins = [0, 50, 120, 180, 253] labels = [0, 1, 2, 3] df['Wear_stage_code'] = pd.cut(df['Tool wear [min]'], bins=bins, labels=labels).astype(float) type_map = {'L': 0, 'M': 1, 'H': 2} df['Type_code'] = df['Type'].map(type_map) return df
df = load_and_prepare() print(f'数据加载完成: {df.shape[0]} 行 × {df.shape[1]} 列')
|
二、业务理解:LLM 能帮数据分析师做什么?
在写代码之前,先搞清楚 LLM 在数据分析中到底能帮什么忙。
2.1 传统 EDA 的痛点
回忆一下第 4 篇做 EDA 的过程,至少有三个耗时环节:
| 环节 |
传统做法 |
耗时 |
痛点 |
| 统计计算 |
df.describe() + 手动整理 |
10 分钟 |
数字出来了,但每个数字意味着什么?需要经验解读 |
| 可视化 |
手写 Seaborn 代码画图 |
30 分钟 |
每种图都要写代码、调参数、选颜色 |
| 撰写报告 |
看图写 Markdown 结论 |
30 分钟 |
完全依赖分析师经验和表达能力 |
加起来,一份完整的 EDA 报告至少需要 1-2 小时。
2.2 LLM 辅助的三种模式
我们按「LLM 介入深度」从浅到深,分三个层次:
模式一:LLM 当「翻译官」
把统计结果和图表丢给 LLM,让它帮你写分析报告。你仍然负责写代码和画图,LLM 只负责解读和生成文字。
- 适用场景:你已经会做 EDA,只是想加速报告撰写
- 技术难度:最低,只需要调 API 发文本
- 风险:最低,你仍然控制所有代码
模式二:LLM 当「分析师」
用自然语言问 LLM 问题(如「扭矩和转速的相关性如何?」),LLM 自动生成 Python 代码、执行代码、返回结果。
- 适用场景:不会写 Python 的运维人员 / 想快速探索多个角度
- 技术难度:中等,需要让 LLM 生成可执行的代码
- 风险:中等,LLM 可能生成错误代码,需要沙箱环境
模式三:LLM 当「Agent」
给 LLM 一套工具(计算器、画图器、数据库查询器),让它自主规划分析步骤、调用工具、整合结果,完成端到端的分析任务。
- 适用场景:构建完整的智能分析助手产品
- 技术难度:最高,需要 Agent 框架
- 风险:最高,LLM 可能走入死胡同,需要人工监督
本篇按这三个模式逐步深入。
三、模式一:LLM 当「翻译官」——自动化 EDA 报告
最简单的用法:你做完统计计算,把结果喂给 LLM,让它生成可读的分析报告。
3.1 核心思路
1 2
| 传统流程:数据 → Python 计算 → 人工看图 → 手写报告 LLM 辅助:数据 → Python 计算 → 结果文本 → LLM 生成报告
|
关键变化在最后一步:原来需要你自己写的「分析结论」,现在让 LLM 帮你写。你只需要把统计数字整理成结构化文本传给 LLM。
3.2 收集统计数据
先把所有 EDA 需要的统计量收集起来,打包成一段文本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| def collect_eda_stats(df): """收集 EDA 所需的全部统计信息,返回结构化文本。""" lines = [] lines.append(f'## 数据集基本信息') lines.append(f'- 行数: {df.shape[0]}') lines.append(f'- 列数: {df.shape[1]}') lines.append(f'- 列名: {list(df.columns)}') lines.append(f'\n## 数值特征统计描述') desc = df.describe().round(2) lines.append(desc.to_markdown()) missing = df.isnull().sum() missing_cols = missing[missing > 0] if len(missing_cols) > 0: lines.append(f'\n## 缺失值') for col, cnt in missing_cols.items(): lines.append(f'- {col}: {cnt} ({cnt/len(df)*100:.1f}%)') else: lines.append(f'\n## 缺失值: 无') lines.append(f'\n## 类别特征分布') for col in df.select_dtypes(include='object').columns: vc = df[col].value_counts() lines.append(f'\n### {col}') for val, cnt in vc.items(): lines.append(f'- {val}: {cnt} ({cnt/len(df)*100:.1f}%)') if 'Machine failure' in df.columns: lines.append(f'\n## 目标变量分布 (Machine failure)') vc = df['Machine failure'].value_counts() for val, cnt in vc.items(): lines.append(f'- {"故障" if val == 1 else "正常"}: {cnt} ({cnt/len(df)*100:.1f}%)') lines.append(f'\n## 相关性矩阵(数值特征)') num_cols = df.select_dtypes(include=[np.number]).columns.tolist() num_cols = [c for c in num_cols if c not in ['UDI', 'Product ID']] corr = df[num_cols].corr().round(3) lines.append(corr.to_markdown()) lines.append(f'\n## 高相关性特征对(|r| > 0.3)') high_corr = [] for i in range(len(num_cols)): for j in range(i+1, len(num_cols)): r = corr.iloc[i, j] if abs(r) > 0.3: high_corr.append((num_cols[i], num_cols[j], r)) if high_corr: for feat1, feat2, r in sorted(high_corr, key=lambda x: abs(x[2]), reverse=True): lines.append(f'- {feat1} ↔ {feat2}: r = {r:.3f}') else: lines.append('- 无(所有特征对的 |r| ≤ 0.3)') return '\n'.join(lines)
stats_text = collect_eda_stats(df) print(f'统计信息收集完成,共 {len(stats_text)} 字符') print(stats_text[:500])
|
3.3 调用 LLM 生成报告
把统计文本发给 LLM,让它生成结构化的 EDA 报告:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| def generate_eda_report(stats_text, model='gpt-4o-mini'): """用 LLM 基于统计信息生成 EDA 分析报告。""" system_prompt = """你是一位资深工业数据分析师。用户会给你一份数据集的统计信息,你需要生成一份完整的 EDA(探索性数据分析)报告。
报告要求: 1. 用中文撰写 2. 结构清晰,用 Markdown 格式 3. 每个发现都要解释「这意味着什么」和「对建模有什么影响」 4. 结合 CNC 铣床的业务背景解读数据(这是 CNC 铣床的预测性维护数据) 5. 如果发现潜在问题(如样本不均衡、异常值、高相关性),要明确指出并给出建议 6. 最后给出 3-5 条对后续建模的具体建议
不要编造数据中没有的信息。如果某个统计量缺失,直接说明。"""
user_prompt = f"""以下是 AI4I 2020 预测性维护数据集的统计信息,请生成完整的 EDA 报告:
{stats_text}"""
response = client.chat.completions.create( model=model, messages=[ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': user_prompt} ], temperature=0.3, max_tokens=3000 ) return response.choices[0].message.content
report = generate_eda_report(stats_text) print(report)
|
输出示例(LLM 生成,实际输出因模型版本而异):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| # AI4I 2020 预测性维护数据集 EDA 报告
## 1. 数据概况 数据集包含 10,000 条记录、18 列,覆盖了 CNC 铣床运行的多个维度...
## 2. 关键发现
### 2.1 样本严重不均衡 故障样本仅占 3.4%,这意味着...建议采用 SMOTE 过采样或调整类别权重...
### 2.2 扭矩与转速的强负相关 Rotational speed 与 Torque 的相关系数为 -0.468,这符合电机特性...
## 3. 建模建议 1. 优先处理样本不均衡问题... 2. 构造温度差特征... 3. ...
|
💡 提示:temperature=0.3 是关键设置。数据分析场景需要准确性而非创造性,所以温度要低。如果写营销文案,可以调高到 0.8-1.0。
3.4 加入对比视角
让 LLM 同时给出「传统分析师视角」和「LLM 可能遗漏的点」,做一个自我审视:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| def review_eda_report(stats_text, report, model='gpt-4o-mini'): """让 LLM 审视自己生成的报告,找出可能遗漏的点。""" prompt = f"""以下是一份数据集的统计信息和基于它生成的 EDA 报告。 请审视这份报告,找出: 1. 报告中遗漏了哪些重要发现? 2. 哪些结论可能过度推断(数据不足以支撑)? 3. 如果让你补充 3 个分析点,你会加什么?
## 统计信息 {stats_text}
## 报告 {report}"""
response = client.chat.completions.create( model=model, messages=[{'role': 'user', 'content': prompt}], temperature=0.2, max_tokens=1500 ) return response.choices[0].message.content
review = review_eda_report(stats_text, report) print('=== 报告审视结果 ===') print(review)
|
这一步的价值在于:LLM 审视 LLM 的输出,可以捕获第一轮生成时的盲点。当然,最终审核仍然需要人类——LLM 可能两轮都犯同一个错误。
3.5 保存报告
1 2 3 4 5 6 7 8 9 10
| with open('eda_report_auto.md', 'w', encoding='utf-8') as f: f.write('# LLM 自动生成的 EDA 报告\n\n') f.write('> 基于 AI4I 2020 数据集,由 LLM 自动生成\n\n') f.write(report) f.write('\n\n---\n\n') f.write('## 报告审视与补充\n\n') f.write(review)
print('报告已保存到 eda_report_auto.md')
|
四、模式二:LLM 当「分析师」——自然语言查数据
模式一只是让 LLM 写报告,代码还是你写的。模式二更进一步:让 LLM 自己写代码来分析数据。
4.1 为什么需要自然语言查数据?
在实际工厂里,不是所有人都会写 Python。运维主管可能想知道「高扭矩时段(> 60Nm)的故障率是多少」,但他不会写 df[df['Torque [Nm]'] > 60]['Machine failure'].mean()。
如果能让他直接用中文问问题,系统自动生成代码、执行、返回结果——这就是 Text-to-Pandas(自然语言转 Pandas 代码)的价值。
4.2 Text-to-Pandas 的实现原理
整体流程很简单:
1
| 用户中文提问 → LLM 翻译成 Pandas 代码 → 执行代码 → 返回结果 → LLM 解读结果
|
关键环节是第二步:LLM 需要知道数据长什么样(有哪些列、每列什么类型),才能写出正确的代码。所以我们先把数据的「元信息」告诉 LLM。
4.3 构造数据元信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| def build_dataframe_context(df): """构造 DataFrame 的元信息描述,让 LLM 了解数据结构。""" lines = [] lines.append(f'DataFrame 名称: df') lines.append(f'行数: {df.shape[0]}, 列数: {df.shape[1]}') lines.append(f'\n列信息:') for col in df.columns: dtype = str(df[col].dtype) nunique = df[col].nunique() null_count = df[col].isnull().sum() if df[col].dtype in [np.float64, np.int64, np.float32, np.int32]: min_val = df[col].min() max_val = df[col].max() mean_val = df[col].mean() lines.append(f'- {col}: {dtype}, 范围 [{min_val:.1f}, {max_val:.1f}], ' f'均值 {mean_val:.1f}, {nunique} 个唯一值, {null_count} 个缺失') elif df[col].dtype == 'object': values = df[col].unique().tolist()[:5] lines.append(f'- {col}: {dtype}, 取值 {values}, {nunique} 个唯一值, {null_count} 个缺失') else: lines.append(f'- {col}: {dtype}, {nunique} 个唯一值, {null_count} 个缺失') lines.append(f'\n数据前 3 行示例:') lines.append(df.head(3).to_markdown(index=False)) lines.append(f'\n业务背景: 这是 CNC 铣床的预测性维护数据集。') lines.append(f'- Machine failure = 1 表示发生了故障') lines.append(f'- TWF/HDF/PWF/OSF/RNF 是 5 种故障类型(1=发生,0=未发生)') lines.append(f'- Type 是产品规格: L(低)/M(中)/H(高)') lines.append(f'- Temperature_diff = 工艺温度 - 环境温度') lines.append(f'- Power_approx = 转速 × 扭矩(近似功率)') lines.append(f'- Wear_stage_code = 刀具磨损阶段(0-3)') return '\n'.join(lines)
context = build_dataframe_context(df) print(context)
|
4.4 Text-to-Pandas 核心函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| def ask_data(question, df, context=None, model='gpt-4o-mini'): """用自然语言查询数据:中文提问 → LLM 生成代码 → 执行 → 返回结果。 参数: question: 用户的中文问题 df: 要分析的 DataFrame context: 数据元信息(如果为 None,会自动生成) model: 使用的 LLM 模型 """ if context is None: context = build_dataframe_context(df) code_prompt = f"""你是一个数据分析助手。用户会用中文提问,你需要生成对应的 Pandas 代码来回答。
规则: 1. DataFrame 名称为 df,已经存在于环境中 2. 只生成代码,不要解释,不要 import 3. 把最终答案赋值给变量 `result` 4. result 应该是字符串、数字或小型 DataFrame 5. 如果需要分组统计,用 groupby 6. 所有输出用中文标注
数据结构: {context}
用户问题:{question}"""
response = client.chat.completions.create( model=model, messages=[{'role': 'user', 'content': code_prompt}], temperature=0, max_tokens=500 ) code = response.choices[0].message.content.strip() if code.startswith('```'): code = code.split('\n', 1)[1] if code.endswith('```'): code = code[:-3] code = code.strip() print(f'📝 生成的代码:\n{code}\n') try: exec_globals = {'df': df, 'np': np, 'pd': pd} exec(code, exec_globals) result = exec_globals.get('result', '代码执行完成,但未找到 result 变量') print(f'✅ 执行结果:\n{result}') return result, code except Exception as e: print(f'❌ 代码执行错误: {e}') return None, code
|
4.5 实际测试
测试几个典型的运维问题:
1 2
| result, code = ask_data('高扭矩(大于 60Nm)的样本有多少条?其中故障率是多少?', df, context)
|
1 2
| result, code = ask_data('不同产品规格(Type L/M/H)的故障率分别是多少?', df, context)
|
1 2
| result, code = ask_data('刀具磨损超过 200 分钟且转速高于 1800rpm 的样本中,有多少发生了故障?', df, context)
|
1 2
| result, code = ask_data('按刀具磨损阶段分组,每个阶段的平均扭矩和故障率分别是多少?', df, context)
|
4.6 安全沙箱:为什么不能直接 exec?
上面的代码用了 exec() 直接执行 LLM 生成的代码——这在生产环境中是非常危险的。
为什么?因为 LLM 可能生成恶意代码(如 import os; os.system('rm -rf /')),或者生成错误代码导致程序崩溃。在生产环境中,必须加沙箱保护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import subprocess import tempfile
def safe_execute_code(code, df, timeout=10): """在子进程中安全执行 LLM 生成的代码。 用独立进程执行,即使代码有问题也不会影响主程序。 """ with tempfile.NamedTemporaryFile(suffix='.csv', delete=False, mode='w') as f: df.to_csv(f.name, index=False) csv_path = f.name script = f""" import pandas as pd import numpy as np import warnings warnings.filterwarnings('ignore')
df = pd.read_csv('{csv_path}')
{code}
print(result) """ try: result = subprocess.run( ['python', '-c', script], capture_output=True, text=True, timeout=timeout ) if result.returncode == 0: print(f'✅ 执行结果:\n{result.stdout}') return result.stdout.strip() else: print(f'❌ 执行错误:\n{result.stderr}') return None except subprocess.TimeoutExpired: print(f'⏰ 执行超时({timeout}秒),代码可能有死循环') return None
|
⚠️ 核心原则:永远不要在 Web 服务中直接 exec() 用户输入或 LLM 生成的代码。要么用子进程沙箱,要么用 Docker 容器隔离。
五、模式三:LLM 当「Agent」——自主分析的智能助手
模式二里,LLM 只能回答单个问题。如果你想要它自主规划分析步骤,完成端到端的分析任务,就需要 Agent 模式。
5.1 什么是 Agent?
Agent(智能体)= LLM + 工具 + 规划能力。
- 普通 LLM:你问一个问题,它回答一个答案
- Agent:你给它一个任务,它自己拆解步骤、调用工具、根据中间结果决定下一步,直到任务完成
类比:普通 LLM 像一个只会回答问题的顾问,Agent 像一个会主动干活的实习生。
5.2 给 Agent 配备工具
Agent 需要「工具」才能和数据交互。我们给它三个基础工具:
| 工具 |
功能 |
什么时候用 |
run_code |
执行 Pandas 代码 |
需要计算统计量、筛选数据、分组聚合 |
describe_dataframe |
获取 DataFrame 的描述信息 |
需要了解数据结构 |
save_insight |
保存分析发现 |
记录关键结论,最终整合成报告 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
def run_code(code: str) -> str: """执行 Pandas 代码并返回结果。""" try: exec_globals = {'df': df.copy(), 'np': np, 'pd': pd} exec(code, exec_globals) result = exec_globals.get('result', '未找到 result 变量') return str(result) except Exception as e: return f'执行错误: {str(e)}'
def describe_dataframe() -> str: """获取 DataFrame 的结构和统计信息。""" return build_dataframe_context(df)
tools = { 'run_code': { 'function': run_code, 'description': '执行 Pandas 代码。代码中用 df 引用数据,结果赋值给 result 变量。', 'parameters': { 'code': {'type': 'string', 'description': '要执行的 Python/Pandas 代码'} } }, 'describe_dataframe': { 'function': describe_dataframe, 'description': '获取 DataFrame 的结构信息(列名、类型、范围)。', 'parameters': {} } }
|
5.3 Agent 循环:思考 → 行动 → 观察
Agent 的核心是一个循环:
1 2 3 4 5
| 1. 思考(Thought):基于当前信息,下一步该做什么? 2. 行动(Action):调用一个工具 3. 观察(Observation):看工具返回了什么结果 4. 重复 1-3,直到收集到足够信息 5. 总结(Final Answer):整合所有发现,输出最终报告
|
这就是经典的 ReAct(Reasoning + Acting) 模式。
5.4 简易 Agent 实现
下面实现一个简化版的 EDA Agent(不依赖 LangChain,纯 OpenAI API):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
| import json
def eda_agent(task, df, max_steps=10, model='gpt-4o-mini'): """一个简易的 EDA Agent,能自主规划并执行数据分析任务。 参数: task: 分析任务描述(中文) df: 要分析的数据 max_steps: 最大步骤数(防止无限循环) model: 使用的模型 """ context = build_dataframe_context(df) insights = [] system_prompt = f"""你是一个数据分析 Agent。你的任务是用工具分析数据,回答用户的问题。
你可以使用以下工具: 1. run_code(code): 执行 Pandas 代码,结果赋值给 result 变量 2. describe_dataframe(): 获取数据结构信息
工作流程: 1. 先分析任务需要哪些步骤 2. 逐步调用工具获取信息 3. 每一步只调用一个工具 4. 当你收集到足够信息后,输出最终分析结果
规则: - 用 JSON 格式回复,格式为 {{"thought": "你的思考", "action": "工具名", "action_input": "参数"}} - 当你要输出最终结果时,格式为 {{"thought": "你的思考", "final_answer": "最终分析结果"}} - run_code 的 action_input 是代码字符串 - describe_dataframe 不需要 action_input
数据结构: {context}
业务背景:CNC 铣床预测性维护数据。Machine failure=1 是故障。Type L/M/H 是产品规格。"""
messages = [ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': f'分析任务:{task}'} ] print(f'🤖 Agent 收到任务: {task}\n') for step in range(max_steps): print(f'--- 步骤 {step + 1}/{max_steps} ---') response = client.chat.completions.create( model=model, messages=messages, temperature=0.2, max_tokens=800 ) reply = response.choices[0].message.content.strip() messages.append({'role': 'assistant', 'content': reply}) try: json_start = reply.find('{') json_end = reply.rfind('}') + 1 action = json.loads(reply[json_start:json_end]) except json.JSONDecodeError: print(f'⚠️ LLM 回复格式错误,尝试继续...') messages.append({'role': 'user', 'content': '请用 JSON 格式回复'}) continue thought = action.get('thought', '') print(f'💭 思考: {thought}') if 'final_answer' in action: print(f'\n✅ 最终分析结果:\n{action["final_answer"]}') return action['final_answer'] tool_name = action.get('action', '') tool_input = action.get('action_input', '') if tool_name == 'run_code': print(f'🔧 执行代码:\n{tool_input}') result = run_code(tool_input) print(f'📊 结果: {result[:500]}') insights.append({'step': step + 1, 'code': tool_input, 'result': result[:500]}) messages.append({'role': 'user', 'content': f'工具返回结果:\n{result}'}) elif tool_name == 'describe_dataframe': result = describe_dataframe() print(f'📋 数据结构已获取') messages.append({'role': 'user', 'content': f'工具返回结果:\n{result}'}) else: messages.append({'role': 'user', 'content': f'未知工具 {tool_name},请使用 run_code 或 describe_dataframe'}) print(f'\n⚠️ 达到最大步骤数 ({max_steps}),强制输出结果') return '分析未完成,已达最大步骤数'
task = """分析这份数据中的故障模式: 1. 哪些特征组合最容易导致故障? 2. 不同产品规格的故障模式有什么不同? 3. 给出 3 条降低故障率的具体建议"""
agent_result = eda_agent(task, df, max_steps=8)
|
5.5 Agent 的输出解读
Agent 的执行过程大致如下(实际输出因模型而异):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 🤖 Agent 收到任务: 分析这份数据中的故障模式...
--- 步骤 1/8 --- 💭 思考: 先了解数据的整体结构 🔧 describe_dataframe() 📋 数据结构已获取
--- 步骤 2/8 --- 💭 思考: 看看故障样本在各特征上的分布 🔧 run_code: result = df.groupby('Machine failure')[['Air temperature [K]', ...]].mean().round(2) 📊 结果: 正常样本和故障样本的特征均值对比...
--- 步骤 3/8 --- 💭 思考: 进一步分析高扭矩+高转速的故障率 🔧 run_code: high_torque = df[df['Torque [Nm]'] > 60] result = f"高扭矩样本{len(high_torque)}条,故障率{high_torque['Machine failure'].mean():.1%}" 📊 结果: 高扭矩样本xxx条,故障率xx.x%...
... (继续分析)
--- 步骤 6/8 --- ✅ 最终分析结果: 1. 扭矩 > 55Nm 且刀具磨损 > 180min 时故障率最高(约 xx%) 2. Type H 产品故障率最高,主要故障类型是刀具磨损故障(TWF) 3. 建议:(1) 扭矩超过 55Nm 时降低转速 (2) 磨损超过 180min 强制换刀 (3) ...
|
💡 提示:Agent 的分析质量取决于底层 LLM 的能力。GPT-4o 级别效果最好,gpt-4o-mini 性价比高,本地开源模型(如 Qwen2.5-14B)也能用但需要更多引导。
六、模式二进阶:智能报告生成
前面讲了「用 LLM 分析数据」,现在讲「用 LLM 解读模型预测结果」——这在工厂里更有实际价值。
6.1 场景:模型预测后自动生成运维建议
第 5 篇的 XGBoost 模型输出了「这条数据预测为故障」,但运维人员需要的不只是是/否的答案,还需要:
- 为什么模型认为会出故障?(哪些特征异常?)
- 有多紧急?(预测概率是多少?)
- 该怎么办?(具体的处置建议)
6.2 整合 SHAP 解释与 LLM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| from xgboost import XGBClassifier from sklearn.model_selection import train_test_split import shap
FEATURES = [ 'Air temperature [K]', 'Rotational speed [rpm]', 'Torque [Nm]', 'Tool wear [min]', 'Temperature_diff', 'Power_approx', 'Speed_torque_ratio', 'Wear_stage_code' ]
X = df[FEATURES].values y = df['Machine failure'].values
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y )
model = XGBClassifier( n_estimators=200, max_depth=5, learning_rate=0.1, scale_pos_weight=28, eval_metric='logloss', random_state=42 ) model.fit(X_train, y_train)
explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X_test)
print(f'模型训练完成,SHAP 值计算完成') print(f'测试集大小: {X_test.shape[0]} 条')
|
6.3 为单条预测生成智能报告
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| def generate_prediction_report(sample_idx, features, values, shap_vals, prediction, proba, model=None): """为单条预测生成 LLM 辅助的运维建议报告。 参数: sample_idx: 样本编号 features: 特征名列表 values: 特征值 shap_vals: SHAP 值(该样本的每个特征贡献) prediction: 模型预测结果(0/1) proba: 预测概率 """ lines = [] lines.append(f'## 样本 #{sample_idx} 预测结果') lines.append(f'- 模型预测: {"⚠️ 故障" if prediction == 1 else "✅ 正常"}') lines.append(f'- 故障概率: {proba:.1%}') lines.append(f'\n## 特征值与 SHAP 贡献') lines.append(f'| 特征 | 当前值 | SHAP 贡献 | 影响方向 |') lines.append(f'| --- | --- | --- | --- |') feature_shap = list(zip(features, values, shap_vals)) feature_shap.sort(key=lambda x: abs(x[2]), reverse=True) for feat, val, shap_val in feature_shap: direction = '推高故障概率 ↑' if shap_val > 0 else '降低故障概率 ↓' lines.append(f'| {feat} | {val:.1f} | {shap_val:+.4f} | {direction} |') input_text = '\n'.join(lines) prompt = f"""你是一位 CNC 铣床运维专家。以下是故障预警模型对一台设备的诊断结果。
{input_text}
请给出: 1. **故障原因分析**:用通俗的语言解释为什么模型认为会(或不会)出故障 2. **紧急程度评估**:根据故障概率和特征异常程度,给出紧急等级(低/中/高/紧急) 3. **具体处置建议**:运维人员现在应该做什么(具体到操作步骤) 4. **预防措施**:长期来看,怎么避免类似情况
注意: - 结合 CNC 铣床的实际工况 - 建议要具体可执行,不要泛泛而谈 - 如果故障概率低,也要说明需要持续监控的指标"""
response = client.chat.completions.create( model='gpt-4o-mini', messages=[{'role': 'user', 'content': prompt}], temperature=0.3, max_tokens=1000 ) return response.choices[0].message.content
y_pred = model.predict(X_test) y_proba = model.predict_proba(X_test)[:, 1]
fault_indices = np.where((y_pred == 1) & (y_proba > 0.5))[0] top_fault = sorted(fault_indices, key=lambda i: y_proba[i], reverse=True)[:3]
for idx in top_fault: print(f'\n{"="*60}') report = generate_prediction_report( sample_idx=idx, features=FEATURES, values=X_test[idx], shap_vals=shap_values[idx], prediction=y_pred[idx], proba=y_proba[idx] ) print(report)
|
输出示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ## 样本 #1847 智能运维报告
### 故障原因分析 模型判断该设备有 78.3% 的概率即将发生故障,主要原因是: 1. **刀具磨损严重**(当前 231 分钟,已进入第 4 阶段)——这是最大的风险因素 2. **扭矩异常偏高**(67.2 Nm,远超均值 39.9)——说明切削阻力很大 3. **转速-扭矩比异常**——在高磨损下仍保持高转速,属于过载工况
### 紧急程度:🔴 高 建议在本批次加工完成后立即停机检查。
### 处置建议 1. 立即检查刀具磨损状态,如确认磨损超标则更换刀具 2. 检查工件材料硬度是否异常(可能混入了不合格批次的毛坯) 3. 适当降低转速至 1400rpm 以下,减少切削力
### 预防措施 - 建立刀具寿命管理台账,磨损超过 180 分钟强制更换 - 在加工程序中增加扭矩监控阈值,超过 55Nm 自动降速
|
七、效果对比:LLM 辅助 vs 手动 EDA
到目前为止,我们演示了 LLM 辅助数据分析的三种模式。现在做一个系统对比,帮你判断什么时候该用、什么时候不该用。
7.1 效率对比
| 维度 |
手动 EDA(第 4 篇) |
LLM 辅助 EDA(本篇) |
差距 |
| 统计计算 |
30 分钟(写代码+调参) |
10 分钟(写收集函数+调 API) |
快 3 倍 |
| 报告撰写 |
30 分钟(看图写文字) |
2 分钟(LLM 自动生成) |
快 15 倍 |
| 自然语言查询 |
不适用(需要写 Python) |
即时(10 秒/问题) |
从无到有 |
| 运维建议 |
不适用(需要领域专家) |
5 分钟(LLM 生成初稿) |
从无到有 |
7.2 质量对比
| 维度 |
手动 EDA |
LLM 辅助 EDA |
| 准确性 |
高(人类专家验证) |
中(LLM 可能编造不存在的数据关系) |
| 完整性 |
依赖分析师经验 |
较全面(LLM 会系统性检查各个维度) |
| 可解释性 |
高(分析师知道为什么这样写) |
中(LLM 是黑盒,可能给出奇怪的解释) |
| 一致性 |
低(不同分析师写出来的报告差异大) |
高(同一模型生成的报告风格统一) |
| 业务深度 |
高(资深分析师理解工厂实际) |
中(LLM 的业务知识来自训练数据,可能不够深入) |
7.3 什么时候用 LLM、什么时候不用
| 场景 |
推荐 |
原因 |
| 快速初探一份新数据 |
✅ 用 LLM |
5 分钟内出报告,快速建立初步认知 |
| 给非技术人员解释数据 |
✅ 用 LLM |
自然语言查询降低门槛 |
| 生成运维建议初稿 |
✅ 用 LLM |
LLM 给初稿,人类专家修改完善 |
| 发表学术论文 |
❌ 不用 LLM |
每个结论都需要严格验证,LLM 可能编造 |
| 关键生产决策 |
❌ 不完全依赖 LLM |
LLM 建议仅供参考,必须人类专家终审 |
| 数据包含敏感信息 |
⚠️ 看情况 |
用本地模型(Ollama),不要把数据发给云端 API |
八、成本与性能考量
8.1 API 调用成本
| 操作 |
Token 消耗(估算) |
成本(gpt-4o-mini) |
成本(gpt-4o) |
| 生成 EDA 报告 |
~5000 input + ~3000 output |
≈ ¥0.01 |
≈ ¥0.15 |
| 单次自然语言查询 |
~2000 input + ~200 output |
≈ ¥0.003 |
≈ ¥0.05 |
| Agent 完整分析 |
~15000 input + ~3000 output |
≈ ¥0.03 |
≈ ¥0.50 |
| 智能运维报告 |
~2000 input + ~500 output |
≈ ¥0.005 |
≈ ¥0.08 |
💡 提示:gpt-4o-mini 性价比极高,日常分析用它就够了。gpt-4o 适合需要深度推理的复杂分析。如果你一天做 100 次查询,gpt-4o-mini 一天才花 ¥0.3。
8.2 延迟优化
LLM API 的延迟(响应时间)在数据分析场景中不可忽视:
| 操作 |
典型延迟 |
优化方法 |
| EDA 报告生成 |
10-20 秒 |
分批统计,并行请求 |
| 自然语言查询 |
2-5 秒 |
缓存常见问题的回答 |
| Agent 分析 |
30-60 秒 |
减少最大步骤数,使用更快的模型 |
对于交互式场景(如运维人员实时查询),2-5 秒的延迟完全可以接受。但对于自动化流水线,需要异步处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import asyncio from openai import AsyncOpenAI
async_client = AsyncOpenAI()
async def async_ask_data(question, df, context): """异步版本的自然语言查询,适合同时处理多个请求。""" code_prompt = f"""生成 Pandas 代码回答问题。DataFrame 名称: df。 只返回代码,不要解释。
{context}
问题:{question}"""
response = await async_client.chat.completions.create( model='gpt-4o-mini', messages=[{'role': 'user', 'content': code_prompt}], temperature=0, max_tokens=500 ) code = response.choices[0].message.content.strip() if code.startswith('```'): code = code.split('\n', 1)[1] if code.endswith('```'): code = code[:-3] code = code.strip() exec_globals = {'df': df, 'np': np, 'pd': pd} exec(code, exec_globals) return exec_globals.get('result', '无结果')
async def batch_query(): questions = [ '故障样本的平均扭矩是多少?', '转速最高的 10 条记录中有多少是故障?', '不同磨损阶段的样本数量分布', ] context = build_dataframe_context(df) tasks = [async_ask_data(q, df, context) for q in questions] results = await asyncio.gather(*tasks) for q, r in zip(questions, results): print(f'Q: {q}\nA: {r}\n')
|
九、本地部署方案:数据不出内网
在工厂场景中,数据往往不能发到外部 API(安全合规要求)。这时需要用本地部署的开源模型。
9.1 Ollama + Qwen2.5
1 2 3 4 5 6 7 8
|
ollama pull qwen2.5:14b
ollama pull qwen2.5:7b
|
9.2 对接本地模型
Ollama 提供兼容 OpenAI 的 API,代码几乎不用改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| local_client = OpenAI( base_url='http://localhost:11434/v1', api_key='ollama' )
def generate_eda_report_local(stats_text, model='qwen2.5:14b'): """用本地模型生成 EDA 报告(数据不出内网)。""" response = local_client.chat.completions.create( model=model, messages=[ {'role': 'system', 'content': '你是资深工业数据分析师,用中文撰写分析报告。'}, {'role': 'user', 'content': f'请分析以下统计数据并生成 EDA 报告:\n{stats_text}'} ], temperature=0.3, max_tokens=3000 ) return response.choices[0].message.content
|
9.3 本地 vs 云端选型
| 维度 |
云端 API(OpenAI) |
本地模型(Ollama + Qwen) |
| 数据安全 |
数据发到外部服务器 |
数据完全不出内网 |
| 分析质量 |
更好(GPT-4o 是目前最强) |
稍弱(开源模型有差距) |
| 成本 |
按量付费 |
硬件一次性投入 |
| 延迟 |
受网络影响(2-10秒) |
本地推理更快(1-3秒) |
| 适合场景 |
研究/学习/非敏感数据 |
工厂/医疗/金融等敏感场景 |
📌 实际建议:先用云端 API 验证效果、跑通流程,确认方案可行后,再根据数据安全要求决定是否迁移到本地模型。
十、LLM 辅助 EDA 的最佳实践
经过前面几个模式的实战,总结几条落地经验:
10.1 Prompt 工程要点
| 要点 |
说明 |
示例 |
| 给角色 |
告诉 LLM 它是什么身份 |
「你是资深工业数据分析师」 |
| 给数据 |
提供结构化统计信息而非原始数据 |
df.describe() 的输出而非 CSV |
| 给约束 |
明确输出格式和限制 |
「用 Markdown 格式,不超过 2000 字」 |
| 给业务背景 |
告诉 LLM 数据来自什么场景 |
「CNC 铣床预测性维护数据」 |
| 低温度 |
数据分析场景 temperature ≤ 0.3 |
减少随机性,提高准确性 |
10.2 常见坑与避坑方法
| 坑 |
表现 |
解决方法 |
| LLM 编造数据 |
报告中出现数据集中不存在的数字 |
在 Prompt 中明确「不要编造数据中没有的信息」 |
| 代码执行错误 |
LLM 生成的代码用了不存在的列名 |
提供完整的列名和类型信息 |
| 过度推断 |
从少量数据得出过于绝对的结论 |
要求 LLM 标注结论的置信度 |
| 上下文窗口溢出 |
数据太大,LLM 看不完 |
只发送统计摘要,不发原始数据 |
| 安全漏洞 |
LLM 生成恶意代码 |
用沙箱执行,限制可用模块 |
10.3 推荐的混合工作流
1 2 3 4 5 6 7 8 9
| 第一步:手动用 Pandas 做基础统计(5 分钟) ↓ 第二步:把统计结果发给 LLM,生成 EDA 报告初稿(2 分钟) ↓ 第三步:人类审核报告,修正错误结论(10 分钟) ↓ 第四步:针对感兴趣的发现,用自然语言追问 LLM(每个问题 10 秒) ↓ 第五步:LLM 生成最终报告 + 运维建议(5 分钟)
|
总耗时约 25 分钟,相比纯手动(1-2 小时)快 3-4 倍,且报告质量有保证。
总结与回顾
| 要点 |
总结 |
| 三种模式 |
翻译官(写报告)→ 分析师(查数据)→ Agent(自主分析),介入深度递增 |
| 自动化 EDA |
收集统计量 → LLM 生成报告 → LLM 自我审视 → 人类终审 |
| 自然语言查询 |
中文提问 → LLM 生成 Pandas 代码 → 沙箱执行 → 返回结果 |
| Agent 模式 |
LLM + 工具 + ReAct 循环,自主规划分析步骤 |
| 智能报告 |
模型预测 + SHAP 解释 + LLM 运维建议 = 端到端智能预警 |
| 安全 |
生产环境必须用沙箱执行代码,敏感数据用本地模型 |
| 成本 |
gpt-4o-mini 日常使用成本极低(¥0.3/天),性价比最优 |
| 最佳实践 |
低温度 + 结构化输入 + 业务背景 + 人类审核 |
核心认知:
- LLM 是加速器,不是替代品——你必须先知道正确答案长什么样,才能判断 LLM 的输出对不对
- 人类审核不可省略——LLM 可能编造数据、过度推断、生成有漏洞的代码,每一步都需要人类把关
- 从简单开始——先用模式一(翻译官)提效,确认效果后再升级到模式二(分析师)和模式三(Agent)
下篇预告
第 11 篇:自然语言查数据 —— 本篇演示了基础的 Text-to-Pandas,下篇我们要构建一个完整的自然语言数据分析系统:支持多轮对话、自动纠错、图表生成、历史记录查询——让不会写代码的运维人员也能像和数据「聊天」一样分析设备运行状态。
本文为「从零到落地:机器学习分析数据实战系列」第 10 篇,完整系列持续更新中。