从零构建自动化机器学习实验平台:Discovery Loop核心架构与Python实战

📅 2026/8/8 12:50:18
从零构建自动化机器学习实验平台:Discovery Loop核心架构与Python实战
最近在技术圈看到不少关于 Jeff Dean 创办 Discovery Loop 的讨论作为 AI 和系统架构领域的传奇人物他的新动向自然备受关注。虽然这本身是一个行业新闻但背后折射出的技术趋势——特别是大规模机器学习系统、AI 基础设施以及高效能计算的研究方向——对于我们开发者而言具有极强的学习和借鉴意义。本文将从技术视角出发不讨论新闻本身而是深入拆解构建类似 Discovery Loop 所可能涉及的核心技术栈、架构思想以及工程实践为致力于构建下一代智能系统的开发者提供一份实用的参考指南。无论你是对分布式系统感兴趣的后端工程师还是专注于机器学习平台搭建的算法工程师亦或是希望理解顶尖 AI 实验室技术选型的学生本文都将通过概念梳理、架构分析和模拟实践帮助你建立起相关的知识框架。我们将从基础概念入手逐步深入到系统设计、关键组件实现以及生产环境中的挑战与最佳实践。1. 背景与核心概念从研究到系统的跨越在深入技术细节之前我们有必要理解这类项目背后的核心驱动力。传统的机器学习工作流往往是一个相对线性的过程数据收集 - 特征工程 - 模型训练 - 评估部署。然而对于探索性研究、自动化机器学习AutoML或大规模模型搜索而言需要一个能够自动执行“提出假设 - 运行实验 - 分析结果 - 生成新假设”的闭环系统。这就是“发现循环”Discovery Loop或“研究循环”的核心思想。1.1 什么是 Discovery Loop从工程角度看Discovery Loop 是一个高度自动化的、可扩展的实验管理与智能探索平台。它旨在减少人类研究员在重复性实验任务上的投入通过系统化的方式管理海量实验并利用实验反馈自动调整研究方向或参数加速科学发现或模型迭代。其关键技术特征包括大规模实验编排与管理能够并发调度成千上万个计算任务管理其依赖、资源与生命周期。自动化与智能引导集成优化算法如贝叶斯优化、强化学习来根据历史实验结果智能地建议下一组最有潜力的实验参数。数据与知识沉淀所有实验的配置、代码、结果、指标和衍生数据都被完整记录、版本化并建立关联形成可查询、可复现的知识库。异构计算支持无缝支持 CPU、GPU、TPU 等多种硬件后端以及模拟器、真实物理设备等不同计算环境。1.2 核心组件与相关技术栈构建这样一个系统通常会涉及以下几个层面的技术资源管理与调度层类似 Kubernetes 的容器编排系统用于管理计算节点和任务调度。对于 ML 任务Kubeflow、Apache Airflow 或 Meta 的 Ax 等框架常被集成或作为参考。实验跟踪与元数据管理MLflow、Weights Biases、Neptune.ai 等工具的核心功能。需要自定义存储方案来记录实验的完整上下文。自动化机器学习引擎集成如 Google Vizier、Optuna、Hyperopt 等超参数优化框架或更广义的算法来指导实验探索。大规模数据处理与存储处理实验生成的海量数据、模型检查点和日志可能用到 Apache Beam、TensorFlow Data API 以及云存储或分布式文件系统如 HDFS、S3。高性能计算与通信涉及 GPU 集群管理、高速网络如 InfiniBand以及分布式训练框架如 TensorFlow、PyTorch DDP、JAX的深度集成。理解这些概念后我们将从一个简化的、可实操的模拟系统入手逐步揭示其内部机制。2. 环境准备与版本说明为了进行概念验证和模拟我们将使用 Python 作为主要语言构建一个轻量级的本地 Discovery Loop 模拟器。这个模拟器将涵盖实验管理、简单智能引导和结果分析的核心流程。环境要求操作系统Linux (Ubuntu 20.04)、macOS 或 WSL2 (Windows)。本文示例基于 Linux 环境。Python版本 3.8 或 3.9。建议使用虚拟环境venv 或 conda。关键 Python 包sqlalchemy用于实验元数据的 ORM 和存储。optuna用于超参数优化和实验建议。mlflow用于实验跟踪我们将模拟其部分核心逻辑。numpy,scikit-learn用于模拟机器学习任务。docker(可选)用于模拟容器化任务执行。存储本地文件系统用于存储结果SQLite 数据库用于存储元数据。版本说明本文示例代码基于以下常见版本但重点在于演示架构和思路。实际生产环境需要根据需求选择更稳定、可扩展的组件和版本。# 示例依赖文件 requirements.txt sqlalchemy1.4.0 optuna2.10.0 mlflow1.24.0 numpy1.21.0 scikit-learn1.0.0你可以通过以下命令创建环境并安装依赖python -m venv discovery_loop_env source discovery_loop_env/bin/activate # Linux/macOS # discovery_loop_env\Scripts\activate # Windows pip install -r requirements.txt3. 核心架构与原理拆解一个基础的 Discovery Loop 系统可以抽象为以下几个核心模块它们协同工作形成闭环。3.1 实验定义与配置管理实验是系统的基本单元。每个实验需要包含完整的、可复现的配置。配置内容算法参数、数据集路径、环境变量、代码版本Git Commit、资源需求CPU/GPU 内存。序列化通常使用 YAML 或 JSON 格式存储配置便于版本控制和差异比较。唯一标识每个实验应有唯一的 ID如 UUID用于在整个生命周期中追踪。3.2 任务调度与执行引擎负责将实验配置转化为实际运行的计算任务。执行器可以是本地进程、Docker 容器、Kubernetes Job 或提交到 Slurm 集群。核心是隔离性与可重复性。状态机实验任务通常有PENDING、RUNNING、SUCCESS、FAILED、STOPPED等状态需要持久化状态并处理状态转换。队列与优先级管理待执行实验的队列支持基于优先级、资源可用性或依赖关系的调度。3.3 元数据存储与实验追踪这是系统的“记忆”部分至关重要。存储内容实验配置输入参数。运行指标损失、准确率等随时间变化的指标。输出产物模型文件、可视化图表、日志文件的存储路径或引用。系统指标资源使用情况CPU/内存/GPU 利用率、开始结束时间。数据库设计需要设计良好的关系型或文档型数据库 schema 来高效关联和查询这些数据。3.4 智能建议引擎Loop 的核心这是实现“循环”的关键。它分析历史实验数据并生成新的实验配置。输入所有已完成实验的配置和结果。算法网格/随机搜索基础方法无智能引导。贝叶斯优化如使用 Gaussian Process 建模目标函数与参数的关系寻找最有可能提升的区域。Optuna、Google Vizier 采用此类方法。进化算法模拟自然选择适用于参数空间复杂、非连续的情况。多目标优化同时优化多个指标如精度和推理速度。输出一组新的、待执行的实验配置建议。3.5 结果分析与可视化为用户提供洞察帮助理解系统探索的过程和结果。平行坐标图用于可视化高维参数空间与结果的关系。重要性分析分析各个超参数对最终结果的影响程度。结果对比轻松对比不同实验组的结果。理解了这些原理我们开始动手构建一个简化版的模拟系统。4. 完整实战案例构建轻量级 Discovery Loop 模拟器我们将创建一个名为SimpleDiscoveryLoop的模拟系统它包含一个本地执行器、一个基于 SQLite 的元数据存储和一个集成 Optuna 的建议引擎。4.1 项目结构创建首先创建项目目录结构。mkdir simple_discovery_loop cd simple_discovery_loop mkdir -p core storage executor touch __init__.py touch core/__init__.py core/experiment.py core/suggester.py touch storage/__init__.py storage/metadata_store.py touch executor/__init__.py executor/local_executor.py touch main.py config.yaml4.2 定义实验数据模型core/experiment.py这是系统的核心数据对象。# core/experiment.py import uuid from dataclasses import dataclass, field, asdict from datetime import datetime from enum import Enum from typing import Any, Dict, Optional class ExperimentStatus(Enum): PENDING PENDING RUNNING RUNNING SUCCESS SUCCESS FAILED FAILED STOPPED STOPPED dataclass class Experiment: 实验实体类代表一个可运行的任务单元。 experiment_id: str field(default_factorylambda: str(uuid.uuid4())) name: str # 实验配置例如超参数 config: Dict[str, Any] field(default_factorydict) # 实验状态 status: ExperimentStatus ExperimentStatus.PENDING # 实验结果指标例如 accuracy, loss metrics: Dict[str, float] field(default_factorydict) # 产出物路径例如模型文件路径 artifacts: Dict[str, str] field(default_factorydict) # 日志路径 log_path: Optional[str] None # 创建和更新时间 created_at: datetime field(default_factorydatetime.now) updated_at: datetime field(default_factorydatetime.now) # 所属的研究或项目ID用于分组 project_id: str default def to_dict(self) - Dict[str, Any]: 将实验对象转换为字典便于存储和序列化。 data asdict(self) data[status] self.status.value data[created_at] self.created_at.isoformat() data[updated_at] self.updated_at.isoformat() return data classmethod def from_dict(cls, data: Dict[str, Any]) - Experiment: 从字典还原实验对象。 data[status] ExperimentStatus(data[status]) data[created_at] datetime.fromisoformat(data[created_at]) data[updated_at] datetime.fromisoformat(data[updated_at]) # 移除可能多余的字段 return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})4.3 实现元数据存储storage/metadata_store.py使用 SQLAlchemy 和 SQLite 持久化实验数据。# storage/metadata_store.py import json import sqlite3 from typing import List, Optional, Dict, Any from core.experiment import Experiment, ExperimentStatus class MetadataStore: 基于SQLite的简易元数据存储。 def __init__(self, db_path: str discovery_loop.db): self.db_path db_path self._init_db() def _init_db(self): 初始化数据库表。 conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS experiments ( experiment_id TEXT PRIMARY KEY, name TEXT, config TEXT, status TEXT, metrics TEXT, artifacts TEXT, log_path TEXT, created_at TEXT, updated_at TEXT, project_id TEXT ) ) conn.commit() conn.close() def save_experiment(self, experiment: Experiment): 保存或更新实验记录。 conn sqlite3.connect(self.db_path) cursor conn.cursor() data experiment.to_dict() # 将字典字段序列化为JSON字符串存储 data[config] json.dumps(data[config]) data[metrics] json.dumps(data[metrics]) data[artifacts] json.dumps(data[artifacts]) cursor.execute( INSERT OR REPLACE INTO experiments (experiment_id, name, config, status, metrics, artifacts, log_path, created_at, updated_at, project_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) , ( data[experiment_id], data[name], data[config], data[status], data[metrics], data[artifacts], data[log_path], data[created_at], data[updated_at], data[project_id] )) conn.commit() conn.close() def get_experiment(self, experiment_id: str) - Optional[Experiment]: 根据ID获取实验。 conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row cursor conn.cursor() cursor.execute(SELECT * FROM experiments WHERE experiment_id ?, (experiment_id,)) row cursor.fetchone() conn.close() if row: data dict(row) data[config] json.loads(data[config]) data[metrics] json.loads(data[metrics]) data[artifacts] json.loads(data[artifacts]) return Experiment.from_dict(data) return None def get_experiments_by_status(self, status: ExperimentStatus) - List[Experiment]: 根据状态获取实验列表。 conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row cursor conn.cursor() cursor.execute(SELECT * FROM experiments WHERE status ?, (status.value,)) rows cursor.fetchall() conn.close() experiments [] for row in rows: data dict(row) data[config] json.loads(data[config]) data[metrics] json.loads(data[metrics]) data[artifacts] json.loads(data[artifacts]) experiments.append(Experiment.from_dict(data)) return experiments def get_all_experiments(self, project_id: str default) - List[Experiment]: 获取指定项目的所有实验。 conn sqlite3.connect(self.db_path) conn.row_factory sqlite3.Row cursor conn.cursor() cursor.execute(SELECT * FROM experiments WHERE project_id ? ORDER BY created_at DESC, (project_id,)) rows cursor.fetchall() conn.close() experiments [] for row in rows: data dict(row) data[config] json.loads(data[config]) data[metrics] json.loads(data[metrics]) data[artifacts] json.loads(data[artifacts]) experiments.append(Experiment.from_dict(data)) return experiments4.4 实现本地任务执行器executor/local_executor.py这个执行器模拟运行一个“机器学习任务”——在这里是训练一个简单的 sklearn 模型。# executor/local_executor.py import subprocess import sys import time import logging from typing import Dict, Any from core.experiment import Experiment, ExperimentStatus logging.basicConfig(levellogging.INFO) logger logging.getLogger(__name__) class LocalExecutor: 本地进程执行器模拟运行实验任务。 def run(self, experiment: Experiment) - Experiment: 执行实验。在实际系统中这里可能会启动一个Docker容器或提交到集群。 此处我们模拟一个计算过程。 logger.info(fStarting experiment {experiment.experiment_id}: {experiment.name}) experiment.status ExperimentStatus.RUNNING experiment.updated_at datetime.now() try: # 模拟一个“训练任务”使用配置的参数生成一个模拟的准确率 # 这里我们假设 config 里有 learning_rate 和 n_estimators 等参数 config experiment.config lr config.get(learning_rate, 0.01) n_est config.get(n_estimators, 100) # 一个非常简单的模拟准确率与 learning_rate 和 n_estimators 有关 # 这只是为了演示真实情况是运行真实的训练代码 simulated_accuracy 0.5 0.3 * (lr / 0.1) 0.1 * (n_est / 200) simulated_accuracy min(max(simulated_accuracy, 0.5), 0.95) # 限制在0.5-0.95之间 # 模拟运行时间 time.sleep(2) # 更新实验结果 experiment.metrics { accuracy: simulated_accuracy, loss: 1 - simulated_accuracy, training_time_seconds: 2.0 } experiment.artifacts { model_path: f/fake/path/to/model_{experiment.experiment_id}.pkl } experiment.status ExperimentStatus.SUCCESS logger.info(fExperiment {experiment.experiment_id} succeeded with accuracy: {simulated_accuracy:.4f}) except Exception as e: logger.error(fExperiment {experiment.experiment_id} failed: {e}) experiment.status ExperimentStatus.FAILED experiment.metrics[error] str(e) experiment.updated_at datetime.now() return experiment # 注意需要从datetime模块导入datetime from datetime import datetime4.5 实现智能建议引擎core/suggester.py集成 Optuna 来根据历史实验结果建议新的参数。# core/suggester.py import optuna from typing import List, Dict, Any from core.experiment import Experiment from storage.metadata_store import MetadataStore class OptunaSuggester: 使用Optuna进行参数建议的引擎。 def __init__(self, storage: MetadataStore, study_name: str discovery_loop_study): self.storage storage self.study_name study_name # 使用内存存储生产环境需用数据库存储 self.study optuna.create_study( study_namestudy_name, storagesqlite:///optuna_study.db, # 示例使用SQLite持久化Optuna study load_if_existsTrue, directionmaximize # 我们假设目标是最大化 accuracy ) def _create_objective(self, completed_experiments: List[Experiment]): 根据历史实验动态构建Optuna目标函数。 # 这是一个简化的示例。更复杂的实现可以训练一个代理模型Surrogate Model。 # 这里我们让Optuna基于历史数据直接进行贝叶斯优化。 def objective(trial): # 定义新的搜索空间 learning_rate trial.suggest_float(learning_rate, 1e-4, 0.5, logTrue) n_estimators trial.suggest_int(n_estimators, 10, 500) # 可以添加更多参数... # 关键我们需要一个方法来评估这组参数。 # 在真正的Discovery Loop中这里会返回一个需要被外部执行器运行的任务。 # 但对于Optuna我们需要一个立即返回的数值。 # 因此这个suggester的作用是“生成建议”而不是“评估”。 # 评估将由外部执行器完成结果再反馈回来。 # 所以这个objective函数在这里并不直接计算目标值。 # 相反我们让suggester只负责生成参数评估由主循环完成。 # 返回一个占位值真正的优化由外部循环驱动。 return 0.0 # 占位符 return objective def suggest_new_parameters(self, n_trials: int 1) - List[Dict[str, Any]]: 基于历史实验建议一组新的参数配置。 返回一个参数配置的字典列表。 suggestions [] # 获取所有成功实验的历史数据用于引导优化 all_experiments self.storage.get_all_experiments() successful_exps [exp for exp in all_experiments if exp.status.name SUCCESS] if len(successful_exps) 0: # 如果有历史数据告诉Optuna这些先验知识enqueue for exp in successful_exps: trial optuna.trial.create_trial( paramsexp.config, distributions{ learning_rate: optuna.distributions.FloatDistribution(1e-4, 0.5, logTrue), n_estimators: optuna.distributions.IntDistribution(10, 500), }, valueexp.metrics.get(accuracy, 0.0) ) self.study.add_trial(trial) else: # 没有历史数据从先验分布中采样 pass # 让Optuna基于现有研究包含先验知识建议新参数 for _ in range(n_trials): trial self.study.ask() # 请求一个新的 trial params trial.params suggestions.append(params) return suggestions4.6 组装主循环main.py现在我们将所有组件组装起来形成一个可以运行多个迭代的 Discovery Loop。# main.py import time import logging from datetime import datetime from core.experiment import Experiment, ExperimentStatus from storage.metadata_store import MetadataStore from executor.local_executor import LocalExecutor from core.suggester import OptunaSuggester logging.basicConfig(levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) def main(): Discovery Loop 主程序。 # 初始化组件 storage MetadataStore() executor LocalExecutor() suggester OptunaSuggester(storage) project_id demo_project max_iterations 5 # 最大循环迭代次数 experiments_per_iteration 2 # 每次迭代并发生成的实验数 for iteration in range(max_iterations): logger.info(f Starting Discovery Loop Iteration {iteration 1} ) # 阶段1: 生成建议 suggested_params_list suggester.suggest_new_parameters(n_trialsexperiments_per_iteration) experiments_to_run [] for i, params in enumerate(suggested_params_list): exp Experiment( namefiter_{iteration1}_exp_{i1}, configparams, project_idproject_id ) experiments_to_run.append(exp) storage.save_experiment(exp) logger.info(fCreated experiment {exp.experiment_id} with config: {params}) # 阶段2: 执行实验 completed_experiments [] for exp in experiments_to_run: logger.info(fExecuting experiment {exp.experiment_id}) updated_exp executor.run(exp) # 执行 storage.save_experiment(updated_exp) # 保存结果 completed_experiments.append(updated_exp) logger.info(fCompleted experiment {exp.experiment_id} with status: {updated_exp.status.name}, accuracy: {updated_exp.metrics.get(accuracy, N/A)}) # 阶段3: 分析结果 (此处简化仅打印) logger.info(fIteration {iteration1} results summary:) for exp in completed_experiments: if exp.status ExperimentStatus.SUCCESS: logger.info(f - {exp.experiment_id}: accuracy{exp.metrics.get(accuracy):.4f}) time.sleep(1) # 模拟循环间隔 logger.info( Discovery Loop Finished ) # 打印最终所有实验 all_exps storage.get_all_experiments(project_idproject_id) logger.info(fTotal experiments run: {len(all_exps)}) for exp in all_exps: logger.info(f ID: {exp.experiment_id}, Name: {exp.name}, Status: {exp.status.name}, Accuracy: {exp.metrics.get(accuracy, N/A)}) if __name__ __main__: main()4.7 运行与验证在项目根目录下运行主程序python main.py预期你会看到类似以下的输出展示了循环的迭代过程2023-10-27 10:00:00,000 - __main__ - INFO - Starting Discovery Loop Iteration 1 2023-10-27 10:00:00,001 - __main__ - INFO - Created experiment abc123... with config: {learning_rate: 0.05, n_estimators: 150} ... 2023-10-27 10:00:02,005 - executor.local_executor - INFO - Experiment abc123... succeeded with accuracy: 0.8123 ... 2023-10-27 10:00:10,000 - __main__ - INFO - Discovery Loop Finished 2023-10-27 10:00:10,001 - __main__ - INFO - Total experiments run: 10同时会在当前目录下生成discovery_loop.db和optuna_study.db两个 SQLite 数据库文件分别存储实验元数据和 Optuna 优化过程的数据。5. 常见问题与排查思路在构建和运行此类系统时会遇到一些典型问题。问题现象可能原因排查思路与解决方案实验一直处于 PENDING 状态1. 任务队列堵塞或调度器未启动。2. 资源不足如 GPU 内存不够。3. 任务依赖未满足。1. 检查调度器日志确认是否有死锁或错误。2. 使用nvidia-smi或集群管理工具查看资源利用率。3. 检查实验的依赖关系图是否正确。实验失败率高1. 代码或环境镜像有 bug。2. 配置参数超出合理范围如学习率过大。3. 数据路径错误或权限问题。4. 计算节点不稳定。1. 查看失败实验的详细日志和堆栈跟踪。2. 为数值参数设置合理的搜索边界和先验分布。3. 在任务启动脚本中加入路径检查和权限验证。4. 引入重试机制并监控节点健康状态。智能建议引擎没有效果1. 历史实验数据太少优化算法无法有效学习。2. 目标函数定义不合理与业务目标不符。3. 参数搜索空间过大或过小。4. 优化算法本身不适合该问题如离散参数多。1. 初期结合随机搜索积累一定数据后再启用贝叶斯优化。2. 重新审视和设计评估指标Metrics。3. 基于领域知识调整搜索空间可进行分阶段搜索。4. 尝试不同的优化器如 TPE, CMA-ES, 随机搜索。元数据存储性能瓶颈1. 实验数量极大单表查询慢。2. 存储了过大的二进制文件如模型到数据库。1. 对常用查询字段如project_id,status,created_at建立索引。2. 将大型产出物模型文件、日志存储在对象存储如 S3或文件系统数据库中只存路径引用。考虑分库分表。系统无法水平扩展1. 中心化的调度器或数据库成为单点瓶颈。2. 任务间存在复杂的共享状态。1. 采用微服务架构将调度器、元数据存储、UI 等服务解耦各自独立扩展。使用消息队列如 Kafka, RabbitMQ解耦组件。2. 尽可能设计无状态的任务状态外置到存储服务。6. 最佳实践与工程建议将模拟系统扩展到生产环境需要考虑更多的工程细节。6.1 可复现性与版本控制代码版本实验启动时必须记录代码仓库的 Git Commit Hash。可以使用git rev-parse HEAD自动获取。环境固化使用 Docker 镜像来封装运行时环境Python 版本、库依赖。镜像本身也应有版本标签。数据版本如果实验依赖特定数据集应使用数据版本管理工具如 DVC或记录数据集的唯一标识符如 S3 文件 ETag。6.2 容错与健壮性任务重试对于因临时网络问题或节点故障导致的失败应设计自动重试逻辑并设置最大重试次数。心跳与超时执行器应定期向调度器上报心跳。调度器需监控任务超时并终止或重新调度僵尸任务。优雅终止支持用户手动停止实验系统应能发送终止信号并清理临时资源。6.3 监控与可观测性系统指标监控调度队列长度、各服务 CPU/内存、数据库连接数、任务成功率/失败率。业务指标监控最佳实验指标的历史趋势、参数空间的探索进度。集中日志所有组件的日志应聚合到如 ELK 或 Loki 等日志平台方便根据实验 ID 进行关联查询。6.4 安全与权限多租户隔离不同团队或用户的实验、数据、计算资源需要逻辑或物理隔离。资源配额防止单个用户或项目耗尽所有集群资源需设置 CPU/GPU/内存配额。认证与授权API 和 Web UI 需要集成公司统一的 SSO并对“创建实验”、“停止他人实验”、“查看结果”等操作进行细粒度权限控制。6.5 性能优化异步化任务提交、状态更新、结果回调等 I/O 密集型操作应使用异步框架如 asyncio, Celery避免阻塞主循环。批量操作读写数据库或存储时尽量使用批量接口减少网络往返。缓存对频繁访问且不常变的元数据如实验配置模板进行缓存。构建一个成熟的 Discovery Loop 系统是一项复杂的工程它融合了分布式系统、数据库、机器学习和大数据等多个领域的知识。本文提供的模拟器是一个起点帮助你理解其核心组件和运作流程。在实际项目中你很可能基于 Kubeflow Pipelines、MLflow Projects、Metaflow 等开源平台进行二次开发或者直接使用云厂商提供的托管服务如 Google Vertex AI、Azure Machine Learning。无论选择哪条路径理解其底层设计原理都将使你更好地驾驭它从而真正提升研究和工程效率。