Hsqcsu commited on
Commit
f9fff4f
·
verified ·
1 Parent(s): 351c0a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +29 -40
app.py CHANGED
@@ -9,14 +9,14 @@ from rdkit import Chem
9
  from rdkit.Chem.Draw import rdMolDraw2D
10
  from huggingface_hub import hf_hub_download
11
 
12
- # 导入网络结构与预处理模块
13
  from IR_encoder import IRModel
14
  from SMILES_encoder import SmilesModel
 
 
15
  from ir_process import (
16
  preprocess_jdx_spectra_higer_500,
17
- preprocess_jdx_spectra_lower_500,
18
- preprocess_csv_spectra_higer_500,
19
- preprocess_csv_spectra_lower_than_500
20
  )
21
 
22
  # ==================== 全局配置 ====================
@@ -117,10 +117,10 @@ def load_model_instance(model_instance, weight_filename, device):
117
  return model_instance
118
 
119
 
120
- # ==================== 1. 模型初始化 (双分支权重) ====================
121
  print("Initializing Models and Feature Libraries...")
122
 
123
- # 通用模型权重 (NIST / Custom 库)
124
  ir_model_general = load_model_instance(IRModel(), "overall_best_top1_ir.pth", DEVICE)
125
  smiles_model_general = load_model_instance(
126
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
@@ -128,7 +128,7 @@ smiles_model_general = load_model_instance(
128
  DEVICE
129
  )
130
 
131
- # 专属 PS 模型权重 (PS Existing / PS Derivative 库)
132
  ir_model_ps = load_model_instance(IRModel(), "overall_best_nps_recall10_ir.pth", DEVICE)
133
  smiles_model_ps = load_model_instance(
134
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
@@ -136,7 +136,7 @@ smiles_model_ps = load_model_instance(
136
  DEVICE
137
  )
138
 
139
- # 载入分子库特征
140
  library_nist_features = load_gz_features("library_NIST_smiles_features_fp16.gz", DEVICE)
141
  general_smiles_nist = load_smiles_list("NIST_smiles_can.txt")
142
 
@@ -153,24 +153,17 @@ ALL_LIBS = {
153
  }
154
 
155
 
156
- # ==================== 2. 置信度校准与自定义分子编码 ====================
157
  def calculate_calibrated_confidence(cosine_sim: float, smiles_model_instance, model_type: str = "general") -> float:
158
- """
159
- 使用从离线测试集拟合的 Platt Scaling 参数计算校准概率
160
- """
161
  with torch.no_grad():
162
  t = torch.exp(smiles_model_instance.t_prime).cpu().item()
163
  b = smiles_model_instance.bias.cpu().item()
164
-
165
- # 1. 计算双塔模型原始 Logit
166
  raw_logit = cosine_sim * t + b
167
-
168
- # 2. 读取对应模型的 Platt A 与 B 参数
169
  params = PLATT_CALIBRATION_PARAMS.get(model_type, PLATT_CALIBRATION_PARAMS["general"])
170
  A = params["A"]
171
  B = params["B"]
172
-
173
- # 3. 逻辑回归推断: P = 1 / (1 + exp(-(A * Logit + B)))
174
  calibrated_logit = A * raw_logit + B
175
  confidence = 1.0 / (1.0 + np.exp(-calibrated_logit))
176
  return float(np.clip(confidence, 0.0, 1.0))
@@ -187,7 +180,7 @@ def process_custom_smiles(smiles_str, smiles_model_instance):
187
  original_map[can] = s
188
  if not valid_smiles:
189
  return None, [], []
190
-
191
  tokenizer = smiles_model_instance.smiles_tokenizer
192
  encoded = [tokenizer(s, max_length=300, padding='max_length', truncation=True, return_tensors='pt') for s in valid_smiles]
193
  ids = torch.cat([item['input_ids'] for item in encoded], dim=0).to(DEVICE)
@@ -197,7 +190,7 @@ def process_custom_smiles(smiles_str, smiles_model_instance):
197
  return feats, valid_smiles, [original_map[s] for s in valid_smiles]
198
 
199
 
200
- # ==================== 3. 检索主任务 ====================
201
  def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_str, access_key, progress=gr.Progress()):
202
  if library_name == "PS Derivative Library":
203
  if access_key != ADMIN_KEY:
@@ -206,7 +199,7 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
206
  if ir_file is None:
207
  raise gr.Error("Please upload an IR spectrum file.")
208
 
209
- # 动态切换模型分支
210
  is_ps_lib = library_name in ["PS Existing Library", "PS Derivative Library"]
211
  current_ir_model = ir_model_ps if is_ps_lib else ir_model_general
212
  current_smiles_model = smiles_model_ps if is_ps_lib else smiles_model_general
@@ -218,34 +211,27 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
218
  if file_path.lower().endswith('.csv'):
219
  try:
220
  df = pd.read_csv(file_path, header=None)
221
- w, i = df.iloc[:, 0].values, df.iloc[:, 1].values
222
- if spectrum_type == "Transmittance":
223
- ir_data = preprocess_csv_spectra_higer_500(w, i) if w[0] > 500 else preprocess_csv_spectra_lower_than_500(w, i)
224
- else:
225
- ir_data = preprocess_csv_spectra_higer_500(w, i)
226
  except Exception as e:
227
  raise gr.Error(f"Error processing CSV: {str(e)}")
228
 
229
  elif file_path.lower().endswith('.jdx'):
230
  try:
231
  data = jcamp.jcamp_readfile(file_path)
232
- w, i = np.array(data['x']), np.array(data['y'])
233
- if spectrum_type == "Absorbance":
234
- ir_data = preprocess_jdx_spectra_higer_500(w, i) if w[0] > 500 else preprocess_jdx_spectra_lower_500(w, i)
235
- else:
236
- ir_data = preprocess_jdx_spectra_higer_500(w, i)
237
  except Exception as e:
238
  raise gr.Error(f"Error processing JDX: {str(e)}")
239
 
240
  if ir_data is None:
241
- raise gr.Error("Unsupported file format or invalid selection. Please check your file and 'Type' setting.")
242
 
243
- # 特征提取
244
  ir_tensor = torch.tensor(ir_data, dtype=torch.float32).unsqueeze(0).to(DEVICE)
245
  with torch.no_grad():
246
  ir_feat = current_ir_model(ir_tensor).to(torch.float32)
247
 
248
- # 匹配库读取
249
  if library_name == "Custom Library":
250
  lib_features, lib_smiles, display_smiles = process_custom_smiles(custom_smiles_str, current_smiles_model)
251
  if lib_features is None:
@@ -254,17 +240,16 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
254
  lib_features, lib_smiles = ALL_LIBS[library_name]
255
  display_smiles = lib_smiles
256
 
257
- # 相似度矩阵与 Top10
258
  sims = ir_feat @ lib_features.to(DEVICE).to(torch.float32).t()
259
  scores, indices = sims.topk(min(10, lib_features.shape[0]), dim=1)
260
  res_smiles = [display_smiles[idx.item()] for idx in indices[0]]
261
  res_scores = [sco.item() for sco in scores[0]]
262
 
263
- # 置信度计算
264
  top1_score = res_scores[0]
265
  top1_calibrated_conf = calculate_calibrated_confidence(top1_score, current_smiles_model, model_type=model_type_flag)
266
 
267
- # 渲染置信度卡片 (按要求更新说明文本)
268
  conf_summary = (
269
  f'<div style="background-color: #ffffff; color: #1a1a1a; padding: 22px; border-radius: 12px; border: 1px solid #e0e0e0; text-align: left; box-shadow: 0 2px 6px rgba(0,0,0,0.03);">'
270
  f'<h3 style="color: #00bfa5; margin-top: 0; margin-bottom: 14px; text-align: center; font-size: 1.3em;">📊 置信度分析 (Confidence Analysis)</h3>'
@@ -276,7 +261,7 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
276
  f'</div>'
277
  f'<p style="font-size: 0.88em; color: #64748b; line-height: 1.6; margin: 0; text-align: justify;">'
278
  f'<b>Note:</b> 注意,该置信度校准为 manuscript 中 “Multi-perspective Interpretability Analysis” 节中的 '
279
- f'<b>Confidence calibration analysis</b> 获取的拟合参数。鉴于受到搜索库大小与搜索对象的差异,置信度可能发生偏差,因此,建议根据给出的前置候选物进行综合人工考量。'
280
  f'</p>'
281
  f'</div>'
282
  )
@@ -285,6 +270,10 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
285
  grid_filename = "search_results_grid.png"
286
  grid_img_path = generate_mol_grid_custom(res_smiles, legends, grid_filename)
287
 
 
 
 
 
288
  return (
289
  gr.update(visible=True),
290
  grid_img_path,
@@ -294,12 +283,12 @@ def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_st
294
  "Cosine Similarity": [round(s, 4) for s in res_scores],
295
  "Calibrated Probability": [f"{calculate_calibrated_confidence(s, current_smiles_model, model_type=model_type_flag)*100:.2f}%" for s in res_scores]
296
  }),
