介绍

麻烦的人工智导的作业(不是我的),依据下面数据库和规则库,用 Python 设计一个基于产生式系统的病情诊断系统。要求用户输入相关症状情况,系统根据诊断规则判断出对应的疾病。请根据诊断规则编写程序:

感冒:{症状1:{鼻塞,流涕,头痛},症状2:{发热,喉咙痛},症状3:{乏力,肌肉疼痛}}
流感:{症状1:{鼻塞,流涕,头痛},症状2:{高热,咳嗽},症状3:{乏力,肌肉疼痛,喉咙痛}}
胃炎:{症状1:{胃疼,恶心,呕吐},症状2:{食欲不振、嗳气},症状3:{乏力,体重下降}}
肝炎:{症状1:{肝区疼痛,恶心,呕吐},症状2:{黄疸,食欲不振},症状3:{乏力,易疲劳,体重下降}}

下面是诊断规则:

感冒:如果感冒的症状1、症状2和症状3都出现,那么可能是感冒。
流感:如果流感的症状1、症状2和症状3都出现,并且症状2和症状3出现的时间较短,那么可能是流感。
胃炎:如果胃炎的症状1和症状2都出现,并且症状持续时间较长,那么可能是胃炎。
肝炎:如果肝炎的症状1、症状2和症状3都出现,那么可能是肝炎。

算法的描述

初始化和数据结构:

我们使用面向对象编程,首先定义了一个 DiseaseDiagnosisSystem 类,包括了一些初始化工作。对于疾病诊断系统,使用类的封装性,可以控制对数据和方法的访问,确保数据的完整性并隐藏实现细节。这样可以更好地管理系统的状态和行为。对于这个题目,我们分为四个部分编程。分别是在类中内置数据库、记录症状、进行诊断、输出结果。主要框架如下:

class DiseaseDiagnosisSystem:
def __init__(self):
# 类中内置数据库...

def record_symptoms(self, symptoms):
# 记录症状的方法...

def diagnose(self):
# 进行诊断的方法...

def main()
# 主函数...

if __name__ == "__main__":
main()

用户交互:

首先我们需要引导用户输入正确的症状,所以我们在类中创建了一个症状列表,该列表中列出了所有可以诊断的症状。

self.symptoms = ["鼻塞", "流涕", "头痛", "发热", "高热", "喉咙痛", "咳嗽", "乏力", "肌肉疼痛", "胃疼", "恶心", "呕吐", "食欲不振", "嗳气", "体重下降", "肝区疼痛", "黄疸", "易疲劳", "尿液异常", "水肿", "高血压"]

同时在类中定义一个list_symptoms 函数,作用是在用户输入前打印出用户能够选择的症状。

def list_symptoms(self):
print("可供选择的症状有:" + ", ".join(self.symptoms))

.join是python自带的函数,含义是将列表中的每个成员以字符','分隔开再拼接成一个字符串

使用 while 循环来获取用户的输入,当用户输入一个症状回车时,比如用户输入"发热",判断用户输入的值是否为exit,若是则跳出循环,若不是则将"发热"赋值给symptom,然后将symptom传递给类中的add_symptom函数,执行症状记录。

system.list_symptoms() # 调用类中的函数,引导用户输入
while True:
symptom = input("请输入您的症状(输入一个症状,exit退出):")
if symptom.lower() == "exit":
break
system.add_symptom(symptom)

症状记录

那么如何编写add_symptom函数使其能够执行症状记录的功能呢?

首先创建一个疾病嵌套字典,名为diseases,字典的键为”感冒“”流感“...值为”症状1“”症状2“的嵌套字典,症状1症状2的值为具体症状,创建的字典如下:

diseases = {
"感冒": {
"症状1": {"鼻塞", "流涕", "头痛"},
"症状2": {"发热", "喉咙痛"},
"症状3": {"乏力", "肌肉疼痛"}
},
"流感": {
"症状1": {"鼻塞", "流涕", "头痛"},
"症状2": {"高热", "咳嗽"},
"症状3": {"乏力", "肌肉疼痛", "喉咙痛"}
},
... # ppt应该放不下,这里直接省略号就行
}

但是仅仅创建一个疾病字典并不能实现对症状的计数,所以同时我们又创建了一个计数字典,计数字典的键为症状,值为[症状1,症状2,症状3]的计数列表,如下

