【开源解析】基于深度学习的双色球预测系统:从数据获取到可视化分析

【开源解析】基于深度学习的双色球预测系统:从数据获取到可视化分析

基于深度学习的双色球预测系统:从数据获取到可视化分析

🌈 个人主页:创客白泽 - ZEEKLOG博客
🔥 系列专栏:🐍《Python开源项目实战》
💡 热爱不止于代码,热情源自每一个灵感闪现的夜晚。愿以开源之火,点亮前行之路。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享给更多人哦
请添加图片描述


在这里插入图片描述

概述

在当今大数据和人工智能时代,机器学习技术已广泛应用于各个领域。本文将介绍一个基于PyQt5和TensorFlow开发的双色球预测系统,该系统集数据获取、深度学习模型训练、预测分析和可视化展示于一体。

本项目采用Python语言开发,主要技术栈包括:

  • PyQt5:构建美观的图形用户界面
  • TensorFlow/Keras:实现LSTM、GRU等深度学习模型
  • Pandas/Numpy:数据处理与分析
  • WebEngineView:现代化的结果展示方式

系统通过分析历史开奖数据,运用多种神经网络模型预测下一期可能的开奖号码,并提供直观的数据可视化功能。本文将从功能设计、实现原理、代码解析等多个维度详细介绍该系统。

功能特点

1. 数据获取模块

系统内置网络爬虫功能,能够自动从官方数据源获取最新的双色球开奖数据。主要特点包括:

  • 自动处理网络请求和JSON数据解析
  • 数据清洗和异常处理机制
  • 本地CSV文件缓存
  • 进度条实时显示获取进度

2. 预测分析模块

核心预测功能基于TensorFlow深度学习框架,提供三种预测策略:

  1. LSTM模型:长短期记忆网络,擅长捕捉时间序列中的长期依赖关系
  2. GRU模型:门控循环单元,比LSTM更轻量高效
  3. 混合模型:结合CNN、LSTM、GRU和注意力机制的多层次特征提取

3. 可视化分析模块

系统采用现代化的Web技术展示分析结果:

  • 交互式HTML表格展示历史数据
  • 动态气泡图展示号码频率分布
  • 响应式设计适配不同屏幕尺寸
  • 动画效果增强用户体验

4. 参数配置界面

用户友好的参数配置界面,可调整:

  • 训练集比例(50%-95%)
  • 训练轮次(50-1000)
  • 批量大小(16-128)
  • 回溯期数(5-30)
  • 预测策略(LSTM/GRU/混合模型)

系统展示

主界面截图

在这里插入图片描述

主界面采用标签页设计,分为"数据获取"、"开始预测"和"统计分析"三个主要功能区域。界面风格现代化,配色以蓝色和红色为主,符合双色球主题。

数据获取界面

在这里插入图片描述

左侧为操作面板,右侧以卡片式布局展示获取到的历史数据,支持分页浏览。数据表格采用响应式设计,在移动设备上也能良好显示。

预测结果展示

在这里插入图片描述

预测结果以醒目的红蓝球号码展示,并附带模型参数和训练信息。页面加载时有精美的加载动画,提升用户体验。

统计分析可视化

在这里插入图片描述

采用气泡图直观展示各号码的出现频率,高频号码会有脉冲动画效果。同时提供TOP10号码列表和详细统计数据。

实现原理

1. 整体架构

系统采用MVC(Model-View-Controller)设计模式:

  • Model:数据处理和预测算法(DataFetcher, EnhancedPredictor)
  • View:PyQt5界面和Web展示(MainWindow, HTML模板)
  • Controller:事件处理和业务逻辑(MainWindow方法)

2. 数据流图

在这里插入图片描述

3. 关键技术点

  1. 惰性导入:仅在需要时导入TensorFlow等重型库,加快启动速度
  2. 多线程处理:使用QThread防止界面卡顿
  3. 异常处理:全面的错误捕获和备用方案
  4. 响应式设计:适配不同尺寸屏幕
  5. 动画效果:增强用户体验

代码解析

1. 数据获取模块

classDataFetcher(QThread): finished = pyqtSignal(str) progress = pyqtSignal(int)defrun(self):try:import requests import pandas as pd url ='http://www.cwl.gov.cn/cwl_admin/front/cwlkj/search/kjxx/findDrawNotice' params ={'name':'ssq','issueCount':'','issueStart':'','issueEnd':'','dayStart':'','dayEnd':'','pageNo':'1','pageSize':'9999','week':'','systemType':'PC'} response = requests.get(url, params=params) jsondata = response.json()# 数据处理逻辑... df.to_csv('data.csv', index=False, encoding='utf-8-sig') self.finished.emit(f"数据获取成功!共获取{len(data)}期数据。")except Exception as e: self.finished.emit(f"数据获取失败:{str(e)}")

关键点:

  • 继承QThread实现多线程
  • 使用requests库获取数据
  • 完善的数据清洗和异常处理
  • 进度信号实时更新界面

2. 预测模型模块

classEnhancedPredictor(QThread):# ...初始化代码...defcreate_lstm_model(self, input_shape):import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, LSTM, BatchNormalization, Dropout model = Sequential([ Input(shape=input_shape), LSTM(512, return_sequences=True, kernel_regularizer=l2(0.01), recurrent_regularizer=l2(0.01), dropout=0.2, recurrent_dropout=0.2), BatchNormalization(), LSTM(256, return_sequences=True),# ...更多层... Dense(7, activation='sigmoid')]) model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001), metrics=['mae'])return model 

关键点:

  • 三种模型架构可选
  • 添加正则化和Dropout防止过拟合
  • BatchNormalization加速训练
  • 自定义Attention层增强关键特征

3. 界面交互设计

classMainWindow(QMainWindow):def__init__(self):super().__init__() self.setWindowTitle("红篮球预测系统") self.setGeometry(100,100,1280,900) self.setStyleSheet(""" QMainWindow { background-color: #f5f7fa; } /* 更多样式... */ """)# 初始化UI组件 self.initUI()definitUI(self):# 创建标签页 self.tabs = QTabWidget()# 数据获取页 data_tab = QWidget()# ...布局和组件...# 预测页 predict_tab = QWidget()# ...布局和组件...# 统计页 stats_tab = QWidget()# ...布局和组件... self.tabs.addTab(data_tab,"数据获取") self.tabs.addTab(predict_tab,"开始预测") self.tabs.addTab(stats_tab,"统计分析")

关键点:

  • 现代化CSS样式
  • 响应式布局
  • 标签页导航
  • WebEngineView集成

使用教程

1. 数据获取步骤

  1. 点击"数据获取"标签页
  2. 点击"获取最新红篮球数据"按钮
  3. 等待进度条完成
  4. 查看右侧表格展示的历史数据

2. 预测分析步骤

  1. 调整模型参数:
    • 训练集比例:70-80%
    • 训练轮次:100-300
    • 批量大小:32-64
    • 回溯期数:10-20
    • 预测策略:根据需求选择
  2. 点击"开始预测"按钮
  3. 等待训练完成(有进度显示)
  4. 查看预测结果

3. 统计分析查看

  1. 数据获取完成后自动生成统计信息
  2. 点击"统计分析"标签页
  3. 查看号码频率分布和TOP10列表

源码解析

1. 核心类关系

在这里插入图片描述

2. 关键方法说明

数据预处理
defpreprocess_data(self, df):# 日期处理 df['date']= pd.to_datetime(df['日期'], errors='coerce')# 添加时间特征 df['year']= df['date'].dt.year df['month']= df['date'].dt.month # ...其他特征...# 数值标准化 numeric_cols =[col for col in df.columns if col notin non_numeric_cols] self.scaler = MinMaxScaler() scaled_data = self.scaler.fit_transform(df[numeric_cols])return scaled_data, numeric_cols 
序列数据生成
defgenerate_sequences(self, data, lookback): X, y =[],[]for i inrange(len(data)- lookback): X.append(data[i:i + lookback]) y.append(data[i + lookback,:7])# 预测7个号码(6红+1蓝)return np.array(X), np.array(y)
结果后处理
defenhanced_postprocessing(self, prediction):# 处理预测结果 prediction = np.nan_to_num(prediction, nan=0.5)# 映射到实际号码范围 red_balls =[]for i inrange(6): ball =int(round(prediction[0][i]*32+1)) ball =max(1,min(33, ball)) red_balls.append(ball)# 处理蓝球 blue_ball =int(round(prediction[0][6]*15+1)) blue_ball =max(1,min(16, blue_ball))returnsorted(red_balls), blue_ball 

源码下载

