【问题标题】:How to load a heavy model when using MVC design pattern使用 MVC 设计模式时如何加载重模型
【发布时间】:2019-06-10 20:04:25
【问题描述】:

我正在使用 wxPython 和我使用 Tensorflow 创建的深度学习模型构建一个应用程序。我使用的设计模式是 MVC。 我的问题是深度学习模型非常重,加载需要很长时间(大约 2 分钟),同时 GUI 挂起。 我创建了一个描述该过程的示例代码。 这是加载时 GUI 的样子:

enter image description here

这是加载后 GUI 的样子: enter image description here

问题是如何让应用程序在模型加载时运行? 我还想在 GUI 中添加一条状态行,指示模型正在加载或已经加载。

我正在添加显示我的应用程序是如何构建的示例代码。

import wx
import time

class Model:
    def __init__(self):
        '''This part is simulating the loading of tensorflow'''
        x = 0
        while x < 15:
            time.sleep(1)
            print(x)
            x += 1

class View(wx.Frame):
    def __init__(self, parent, title):
        super(View, self).__init__(parent, title=title, size=(400, 400))
        self.InitUI()

    def InitUI(self):
        # Defines the GUI controls
        masterPanel = wx.Panel(self)
        masterPanel.SetBackgroundColour("gold")
        self.vbox = wx.BoxSizer(wx.VERTICAL)
        self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
        id = wx.StaticText(self, label="ID:")
        firstName = wx.StaticText(self, label="First name:")
        lastName = wx.StaticText(self, label="Last name:")
        self.idTc = wx.TextCtrl(self)
        self.firstNameTc = wx.TextCtrl(self)
        self.lastNameTc = wx.TextCtrl(self)
        self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                         firstName, (self.firstNameTc, 1, wx.EXPAND),
                         lastName, (self.lastNameTc, 1, wx.EXPAND)])
        self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
   border=15)
        self.SetSizer(self.vbox)
        self.vbox.Fit(self)
        self.Layout()

class Controller:
    def __init__(self):
        self.view = View(None, title='Test')
        self.view.Show()
        self.model = Model()

def main():
    app = wx.App()
    controller = Controller()
    app.MainLoop()

if __name__ == '__main__':
    main()

