人工智能与机器学习课程大作业(一、知识工程基础)


本文为人工智能与机器学习课程大作业第一部分(一、知识工程基础)

本文仅作学习参考使用!


 其他章节跳转:

一、知识工程基础

二、函数逼近

三、模糊逻辑

四、函数优化


目 录

一、知识工程基础


1.1 问题一分析与求解

Q1: 知识有什么特性?智能系统知识表示有哪些方法?写出三种典型知识表示方法的要点。实际中如何来选择和建立合适的知识表示方法?

1.1.1 知识有什么特性

       知识是经过加工的信息(Feigenbaum),它包括事实、信念和启发式规则(Hayes-Roth)。知识是由符号组成,还包括了符号之间的关系以及处理这些符号的规则或过程。知识在信息的基础上增加了上下文信息,提供了更多的意义因此也就更加有用和有价值。知识是随着时间的变化而动态变化的,新的知识可以根据规则和已有的知识推导出来。

       知识的特性有:

       (1) 相对正确性:在一定条件及环境下,知识一般是正确的,可信任的;

       (2) 不确定性:包括四个内容的不确定性:由随机性引起的不确定性、由模糊性引起的不确定性、由不完全性引起的不确定性、由经验性引起的不确定性;

       (3) 可表示性和可利用性:知识是可以表示出来的、可以利用的。

1.1.2 智能系统知识表示有哪些方法

       智能系统知识表示有谓词逻辑 (Predicate Logic)、产生式 (Production)、框架(Framework)、语义网络 (Semantic Network)、剧本 (Script)、过程 (Procedure)、面向对象 (Object-Oriented)、Petri网 (Petri Network)、信念网 (Belief Network)、本体论 (Ontology) 等知识表示方法。

1.1.3 几种典型知识表示方法的要点

1、谓词逻辑 (Predicate Logic) 表示法

       谓词逻辑表示法是把一些知识表示为经典逻辑中的谓词表示式,其适合于表示事物的状态、属性、概念等事实性知识,也可用于表示事物间具有确定因果关系的规则性知识,而对不确定的知识无法有效表示,同时这种表示方法也不能很好地体现知识的内在联系。

       用谓词公式表示知识的步骤:

       (1) 定义谓词及个体,确定每个谓词及个体的确切含义;

       (2) 根据所要表达的事物或概念,为每个谓词中的变元赋予特定的值;

       (3) 根据所要表达的知识的语义,用恰当的连接符号将各个谓词连接起来,形成谓词公式。

2、产生式 (Production) 表示法

       产生式表示法又称为产生式规则 (production rule) 表示法。产生式这一术语是由美国数学家波斯特 (E.Post) 1943年首先提出来的,如今已被应用于多领域,成为人工智能中应用最多的一种知识表示方法。目前,在专家系统中,产生式表示法是应用最多的知识表示形式。基本形式是:PàQ或者if P then Q,P是产生式的前提,Q是产生式的后件,或者称为结论或操作。把一组产生式放在一起,让它们互相配合、协同作用,一个产生式生成的结论可以供给另一个产生式作为已知事实使用,以求得问题的解,这样的系统称为产生式系统。一般来说,一个产生式系统由规则库、综合数据库、控制系统(推理机)三部分组成。它们之间的关系如下图所示。其基本结构如图1-1所示。

 图1-1 产生式表示法结构图 (产生式系统)

       其中,规则库是IF-THEN型规则的集合;综合数据库是记载问题求解的初始状态、中间结果及目标状态;控制子系统是执行识别行为循环,并在每一个循环中选择激活的规则和执行其动作,一般包括匹配器、冲突消解器和规则解释器。

       产生式系统求解问题的一般步骤是:

       (1) 初始化综合数据库,把问题的初始已知事实送入综合数据库中;

       (2) 若规则库中存在尚未使用过的规则,而且它的前提可与综合数据库中的已知事实匹配,则继续;若不存在这样的事实,则转第 (5) 步;

       (3) 执行当前选中的规则,并对该规则做上标记,把该规则执行后得到的结论送入综合数据库中。若该规则的结论部分指出的是某些操作,则执行这些操作;

       (4) 检查综合数据库中是否已包含了问题的解,若已包含,则终止问题的求解过程;否则,转第 (2) 步;

       (5) 要求用户提供进一步的关于问题的已知事实,若能提供,则转 (2) 步;否则,终止问题求解过程;

       (6) 若规则中不再有未使用过的规则,则终止问题的求解过程。

