pandas 中的函数—— .reset_index()

博客主要围绕pandas中的.reset_index()函数展开,虽未详细阐述函数内容,但表明会通过例子进行说明,聚焦于该函数在信息技术领域数据处理方面的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

话不多说,直接上例子:
在这里插入图片描述

import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from sklearn.preprocessing import MinMaxScaler import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping import os plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体 plt.rcParams['axes.unicode_minus'] = False class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型") self.root.geometry("1200x800") # 初始化数据 self.train_df = None self.test_df = None self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) # 创建主界面 self.create_widgets() def create_widgets(self): # 创建主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择部分 file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) # 训练集选择 ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) # 测试集选择 ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) # 参数设置部分 param_frame = ttk.LabelFrame(control_frame, text="模型参数", padding=10) param_frame.pack(fill=tk.X, pady=10) # 时间窗口大小 ttk.Label(param_frame, text="时间窗口大小:").grid(row=0, column=0, sticky=tk.W, pady=5) self.window_size_var = tk.IntVar(value=60) ttk.Spinbox(param_frame, from_=10, to=200, increment=5, textvariable=self.window_size_var, width=10).grid(row=0, column=1, padx=5) # LSTM单元数量 ttk.Label(param_frame, text="LSTM单元数:").grid(row=1, column=0, sticky=tk.W, pady=5) self.lstm_units_var = tk.IntVar(value=50) ttk.Spinbox(param_frame, from_=10, to=200, increment=10, textvariable=self.lstm_units_var, width=10).grid(row=1, column=1, padx=5) # 训练轮次 ttk.Label(param_frame, text="训练轮次:").grid(row=2, column=0, sticky=tk.W, pady=5) self.epochs_var = tk.IntVar(value=100) ttk.Spinbox(param_frame, from_=10, to=500, increment=10, textvariable=self.epochs_var, width=10).grid(row=2, column=1, padx=5) # 批处理大小 ttk.Label(param_frame, text="批处理大小:").grid(row=3, column=0, sticky=tk.W, pady=5) self.batch_size_var = tk.IntVar(value=32) ttk.Spinbox(param_frame, from_=16, to=128, increment=16, textvariable=self.batch_size_var, width=10).grid(row=3, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示区域 result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建标签页 self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 初始化绘图区域 self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 文件选择 def select_file(self, file_type): """选择Excel文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: try: # 读取Excel文件 df = pd.read_excel(file_path) # 时间特征列 time_features = ['year', 'month', 'day'] missing_time_features = [feat for feat in time_features if feat not in df.columns] if '水位' not in df.columns: messagebox.showerror("列名错误", "Excel文件必须包含'水位'列") return if missing_time_features: messagebox.showerror("列名错误", f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}\n" "请确保已使用预处理功能添加这些列") return # 创建完整的时间戳列 # 处理可能缺失的小时、分钟、秒数据 if 'hour' in df.columns and 'minute' in df.columns and 'second' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute', 'second']] ) elif 'hour' in df.columns and 'minute' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute']].assign(second=0) ) else: df['datetime'] = pd.to_datetime(df[['year', 'month', 'day']]) # 设置时间索引 df = df.set_index('datetime') # 保存数据 if file_type == "train": self.train_df = df self.train_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据") else: self.test_df = df self.test_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") def create_dataset(self, data, window_size): """创建时间窗口数据集""" X, y = [], [] for i in range(len(data) - window_size): X.append(data[i:(i + window_size), 0]) y.append(data[i + window_size, 0]) return np.array(X), np.array(y) def create_dynamic_plot_callback(self): """创建动态绘图回调实例,用于实时显示训练损失曲线""" class DynamicPlotCallback(tf.keras.callbacks.Callback): def __init__(self, gui_app): self.gui_app = gui_app # 引用主GUI实例 self.train_loss = [] # 存储训练损失 self.val_loss = [] # 存储验证损失 def on_epoch_end(self, epoch, logs=None): """每个epoch结束时更新图表""" logs = logs or {} # 收集损失数据 self.train_loss.append(logs.get('loss')) self.val_loss.append(logs.get('val_loss')) # 更新GUI中的图表(在主线程中执行) self.gui_app.root.after(0, self._update_plot) def _update_plot(self): """实际更新图表的函数""" try: # 清除现有图表 self.gui_app.loss_ax.clear() # 绘制训练和验证损失曲线 epochs = range(1, len(self.train_loss) + 1) self.gui_app.loss_ax.plot(epochs, self.train_loss, 'b-', label='训练损失') self.gui_app.loss_ax.plot(epochs, self.val_loss, 'r-', label='验证损失') # 设置图表属性 self.gui_app.loss_ax.set_title('模型训练损失') self.gui_app.loss_ax.set_xlabel('轮次') self.gui_app.loss_ax.set_ylabel('损失', rotation=0) self.gui_app.loss_ax.legend(loc='upper right') self.gui_app.loss_ax.grid(True, alpha=0.3) # 自动调整Y轴范围 all_losses = self.train_loss + self.val_loss min_loss = max(0, min(all_losses) * 0.9) max_loss = max(all_losses) * 1.1 self.gui_app.loss_ax.set_ylim(min_loss, max_loss) # 刷新画布 self.gui_app.loss_canvas.draw() # 更新状态栏显示最新损失 current_epoch = len(self.train_loss) if current_epoch > 0: latest_train_loss = self.train_loss[-1] latest_val_loss = self.val_loss[-1] if self.val_loss else 0 self.gui_app.status_var.set( f"训练中 | 轮次: {current_epoch} | " f"训练损失: {latest_train_loss:.6f} | " f"验证损失: {latest_val_loss:.6f}" ) self.gui_app.root.update() except Exception as e: print(f"更新图表时出错: {str(e)}") # 返回回调实例 return DynamicPlotCallback(self) def train_model(self): """训练LSTM模型""" if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("正在预处理数据...") self.root.update() # 数据预处理 train_scaled = self.scaler.fit_transform(self.train_df[['水位']]) # 创建时间窗口数据集 window_size = self.window_size_var.get() X_train, y_train = self.create_dataset(train_scaled, window_size) # 调整LSTM输入格式 X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) # 构建LSTM模型 self.model = Sequential() self.model.add(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1) )) self.model.add(LSTM(self.lstm_units_var.get())) self.model.add(Dense(1)) self.model.compile( optimizer=Adam(learning_rate=0.001), loss='mean_squared_error' ) # 添加早停机制 early_stopping = EarlyStopping( monitor='val_loss', # 监控验证集损失 patience=20, # 连续10轮无改善则停止 min_delta=0.0001, # 最小改善阈值 restore_best_weights=True, # 恢复最佳权重 verbose=1 # 显示早停信息 ) # 训练模型 self.status_var.set("正在训练模型...") self.root.update() history = self.model.fit( X_train, y_train, epochs=self.epochs_var.get(), batch_size=self.batch_size_var.get(), validation_split=0.2, # 使用20%数据作为验证集 callbacks=[early_stopping], # 添加早停回调 verbose=0 ) # 绘制损失曲线 self.loss_ax.clear() self.loss_ax.plot(history.history['loss'], label='训练损失') self.loss_ax.plot(history.history['val_loss'], label='验证损失') self.loss_ax.set_title('模型训练损失') self.loss_ax.set_xlabel('轮次') self.loss_ax.set_ylabel('损失',rotation=0) self.loss_ax.legend() self.loss_ax.grid(True) self.loss_canvas.draw() # 根据早停情况更新状态信息 if early_stopping.stopped_epoch > 0: stopped_epoch = early_stopping.stopped_epoch best_epoch = early_stopping.best_epoch final_loss = history.history['loss'][-1] best_loss = min(history.history['val_loss']) self.status_var.set( f"训练在{stopped_epoch + 1}轮提前终止 | " f"最佳模型在第{best_epoch + 1}轮 | " f"最终损失: {final_loss:.6f} | " f"最佳验证损失: {best_loss:.6f}" ) messagebox.showinfo( "训练完成", f"模型训练提前终止!\n" f"最佳模型在第{best_epoch + 1}轮\n" f"最佳验证损失: {best_loss:.6f}" ) else: final_loss = history.history['loss'][-1] self.status_var.set(f"模型训练完成 | 最终损失: {final_loss:.6f}") messagebox.showinfo("训练完成", "模型训练成功完成!") except Exception as e: messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}") self.status_var.set("训练失败") def predict(self): """使用模型进行预测""" if self.model is None: messagebox.showwarning("警告", "请先训练模型") return if self.test_df is None: messagebox.showwarning("警告", "请先选择测试集文件") return try: self.status_var.set("正在生成预测...") self.root.update() # 预处理测试数据 test_scaled = self.scaler.transform(self.test_df[['水位']]) # 创建测试集时间窗口 window_size = self.window_size_var.get() X_test, y_test = self.create_dataset(test_scaled, window_size) X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) # 进行预测 test_predict = self.model.predict(X_test) # 反归一化 test_predict = self.scaler.inverse_transform(test_predict) y_test_orig = self.scaler.inverse_transform([y_test]).T # 创建时间索引 test_time = self.test_df.index[window_size:window_size + len(test_predict)] # 绘制结果 self.ax.clear() self.ax.plot(self.train_df.index, self.train_df['水位'], 'b-', label='训练集数据') self.ax.plot(test_time, self.test_df['水位'][window_size:window_size + len(test_predict)], 'g-', label='测试集数据') self.ax.plot(test_time, test_predict, 'r--', label='模型预测') # 添加分隔线 split_point = test_time[0] self.ax.axvline(x=split_point, color='k', linestyle='--', alpha=0.5) self.ax.text(split_point, self.ax.get_ylim()[0] * 0.9, ' 训练/测试分界', rotation=90) self.ax.set_title('大坝渗流水位预测结果') self.ax.set_xlabel('时间') self.ax.set_ylabel('测压管水位',rotation=0) self.ax.legend() self.ax.grid(True) self.ax.tick_params(axis='x', rotation=45) self.fig.tight_layout() self.canvas.draw() self.status_var.set("预测完成,结果已显示") except Exception as e: messagebox.showerror("预测错误", f"预测失败:\n{str(e)}") self.status_var.set("预测失败") def save_results(self): """保存预测结果""" if not hasattr(self, 'test_predict') or self.test_predict is None: messagebox.showwarning("警告", "请先生成预测结果") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not save_path: return try: # 创建包含预测结果的DataFrame window_size = self.window_size_var.get() test_time = self.test_df.index[window_size:window_size + len(self.test_predict)] result_df = pd.DataFrame({ '时间': test_time, '实际水位': self.test_df['水位'][window_size:window_size + len(self.test_predict)].values, '预测水位': self.test_predict.flatten() }) # 保存到Excel result_df.to_excel(save_path, index=False) # 保存图表 chart_path = os.path.splitext(save_path)[0] + "_chart.png" self.fig.savefig(chart_path, dpi=300) self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}") messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}") except Exception as e: messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}") def reset(self): """重置程序状态""" self.train_df = None self.test_df = None self.model = None self.train_file_var.set("") self.test_file_var.set("") self.ax.clear() self.loss_ax.clear() self.canvas.draw() self.loss_canvas.draw() self.data_text.delete(1.0, tk.END) self.status_var.set("已重置,请选择新数据") messagebox.showinfo("重置", "程序已重置,可以开始新的分析") if __name__ == "__main__": root = tk.Tk() app = DamSeepageModel(root) root.mainloop() 这个代码是大坝渗流时间——水位预测训练模型 现在我想: 1.添加MSE,RMSE,MAE,MAPE,R²作为评估指标并且投入到模型训练中 2.想再添加一个窗口名称为“评估指标”,绘制出评估指标随着训练轮次的变化曲线 3.想进一步优化模型,使得拟合情况更优秀 4.请你在写完代码后检查一下看看有没有能优化的地方,比如减少冗余,优化运行空间
最新发布
07-18
<think>好的,我现在需要解决用户的问题,首先我需要仔细阅读用户提供的代码,并理解用户的需求。用户希望在现有的大坝渗流预测模型中添加四个评估指标(MSE, RMSE, MAE, MAPE, R²),并在训练过程中监控这些指标的变化;添加一个名为“评估指标”的标签页来展示这些指标的变化曲线;优化模型以提高拟合效果;最后检查代码是否有冗余或可以优化的地方。 首先,处理第一个需求:添加评估指标。用户提到的MSE(均方误差)、RMSE(均方根误差)、MAE(平均绝对误差)、MAPE(平均绝对百分比误差)和R²(决定系数)。这些指标需要在模型训练时计算,并记录每个epoch的结果。在TensorFlow中,可以通过自定义指标函数来实现,然后在model.compile中指定这些指标。 接下来,第二个需求是添加“评估指标”标签页。需要在创建标签页的部分添加一个新的Frame,并将其添加到notebook中。然后,在训练过程中,收集每个epoch的评估指标值,并在这个新标签页中绘制曲线。 第三个需求是优化模型。可能的优化方法包括调整模型结构(如增加LSTM层、调整单元数)、调整超参数(学习率、批大小、时间窗口)、添加正则化(Dropout、L2正则化)、使用更复杂的模型结构(如BiLSTM、GRU)、调整数据预处理(特征工程,添加更多相关特征)等。 第四个需求是检查代码冗余和优化运行空间。需要检查是否有重复的代码段,例如数据预处理部分是否可以函数化;是否有不必要的变量存储;是否可以使用更高效的数据结构;是否有内存泄漏的可能(如未及时释放的大数组)等。 现在,逐步处理每个需求: 1. **添加评估指标**:需要定义这些指标的函数。注意,TensorFlow的指标需要处理张量,所以需要用tf函数来实现。例如,MAPE需要注意除零问题,R²需要计算总平方和和残差平方和。然后在model.compile中添加这些指标,并在训练时记录历史数据。 2. **添加评估指标标签页**:在create_widgets方法中,创建新的Frame,并添加到notebook中。然后初始化对应的图表(fig和ax),并在训练回调中更新这些图表,显示各指标随epoch的变化。 3. **模型优化**:可能的优化点包括: - 添加Dropout层防止过拟合。 - 使用双向LSTM(BiLSTM)捕捉双向时间依赖。 - 调整学习率,使用学习率调度器(如ReduceLROnPlateau)。 - 增加特征,除了水位,可能还有其他影响渗流的因素(如降雨量、温度等),如果数据中有的话。 - 调整时间窗口大小,可能需要通过实验找到最优值。 - 使用更复杂的模型结构,如堆叠LSTM层,或者结合CNN的特征提取。 4. **代码优化**:检查现有代码,例如: - select_file方法中读取Excel的代码可以提取为一个函数,避免重复。 - create_dataset方法可以考虑向量化操作,替代循环,提高效率。 - 绘图部分的代码可以统一管理,减少重复的clear和draw操作。 - 状态变量的更新可以更高效,避免频繁的root.update()。 - 检查是否有未使用的导入(如numpy、matplotlib的某些模块),但用户代码中可能需要,所以需确认。 现在,具体修改代码: **步骤1:定义评估指标函数** 在模型类中添加这些指标的函数,或者在全局定义。例如: ```python def rmse(y_true, y_pred): return tf.sqrt(tf.reduce_mean(tf.square(y_true - y_pred))) def mae(y_true, y_pred): return tf.reduce_mean(tf.abs(y_true - y_pred)) def mape(y_true, y_pred): # 避免除零,添加小量epsilon epsilon = 1e-8 return tf.reduce_mean(tf.abs((y_true - y_pred) / (y_true + epsilon))) * 100 def r_squared(y_true, y_pred): ss_res = tf.reduce_sum(tf.square(y_true - y_pred)) ss_tot = tf.reduce_sum(tf.square(y_true - tf.reduce_mean(y_true))) return 1 - (ss_res / (ss_tot + tf.keras.backend.epsilon())) ``` 然后在model.compile中添加这些指标: ```python self.model.compile( optimizer=Adam(learning_rate=0.001), loss='mean_squared_error', metrics=['mse', rmse, mae, mape, r_squared] # 注意MSE可以直接用'mse',RMSE需要自定义 ) ``` **步骤2:添加评估指标标签页** 在create_widgets的notebook部分添加新的Frame: ```python # 评估指标标签页 self.metrics_frame = ttk.Frame(self.notebook) self.notebook.add(self.metrics_frame, text="评估指标") ``` 初始化对应的图表: ```python # 评估指标绘图区域 self.metrics_fig, self.metrics_ax = plt.subplots(figsize=(10, 6)) self.metrics_canvas = FigureCanvasTkAgg(self.metrics_fig, master=self.metrics_frame) self.metrics_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) ``` **步骤3:修改训练回调以记录评估指标** 现有的DynamicPlotCallback只记录了损失和验证损失,需要修改以记录所有评估指标。在on_epoch_end中,从logs中获取指标值,例如: ```python def on_epoch_end(self, epoch, logs=None): logs = logs or {} self.train_loss.append(logs.get('loss')) self.val_loss.append(logs.get('val_loss')) # 新增评估指标记录 self.train_mse.append(logs.get('mse')) self.val_mse.append(logs.get('val_mse')) self.train_rmse.append(logs.get('rmse')) self.val_rmse.append(logs.get('val_rmse')) self.train_mae.append(logs.get('mae')) self.val_mae.append(logs.get('val_mae')) self.train_mape.append(logs.get('mape')) self.val_mape.append(logs.get('val_mape')) self.train_r2.append(logs.get('r_squared')) self.val_r2.append(logs.get('val_r_squared')) # 更新图表 self.gui_app.root.after(0, self._update_plot) ``` 同时,在_update_plot方法中,除了更新损失曲线,还需要更新评估指标的图表。或者,可能需要为评估指标单独创建一个回调,或者修改现有的回调来处理多个图表。 另外,在train_model方法中,训练完成后,history对象会包含这些指标的历史数据,可以用于绘制。 **步骤4:绘制评估指标曲线** 在metrics_frame的图表中,绘制各指标的训练和验证曲线。例如,在训练完成后,或者在每个epoch更新时: ```python def _update_metrics_plot(self): self.gui_app.metrics_ax.clear() epochs = range(1, len(self.train_mse) + 1) # 绘制MSE self.gui_app.metrics_ax.plot(epochs, self.train_mse, 'b-', label='训练MSE') self.gui_app.metrics_ax.plot(epochs, self.val_mse, 'b--', label='验证MSE') # 绘制RMSE self.gui_app.metrics_ax.plot(epochs, self.train_rmse, 'r-', label='训练RMSE') self.gui_app.metrics_ax.plot(epochs, self.val_rmse, 'r--', label='验证RMSE') # 绘制MAE self.gui_app.metrics_ax.plot(epochs, self.train_mae, 'g-', label='训练MAE') self.gui_app.metrics_ax.plot(epochs, self.val_mae, 'g--', label='验证MAE') # 绘制MAPE self.gui_app.metrics_ax.plot(epochs, self.train_mape, 'm-', label='训练MAPE') self.gui_app.metrics_ax.plot(epochs, self.val_mape, 'm--', label='验证MAPE') # 绘制R² self.gui_app.metrics_ax.plot(epochs, self.train_r2, 'c-', label='训练R²') self.gui_app.metrics_ax.plot(epochs, self.val_r2, 'c--', label='验证R²') self.gui_app.metrics_ax.set_title('评估指标变化曲线') self.gui_app.metrics_ax.set_xlabel('轮次') self.gui_app.metrics_ax.set_ylabel('指标值') self.gui_app.metrics_ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left') # 调整图例位置避免重叠 self.gui_app.metrics_fig.tight_layout() self.gui_app.metrics_canvas.draw() ``` **步骤5:模型优化** - **添加Dropout层**:在LSTM层后添加Dropout,防止过拟合。例如: ```python self.model.add(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1), dropout=0.2, # 输入单元的丢弃率 recurrent_dropout=0.2 # 循环单元的丢弃率 )) self.model.add(LSTM(self.lstm_units_var.get(), dropout=0.2, recurrent_dropout=0.2)) ``` - **使用双向LSTM**:将LSTM层包裹在Bidirectional中: ```python from tensorflow.keras.layers import Bidirectional self.model.add(Bidirectional(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1) ))) ``` - **调整学习率**:使用学习率调度器,如ReduceLROnPlateau,当验证损失停止下降时降低学习率: ```python from tensorflow.keras.callbacks import ReduceLROnPlateau lr_scheduler = ReduceLROnPlateau( monitor='val_loss', factor=0.5, # 学习率降低的因子 patience=10, # 等待轮数 min_lr=1e-6, # 最小学习率 verbose=1 ) ``` 并在model.fit的callbacks中添加lr_scheduler。 - **特征工程**:如果数据中有其他相关特征(如降雨量、温度等),可以将其加入模型输入,提高预测能力。需要调整create_dataset方法,将多个特征包含进去。 **步骤6:代码优化** - **提取重复代码**:例如,select_file中的Excel读取和预处理可以提取为一个函数,如load_and_preprocess_data(file_path),返回处理后的DataFrame。 - **向量化数据生成**:create_dataset方法使用循环生成X和y,对于大数据集效率较低。可以使用滑动窗口的向量化操作,例如使用numpy的stride_tricks.as_strided或tensorflow的window操作。 - **释放不必要的变量**:在训练或预测完成后,释放不再需要的大数组,如训练后的原始数据如果不再需要,可以设为None,帮助垃圾回收。 - **优化绘图更新**:在动态绘图时,使用blitting技术减少绘制时间,提高实时性。 - **检查未使用的导入**:例如,代码中导入了numpy as np,但可能有些地方未使用,或者可以优化导入方式。 现在,将这些修改整合到原始代码中,并确保各部分协调工作。需要注意的是,添加新的评估指标后,训练时的日志会包含这些指标,需要确保回调函数正确捕获它们。同时,新的标签页需要正确初始化和更新。 最后,检查代码是否有冗余,例如: - 在select_file方法中,训练集和测试集的处理逻辑重复,可以提取为一个函数。 - 在train_model和predict方法中,数据预处理(如缩放、创建时间窗口)有重复代码,可以提取为单独的方法。 - 绘图部分的clear、设置标题、标签等操作可以封装为函数,减少重复代码。 总结,通过以上步骤,可以满足用户的需求,并优化代码结构和模型性能。 </think>以下是针对您需求的修改和优化后的代码,主要包含评估指标添加、新标签页创建、模型优化及代码冗余优化: ```python import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from sklearn.preprocessing import MinMaxScaler import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Bidirectional, Dropout from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau import os # 解决中文显示问题 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 自定义评估指标函数 def rmse(y_true, y_pred): return tf.sqrt(tf.reduce_mean(tf.square(y_true - y_pred))) def mape(y_true, y_pred): epsilon = 1e-8 return tf.reduce_mean(tf.abs((y_true - y_pred) / (y_true + epsilon))) * 100 def r_squared(y_true, y_pred): ss_res = tf.reduce_sum(tf.square(y_true - y_pred)) ss_tot = tf.reduce_sum(tf.square(y_true - tf.reduce_mean(y_true))) return 1 - (ss_res / (ss_tot + tf.keras.backend.epsilon())) class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型") self.root.geometry("1400x900") # 初始化数据 self.train_df = None self.test_df = None self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) self.metrics_history = { # 新增:存储评估指标历史数据 'train': {'mse': [], 'rmse': [], 'mae': [], 'mape': [], 'r2': []}, 'val': {'mse': [], 'rmse': [], 'mae': [], 'mape': [], 'r2': []} } self.create_widgets() def create_widgets(self): main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板(精简布局) control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择(提取为函数) self._create_file_selector(control_frame) # 参数设置(优化布局) param_frame = ttk.LabelFrame(control_frame, text="模型参数", padding=10) param_frame.pack(fill=tk.X, pady=10) self._create_param_selector(param_frame) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示(新增评估指标标签页) result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 新增:评估指标标签页 self.metrics_frame = ttk.Frame(self.notebook) self.notebook.add(self.metrics_frame, text="评估指标") # 初始化绘图区域(优化画布管理) self._init_plots() def _create_file_selector(self, parent): """提取文件选择模块为独立方法""" file_frame = ttk.LabelFrame(parent, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) def _create_param_selector(self, parent): """提取参数选择模块为独立方法""" params = [ ("时间窗口大小:", self.window_size_var := tk.IntVar(value=60), 10, 200, 5), ("LSTM单元数:", self.lstm_units_var := tk.IntVar(value=64), 10, 200, 10), ("训练轮次:", self.epochs_var := tk.IntVar(value=150), 10, 500, 10), ("批处理大小:", self.batch_size_var := tk.IntVar(value=64), 16, 128, 16) ] for idx, (label, var, _from, _to, inc) in enumerate(params): ttk.Label(parent, text=label).grid(row=idx, column=0, sticky=tk.W, pady=5) ttk.Spinbox(parent, from_=_from, to=_to, increment=inc, textvariable=var, width=10).grid(row=idx, column=1, padx=5) def _init_plots(self): """统一初始化绘图区域""" # 预测结果图 self.pred_fig, self.pred_ax = plt.subplots(figsize=(12, 6)) self.pred_canvas = FigureCanvasTkAgg(self.pred_fig, master=self.prediction_frame) self.pred_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 损失曲线图 self.loss_fig, self.loss_ax = plt.subplots(figsize=(12, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 评估指标图 self.metrics_fig, self.metrics_ax = plt.subplots(figsize=(12, 6)) self.metrics_canvas = FigureCanvasTkAgg(self.metrics_fig, master=self.metrics_frame) self.metrics_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) def select_file(self, file_type): """优化文件选择逻辑,提取公共处理""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if not file_path: return try: df = self._load_and_preprocess_data(file_path) # 调用预处理函数 target_var = self.train_file_var if file_type == "train" else self.test_file_var target_var.set(os.path.basename(file_path)) setattr(self, f"{file_type}_df", df) self.status_var.set(f"已加载{file_type}集: {len(df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") def _load_and_preprocess_data(self, file_path): """提取数据加载和预处理为独立方法""" df = pd.read_excel(file_path) required_cols = ['year', 'month', 'day', '水位'] missing_cols = [col for col in required_cols if col not in df.columns] if missing_cols: raise ValueError(f"缺少必要列: {', '.join(missing_cols)}") # 构造时间戳(优化时间处理) time_cols = ['year', 'month', 'day'] if 'hour' in df.columns: time_cols.extend(['hour', 'minute', 'second'] if 'second' in df.columns else ['hour', 'minute']) df['datetime'] = pd.to_datetime(df[time_cols]) return df.set_index('datetime').sort_index() # 按时间排序 def create_dataset(self, data, window_size): """向量化实现时间窗口(优化性能)""" data = data.reshape(-1) n_samples = len(data) - window_size # 使用滑动窗口向量化生成(替代循环) window_indices = np.arange(window_size).reshape(1, -1) + np.arange(n_samples).reshape(-1, 1) X = data[window_indices] y = data[window_size:] return X[..., np.newaxis], y # 保持LSTM输入格式 def train_model(self): if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("数据预处理中...") train_data = self.scaler.fit_transform(self.train_df[['水位']]) window_size = self.window_size_var.get() X_train, y_train = self.create_dataset(train_data, window_size) # 模型优化:双向LSTM+Dropout self.model = Sequential([ Bidirectional(LSTM(self.lstm_units_var.get(), return_sequences=True, dropout=0.2, recurrent_dropout=0.2), input_shape=(window_size, 1)), LSTM(self.lstm_units_var.get(), dropout=0.2, recurrent_dropout=0.2), Dense(1) ]) self.model.compile( optimizer=Adam(learning_rate=0.001), loss='mse', metrics=['mse', rmse, 'mae', mape, r_squared] # 添加评估指标 ) # 高级回调:早停+学习率衰减 callbacks = [ EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True), ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6), self._create_dynamic_callback() # 动态绘图回调 ] history = self.model.fit( X_train, y_train, epochs=self.epochs_var.get(), batch_size=self.batch_size_var.get(), validation_split=0.2, callbacks=callbacks, verbose=0 ) # 保存评估指标历史 self._update_metrics_history(history.history) self._plot_final_metrics() # 绘制最终指标曲线 self.status_var.set("模型训练完成") except Exception as e: messagebox.showerror("训练错误", f"训练失败: {str(e)}") def _create_dynamic_callback(self): """优化动态回调,支持多指标更新""" class DynamicCallback(tf.keras.callbacks.Callback): def __init__(self, app): self.app = app self.epochs = [] def on_epoch_end(self, epoch, logs=None): self.epochs.append(epoch+1) # 更新损失曲线 self.app.loss_ax.clear() self.app.loss_ax.plot(self.epochs, logs['loss'], label='训练损失') self.app.loss_ax.plot(self.epochs, logs['val_loss'], label='验证损失') self.app.loss_ax.set_title('训练损失变化') self.app.loss_ax.legend() self.app.loss_canvas.draw() # 更新状态信息 self.app.status_var.set( f"训练轮次: {epoch+1} | 训练损失: {logs['loss']:.4f} | 验证损失: {logs['val_loss']:.4f}" ) return DynamicCallback(self) def _update_metrics_history(self, history): """更新评估指标历史数据""" metrics_map = { 'mse': 'mse', 'rmse': 'rmse', 'mae': 'mae', 'mape': 'mape', 'r_squared': 'r2' } for metric, key in metrics_map.items(): self.metrics_history['train'][key] = history[metric] self.metrics_history['val'][key] = history[f'val_{metric}'] def _plot_final_metrics(self): """绘制最终评估指标曲线""" self.metrics_ax.clear() epochs = range(1, len(self.metrics_history['train']['mse'])+1) # 绘制MSE/RMSE self.metrics_ax.plot(epochs, self.metrics_history['train']['mse'], 'b-', label='训练MSE') self.metrics_ax.plot(epochs, self.metrics_history['val']['mse'], 'b--', label='验证MSE') self.metrics_ax.plot(epochs, self.metrics_history['train']['rmse'], 'r-', label='训练RMSE') self.metrics_ax.plot(epochs, self.metrics_history['val']['rmse'], 'r--', label='验证RMSE') # 绘制MAE/MAPE self.metrics_ax.plot(epochs, self.metrics_history['train']['mae'], 'g-', label='训练MAE') self.metrics_ax.plot(epochs, self.metrics_history['val']['mae'], 'g--', label='验证MAE') self.metrics_ax.plot(epochs, self.metrics_history['train']['mape'], 'm-', label='训练MAPE(%)') self.metrics_ax.plot(epochs, self.metrics_history['val']['mape'], 'm--', label='验证MAPE(%)') # 绘制R² self.metrics_ax.plot(epochs, self.metrics_history['train']['r2'], 'c-', label='训练R²') self.metrics_ax.plot(epochs, self.metrics_history['val']['r2'], 'c--', label='验证R²') self.metrics_ax.set_title('评估指标变化曲线') self.metrics_ax.set_xlabel('训练轮次') self.metrics_ax.set_ylabel('指标值') self.metrics_ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left') self.metrics_fig.tight_layout() self.metrics_canvas.draw() # 其他方法(predict/save_results/reset)保持核心逻辑,根据需要调整 def predict(self): if not self.model or not self.test_df: messagebox.showwarning("警告", "请先训练模型并选择测试集") return try: test_data = self.scaler.transform(self.test_df[['水位']]) window_size = self.window_size_var.get() X_test, y_test = self.create_dataset(test_data, window_size) y_pred = self.model.predict(X_test) # 反归一化并绘图(保持原有逻辑优化) self.pred_ax.clear() self.pred_ax.plot(self.test_df.index[window_size:], self.scaler.inverse_transform(y_test.reshape(-1,1)), label='实际值', alpha=0.7) self.pred_ax.plot(self.test_df.index[window_size:], self.scaler.inverse_transform(y_pred), label='预测值', linestyle='--') self.pred_ax.set_title('大坝渗流水位预测
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫咪钓鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值