【问题讨论】:

    标签: python model-view-controller deep-learning wxpython


    【解决方案1】:

    您可以使用_thread 模块和PyPubSub 模块在模型加载期间保持主线程完全正常工作。

    但是,请记住,如果您在 GUI 中有 wx.Button 绑定到 method A 并且 method A 需要加载完整模型才能正常运行,那么用户将能够单击 wx.Button并且程序可能会挂起,因为模型仍未完全加载。如果是这种情况,您可以使用方法Disable()(加载模型时)和Enable()(加载模型后)来防止这种情况。

    带有 cmets 的代码 (####)。

    import wx
    import time
    import _thread
    from pubsub import pub
    
    class Model:
        def __init__(self):
            '''This part is simulating the loading of tensorflow'''
            x = 0
            while x < 15:
                time.sleep(1)
                print(x)
                #### This is how you broadcast the 'Loading' message 
                #### from a different thread.
                wx.CallAfter(pub.sendMessage, 'Loading', x=x)
                x += 1
            #### The same as before
            wx.CallAfter(pub.sendMessage, 'Loading', x=x)
    
    class View(wx.Frame):
        def __init__(self, parent, title):
            super(View, self).__init__(parent, title=title, size=(400, 400))
            self.InitUI()
    
        def InitUI(self):
            # Defines the GUI controls
            #### It needed to set the size of the panel to cover the frame
            #### because it was not covering the entire frame before
            masterPanel = wx.Panel(self, size=(400, 400))
            masterPanel.SetBackgroundColour("gold")
            self.vbox = wx.BoxSizer(wx.VERTICAL)
            self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
            id = wx.StaticText(self, label="ID:")
            firstName = wx.StaticText(self, label="First name:")
            lastName = wx.StaticText(self, label="Last name:")
            self.idTc = wx.TextCtrl(self)
            self.firstNameTc = wx.TextCtrl(self)
            self.lastNameTc = wx.TextCtrl(self)
            self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                             firstName, (self.firstNameTc, 1, wx.EXPAND),
                             lastName, (self.lastNameTc, 1, wx.EXPAND)])
            self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
       border=15)
            self.SetSizer(self.vbox)
            self.vbox.Fit(self)
            self.Layout()
    
            #### Create status bar to show loading progress. 
            self.statusbar = self.CreateStatusBar(1)
            self.statusbar.SetStatusText('Loading model')
            #### Set the size of the window because the status bar steals space
            #### in the height direction.
            self.SetSize(wx.DefaultCoord, 160)
            #### Subscribe the class to the message 'Loading'. This means that every
            #### time the meassage 'Loading' is broadcast the method 
            #### ShowLoadProgress will be executed.
            pub.subscribe(self.ShowLoadProgress, 'Loading')
            #### Start the thread that will load the model
            _thread.start_new_thread(self.LoadModel, ('test',))
    
        def LoadModel(self, test):
            """
            Load the Model
            """
            #### Change depending on how exactly are you going to create/load the 
            #### model
            self.model = Model()
    
        def ShowLoadProgress(self, x):
            """
            Show the loading progress 
            """
            if x < 15:
                self.statusbar.SetStatusText('Loading progress: ' + str(x))
            else:
                self.statusbar.SetStatusText('All loaded')
    
    class Controller:
        def __init__(self):
            self.view = View(None, title='Test')
            self.view.Show()
            #### The line below is not needed now because the model is 
            #### loaded now from the thread started in View.InitUI
            #self.model = Model()
    
    def main():
        app = wx.App()
        controller = Controller()
        app.MainLoop()
    
    if __name__ == '__main__':
        main()
    

    如果您从 class View 中的方法加载模型,那么您将不需要 PyPubSub 模块,因为您只需调用 wx.CallAfter(self.ShowLoadProgress, x)

    【讨论】:

    • 我更喜欢这个答案,而不是我最初的、简单化的答案。
    【解决方案2】:

    只是为了好玩,因为我更喜欢 kbr85 给我的简单的第一个答案的答案,这是一个线程变体,在 statusbar 中带有一个 gauge 和一个忙碌光标,尽管我的屏幕截图程序没有选择它.
    有一个 Stop 按钮,一旦加载完成,statusbar 就会被删除。
    我没有使用pubsub,而是使用wxpython event 进行通信。

    import wx
    import time
    from threading import Thread
    import wx.lib.newevent
    progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
    load_status=["Model Loading","Model Loaded","Model Cancelled"]
    
    class Model(Thread):
        def __init__(self,parent):
            Thread.__init__(self)
            '''This thread simulates the loading of tensorflow'''
            self.stopthread = 0
            self.target = parent
            self.start()
    
        def run(self):
            while not self.stopthread:
                for i in range(20):
                    if self.stopthread:
                        break
                    time.sleep(0.5)
                    evt = progress_event(count=i, status=self.stopthread)
                    wx.PostEvent(self.target, evt)
                if self.stopthread == 0:
                    self.stopthread = 1
            evt = progress_event(count=i, status=self.stopthread)
            wx.PostEvent(self.target, evt)
    
        def terminate(self):
            self.stopthread = 2
    
    class View(wx.Frame):
        def __init__(self, parent, title):
            super(View, self).__init__(parent, title=title, size=(400, 400))
            self.InitUI()
    
        def InitUI(self):
            self.vbox = wx.BoxSizer(wx.VERTICAL)
            self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
            id = wx.StaticText(self, label="ID:")
            firstName = wx.StaticText(self, label="First name:")
            lastName = wx.StaticText(self, label="Last name:")
            self.idTc = wx.TextCtrl(self)
            self.firstNameTc = wx.TextCtrl(self)
            self.lastNameTc = wx.TextCtrl(self)
            self.stop = wx.Button(self, -1, "Stop Load")
    
            self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                             firstName, (self.firstNameTc, 1, wx.EXPAND),
                             lastName, (self.lastNameTc, 1, wx.EXPAND),
                             (self.stop,1,wx.EXPAND)])
    
            self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,border=15)
            #Bind to the progress event issued by the thread
            self.Bind(EVT_PROGRESS_EVENT, self.OnProgress)
            #Bind to Stop button
            self.Bind(wx.EVT_BUTTON, self.OnStop)
            #Bind to Exit on frame close
            self.Bind(wx.EVT_CLOSE, self.OnExit)
            self.SetSizer(self.vbox)
            self.Layout()
    
            self.statusbar = self.CreateStatusBar(2)
            self.text = wx.StaticText(self.statusbar,-1,("No Model loaded"))
            self.progress = wx.Gauge(self.statusbar, range=20)
            sizer = wx.BoxSizer(wx.HORIZONTAL)
            sizer.Add(self.text, 0, wx.ALIGN_TOP|wx.ALL, 5)
            sizer.Add(self.progress, 1, wx.ALIGN_TOP|wx.ALL, 5)
            self.statusbar.SetSizer(sizer)
            wx.BeginBusyCursor()
            self.loadthread = Model(self)
    
    
        def OnProgress(self, event):
            self.text.SetLabel(load_status[event.status])
            #self.progress.SetValue(event.count)
            #or for indeterminate progress
            self.progress.Pulse()
            if event.status != 0:
                self.statusbar.Hide()
                wx.EndBusyCursor()
                self.Layout()
    
        def OnStop(self, event):
            if self.loadthread.isAlive():
                self.loadthread.terminate() # Shutdown the thread
                self.loadthread.join() # Wait for it to finish
    
        def OnExit(self, event):
            if self.loadthread.isAlive():
                self.loadthread.terminate() # Shutdown the thread
                self.loadthread.join() # Wait for it to finish
            self.Destroy()
    
    class Controller:
        def __init__(self):
            self.view = View(None, title='Test')
            self.view.Show()
    
    def main():
        app = wx.App()
        controller = Controller()
        app.MainLoop()
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 现在是我的答案,看起来很简单:)。我完全同意使用 wx.lib.newevent 比使用 pubsub 更好。我只是不知道如何正确使用它,现在我知道了,谢谢。我刚刚将 line sizer.Fit(self.statusbar) 添加到代码中,因为没有它,在 macOS 上运行时小部件无法正确显示。
    【解决方案3】:

    您应该在self.view.Show() 命令之后调用wx.GetApp().Yield(),这会将控制权暂时释放给MainLoop
    如果模型加载代码以增量方式执行,您也可以在加载期间定期调用 Yield。
    下面是一个简单的方法来通知用户正在发生的事情。如果您想要取消模型加载的选项,则必须将其包装在一个对话框中,假设它是增量加载的。

    import wx
    import time
    
    class Model:
        def __init__(self):
            '''This part is simulating the loading of tensorflow'''
            x = 0
            #If the model load is perform in increments you could call wx.Yield
            # between the increments.
            while x < 15:
                time.sleep(1)
                wx.GetApp().Yield()
                print(x)
                x += 1
    
    class View(wx.Frame):
        def __init__(self, parent, title):
            super(View, self).__init__(parent, title=title, size=(400, 400))
            self.InitUI()
    
        def InitUI(self):
            # Defines the GUI controls
            #masterPanel = wx.Panel(self)
            #masterPanel.SetBackgroundColour("gold")
            self.vbox = wx.BoxSizer(wx.VERTICAL)
            self.fgs = wx.FlexGridSizer(6, 2, 10, 25)
            id = wx.StaticText(self, label="ID:")
            firstName = wx.StaticText(self, label="First name:")
            lastName = wx.StaticText(self, label="Last name:")
            self.idTc = wx.TextCtrl(self)
            self.firstNameTc = wx.TextCtrl(self)
            self.lastNameTc = wx.TextCtrl(self)
            self.fgs.AddMany([id, (self.idTc, 1, wx.EXPAND),
                             firstName, (self.firstNameTc, 1, wx.EXPAND),
                             lastName, (self.lastNameTc, 1, wx.EXPAND)])
            self.vbox.Add(self.fgs, proportion=1, flag=wx.ALL | wx.EXPAND,
       border=15)
            self.CreateStatusBar() # A Statusbar in the bottom of the window
            self.StatusBar.SetStatusText("No model loaded", 0)
            self.SetSizer(self.vbox)
            self.vbox.Fit(self)
            self.Layout()
    
    class Controller:
        def __init__(self):
            self.view = View(None, title='Test')
            self.view.Show()
            self.view.SetStatusText("Model loading", 0)
            wait = wx.BusyInfo("Please wait, loading model...")
            #Optionally add parent to centre message on self.view
            #wait = wx.BusyInfo("Please wait, loading model...", parent=self.view)
            wx.GetApp().Yield()
            self.model = Model()
            self.view.SetStatusText("Model loaded", 0)
            del wait
    
    def main():
        app = wx.App()
        controller = Controller()
        app.MainLoop()
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      • 1970-01-01
      • 2016-10-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多