3、框架 (Framework) 表示法

       框架 (Framework):一种描述所论对象 (一个事物、事件或概念) 属性的数据结构。一个框架由若干个被称为“槽”(slot) 的结构组成,每一个槽又可根据实际情况划分为若干个“侧面”(faced)。一个槽用于描述所论对象某一方面的属性。一个侧面用于描述相应属性的一个方面。槽和侧面所具有的属性值分别被称为槽值和侧面值。具体结构如图1-2所示。

 图1-2 框架表示法结构图

4、语义网络 (Semantic Network) 表示法

       (1) 语义网络中的节点:表示各种事物、概念、情况、属性、动作、状态等,每个节点可以带有若干属性,一般用框架或元组表示。此外,节点还可以是一个语义子网络,形成一个多层次的嵌套结构。

       (2) 语义网络中的弧:表示各种语义联系,指明它所连接的节点间某种语义关系。

       (3) 节点和弧都必须带有标识,以便区分各种不同对象以及对象间各种不同的语义联系。语义基元可用元组来描述。它的结构可以用一个基本网元来表示。例如,若用A、B分别表示三元组中的节点1、节点2,用R表示A与B之间的语义联系,那么它所对应的基本网元的结构如图1-3所示。

图1-3 语义网络表示法结构图

       常用语义联系有从属关系、包含关系、属性关系、时间关系、空间关系和相似关系。

       语义网络系统求解问题的主要过程:

       (1) 根据待求解问题的要求构造一个网络片断,其中有些节点或弧的标识是空的,反映待求解的问题。

       (2) 依此网络片断到知识库中去寻找可匹配的网络,以找出所需要的信息。当然,这种匹配一般不是完全的,具有不确定性,因此需要解决不确定性匹配问题。

       (3) 当问题的语义网络片断与知识库中的某语义网络片断匹配时,则与询问处匹配的事实就是问题的解。

5Petri (Petri Network) 表示法

       Petri网的概念是德国学者Cah Abam Petri于1962年首先提出的,用于构造系统模型及进行动态特性分析,后来逐渐被用作知识表示方法。

       Petri网的三个基本元素:位置、转换、标记。S = (P, T, Y)

图1-4 Petri网知识表示法结构图

       对于比较复杂的知识,Petri网通常用一个八元组来表示知识间的因果关系,具体形式是:(P,T,D,I,O,f,α,β)。

       P是位置的有限集,记为P={p1,p2,…,pn};

       T是转换的有限集,记为t={t1,t2,…,tn};

       D是命题的有限集,记为D={d1,d2,…,dn};

       I为输入函数,表示从位置到转换的映射;

       O为输出函数,表示从转换到位置的映射;

       f为相关函数,表示规则强度,取值范围[0, 1];

       α为相关函数,表示位置对应命题的可信度[0, 1];

       β为相关函数,表示从位置到命题的映射。

1.1.4 实际中如何来选择和建立合适的知识表示方法

       知识表示问题是人工智能研究的核心问题之一。1) 同一问题可有许多不同的表示方法。对于特定问题有的表示方法比较有效,其它表示方法可能不大适用,或者不是好的表示方法。2) 表示和求解比较复杂的问题时,采用单一的知识表示方法是远远不够的。往往必须采用多种方法混合表示。3) 在选择知识表示方法时,还要考虑所使用的程序设计语言所提供的功能和特点,以便能够更好地描述这些表示方法。

       在这些表示方法中,谓词逻辑、产生式系统和状态空间表示法属于非结构化的知识表示范畴,语义网络、框架、面向对象和脚本技术属于结构化的知识表示范畴。这些表示方法各有其长处,分别适用于不同的情况。目前的知识表示一般都是从具体应用中提出的,后来虽然不断发展变化,但是仍然偏重于实际应用,缺乏系统的知识表示理论。而且由于这些知识表示方法都是面向领域知识的对于常识性知识的表示仍没有取得大的进展,这是一个亟待解决的问题。知识表示对专家系统十分重要。知识可以用许多种方法来分类,如先验知识和后验知识,过程的、陈述的和默认的知识。逻辑方法、产生式、语义网络和框架是专家系统中常用的知识表示方法。脚本和本体是自然语言理解中常用的方法。状态空间法常用于控制系统和机器人系统中。面向对象的知识表示方法是一种综合的方法。每一种知识表示方法都有其优缺点,在设计一个基于知识的系统前,应决定选用哪种方法可以更好地解决问题,对特定的问题选用最合适的工具。