self.symptom_counts = {
"感冒": [0, 0, 0],
"流感": [0, 0, 0],
"胃炎": [0, 0, 0],
"肝炎": [0, 0, 0],
"肾炎": [0, 0, 0]
}

我们的想法是当用户输入一个症状时,我们遍历疾病字典,看用户输入的疾病是否在疾病字典中存在。若存在,则在计数字典中症状出现的相应位置+1。比如当用户输入鼻塞时,遍历diseases字典,找到鼻塞在感冒和流感的症状1的位置,然后再计数字典中更新感冒和流感的计数列表以此实现用户疾病的计数。

需要着重注明的是数据的类型,这里数据类型把我看裂开。

# items() 遍历方法把字典中每对 key 和 value 组成一个元组,并把这些元组放在列表中返回。
for disease, symptoms in diseases.items():
# diseases.items() 是列表[("感冒": {"症状1": ...,"症状2": ...,"症状3": ...}),("流感":{...}),...]
# symptoms 是字典 {"症状1": ...,"症状2": ...,"症状3": ...}
for key, symptom_set in symptoms.items():
# symptoms.items() 是列表[("症状1": {"鼻塞", "流涕", "头痛"}),("症状2":{...}),...]
# symptom_set 是集合 {"鼻塞", "流涕", "头痛"}
if symptom in symptom_set:
# list() 将symptoms.keys()中的键全部取出形成列表 list.index(key)查询key的下标
# list(symptoms.keys()) ["症状1","症状2", "症状3"]
index = list(symptoms.keys()).index(key)
self.symptom_counts[disease][index] += 1
# self.symptom_counts[disease] 是症状列表[0,0,0] 后面的[index]是索引

诊断算法:

当然仅仅是计数还不能满足我们的要求,我们还需要根据规则库来判断用户可能患有的疾病。所以首先先建立一个空列表 possible_diseases 代表用户可能患有的疾病,当用户输入症状的时候,在更新计数字典的同时我们把对应的疾病名称添加到该列表中

def diagnose(self):
possible_diseases = []
for disease, counts in self.symptom_counts.items():
count = sum(counts)
if count > 0:
possible_diseases.append(disease)

当然这只是用户可能患有的疾病,我们还需要根据规则库来进一步判断。但是在实现算法的过程中,我们发现在用户多次输入同一个症状的内容时程序会不停的计数,对后续的诊断会产生影响,所以我们在诊断之前先将计数列表中大于0的值置1,然后根据计数字典中"感冒"列表值的和是否为3来决定输出。

# 将计数列表self.symptom_counts中大于0的值置1,避免当用户输入症状过少或者用户一直输入症状1的内容时程序误判
for disease in possible_diseases:
# enumerate() 函数同时列出数据和数据下标
for index, value in enumerate(self.symptom_counts[disease]):
if value > 0:
self.symptom_counts[disease][index] = 1

和用户可能患有的疾病一样,创建一个results结果空列表。我们遍历possible_diseases中记录的疾病,根据规则库来判断用户的症状是否满足规则库,比如当用户输入"鼻塞""发热""乏力"的时候,计数字典中"感冒"列表就会变成[1,1,1],此时使用sum求和计算列表值的和。

results = []
for disease in possible_diseases:
if disease == "流感" and sum(self.symptom_counts[disease]) == 3:
result = self.ask_duration_question(disease)
if result:
results.append(result)
elif disease == "感冒" and sum(self.symptom_counts[disease]) == 3:
results.append(disease)
elif disease == "胃炎" and sum(self.symptom_counts[disease][:2]) == 2:
result = self.ask_gastritis_question(disease)
if result:
results.append(result)
elif disease == "肝炎" and sum(self.symptom_counts[disease]) == 3:
results.append(disease)
elif disease == "肾炎" and sum(self.symptom_counts[disease]) == 3:
result = self.ask_nephritis_question(disease)
if result:
results.append(result)
return results

根据规则库,在诊断过程中,针对特定的疾病(如流感、胃炎、肾炎),还需要进一步对一些特定的问题询问,以更准确地诊断出疾病。

