讀古今文學網 > 機器學習實戰 > 9.7 使用Python的Tkinter庫創建GUI >

9.7 使用Python的Tkinter庫創建GUI

機器學習給我們提供了一些強大的工具,能從未知數據中抽取出有用的信息。因此,能否將這些信息以易於人們理解的方式呈現十分重要。再者,假如人們可以直接與算法和數據交互,將可以比較輕鬆地進行解釋。如果僅僅只是繪製出一幅靜態圖像,或者只是在Python命令行中輸出一些數字,那麼對結果做分析和交流將非常困難。如果能讓用戶不需要任何指令就可以按照他們自己的方式來分析數據,就不需要對數據做出過多解釋。其中一個能同時支持數據呈現和用戶交互的方式就是構建一個圖形用戶界面(GUI,Graphical User Interface),如圖9-7所示。

圖9-7 默認的treeExplore圖形用戶界面,該界面同時顯示了輸入數據和一個回歸樹模型,其中的參數tolN = 10tolS = 1.0

示例:利用GUI對回歸樹調優

  1. 收集數據:所提供的文本文件。
  2. 準備數據:用Python解析上述文件,得到數值型數據。
  3. 分析數據:用Tkinter構建一個GUI來展示模型和數據。
  4. 訓練算法:訓練一棵回歸樹和一棵模型樹,並與數據集一起展示出來。
  5. 測試算法:這裡不需要測試過程。
  6. 使用算法: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 regTrees

def reDraw(tolS,tolN):
    pass

def drawNewTree:
    pass

root=Tk

Label(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 = IntVar
chkBtn = 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方法設定行和列的位置。另外,也可以通過設定columnspanrowspan的值來告訴佈局管理器是否允許一個小部件跨行或跨列。除此之外還有其他設置項可供使用。

還有一些新的小部件暫時未使用,這些小部件包括文本輸入框(Entry)、復選按鈕(Checkbutton)和按鈕整數值(IntVar)等。其中Entry部件是一個允許單行文本輸入的文本框。CheckbuttonIntVar的功能顯而易見:為了讀取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的構建程序包含一個前端,也就是面向用戶的一些代碼,如plotscatter方法等。事實上,它同時創建了一個後端,用於實現繪圖和不同應用之間接口。通過改變後端可以將圖像繪製在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.show
reDraw.canvas.get_tk_widget.grid(row=0, columnspan=3)  
  

現在將樹創建函數與該畫布鏈接起來。看一下實際效果,打開treeExplore.py並添加下面的代碼。注意我們之前實現過reDrawdrawTree的存根(stub),確保同一個函數不要重複出現。

程序清單9-7 Matplotlib和Tkinter的代碼集成

import matplotlib
matplotlib.use(\'TkAgg\')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

def 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.show

def 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,tolS

def 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的主要目的是把樹繪製出來。該函數假定輸入是合法的,它首先要做的是清空之前的圖像,使得前後兩個圖像不會重疊。清空時圖像的各個子圖也都會被清除,所以需要重新添加一個新圖。接下來函數會檢查復選框是否被選中❷。根據復選框是否被選中,確定基於tolStolN參數構建模型樹還是回歸樹。當樹構建完成之後就對測試集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。讀者可以測試一下並觀察執行的效果。