2

今天我遇到了repaint()QT 库中的函数问题。长话短说,我有一个插槽,可以使用 BP 算法训练我的神经网络。我已经在控制台中测试了整个算法,然后想把它移到 GUI 应用程序中。除了令人耳目一新,一切都很好。神经网络的训练是一个包含大量计算的过程,这些计算是在bp_alg函数(训练)和licz_mse函数(计算当前错误)中进行的。变量ilosc_epok可设置为 1e10。因此整个过程可能会持续几个小时。这就是为什么我想在每 100000 个 epochs(最后一个ifcontition)之后显示一个当前进度。wyniki是用于显示进度的 QTextEdit 类的对象。很遗憾,repaint()没有按预期工作。一开始它会在 GUI 中刷新wyniki,但经过一段时间后它会停止工作。外部循环完成后,它会再次刷新以显示所有更改。

我试图改变刷新频率,但迟早它总是会停止(除非整个训练过程因为满足休息条件而足够早地停止)。由于计算量过多,应用程序似乎在某个时刻决定停止刷新。海事组织它不应该发生。我在旧问题中寻找解决方案,并在我使用qApp->processEvents(QEventLoop::ExcludeUserInputEvents);而不是wyniki->repaint();. 但是,我仍然很好奇为什么repaint()停止工作。

下面我将部分代码粘贴到有问题的部分。如果有帮助,我正在使用 QT Creator 2.4.1 和 QT Libraries 4.8.1。

unsigned long int ile_epok;
double mse_w_epoce;
for (ile_epok=0; ile_epok<ilosc_epok; ile_epok++) {   //external loop of training
    mse_w_epoce = 0;
    for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) {   //internal loop of training
        alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
        mse_w_epoce += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
    }
    //checking break condition
    if (mse_w_epoce < warunek_stopu) {
        wyniki->append("Zakończono uczenie po " + QString::number(ile_epok) + " epokach, osiągając MSE: " + QString::number(mse_w_epoce));
        break;
    }
    //problematic part
    if ((ile_epok+1)%(100000) == 0) {
        wyniki->append("Uczenie w toku, po " + QString::number(ile_epok+1) + " epokach MSE wynosi: " + QString::number(mse_w_epoce));
        wyniki->repaint();
    }
}
4

1 回答 1

0

您正在阻止您的 GUI 线程,因此重绘将不起作用,这显然是糟糕的设计。你永远不应该阻止 GUI 线程。

如果你坚持在 GUI 线程中做工作,你必须强行将工作切成小块,并在每个块之后返回主事件循环。嵌套事件循环是邪恶的,所以甚至不要认为你想要一个。所有这些都带有不好的代码气味,所以请远离。

或者,只需将您的计算QObject移至工作线程并在那里完成工作。

下面的代码演示了这两种技术。很容易注意到,工作分割需要在工作对象内部保持循环状态,而不仅仅是在循环中本地。它更混乱,代码闻起来很糟糕,再次 - 避免它。

该代码适用于 Qt 4.8 和 5.1。

截屏

//main.cpp
#include <QApplication>
#include <QThread>
#include <QWidget>
#include <QBasicTimer>
#include <QElapsedTimer>
#include <QGridLayout>
#include <QPlainTextEdit>
#include <QPushButton>

class Helper : private QThread {
public:
    using QThread::usleep;
};

class Trainer : public QObject {
    Q_OBJECT
    Q_PROPERTY(float stopMSE READ stopMSE WRITE setStopMSE)
    float m_stopMSE;
    int m_epochCounter;
    QBasicTimer m_timer;
    void timerEvent(QTimerEvent * ev);
public:
    Trainer(QObject *parent = 0) : QObject(parent), m_stopMSE(1.0) {}
    Q_SLOT void startTraining() {
        m_epochCounter = 0;
        m_timer.start(0, this);
    }
    Q_SLOT void moveToGUIThread() { moveToThread(qApp->thread()); }
    Q_SIGNAL void hasNews(const QString &);
    float stopMSE() const { return m_stopMSE; }
    void setStopMSE(float m) { m_stopMSE = m; }
};

void Trainer::timerEvent(QTimerEvent * ev)
{
    const int updateTime = 50; //ms
    const int maxEpochs = 5000000;
    if (ev->timerId() != m_timer.timerId()) return;

    QElapsedTimer t;
    t.start();
    while (1) {
        // do the work here
        float currentMSE;
#if 0
        for (int i=0; i<zbior_uczacy_rozmiary[0]; i++) {   //internal loop of training
            alg_bp(zbior_uczacy[i], &zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
            currentMSE += licz_mse(&zbior_uczacy[i][zbior_uczacy_rozmiary[1]]);
        }
#else
        Helper::usleep(100); // pretend we're busy doing some work
        currentMSE = 2E4/m_epochCounter;
#endif
        // bail out if we're done
        if (currentMSE <= m_stopMSE || m_epochCounter >= maxEpochs) {
            QString s = QString::fromUtf8("Zakończono uczenie po %1 epokach, osiągając MSE: %2")
                    .arg(m_epochCounter).arg(currentMSE);
            emit hasNews(s);
            m_timer.stop();
            break;
        }
        // send out periodic updates
        // Note: QElapsedTimer::elapsed() may be expensive, so we don't call it all the time
        if ((m_epochCounter % 128) == 1 && t.elapsed() > updateTime) {
            QString s = QString::fromUtf8("Uczenie w toku, po %1  epokach MSE wynosi: %2")
                        .arg(m_epochCounter).arg(currentMSE);
            emit hasNews(s);
            // return to the event loop if we're in the GUI thread
            if (QThread::currentThread() == qApp->thread()) break; else t.restart();
        }
        m_epochCounter++;
    }
}

class Window : public QWidget {
    Q_OBJECT
    QPlainTextEdit *m_log;
    QThread *m_worker;
    Trainer *m_trainer;
    Q_SIGNAL void startTraining();
    Q_SLOT void showNews(const QString & s) { m_log->appendPlainText(s); }
    Q_SLOT void on_startGUI_clicked() {
        QMetaObject::invokeMethod(m_trainer, "moveToGUIThread");
        emit startTraining();
    }
    Q_SLOT void on_startWorker_clicked() {
        m_trainer->moveToThread(m_worker);
        emit startTraining();
    }
public:
    Window(QWidget *parent = 0, Qt::WindowFlags f = 0) :
        QWidget(parent, f), m_log(new QPlainTextEdit), m_worker(new QThread(this)), m_trainer(new Trainer)
    {
        QGridLayout * l = new QGridLayout(this);
        QPushButton * btn;
        btn = new QPushButton("Start in GUI Thread");
        btn->setObjectName("startGUI");
        l->addWidget(btn, 0, 0, 1, 1);
        btn = new QPushButton("Start in Worker Thread");
        btn->setObjectName("startWorker");
        l->addWidget(btn, 0, 1, 1, 1);
        l->addWidget(m_log, 1, 0, 1, 2);
        connect(m_trainer, SIGNAL(hasNews(QString)), SLOT(showNews(QString)));
        m_trainer->connect(this, SIGNAL(startTraining()), SLOT(startTraining()));
        m_worker->start();
        QMetaObject::connectSlotsByName(this);
    }
    ~Window() {
        m_worker->quit();
        m_worker->wait();
        delete m_trainer;
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Window w;
    w.show();
    return a.exec();
}

#include "main.moc"
于 2013-08-26T20:41:32.673 回答