从 ERP 出发:用图数据库 + 规则引擎落地供应链知识语义化

本文面向有 ERP/业务系统背景的 Java 工程师,介绍如何从关系型数据库出发,通过图数据库和规则引擎渐进落地知识语义化,以供应链溯源与风险识别为核心场景,给出完整的架构设计和 Java 代码示例。

一、为什么供应链需要"知识语义化"?

1.1 传统 ERP 的结构性困境

在典型的制造/采购企业中,供应链数据散落在多张关系表里:

复制代码
供应商表(supplier)→ 采购订单表(purchase_order)
→ 物料表(material)→ BOM 表(bom)→ 产品表(product)

当你要回答这些问题时,SQL 会突然变得很难写:

  • "如果供应商 A 被列入风险名单,会影响哪些最终产品?"
  • "某批原材料出现质量问题,向上追溯到原始供应商,向下影响哪些订单?"
  • "某供应商的交付延迟,会传导到哪些客户合同?"

这些问题本质是多跳路径查询,关系型数据库的 JOIN 会越来越多,SQL 越来越复杂,性能也越来越差。

1.2 本体与知识图谱能带来什么

"语义化"的核心是把数据从"表结构"升级为"语义网络":

维度 关系型数据库 知识图谱(语义化)
数据模型 表 + 外键 实体 + 关系 + 属性
多跳查询 N 层 JOIN,性能差 图遍历,天然高效
推理能力 无(只能查) 有(规则推理、路径推导)
数据融合 跨系统需要 ETL 统一语义层,天然可融合
业务理解 需要懂表结构 图结构即业务语义

1.3 为什么不用纯本体(OWL + SPARQL)?

学术路线是:直接建立 OWL 本体 → 用三元组存储 → SPARQL 查询推理。

这条路的问题:

  • 学习曲线陡峭:OWL 的 Description Logic 对业务工程师门槛太高
  • 工具链割裂:主流 Java/Spring 生态没有成熟的 OWL 工作流
  • 推理性能差:HermiT 等推理器在百万级实例下性能堪忧
  • 与现有系统集成成本高:需要从零重建数据管道

务实路线:关系型 DB → 图数据库(知识图谱层)→ 规则引擎(推理层)→ 应用层。这条路的每一步都可以渐进完成,不影响现有系统运行。

二、整体架构设计

2.1 四层架构

复制代码
┌─────────────────────────────────────────────┐
│              应用服务层                      │
│  图谱查询 API · 风险预警接口 · 语义搜索     │
└────────────────┬────────────────────────────┘
                 │
┌────────────────▼────────────────────────────┐
│          核心能力层(新建)                   │
│  ┌──────────────┐  ┌──────────────────┐    │
│  │ 图数据库       │  │ 规则引擎          │    │
│  │ (Neo4j)      │←→│ (Drools)         │    │
│  │ 实体/关系存储  │  │ 风险推理规则      │    │
│  └──────────────┘  └──────────────────┘    │
└────────────────┬────────────────────────────┘
                 │
┌────────────────▼────────────────────────────┐
│           数据同步层(桥接)                  │
│  CDC 捕获 · 实体映射 · 关系抽取             │
└────────────────┬────────────────────────────┘
                 │
┌────────────────▼────────────────────────────┐
│           现有系统层(不动)                  │
│  关系型数据库 · ERP · 业务应用系统            │
└─────────────────────────────────────────────┘

2.2 数据流向

复制代码
ERP/关系库  ──CDC──▶  同步服务  ──映射──▶  图数据库(Neo4j)
                                    │
                                    ├──▶ 规则引擎(Drools)推理
                                    │
应用层  ◀──API──  图谱查询服务  ◀──┘

关键原则:原有系统不动,图数据库是只读副本 + 推理结果缓存,不会引入双写一致性风险。


三、图数据库选型与建模

3.1 Neo4j vs NebulaGraph 对比

维度 Neo4j (Community/Enterprise) NebulaGraph
开源协议 AGPL(社区版受限) Apache 2.0
查询语言 Cypher(行业事实标准) nGQL(类 SQL)
Java 生态 Spring Data Neo4j 成熟 客户端较新
性能 单节点中等,集群需企业版 分布式原生,性能好
运维复杂度 中高
推荐场景 中小规模、快速落地 大规模、分布式场景

