天天看點

【好文】帶你用Python開發個機器學習軟體!

Python 開發windows GUI程式是比較簡單的,并且相比于C++學習成本比較低,并且其效果也不錯。常用的Python GUI架構有: Tkinter,Wxpython,Pygtk,Pyqt,Pyside,Kivy。在這裡基于我個人推薦的話建議認真學習Pyqt和Kivy(我自己僅熟悉這兩個),下面我們先簡單的介紹一下這幾個Python GUI架構,然後基于一個最簡單的機器學習模型,實作一個基于GUI的機器學習應用。 1.Python GUI架構介紹

Tkinter: 是Python内嵌的GUI環境,使用TCL實作,其中Python IDLE 就是由Tkinter實作,其發展曆史比較悠久,并且在Python的标準安裝包中就包含了Tkinter,易學易用,跨平台,聽說實作出的效果比較簡陋(關鍵靠人)

Wxpython:由C++編寫,跨平台,說明文檔比較少,學起來有些費勁。

PyGTK: Python對GTK + GUI庫的封裝,GTK在Windows下的相容性會有一定的問題

Pyqt: QT是由C++實作的,Pyqt是Python對QT的包裝,跨平台性能好,并且Pyqt與QT的函數接口一緻,QT的開發文檔相當豐富,導緻Pyqt的開發文檔也是很豐富(我們下面會使用Pyqt5實作)

Pyside: 同時也是Python對QT的封裝,與Pyqt的API是一緻的(還是直接Pyqt吧)

Kivy: 使用Python與cpython編寫,可以做移動端裝置的APP開發,主要針對多點觸摸應用,支援Android和ios,布局設定時可以使用Kivy Language,好像也有類似于Qt Designer的kivy-designer(個人感覺不大好用,我一般直接壘Kivy語言)

那麼本小項目我們使用Pyqt5做GUI的開發,原因很簡單,簡單易學,API和QT一緻,開發文檔豐富(遇到問題好解決),使用Qt designer實作直覺布局設定,同時也友善打包釋出軟體。

可以在Qt designer上設計你的UI界面了。

【好文】帶你用Python開發個機器學習軟體!

2.首先來一個簡單的ML model

在這裡我們為了說明問題僅使用一個最簡單的機器學習model,假設我是美國一家房産中介的資料分析師,Leader要求我建構一個房産定價模型(如果按照下面方式去模組化,估計我會被炒,這裡隻是假設,你就當故事聽就OK了)。首先我們要有資料,假設這個資料就是著名的波士頓房價資料(這裡就不介紹這個公共資料集了,你可以百度或Google獲得詳細的解釋或在R,Python中看到這個公共資料集的說明),我要建構幾個價格預測模型,供客戶選擇,這裡我就建構6種回歸模型(假設使我們精挑細選訓練的):簡單的線性回歸,嶺回歸,Lasso回歸,K近鄰回歸,回歸樹,支援向量機回歸,加載資料和訓練的代碼如下:

# step0:加載必要的模型

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import sklearn.datasets as datasets

from sklearn.linear_model import LinearRegression

from sklearn.linear_model import Ridge

from sklearn.linear_model import Lasso

from sklearn.neighbors import KNeighborsRegressor

from sklearn.tree import DecisionTreeRegressor

from sklearn.svm import SVR

from sklearn.model_selection import train_test_split

from sklearn.externals import joblib

# step1:資料擷取

boston = datasets.load_boston()

train = boston.data

target = boston.target

X_train,x_test,y_train,y_true = train_test_split(train,target,test_size=0.2)

print(X_train,x_test)

print(y_train,y_true)

# step2: 初始化model

LR = LinearRegression()

RR= Ridge()

LLR = Lasso()

KNNR = KNeighborsRegressor()

DR = DecisionTreeRegressor()

SVMR = SVR()

# step3: Train and save and predict Model

# 這裡省略了模型訓練中評價和調參的過程

# 假設這就是美國房産中介Leader要我訓練的

# 最終Model

LR.fit(X_train,y_train)

RR.fit(X_train,y_train)

LLR.fit(X_train,y_train)

KNNR.fit(X_train,y_train)

DR.fit(X_train,y_train)

SVMR.fit(X_train,y_train)

# 模型儲存與持久化

joblib.dump(LR, "LR_model.m")

joblib.dump(RR, "RR_model.m")

joblib.dump(LLR, "LLR_model.m")

joblib.dump(KNNR, "KNNR_model.m")

joblib.dump(DR, "DR_model.m")

joblib.dump(SVMR, "SVMR_model.m")

# 模型加載

lr_m = joblib.load("LR_model.m")

rr_m = joblib.load("RR_model.m")

llr_m = joblib.load("LLR_model.m")

knnr_m = joblib.load("KNNR_model.m")

dr_m = joblib.load("DR_model.m")