import sys import os # Simple Python modules firstimport json from collections import Counter # Core PyQt imports for UIfrom PyQt5.QtWidgets import(QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QTextEdit, QSpinBox, QDoubleSpinBox, QProgressBar, QMessageBox, QTabWidget, QGroupBox, QFormLayout, QComboBox)from PyQt5.QtCore import QThread, pyqtSignal, Qt, QUrl, QObject, pyqtSlot from PyQt5.QtGui import QIcon from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from PyQt5.QtWebChannel import QWebChannel # Import custom module for bubble charts# from bubbles import generate_bubble_chart_htmldefresource_path(relative_path):""" 解决打包后资源文件路径问题 """ifhasattr(sys,'_MEIPASS'):return os.path.join(sys._MEIPASS, relative_path)return os.path.join(os.path.abspath("."), relative_path)classAttentionLayer:def__init__(self,**kwargs):# Import TensorFlow here when the class is instantiatedimport tensorflow as tf from tensorflow.keras.layers import Layer self.tf = tf super(Layer, self).__init__(**kwargs)defbuild(self, input_shape): self.W = self.add_weight(name='attention_weight', shape=(input_shape[-1],1), initializer='random_normal', trainable=True) self.b = self.add_weight(name='attention_bias', shape=(input_shape[1],1), initializer='zeros', trainable=True)super(AttentionLayer, self).build(input_shape)defcall(self, x): e = self.tf.tanh(self.tf.matmul(x, self.W)+ self.b) a = self.tf.nn.softmax(e, axis=1) output = x * a return self.tf.reduce_sum(output, axis=1)defis_prime(n):"""判断是否为质数"""if n <=1:returnFalsefor i inrange(2,int(n **0.5)+1):if n % i ==0:returnFalsereturnTrueclassDataFetcher(QThread): finished = pyqtSignal(str) progress = pyqtSignal(int)defrun(self):try:# Import requests only when needed for data fetchingimport requests import pandas as pd url ='http://www.cwl.gov.cn/cwl_admin/front/cwlkj/search/kjxx/findDrawNotice' params ={'name':'ssq','issueCount':'','issueStart':'','issueEnd':'','dayStart':'','dayEnd':'','pageNo':'1','pageSize':'9999','week':'','systemType':'PC'} response = requests.get(url, params=params) jsondata = response.json()if jsondata['state']==0: data =[] total =len(jsondata['result'])# 收集所有的日期字符串,用于日志记录 all_dates =[]for i, item inenumerate(jsondata['result']):# 获取日期字符串 date_str = item['date'] all_dates.append(date_str)# 更严格地处理日期格式try:# 首先尝试分离掉括号部分(如果有) date_str = date_str.split('(')[0].strip()# 移除日期字符串中的非法字符 clean_date =''.join([c for c in date_str if c.isdigit()or c =='-'])# 确保日期格式为YYYY-MM-DD并且长度正确iflen(clean_date)>=10: clean_date = clean_date[:10]else:# 对于格式不正确的日期,使用期号的前8位作为替代(如果可能) code = item['code']iflen(code)>=8: year = code[:4] month = code[4:6] day = code[6:8] clean_date =f"{year}-{month}-{day}"else:# 最后的备选方案 clean_date ="2020-01-01"# 使用占位符except Exception as e:print(f"处理日期时出错: {str(e)}, 原始日期: {date_str}") clean_date ="2020-01-01"# 使用占位符# 获取蓝球和红球try: blue_ball =int(item['blue']) red_balls =[int(rb)for rb in item['red'].split(',')]# 确保有足够的红球whilelen(red_balls)<6: red_balls.append(1)# 使用1作为占位符# 添加到数据列表 data.append([item['code'], clean_date, red_balls[0], red_balls[1], red_balls[2], red_balls[3], red_balls[4], red_balls[5], blue_ball])except Exception as e:print(f"处理球号时出错: {str(e)}, 期号: {item['code']}")# 跳过这条数据continue self.progress.emit(int((i +1)/ total *100))# 记录日期收集情况print(f"收集到 {len(all_dates)} 个日期")iflen(all_dates)>0:print(f"示例日期: {all_dates[0]}") df = pd.DataFrame(data, columns=['期号','日期','red1','red2','red3','red4','red5','red6','blue'])# 检查数据有效性print(f"数据总行数: {len(df)}")print(f"日期列类型: {df['日期'].dtype}")print(f"缺失值数量: {df.isna().sum().sum()}")# 保存前排序try: df['temp_date']= pd.to_datetime(df['日期'], errors='coerce') df = df.sort_values(by='temp_date', ascending=False)# 修复链式赋值警告,避免使用 inplace=True df = df.drop('temp_date', axis=1)except Exception as e:print(f"排序数据时出错: {str(e)}") df.to_csv('data.csv', index=False, encoding='utf-8-sig') self.finished.emit(f"数据获取成功!共获取{len(data)}期数据。")else: self.finished.emit("数据获取失败:服务器返回错误状态。")except Exception as e:import traceback error_details = traceback.format_exc() self.finished.emit(f"数据获取失败:{str(e)}\n\n详细错误信息:\n{error_details}")classEnhancedPredictor(QThread): finished = pyqtSignal(str) progress = pyqtSignal(int) stats_ready = pyqtSignal(dict) model_trained = pyqtSignal(object)def__init__(self, train_ratio, epochs, batch_size, lookback, strategy, shared_css=None):super().__init__() self.train_ratio = train_ratio self.epochs = epochs self.batch_size = batch_size self.lookback = lookback self.strategy = strategy.lower() self.red_stats =None self.blue_stats =None self.scaler =None self.model =None self.shared_css = shared_css or""# Default to empty string if None# We'll import tensorflow and other ML libraries on demand when run() is calleddefget_model_type(self):"""获取当前使用的模型类型"""if self.strategy =='lstm':return"多层LSTM神经网络"elif self.strategy =='gru':return"双向GRU神经网络"else:return"混合模型(CNN+LSTM+GRU+Attention)"defget_model_architecture(self):"""获取模型架构描述"""if self.strategy =='lstm':return"LSTM(512)→LSTM(256)→Dense(128)"elif self.strategy =='gru':return"BiGRU(256)→GRU(128)→Dense(64)"else:return"Conv1D→BiLSTM→GRU→Attention→Dense"defcalculate_enhanced_stats(self, data):"""增强的统计分析"""try:# Import pandas here to use within this functionimport pandas as pd import numpy as np stats ={}# 确保数据是数值型for i inrange(1,7):iff'red{i}'in data.columns: data[f'red{i}']= pd.to_numeric(data[f'red{i}'], errors='coerce')if'blue'in data.columns: data['blue']= pd.to_numeric(data['blue'], errors='coerce')# 填充可能的NaN值 - 修复链式赋值警告for i inrange(1,7):iff'red{i}'in data.columns:# 使用推荐的方式替代 inplace=True data[f'red{i}']= data[f'red{i}'].fillna(data[f'red{i}'].median())if'blue'in data.columns:# 使用推荐的方式替代 inplace=True data['blue']= data['blue'].fillna(data['blue'].median())# 红球分析 red_balls =[]for i inrange(1,7):iff'red{i}'in data.columns: red_balls.extend(data[f'red{i}'].values)# 计算所有红球出现次数 red_counts = pd.Series(red_balls).value_counts().sort_index() red_probs =(red_counts / red_counts.sum()).sort_values(ascending=False)# 近期分析(最近100期) recent_data = data.tail(min(100,len(data))) recent_red =[]for i inrange(1,7):iff'red{i}'in recent_data.columns: recent_red.extend(recent_data[f'red{i}'].values) recent_red_counts = pd.Series(recent_red).value_counts() recent_red_probs =(recent_red_counts / recent_red_counts.sum()).sort_values(ascending=False)# 蓝球分析if'blue'in data.columns: blue_counts = data['blue'].value_counts().sort_index() blue_probs =(blue_counts / blue_counts.sum()).sort_values(ascending=False) recent_blue_counts = recent_data['blue'].value_counts() recent_blue_probs =(recent_blue_counts / recent_blue_counts.sum()).sort_values(ascending=False)else:# 创建默认值 blue_probs = pd.Series([1/16]*16, index=range(1,17)).sort_values(ascending=False) recent_blue_probs = blue_probs.copy() stats['red']={'all_time_top10':dict(list(red_probs.head(10).items())),'recent_top10':dict(list(recent_red_probs.head(10).items())),'all_time_sorted':dict(red_probs.sort_index())} stats['blue']={'all_time_top10':dict(list(blue_probs.head(10).items())),'recent_top10':dict(list(recent_blue_probs.head(10).items())),'all_time_sorted':dict(blue_probs.sort_index())}return stats except Exception as e:print(f"统计分析出错: {str(e)}")# 创建一个默认统计结果 default_stats ={'red':{'all_time_top10':{i:1/33for i inrange(1,11)},'recent_top10':{i:1/33for i inrange(1,11)},'all_time_sorted':{i:1/33for i inrange(1,34)}},'blue':{'all_time_top10':{i:1/16for i inrange(1,11)},'recent_top10':{i:1/16for i inrange(1,11)},'all_time_sorted':{i:1/16for i inrange(1,17)}}}return default_stats defcreate_lstm_model(self, input_shape):"""创建LSTM模型"""# Import TensorFlow components when the model is actually being createdimport tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, LSTM, BatchNormalization, Dropout, Input from tensorflow.keras.regularizers import l2 from tensorflow.keras.optimizers import Adam model = Sequential([ Input(shape=input_shape), LSTM(512, return_sequences=True, kernel_regularizer=l2(0.01), recurrent_regularizer=l2(0.01), dropout=0.2, recurrent_dropout=0.2), BatchNormalization(), LSTM(256, return_sequences=True), BatchNormalization(), Dropout(0.3), LSTM(128), BatchNormalization(), Dropout(0.3), Dense(128, activation='relu', kernel_regularizer=l2(0.01)), Dense(64, activation='relu'), Dense(7, activation='sigmoid')]) model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001), metrics=['mae'])return model defcreate_gru_model(self, input_shape):"""创建GRU模型"""# Import TensorFlow components when the model is actually being createdimport tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, GRU, BatchNormalization, Dropout, Input, Bidirectional from tensorflow.keras.regularizers import l2 from tensorflow.keras.optimizers import Adam model = Sequential([ Input(shape=input_shape), Bidirectional(GRU(256, return_sequences=True, kernel_regularizer=l2(0.01), recurrent_regularizer=l2(0.01), dropout=0.2, recurrent_dropout=0.2)), BatchNormalization(), GRU(128, return_sequences=True), BatchNormalization(), Dropout(0.3), GRU(64), BatchNormalization(), Dropout(0.3), Dense(64, activation='relu'), Dense(32, activation='relu'), Dense(7, activation='sigmoid')]) model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001), metrics=['mae'])return model defcreate_hybrid_model(self, input_shape):"""创建混合模型"""# Import TensorFlow components when the model is actually being createdimport tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import(Dense, LSTM, BatchNormalization, Dropout, Input, Bidirectional, GRU, Conv1D, MaxPooling1D)from tensorflow.keras.regularizers import l2 from tensorflow.keras.optimizers import Adam # Import the AttentionLayer with TensorFlowglobal AttentionLayer from tensorflow.keras.layers import Layer # Redefine the class with proper TensorFlow inheritanceclassAttentionLayer(Layer):def__init__(self,**kwargs):super(AttentionLayer, self).__init__(**kwargs)defbuild(self, input_shape): self.W = self.add_weight(name='attention_weight', shape=(input_shape[-1],1), initializer='random_normal', trainable=True) self.b = self.add_weight(name='attention_bias', shape=(input_shape[1],1), initializer='zeros', trainable=True)super(AttentionLayer, self).build(input_shape)defcall(self, x): e = tf.tanh(tf.matmul(x, self.W)+ self.b) a = tf.nn.softmax(e, axis=1) output = x * a return tf.reduce_sum(output, axis=1) model = Sequential([ Input(shape=input_shape), Conv1D(64,3, activation='relu', padding='same'), MaxPooling1D(2), BatchNormalization(), Bidirectional(LSTM(256, return_sequences=True)), BatchNormalization(), GRU(128, return_sequences=True), AttentionLayer(), BatchNormalization(), Dropout(0.4), Dense(128, activation='relu'), Dense(64, activation='relu'), Dense(7, activation='sigmoid')]) model.compile(loss='mean_squared_error', optimizer=Adam(learning_rate=0.001), metrics=['mae'])return model deffix_date_column(self, data):"""修复日期列,确保它不会被转换为数值型"""# Import pandas here within the methodimport pandas as pd import numpy as np # 创建一个新的日期列,避免破坏原始数据# 首先确保日期列是字符串类型 data['日期']= data['日期'].astype(str)# 检查日期格式并纠正 valid_dates =[]for date_str in data['日期']:# 清理日期字符串,只保留数字和连字符 clean_date =''.join([c for c in date_str if c.isdigit()or c =='-'])# 确保日期格式是YYYY-MM-DDiflen(clean_date)>=10: clean_date = clean_date[:10]# 只取前10个字符 valid_dates.append(clean_date)else:# 无效日期使用占位符 valid_dates.append('2000-01-01')# 使用占位符# 用清理后的日期创建新列 data['clean_date']= valid_dates try:# 转换为日期时间格式 data['date']= pd.to_datetime(data['clean_date'], errors='coerce')# 检查是否有无效日期 invalid_dates = data['date'].isna().sum()if invalid_dates >0:print(f"警告: 发现{invalid_dates}个无效日期,已替换为NaT")# 如果所有日期无效,抛出异常进入替代方案if invalid_dates ==len(data):raise ValueError("所有日期转换均失败")# 手动添加时间特征,避免使用.dt属性# 检查date列是否正确转换为datetimeif pd.api.types.is_datetime64_any_dtype(data['date']):# 安全地使用.dt访问器 data['year']= data['date'].dt.year data['month']= data['date'].dt.month data['day']= data['date'].dt.day data['day_of_week']= data['date'].dt.dayofweek data['day_of_year']= data['date'].dt.dayofyear else:raise TypeError("date列不是datetime类型")except Exception as e:print(f"日期转换异常: {str(e)}")# 创建一个假的日期序列 date_range = pd.date_range(start='2020-01-01', periods=len(data)) data['date']= date_range # 基于生成的日期序列添加特征 data['year']= date_range.year data['month']= date_range.month data['day']= date_range.day data['day_of_week']= date_range.dayofweek data['day_of_year']= date_range.dayofyear print("使用生成的日期序列代替原始日期")return data defprepare_data(self, data):"""数据预处理和特征工程"""# Import needed libraries import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 如果没有date列,先修复日期if'date'notin data.columns: data = self.fix_date_column(data)# 添加时间特征try:# 确保date列是datetime类型ifnot pd.api.types.is_datetime64_any_dtype(data['date']):# 尝试转换date列为datetime data['date']= pd.to_datetime(data['date'], errors='coerce')# 检查转换后是否有效if data['date'].isna().all():raise ValueError("无法将date列转换为日期时间")# 现在安全地添加时间特征 data['year']= data['date'].dt.year data['month']= data['date'].dt.month data['day']= data['date'].dt.day data['day_of_week']= data['date'].dt.dayofweek data['day_of_year']= data['date'].dt.dayofyear except Exception as e:print(f"时间特征提取错误: {str(e)}")print("使用默认时间特征...")# 创建默认时间特征 date_range = pd.date_range(start='2020-01-01', periods=len(data)) data['date']= date_range data['year']= date_range.year data['month']= date_range.month data['day']= date_range.day data['day_of_week']= date_range.dayofweek data['day_of_year']= date_range.dayofyear # 明确排除非数值列 non_numeric_cols =['期号','日期','date','clean_date']# 添加统计特征for i inrange(1,7): data[f'red{i}_rolling_mean_10']= data[f'red{i}'].rolling(10).mean() data[f'red{i}_rolling_std_10']= data[f'red{i}'].rolling(10).std()# 添加组合特征 data['red_sum']= data[[f'red{i}'for i inrange(1,7)]].sum(axis=1) data['red_odd_count']= data[[f'red{i}'for i inrange(1,7)]].apply(lambda x: x %2).sum(axis=1) data['red_prime_count']= data[[f'red{i}'for i inrange(1,7)]].apply(lambda x: x.apply(is_prime)).sum(axis=1)# 添加滞后特征for lag in[1,2,3,5,10]:for i inrange(1,7): data[f'red{i}_lag{lag}']= data[f'red{i}'].shift(lag) data[f'blue_lag{lag}']= data['blue'].shift(lag)# 确保所有特征值为数值型并填充NaN值 numeric_cols =[col for col in data.columns if col notin non_numeric_cols]# 转换为数值类型并处理错误for col in numeric_cols:try: data[col]= pd.to_numeric(data[col], errors='coerce')except Exception as e:print(f"无法将列 {col} 转换为数值型: {str(e)}")# 填充NaN值 data[numeric_cols]= data[numeric_cols].fillna(data[numeric_cols].mean())# 标准化前,确保没有NaN值 data[numeric_cols]= np.nan_to_num(data[numeric_cols])# 标准化 self.scaler = MinMaxScaler() scaled_data = self.scaler.fit_transform(data[numeric_cols])return scaled_data, numeric_cols defgenerate_sequences(self, data, lookback):"""生成时间序列数据"""# Import numpy for array operationsimport numpy as np X, y =[],[]for i inrange(len(data)- lookback): X.append(data[i:i + lookback]) y.append(data[i + lookback,:7])# 只预测红球和蓝球return np.array(X), np.array(y)defenhanced_postprocessing(self, prediction):"""改进的后处理方法,增强稳健性"""# Import numpy for array operationsimport numpy as np try:# 首先确保预测结果不含NaN值 prediction = np.nan_to_num(prediction, nan=0.5)# 检查预测数据形状if prediction.shape[0]==0or prediction.shape[1]<7:print(f"警告: 预测结果形状不正确: {prediction.shape}")# 创建一个随机预测作为备选 random_preds = np.random.random((1,7)) red_pred = random_preds[0][:6] blue_pred = random_preds[0][6]else: red_pred = prediction[0][:6] blue_pred = prediction[0][6]# 处理红球 red_balls =[]for i inrange(6):# 再次检查并处理NaN值或无效值if np.isnan(red_pred[i])or red_pred[i]<0or red_pred[i]>1: red_pred[i]= np.random.random()# 使用随机值代替无效值# 将0-1之间的值映射到1-33的整数 ball =int(round(red_pred[i]*32+1)) ball =max(1,min(33, ball))# 确保在合法范围内# 避免重复 attempt =0while ball in red_balls and attempt <10:# 避免死循环if ball <33: ball +=1else: ball =max(1, ball -1) attempt +=1# 如果经过10次尝试后仍然有重复,生成一个不在当前列表中的随机号码if ball in red_balls: available =[num for num inrange(1,34)if num notin red_balls]if available: ball = np.random.choice(available) red_balls.append(ball)# 确保红球排序 red_balls =sorted(red_balls)# 处理蓝球# 再次检查并处理NaN值或无效值if np.isnan(blue_pred)or blue_pred <0or blue_pred >1: blue_pred = np.random.random()# 使用随机值代替无效值# 将0-1之间的值映射到1-16的整数 blue_ball =int(round(blue_pred *15+1)) blue_ball =max(1,min(16, blue_ball))# 确保在合法范围内return red_balls, blue_ball except Exception as e:print(f"后处理过程中出错: {str(e)}")# 生成随机预测作为备选 red_balls =sorted(np.random.choice(range(1,34),6, replace=False)) blue_ball = np.random.randint(1,17)return red_balls, blue_ball defrun(self):try:# Import pandas, numpy, sklearn, and tensorflow hereimport pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler from sklearn.model_selection import train_test_split import tensorflow as tf from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint print("开始预测流程...")# 1. 数据加载 - 优先使用预处理过的数据 self.progress.emit(5)if os.path.exists('data_processed.csv'):print("使用预处理好的数据文件") data = pd.read_csv('data_processed.csv')# 使用已预处理的数据时,调用特定于预测的特征添加方法try: scaled_data, feature_cols = self.add_prediction_features(data)except Exception as e:print(f"添加预测特征出错: {str(e)}")# 失败时尝试使用默认预处理 scaled_data, feature_cols = self.prepare_data(data)else:print("找不到预处理数据,使用原始数据") data = pd.read_csv('data.csv')# 确保数据为数值型,但排除日期列for col in data.columns:if col notin['期号','日期']:try: data[col]= pd.to_numeric(data[col], errors='coerce')except Exception as e:print(f"无法将列 {col} 转换为数值型: {str(e)}")# 填充可能的NaN值 numeric_cols =[col for col in data.columns if col notin['期号','日期']] data[numeric_cols]= data[numeric_cols].fillna(data[numeric_cols].mean())# 尝试为日期处理添加基本清理if'date'notin data.columns: data = self.fix_date_column(data)# 对原始数据进行完整特征工程 scaled_data, feature_cols = self.prepare_data(data)# 2. 统计分析 self.progress.emit(10)print("进行统计分析...")try: stats = self.calculate_enhanced_stats(data) self.stats_ready.emit(stats) self.red_stats = stats['red'] self.blue_stats = stats['blue']except Exception as e:print(f"统计分析出错,使用默认值: {str(e)}")# 创建默认统计信息 stats ={'red':{'all_time_top10':{i:1/33for i inrange(1,11)},'recent_top10':{i:1/33for i inrange(1,11)},'all_time_sorted':{i:1/33for i inrange(1,34)}},'blue':{'all_time_top10':{i:1/16for i inrange(1,11)},'recent_top10':{i:1/16for i inrange(1,11)},'all_time_sorted':{i:1/16for i inrange(1,17)}}} self.stats_ready.emit(stats) self.red_stats = stats['red'] self.blue_stats = stats['blue']# 3. 数据预处理 self.progress.emit(20)print("进行数据预处理...")try:# 确保scaled_data中没有NaN值 scaled_data = np.nan_to_num(scaled_data, nan=0.5)except Exception as e:print(f"数据预处理出错: {str(e)}")# 创建一个随机数据作为备选print("使用随机数据继续...")# 创建一个简单的随机数据代替 scaled_data = np.random.random((len(data),20))# 打印数据形状信息,便于调试print(f"处理后数据形状: {scaled_data.shape}")# 4. 划分训练集和测试集 self.progress.emit(30)print("划分训练集和测试集...")try: train_size =int(len(scaled_data)* self.train_ratio) train_data = scaled_data[:train_size] test_data = scaled_data[train_size:]# 打印训练集测试集大小print(f"训练集大小: {train_data.shape}, 测试集大小: {test_data.shape}")except Exception as e:print(f"数据集划分出错: {str(e)}")# 使用简单划分作为备选 mid_point =len(scaled_data)//2 train_data = scaled_data[:mid_point] test_data = scaled_data[mid_point:]# 5. 生成序列数据 self.progress.emit(40)print("生成序列数据...")try: X_train, y_train = self.generate_sequences(train_data, self.lookback) X_test, y_test = self.generate_sequences(test_data, self.lookback)# 再次确保训练数据不含NaN值 X_train = np.nan_to_num(X_train, nan=0.5) y_train = np.nan_to_num(y_train, nan=0.5) X_test = np.nan_to_num(X_test, nan=0.5) y_test = np.nan_to_num(y_test, nan=0.5)# 打印训练数据形状print(f"训练数据形状: X={X_train.shape}, y={y_train.shape}")print(f"测试数据形状: X={X_test.shape}, y={y_test.shape}")except Exception as e:print(f"序列数据生成出错: {str(e)}")# 创建简化的序列数据作为备选 feature_count = scaled_data.shape[1]iflen(scaled_data.shape)>1else10 X_train = np.random.random((10, self.lookback, feature_count)) y_train = np.random.random((10,7)) X_test = np.random.random((5, self.lookback, feature_count)) y_test = np.random.random((5,7))# 6. 创建并训练模型 self.progress.emit(50)print(f"创建{self.strategy}模型...")try:if self.strategy =='lstm': self.model = self.create_lstm_model((self.lookback, X_train.shape[2]))elif self.strategy =='gru': self.model = self.create_gru_model((self.lookback, X_train.shape[2]))else: self.model = self.create_hybrid_model((self.lookback, X_train.shape[2]))# 自定义回调函数用于更新进度classProgressCallback(tf.keras.callbacks.Callback):def__init__(self, progress_signal):super().__init__() self.progress_signal = progress_signal self.epoch_count =0defon_epoch_end(self, epoch, logs=None): self.epoch_count +=1 progress =50+(self.epoch_count / self.params['epochs'])*40 self.progress_signal.emit(int(progress))print("开始训练模型...")# 训练模型 history = self.model.fit( X_train, y_train, epochs=self.epochs, batch_size=self.batch_size, validation_data=(X_test, y_test), callbacks=[ EarlyStopping(monitor='val_loss', patience=20), ModelCheckpoint('best_model.h5', save_best_only=True), ProgressCallback(self.progress)], verbose=0)print("模型训练完成")except Exception as e:print(f"模型训练出错: {str(e)}")# 创建一个简单的模型作为替代print("使用简单模型替代...")# 创建一个简单的Dense模型 simple_model = tf.keras.Sequential([ tf.keras.layers.Flatten(input_shape=(self.lookback, X_train.shape[2])), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(7, activation='sigmoid')]) simple_model.compile(optimizer='adam', loss='mse') simple_model.fit(X_train, y_train, epochs=2, batch_size=32, verbose=0) self.model = simple_model # 8. 预测下一期 self.progress.emit(95)print("生成预测结果...")try: last_data = scaled_data[-self.lookback:] last_data = last_data[None,...]# 确保预测数据不含NaN值 last_data = np.nan_to_num(last_data, nan=0.5)# 检查预测数据的形状和是否包含NaN值print(f"预测数据形状: {last_data.shape}") nan_count = np.isnan(last_data).sum()if nan_count >0:print(f"警告: 预测数据中有{nan_count}个NaN值,已替换为0.5") prediction = self.model.predict(last_data)# 检查预测结果print(f"预测结果形状: {prediction.shape}")print(f"预测结果内容: {prediction}") nan_count = np.isnan(prediction).sum()if nan_count >0:print(f"警告: 预测结果中有{nan_count}个NaN值,已替换为0.5")# 确保预测结果不含NaN值 prediction = np.nan_to_num(prediction, nan=0.5)except Exception as e:print(f"预测过程出错: {str(e)}")# 创建随机预测作为替代 prediction = np.random.random((1,7))# 9. 后处理try: red_balls, blue_ball = self.enhanced_postprocessing(prediction)print(f"最终预测号码 - 红球: {red_balls}, 蓝球: {blue_ball}")except Exception as e:print(f"后处理出错: {str(e)}")# 生成随机号码作为备选 red_balls =sorted(np.random.choice(range(1,34),6, replace=False)) blue_ball = np.random.randint(1,17)print(f"使用随机号码 - 红球: {red_balls}, 蓝球: {blue_ball}")# 11. 生成最终结果try: result = self.generate_result(red_balls, blue_ball, stats)except Exception as e:print(f"结果生成出错: {str(e)}")# 创建基本结果字符串 result =f"=== 红篮球优化预测结果 ===\n\n预测号码:\n红球: {', '.join(map(str, red_balls))}\n蓝球: {blue_ball}\n\n" result +=f"=== 模型参数 ===\n模型类型: {self.get_model_type()}\n训练轮次: {self.epochs}\n" self.progress.emit(100)if self.model isnotNone: self.model_trained.emit(self.model)print("预测完成,发送结果...") self.finished.emit(result)except Exception as e:import traceback error_details = traceback.format_exc()print(f"预测失败,详细错误: {error_details}")# 即使失败,也要返回一些结果# 生成随机结果import numpy as np red_balls =sorted(np.random.choice(range(1,34),6, replace=False)) blue_ball = np.random.randint(1,17)# 创建简单的结果文本 result =f"=== 预测过程中出错,显示随机结果 ===\n\n" result +=f"错误信息: {str(e)}\n\n" result +=f"随机预测号码:\n红球: {', '.join(map(str, red_balls))}\n蓝球: {blue_ball}\n\n" result +=f"=== 模型参数 ===\n模型类型: {self.get_model_type()}\n训练轮次: {self.epochs}\n" self.finished.emit(result)defadd_prediction_features(self, data):"""添加特定于预测的特征,用于已预处理的数据"""import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 检查date列是否正确存在if'date'in data.columns:# 确保date列是datetime类型ifnot pd.api.types.is_datetime64_any_dtype(data['date']):try:# 尝试转换为datetime data['date']= pd.to_datetime(data['date'], errors='coerce')# 检查转换是否成功if data['date'].isna().all():raise ValueError("date列无法转换为有效日期")# 添加时间特征 data['year']= data['date'].dt.year data['month']= data['date'].dt.month data['day']= data['date'].dt.day data['day_of_week']= data['date'].dt.dayofweek data['day_of_year']= data['date'].dt.dayofyear except Exception as e:print(f"特征转换中的日期错误: {str(e)}")# 生成替代日期特征 date_range = pd.date_range(start='2020-01-01', periods=len(data)) data['date']= date_range data['year']= date_range.year data['month']= date_range.month data['day']= date_range.day data['day_of_week']= date_range.dayofweek data['day_of_year']= date_range.dayofyear # 添加统计特征for i inrange(1,7): data[f'red{i}_rolling_mean_10']= data[f'red{i}'].rolling(10).mean() data[f'red{i}_rolling_std_10']= data[f'red{i}'].rolling(10).std()# 添加组合特征 data['red_sum']= data[[f'red{i}'for i inrange(1,7)]].sum(axis=1) data['red_odd_count']= data[[f'red{i}'for i inrange(1,7)]].apply(lambda x: x %2).sum(axis=1) data['red_prime_count']= data[[f'red{i}'for i inrange(1,7)]].apply(lambda x: x.apply(is_prime)).sum(axis=1)# 添加滞后特征for lag in[1,2,3,5,10]:for i inrange(1,7): data[f'red{i}_lag{lag}']= data[f'red{i}'].shift(lag) data[f'blue_lag{lag}']= data['blue'].shift(lag)# 明确排除非数值列 non_numeric_cols =['期号','日期','date','clean_date']# 确保所有特征值为数值型 numeric_cols =[col for col in data.columns if col notin non_numeric_cols]for col in numeric_cols:try: data[col]= pd.to_numeric(data[col], errors='coerce')except Exception as e:print(f"无法将列 {col} 转换为数值型: {str(e)}")# 填充NaN值 data[numeric_cols]= data[numeric_cols].fillna(data[numeric_cols].mean())# 标准化前,确保没有NaN值 data[numeric_cols]= np.nan_to_num(data[numeric_cols])# 标准化 self.scaler = MinMaxScaler() scaled_data = self.scaler.fit_transform(data[numeric_cols])return scaled_data, numeric_cols defgenerate_result(self, red_balls, blue_ball, stats):"""生成预测结果""" result ="=== 红篮球优化预测结果 ===\n\n" result +=f"预测号码:\n红球: {', '.join(map(str, red_balls))}\n蓝球: {blue_ball}\n\n" result +="=== 模型参数 ===\n" result +=f"模型类型: {self.get_model_type()}\n" result +=f"网络结构: {self.get_model_architecture()}\n" result +=f"训练集比例: {int(self.train_ratio *100)}%\n" result +=f"训练轮次: {self.epochs}\n" result +=f"批量大小: {self.batch_size}\n" result +=f"回溯期数: {self.lookback}\n"return result defformat_prediction_result(self, result):"""将预测结果格式化为HTML"""# 解析预测结果 lines = result.split('\n')# 提取预测号码 red_balls =[] blue_ball =None# 增强的解析逻辑,支持更多格式和更好的错误处理try:# 尝试提取红球号码for line in lines:# 支持中英文冒号格式if'红球:'in line or'红球:'in line:try:# 根据冒号类型提取数字部分 nums_text = line.split(':',1)[1].strip()if':'in line else line.split(':',1)[1].strip()# 仅提取有效数字,过滤非数字字符 red_balls =[int(num.strip())for num in nums_text.split(',')if num.strip().isdigit()]except Exception as e:print(f"提取红球号码时出错: {str(e)}")# 支持中英文冒号格式elif'蓝球:'in line or'蓝球:'in line:try:# 根据冒号类型提取数字部分 nums_text = line.split(':',1)[1].strip()if':'in line else line.split(':',1)[1].strip()# 确保转换为整数前是有效数字if nums_text.strip().isdigit(): blue_ball =int(nums_text.strip())except Exception as e:print(f"提取蓝球号码时出错: {str(e)}")# 检查提取结果的有效性ifnot red_balls orlen(red_balls)!=6:print(f"警告: 提取的红球数量不正确: {len(red_balls)}")# 如果没有提取到6个红球,生成随机球号补充import numpy as np existing_reds =set(red_balls) remaining_count =6-len(existing_reds)if remaining_count >0: available_numbers =[n for n inrange(1,34)if n notin existing_reds] additional_reds =sorted(np.random.choice(available_numbers, remaining_count, replace=False)) red_balls =sorted(list(existing_reds)+ additional_reds)print(f"已补充红球号码: {red_balls}")if blue_ball isNone:print("警告: 未提取到蓝球号码")# 如果没有提取到蓝球,生成随机蓝球import numpy as np blue_ball = np.random.randint(1,17)print(f"已生成随机蓝球号码: {blue_ball}")except Exception as e:# 在整个解析过程出错时的全局异常处理print(f"解析预测结果时出现严重错误: {str(e)}")# 生成完全随机的号码import numpy as np red_balls =sorted(np.random.choice(range(1,34),6, replace=False)) blue_ball = np.random.randint(1,17)print(f"已生成完全随机的号码 - 红球: {red_balls}, 蓝球: {blue_ball}")# 构建HTML html =f""" <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>红篮球预测结果</title> <style> {self.shared_css} /* 预测结果特定样式 */ .prediction-card {{ background-color: #ffffff; border-radius: 12px; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); padding: 25px; margin-bottom: 30px; text-align: center; }} .param-section {{ background-color: #ffffff; border-radius: 12px; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); padding: 25px; margin-bottom: 30px; position: relative; overflow: hidden; }} .param-section::before {{ content: ''; position: absolute; left: 0; top: 0; height: 100%; width: 4px; background-color: #4361ee; }} .param {{ display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; margin: 10px 0; background-color: #f8fafc; border-radius: 8px; }} .param:hover {{ background-color: #f1f5f9; }} .param-name {{ font-weight: 600; color: #1e293b; }} .param-value {{ color: #64748b; }} /* 调整球的大小 */ .ball {{ width: 65px; height: 65px; line-height: 65px; font-size: 26px; margin: 0 3px; }} </style> </head> <body> <div> <h1>红篮球预测结果</h1> <div> <div>预测号码</div> <div> """# 添加红球if red_balls:for i, ball inenumerate(red_balls): html +=f'<span>{ball}</span>'if i <len(red_balls)-1: html +=f'<span></span>'else: html +=f'<span>|</span>'# 添加蓝球if blue_ball: html +=f'<span>{blue_ball}</span>' html +=""" </div> </div> <div> <div>模型参数</div> """# 添加模型参数信息,更灵活的解析 parameter_added =False# 跟踪是否添加了任何参数for line in lines:# 更灵活的参数匹配 parameter_keywords =["模型类型","网络结构","训练集比例","训练轮次","批量大小","回溯期数"]for keyword in parameter_keywords:if keyword in line and(":"in line or":"in line):try:# 支持中英文冒号 parts = line.split(":",1)if":"in line else line.split(":",1)iflen(parts)==2: param_name = parts[0].strip() param_value = parts[1].strip() html +=f''' <div> <span>{param_name}</span> <span>{param_value}</span> </div> ''' parameter_added =Trueexcept Exception as e:print(f"解析参数行时出错: {str(e)}")# 如果没有添加任何参数,添加默认参数信息ifnot parameter_added: html +=f''' <div> <span>模型类型</span> <span>神经网络预测模型</span> </div> <div> <span>说明</span> <span>参数解析异常,部分信息可能未显示</span> </div> ''' html +=""" </div> <footer> <p>© 2025 红篮球预测系统 | 纯属娱乐,请理性buy</p> </footer> </div> </body> </html> """return html classMainWindow(QMainWindow):def__init__(self):super().__init__() self.setWindowTitle("红篮球预测系统 - 多维度神经网络模型 开发:Killerzeno 美化:nobiyou by 吾爱破解") self.setGeometry(100,100,1280,900) self.setStyleSheet(""" QMainWindow { background-color: #f5f7fa; } QTabWidget::pane { border: 1px solid #4a6cf7; border-top-left-radius: 0px; border-top-right-radius: 5px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; background-color: #f5f7fa; margin: 0px; padding: 5px; } QTabBar::tab { background-color: #e1e5eb; color: #5e6470; min-width: 100px; min-height: 30px; padding: 5px 15px; border-top-left-radius: 5px; border-top-right-radius: 5px; margin-right: 2px; font-weight: bold; } QTabBar::tab:selected { background-color: #4a6cf7; color: white; } QGroupBox { background-color: white; border-radius: 10px; border: none; margin-top: 15px; font-weight: bold; padding: 15px; color: #2d3748; } QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 10px; color: #4a6cf7; font-size: 14px; } QPushButton { background-color: #4a6cf7; color: white; border: none; border-radius: 5px; padding: 8px 15px; font-weight: bold; min-height: 30px; } QPushButton:hover { background-color: #3c5fe0; } QPushButton:pressed { background-color: #2d46bd; } QPushButton:disabled { background-color: #a0aec0; } QProgressBar { border: none; background-color: #e2e8f0; border-radius: 5px; text-align: center; color: white; font-weight: bold; min-height: 25px; } QProgressBar::chunk { background-color: #4a6cf7; border-radius: 5px; } QTextEdit, QLabel { background-color: white; border-radius: 5px; padding: 5px; border: 1px solid #e2e8f0; } QSpinBox, QDoubleSpinBox, QComboBox { border: 1px solid #e2e8f0; border-radius: 4px; padding: 5px; background-color: white; min-height: 25px; } QComboBox::drop-down { border: none; width: 20px; } """)# 设置图标 icon_path = resource_path('logo.ico')if os.path.exists(icon_path): self.setWindowIcon(QIcon(icon_path)) self.stats_data =None self.model =None# 设置Web通道 self.channel = QWebChannel() self.handler = PageNavigator(self) self.channel.registerObject("pyObj", self.handler)# 共享CSS样式 self.shared_css =""" @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); :root { --primary-color: #4361ee; --red-ball-color: #e63946; --blue-ball-color: #0077b6; --background-color: #fff; --card-background: #ffffff; --text-primary: #1e293b; --text-secondary: #64748b; --border-radius: 12px; --box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08); } * { margin: 0; padding: 0; box-sizing: border-box; } /* Custom Scrollbar Styling */ ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; } ::-webkit-scrollbar-thumb { background: linear-gradient(to bottom, #4a6cf7, #3a0ca3); border-radius: 10px; border: 2px solid #f1f1f1; } ::-webkit-scrollbar-thumb:hover { background: linear-gradient(to bottom, #3a0ca3, #4a6cf7); } /* For Firefox */ html { scrollbar-width: thin; scrollbar-color: #4a6cf7 #f1f1f1; } body { font-family: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: var(--background-color); color: var(--text-primary); padding: 20px; line-height: 1.6; } .container { max-width: 1200px; margin: 0 auto; } h1 { color: var(--text-primary); font-size: 28px; font-weight: 700; margin-bottom: 20px; text-align: center; position: relative; } h1:after { content: ''; position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%); width: 100px; height: 4px; background: linear-gradient(to right, var(--red-ball-color), var(--blue-ball-color)); border-radius: 4px; } .card { background-color: var(--card-background); border-radius: var(--border-radius); box-shadow: var(--box-shadow); padding: 25px; margin-bottom: 30px; } .section-title { font-weight: 700; color: var(--text-primary); font-size: 20px; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #e2e8f0; } /* Ball styling */ .ball-container { display: flex; justify-content: center; align-items: center; flex-wrap: wrap; gap: 10px; } .ball { width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; border-radius: 50%; font-weight: 700; font-size: 20px; position: relative; overflow: hidden; } .red-ball { background: linear-gradient(135deg, #ff6b6b, var(--red-ball-color)); color: white; box-shadow: 0 4px 8px rgba(230, 57, 70, 0.4); } .blue-ball { background: linear-gradient(135deg, #48cae4, var(--blue-ball-color)); color: white; box-shadow: 0 4px 8px rgba(0, 119, 182, 0.4); } .ball::before { content: ''; position: absolute; top: -5px; left: -5px; width: 15px; height: 15px; background-color: rgba(255, 255, 255, 0.3); border-radius: 50%; } .separator { color: var(--text-secondary); font-weight: 300; font-size: 18px; margin: 0 5px; } footer { text-align: center; margin-top: 30px; color: var(--text-secondary); font-size: 14px; } /* Responsive adjustments */ @media (max-width: 768px) { .ball { width: 40px; height: 40px; font-size: 16px; } h1 { font-size: 24px; } } """ self.initUI()definitUI(self): main_widget = QWidget() layout = QVBoxLayout()# 创建标签页 self.tabs = QTabWidget()# 第一页 - 数据获取功能 data_tab = QWidget() data_layout = QHBoxLayout()# 使用水平布局,左右分栏# 左侧 - 操作区 data_left_widget = QWidget() data_left_layout = QVBoxLayout()# 数据获取部分 data_group = QGroupBox("数据获取操作") data_control_layout = QVBoxLayout() self.fetch_btn = QPushButton("获取最新红篮球数据") self.fetch_btn.clicked.connect(self.fetch_data) data_control_layout.addWidget(self.fetch_btn) self.fetch_progress = QProgressBar() data_control_layout.addWidget(self.fetch_progress) self.data_status = QTextEdit() self.data_status.setReadOnly(True) self.data_status.setFixedHeight(200) data_control_layout.addWidget(self.data_status) data_group.setLayout(data_control_layout) data_left_layout.addWidget(data_group) data_left_layout.addStretch(1)# 添加弹性空间 data_left_widget.setLayout(data_left_layout)# 右侧 - 数据展示区 data_right_widget = QWidget() data_right_layout = QVBoxLayout() data_display_group = QGroupBox("红篮球数据展示") data_display_layout = QVBoxLayout()# 替换QTextEdit为QWebEngineView self.data_display = QWebEngineView() data_display_layout.addWidget(self.data_display)# 初始化分页变量 self.current_page =1 self.items_per_page =5 self.total_data =None# 添加分页控制 - 隐藏显示,使用JS中的按钮 pagination_widget = QWidget() pagination_layout = QHBoxLayout() pagination_layout.setContentsMargins(0,0,0,0)# 网页风格分页按钮 - 隐藏显示 self.page_prev_btn = QPushButton("上一页") self.page_prev_btn.setVisible(False)# 隐藏按钮 self.page_prev_btn.clicked.connect(lambda: self.change_data_page(-1)) self.page_label = QLabel("第1页") self.page_label.setVisible(False)# 隐藏标签 self.page_next_btn = QPushButton("下一页") self.page_next_btn.setVisible(False)# 隐藏按钮  self.page_next_btn.clicked.connect(lambda: self.change_data_page(1)) pagination_layout.addWidget(self.page_prev_btn) pagination_layout.addWidget(self.page_label) pagination_layout.addWidget(self.page_next_btn) pagination_widget.setLayout(pagination_layout) pagination_widget.setVisible(False)# 隐藏整个区域 data_display_layout.addWidget(pagination_widget) data_display_group.setLayout(data_display_layout) data_right_layout.addWidget(data_display_group) data_right_widget.setLayout(data_right_layout)# 添加左右两部分到数据页面 data_layout.addWidget(data_left_widget,1)# 左侧占1份宽度 data_layout.addWidget(data_right_widget,2)# 右侧占2份宽度 data_tab.setLayout(data_layout)# 第二页 - 预测功能 predict_tab = QWidget() predict_layout = QHBoxLayout()# 使用水平布局,左右分栏# 左侧 - 操作区 predict_left_widget = QWidget() predict_left_layout = QVBoxLayout()# 预测参数设置 param_group = QGroupBox("模型参数设置") param_layout = QFormLayout()# 训练集比例 self.train_ratio = QDoubleSpinBox() self.train_ratio.setRange(50,95) self.train_ratio.setValue(75) self.train_ratio.setSingleStep(5) self.train_ratio.setSuffix("%") param_layout.addRow("训练集比例 (推荐70-80%):", self.train_ratio)# 训练轮次 self.epochs = QSpinBox() self.epochs.setRange(50,1000) self.epochs.setValue(200) param_layout.addRow("训练轮次 (推荐100-300):", self.epochs)# 批量大小 self.batch_size = QSpinBox() self.batch_size.setRange(16,128) self.batch_size.setValue(32) param_layout.addRow("批量大小 (推荐32-64):", self.batch_size)# 回溯期数 self.lookback = QSpinBox() self.lookback.setRange(5,30) self.lookback.setValue(15) param_layout.addRow("回溯期数 (推荐10-20):", self.lookback)# 预测策略 self.strategy = QComboBox() self.strategy.addItems(['LSTM','GRU','混合模型']) self.strategy.setCurrentText('LSTM') param_layout.addRow("预测策略:", self.strategy) param_group.setLayout(param_layout) predict_left_layout.addWidget(param_group)# 添加预测策略说明 strategy_info_group = QGroupBox("预测策略说明") strategy_info_layout = QVBoxLayout() strategy_info_text = QTextEdit() strategy_info_text.setReadOnly(True) strategy_info_text.setStyleSheet(""" background-color: #f8f9fa; border: 1px solid #e2e8f0; /* 添加与全局样式一致的滚动条样式 */ QScrollBar:vertical { width: 10px; background: #f1f1f1; border-radius: 5px; } QScrollBar::handle:vertical { background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #4a6cf7, stop:1 #3a0ca3); border-radius: 5px; min-height: 20px; border: 2px solid #f1f1f1; } QScrollBar::handle:vertical:hover { background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 #3a0ca3, stop:1 #4a6cf7); } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } """) strategy_info_text.setFixedHeight(150)# 设置固定高度为150像素 strategy_info_text.setHtml(""" <style> body { font-family: Arial, sans-serif; margin: 5px; color: #333; } h4 { margin-top: 10px; margin-bottom: 5px; color: #3a0ca3; } p { margin-top: 0; margin-bottom: 8px; line-height: 1.4; } .highlight { background-color: #f0f4ff; padding: 2px 4px; border-radius: 3px; font-weight: bold; } </style> <h4>LSTM 模型</h4> <p>长短期记忆网络,专注于<span>捕捉长期依赖关系</span>,对连续性强的数据表现良好。适合历史走势明显的红球预测。</p> <h4>GRU 模型</h4> <p>门控循环单元网络,比LSTM<span>更轻量快速</span>,双向结构可同时考虑过去和未来信息。适合低延迟和资源有限环境。</p> <h4>混合模型</h4> <p>结合CNN、LSTM、GRU和注意力机制,<span>提取多层次特征</span>,性能最强但训练最慢。适合寻求最高预测精度的场景。</p> """) strategy_info_layout.addWidget(strategy_info_text) strategy_info_group.setLayout(strategy_info_layout) predict_left_layout.addWidget(strategy_info_group)# 预测按钮 self.predict_btn = QPushButton("开始预测") self.predict_btn.clicked.connect(self.start_predict) predict_left_layout.addWidget(self.predict_btn)# 预测进度 self.predict_progress = QProgressBar() predict_left_layout.addWidget(self.predict_progress) predict_left_layout.addStretch(1)# 添加弹性空间 predict_left_widget.setLayout(predict_left_layout)# 右侧 - 结果展示区 predict_right_widget = QWidget() predict_right_layout = QVBoxLayout() result_group = QGroupBox("预测结果") result_layout = QVBoxLayout() self.result_display = QWebEngineView() result_layout.addWidget(self.result_display) result_group.setLayout(result_layout) predict_right_layout.addWidget(result_group) predict_right_widget.setLayout(predict_right_layout)# 添加左右两部分到预测页面 predict_layout.addWidget(predict_left_widget,1)# 左侧占1份宽度 predict_layout.addWidget(predict_right_widget,2)# 右侧占2份宽度 predict_tab.setLayout(predict_layout)# 第三页 - 统计分析 stats_tab = QWidget() stats_layout = QVBoxLayout()# 统计结果显示 - 使用HTML气泡图 stats_group = QGroupBox("号码频率统计分析") stats_layout_inner = QVBoxLayout() self.stats_display = QWebEngineView() stats_layout_inner.addWidget(self.stats_display) stats_group.setLayout(stats_layout_inner) stats_layout.addWidget(stats_group) stats_tab.setLayout(stats_layout)# 添加标签页 self.tabs.addTab(data_tab,"数据获取") self.tabs.addTab(predict_tab,"开始预测") self.tabs.addTab(stats_tab,"统计分析")# 添加到主布局 layout.addWidget(self.tabs) main_widget.setLayout(layout) self.setCentralWidget(main_widget)# 初始化所有WebEngineView的WebChannel self.data_display.page().setWebChannel(self.channel) self.result_display.page().setWebChannel(self.channel) self.stats_display.page().setWebChannel(self.channel)# 添加状态栏 self.statusBar().showMessage("就绪")defchange_data_page(self, direction):"""切换数据显示页"""if self.total_data isNoneorlen(self.total_data)==0:return# Import pandas for this methodimport pandas as pd new_page = self.current_page + direction max_page =(len(self.total_data)+ self.items_per_page -1)// self.items_per_page if1<= new_page <= max_page: self.current_page = new_page # 计算当前页的数据范围 start_idx =(self.current_page -1)* self.items_per_page end_idx =min(start_idx + self.items_per_page,len(self.total_data))# 显示当前页的数据 self.display_styled_data(self.total_data.iloc[start_idx:end_idx])deffetch_data(self):ifnot self.check_internet_connection(): QMessageBox.warning(self,"警告","无法连接到互联网,请检查网络连接!")return self.fetch_btn.setEnabled(False) self.data_status.append("正在获取数据...") self.fetch_progress.setValue(0) self.fetcher = DataFetcher() self.fetcher.finished.connect(self.on_fetch_finished) self.fetcher.progress.connect(self.fetch_progress.setValue) self.fetcher.start()defcheck_internet_connection(self):"""检查网络连接"""try:# Import requests only when neededimport requests requests.get('http://www.baidu.com', timeout=5)returnTrueexcept:returnFalsedefon_fetch_finished(self, message): self.fetch_btn.setEnabled(True) self.data_status.append(message) self.data_status.append("="*50) self.statusBar().showMessage("数据获取完成")# 在数据获取完成后显示数据try:if os.path.exists('data.csv'):# Import pandas here to read the CSVimport pandas as pd # 读取数据并预处理 df = pd.read_csv('data.csv') df = self.preprocess_data(df)# 存储处理好的数据 df.to_csv('data_processed.csv', index=False, encoding='utf-8-sig')# 存储总数据 self.total_data = df # 重置页码 self.current_page =1# 显示第一页 self.display_styled_data(df.head(self.items_per_page))# 更新分页按钮状态 self.page_prev_btn.setEnabled(False)# 第一页,禁用上一页按钮iflen(df)> self.items_per_page: self.page_next_btn.setEnabled(True)else: self.page_next_btn.setEnabled(False)except Exception as e: error_html =f""" <html><body> <h1>数据加载错误</h1> <p>读取数据失败:{str(e)}</p> </body></html> """ self.data_display.setHtml(error_html)# 禁用分页按钮 self.page_prev_btn.setEnabled(False) self.page_next_btn.setEnabled(False)defcalculate_next_draw_date(self, last_draw_date=None):"""计算下一期开奖时间,双色球通常每周二、四、日开奖"""import pandas as pd from datetime import datetime, timedelta # 定义开奖日为周二(1)、周四(3)和周日(6) draw_days =[1,3,6]# 对应星期二、星期四和星期日try:# 始终使用当前日期作为参考点 current_date = datetime.now()# 获取当前日期的星期几 (0-6, 0是星期一) current_weekday = current_date.weekday()# 计算到下一个开奖日的天数 days_until_next_draw =min((day - current_weekday)%7for day in draw_days)# 如果当天就是开奖日,且已经过了开奖时间(通常为21:15),则找下一个开奖日if days_until_next_draw ==0and current_date.hour >=21and current_date.minute >=15: days_until_next_draw =min((day - current_weekday)%7for day in draw_days if day != current_weekday)or7# 计算下一期开奖日期 next_draw_date = current_date + timedelta(days=days_until_next_draw)# 设置开奖时间为21:15 next_draw_date = next_draw_date.replace(hour=21, minute=15, second=0)# 格式化日期 formatted_date = next_draw_date.strftime("%Y年%m月%d日 %H:%M") weekday_names =["星期一","星期二","星期三","星期四","星期五","星期六","星期日"] weekday = weekday_names[next_draw_date.weekday()]return{"date": formatted_date,"weekday": weekday,"datetime": next_draw_date }except Exception as e:print(f"计算下一期开奖时间出错: {str(e)}")# 出错时返回占位信息return{"date":"敬请期待","weekday":"","datetime": datetime.now()+ timedelta(days=2)}defdisplay_styled_data(self, data):"""显示带有样式的红篮球数据"""# Import pandas to work with the dataframeimport pandas as pd # 获取当前页码和总页数 max_page =(len(self.total_data)+ self.items_per_page -1)// self.items_per_page if self.total_data isnotNoneelse1# 计算下一期开奖时间 next_draw = self.calculate_next_draw_date() html =f""" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>红篮球历史数据</title> <script src="qrc:///qtwebchannel/qwebchannel.js"></script> <script> // 设置Web通道,连接Python对象 var pyObj; window.onload = function() {{ new QWebChannel(qt.webChannelTransport, function(channel) {{ pyObj = channel.objects.pyObj; }}); }} // 分页函数 function prevPage() {{ if (pyObj) {{ pyObj.prevPage(); }} else {{ console.error("Python对象未加载"); }} }} function nextPage() {{ if (pyObj) {{ pyObj.nextPage(); }} else {{ console.error("Python对象未加载"); }} }} </script> <style> {self.shared_css} /* 数据页面特定样式 */ header {{ text-align: center; margin-bottom: 15px; }} .header-desc {{ color: var(--text-secondary); font-size: 1.1rem; max-width: 600px; margin: 20px auto 0; }} .next-draw-info {{ margin-top: 15px; padding: 10px 20px; background: linear-gradient(135deg, #f8f9fa, #e9ecef); border-radius: 50px; display: inline-block; font-size: 1rem; color: #333; border: 1px solid #dee2e6; box-shadow: 0 2px 5px rgba(0,0,0,0.05); }} .next-draw-info strong {{ color: #e63946; }} .data-card {{ background-color: var(--card-background); border-radius: var(--border-radius); box-shadow: var(--box-shadow); overflow: hidden; margin-bottom: 30px; }} table {{ width: 100%; border-collapse: collapse; }} th, td {{ padding: 10px 5px; text-align: center; }} th {{ background: linear-gradient(to right, var(--primary-color), #3a0ca3); color: white; font-weight: 600; font-size: 1rem; letter-spacing: 0.5px; text-transform: uppercase; }} tr:nth-child(even) {{ background-color: rgba(243, 244, 246, 0.7); }} tr:hover {{ background-color: rgba(224, 231, 255, 0.5); transition: all 0.3s ease; }} .issue {{ font-weight: 600; font-size: 1rem; color: var(--text-primary); }} .date {{ font-size: 0.95rem; color: var(--text-secondary); }} .pagination {{ display: flex; justify-content: center; align-items: center; padding: 15px 0 25px 0; }} .pagination-button {{ background-color: #f2f3f5; color: #4361ee; border: 1px solid #e2e8f0; border-radius: 3px; padding: 6px 15px; margin: 0 5px; font-size: 14px; cursor: pointer; transition: all 0.2s ease; }} .pagination-button:hover {{ background-color: #e2e8f0; }} .pagination-current {{ background-color: #4361ee; color: white; border-radius: 3px; padding: 6px 15px; margin: 0 5px; font-size: 14px; }} </style> </head> <body> <div> <header> <h1>红篮球历史数据</h1> <p>查看历史开奖结果,分析走势,把握规律</p> <div> 下期开奖:<strong>{next_draw["date"]}{next_draw["weekday"]}</strong> </div> </header> <div> <table> <thead> <tr> <th>期号</th> <th>日期</th> <th>开奖号码</th> </tr> </thead> <tbody> """# 添加数据行for _, row in data.iterrows(): html +=f""" <tr> <td>第{row['期号']}期</td> <td>{row['日期']}</td> <td> <div> """# 添加红球for i inrange(1,7): html +=f'<div>{int(row[f"red{i}"])}</div>'if i <6: html +=f'<span></span>'else: html +=f'<span>|</span>'# 添加蓝球 html +=f'<div>{int(row["blue"])}</div>' html +=""" </div> </td> </tr> """# 添加分页控件 html +=""" </tbody> </table> <!-- 添加网页版分页 --> <div> """# 动态生成上一页按钮if self.current_page >1: html +=f'<button>上一页</button>'else: html +=f'<button disabled>上一页</button>'# 当前页码 html +=f'<span>第{self.current_page}页</span>'# 动态生成下一页按钮if self.current_page < max_page: html +=f'<button>下一页</button>'else: html +=f'<button disabled>下一页</button>' html +=""" </div> </div> <footer> <p>© 2025 红篮球数据统计 | 仅供参考,请理性buy</p> </footer> </div> </body> </html> """# 设置HTML内容 self.data_display.setHtml(html)# 连接Web通道 self.data_display.page().setWebChannel(self.channel)defstart_predict(self):ifnot os.path.exists('data.csv'): QMessageBox.warning(self,"警告","请先获取数据!")return self.predict_btn.setEnabled(False)# 创建一个加载动画HTML - 保留红篮球的跳动动画 loading_html =f""" <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>加载中</title> <style> {self.shared_css} body {{ text-align: center; }} .loader-container {{ display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 80px; background-color: white; border-radius: 16px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08); width: 90%; max-width: 500px; margin: 0 auto; position: relative; }} .loader {{ position: relative; width: 140px; height: 140px; margin: 20px auto; }} .lottery-balls {{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 120px; height: 120px; }} @keyframes bounce {{ 0% {{ transform: translateY(0); }} 100% {{ transform: translateY(-20px); }} }} .ball:nth-child(1) {{ top: 10px; left: 10px; animation: bounce 1.5s ease-in-out infinite alternate; animation-delay: 0s; }} .ball:nth-child(2) {{ top: 10px; right: 10px; animation: bounce 1.5s ease-in-out infinite alternate; animation-delay: 0.2s; }} .ball:nth-child(3) {{ bottom: 10px; left: 10px; animation: bounce 1.5s ease-in-out infinite alternate; animation-delay: 0.4s; }} .ball:nth-child(4) {{ bottom: 10px; right: 10px; animation: bounce 1.5s ease-in-out infinite alternate; animation-delay: 0.6s; }} .status-text {{ font-size: 20px; font-weight: 600; margin: 20px 0; color: #1e293b; }} .info-text {{ font-size: 14px; color: #64748b; margin-bottom: 20px; max-width: 400px; margin: 0 auto; }} .progress-container {{ width: 80%; height: 8px; background-color: #e2e8f0; border-radius: 10px; margin: 20px auto; position: relative; overflow: hidden; }} #progress-bar {{ height: 100%; background: linear-gradient(90deg, #4a6cf7, #3a0ca3); width: 0%; transition: width 0.3s; border-radius: 10px; }} #progress-text {{ margin-top: 5px; font-size: 14px; font-weight: 600; color: #4a6cf7; }} </style> </head> <body> <div> <div> <h1>神经网络模型训练中</h1> <p>我们正在使用红篮球历史数据训练深度学习模型,这可能需要几分钟时间。</p> <div> <div> <div>6</div> <div>18</div> <div>28</div> <div>12</div> </div> </div> <div> <div></div> </div> <div>0%</div> <div>初始化模型中...</div> </div> </div> </body> </html> """# 使用setHtml显示带有动画的进度页面 self.result_display.setHtml(loading_html) self.predict_progress.setValue(0)# 连接进度条更新信号到我们的自定义函数 self.predict_progress.valueChanged.connect(self.update_html_progress) train_ratio = self.train_ratio.value()/100 epochs = self.epochs.value() batch_size = self.batch_size.value() lookback = self.lookback.value() strategy = self.strategy.currentText().lower() self.predictor = EnhancedPredictor(train_ratio, epochs, batch_size, lookback, strategy, self.shared_css) self.predictor.finished.connect(self.on_predict_finished) self.predictor.progress.connect(self.predict_progress.setValue) self.predictor.stats_ready.connect(self.update_stats) self.predictor.model_trained.connect(self.set_model) self.predictor.start()# 添加一个新函数,用于更新HTML进度条defupdate_html_progress(self, value):"""更新HTML页面中的进度条,使用最简单的JavaScript"""# 直接使用DOM操作更新进度条 script =""" (function() { try { var progressBar = document.getElementById('progress-bar'); var progressText = document.getElementById('progress-text'); var status = document.getElementById('status'); if (progressBar && progressText) { progressBar.style.width = '%d%%'; progressText.textContent = '%d%%'; // 更新状态文本 if (status) { if (%d < 30) { status.textContent = '初始化模型中...'; } else if (%d < 80) { status.textContent = '训练神经网络中...'; } else { status.textContent = '生成预测结果中...'; } } return true; } } catch(e) { console.error('更新进度条出错:', e); } return false; })(); """%(value, value, value, value)# 执行JavaScript脚本 self.result_display.page().runJavaScript(script)defon_predict_finished(self, result): self.predict_btn.setEnabled(True)# 记录收到的结果print(f"收到预测结果: {result[:100]}...")# 只打印前100个字符避免过长# 检查结果是否为空ifnot result orlen(result.strip())==0:print("警告: 收到空结果")# 创建一个错误结果import numpy as np red_balls =sorted(np.random.choice(range(1,34),6, replace=False)) blue_ball = np.random.randint(1,17) result =f"=== 预测结果异常,显示随机号码 ===\n\n预测号码:\n红球: {', '.join(map(str, red_balls))}\n蓝球: {blue_ball}\n\n" result +="=== 模型参数 ===\n模型类型: 随机生成\n" html_result =""try:# 尝试使用优化后的HTML格式美化预测结果print("尝试格式化预测结果...") html_result = self.predictor.format_prediction_result(result)# 验证HTML结果是否有效ifnot html_result orlen(html_result.strip())==0or"<!DOCTYPE html>"notin html_result:print("警告: HTML格式化结果无效")raise ValueError("生成的HTML无效")except Exception as e:# 捕获任何格式化过程中的错误print(f"格式化预测结果出错: {str(e)}")print("使用备用HTML生成方法...") html_result = self.create_fallback_html_result(result)# 确保HTML结果有效,最后的安全检查ifnot html_result orlen(html_result.strip())==0:print("严重错误: 两种HTML生成方法均失败,创建基本HTML")# 创建最简单的HTML结果 html_result =f""" <!DOCTYPE html> <html><head><title>预测结果</title> <style> body {{ font-family: Arial; padding: 20px; }} .error {{ color: red; }} pre {{ background: #f0f0f0; padding: 10px; }} </style> </head> <body> <h2>预测结果</h2> <div>注意: 结果格式化失败,显示原始结果</div> <pre>{result.replace('<','<').replace('>','>')}</pre> </body></html> """# 设置HTML内容print("设置HTML结果...") self.result_display.setHtml(html_result)# 确保Web通道连接 self.result_display.page().setWebChannel(self.channel) self.statusBar().showMessage("预测完成")defcreate_fallback_html_result(self, result):"""创建一个简单的HTML结果,用于format_prediction_result失败时"""# 解析预测结果中的基本信息 red_balls =[] blue_ball =None model_info ={}# 尝试从结果文本中提取红蓝球号码和模型信息try: lines = result.split('\n')for line in lines:# 提取红球号码(支持多种格式)ifany(marker in line for marker in['红球:','红球:','红球号码','红球预测']):try:# 尝试从行中提取数字import re numbers = re.findall(r'\d+', line)# 确保只处理1-33范围内的数字 valid_numbers =[int(num)for num in numbers if num.isdigit()and1<=int(num)<=33]# 避免重复 red_balls =sorted(list(set(valid_numbers)))[:6]# 最多取6个except Exception as e:print(f"备用方法提取红球出错: {str(e)}")# 提取蓝球号码(支持多种格式)elifany(marker in line for marker in['蓝球:','蓝球:','蓝球号码','蓝球预测']):try:# 尝试从行中提取数字import re numbers = re.findall(r'\d+', line)# 确保是1-16范围内的数字for num in numbers:if num.isdigit()and1<=int(num)<=16: blue_ball =int(num)breakexcept Exception as e:print(f"备用方法提取蓝球出错: {str(e)}")# 提取模型信息elifany(keyword in line for keyword in["模型类型","网络结构","训练集比例","训练轮次","批量大小","回溯期数"]):try:if":"in line or":"in line: parts = line.split(":",1)if":"in line else line.split(":",1)iflen(parts)==2: key = parts[0].strip() value = parts[1].strip() model_info[key]= value except Exception as e:print(f"备用方法提取模型信息出错: {str(e)}")except Exception as e:print(f"备用方法解析预测结果出错: {str(e)}")# 补充缺失的红球号码ifnot red_balls orlen(red_balls)<6:import numpy as np existing =set(red_balls) needed =6-len(existing)if needed >0: available =[n for n inrange(1,34)if n notin existing] additional =sorted(np.random.choice(available, needed, replace=False)) red_balls =sorted(list(existing)+ additional)print(f"备用方法补充红球: {red_balls}")# 补充缺失的蓝球号码if blue_ball isNone:import numpy as np blue_ball = np.random.randint(1,17)print(f"备用方法生成蓝球: {blue_ball}")# 创建一个简单但美观的HTML html =f""" <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>预测结果</title> <style> {self.shared_css} .result-container {{ background-color: white; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); margin-bottom: 20px; text-align: center; }} .model-info {{ display: flex; flex-wrap: wrap; justify-content: space-between; margin-top: 15px; }} .info-item {{ flex: 0 0 48%; background-color: #f8f9fa; padding: 10px; margin-bottom: 10px; border-radius: 5px; }} .info-name {{ font-weight: bold; color: #4361ee; }} .raw-result {{ white-space: pre-wrap; background-color: #f1f3f5; padding: 15px; border-radius: 5px; font-family: monospace; margin-top: 20px; text-align: left; max-height: 200px; overflow-y: auto; }} .note {{ font-style: italic; color: #6c757d; margin-top: 10px; font-size: 0.9em; }} </style> </head> <body> <div> <h1>红篮球预测结果</h1> <div> <div>预测号码</div> <div> """# 添加红球for ball in red_balls: html +=f'<div>{ball}</div>' html +='<div>|</div>'# 添加蓝球 html +=f'<div>{blue_ball}</div>'# 添加模型信息 html +=""" </div> </div> <div> <div>模型参数</div> <div> """# 添加提取到的模型信息if model_info:for key, value in model_info.items(): html +=f""" <div> <div>{key}</div> <div>{value}</div> </div> """else:# 如果没有提取到模型信息,添加默认信息 html +=""" <div> <div>模型类型</div> <div>神经网络预测模型</div> </div> <div> <div>备注</div> <div>原始模型参数解析失败</div> </div> """# 添加原始结果(有限的高度,可滚动) html +=""" </div> <p>注:使用备用格式化方法</p> </div> <div> <div>原始预测结果</div> <div> """ html += result.replace('<','<').replace('>','>').replace('\n','<br>') html +=""" </div> </div> <footer> <p>© 2025 红篮球预测系统 | 纯属娱乐,请理性buy</p> </footer> </div> </body> </html> """return html defset_model(self, model): self.model = model defupdate_stats(self, stats):try: self.stats_data = stats # 使用内置的方法生成HTML气泡图,而不是调用外部模块# 注意:原有的StatsCanvas类已从代码中移除,使用HTML气泡图替代了matplotlib可视化 html = self.generate_bubble_chart_html(stats)# 设置HTML内容 self.stats_display.setHtml(html)# 确保Web通道连接 self.stats_display.page().setWebChannel(self.channel)except Exception as e:print(f"更新统计信息出错: {str(e)}")import traceback traceback_details = traceback.format_exc()print(f"详细错误: {traceback_details}")# 处理异常情况 error_html =f""" <html><body> <h1>统计数据处理错误</h1> <p>请重新获取数据。错误信息: {str(e)}</p> </body></html> """ self.stats_display.setHtml(error_html) self.statusBar().showMessage("统计数据更新失败")defpreprocess_data(self, df):"""集中处理数据类型转换和缺失值填充,避免重复操作"""# 导入所需库import pandas as pd import numpy as np # 1. 确保日期处理正确try:# 首先确保日期列是字符串类型 df['日期']= df['日期'].astype(str)# 清理日期字符串,创建标准化日期列 valid_dates =[]for date_str in df['日期']:# 清理日期字符串,只保留数字和连字符 clean_date =''.join([c for c in date_str if c.isdigit()or c =='-'])# 确保日期格式是YYYY-MM-DDiflen(clean_date)>=10: clean_date = clean_date[:10]# 只取前10个字符 valid_dates.append(clean_date)else:# 无效日期使用占位符 valid_dates.append('2000-01-01')# 添加清理后的日期列 df['clean_date']= valid_dates # 转换为日期时间格式并添加日期特征 df['date']= pd.to_datetime(df['clean_date'], errors='coerce')# 检查日期转换是否成功if df['date'].isna().all():raise ValueError("所有日期都转换失败")# 确认date列是datetime类型ifnot pd.api.types.is_datetime64_any_dtype(df['date']):print("警告: date列不是datetime类型,使用替代方法")raise TypeError("date列不是datetime类型")# 添加时间特征 df['year']= df['date'].dt.year df['month']= df['date'].dt.month df['day']= df['date'].dt.day df['day_of_week']= df['date'].dt.dayofweek df['day_of_year']= df['date'].dt.dayofyear except Exception as e:print(f"日期转换异常: {str(e)}")print("使用生成的日期序列代替原始日期")# 创建一个假的日期序列作为备选 date_range = pd.date_range(start='2020-01-01', periods=len(df)) df['date']= date_range # 基于生成的日期序列添加特征 df['year']= date_range.year df['month']= date_range.month df['day']= date_range.day df['day_of_week']= date_range.dayofweek df['day_of_year']= date_range.dayofyear # 2. 数值转换和清理# 确保所有数值列为数值型 non_numeric_cols =['期号','日期','date','clean_date'] numeric_cols =[col for col in df.columns if col notin non_numeric_cols]for col in numeric_cols:try: df[col]= pd.to_numeric(df[col], errors='coerce')except Exception as e:print(f"无法将列 {col} 转换为数值型: {str(e)}")# 用均值填充数值列中的NaN值 df[numeric_cols]= df[numeric_cols].fillna(df[numeric_cols].mean())# 用0填充任何剩余的NaN值 df = df.fillna(0)return df defgenerate_bubble_chart_html(self, stats):"""根据统计数据生成HTML气泡图 此方法替代了原先基于matplotlib的StatsCanvas类可视化功能 使用纯HTML/CSS实现气泡图,减少了对matplotlib的依赖,提高了加载速度 Args: stats: 包含红球和蓝球统计数据的字典 Returns: html: 包含完整气泡图的HTML字符串 """ html =f""" <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <style> {self.shared_css} /* 气泡图特定样式 */ .stats-grid {{ display: flex; flex-wrap: wrap; justify-content: space-between; margin: 0 -10px 20px -10px; }} .stats-card {{ flex: 0 0 calc(25% - 30px); background-color: #ffffff; border-radius: 12px; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); padding: 25px; position: relative; overflow: hidden; margin: 15px; transition: transform 0.3s ease, box-shadow 0.3s ease; }} .stats-card:hover {{ transform: translateY(-5px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12); }} .red-border::before {{ content: ''; position: absolute; left: 0; top: 0; height: 100%; width: 4px; background-color: #e63946; }} .blue-border::before {{ content: ''; position: absolute; left: 0; top: 0; height: 100%; width: 4px; background-color: #0077b6; }} .stats-title {{ font-size: 18px; font-weight: 700; color: #1e293b; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #e2e8f0; }} .stats-list {{ list-style-type: none; margin: 0; padding: 0; }} .stats-item {{ display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; margin-bottom: 8px; background-color: #f8fafc; border-radius: 8px; transition: transform 0.2s ease, background-color 0.2s ease; }} .stats-item:hover {{ transform: translateX(5px); background-color: #f1f5f9; }} .item-left {{ display: flex; align-items: center; }} .rank {{ display: inline-block; width: 25px; height: 25px; line-height: 25px; text-align: center; background-color: #94a3b8; color: white; border-radius: 50%; font-weight: 600; font-size: 14px; }} .red-ball {{ display: inline-block; width: 40px; height: 40px; line-height: 40px; text-align: center; background-color: #e63946; color: white; border-radius: 50%; font-weight: 700; font-size: 18px; box-shadow: 0 3px 6px rgba(230, 57, 70, 0.4); transition: transform 0.2s ease, box-shadow 0.2s ease; }} .blue-ball {{ display: inline-block; width: 40px; height: 40px; line-height: 40px; text-align: center; background-color: #0077b6; color: white; border-radius: 50%; font-weight: 700; font-size: 18px; box-shadow: 0 3px 6px rgba(0, 119, 182, 0.4); transition: transform 0.2s ease, box-shadow 0.2s ease; }} .stats-item:hover .red-ball, .stats-item:hover .blue-ball {{ transform: scale(1.1); box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); }} .probability {{ display: inline-block; padding: 4px 10px; background-color: #e2e8f0; color: #334155; border-radius: 20px; font-weight: 600; font-size: 14px; transition: background-color 0.2s ease; }} .stats-item:hover .probability {{ background-color: #cbd5e1; }} /* 静态气泡图样式 */ .bubble-chart-container {{ background-color: #ffffff; border-radius: 12px; box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); padding: 20px; margin: 20px 0; transition: transform 0.3s ease, box-shadow 0.3s ease; position: relative; overflow: hidden; }} .bubble-chart-container:hover {{ transform: translateY(-5px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12); }} .bubble-chart-title {{ font-size: 18px; font-weight: 700; color: #1e293b; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #e2e8f0; text-align: center; }} .bubble-chart {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); gap: 15px; padding: 45px 20px 45px 20px; position: relative; min-height: 180px; background-color: #fafbff; border-radius: 8px; justify-items: center; align-items: center; }} .bubble-chart-red {{ background: linear-gradient(to bottom, #fff5f5, #fafbff); }} .bubble-chart-blue {{ background: linear-gradient(to bottom, #f0f7ff, #fafbff); }} .bubble-chart::before {{ content: ''; position: absolute; left: 0; right: 0; top: 50%; height: 1px; border-top: 1.5px dashed #94a3b8; z-index: 0; opacity: 0.6; }} .bubble-chart-label {{ position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); background-color: white; font-size: 12px; color: #334155; font-weight: 600; padding: 5px 12px; border-radius: 20px; box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); z-index: 5; transition: transform 0.2s ease, box-shadow 0.2s ease; border: 1px solid #e2e8f0; }} .bubble-chart-label:hover {{ transform: translateX(-50%) translateY(-3px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); }} .static-bubble {{ width: 50px; height: 50px; border-radius: 50%; margin: 0; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; position: relative; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); z-index: 2; transform-origin: center bottom; transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.3s ease; animation: bubbleFloat 3s ease-in-out infinite; animation-delay: calc(var(--delay) * 0.3s); }} @keyframes bubbleFloat {{ 0%, 100% {{ transform: translateY(0) scale(1); }} 50% {{ transform: translateY(-10px) scale(1.03); }} }} .static-bubble:hover {{ transform: scale(1.2) translateY(-10px); z-index: 10; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); animation-play-state: paused; }} .static-bubble-red {{ background: linear-gradient(135deg, #ff6b6b, #e63946); border: 2px solid #C0392B; }} .static-bubble-blue {{ background: linear-gradient(135deg, #48cae4, #0077b6); border: 2px solid #2980B9; }} .bubble-number {{ font-size: 16px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); }} .bubble-freq {{ position: absolute; top: -25px; left: 50%; transform: translateX(-50%); background-color: white; color: #1e293b; font-size: 10px; padding: 3px 8px; border-radius: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); white-space: nowrap; transition: opacity 0.2s ease, transform 0.2s ease; opacity: 0.9; }} .static-bubble:hover .bubble-freq {{ opacity: 1; transform: translateX(-50%) translateY(-3px); }} .high-freq {{ animation: pulse 2s infinite; }} @keyframes pulse {{ 0% {{ box-shadow: 0 0 0 0 rgba(230, 57, 70, 0.4); }} 70% {{ box-shadow: 0 0 0 10px rgba(230, 57, 70, 0); }} 100% {{ box-shadow: 0 0 0 0 rgba(230, 57, 70, 0); }} }} .red-pulse {{ animation: redPulse 2s infinite; }} @keyframes redPulse {{ 0% {{ box-shadow: 0 0 0 0 rgba(230, 57, 70, 0.4); }} 70% {{ box-shadow: 0 0 0 10px rgba(230, 57, 70, 0); }} 100% {{ box-shadow: 0 0 0 0 rgba(230, 57, 70, 0); }} }} .blue-pulse {{ animation: bluePulse 2s infinite; }} @keyframes bluePulse {{ 0% {{ box-shadow: 0 0 0 0 rgba(0, 119, 182, 0.4); }} 70% {{ box-shadow: 0 0 0 10px rgba(0, 119, 182, 0); }} 100% {{ box-shadow: 0 0 0 0 rgba(0, 119, 182, 0); }} }} .bubble-chart-divider-label {{ position: absolute; left: 10px; top: 50%; transform: translateY(-50%); background-color: white; font-size: 11px; color: #64748b; font-weight: 500; padding: 2px 7px; border-radius: 10px; z-index: 2; border: 1px solid #e2e8f0; line-height: 1.2; opacity: 0.9; }} .bubble-chart-bottom-label {{ position: absolute; left: 10px; bottom: 10px; background-color: white; font-size: 11px; color: #64748b; font-weight: 500; padding: 2px 7px; border-radius: 10px; z-index: 2; border: 1px solid #e2e8f0; line-height: 1.2; opacity: 0.9; }} </style> </head> <body> <div> <h1>红篮球数据统计分析</h1> <div> <!-- 红球历史高频 --> <div> <div>红球历史高频TOP10</div> <ul> """# 添加红球历史高频TOP10列表for i,(num, prob)inenumerate(stats['red']['all_time_top10'].items()): pulse_class ="red-pulse"if i <3else"" html +=f""" <li> <div> <spantoken interpolation">{pulse_class}">{int(num)}</span> </div> <span>{prob *100:.2f}%</span> </li> """ html +=""" </ul> </div> <!-- 红球近期高频 --> <div> <div>红球近期高频TOP10</div> <ul> """# 添加红球近期高频TOP10列表for i,(num, prob)inenumerate(stats['red']['recent_top10'].items()): pulse_class ="red-pulse"if i <3else"" html +=f""" <li> <div> <spantoken interpolation">{pulse_class}">{int(num)}</span> </div> <span>{prob *100:.2f}%</span> </li> """ html +=""" </ul> </div> <!-- 蓝球历史高频 --> <div> <div>蓝球历史高频TOP10</div> <ul> """# 添加蓝球历史高频TOP10列表for i,(num, prob)inenumerate(stats['blue']['all_time_top10'].items()): pulse_class ="blue-pulse"if i <3else"" html +=f""" <li> <div> <spantoken interpolation">{pulse_class}">{int(num)}</span> </div> <span>{prob *100:.2f}%</span> </li> """ html +=""" </ul> </div> <!-- 蓝球近期高频 --> <div> <div>蓝球近期高频TOP10</div> <ul> """# 添加蓝球近期高频TOP10列表for i,(num, prob)inenumerate(stats['blue']['recent_top10'].items()): pulse_class ="blue-pulse"if i <3else"" html +=f""" <li> <div> <spantoken interpolation">{pulse_class}">{int(num)}</span> </div> <span>{prob *100:.2f}%</span> </li> """ html +=""" </ul> </div> </div> <!-- 红球静态气泡图 --> <div> <div>红球号码频率分布</div> <div> """# 计算红球平均频率 red_values =list(stats['red']['all_time_sorted'].values()) red_avg =sum(red_values)/len(red_values)if red_values else0 red_max =max(red_values)if red_values else0 red_min =min(red_values)if red_values else0# 添加红球平均频率标签 html +=f""" <div>平均出现频率: {red_avg *100:.2f}% | 期望值: {(1/33)*100:.2f}%</div> <div>高频号码</div> <div>低频号码</div> """# 对红球数据按频率排序,便于确定TOP5 red_sorted =sorted(stats['red']['all_time_sorted'].items(), key=lambda x: x[1], reverse=True) red_top5_threshold = red_sorted[4][1]iflen(red_sorted)>4else0# 添加红球气泡# 先按频率排序 sorted_red_balls =sorted(stats['red']['all_time_sorted'].items(), key=lambda x:float(x[1]), reverse=True) avg_freq = red_avg for i,(num, freq)inenumerate(sorted_red_balls):# 计算气泡大小 (30px - 70px) size_factor =(freq - red_min)/(red_max - red_min)if red_max > red_min else0.5 size =35+ size_factor *35# 确定垂直位置 - 频率高于平均值的在上方,低于的在下方 above_average = freq > avg_freq y_offset =-40if above_average else40# 简化为上/下两行# 确定是否为TOP5,添加频率标签 freq_label =""if i <5:# 前5名显示标签 freq_label =f'<div>{freq *100:.2f}%</div>'# 确定是否添加脉冲效果和延迟动画 pulse_class ="red-pulse"if i <3else""# 前3名添加脉冲 animation_delay = i %8# 0-7的延迟变化# 计算整体透明度 - 频率越高越不透明 opacity =0.5+0.5* size_factor html +=f""" <divtoken interpolation">{pulse_class}"token interpolation">{size}px; height:{size}px; transform:translateY({y_offset}px); --delay:{animation_delay}; opacity:{opacity}"> {freq_label} <span>{int(num)}</span> </div> """ html +=""" </div> </div> <!-- 蓝球静态气泡图 --> <div> <div>蓝球号码频率分布</div> <div> """# 计算蓝球平均频率 blue_values =list(stats['blue']['all_time_sorted'].values()) blue_avg =sum(blue_values)/len(blue_values)if blue_values else0 blue_max =max(blue_values)if blue_values else0 blue_min =min(blue_values)if blue_values else0# 添加蓝球平均频率标签 html +=f""" <div>平均出现频率: {blue_avg *100:.2f}% | 期望值: {(1/16)*100:.2f}%</div> <div>高频号码</div> <div>低频号码</div> """# 对蓝球数据按频率排序,便于确定TOP5 blue_sorted =sorted(stats['blue']['all_time_sorted'].items(), key=lambda x: x[1], reverse=True) blue_top5_threshold = blue_sorted[4][1]iflen(blue_sorted)>4else0# 添加蓝球气泡# 先按频率排序 sorted_blue_balls =sorted(stats['blue']['all_time_sorted'].items(), key=lambda x:float(x[1]), reverse=True) avg_freq = blue_avg for i,(num, freq)inenumerate(sorted_blue_balls):# 计算气泡大小 (30px - 70px) size_factor =(freq - blue_min)/(blue_max - blue_min)if blue_max > blue_min else0.5 size =35+ size_factor *35# 确定垂直位置 - 频率高于平均值的在上方,低于的在下方 above_average = freq > avg_freq y_offset =-40if above_average else40# 简化为上/下两行# 确定是否为TOP5,添加频率标签 freq_label =""if i <5:# 前5名显示标签 freq_label =f'<div>{freq *100:.2f}%</div>'# 确定是否添加脉冲效果和延迟动画 pulse_class ="blue-pulse"if i <3else""# 前3名添加脉冲 animation_delay = i %8# 0-7的延迟变化# 计算整体透明度 - 频率越高越不透明 opacity =0.5+0.5* size_factor html +=f""" <divtoken interpolation">{pulse_class}"token interpolation">{size}px; height:{size}px; transform:translateY({y_offset}px); --delay:{animation_delay}; opacity:{opacity}"> {freq_label} <span>{int(num)}</span> </div> """ html +=""" </div> </div> <footer> <p>© 2025 红篮球数据统计 | 仅供参考,请理性buy</p> </footer> </div> </body> </html> """return html # 页面导航器,用于JavaScript与PyQt交互classPageNavigator(QObject):def__init__(self, main_window):super().__init__() self.main_window = main_window @pyqtSlot()defprevPage(self): self.main_window.change_data_page(-1)@pyqtSlot()defnextPage(self): self.main_window.change_data_page(1)@pyqtSlot()defpageLoaded(self):print("页面加载完成,JavaScript环境已就绪")if __name__ =="__main__":# Delay imports until actually needed to speed up startup app = QApplication(sys.argv) icon_path = resource_path('logo.ico')if os.path.exists(icon_path): app.setWindowIcon(QIcon(icon_path)) window = MainWindow() window.show() sys.exit(app.exec_())

