np.logic_and/or/not用法

本文详细解析了如何在优快云上发布一篇高质量的技术博客,包括标题、标签和内容的优化策略,旨在帮助作者提高文章的可见性和吸引力。
import cv2 import numpy as np import time import win32gui import win32con from PIL import ImageGrab import win32api import os import random from image_processor import ImageProcessor from mouse_controller import MouseController from assist import Assist class Kaitu: def __init__(self, assist = None,日志输出=None, settings=None,延时=None): self.hwnd =assist.hwnd self.延时 = 延时 self.日志输出 = 日志输出 self.assist = assist self.kaitudata = { 'kaitu': settings['kaitu'], #开图功能 'tandong': settings['tandong'],#探洞 } def openyingdi(self): """打开营地的操作""" #print("开始执行打开营地操作") self.日志输出.info("开始执行打开营地操作") # 查找ts1或ts2图片 tsimgs = { 'ts1': 'img/ts1.bmp', 'ts2': 'img/ts2.bmp', 'ts3': 'img/ts3.bmp', } ts1_location = self.assist.img_processor.find_images(tsimgs,0.75) if not ts1_location : self.日志输出.info("未找到探索") #判断 return 3 # 使用找到的图片位置 target_location = ts1_location if ts1_location else ts2_location if ts2_location else ts3_location self.日志输出.info("找到探索") # 计算随机偏移量 offset_x = random.randint(15, 30) offset_y = random.randint(40, 60) # 点击位置(带偏移) #print(f"点击位置偏移: x={offset_x}, y={offset_y}") self.assist.mouse.click_position(target_location, offset_x, offset_y) self.延时.delay() # 查找openyingdi图片 max_attempts = 3 attempt = 0 openyingdi_location = None while attempt < max_attempts: #print(f"第{attempt + 1}次尝试查找openyingdi图片...") self.日志输出.info(f"第{attempt + 1}次尝试查找openyingdi图片...") openyingdi_location = self.assist.img_processor.find_image('img/openyingdi.bmp', threshold=0.6) if openyingdi_location: self.日志输出.info("找到openyingdi图片") break print(f"第{attempt + 1}次未找到openyingdi图片") attempt += 1 if attempt < max_attempts: wait_time = random.uniform(0.5, 1.0) self.日志输出.info(f"等待{wait_time:.2f}秒后重试...") time.sleep(wait_time) if not openyingdi_location: self.日志输出.info("多次尝试后仍未找到openyingdi图片") #按下esc 弹起esc win32api.keybd_event(27, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0) # 按下ESC time.sleep(0.1) # 短暂延迟 win32api.keybd_event(27, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0) # 释放ESC self.日志输出.info("按下ESC") time.sleep(0.3) # 等待界面响应 win32api.keybd_event(27, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0) # 再次按下ESC time.sleep(0.1) # 短暂延迟 return 2 # 点击openyingdi位置 self.日志输出.info("找到openyingdi,正在点击") self.assist.mouse.click_position(openyingdi_location) time.sleep(0.5) # 等待界面响应 # 查找chgl图片 chgl_location = self.assist.img_processor.find_image('img/chgl.bmp') if not chgl_location: self.日志输出.info("没有打开伺候营地") return 2 self.日志输出.info("成功打开伺候营地") return 1 def check_task(self): """检查任务状态 返回值: 1 - 在第一个区域找到图片 0 - 在第二个区域找到图片 3 - 两个区域都没找到图片 """ #检查斥候是否在忙碌 # 第一次查找并点击tansuo图片 tansuo_imgs = {'tansuo': 'img/tansuo.bmp', 'tansuo2': 'img/tansuo2.bmp' } print("开始查找tansuo图片") tansuo_location = self.assist.img_processor.find_images(tansuo_imgs,0.75) if not tansuo_location: #关闭 查找关闭 guanbi_location = self.assist.img_processor.find_image('img/guanbi.bmp') if not guanbi_location: print("未找到关闭图片") return False print("找到关闭,正在点击") self.assist.mouse.click_position(guanbi_location) return False print("开始检查任务状态") #判断是否有投靠 region2 = (580, 260, 650, 290) result2 = self.assist.img_processor.find_image('img/tssj.bmp', region=region2) # if result2: # print("有投靠任务") # return 4 # 第一个区域 (655,223) 到 (755,274) if self.kaitudata['tandong'] != False: region1 = (720, 260, 820, 300) result1 = self.assist.img_processor.find_image('img/tssj.bmp', region=region1) if result1: print("有山洞任务") return 1 # 第二个区域 (812,232) 到 (896,289) region2 = (860, 260, 980, 300) result2 = self.assist.img_processor.find_image('img/tssj.bmp', region=region2) if result2: print("有其他") return 2 print("无特殊任务") return 3 """带重试机制的找图""" location = self.assist.img_processor.wait_find_images(image_path, threshold=0.85,timeout=3) # 使用更高的阈值 if not location: print("多次尝试后仍未找到diaocha图片") return None # 返回None表示未找到图片 def tsshandong(self): """处理调查和点击操作""" #print("开始执行调查和点击操作") self.日志输出.info("开始执行调查和点击操作") # 查找是否正在调查 #首先判断如果chc图片存在 则调用 scroll_wheel("down") diaocha_location = self.assist.img_processor.find_image('img/chc.bmp', threshold=0.75) if diaocha_location: self.日志输出.info("找到chc,正在向下滚动") self.assist.mouse.scroll_wheel("down") time.sleep(0.5) # 等待界面响应 #然后查找 判断有几个 图片img/diaochazhong.bmp self.日志输出.info("开始查找调查图片...") diaocha_location = self.assist.img_processor.find_image_vertically('img/diaochazhong.bmp', threshold=0.6) #如果没有点击第一个 diancha_cont = len(diaocha_location) self.日志输出.info(f"diaocha_location: 共计找到了{diancha_cont}") diaocha_status = 0 self.日志输出.info(f"diaocha_location: {diaocha_location}") # diaocha_location = self.assist.img_processor.find_image('img/diaochazhong.bmp', threshold=0.6) if not diaocha_location: self.日志输出.info("未找到diaochazhong.bmp,尝试查找diaochazhong2.bmp") diaocha_location2 = self.assist.img_processor.find_image('img/diaochazhong2.bmp', threshold=0.6) else: self.日志输出.info("找到diaochazhong.bmp") diaocha_location2 = None self.日志输出.info("检测到正在调查中") diaocha_status = 1 #点击山洞 # 查找shandong图片 if not self.dianjichsd(): return False #随机延时 time.sleep(random.randint(1, 3) / 10) # 0.1到0.5秒的随机延时 #查找前往的数量 如果数量和 len(diaocha_location) 一样 则返回 直接开图 返回 qianwang_location = self.assist.img_processor.find_image_vertically('img/qw.bmp', threshold=0.6) if diancha_cont == len(qianwang_location): self.日志输出.info("数量和 len(diaocha_location) 一样 则返回 直接开图 ") return False # 使用智能等待机制等待qw图片出现 qw_locations = [] max_attempts = 3 attempt = 0 # 在指定区域查找qw或qw2 qianwangzb = [ (800, 350, 950, 390), (800, 430, 950, 470), (800, 510, 950, 550) ] qwdata = { 'qw': 'img/qw.bmp', 'qw2': 'img/qw2.bmp', 'qw3': 'img/qw3.bmp', 'qw4': 'img/qw4.bmp', } qw_location = self.assist.img_processor.find_images(qwdata,0.7, region=qianwangzb[diancha_cont]) if not qw_location: self.日志输出.info("未找到qw图片") return False #随机延时 time.sleep(random.randint(1, 5) / 10) # 0.1到0.5秒的随机延时 if qw_location: self.日志输出.info("在指定区域找到qw图片,正在点击") self.assist.mouse.click_position(qw_location) time.sleep(random.randint(1, 5) / 10) # 0.1到0.5秒的随机延时 else: return False # 使用智能等待机制查找调查图片 max_attempts = 3 attempt = 0 diaocha_location = None diaochaimgs = { 'diaocha': 'img/diaocha.bmp', 'diaocha2': 'img/diaocha2.bmp', } #延时1- 1.5秒 #开启一个计时器开始计时 diaocha_location=self.assist.img_processor.wait_find_images(diaochaimgs,0.7,timeout=5) for i in range(3): time.sleep(random.uniform(0.3, 0.5)) print(f"diaocha_location:{diaocha_location}") if diaocha_location: #判断是否有探洞tdlvdian 如果有返回 sdzb = (520,270,820,520) tdlvdian_location = self.assist.img_processor.find_image('img/tdlvdian.bmp',0.85,sdzb) if tdlvdian_location: self.日志输出.info("该山洞已经有伺候探寻") self.assist.gotocs('cs') #随机延时 0.3 -0.5,秒 self.日志输出.info("该山洞已经有伺候探寻 已回城市 准备点击营地") time.sleep(1) self.openyingdi() self.日志输出.info("该山洞已经有伺候探寻 营地点击成功") #随机延时 time.sleep(random.uniform(0.5, 0.8)) self.日志输出.info("点击斥候管理的山洞 准备点击") if not self.dianjichsd(): self.日志输出.info("点击斥候管理的山洞失败") return False self.日志输出.info(f"点击斥候管理的山洞成功 ,查找第{i+1}个山洞") qw_location = self.assist.img_processor.find_images(qwdata,0.7, region=qianwangzb[i]) if not qw_location: self.日志输出.info("第一次重复后再次探洞") return False else: if qw_location: self.日志输出.info(f"点击第{i+1}个山洞") self.assist.mouse.click_position(qw_location) diaocha_location=self.assist.img_processor.wait_find_images(diaochaimgs,0.7,timeout=5) else: self.日志输出.info("该山洞没有被探寻,继续") break if not diaocha_location: self.日志输出.info("多次尝试后仍未找到diaocha图片") return False wgxzb = (diaocha_location['x'] -70, diaocha_location['y'], diaocha_location['x'], diaocha_location ['y'] + 70) wgx_location = self.assist.img_processor.find_image('img/wgx.bmp',0.7,wgxzb) if wgx_location: self.日志输出.info("没有勾选驻扎,正在点击") self.assist.mouse.click_position(wgx_location) time.sleep(random.randint(1, 3) / 1) # 随机延时0.1-0.3秒 self.日志输出.info("找到diaocha,正在点击") self.assist.mouse.click_position(diaocha_location) time.sleep(random.uniform(0.3, 0.5)) # 使用智能等待机制查找派遣图片 #910 75 dao 1080 620查找 region = (1000, 120, 1200, 500) paiqian_location = self.assist.img_processor.wait_find_images({'paiqian':'img/paiqian.bmp'},0.65, region=region) if paiqian_location: #print("找到paiqian,正在点击") self.日志输出.info("找到派遣") self.assist.mouse.click_position(paiqian_location) self.延时.delay() #随机延时 #随机延时 #再次检测派遣是否还在 paiqian_location = self.assist.img_processor.find_image('img/paiqian.bmp',0.65, region=region) if not paiqian_location: self.日志输出.info("已成功派遣") return True #定义一个函数 点击斥候管理里面的山洞 def dianjichsd(self): """处理点击斥候管理里面的山洞操作""" shandong_location = self.assist.img_processor.find_image('img/shandong.bmp',0.75) if not shandong_location: #print("未找到shandong图片") self.日志输出.info("未找到山洞") return False # 计算随机偏移量 offset_x = random.randint(-20, 50) offset_y = random.randint(-2, 15) # 点击shandong位置(带偏移) #print(f"找到shandong,点击位置偏移: x={offset_x}, y={offset_y}") self.assist.mouse.click_position(shandong_location, offset_x, offset_y) #随机延时 #time.sleep(random.uniform(0.3, 0.5)) self.延时.delay() return True def tsqita(self): """处理其他任务点击操作""" #print("开始执行其他任务点击操作") self.日志输出.info("开始执行其他任务点击操作") # 查找qita图片 qita_location = self.assist.img_processor.find_image('img/qita.bmp') if not qita_location: self.日志输出.info("未找到其他任务图片") return False # 点击qita位置(带偏移) #print(f"找到qita,正在点击") self.assist.mouse.click_position(qita_location) self.延时.delay() # 等待界面响应 前往img = { '前往':'img/qw.bmp', '前往2':'img/qw2.bmp', } # 查找qw图片 qw_location = self.assist.img_processor.find_images(前往img,0.65) if not qw_location: self.日志输出.info("未找到前往") return False # 点击qw位置 #print("找到qw,正在点击") self.日志输出.info("点击前往") self.assist.mouse.click_position(qw_location) self.延时.delay() return True def cunzhuang(self): """处理村庄操作""" #print("开始执行村庄任务") self.日志输出.info("开始执行村庄任务") # 查找qita图片 qita_location = self.assist.img_processor.find_image('img/cunzhuang.bmp') if not qita_location: self.日志输出.info("未找到村庄图片") return False # 点击qita位置(带偏移) #print(f"找到村庄,正在点击") self.assist.mouse.click_position(qita_location) self.延时.delay() self.日志输出.info("点击村庄") # 查找qw图片 qw_location = self.assist.img_processor.find_image('img/dahuzi.bmp',0.65) if not qw_location: qw_location = self.assist.img_processor.find_image('img/dahuzi2.bmp',0.65) if not qw_location: print("未找到大胡子图片") return False # 点击大胡子位置 self.assist.mouse.click_position(qw_location) time.sleep(1) # 等待界面响应 res = self.cunmin() #如果返回false 则退出 点击完大胡子 没有找到 村民巨鼎 判断需要进入世界 重新来过 if res == False: if not self.assist.gotocs('sj'): return False return False return True def cunmin(self): """处理村民相关操作""" print("开始执行村民相关操作") # 查找村民图片 cunmin_location = self.assist.img_processor.find_image('img/cunmin.bmp',0.85) if not cunmin_location: print("未找到村民图片") return False else: print("检测到村民") #调用村民 print(cunmin_location) move_x = random.randint(10,10 ) move_y = random.randint(40, 50) #调鼠标点击 self.assist.mouse.click_position(cunmin_location, 0, 40) #延时 time.sleep(random.uniform(0.5, 1.0)) self.assist.mouse.click_position(cunmin_location, 0, 40) #移动到一旁 time.sleep(random.uniform(0.5, 1.0)) move_x = random.randint(30, 80) move_y = random.randint(-50, 50) self.assist.mouse.move_to_side(cunmin_location, move_x, move_y) #延时 time.sleep(random.uniform(0.5, 1.0)) #cunminzi print("检测到村民子") # 智能点击和验证 cunminn ={ 'cunminzi1': 'img/cunminzi.bmp', 'cunminzi2': 'img/cunminzi2.bmp', 'lingqu': 'img/lingqu.bmp', 'lingqu2': 'img/lingqu2.bmp' } cunminzi_location = self.assist.img_processor.find_images(cunminn,0.75) print(" 查找多图:") print(cunminzi_location) if cunminzi_location == None: print("未找到村民子图片") return False elif cunminzi_location['key']== 'lingqu' or cunminzi_location['key']== 'lingqu2' : print("直接领取") self.assist.mouse.click_position(cunminzi_location) time.sleep(1) # 等待界面响应 if not self.assist.gotocs('sj'): return False #随机延时 time.sleep(random.uniform(0.5, 1.0)) if not self.assist.gotocs('cs'): return False return True elif cunminzi_location['key']== 'cunminzi1' or cunminzi_location['key']== 'cunminzi2': print("村民村民 村民") print("检测到村民子") # 计算随机偏移量 offset_x = random.randint(0, 5) offset_y = random.randint(20, 30) # 点击前等待 time.sleep(random.uniform(0.2, 0.4)) # 点击村民子位置 self.assist.mouse.click_position(cunminzi_location) time.sleep(0.1) # 短暂延迟 self.assist.mouse.click_position(cunminzi_location, offset_x, offset_y) # 等待界面响应 time.sleep(random.uniform(0.3, 0.6)) # 如果没有找到领取按钮,再次点击 # 检查当前环境 # 检查领取按钮 #循环3遍 for i in range(3): print(f"第{i+1}次尝试查找领取按钮") # 查找领取按钮 lingqu_location = self.assist.img_processor.find_images(cunminn, 0.75) if lingqu_location: if lingqu_location['key']== 'lingqu' or lingqu_location['key']== 'lingqu2' : print("找到领取按钮,直接点击") self.assist.mouse.click_position(lingqu_location) time.sleep(1) # 等待界面响应 if not self.assist.gotocs('sj'): return False time.sleep(1) # 等待界面响应 if not self.assist.gotocs('cs'): return False return True else: print("找到村民子,再次点击") self.assist.mouse.click_position(cunminzi_location) time.sleep(0.1) # 短暂延迟 self.assist.mouse.click_position(cunminzi_location, offset_x, offset_y) # 按下ESC win32api.keybd_event(0x1B, 0, 0, 0) # 按下ESC time.sleep(0.1) win32api.keybd_event(0x1B, 0, win32con.KEYEVENTF_KEYUP, 0) # 释放ESC time.sleep(random.uniform(0.3, 0.6)) return False else: print("未找到村民子图片") return False def tszc(self): """处理探索和派遣操作""" if self.kaitudata['kaitu'] ==False: self.日志输出.info("开图功能未开启") #print("开图功能未开启") return False self.日志输出.info("开始执行探索和派遣操作") #查找 weixuanzc 是否存在 如果存在则点击 #查找cha 如果不存在 则点击 cha_location = self.assist.img_processor.find_image('img/cha.bmp') if not cha_location: self.日志输出.info("未找到cha图片,当前不在侦查界面") weixuanzc_location = self.assist.img_processor.find_image('img/weixuanzc.bmp',0.85) if weixuanzc_location: self.日志输出.info("找到weixuanzc,正在点击") self.assist.mouse.click_position(weixuanzc_location) time.sleep(0.5) else: self.日志输出.info("应该不在侦查界面") return False # 第一次查找并点击tansuo图片 tansuo_location = self.assist.img_processor.find_image('img/tansuo.bmp') if not tansuo_location: tansuo_location = self.assist.img_processor.find_image('img/tansuo2.bmp', threshold=0.7) if not tansuo_location: self.日志输出.info("未找到tansuo图片") #关闭 查找关闭 guanbi_location = self.assist.img_processor.find_image('img/guanbi.bmp') if not guanbi_location: self.日志输出.info("未找到关闭图片") return False self.日志输出.info("找到关闭,正在点击") self.assist.mouse.click_position(guanbi_location) return False self.日志输出.info("找到tansuo斥候管理里面的探索,正在点击") self.assist.mouse.click_position(tansuo_location) 探索图片 = { 'ts1':'img/tansuo.bmp', 'ts2':'img/tansuo2.bmp', } self.延时.delay() self.延时.delay() # 第二次查找并点击tansuo图片 探索返回 = self.assist.img_processor.wait_find_images(探索图片,threshold=0.75,timeout=5) if 探索返回: self.日志输出.info("找到tansuo世界探索任务,正在点击") self.延时.delay() #查找图片wgx 如果存在 则点击 #起始点 x1= tansuo_location x1 -70 y1 = tansuo_location y1 x2 = tansuo_location x1 y2 = tansuo_location y1 + 70 wgxzb = (探索返回['x'] -70, 探索返回['y'], 探索返回['x'], 探索返回 ['y'] + 70) wgx_location = self.assist.img_processor.find_image('img/wgx.bmp',0.85,wgxzb) if wgx_location: self.日志输出.info("没有勾选驻扎,正在点击") self.assist.mouse.click_position(wgx_location) self.延时.delay() self.日志输出.info(f"点击探索") self.assist.mouse.click_position(探索返回) #self.assist.mouse.click_position(探索返回) self.延时.delay() else: return False self.延时.delay() # 使用智能等待机制查找派遣图片 paiqian_location = self.findpaiqian() if not paiqian_location: self.日志输出.info("多次尝试后仍未找到paiqian图片,尝试关闭斥候管理") #关闭 查找关闭 max_attempts = 3 attempt = 0 guanbi_location = None while attempt < max_attempts: guanbi_location = self.assist.img_processor.find_image('img/guanbi.bmp') if guanbi_location: self.日志输出.info("找到关闭按钮,正在点击") self.assist.mouse.click_position(guanbi_location) time.sleep(random.randint(1, 3) / 10) # 随机延时0.1-0.3秒 break self.日志输出.info(f"第{attempt + 1}次尝试未找到关闭按钮") attempt += 1 time.sleep(random.randint(1, 3) / 10) # 每次尝试之间随机等待0.1-0.3秒 if not guanbi_location: self.日志输出.info("多次尝试后仍未找到关闭按钮") return False #再次确认派遣是否存在 region = (910, 75, 1080, 620) paiqian_location = self.assist.img_processor.find_image('img/paiqian.bmp',0.65, region=region) if not paiqian_location: self.日志输出.info("多次尝试后仍未找到派遣图片") paiqian_location = self.findpaiqian() return False return True def findpaiqian(self): """查找派遣图片""" #910 75 dao 1080 620查找 region = (1000, 120, 1200, 500) paiqian_location = self.assist.img_processor.wait_find_images({'paiqian':'img/paiqian.bmp'},0.65, region=region) if paiqian_location: self.日志输出.info("找到paiqian,正在点击") #随机延时 time.sleep(random.randint(1, 3) / 10) # 随机延时0.1-0.3秒 self.assist.mouse.click_position(paiqian_location) return True #随机延时 else: self.日志输出.info("派遣伺候失败") return False def execute(self): """执行主逻辑""" while True: # 检查停止信号 if hasattr(self, 'should_stop_ref') and callable(self.should_stop_ref) and self.should_stop_ref(): print("[DEBUG] kaitu.execute() 收到停止信号") self.日志输出.info("📋 开图任务已终止") return True if self.assist.gotocs('cs'): #print("成功回到城市") #判断返回值 rt = self.openyingdi() if rt == 1: self.日志输出.info("成功打开营地") elif rt == 2: self.日志输出.info("打开营地失败") elif rt == 3: self.日志输出.info("斥候任务中") else: self.日志输出.info("回到城市失败") rt = self.check_task() if rt == 1: self.日志输出.info("有山洞") if self.tsshandong(): self.日志输出.info("山洞任务完成") else: self.日志输出.info("山洞任务失败 去开图") if self.tszc(): self.日志输出.info("开图任务完成") else: self.日志输出.info("开图任务失败") elif rt == 2: self.日志输出.info("其他") if self.tsqita(): self.日志输出.info("其他任务完成") else: self.日志输出.info("其他任务失败") elif rt == 3: #判断开图是否开启 self.日志输出.info("开图") if self.tszc(): self.日志输出.info("开图任务完成") else: self.日志输出.info("开图任务失败") elif rt == 4: self.日志输出.info("有村庄投靠") if self.cunzhuang(): self.日志输出.info("村庄任务完成") else: self.日志输出.info("村庄任务失败") else : self.日志输出.info("无任务") return False 加入我的结构
最新发布
11-01
import os import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset, DataLoader, random_split import pandas as pd from torch.optim import AdamW from tqdm import tqdm import re import math from dataset import Evo2SpliceSiteDataset from data_preparation import prepare_datapoints_ensemble from Spliceformer.Code.src.evaluation_metrics import print_topl_statistics, cross_entropy_2d from sklearn.utils.class_weight import compute_class_weight import numpy as np from transformers import Trainer, TrainingArguments import argparse from sklearn.metrics import precision_recall_curve, auc, average_precision_score try: from evo2 import Evo2 from vortex.model.layers import RMSNorm except ImportError: print("Evo2库未找到。请确保已正确安装。") print("您可能需要在evo2-main目录下运行: pip install .") exit() # LoRA层定义 class LoRALayer(nn.Module): def __init__(self, original_layer: nn.Linear, rank: int, alpha: int): super().__init__() self.original_layer = original_layer self.rank = rank self.alpha = alpha self.in_features = original_layer.in_features self.out_features = original_layer.out_features model_dtype = getattr(original_layer.weight, 'dtype', torch.float32) if torch.cuda.is_available() and torch.cuda.get_device_capability()[0] >= 8: model_dtype = torch.bfloat16 self.lora_A = nn.Parameter(torch.zeros(self.in_features, rank, dtype=model_dtype)) self.lora_B = nn.Parameter(torch.zeros(rank, self.out_features, dtype=model_dtype)) nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5)) self.scaling = (self.alpha / self.rank) if model_dtype == torch.bfloat16: self.scaling = torch.tensor(self.scaling, dtype=torch.bfloat16) self.original_layer.weight.requires_grad = False if self.original_layer.bias is not None: self.original_layer.bias.requires_grad = False def forward(self, x: torch.Tensor) -> torch.Tensor: cloned_weight = self.original_layer.weight.clone() cloned_bias = self.original_layer.bias.clone() if self.original_layer.bias is not None else None original_output = F.linear(x, cloned_weight, cloned_bias) if self.rank > 0: if isinstance(self.scaling, torch.Tensor): current_scaling = self.scaling.to(device=x.device, dtype=x.dtype) else: current_scaling = torch.tensor(self.scaling, dtype=x.dtype, device=x.device) lora_A_final = self.lora_A.to(x.dtype) lora_B_final = self.lora_B.to(x.dtype) lora_addition = (x @ lora_A_final @ lora_B_final) lora_output = lora_addition * current_scaling return original_output + lora_output return original_output def extra_repr(self): return f'original_in_features={self.in_features}, original_out_features={self.out_features}, rank={self.rank}, alpha={self.alpha}' # 应用LoRA到Evo2 def apply_lora_to_evo2_model(evo2_base_module: nn.Module, lora_rank: int, lora_alpha: int, target_module_patterns: list[str]): lora_applied_count = 0 for name, module in evo2_base_module.named_modules(): if isinstance(module, nn.Linear): for pattern in target_module_patterns: if re.fullmatch(pattern, name): try: parent_name, child_name = name.rsplit('.', 1) parent_module = evo2_base_module.get_submodule(parent_name) original_linear_layer = getattr(parent_module, child_name) if not isinstance(original_linear_layer, LoRALayer): lora_replacement = LoRALayer(original_linear_layer, lora_rank, lora_alpha) setattr(parent_module, child_name, lora_replacement) lora_applied_count += 1 print(f"已将LoRA应用于: {name}") else: print(f"LoRA已存在于: {name}, 跳过。") break except Exception as e: print(f"为 {name} 应用LoRA失败: {e}") if lora_applied_count == 0: print("警告: 没有层与目标LoRA模式匹配。") else: print(f"总共将LoRA应用于 {lora_applied_count} 个层。") return lora_applied_count # 新增一个包含分类头的Evo2模型封装类 class Evo2ForSequenceLabeling(nn.Module): def __init__(self, evo2_base: nn.Module, hidden_dim: int, num_classes: int): super().__init__() self.evo2_base = evo2_base self.layer_name='blocks.20.mlp.l3' # Evo2模型通常有embed_dim,也就是hidden_dim # 这里hidden_dim需要与Evo2输出隐层维度保持一致 # 假设evo2_base的输出跟词表一样维度,但我们需要隐藏层,这里直接调用evo2_base得到logits,实际你需要找到合适隐层输出 # 为方便,此处先用线性层凸显设计,假设evo2_base输出是隐层 self.classifier = nn.Linear(hidden_dim, num_classes) self.classifier = self.classifier.to(dtype=torch.bfloat16) def forward(self, x): # 假设evo2_base返回的第一个输出是logits或隐层,形状 (B, L, H) # 如果evo2_base直接给的是词表大小,且没暴露隐层,需要适配模型代码暴露隐层或用proxy策略 # 这里暂将evo2_base输出作为隐层,如有需要请自行修改 base_output, _ = self.evo2_base(x) # (B, L, vocab_size) #outputs, base_output = self.evo2_base(x, return_embeddings=True, layer_names=[self.layer_name]) # 由于 base_output 是 vocab_size 维度,我们要切换为隐层向量,正常需修改 base 模型源码 # 这里做一个简单示范:用 base_output 作为线性层输入(不合理,但方便跑代码,原则上需改模型源码) # 建议真正项目中,基于evo2_base.model前向方法加hook获取隐藏层 logits = self.classifier(base_output) # (B, L, num_classes) return logits class Evo2ForSequenceLabeling_v2(nn.Module): def __init__(self, evo2_base: nn.Module, hidden_dim: int, num_classes: int, target_layer_idx: int = 20): super().__init__() self.evo2_base = evo2_base self.target_layer_idx = target_layer_idx self.hidden_states = None # Register hook on specific block self.hook_handle = self.evo2_base.blocks[target_layer_idx].register_forward_hook(self.hook_fn) # Classification head #self.classifier = nn.Linear(hidden_dim, num_classes) self.classifier = nn.Sequential( nn.Linear(hidden_dim, 512), nn.LayerNorm(512), nn.GELU(), nn.Dropout(0.1), nn.Linear(512, 128), nn.LayerNorm(128), nn.GELU(), nn.Dropout(0.1), nn.Linear(128, num_classes) ) self.classifier = self.classifier.to(dtype=torch.bfloat16) def hook_fn(self, module, input, output): """Hook function to capture hidden states""" if isinstance(output, tuple): self.hidden_states = output[0].clone() # (B, L, H) else: self.hidden_states = output.clone() def forward(self, x): # Reset hidden states self.hidden_states = None # Forward pass through base model (this will trigger the hook) _ = self.evo2_base(x) # This triggers the hook if self.hidden_states is None: raise RuntimeError(f"Failed to capture hidden states from layer {self.target_layer_idx}") # Use captured hidden states for classification logits = self.classifier(self.hidden_states) # (B, L, num_classes) return logits def __del__(self): """Clean up hook when object is destroyed""" if hasattr(self, 'hook_handle') and self.hook_handle is not None: self.hook_handle.remove() class Evo2ForSequenceLabeling_v4(nn.Module): """基于真实Evo2模型结构的序列标注模型""" def __init__(self, evo2_base: nn.Module, hidden_dim: int, num_classes: int, target_layer_idx: int = 20): super().__init__() self.evo2_base = evo2_base self.target_layer_idx = target_layer_idx self.hidden_dim = hidden_dim # Classification head self.classifier = nn.Linear(hidden_dim, num_classes) self.classifier = self.classifier.to(dtype=torch.bfloat16) # 获取目标设备(与目标层相同的设备) self.target_device = next(evo2_base.blocks[target_layer_idx].parameters()).device self.classifier = self.classifier.to(self.target_device) def forward(self, x): """手动执行前向传播到目标层""" batch_size, seq_len = x.shape # 1. Embedding层 # 确保输入在正确设备上 embed_device = next(self.evo2_base.embedding_layer.parameters()).device x = x.to(embed_device) hidden_states = self.evo2_base.embedding_layer(x) # (B, L, H) # 2. 执行到目标层(包括目标层) for i in range(self.target_layer_idx + 1): block = self.evo2_base.blocks[i] # 处理跨设备传输 block_device = next(block.parameters()).device hidden_states = hidden_states.to(block_device) # 执行block的前向传播 hidden_states, _ = block(hidden_states, inference_params=None, padding_mask=None) # 3. 确保hidden_states在分类器设备上 hidden_states = hidden_states.to(self.target_device) # 4. 分类头 logits = self.classifier(hidden_states) # (B, L, num_classes) return logits def set_lora_requires_grad(evo2_base_module, target_patterns): """ 仅对匹配pattern的LoRALayer打开梯度,其他LoRA参数冻结。 """ for name, module in evo2_base_module.named_modules(): if isinstance(module, LoRALayer): # 是否匹配允许训练的pattern中任意一个 if any(re.fullmatch(pattern, name) for pattern in target_patterns): # 允许这些LoRA参数参与训练 module.lora_A.requires_grad_(True) module.lora_B.requires_grad_(True) print(f"开启LoRA梯度: {name}") else: # 其他LoRA层冻结 module.lora_A.requires_grad_(False) module.lora_B.requires_grad_(False) print(f"冻结LoRA梯度: {name}") def prepare_datasets(data_dir, SL, CL_max, tokenizer, train_val_split=0.9): print("Preparing training dataset...") train_datapoints, train_seqData = prepare_datapoints_ensemble(data_dir, "train", SL, CL_max) print(f"Number of training datapoints: {len(train_datapoints)}") full_train_dataset = Evo2SpliceSiteDataset( train_datapoints, train_seqData, tokenizer, max_length=SL, cache_file="cached_dataset_ensemble_5000" ) print(f"Number of full_train_dataset: {len(full_train_dataset)}") train_size = int(train_val_split * len(full_train_dataset)) val_size = len(full_train_dataset) - train_size train_dataset, val_dataset = random_split(full_train_dataset, [train_size, val_size]) return train_dataset, val_dataset def extract_all_labels_from_dataset(dataset, pad_token_id=-100): all_labels = [] for _, labels in dataset: # labels是Tensor,比如 (L,) filtered = labels[labels != pad_token_id].cpu().numpy() all_labels.extend(filtered.tolist()) return all_labels def test_model(model, test_dataset, device='cuda:0', batch_size=1, CL_max=0): """测试模型函数""" print("Starting model evaluation...") # 确保模型在指定设备上(测试时使用单GPU) model = model.to(device) model.eval() test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=4, pin_memory=True) Y_true_acceptor = [] Y_true_donor = [] Y_pred_acceptor = [] Y_pred_donor = [] ce_2d = [] with torch.no_grad(): for input_ids, targets in tqdm(test_loader, desc="Testing"): input_ids = input_ids.to(device) targets = targets.to(device) # 中心区域截取 (类似参考代码) if CL_max > 0: targets = targets[:, CL_max//2:-CL_max//2] outputs = model(input_ids) if outputs.dim() == 3: if outputs.shape[1] == 3: # [B, 3, L] outputs = outputs.permute(0, 2, 1) # -> [B, L, 3] # 应用softmax获得概率 outputs = torch.softmax(outputs, dim=-1) # 截取对应区域的输出 if CL_max > 0: outputs = outputs[:, CL_max//2:-CL_max//2, :] # 转换为numpy targets_np = targets.cpu().numpy() # [B, L] outputs_np = outputs.cpu().numpy() # [B, L, 3] # 转换标签为one-hot格式 targets_onehot = np.zeros((targets_np.shape[0], targets_np.shape[1], 3)) for i in range(3): targets_onehot[:, :, i] = (targets_np == i).astype(float) # 计算交叉熵 ce_2d.append(cross_entropy_2d(targets_onehot, outputs_np)) # 筛选有表达的样本 is_expr = (targets_onehot.sum(axis=(1,2)) >= 1) # 收集acceptor和donor的真实值和预测值 Y_true_acceptor.extend(targets_onehot[is_expr, :, 1].flatten()) Y_true_donor.extend(targets_onehot[is_expr, :, 2].flatten()) Y_pred_acceptor.extend(outputs_np[is_expr, :, 1].flatten()) Y_pred_donor.extend(outputs_np[is_expr, :, 2].flatten()) # 计算平均交叉熵 mean_ce = np.mean(ce_2d) print('Cross entropy = {:.5f}'.format(mean_ce)) # 转换为numpy数组 Y_true_acceptor = np.array(Y_true_acceptor) Y_pred_acceptor = np.array(Y_pred_acceptor) Y_true_donor = np.array(Y_true_donor) Y_pred_donor = np.array(Y_pred_donor) print("\n\033[1mAcceptor:\033[0m") acceptor_results = print_topl_statistics(Y_true_acceptor, Y_pred_acceptor) print("\n\033[1mDonor:\033[0m") donor_results = print_topl_statistics(Y_true_donor, Y_pred_donor) return { "cross_entropy": mean_ce, "acceptor_results": acceptor_results, "donor_results": donor_results } def apply_model_patches(): """应用模型补丁""" # --- Monkey-patch RMSNorm to avoid 'Inference tensors cannot be saved for backward' --- START if 'RMSNorm' in globals() and hasattr(RMSNorm, 'forward'): print("Attempting to patch RMSNorm.forward...") def patched_rmsnorm_forward(self_rmsnorm, x_input: torch.Tensor) -> torch.Tensor: variance = x_input.float().pow(2).mean(-1, keepdim=True) rsqrt_term = torch.rsqrt(variance + self_rmsnorm.eps) normalized_and_typed_x = (x_input * rsqrt_term).type_as(x_input) cloned_weight = self_rmsnorm.scale.clone() return cloned_weight * normalized_and_typed_x RMSNorm.forward = patched_rmsnorm_forward print("RMSNorm.forward method has been successfully patched.") else: print("RMSNorm class not found or does not have 'forward' attribute, skipping patch.") def apply_unembed_patch(evo2_base_module): """应用unembed补丁""" if hasattr(evo2_base_module, 'unembed') and \ hasattr(evo2_base_module.unembed, 'func') and \ hasattr(evo2_base_module.unembed.func, '__self__'): print("尝试修补 unembed 函数以解决 inference tensor 问题...") embedding_module_instance = evo2_base_module.unembed.func.__self__ if not hasattr(embedding_module_instance, 'weight'): print("警告: 用于 unembed 补丁的目标嵌入模块没有 'weight' 属性。跳过补丁。") else: def patched_unembed_logic(u_tensor: torch.Tensor) -> torch.Tensor: if not hasattr(embedding_module_instance, 'process_group') or embedding_module_instance.process_group is None: cloned_weight = embedding_module_instance.weight.clone() return u_tensor @ cloned_weight.T else: print(f"已修补的 unembed 在 {type(embedding_module_instance).__name__} 上调用,并带有 process_group。此情况按原始逻辑引发 NotImplementedError。") raise NotImplementedError("VocabParallelEmbedding 中未实现带 process_group 的 Unembed。") evo2_base_module.unembed.func = patched_unembed_logic print(f"已成功为 {type(embedding_module_instance).__name__} 修补 'unembed.func' 以处理 inference tensor。") else: print("警告: 未能找到 'evo2_base_module.unembed.func' 或其 '__self__' 属性进行修补。跳过 unembed 补丁。") def load_trained_model(checkpoint_path, evo2_base_module, device): """Load trained model from checkpoint""" checkpoint = torch.load(checkpoint_path, map_location=device) config = checkpoint['config'] # Recreate model wrapper model_wrapper = Evo2ForSequenceLabeling_v4( evo2_base_module, hidden_dim=config['hidden_dim'], num_classes=config['num_classes'], target_layer_idx=config['target_layer_idx'] ).to(device) # Load LoRA weights evo2_base_module.load_state_dict(checkpoint['lora_weights'], strict=False) # Load classifier weights model_wrapper.classifier.load_state_dict(checkpoint['classifier_weights']) return model_wrapper def compute_metrics(eval_pred): """计算评估指标""" predictions, labels = eval_pred print(f"predictions shape: {predictions.shape}") print(f"labels shape: {labels.shape}") print(f"compute_metrics called - predictions shape: {predictions.shape}, labels shape: {labels.shape}") predictions = torch.softmax(torch.from_numpy(predictions), dim=-1).numpy() # 转换标签为one-hot格式 targets_onehot = np.zeros((labels.shape[0], labels.shape[1], 3)) for i in range(3): targets_onehot[:, :, i] = (labels == i).astype(float) # 计算交叉熵 ce_2d_values = [] for i in range(predictions.shape[0]): ce_val = cross_entropy_2d( targets_onehot[i:i+1], predictions[i:i+1] ) ce_2d_values.append(ce_val) mean_ce = np.mean(ce_2d_values) is_expr = (targets_onehot.sum(axis=(1,2)) >= 1) # 提取acceptor和donor的真实标签和预测 Y_true_acceptor = targets_onehot[is_expr, :, 1].flatten() Y_true_donor = targets_onehot[is_expr, :, 2].flatten() Y_pred_acceptor = predictions[is_expr, :, 1].flatten() Y_pred_donor = predictions[is_expr, :, 2].flatten() acceptor_results = _calculate_metrics(Y_true_acceptor, Y_pred_acceptor) donor_results = _calculate_metrics(Y_true_donor, Y_pred_donor) metrics = { "cross_entropy": mean_ce, "acceptor_topkl_accuracy": acceptor_results[0], "acceptor_auprc": acceptor_results[1], "acceptor_thr": acceptor_results[2], "acceptor_correct_length": acceptor_results[3], "acceptor_tol_length": acceptor_results[4], "acceptor_idx_true": acceptor_results[5], "donor_topkl_accuracy": donor_results[0], "donor_auprc": donor_results[1], "donor_thr": donor_results[2], "donor_correct_length": donor_results[3], "donor_tol_length": donor_results[4], "donor_idx_true": donor_results[5], } print(f"Computed metrics: {metrics}") return metrics def _calculate_metrics(y_true, y_pred): """计算TopKL准确率和AUPRC指标""" idx_true = np.nonzero(y_true == 1)[0] argsorted_y_pred = np.argsort(y_pred) sorted_y_pred = np.sort(y_pred) topkl_accuracy = [] threshold = [] for top_length in [0.5, 1, 2, 4]: idx_pred = argsorted_y_pred[-int(top_length*len(idx_true)):] correct = np.size(np.intersect1d(idx_true, idx_pred)) total = float(min(len(idx_pred), len(idx_true))) if top_length == 1: correct_1 = correct total_1 = total topkl_accuracy.append(correct / total) # 计算阈值:使用 min 来防止索引越界 threshold_value = sorted_y_pred[-min(int(top_length * len(idx_true)), len(sorted_y_pred))] threshold.append(threshold_value) auprc = average_precision_score(y_true, y_pred) threshold_print = ["{:0.4f}".format(v.item() if isinstance(v, np.ndarray) else v) for v in threshold] for i in range(len(topkl_accuracy)): topkl_accuracy[i] = np.round(topkl_accuracy[i], 4) return (topkl_accuracy, np.round(auprc, 4), threshold_print, correct_1, total_1, len(idx_true)) class Evo2Trainer(Trainer): def __init__(self, class_weights_tensor=None, *args, **kwargs): super().__init__(*args, **kwargs) self.class_weights_tensor = class_weights_tensor if class_weights_tensor is not None: self.class_weights_tensor = class_weights_tensor.to(self.args.device) def compute_loss(self, model, inputs, return_outputs=False): """ 重写 compute_loss,应用加权交叉熵 假设 inputs 内含 'labels' """ labels = inputs.get("labels") outputs = model(**inputs) # 模型返回 logits,通常第0个元素是 logits logits = outputs.logits if hasattr(outputs, "logits") else outputs # 损失函数使用带权重的交叉熵 if self.class_weights_tensor is not None: loss_fct = nn.CrossEntropyLoss(weight=self.class_weights_tensor, ignore_index=self.args.pad_token_id) else: loss_fct = nn.CrossEntropyLoss(ignore_index=self.args.pad_token_id) # logits shape: (B, L, num_classes),需要扁平化 loss = loss_fct(logits.view(-1, logits.size(-1)), labels.view(-1)) return (loss, outputs) if return_outputs else loss def main(args): # 设置随机种子和安全设置 torch.serialization.add_safe_globals([__import__('_codecs').encode]) # 创建输出目录 os.makedirs(args.output_dir, exist_ok=True) # 加载基础模型 print(f"加载基础模型: {args.base_model_name}...") try: evo2_wrapper = Evo2(model_name=args.base_model_name, local_path=args.local_model_path) except Exception as e: print(f"加载Evo2模型失败: {e}") return tokenizer = evo2_wrapper.tokenizer evo2_base_module = evo2_wrapper.model # 应用模型补丁 apply_model_patches() apply_unembed_patch(evo2_base_module) # 配置LoRA微调 print("冻结原始模型参数并应用LoRA...") for param in evo2_base_module.parameters(): param.requires_grad = False apply_lora_to_evo2_model(evo2_base_module, args.lora_rank, args.lora_alpha, args.target_module_patterns) set_lora_requires_grad(evo2_base_module, args.trainable_lora_patterns) lora_params = [p for p in evo2_base_module.parameters() if p.requires_grad] if not lora_params: print("未找到可训练的LoRA参数,退出。") return print(f"找到 {len(lora_params)} 个可训练参数(LoRA部分)。") # 创建模型包装器 target_layer = evo2_base_module.blocks[args.target_layer_idx].mlp.l3 hidden_dim = target_layer.out_features model_wrapper = Evo2ForSequenceLabeling_v2( evo2_base_module, hidden_dim=hidden_dim, num_classes=args.num_classes, target_layer_idx=args.target_layer_idx ) # 启用分类器参数训练 for param in model_wrapper.classifier.parameters(): param.requires_grad = True # 准备数据集 print("加载数据集...") train_dataset, val_dataset = prepare_datasets(args.data_dir, args.sequence_length, args.CL_max, tokenizer) if len(train_dataset) == 0: print("训练集为空,退出。") return # 计算类权重 all_train_labels = extract_all_labels_from_dataset(train_dataset, pad_token_id=args.pad_token_id) classes = np.array([i for i in range(args.num_classes)]) class_weights = compute_class_weight(class_weight='balanced', classes=classes, y=all_train_labels) class_weights_tensor = torch.tensor(class_weights, dtype=torch.float32) training_args = TrainingArguments( output_dir="./output_dir", num_train_epochs=args.epochs, per_device_train_batch_size=args.batch_size, per_device_eval_batch_size=args.batch_size, learning_rate=args.learning_rate, evaluation_strategy="epoch", # 或 'steps' eval_steps=args.eval_steps, save_strategy="epoch", gradient_accumulation_steps=args.grad_accumulation_steps, logging_dir="./logs", logging_steps=50, save_total_limit=3, bf16=args.bf16, ) # 创建训练器并开始训练 trainer = Evo2Trainer( model=model_wrapper, args=training_args, train_dataset=train_dataset, eval_dataset=val_dataset, class_weights_tensor=class_weights_tensor, tokenizer=tokenizer, compute_metrics=compute_metrics ) trainer.train() # 最终评估 metrics = trainer.evaluate() print(metrics) if __name__ == "__main__": # 参数解析 parser = argparse.ArgumentParser(description="Evo2 序列标注微调") # 模型参数 parser.add_argument("--base_model_name", type=str, default="evo2_1b_base", help="基础模型名称") parser.add_argument("--local_model_path", type=str, default='/ailab/user/wuhaoning/liumingfei/evo2_1b_base/evo2_1b_base.pt', help="本地模型路径") parser.add_argument("--output_dir", type=str, default="evo2_finetuned_seq_labeling_lora", help="输出目录") parser.add_argument("--num_classes", type=int, default=3, help="分类数量") parser.add_argument("--target_layer_idx", type=int, default=20, help="目标层索引") # 训练参数 parser.add_argument("--batch_size", type=int, default=2, help="批大小") parser.add_argument("--epochs", type=int, default=3, help="训练轮数") parser.add_argument("--learning_rate", type=float, default=5e-5, help="学习率") parser.add_argument("--grad_accumulation_steps", type=int, default=8, help="梯度累积步数") parser.add_argument("--pad_token_id", type=int, default=-100, help="填充标记ID") parser.add_argument('--bf16', type=bool, default=True, help='使用bf16混合精度') # 数据参数 parser.add_argument("--data_dir", type=str, default="../Data", help="数据目录") parser.add_argument("--sequence_length", type=int, default=5000, help="序列长度") parser.add_argument("--CL_max", type=int, default=0, help="CL最大值") parser.add_argument("--train_val_split", type=float, default=0.9, help="训练验证集分割比例") parser.add_argument("--cache_file", type=str, default="cached_dataset_ensemble_5000", help="缓存文件") # 验证策略参数 parser.add_argument("--eval_strategy", type=str, default="steps", choices=["epoch", "steps"], help="验证策略: 'epoch' 每个epoch后验证, 'steps' 每多少步验证一次") parser.add_argument("--eval_steps", type=int, default=500, help="当eval_strategy='steps'时,每多少步验证一次") # LoRA参数 parser.add_argument("--lora_rank", type=int, default=4, help="LoRA rank") parser.add_argument("--lora_alpha", type=int, default=8, help="LoRA alpha") parser.add_argument("--target_module_patterns", type=str, nargs='+', default=[ r"blocks\.\d+\.mlp\.l1", r"blocks\.\d+\.mlp\.l3", r"blocks\.\d+\.inner_mha_cls\.Wqkv", ], help="LoRA目标模块模式") parser.add_argument("--trainable_lora_patterns", type=str, nargs='+', default=[ r"blocks\.20\.mlp\.l3", r"blocks\.20\.mlp\.l1", r"blocks\.19\.mlp\.l3", r"blocks\.19\.mlp\.l1", r"blocks\.18\.mlp\.l3", r"blocks\.18\.mlp\.l1", r"blocks\.17\.mlp\.l3", r"blocks\.17\.mlp\.l1", r"blocks\.17\.inner_mha_cls\.Wqkv", ], help="可训练的LoRA层模式") args = parser.parse_args() main(args) 以上代码训练的时候没有任务输出日志或者打印内容,请添加,方便我监视训练状态
07-30
程序主入口main.py代码为下列所示show_message_signal = pyqtSignal(str, str, str) # 类型, 标题, 内容 def __init__(self): super().__init__() ui_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wanhao.bak.ui') self.ui = uic.loadUi(ui_path) self.selected_image_path = None self.delay_system = delay_system # 提前初始化延时系统 # 移除所有授权验证相关的初始化 self.setup_ui() self.connect_signals() self.load_settings() # 先初始化logger log_widget = self.ui.findChild(QTextEdit, "findPicLog") self.logger = Logger(name='MainWindow', log_file='logs/app.log', max_lines=1000, ui=log_widget) # 然后注册热键(这样logger就可用了) self.register_hotkey() self.找图类 = ImageProcessor() # 添加信号与槽的连接 self.show_message_signal.connect(self.show_message_slot) # 为时间控件添加数字验证器 self.setup_number_validators() # 添加线程暂停控制变量 self.should_pause = False self.pause_event = threading.Event() self.pause_event.set() # 初始设置为运行状态 # 初始化序列号显示为永久有效 self._init_serial_number_display() def _init_serial_number_display(self): """初始化序列号显示为永久有效""" try: # 获取序列号输入控件 self.serial_input = self.ui.findChild(QLineEdit, "serialNumberInput") # 获取到期时间显示控件 self.endtime_label = self.ui.findChild(QLabel, "endtimeti") if self.serial_input: self.serial_input.setText("永久授权版") self.serial_input.setReadOnly(True) # 设置为只读 if self.endtime_label: self.endtime_label.setText("永久有效") self.endtime_label.setStyleSheet("color: green; font-weight: bold;") except Exception as e: print(f"初始化序列号显示失败: {str(e)}") def on_serial_number_changed(self): """序列号输入框内容变化时的处理 - 已禁用""" pass def verify_serial_and_update_display(self, serial): """验证序列号并更新显示 - 已禁用""" pass def _background_verify_serial_for_display(self, serial): """后台线程中查询序列号状态 - 已禁用""" pass def update_endtime_display(self, endtime_text, status_text): """更新到期时间显示 - 直接显示永久有效""" try: if self.endtime_label: self.endtime_label.setText("永久有效") self.endtime_label.setStyleSheet("color: green; font-weight: bold;") except Exception as e: print(f"更新显示失败: {str(e)}") def closeEvent(self, event): """程序关闭事件 - 移除授权验证清理""" try: # 移除所有授权验证相关的停止代码 if hasattr(self, 'token_timer'): self.token_timer.stop() if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): self.关闭线程() except Exception as e: print(f"关闭程序时出错: {str(e)}") finally: event.accept() def register_hotkey(self): """注册F10和F12全局热键(兼容版,处理不同keyboard库版本)""" try: import keyboard # 尝试清除之前的热键注册(兼容不同版本) try: keyboard.clear_all_hotkeys() except AttributeError: # 旧版本keyboard库可能没有这个方法 pass # 使用更兼容的热键注册方式 def safe_add_hotkey(key, callback): try: keyboard.add_hotkey(key, callback) return True except Exception as e: print(f"[WARNING] 热键 {key} 注册失败: {e}") return False # 注册热键 f10_success = safe_add_hotkey('F10', self.on_start_clicked) f12_success = safe_add_hotkey('F12', self.on_end_clicked) if f10_success and f12_success: self.logger.info("✅ F10和F12热键注册成功") elif f10_success: self.logger.info("✅ F10热键注册成功,F12注册失败") elif f12_success: self.logger.info("✅ F12热键注册成功,F10注册失败") else: self.logger.warning("⚠️ 热键注册失败,请使用界面按钮") except ImportError: self.logger.warning("⚠️ keyboard库未安装,热键功能不可用") except Exception as e: self.logger.error(f"❌ 热键注册失败: {str(e)}") self.logger.info("💡 可以使用界面按钮代替热键") def setup_ui(self): """设置UI界面""" self.ui.show() # 设置findPicLog控件为不可编辑 find_pic_log_widget = self.ui.findChild(QTextEdit, "findPicLog") if find_pic_log_widget: find_pic_log_widget.setReadOnly(True) # 添加morenwj控件的处理 self.morenwj_checkbox = self.ui.findChild(QCheckBox, "morenwj") if self.morenwj_checkbox: self.morenwj_checkbox.toggled.connect(self.on_morenwj_toggled) # 初始状态处理 self.on_morenwj_toggled(self.morenwj_checkbox.isChecked()) else: print("未找到morenwj控件") # 设置滑块控件与标签映射 self.setup_sliders() # 只连接必要信号,控件显示状态在load_settings中处理 for i in range(1, 6): tian_combo = self.ui.findChild(QComboBox, f"caijidl{i}tian") combo_name = f"caijidl{i}kj" combo = self.ui.findChild(QComboBox, combo_name) if tian_combo: tian_combo.currentTextChanged.connect( lambda text, idx=i: self.on_caijidl_tian_changed(text, idx) ) else: print(f"未找到caijidl{i}tian控件") if combo: combo.setVisible(False) else: print(f"未找到{combo_name}控件") # 获取start按钮并连接点击事件 start_btn = self.ui.findChild(QPushButton, "start") if start_btn: start_btn.clicked.connect(self.on_start_clicked) else: print("未找到start按钮") # 获取中心点扩散找图按钮并连接点击事件 start_btn = self.ui.findChild(QPushButton, "kuoFindPic") if start_btn: start_btn.clicked.connect(self.on_kuoFindPic_clicked) else: print("未找到kuoFindPic按钮") # 设置滑块控件与标签映射 self.setup_sliders() def setup_sliders(self): """设置滑块控件与标签的映射关系""" # 流畅度滑块 (0-100 整数) self.liucangdu_slider = self.ui.findChild(QSlider, "liucangdu") self.liucangdunum_label = self.ui.findChild(QLabel, "liucangdunum") if self.liucangdu_slider and self.liucangdunum_label: self.liucangdu_slider.setRange(0, 100) self.liucangdu_slider.setValue(20) # 默认值 self.liucangdunum_label.setText(f"{self.liucangdu_slider.value()}") self.liucangdu_slider.valueChanged.connect(self.on_liucangdu_changed) else: print("未找到流畅度滑块或标签控件") # Findhuadong滑块 (0-4 支持小数点,使用0-400表示0.00-4.00) self.Findhuadong_slider = self.ui.findChild(QSlider, "Findhuadong") self.Findhuadongnum_label = self.ui.findChild(QLabel, "Findhuadongnum") if self.Findhuadong_slider and self.Findhuadongnum_label: self.Findhuadong_slider.setRange(0, 400) # 设置默认值0.3 self.Findhuadong_slider.setValue(30) # 默认0.3 self.Findhuadongnum_label.setText("0.30") self.Findhuadong_slider.valueChanged.connect(self.on_Findhuadong_changed) else: print("未找到Findhuadong滑块或标签控件") # 随机因子滑块 (0-5 支持小数点,使用0-500表示0.00-5.00) self.suiji_slider = self.ui.findChild(QSlider, "suiji") self.suijinum_label = self.ui.findChild(QLabel, "suijinum") if self.suiji_slider and self.suijinum_label: self.suiji_slider.setRange(0, 500) self.suiji_slider.setValue(40) # suiji_value = self.suiji_slider.value() / 100 self.suijinum_label.setText(f"{suiji_value:.2f}") self.suiji_slider.valueChanged.connect(self.on_suiji_changed) else: print("未找到随机因子滑块或标签控件") # 延时滑块 (0-4 支持小数点,使用0-400表示0.00-4.00) self.yanshi_slider = self.ui.findChild(QSlider, "yanshi") self.yanshinum_label = self.ui.findChild(QLabel, "yanshinum") if self.yanshi_slider and self.yanshinum_label: self.yanshi_slider.setRange(0, 400) self.yanshi_slider.setValue(60) # 默认2.0 yanshi_value = self.yanshi_slider.value() / 100 self.yanshinum_label.setText(f"{yanshi_value:.2f}s") self.yanshi_slider.valueChanged.connect(self.on_yanshi_changed) else: print("未找到延时滑块或标签控件") # 添加startFindNPic按钮并连接点击事件 start_npic_btn = self.ui.findChild(QPushButton, "startFindNPic") if start_npic_btn: start_npic_btn.clicked.connect(self.on_startFindNPic_clicked) else: print("未找到startFindNPic按钮") # 添加CleanLog按钮并连接点击事件 clean_log_btn = self.ui.findChild(QPushButton, "CleanLog") if clean_log_btn: clean_log_btn.clicked.connect(self.on_clean_log_clicked) else: print("未找到CleanLog按钮") # 获取startFindPic按钮并连接点击事件 start_find_pic_btn = self.ui.findChild(QPushButton, "startFindPic") # 获取findYeman按钮并连接点击事件 find_yeman_btn = self.ui.findChild(QPushButton, "findYeman") if find_yeman_btn: find_yeman_btn.clicked.connect(self.on_findYeman_clicked) else: print("未找到findYeman按钮") # 添加selectPic1到selectPic8按钮连接 for i in range(1, 9): select_pic_btn = self.ui.findChild(QPushButton, f"selectPic{i}") if select_pic_btn: # 确保只连接一次 try: # 先断开所有连接,再重新连接 select_pic_btn.clicked.disconnect() except: pass # 动态连接到对应的事件处理方法 select_pic_btn.clicked.connect(lambda checked, idx=i: self.on_selectPic_clicked(idx)) if start_find_pic_btn: start_find_pic_btn.clicked.connect(self.on_start_find_pic_clicked) else: print("未找到startFindPic按钮") # 添加End按钮连接 stop_btn = self.ui.findChild(QPushButton, "End") if stop_btn: stop_btn.clicked.connect(self.on_end_clicked) else: print("未找到stopButton") def on_liucangdu_changed(self, value): """流畅度滑块值变化处理""" print(value) # 检查标签控件是否已成功初始化 if self.liucangdunum_label: self.liucangdunum_label.setText(f"{value}") else: print("流畅度标签控件未初始化") # 更新延时系统的帧率 fps = 5 + 55 * (value / 100) ** 1.5 self.delay_system.set_target_fps(int(round(fps))) def on_suiji_changed(self, value): """随机因子滑块值变化处理""" suiji_value = value / 100 self.suijinum_label.setText(f"{suiji_value:.2f}") # 更新延时系统的随机因子 self.delay_system.set_random_factor(min(5.0, max(0.0, suiji_value))) def on_yanshi_changed(self, value): """延时滑块值变化处理""" yanshi_value = value / 100 self.yanshinum_label.setText(f"{yanshi_value:.2f}s") # 更新延时系统的基础延时 self.delay_system.set_base_delay(yanshi_value) def on_Findhuadong_changed(self, value): """Findhuadong滑块值变化处理""" findhuadong_value = value / 100 self.Findhuadongnum_label.setText(f"{findhuadong_value:.2f}") # 保存设置 self.save_settings() def setup_number_validators(self): """为数字输入控件添加验证器""" # 创建验证器 (0-1000的整数范围) validator = QIntValidator(0, 1000, self) # 为三个时间控件添加验证器 time_controls = ["caiji_beisaotiantime", "jiancetime", "bangzhutime"] for control_name in time_controls: control = self.ui.findChild(QLineEdit, control_name) if control: control.setValidator(validator) # 确保现有文本是数字,如果不是则清空 text = control.text() if text and not text.isdigit(): control.clear() def on_selectPic_clicked(self, button_index=1): print(f"点击了选择图片按钮{button_index}") file_dialog = QFileDialog() # 获取对应按钮的已选路径 image_path_attr = f"selected_image_path{button_index}" if hasattr(self, image_path_attr): selected_path = getattr(self, image_path_attr) if selected_path: # 如果已有选择路径,则设为初始目录 file_dialog.setDirectory(os.path.dirname(selected_path)) else: file_dialog.setDirectory("img") # 默认目录 file_path, _ = file_dialog.getOpenFileName( self.ui, f"选择图片{button_index}", "", "图片文件 (*.bmp *.png *.jpg)" ) if file_path: # 保存到对应的属性 setattr(self, image_path_attr, file_path) log_message = f"已选择图片{button_index}: {file_path}" print(log_message) self.logger.info(log_message) # 将图片地址添加到对应的imgaddress控件中 imgaddress_widget = self.ui.findChild(QLineEdit, f"imgaddress{button_index}") if imgaddress_widget: imgaddress_widget.setText(file_path) return #点击开始 def on_start_clicked(self): # 检查是否已有线程在运行 if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): print("线程已在运行中") return # 创建并启动工作线程 self.worker_thread = threading.Thread(target=self.execute_business_logic) self.worker_thread.daemon = True # 设置为守护线程 self.worker_thread.start() # 切换到运行日志页面(tab_6) QTimer.singleShot(0, self.switch_to_log_tab) # 获取日志控件 self.logger.info("业务逻辑线程已启动") def switch_to_log_tab(self): """切换到运行日志标签页""" # 尝试通过self.ui查找QTabWidget控件 panel = self.ui.findChild(QTabWidget, "panel") if not panel: print("未找到QTabWidget控件: panel") self.logger.error("未找到QTabWidget控件: panel") # 打印UI的所有子控件名称,用于调试 print("UI子控件列表:") for child in self.ui.children(): print(f" {child.objectName()}") return # 检查标签页数量,避免潜在的索引错误 if panel.count() == 0: print("标签页控件为空") self.logger.error("标签页控件为空") return # 查找tab_6标签页 for i in range(panel.count()): if panel.widget(i).objectName() == "tab_6": panel.setCurrentIndex(i) return # 如果没找到tab_6,切换到第一个标签页 panel.setCurrentIndex(0) def execute_business_logic(self): """在新线程中执行业务逻辑""" # 直接执行业务逻辑,无需登录验证 self.logger.info("✅ 永久授权版 - 开始执行业务逻辑") # 创建BusinessLogic实例 business_logic = BusinessLogic(日志=self.logger) # 将暂停控制属性和终止信号传递给business_logic business_logic.should_pause = False business_logic.pause_event = self.pause_event business_logic.should_stop = False # 初始化终止标志 # 设置主线程关闭函数回调,让business_logic能够调用主程序的关闭线程 business_logic.main_thread_closer = self.关闭线程 # 保存business_logic引用,以便主线程能够控制它 self.business_logic = business_logic if business_logic.execute(): self.logger.info("✅ 业务逻辑执行成功") self.logger.info(f"开图设置: {'已启用' if business_logic.get_setting('kaitusetting') else '未启用'}") self.logger.info(f"清货设置: {'已启用' if business_logic.get_setting('qinghuosetting') else '未启用'}") self.logger.info(f"综合设置: {'已启用' if business_logic.get_setting('zonghesetting') else '未启用'}") self.logger.info(f"采集设置: {'已启用' if business_logic.get_setting('caijisetting') else '未启用'}") else: # 添加延时 self.logger.info("业务逻辑执行失败,开始停止") def on_end_clicked(self): """停止线程的执行(增强版,确保真正终止)""" try: # 添加调试信息 print("[DEBUG] on_end_clicked 被调用") self.logger.info("⏹️ 收到停止指令") # 检查是否有活跃线程 if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): print("[DEBUG] 发现活跃线程,开始停止流程") # 立即停止线程 self.关闭线程() else: print("[DEBUG] 没有活跃线程需要停止") self.logger.info("✅ 程序已停止") except Exception as e: print(f"[ERROR] on_end_clicked 执行失败: {str(e)}") self.logger.error(f"停止程序时出错: {str(e)}") # 强制重置状态 if hasattr(self, '_stopping'): self._stopping = False def on_start_clicked1(self): print("点击了开始按钮") hwnd = win32gui.FindWindow(None, "万国觉醒") if not hwnd: msg = "未找到目标窗口" print(msg) self.logger.info(msg) return Zd = Zhongdi(hwnd) result =Zd.get_ziyuan_cont("玉米") if result: print(f"找到玉米,位置: ({result['x']}, {result['y']})") self.logger.info(f"找到玉米,位置: ({result['x']}, {result['y']})") def on_start_find_pic_clicked(self): image_path = None imgaddress_widget = self.ui.findChild(QLineEdit, f"imgaddress1") if imgaddress_widget and imgaddress_widget.text(): image_path = imgaddress_widget.text() if not image_path: msg = "请先选择图片" self.logger.info(msg) return hwnd = win32gui.FindWindow(None, "万国觉醒") if not hwnd: msg = "未找到目标窗口" self.logger.info(msg) return # 获取SimScore输入框的值 sim_score_input = self.ui.findChild(QLineEdit, "SimScore") sim_score = 0.85 # 默认相似度 if sim_score_input and sim_score_input.text(): try: sim_score = float(sim_score_input.text()) except ValueError: msg = "相似度输入无效,使用默认值0.85" self.logger.info(msg) processor = ImageProcessor(hwnd,self.logger) # 调用找图功能时传入相似度参数 result = processor.find_image(image_path, sim_score) if not os.path.exists(image_path): msg = f"错误:图片不存在 {image_path}" self.logger.info(msg) return False if result: # 绘制红色矩形框标记找到的区域(使用文件顶部已导入的win32gui和win32con) #鼠标移动到图片位置 # 输出日志信息 msg = f"找到图片位置: ({result['x']}, {result['y']}),相似度: {sim_score}" self.logger.info(msg) else: msg = f"未找到图片(相似度: {sim_score})" self.logger.info(msg) def on_findYeman_clicked(self): self.logger.info("开始图片文字识别") # 移除登录验证 def on_kuoFindPic_clicked(self): image_path = None imgaddress_widget = self.ui.findChild(QLineEdit, f"imgaddress1") if imgaddress_widget and imgaddress_widget.text(): image_path = imgaddress_widget.text() if not image_path: msg = "请先选择图片" self.logger.info(msg) return hwnd = win32gui.FindWindow(None, "万国觉醒") if not hwnd: msg = "未找到目标窗口" self.logger.info(msg) return # 获取SimScore输入框的值 sim_score_input = self.ui.findChild(QLineEdit, "SimScore") sim_score = 0.85 # 默认相似度 if sim_score_input and sim_score_input.text(): try: sim_score = float(sim_score_input.text()) except ValueError: msg = "相似度输入无效,使用默认值0.85" self.logger.info(msg) processor = ImageProcessor(hwnd) # 调用找图功能时传入相似度参数 result = processor.find_image_from_center(self.selected_image_path, sim_score) if not os.path.exists(self.selected_image_path): msg = f"错误:图片不存在 {self.selected_image_path}" self.logger.info(msg) return if result: # 绘制红色矩形框标记找到的区域(使用文件顶部已导入的win32gui和win32con) #鼠标移动到图片位置 mouse = MouseController(hwnd) mouse.click_position( result) # 输出日志信息 msg = f"找到图片,位置: ({result['x']}, {result['y']}),相似度: {sim_score}" self.logger.info(msg) else: msg = f"未找到图片(相似度: {sim_score})" self.logger.info(msg) def on_startFindNPic_clicked(self): msg = "开始批量查找图片" self.logger.info(msg) hwnd = win32gui.FindWindow(None, "万国觉醒") if not hwnd: msg = "未找到目标窗口" self.logger.info(msg) return # 获取SimScore输入框的值 sim_score_input = self.ui.findChild(QLineEdit, "SimScore") sim_score = 0.85 # 默认相似度 if sim_score_input and sim_score_input.text(): try: sim_score = float(sim_score_input.text()) except ValueError: msg = "相似度输入无效,使用默认值0.85" self.logger.info(msg) processor = ImageProcessor(hwnd,self.logger) # 遍历imgaddress1到imgaddress8 for i in range(1, 9): # 获取图片地址 imgaddress_widget = self.ui.findChild(QLineEdit, f"imgaddress{i}") if imgaddress_widget and imgaddress_widget.text(): image_path = imgaddress_widget.text() if not os.path.exists(image_path): msg = f"错误:图片不存在 {image_path}" self.logger.info(msg) continue msg = f"正在查找图片 {i}: {image_path}" self.logger.info(msg) # 调用找图功能 result = processor.find_image(image_path, sim_score) if result: # 输出日志信息 msg = f"【找到图片 {i},位置: ({result['x']}, {result['y']}),相似度: {sim_score}】" self.logger.info(msg) else: msg = f"未找到图片 {i} (相似度: {sim_score})" self.logger.info(msg) else: msg = f"图片地址 {i} 为空,跳过查找" self.logger.info(msg) msg = "批量查找图片完成" self.logger.info(msg) def on_clean_log_clicked(self): """清空日志文本框""" self.logger.clear() self.logger.info("日志已清空") def on_selectGemeadd_clicked(self): """处理selectGemeadd按钮点击事件,弹出文件选择器并显示选择的文件路径""" file_dialog = QFileDialog() file_path, _ = file_dialog.getOpenFileName( self.ui, "选择游戏文件", "", "所有文件 (*.*)" ) if file_path: # 将选择的文件路径显示到gameaddress控件中 gameaddress_widget = self.ui.findChild(QLineEdit, "gameaddress") if gameaddress_widget: gameaddress_widget.setText(file_path) self.logger.info(f"已选择游戏文件: {file_path}") def on_caijidl_tian_changed(self, text, index): """处理采集队选择变化事件""" pass def load_settings(self): """加载设置文件 - 完整优化版""" try: # 检查并初始化设置文件 if not os.path.exists('settings.json') or os.path.getsize('settings.json') == 0: with open('settings.json', 'w', encoding='utf-8') as f: json.dump({ "kaitusetting": False, "qinghuosetting": False, "zonghesetting": False, "caijisetting": False, "tandong": False, "liucangdu": 20, "suiji": 30, "yanshi": 60, "Findhuadong": 30, "cundang": False, "bszhuzha": False }, f, ensure_ascii=False, indent=4) return # 读取并解析设置文件 with open('settings.json', 'r', encoding='utf-8') as f: content = f.read().strip() if not content: raise ValueError("设置文件为空") settings = json.loads(content) # 处理所有控件类型 widget_handlers = { QLineEdit: lambda w, v: w.setText(str(v)), QSpinBox: lambda w, v: w.setValue(int(v)), QCheckBox: lambda w, v: w.setChecked(bool(v)), QComboBox: lambda w, v: w.setCurrentText(str(v)), QSlider: lambda w, v: w.setValue(int(v)) } # 批量处理设置项 for name, value in settings.items(): if not name: continue try: # 查找控件 widget = None # 优先处理特定类型的控件 if name.endswith(('kj', 'tian')): widget = self.ui.findChild(QComboBox, name) elif name in ['liucangdu', 'suiji', 'yanshi', 'Findhuadong']: widget = self.ui.findChild(QSlider, name) else: # 通用查找 for widget_type in widget_handlers: widget = self.ui.findChild(widget_type, name) if widget: break if not widget: continue # 设置值 for widget_type, handler in widget_handlers.items(): if isinstance(widget, widget_type): try: handler(widget, value) # 特殊处理滑块控件的标签更新 if isinstance(widget, QSlider): value_int = int(value) if name == 'liucangdu': label = self.ui.findChild(QLabel, "liucangdunum") if label: label.setText(str(value_int)) if hasattr(self, 'delay_system') and self.delay_system: fps = 5 + 55 * (value_int / 100) ** 1.5 self.delay_system.set_target_fps(int(round(fps))) elif name == 'suiji': label = self.ui.findChild(QLabel, "suijinum") if label: label.setText(f"{value_int / 100:.2f}") if hasattr(self, 'delay_system') and self.delay_system: self.delay_system.set_random_factor(min(5.0, max(0.0, value_int / 100))) elif name == 'yanshi': label = self.ui.findChild(QLabel, "yanshinum") if label: label.setText(f"{value_int / 100:.2f}s") if hasattr(self, 'delay_system') and self.delay_system: self.delay_system.set_base_delay(value_int / 100) elif name == 'Findhuadong': label = self.ui.findChild(QLabel, "Findhuadongnum") if label: label.setText(f"{value_int / 100:.2f}") except (ValueError, TypeError) as e: print(f"设置控件{name}的值失败: {e}") break except Exception as e: print(f"处理设置项{name}时出错: {str(e)}") continue except Exception as e: print(f"加载设置文件出错: {str(e)}") with open('settings.json', 'w', encoding='utf-8') as f: json.dump({}, f, ensure_ascii=False, indent=4) # ===== 采集队控件显示逻辑初始化 ===== try: # 获取控制复选框 cundang = self.ui.findChild(QCheckBox, "cundang") bszhuzha = self.ui.findChild(QCheckBox, "bszhuzha") if cundang and bszhuzha: huncun_checked = cundang.isChecked() bszhuzha_checked = bszhuzha.isChecked() # 处理所有采集队控件 for i in range(1, 8): # 宝石采集队(要被控制的控件) caijidl_kj = self.ui.findChild(QComboBox, f"caijidl{i}kj") # 存储采集队(控制源控件) caijidl_tian = self.ui.findChild(QComboBox, f"caijidl{i}tian") # 根据需求设置宝石采集队的可见性 if caijidl_kj: # 当cundang勾选时显示宝石采集队 # 或者当bszhuzha勾选时也显示宝石采集队(但值由caijidl_tian决定) should_show = huncun_checked or bszhuzha_checked caijidl_kj.setVisible(should_show) # 当bszhuzha勾选时,同步存储采集队的值到宝石采集队 if bszhuzha_checked and caijidl_tian and caijidl_kj: current_text = caijidl_tian.currentText() index = caijidl_kj.findText(current_text) if index >= 0: caijidl_kj.setCurrentIndex(index) else: caijidl_kj.setCurrentText(current_text) except Exception as e: print(f"初始化采集队控件显示失败: {str(e)}") def connect_signals(self): """连接所有控件的值变化信号""" # 连接采集队控制复选框 cundang = self.ui.findChild(QCheckBox, "cundang") bszhuzha = self.ui.findChild(QCheckBox, "bszhuzha") morenwj = self.ui.findChild(QCheckBox, "morenwj") if cundang: cundang.toggled.connect(self.save_settings) cundang.toggled.connect(self.update_caijidl_visibility) if bszhuzha: bszhuzha.toggled.connect(self.save_settings) bszhuzha.toggled.connect(self.update_caijidl_visibility) if morenwj: # 添加morenwj控件的信号连接到update_caijidl_visibility方法 morenwj.toggled.connect(self.update_caijidl_visibility) # 添加selectGemeadd按钮的信号连接 select_gemeadd_btn = self.ui.findChild(QPushButton, "selectGemeadd") if select_gemeadd_btn: select_gemeadd_btn.clicked.connect(self.on_selectGemeadd_clicked) # 连接存储采集队控件的变化信号 for i in range(1, 8): caijidl_tian = self.ui.findChild(QComboBox, f"caijidl{i}tian") if caijidl_tian: caijidl_tian.currentIndexChanged.connect(self.update_caijidl_visibility) # 初始化采集队显示 self.update_caijidl_visibility() # 连接其他控件信号 for widget in self.ui.findChildren((QLineEdit, QSpinBox, QCheckBox, QPushButton, QComboBox, QSlider)): name = widget.objectName() if not name: # 跳过没有名称的控件 continue if isinstance(widget, QLineEdit): widget.textChanged.connect(self.save_settings) elif isinstance(widget, QSpinBox): widget.valueChanged.connect(self.save_settings) elif isinstance(widget, QCheckBox): widget.toggled.connect(self.save_settings) elif isinstance(widget, QPushButton) and widget.isCheckable(): widget.toggled.connect(self.save_settings) elif isinstance(widget, QComboBox): widget.currentIndexChanged.connect(self.save_settings) elif isinstance(widget, QSlider): widget.valueChanged.connect(self.save_settings) def on_morenwj_toggled(self, checked): """处理morenwj复选框的勾选状态变化""" # 禁用或启用采集队主将控件 for i in range(1, 8): # 主将控件 zhu_combo = self.ui.findChild(QComboBox, f"caijidl{i}zhu") if zhu_combo: zhu_combo.setEnabled(not checked) # 副将控件 fu_combo = self.ui.findChild(QComboBox, f"caijidl{i}fu") if fu_combo: fu_combo.setEnabled(not checked) def update_caijidl_visibility(self): """更新采集队控件显示状态""" try: cundang = self.ui.findChild(QCheckBox, "cundang") bszhuzha = self.ui.findChild(QCheckBox, "bszhuzha") if not cundang or not bszhuzha: return huncun_checked = cundang.isChecked() bszhuzha_checked = bszhuzha.isChecked() # 查找morenwj控件并根据其状态设置bszhuzha和cundang的可设置性 morenwj = self.ui.findChild(QCheckBox, "morenwj") if morenwj and morenwj.isChecked(): # 当morenwj被勾选时,bszhuzha和cundang更改为不可设置 cundang.setEnabled(False) bszhuzha.setEnabled(False) else: # 当morenwj未被勾选时,bszhuzha和cundang保持可设置 cundang.setEnabled(True) bszhuzha.setEnabled(True) # 当bszhuzha被勾选时,自动勾选cundang if bszhuzha_checked and not huncun_checked: cundang.setChecked(True) huncun_checked = True # 处理所有采集队控件 for i in range(1, 8): # 宝石采集队控件 caijidl_kj = self.ui.findChild(QComboBox, f"caijidl{i}kj") # 存储采集队控件 caijidl_tian = self.ui.findChild(QComboBox, f"caijidl{i}tian") # 控制宝石采集队的可见性 if caijidl_kj: # 检查cundang是否可设置 if not cundang.isEnabled(): # 当cundang不可设置时,隐藏宝石采集队控件 caijidl_kj.setVisible(False) else: # 当cundang可设置时,根据cundang是否被勾选决定显示/隐藏 should_show = huncun_checked caijidl_kj.setVisible(should_show) # 不对caijidl_tian进行隐藏操作,始终显示 # 当bszhuzha勾选且宝石采集队可见时,同步存储采集队的值到宝石采集队 if bszhuzha_checked and caijidl_tian and caijidl_kj and caijidl_kj.isVisible(): current_text = caijidl_tian.currentText() index = caijidl_kj.findText(current_text) if index >= 0: caijidl_kj.setCurrentIndex(index) else: caijidl_kj.setCurrentText(current_text) except Exception as e: print(f"更新采集队控件显示失败: {str(e)}") def save_settings(self): """保存所有UI控件的设置到文件""" settings = {} # 保存所有控件的设置,不再限制特定名称 widgets = self.ui.findChildren((QLineEdit, QSpinBox, QCheckBox, QPushButton, QRadioButton, QComboBox, QSlider)) for widget in widgets: name = widget.objectName() if name: # 只要有名称的控件都保存 if isinstance(widget, QLineEdit): settings[name] = widget.text() elif isinstance(widget, QSpinBox): settings[name] = widget.value() elif isinstance(widget, QCheckBox): settings[name] = widget.isChecked() elif isinstance(widget, QPushButton) and widget.isCheckable(): settings[name] = widget.isChecked() elif isinstance(widget, QRadioButton): settings[name] = widget.isChecked() elif isinstance(widget, QComboBox): settings[name] = widget.currentText() # 保存当前选中的文本 elif isinstance(widget, QSlider): settings[name] = widget.value() # 保存滑块的值 try: with open('settings.json', 'w', encoding='utf-8') as f: json.dump(settings, f, indent=4, ensure_ascii=False) # 关键修改 print("设置保存成功") except Exception as e: print(f"保存设置失败: {str(e)}") def handle_login(self): """F10登录功能 - 已移除授权验证,直接返回成功""" self.logger.info("✅ 永久授权版 - 无需登录验证") return True def show_message_slot(self, msg_type, title, content): """主线程中显示消息框的槽函数""" if msg_type == 'information': QMessageBox.information(self, title, content) elif msg_type == 'warning': # 显示警告消息框,但不关闭程序 result = QMessageBox.warning(self, title, content, QMessageBox.StandardButton.Ok) elif msg_type == 'critical': # 显示错误消息框,但不关闭程序 QMessageBox.critical(self, title, content) else: QMessageBox.about(self, title, content) def 关闭线程(self): """停止线程执行和心跳定时器(快速响应版)""" print("[DEBUG] 关闭线程函数被调用") self.logger.info("⏹️ 正在停止程序运行...") # 停止工作线程 if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): print("[DEBUG] 工作线程存在且活跃,开始停止") # 通知业务逻辑线程终止 - 立即设置 if hasattr(self, 'business_logic'): print("[DEBUG] 设置business_logic.should_stop = True") self.business_logic.should_stop = True # 如果有暂停事件,确保线程能从暂停状态恢复并退出 if hasattr(self.business_logic, 'pause_event') and self.business_logic.pause_event: print("[DEBUG] 设置business_logic.pause_event唤醒暂停线程") self.business_logic.pause_event.set() else: print("[DEBUG] 没有找到business_logic实例") # 添加线程安全终止标志 if hasattr(self, 'should_stop'): print("[DEBUG] 设置should_stop = True") self.should_stop = True # 确保线程能从暂停状态恢复并退出 if hasattr(self, 'pause_event'): print("[DEBUG] 设置pause_event唤醒暂停线程") self.pause_event.set() # 唤醒暂停的线程 # 快速等待 - 只等待3秒 print("[DEBUG] 快速等待线程结束...") import threading current_thread_id = threading.get_ident() worker_thread_id = self.worker_thread.ident print(f"[DEBUG] 当前线程ID: {current_thread_id}, 工作线程ID: {worker_thread_id}") if current_thread_id != worker_thread_id: self.worker_thread.join(timeout=3.0) # 只等待3秒 # 如果还在运行,立即强制终止 if self.worker_thread.is_alive(): print("[DEBUG] 线程在3秒内未停止,立即强制终止") # 强制终止线程 import ctypes try: tid = self.worker_thread.ident res = ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(tid), ctypes.py_object(SystemExit) ) if res == 0: print("[DEBUG] 强制终止失败: 无效的线程ID") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0) print("[DEBUG] 强制终止失败: 系统错误") elif res == 1: print("[DEBUG] 强制终止成功") self.logger.info("✅ 程序已强制停止") except Exception as e: print(f"[DEBUG] 强制终止异常: {e}") self.logger.warning("⚠️ 程序停止时遇到问题") else: print("[DEBUG] 线程已正常结束") self.logger.info("✅ 程序已完全停止") else: print("[DEBUG] 没有活跃的工作线程") self.logger.info("✅ 程序已停止") def pause_thread(self): """暂停线程执行""" if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): self.should_pause = True self.pause_event.clear() # 清除事件,使线程进入等待状态 def resume_thread(self): """恢复线程执行""" if hasattr(self, 'worker_thread') and self.worker_thread.is_alive(): self.should_pause = False self.pause_event.set() # 设置事件,唤醒线程继续执行 if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.ui.show() sys.exit(app.exec())
11-01
//----------------------------------------------------------------------------- // // (c) Copyright 2012-2012 Xilinx, Inc. All rights reserved. // // This file contains confidential and proprietary information // of Xilinx, Inc. and is protected under U.S. and // international copyright and other intellectual property // laws. // // DISCLAIMER // This disclaimer is not a license and does not grant any // rights to the materials distributed herewith. Except as // otherwise provided in a valid license issued to you by // Xilinx, and to the maximum extent permitted by applicable // law: (1) THESE MATERIALS ARE MADE AVAILABLE "AS IS" AND // WITH ALL FAULTS, AND XILINX HEREBY DISCLAIMS ALL WARRANTIES // AND CONDITIONS, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING // BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, NON- // INFRINGEMENT, OR FITNESS FOR ANY PARTICULAR PURPOSE; and // (2) Xilinx shall not be liable (whether in contract or tort, // including negligence, or under any other theory of // liability) for any loss or damage of any kind or nature // related to, arising under or in connection with these // materials, including for any direct, or any indirect, // special, incidental, or consequential loss or damage // (including loss of data, profits, goodwill, or any type of // loss or damage suffered as a result of any action brought // by a third party) even if such damage or loss was // reasonably foreseeable or Xilinx had been advised of the // possibility of the same. // // CRITICAL APPLICATIONS // Xilinx products are not designed or intended to be fail- // safe, or for use in any application requiring fail-safe // performance, such as life-support or safety devices or // systems, Class III medical devices, nuclear facilities, // applications related to the deployment of airbags, or any // other applications that could lead to death, personal // injury, or severe property or environmental damage // (individually and collectively, "Critical // Applications"). Customer assumes the sole risk and // liability of any use of Xilinx products in Critical // Applications, subject only to applicable laws and // regulations governing limitations on product liability. // // THIS COPYRIGHT NOTICE AND DISCLAIMER MUST BE RETAINED AS // PART OF THIS FILE AT ALL TIMES. // //----------------------------------------------------------------------------- // // Project : UltraScale+ FPGA PCI Express v4.0 Integrated Block // File : cgator_wrapper.v // Version : 1.3 //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // // Project : Ultrascale FPGA Gen4 Integrated Block for PCI Express // File : cgator_wrapper.v // Version : 1.0 //----------------------------------------------------------------------------- `timescale 1ns/1ns (* DowngradeIPIdentifiedWarnings = "yes" *) module cgator_wrapper #( // Configurator parameters parameter PCIE_LOCATION = "X0Y0", parameter TCQ = 1, parameter AXISTEN_IF_RQ_ALIGNMENT_MODE = "TRUE", parameter PIPE_SIM_MODE = "FALSE", parameter EXTRA_PIPELINE = 1, parameter ROM_FILE = "cgator_cfg_rom.data", parameter ROM_SIZE = 32, parameter [15:0] REQUESTER_ID = 16'h10EE, parameter PCIE_EXT_CLK = "FALSE", // Use External Clocking Module parameter [2:0] PL_LINK_CAP_MAX_LINK_SPEED = 3'h4, // 1- GEN1, 2 - GEN2, 4 - GEN3 parameter [4:0] PL_LINK_CAP_MAX_LINK_WIDTH = 5'h8, // 1- X1, 2 - X2, 4 - X4, 8 - X8 parameter PL_DISABLE_EI_INFER_IN_L0 = "TRUE", parameter PL_DISABLE_UPCONFIG_CAPABLE = "FALSE", // USER_CLK[1/2]_FREQ :[0] = Disable user clock; [1] = 31.25 MHz; [2] = 62.50 MHz (default); [3] = 125.00 MHz; [4] = 250.00 MHz; [5] = 500.00 MHz; parameter integer USER_CLK2_FREQ = 2, parameter REF_CLK_FREQ = 0, // 0 - 100 MHz, 1 - 125 MHz, 2 - 250 MHz parameter AXISTEN_IF_RQ_PARITY_CHECK = "FALSE", parameter AXI4_CQ_TUSER_WIDTH = 88, parameter AXI4_CC_TUSER_WIDTH = 33, parameter AXI4_RQ_TUSER_WIDTH = 62, parameter AXI4_RC_TUSER_WIDTH = 75, parameter C_DATA_WIDTH = 64, parameter KEEP_WIDTH = C_DATA_WIDTH / 32 ) ( //------------------------------------------------------- // 0. Configurator I/Os //------------------------------------------------------- input start_config, output finished_config, output failed_config, //------------------------------------------------------- // 1. PCI Express (pci_exp) Interface //------------------------------------------------------- output [PL_LINK_CAP_MAX_LINK_WIDTH-1:0] pci_exp_txp, output [PL_LINK_CAP_MAX_LINK_WIDTH-1:0] pci_exp_txn, input [PL_LINK_CAP_MAX_LINK_WIDTH-1:0] pci_exp_rxp, input [PL_LINK_CAP_MAX_LINK_WIDTH-1:0] pci_exp_rxn, //------------------------------------------------------- // 2. Transaction (AXIS) Interface //------------------------------------------------------- output user_clk_out, output user_reset_out, output user_lnk_up, output phy_rdy_out, //------------------------------------------------------- output s_axis_rq_tready, input [C_DATA_WIDTH-1:0] s_axis_rq_tdata, input [KEEP_WIDTH-1:0] s_axis_rq_tkeep, input [AXI4_RQ_TUSER_WIDTH-1:0] s_axis_rq_tuser, input s_axis_rq_tlast, input s_axis_rq_tvalid, //------------------------------------------------------- output [C_DATA_WIDTH-1:0] m_axis_rc_tdata, output [KEEP_WIDTH-1:0] m_axis_rc_tkeep, output m_axis_rc_tlast, output m_axis_rc_tvalid, output [AXI4_RC_TUSER_WIDTH-1:0] m_axis_rc_tuser, input m_axis_rc_tready, //------------------------------------------------------- output wire [C_DATA_WIDTH-1:0] m_axis_cq_tdata, output wire [AXI4_CQ_TUSER_WIDTH-1:0] m_axis_cq_tuser, output wire m_axis_cq_tlast, output wire [KEEP_WIDTH-1:0] m_axis_cq_tkeep, output wire m_axis_cq_tvalid, input m_axis_cq_tready, //------------------------------------------------------- input [C_DATA_WIDTH-1:0] s_axis_cc_tdata, input [AXI4_CC_TUSER_WIDTH-1:0] s_axis_cc_tuser, input s_axis_cc_tlast, input [KEEP_WIDTH-1:0] s_axis_cc_tkeep, input s_axis_cc_tvalid, output wire [3:0] s_axis_cc_tready, //------------------------------------------------------- // 3. Configuration (CFG) Interface - EP and RP //------------------------------------------------------- output [3:0] pcie_tfc_nph_av, output [3:0] pcie_tfc_npd_av, //------------------------------------------------------- // Error Reporting Interface //------------------------------------------------------- output wire [5:0] pcie_rq_seq_num0, output wire pcie_rq_seq_num_vld0, output wire [5:0] pcie_rq_seq_num1, output wire pcie_rq_seq_num_vld1, output wire [7:0] pcie_rq_tag0, output wire pcie_rq_tag_vld0, output wire [7:0] pcie_rq_tag1, output wire pcie_rq_tag_vld1, output wire [3:0] pcie_rq_tag_av, input [1:0] pcie_cq_np_req, output [5:0] pcie_cq_np_req_count, output cfg_phy_link_down, output [1:0] cfg_phy_link_status, output [2:0] cfg_negotiated_width, output [1:0] cfg_current_speed, output [1:0] cfg_max_payload, output [2:0] cfg_max_read_req, output [15:0] cfg_function_status, output [11:0] cfg_function_power_state, output [503:0] cfg_vf_status, output [755:0] cfg_vf_power_state, output [1:0] cfg_link_power_state, output cfg_err_cor_out, output cfg_err_nonfatal_out, output cfg_err_fatal_out, output [4:0] cfg_local_error_out, output cfg_local_error_valid, output [5:0] cfg_ltssm_state, output [1:0] cfg_rx_pm_state, output [1:0] cfg_tx_pm_state, output [3:0] cfg_rcb_status, output [1:0] cfg_obff_enable, output cfg_pl_status_change, output [3:0] cfg_tph_requester_enable, output [11:0] cfg_tph_st_mode, output [251:0] cfg_vf_tph_requester_enable, output [755:0] cfg_vf_tph_st_mode, //------------------------------------------------------- // Interrupt Interface Signals //------------------------------------------------------- input [3:0] cfg_interrupt_int, input [1:0] cfg_interrupt_pending, output cfg_interrupt_sent, output [3:0] cfg_interrupt_msi_enable, output [11:0] cfg_interrupt_msi_mmenable, output cfg_interrupt_msi_mask_update, output [31:0] cfg_interrupt_msi_data, input [1:0] cfg_interrupt_msi_select, input [31:0] cfg_interrupt_msi_int, input [63:0] cfg_interrupt_msi_pending_status, output cfg_interrupt_msi_sent, output cfg_interrupt_msi_fail, input [2:0] cfg_interrupt_msi_attr, input cfg_interrupt_msi_tph_present, input [1:0] cfg_interrupt_msi_tph_type, input [7:0] cfg_interrupt_msi_tph_st_tag, input cfg_interrupt_msi_pending_status_data_enable, input [3:0] cfg_interrupt_msi_pending_status_function_num, input [2:0] cfg_interrupt_msi_function_number, //------------------------------------------------------- input sys_clk, input sys_clk_gt, input sys_reset_n //------------------------------------------------------- ); //--------------------------------------------------------------------------------------------------------------------// // Connections between Root Port and Configurator //--------------------------------------------------------------------------------------------------------------------// wire [3:0] rport_s_axis_rq_tready; wire [C_DATA_WIDTH-1:0] rport_s_axis_rq_tdata; wire [KEEP_WIDTH-1:0] rport_s_axis_rq_tkeep; wire [AXI4_RQ_TUSER_WIDTH-1:0] rport_s_axis_rq_tuser; wire rport_s_axis_rq_tlast; wire rport_s_axis_rq_tvalid; wire [C_DATA_WIDTH-1:0] rport_m_axis_rc_tdata; wire [KEEP_WIDTH-1:0] rport_m_axis_rc_tkeep; wire rport_m_axis_rc_tlast; wire rport_m_axis_rc_tvalid; wire rport_m_axis_rc_tready; wire [AXI4_RC_TUSER_WIDTH-1:0] rport_m_axis_rc_tuser; // wire cfg_msg_received; // wire [7 : 0] cfg_msg_received_data; // wire [4 : 0] cfg_msg_received_type; // ila_32 ila_cgator_0 ( // .clk(user_clk_out), // .probe0({ // cfg_msg_received, // cfg_msg_received_data, // cfg_msg_received_type // }) // ); //---------------------------------------------------------------------------------------// // Core Top Level Wrapper generate if (PCIE_LOCATION == "X0Y0") begin pcie4_uscale_plus_0 pcie4_uscale_plus_0_i ( //---------------------------------------------------------------------------------------// // PCI Express (pci_exp) Interface // //---------------------------------------------------------------------------------------// //---------------------------------------------------------------------------------------// // PCI Express (pci_exp) Interface // //---------------------------------------------------------------------------------------// // Tx .pci_exp_txn ( pci_exp_txn ), .pci_exp_txp ( pci_exp_txp ), // Rx .pci_exp_rxn ( pci_exp_rxn ), .pci_exp_rxp ( pci_exp_rxp ), //---------------------------------------------------------------------------------------// // AXI Interface // //---------------------------------------------------------------------------------------// .user_clk ( user_clk_out ), .user_reset ( user_reset_out ), .user_lnk_up ( user_lnk_up ), .phy_rdy_out ( phy_rdy_out ), .s_axis_rq_tlast ( rport_s_axis_rq_tlast ), .s_axis_rq_tdata ( rport_s_axis_rq_tdata ), .s_axis_rq_tuser ( rport_s_axis_rq_tuser ), .s_axis_rq_tkeep ( rport_s_axis_rq_tkeep ), .s_axis_rq_tready ( rport_s_axis_rq_tready ), .s_axis_rq_tvalid ( rport_s_axis_rq_tvalid ), .m_axis_rc_tdata ( rport_m_axis_rc_tdata ), .m_axis_rc_tuser ( rport_m_axis_rc_tuser ), .m_axis_rc_tlast ( rport_m_axis_rc_tlast ), .m_axis_rc_tkeep ( rport_m_axis_rc_tkeep ), .m_axis_rc_tvalid ( rport_m_axis_rc_tvalid ), .m_axis_rc_tready ( rport_m_axis_rc_tready ), .m_axis_cq_tdata ( m_axis_cq_tdata ), .m_axis_cq_tuser ( m_axis_cq_tuser ), .m_axis_cq_tlast ( m_axis_cq_tlast ), .m_axis_cq_tkeep ( m_axis_cq_tkeep ), .m_axis_cq_tvalid ( m_axis_cq_tvalid ), .m_axis_cq_tready ( m_axis_cq_tready ), .s_axis_cc_tdata ( s_axis_cc_tdata ), .s_axis_cc_tuser ( s_axis_cc_tuser ), .s_axis_cc_tlast ( s_axis_cc_tlast ), .s_axis_cc_tkeep ( s_axis_cc_tkeep ), .s_axis_cc_tvalid ( s_axis_cc_tvalid ), .s_axis_cc_tready ( s_axis_cc_tready ), //---------------------------------------------------------------------------------------// // Configuration (CFG) Interface // //---------------------------------------------------------------------------------------// .pcie_tfc_nph_av ( pcie_tfc_nph_av ), .pcie_tfc_npd_av ( pcie_tfc_npd_av ), .pcie_rq_seq_num0 ( pcie_rq_seq_num0) , .pcie_rq_seq_num_vld0 ( pcie_rq_seq_num_vld0) , .pcie_rq_seq_num1 ( pcie_rq_seq_num1) , .pcie_rq_seq_num_vld1 ( pcie_rq_seq_num_vld1) , .pcie_rq_tag0 ( pcie_rq_tag0) , .pcie_rq_tag1 ( pcie_rq_tag1) , .pcie_rq_tag_av ( pcie_rq_tag_av) , .pcie_rq_tag_vld0 ( pcie_rq_tag_vld0) , .pcie_rq_tag_vld1 ( pcie_rq_tag_vld1) , .pcie_cq_np_req ( pcie_cq_np_req ), .pcie_cq_np_req_count ( pcie_cq_np_req_count ), .cfg_phy_link_down ( cfg_phy_link_down ), .cfg_phy_link_status ( cfg_phy_link_status), .cfg_negotiated_width ( cfg_negotiated_width ), .cfg_current_speed ( cfg_current_speed ), .cfg_max_payload ( cfg_max_payload ), .cfg_max_read_req ( cfg_max_read_req ), .cfg_function_status ( cfg_function_status ), .cfg_function_power_state ( cfg_function_power_state ), .cfg_vf_status ( cfg_vf_status ), .cfg_vf_power_state ( cfg_vf_power_state ), .cfg_link_power_state ( cfg_link_power_state ), // Error Reporting Interface .cfg_err_cor_out ( cfg_err_cor_out ), .cfg_err_nonfatal_out ( cfg_err_nonfatal_out ), .cfg_err_fatal_out ( cfg_err_fatal_out ), .cfg_local_error_out ( cfg_local_error_out ), .cfg_local_error_valid ( cfg_local_error_valid ), .cfg_ltssm_state ( cfg_ltssm_state ), .cfg_rx_pm_state ( cfg_rx_pm_state ), .cfg_tx_pm_state ( cfg_tx_pm_state ), .cfg_rcb_status ( cfg_rcb_status ), .cfg_obff_enable ( cfg_obff_enable ), .cfg_pl_status_change ( cfg_pl_status_change ), .cfg_tph_requester_enable ( cfg_tph_requester_enable ), .cfg_tph_st_mode ( cfg_tph_st_mode ), .cfg_vf_tph_requester_enable ( cfg_vf_tph_requester_enable ), .cfg_vf_tph_st_mode ( cfg_vf_tph_st_mode ), // /* agan add @ 20220603, for debug*/ // .cfg_msg_received ( cfg_msg_received ) , // .cfg_msg_received_data ( cfg_msg_received_data ), // .cfg_msg_received_type ( cfg_msg_received_type ), // // //-------------------------------------------------------------------------------// // EP Only // //-------------------------------------------------------------------------------// // Interrupt Interface Signals .cfg_interrupt_int ( cfg_interrupt_int ), .cfg_interrupt_pending ( {2'b0,cfg_interrupt_pending} ), .cfg_interrupt_sent ( cfg_interrupt_sent ), .cfg_interrupt_msi_enable ( cfg_interrupt_msi_enable ), .cfg_interrupt_msi_mmenable ( cfg_interrupt_msi_mmenable ), .cfg_interrupt_msi_mask_update ( cfg_interrupt_msi_mask_update ), .cfg_interrupt_msi_data ( cfg_interrupt_msi_data ), .cfg_interrupt_msi_select ( cfg_interrupt_msi_select ), .cfg_interrupt_msi_int ( cfg_interrupt_msi_int ), .cfg_interrupt_msi_pending_status ( cfg_interrupt_msi_pending_status [31:0]), .cfg_interrupt_msi_sent ( cfg_interrupt_msi_sent ), .cfg_interrupt_msi_fail ( cfg_interrupt_msi_fail ), .cfg_interrupt_msi_attr ( cfg_interrupt_msi_attr ), .cfg_interrupt_msi_tph_present ( cfg_interrupt_msi_tph_present ), .cfg_interrupt_msi_tph_type ( cfg_interrupt_msi_tph_type ), .cfg_interrupt_msi_tph_st_tag ( cfg_interrupt_msi_tph_st_tag ), .cfg_interrupt_msi_pending_status_function_num (2'b0), .cfg_interrupt_msi_pending_status_data_enable (1'b0), .cfg_interrupt_msi_function_number (8'b0 ), //--------------------------------------------------------------------------------------// // System(SYS) Interface // //--------------------------------------------------------------------------------------// .sys_clk ( sys_clk ), .sys_clk_gt ( sys_clk_gt ), .sys_reset ( sys_reset_n ) ); end else if (PCIE_LOCATION == "X0Y1") begin pcie4_uscale_plus_1 pcie4_uscale_plus_0_i ( //---------------------------------------------------------------------------------------// // PCI Express (pci_exp) Interface // //---------------------------------------------------------------------------------------// //---------------------------------------------------------------------------------------// // PCI Express (pci_exp) Interface // //---------------------------------------------------------------------------------------// // Tx .pci_exp_txn ( pci_exp_txn ), .pci_exp_txp ( pci_exp_txp ), // Rx .pci_exp_rxn ( pci_exp_rxn ), .pci_exp_rxp ( pci_exp_rxp ), //---------------------------------------------------------------------------------------// // AXI Interface // //---------------------------------------------------------------------------------------// .user_clk ( user_clk_out ), .user_reset ( user_reset_out ), .user_lnk_up ( user_lnk_up ), .phy_rdy_out ( phy_rdy_out ), .s_axis_rq_tlast ( rport_s_axis_rq_tlast ), .s_axis_rq_tdata ( rport_s_axis_rq_tdata ), .s_axis_rq_tuser ( rport_s_axis_rq_tuser ), .s_axis_rq_tkeep ( rport_s_axis_rq_tkeep ), .s_axis_rq_tready ( rport_s_axis_rq_tready ), .s_axis_rq_tvalid ( rport_s_axis_rq_tvalid ), .m_axis_rc_tdata ( rport_m_axis_rc_tdata ), .m_axis_rc_tuser ( rport_m_axis_rc_tuser ), .m_axis_rc_tlast ( rport_m_axis_rc_tlast ), .m_axis_rc_tkeep ( rport_m_axis_rc_tkeep ), .m_axis_rc_tvalid ( rport_m_axis_rc_tvalid ), .m_axis_rc_tready ( rport_m_axis_rc_tready ), .m_axis_cq_tdata ( m_axis_cq_tdata ), .m_axis_cq_tuser ( m_axis_cq_tuser ), .m_axis_cq_tlast ( m_axis_cq_tlast ), .m_axis_cq_tkeep ( m_axis_cq_tkeep ), .m_axis_cq_tvalid ( m_axis_cq_tvalid ), .m_axis_cq_tready ( m_axis_cq_tready ), .s_axis_cc_tdata ( s_axis_cc_tdata ), .s_axis_cc_tuser ( s_axis_cc_tuser ), .s_axis_cc_tlast ( s_axis_cc_tlast ), .s_axis_cc_tkeep ( s_axis_cc_tkeep ), .s_axis_cc_tvalid ( s_axis_cc_tvalid ), .s_axis_cc_tready ( s_axis_cc_tready ), //---------------------------------------------------------------------------------------// // Configuration (CFG) Interface // //---------------------------------------------------------------------------------------// .pcie_tfc_nph_av ( pcie_tfc_nph_av ), .pcie_tfc_npd_av ( pcie_tfc_npd_av ), .pcie_rq_seq_num0 ( pcie_rq_seq_num0) , .pcie_rq_seq_num_vld0 ( pcie_rq_seq_num_vld0) , .pcie_rq_seq_num1 ( pcie_rq_seq_num1) , .pcie_rq_seq_num_vld1 ( pcie_rq_seq_num_vld1) , .pcie_rq_tag0 ( pcie_rq_tag0) , .pcie_rq_tag1 ( pcie_rq_tag1) , .pcie_rq_tag_av ( pcie_rq_tag_av) , .pcie_rq_tag_vld0 ( pcie_rq_tag_vld0) , .pcie_rq_tag_vld1 ( pcie_rq_tag_vld1) , .pcie_cq_np_req ( pcie_cq_np_req ), .pcie_cq_np_req_count ( pcie_cq_np_req_count ), .cfg_phy_link_down ( cfg_phy_link_down ), .cfg_phy_link_status ( cfg_phy_link_status), .cfg_negotiated_width ( cfg_negotiated_width ), .cfg_current_speed ( cfg_current_speed ), .cfg_max_payload ( cfg_max_payload ), .cfg_max_read_req ( cfg_max_read_req ), .cfg_function_status ( cfg_function_status ), .cfg_function_power_state ( cfg_function_power_state ), .cfg_vf_status ( cfg_vf_status ), .cfg_vf_power_state ( cfg_vf_power_state ), .cfg_link_power_state ( cfg_link_power_state ), // Error Reporting Interface .cfg_err_cor_out ( cfg_err_cor_out ), .cfg_err_nonfatal_out ( cfg_err_nonfatal_out ), .cfg_err_fatal_out ( cfg_err_fatal_out ), .cfg_local_error_out ( cfg_local_error_out ), .cfg_local_error_valid ( cfg_local_error_valid ), .cfg_ltssm_state ( cfg_ltssm_state ), .cfg_rx_pm_state ( cfg_rx_pm_state ), .cfg_tx_pm_state ( cfg_tx_pm_state ), .cfg_rcb_status ( cfg_rcb_status ), .cfg_obff_enable ( cfg_obff_enable ), .cfg_pl_status_change ( cfg_pl_status_change ), .cfg_tph_requester_enable ( cfg_tph_requester_enable ), .cfg_tph_st_mode ( cfg_tph_st_mode ), .cfg_vf_tph_requester_enable ( cfg_vf_tph_requester_enable ), .cfg_vf_tph_st_mode ( cfg_vf_tph_st_mode ), // /* agan add @ 20220603, for debug*/ // .cfg_msg_received ( cfg_msg_received ) , // .cfg_msg_received_data ( cfg_msg_received_data ), // .cfg_msg_received_type ( cfg_msg_received_type ), // // //-------------------------------------------------------------------------------// // EP Only // //-------------------------------------------------------------------------------// // Interrupt Interface Signals .cfg_interrupt_int ( cfg_interrupt_int ), .cfg_interrupt_pending ( {2'b0,cfg_interrupt_pending} ), .cfg_interrupt_sent ( cfg_interrupt_sent ), .cfg_interrupt_msi_enable ( cfg_interrupt_msi_enable ), .cfg_interrupt_msi_mmenable ( cfg_interrupt_msi_mmenable ), .cfg_interrupt_msi_mask_update ( cfg_interrupt_msi_mask_update ), .cfg_interrupt_msi_data ( cfg_interrupt_msi_data ), .cfg_interrupt_msi_select ( cfg_interrupt_msi_select ), .cfg_interrupt_msi_int ( cfg_interrupt_msi_int ), .cfg_interrupt_msi_pending_status ( cfg_interrupt_msi_pending_status [31:0]), .cfg_interrupt_msi_sent ( cfg_interrupt_msi_sent ), .cfg_interrupt_msi_fail ( cfg_interrupt_msi_fail ), .cfg_interrupt_msi_attr ( cfg_interrupt_msi_attr ), .cfg_interrupt_msi_tph_present ( cfg_interrupt_msi_tph_present ), .cfg_interrupt_msi_tph_type ( cfg_interrupt_msi_tph_type ), .cfg_interrupt_msi_tph_st_tag ( cfg_interrupt_msi_tph_st_tag ), .cfg_interrupt_msi_pending_status_function_num (2'b0), .cfg_interrupt_msi_pending_status_data_enable (1'b0), .cfg_interrupt_msi_function_number (8'b0 ), //--------------------------------------------------------------------------------------// // System(SYS) Interface // //--------------------------------------------------------------------------------------// .sys_clk ( sys_clk ), .sys_clk_gt ( sys_clk_gt ), .sys_reset ( sys_reset_n ) ); end endgenerate // IBUF sys_reset_n_ibuf (.O(sys_rst_n_c), .I(sys_reset_n)); //IBUFDS_GTE3 refclk_ibuf (.O(sys_clk_gt), .ODIV2(sys_clk), .I(sys_clk_p), .CEB(1'b0), .IB(sys_clk_n)); //--------------------------------------------------------------------------------------------------------------------// // Instantiate Configurator design //--------------------------------------------------------------------------------------------------------------------// cgator #( .TCQ ( TCQ ), .AXISTEN_IF_RQ_ALIGNMENT_MODE (AXISTEN_IF_RQ_ALIGNMENT_MODE), .EXTRA_PIPELINE ( EXTRA_PIPELINE ), .ROM_SIZE ( ROM_SIZE ), .ROM_FILE ( ROM_FILE ), .REQUESTER_ID ( REQUESTER_ID ), .C_DATA_WIDTH ( C_DATA_WIDTH ), .KEEP_WIDTH ( KEEP_WIDTH ) ) cgator_i ( // globals .user_clk ( user_clk_out ), .reset ( user_reset_out ), // User interface for configuration .start_config ( start_config ), .finished_config ( finished_config ), .failed_config ( failed_config ), // Rport AXIS interfaces .rport_s_axis_rq_tready ( rport_s_axis_rq_tready[0]), .rport_s_axis_rq_tdata ( rport_s_axis_rq_tdata ), .rport_s_axis_rq_tkeep ( rport_s_axis_rq_tkeep ), .rport_s_axis_rq_tuser ( rport_s_axis_rq_tuser ), .rport_s_axis_rq_tlast ( rport_s_axis_rq_tlast ), .rport_s_axis_rq_tvalid ( rport_s_axis_rq_tvalid ), .rport_m_axis_rc_tdata ( rport_m_axis_rc_tdata ), .rport_m_axis_rc_tkeep ( rport_m_axis_rc_tkeep ), .rport_m_axis_rc_tlast ( rport_m_axis_rc_tlast ), .rport_m_axis_rc_tvalid ( rport_m_axis_rc_tvalid ), .rport_m_axis_rc_tready ( rport_m_axis_rc_tready ), .rport_m_axis_rc_tuser ( rport_m_axis_rc_tuser ), // User AXIS interfaces .usr_s_axis_rq_tready ( s_axis_rq_tready ), .usr_s_axis_rq_tdata ( s_axis_rq_tdata ), .usr_s_axis_rq_tkeep ( s_axis_rq_tkeep ), .usr_s_axis_rq_tuser ( s_axis_rq_tuser ), .usr_s_axis_rq_tlast ( s_axis_rq_tlast ), .usr_s_axis_rq_tvalid ( s_axis_rq_tvalid ), .usr_m_axis_rc_tdata ( m_axis_rc_tdata ), .usr_m_axis_rc_tkeep ( m_axis_rc_tkeep ), .usr_m_axis_rc_tlast ( m_axis_rc_tlast ), .usr_m_axis_rc_tvalid ( m_axis_rc_tvalid ), .usr_m_axis_rc_tuser ( m_axis_rc_tuser ), .usr_m_axis_rc_tready ( m_axis_rc_tready ) // Rport CFG interface // User CFG interface // Rport PL interface ); //--------------------------------------------------------------------------------------------------------------------// endmodule // cgator_wrapper 这是cgator_wrapper的文件,ila核应该怎么配置?
08-07
# -*- coding: utf-8 -*- # 重新增加了然门控变得更快得方式:1.beta_l0更大;2.log_alpha的学习率变为2.0;3.添加熵正则化。 from __future__ import annotations import math import os import random import time from collections import deque from pathlib import Path from typing import Tuple import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR from torch.utils.data import DataLoader from torchvision import datasets, models, transforms from sklearn.cluster import KMeans import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.metrics import ( silhouette_score, silhouette_samples, calinski_harabasz_score, davies_bouldin_score, ) from sklearn.manifold import TSNE try: import umap # 只有 umap-learn 才带 UMAP 类 HAS_UMAP = hasattr(umap, "UMAP") or hasattr(umap, "umap_") except ImportError: HAS_UMAP = False from datetime import datetime from matplotlib.patches import Rectangle import warnings # -------------------------- Global configuration -------------------------- # class CFG: # Paths data_root: str = r"D:\dataset\TILDA_8class_73" save_root: str = r"D:\SCI_exp\7_29\exp_file" # Dataset & DL batch_size: int = 128 num_workers: int = 0 # tune to your CPU img_size: int = 224 # F2013 images are 48×48; we upscale for ResNet‐18 # Model dimensions (§3.5.1) d_backbone: int = 512 d_proj: int = 128 K_max: int = 3 mem_size: int = 4096 # Optimisation (§3.5.1) lr_warmup: float = 1e-3 lr_joint: float = 3e-4 lr_ft: float = 1e-4 weight_decay: float = 5e-4 n_epochs_warmup: int = 15#5 n_epochs_joint: int = 150 #20 n_epochs_ft: int = 25 #15 # Loss hyper‑params lambda1: float = 0.5 # push–pull alpha_proto: float = 0.1 scale_ce: float = 30.0 gamma_se: float = 20 # 自表示权重 0.5 # ---------- Hard-Concrete ---------- tau0_hc: float = 1.5 # 初始温度 tau_min_hc: float = 0.15 # 最低温度 anneal_epochs_hc: int = 5 gamma_hc: float = -0.1 # stretch 下界 zeta_hc: float = 1.1 # stretch 上界 beta_l0: float = 5e-2 # L0 正则系数 5e-2 hc_threshold: float = 0.35 # Misc seed: int = 42 device: str = "cuda" if torch.cuda.is_available() else "cpu" # ---------- datetime ---------- # def get_timestamp(): """获取当前时间戳,格式:YYYYMMDD_HHMMSS""" return datetime.now().strftime("%Y%m%d_%H%M%S") # ---------- diagnostics ---------- # MAX_SAMPLED = 5_000 # None → 全量 timestamp = get_timestamp() # 获取当前时间戳 DIAG_DIR = Path(CFG.save_root) / f"diagnostics_{timestamp}" # 文件夹名包含时间戳 DIAG_DIR.mkdir(parents=True, exist_ok=True) # -------------------------- Reproducibility -------------------------- # torch.manual_seed(CFG.seed) random.seed(CFG.seed) # -------------------------- Utility functions -------------------------- # def L2_normalise(t: torch.Tensor, dim: int = 1, eps: float = 1e-12) -> torch.Tensor: return F.normalize(t, p=2, dim=dim, eps=eps) def pairwise_cosine(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: """Compute cosine similarity between all pairs in *x* and *y*.""" x = L2_normalise(x) y = L2_normalise(y) return x @ y.T # (N, M) # -------------------------- Memory bank (FIFO queue) -------------------------- # class MemoryBank: """Fixed‑size FIFO queue storing (p, q, y_c). All tensors are detached.""" def __init__(self, dim: int, size: int): self.size = size self.dim = dim self.ptr = 0 self.is_full = False # pre‑allocate self.p_bank = torch.zeros(size, dim, device=CFG.device) self.q_bank = torch.zeros_like(self.p_bank) self.y_bank = torch.zeros(size, dtype=torch.long, device=CFG.device) @torch.no_grad() def enqueue(self, p: torch.Tensor, q: torch.Tensor, y: torch.Tensor): b = p.size(0) if b > self.size: p, q, y = p[-self.size:], q[-self.size:], y[-self.size:] b = self.size idx = (torch.arange(b, device=CFG.device) + self.ptr) % self.size self.p_bank[idx] = p.detach() self.q_bank[idx] = q.detach() self.y_bank[idx] = y.detach() self.ptr = (self.ptr + b) % self.size if self.ptr == 0: self.is_full = True def get(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: valid = self.size if self.is_full else self.ptr return ( self.p_bank[:valid].detach(), self.q_bank[:valid].detach(), self.y_bank[:valid].detach(), ) # -------------------------- Projection heads -------------------------- # class MLPHead(nn.Module): def __init__(self, in_dim: int, out_dim: int): super().__init__() self.mlp = nn.Sequential( nn.Linear(in_dim, out_dim//2, bias=False), nn.BatchNorm1d(out_dim//2), nn.ReLU(inplace=True), nn.Linear(out_dim//2, out_dim, bias=True), ) def forward(self, x: torch.Tensor): return self.mlp(x) # -------------------------- Cosine classifier -------------------------- # class CosineLinear(nn.Module): """Cosine classifier with fixed scale *s* (Eq. CE).""" def __init__(self, in_dim: int, n_classes: int, s: float = CFG.scale_ce): super().__init__() self.s = s self.weight = nn.Parameter(torch.randn(n_classes, in_dim)) nn.init.xavier_uniform_(self.weight) def forward(self, x: torch.Tensor): # x ∈ ℝ^{B×d_p} x = L2_normalise(x) w = L2_normalise(self.weight) # logits = s * cos(θ) return self.s * (x @ w.T) # -------------------------- BaPSTO model -------------------------- # class BaPSTO(nn.Module): """Backbone + DASSER heads + BPGSNet prototypes & gates.""" def __init__(self, n_classes: int): super().__init__() # --- Backbone (ResNet‑18) ------------------------------------------------ resnet = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1) pretrained_path = Path(CFG.save_root) / "resnet18_best_TILDA_8class_73_7446.pth" if pretrained_path.exists(): print(f"Loading pretrained weights from {pretrained_path}") pretrained = torch.load(pretrained_path, map_location=CFG.device, weights_only=True) # 创建临时模型来获取预训练权重的正确映射 temp_model = models.resnet18() temp_model.fc = nn.Linear(temp_model.fc.in_features, n_classes) temp_model.load_state_dict(pretrained["state_dict"], strict=False) # 复制预训练权重到我们的模型中(除了fc层) resnet_dict = resnet.state_dict() pretrained_dict = {k: v for k, v in temp_model.state_dict().items() if k in resnet_dict and 'fc' not in k} resnet_dict.update(pretrained_dict) resnet.load_state_dict(resnet_dict) print("✓ Successfully loaded pretrained backbone weights!") else: print(f"⚠️ Pretrained weights not found at {pretrained_path}. Using ImageNet weights.") # --- Backbone ------------------------------------------------ in_feat = resnet.fc.in_features # 512 resnet.fc = nn.Identity() self.backbone = resnet # project to d_backbone (512-64-128) #self.fc_backbone = nn.Linear(in_feat, CFG.d_backbone, bias=False) #nn.init.xavier_uniform_(self.fc_backbone.weight) # 这一句的 # --- Projection heads --------------------------------------------------- self.g_SA = MLPHead(CFG.d_backbone, CFG.d_proj) self.g_FV = MLPHead(CFG.d_backbone, CFG.d_proj) # Cosine classifier (coarse level) self.classifier = CosineLinear(CFG.d_proj, n_classes) # --- BPGSNet prototypes & gate logits ----------------------------------- self.prototypes = nn.Parameter( torch.randn(n_classes, CFG.K_max, CFG.d_proj) ) # (K_C, K_max, d_p) nn.init.xavier_uniform_(self.prototypes) self.log_alpha = nn.Parameter( torch.randn(n_classes, CFG.K_max) * 0.01 # 随机初始化 ) # (K_C, K_max) self.register_buffer("global_step", torch.tensor(0, dtype=torch.long)) # ---------------- Forward pass ---------------- # def forward(self, x: torch.Tensor, y_c: torch.Tensor, mem_bank: MemoryBank, use_bpgs: bool = True ) -> tuple[torch.Tensor, dict[str, float], torch.Tensor, torch.Tensor]: """Return full loss components (Section §3.3 & §3.4).""" B = x.size(0) # --- Backbone & projections ------------------------------------------- z = self.backbone(x) # (B, 512) p = L2_normalise(self.g_SA(z)) # (B, d_p) q = L2_normalise(self.g_FV(z)) # (B, d_p) bank_p, bank_q, bank_y = mem_bank.get() # ---------------- DASSER losses ---------------- # # L_SA, L_ortho, L_ce_dasser = self._dasser_losses( # p, q, y_c, bank_p, bank_q, bank_y # ) # total_loss = L_SA + L_ortho + L_ce_dasser # stats = { # "loss": total_loss.item(), # "L_SA": L_SA.item(), # "L_ortho": L_ortho.item(), # "L_ce_dasser": L_ce_dasser.item(), # } L_SA, L_ortho, L_ce_dasser, L_se = self._dasser_losses( p, q, y_c, bank_p, bank_q, bank_y ) total_loss = ( L_SA + L_ortho + L_ce_dasser + CFG.gamma_se * L_se # NEW ) stats = { "loss": total_loss.item(), "L_SA": L_SA.item(), "L_ortho": L_ortho.item(), "L_ce_dasser": L_ce_dasser.item(), "L_se": L_se.item(), # NEW } # ---------------- BPGSNet (conditional) -------- # if use_bpgs: L_ce_bpgs, L_proto, L_gate, coarse_logits = self._bpgs_losses(q, y_c) total_loss = total_loss + L_ce_bpgs + L_proto + L_gate stats.update({ "L_ce_bpgs": L_ce_bpgs.item(), "L_proto": L_proto.item(), "L_gate": L_gate.item(), }) else: coarse_logits = None return total_loss, stats, p.detach(), q.detach() # ---------------------- Internal helpers ---------------------- # def _dasser_losses( self, p: torch.Tensor, q: torch.Tensor, y_c: torch.Tensor, bank_p: torch.Tensor, bank_q: torch.Tensor, bank_y: torch.Tensor, ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: """ DASSER 损失: • 语义对齐 L_SA • 正交 L_ortho • 粗粒度 CE L_ce • 自表示 L_se (NEW) """ # ---------- 拼 batch + memory ---------- # p_all = torch.cat([p, bank_p], dim=0) if bank_p.numel() > 0 else p q_all = torch.cat([q, bank_q], dim=0) if bank_q.numel() > 0 else q y_all = torch.cat([y_c, bank_y], dim=0) if bank_y.numel() > 0 else y_c # ---------- 1) 语义对齐 (原有) ---------- # G = pairwise_cosine(p_all, p_all) # (N,N) :contentReference[oaicite:2]{index=2} G.fill_diagonal_(0.0) same = y_all.unsqueeze(0) == y_all.unsqueeze(1) diff = ~same L_SA = ((same * (1 - G)).sum() + CFG.lambda1 * (diff * G.clamp_min(0)).sum()) / (p_all.size(0) ** 2) # ---------- 2) 正交 (原有) --------------- # L_ortho = (1.0 / CFG.d_proj) * (p_all @ q_all.T).pow(2).sum() # ---------- 3) 自表示 (NEW) -------------- # C_logits = pairwise_cosine(p_all, p_all) # 再算一次以免受上一步改动 C_logits.fill_diagonal_(-1e4) # 置 −∞ → softmax≈0 C = F.softmax(C_logits, dim=1) # 行归一化 :contentReference[oaicite:3]{index=3} Q_recon = C @ q_all # 线性重构 L_se = F.mse_loss(Q_recon, q_all) # :contentReference[oaicite:4]{index=4} # ---------- 4) 粗粒度 CE (原有) ---------- # logits_coarse = self.classifier(p) L_ce = F.cross_entropy(logits_coarse, y_c) return L_SA, L_ortho, L_ce, L_se # ---------------------- 放到 BaPSTO 类里,直接替换原函数 ---------------------- # def _bpgs_losses( self, q: torch.Tensor, y_c: torch.Tensor ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: """ 计算 BPGSNet 损失(正确的 log-sum-exp 版) """ B = q.size(0) # q是batch*128的矩阵,获得批次大小 K_C, K_M = self.prototypes.size(0), self.prototypes.size(1) # K_C 是类别数,K_M 是每个类别的原型数 # (1) 欧氏距离 d = ((q.unsqueeze(1).unsqueeze(2) - self.prototypes.unsqueeze(0)) ** 2).sum(-1) # (B,K_C,K_M) s = 30.0 # ===== (2) 退火温度 τ ===== # τ 线性退火 epoch = self.global_step.item() / self.steps_per_epoch tau = max(CFG.tau_min_hc, CFG.tau0_hc - (CFG.tau0_hc - CFG.tau_min_hc) * min(1., epoch / CFG.anneal_epochs_hc)) # ----- (3) Hard- ----- log_alpha = self.log_alpha # (C,K) z, _s = self._sample_hardConcrete(log_alpha, tau) # z: (C,K) g = z.unsqueeze(0) # (1,C,K) 广播到 batch # (1,C,K) # ----- (4) coarse logits ----- mask_logits = -d * s + torch.log(g + 1e-12) # (B,C,K) coarse_logits = torch.logsumexp(mask_logits, dim=2) # (B,C) # ----- (5) losses ----- L_ce = F.cross_entropy(coarse_logits, y_c) y_hat = torch.softmax(mask_logits.detach(), dim=2) # stop-grad L_proto = CFG.alpha_proto * (y_hat * d).mean() # ---------- Hard-Concrete 的 L0 正则 ---------- temp = (log_alpha - tau * math.log(-CFG.gamma_hc / CFG.zeta_hc)) # (C,K) p_active = torch.sigmoid(temp) # 激活概率 p_active 是解析期望 pa(z大于0) # 新增加得loss pa = torch.sigmoid(log_alpha) entropy_penalty = 0.05 * (pa * torch.log(pa + 1e-8) + (1-pa) * torch.log(1-pa + 1e-8)).mean() # 新增加得loss,控制全局稀疏率 L_gate = CFG.beta_l0 * p_active.mean() - entropy_penalty # L0 正则 beta_l0 控控制全局稀疏率 return L_ce, L_proto, L_gate, coarse_logits def _sample_hardConcrete(self, log_alpha, tau): """return z ~ HardConcrete, and its stretched unclipped \tilde z""" u = torch.rand_like(log_alpha).clamp_(1e-6, 1-1e-6) s = torch.sigmoid((log_alpha + torch.log(u) - torch.log(1-u)) / tau) s = s * (CFG.zeta_hc - CFG.gamma_hc) + CFG.gamma_hc # stretch z_hard = s.clamp(0.0, 1.0) z = z_hard + (s - s.detach()) # ST estimator,让梯度穿过 return z, s # z用于前向, s用于梯度 # -------------------------- K-means++ initialisation -------------------------- # @torch.no_grad() def kmeans_init(model: BaPSTO, loader: DataLoader): """Use q‑features to initialise prototypes with K‑means++ (§3.4.1).""" print("[Init] Running K‑means++ for prototype initialisation...") model.eval() all_q, all_y = [], [] for x, y in loader: x = x.to(CFG.device) z = L2_normalise(model.g_FV(model.backbone(x))) all_q.append(z.cpu()) all_y.append(y) all_q = torch.cat(all_q) # (N, d_p) all_y = torch.cat(all_y) # (N,) for c in range(model.prototypes.size(0)): feats = all_q[all_y == c] kmeans = KMeans( n_clusters=CFG.K_max, init="k-means++", n_init=10, max_iter=100, random_state=CFG.seed, ).fit(feats.numpy()) centroids = torch.from_numpy(kmeans.cluster_centers_).to(CFG.device) centroids = L2_normalise(centroids) # (K_max, d_p) model.prototypes.data[c] = centroids print("[Init] Prototype initialisation done.") # -------------------------- Training utilities -------------------------- # def accuracy(output: torch.Tensor, target: torch.Tensor) -> float: """Compute top‑1 accuracy (coarse).""" with torch.no_grad(): pred = output.argmax(dim=1) correct = pred.eq(target).sum().item() return correct / target.size(0) @torch.no_grad() def _collect_Q_labels(model: BaPSTO, loader: DataLoader): """遍历 *loader*,返回 (Q features, coarse-ID, proto-ID);采样上限 MAX_SAMPLED.""" model.eval() qs, cls, subs = [], [], [] for x, y in loader: x = x.to(CFG.device) q = L2_normalise(model.g_FV(model.backbone(x))) # (B,d) # —— 预测最近原型 idx —— # d = ((q.unsqueeze(1).unsqueeze(2) - model.prototypes.unsqueeze(0))**2).sum(-1) # (B,C,K) proto_id = d.view(d.size(0), -1).argmin(dim=1) # flatten idx = C*K + k qs.append(q.cpu()) cls.append(y) subs.append(proto_id.cpu()) if MAX_SAMPLED and (sum(len(t) for t in qs) >= MAX_SAMPLED): break Q = torch.cat(qs)[:MAX_SAMPLED] # (N,d) Yc = torch.cat(cls)[:MAX_SAMPLED] # coarse Ysub = torch.cat(subs)[:MAX_SAMPLED] # pseudo-fine return Q.numpy(), Yc.numpy(), Ysub.numpy() def _plot_heatmap(mat: np.ndarray, title: str, path: Path, boxes: list[tuple[int,int]] | None = None): """ mat : 排好序的相似度矩阵 boxes : [(row_start,row_end), ...];坐标在排序后的索引系中 """ plt.figure(figsize=(6, 5)) ax = plt.gca() im = ax.imshow(mat, cmap="viridis", aspect="auto") plt.colorbar(im) if boxes: # 逐个 coarse-class 画框 for s, e in boxes: w = e - s rect = Rectangle((s - .5, s - .5), w, w, linewidth=1.5, edgecolor="white", facecolor="none") ax.add_patch(rect) plt.title(title) plt.tight_layout() plt.savefig(path, dpi=300) plt.close() def compute_and_save_diagnostics(model: BaPSTO, loader: DataLoader, tag: str): """ • 计算三个内部指标并保存 csv • 绘制五张图 (C heatmap, t-SNE / UMAP, Laplacian spectrum, Silhouette bars, Gate heatmap(opt)) """ print(f"[Diag] computing metrics ({tag}) ...") timestamp = get_timestamp() Q, Yc, Ysub = _collect_Q_labels(model, loader) # ========== 1) 聚类指标 ========== # sil = silhouette_score(Q, Ysub, metric="cosine") ch = calinski_harabasz_score(Q, Ysub) db = davies_bouldin_score(Q, Ysub) pd.DataFrame( {"tag":[tag], "silhouette":[sil], "calinski":[ch], "davies":[db]} ).to_csv(DIAG_DIR / f"cluster_metrics_{tag}_{timestamp}.csv", index=False) # ========== 2) C heatmap & Laplacian ========== # GRAPH_LEVEL = 'coarse' # ← 这里换 'sub' 就看细粒度--------------------------------------------------- # ① —— 相似度矩阵(始终基于所有样本,用来画热力图) —— # P_all = Q @ Q.T / np.linalg.norm(Q, axis=1, keepdims=True) / np.linalg.norm(Q, axis=1)[:, None] np.fill_diagonal(P_all, -1e4) # 取消自环 C_heat = torch.softmax(torch.tensor(P_all), dim=1).cpu().numpy() # —— 画热力图:完全沿用旧逻辑,不受 GRAPH_LEVEL 影响 —— # order = np.lexsort((Ysub, Yc)) # 先 coarse 再 sub #order = np.argsort(Yc) # 只按粗类别拍平---------------------- # —— 计算每个 coarse-class 的起止行(列) —— # coarse_sorted = Yc[order] bounds = [] # [(start,end),...] start = 0 for i in range(1, len(coarse_sorted)): if coarse_sorted[i] != coarse_sorted[i-1]: bounds.append((start, i)) # [start, end) start = i bounds.append((start, len(coarse_sorted))) # —— 绘图,并把边界传给 boxes 参数 —— # _plot_heatmap(C_heat[order][:, order], f"C heatmap ({tag})", DIAG_DIR / f"C_heatmap_{tag}_{timestamp}.png", boxes=bounds) # ② —— 针对 Laplacian 的图,可选按 coarse/sub 屏蔽 —— # P_graph = P_all.copy() # 从全局矩阵复制一份 if GRAPH_LEVEL == 'coarse': P_graph[Yc[:, None] != Yc[None, :]] = -1e4 # 只留同 coarse 的边 elif GRAPH_LEVEL == 'sub': P_graph[Ysub[:, None] != Ysub[None, :]] = -1e4 # 只留同子簇的边 C_graph = torch.softmax(torch.tensor(P_graph), dim=1).cpu().numpy() D = np.diag(C_graph.sum(1)) L = D - (C_graph + C_graph.T) / 2 eigs = np.sort(np.linalg.eigvalsh(L))[:30] plt.figure(); plt.plot(eigs, marker='o') plt.title(f"Laplacian spectrum ({GRAPH_LEVEL or 'global'} | {tag})") plt.tight_layout() plt.savefig(DIAG_DIR / f"laplacian_{tag}_{timestamp}.png", dpi=300); plt.close() # ========== 3) t-SNE / UMAP (带图例 & 色彩 ≤20) ========== # warnings.filterwarnings("ignore", message="n_jobs value 1") focus_cls = 1#None # ← 若只看 coarse ID=3,把它改成 3 sel = slice(None) if focus_cls is None else (Yc == focus_cls) Q_sel, Ysub_sel = Q[sel], Ysub[sel] # -- 选 UMAP 或 t-SNE -- if HAS_UMAP: # :contentReference[oaicite:2]{index=2} reducer_cls = umap.UMAP if hasattr(umap, "UMAP") else umap.umap_.UMAP reducer = reducer_cls(n_neighbors=30, min_dist=0.1, random_state=CFG.seed) method = "UMAP" else: reducer = TSNE(perplexity=30, init="pca", random_state=CFG.seed) method = "t-SNE" emb = reducer.fit_transform(Q_sel) # (N,2) # ---------- scatter ---------- # unique_sub = np.unique(Ysub_sel) try: # 新版 Matplotlib (≥3.7) cmap = plt.get_cmap("tab20", min(len(unique_sub), 20)) except TypeError: # 旧版 Matplotlib (<3.7) cmap = plt.cm.get_cmap("tab20", min(len(unique_sub), 20)) plt.figure(figsize=(5, 5)) for i, s_id in enumerate(unique_sub): pts = Ysub_sel == s_id plt.scatter(emb[pts, 0], emb[pts, 1], color=cmap(i % 20), s=6, alpha=0.7, label=str(s_id) if len(unique_sub) <= 20 else None) if len(unique_sub) <= 20: plt.legend(markerscale=2, bbox_to_anchor=(1.02, 1), borderaxespad=0.) title = f"{method} ({tag})" if focus_cls is None else f"{method} cls={focus_cls} ({tag})" plt.title(title) plt.tight_layout() plt.savefig(DIAG_DIR / f"embed_{tag}_{timestamp}.png", dpi=300) plt.close() # ========== 4) Silhouette bars ========== # sil_samples = silhouette_samples(Q, Ysub, metric="cosine") order = np.argsort(Ysub) plt.figure(figsize=(6,4)) plt.barh(np.arange(len(sil_samples)), sil_samples[order], color="steelblue") plt.title(f"Silhouette per sample ({tag})"); plt.xlabel("coefficient") plt.tight_layout(); plt.savefig(DIAG_DIR / f"silhouette_bar_{tag}_{timestamp}.png", dpi=300); plt.close() print(f"[Diag] saved to {DIAG_DIR}") def create_dataloaders() -> Tuple[DataLoader, DataLoader, int]: """Load train/val as ImageFolder and return dataloaders + K_C.""" train_dir = Path(CFG.data_root) / "train" val_dir = Path(CFG.data_root) / "test" classes = sorted([d.name for d in train_dir.iterdir() if d.is_dir()]) K_C = len(classes) transform_train = transforms.Compose( [ transforms.Grayscale(num_output_channels=3), transforms.Resize((CFG.img_size, CFG.img_size)), transforms.RandomHorizontalFlip(), transforms.RandomRotation(10), transforms.RandomResizedCrop(CFG.img_size, scale=(0.8, 1.0)), transforms.ToTensor(), transforms.Normalize(mean=[0.5] * 3, std=[0.5] * 3), ] ) transform_val = transforms.Compose( [ transforms.Grayscale(num_output_channels=3), transforms.Resize((CFG.img_size, CFG.img_size)), transforms.ToTensor(), transforms.Normalize(mean=[0.5] * 3, std=[0.5] * 3), ] ) train_ds = datasets.ImageFolder(str(train_dir), transform=transform_train) val_ds = datasets.ImageFolder(str(val_dir), transform=transform_val) train_loader = DataLoader( train_ds, batch_size=CFG.batch_size, shuffle=True, num_workers=CFG.num_workers, pin_memory=True, drop_last=True, ) val_loader = DataLoader( val_ds, batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers, pin_memory=True, ) return train_loader, val_loader, K_C # -------------------------- Main training routine -------------------------- # def train(): best_ckpt_path = None # 记录最佳 joint 权重的完整文件名 best_acc = 0.0 best_epoch = -1 train_loader, val_loader, K_C = create_dataloaders() model = BaPSTO(K_C).to(CFG.device) model.steps_per_epoch = len(train_loader) #print(model) mb = MemoryBank(dim=CFG.d_proj, size=CFG.mem_size) warmup_weights_path = Path(CFG.save_root) / "bapsto_warmup_complete.pth" # 检查是否存在预保存的warm-up权重 if warmup_weights_path.exists(): print(f"找到预训练的warm-up权重,正在加载: {warmup_weights_path}") checkpoint = torch.load(warmup_weights_path, map_location=CFG.device,weights_only=True) model.load_state_dict(checkpoint["state_dict"]) print("✓ 成功加载warm-up权重,跳过warm-up阶段!") else: # ---------- Phase 1: DASSER warm‑up (backbone frozen) ---------- # print("\n==== Phase 1 | DASSER warm‑up ====") for p in model.backbone.parameters(): p.requires_grad = False # —— 冻结 prototypes 和 gate_logits —— # model.prototypes.requires_grad = False model.log_alpha.requires_grad = False # —— 冻结 prototypes 和 gate_logits —— # optimizer = optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr=CFG.lr_warmup, weight_decay=CFG.weight_decay, betas=(0.9, 0.95), ) scheduler = CosineAnnealingLR(optimizer, T_max=len(train_loader) * CFG.n_epochs_warmup) for epoch in range(CFG.n_epochs_warmup): run_epoch(train_loader, model, mb, optimizer, scheduler, epoch, phase="warmup") # 保存warm-up完成后的权重 torch.save( {"epoch": CFG.n_epochs_warmup, "state_dict": model.state_dict()}, warmup_weights_path ) print(f"✓ Warm-up完成,模型权重已保存至: {warmup_weights_path}") # after warm‑up loop, before Phase 2 header kmeans_init(model, train_loader) # <─ 新增 print("K‑means initialisation done. Prototypes are now ready.") compute_and_save_diagnostics(model, train_loader, tag="after_kmeans") # ---------- Phase 2: Joint optimisation (all params trainable) ---------- # print("\n==== Phase 2 | Joint optimisation ====") for p in model.backbone.parameters(): p.requires_grad = True # —— 解冻 prototypes 和 gate logits —— # model.prototypes.requires_grad = True model.log_alpha.requires_grad = True # —— 解冻 prototypes 和 gate logits —— # param_groups = [ {"params": [p for n,p in model.named_parameters() if n!='log_alpha'], "lr": CFG.lr_joint}, {"params": [model.log_alpha], "lr": CFG.lr_joint * 2.0} ] optimizer = optim.AdamW( param_groups, weight_decay=CFG.weight_decay, betas=(0.9, 0.95), ) scheduler = CosineAnnealingLR(optimizer, T_max=len(train_loader) * CFG.n_epochs_joint) best_acc = 0.0 best_epoch = -1 epochs_no_improve = 0 for epoch in range(CFG.n_epochs_joint): stats = run_epoch(train_loader, model, mb, optimizer, scheduler, epoch, phase="joint") # ─────────────────────────────────────────── if (epoch + 1) % 1 == 0: # 每个 epoch 都跑验证 # —— 每 5 个 epoch 额外保存 Gate & 聚类诊断 —— # if (epoch + 1) % 5 == 0: timestamp = get_timestamp() gate_prob = torch.sigmoid(model.log_alpha.detach().cpu()) _plot_heatmap( gate_prob, f"Gate prob (ep{epoch+1})", DIAG_DIR / f"gate_ep{epoch+1}_{timestamp}.png", ) compute_and_save_diagnostics( model, train_loader, tag=f"joint_ep{epoch+1}" ) # ---------- 统计指标 ---------- val_loss, val_acc, per_cls_acc, auc = metrics_on_loader(val_loader, model) train_acc = metrics_on_loader (train_loader, model)[1] # 只取整体训练准确率 print(f"[Val] ep {epoch+1:02d} | loss {val_loss:.3f} | " f"acc {val_acc:.3f} | train-acc {train_acc:.3f} |\n" f" per-cls-acc {np.round(per_cls_acc, 2)} |\n" f" AUC {np.round(auc, 2)}") # —— checkpoint —— # if val_acc > best_acc: best_acc = val_acc best_epoch = epoch epochs_no_improve = 0 best_ckpt_path = save_ckpt(model, epoch, tag="best_joint", acc=val_acc, optimizer=optimizer, scheduler=scheduler) # ← 传进去 else: epochs_no_improve += 1 # —— gate 修剪 —— # if epoch+1 >= 10: # 先训练 10 个 epoch 再剪 prune_gates(model, threshold=0.25, min_keep=1, hc_threshold=CFG.hc_threshold) # —— early stopping —— # if epochs_no_improve >= 50: print("Early stopping triggered in joint phase.") break # ─────────────────────────────────────────── model.global_step += 1 print(model.prototypes.grad.norm()) # 非零即可证明 L_proto 对原型确实有更新压力 model.global_step.zero_() # Joint训练结束后,重命名最佳模型文件,添加准确率 best_acc_int = round(best_acc * 1e4) # 将0.7068转换为7068 joint_ckpt_path = Path(CFG.save_root) / "bapsto_best_joint.pth" renamed_path = Path(CFG.save_root) / f"bapsto_best_joint_{best_acc_int}.pth" if joint_ckpt_path.exists(): joint_ckpt_path.rename(renamed_path) best_ckpt_path = renamed_path # ★ 同步路径,供 fine-tune 使用 print(f"✓ 最优联合训练模型已重命名: {renamed_path.name} " f"(epoch {best_epoch+1}, ACC: {best_acc:.4f})") # ---------- Phase 3: Fine‑tune (prototypes & gates frozen) ---------- # print("\n==== Phase 3 | Fine‑tuning ====") best_ft_acc = 0.0 best_ft_epoch = -1 # 若有最佳 joint 权重则加载 if best_ckpt_path is not None and Path(best_ckpt_path).exists(): ckpt = torch.load(best_ckpt_path, map_location=CFG.device, weights_only=True) model.load_state_dict(ckpt["state_dict"]) epoch_loaded = ckpt["epoch"] + 1 # 以 1 为起点的人类可读轮次 acc_loaded = ckpt.get("acc", -1) # 若早期代码没存 acc,给个占位 print(f"✓ loaded best joint ckpt (epoch {epoch_loaded}, ACC {acc_loaded:.4f})") else: print("⚠️ best_ckpt_path 未找到,继续沿用上一轮权重。") for param in [model.prototypes, model.log_alpha]: param.requires_grad = False for p in model.parameters(): if p.requires_grad: p.grad = None # clear any stale gradients optimizer = optim.AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr=CFG.lr_ft, weight_decay=CFG.weight_decay, betas=(0.9, 0.95), ) scheduler = CosineAnnealingLR(optimizer, T_max=len(train_loader) * CFG.n_epochs_ft) for epoch in range(CFG.n_epochs_ft): run_epoch(train_loader, model, mb, optimizer, scheduler, epoch, phase="finetune") if (epoch + 1) % 1 == 0: # 每个 epoch 都评估 val_acc = evaluate(val_loader, model) print(f"[FT] ep {epoch+1:02d} | acc {val_acc:.4f}") # ① 按 epoch 保存快照(可选) save_ckpt(model, epoch, tag="ft") # ② 维护 “fine-tune 最佳” if val_acc > best_ft_acc: best_ft_acc = val_acc best_ft_epoch = epoch best_ft_acc_int = round(best_ft_acc * 1e4) # 将0.7068转换为7068 best_ft_ckpt_path = Path(CFG.save_root) / f"bapsto_best_ft_{best_ft_acc_int}.pth" save_ckpt(model, epoch, tag="best_ft", acc=val_acc) # 只保留一个最新 best_ft # 重命名保存文件 if best_ft_ckpt_path.exists(): best_ft_ckpt_path.rename(best_ft_ckpt_path) print(f"✓ Fine-tune最佳模型已重命名: {best_ft_ckpt_path.name} (epoch {best_ft_epoch+1}, ACC: {best_ft_acc:.4f})") print(f"Training completed. Best FT ACC {best_ft_acc:.4f}") # -------------------------- Helper functions -------------------------- # def run_epoch(loader, model, mem_bank: MemoryBank, optimizer, scheduler, epoch, phase:str): model.train() running = {"loss": 0.0} use_bpgs = (phase != "warmup") for step, (x, y) in enumerate(loader): x, y = x.to(CFG.device), y.to(CFG.device) optimizer.zero_grad() loss, stats, p_det, q_det = model(x, y, mem_bank, use_bpgs=use_bpgs) loss.backward() optimizer.step() scheduler.step() mem_bank.enqueue(p_det, q_det, y.detach()) # accumulate for k, v in stats.items(): running[k] = running.get(k, 0.0) + v # ★★★★★ Hard-Concrete 梯度健康检查 ★★★★★ if phase == "joint" and step % 100 == 0: # ─── Hard-Concrete 监控 ─── tau_now = max( CFG.tau_min_hc, CFG.tau0_hc - (CFG.tau0_hc - CFG.tau_min_hc) * min(1.0, model.global_step.item() / (model.steps_per_epoch * CFG.anneal_epochs_hc)) ) pa = torch.sigmoid(model.log_alpha) # (C,K) p_act = pa.mean().item() alive = (pa > 0.4).float().sum().item() # 0.4 与 prune 阈值一致 total = pa.numel() # = C × K grad_nm = (model.log_alpha.grad.detach().norm().item() if model.log_alpha.grad is not None else 0.0) pa = torch.sigmoid(model.log_alpha) print(f"[DBG] τ={tau_now:.3f} p̄={pa.mean():.3f} " f"min={pa.min():.2f} max={pa.max():.2f} " f"alive={(pa>0.25).sum().item()}/{pa.numel()} " f"‖∇α‖={grad_nm:.2e}") # ★★★★★ 监控段结束 ★★★★★ if (step + 1) % 50 == 0: avg_loss = running["loss"] / (step + 1) print( f"Epoch[{phase} {epoch+1}] Step {step+1}/{len(loader)} | " f"loss: {avg_loss:.4f}", end="\r", ) # epoch summary print(f"Epoch [{phase} {epoch+1}]: " + ', '.join(f"{k}: {running[k]:.4f}" for k in running)) return running @torch.no_grad() def evaluate(loader, model): model.eval() total_correct, total_samples = 0, 0 K_C, K_M = model.prototypes.size(0), model.prototypes.size(1) gate_hard = (model.log_alpha > 0).float() # (K_C,K_M) for x, y in loader: x, y = x.to(CFG.device), y.to(CFG.device) b = x.size(0) # --- 特征 & 距离 --- q = L2_normalise(model.g_FV(model.backbone(x))) # (b,d_p) d = ((q.unsqueeze(1).unsqueeze(2) - model.prototypes.unsqueeze(0))**2).sum(-1) # (b,K_C,K_M) s = 30.0 # scale for logits # --- 子簇 logit & 粗 logit --- mask_logits = -d * s + torch.log(gate_hard + 1e-12) # (b,K_C,K_M) # 这里由于是log,所以二者相加 coarse_logits = torch.logsumexp(mask_logits, dim=2) # (b,K_C) # --- 统计准确率 --- total_correct += coarse_logits.argmax(1).eq(y).sum().item() total_samples += b return total_correct / total_samples @torch.no_grad() def metrics_on_loader(loader, model): """ 返回: loss_avg – 均值交叉熵 acc – overall top-1 per_cls_acc (C,) – 每个 coarse 类别准确率 auc (C,) – 每类 one-vs-rest ROC-AUC """ model.eval() n_cls = model.prototypes.size(0) total_loss, total_correct, total_samples = 0., 0, 0 # —— 用来存储全量 logits / labels —— # logits_all, labels_all = [], [] ce_fn = nn.CrossEntropyLoss(reduction="sum") # 累加再除 for x, y in loader: x, y = x.to(CFG.device), y.to(CFG.device) # 前向 with torch.no_grad(): q = L2_normalise(model.g_FV(model.backbone(x))) d = ((q.unsqueeze(1).unsqueeze(2) - model.prototypes.unsqueeze(0))**2).sum(-1) logits = torch.logsumexp(-d*30 + torch.log((model.log_alpha>0).float()+1e-12), dim=2) total_loss += ce_fn(logits, y).item() total_correct += logits.argmax(1).eq(y).sum().item() total_samples += y.size(0) logits_all.append(logits.cpu()) labels_all.append(y.cpu()) # —— overall —— # loss_avg = total_loss / total_samples acc = total_correct / total_samples # —— 拼接 & 转 numpy —— # logits_all = torch.cat(logits_all).numpy() labels_all = torch.cat(labels_all).numpy() # —— per-class ACC —— # per_cls_acc = np.zeros(n_cls) for c in range(n_cls): mask = labels_all == c if mask.any(): per_cls_acc[c] = (logits_all[mask].argmax(1) == c).mean() # —— per-class AUC —— # try: from sklearn.metrics import roc_auc_score prob = torch.softmax(torch.from_numpy(logits_all), dim=1).numpy() auc = roc_auc_score(labels_all, prob, multi_class="ovr", average=None) except Exception: # 组数太少或只有 1 类样本时会报错 auc = np.full(n_cls, np.nan) return loss_avg, acc, per_cls_acc, auc def save_ckpt(model, epoch:int, tag:str, acc:float|None=None, optimizer=None, scheduler=None): """ 通用保存函数 • 返回 ckpt 文件完整路径,方便上层记录 • 可选把 opt / sched state_dict 一起存进去,便于 resume """ save_dir = Path(CFG.save_root) save_dir.mkdir(parents=True, exist_ok=True) # -------- 路径策略 -------- # if tag == "best_joint": # 只保留一个最新最优 joint ckpt_path = save_dir / "bapsto_best_joint.pth" else: # 其他阶段带时间戳 ckpt_path = save_dir / f"bapsto_{tag}_epoch{epoch+1}_{get_timestamp()}.pth" # -------- 组装 payload -------- # # • vars(CFG) 可以拿到用户自己在 CFG 里写的字段 # • 再过滤掉 __ 开头的内部键、防止把 Python meta-data 也 dump 进去 cfg_dict = {k: v for k, v in vars(CFG).items() if not k.startswith("__")} payload = { "epoch": epoch, "state_dict": model.state_dict(), "cfg": cfg_dict, # ← 改在这里 } if acc is not None: payload["acc"] = acc if optimizer is not None: payload["optimizer"] = optimizer.state_dict() if scheduler is not None: payload["scheduler"] = scheduler.state_dict() torch.save(payload, ckpt_path) print(f"✓ checkpoint saved to {ckpt_path}") return ckpt_path @torch.no_grad() def prune_gates(model: BaPSTO, threshold=0.05, min_keep=2, hc_threshold=0.35): """ Disable sub-clusters whose mean gate probability < threshold. After setting them to -10, we do another **row normalization**: Each coarse class row is subtracted by the max logit of that row, ensuring the maximum logit for active clusters is 0 and inactive clusters ≈ -10 → softmax(-10) ≈ 0. Also check for Hard-Concrete (HC) weights below a threshold (e.g., 0.35) to disable sub-clusters. """ # softmax probabilities (K_C, K_max) p_active = torch.sigmoid(model.log_alpha) # Activation probability mask = (p_active < threshold) # Check HC thresholds and disable low weight clusters low_weight_mask = (p_active < hc_threshold) # Find sub-clusters with low HC weight mask = mask | low_weight_mask # Combine with existing mask # Ensure at least `min_keep` sub-clusters are kept per coarse class keep_mask = (mask.cumsum(1) >= (CFG.K_max - min_keep)) mask = mask & ~keep_mask pruned = mask.sum().item() if pruned == 0: return model.log_alpha.data[mask] = -10.0 # Set log_alpha of pruned sub-clusters to a very low value print(f"Pruned {pruned} sub-clusters (ḡ<{threshold}, keep≥{min_keep}/class)") # Reassign samples from pruned sub-clusters to active sub-clusters if pruned > 0: # Find the indices of the pruned sub-clusters pruned_clusters = mask.sum(dim=1) > 0 # (K_C,) for c in range(model.prototypes.size(0)): # Loop through each coarse class if pruned_clusters[c]: pruned_indices = mask[c] # Get indices of pruned sub-clusters for class `c` active_indices = ~pruned_indices # Get indices of active sub-clusters active_prototypes = model.prototypes[c][active_indices] # Get active prototypes q = model.q # Get features # Reassign samples from pruned clusters to active clusters d_active = pairwise_cosine(q, active_prototypes) # Compute distance to active prototypes best_active = d_active.argmin(dim=1) # Assign samples to the nearest active sub-cluster # Update the model with reallocated samples (you can implement reallocation logic here) print(f"Reassigning samples from pruned sub-clusters of class {c} to active clusters.") # -------------------------- Entrypoint -------------------------- # if __name__ == "__main__": os.makedirs(CFG.save_root, exist_ok=True) start = time.time() train() print(f"Total runtime: {(time.time() - start) / 3600:.2f} h") 逐行详细解释代码
09-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值