1.2 问题二分析与求解

Q2: 至少用两种知识表示方法表示如下命题:

       (1) 每个力都存在一个大小相等、方向相反的反作用力;

       第一种方法:谓词逻辑表示法

定义谓词如下:F(x)—x是力;FF(x,y)—x是y的反作用力;EQ(x,y)—x与y大小相等;EP(x,y)—x与y方向相反;则有:

       第二种方法:产生式表示法

       第三种方法:语义网络表示法

 图1-5 语义网络表示法Q2-1

       (2) 李文的班主任给班级同学每人一份礼物;

       第一种方法:谓词逻辑表示法

定义谓词如下:ST(x)—x是班级同学;NA(x)——x是李文;GIFT(x,y)—x给y一份礼物;TE(x,y)—x是y的班主任,则有:

       第二种方法:产生式表示法

       第三种方法:语义网络表示法

 图1-6 语义网络表示法Q2-2

       (3) 王丽是天发电脑公司的经理,她35岁,公司位于南环街50号。

       第一种方法:谓词逻辑表示法

定义谓词如下:N(x)—x是王丽;A(x)—x为35岁;L(x)—x位于南环姐50号;C(x)—x是天发电脑公司;MA(x,y)—x是y的经理,则有:

        第二种方法:产生式表示法

        第三种方法:语义网络表示法

 图1-7 语义网络表示法Q2-3


1.3 问题三分析与求解

Q3: 什么是搜索?有哪些不同的搜索方法?各自的特点如何?选择一种搜索算法完成如下滑动木块游戏:

B B B W W W E

其中,B为黑色木块,W为白色木块,E为空格,游戏规则为:每个木块可以移入相邻的空格,或者最多跳过两个其他的木块进入一个空格。游戏的最终目标是将所有白色木块都移到黑色木块的左边(不考虑空格的位置)。

1.3.1 什么是搜索

       根据问题的实际情况不断寻找可利用的知识,构造出一条代价较少的推理路线,使问题得到圆满解决的过程称为搜索,例如,找到从初始事实到问题最终答案的一条推理路径,找到的这条路径在时间和空间上复杂度最小。

1.3.2 有哪些不同的搜索方法?各自的特点如何?

       有盲目搜索、启发式搜索、与/或树搜索、博弈树搜索和约束满足搜索

       (1) 按是否使用启发式信息分类,可分为盲目搜索和启发式搜索;盲目搜索也称为无信息搜索,即只按预定的控制策略进行搜索,在搜索过程中获得的中间信息不被用来改进控制策略。启发式搜索指在搜索中加入了与问题有关的启发性信息,用于指导搜索朝着最有希望的方向进行,加速问题的求解过程并找到最优解。

       (2) 按问题的表示方式分类,可分为状态空间搜索以及与/或树搜索。状态空间搜索指用状态空间法来求解问题时所进行的搜索;与/或树搜索指用问题规约法来求解问题时所进行的搜索。

       (3) 博弈中两种最基本的搜索方法:极大极小分析法以及α-β剪枝技术;

       (4) 约束满足问题的回溯搜索,算法思想:每次给一个变量赋值,当没有合法赋值(不满足约束时)就要推翻前一个变量的赋值,重新给其赋值,这就是回溯。

1.3.3 选择一种搜索算法完成滑动木块游戏

B B B W W W E

其中,B为黑色木块,W为白色木块,E为空格,游戏规则为:每个木块可以移入相邻的空格,或者最多跳过两个其他的木块进入一个空格。游戏的最终目标是将所有白色木块都移到黑色木块的左边(不考虑空格的位置)。

