bread-good111 commited on
Commit
ce8e22b
·
verified ·
1 Parent(s): 23c8921

Delete scripts/inference_longemotion.py

Browse files
Files changed (1) hide show
  1. scripts/inference_longemotion.py +0 -477
scripts/inference_longemotion.py DELETED
@@ -1,477 +0,0 @@
1
- """
2
- LongEmotion 测试集推理脚本
3
- 专门处理 LongEmotion 格式的长文本多段落情感检测
4
- """
5
- import os
6
- import sys
7
- import json
8
- import torch
9
- from pathlib import Path
10
- from typing import Dict, List, Any, Optional
11
- from tqdm import tqdm
12
- from collections import Counter
13
-
14
- # 添加项目根目录到路径
15
- sys.path.append(str(Path(__file__).parent.parent.parent))
16
-
17
- from transformers import BertTokenizer, BertModel
18
-
19
-
20
- class LongEmotionInference:
21
- """LongEmotion 推理器"""
22
-
23
- # 情感标签映射 (dair数据集的6类情感)
24
- EMOTION_LABELS = {
25
- 0: "sadness",
26
- 1: "joy",
27
- 2: "love",
28
- 3: "anger",
29
- 4: "fear",
30
- 5: "surprise"
31
- }
32
-
33
- def __init__(
34
- self,
35
- model_path: str,
36
- device: str = "cuda" if torch.cuda.is_available() else "cpu",
37
- max_length: int = 512,
38
- batch_size: int = 16
39
- ):
40
- """
41
- 初始化推理器
42
-
43
- Args:
44
- model_path: 模型权重文件路径 (best_model.pt)
45
- device: 设备
46
- max_length: 最大序列长度
47
- batch_size: 批次大小
48
- """
49
- # 检查设备可用性
50
- if device == "cuda" and not torch.cuda.is_available():
51
- print("警告: CUDA不可用,切换到CPU")
52
- device = "cpu"
53
-
54
- self.device = device
55
- self.max_length = max_length
56
- self.batch_size = batch_size
57
-
58
- print(f"正在加载模型从 {model_path}...")
59
- print(f"使用设备: {device}")
60
-
61
- # 加载分词器
62
- try:
63
- self.tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
64
- except Exception as e:
65
- print(f"加载分词器失败,尝试从本地加载: {e}")
66
- self.tokenizer = BertTokenizer.from_pretrained("bert-base-chinese", local_files_only=False)
67
-
68
- # 加载模型
69
- self.model = self._load_model(model_path)
70
- self.model.eval()
71
-
72
- print("模型加载完成!")
73
-
74
- def _load_model(self, model_path: str):
75
- """加载训练好的模型 - 使用与训练时相同的简单结构"""
76
- try:
77
- from transformers import AutoModel
78
- import torch.nn as nn
79
-
80
- # 定义简单的分类器(与simple_train.py中的结构完全一致)
81
- class SimpleEmotionClassifier(nn.Module):
82
- """简单情感分类器 - 单层Linear"""
83
- def __init__(self, model_name, num_labels=6):
84
- super().__init__()
85
- self.bert = AutoModel.from_pretrained(model_name)
86
- self.dropout = nn.Dropout(0.1)
87
- self.classifier = nn.Linear(self.bert.config.hidden_size, num_labels)
88
-
89
- def forward(self, input_ids, attention_mask):
90
- outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask)
91
- pooled_output = outputs.pooler_output
92
- pooled_output = self.dropout(pooled_output)
93
- logits = self.classifier(pooled_output)
94
- return logits
95
-
96
- # 创建模型
97
- print("创建模型结构(简单单层分类器)...")
98
- model = SimpleEmotionClassifier(
99
- model_name="bert-base-chinese",
100
- num_labels=6 # dair数据集的6类
101
- )
102
-
103
- # 加载权重
104
- print(f"加载模型权重...")
105
- checkpoint = torch.load(model_path, map_location=self.device)
106
-
107
- # 处理不同的保存格式
108
- if isinstance(checkpoint, dict):
109
- if 'model_state_dict' in checkpoint:
110
- model.load_state_dict(checkpoint['model_state_dict'])
111
- elif 'state_dict' in checkpoint:
112
- model.load_state_dict(checkpoint['state_dict'])
113
- else:
114
- model.load_state_dict(checkpoint)
115
- else:
116
- model.load_state_dict(checkpoint)
117
-
118
- # 移动到设备
119
- print(f"移动模型到设备: {self.device}")
120
- model = model.to(self.device)
121
-
122
- print("[OK] 模型加载成功!")
123
- return model
124
-
125
- except Exception as e:
126
- print(f"[ERROR] 加载模型时出错: {e}")
127
- print(f"错误类型: {type(e).__name__}")
128
- import traceback
129
- traceback.print_exc()
130
- raise
131
-
132
- def predict_segment(self, text: str) -> Dict[str, Any]:
133
- """
134
- 预测单个段落的情感
135
-
136
- Args:
137
- text: 段落文本
138
-
139
- Returns:
140
- 预测结果: {emotion_id, emotion_name, probabilities, confidence}
141
- """
142
- # 文本预处理
143
- encoding = self.tokenizer(
144
- text,
145
- padding='max_length',
146
- truncation=True,
147
- max_length=self.max_length,
148
- return_tensors='pt'
149
- )
150
-
151
- input_ids = encoding['input_ids'].to(self.device)
152
- attention_mask = encoding['attention_mask'].to(self.device)
153
-
154
- # 推理
155
- with torch.no_grad():
156
- outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
157
- logits = outputs['logits']
158
-
159
- # 对于分类任务,使用 softmax
160
- probabilities = torch.softmax(logits, dim=-1)
161
-
162
- # 获取最高概率的情感
163
- predicted_id = torch.argmax(probabilities, dim=-1).item()
164
- confidence = probabilities[0, predicted_id].item()
165
-
166
- return {
167
- 'emotion_id': predicted_id,
168
- 'emotion_name': self.EMOTION_LABELS[predicted_id],
169
- 'probabilities': {
170
- self.EMOTION_LABELS[i]: float(probabilities[0, i])
171
- for i in range(len(self.EMOTION_LABELS))
172
- },
173
- 'confidence': confidence
174
- }
175
-
176
- def predict_segments_batch(self, texts: List[str]) -> List[Dict[str, Any]]:
177
- """
178
- 批量预测多个段落的情感
179
-
180
- Args:
181
- texts: 段落文本列表
182
-
183
- Returns:
184
- 预测结果列表
185
- """
186
- results = []
187
-
188
- # 分批处理
189
- for i in range(0, len(texts), self.batch_size):
190
- batch_texts = texts[i:i + self.batch_size]
191
-
192
- # 文本预处理
193
- encoding = self.tokenizer(
194
- batch_texts,
195
- padding='max_length',
196
- truncation=True,
197
- max_length=self.max_length,
198
- return_tensors='pt'
199
- )
200
-
201
- input_ids = encoding['input_ids'].to(self.device)
202
- attention_mask = encoding['attention_mask'].to(self.device)
203
-
204
- # 推理
205
- with torch.no_grad():
206
- logits = self.model(input_ids=input_ids, attention_mask=attention_mask)
207
-
208
- # 对于分类任务,使用 softmax
209
- probabilities = torch.softmax(logits, dim=-1)
210
-
211
- # 获取最高概率的情感
212
- predicted_ids = torch.argmax(probabilities, dim=-1)
213
-
214
- # 处理批次结果
215
- for j in range(len(batch_texts)):
216
- predicted_id = predicted_ids[j].item()
217
- confidence = probabilities[j, predicted_id].item()
218
-
219
- result = {
220
- 'emotion_id': predicted_id,
221
- 'emotion_name': self.EMOTION_LABELS[predicted_id],
222
- 'probabilities': {
223
- self.EMOTION_LABELS[k]: float(probabilities[j, k])
224
- for k in range(len(self.EMOTION_LABELS))
225
- },
226
- 'confidence': confidence
227
- }
228
- results.append(result)
229
-
230
- return results
231
-
232
- def find_unique_emotion_segment(
233
- self,
234
- segment_predictions: List[Dict[str, Any]]
235
- ) -> Dict[str, Any]:
236
- """
237
- 找出表达独特情感的段落
238
-
239
- 在 n 个段落中,n-1 个段落表达相同情感,1 个段落表达独特情感
240
-
241
- Args:
242
- segment_predictions: 每个段落的预测结果
243
-
244
- Returns:
245
- 独特情感段落信息
246
- """
247
- # 统计每种情感出现的次数
248
- emotion_counts = Counter([pred['emotion_name'] for pred in segment_predictions])
249
-
250
- # 找出只出现1次的情感 (独特情感)
251
- unique_emotions = [emotion for emotion, count in emotion_counts.items() if count == 1]
252
-
253
- if len(unique_emotions) == 1:
254
- # 找到独特情感
255
- unique_emotion = unique_emotions[0]
256
-
257
- # 找到该情感对应的段落索引
258
- for idx, pred in enumerate(segment_predictions):
259
- if pred['emotion_name'] == unique_emotion:
260
- return {
261
- 'unique_segment_index': idx,
262
- 'unique_emotion': unique_emotion,
263
- 'confidence': pred['confidence'],
264
- 'emotion_distribution': dict(emotion_counts),
265
- 'total_segments': len(segment_predictions),
266
- 'status': 'success'
267
- }
268
-
269
- # 如果没有找到唯一的独特情感,使用启发式方法
270
- # 方法1: 找出现次数最少且置信度最高的情感
271
- min_count = min(emotion_counts.values())
272
- rare_emotions = [emotion for emotion, count in emotion_counts.items() if count == min_count]
273
-
274
- # 在这些稀有情感中,找置信度最高的
275
- best_idx = None
276
- best_confidence = 0
277
- best_emotion = None
278
-
279
- for idx, pred in enumerate(segment_predictions):
280
- if pred['emotion_name'] in rare_emotions and pred['confidence'] > best_confidence:
281
- best_idx = idx
282
- best_confidence = pred['confidence']
283
- best_emotion = pred['emotion_name']
284
-
285
- return {
286
- 'unique_segment_index': best_idx,
287
- 'unique_emotion': best_emotion,
288
- 'confidence': best_confidence,
289
- 'emotion_distribution': dict(emotion_counts),
290
- 'total_segments': len(segment_predictions),
291
- 'status': 'heuristic',
292
- 'note': f'No single unique emotion found. Used heuristic: rarest emotion ({min_count} occurrences) with highest confidence.'
293
- }
294
-
295
- def inference_longemotion_test(
296
- self,
297
- test_file: str,
298
- output_file: str,
299
- output_detailed: Optional[str] = None
300
- ):
301
- """
302
- 对 LongEmotion 格式的测试集进行推理
303
-
304
- Args:
305
- test_file: 测试集文件路径 (JSONL格式)
306
- output_file: 输出文件路径 (提交格式)
307
- output_detailed: 详细结果输出路径 (可选)
308
- """
309
- print(f"读取测试集: {test_file}")
310
-
311
- # 读取测试集
312
- test_samples = []
313
- with open(test_file, 'r', encoding='utf-8') as f:
314
- for line in f:
315
- if line.strip():
316
- test_samples.append(json.loads(line))
317
-
318
- print(f"测试样本数: {len(test_samples)}")
319
-
320
- # 推理结果
321
- results = []
322
- detailed_results = []
323
-
324
- # 处理每个样本
325
- for sample_idx, sample in enumerate(tqdm(test_samples, desc="推理进度")):
326
- # 提取段落
327
- segments = sample['text']
328
-
329
- # 提取每个段落的文本
330
- segment_texts = [seg['context'] for seg in segments]
331
-
332
- # 批量预测所有段落
333
- segment_predictions = self.predict_segments_batch(segment_texts)
334
-
335
- # 找出独特情感段落
336
- unique_result = self.find_unique_emotion_segment(segment_predictions)
337
-
338
- # 构建输出结果 (提交格式)
339
- result = {
340
- 'sample_id': sample_idx,
341
- 'unique_segment_index': unique_result['unique_segment_index'],
342
- 'unique_emotion': unique_result['unique_emotion'],
343
- 'confidence': unique_result['confidence']
344
- }
345
- results.append(result)
346
-
347
- # 构建详细结果
348
- if output_detailed:
349
- detailed_result = {
350
- 'sample_id': sample_idx,
351
- 'total_length': sample.get('length', 0),
352
- 'total_segments': len(segments),
353
- 'unique_segment_index': unique_result['unique_segment_index'],
354
- 'unique_emotion': unique_result['unique_emotion'],
355
- 'confidence': unique_result['confidence'],
356
- 'emotion_distribution': unique_result['emotion_distribution'],
357
- 'status': unique_result['status'],
358
- 'segment_predictions': [
359
- {
360
- 'index': seg['index'],
361
- 'text_preview': seg['context'][:100] + '...' if len(seg['context']) > 100 else seg['context'],
362
- 'predicted_emotion': pred['emotion_name'],
363
- 'confidence': pred['confidence']
364
- }
365
- for seg, pred in zip(segments, segment_predictions)
366
- ]
367
- }
368
-
369
- if 'note' in unique_result:
370
- detailed_result['note'] = unique_result['note']
371
-
372
- detailed_results.append(detailed_result)
373
-
374
- # 保存结果
375
- print(f"\n保存结果到: {output_file}")
376
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
377
-
378
- with open(output_file, 'w', encoding='utf-8') as f:
379
- for result in results:
380
- json.dump(result, f, ensure_ascii=False)
381
- f.write('\n')
382
-
383
- # 保存详细结果
384
- if output_detailed:
385
- print(f"保存详细结果到: {output_detailed}")
386
- with open(output_detailed, 'w', encoding='utf-8') as f:
387
- json.dump(detailed_results, f, ensure_ascii=False, indent=2)
388
-
389
- # 统计信息
390
- print("\n=== 推理统计 ===")
391
- print(f"总样本数: {len(results)}")
392
-
393
- emotion_counts = Counter([r['unique_emotion'] for r in results])
394
- print(f"\n独特情感分布:")
395
- for emotion, count in emotion_counts.most_common():
396
- print(f" {emotion}: {count} ({count/len(results)*100:.1f}%)")
397
-
398
- avg_confidence = sum(r['confidence'] for r in results) / len(results)
399
- print(f"\n���均置信度: {avg_confidence:.4f}")
400
-
401
- # 成功率统计
402
- if detailed_results:
403
- success_count = sum(1 for r in detailed_results if r['status'] == 'success')
404
- print(f"找到唯一独特情感的样本: {success_count}/{len(results)} ({success_count/len(results)*100:.1f}%)")
405
-
406
- print("\n推理完成!")
407
-
408
-
409
- def main():
410
- """主函数"""
411
- import argparse
412
-
413
- parser = argparse.ArgumentParser(description="LongEmotion 测试集推理")
414
- parser.add_argument(
415
- "--model_path",
416
- type=str,
417
- default="../model/best_model.pt",
418
- help="模型权重文件路径"
419
- )
420
- parser.add_argument(
421
- "--test_file",
422
- type=str,
423
- default="../test_data/test.jsonl",
424
- help="测试集文件路径"
425
- )
426
- parser.add_argument(
427
- "--output_file",
428
- type=str,
429
- default="../submission/predictions.jsonl",
430
- help="预测结果输出路径"
431
- )
432
- parser.add_argument(
433
- "--output_detailed",
434
- type=str,
435
- default="../submission/predictions_detailed.json",
436
- help="详细结果输出路径"
437
- )
438
- parser.add_argument(
439
- "--device",
440
- type=str,
441
- default="cuda" if torch.cuda.is_available() else "cpu",
442
- help="设备 (cuda/cpu)"
443
- )
444
- parser.add_argument(
445
- "--max_length",
446
- type=int,
447
- default=512,
448
- help="最大序列长度"
449
- )
450
- parser.add_argument(
451
- "--batch_size",
452
- type=int,
453
- default=16,
454
- help="批次大小"
455
- )
456
-
457
- args = parser.parse_args()
458
-
459
- # 创建推理器
460
- inference = LongEmotionInference(
461
- model_path=args.model_path,
462
- device=args.device,
463
- max_length=args.max_length,
464
- batch_size=args.batch_size
465
- )
466
-
467
- # 执行推理
468
- inference.inference_longemotion_test(
469
- test_file=args.test_file,
470
- output_file=args.output_file,
471
- output_detailed=args.output_detailed
472
- )
473
-
474
-
475
- if __name__ == "__main__":
476
- main()
477
-