svmr_m = joblib.load("SVMR_model.m")

y_LR = lr_m.predict(x_test)

y_RR = rr_m.predict(x_test)

y_LLR = llr_m.predict(x_test)

y_KNNR = knnr_m.predict(x_test)

y_DR = dr_m.predict(x_test)

y_SVMR = svmr_m.predict(x_test)

model_pre = pd.DataFrame({'LinearRegression()':list(y_LR),'Ridge()':list(y_RR),'Lasso()':list(y_LLR),

   'KNeighborsRegressor()':list(y_KNNR),'DecisionTreeRegressor()':list(y_DR),

   'SVR()':list(y_SVMR)})

# Plot

def model_plot(y_true,model_pre):

   '''

   y_true:真實的label

   model_pre: 預測的資料(資料框)

   cols = model_pre.columns

   plt.style.use("ggplot")

   plt.figure(figsize=(24,24))

   for i in range(6):

       plt.subplot(2,3,i+1)

       plt.scatter(x=range(len(y_true)),y=y_true,label='true')

       plt.scatter(x=range(len(model_pre[cols[i]])),y=model_pre[cols[i]],label=cols[i])

       plt.legend()

   plt.savefig("model_plot.png")

model_plot(y_true, model_pre)

訓練好的模型要交給每一個業務人員使用,但是我們不能每一個員工都要安裝一個Python然後,叫他們怎樣處理資料跑代碼吧?(這顯然是不行的)可以把儲存好的Model做成一個帶GUI的小工具,下面我們就把訓練好的模型和Pyqt結合,做成一個帶有UI界面的exe可執行檔案,這樣使用者就可以僅安裝拷貝這個exe檔案就可以使用模型做分析預測。

3.GUI封裝Model

使用Pyqt5這個架構,把機器學習模型和GUI結合在一起,我直接上代碼和最終呈現方式了,詳細的源碼可以去我下文提到的位址中下載下傳,下面看一下涉及整個項目的檔案:

【好文】帶你用Python開發個機器學習軟體!

渲染UI的代碼

class Ui_MainWindow(object):

   def setupUi(self, MainWindow):

       MainWindow.setObjectName("MainWindow")

       MainWindow.resize(930, 600)

       icon = QtGui.QIcon()

       icon.addPixmap(QtGui.QPixmap(":/my_pic/pic/kh.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       MainWindow.setWindowIcon(icon)

       self.centralWidget = QtWidgets.QWidget(MainWindow)

       self.centralWidget.setObjectName("centralWidget")

       self.groupBox = QtWidgets.QGroupBox(self.centralWidget)

       self.groupBox.setGeometry(QtCore.QRect(20, 20, 191, 161))

       self.groupBox.setObjectName("groupBox")

       self.pushButton = QtWidgets.QPushButton(self.groupBox)

       self.pushButton.setGeometry(QtCore.QRect(20, 30, 151, 31))

       self.pushButton.setFixedHeight(40)  

       self.pushButton.setStyleSheet("QPushButton{color:white;background:LimeGreen;border-radius:20px;border:3px solid black;}")

       icon1 = QtGui.QIcon()

       icon1.addPixmap(QtGui.QPixmap(":/my_pic/pic/lajiao.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.pushButton.setIcon(icon1)

       self.pushButton.setObjectName("pushButton")

       self.pushButton_2 = QtWidgets.QPushButton(self.groupBox)

       self.pushButton_2.setGeometry(QtCore.QRect(20, 90, 151, 31))

       self.pushButton_2.setFixedHeight(40)  

       self.pushButton_2.setStyleSheet("QPushButton{color:white;background:Orange;border-radius:20px;border:3px solid black;}") 

       icon2 = QtGui.QIcon()

       icon2.addPixmap(QtGui.QPixmap(":/my_pic/pic/xiaoche.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.pushButton_2.setIcon(icon2)

       self.pushButton_2.setObjectName("pushButton_2")

       self.groupBox_2 = QtWidgets.QGroupBox(self.centralWidget)

       self.groupBox_2.setGeometry(QtCore.QRect(240, 20, 671, 521))

       self.groupBox_2.setObjectName("groupBox_2")

       self.graphicsView = QtWidgets.QGraphicsView(self.groupBox_2)

       self.graphicsView.setGeometry(QtCore.QRect(10, 20, 651, 491))

       self.graphicsView.setStyleSheet("background-image: url(:/my_pic/pic/face.png);")

       self.graphicsView.setObjectName("graphicsView")

       self.line = QtWidgets.QFrame(self.centralWidget)

       self.line.setGeometry(QtCore.QRect(213, 20, 20, 521))

       self.line.setFrameShape(QtWidgets.QFrame.VLine)

       self.line.setFrameShadow(QtWidgets.QFrame.Sunken)

       self.line.setObjectName("line")

       self.calendarWidget = QtWidgets.QCalendarWidget(self.centralWidget)

       self.calendarWidget.setGeometry(QtCore.QRect(20, 260, 201, 271))

       self.calendarWidget.setObjectName("calendarWidget")

       MainWindow.setCentralWidget(self.centralWidget)

       self.menuBar = QtWidgets.QMenuBar(MainWindow)

       self.menuBar.setGeometry(QtCore.QRect(0, 0, 930, 23))

       self.menuBar.setObjectName("menuBar")

       self.menu = QtWidgets.QMenu(self.menuBar)

       self.menu.setObjectName("menu")

       self.menu_2 = QtWidgets.QMenu(self.menuBar)

       self.menu_2.setObjectName("menu_2")

       self.menu_3 = QtWidgets.QMenu(self.menuBar)

       self.menu_3.setObjectName("menu_3")

       MainWindow.setMenuBar(self.menuBar)

       self.action = QtWidgets.QAction(MainWindow)

       icon3 = QtGui.QIcon()

       icon3.addPixmap(QtGui.QPixmap(":/my_pic/pic/open.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.action.setIcon(icon3)

       self.action.setObjectName("action")

       self.action_2 = QtWidgets.QAction(MainWindow)

       icon4 = QtGui.QIcon()

       icon4.addPixmap(QtGui.QPixmap(":/my_pic/pic/close.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.action_2.setIcon(icon4)

       self.action_2.setObjectName("action_2")

       self.action_3 = QtWidgets.QAction(MainWindow)

       icon5 = QtGui.QIcon()

       icon5.addPixmap(QtGui.QPixmap(":/my_pic/pic/connect.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.action_3.setIcon(icon5)

       self.action_3.setObjectName("action_3")

       self.action_4 = QtWidgets.QAction(MainWindow)

       icon6 = QtGui.QIcon()

       icon6.addPixmap(QtGui.QPixmap(":/my_pic/pic/aboutme.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.action_4.setIcon(icon6)

       self.action_4.setObjectName("action_4")

       self.action_QT = QtWidgets.QAction(MainWindow)

       icon7 = QtGui.QIcon()

       icon7.addPixmap(QtGui.QPixmap(":/my_pic/pic/aboutqt.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)

       self.action_QT.setIcon(icon7)

       self.action_QT.setObjectName("action_QT")

       self.menu.addAction(self.action)

       self.menu.addAction(self.action_2)

       self.menu_2.addAction(self.action_3)

       self.menu_3.addAction(self.action_4)

       self.menu_3.addAction(self.action_QT)

       self.menuBar.addAction(self.menu.menuAction())

       self.menuBar.addAction(self.menu_2.menuAction())

       self.menuBar.addAction(self.menu_3.menuAction())

       self.retranslateUi(MainWindow)

       QtCore.QMetaObject.connectSlotsByName(MainWindow)

   def retranslateUi(self, MainWindow):

       _translate = QtCore.QCoreApplication.translate

       MainWindow.setWindowTitle(_translate("MainWindow", "波士頓房價預測系統 v1.0"))

       self.groupBox.setTitle(_translate("MainWindow", "房價預測操作"))

       self.pushButton.setText(_translate("MainWindow", "加載資料源"))

       self.pushButton_2.setText(_translate("MainWindow", "模型預測房價"))

       self.groupBox_2.setTitle(_translate("MainWindow", "模型預測結果"))

       self.menu.setTitle(_translate("MainWindow", "檔案"))

       self.menu_2.setTitle(_translate("MainWindow", "幫助"))

       self.menu_3.setTitle(_translate("MainWindow", "關于"))

       self.action.setText(_translate("MainWindow", "打開"))

       self.action_2.setText(_translate("MainWindow", "關閉"))

       self.action_3.setText(_translate("MainWindow", "聯系我們"))

       self.action_4.setText(_translate("MainWindow", "關于我們"))

       self.action_QT.setText(_translate("MainWindow", "關于QT"))

import my_pic_rc

if __name__ == "__main__":

   import sys

   app = QtWidgets.QApplication(sys.argv)

   MainWindow = QtWidgets.QMainWindow()

   ui = Ui_MainWindow()

   ui.setupUi(MainWindow)

   MainWindow.show()

   sys.exit(app.exec_())

信号與槽函數代碼

class MainWindow(QMainWindow, Ui_MainWindow):

   """

   Class documentation goes here.

   def __init__(self, parent=None):

       """

       Constructor

       @param parent reference to the parent widget

       @type QWidget

       super(MainWindow, self).__init__(parent)

       self.setupUi(self)

   def model_plot(self, y_true,model_pre):

       '''

       y_true:真實的label

       model_pre: 預測的資料(資料框)

       cols = model_pre.columns

       plt.style.use("ggplot")

       plt.figure(figsize=(24,24))

       plt.rcParams['font.sans-serif'] = ['FangSong'] 

       plt.rcParams['axes.unicode_minus'] = False 

       for i in range(6):

           plt.subplot(2,3,i+1)

           plt.scatter(x=range(len(y_true)),y=y_true,label='true')

           plt.scatter(x=range(len(model_pre[cols[i]])),y=model_pre[cols[i]],label=cols[i])

           plt.title(str(cols[i])+':真實Label Vs 預測Label')

           plt.legend()

       plt.savefig("model_plot.png")

   @pyqtSlot()

   def on_pushButton_clicked(self):

       Slot documentation goes here.

       # TODO: not implemented yet

       print("加載資料")

       boston = datasets.load_boston()

       train = boston.data

       target = boston.target

       self.X_train,self.x_test,self.y_train,self.y_true = train_test_split(train,target,test_size=0.2)

   def on_pushButton_2_clicked(self):

       print("模型預測")

       # 模型加載

       lr_m = joblib.load("model/LR_model.m")

       rr_m = joblib.load("model/RR_model.m")

       llr_m = joblib.load("model/LLR_model.m")

       knnr_m = joblib.load("model/KNNR_model.m")

       dr_m = joblib.load("model/DR_model.m")

       svmr_m = joblib.load("model/SVMR_model.m")

       try:

           y_LR = lr_m.predict(self.x_test)

           y_RR = rr_m.predict(self.x_test)

           y_LLR = llr_m.predict(self.x_test)

           y_KNNR = knnr_m.predict(self.x_test)

           y_DR = dr_m.predict(self.x_test)

           y_SVMR = svmr_m.predict(self.x_test)

           model_pre = pd.DataFrame({'LinearRegression()':list(y_LR),'Ridge()':list(y_RR),'Lasso()':list(y_LLR), \

           'KNeighborsRegressor()':list(y_KNNR),'DecisionTreeRegressor()':list(y_DR),'SVR()':list(y_SVMR)})

           self.model_plot(self.y_true, model_pre)

           self.graphicsView.setStyleSheet("border-image: url(model_plot.png);")

       except:

           my_button_w3=QMessageBox.warning(self,"嚴重警告", '請務必先加載資料然後再點選模型預測!!!', QMessageBox.Ok|QMessageBox.Cancel,  QMessageBox.Ok)  

   def on_action_triggered(self):

       print('打開')

       my_button_open = QMessageBox.about(self, '打開', '點選我打開某些檔案')

   def on_action_2_triggered(self):

       print('關閉')

       sys.exit(0)

   def on_action_3_triggered(self):

       print('聯系我們')

       my_button_con_me = QMessageBox.about(self, '聯系我們', '這個位置放的是聯系我們的介紹')

   def on_action_4_triggered(self):

       print('關于我們')

       my_button_about_me = QMessageBox.about(self, '關于我們', '這個位置放的是關于我們的介紹')

   def on_action_QT_triggered(self):

       print('關于qt')

       my_button_about_QT = QMessageBox.aboutQt(self, '關于QT')

   splash = QSplashScreen(QtGui.QPixmap(':/my_pic/pic/face.png'))

   splash.show()

   QThread.sleep(0.5)

   splash.showMessage('正在加載機器學習算法...' )

   QThread.sleep(1)

   splash.showMessage('正在初始化程式...')

   #splash.show()

   app. processEvents()

   ui =MainWindow()

   # ui.setDaemon(True`)

   # ui.start()

   ui.show()

   splash.finish(ui)