本文选用 Neo4j :Cypher 生态成熟,Spring 集成简单,适合作为第一篇落地文章的技术栈。

3.2 供应链图模型设计

核心实体与关系:

复制代码
(:Supplier)-[:SUPPLIES]->(:Material)
(:Material)-[:PART_OF]->(:BOM)-[:PRODUCES]->(:Product)
(:PurchaseOrder)-[:ORDERS]->(:Material)
(:RiskEvent)-[:AFFECTS]->(:Supplier)
(:RiskEvent)-[:PROPAGATES_TO]->(:Product)  ← 推理生成的边

节点属性设计(以 Supplier 为例):

cypher 复制代码
CREATE (s:Supplier {
  id: "SUP-001",
  name: "深圳精密电子有限公司",
  riskLevel: "MEDIUM",        // 低风险/中风险/高风险
  riskTags: ["交付延迟", "质量波动"],
  lastAuditDate: "2025-11-01",
  status: "ACTIVE"
})

3.3 初始数据导入

用 Neo4j 的 LOAD CSV 从现有 ERP 导出文件导入:

cypher 复制代码
// 导入供应商
LOAD CSV WITH HEADERS FROM "file:///suppliers.csv" AS row
CREATE (s:Supplier {
  id: row.supplier_id,
  name: row.supplier_name,
  riskLevel: row.risk_level,
  status: "ACTIVE"
});

// 导入物料并建立 SUPPLIES 关系
LOAD CSV WITH HEADERS FROM "file:///materials.csv" AS row
MATCH (s:Supplier {id: row.supplier_id})
MATCH (m:Material {id: row.material_id})
CREATE (s)-[:SUPPLIES {since: row.since}]->(m);

四、数据同步:从关系库到图数据库

4.1 同步策略选择

策略 优点 缺点 适用场景
定时全量同步 简单 延迟高,数据量大 小规模,T+1 场景
双写(应用层) 实时 侵入业务代码,一致性难保证 新系统开发
CDC(变更数据捕获) 准实时,不侵入业务 需要部署 CDC 中间件 推荐方案

4.2 用 Debezium + Kafka 实现 CDC 同步

架构:

复制代码
MySQL Binlog ──▶ Debezium Connector ──▶ Kafka Topic
                                    │
                                    ▼
                            Spring Boot 消费者
                                    │
                                    ▼
                            Neo4j(写入图数据库)

Spring Boot 消费者核心代码:

java 复制代码
@Component
public class SupplierSyncConsumer {

    private final Driver neo4jDriver;

    public SupplierSyncConsumer(@Autowired Driver neo4jDriver) {
        this.neo4jDriver = neo4jDriver;
    }

    @KafkaListener(topics = "erp.db.supplier")
    public void consumeSupplierChange(ConsumerRecord<String, String> record) {
        // Debezium 格式:before/after + op (c=create, u=update, d=delete)
        JsonNode changeEvent = parseDebeziumEvent(record.value());
        String op = changeEvent.get("op").asText();
        JsonNode after = changeEvent.get("after");

        try (Session session = neo4jDriver.session()) {
            switch (op) {
                case "c", "r" -> // 新增或快照
                    session.run(
                        "MERGE (s:Supplier {id: $id}) " +
                        "SET s.name = $name, s.riskLevel = $riskLevel, s.status = $status",
                        Values.parameters(
                            "id", after.get("supplier_id").asText(),
                            "name", after.get("supplier_name").asText(),
                            "riskLevel", after.get("risk_level").asText("LOW"),
                            "status", "ACTIVE"
                        )
                    );
                case "u" -> // 更新
                    session.run(
                        "MATCH (s:Supplier {id: $id}) " +
                        "SET s.name = $name, s.riskLevel = $riskLevel",
                        Values.parameters(
                            "id", after.get("supplier_id").asText(),
                            "name", after.get("supplier_name").asText(),
                            "riskLevel", after.get("risk_level").asText("LOW")
                        )
                    );
                case "d" -> // 删除(软删除:标记 status = INACTIVE)
                    session.run(
                        "MATCH (s:Supplier {id: $id}) SET s.status = 'INACTIVE'",
                        Values.parameters("id", changeEvent.get("before").get("supplier_id").asText())
                    );
            }
        }
    }
}

