mirror of
https://github.com/RYDE-WORK/lnp_ml.git
synced 2026-09-18 15:03:20 +08:00
fix: 修正 size 预测的反归一化并将 MoE+LLM 模型接入应用
This commit is contained in:
parent
866941039f
commit
71f915e683
@ -12,8 +12,6 @@ encapsulation_efficiency_2: score = weight, where weight=0.08
|
|||||||
|
|
||||||
pdi_0: score = weight, where weight=0.08
|
pdi_0: score = weight, where weight=0.08
|
||||||
pdi_1: score = weight, where weight=0.02
|
pdi_1: score = weight, where weight=0.02
|
||||||
pdi_2: score = weight, where weight=0
|
|
||||||
pdi_3: score = weight, where weight=0
|
|
||||||
|
|
||||||
toxicity_0: score=weight, where weight=0.2
|
toxicity_0: score=weight, where weight=0.2
|
||||||
toxicity_1: score=weight, where weight=0
|
toxicity_1: score=weight, where weight=0
|
||||||
|
|||||||
20
app/api.py
20
app/api.py
@ -65,7 +65,7 @@ class ScoringWeightsRequest(BaseModel):
|
|||||||
delivery_weight: float = Field(default=0.0, ge=0.0, description="量化递送权重")
|
delivery_weight: float = Field(default=0.0, ge=0.0, description="量化递送权重")
|
||||||
size_weight: float = Field(default=0.0, ge=0.0, description="粒径权重 (80-150nm)")
|
size_weight: float = Field(default=0.0, ge=0.0, description="粒径权重 (80-150nm)")
|
||||||
ee_class_weights: List[float] = Field(default=[0.0, 0.0, 0.0], description="EE 分类权重 [class0, class1, class2]")
|
ee_class_weights: List[float] = Field(default=[0.0, 0.0, 0.0], description="EE 分类权重 [class0, class1, class2]")
|
||||||
pdi_class_weights: List[float] = Field(default=[0.0, 0.0, 0.0, 0.0], description="PDI 分类权重 [class0, class1, class2, class3]")
|
pdi_class_weights: List[float] = Field(default=[0.0, 0.0], description="PDI 分类权重 [class0(<0.2), class1(>=0.2)]")
|
||||||
toxic_class_weights: List[float] = Field(default=[0.0, 0.0], description="毒性分类权重 [无毒, 有毒]")
|
toxic_class_weights: List[float] = Field(default=[0.0, 0.0], description="毒性分类权重 [无毒, 有毒]")
|
||||||
|
|
||||||
def to_scoring_weights(self) -> ScoringWeights:
|
def to_scoring_weights(self) -> ScoringWeights:
|
||||||
@ -87,7 +87,7 @@ class OptimizeRequest(BaseModel):
|
|||||||
top_k: int = Field(default=20, ge=1, le=100, description="Number of top formulations to return")
|
top_k: int = Field(default=20, ge=1, le=100, description="Number of top formulations to return")
|
||||||
num_seeds: Optional[int] = Field(default=None, ge=1, le=500, description="Number of seed points from first iteration (default: top_k * 5)")
|
num_seeds: Optional[int] = Field(default=None, ge=1, le=500, description="Number of seed points from first iteration (default: top_k * 5)")
|
||||||
top_per_seed: int = Field(default=1, ge=1, le=10, description="Number of local best to keep per seed in refinement")
|
top_per_seed: int = Field(default=1, ge=1, le=10, description="Number of local best to keep per seed in refinement")
|
||||||
rerank_top_n: int = Field(default=0, ge=0, le=1000, description="两阶段推理:粗筛后用完整模型重排的候选数,0 表示关闭")
|
rerank_top_n: int = Field(default=200, ge=0, le=1000, description="两阶段推理:粗筛后用完整模型重排的候选数。0 表示关闭,此时 LLM 会在粗筛阶段跑满全部候选")
|
||||||
step_sizes: Optional[List[float]] = Field(default=None, description="Mol ratio step sizes for each iteration (default: [10, 2, 1])")
|
step_sizes: Optional[List[float]] = Field(default=None, description="Mol ratio step sizes for each iteration (default: [10, 2, 1])")
|
||||||
wr_step_sizes: Optional[List[float]] = Field(default=None, description="Weight ratio step sizes for each iteration (default: [5, 2, 1])")
|
wr_step_sizes: Optional[List[float]] = Field(default=None, description="Weight ratio step sizes for each iteration (default: [5, 2, 1])")
|
||||||
comp_ranges: Optional[CompRangesRequest] = Field(default=None, description="组分范围配置(默认使用标准范围)")
|
comp_ranges: Optional[CompRangesRequest] = Field(default=None, description="组分范围配置(默认使用标准范围)")
|
||||||
@ -127,7 +127,7 @@ class FormulationResult(BaseModel):
|
|||||||
quantified_delivery: Optional[float] = None
|
quantified_delivery: Optional[float] = None
|
||||||
unnormalized_delivery: Optional[float] = None # 反推的原始递送值(z-score 逆变换)
|
unnormalized_delivery: Optional[float] = None # 反推的原始递送值(z-score 逆变换)
|
||||||
size: Optional[float] = None
|
size: Optional[float] = None
|
||||||
pdi_class: Optional[int] = None # PDI 分类 (0: <0.2, 1: 0.2-0.3, 2: 0.3-0.4, 3: >0.4)
|
pdi_class: Optional[int] = None # PDI 分类 (0: <0.2, 1: ≥0.2)
|
||||||
ee_class: Optional[int] = None # EE 分类 (0: <80%, 1: 80-90%, 2: >90%)
|
ee_class: Optional[int] = None # EE 分类 (0: <80%, 1: 80-90%, 2: >90%)
|
||||||
toxic_class: Optional[int] = None # 毒性分类 (0: 无毒, 1: 有毒)
|
toxic_class: Optional[int] = None # 毒性分类 (0: 无毒, 1: 有毒)
|
||||||
|
|
||||||
@ -146,6 +146,9 @@ class HealthResponse(BaseModel):
|
|||||||
model_loaded: bool
|
model_loaded: bool
|
||||||
device: str
|
device: str
|
||||||
available_organs: List[str]
|
available_organs: List[str]
|
||||||
|
use_moe: bool = False
|
||||||
|
use_llm: bool = False
|
||||||
|
use_rag: bool = False
|
||||||
|
|
||||||
|
|
||||||
# ============ Global State ============
|
# ============ Global State ============
|
||||||
@ -239,11 +242,16 @@ app.add_middleware(
|
|||||||
@app.get("/", response_model=HealthResponse)
|
@app.get("/", response_model=HealthResponse)
|
||||||
async def health_check():
|
async def health_check():
|
||||||
"""健康检查"""
|
"""健康检查"""
|
||||||
|
_m = state.model
|
||||||
|
_llm = getattr(_m, "llm_prompt", None) if _m is not None else None
|
||||||
return HealthResponse(
|
return HealthResponse(
|
||||||
status="healthy" if state.model is not None else "model_not_loaded",
|
status="healthy" if _m is not None else "model_not_loaded",
|
||||||
model_loaded=state.model is not None,
|
model_loaded=_m is not None,
|
||||||
device=str(state.device),
|
device=str(state.device),
|
||||||
available_organs=AVAILABLE_ORGANS,
|
available_organs=AVAILABLE_ORGANS,
|
||||||
|
use_moe=getattr(_m, "moe", None) is not None,
|
||||||
|
use_llm=_llm is not None,
|
||||||
|
use_rag=bool(getattr(_llm, "use_rag", False)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -368,6 +376,8 @@ async def optimize_formulation(request: OptimizeRequest):
|
|||||||
logger.error(f"Optimization failed: {e}")
|
logger.error(f"Optimization failed: {e}")
|
||||||
if getattr(state.model, "llm_prompt", None) is not None:
|
if getattr(state.model, "llm_prompt", None) is not None:
|
||||||
state.model.set_llm_enabled(True)
|
state.model.set_llm_enabled(True)
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
torch.cuda.empty_cache()
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
38
app/app.py
38
app/app.py
@ -143,6 +143,7 @@ def call_optimize_api(
|
|||||||
top_k: int = 20,
|
top_k: int = 20,
|
||||||
num_seeds: int = None,
|
num_seeds: int = None,
|
||||||
top_per_seed: int = 1,
|
top_per_seed: int = 1,
|
||||||
|
rerank_top_n: int = 200,
|
||||||
step_sizes: list = None,
|
step_sizes: list = None,
|
||||||
wr_step_sizes: list = None,
|
wr_step_sizes: list = None,
|
||||||
comp_ranges: dict = None,
|
comp_ranges: dict = None,
|
||||||
@ -156,6 +157,7 @@ def call_optimize_api(
|
|||||||
"top_k": top_k,
|
"top_k": top_k,
|
||||||
"num_seeds": num_seeds,
|
"num_seeds": num_seeds,
|
||||||
"top_per_seed": top_per_seed,
|
"top_per_seed": top_per_seed,
|
||||||
|
"rerank_top_n": rerank_top_n,
|
||||||
"step_sizes": step_sizes,
|
"step_sizes": step_sizes,
|
||||||
"wr_step_sizes": wr_step_sizes,
|
"wr_step_sizes": wr_step_sizes,
|
||||||
"comp_ranges": comp_ranges,
|
"comp_ranges": comp_ranges,
|
||||||
@ -270,9 +272,18 @@ def main():
|
|||||||
# API 状态
|
# API 状态
|
||||||
if api_online:
|
if api_online:
|
||||||
st.success("🟢 API 服务在线")
|
st.success("🟢 API 服务在线")
|
||||||
|
try:
|
||||||
|
with httpx.Client(timeout=5) as _c:
|
||||||
|
_info = _c.get(f"{API_URL}/").json()
|
||||||
|
_caps = [n for n, k in (("MoE", "use_moe"), ("LLM", "use_llm"), ("RAG", "use_rag"))
|
||||||
|
if _info.get(k)]
|
||||||
|
st.caption(f"模型: {' + '.join(_caps) if _caps else '仅 backbone'}|{_info.get('device', '?')}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
st.error("🔴 API 服务离线")
|
st.error("🔴 API 服务离线")
|
||||||
st.info("请先启动 API 服务:\n```\nuvicorn app.api:app --port 8000\n```")
|
st.info(f"请先启动 API 服务:\n```\nuvicorn app.api:app --port 8010\n```\n"
|
||||||
|
f"当前 API_URL: {API_URL}")
|
||||||
|
|
||||||
# st.divider()
|
# st.divider()
|
||||||
|
|
||||||
@ -351,6 +362,18 @@ def main():
|
|||||||
step=1,
|
step=1,
|
||||||
help="后续迭代中,每个种子点邻域保留的局部最优数量",
|
help="后续迭代中,每个种子点邻域保留的局部最优数量",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
st.markdown("**LLM 推理精度**")
|
||||||
|
rerank_top_n = st.slider(
|
||||||
|
"LLM 重排候选数 (rerank_top_n)",
|
||||||
|
min_value=20,
|
||||||
|
max_value=500,
|
||||||
|
value=200,
|
||||||
|
step=20,
|
||||||
|
help="粗筛阶段只用 backbone 打分,再用完整模型对前 N 个"
|
||||||
|
"候选重新预测并排序。"
|
||||||
|
"调大更接近完整模型的判断,但耗时线性增加。",
|
||||||
|
)
|
||||||
|
|
||||||
st.markdown("**迭代步长与轮数**")
|
st.markdown("**迭代步长与轮数**")
|
||||||
use_custom_steps = st.checkbox(
|
use_custom_steps = st.checkbox(
|
||||||
@ -512,7 +535,7 @@ def main():
|
|||||||
help="score = normalize(delivery, route) × weight",
|
help="score = normalize(delivery, route) × weight",
|
||||||
)
|
)
|
||||||
sw_size = st.number_input(
|
sw_size = st.number_input(
|
||||||
"粒径 (Size, 80-150nm)",
|
"粒径 (Size, 60-150nm)",
|
||||||
min_value=0.00, max_value=10.00, value=0.05,
|
min_value=0.00, max_value=10.00, value=0.05,
|
||||||
step=0.05, format="%.2f", key="sw_size",
|
step=0.05, format="%.2f", key="sw_size",
|
||||||
help="score = (1 if 60≤size≤150 else 0) × weight",
|
help="score = (1 if 60≤size≤150 else 0) × weight",
|
||||||
@ -528,15 +551,11 @@ def main():
|
|||||||
sw_ee2 = st.number_input(">80% (高)", min_value=0.00, max_value=1.00, value=0.08, step=0.01, format="%.2f", key="sw_ee2")
|
sw_ee2 = st.number_input(">80% (高)", min_value=0.00, max_value=1.00, value=0.08, step=0.01, format="%.2f", key="sw_ee2")
|
||||||
|
|
||||||
st.caption("**PDI 分类权重**")
|
st.caption("**PDI 分类权重**")
|
||||||
col1, col2, col3, col4 = st.columns(4)
|
col1, col2 = st.columns(2)
|
||||||
with col1:
|
with col1:
|
||||||
sw_pdi0 = st.number_input("<0.2 (优)", min_value=0.00, max_value=1.00, value=0.08, step=0.01, format="%.2f", key="sw_pdi0")
|
sw_pdi0 = st.number_input("<0.2 (优)", min_value=0.00, max_value=1.00, value=0.08, step=0.01, format="%.2f", key="sw_pdi0")
|
||||||
with col2:
|
with col2:
|
||||||
sw_pdi1 = st.number_input("0.2-0.3 (良)", min_value=0.00, max_value=1.00, value=0.02, step=0.01, format="%.2f", key="sw_pdi1")
|
sw_pdi1 = st.number_input("≥0.2 (欠佳)", min_value=0.00, max_value=1.00, value=0.00, step=0.01, format="%.2f", key="sw_pdi1")
|
||||||
with col3:
|
|
||||||
sw_pdi2 = st.number_input("0.3-0.4 (中)", min_value=0.00, max_value=1.00, value=0.00, step=0.01, format="%.2f", key="sw_pdi2")
|
|
||||||
with col4:
|
|
||||||
sw_pdi3 = st.number_input(">0.4 (差)", min_value=0.00, max_value=1.00, value=0.00, step=0.01, format="%.2f", key="sw_pdi3")
|
|
||||||
|
|
||||||
st.caption("**毒性分类权重**")
|
st.caption("**毒性分类权重**")
|
||||||
col1, col2 = st.columns(2)
|
col1, col2 = st.columns(2)
|
||||||
@ -550,7 +569,7 @@ def main():
|
|||||||
"delivery_weight": sw_delivery,
|
"delivery_weight": sw_delivery,
|
||||||
"size_weight": sw_size,
|
"size_weight": sw_size,
|
||||||
"ee_class_weights": [sw_ee0, sw_ee1, sw_ee2],
|
"ee_class_weights": [sw_ee0, sw_ee1, sw_ee2],
|
||||||
"pdi_class_weights": [sw_pdi0, sw_pdi1, sw_pdi2, sw_pdi3],
|
"pdi_class_weights": [sw_pdi0, sw_pdi1],
|
||||||
"toxic_class_weights": [sw_toxic0, sw_toxic1],
|
"toxic_class_weights": [sw_toxic0, sw_toxic1],
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
@ -602,6 +621,7 @@ def main():
|
|||||||
top_k=top_k,
|
top_k=top_k,
|
||||||
num_seeds=num_seeds,
|
num_seeds=num_seeds,
|
||||||
top_per_seed=top_per_seed,
|
top_per_seed=top_per_seed,
|
||||||
|
rerank_top_n=rerank_top_n,
|
||||||
step_sizes=step_sizes,
|
step_sizes=step_sizes,
|
||||||
wr_step_sizes=wr_step_sizes_val,
|
wr_step_sizes=wr_step_sizes_val,
|
||||||
comp_ranges=comp_ranges,
|
comp_ranges=comp_ranges,
|
||||||
|
|||||||
@ -146,6 +146,16 @@ if not DELIVERY_NORM:
|
|||||||
logger.warning("DELIVERY_NORM is empty — scoring normalization for delivery will be disabled")
|
logger.warning("DELIVERY_NORM is empty — scoring normalization for delivery will be disabled")
|
||||||
|
|
||||||
|
|
||||||
|
_SIZE_STATS_PATH = Path(__file__).resolve().parent / "size_zscore_stats.json"
|
||||||
|
if _SIZE_STATS_PATH.exists():
|
||||||
|
with open(_SIZE_STATS_PATH) as _f:
|
||||||
|
SIZE_ZSCORE_STATS: Dict[str, float] = json.load(_f)
|
||||||
|
logger.info(f"Loaded size stats from {_SIZE_STATS_PATH}")
|
||||||
|
else:
|
||||||
|
SIZE_ZSCORE_STATS = {}
|
||||||
|
logger.warning(f"size_zscore_stats.json not found at {_SIZE_STATS_PATH}, "
|
||||||
|
"pred_size 将置为 NaN,run 'make preprocess' to generate it")
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ScoringWeights:
|
class ScoringWeights:
|
||||||
"""
|
"""
|
||||||
@ -161,7 +171,7 @@ class ScoringWeights:
|
|||||||
size_weight: float = 0.0 # score = (1 if 80<=size<=150 else 0) * weight
|
size_weight: float = 0.0 # score = (1 if 80<=size<=150 else 0) * weight
|
||||||
# 分类任务:per-class 权重(预测为该类时,得分 = 对应权重)
|
# 分类任务:per-class 权重(预测为该类时,得分 = 对应权重)
|
||||||
ee_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) # EE class 0, 1, 2
|
ee_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) # EE class 0, 1, 2
|
||||||
pdi_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0, 0.0]) # PDI class 0, 1, 2, 3
|
pdi_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0]) # PDI class 0(<0.2), 1(>=0.2)
|
||||||
toxic_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0]) # Toxic class 0, 1
|
toxic_class_weights: List[float] = field(default_factory=lambda: [0.0, 0.0]) # Toxic class 0, 1
|
||||||
|
|
||||||
|
|
||||||
@ -299,7 +309,7 @@ class Formulation:
|
|||||||
quantified_delivery: Optional[float] = None
|
quantified_delivery: Optional[float] = None
|
||||||
unnormalized_delivery: Optional[float] = None # 反推的原始递送值(z-score 逆变换)
|
unnormalized_delivery: Optional[float] = None # 反推的原始递送值(z-score 逆变换)
|
||||||
size: Optional[float] = None
|
size: Optional[float] = None
|
||||||
pdi_class: Optional[int] = None # PDI 分类 (0-3)
|
pdi_class: Optional[int] = None # PDI 分类 (0: <0.2, 1: ≥0.2)
|
||||||
ee_class: Optional[int] = None # EE 分类 (0-2)
|
ee_class: Optional[int] = None # EE 分类 (0-2)
|
||||||
toxic_class: Optional[int] = None # 毒性分类 (0: 无毒, 1: 有毒)
|
toxic_class: Optional[int] = None # 毒性分类 (0: 无毒, 1: 有毒)
|
||||||
|
|
||||||
@ -596,9 +606,13 @@ def predict_all(
|
|||||||
# 添加到 DataFrame
|
# 添加到 DataFrame
|
||||||
for i, col in enumerate(TARGET_BIODIST):
|
for i, col in enumerate(TARGET_BIODIST):
|
||||||
df[f"pred_{col}"] = biodist_preds[:, i]
|
df[f"pred_{col}"] = biodist_preds[:, i]
|
||||||
|
|
||||||
# size 模型输出为 log(size),转换回真实粒径 (nm)
|
if SIZE_ZSCORE_STATS:
|
||||||
df["pred_size"] = np.exp(size_preds)
|
df["pred_size"] = np.exp(
|
||||||
|
size_preds * SIZE_ZSCORE_STATS["std"] + SIZE_ZSCORE_STATS["mean"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
df["pred_size"] = np.nan
|
||||||
df["pred_delivery"] = delivery_preds
|
df["pred_delivery"] = delivery_preds
|
||||||
df["pred_pdi_class"] = pdi_preds
|
df["pred_pdi_class"] = pdi_preds
|
||||||
df["pred_ee_class"] = ee_preds
|
df["pred_ee_class"] = ee_preds
|
||||||
@ -688,7 +702,7 @@ def select_top_k(
|
|||||||
# 额外预测值
|
# 额外预测值
|
||||||
quantified_delivery=row.get("pred_delivery"),
|
quantified_delivery=row.get("pred_delivery"),
|
||||||
unnormalized_delivery=unnorm_delivery,
|
unnormalized_delivery=unnorm_delivery,
|
||||||
size=row.get("pred_size"),
|
size=float(row["pred_size"]) if pd.notna(row.get("pred_size")) else None,
|
||||||
pdi_class=int(row.get("pred_pdi_class")) if row.get("pred_pdi_class") is not None else None,
|
pdi_class=int(row.get("pred_pdi_class")) if row.get("pred_pdi_class") is not None else None,
|
||||||
ee_class=int(row.get("pred_ee_class")) if row.get("pred_ee_class") is not None else None,
|
ee_class=int(row.get("pred_ee_class")) if row.get("pred_ee_class") is not None else None,
|
||||||
toxic_class=int(row.get("pred_toxic_class")) if row.get("pred_toxic_class") is not None else None,
|
toxic_class=int(row.get("pred_toxic_class")) if row.get("pred_toxic_class") is not None else None,
|
||||||
@ -817,7 +831,7 @@ def optimize(
|
|||||||
routes: Optional[List[str]] = None,
|
routes: Optional[List[str]] = None,
|
||||||
scoring_weights: Optional[ScoringWeights] = None,
|
scoring_weights: Optional[ScoringWeights] = None,
|
||||||
batch_size: int = 256,
|
batch_size: int = 256,
|
||||||
rerank_top_n: int = 0,
|
rerank_top_n: int = 200,
|
||||||
rerank_batch_size: int = 16,
|
rerank_batch_size: int = 16,
|
||||||
) -> List[Formulation]:
|
) -> List[Formulation]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
4
app/size_zscore_stats.json
Normal file
4
app/size_zscore_stats.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"mean": 4.593287493148074,
|
||||||
|
"std": 0.42933069758929093
|
||||||
|
}
|
||||||
@ -459,12 +459,20 @@ class LNPModel(nn.Module):
|
|||||||
完整的多任务 forward。
|
完整的多任务 forward。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dict[str, Tensor]: size [B,1], pdi [B,4], ee [B,3],
|
Dict[str, Tensor]: size [B,1], pdi [B,2], ee [B,3],
|
||||||
delivery [B,1], biodist [B,7], toxic [B,2]
|
delivery [B,1], biodist [B,7], toxic [B,2]
|
||||||
"""
|
"""
|
||||||
fused = self.forward_backbone(smiles, tabular)
|
fused = self.forward_backbone(smiles, tabular)
|
||||||
return self.head(fused, getattr(self, "_last_pooled", None) if getattr(self, "reg_bypass", True) else None)
|
return self.head(fused, getattr(self, "_last_pooled", None) if getattr(self, "reg_bypass", True) else None)
|
||||||
|
|
||||||
|
def set_llm_enabled(self, enabled: bool) -> None:
|
||||||
|
"""开关 LLM 旁路,供两阶段推理在粗筛阶段跳过 7B 前向。
|
||||||
|
|
||||||
|
forward 用 getattr(self, "_llm_enabled", True) 读取,因此没调用过
|
||||||
|
本方法的实例默认开启,与改动前行为一致。
|
||||||
|
"""
|
||||||
|
self._llm_enabled = bool(enabled)
|
||||||
|
|
||||||
def clear_cache(self) -> None:
|
def clear_cache(self) -> None:
|
||||||
"""清空所有 encoder 的缓存"""
|
"""清空所有 encoder 的缓存"""
|
||||||
self.rdkit_encoder.clear_cache()
|
self.rdkit_encoder.clear_cache()
|
||||||
|
|||||||
@ -1095,12 +1095,12 @@ def main(
|
|||||||
|
|
||||||
# 创建完整数据集(仅用于获取样本数做 split)
|
# 创建完整数据集(仅用于获取样本数做 split)
|
||||||
n_samples = len(LNPDataset(df))
|
n_samples = len(LNPDataset(df))
|
||||||
|
|
||||||
# 外层 CV split
|
# 外层 CV split
|
||||||
outer_cv = StratifiedKFold(
|
outer_cv = StratifiedKFold(
|
||||||
n_splits=n_outer_folds, shuffle=True, random_state=seed
|
n_splits=n_outer_folds, shuffle=True, random_state=seed
|
||||||
)
|
)
|
||||||
|
|
||||||
device_str = str(device)
|
device_str = str(device)
|
||||||
fold_args = []
|
fold_args = []
|
||||||
for outer_fold, (outer_train_idx, outer_test_idx) in enumerate(
|
for outer_fold, (outer_train_idx, outer_test_idx) in enumerate(
|
||||||
|
|||||||
@ -68,6 +68,12 @@ def main(
|
|||||||
df["size"] = pd.to_numeric(df["size"], errors="coerce")
|
df["size"] = pd.to_numeric(df["size"], errors="coerce")
|
||||||
df["size"] = np.log(df["size"].replace(0, np.nan)) # 避免 log(0)
|
df["size"] = np.log(df["size"].replace(0, np.nan)) # 避免 log(0)
|
||||||
|
|
||||||
|
size_stats = {"mean": float(df["size"].mean()), "std": float(df["size"].std())}
|
||||||
|
size_stats_path = APP_DIR / "size_zscore_stats.json"
|
||||||
|
with open(size_stats_path, "w") as f:
|
||||||
|
json.dump(size_stats, f, indent=2)
|
||||||
|
logger.success(f"Saved size stats to {size_stats_path}: {size_stats}")
|
||||||
|
|
||||||
# 保存
|
# 保存
|
||||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
df.to_csv(output_path, index=False)
|
df.to_csv(output_path, index=False)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user