我记得我在初识PyQt5时认为,一般都用Qtdesigner辅助UI软件的制作,可学到后面才发现原来真正的大佬写UI真的是用纯代码。我主要的自学资源是B站李宁老师的课,学了大概40来个视频后感觉应该可以先尝试做一个简单的GUI。
首先构思预期功能:
主要功能:自动读入数据,根据用户选择的算法(供选为:gray-model、kalman、bp-NN)对数据进行拟合。
1.UI运行后有一个登录界面,输入账号和密码,正确进入主界面,错误弹出提示框。
2.主界面左边为功能选择,包括输入数据文件路径、选择方法等,右侧为拟合图形(结果)。
所涉及到的类如下:
class BPnetwork() # 神经网络类,完成神经网络拟合
class GM_1_1 # 灰色系统类,完成灰色算法拟合
class KalmanFilter(object) # kalman类,完成kalman拟合
class mainWindow(QWidget) # 主界面窗口类
class warningWindow(QDialog) # 错误提示窗口类
class loginDialog(QDialog) # 登录界面窗口类
注:class BPnetwork()和class KalmanFilter(object)来源为github
前三个算法类不是今日重点,今天主要介绍后三个窗口类
1.登陆窗口
class loginDialog(QDialog):
def __init__(self):
super().__init__()
# 类初始化函数
self.initUI()
def initUI(self):
# 设置窗口名称
self.setWindowTitle("登录窗口")
# 创建并设置所需要的控件
namelabel = QLabel()
passwordlabel = QLabel()
namelabel.setText("账户:")
passwordlabel.setText("密码:")
self.namelineedit = QLineEdit()
self.passwordlineedit = QLineEdit()
self.namelineedit.setPlaceholderText("输入账户")
self.passwordlineedit.setPlaceholderText("输入密码")
self.passwordlineedit.setEchoMode(QLineEdit.Password)
buttonOK = QPushButton()
buttonOK.setText("登录")
# 创建布局
Layout = QGridLayout()
Layout.addWidget(namelabel,0,0)
Layout.addWidget(self.namelineedit,0,1,1,2)
Layout.addWidget(passwordlabel,1,0)
Layout.addWidget(self.passwordlineedit,1,1,1,2)
Layout.addWidget(buttonOK,2,2)
# 设置信号与槽
buttonOK.clicked.connect(self.transfer)
# 设置布局
self.setLayout(Layout)
# 定义槽函数
def transfer(self):
name = self.namelineedit.text()
password = self.passwordlineedit.text()
name = eval(name)
password = eval(password)
# 界面跳转实现
if name == 4 and password == 123: # 简单的账户密码设置(此处和设置账户密码规则)
# 创建主窗口类
# 注意:不能使用main = mainWindow(),这样类就是一个局部变量,函数运行结束就会被清除,所以得用self.main
self.main = mainWindow()
# 关闭自己(即登录界面)
self.close()
# 显示主窗口
self.main.show()
else:
# 创建警告窗口类
self.warn = warningWindow()
# 显示警告窗口
self.warn.show()
其中涉及到的很多控件的API就不一一介绍了,很容易看懂或查到其功能。
2.警告窗口
class warningWindow(QDialog):
def __init__(self):
super().__init__()
# 类初始函数
self.initUI()
def initUI(self):
self.setWindowTitle("错误")
self.resize(300,100)
label = QLabel()
label.setText("账号或密码错误!")
layout = QHBoxLayout()
layout.addWidget(label)
self.setLayout(layout)
3.主界面窗口
class mainWindow(QWidget):
def __init__(self):
super().__init__()
# 初始化类
self.initUI()
def initUI(self):
# 设置窗口名称
self.setWindowTitle("数据预测系统")
# 设置窗口大小及位置
self.setGeometry(300,220,1300,700)
# 创建所需控件
self.filepathlabel = QLabel("请输入数据文件完整路径:",self)
self.filepathlabel.move(50,100) # 使用绝对布局
self.filepathLineEdit = QLineEdit(self)
self.filepathLineEdit.setPlaceholderText("请输入数据文件完整路径")
self.filepathLineEdit.resize(350,30)
self.filepathLineEdit.move(50,130)
self.differlabel = QLabel("数据是否进行差分:",self)
self.diffoption = QRadioButton(self)
self.diffoption.setChecked(True)
self.differlabel.move(50,180)
self.diffoption.move(210,180)
self.methodlabel = QLabel("请选择预测方法:",self)
self.methodlabel.move(50,250)
self.methodcb = QComboBox(self)
self.methodcb.addItems(['灰色模型预测','BP神经网络预测','卡尔曼滤波预测'])
self.methodcb.move(220,248)
self.BPlabel = QLabel("请输入影响因子数据文件路径: ",self)
self.BPlabel.move(50,320)
self.BPdataLineEdit = QLineEdit(self)
self.BPdataLineEdit.move(50,350)
self.BPdataLineEdit.resize(350, 30)
self.BPlabel.setVisible(False) # 初始化影响因子控件为不可见
self.BPdataLineEdit.setVisible(False)
self.predictButton = QPushButton("开始预测",self)
self.predictButton.move(130,450)
# 设置信号与槽
self.predictButton.clicked.connect(self.predict) # 执行预测
self.methodcb.currentIndexChanged.connect(self.methodoption) # 方法切换
# 设置绘图,此处用了一个教QGroupBox的控件,其控件中可以添加布局
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure) # 设置画布
self.fig = QGroupBox(self)
self.fig.move(500,50)
self.fig.resize(750,570)
layout = QVBoxLayout()
layout.addWidget(self.canvas) # 将画布加到布局中
self.fig.setLayout(layout) # 将布局加到QGroupBox中
# 槽函数:当方法选中为BP时,将相关控件设置为可见,为其他方法时不可见
def methodoption(self):
method = self.sender()
if method.currentText() == 'BP神经网络预测':
self.BPlabel.setVisible(True)
self.BPdataLineEdit.setVisible(True)
else:
self.BPlabel.setVisible(False)
self.BPdataLineEdit.setVisible(False)
# 槽函数:预测
def predict(self):
method = self.methodcb.currentText()
filepath = self.filepathLineEdit.text()
df = pd.read_excel(filepath)
test_data = np.array(df["MD09"], dtype=np.float)
# 差分函数
def diff(arr):
res = list()
res.append(0)
for i in range(len(arr) - 1):
res.append(arr[i + 1] - arr[i])
return res
# 根据控件的选择情况判断是否进行差分
if self.diffoption.isChecked() == True:
test_data = diff(test_data)[0:41]
train_data = test_data[0:31]
# 用if-elif-else控制预测
if method == "BP神经网络预测":
plt.cla() # 清空画布
# 这是影响因子数据清洗的过程,可跳过
df2 = pd.read_excel(self.BPdataLineEdit.text())
data = pd.DataFrame(df2)
number1 = data['降雨']
number2 = data['温度']
ls_data1 = list()
ls_data2 = list()
key = True
count = 1
while key:
temp_np1 = np.array(number1)[30 * (count - 1):30 * count]
temp_np2 = np.array(number2)[30 * (count - 1):30 * count]
temp_ls1 = list()
temp_ls2 = list()
for k in temp_np1:
if np.isnan(k): # 空值判断
continue
temp_ls1.append(k)
for j in temp_np2:
if np.isnan(j):
continue
temp_ls2.append(j)
temp1 = np.array(temp_ls1)
temp2 = np.array(temp_ls2)
flag1 = np.sum(temp1) / len(temp1)
flag2 = np.sum(temp2) / len(temp2)
ls_data1.append(flag1)
ls_data2.append(flag2)
if count * 30 > len(np.array(number1)):
break
count = count + 1
data1 = np.array(ls_data1, dtype=np.float)
data2 = np.array(ls_data2, dtype=np.float)
data = np.vstack((data1[0:31], data2[0:31]))
data = data.T
train_data = train_data[0:31]
count = len(train_data)
train_data = np.array(train_data)
label = return_any(train_data.reshape(count,1), 0.1, 1)
label = label.reshape(len(label), 1)
bp = BPnetwork([2, 9, 1], 4000)
bp.train(data, label)
pred = bp.predict(data)
# 绘图
plt.plot(label,label="True", marker='x')
plt.plot(pred,label="BP Prediction",marker="*",c='r')
plt.legend()
plt.title("BP Predict")
plt.grid()
# 将绘制的图在画布上展示
self.canvas.draw()
elif method == "灰色模型预测":
plt.cla()
GM_model = GM_1_1()
GM_model.set_model(train_data)
GM_model.plot()
self.canvas.draw()
else:
plt.cla()
dt = 1.0 / 60
F = np.array([[1, dt, 0], [0, 1, dt], [0, 0, 1]])
H = np.array([1, 0, 0]).reshape(1, 3)
Q = np.array([[0.05, 0.05, 0.0], [0.05, 0.05, 0.0], [0.0, 0.0, 0.0]])
R = np.array([0.5]).reshape(1, 1)
measurements = train_data
kf = KalmanFilter(F=F, H=H, Q=Q, R=R)
predictions = []
for z in measurements:
predictions.append(np.dot(H, kf.predict())[0])
kf.update(z)
test = list()
for i in range(5):
test.append(kf.predict()[0])
plt.plot(range(len(measurements)), measurements, label="Measurements", marker='x',)
plt.plot(range(len(predictions)), np.array(predictions),
c='r', label="Kalman Filter Prediction", marker="*")
plt.legend()
plt.title("Kalman Filter Predict")
plt.grid()
self.canvas.draw()
简单介绍一下,目前是拥有一个监测点的15-18年的累计位移监测数据和该地的对应时间序列的降雨和温度数据,位移数据大致的取样间隔为一月,而温度和降雨的取样间隔为一日。在用GM模型和kalman时只需要对位移数据进行差分即可进行建模拟合。而在使用BP建模时需要影响因子一并训练,由于其数据量不统一故需要数据清洗。整体监测数据不便公布,部分原始监测数据如下:
[ 15. 16. 18. 18. 19. 21. 22. 27. 35. 39. 39. 41. 41. 44. 48. 47. 48. 55. 58. 62. 63. 68. 84. 89. 109. 120. 142. 158. 169. 183. 192. 221. 231. 238. 241. 245. 249. 254. 255. 256. 267. 264. 263. 255. 253. 258.]
最终效果图为(不能放视频就放图片):
最后附上全部代码请各位读者批评指正!
from PyQt5.QtWidgets import *
import pandas as pd
import numpy as np
import matplotlib
matplotlib.use('Qt5Agg')
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
import sys
# BP用到模块
def return_any(X, m, n):
Y = np.zeros_like(X, dtype=np.float64)
i, j = X.shape
m = float(m)
n = float(n)
X = np.array(X)
for p in range(j):
for k in range(i):
v = (n - m) / (X[:, p].max() - X[:, p].min())
Y[k, p] = m + v * (X[k, p] - X[:, p].min())
Y = np.array(Y)
return Y
class BPnetwork(): # BP预测模型
def __init__(self, layers_num, epoches):
assert len(layers_num) == 3, "初始化参数必须为三个"
self.epoches = epoches
self.input_size = layers_num[0]
self.hidden_size = layers_num[1]
self.output_size = layers_num[2]
# 初始化权值
self.X = np.ones([1, self.input_size])
self.W = np.random.random_sample(size=(self.input_size, self.hidden_size))
self.V = np.random.random_sample(size=(self.hidden_size, self.output_size))
self.H = np.ones(self.hidden_size)
self.Y = np.zeros(self.output_size)
self.alpha = np.random.random
self.beta = np.random.random
self.label = []
self.error_list = list()
self.train_end = list()
def train(self, datas, labels):
for epoch in range(self.epoches):
error = 0
for index in range(datas.shape[0]):
self.X = np.array(datas[index])
self.label = np.array(labels[index])
pred = self.forward()
if epoch == self.epoches - 1:
self.train_end.append(pred[0])
self.backword()
for i in range(pred.shape[0]):
error += (pred[i] - self.label[i]) * (pred[i] - self.label[i])
if epoch % 5 == 0:
self.error_list.append(error)
def forward(self):
self.H = np.dot(self.X, self.W)
self.H = np.array([self.sigmod(i) for i in self.H])
self.Y = np.dot(self.H, self.V)
self.Y = np.array([self.sigmod(i) for i in self.Y])
return self.Y
def backword(self):
for j in range(self.V.shape[1]):
g_j = -(self.label[j] - self.Y[j]) * self.Y[j] * (1 - self.Y[j])
for i in range(self.V.shape[0]):
self.V[i][j] = self.V[i][j] - g_j * self.H[i]
for j in range(self.W.shape[1]):
delta = 0
for k in range(self.Y.shape[0]):
g_k = -(self.label[k] - self.Y[k]) * self.Y[k] * (1 - self.Y[k])
delta += g_k * self.V[j][k]
delta = self.H[j] * (1 - self.H[j]) * delta
for i in range(self.W.shape[0]):
self.W[i][j] = self.W[i][j] - delta * self.X[i]
def sigmod(self, x):
return 1.0 / (1.0 + np.exp(-x))
def predict(self, data):
self.X = data
return self.forward()
# 灰色用到模块
class GM_1_1:
def __init__(self):
self.test_data = np.array(())
self.add_data = np.array(())
self.argu_a = 0
self.argu_b = 0
self.MAT_B = np.array(())
self.MAT_Y = np.array(())
def set_model(self,arr):
self.__acq_data(arr)
self.__compute()
def __acq_data(self,arr):
self.test_data = np.array(arr).flatten()
add_data = list()
sum = 0
for i in range(len(self.test_data)):
sum = sum + self.test_data[i]
add_data.append(sum)
self.add_data = np.array(add_data)
ser = list()
for i in range(len(self.add_data)-1):
temp = (-1) * ((1/6)*self.add_data[i] + (5/6)*self.add_data[i+1])
ser.append(temp)
B = np.vstack((np.array(ser).flatten(),np.ones(len(ser),).flatten()))
self.MAT_B = np.array(B).T
Y = np.array(self.test_data[1:])
self.MAT_Y = np.reshape(Y,(len(Y),1))
def __compute(self):
temp_1 = np.dot(self.MAT_B.T,self.MAT_B)
temp_2 = np.matrix(temp_1).I
temp_3 = np.dot(np.array(temp_2),self.MAT_B.T)
vec = np.dot(temp_3,self.MAT_Y)
self.argu_a = vec.flatten()[0]
self.argu_b = vec.flatten()[1]
def predict(self,k):
part_1 = 1-pow(np.e,self.argu_a)
part_2 = self.test_data[0] - self.argu_b/self.argu_a
part_3 = pow(np.e,(-1)*self.argu_a*k)
return part_1*part_2*part_3
def plot(self):
pre = list()
pre.append(self.test_data[0])
for i in range(len(self.test_data) - 1):
pre.append(self.predict(i + 2))
plt.plot([i for i in range(1, len(self.test_data) + 1)], self.test_data, marker='x', label='True')
plt.plot([i for i in range(1, len(self.test_data) + 1)], pre, c='r', marker='*', label='Predicton')
plt.title("Gray Model Prediction")
plt.grid()
plt.legend()
# 卡尔曼滤波用到模块
class KalmanFilter(object):
def __init__(self, F=None, B=None, H=None, Q=None, R=None, P=None, x0=None):
if F is None or H is None:
raise ValueError("Set proper system dynamics.")
self.n = F.shape[1]
self.m = H.shape[1]
self.F = F
self.H = H
self.B = 0 if B is None else B
self.Q = np.eye(self.n) if Q is None else Q
self.R = np.eye(self.n) if R is None else R
self.P = np.eye(self.n) if P is None else P
self.x = np.zeros((self.n, 1)) if x0 is None else x0
def predict(self, u=0):
# print(self.x)
self.x = np.dot(self.F, self.x) + np.dot(self.B, u)
self.P = np.dot(np.dot(self.F, self.P), self.F.T) + self.Q
return self.x
def update(self, z):
# print(self.x)
y = z - np.dot(self.H, self.x)
S = self.R + np.dot(self.H, np.dot(self.P, self.H.T))
K = np.dot(np.dot(self.P, self.H.T), np.linalg.inv(S))
self.x = self.x + np.dot(K, y)
I = np.eye(self.n)
self.P = np.dot(
np.dot(I - np.dot(K, self.H), self.P), (I - np.dot(K, self.H)).T
) + np.dot(np.dot(K, self.R), K.T)
class mainWindow(QWidget):
def __init__(self):
super().__init__()
# 初始化类
self.initUI()
def initUI(self):
# 设置窗口名称
self.setWindowTitle("数据预测系统")
# 设置窗口大小及位置
self.setGeometry(300,220,1300,700)
# 创建所需控件
self.filepathlabel = QLabel("请输入数据文件完整路径:",self)
self.filepathlabel.move(50,100) # 使用绝对布局
self.filepathLineEdit = QLineEdit(self)
self.filepathLineEdit.setPlaceholderText("请输入数据文件完整路径")
self.filepathLineEdit.resize(350,30)
self.filepathLineEdit.move(50,130)
self.differlabel = QLabel("数据是否进行差分:",self)
self.diffoption = QRadioButton(self)
self.diffoption.setChecked(True)
self.differlabel.move(50,180)
self.diffoption.move(210,180)
self.methodlabel = QLabel("请选择预测方法:",self)
self.methodlabel.move(50,250)
self.methodcb = QComboBox(self)
self.methodcb.addItems(['灰色模型预测','BP神经网络预测','卡尔曼滤波预测'])
self.methodcb.move(220,248)
self.BPlabel = QLabel("请输入影响因子数据文件路径: ",self)
self.BPlabel.move(50,320)
self.BPdataLineEdit = QLineEdit(self)
self.BPdataLineEdit.move(50,350)
self.BPdataLineEdit.resize(350, 30)
self.BPlabel.setVisible(False) # 初始化影响因子控件为不可见
self.BPdataLineEdit.setVisible(False)
self.predictButton = QPushButton("开始预测",self)
self.predictButton.move(130,450)
# 设置信号与槽
self.predictButton.clicked.connect(self.predict) # 执行预测
self.methodcb.currentIndexChanged.connect(self.methodoption) # 方法切换
# 设置绘图,此处用了一个教QGroupBox的控件,其控件中可以添加布局
self.figure = plt.figure()
self.canvas = FigureCanvas(self.figure) # 设置画布
self.fig = QGroupBox(self)
self.fig.move(500,50)
self.fig.resize(750,570)
layout = QVBoxLayout()
layout.addWidget(self.canvas) # 将画布加到布局中
self.fig.setLayout(layout) # 将布局加到QGroupBox中
# 槽函数:当方法选中为BP时,将相关控件设置为可见,为其他方法时不可见
def methodoption(self):
method = self.sender()
if method.currentText() == 'BP神经网络预测':
self.BPlabel.setVisible(True)
self.BPdataLineEdit.setVisible(True)
else:
self.BPlabel.setVisible(False)
self.BPdataLineEdit.setVisible(False)
# 槽函数:预测
def predict(self):
method = self.methodcb.currentText()
filepath = self.filepathLineEdit.text()
df = pd.read_excel(filepath)
test_data = np.array(df["MD09"], dtype=np.float)
# 差分函数
def diff(arr):
res = list()
res.append(0)
for i in range(len(arr) - 1):
res.append(arr[i + 1] - arr[i])
return res
# 根据控件的选择情况判断是否进行差分
if self.diffoption.isChecked() == True:
test_data = diff(test_data)[0:41]
train_data = test_data[0:31]
# 用if-elif-else控制预测
if method == "BP神经网络预测":
plt.cla() # 清空画布
# 这是影响因子数据清洗的过程,可跳过
df2 = pd.read_excel(self.BPdataLineEdit.text())
data = pd.DataFrame(df2)
number1 = data['降雨']
number2 = data['温度']
ls_data1 = list()
ls_data2 = list()
key = True
count = 1
while key:
temp_np1 = np.array(number1)[30 * (count - 1):30 * count]
temp_np2 = np.array(number2)[30 * (count - 1):30 * count]
temp_ls1 = list()
temp_ls2 = list()
for k in temp_np1:
if np.isnan(k): # 空值判断
continue
temp_ls1.append(k)
for j in temp_np2:
if np.isnan(j):
continue
temp_ls2.append(j)
temp1 = np.array(temp_ls1)
temp2 = np.array(temp_ls2)
flag1 = np.sum(temp1) / len(temp1)
flag2 = np.sum(temp2) / len(temp2)
ls_data1.append(flag1)
ls_data2.append(flag2)
if count * 30 > len(np.array(number1)):
break
count = count + 1
data1 = np.array(ls_data1, dtype=np.float)
data2 = np.array(ls_data2, dtype=np.float)
data = np.vstack((data1[0:31], data2[0:31]))
data = data.T
train_data = train_data[0:31]
count = len(train_data)
train_data = np.array(train_data)
label = return_any(train_data.reshape(count,1), 0.1, 1)
label = label.reshape(len(label), 1)
bp = BPnetwork([2, 9, 1], 4000)
bp.train(data, label)
pred = bp.predict(data)
# 绘图
plt.plot(label,label="True", marker='x')
plt.plot(pred,label="BP Prediction",marker="*",c='r')
plt.legend()
plt.title("BP Predict")
plt.grid()
# 将绘制的图在画布上展示
self.canvas.draw()
elif method == "灰色模型预测":
plt.cla()
GM_model = GM_1_1()
GM_model.set_model(train_data)
GM_model.plot()
self.canvas.draw()
else:
plt.cla()
dt = 1.0 / 60
F = np.array([[1, dt, 0], [0, 1, dt], [0, 0, 1]])
H = np.array([1, 0, 0]).reshape(1, 3)
Q = np.array([[0.05, 0.05, 0.0], [0.05, 0.05, 0.0], [0.0, 0.0, 0.0]])
R = np.array([0.5]).reshape(1, 1)
measurements = train_data
kf = KalmanFilter(F=F, H=H, Q=Q, R=R)
predictions = []
for z in measurements:
predictions.append(np.dot(H, kf.predict())[0])
kf.update(z)
test = list()
for i in range(5):
test.append(kf.predict()[0])
plt.plot(range(len(measurements)), measurements, label="Measurements", marker='x',)
plt.plot(range(len(predictions)), np.array(predictions),
c='r', label="Kalman Filter Prediction", marker="*")
plt.legend()
plt.title("Kalman Filter Predict")
plt.grid()
self.canvas.draw()
class warningWindow(QDialog):
def __init__(self):
super().__init__()
# 类初始函数
self.initUI()
def initUI(self):
self.setWindowTitle("错误")
self.resize(300,100)
label = QLabel()
label.setText("账号或密码错误!")
layout = QHBoxLayout()
layout.addWidget(label)
self.setLayout(layout)
class loginDialog(QDialog):
def __init__(self):
super().__init__()
# 类初始化函数
self.initUI()
def initUI(self):
# 设置窗口名称
self.setWindowTitle("登录窗口")
# 创建并设置所需要的控件
namelabel = QLabel()
passwordlabel = QLabel()
namelabel.setText("账户:")
passwordlabel.setText("密码:")
self.namelineedit = QLineEdit()
self.passwordlineedit = QLineEdit()
self.namelineedit.setPlaceholderText("输入账户")
self.passwordlineedit.setPlaceholderText("输入密码")
self.passwordlineedit.setEchoMode(QLineEdit.Password)
buttonOK = QPushButton()
buttonOK.setText("登录")
# 创建布局
Layout = QGridLayout()
Layout.addWidget(namelabel,0,0)
Layout.addWidget(self.namelineedit,0,1,1,2)
Layout.addWidget(passwordlabel,1,0)
Layout.addWidget(self.passwordlineedit,1,1,1,2)
Layout.addWidget(buttonOK,2,2)
# 设置信号与槽
buttonOK.clicked.connect(self.transfer)
# 设置布局
self.setLayout(Layout)
# 定义槽函数
def transfer(self):
name = self.namelineedit.text()
password = self.passwordlineedit.text()
name = eval(name)
password = eval(password)
# 界面跳转实现
if name == 4 and password == 123: # 简单的账户密码设置(此处和设置账户密码规则)
# 创建主窗口类
# 注意:不能使用main = mainWindow(),这样类就是一个局部变量,函数运行结束就会被清除,所以得用self.main
self.main = mainWindow()
# 关闭自己(即登录界面)
self.close()
# 显示主窗口
self.main.show()
else:
# 创建警告窗口类
self.warn = warningWindow()
# 显示警告窗口
self.warn.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = loginDialog()
window.show()
sys.exit(app.exec_())