4.3 实体映射:关系表 → 图模型

ERP 中的关系表 supplier_material 需要映射为图里的 [:SUPPLIES] 边,同步消费者:

java 复制代码
@KafkaListener(topics = "erp.db.supplier_material")
public void consumeSupplierMaterial(String payload) {
    JsonNode event = parseDebeziumEvent(payload);
    JsonNode after = event.get("after");

    try (Session session = neo4jDriver.session()) {
        session.run(
            "MATCH (s:Supplier {id: $supplierId}) " +
            "MATCH (m:Material {id: $materialId}) " +
            "MERGE (s)-[r:SUPPLIES]->(m) " +
            "SET r.since = $since, r.status = 'ACTIVE'",
            Values.parameters(
                "supplierId", after.get("supplier_id").asText(),
                "materialId", after.get("material_id").asText(),
                "since", after.get("created_at").asText()
            )
        );
    }
}

五、规则引擎:实现风险传导推理

5.1 推理需求分析

供应链风险传导的典型规则:

  1. 直接风险:供应商 A 出现风险事件 → A 的 riskLevel 升级
  2. 一级传导:供应商 A 风险 → 影响 A 供应的所有物料
  3. 二级传导:物料风险 → 影响使用该物料的所有产品
  4. 客户影响:产品风险 → 影响该产品的客户订单

这些规则用 SQL 很难表达,用规则引擎则非常自然。

5.2 Drools 规则文件设计

src/main/resources/rules/supply-chain-risk.drl

drl 复制代码
package com.example.supplychain.risk

import com.example.supplychain.model.*;

// ===== 规则1:供应商风险事件触发供应商风险等级升级 =====
rule "Supplier risk event triggers risk level upgrade"
    when
        $event: RiskEvent(status == "ACTIVE", severity == "HIGH")
        $supplier: Supplier(id == $event.targetSupplierId, riskLevel != "HIGH")
    then
        System.out.println("[推理] 供应商 " + $supplier.getName() + " 风险等级升级为 HIGH");
        $supplier.setRiskLevel("HIGH");
        update($supplier);
end

// ===== 规则2:供应商高风险 → 供应物料标记为风险物料 =====
rule "High risk supplier propagates to materials"
    when
        $supplier: Supplier(riskLevel == "HIGH")
        $rel: SuppliesRel(supplierId == $supplier.getId())
        $material: Material(id == $rel.getMaterialId(), riskLevel != "HIGH")
    then
        System.out.println("[推理] 物料 " + $material.getName() + " 被供应商风险传导,标记为 HIGH");
        $material.setRiskLevel("HIGH");
        update($material);
end

// ===== 规则3:物料高风险 → 使用物料的产品标记为风险产品 =====
rule "High risk material propagates to products"
    when
        $material: Material(riskLevel == "HIGH")
        $bom: BomItem(materialId == $material.getId())
        $product: Product(id == $bom.getProductId(), riskLevel != "HIGH")
    then
        System.out.println("[推理] 产品 " + $product.getName() + " 受物料风险影响,标记为 HIGH");
        $product.setRiskLevel("HIGH");
        update($product);
end

// ===== 规则4:产品高风险 → 触发客户订单预警 =====
rule "High risk product triggers order alert"
    when
        $product: Product(riskLevel == "HIGH")
        $order: PurchaseOrder(productId == $product.getId(), status != "CANCELLED")
        not (Alert(orderId == $order.getId(), type == "PRODUCT_RISK"))
    then
        Alert alert = new Alert();
        alert.setOrderId($order.getId());
        alert.setType("PRODUCT_RISK");
        alert.setMessage("产品 " + $product.getName() + " 存在高风险,请核查订单");
        alert.setCreatedAt(java.time.Instant.now());
        insert(alert);
        System.out.println("[推理] 生成订单预警:" + alert.getMessage());
end

5.3 Java 推理服务实现

java 复制代码
@Service
public class RiskInferenceService {

    private final KieContainer kieContainer;
    private final Driver neo4jDriver;

    public RiskInferenceService(
            @Autowired KieContainer kieContainer,
            @Autowired Driver neo4jDriver) {
        this.kieContainer = kieContainer;
        this.neo4jDriver = neo4jDriver;
    }