1A*(A-Star)算法

       A-star算法是一种图形搜索算法,常用于寻路。它是个以广度优先搜索为基础,集Dijkstra算法与最佳优先(best fit)算法特点于一身的一种算法。

       它通过式(1-1)来计算每个节点的优先级,然后选择优先级最高的节点作为下一个待遍历的节点。优先级函数:

 式中,g(x)是节点n距离起点的代价;h(n)是节点距离终点的预计代价,也就是A*算法的启发函数;

       而启发函数主要从以下几个方面影响A*算法的行为:

       1) 在极端情况下,当启发函数h(n)始终为0,则由来决定节点的优先级,此时算法就退化成了Dijkstra算法。

       2) 在另一个极端情况下,如果h(n)相较于大很多,则此时只有产生效果,这也就变成了最佳优先搜索。

       3) 如果h(n)始终小于等于节点n到终点的代价,被称为可接受启发式的A*,则A*算法一定保证能够找到最短路径。但是当的值越小,算法遍历越多的节点,导致算法越慢。

       4) 如果h(n)完全等于节点n到终点的代价,则A*算法将找到最佳路径并且速度很快。但是很难做到这一点,因为很难确切算出距离终点还有多远。

       5) 如果h(n)的值比节点n到终点的代价要大,则A*算法不能保证找到最短路径,不过此时会很快。

       由上面这些信息可以知道,通过调节启发函数可以控制算法的速度和精确度。因为在一些情况,可能未必需要最短路径,而是希望能够尽快找到一个路径即可。这也是A*算法比较灵活的地方。

2、结果及分析说明

       使用A*算法实现滑动木块游戏,在JAVA中编写多个A*的类与方法,在终端中可以得到运行结图1-8 终端运行结果图 (Rround 1)果如图 (Rround 1):

 图1-8 终端运行结果图 (Rround 1)

 图1-9 木块移动流程图 (Rround 1)

       为证明程序的正确性以及算法的普适性,我增加了以下四种不同的初始情况的试验验证以及结果。

Rround 2. 初始情况一、

B B B E W W W

 图1-10 终端运行结果图 (Rround 2)

 图1-11 木块移动流程图 (Rround 2)

Rround 3. 初始情况二、

B E B B W W W

 图1-12 终端运行结果图 (Rround 3)

 图1-13 木块移动流程图 (Rround 3)

Rround 4. 初始情况三、

E B B B W W W

 图1-14 终端运行结果图 (Rround 4)

 图1-15 木块移动流程图 (Rround 4)

Rround 5. 初始情况四、

B W B B W W W

 图1-16 终端运行结果图 (Rround 5)

 图1-17 木块移动流程图 (Rround 5)

3、源码

源码均保存在AStarToBlockProblem文件夹中,直接运行Main.java即可。

/*
    主程序:Main.java
*/
// main function
public class Main {
    public static void main(String[] args) {
        AStar aStar = new AStar();
        System.out.println("Rround 1");
        aStar.run(new GameBoard("BBBWWW "));
        System.out.println("Rround 2");
        aStar.run(new GameBoard("BBB WWW"));
        System.out.println("Rround 3");
        aStar.run(new GameBoard("B BBWWW"));
        System.out.println("Rround 4");
        aStar.run(new GameBoard(" BBBWWW"));
        System.out.println("Rround 5");
        aStar.run(new GameBoard("BWB WBW"));
    }
}
/*
    子类:GameBoard.java
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class GameBoard {
    private List<Character> board;
    public GameBoard() {
        board.add('B');
        board = Arrays.asList('B', 'B', 'B', 'W', 'W', 'W', ' ');
    }
 
    public GameBoard(List<Character> board) {
        this.board = board;
    }
 
    public GameBoard(String configuration) {
        board = new ArrayList<>();
        for (int i = 0; i < configuration.length(); i++) {
            board.add(configuration.charAt(i));
        }
    }
 
    public boolean isSolved() {
        int numWhiteFound = 0;
        for (Character tile : board) {
            if (tile.equals('W')) {
                numWhiteFound++;
                if (numWhiteFound == 3) {
                    return true;
                }
            } else if (tile.equals('B')) {
                return false;
            }
        }
        return false;
    }
 
    public List<GameBoard> getPossibleMoves() {
        int indexOfSpace = board.indexOf(' ');
        List<GameBoard> possibleMoves = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            if (indexOfSpace < board.size() - i) {
                GameBoard boardSlideLeft = clone();
                swap(boardSlideLeft.board, indexOfSpace, indexOfSpace+i);
                possibleMoves.add(boardSlideLeft);
            }
        }
        for (int i = 0; i <= 2; i++) {
            if (indexOfSpace > i) {
                GameBoard boardSlideRight = clone();
                swap(boardSlideRight.board, indexOfSpace, indexOfSpace-(i+1));
                possibleMoves.add(boardSlideRight);
            }
        }
        return possibleMoves;
    }
 
    private void swap(List<Character> list, int index1, int index2) {
        Character temp = list.get(index1);
        list.set(index1, list.get(index2));
        list.set(index2, temp);
    }
 
    @Override
    public GameBoard clone() {
        return new GameBoard(new ArrayList<>(board));
    }
 
    public List<Character> getBoard() {
        return board;
    }
 
    public void setBoard(List<Character> board) {
        this.board = board;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GameBoard gameBoard = (GameBoard) o;
        return Objects.equals(board, gameBoard.board);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(board);
    }
 
    @Override
    public String toString() {
        return board.toString();
    }
}
/*
    子类:Heuristic.java
*/
public class Heuristic {
    public static int easyHeuristic(GameBoard gameBoard) {
        int num = 0;
        for (Character character : gameBoard.getBoard()) {
            if (character.equals('B')) {
                num++;
            } else if (character.equals('W')) {
                return num;
            }
        }
        return num;
    }
}
/*
    子类:Astar.java
*/
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
public class AStar {
    public AStar() {
    }
 