def ask_duration_question(self, disease):
answer = input(f"您出现的高热、咳嗽、乏力、肌肉疼痛或喉咙痛的症状持续时间是否较短?(是/否) ")
if answer.lower() == "是":
return disease

def ask_gastritis_question(self, disease):
answer = input(f"您出现的胃疼、恶心、呕吐、食欲不振或者嗳气的症状持续时间是否较长?(是/否) ")
if answer.lower() == "是":
return disease

def ask_nephritis_question(self, disease):
answer = input(f"您出现的水肿和尿液异常的症状持续时间是否较长?(是/否) ")
if answer.lower() == "是":
return disease

实验结果及分析

运行结果

模拟用户输入多种症状以及输入无关项时的程序输出结果

可供选择的症状有:鼻塞, 流涕, 头痛, 发热, 高热, 喉咙痛, 咳嗽, 乏力, 肌肉疼痛, 胃疼, 恶心, 呕吐, 食欲不振, 嗳气, 体重下降,  肝区疼痛, 黄疸, 易疲劳, 尿液异常, 水肿,  高血压
请输入您的症状(每次输入一个症状,输入exit退出):鼻塞
请输入您的症状(每次输入一个症状,输入exit退出):发热
请输入您的症状(每次输入一个症状,输入exit退出):乏力
请输入您的症状(每次输入一个症状,输入exit退出):高热
请输入您的症状(每次输入一个症状,输入exit退出):易疲劳
请输入您的症状(每次输入一个症状,输入exit退出):高血压
请输入您的症状(每次输入一个症状,输入exit退出):水肿
请输入您的症状(每次输入一个症状,输入exit退出):123
请输入您的症状(每次输入一个症状,输入exit退出):abc
请输入您的症状(每次输入一个症状,输入exit退出):
请输入您的症状(每次输入一个症状,输入exit退出):exit
您出现的高热、咳嗽、乏力、肌肉疼痛或喉咙痛的症状持续时间是否较短?(是/否) 是
您出现的水肿和尿液异常的症状持续时间是否较长?(是/否) 是
诊断结果:感冒, 流感, 肾炎

优化

程序本身经过了多次优化,在最初的版本中由于没有考虑到用户的需求,在用户输入多种符合症状的疾病时只能输出一种结果。并且对于这种严谨程度高的医疗诊断系统来说,还需要测试模块来保证程序的运行结果。程序还有许多可以优化的地方,我们自认为另外创建一个字典用于计数或多或少是做的复杂了,或许根据嵌套字典中症状键值的布尔值来改变症状的键为布尔值是一种可行的方法,又或者可以减少字典和嵌套循环的使用来减少程序的运行时间和占用。而且也不一定需要使用列表来记录症状合集,增加重复的工作并且不好维护,这都是可以优化的地方。

完整代码

# 定义了一个名为 DiseaseDiagnosisSystem 的类
class DiseaseDiagnosisSystem():
def __init__(self): # 定义__init__
# 类中添加对象属性
self.symptoms = ["鼻塞", "流涕", "头痛", "发热", "高热", "喉咙痛", "咳嗽", "乏力", "肌肉疼痛", "胃疼", "恶心", "呕吐", "食欲不振", "嗳气", "体重下降", "肝区疼痛", "黄疸", "易疲劳", "尿液异常", "水肿", "高血压"]
# 初始化症状计数字典,键为列表
self.symptom_counts = {
"感冒": [0, 0, 0],
"流感": [0, 0, 0],
"胃炎": [0, 0, 0],
"肝炎": [0, 0, 0],
"肾炎": [0, 0, 0]
}

# 提示函数
def list_symptoms(self):
# .join是函数str.join(item)的缩写','.join('abc')的含义是将字符串abc中的每个成员以字符','分隔开再拼接成一个字符串'a,b,c'
print("可供选择的症状有:" + ", ".join(self.symptoms))