    /**
     * 触发风险推理:从 Neo4j 加载数据 → Drools 推理 → 将推理结果写回 Neo4j
     */
    public InferenceResult runInference(String triggerEventId) {
        KieSession kieSession = kieContainer.newKieSession();

        try {
            // 1. 从 Neo4j 加载相关事实(供应商、物料、产品、订单)
            List<Object> facts = loadFactsFromNeo4j(triggerEventId);
            facts.forEach(kieSession::insert);

            // 2. 执行推理
            int firedRules = kieSession.fireAllRules();
            System.out.println("触发规则次数:" + firedRules);

            // 3. 收集推理结果(Alert 对象)
            List<Alert> alerts = kieSession.getObjects().stream()
                    .filter(o -> o instanceof Alert)
                    .map(o -> (Alert) o)
                    .toList();

            // 4. 将推理结果(风险等级变化、新关系)写回 Neo4j
            writeInferenceResultsToNeo4j(kieSession);

            return new InferenceResult(firedRules, alerts);

        } finally {
            kieSession.dispose();
        }
    }

    private List<Object> loadFactsFromNeo4j(String eventId) {
        List<Object> facts = new ArrayList<>();
        try (Session session = neo4jDriver.session()) {
            // 加载触发事件
            session.run("MATCH (e:RiskEvent {id: $id}) RETURN e", Values.parameters("id", eventId))
                    .forEachRemaining(r -> facts.add(toRiskEvent(r.get("e").asNode())));

            // 加载关联供应商
            session.run("""
                MATCH (e:RiskEvent {id: $id})-[:AFFECTS]->(s:Supplier)
                RETURN s
                """, Values.parameters("id", eventId))
                    .forEachRemaining(r -> facts.add(toSupplier(r.get("s").asNode())));

            // 加载所有物料
            session.run("MATCH (m:Material) RETURN m")
                    .forEachRemaining(r -> facts.add(toMaterial(r.get("m").asNode())));

            // 加载所有产品
            session.run("MATCH (p:Product) RETURN p")
                    .forEachRemaining(r -> facts.add(toProduct(r.get("p").asNode())));

            // 加载 BOM 关系
            session.run("MATCH (b:BomItem) RETURN b")
                    .forEachRemaining(r -> facts.add(toBomItem(r.get("b").asNode())));

            // 加载订单
            session.run("MATCH (o:PurchaseOrder) RETURN o")
                    .forEachRemaining(r -> facts.add(toPurchaseOrder(r.get("o").asNode())));
        }
        return facts;
    }

    private void writeInferenceResultsToNeo4j(KieSession session) {
        // 将 Drools 中更新的风险等级同步回 Neo4j
        session.getObjects().stream()
            .filter(o -> o instanceof Supplier s && "HIGH".equals(s.getRiskLevel()))
            .map(o -> (Supplier) o)
            .forEach(s -> updateSupplierRiskInNeo4j(s.getId(), "HIGH"));

        session.getObjects().stream()
            .filter(o -> o instanceof Material m && "HIGH".equals(m.getRiskLevel()))
            .map(o -> (Material) o)
            .forEach(m -> updateMaterialRiskInNeo4j(m.getId(), "HIGH"));
    }

    private void updateSupplierRiskInNeo4j(String supplierId, String riskLevel) {
        try (Session s = neo4jDriver.session()) {
            s.run("MATCH (s:Supplier {id: $id}) SET s.riskLevel = $rl, s.riskUpdatedAt = $ts",
                Values.parameters("id", supplierId, "rl", riskLevel, "ts", Instant.now().toString()));
        }
    }

    private void updateMaterialRiskInNeo4j(String materialId, String riskLevel) {
        try (Session s = neo4jDriver.session()) {
            s.run("MATCH (m:Material {id: $id}) SET m.riskLevel = $rl, m.riskUpdatedAt = $ts",
                Values.parameters("id", materialId, "rl", riskLevel, "ts", Instant.now().toString()));
        }
    }
}

5.4 领域模型(Fact 类)

java 复制代码
// 规则引擎中的 Fact 对象,与 Neo4j 节点对应
public class Supplier {
    private String id;
    private String name;
    private String riskLevel; // LOW / MEDIUM / HIGH
    // getters & setters
}

public class Material {
    private String id;
    private String name;
    private String riskLevel;
    // getters & setters
}