    public void run(GameBoard startingGameboard) {
        PriorityQueue<Path> frontier = new PriorityQueue<>();
        List<GameBoard> closed = new ArrayList<>();
        frontier.add(new Path(startingGameboard));
        while(!frontier.isEmpty()) {
            Path path = frontier.poll();
            if (path.getLastItem().isSolved()) {
                System.out.println(path);
                return;
            }
            closed.add(path.getLastItem());
            for (GameBoard gameBoard : path.getLastItem().getPossibleMoves()) {
                if (!closed.contains(gameBoard)) {
                    Path cloned = path.clone();
                    cloned.addToPath(gameBoard, costBetweenMoves(gameBoard, path.getLastItem()));
                    frontier.add(cloned);
                }
            }
        }
        System.out.println("No solution found!");
    }
 
    private int costBetweenMoves(GameBoard board1, GameBoard board2) {
        int index1 = board1.getBoard().indexOf(' ');
        int index2 = board2.getBoard().indexOf(' ');
        if (Math.abs(index1 - index2) == 3) {
            return 2;
        }
        return 1;
    }
}
/*
    子类:Path.java
*/
import java.util.ArrayList;
import java.util.List;
public class Path implements Comparable<Path> {
    private int cost = 0;
    private List<GameBoard> boards;
    public Path() {
        boards = new ArrayList<>();
    }
 
    public Path(GameBoard gameBoard) {
        this();
        boards.add(gameBoard);
    }
 
    public void addToPath(GameBoard gameBoard, int cost) {
        boards.add(gameBoard);
        this.cost += cost;
    }
 
    public int getCost() {
        return cost;
    }
 
    public List<GameBoard> getBoards() {
        return boards;
    }
 
    public GameBoard getLastItem() {
        return boards.get(boards.size() - 1);
    }
 
    public int getCostIncludingHeuristic() {
        int heuristic = 0;
        if (!boards.isEmpty()) {
            heuristic = Heuristic.easyHeuristic(boards.get(boards.size() -1));
        }
        return cost + heuristic;
    }
 
    public int compareTo(Path other) {
        return getCostIncludingHeuristic() - other.getCostIncludingHeuristic();
    }
 
    @Override
    public Path clone() {
        Path other = new Path();
        other.cost = cost;
        other.boards = new ArrayList<>(boards);
        return other;
    }
 
    @Override
    public String toString() {
        String str = "Cost: " + cost + "\n";
        for (GameBoard board : boards) {
            str += board + "\n";
        }
        return str;
    }
}

1.4 问题四分析与求解

Q4: 什么是推理?有哪几种主要的推理方式?每一种推理方式有何特点?设已知如下事实:

       (1) 计算机系的学生都要学编程课程;

       (2) C语言是一门编程课程;

       (3) 小王是一名计算机系的学生。

