| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- """Sample validation for the generated per-cycle pretraining samples.
- Cross-checks a sampled subset of the generated .npy files against the database:
- 1. 周期数核对: npy 文件数与 manifest 中的 cycle_count 一致。
- 2. 顺序核对: 每个 npy 内的信号列与 DB 中 [start, end) 区间的原始行完全一致,
- 且 sample_index 逐行递增(时间顺序未打乱)。
- 3. 边界核对: 周期起点/终点处的 second_value >= 30(键相触发),
- 第 3、4 列全为 1,信号列为有限值。
- 4. 命名核对: 文件名中的 sample_index 起止与 DB 实测起止一致。
- Usage: python3 validate_samples.py [--all] [--max-files N]
- """
- import argparse
- import csv
- import re
- import sys
- from pathlib import Path
- import numpy as np
- from tqdm import tqdm
- ROOT = Path(__file__).resolve().parents[1]
- sys.path.insert(0, str(ROOT))
- from app.algorithms.cycles import detect_cycles # noqa: E402
- from app.db import get_connection # noqa: E402
- HERE = Path(__file__).resolve().parent
- OUTPUT_DIR = HERE / "inputdatas"
- MANIFEST_PATH = HERE / "manifest.csv"
- NAME_RE = re.compile(r"^(\d+)_(\d+)_(\d+)_(\d+)\.npy$")
- def load_manifest() -> list[dict[str, str]]:
- with MANIFEST_PATH.open(encoding="utf-8-sig") as handle:
- return list(csv.DictReader(handle))
- def list_npy_files(folder: Path, file_id: int | None = None) -> list[Path]:
- pattern = f"{file_id}_*.npy" if file_id is not None else "*.npy"
- return sorted(folder.glob(pattern))
- def read_wave_samples(connection, file_id: int) -> np.ndarray:
- with connection.cursor() as cursor:
- cursor.execute(
- """
- SELECT sample_index, signal_value, second_value
- FROM wave_sample
- WHERE wave_file_id = %s
- ORDER BY wave_file_id, sample_index
- """,
- (file_id,),
- )
- rows = cursor.fetchall()
- return np.asarray(
- [
- (
- float(row["sample_index"]),
- float(row["signal_value"]),
- float(row["second_value"]) if row["second_value"] is not None else np.nan,
- )
- for row in rows
- ],
- dtype=float,
- )
- def validate_file(connection, manifest_row: dict[str, str]) -> list[str]:
- problems: list[str] = []
- file_id = int(manifest_row["wave_file_id"])
- folder = OUTPUT_DIR / manifest_row["group"]
- files = sorted(folder.glob(f"{file_id}_*.npy"))
- expected_count = int(manifest_row["cycle_count"] or 0)
- if len(files) != expected_count:
- problems.append(
- f"周期数不符: manifest={expected_count}, 实际 npy 文件={len(files)}"
- )
- return problems
- samples = read_wave_samples(connection, file_id)
- cycles, _ = detect_cycles(samples)
- if len(cycles) != expected_count:
- problems.append(f"重算周期数不符: detect_cycles={len(cycles)}, manifest={expected_count}")
- by_number: dict[int, Path] = {}
- parsed: list[tuple[int, int, int]] = []
- for path in files:
- match = NAME_RE.match(path.name)
- if not match:
- problems.append(f"文件名不符合规范: {path.name}")
- continue
- file_id_name, cycle_number, start_name, end_name = (int(part) for part in match.groups())
- if file_id_name != file_id:
- problems.append(f"文件名 id 不符: {path.name}")
- by_number[cycle_number] = path
- parsed.append((cycle_number, start_name, end_name))
- for cycle in cycles:
- start_sample_index, end_exclusive = (
- int(samples[cycle.start_offset, 0]),
- int(samples[cycle.end_offset, 0]) if cycle.end_offset < len(samples) else int(samples[-1, 0]) + 1,
- )
- path = by_number.get(cycle.number)
- if path is None:
- problems.append(f"周期 {cycle.number} 缺少 npy 文件")
- continue
- expected_name = f"{file_id}_{cycle.number}_{start_sample_index}_{end_exclusive}.npy"
- if path.name != expected_name:
- problems.append(
- f"周期 {cycle.number} 文件名不符: 应为 {expected_name}, 实际 {path.name}"
- )
- matrix = np.load(path)
- if matrix.ndim != 2 or matrix.shape[1] != 4:
- problems.append(f"周期 {cycle.number} 矩阵形状异常: {matrix.shape}")
- continue
- if not np.all(np.isfinite(matrix[:, :2])):
- problems.append(f"周期 {cycle.number} 信号列存在非有限值")
- signal = samples[cycle.start_offset : cycle.end_offset, 1]
- second = samples[cycle.start_offset : cycle.end_offset, 2]
- if matrix.shape[0] != len(signal):
- problems.append(
- f"周期 {cycle.number} 行数不符: npy={matrix.shape[0]}, DB={len(signal)}"
- )
- if not np.array_equal(matrix[:, 0], signal):
- problems.append(f"周期 {cycle.number} signal_value 与 DB 不一致")
- if not np.array_equal(matrix[:, 1], second):
- problems.append(f"周期 {cycle.number} second_value 与 DB 不一致")
- if not np.all(matrix[:, 2] == 1) or not np.all(matrix[:, 3] == 1):
- problems.append(f"周期 {cycle.number} 扩展列不为 1")
- indices = samples[cycle.start_offset : cycle.end_offset, 0]
- if len(indices) > 1 and np.any(np.diff(indices) <= 0):
- problems.append(f"周期 {cycle.number} sample_index 未严格递增")
- boundary_start = samples[cycle.start_offset, 2]
- boundary_end = samples[cycle.end_offset, 2]
- if boundary_start < 30 or boundary_end < 30:
- problems.append(
- f"周期 {cycle.number} 零标记边界未达触发阈值 30 (start={boundary_start:.3f}, end={boundary_end:.3f})"
- )
- return problems
- def main() -> int:
- parser = argparse.ArgumentParser(description="抽样验证预训练样本数据")
- parser.add_argument("--all", action="store_true", help="验证全部文件而非抽样")
- parser.add_argument("--max-files", type=int, default=20, help="抽样文件数上限(默认 20)")
- args = parser.parse_args()
- manifest = [row for row in load_manifest() if not row.get("error")]
- if not manifest:
- print("manifest 中没有成功记录,请先运行 generate_samples.py")
- return 1
- if args.all:
- sampled = manifest
- else:
- step = max(1, len(manifest) // args.max_files)
- sampled = manifest[::step][: args.max_files]
- if len(sampled) < len(manifest):
- sampled.append(manifest[-1])
- print(f"抽样验证 {len(sampled)}/{len(manifest)} 个文件: " + ", ".join(item["wave_file_id"] for item in sampled))
- problems_by_id: dict[str, list[str]] = {}
- connection = get_connection()
- try:
- for row in tqdm(sampled, desc="验证样本", unit="文件"):
- problems = validate_file(connection, row)
- if problems:
- problems_by_id[row["wave_file_id"]] = problems
- print(f" id={row['wave_file_id']} ({row['group']}): {len(problems)} 个问题")
- for problem in problems[:8]:
- print(f" - {problem}")
- finally:
- connection.close()
- checked = sum(1 for item in sampled if item["wave_file_id"] not in problems_by_id)
- print(
- f"\n结果: {checked}/{len(sampled)} 个文件完全通过"
- if not problems_by_id
- else f"\n结果: {checked}/{len(sampled)} 通过, {len(problems_by_id)} 个文件存在问题"
- )
- return 1 if problems_by_id else 0
- if __name__ == "__main__":
- sys.exit(main())
|