public class Product {
    private String id;
    private String name;
    private String riskLevel;
    // getters & setters
}

public class BomItem {
    private String materialId;
    private String productId;
    // getters & setters
}

public class PurchaseOrder {
    private String id;
    private String productId;
    private String status;
    // getters & setters
}

public class RiskEvent {
    private String id;
    private String targetSupplierId;
    private String severity; // LOW / MEDIUM / HIGH
    private String status;   // ACTIVE / RESOLVED
    // getters & setters
}

public class Alert {
    private String orderId;
    private String type;
    private String message;
    private Instant createdAt;
    // getters & setters
}

// 关系 Fact(用于规则中匹配)
public class SuppliesRel {
    private String supplierId;
    private String materialId;
    // getters & setters
}

六、图谱查询 API:面向应用层的服务封装

6.1 风险溯源查询(Cypher)

核心需求:给定一个风险事件,查询影响链路。

java 复制代码
@RestController
@RequestMapping("/api/supply-chain")
public class SupplyChainController {

    private final Driver neo4jDriver;

    public SupplyChainController(@Autowired Driver neo4jDriver) {
        this.neo4jDriver = neo4jDriver;
    }

    /**
     * 风险溯源:查询某供应商的风险影响链路
     * GET /api/supply-chain/risk-trace?supplierId=SUP-001
     */
    @GetMapping("/risk-trace")
    public List<RiskTraceNode> getRiskTrace(@RequestParam String supplierId) {
        try (Session session = neo4jDriver.session()) {
            return session.run("""
                MATCH path = (s:Supplier {id: $sid})-[:SUPPLIES*1..3]->(target)
                WHERE s.riskLevel = 'HIGH'
                RETURN path
                """, Values.parameters("sid", supplierId))
                .list(r -> toRiskTraceNode(r.get("path")));
        }
    }

    /**
     * 多跳影响分析:从风险事件出发,找到所有受影响的产品和客户
     * GET /api/supply-chain/impact-analysis?eventId=EVT-001
     */
    @GetMapping("/impact-analysis")
    public ImpactAnalysisResult getImpactAnalysis(@RequestParam String eventId) {
        try (Session session = neo4jDriver.session()) {
            // 查询受影响的产品
            List<String> affectedProducts = session.run("""
                MATCH (e:RiskEvent {id: $eid})-[:AFFECTS]->(s:Supplier)
                MATCH (s)-[:SUPPLIES]->(m:Material)
                MATCH (m)<-[:PART_OF]-(b:BomItem)-[:PRODUCES]->(p:Product)
                RETURN DISTINCT p.id as productId, p.name as productName
                """, Values.parameters("eid", eventId))
                .list(r -> r.get("productId").asString());

            // 查询受影响的订单
            List<OrderSummary> affectedOrders = session.run("""
                MATCH (e:RiskEvent {id: $eid})-[:AFFECTS]->(s:Supplier)
                MATCH (s)-[:SUPPLIES]->(m:Material)
                MATCH (m)<-[:PART_OF]-(b:BomItem)-[:PRODUCES]->(p:Product)
                MATCH (o:PurchaseOrder {productId: p.id})
                RETURN o.id as orderId, o.customerName as customer, p.name as product
                """, Values.parameters("eid", eventId))
                .list(r -> new OrderSummary(
                    r.get("orderId").asString(),
                    r.get("customer").asString(),
                    r.get("product").asString()
                ));

            return new ImpactAnalysisResult(affectedProducts, affectedOrders);
        }
    }
}

6.2 图谱可视化数据接口

前端用 D3.js / ECharts 渲染图谱,需要返回节点+边的格式:

java 复制代码
@GetMapping("/graph")
public GraphData getGraph(@RequestParam(required = false) String centerNodeId) {
    try (Session session = neo4jDriver.session()) {
        List<NodeDto> nodes = new ArrayList<>();
        List<EdgeDto> edges = new ArrayList<>();

        String cypher = centerNodeId == null
            ? "MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 200"
            : "MATCH (n {id: $id})-[r*1..2]-(m) RETURN n, r, m LIMIT 200";

        session.run(cypher, centerNodeId == null ? Values.parameters() : Values.parameters("id", centerNodeId))
            .forEachRemaining(r -> {
                // 解析节点和关系,构建 GraphData
                // ...(省略具体解析代码,核心是提取 id/label/type)
            });

        return new GraphData(nodes, edges);
    }
}