运行环境配置

  1. 安装Python 3.8+
  2. 安装依赖库:
 pip install-r requirements.txt 

requirements.txt内容:

PyQt5==5.15.4 tensorflow==2.6.0 pandas==1.3.0 numpy==1.21.0 requests==2.26.0 PyQtWebEngine==5.15.4 
  1. 运行主程序:
 python main.py 

总结与展望

本文详细介绍了一个基于深度学习的双色球预测系统的设计与实现。该系统具有以下优势:

  1. 完整的业务流程:从数据获取到预测分析一站式解决
  2. 多种模型选择:LSTM、GRU和混合模型满足不同需求
  3. 美观的交互界面:现代化的Web风格UI
  4. 全面的可视化:直观展示分析结果

未来可能的改进方向:

  1. 增加更多特征工程方法
  2. 集成XGBoost等传统机器学习算法
  3. 添加模型性能对比功能
  4. 支持自动参数调优
  5. 增加模型解释性分析

需要注意的是,彩票预测本质上属于随机事件,本系统仅供技术研究和学习使用。开发者不保证预测结果的准确性,也不鼓励任何形式的赌博行为。

希望通过本文的介绍,读者能够了解如何将深度学习技术应用于时间序列预测问题,并掌握PyQt5开发复杂GUI应用程序的方法。