求证:小王学过C语言。

1.4.1 什么是推理?

       推理是指根据已知事实(证据)和知识,通过某种策略得到结论。自动推理早期的工作主要集中在机器定理证明。机器定理证明的中心问题是寻找判定公式是否是有效的(或是不一致的)通用程序。1930年Herbrand为定理证明建立了一种重要方法,他的方法奠定了机器定理证明的基础。机器定理证明的主要工作是1965年由Robinson做出的,他建立了所谓归结原理,使机器定理证明达到了应用阶段。后来又出现了自然演绎法和等式重写式等,这些方法在某些方面优于归结法,但它们在本质上都存在组合问题,都受到难解性的制约。

1.4.2 有哪几种主要的推理方式?每一种推理方式有何特点?

       按照推理的方向,推理方法可以分为正向推理和逆向推理。正向推理就是正向地使用规则,从已知条件出发向目标进行推理。其基本思想是:检验是否有规则的前提被动态数据库中的已知事实满足,如果被满足,则将该规则的结论放入动态数据库中,再检查其他的规则是否有前提被满足;反复该过程,直到目标被某个规则推出结束,或者再也没有新结论被推出为止。由于这种推理方法是从规则的前提向结论进行推理,所以称为正向推理。由于正向推理是通过动态数据库中的数据来“触发”规则进行推理的,所以又称为数据驱动的推理。逆向推理又被称为反向推理,是逆向地使用规则,先将目标作为假设,查看是否有某条规则支持该假设,即规则的结论与假设是否一致,然后看结论与假设一致的规则其前提是否成立。如果前提成立(在动态数据库中进行匹配),则假设被验证,结论放入动态数据库中;否则将该规则的前提加入假设集中,一个一个地验证这些假设,直到目标假设被验证为止。由于逆向推理是从假设求解目标成立、逆向使用规则进行推理的,所以又称为目标驱动的推理。推理又可分为确定性推理和不确定性推理,确定性推理,每个规则都是确定性的,用户给出的事实也是确定性的,但随机性、模糊性和不完全性均可以导致非确定性,这些非确定性的问题,则需要非确定性推理。

       在课程中,按推理的逻辑基础可分为演绎推理、归纳推理、默认推理。演绎推理:从已知的一般性知识出发,推理出适合于某种个别情况的结论的过程,即一般到个别的推理;归纳推理:从大量特殊事例出发,归纳出一般性结论的推理过程,是一种由个别到一般的推理方法;默认推理:也叫缺省推理,是指在知识不完全的情况下假设某些条件已经具备所进行的推理。在进行推理的过程中,如果发现原来的假设不正确,就撤销原来的假设以及由此假设所推出的所有结论,重新按新情况进行处理。按所用知识的确定性可分为确定性推理和不确定性推理。确定性推理:推理时所用的知识和推出的结论都是可以精确表示的,其真值要么为真,要么为假。不确定性推理:推理时所用的知识和推出的结论不都是完全确定的,其真值介于真和假之间。按推理过程的单调性可分为单调推理和非单调推理。单调推理:在推理过程中随着推理的向前推进以及新知识的加入,推出的结论呈单调增加的趋势,并且越来越接近最终目标,在推理过程中不会出现反复的情况,即不会由于新知识的加入否定了前面推出的结论,从而使推理又退回到前面的某一步。非单调推理:在推理过程中由于新知识的加入,不仅没有加强已推出的结论,反而要否定它,使得推理退回到前面的某一步,重新开始。非单调推理是在知识不完全的情况下发生的。

1.4.3 推理示例

设已知如下事实:

       (1) 计算机系的学生都要学编程课程;

       (2) C语言是一门编程课程;

       (3) 小王是一名计算机系的学生。

求证:小王学过C语言。

证明:因为

       小王是计算机系的学生,计算机系得学生都要学编程课程

       所以小王学编程课程

       因为C语言是一门编程课程

       所以小王学过C语言。


 人工智能与机器学习课程大作业其他章节内容:二、函数逼近三、模糊逻辑四、函数优化

猜你喜欢

转载自blog.csdn.net/HISJJ/article/details/130298027