七、运维与演进要点

7.1 规则版本化管理

规则文件(.drl)纳入 Git 管理,每次修改打 Tag,推理服务支持热加载:

java 复制代码
@Component
public class DroolsRuleManager {

    @Value("${rules.directory:classpath:rules/}")
    private Resource rulesDir;

    /**
     * 热加载规则(不重启服务)
     */
    public KieContainer reloadRules() {
        KieServices ks = KieServices.Factory.get();
        KieFileSystem kfs = ks.newKieFileSystem();

        // 从目录加载最新规则文件
        for (Resource ruleFile : getRuleFiles()) {
            kfs.write(ruleFile.getFilename(), ruleFile);
        }

        KieBuilder kb = ks.newKieBuilder(kfs).buildAll();
        if (kb.getResults().hasMessages(Message.Level.ERROR)) {
            throw new RuntimeException("规则编译失败:" + kb.getResults().getMessages());
        }

        return ks.newKieContainer(ks.getRepository().getDefaultReleaseId());
    }
}

7.2 推理结果缓存

推理结果(风险等级、影响链路)变化频率低,适合缓存:

java 复制代码
@Service
public class CachedInferenceService {

    private final RiskInferenceService inferenceService;
    private final Cache<String, InferenceResult> resultCache =
        Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build();

    public InferenceResult getOrRunInference(String eventId) {
        return resultCache.get(eventId, k -> inferenceService.runInference(k));
    }
}

7.3 性能监控指标

指标 说明 告警阈值
neo4j.query.duration Cypher 查询耗时 > 1000ms
drools.rules.fired 规则触发次数 突然暴增说明规则有问题
cdc.lag.seconds CDC 同步延迟 > 30s
inference.duration 推理执行耗时 > 5000ms

八、与 LLM 结合的下一步

当前方案已经能解决供应链风险溯源的核心问题。下一步可以结合 LLM:

  1. 自然语言查询:用户问"哪个供应商出问题会影响 iPhone 16 的生产?" → LLM 生成 Cypher → 执行查询
  2. 自动规则生成:LLM 根据历史风险案例,辅助生成 Drools 规则
  3. 风险报告生成:推理结果 + LLM → 生成可读的风险分析报告

九、总结

阶段 做什么 工期参考
第1阶段 Neo4j 部署 + 手动导入 ERP 数据 1-2周
第2阶段 CDC 同步链路搭建 2-3周
第3阶段 Drools 规则引擎集成 + 核心推理规则 2-3周
第4阶段 查询 API + 前端可视化 2周
第5阶段 规则版本化 + 监控运维 1-2周

核心收益:从"写不出 SQL"到"3 行 Cypher 搞定多跳查询",从"被动响应风险"到"主动推理预警"。

相关推荐
codefan※7 小时前
RAG 加速指南:Faiss / Milvus / Qdrant 向量库选型与调优
知识图谱·milvus·faiss·向量数据库·rag·qdrant
__log1 天前
如何优雅地“借鉴”任何网站的设计系统
人工智能·架构·知识图谱
想你依然心痛1 天前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“图谱智脑“——PC端AI智能体沉浸式知识图谱构建工作台
人工智能·ar·知识图谱·harmonyos·智能体
__log1 天前
Codex默认调用本地Ollama模型配置指南
人工智能·知识图谱
帅次1 天前
AI数字营销实战测评:CSDN AI智选主题如何提升技术博客创作效率与质量?
人工智能·深度学习·机器学习·语言模型·自然语言处理·数据挖掘·知识图谱
图特摩斯科技2 天前
OntoFlow本体智能应用平台:从实时走向实时流式端到端的本体构建架构重塑
人工智能·知识图谱·palantir·ontology·ontoflow
Slow菜鸟3 天前
AI 代码知识图谱 教程(二)| Graphify(代码+文档)
人工智能·知识图谱
Slow菜鸟3 天前
AI 代码知识图谱 教程(一)| Codegraph(纯代码)
人工智能·知识图谱
高洁013 天前
设备故障?数字孪生提前预警
深度学习·机器学习·数据挖掘·transformer·知识图谱