机器学习给我们提供了一些强大的工具,能从未知数据中抽取出有用的信息。因此,能否将这些信息以易于人们理解的方式呈现十分重要。再者,假如人们可以直接与算法和数据交互,将可以比较轻松地进行解释。如果仅仅只是绘制出一幅静态图像,或者只是在Python命令行中输出一些数字,那么对结果做分析和交流将非常困难。如果能让用户不需要任何指令就可以按照他们自己的方式来分析数据,就不需要对数据做出过多解释。其中一个能同时支持数据呈现和用户交互的方式就是构建一个图形用户界面(GUI,Graphical User Interface),如图9-7所示。
图9-7 默认的treeExplore
图形用户界面,该界面同时显示了输入数据和一个回归树模型,其中的参数tolN = 10
,tolS = 1.0
示例:利用GUI对回归树调优
- 收集数据:所提供的文本文件。
- 准备数据:用Python解析上述文件,得到数值型数据。
- 分析数据:用Tkinter构建一个GUI来展示模型和数据。
- 训练算法:训练一棵回归树和一棵模型树,并与数据集一起展示出来。
- 测试算法:这里不需要测试过程。
- 使用算法:GUI使得人们可以在预剪枝时测试不同参数的影响,还可以帮助我们选择模型的类型。
接下来将介绍如何用Python来构建GUI。首先介绍如何利用一个现有的模块Tkinter来构建GUI,之后介绍如何在Tkinter和绘图库之间交互,最后通过创建GUI使人们能够自己探索模型树和回归树的奥秘。
9.7.1 用Tkinter创建GUI
Python有很多GUI框架,其中一个易于使用的Tkinter,是随Python的标准编译版本发布的。Tkinter可以在Windows、Mac OS和大多数的Linux平台上使用。
下面先从最简单的Hello World例子开始。在Python提示符下输入以下命令:
>>> from Tkinter import *>>> root = Tk
这时会出现一个小窗口或者一些错误提示。要想在窗口上显示一些文字,可以输入如下命令:
>>> myLabel = Label(root, text=/"Hello World/")>>> myLabel.grid
输入完毕后,文本框里就会显示出你刚才输入的文字。非常简单吧!
为了程序的完整,应该再输入以下命令:
>>> root.mainloop
这条命令将启动事件循环,使该窗口在众多事件中可以响应鼠标点击、按键和重绘等动作。
Tkinter的GUI由一些小部件(Widget)组成。所谓小部件,指的是文本框(Text Box)、按钮(Button)、标签(Label)和复选按钮(Check Button)等对象。在刚才的Hello World例子中,标签myLabel
就是其中唯一的小部件。当调用myLabel
的.grid
方法时,就等于把myLabel
的位置告诉了布局管理器(Geometry Manager)。Tkinter中提供了几种不同的布局管理器,其中的.grid
方法会把小部件安排在一个二维的表格中。用户可以设定每个小部件所在的行列位置。这里没有做任何设定,myLabel
会默认显示在0行0列。
下面将所需的小部件集成在一起构建树管理器。建立一个新的Python文件treeExplore.py
,并在其中加入程序清单9-6的代码。
程序清单9-6 用于构建树管理器界面的Tkinter小部件
from numpy import *from Tkinter import *import regTreesdef reDraw(tolS,tolN): passdef drawNewTree: passroot=TkLabel(root, text=/"Plot Place Holder/").grid(row=0, columnspan=3)Label(root, text=/"tolN/").grid(row=1, column=0)tolNentry = Entry(root)tolNentry.grid(row=1, column=1)tolNentry.insert(0,/'10/')Label(root, text=/"tolS/").grid(row=2, column=0)tolSentry = Entry(root)tolSentry.grid(row=2, column=1)tolSentry.insert(0,/'1.0/')Button(root, text=/"ReDraw/", command=drawNewTree).grid(row=1, column=2,rowspan=3)chkBtnVar = IntVarchkBtn = Checkbutton(root, text=/"Model Tree/", variable = chkBtnVar)chkBtn.grid(row=3, column=0, columnspan=2)reDraw.rawDat = mat(regTrees.loadDataSet(/'sine.txt/'))reDraw.testDat = arange(min(reDraw.rawDat[:,0]),max(reDraw.rawDat[:,0]),0.01)reDraw(1.0, 10)root.mainloop
程序清单9-6的代码建立了一组Tkinter模块,并用网格布局管理器安排了它们的位置,这里还给出了两个绘制占位符(plot placeholder)函数,这两个函数的内容会在后面补充。这里所使用代码的格式与前面的例子一致,即首先创建一个Tk
类型的根部件然后插入标签。读者可以使用.grid
方法设定行和列的位置。另外,也可以通过设定columnspan
和rowspan
的值来告诉布局管理器是否允许一个小部件跨行或跨列。除此之外还有其他设置项可供使用。
还有一些新的小部件暂时未使用,这些小部件包括文本输入框(Entry
)、复选按钮(Checkbutton
)和按钮整数值(IntVar
)等。其中Entry
部件是一个允许单行文本输入的文本框。Checkbutton
和IntVar
的功能显而易见:为了读取Checkbutton
的状态需要创建一个变量,也就是IntVar
。
最后初始化一些与reDraw
关联的全局变量,这些变量会在后面用到。这里没有给出“退出”按钮,因为如果用户想退出,可以通过点击右上角关闭整个窗口,增加额外的退出按钮有点多此一举。假如读者真的想添加一个的话,可以输入下面的代码来实现:
Button(root, text=/'Quit/',fg=/"black/", command=root.quit).grid(row=1,column=2)
保存程序清单9-6的代码并执行,可以看到与图9-8类似的图。
图9-8 使用多个Tkinter部件创建的树管理器
现在GUI可以按照要求正常运行了,下面利用它来绘图。接下来的小节中将在同一幅图上绘出原始数据集及其对应的树回归预测值。
9.7.2 集成Matplotlib和Tkinter
本书已经用Matplotlib绘制过很多图像,能否将这些图像直接放在GUI上呢?下面将首先介绍“后端”的概念,然后通过修改Matplotlib后端(仅在我们的GUI上)达到在Tkinter的GUI上绘图的目的。
Matplotlib的构建程序包含一个前端,也就是面向用户的一些代码,如plot
和scatter
方法等。事实上,它同时创建了一个后端,用于实现绘图和不同应用之间接口。通过改变后端可以将图像绘制在PNG、PDF、SVG等格式的文件上。下面将设置后端为TkAgg(Agg是一个C++的库,可以从图像创建光栅图1)。TkAgg可以在所选GUI框架上调用Agg,把Agg呈现在画布上。我们可以在Tk的GUI上放置一个画布,并用.grid
来调整布局。
1. 光栅图也称为位图、点阵图、像素图,按点阵保存图像,放大会有失真。与光栅图相对的是矢量图,也称为向量图。——译者注
先用画布来替换绘制占位符,删掉对应标签并添加以下代码:
reDraw.f = Figure(figsize=(5,4), dpi=100)reDraw.canvas = FigureCanvasTkAgg(reDraw.f, master=root)reDraw.canvas.showreDraw.canvas.get_tk_widget.grid(row=0, columnspan=3)
现在将树创建函数与该画布链接起来。看一下实际效果,打开treeExplore.py
并添加下面的代码。注意我们之前实现过reDraw
和drawTree
的存根(stub),确保同一个函数不要重复出现。
程序清单9-7 Matplotlib和Tkinter的代码集成
import matplotlibmatplotlib.use(/'TkAgg/')from matplotlib.backends.backend_tkagg import FigureCanvasTkAggfrom matplotlib.figure import Figuredef reDraw(tolS,tolN): reDraw.f.clf reDraw.a = reDraw.f.add_subplot(111) if chkBtnVar.get: #❶ 检查复选框是否选中 if tolN < 2: tolN = 2 myTree=regTrees.createTree(reDraw.rawDat, regTrees.modelLeaf,regTrees.modelErr, (tolS,tolN)) yHat = regTrees.createForeCast(myTree, reDraw.testDat, regTrees.modelTreeEval) else: myTree=regTrees.createTree(reDraw.rawDat, ops=(tolS,tolN)) yHat = regTrees.createForeCast(myTree, reDraw.testDat) reDraw.a.scatter(reDraw.rawDat[:,0], reDraw.rawDat[:,1], s=5) reDraw.a.plot(reDraw.testDat, yHat, linewidth=2.0) reDraw.canvas.showdef getInputs: try: tolN = int(tolNentry.get) except: tolN = 10 print /"enter Integer for tolN/" #❷(以下两行)清除错误的输入并用默认值替换 tolNentry.delete(0, END) tolNentry.insert(0,/'10/') try: tolS = float(tolSentry.get) except: tolS = 1.0 print /"enter Float for tolS/" tolSentry.delete(0, END) tolSentry.insert(0,/'1.0/') return tolN,tolSdef drawNewTree: tolN,tolS = getInputs reDraw(tolS,tolN)
上述程序中一开始导入Matplotlib文件并设定后端为TkAgg。接下来的两个import
声明将TkAgg和Matplotlib图链接起来。
先来介绍函数drawNewTree
。从程序清单9-6可知,在有人点击ReDraw
按钮时就会调用该函数。函数实现了两个功能:第一,调用getInputs
方法得到输入框的值;第二,利用该值调用 reDraw
方法生成一个漂亮的图。下面对这些函数进行逐个介绍。
函数getInputs
试图理解用户的输入并防止程序崩溃。其中tolS
期望的输入是浮点数,而tolN
期望的输入是整数。为了得到用户输入的文本,可以在Entry
部件上调用.get
方法。虽然表单验证会在GUI编程时花费大量的时间,但这一点对于用户体验来说必不可少。另外,这里使用了try:
和except:
模式。如果Python可以把输入文本解析成整数就继续执行,如果不能识别则输出错误消息,同时清空输入框并恢复其默认值❶。对tolS
而言也存在同样的处理过程,最后返回输入值。
函数reDraw
的主要目的是把树绘制出来。该函数假定输入是合法的,它首先要做的是清空之前的图像,使得前后两个图像不会重叠。清空时图像的各个子图也都会被清除,所以需要重新添加一个新图。接下来函数会检查复选框是否被选中❷。根据复选框是否被选中,确定基于tolS
和tolN
参数构建模型树还是回归树。当树构建完成之后就对测试集testDat
进行预测,该测试集与训练集有相同的范围且点的分布均匀。最后,真实数据和预测值都被绘制出来。具体实现是,真实值采用scatter
方法绘制,而预测值则采用plot
方法绘制,这是因为scatter
方法构建的是离散型散点图,而plot
方法则构建连续曲线。
下面看一下实际效果,保存treeExplore.py
并执行。如果读者使用开发环境IDE来编码,那么可以用run
命令来运行程序。在命令行下可以直接使用命令python treeExplore.py
来运行。执行完之后应该可以看到类似于图9-7的结果。
图9-7的GUI包含了图9-8所有的小部件,而占位符采用Matplotlib图替换。默认情况下会给出一棵包含八个叶节点的回归树(参见图9-7)。我们也可以尝试模型树。通过选中模型树的复选框,再点击ReDraw按钮,就应该可以看到类似于图9-9的模型树结果。
图9-9 用treeExplore
的GUI构建的模型树,该图使用了与图9-7相同的数据和参数。与回归树相比,模型树取得了更好的预测效果
读者可以在上述treeExplore
中尝试不同的参数值。整个数据集包含200个样本,可以将tolN
设为150后观察执行效果。为构建尽可能大的树,应当将tolN
设为1,将tolS设为0。读者可以测试一下并观察执行的效果。