sklearn+pyqt5實現kmeans算法的使用界面可視化

 

實習中同事分給我的一個需求:把sklearn中的kmeans算法封裝起來,使用界面可視化,提供給不會改代碼的領導使用

對pyqt5一竅不通的我經過幾天的摸索終於完成任務!參考的內容大多來自於這個帖子:

https://mp.weixin.qq.com/s/Wy1iTYoX7_O81ChMflXXfg  感謝!

首先展示一下封裝-可視化後的最終效果:

如上圖,我在使用界面暴露了聚類個數、最大迭代次數、初始化質心次數這幾個參數並設置了默認值,以供使用者自行修改

點擊【開始】後執行kmeans算法過程,執行成功之後將結果輸出爲聚類結果.csv和聚類中心.csv

在這個過程中,使用者不需要去代碼中修改數據集位置和kmeans算法運行的參數,只需要在可視化界面裏面點點點就可以了,下面貼出代碼。

首先是可視化頁面的代碼,首先是界面主體

#界面主體
self.resize(600, 400)
self.setWindowTitle('Kmeans聚類')

插入文件按鈕的寫法 

#選擇文件部分設置三個對象:標籤1、標籤1_、按鈕1
#標籤1顯示“文件位置4個字”
self.lb1 = QLabel("文件位置:", self)
self.lb1.move(20, 40)
#標籤1_用來儲存文件路徑並顯示
self.lb1_ = QLabel(self)
self.lb1_.setGeometry(110, 40, 300, 20)
#按鈕1用來連接打開文件函數、選擇文件位置
self.bt1 = QPushButton('選擇文件位置', self)
self.bt1.move(400, 35)
self.bt1.clicked.connect(self.openfile)#連接到打開文件函數

這個按鈕對應的打開文件函數

#解析上傳文件
def openfile(self):
    inputfile = QFileDialog.getOpenFileName(self, '打開文件','./')
    path=inputfile[0]
    self.lb1_.setText(path)
    #print(str(path))
    #return path

我不會截動態圖,效果不方便展示,讀者可自行復制上述代碼運行觀察效果。

其他的幾個修改參數按鈕的設計規則和上個按鈕一樣,都是兩個標籤、一個按鈕對應一個函數

整個界面主體的函數

    def initUI(self):

        # 界面主體
        self.resize(600, 400)
        self.setWindowTitle('Kmeans聚類')

        #選擇文件部分設置三個對象:標籤1、標籤1_、按鈕1
        self.lb1 = QLabel("文件位置:", self)
        self.lb1.move(20, 40)
        self.lb1_ = QLabel(self)
        self.lb1_.setGeometry(110, 40, 300, 20)
        self.bt1 = QPushButton('選擇文件位置', self)
        self.bt1.move(400, 35)
        self.bt1.clicked.connect(self.openfile)#連接到打開文件函數

        # 選擇聚類個數設置三個對象:標籤2、標籤2_、按鈕2
        self.lb2 = QLabel("聚類個數:", self)
        self.lb2.move(20, 80)
        self.lb2_ = QLabel("8",self)
        self.lb2_.setGeometry(110, 80, 180, 20)
        self.bt2 = QPushButton('選擇聚類個數', self)
        self.bt2.move(400, 75)
        self.bt2.clicked.connect(self.choice_clusters)  # 連接到選擇聚類中心函數

        # 選擇最大迭代次數,設置三個對象:標籤3、標籤3_、按鈕3
        self.lb3= QLabel("最大迭代次數:", self)
        self.lb3.move(20, 120)
        self.lb3_ = QLabel("300",self)
        self.lb3_.setGeometry(140, 120, 180, 20)
        self.bt3 = QPushButton('修改最大迭代次數', self)
        self.bt3.move(400, 115)
        self.bt3.clicked.connect(self.choice_max_iter)  # 連接到選擇最大迭代次數函數

        # 選擇初始化質心次數,設置三個對象:標籤4、標籤4_、按鈕4
        self.lb4 = QLabel("初始化質心次數:", self)
        self.lb4.move(20, 160)
        self.lb4_ = QLabel("10", self)
        self.lb4_.setGeometry(160, 160, 180, 20)
        self.bt4 = QPushButton('修改初始化質心次數', self)
        self.bt4.move(400, 155)
        self.bt4.clicked.connect(self.choice_n_init)  # 連接到選擇最大迭代次數函數


        #執行聚類過程
        self.bt5 = QPushButton('開始', self)
        self.bt5.move(200,300)
        self.bt5.clicked.connect(self.kmeans_building)

        self.show()

