最近因为大创的事情,开始学习机器学习。在学习决策树的过程中,看到了有关决策树可视化的相关操作。
首先,使用的是sklearn库的tree对象进行建树与模型训练,我用的数据集是sklearn.datasets.load_breast_cancer():
from sklearn import tree
# 建立决策树分类器
dtc = tree.DecisionTreeClassifier()
# 训练决策树模型
dtc.fit(x_train, y_train)
当然,这个不是本文关注的重点,由于这样生成的训练模型,内部信息并不直观,只能够调用一些诸如.score()(查看对于某一数据集适应性的决定系数)的数值来粗略地判断模型对于新的数据集的适应程度,显然不太符合当下可视化的潮流,因此tree对象自身定义了一些导出(export)的方法,来将本来是抽象数据矩阵的决策树算法,转化为了可读性较高的文字语言。
先来看一看第一种方法:
tree.export_text(dtc)
这种方法所做的和它的名称一样,是将原来的tree对象(代码中叫作dtc)表达为一段文本(text),下面是打印出来的实际效果
还是很不直观,每个feature是什么并没有体现在文本上。这个问题我尝试解决,虽然在这个方法里面有一个feature_names的参数,但是我设置了一下,结果给我报了个这样的错:
我拿着它的源码来来回回看了几遍,都没发现问题出在哪里。辛亏急中生智,去搜了一下报错最后的a.any()和a.all()的含义,隐隐约约感觉是数据类型出了问题(我的feature_names参数指定的是一个ndarray数组),于是将ndarray数组类型转化成了list试一下:
tree.export_text(dtc, feature_names=list(feature_names))
结果:
Nice!
但是这样的表示方法还是过于冗长,可读性上也不是很好,因此我还是决定采用第二种方法:
# 导出一个可视化的决策树图
with open('breast_cancer_tree_graph.dot', 'w') as dot_file:
tree.export_graphviz(dtc, out_file=dot_file, feature_names=feature_names)
这种方法会生成一个.dot文档,里面保存了关于决策树的信息。由于Windows本身会按照文档的格式对.dot进行打开,因此看到的是这样的一个文档:
这样显然不是我所想要的直观的表示方式。想要用图像化的形式来对决策树进行解析的话,还需要安装Graphviz库
(其实我所使用的pycharm professional可以下载解析.dot文档的扩展,但是毕竟是用的教育邮箱,还是要以防万一,而且通过扩展加载出来的效果并不是很好,清晰度很低,而且还不能缩放和保存)
提示:使用pycharm中包管理工具下载的Graphviz库好像并不包含我们之后所要使用的工具,因此建议大家还是到官网下载安装包。
下面是Graphviz库的官方网站:
Graphviz下载地址
从上面的信息来看,Linux系统好像是可以通过sudo命令直接下载,但是Windows就只能老老实实下载安装包了。这里我下载的是最新的2.50.0版本的。
之后就是按照提示进行安装,这里就不再赘述过程。安装完后,需要对安装文件夹中的bin文件夹进行path路径环境变量配置:
Graphviz2.50是总的安装目录,打开应该是有下面四个文件夹,我们需要将第一个bin文件夹添加到path环境变量中,具体方法可以自行查找环境变量配置的相关文章教程,总的来说就是新建一行,然后将bin文件夹的绝对路径填入输入框中,如上图所示。
配置好之后,我们可以到命令行输入dot -version来检查是否成功安装了和配置了Graphviz库:
成功安装了之后,我们便可以开始最后一步转化了:
在命令行中先切换目录到.dot文档所在目录,再输入如下的指令:
dot -Tpng breast_cancer_tree_graph.dot -o tree.png
breast_cancer_tree_graph.dot:.dot文档名
tree.png:要生成的图片文件名,可以是别的类型,不一定要是png
然后就得到了我们想要的可以自由缩放的图片文件:
可以看到,这样的图像化表示方式,就要比之前的纯文本形式清晰多了
但是仔细想想,总觉得有哪里不太对劲……
对了! 每次生成一张图片,我们都需要进入命令行,切换directory,然后再输入上面那串冗长的命令。漆黑一片的命令行里,一不小心就会输错字母,十分搞人心态。要是能够在IDE中直接通过一行简单的代码调用,就能生成这样一张图片,那该多好啊!
这就是我真正想做的。
现在是凌晨一点二十五,废话少说,先上代码:
# _*_ coding utf-8 _*_
# Designer: はなちゃん
# Time: 2022/1/29 19:20
# Name: dot2png.py
from pathlib import Path
import subprocess
def check_valid_path(path):
"""检查文件路径是否有效,并返回以'/'分割的文件路径"""
if '\\' in path:
elements = str(Path(path)).split(sep='\\')
final_path = Path('/'.join(elements))
elif '/' in path:
final_path = Path(path)
else:
if not Path(path).exists():
raise Exception("Error: File path pattern is not correct.")
else:
final_path = Path(path)
if not final_path.exists():
raise Exception("Error: Your file path does not exist.")
if final_path != '':
return final_path
else:
return None
def dot2png(dot_file_path=None, img_path=None):
"""决策树可视化中.dot文件转化为.png图片的函数"""
if not dot_file_path:
raise Exception(".dot file is not given.")
elif not dot_file_path.endswith('.dot'):
raise Exception("file provided is not '.dot' type.")
DOT_PATH = check_valid_path(dot_file_path)
if not img_path:
img_path = 'dt_png.png'
elif not img_path.endswith('.png'):
raise Exception("image file not end with '.png'.")
IMG_PATH = img_path
cmd_args = ['dot', '-Tpng', DOT_PATH, '-o', IMG_PATH]
cmd_pro = subprocess.Popen(args=cmd_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
retval = cmd_pro.stdout.read().decode('gbk')
if retval == '':
print("successfully create file " + IMG_PATH)
else:
print("The program encountered some error: ")
print(retval)
使用了subprocess库来在后台开启子进程(今天临时抱佛脚刚学的。哦不,是昨天),文件路径处理方面采用了Path类,这是一个被我遗忘了很久,但确实很好用的文件路径处理模块。
第一个check_valid_path函数没有什么好说的,就是一些处理路径,并在合适的时候抛出异常,用来应付一下用户们千奇百怪的文件路径错误;dot2png函数是由.dot文档生成图片的主调函数,给定了两个参数:
dot_file_path:.dot文档的路径,绝对相对都可以,但是一定要正确指向文件,不然不出意外的话,应该会出现异常
img_path:生成图片的路径,可选参数,如果不写的话会默认在当前的cwd下新建一张名为dt_png的图片
最后是通过Popen调用命令行,我测试了一下,好像一般正常执行的话,得到的返回值retval是不会有内容的,反之若发生了异常,则会返回描述异常的字符串。因此通过这一标识来判定是否生成成功了。
当然,这个小程序肯定还存在很多没有被发现和测试到的漏洞。如果大家在使用过程中遇到了什么问题,欢迎在评论区提出来。虽然提出来了我也不一定会想回答——因为我懒 ╯︿╰。
写这篇博客是晚上十一点多,我写到一半的时候,因为打错了个回车不小心按了下ctrl+z,结果一瞬间整篇博客回到了差不多刚开头的地方,惊慌失措的我马上下意识地按了下ctrl+y,结果是重做的框也没了……我在原地怔了一会,想着是不是太久没写东西了,电脑都嫌我文风太啰嗦了。
最后祝大家新年快乐!