Read more

GitHub Agent HQ 全流程实战教程:从 Copilot Pro + 接入到代码库全生命周期管理(重构 + 测试 + 部署自动化 + 权限避坑)

GitHub Agent HQ 全流程实战教程:从 Copilot Pro + 接入到代码库全生命周期管理(重构 + 测试 + 部署自动化 + 权限避坑)

背景引入:AI 驱动的代码库全生命周期管理新范式 问题驱动 随着软件项目复杂度提升,开发者面临代码重构效率低、测试覆盖不足、部署流程繁琐、权限管理易疏漏等痛点。传统开发模式下,从代码编写到生产部署需跨多个工具链,上下文割裂导致协作成本高。GitHub Agent HQ 作为基于 Copilot Pro 的智能开发代理,通过大语言模型(LLM)深度理解代码库上下文,实现重构、测试、部署的全流程自动化,成为提升开发效率的核心工具。 技术趋势 2026 年,AI 辅助开发已从“代码补全”进化为“全流程代理”。GitHub Agent HQ 依托 Copilot Pro 的增强型 LLM 能力,结合 GitHub 原生生态,支持代码库深度索引、多步骤任务编排、工具链自动调用,是当前

By Ne0inhk

对于VScode中Copilot插件使用卡顿问题的解决办法