4.打包成exe可執行檔案

上面代碼運作通過後,我們的基本任務也已經完成,但是業務人員仍然不可用,我們可以生成exe可執行檔案,把運作環境所需要的dll檔案打包,這樣業務人員就可以脫離Python環境進行算法的使用了。 Python中打包成exe可執行檔案的方式有很多種,這裡我們使用pyinstaller來打包成可執行檔案,打包指令為:

pyinstaller my_main_ui.spec

OK,我們打包成功後就可以運作了,運作結果如下,我們生成的工具放在了百度雲盤,可以在百度雲盤下載下傳測試(注意:生成的版本是win7 64位的版本

【好文】帶你用Python開發個機器學習軟體!
5.小結

這樣我們就完成我們的小項目了,當然Pyqt的很多控件和方法我們都沒有講到,如果你對Python GUI的開發感興趣,可以詳細的玩一下Pyqt的其他控件,做出一個高大上的應用。本文中提到的所有源碼我都放在了我的Github上,歡迎小夥伴們fork和留下star,同時也歡迎小夥伴留下自己的issues,源碼托管位址:https://github.com/DataXujing/boston_model

原文釋出時間為:2018-09-5

本文作者:徐靜

本文來自雲栖社群合作夥伴“

Python愛好者社群

”,了解相關資訊可以關注“

”。