它們對應的操作函數,這幾個函數與上面幾個按鈕一一對應,注意觀察

#解析上傳文件
    def openfile(self):
        inputfile = QFileDialog.getOpenFileName(self, '打開文件','./')
        path=inputfile[0]
        self.lb1_.setText(path)
        #print(str(path))
        #return path


    #選擇聚類個數
    def choice_clusters(self):
        text, ok = QInputDialog.getInt(self, '聚類個數', '請輸入聚類個數:', min=1)
        if ok:
            types_num =int(text)
            self.lb2_.setText(str(types_num))
            #print(types_num)
        #return types_num

    #選擇最大迭代次數
    def choice_max_iter(self):
        text, ok = QInputDialog.getInt(self, '修改最大迭代次數', '請輸入最大迭代次數:', min=10)
        if ok:
            iter_num =int(text)
            self.lb3_.setText(str(iter_num))


    #選擇初始化質心次數
    def choice_n_init(self):
        text, ok = QInputDialog.getInt(self, '修改初始化質心次數', '請輸入初始化質心次數:', min=1)
        if ok:
            init_num =int(text)
            self.lb4_.setText(str(init_num))


    # kmeans使用函數
    def kmeans_building(self):
        #取標籤1_ 2_ 3_ 4_裏面的值
        inputfile=str(self.lb1_.text())
        types_num=int(self.lb2_.text())
        iter_num = int(self.lb3_.text())
        init_num=int(self.lb4_.text())
        #讀取數據
        data = pd.read_csv(inputfile, encoding='gb18030', error_bad_lines=True, engine='python')
        data2=data.copy()
        #數據空值填充
        data = self.fillNan(data)
        #數據標準化
        data = self.standardize(data)
        #sklearn中的kmeans算法執行
        kmodel = KMeans(n_clusters=types_num,max_iter=iter_num,n_init=init_num).fit(data)  # 訓練模型
        r1 = pd.Series(kmodel.labels_).value_counts()  # 統計各個類別的數目
        r2 = pd.DataFrame(kmodel.cluster_centers_)  # 找出聚類中心
        r3 = self.reStandardize(data2,r2)#將聚類中心還原爲標準化前的數值
        result_1 = pd.concat([r3, r1], axis=1)  # 橫向連接(0是縱向),得到聚類中心對應的類別下的數目
        result_1.columns = list(data.columns) + [u'聚類個數']  # 重命名錶頭
        # 把聚類中心文件輸出到原始文件的路徑下
        path = inputfile.split('/')[:-1]
        path.append('')
        path = '/'.join(path)
        result_1.to_csv(path + '聚類中心.csv', encoding='gb18030', index=True)
        # 把聚類結果文件輸出到原始文件的路徑下
        result_2 = pd.concat([data2, pd.Series(kmodel.labels_, index=data.index)], axis=1)  # 詳細輸出每個樣本對應的類別
        result_2.columns = list(data.columns) + [u'聚類類別']  # 重命名錶頭
        result_2.to_csv(path + '聚類結果.csv', encoding='gb18030', index=True)  # 保存分類結果
        #加入進度條
        progress = QProgressDialog(self)
        progress.setWindowTitle("請稍等")
        progress.setLabelText("正在操作...")
        progress.setCancelButtonText("取消")
        progress.setMinimumDuration(5)
        progress.setWindowModality(Qt.WindowModal)
        progress.setRange(0, len(result_2))
        for i in range(len(result_2)):
            progress.setValue(i)
            if progress.wasCanceled():
                QMessageBox.warning(self, "提示", "操作失敗")
                break
        else:
            progress.setValue(len(result_2))
            QMessageBox.information(self, "提示", "操作成功")