copilot卡顿主要是网络和内存占用原因。 VScode内存优化解决办法: 结合链接和我补充的基本都可以解决。 解决VSCode无缘无故卡顿的问题_vscode卡顿-ZEEKLOG博客 在VScode中打开setting.json文件,打开方法ctrl+shift+p,输入Preferences: Open User Settings (JSON), 然后添加如下代码: { "search.followSymlinks": false, "git.autorefresh": false, "editor.formatOnSave": false } 结合链接和我补充的基本都可以解决。 VScode代理问题: vscode copilot长时间没反应_vscode中copilot总是卡住-ZEEKLOG博客 配置代理的话两种方法,上面是一种,推荐两种结合起来用(不冲突) 还是在setting.json文件中,添加如下代码: { "http.proxy": "http://127.

By Ne0inhk
【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

【优质开源项目】AIGC开源推荐-全球情报监控平台worldmonitor

1.概述 World Monitor 是一个开源的实时情报/监测仪表盘,聚合多类数据源(新闻、地理/卫星、航运/空中、财经、威胁情报等),提供交互式地理视图、AI 摘要、事件聚合与报警,支持 Web / PWA / Tauri 桌面三种运行方式,并可通过变体(WORLD / TECH / FINANCE)切换功能集。 2. 总体技术架构(分层视角) 客户端层(Browser / PWA / Tauri desktop) * • React + TypeScript + Vite 构建。 * • 地图/可视化:deck.gl(WebGL 3D globe)、MapLibre GL、D3

By Ne0inhk
GitHub Copilot 学生认证详细教程

GitHub Copilot 学生认证详细教程

GitHub Copilot 是 GitHub 提供的 AI 代码助手工具,学生可以通过 GitHub Student Developer Pack(学生开发者包)免费获取 Copilot Pro 版本(通常每月收费 10 美元)。这个过程涉及验证你的学生身份,一旦通过,你可以免费使用 Copilot Pro,直到你的学生身份到期(通常每年需要重新验证)。以下是最详细的教程,基于 GitHub 官方文档和社区指南,涵盖从准备到激活的所有步骤。我会逐步分解,确保每个步骤都清晰、可操作。如果你是第一次申请,预计整个过程可能需要 1-3 天(验证通常在 72 小时内完成)。 第一部分:资格要求和准备工作 在开始前,确保你符合条件。如果不符合,申请会被拒绝。 * 资格标准: * 你必须是当前在读学生,

By Ne0inhk