为 EOT Studio 增加可展开的模型分数时间轴
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
"""Export causal, continuous MaAI scores for EOT Studio (no label gating)."""
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from smoke_run import HERE, ROOT, EXPECTED, WEIGHTS, CPC, np, sf, soxr, torch
|
||||
from evaluate_can_reply import model
|
||||
|
||||
|
||||
def main():
|
||||
torch.set_num_threads(4)
|
||||
torch.set_num_interop_threads(1)
|
||||
for p in (WEIGHTS, CPC):
|
||||
assert hashlib.sha256(p.read_bytes()).hexdigest() == EXPECTED[p.name]
|
||||
with (HERE/'results/work-data-can-reply/predictions.csv').open(encoding='utf-8-sig') as f:
|
||||
checkpoints = list(csv.DictReader(f))
|
||||
calls = []
|
||||
for call in sorted({r['call_id'] for r in checkpoints}):
|
||||
path = ROOT/'data/韩泰机器人抢话/音频'/f'{call}.mp3'
|
||||
pcm, sr = sf.read(path, dtype='float32', always_2d=True)
|
||||
assert pcm.shape[1] == 2
|
||||
maai = model()
|
||||
maai.CALC_PROCESS_TIME_INTERVAL = 10**9
|
||||
resampler = soxr.ResampleStream(sr,16000,2,dtype='float32',quality='HQ')
|
||||
pending = np.empty((0,2),dtype=np.float32)
|
||||
points = []
|
||||
# Streaming resampling never reads the next input block. Timestamp each
|
||||
# result when its audio is actually available, including filter delay.
|
||||
for start in range(0,len(pcm),sr//10):
|
||||
end = min(len(pcm),start+sr//10)
|
||||
block = resampler.resample_chunk(pcm[start:end],last=end==len(pcm))
|
||||
pending = np.concatenate([pending,block])
|
||||
score = None
|
||||
while len(pending)>=1600:
|
||||
frame,pending = pending[:1600],pending[1600:]
|
||||
maai.process(np.ascontiguousarray(frame[:,0]),np.ascontiguousarray(frame[:,1]))
|
||||
result = maai.result_dict_queue.get_nowait()
|
||||
score = 1-float(result['p_now'][0])
|
||||
assert np.isfinite(score) and 0<=score<=1
|
||||
if score is not None:
|
||||
points.append([round(end*1000/sr,3),round(score,7)])
|
||||
calls.append(dict(call_id=call,audio_path=f'韩泰机器人抢话/音频/{call}.mp3',
|
||||
audio_sha256=hashlib.sha256(path.read_bytes()).hexdigest(),
|
||||
duration_ms=round(len(pcm)*1000/sr,3),points=points,
|
||||
checkpoints=sorted([[int(r['decision_ms']),round(1-float(r['p_now_user']),7)]
|
||||
for r in checkpoints if r['call_id']==call])))
|
||||
del maai
|
||||
print(f'{call[:8]}: {len(points)} timeline scores',flush=True)
|
||||
output = dict(schema_version=1,model='maai',name='MaAI',sample_interval_ms=100,
|
||||
score='1 - p_now[user]',
|
||||
method='Continuous stereo replay, 16kHz, 10Hz, CPC + ch_kyoto noise-robust VAP, 20s KV cache. Causal streaming resampler; timestamps denote input availability. Checkpoints retain the earlier independent <=20s point evaluation.',
|
||||
model_sha256=EXPECTED,script_sha256=hashlib.sha256((HERE/'export_timeline.py').read_bytes()).hexdigest(),calls=calls)
|
||||
(HERE/'results/score-timeline.json').write_text(json.dumps(output,ensure_ascii=False,separators=(',',':'))+'\n')
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,65 @@
|
||||
"""Export real Smart Turn scores across complete recordings for EOT Studio."""
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
import numpy as np
|
||||
import soundfile as sf
|
||||
from loguru import logger
|
||||
from pipecat.audio.turn.smart_turn.local_smart_turn_v3 import LocalSmartTurnAnalyzerV3
|
||||
from evaluate_can_reply import HERE, ROOT, block_origin
|
||||
|
||||
|
||||
def read(p):
|
||||
with p.open(encoding='utf-8-sig') as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
|
||||
def main():
|
||||
logger.remove()
|
||||
checkpoints = read(HERE/'results/work-data-can-reply/predictions.csv')
|
||||
history = read(ROOT/'labels/EOT标注/工作数据/asr_history.csv')
|
||||
events = read(HERE/'results/work-data-pipecat/events.csv')
|
||||
weight = HERE/'smart-turn-v3.2-cpu.onnx'
|
||||
analyzer = LocalSmartTurnAnalyzerV3(smart_turn_model_path=str(weight),cpu_count=4)
|
||||
calls=[]
|
||||
for call in sorted({r['call_id'] for r in checkpoints}):
|
||||
path=ROOT/'data/韩泰机器人抢话/音频'/f'{call}.mp3'
|
||||
pcm,sr=sf.read(path,dtype='int16',always_2d=True)
|
||||
duration=round(len(pcm)*1000/sr,3)
|
||||
call_labels=[r for r in checkpoints if r['call_id']==call]
|
||||
exact={int(r['decision_ms']):float(r['probability']) for r in call_labels}
|
||||
times=sorted(set(range(200,int(duration)+1,200))|set(exact)|{int(duration)})
|
||||
call_history=[r for r in history if r['call_id']==call]
|
||||
starts=sorted(float(e['event_ms']) for e in events if e['call_id']==call and e['event']=='vad_start')
|
||||
analyzer.set_sample_rate(sr)
|
||||
points=[]
|
||||
for decision in times:
|
||||
origin=block_origin(call_history,decision)
|
||||
if origin is None:
|
||||
past=[t for t in starts if t<=decision]
|
||||
origin=max(0,past[-1]+32-200-500) if past else 0
|
||||
end=decision*sr//1000
|
||||
start=max(round(origin*sr/1000),end-sr*8)
|
||||
assert 0<=start<end<=len(pcm)
|
||||
# Endpoint resampling can only read the already cropped prefix.
|
||||
samples=np.ascontiguousarray(pcm[start:end,0].astype(np.float32)/32768.)
|
||||
score=float(analyzer._predict_endpoint(samples)['probability'])
|
||||
assert np.isfinite(score) and 0<=score<=1
|
||||
if decision in exact:
|
||||
assert abs(score-exact[decision])<1e-4,(call,decision,score,exact[decision])
|
||||
points.append([decision,round(score,7)])
|
||||
calls.append(dict(call_id=call,audio_path=f'韩泰机器人抢话/音频/{call}.mp3',
|
||||
audio_sha256=hashlib.sha256(path.read_bytes()).hexdigest(),duration_ms=duration,
|
||||
points=points,checkpoints=[[t,round(p,7)] for t,p in sorted(exact.items())]))
|
||||
print(f'{call[:8]}: {len(points)} timeline scores; checkpoints verified',flush=True)
|
||||
output=dict(schema_version=1,model='smart_turn',name='Smart Turn',sample_interval_ms=200,
|
||||
score='completion probability',
|
||||
method='Direct endpoint call every 200ms and at every original annotation. Same past-ASR block origin, 500ms pre-speech, <=3s continuation and last 8s user-channel audio as the point baseline; past VAD fallback when ASR missing. No EOT/timeout gates. Crop before resampling.',
|
||||
model_sha256=hashlib.sha256(weight.read_bytes()).hexdigest(),
|
||||
script_sha256=hashlib.sha256((HERE/'export_timeline.py').read_bytes()).hexdigest(),calls=calls)
|
||||
(HERE/'results/score-timeline.json').write_text(json.dumps(output,ensure_ascii=False,separators=(',',':'))+'\n')
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
main()
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user