├── 3.决策树
├── data
│ ├── 1.png
│ ├── 2.png
│ ├── 1.csv
│ └── 2.csv
├── main.py
├── README.md
└── trees.py
├── README.md
├── 1.井字棋
├── data
│ ├── tree1.png
│ ├── tree2.png
│ └── tree3.png
├── README.md
└── game.py
├── 2.推理机
├── data
│ ├── topo.png
│ ├── data.txt
│ └── topo.xml
├── README.md
├── back.py
└── forward.py
└── .idea
├── vcs.xml
├── modules.xml
├── misc.xml
└── expert-system.iml
/3.决策树/data/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/3.决策树/data/1.png
--------------------------------------------------------------------------------
/3.决策树/data/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/3.决策树/data/2.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 专家系统
2 |
3 | 1. 井字棋:Minimax算法 + Alpha-Beta算法
4 | 2. 推理机:正向推理机、反向推理机
5 | 3. 决策树:ID3算法
6 |
--------------------------------------------------------------------------------
/1.井字棋/data/tree1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/1.井字棋/data/tree1.png
--------------------------------------------------------------------------------
/1.井字棋/data/tree2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/1.井字棋/data/tree2.png
--------------------------------------------------------------------------------
/1.井字棋/data/tree3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/1.井字棋/data/tree3.png
--------------------------------------------------------------------------------
/2.推理机/data/topo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhangbincheng1997/expert-system/HEAD/2.推理机/data/topo.png
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/2.推理机/data/data.txt:
--------------------------------------------------------------------------------
1 | 有毛 哺乳类
2 | 有奶 哺乳类
3 | 有羽毛 鸟类
4 | 会飞 会生蛋 鸟类
5 | 吃肉 哺乳类 肉食类
6 | 犬牙利爪 眼睛向前 肉食类
7 | 反刍食物 哺乳类 偶蹄类
8 | 有蹄 哺乳类 有蹄类
9 | 黄褐色 暗斑点 肉食类 金钱豹
10 | 黄褐色 黑条纹 肉食类 老虎
11 | 长腿 长脖子 黄褐色 暗斑点 有蹄类 长颈鹿
12 | 黑白条纹 有蹄类 斑马
13 | 不会飞 长腿 长脖子 鸟类 鸵鸟
14 | 不会飞 善游泳 黑白色 鸟类 企鹅
15 | 善飞 鸟类 信天翁
16 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/1.井字棋/README.md:
--------------------------------------------------------------------------------
1 | # 井字棋
2 |
3 | ## Minimax算法
4 | 如图所示,最佳移动位于中间,因为最大值位于左侧的第二个节点上。
5 | 
6 |
7 | 简化游戏树:
8 | 
9 |
10 | ## Alpha-Beta算法
11 | Alpha-Beta剪枝优化,它允许我们忽略搜索树中的某些分支,因为他在搜索中剪切了不相关的节点(子树)。
12 | 
13 |
14 | ## 参考
15 | 1. https://github.com/Cledersonbc/tic-tac-toe-minimax
16 | 2. http://www.stratigery.com/ttt_ab.py
17 |
--------------------------------------------------------------------------------
/3.决策树/data/1.csv:
--------------------------------------------------------------------------------
1 | DQ,JZZY,YJZJ,YJHF,Class
2 | a2,b1,c1,d1,N
3 | a2,b1,c2,d1,N
4 | a2,b1,c3,d1,N
5 | a1,b1,c1,d1,Y
6 | a1,b1,c3,d1,Y
7 | a3,b2,c1,d1,N
8 | a3,b2,c3,d1,N
9 | a3,b1,c1,d2,Y
10 | a3,b3,c3,d2,N
11 | a3,b1,c2,d2,N
12 | a1,b3,c2,d2,Y
13 | a1,b3,c3,d2,Y
14 | a2,b2,c1,d1,N
15 | a2,b2,c3,d1,N
16 | a2,b3,c1,d2,Y
17 | a2,b3,c3,d2,Y
18 | a3,b2,c1,d2,N
19 | a3,b2,c3,d2,N
20 | a2,b2,c3,d2,Y
21 | a2,b2,c2,d2,Y
22 | a1,b2,c2,d1,Y
23 | a1,b2,c3,d1,Y
24 | a1,b1,c1,d2,Y
25 | a3,b2,c2,d1,N
26 |
--------------------------------------------------------------------------------
/3.决策树/data/2.csv:
--------------------------------------------------------------------------------
1 | Education,Sex,Language,Character,Job,Class
2 | undergraduate,man,cet4,a1,b1,Y
3 | undergraduate,woman,cet6,a3,b1,N
4 | undergraduate,man,cet6,a2,b2,Y
5 | undergraduate,man,cet4,a1,b3,Y
6 | undergraduate,woman,cet4,a2,b2,Y
7 | undergraduate,man,no,a3,b3,Y
8 | undergraduate,woman,cet4,a2,b3,N
9 | undergraduate,woman,cet4,a1,b1,Y
10 | college,man,no,a1,b1,N
11 | college,man,cet4,a2,b2,Y
12 | college,woman,cet4,a3,b3,Y
13 | college,woman,no,a3,b3,Y
14 | postgraduate,man,cet6,a2,b2,Y
15 | postgraduate,woman,cet6,a2,b2,Y
16 | postgraduate,man,cet4,a1,b3,N
17 | postgraduate,woman,cet6,a1,b1,Y
18 |
19 |
--------------------------------------------------------------------------------
/.idea/expert-system.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/3.决策树/main.py:
--------------------------------------------------------------------------------
1 | import trees
2 | import pandas as pd
3 |
4 | df = pd.read_csv('data/1.csv')
5 | col = df.columns.tolist()
6 | data = df.values.tolist()
7 | tree = trees.createTree(data, col)
8 | print(tree)
9 |
10 | col = df.columns.tolist()[:-1]
11 | input1 = ['a3', 'b1', 'c2', 'd1'] # N
12 | input2 = ['a3', 'b3', 'c1', 'd1'] # N
13 | input3 = ['a2', 'b1', 'c1', 'd2'] # Y
14 | input4 = ['a1', 'b1', 'c1', 'd1'] # Y
15 | print(trees.classify(tree, col, input1))
16 | print(trees.classify(tree, col, input2))
17 | print(trees.classify(tree, col, input3))
18 | print(trees.classify(tree, col, input4))
19 |
20 | df = pd.read_csv('data/2.csv')
21 | col = df.columns.tolist()
22 | data = df.values.tolist()
23 | tree = trees.createTree(data, col)
24 | print(tree)
25 |
26 | col = df.columns.tolist()[:-1]
27 | input1 = ['undergraduate', 'man', 'cet6', 'a1', 'b1'] # N
28 | input2 = ['undergraduate', 'man', 'cet4', 'a1', 'b1'] # Y
29 | input3 = ['postgraduate', 'man', 'no', 'a1', 'b2'] # Y
30 | input4 = ['undergraduate', 'man', 'no', 'a1', 'b3'] # Y
31 | input5 = ['undergraduate', 'man', 'no', 'a3', 'b3'] # Y
32 | print(trees.classify(tree, col, input1))
33 | print(trees.classify(tree, col, input2))
34 | print(trees.classify(tree, col, input3))
35 | print(trees.classify(tree, col, input4))
36 | print(trees.classify(tree, col, input5))
37 |
--------------------------------------------------------------------------------
/3.决策树/README.md:
--------------------------------------------------------------------------------
1 | # 决策树
2 |
3 | ## 第一题
4 |
5 | #### 数据集
6 | {DQ, JZZY, YJZJ, YJHF}
7 | 1. 所在地区(DQ) Value(DQ) = {a1, a2, a3}
8 | 2. 机主职业(JZZY) Value(JZZY) = {b1, b2, b3}
9 | 3. 月均主叫次数范围(YJZJ) Value(YJZJ) = {c1, c2, c3}
10 | 4. 月均话费范围(YJHF) Value(YJHF) = {d1, d2}
11 | 5. 分类结果(Class) Value(Class) = {能发展, 不能发展} => {Y, N}
12 |
13 | #### ID3树
14 | ```
15 | {'DQ': {'a2': {'YJHF': {'d1': 'N', 'd2': 'Y'}}, 'a3': {'JZZY': {'b1': {'YJZJ': {'c2': 'N', 'c1': 'Y'}}, 'b2': 'N', 'b3': 'N'}}, 'a1': 'Y'}}
16 | ```
17 |
18 | #### 可视化结果
19 | 
20 |
21 |
22 | ## 第二题
23 |
24 | #### 数据集
25 | {Education, Sex, Language, Character, Job}
26 | 1. 学历(Education) Value(Education) = {研究生, 本科, 专科} => {postgraduate, undergraduate, college}
27 | 2. 性别(Sex) Value(Sex) = {男, 女} => {man, woman}
28 | 3. 外语水平(Language) Value(Language) = {4级以下, 4级, 6级} => {no, cet4, cet6}
29 | 4. 性格特征(Character) Value(Character) = {a1, a2, a3}
30 | 5. 岗位性质(Job) Value(Job) = {b1, b2, b3}
31 | 6. 分类结果(Class) Value(Class) = {称职, 基本称职或不称职} => {Y, N}
32 |
33 | #### ID3树
34 | ```
35 | {'Job': {'b3': {'Character': {'a2': 'N', 'a3': 'Y', 'a1': {'Education': {'postgraduate': 'N', 'undergraduate': 'Y'}}}}, 'b1': {'Language': {'cet4': 'Y', 'no': 'N', 'cet6': {'Education': {'postgraduate': 'Y', 'undergraduate': 'N'}}}}, 'b2': 'Y'}}
36 | ```
37 |
38 | #### 可视化结果
39 | 
40 |
--------------------------------------------------------------------------------
/2.推理机/README.md:
--------------------------------------------------------------------------------
1 | # 推理机
2 |
3 | 1. 推理机
4 | https://zh.wikipedia.org/wiki/推理机
5 |
6 | 2. 正向推理机
7 | https://en.wikipedia.org/wiki/Forward_chaining
8 |
9 | 3. 反向推理机
10 | https://en.wikipedia.org/wiki/Backward_chaining
11 |
12 | ## 数据集
13 | 
14 | 1. 有毛的动物是哺乳类;
15 | 2. 有奶的动物是哺乳类;
16 | 3. 有羽毛的动物是鸟类;
17 | 4. 若动物会飞且会生蛋,则它是鸟类;
18 | 5. 吃肉的哺乳类是肉食动物;
19 | 6. 犬牙利爪,眼睛向前的是肉食动物;
20 | 7. 反刍食物的哺乳类是偶蹄类;
21 | 8. 有蹄的哺乳类是有蹄类;
22 | 9. 黄褐色有暗斑点的肉食类是金钱豹;
23 | 10. 黄褐色有黑条纹的肉食类是老虎;
24 | 11. 长腿长脖子有黄褐色暗斑的有蹄类是长颈鹿;
25 | 12. 有黑白条纹的有蹄类是斑马;
26 | 13. 不会飞长腿长脖子的鸟是鸵鸟;
27 | 14. 不会飞善游泳黑白色的鸟是企鹅;
28 | 15. 善飞的鸟是信天翁。
29 |
30 | ## 伪代码:课本上的说明,并不适用于Python
31 | ```
32 | # 1. recall
33 | # (recall fact)
34 | # 判断变量fact中的一个事实是否在表facts中,若是,recall返回值是fact中的事实;否则,返回nil
35 | def recall(fact):
36 | pass
37 |
38 |
39 | # 2. test-if
40 | # (test-if rule)
41 | # 判断变量rule中的一条规则的前件包含的全部事实是否在表facts中,若是,test-if返回t;否则,返回nil
42 | def test_if(rule):
43 | pass
44 |
45 |
46 | # 3. remember
47 | # (remember new)
48 | # 判断变量new中的一个事实是否在表facts中,若是,remember返回nil;否则,将new中的事实添加到表facts的表头,
49 | # 且remember返回new中的事实
50 | def remember(new):
51 | pass
52 |
53 |
54 | # 4. use-then
55 | # (use-then rule)
56 | # 判断变量rule中的一条规则的后件包含的全部结论是否在表facts中,若全部结论都在facts中,则use-then返回nil;
57 | # 否则,将不在facts中的结论逐一添加到表facts中,且use-then返回t
58 | def use_then(rule):
59 | pass
60 |
61 |
62 | # 5. try-rule
63 | # (try-raw rule)
64 | # 判断规则变量rule中的一条规则的前件包含的全部事实是否在表facts中,若全部事实都在facts中,且规则后件有不在facts中的结论,
65 | # 则把不在facts中的结论逐一添加到表facts中,try-rule返回t;否则,try-rule返回nil
66 | def try_rule(rule):
67 | pass
68 |
69 |
70 | # 6. step-forward
71 | # (step-forward rules)
72 | # 逐次扫描规则库rules中的规则,若发现rules中有一条可用规则,即该规则的前件包含的全部事实在表facts中,
73 | # 则把该规则的后件中不在facts中的所有结论添加到facts中,且step-forward返回t;
74 | # 若rules中没有一条可用规则,则step-forward返回nil
75 | def step_forward(rules):
76 | pass
77 |
78 |
79 | # 7. deduce
80 | # (deduce facts)
81 | # 连续不断地从规则库rules中选择可用规则,每选择到一条可用规则,则把该规则的后件中不在facts中的所有结论添加到facts中,
82 | # 对facts扩充,由扩充了的facts来选择下一条可用规则对facts再次扩充,直至没有可用规则可选为止。
83 | # 若曾找到一条可用规则对facts进行过一次扩充,则deduce返回t;否则,deduce返回nil
84 | def deduce(facts):
85 | pass
86 | ```
87 |
--------------------------------------------------------------------------------
/2.推理机/data/topo.xml:
--------------------------------------------------------------------------------
1 | 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
--------------------------------------------------------------------------------
/2.推理机/back.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: UTF-8 -*-
3 |
4 | class Rule():
5 | def __init__(self, feature, fact):
6 | self.feature = feature
7 | self.fact = fact
8 |
9 |
10 | # 加载规则
11 | def load_rule(path):
12 | P_list = []
13 | Q_list = []
14 | with open(path, 'r') as f:
15 | lines = f.readlines()
16 | for line in lines:
17 | if line:
18 | pro = line.strip().split(' ')
19 | P_list.append(pro[:-1])
20 | Q_list.append(pro[-1])
21 | return P_list, Q_list
22 |
23 |
24 | # 添加规则
25 | def add_rule(k, v, rules):
26 | for rule in rules:
27 | if set(k) == set(rule.feature):
28 | print('特征已在数据库')
29 | return
30 | rules.append(Rule(k, v))
31 | print('特征成功添加 >>> %s ----> %s' % (k, v))
32 |
33 |
34 | # 删除规则
35 | def del_rule(k, rules):
36 | for rule in rules:
37 | if set(k) == set(rule.feature):
38 | rules.remove(rule)
39 | print('特征成功删除 >>> %s' % k)
40 | return
41 | print('特征不在数据库')
42 |
43 |
44 | # 拓扑排序
45 | def topological(rules):
46 | # 规则
47 | topo_rules = []
48 | P_list = [rule.feature for rule in rules]
49 | Q_list = [rule.fact for rule in rules]
50 |
51 | # 计算入度
52 | ind_list = []
53 | for i in P_list:
54 | sum = 0
55 | for x in i:
56 | if Q_list.count(x) > 0:
57 | sum += Q_list.count(x)
58 | ind_list.append(sum)
59 |
60 | # 拓扑排序
61 | while True:
62 | if ind_list.count(-1) == len(ind_list):
63 | break
64 |
65 | for i, ind in enumerate(ind_list):
66 | if ind == 0:
67 | topo_rules.append(Rule(P_list[i], Q_list[i]))
68 | ind_list[i] = -1
69 | # 更新入度
70 | for j, P in enumerate(P_list):
71 | if Q_list[i] in P:
72 | ind_list[j] -= 1
73 | return topo_rules
74 |
75 |
76 | # 反向推理机
77 | def infer(facts, topo_rules):
78 | flag = False
79 | for rule in topo_rules:
80 | # 如果所有规则都在输入中
81 | if list_in_set(rule.feature, facts):
82 | # 添加推导出的条件
83 | facts.append(rule.fact)
84 | flag = True
85 | print('{key} ----> {value}\n'.format(key=rule.feature, value=rule.fact))
86 |
87 | if flag:
88 | print('最终推出 ----> %s' % facts[-1])
89 | else:
90 | print('无法推出')
91 |
92 |
93 | # 判断list中所有元素是否都在集合set中
94 | def list_in_set(list, set):
95 | for i in list:
96 | if i not in set:
97 | return False
98 | return True
99 |
100 |
101 | if __name__ == '__main__':
102 | rules = []
103 | P_list, Q_list = load_rule('data/data.txt')
104 | for k, v in zip(P_list, Q_list):
105 | add_rule(k, v, rules)
106 | topo_rules = topological(rules)
107 |
108 | while True:
109 | choice = input("请输入选择:'test/add/del/show/exit',默认'test' >>> ")
110 | if choice == 'add':
111 | k = input('请输入添加的特征组合:').strip().split(' ')
112 | v = input('请输入添加的结论:')
113 | add_rule(k, v, rules)
114 | topo_rules = topological(rules) # 与正向不同,每次都要更新
115 |
116 | elif choice == 'del':
117 | k = input('请输入删除的特征组合:').strip().split(' ')
118 | del_rule(k, rules)
119 | topo_rules = topological(rules) # 与正向不同,每次都要更新
120 |
121 | elif choice == 'show':
122 | for rule in rules:
123 | print('%s ----> %s' % (rule.feature, rule.fact))
124 |
125 | elif choice == 'exit':
126 | exit(0)
127 |
128 | else:
129 | k = input('请输入测试的特征组合:').strip().split(' ')
130 | infer(k, topo_rules)
131 |
--------------------------------------------------------------------------------
/3.决策树/trees.py:
--------------------------------------------------------------------------------
1 | from math import log
2 | import operator
3 |
4 |
5 | # 计算信息熵
6 | def calcShannonEnt(dataSet):
7 | numEntries = len(dataSet)
8 | labelCounts = {}
9 | for featVec in dataSet:
10 | currentLabel = featVec[-1]
11 | if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0
12 | labelCounts[currentLabel] += 1
13 | shannonEnt = 0.0
14 | for key in labelCounts:
15 | prob = float(labelCounts[key]) / numEntries
16 | shannonEnt -= prob * log(prob, 2) # H=−∑p(xi)log(p(xi))
17 | return shannonEnt
18 |
19 | # 按照给定特征划分数据集,选择所占列中等于选择值的项
20 | def splitDataSet(dataSet, axis, value):
21 | retDataSet = []
22 | for featVec in dataSet:
23 | if featVec[axis] == value:
24 | reducedFeatVec = featVec[:axis] # 左边一个
25 | reducedFeatVec.extend(featVec[axis + 1:]) # 右边一个
26 | retDataSet.append(reducedFeatVec)
27 | return retDataSet
28 |
29 |
30 | # 寻找最佳特征,用来划分数据集
31 | def chooseBestFeatureToSplit(dataSet):
32 | numFeatures = len(dataSet[0]) - 1 # 获取特征数量
33 | baseEntropy = calcShannonEnt(dataSet) # 计算数据集的熵
34 | bestInfoGain = 0.0
35 | bestFeature = -1
36 | for i in range(numFeatures): # 遍历所有特征
37 | featList = [example[i] for example in dataSet] # 获取当前特征列表
38 | uniqueVals = set(featList)
39 | newEntropy = 0.0
40 | for value in uniqueVals: # 遍历当前特征列表
41 | subDataSet = splitDataSet(dataSet, i, value)
42 | prob = len(subDataSet) / float(len(dataSet))
43 | newEntropy += prob * calcShannonEnt(subDataSet) # 计算特征对数据集的熵
44 | infoGain = baseEntropy - newEntropy # 计算信息增益
45 | if infoGain > bestInfoGain:
46 | bestInfoGain = infoGain # 选择信息增益最大的特征
47 | bestFeature = i
48 | return bestFeature
49 |
50 |
51 | # 辅助函数:采用多数判决的方法决定该节点的标签
52 | def majorityCnt(classList):
53 | classCount = {}
54 | for vote in classList:
55 | if vote not in classCount.keys(): classCount[vote] = 0
56 | classCount[vote] += 1
57 | sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
58 | return sortedClassCount[0][0]
59 |
60 |
61 | # ID3
62 | def createTree(dataSet, labels):
63 | classList = [example[-1] for example in dataSet] # 获取标签列表
64 | if classList.count(classList[0]) == len(classList): # 若数据集中所有实例属于同一类Ck,则为单节点树,并将Ck作为该节点的类标记
65 | return classList[0]
66 | if len(dataSet[0]) == 0: # 若特征集为空集,则为单节点树,并将数据集中实例数最大的类Ck作为该节点的类标记
67 | return majorityCnt(classList) # 返回占多数的标签
68 | bestFeat = chooseBestFeatureToSplit(dataSet) # 寻找最佳特征,用来划分数据集
69 | bestFeatLabel = labels[bestFeat]
70 | myTree = {bestFeatLabel: {}}
71 | del (labels[bestFeat])
72 | featValues = [example[bestFeat] for example in dataSet] # 获取最佳特征列表
73 | uniqueVals = set(featValues) # 特征去重
74 | for value in uniqueVals: # 对每个最佳特征,分割成若干个非空子集
75 | subLabels = labels[:]
76 |
77 | """
78 | [a1, b1, c2]
79 | [a2, b1, c3]
80 |
81 | [a3, b2, c1]
82 | [a1, b2, c2]
83 |
84 | [a2, b3, c3]
85 | [a3, b3, c1]
86 |
87 | 假设b为最佳特征,对每个最佳特征,分割成若干个非空子集====>
88 |
89 | b1: [a1, c2], [a2, c3]
90 | b2: [a3, c1], [a1, c2]
91 | b3: [a2, c3], [a3, c1]
92 | """
93 |
94 | myTree[bestFeatLabel][value] = createTree(splitDataSet(dataSet, bestFeat, value), subLabels) # 递归创建子树
95 | return myTree
96 |
97 |
98 | # 分类
99 | def classify(inputTree, featLabels, testVec):
100 | firstStr = list(inputTree.keys())[0]
101 | secondDict = inputTree[firstStr]
102 | featIndex = featLabels.index(firstStr)
103 | key = testVec[featIndex]
104 | valueOfFeat = secondDict[key]
105 | if isinstance(valueOfFeat, dict): # 递归遍历子树
106 | classLabel = classify(valueOfFeat, featLabels, testVec)
107 | else: # 直到叶子节点
108 | classLabel = valueOfFeat
109 | return classLabel
110 |
--------------------------------------------------------------------------------
/2.推理机/forward.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: UTF-8 -*-
3 |
4 | """
5 | 正向推理(数据驱动),其基本思想是:
6 | 1. 从问题已有的事实(初始证据)出发,正向使用规则,
7 | 2. 当规则的条件部分与已有的事实匹配时,就把该规则作为可用规则放入候选规则队列中,
8 | 3. 然后通过冲突消解,在候选队列中选择一条规则作为启用规则进行推理,并将其结论放入数据库中,作为下一步推理时的证据。
9 | 4. 如此重复这个过程,直到再无可用规则可被选用或者求得了所要求的解为止。
10 |
11 | 冲突消解:
12 | 1. 优先度排序。事先给知识库中每条规则设定优先度参数,优先度高的规则先执行。
13 | 2. 规则的条件详细度排序。条件较多、较详细的规则,其结论一般更接近于目标,优先执行。
14 | 3. 匹配度排序。事先给知识库中每条规则设定匹配度参数,匹配度高的规则先执行。
15 | 4. 根据领域问题的特点排序。根据领域知识可以知道的某些特点,事先设定知识库中的规则的使用顺序。
16 | """
17 |
18 |
19 | class Rule():
20 | def __init__(self, feature, fact):
21 | self.feature = feature
22 | self.fact = fact
23 |
24 |
25 | # 加载规则
26 | def load_rule(path):
27 | P_list = []
28 | Q_list = []
29 | with open(path, 'r') as f:
30 | lines = f.readlines()
31 | for line in lines:
32 | if line:
33 | pro = line.strip().split(' ')
34 | P_list.append(pro[:-1])
35 | Q_list.append(pro[-1])
36 | return P_list, Q_list
37 |
38 |
39 | # 添加规则
40 | def add_rule(k, v, rules):
41 | for rule in rules:
42 | if set(k) == set(rule.feature):
43 | print('特征已在数据库')
44 | return
45 | rules.append(Rule(k, v))
46 | print('特征成功添加 >>> %s ----> %s' % (k, v))
47 |
48 |
49 | # 删除规则
50 | def del_rule(k, rules):
51 | for rule in rules:
52 | if set(k) == set(rule.feature):
53 | rules.remove(rule)
54 | print('特征成功删除 >>> %s' % k)
55 | return
56 | print('特征不在数据库')
57 |
58 |
59 | # 冲突消解
60 | def resolve(candidate_rule, method=1):
61 | if method == 1: # 1. 最长匹配策略
62 | return max(candidate_rule, key=lambda x: len(x.feature))
63 | elif method == 2: # 2. 最早匹配策略
64 | return candidate_rule[0]
65 | elif method == 3: # 3. 最晚匹配策略
66 | return candidate_rule[-1]
67 | else:
68 | exit(0)
69 |
70 |
71 | # 正向推理机
72 | def infer(facts, rules):
73 | candidate_rule = []
74 | visited_rule = []
75 | flag = False
76 |
77 | while True:
78 | candidate_rule.clear()
79 | # 1. 从问题已有的事实(初始证据)出发,正向使用规则
80 | for rule in rules:
81 | # 2. 当规则的条件部分与已有的事实匹配时,就把该规则作为可用规则放入候选规则队列中
82 | if list_in_set(rule.feature, facts) and rule not in visited_rule:
83 | candidate_rule.append(rule)
84 | visited_rule.append(rule)
85 | print('规约:{key} ----> {value}'.format(key=rule.feature, value=rule.fact))
86 |
87 | # 3. 然后通过冲突消解,在候选队列中选择一条规则作为启用规则进行推理,并将其结论放入数据库中,作为下一步推理时的证据
88 | if len(candidate_rule) != 0:
89 | result = resolve(candidate_rule)
90 | facts.append(result.fact)
91 | flag = True
92 | print('冲突消解:{key} ====> {value}\n'.format(key=result.feature, value=result.fact))
93 | # 4. 如此重复这个过程,直到再无可用规则可被选用或者求得了所要求的解为止
94 | else:
95 | break
96 |
97 | if flag:
98 | print('最终推出 ----> %s' % facts[-1])
99 | else:
100 | print('无法推出')
101 |
102 |
103 | # 判断list中所有元素是否都在集合set中
104 | def list_in_set(list, set):
105 | for i in list:
106 | if i not in set:
107 | return False
108 | return True
109 |
110 |
111 | if __name__ == '__main__':
112 | rules = []
113 | P_list, Q_list = load_rule('data/data.txt')
114 | for k, v in zip(P_list, Q_list):
115 | add_rule(k, v, rules)
116 |
117 | while True:
118 | choice = input("请输入选择:'test/add/del/show/exit',默认'test' >>> ")
119 | if choice == 'add':
120 | k = input('请输入添加的特征组合:').strip().split(' ')
121 | v = input('请输入添加的结论:')
122 | add_rule(k, v, rules)
123 |
124 | elif choice == 'del':
125 | k = input('请输入删除的特征组合:').strip().split(' ')
126 | del_rule(k, rules)
127 |
128 | elif choice == 'show':
129 | for rule in rules:
130 | print('%s ----> %s' % (rule.feature, rule.fact))
131 |
132 | elif choice == 'exit':
133 | exit(0)
134 |
135 | else:
136 | k = input('请输入测试的特征组合:').strip().split(' ')
137 | infer(k, rules)
138 |
--------------------------------------------------------------------------------
/1.井字棋/game.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: UTF-8 -*-
3 |
4 | import random
5 | from abc import ABCMeta, abstractmethod
6 |
7 | ROW = COL = 3 # 棋盘大小
8 | SPACE = '-' # 空格标签
9 | HUMAN = 'HUMAN' # 人类棋子标签
10 | COMPUTER = 'COMPUTER' # 电脑棋子标签
11 |
12 |
13 | # 棋盘是否有空位
14 | def empty(board):
15 | if board.count(SPACE) == 0:
16 | return False
17 | else:
18 | return True
19 |
20 |
21 | # 判断player是否胜利
22 | def winner(board, player):
23 | """
24 | --------------
25 | | 0 || 1 || 2 |
26 | | 3 || 4 || 5 |
27 | | 6 || 7 || 8 |
28 | --------------
29 | 获胜的算法:
30 | 012 345 678
31 | 036 147 258
32 | 048 246
33 | """
34 | wins = [[board[0], board[1], board[2]], [board[3], board[4], board[5]], [board[6], board[7], board[8]],
35 | [board[0], board[3], board[6]], [board[1], board[4], board[7]], [board[2], board[5], board[8]],
36 | [board[0], board[4], board[8]], [board[2], board[4], board[6]]]
37 | state = [player, player, player]
38 | if state in wins:
39 | return True
40 | else:
41 | return False
42 |
43 |
44 | # 定义抽象基类
45 | class Player(metaclass=ABCMeta):
46 |
47 | def __init__(self, chess):
48 | self.chess = chess
49 |
50 | @abstractmethod
51 | def move(self):
52 | pass
53 |
54 |
55 | class Computer(Player):
56 |
57 | def __init__(self, chess='O'):
58 | Player.__init__(self, chess)
59 |
60 | def minimax(self, board, player, next_player, alpha=-2, beta=2):
61 | if winner(board, COMPUTER): # 电脑胜利
62 | return +1
63 | if winner(board, HUMAN): # 人类胜利
64 | return -1
65 | elif not empty(board):
66 | return 0 # 平局
67 |
68 | for move in range(ROW * COL):
69 | if board[move] == SPACE: # 尝试下棋
70 | board[move] = player # 记录
71 | val = self.minimax(board, next_player, player, alpha, beta) # 继续思考对手怎么下棋
72 | board[move] = SPACE # 重置
73 |
74 | if player == COMPUTER: # 极大 max value
75 | if val > alpha:
76 | alpha = val
77 | if alpha >= beta: # 剪枝
78 | return beta
79 | else: # 极小 min value
80 | if val < beta:
81 | beta = val
82 | if beta <= alpha: # 剪枝
83 | return alpha
84 |
85 | if player == COMPUTER:
86 | return alpha
87 | else:
88 | return beta
89 |
90 | def move(self, board):
91 | best = -2
92 | my_moves = []
93 | for move in range(ROW * COL):
94 | if board[move] == SPACE: # 尝试下棋
95 | board[move] = COMPUTER # 记录
96 | score = self.minimax(board, HUMAN, COMPUTER) # 思考对手怎么下棋
97 | board[move] = SPACE # 重置
98 |
99 | if score > best: # 找到更优的位置
100 | best = score
101 | my_moves = [move]
102 | if score == best: # 一样优秀的位置
103 | my_moves.append(move)
104 |
105 | pos = random.choice(my_moves) # 随机挑出一个位置
106 | board[pos] = COMPUTER
107 |
108 |
109 | class Human(Player):
110 |
111 | def __init__(self, chess='X'):
112 | Player.__init__(self, chess)
113 |
114 | def move(self, board):
115 | looping = True
116 | while looping:
117 | try:
118 | inp = input("请输入下棋位置[1-9]:")
119 | pos = int(inp) - 1 # 输入的下标从1开始
120 | if 0 <= pos <= 8:
121 | if board[pos] == SPACE:
122 | looping = False
123 | else:
124 | print("此处不允许下棋")
125 | else:
126 | print("输入不合法:[1-9]")
127 | except:
128 | print("输入不合法:Error")
129 |
130 | board[pos] = HUMAN
131 |
132 |
133 | class Game:
134 |
135 | def __init__(self):
136 | # 初始化游戏
137 | self.board = [SPACE] * (ROW * COL)
138 | self.computer = Computer()
139 | self.human = Human()
140 |
141 | choice = input("请选择棋子类型:[X/O],默认'X' >>> ")
142 | if choice == 'O':
143 | self.computer.chess = 'X'
144 | self.human.chess = 'O'
145 | else:
146 | self.computer.chess = 'O'
147 | self.human.chess = 'X'
148 |
149 | choice = input("请选择是否先手:[T/F],默认'T' >>> ")
150 | if choice == 'F':
151 | self.current_player = self.computer
152 | else:
153 | self.current_player = self.human
154 |
155 | # 切换玩家
156 | def switch(self):
157 | if self.current_player == self.computer:
158 | self.current_player = self.human
159 | else:
160 | self.current_player = self.computer
161 |
162 | # 渲染游戏
163 | def render(self):
164 | print('--------------')
165 | for i in range(ROW):
166 | for j in range(COL):
167 | k = i * ROW + j
168 | if self.board[k] == HUMAN:
169 | print('|', self.human.chess, '|', end='')
170 | elif self.board[k] == COMPUTER:
171 | print('|', self.computer.chess, '|', end='')
172 | else:
173 | print('|', self.board[k], '|', end='')
174 | print()
175 | print('--------------')
176 |
177 | # 开始游戏
178 | def start(self):
179 | # 渲染游戏
180 | self.render()
181 |
182 | # 游戏状态机
183 | while True:
184 | self.current_player.move(self.board)
185 | self.render()
186 |
187 | if winner(self.board, HUMAN):
188 | print("人类胜利!!!")
189 | exit(0)
190 | elif winner(self.board, COMPUTER):
191 | print("电脑胜利!!!")
192 | exit(0)
193 | elif not empty(self.board):
194 | print("平局!!!")
195 | exit(0)
196 |
197 | # 切换玩家
198 | self.switch()
199 |
200 |
201 | if __name__ == '__main__':
202 | Game().start()
203 |
--------------------------------------------------------------------------------