其中kmeans使用函數主要跟sklearn的使用有關,非常簡單,可以去找別的帖子看一下,在此不再詳細講。

最後附上完整代碼

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PyQt5.QtWidgets import QLabel, QWidget, QApplication, QPushButton, QFileDialog, QInputDialog, QProgressDialog,QMessageBox
from PyQt5.QtCore import Qt
import sys
import pandas as pd
from sklearn.cluster import KMeans




class Kmeans_(QWidget):
    def __init__(self):
        super().__init__()

        self.initUI()
    def initUI(self):

        # 界面主體
        self.resize(600, 400)
        self.setWindowTitle('Kmeans聚類')

        #選擇文件部分設置三個對象:標籤1、標籤1_、按鈕1
        self.lb1 = QLabel("文件位置:", self)
        self.lb1.move(20, 40)
        self.lb1_ = QLabel(self)
        self.lb1_.setGeometry(110, 40, 300, 20)
        self.bt1 = QPushButton('選擇文件位置', self)
        self.bt1.move(400, 35)
        self.bt1.clicked.connect(self.openfile)#連接到打開文件函數

        # 選擇聚類個數設置三個對象:標籤2、標籤2_、按鈕2
        self.lb2 = QLabel("聚類個數:", self)
        self.lb2.move(20, 80)
        self.lb2_ = QLabel("8",self)
        self.lb2_.setGeometry(110, 80, 180, 20)
        self.bt2 = QPushButton('選擇聚類個數', self)
        self.bt2.move(400, 75)
        self.bt2.clicked.connect(self.choice_clusters)  # 連接到選擇聚類中心函數

        # 選擇最大迭代次數,設置三個對象:標籤3、標籤3_、按鈕3
        self.lb3= QLabel("最大迭代次數:", self)
        self.lb3.move(20, 120)
        self.lb3_ = QLabel("300",self)
        self.lb3_.setGeometry(140, 120, 180, 20)
        self.bt3 = QPushButton('修改最大迭代次數', self)
        self.bt3.move(400, 115)
        self.bt3.clicked.connect(self.choice_max_iter)  # 連接到選擇最大迭代次數函數

        # 選擇初始化質心次數,設置三個對象:標籤4、標籤4_、按鈕4
        self.lb4 = QLabel("初始化質心次數:", self)
        self.lb4.move(20, 160)
        self.lb4_ = QLabel("10", self)
        self.lb4_.setGeometry(160, 160, 180, 20)
        self.bt4 = QPushButton('修改初始化質心次數', self)
        self.bt4.move(400, 155)
        self.bt4.clicked.connect(self.choice_n_init)  # 連接到選擇最大迭代次數函數


        #執行聚類過程
        self.bt5 = QPushButton('開始', self)
        self.bt5.move(200,300)
        self.bt5.clicked.connect(self.kmeans_building)

        self.show()


    # 標準化函數
    def standardize(self,df):
        for i in df.columns:
            df[i] = (df[i] - df[i].mean(axis=0)) / (df[i].std(axis=0))
        return df

    #反標準化函數
    def reStandardize(self,df1,df2):
        mean_value=[]
        std_value=[]
        for i in df1.columns:
            mean=df1[i].mean(axis=0)
            mean_value.append(mean)
            std=df1[i].std(axis=0)
            std_value.append(std)
        for indexs in df2.index:
            df2.loc[indexs]=df2.loc[indexs]*std_value+mean_value
        return df2

    # 填充空值函數
    def fillNan(self,dataframe):
        for i in dataframe.columns:
            dataframe[i] = dataframe[i].fillna(dataframe[i].mean())
        return dataframe

    #解析上傳文件
    def openfile(self):
        inputfile = QFileDialog.getOpenFileName(self, '打開文件','./')
        path=inputfile[0]
        self.lb1_.setText(path)
        #print(str(path))
        #return path


    #選擇聚類個數
    def choice_clusters(self):
        text, ok = QInputDialog.getInt(self, '聚類個數', '請輸入聚類個數:', min=1)
        if ok:
            types_num =int(text)
            self.lb2_.setText(str(types_num))
            #print(types_num)
        #return types_num

    #選擇最大迭代次數
    def choice_max_iter(self):
        text, ok = QInputDialog.getInt(self, '修改最大迭代次數', '請輸入最大迭代次數:', min=10)
        if ok:
            iter_num =int(text)
            self.lb3_.setText(str(iter_num))


    #選擇初始化質心次數
    def choice_n_init(self):
        text, ok = QInputDialog.getInt(self, '修改初始化質心次數', '請輸入初始化質心次數:', min=1)
        if ok:
            init_num =int(text)
            self.lb4_.setText(str(init_num))


    # kmeans使用函數
    def kmeans_building(self):
        #取標籤裏面的值
        inputfile=str(self.lb1_.text())
        types_num=int(self.lb2_.text())
        iter_num = int(self.lb3_.text())
        init_num=int(self.lb4_.text())
        #讀取數據
        data = pd.read_csv(inputfile, encoding='gb18030', error_bad_lines=True, engine='python')
        data2=data.copy()
        #數據空值填充
        data = self.fillNan(data)
        #數據標準化
        data = self.standardize(data)
        #sklearn中的kmeans算法執行
        kmodel = KMeans(n_clusters=types_num,max_iter=iter_num,n_init=init_num).fit(data)  # 訓練模型
        r1 = pd.Series(kmodel.labels_).value_counts()  # 統計各個類別的數目
        r2 = pd.DataFrame(kmodel.cluster_centers_)  # 找出聚類中心
        r3 = self.reStandardize(data2,r2)#將聚類中心還原爲標準化前的數值
        result_1 = pd.concat([r3, r1], axis=1)  # 橫向連接(0是縱向),得到聚類中心對應的類別下的數目
        result_1.columns = list(data.columns) + [u'聚類個數']  # 重命名錶頭
        # 把聚類中心文件輸出到原始文件的路徑下
        path = inputfile.split('/')[:-1]
        path.append('')
        path = '/'.join(path)
        result_1.to_csv(path + '聚類中心.csv', encoding='gb18030', index=True)
        # 把聚類結果文件輸出到原始文件的路徑下
        result_2 = pd.concat([data2, pd.Series(kmodel.labels_, index=data.index)], axis=1)  # 詳細輸出每個樣本對應的類別
        result_2.columns = list(data.columns) + [u'聚類類別']  # 重命名錶頭
        result_2.to_csv(path + '聚類結果.csv', encoding='gb18030', index=True)  # 保存分類結果
        #加入進度條
        progress = QProgressDialog(self)
        progress.setWindowTitle("請稍等")
        progress.setLabelText("正在操作...")
        progress.setCancelButtonText("取消")
        progress.setMinimumDuration(5)
        progress.setWindowModality(Qt.WindowModal)
        progress.setRange(0, len(result_2))
        for i in range(len(result_2)):
            progress.setValue(i)
            if progress.wasCanceled():
                QMessageBox.warning(self, "提示", "操作失敗")
                break
        else:
            progress.setValue(len(result_2))
            QMessageBox.information(self, "提示", "操作成功")





if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = Kmeans_()
    sys.exit(app.exec_())

這個帖子寫的比較匆忙,後續會不斷完善,有問題可以提出來互相探討。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章