297
- gr.update(visible=True, value=pd.DataFrame({'wavenumber': w, 'intensity': i})),
298
  gr.update(value=conf_summary, visible=True)
299
  )
300
 
301
 
302
- # ==================== 4. 界面前端 ====================
303
  CSS = """
304
  * { font-family: 'Times New Roman', Times, serif !important; }
305
  body { background-color: #ffffff !important; color: #333333 !important; }
 
9
  from rdkit.Chem.Draw import rdMolDraw2D
10
  from huggingface_hub import hf_hub_download
11
 
12
+ # 导入网络模型结构
13
  from IR_encoder import IRModel
14
  from SMILES_encoder import SmilesModel
15
+
16
+ # 导入指定的统一网格预处理模块
17
  from ir_process import (
18
  preprocess_jdx_spectra_higer_500,
19
+ preprocess_csv_spectra_higer_500
 
 
20
  )
21
 
22
  # ==================== 全局配置 ====================
 
117
  return model_instance
118
 
119
 
120
+ # ==================== 1. 初始化双分支检索模型 ====================
121
  print("Initializing Models and Feature Libraries...")
122
 
123
+ # 通用分支 (NIST / Custom 库)
124
  ir_model_general = load_model_instance(IRModel(), "overall_best_top1_ir.pth", DEVICE)
125
  smiles_model_general = load_model_instance(
126
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
 
128
  DEVICE
129
  )
130
 
131
+ # 专属 PS 分支 (PS Existing / PS Derivative 库)
132
  ir_model_ps = load_model_instance(IRModel(), "overall_best_nps_recall10_ir.pth", DEVICE)
133
  smiles_model_ps = load_model_instance(
134
  SmilesModel(roberta_model_path=None, roberta_tokenizer_path=TOKENIZER_PATH, smiles_maxlen=300, feature_dim=768),
 
136
  DEVICE
137
  )
138
 
139
+ # 载入分子特征库
140
  library_nist_features = load_gz_features("library_NIST_smiles_features_fp16.gz", DEVICE)
141
  general_smiles_nist = load_smiles_list("NIST_smiles_can.txt")
142
 
 
153
  }
154
 
155
 
156
+ # ==================== 2. 置信度计算与分子编码 ====================
157
  def calculate_calibrated_confidence(cosine_sim: float, smiles_model_instance, model_type: str = "general") -> float:
 
 
 
158
  with torch.no_grad():
159
  t = torch.exp(smiles_model_instance.t_prime).cpu().item()
160
  b = smiles_model_instance.bias.cpu().item()
161
+
 
162
  raw_logit = cosine_sim * t + b
 
 
163
  params = PLATT_CALIBRATION_PARAMS.get(model_type, PLATT_CALIBRATION_PARAMS["general"])
164
  A = params["A"]
165
  B = params["B"]
166
+
 
167
  calibrated_logit = A * raw_logit + B
168
  confidence = 1.0 / (1.0 + np.exp(-calibrated_logit))
169
  return float(np.clip(confidence, 0.0, 1.0))
 
180
  original_map[can] = s
181
  if not valid_smiles:
182
  return None, [], []
183
+
184
  tokenizer = smiles_model_instance.smiles_tokenizer
185
  encoded = [tokenizer(s, max_length=300, padding='max_length', truncation=True, return_tensors='pt') for s in valid_smiles]
186
  ids = torch.cat([item['input_ids'] for item in encoded], dim=0).to(DEVICE)
 
190
  return feats, valid_smiles, [original_map[s] for s in valid_smiles]
191
 
192
 
193
+ # ==================== 3. 检索主业务流程 ====================
194
  def run_unified_retrieval(ir_file, spectrum_type, library_name, custom_smiles_str, access_key, progress=gr.Progress()):
195
  if library_name == "PS Derivative Library":
196
  if access_key != ADMIN_KEY:
 
199
  if ir_file is None:
200
  raise gr.Error("Please upload an IR spectrum file.")
201
 
202
+ # 动态切换专属权重
203
  is_ps_lib = library_name in ["PS Existing Library", "PS Derivative Library"]
204
  current_ir_model = ir_model_ps if is_ps_lib else ir_model_general
205
  current_smiles_model = smiles_model_ps if is_ps_lib else smiles_model_general
 
211
  if file_path.lower().endswith('.csv'):
212
  try:
213
  df = pd.read_csv(file_path, header=None)
214
+ w_raw, i_raw = df.iloc[:, 0].values, df.iloc[:, 1].values
215
+ ir_data = preprocess_csv_spectra_higer_500(w_raw, i_raw)
 
 
 
216
  except Exception as e:
217
  raise gr.Error(f"Error processing CSV: {str(e)}")
218
 
219
  elif file_path.lower().endswith('.jdx'):
220
  try:
221
  data = jcamp.jcamp_readfile(file_path)
222
+ w_raw, i_raw = np.array(data['x']), np.array(data['y'])
223
+ ir_data = preprocess_jdx_spectra_higer_500(w_raw, i_raw)
 
 
 
224
  except Exception as e:
225
  raise gr.Error(f"Error processing JDX: {str(e)}")
226
 
227
  if ir_data is None:
228
+ raise gr.Error("Unsupported file format or invalid data points. Please check your spectrum file.")
229
 
230
+ # 光谱编码与检索
231
  ir_tensor = torch.tensor(ir_data, dtype=torch.float32).unsqueeze(0).to(DEVICE)
232
  with torch.no_grad():
233
  ir_feat = current_ir_model(ir_tensor).to(torch.float32)
234
 
 
235
  if library_name == "Custom Library":
236
  lib_features, lib_smiles, display_smiles = process_custom_smiles(custom_smiles_str, current_smiles_model)
237
  if lib_features is None:
 
240
  lib_features, lib_smiles = ALL_LIBS[library_name]
241
  display_smiles = lib_smiles
242
 
 
243
  sims = ir_feat @ lib_features.to(DEVICE).to(torch.float32).t()
244
  scores, indices = sims.topk(min(10, lib_features.shape[0]), dim=1)
245
  res_smiles = [display_smiles[idx.item()] for idx in indices[0]]
246
  res_scores = [sco.item() for sco in scores[0]]
247
 
248
+ # 置信度分析
249
  top1_score = res_scores[0]
250
  top1_calibrated_conf = calculate_calibrated_confidence(top1_score, current_smiles_model, model_type=model_type_flag)
251
 
252
+ # 规范置信度卡片展示
253
  conf_summary = (
254
  f'<div style="background-color: #ffffff; color: #1a1a1a; padding: 22px; border-radius: 12px; border: 1px solid #e0e0e0; text-align: left; box-shadow: 0 2px 6px rgba(0,0,0,0.03);">'
255
  f'<h3 style="color: #00bfa5; margin-top: 0; margin-bottom: 14px; text-align: center; font-size: 1.3em;">📊 置信度分析 (Confidence Analysis)</h3>'
 
261
  f'</div>'
262
  f'<p style="font-size: 0.88em; color: #64748b; line-height: 1.6; margin: 0; text-align: justify;">'
263
  f'<b>Note:</b> 注意,该置信度校准为 manuscript 中 “Multi-perspective Interpretability Analysis” 节中的 '
264
+ f'<b>Confidence calibration analysis</b> 获取的拟合参数,鉴于受到搜索库大小与搜索对象的差异,置信度可能发生偏差,因此,建议根据给出的前置候选物进行综合人工考量。'
265
  f'</p>'
266
  f'</div>'
267
  )
 
270
  grid_filename = "search_results_grid.png"
271
  grid_img_path = generate_mol_grid_custom(res_smiles, legends, grid_filename)
272
 
273
+ # 归一化绘图数据 (4000 -> 500)
274
+ wavenumbers_plot = np.linspace(4000, 500, 3500)
275
+ plot_df = pd.DataFrame({'wavenumber': wavenumbers_plot, 'intensity': ir_data})
276
+
277
  return (
278
  gr.update(visible=True),
279
  grid_img_path,
 
283
  "Cosine Similarity": [round(s, 4) for s in res_scores],
284
  "Calibrated Probability": [f"{calculate_calibrated_confidence(s, current_smiles_model, model_type=model_type_flag)*100:.2f}%" for s in res_scores]
285
  }),
286
+ gr.update(visible=True, value=plot_df),
287
  gr.update(value=conf_summary, visible=True)
288
  )
289
 
290
 
291
+ # ==================== 4. 界面前端构建 ====================
292
  CSS = """
293
  * { font-family: 'Times New Roman', Times, serif !important; }
294
  body { background-color: #ffffff !important; color: #333333 !important; }