# 定义一个字典 diseases,传参
def add_symptom(self, symptom):
diseases = {
"感冒": {
"症状1": {"鼻塞", "流涕", "头痛"},
"症状2": {"发热", "喉咙痛"},
"症状3": {"乏力", "肌肉疼痛"}
},
"流感": {
"症状1": {"鼻塞", "流涕", "头痛"},
"症状2": {"高热", "咳嗽"},
"症状3": {"乏力", "肌肉疼痛", "喉咙痛"}
},
"胃炎": {
"症状1": {"胃疼", "恶心", "呕吐"},
"症状2": {"食欲不振", "嗳气"},
"症状3": {"乏力", "体重下降"}
},
"肝炎": {
"症状1": {"肝区疼痛", "恶心", "呕吐"},
"症状2": {"黄疸", "食欲不振"},
"症状3": {"乏力", "易疲劳", "体重下降"}
},
"肾炎": {
"症状1": {"高血压", "高热"},
"症状2": {"水肿", "尿液异常"},
"症状3": {"乏力", "易疲劳", "体重下降"}
}
}

# items() 遍历方法把字典中每对 key 和 value 组成一个元组,并把这些元组放在列表中返回。
for disease, symptoms in diseases.items():
# for ... in ... 后接列表
# diseases.items() [("感冒": {"症状1": ...,"症状2": ...,"症状3": ...}),("流感":{...}),...]
# symptoms {"症状1": ...,"症状2": ...,"症状3": ...}
for key, symptom_set in symptoms.items():
# symptoms.items() [("症状1": {"鼻塞", "流涕", "头痛"}),("症状2":{...}),...]
# symptom_set {"鼻塞", "流涕", "头痛"}
if symptom in symptom_set:
# list() 将symptoms.keys()中的键全部取出形成列表 list.index(key)查询key的下标
# list(symptoms.keys()) ["症状1","症状2", "症状3"]
index = list(symptoms.keys()).index(key)
self.symptom_counts[disease][index] += 1
# self.symptom_counts[disease] 是症状列表[0,0,0] 后面的[index]是索引

def diagnose(self):
possible_diseases = []
for disease, counts in self.symptom_counts.items():
# sum 求列表中的元素求和 counts [0,0,0]
count = sum(counts)
if count > 0:
possible_diseases.append(disease)

for disease in possible_diseases:
# enumerate() 函数同时列出数据和数据下标
for index, value in enumerate(self.symptom_counts[disease]):
if value > 0:
self.symptom_counts[disease][index] = 1

results = []
for disease in possible_diseases:
if disease == "流感" and sum(self.symptom_counts[disease]) == 3:
result = self.ask_duration_question(disease)
if result:
results.append(result)
elif disease == "感冒" and sum(self.symptom_counts[disease]) == 3:
results.append(disease)
elif disease == "胃炎" and sum(self.symptom_counts[disease][:2]) == 2:
result = self.ask_gastritis_question(disease)
if result:
results.append(result)
elif disease == "肝炎" and sum(self.symptom_counts[disease]) == 3:
results.append(disease)
elif disease == "肾炎" and sum(self.symptom_counts[disease]) == 3:
result = self.ask_nephritis_question(disease)
if result:
results.append(result)
return results

def ask_duration_question(self, disease):
answer = input(f"您出现的高热、咳嗽、乏力、肌肉疼痛或喉咙痛的症状持续时间是否较短?(是/否) ")
if answer.lower() == "是":
return disease

def ask_gastritis_question(self, disease):
answer = input(f"您出现的胃疼、恶心、呕吐、食欲不振或者嗳气的症状持续时间是否较长?(是/否) ")
if answer.lower() == "是":
return disease

def ask_nephritis_question(self, disease):
answer = input(f"您出现的水肿和尿液异常的症状持续时间是否较长?(是/否) ")
if answer.lower() == "是":
return disease

# 主函数
def main():
system = DiseaseDiagnosisSystem() # 创建对象
system.list_symptoms() # 调用类中的函数
while True:
symptom = input("请输入您的症状(输入一个症状,exit退出):")
# str.lower() 返回将字符串中所有大写字符转换为小写后生成的字符串
if symptom.lower() == "exit":
break
system.add_symptom(symptom)
result = system.diagnose()
if result:
print("诊断结果:" + ", ".join(result))
else:
print("未能诊断出疾病")

if __name__ == "__main__":
main()

吐槽

这玩意儿和人工智能有关系吗?就是逻辑 if...else 判断。。。还有和一个python忘光光的人(列表都忘了)解释代码逻辑真的好难,我还专门写了这个文档为了他们做 pre 用,哭死😭。


陕ICP备2022011813 | 由又拍云提供CDN加速
| 基于 Stellar 主题
十年之约