faeea commited on
Commit
f8a1702
·
verified ·
1 Parent(s): 9038e58

Release frozen StreamPA seed 1337 with validated standalone inference

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. LICENSE +29 -0
  2. README.md +172 -120
  3. README_zh.md +109 -0
  4. SHA256SUMS +41 -0
  5. THIRD_PARTY_NOTICES.md +43 -0
  6. assets/phn_dict.json +41 -0
  7. assets/prepared_resource_hashes.json +102 -0
  8. assets/pronunciation_lexicon.json +0 -0
  9. assets/runtime_sources.json +74 -0
  10. bundle_manifest.json +0 -107
  11. charsiu_en_w2v2_tiny_fc_10ms/config.json +0 -75
  12. charsiu_en_w2v2_tiny_fc_10ms/preprocessor_config.json +0 -1
  13. charsiu_en_w2v2_tiny_fc_10ms/pytorch_model.bin +0 -3
  14. examples/eval_streaming_gopt_test.py +0 -119
  15. examples/infer_one_audio.py +0 -395
  16. examples/sa1_infer_output.json +0 -30
  17. examples/session.py +21 -0
  18. licenses/Apache-2.0.txt +203 -0
  19. licenses/Charsiu-MIT.txt +21 -0
  20. licenses/G2P-Apache-2.0.txt +201 -0
  21. licenses/Whisper-MIT.txt +21 -0
  22. model_manifest.json +46 -0
  23. pyproject.toml +13 -0
  24. requirements-lock.txt +58 -0
  25. scorer/config.json +180 -0
  26. scorer/inference_assets.json +50 -0
  27. streaming_gopt_best/best_audio_model.pth → scorer/model.safetensors +2 -2
  28. scripts/setup_assets.py +74 -0
  29. scripts/verify_bundle.py +21 -0
  30. streaming_gopt_best/config.json +0 -42
  31. streaming_gopt_best/inference_assets.json +0 -48
  32. streaming_gopt_best/result.csv +0 -101
  33. streaming_gopt_best/test_metrics.json +0 -37
  34. streampa/__init__.py +4 -0
  35. streampa/charsiu_model.py +55 -0
  36. streampa/frontend.py +1556 -0
  37. streampa/infer.py +19 -0
  38. streampa/pipeline.py +318 -0
  39. streampa/stream.py +28 -0
  40. streampa/streaming_gopt.py +238 -0
  41. streampa/streaming_pcn_gopt.py +422 -0
  42. tests/test_runtime.py +88 -0
  43. validation_summary.json +139 -0
  44. {whisper_best_model → whisper}/added_tokens.json +0 -0
  45. {whisper_best_model → whisper}/config.json +0 -1
  46. {whisper_best_model → whisper}/generation_config.json +0 -0
  47. {whisper_best_model → whisper}/merges.txt +0 -0
  48. {whisper_best_model → whisper}/model.safetensors +0 -0
  49. {whisper_best_model → whisper}/normalizer.json +0 -0
  50. {whisper_best_model → whisper}/preprocessor_config.json +0 -0
LICENSE ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2022, Yuan Gong
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
README.md CHANGED
@@ -1,144 +1,196 @@
1
  ---
2
  tags:
3
  - speech
4
- - whisper
5
- - forced-alignment
6
  - pronunciation-assessment
7
- - gopt
 
 
 
 
8
  license: other
9
  ---
10
 
11
- # custom-gopt-252-eval
12
-
13
- This repository bundles the models needed for the local evaluation pipeline:
14
-
15
- `Whisper ASR -> Charsiu phone alignment -> Streaming GOPT pronunciation scoring`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- The current Streaming GOPT checkpoint is the v6 ASR-confidence version. It uses 47-dimensional phone-segment features: the previous Charsiu-derived acoustic features plus one ASR confidence feature.
18
-
19
- ## Files
20
-
21
- - `streaming_gopt_best/best_audio_model.pth`: best validation Streaming GOPT checkpoint.
22
- - `streaming_gopt_best/config.json`: model architecture and training arguments.
23
- - `streaming_gopt_best/inference_assets.json`: normalization statistics and phone-id mapping used by the inference example.
24
- - `streaming_gopt_best/result.csv`: per-epoch training and validation metrics.
25
- - `streaming_gopt_best/test_metrics.json`: held-out test metrics for the selected checkpoint.
26
- - `whisper_best_model/`: Whisper ASR model used by the pipeline.
27
- - `charsiu_en_w2v2_tiny_fc_10ms/`: Charsiu frame-level phone alignment model.
28
- - `examples/infer_one_audio.py`: one-audio inference example.
29
-
30
- ## Download
31
 
32
- ```python
33
- from huggingface_hub import snapshot_download
 
 
 
 
 
34
 
35
- snapshot_download(
36
- repo_id="faeea/custom-gopt-252-eval",
37
- repo_type="model",
38
- local_dir="./hf_models/custom-gopt-252-eval",
39
- )
40
- ```
41
 
42
- Then set:
 
 
 
43
 
44
  ```bash
45
- export BUNDLE_DIR=$PWD/hf_models/custom-gopt-252-eval
 
46
  ```
47
 
48
- ## Code Dependencies
 
 
 
49
 
50
- This model bundle is not a standalone Transformers model. The inference script needs the model definitions from `custom-gopt` and the official Charsiu code.
 
 
 
 
51
 
52
- ```bash
53
- git clone https://github.com/hf49w/custom-gopt.git
54
- git clone https://github.com/lingjzhu/charsiu third_party/charsiu_repo
55
- git -C third_party/charsiu_repo checkout 13a69f2a22ca0c0962b75cc693399b0ae23a12c9
56
- ```
57
-
58
- Install the project dependencies in the `custom-gopt` repository, then install the small extra NLTK assets used by Charsiu/G2P:
59
 
60
- ```bash
61
- pip install -r requirements.txt
62
- python -m pip install nltk
63
- python -m nltk.downloader cmudict averaged_perceptron_tagger averaged_perceptron_tagger_eng
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  ```
65
 
66
- ## One-Audio Inference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  ```bash
69
- python "$BUNDLE_DIR/examples/infer_one_audio.py" \
70
- --audio /path/to/demo.wav \
71
- --bundle-dir "$BUNDLE_DIR" \
72
- --repo-root /path/to/custom-gopt \
73
- --charsiu-src-dir /path/to/third_party/charsiu_repo \
74
- --device cuda \
75
- --output-json ./one_audio_score.json
76
- ```
77
-
78
- Use `--device cpu` when CUDA is unavailable.
79
-
80
- The example accepts an English short utterance audio file. A mono 16 kHz WAV is preferred; other sample rates are resampled inside the script.
81
-
82
- ## Output Meaning
83
-
84
- The Streaming GOPT model forward pass returns:
85
-
86
- - `u1`: utterance-level accuracy.
87
- - `u2`: utterance-level completeness.
88
- - `u3`: utterance-level fluency.
89
- - `u4`: utterance-level prosodic score.
90
- - `u5`: utterance-level total score.
91
- - `p`: phone-level pronunciation score for each visible phone token.
92
- - `w1`: word-level accuracy.
93
- - `w2`: word-level stress.
94
- - `w3`: word-level total score.
95
- - `w4`: word-level ASR accuracy.
96
-
97
- The example script reports the user-facing utterance scores as:
98
-
99
- ```json
100
- {
101
- "utterance_scores": {
102
- "accuracy": 8.4,
103
- "completeness": 10.0,
104
- "fluency": 8.3,
105
- "prosodic": 7.9,
106
- "total": 8.0
107
- },
108
- "overall_score": 8.0
109
- }
110
  ```
111
 
112
- For `accuracy`, `completeness`, `fluency`, `prosodic`, and `total`, higher is better. These scores follow the SpeechOcean-style pronunciation scoring scale used during training.
113
-
114
- The word-level outputs have this meaning:
115
-
116
- - `word_accuracy`: pronunciation accuracy for the word.
117
- - `word_stress`: stress score for the word.
118
- - `word_total`: overall word score.
119
- - `word_asr_accuracy`: whether the ASR-driven word matched the expected word.
120
-
121
- `word_asr_accuracy` is a 0/1-style score. In practice, it can be used as a word-read-correctly indicator: a value near `1` means the word was recognized/matched as read correctly, while a value near `0` means the word was not matched, not recognized correctly, or was not committed yet in the streaming prefix.
122
-
123
- 中文提示:词级别的 `asr_accuracy` 评分可以当作“这个词是否读对”的辅助指标。它不替代发音分数本身,但很适合用来标记某个词在 ASR 视角下是否正确读出。
124
-
125
- ## Test Metrics
126
-
127
- Best checkpoint epoch: `11`
128
-
129
- Held-out test metrics:
130
-
131
- - `phone_test_mse`: `0.049197`
132
- - `phone_test_pcc`: `0.397571`
133
- - `utt_test_pcc`: `[0.651909, 0.012690, 0.724998, 0.733279, 0.681940]`
134
- - `word_test_pcc`: `[0.404714, -0.003912, 0.412258, 0.417467]`
135
-
136
- The word PCC order is:
137
-
138
- `accuracy`, `stress`, `total`, `asr_accuracy`
139
-
140
- ## Notes
141
-
142
- - The model was trained on SpeechOcean762-style English learner speech.
143
- - The pipeline does not require a reference transcript at inference time; it first obtains a transcript from Whisper, aligns phones with Charsiu, then scores with Streaming GOPT.
144
- - ASR errors can affect downstream word and phone alignment. Use `word_asr_accuracy` to identify words that the ASR-driven pipeline likely did or did not match correctly.
 
 
 
 
1
  ---
2
  tags:
3
  - speech
 
 
4
  - pronunciation-assessment
5
+ - streaming
6
+ - whisper
7
+ - charsiu
8
+ language:
9
+ - en
10
  license: other
11
  ---
12
 
13
+ # StreamPA
14
+
15
+ Frozen StreamPA pronunciation-assessment inference: fine-tuned Whisper Base
16
+ N-best -> G2P / prefix-local Charsiu / PCN / prosody -> slot and utterance scores.
17
+ This is a downloadable, multi-component Python system, not a hosted endpoint
18
+ or a standard Transformers `AutoModel`/`pipeline` implementation.
19
+
20
+ ## Published identity
21
+
22
+ * Experiment: `M_stress_scalar_gate_capacity64` (Ours-M), primary seed **1337**.
23
+ * Original checkpoint SHA256:
24
+ `870df8dbe3c787487f094e0c4d4ade6bed8fcc5d86b578c4edc57581e81023b4`.
25
+ * All original tensors are retained losslessly in `scorer/model.safetensors`.
26
+ Strict loading retains the historical inactive `utt_head`; the active
27
+ utterance path is the frozen `gru_visible` head.
28
+ * 64 hidden dimensions, three local-attention blocks, four heads, scalar
29
+ reliability gate, detached stress branch, 64-dimensional recurrent state.
30
+ Inputs: 40 phone columns including epsilon, 14 global prosody features,
31
+ 17 slot prosody features, and at most **72 PCN slots**.
32
+ * The included Whisper weights are the actual fine-tuned Base checkpoint,
33
+ SHA256 `8917c9612b44f73bb5ef2ffec48cf44a4ee0dfa5a94af25e9e6710b9fd66a17f`.
34
+ They must not be replaced with generic Whisper Base. Beam size 8, N-best 5,
35
+ maximum 128 decoded tokens; original hypothesis-score computation retained.
36
+ * The paper's four-seed aggregate (1337, 202601, 202602, 202603) is not the
37
+ performance of this single checkpoint. No unverified paper metric is copied
38
+ into this card. Interface tests do not establish assessment PCC.
39
+
40
+ ## Download and install
41
+
42
+ Verified platform: **Python 3.10 on Ubuntu in local Windows WSL2, CPU FP32**.
43
+ Native Windows and GPU inference are not part of the verified platform.
44
+ Use a new Python 3.10 environment. The fixed release tag below is created only
45
+ after fresh Hub-download inference verification succeeds.
46
 
47
+ ```bash
48
+ python3.10 -m venv .venv-streampa
49
+ source .venv-streampa/bin/activate
50
+ python -m pip install huggingface_hub==0.36.0
51
+ python -c "from huggingface_hub import snapshot_download; snapshot_download('faeea/StreamPA', revision='streampa-v1.0.0', local_dir='./StreamPA')"
52
+ cd StreamPA
53
+ python -m pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements-lock.txt
54
+ python -m pip install --no-deps --no-build-isolation .
55
+ python scripts/setup_assets.py
56
+ python scripts/verify_bundle.py
57
+ ```
 
 
 
58
 
59
+ The public bundle contains the scorer and fine-tuned Whisper weights. Setup
60
+ downloads Charsiu and G2P resources from official, **fixed revisions**, checks
61
+ their SHA256s, and installs them inside this bundle. There is no runtime
62
+ reference to a training checkout or private server. See `THIRD_PARTY_NOTICES.md`
63
+ for licenses and third-party weight terms. Internet is needed for initial
64
+ installation/setup, not for subsequent inference. No author token is required
65
+ for the public release or its official runtime resources.
66
 
67
+ ## One WAV and streaming CLI
 
 
 
 
 
68
 
69
+ Input: your own English **mono 16,000-Hz WAV**, no transcript or labels.
70
+ The frontend supports at most 30 seconds per recording and 72 current PCN
71
+ slots; longer inputs return an explicit error/status, never hidden truncation.
72
+ Output files must not already exist.
73
 
74
  ```bash
75
+ python -m streampa.infer --audio /path/to/demo.wav --bundle-dir . --device cpu --output scores.json
76
+ python -m streampa.stream --audio /path/to/demo.wav --bundle-dir . --device cpu --output prefixes.jsonl
77
  ```
78
 
79
+ The single-file CLI returns all prefixes plus the EOF result. The streaming
80
+ CLI actually reads and appends waveform blocks, then signals EOF. Both run
81
+ the same causal implementation. CLI block size does not change the fixed
82
+ 0.64-second schedule and 0.16-second lookahead.
83
 
84
+ For an appropriate CUDA installation, select one device externally and use
85
+ `--device cuda`. Do not use a broken/busy device. The inherited frontend uses
86
+ FP16 Whisper on CUDA, FP32 Charsiu/scorer; CPU is FP32 throughout. CUDA is
87
+ **untested in this release validation**, and changing precision can change ASR
88
+ decoding. The CPU lockfile deliberately selects CPU PyTorch.
89
 
90
+ ## Python sessions
 
 
 
 
 
 
91
 
92
+ ```python
93
+ import soundfile as sf
94
+ from streampa import StreamPAPipeline
95
+
96
+ pipeline = StreamPAPipeline('./StreamPA', device='cpu', threads=2)
97
+ session = pipeline.create_session()
98
+ with sf.SoundFile('demo.wav') as f:
99
+ assert f.samplerate == 16000 and f.channels == 1
100
+ while True:
101
+ block = f.read(1600, dtype='float32') # 100 ms received audio
102
+ if len(block) == 0:
103
+ break
104
+ for result in session.append(block):
105
+ print(result['audio_end'], result['status'], result['scores'])
106
+ print(session.finish()) # process the exact EOF, including a short final block
107
+ session.reset() # required before a new recording in this session
108
+ assert session.current() is None
109
  ```
110
 
111
+ The pipeline owns frozen models; sessions have separate audio and recurrent
112
+ state. Use sessions sequentially (concurrent GPU service scheduling is not
113
+ provided). `StreamPAPipeline.from_pretrained(repo_id, revision=...)` is also
114
+ implemented, but its downloaded directory must have `setup_assets.py` run
115
+ before loading; use the explicit local-bundle example for first setup.
116
+
117
+ ## JSON and state semantics
118
+
119
+ * `commit_time`: scheduling boundary; `audio_end`: exact prefix waveform end
120
+ supplied to **every** frontend; `received_audio_end`: audio received by the
121
+ caller so far, which may be later when a block spans multiple boundaries.
122
+ A normal prefix is processed only after `commit_time + 0.16` is received.
123
+ EOF alone defines the final endpoint. No reference boundary is consulted.
124
+ * `slots`: raw independent phone-head and word-head predictions per current
125
+ PCN slot, ASR word assignment, slot time, validity and commitment masks.
126
+ Epsilon/gap slots are retained; `valid_phone=false` means their phone score
127
+ is not a localized phone assessment. `asr_word_id=-1` has no word assignment.
128
+ * `words`: mean **word-head** predictions over visible slots assigned to each
129
+ current ASR word. This is a presentation aggregation, not mean phone accuracy.
130
+ It is not the paper's offline reference-word-identity evaluation. ASR words
131
+ and scores may be revised at later prefixes, including committed words.
132
+ * `new_commit_mask`: the frozen legacy boundary-aware state-update rule.
133
+ Only newly committed word representations are consumed by the GRU. With
134
+ no new words the persistent state remains unchanged, but visible features
135
+ still enter the sentence head and may change current utterance scores.
136
+ This heuristic has **no formal exactly-once guarantee** for every possible
137
+ mixed predecessor merge. It is not silently replaced with a corrected rule.
138
+ * `scores.utterance_network_raw` retains native model outputs, including the
139
+ untrained completeness dimension for audit only. Displayed utterance scores
140
+ omit completeness. ASR confidence is not a probability of correct pronunciation.
141
+ * Historical target normalization divides all source scores by 5: native
142
+ word/utterance source scores span 0--10; phone source scores span 0--2.
143
+ Thus displayed 0--5 word/utterance = network output x 2.5, and phone = network
144
+ output x 12.5. Outputs are **not clipped**; out-of-range estimates remain
145
+ visible. Stress has a trained head but is not a calibrated correctness probability.
146
+ * `status != 'ok'` returns `scores=null`, never a false zero grade. Statuses
147
+ include empty/too-short/silent audio, no usable hypothesis, G2P/alignment/
148
+ acoustic failures, nonfinite values and unsupported slot length. Failed
149
+ prefixes remain in the output; previously successful state is preserved.
150
+ * `stages` and `wall_time_sec` measure actual local computation, not capture
151
+ waiting or a universal real-time guarantee. Model loading is separate.
152
+
153
+ ## Offline operation and checks
154
 
155
  ```bash
156
+ python scripts/setup_assets.py --offline
157
+ HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 python -m streampa.infer --audio /path/to/demo.wav --bundle-dir . --device cpu --output offline_scores.json
158
+ python -m pytest tests -q
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  ```
160
 
161
+ Keep the entire prepared bundle, including `charsiu/` and `assets/nltk_data/`.
162
+ Do not delete those setup-generated directories when preparing an offline copy.
163
+ For hash mismatch, stop and re-download the specified revision rather than
164
+ replacing a resource with another model. Missing G2P/tagger resources require
165
+ `setup_assets.py`; inference does not intentionally download them. Proxy
166
+ configuration may be needed during setup in restricted networks. Use a fresh
167
+ output filename for each command. Convert sample rate/channels before opening
168
+ a session; the inference entry point does not resample a hidden full recording.
169
+
170
+ ## Scope and limitations
171
+
172
+ Inference accepts audio only. Training used human annotations and teacher
173
+ supervision. Historical evaluation data were selected by preprocessing filters
174
+ and predominantly contain read speech. Deployment removes the historical
175
+ reference-phone-count ratio filter and uses received-audio/EOF scheduling;
176
+ it does not claim to reproduce historical test membership or PCC on arbitrary
177
+ WAVs. The frozen dictionary is a corpus-derived pronunciation vocabulary, not
178
+ an utterance-specific reference transcript. Original PCN gap semantics,
179
+ alignment, normalization, repetition filtering and legacy commitment are retained.
180
+
181
+ No claim of validated spontaneous/open-ended speech generalization, formal
182
+ exactly-once commitment, GRU-induced stability improvement, or end-to-end
183
+ real-time performance is made. Synthetic validation clips test computation
184
+ and interfaces, not pronunciation-assessment accuracy. See
185
+ `validation_summary.json` for actually completed and untested checks.
186
+
187
+ Observed interface-test limitation: several short prefixes produce repeated
188
+ Whisper hypotheses and are explicitly unscorable. A synthetic "Good morning"
189
+ clip was unscorable at all three prefixes; three separately selected licensed
190
+ read-speech clips all produced valid EOF scores, with 9 valid prefixes out of
191
+ 15 attempted. These failures were not hidden or repaired by changing decoding.
192
+ CPU frontend computation was much slower than the audio duration in these tests.
193
+
194
+ The old v6 47-feature system remains historical only at
195
+ `faeea/StreamPA`, revision `legacy-v6-before-streampa`. It is not this checkpoint
196
+ and must not be mixed with these configurations or entry points.
README_zh.md ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # StreamPA 使用说明
2
+
3
+ 这是冻结的 Ours-M / `M_stress_scalar_gate_capacity64`,默认 seed 1337。
4
+ 它不是旧版 v6 的 47 维 Streaming GOPT。发布的是可下载运行的多组件系统,
5
+ 不是 HF 网页在线推理服务,也不提供虚构的 Transformers `AutoModel` 接口。
6
+
7
+ 原 checkpoint SHA256:
8
+ `870df8dbe3c787487f094e0c4d4ade6bed8fcc5d86b578c4edc57581e81023b4`。
9
+ `model_manifest.json` 记录发布 tensor、配置、源文件和各组件身份。
10
+ scorer 的全部原 tensor 保留为 safetensors,没有训练、微调、量化或修改值。
11
+
12
+ ## 安装
13
+
14
+ 实际验证环境是本机 Windows 的 Ubuntu WSL2,Python 3.10,CPU FP32。
15
+ 没有将服务器 Linux 测试写成 Windows 原生测试。Windows 原生和 CUDA 尚未验证。
16
+ 首次需要联网安装依赖和下载官方固定版本的 Charsiu、词典与 NLTK 资源。
17
+
18
+ ```bash
19
+ python3.10 -m venv .venv-streampa
20
+ source .venv-streampa/bin/activate
21
+ python -m pip install huggingface_hub==0.36.0
22
+ python -c "from huggingface_hub import snapshot_download; snapshot_download('faeea/StreamPA', revision='streampa-v1.0.0', local_dir='./StreamPA')"
23
+ cd StreamPA
24
+ python -m pip install --extra-index-url https://download.pytorch.org/whl/cpu -r requirements-lock.txt
25
+ python -m pip install --no-deps --no-build-isolation .
26
+ python scripts/setup_assets.py
27
+ python scripts/verify_bundle.py
28
+ ```
29
+
30
+ Charsiu 权重未在本仓库重新分发,由 setup 从官方固定 revision 下载并验 hash。
31
+ 微调 Whisper Base 则已在包内,不能替换为通用预训练 Whisper。各组件并非同一
32
+ 许可证,详见 `THIRD_PARTY_NOTICES.md`。初始安装若需要网络代理,由用户环境设置,
33
+ 包内不包含作者 token、代理密码或服务器路径。
34
+
35
+ ## 最短完整推理
36
+
37
+ 输入自己的英文、单声道、16 kHz WAV,不输入参考文本、音素数、GT 边界、分数或 teacher。
38
+ 输出文件必须不存在,以免覆盖旧结果。
39
+
40
+ ```bash
41
+ python -m streampa.infer --audio /path/to/demo.wav --bundle-dir . --device cpu --output scores.json
42
+ python -m streampa.stream --audio /path/to/demo.wav --bundle-dir . --device cpu --output prefixes.jsonl
43
+ ```
44
+
45
+ 单音频命令返回全部 prefix 和 EOF 分数;流式命令逐块读取音频,经过相同的调度和计算。
46
+ 最大原始音频长度为 30 秒,最大当前 PCN 槽位数为 72。超限明确拒绝,不静默截断。
47
+ 实际运行路径:收到的 prefix 音频 -> 微调 Whisper N-best -> G2P、Charsiu、PCN、prosody
48
+ -> StreamPA。没有用预计算预测代替模型运行。
49
+
50
+ ## 会话 API
51
+
52
+ ```python
53
+ from streampa import StreamPAPipeline
54
+ pipeline = StreamPAPipeline('./StreamPA', device='cpu', threads=2)
55
+ session = pipeline.create_session()
56
+ # block 是实际已收到的、16 kHz 单声道 float32 波形数组
57
+ for result in session.append(block):
58
+ print(result)
59
+ print(session.current())
60
+ print(session.finish()) # 调用方 EOF,处理最后不足一个 chunk 的音频
61
+ session.reset() # 新录音前必须重置,清空音频、提交历史和 GRU state
62
+ ```
63
+
64
+ 完整可执行逐块示例:`python examples/session.py --bundle-dir . --audio demo.wav`。
65
+ 不同 session 的 state 独立;当前包不提供多线程服务调度,建议顺序调用。
66
+
67
+ ## 如何读输出
68
+
69
+ - `commit_time` 每 0.64 秒推进,普通 prefix 等收到额外 0.16 秒 lookahead 才处理。
70
+ `audio_end` 是每个前端实际得到的波形末端;`received_audio_end` 可能因整块到达而更晚,
71
+ 但前端不能读取更晚部分。EOF 由调用方决定,不从参考标注推导。
72
+ - `slots` 保留原始独立 phone head 与 word head 输出、当前 ASR 词归属、时间、mask。
73
+ epsilon/gap 槽位保留;`valid_phone=false` 时不能作为有效局部音素评分。
74
+ - `words` 按当前 ASR word ID,平均该词可见槽位的 **word head** 输出用于显示。
75
+ 不是平均 phone accuracy,不等同论文按 reference word ID 的共同单元评价。
76
+ - GRU 只消费 `new_commit_mask` 对应的新词。没有任何新词时 persistent state 不变,
77
+ 但 visible 表示仍进入句级 head,所以句级分数仍可能变化。已提交词不代表其评分永远不变。
78
+ - 保留冻结的 legacy commitment,包括复杂 mixed merge 下的已知限制。
79
+ 不声称“每一个完成词均有形式化 exactly-once 保证”,没有偷偷换成 corrected 规则。
80
+ - 原训练把源标签除以 5;词和句源尺度为 0--10、音素源尺度为 0--2。
81
+ 显示成 0--5 时,词/句网络输出乘 2.5,音素网络输出乘 12.5。不裁剪超界估计。
82
+ 原始网络输出同时保留。completeness 未参与训练,只在 raw 审计字段保留,不显示为可靠评分。
83
+ - 非 `ok` 状态给 `scores=null`,包括空音频、静音、无可用假设、对齐失败、超限等。
84
+ 不用零分代替失败,不静默删掉失败 prefix。ASR confidence 也不等于“读对的概率”。
85
+
86
+ ## 离线和性能
87
+
88
+ ```bash
89
+ python scripts/setup_assets.py --offline
90
+ HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 python -m streampa.infer --audio demo.wav --bundle-dir . --device cpu --output offline_scores.json
91
+ python -m pytest tests -q
92
+ ```
93
+
94
+ 离线复制时保留整个已准备好的 bundle,包括 setup 产生的 `charsiu/` 与
95
+ `assets/nltk_data/`。依赖缺失先补 setup,不要用别的权重替换。
96
+ `stages` 分开记录前端与 scorer 的计算时间;不把文件读取测试声称为包含真实采集等待的
97
+ 端到端实时实验。CUDA 分支保留原 Whisper FP16 行为,Charsiu/scorer FP32,但尚未验证。
98
+
99
+ ## 论文与部署边界
100
+
101
+ 发布 seed 1337 不等于论文四 seed 均值。接口测试音频不证明论文 PCC。
102
+ 训练使用人工监督和 teacher;历史评测样本经过预处理筛选,主要来自朗读数据。
103
+ 部署移除了历史参考音素长度比例筛选,并使用 audio/EOF 调度;这一差异已明确记录,
104
+ 不声称任意 WAV 会复现历史样本集合和指标。词典只是通用词到音素的静态资源,
105
+ 不按当前录音读取其标准文本。未验证自发开放回答泛化,不宣称 GRU/稳定性 loss 的
106
+ 直接稳定性机制已获得证明。
107
+
108
+ 旧 v6 历史版本保留在 `faeea/StreamPA` 的 `legacy-v6-before-streampa` revision;
109
+ 其权重、结构和调用方法不能与本包混用。实际已通过和未测试项目见 `validation_summary.json`。
SHA256SUMS ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ d501f3b58c954ae35e9dac25422a1a4e1dc8a24be3de27c8feedfe383726e52c LICENSE
2
+ 52d8113ecf4424695f4a967fcd8174693642659c3110cb044ab8458bc5d41ddc README.md
3
+ 939a3e3859cc8edf2e4472b73b1068771cbfb8682e75631cdc0c0de95bb3ec4b README_zh.md
4
+ b10037f3f89563db6e3cea1d0872a7d8c6bf648798d13e643682bc95d40ac585 THIRD_PARTY_NOTICES.md
5
+ 34d6a3fd820daf67d0f46d71c71bab83e2f4fe0a714e580098ce1b8981d19932 assets/phn_dict.json
6
+ 3eae62ec67d23c40bd672a3e933ff68f20a5466286956c46cf5933d302a465a6 assets/prepared_resource_hashes.json
7
+ 5d6f12bce4a61905d8fe2b78474bb9a6741f6165f40f390f6041d975a5dd06a2 assets/pronunciation_lexicon.json
8
+ 5162cca670853595e2883504e55e74465f0921f643a43246b16b4ba3818389c7 assets/runtime_sources.json
9
+ 3b74d2f27f7723852677f5acaa5a5fd1271129fc426e6f3cf29a0be41fdf9cb3 examples/session.py
10
+ 77fd4710def9ec3c0f6225800e0235f15a425abd4a8b03559127fcd782612049 licenses/Apache-2.0.txt
11
+ a622a386b2acb06e150e16c26fb93ff6c09b3096eb1d84aa5b6436ac6c52f6bc licenses/Charsiu-MIT.txt
12
+ b40930bbcf80744c86c46a12bc9da056641d722716c378f5659b9e555ef833e1 licenses/G2P-Apache-2.0.txt
13
+ b5d65a59060e68c4ff940e1eddfa6f94b2d68fdf58ed7f4dd57721c997e35e9d licenses/Whisper-MIT.txt
14
+ daa6056f84581c2311acbeaf4d5aff7df5c4aacb4fa23a7f788876257ce43efe model_manifest.json
15
+ f4350f936a2483da11f024c60b36915c21852431904a72908a544884a7afcfa1 pyproject.toml
16
+ dafdbd306609bf108a4b7b44b3c29fa263fdf3ba157824d31a9bde9ba19659d9 requirements-lock.txt
17
+ d508e880fff94cfc882109a1acc5122ea4c442bdc055114780d5c17b8ae1d026 scorer/config.json
18
+ 49c772a0428649c7f676913847d978d4139e4bcaea39acbe83ac834c819dd0a9 scorer/inference_assets.json
19
+ 26b2f4a90b90e997ceb2eae9df2f09166a9173c1297c53ef26b4b843eb778583 scorer/model.safetensors
20
+ b0d43d81486bd7c5216a032a21b9b9521838dd4460cb10cd627e466f3b80d284 scripts/setup_assets.py
21
+ e7b8437efd1496d8717720a5c58776f3b30c0550ea11a4642a1e600ee9961a88 scripts/verify_bundle.py
22
+ 375cbd9410f7bff742e7e49c0aaf8b9a6ba04e3a7e9a8869e115a5bfa8074282 streampa/__init__.py
23
+ 742be32f8decb995a9deb75a2a9d841f69f4eebb0c8dedcc2d1f204b1244ddc3 streampa/charsiu_model.py
24
+ be2e7dd491614553fee0beba238cf6bf66a0f59f2c1ace6b9972151ac22daf74 streampa/frontend.py
25
+ 70e3b07c3f8ec862c56b8c1e3c8df3ebc5afaba412a404bb7238484793b83ccf streampa/infer.py
26
+ 40838175655baa4996aef7c20066b71c77406b1b811282f793c0c97b948b3058 streampa/pipeline.py
27
+ efdeb871324aa0bdacd0f6cf43e926ddbcec3103c77a7256bf617b656b9d0500 streampa/stream.py
28
+ 3643ef2e07ff94dc7dc6e829b825680f73c9846d95a21f88189cb5ce6b0dab68 streampa/streaming_gopt.py
29
+ 1794fa024a698babbdfca90dfc9091d0a7ec051215461870c9f0b597ccee16fd streampa/streaming_pcn_gopt.py
30
+ 000ae55e222f6b246dd337a8b7eecda520e34308bd7bac4f7e6e90c47c6c6153 tests/test_runtime.py
31
+ 0c3b548f54cbb8e54aea347bce2fc75f56368bf30b90bd2b42252eb4b5e1bd15 validation_summary.json
32
+ 9715fd2243b6f06a5858b5e32950d2853f73dd5bc201aafcf76f5082a2d8acd1 whisper/added_tokens.json
33
+ 8dfbe1b3c46fa95ddde6cff3c53ece3b09a050182b152c64da5d90fe7eb68ea1 whisper/config.json
34
+ 7b226ee4c0ea765809d5adb7959e76ca53da1202ca5a67c956bf3221979d2d53 whisper/generation_config.json
35
+ 2df2990a395e35e8dfbc7511e08c12d56018d8d04691e0133e5d63b21e154dc6 whisper/merges.txt
36
+ 8917c9612b44f73bb5ef2ffec48cf44a4ee0dfa5a94af25e9e6710b9fd66a17f whisper/model.safetensors
37
+ bf1c507dc8724ca9cf9903640dacfb69dae2f00edee4f21ceba106a7392f26dd whisper/normalizer.json
38
+ 994838f1fa6462c8b9b3c90edada831f11f3dd8b4664634e18f4694d005c9dbf whisper/preprocessor_config.json
39
+ e67ae3a0aaa99abcd9f187138e12db1f65c16a14761c50ef10eef2c174a7a691 whisper/special_tokens_map.json
40
+ 21a4fc0483c14b87f4e0bbc177a9a357479bfa7c95aaf21ad53d71e9c5afafb9 whisper/tokenizer_config.json
41
+ 50d6a919f0a0601d56a04eb583c780d18553aa388254ba3158eb6a00f13e2c1a whisper/vocab.json
THIRD_PARTY_NOTICES.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Third-party notices and redistribution scope
2
+
3
+ This is a multi-component release, not a claim that every component shares one
4
+ license. The Hub card retains `license: other`; consult the component sources.
5
+
6
+ * GOPT-derived scorer and preprocessing source retain the original project's
7
+ BSD-3-Clause notice (`LICENSE`, copyright Yuan Gong, 2022). The published
8
+ scorer is the publisher's frozen StreamPA checkpoint, not the original GOPT
9
+ model and not the legacy v6 release. No training data or optimizer is bundled.
10
+ * The publisher's fine-tuned Whisper Base weights and their actual tokenizer
11
+ and processor are included. OpenAI's Whisper project publishes code and
12
+ model weights under MIT (`licenses/Whisper-MIT.txt`); the Transformers Hub
13
+ [Whisper Base card](https://huggingface.co/openai/whisper-base) labels its
14
+ converted distribution Apache-2.0. Both upstream notices are retained.
15
+ The included fine-tune is not interchangeable with generic Whisper Base.
16
+ * Charsiu frame-classifier code is from `lingjzhu/charsiu` commit
17
+ `13a69f2a22ca0c0962b75cc693399b0ae23a12c9`, under its MIT notice
18
+ (`licenses/Charsiu-MIT.txt`). The Charsiu weight repository does not expose
19
+ sufficiently explicit weight-redistribution terms for this release to
20
+ assign it a license. Its weights/tokenizer are **not redistributed here**.
21
+ `scripts/setup_assets.py` retrieves their official fixed revisions listed
22
+ in `assets/runtime_sources.json`; users must check the upstream terms.
23
+ * The pronunciation lexicon contains only isolated normalized word-to-phone
24
+ entries required by the frozen frontend. It is derived from
25
+ [speechocean762](https://www.openslr.org/101/), released by Beijing
26
+ Speechocean and Microsoft under CC BY 4.0. It omits utterance IDs, ordered
27
+ transcripts, recordings, boundaries and human scores. The normalization
28
+ and deduplication are adaptations. Attribution: Zhang et al.,
29
+ *speechocean762: An Open-Source Non-native English Speech Corpus For
30
+ Pronunciation Assessment* (2021), https://arxiv.org/abs/2104.01378 .
31
+ CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ .
32
+ * g2p-en 2.1.0 is installed from its official package (Apache-2.0 notice in
33
+ `licenses/G2P-Apache-2.0.txt`). Its runtime uses NLTK CMUdict and English
34
+ POS tagger resources. They are fetched from a fixed `nltk/nltk_data`
35
+ revision, with individual SHA256s, rather than redistributed as corpus
36
+ archives here. Refer to their bundled notices and
37
+ https://github.com/nltk/nltk_data/blob/gh-pages/DATASET-LICENSES.md .
38
+ * PyTorch, Transformers, NumPy, librosa, SoundFile, NLTK and safetensors are
39
+ separately installed packages, each subject to its own upstream license.
40
+
41
+ Synthetic verification audio stays outside the public bundle. This release
42
+ does not distribute private recordings, human assessment labels, experiment
43
+ predictions, proprietary corpus archives or third-party optimizer states.
assets/phn_dict.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "W": 0,
3
+ "IY": 1,
4
+ "K": 2,
5
+ "AO": 3,
6
+ "L": 4,
7
+ "IH": 5,
8
+ "T": 6,
9
+ "B": 7,
10
+ "EH": 8,
11
+ "R": 9,
12
+ "Z": 10,
13
+ "OW": 11,
14
+ "TH": 12,
15
+ "F": 13,
16
+ "AY": 14,
17
+ "V": 15,
18
+ "AH": 16,
19
+ "N": 17,
20
+ "UW": 18,
21
+ "S": 19,
22
+ "G": 20,
23
+ "AA": 21,
24
+ "M": 22,
25
+ "P": 23,
26
+ "NG": 24,
27
+ "HH": 25,
28
+ "EY": 26,
29
+ "SH": 27,
30
+ "AE": 28,
31
+ "D": 29,
32
+ "UH": 30,
33
+ "AW": 31,
34
+ "DH": 32,
35
+ "ER": 33,
36
+ "Y": 34,
37
+ "JH": 35,
38
+ "CH": 36,
39
+ "OY": 37,
40
+ "ZH": 38
41
+ }
assets/prepared_resource_hashes.json ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "g2p_en_version": "2.1.0",
3
+ "g2p_files": [
4
+ {
5
+ "package_relative": "g2p_en/__init__.py",
6
+ "bytes": 21,
7
+ "sha256": "36ffae3e8f2cad9d285131c78963f690327f5046b5273dfecc52707659be949d"
8
+ },
9
+ {
10
+ "package_relative": "g2p_en/checkpoint20.npz",
11
+ "bytes": 3342298,
12
+ "sha256": "b8af35e4596d8dd5836dfd3fe9b2ba4f97b9c311efe8879544cbcfcbd566d8c6"
13
+ },
14
+ {
15
+ "package_relative": "g2p_en/expand.py",
16
+ "bytes": 2492,
17
+ "sha256": "b31f9f2173280f4479de1272d57540382d64496662ef127cf4d05af5a49d70db"
18
+ },
19
+ {
20
+ "package_relative": "g2p_en/g2p.py",
21
+ "bytes": 7637,
22
+ "sha256": "fc2548b855e102f774506a6afe4f4bc88424229b69701ab6040aa16710ddff3a"
23
+ },
24
+ {
25
+ "package_relative": "g2p_en/homographs.en",
26
+ "bytes": 18711,
27
+ "sha256": "0403edcc6aefe8945f8987be398279fd98bf6b767e54b741331d3bc99a307071"
28
+ }
29
+ ],
30
+ "prepared_resources": [
31
+ {
32
+ "path": "charsiu/model/config.json",
33
+ "bytes": 1522,
34
+ "sha256": "49b4e164f0375c7b0eaab8cf4d9727a20f1060e73bf0d899e2442ae95f70cae4"
35
+ },
36
+ {
37
+ "path": "charsiu/model/pytorch_model.bin",
38
+ "bytes": 93368972,
39
+ "sha256": "a5b59d8cc3c548382dbc38c48d537b0bb520b9a67d2fafef2afee7740f9ce0c7"
40
+ },
41
+ {
42
+ "path": "charsiu/tokenizer/special_tokens_map.json",
43
+ "bytes": 85,
44
+ "sha256": "50eb73d51191696209d30d42d6ede50e57e7a542ca1db12df714b2c0aa3da8e2"
45
+ },
46
+ {
47
+ "path": "charsiu/tokenizer/tokenizer_config.json",
48
+ "bytes": 180,
49
+ "sha256": "56e02b54de01a0ec4cc0c705ad3c303d7b73c83d61063a94b84cbf3a55da76a1"
50
+ },
51
+ {
52
+ "path": "charsiu/tokenizer/vocab.json",
53
+ "bytes": 403,
54
+ "sha256": "781514e251e9615ea4a05bbf3760ffcf8ec2ebe67355f411177ea64b0602d897"
55
+ },
56
+ {
57
+ "path": "assets/nltk_data/corpora/cmudict/README",
58
+ "bytes": 3808,
59
+ "sha256": "b0556be7a2b12bea6a667277b75864f802dd4854480657ce298c62e6897e766b"
60
+ },
61
+ {
62
+ "path": "assets/nltk_data/corpora/cmudict/cmudict",
63
+ "bytes": 3820830,
64
+ "sha256": "cad209c39eb87677d64e93d97f8eed10b7e6f9bdd42de8e7ca8efc8e17d62e8a"
65
+ },
66
+ {
67
+ "path": "assets/nltk_data/corpora/cmudict.zip",
68
+ "bytes": 896069,
69
+ "sha256": "d07cca47fd72ad32ea9d8ad1219f85301eeaf4568f8b6b73747506a71fb5afd6"
70
+ },
71
+ {
72
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger/averaged_perceptron_tagger.pickle",
73
+ "bytes": 6138625,
74
+ "sha256": "25a5a19c7ced7b2bac3831da5bc0afcc2c34e5dd01cd4f361bb799949a696238"
75
+ },
76
+ {
77
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger.zip",
78
+ "bytes": 2526731,
79
+ "sha256": "e1f13cf2532daadfd6f3bc481a49859f0b8ea6432ccdcd83e6a49a5f19008de9"
80
+ },
81
+ {
82
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger_eng/averaged_perceptron_tagger_eng.classes.json",
83
+ "bytes": 285,
84
+ "sha256": "d152ffa69d45b8357276341a3d592c6c025911d52ec0d9068d4f382560dd48b6"
85
+ },
86
+ {
87
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger_eng/averaged_perceptron_tagger_eng.tagdict.json",
88
+ "bytes": 25788,
89
+ "sha256": "4c713731cb06727736962bc8cd94ad919217a040e76691059af99bc0c2c9c246"
90
+ },
91
+ {
92
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger_eng/averaged_perceptron_tagger_eng.weights.json",
93
+ "bytes": 5677744,
94
+ "sha256": "789e8e35f6fac656d9b7eccea29ba4e8a137cbb5b525dd31f39e4291a9e80832"
95
+ },
96
+ {
97
+ "path": "assets/nltk_data/taggers/averaged_perceptron_tagger_eng.zip",
98
+ "bytes": 1539115,
99
+ "sha256": "6025f530624335c67d6547d44757b357b4e79bae030a0383e9887a92c1718f0b"
100
+ }
101
+ ]
102
+ }
assets/pronunciation_lexicon.json ADDED
The diff for this file is too large to render. See raw diff
 
assets/runtime_sources.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema": 1,
3
+ "sources": [
4
+ {
5
+ "kind": "hub",
6
+ "repo_id": "charsiu/en_w2v2_tiny_fc_10ms",
7
+ "revision": "8272e6ec07582696d212c2b15bdd271c92ae64ee",
8
+ "filename": "config.json",
9
+ "destination": "charsiu/model/config.json",
10
+ "sha256": "49b4e164f0375c7b0eaab8cf4d9727a20f1060e73bf0d899e2442ae95f70cae4",
11
+ "bytes": 1522
12
+ },
13
+ {
14
+ "kind": "hub",
15
+ "repo_id": "charsiu/en_w2v2_tiny_fc_10ms",
16
+ "revision": "8272e6ec07582696d212c2b15bdd271c92ae64ee",
17
+ "filename": "pytorch_model.bin",
18
+ "destination": "charsiu/model/pytorch_model.bin",
19
+ "sha256": "a5b59d8cc3c548382dbc38c48d537b0bb520b9a67d2fafef2afee7740f9ce0c7",
20
+ "bytes": 93368972
21
+ },
22
+ {
23
+ "kind": "hub",
24
+ "repo_id": "charsiu/tokenizer_en_cmu",
25
+ "revision": "10507401aedf5e0aba164128535b49225ff95260",
26
+ "filename": "vocab.json",
27
+ "destination": "charsiu/tokenizer/vocab.json",
28
+ "sha256": "781514e251e9615ea4a05bbf3760ffcf8ec2ebe67355f411177ea64b0602d897",
29
+ "bytes": 403
30
+ },
31
+ {
32
+ "kind": "hub",
33
+ "repo_id": "charsiu/tokenizer_en_cmu",
34
+ "revision": "10507401aedf5e0aba164128535b49225ff95260",
35
+ "filename": "tokenizer_config.json",
36
+ "destination": "charsiu/tokenizer/tokenizer_config.json",
37
+ "sha256": "56e02b54de01a0ec4cc0c705ad3c303d7b73c83d61063a94b84cbf3a55da76a1",
38
+ "bytes": 180
39
+ },
40
+ {
41
+ "kind": "hub",
42
+ "repo_id": "charsiu/tokenizer_en_cmu",
43
+ "revision": "10507401aedf5e0aba164128535b49225ff95260",
44
+ "filename": "special_tokens_map.json",
45
+ "destination": "charsiu/tokenizer/special_tokens_map.json",
46
+ "sha256": "50eb73d51191696209d30d42d6ede50e57e7a542ca1db12df714b2c0aa3da8e2",
47
+ "bytes": 85
48
+ },
49
+ {
50
+ "kind": "zip",
51
+ "url": "https://raw.githubusercontent.com/nltk/nltk_data/550b6625bcef1f2abff2ff770a5a0d272c9c6b2a/packages/corpora/cmudict.zip",
52
+ "destination": "assets/nltk_data/corpora/cmudict.zip",
53
+ "extract_to": "assets/nltk_data/corpora",
54
+ "sha256": "d07cca47fd72ad32ea9d8ad1219f85301eeaf4568f8b6b73747506a71fb5afd6",
55
+ "bytes": 896069
56
+ },
57
+ {
58
+ "kind": "zip",
59
+ "url": "https://raw.githubusercontent.com/nltk/nltk_data/550b6625bcef1f2abff2ff770a5a0d272c9c6b2a/packages/taggers/averaged_perceptron_tagger.zip",
60
+ "destination": "assets/nltk_data/taggers/averaged_perceptron_tagger.zip",
61
+ "extract_to": "assets/nltk_data/taggers",
62
+ "sha256": "e1f13cf2532daadfd6f3bc481a49859f0b8ea6432ccdcd83e6a49a5f19008de9",
63
+ "bytes": 2526731
64
+ },
65
+ {
66
+ "kind": "zip",
67
+ "url": "https://raw.githubusercontent.com/nltk/nltk_data/550b6625bcef1f2abff2ff770a5a0d272c9c6b2a/packages/taggers/averaged_perceptron_tagger_eng.zip",
68
+ "destination": "assets/nltk_data/taggers/averaged_perceptron_tagger_eng.zip",
69
+ "extract_to": "assets/nltk_data/taggers",
70
+ "sha256": "6025f530624335c67d6547d44757b357b4e79bae030a0383e9887a92c1718f0b",
71
+ "bytes": 1539115
72
+ }
73
+ ]
74
+ }
bundle_manifest.json DELETED
@@ -1,107 +0,0 @@
1
- {
2
- "repo_id": "faeea/custom-gopt-252-eval",
3
- "description": "Bundle of the v6 ASR-confidence Streaming GOPT checkpoint plus the Whisper and Charsiu models required by the local evaluation pipeline.",
4
- "artifacts": [
5
- {
6
- "path": "streaming_gopt_best/best_audio_model.pth",
7
- "type": "streaming_gopt_weights",
8
- "purpose": "Pronunciation scoring model selected by validation phone MSE."
9
- },
10
- {
11
- "path": "streaming_gopt_best/config.json",
12
- "type": "streaming_gopt_config",
13
- "purpose": "Network shape and training arguments used to restore the v6 ASR-confidence model."
14
- },
15
- {
16
- "path": "streaming_gopt_best/result.csv",
17
- "type": "training_metrics",
18
- "purpose": "Per-epoch train/validation metrics."
19
- },
20
- {
21
- "path": "streaming_gopt_best/test_metrics.json",
22
- "type": "test_metrics",
23
- "purpose": "Held-out test metrics for the best validation checkpoint."
24
- },
25
- {
26
- "path": "streaming_gopt_best/inference_assets.json",
27
- "type": "inference_metadata",
28
- "purpose": "Normalization stats, feature dimension, and phone-id mapping required for one-audio local inference."
29
- },
30
- {
31
- "path": "whisper_best_model",
32
- "type": "transformers_whisper_model",
33
- "purpose": "ASR model used to build ASR-driven GOPT chunks."
34
- },
35
- {
36
- "path": "charsiu_en_w2v2_tiny_fc_10ms",
37
- "type": "charsiu_aligner_model",
38
- "purpose": "Frame-level phone alignment model used by preprocessing and inference."
39
- },
40
- {
41
- "path": "examples/infer_one_audio.py",
42
- "type": "example_script",
43
- "purpose": "Minimal one-audio local inference script that prints utterance-level scores."
44
- },
45
- {
46
- "path": "examples/eval_streaming_gopt_test.py",
47
- "type": "example_script",
48
- "purpose": "Minimal evaluation script for val/test split using the bundled best GOPT checkpoint."
49
- }
50
- ],
51
- "model_outputs": {
52
- "utterance": [
53
- "accuracy",
54
- "completeness",
55
- "fluency",
56
- "prosodic",
57
- "total"
58
- ],
59
- "phone": [
60
- "phone pronunciation score"
61
- ],
62
- "word": [
63
- "accuracy",
64
- "stress",
65
- "total",
66
- "asr_accuracy"
67
- ],
68
- "word_asr_accuracy_note": "The word-level ASR accuracy score can be used as an auxiliary indicator of whether the word was read correctly."
69
- },
70
- "best_validation_summary": {
71
- "selection_metric": "phone_val_mse",
72
- "best_epoch": 11,
73
- "phone_val_mse": 0.04760764539241791,
74
- "phone_val_pcc": 0.34831968338670993,
75
- "utt_val_pcc_total": 0.661865583803676,
76
- "word_val_pcc_total": 0.3418123225833971,
77
- "word_val_pcc_asr_accuracy": 0.3633849619530143
78
- },
79
- "test_summary": {
80
- "phone_test_mse": 0.04919730871915817,
81
- "phone_test_pcc": 0.3975705055993251,
82
- "utt_test_pcc": [
83
- 0.651909193610124,
84
- 0.012690043750859805,
85
- 0.7249977587357453,
86
- 0.7332794084754206,
87
- 0.681940037784353
88
- ],
89
- "word_test_pcc": [
90
- 0.4047142491023989,
91
- -0.0039119825110344765,
92
- 0.4122583381095728,
93
- 0.41746734262039764
94
- ],
95
- "word_score_names": [
96
- "accuracy",
97
- "stress",
98
- "total",
99
- "asr_accuracy"
100
- ]
101
- },
102
- "code_dependencies": {
103
- "custom_gopt_repo": "https://github.com/hf49w/custom-gopt.git",
104
- "charsiu_repo": "https://github.com/lingjzhu/charsiu",
105
- "charsiu_repo_commit": "13a69f2a22ca0c0962b75cc693399b0ae23a12c9"
106
- }
107
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
charsiu_en_w2v2_tiny_fc_10ms/config.json DELETED
@@ -1,75 +0,0 @@
1
- {
2
- "activation_dropout": 0.1,
3
- "apply_spec_augment": true,
4
- "architectures": [
5
- "Wav2Vec2ForFrameClassification"
6
- ],
7
- "attention_dropout": 0.1,
8
- "bos_token_id": 1,
9
- "codevector_dim": 256,
10
- "contrastive_logits_temperature": 0.1,
11
- "conv_bias": false,
12
- "conv_dim": [
13
- 512,
14
- 512,
15
- 512,
16
- 512,
17
- 512,
18
- 512,
19
- 512
20
- ],
21
- "conv_kernel": [
22
- 10,
23
- 3,
24
- 3,
25
- 3,
26
- 3,
27
- 2,
28
- 2
29
- ],
30
- "conv_stride": [
31
- 5,
32
- 2,
33
- 2,
34
- 2,
35
- 2,
36
- 2,
37
- 1
38
- ],
39
- "ctc_loss_reduction": "sum",
40
- "ctc_zero_infinity": false,
41
- "diversity_loss_weight": 0.1,
42
- "do_stable_layer_norm": false,
43
- "eos_token_id": 2,
44
- "feat_extract_activation": "gelu",
45
- "feat_extract_norm": "group",
46
- "feat_proj_dropout": 0.1,
47
- "feat_quantizer_dropout": 0.0,
48
- "final_dropout": 0.1,
49
- "gradient_checkpointing": false,
50
- "hidden_act": "gelu",
51
- "hidden_dropout": 0.1,
52
- "hidden_size": 384,
53
- "initializer_range": 0.02,
54
- "intermediate_size": 3072,
55
- "layer_norm_eps": 1e-05,
56
- "layerdrop": 0.1,
57
- "mask_feature_length": 10,
58
- "mask_feature_prob": 0.0,
59
- "mask_time_length": 10,
60
- "mask_time_prob": 0.05,
61
- "model_type": "wav2vec2",
62
- "num_attention_heads": 6,
63
- "num_codevector_groups": 2,
64
- "num_codevectors_per_group": 320,
65
- "num_conv_pos_embedding_groups": 16,
66
- "num_conv_pos_embeddings": 128,
67
- "num_feat_extract_layers": 7,
68
- "num_hidden_layers": 6,
69
- "num_negatives": 100,
70
- "pad_token_id": 0,
71
- "proj_codevector_dim": 256,
72
- "torch_dtype": "float32",
73
- "transformers_version": "4.9.0",
74
- "vocab_size": 42
75
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
charsiu_en_w2v2_tiny_fc_10ms/preprocessor_config.json DELETED
@@ -1 +0,0 @@
1
- Entry not found
 
 
charsiu_en_w2v2_tiny_fc_10ms/pytorch_model.bin DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:a5b59d8cc3c548382dbc38c48d537b0bb520b9a67d2fafef2afee7740f9ce0c7
3
- size 93368972
 
 
 
 
examples/eval_streaming_gopt_test.py DELETED
@@ -1,119 +0,0 @@
1
- import argparse
2
- import json
3
- import sys
4
- from pathlib import Path
5
-
6
- import torch
7
-
8
-
9
- def get_args():
10
- parser = argparse.ArgumentParser(
11
- description="Evaluate the bundled Streaming GOPT checkpoint on a prepared val/test split."
12
- )
13
- parser.add_argument("--data-dir", type=Path, required=True, help="Directory containing *_chunks.npz and metadata.json.")
14
- parser.add_argument("--model-dir", type=Path, required=True, help="Directory containing best_audio_model.pth and config.json.")
15
- parser.add_argument("--repo-src", type=Path, required=True, help="Path to custom-gopt/src.")
16
- parser.add_argument("--split", type=str, default="test", choices=["val", "test", "train"])
17
- parser.add_argument("--device", type=str, default=None, help="cuda / cuda:0 / cpu. Defaults to cuda if available.")
18
- parser.add_argument("--output-json", type=Path, default=None)
19
- parser.add_argument("--main-context-tokens", type=int, default=None, help="Override evaluation main context tokens.")
20
- parser.add_argument("--right-context-tokens", type=int, default=None, help="Override evaluation right context tokens.")
21
- return parser.parse_args()
22
-
23
-
24
- def load_summary(data_dir, model_dir, repo_src, split, device_name, main_context_tokens=None, right_context_tokens=None):
25
- sys.path.insert(0, str(repo_src))
26
-
27
- from models import StreamingGOPT, StreamingGOPTNoPhn
28
- from train_streaming_charsiu import StreamingChunkDataset, load_model_state, make_loader, validate
29
-
30
- metadata = json.loads((data_dir / "metadata.json").read_text(encoding="utf-8"))
31
- cfg = json.loads((model_dir / "config.json").read_text(encoding="utf-8"))
32
- args_dict = cfg["args"]
33
-
34
- class Args:
35
- pass
36
-
37
- args = Args()
38
- for key, value in args_dict.items():
39
- setattr(args, key, value)
40
-
41
- args.main_context_token_choices = args_dict.get("main_context_token_choices") or [
42
- int(item.strip()) for item in str(args_dict["main_context_tokens"]).split(",") if item.strip()
43
- ]
44
- args.right_context_token_choices = args_dict.get("right_context_token_choices") or [
45
- int(item.strip()) for item in str(args_dict["right_context_tokens"]).split(",") if item.strip()
46
- ]
47
-
48
- device = torch.device(device_name or ("cuda" if torch.cuda.is_available() else "cpu"))
49
- if getattr(args, "tf32", False) and device.type == "cuda":
50
- if hasattr(torch, "set_float32_matmul_precision"):
51
- torch.set_float32_matmul_precision("high")
52
- torch.backends.cuda.matmul.allow_tf32 = True
53
- torch.backends.cudnn.allow_tf32 = True
54
-
55
- dataset = StreamingChunkDataset(split, data_dir, metadata, final_only=(split != "train"))
56
- loader = make_loader(dataset, len(dataset), False, int(getattr(args, "num_workers", 0)))
57
-
58
- model_cls = StreamingGOPT if args_dict["model"] == "streaming_gopt" else StreamingGOPTNoPhn
59
- model = model_cls(
60
- embed_dim=int(args_dict["embed_dim"]),
61
- num_heads=int(args_dict["heads"]),
62
- depth=int(args_dict["depth"]),
63
- input_dim=int(cfg["input_dim"]),
64
- seq_len=int(cfg["seq_len"]),
65
- phn_num=int(cfg["phn_num"]),
66
- )
67
-
68
- state = torch.load(model_dir / "best_audio_model.pth", map_location=device)
69
- load_model_state(model, state)
70
- model = model.to(device)
71
- model.eval()
72
-
73
- eval_main_context = int(main_context_tokens if main_context_tokens is not None else max(args.main_context_token_choices))
74
- eval_right_context = int(right_context_tokens if right_context_tokens is not None else max(args.right_context_token_choices))
75
-
76
- mse, corr, utt_mse, utt_corr, word_mse, word_corr = validate(
77
- model,
78
- loader,
79
- args,
80
- -1,
81
- device,
82
- eval_main_context,
83
- eval_right_context,
84
- )
85
-
86
- return {
87
- "split": split,
88
- "device": str(device),
89
- "main_context_tokens": eval_main_context,
90
- "right_context_tokens": eval_right_context,
91
- "phone_mse": float(mse),
92
- "phone_pcc": float(corr),
93
- "utt_mse": [float(x) for x in utt_mse],
94
- "utt_pcc": [float(x) for x in utt_corr],
95
- "word_mse": [float(x) for x in word_mse],
96
- "word_pcc": [float(x) for x in word_corr],
97
- }
98
-
99
-
100
- def main():
101
- args = get_args()
102
- summary = load_summary(
103
- data_dir=args.data_dir,
104
- model_dir=args.model_dir,
105
- repo_src=args.repo_src,
106
- split=args.split,
107
- device_name=args.device,
108
- main_context_tokens=args.main_context_tokens,
109
- right_context_tokens=args.right_context_tokens,
110
- )
111
- payload = json.dumps(summary, ensure_ascii=False, indent=2)
112
- print(payload)
113
- if args.output_json is not None:
114
- args.output_json.parent.mkdir(parents=True, exist_ok=True)
115
- args.output_json.write_text(payload, encoding="utf-8")
116
-
117
-
118
- if __name__ == "__main__":
119
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/infer_one_audio.py DELETED
@@ -1,395 +0,0 @@
1
- import argparse
2
- import json
3
- import sys
4
- import warnings
5
- from pathlib import Path
6
-
7
- import numpy as np
8
- import torch
9
-
10
-
11
- SCORE_NAMES = ["accuracy", "completeness", "fluency", "prosodic", "total"]
12
-
13
-
14
- def get_args():
15
- parser = argparse.ArgumentParser(
16
- description="Run one-audio inference with the bundled Whisper + Charsiu + Streaming GOPT pipeline."
17
- )
18
- parser.add_argument("--audio", type=Path, required=True, help="Path to one wav audio file.")
19
- parser.add_argument("--bundle-dir", type=Path, default=Path(__file__).resolve().parents[1])
20
- parser.add_argument("--repo-root", type=Path, required=True, help="Path to the cloned custom-gopt repository root.")
21
- parser.add_argument("--charsiu-src-dir", type=Path, required=True, help="Path to the official Charsiu repo root or its src directory.")
22
- parser.add_argument("--device", type=str, default=None, help="cuda / cuda:0 / cpu. Defaults to cuda if available.")
23
- parser.add_argument("--output-json", type=Path, default=None)
24
- parser.add_argument("--main-context-tokens", type=int, default=None)
25
- parser.add_argument("--right-context-tokens", type=int, default=None)
26
- return parser.parse_args()
27
-
28
-
29
- def add_repo_paths(repo_root):
30
- repo_src = repo_root / "src"
31
- prep_src = repo_src / "prep_data"
32
- for path in [repo_src, prep_src]:
33
- path_str = str(path)
34
- if path_str not in sys.path:
35
- sys.path.insert(0, path_str)
36
-
37
-
38
- def load_asr_pipeline(whisper_model_dir, device):
39
- from transformers import pipeline
40
- from transformers.utils import logging as hf_logging
41
-
42
- hf_logging.set_verbosity_error()
43
-
44
- if str(device).startswith("cuda"):
45
- pipe_device = int(str(device).split(":", 1)[1]) if ":" in str(device) else 0
46
- torch_dtype = torch.float16
47
- else:
48
- pipe_device = -1
49
- torch_dtype = torch.float32
50
-
51
- pipe = pipeline(
52
- "automatic-speech-recognition",
53
- model=str(whisper_model_dir),
54
- tokenizer=str(whisper_model_dir),
55
- feature_extractor=str(whisper_model_dir),
56
- framework="pt",
57
- device=pipe_device,
58
- dtype=torch_dtype,
59
- )
60
- if hasattr(pipe.model, "generation_config"):
61
- pipe.model.generation_config.use_cache = False
62
- kwargs = {
63
- "return_timestamps": "word",
64
- "generate_kwargs": {
65
- "language": "english",
66
- "task": "transcribe",
67
- "max_new_tokens": 128,
68
- "use_cache": False,
69
- },
70
- }
71
- return pipe, kwargs
72
-
73
-
74
- def load_audio_for_asr(audio_path, sample_rate):
75
- import librosa
76
- import soundfile as sf
77
-
78
- audio, sr = sf.read(str(audio_path))
79
- if audio.ndim > 1:
80
- audio = audio.mean(axis=1)
81
- audio = np.asarray(audio, dtype=np.float32)
82
- if sr != sample_rate:
83
- audio = librosa.resample(audio, orig_sr=sr, target_sr=sample_rate)
84
- sr = sample_rate
85
- return audio, sr
86
-
87
-
88
- def transcribe_audio(asr_pipe, pipe_kwargs, audio_path, sample_rate, normalize_word):
89
- audio, sr = load_audio_for_asr(audio_path, sample_rate)
90
- result = asr_pipe({"raw": audio, "sampling_rate": sr}, **pipe_kwargs)
91
- transcript = (result.get("text") or "").strip()
92
- words = []
93
- for chunk in result.get("chunks", []):
94
- text = normalize_word(chunk.get("text", ""))
95
- timestamp = chunk.get("timestamp") or (None, None)
96
- if not text or timestamp[0] is None or timestamp[1] is None:
97
- continue
98
- words.append(
99
- {
100
- "text": text,
101
- "start": float(timestamp[0]),
102
- "end": float(timestamp[1]),
103
- }
104
- )
105
- return transcript, words
106
-
107
-
108
- def load_model(model_dir, repo_root, device):
109
- add_repo_paths(repo_root)
110
- from models import StreamingGOPT, StreamingGOPTNoPhn
111
-
112
- cfg = json.loads((model_dir / "config.json").read_text(encoding="utf-8"))
113
- model_args = cfg["args"]
114
- model_cls = StreamingGOPT if model_args["model"] == "streaming_gopt" else StreamingGOPTNoPhn
115
- model = model_cls(
116
- embed_dim=int(model_args["embed_dim"]),
117
- num_heads=int(model_args["heads"]),
118
- depth=int(model_args["depth"]),
119
- input_dim=int(cfg["input_dim"]),
120
- seq_len=int(cfg["seq_len"]),
121
- phn_num=int(cfg["phn_num"]),
122
- )
123
- state = torch.load(model_dir / "best_audio_model.pth", map_location=device)
124
- incompatible = model.load_state_dict(state, strict=False)
125
- allowed_missing = {
126
- "mlp_head_word4.0.weight",
127
- "mlp_head_word4.0.bias",
128
- "mlp_head_word4.1.weight",
129
- "mlp_head_word4.1.bias",
130
- }
131
- unexpected = set(incompatible.unexpected_keys)
132
- missing = set(incompatible.missing_keys)
133
- if unexpected:
134
- raise RuntimeError(f"Unexpected checkpoint keys: {sorted(unexpected)}")
135
- disallowed_missing = missing - allowed_missing
136
- if disallowed_missing:
137
- raise RuntimeError(f"Missing required checkpoint keys: {sorted(disallowed_missing)}")
138
- model = model.to(device)
139
- model.eval()
140
- return model, cfg
141
-
142
-
143
- def build_phone_segments(
144
- audio_path,
145
- transcript,
146
- repo_root,
147
- charsiu_src_dir,
148
- charsiu_model_dir,
149
- sample_rate,
150
- device,
151
- phn_dict,
152
- expected_feat_dim,
153
- ):
154
- add_repo_paths(repo_root)
155
- for module_name in ["Charsiu", "models", "utils", "processors"]:
156
- sys.modules.pop(module_name, None)
157
- from build_charsiu_seq_data import (
158
- audio_logits,
159
- build_model_phone_map,
160
- build_silence_keep_mask,
161
- import_official_charsiu_forced_align,
162
- load_official_charsiu_aligner,
163
- normalize_phone,
164
- segment_feature,
165
- )
166
-
167
- charsiu = load_official_charsiu_aligner(
168
- model_name=str(charsiu_model_dir),
169
- device=str(device),
170
- sample_rate=sample_rate,
171
- sil_threshold=4,
172
- lang="en",
173
- charsiu_src_dir=str(charsiu_src_dir),
174
- )
175
- phone_to_frame_id, _, _ = build_model_phone_map(charsiu)
176
- phone_groups, words = charsiu.charsiu_processor.get_phones_and_words(transcript)
177
- if not phone_groups:
178
- raise ValueError("ASR transcript cannot be converted into phones.")
179
-
180
- flat_records = []
181
- for word_id, phones in enumerate(phone_groups):
182
- word_text = str(words[word_id]).lower() if word_id < len(words) else f"word_{word_id}"
183
- for phone in phones:
184
- norm_phone = normalize_phone(phone)
185
- if not norm_phone or norm_phone == "SIL":
186
- continue
187
- if norm_phone not in phone_to_frame_id:
188
- raise ValueError(f"Phone not found in Charsiu frame-classifier vocab: {norm_phone}")
189
- if norm_phone not in phn_dict:
190
- raise ValueError(f"Phone not found in GOPT phone vocab: {norm_phone}")
191
- flat_records.append(
192
- {
193
- "phone": norm_phone,
194
- "word_id": int(word_id),
195
- "word_text": word_text,
196
- }
197
- )
198
-
199
- if not flat_records:
200
- raise ValueError("No valid phones remained after transcript normalization.")
201
-
202
- phone_ids = charsiu.charsiu_processor.get_phone_ids(phone_groups)
203
- target_phone_ids = list(phone_ids[1:-1])
204
- if len(target_phone_ids) != len(flat_records):
205
- raise ValueError(
206
- f"Phone count mismatch after G2P: target_phone_ids={len(target_phone_ids)} flat_records={len(flat_records)}"
207
- )
208
-
209
- probs, audio_duration = audio_logits(
210
- audio_path=str(audio_path),
211
- processor=charsiu.charsiu_processor,
212
- model=charsiu.aligner,
213
- sample_rate=sample_rate,
214
- device=device,
215
- )
216
- keep_mask = build_silence_keep_mask(charsiu, probs)
217
- kept_indices = np.flatnonzero(keep_mask)
218
- kept_probs = probs[keep_mask]
219
- if kept_probs.shape[0] < len(target_phone_ids):
220
- raise ValueError(
221
- f"Not enough non-silence frames for the recognized phones: frames={kept_probs.shape[0]} phones={len(target_phone_ids)}"
222
- )
223
-
224
- forced_align = import_official_charsiu_forced_align(str(charsiu_src_dir))
225
- aligned_phone_ids = np.asarray(forced_align(kept_probs, target_phone_ids), dtype=np.int32)
226
- frame_step = float(audio_duration) / max(len(probs), 1)
227
-
228
- segments = []
229
- for phone_idx, record in enumerate(flat_records):
230
- token_frames = np.flatnonzero(aligned_phone_ids == phone_idx)
231
- if token_frames.size == 0:
232
- raise ValueError(f"Empty aligned segment for phone index {phone_idx} ({record['phone']}).")
233
-
234
- segment_probs = kept_probs[token_frames]
235
- target_id = int(phone_to_frame_id[record["phone"]])
236
- base_feature = segment_feature(segment_probs, target_id, frame_step).astype(np.float32)
237
- expected_base_dim = int(expected_feat_dim) - 1
238
- expected_phone_prob_dim = expected_base_dim - 4
239
- current_phone_prob_dim = int(base_feature.shape[0]) - 4
240
- if current_phone_prob_dim == expected_phone_prob_dim + 1:
241
- # Current local Charsiu exports an extra [PAD] probability channel at the end.
242
- # The released GOPT checkpoint was trained without that channel.
243
- base_feature = np.concatenate([base_feature[:expected_phone_prob_dim], base_feature[-4:]], axis=0)
244
- elif current_phone_prob_dim != expected_phone_prob_dim:
245
- raise ValueError(
246
- f"Unexpected phone probability dimension: current={current_phone_prob_dim} expected={expected_phone_prob_dim}"
247
- )
248
- feature = np.concatenate([base_feature, np.array([0.0], dtype=np.float32)], axis=0)
249
-
250
- start_frame = int(kept_indices[token_frames[0]])
251
- end_frame = int(kept_indices[token_frames[-1]]) + 1
252
- start_time = float(start_frame * frame_step)
253
- end_time = float(min(audio_duration, end_frame * frame_step))
254
-
255
- segments.append(
256
- {
257
- "phone": record["phone"],
258
- "phone_id": int(phn_dict[record["phone"]]),
259
- "word_id": int(record["word_id"]),
260
- "word_text": record["word_text"],
261
- "start_time": start_time,
262
- "end_time": end_time,
263
- "feature": feature,
264
- }
265
- )
266
- return segments
267
-
268
-
269
- def prepare_model_inputs(segments, seq_len, feat_dim, norm_mean, norm_std, device):
270
- if len(segments) > seq_len:
271
- raise ValueError(f"Phone sequence too long for this model: {len(segments)} > seq_len({seq_len})")
272
-
273
- feat = np.zeros((seq_len, feat_dim), dtype=np.float32)
274
- phn = np.full((seq_len,), -1, dtype=np.int64)
275
- for idx, segment in enumerate(segments):
276
- if int(segment["feature"].shape[-1]) != feat_dim:
277
- raise ValueError(
278
- f"Feature dimension mismatch at segment {idx}: got {segment['feature'].shape[-1]}, expected {feat_dim}"
279
- )
280
- feat[idx] = segment["feature"]
281
- phn[idx] = int(segment["phone_id"])
282
-
283
- valid = phn >= 0
284
- feat[valid] = (feat[valid] - float(norm_mean)) / float(norm_std)
285
- x = torch.from_numpy(feat).unsqueeze(0).to(device)
286
- p = torch.from_numpy(phn).unsqueeze(0).to(device)
287
- return x, p
288
-
289
-
290
- def predict_scores(model, x, p, main_context_tokens, right_context_tokens):
291
- with torch.no_grad():
292
- u1, u2, u3, u4, u5, _, _, _, _, _ = model(
293
- x,
294
- p,
295
- main_context_tokens=int(main_context_tokens),
296
- right_context_tokens=int(right_context_tokens),
297
- )
298
- values = torch.cat([u1, u2, u3, u4, u5], dim=1).squeeze(0).cpu().numpy() * 5.0
299
- values = np.clip(values, 0.0, 10.0)
300
- return {name: float(value) for name, value in zip(SCORE_NAMES, values.tolist())}
301
-
302
-
303
- def build_output(audio_path, transcript, asr_words, scores, segments, device, bundle_dir):
304
- return {
305
- "status": "ok",
306
- "audio_path": str(audio_path.resolve()).replace("\\", "/"),
307
- "bundle_dir": str(bundle_dir.resolve()).replace("\\", "/"),
308
- "device": str(device),
309
- "transcript": transcript,
310
- "utterance_scores": scores,
311
- "overall_score": float(scores["total"]),
312
- "num_phone_segments": int(len(segments)),
313
- "num_asr_words": int(len(asr_words)),
314
- "recognized_words": [str(word["text"]).lower() for word in asr_words],
315
- }
316
-
317
-
318
- def main():
319
- warnings.filterwarnings("ignore", message=".*return_token_timestamps.*")
320
- args = get_args()
321
- bundle_dir = args.bundle_dir.resolve()
322
- repo_root = args.repo_root.resolve()
323
- charsiu_src_dir = args.charsiu_src_dir.resolve()
324
- device = torch.device(args.device or ("cuda" if torch.cuda.is_available() else "cpu"))
325
-
326
- if not args.audio.exists():
327
- raise FileNotFoundError(f"Audio file not found: {args.audio}")
328
-
329
- model_dir = bundle_dir / "streaming_gopt_best"
330
- whisper_model_dir = bundle_dir / "whisper_best_model"
331
- charsiu_model_dir = bundle_dir / "charsiu_en_w2v2_tiny_fc_10ms"
332
- inference_assets_path = model_dir / "inference_assets.json"
333
-
334
- inference_assets = json.loads(inference_assets_path.read_text(encoding="utf-8"))
335
- sample_rate = int(inference_assets["sample_rate"])
336
- norm_mean = float(inference_assets["train_norm_mean"])
337
- norm_std = float(inference_assets["train_norm_std"])
338
- phn_dict = {str(key): int(value) for key, value in inference_assets["phn_dict"].items()}
339
-
340
- model, cfg = load_model(model_dir, repo_root, device)
341
- add_repo_paths(repo_root)
342
- from build_charsiu_seq_data import normalize_word
343
-
344
- asr_pipe, asr_kwargs = load_asr_pipeline(whisper_model_dir, device)
345
- transcript, asr_words = transcribe_audio(asr_pipe, asr_kwargs, args.audio, sample_rate, normalize_word)
346
- if not transcript:
347
- raise ValueError("Whisper produced an empty transcript.")
348
-
349
- segments = build_phone_segments(
350
- audio_path=args.audio,
351
- transcript=transcript,
352
- repo_root=repo_root,
353
- charsiu_src_dir=charsiu_src_dir,
354
- charsiu_model_dir=charsiu_model_dir,
355
- sample_rate=sample_rate,
356
- device=device,
357
- phn_dict=phn_dict,
358
- expected_feat_dim=int(cfg["input_dim"]),
359
- )
360
-
361
- seq_len = int(cfg["seq_len"])
362
- feat_dim = int(cfg["input_dim"])
363
- x, p = prepare_model_inputs(
364
- segments=segments,
365
- seq_len=seq_len,
366
- feat_dim=feat_dim,
367
- norm_mean=norm_mean,
368
- norm_std=norm_std,
369
- device=device,
370
- )
371
-
372
- model_args = cfg["args"]
373
- main_context_choices = model_args.get("main_context_token_choices") or [
374
- int(item.strip()) for item in str(model_args["main_context_tokens"]).split(",") if item.strip()
375
- ]
376
- right_context_choices = model_args.get("right_context_token_choices") or [
377
- int(item.strip()) for item in str(model_args["right_context_tokens"]).split(",") if item.strip()
378
- ]
379
-
380
- main_context_tokens = int(args.main_context_tokens if args.main_context_tokens is not None else max(main_context_choices))
381
- right_context_tokens = int(
382
- args.right_context_tokens if args.right_context_tokens is not None else max(right_context_choices)
383
- )
384
- scores = predict_scores(model, x, p, main_context_tokens, right_context_tokens)
385
- payload = build_output(args.audio, transcript, asr_words, scores, segments, device, bundle_dir)
386
-
387
- result_json = json.dumps(payload, ensure_ascii=False, indent=2)
388
- print(result_json)
389
- if args.output_json is not None:
390
- args.output_json.parent.mkdir(parents=True, exist_ok=True)
391
- args.output_json.write_text(result_json, encoding="utf-8")
392
-
393
-
394
- if __name__ == "__main__":
395
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/sa1_infer_output.json DELETED
@@ -1,30 +0,0 @@
1
- {
2
- "status": "ok",
3
- "audio_path": "D:/研究生/模型/charsiu/charsiu_repo/local/SA1.WAV",
4
- "bundle_dir": "D:/研究生/智能体/gopt_charsiu/hf_bundle/custom-gopt-252-eval",
5
- "device": "cpu",
6
- "transcript": "She had your dark suit in greasy wash water all year.",
7
- "utterance_scores": {
8
- "accuracy": 5.0,
9
- "completeness": 5.0,
10
- "fluency": 5.0,
11
- "prosodic": 5.0,
12
- "total": 5.0
13
- },
14
- "overall_score": 5.0,
15
- "num_phone_segments": 34,
16
- "num_asr_words": 11,
17
- "recognized_words": [
18
- "she",
19
- "had",
20
- "your",
21
- "dark",
22
- "suit",
23
- "in",
24
- "greasy",
25
- "wash",
26
- "water",
27
- "all",
28
- "year"
29
- ]
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
examples/session.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Live-style local example. Input: your own mono 16 kHz English WAV."""
2
+ import argparse
3
+ import json
4
+ import soundfile as sf
5
+ from streampa import StreamPAPipeline
6
+
7
+ p=argparse.ArgumentParser()
8
+ p.add_argument('--bundle-dir',required=True); p.add_argument('--audio',required=True)
9
+ p.add_argument('--device',default='cpu')
10
+ a=p.parse_args()
11
+ pipeline=StreamPAPipeline(a.bundle_dir,device=a.device)
12
+ session=pipeline.create_session()
13
+ with sf.SoundFile(a.audio) as audio:
14
+ if audio.samplerate!=16000 or audio.channels!=1: raise ValueError('Expected mono 16-kHz WAV')
15
+ while True:
16
+ block=audio.read(1600,dtype='float32')
17
+ if not len(block): break
18
+ for result in session.append(block): print(json.dumps(result))
19
+ print(json.dumps(session.finish()))
20
+ session.reset()
21
+ assert session.current() is None and session.state is None
licenses/Apache-2.0.txt ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright 2018- The Hugging Face team. All rights reserved.
2
+
3
+ Apache License
4
+ Version 2.0, January 2004
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction,
12
+ and distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ "Licensor" shall mean the copyright owner or entity authorized by
15
+ the copyright owner that is granting the License.
16
+
17
+ "Legal Entity" shall mean the union of the acting entity and all
18
+ other entities that control, are controlled by, or are under common
19
+ control with that entity. For the purposes of this definition,
20
+ "control" means (i) the power, direct or indirect, to cause the
21
+ direction or management of such entity, whether by contract or
22
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
23
+ outstanding shares, or (iii) beneficial ownership of such entity.
24
+
25
+ "You" (or "Your") shall mean an individual or Legal Entity
26
+ exercising permissions granted by this License.
27
+
28
+ "Source" form shall mean the preferred form for making modifications,
29
+ including but not limited to software source code, documentation
30
+ source, and configuration files.
31
+
32
+ "Object" form shall mean any form resulting from mechanical
33
+ transformation or translation of a Source form, including but
34
+ not limited to compiled object code, generated documentation,
35
+ and conversions to other media types.
36
+
37
+ "Work" shall mean the work of authorship, whether in Source or
38
+ Object form, made available under the License, as indicated by a
39
+ copyright notice that is included in or attached to the work
40
+ (an example is provided in the Appendix below).
41
+
42
+ "Derivative Works" shall mean any work, whether in Source or Object
43
+ form, that is based on (or derived from) the Work and for which the
44
+ editorial revisions, annotations, elaborations, or other modifications
45
+ represent, as a whole, an original work of authorship. For the purposes
46
+ of this License, Derivative Works shall not include works that remain
47
+ separable from, or merely link (or bind by name) to the interfaces of,
48
+ the Work and Derivative Works thereof.
49
+
50
+ "Contribution" shall mean any work of authorship, including
51
+ the original version of the Work and any modifications or additions
52
+ to that Work or Derivative Works thereof, that is intentionally
53
+ submitted to Licensor for inclusion in the Work by the copyright owner
54
+ or by an individual or Legal Entity authorized to submit on behalf of
55
+ the copyright owner. For the purposes of this definition, "submitted"
56
+ means any form of electronic, verbal, or written communication sent
57
+ to the Licensor or its representatives, including but not limited to
58
+ communication on electronic mailing lists, source code control systems,
59
+ and issue tracking systems that are managed by, or on behalf of, the
60
+ Licensor for the purpose of discussing and improving the Work, but
61
+ excluding communication that is conspicuously marked or otherwise
62
+ designated in writing by the copyright owner as "Not a Contribution."
63
+
64
+ "Contributor" shall mean Licensor and any individual or Legal Entity
65
+ on behalf of whom a Contribution has been received by Licensor and
66
+ subsequently incorporated within the Work.
67
+
68
+ 2. Grant of Copyright License. Subject to the terms and conditions of
69
+ this License, each Contributor hereby grants to You a perpetual,
70
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71
+ copyright license to reproduce, prepare Derivative Works of,
72
+ publicly display, publicly perform, sublicense, and distribute the
73
+ Work and such Derivative Works in Source or Object form.
74
+
75
+ 3. Grant of Patent License. Subject to the terms and conditions of
76
+ this License, each Contributor hereby grants to You a perpetual,
77
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78
+ (except as stated in this section) patent license to make, have made,
79
+ use, offer to sell, sell, import, and otherwise transfer the Work,
80
+ where such license applies only to those patent claims licensable
81
+ by such Contributor that are necessarily infringed by their
82
+ Contribution(s) alone or by combination of their Contribution(s)
83
+ with the Work to which such Contribution(s) was submitted. If You
84
+ institute patent litigation against any entity (including a
85
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
86
+ or a Contribution incorporated within the Work constitutes direct
87
+ or contributory patent infringement, then any patent licenses
88
+ granted to You under this License for that Work shall terminate
89
+ as of the date such litigation is filed.
90
+
91
+ 4. Redistribution. You may reproduce and distribute copies of the
92
+ Work or Derivative Works thereof in any medium, with or without
93
+ modifications, and in Source or Object form, provided that You
94
+ meet the following conditions:
95
+
96
+ (a) You must give any other recipients of the Work or
97
+ Derivative Works a copy of this License; and
98
+
99
+ (b) You must cause any modified files to carry prominent notices
100
+ stating that You changed the files; and
101
+
102
+ (c) You must retain, in the Source form of any Derivative Works
103
+ that You distribute, all copyright, patent, trademark, and
104
+ attribution notices from the Source form of the Work,
105
+ excluding those notices that do not pertain to any part of
106
+ the Derivative Works; and
107
+
108
+ (d) If the Work includes a "NOTICE" text file as part of its
109
+ distribution, then any Derivative Works that You distribute must
110
+ include a readable copy of the attribution notices contained
111
+ within such NOTICE file, excluding those notices that do not
112
+ pertain to any part of the Derivative Works, in at least one
113
+ of the following places: within a NOTICE text file distributed
114
+ as part of the Derivative Works; within the Source form or
115
+ documentation, if provided along with the Derivative Works; or,
116
+ within a display generated by the Derivative Works, if and
117
+ wherever such third-party notices normally appear. The contents
118
+ of the NOTICE file are for informational purposes only and
119
+ do not modify the License. You may add Your own attribution
120
+ notices within Derivative Works that You distribute, alongside
121
+ or as an addendum to the NOTICE text from the Work, provided
122
+ that such additional attribution notices cannot be construed
123
+ as modifying the License.
124
+
125
+ You may add Your own copyright statement to Your modifications and
126
+ may provide additional or different license terms and conditions
127
+ for use, reproduction, or distribution of Your modifications, or
128
+ for any such Derivative Works as a whole, provided Your use,
129
+ reproduction, and distribution of the Work otherwise complies with
130
+ the conditions stated in this License.
131
+
132
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
133
+ any Contribution intentionally submitted for inclusion in the Work
134
+ by You to the Licensor shall be under the terms and conditions of
135
+ this License, without any additional terms or conditions.
136
+ Notwithstanding the above, nothing herein shall supersede or modify
137
+ the terms of any separate license agreement you may have executed
138
+ with Licensor regarding such Contributions.
139
+
140
+ 6. Trademarks. This License does not grant permission to use the trade
141
+ names, trademarks, service marks, or product names of the Licensor,
142
+ except as required for reasonable and customary use in describing the
143
+ origin of the Work and reproducing the content of the NOTICE file.
144
+
145
+ 7. Disclaimer of Warranty. Unless required by applicable law or
146
+ agreed to in writing, Licensor provides the Work (and each
147
+ Contributor provides its Contributions) on an "AS IS" BASIS,
148
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149
+ implied, including, without limitation, any warranties or conditions
150
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151
+ PARTICULAR PURPOSE. You are solely responsible for determining the
152
+ appropriateness of using or redistributing the Work and assume any
153
+ risks associated with Your exercise of permissions under this License.
154
+
155
+ 8. Limitation of Liability. In no event and under no legal theory,
156
+ whether in tort (including negligence), contract, or otherwise,
157
+ unless required by applicable law (such as deliberate and grossly
158
+ negligent acts) or agreed to in writing, shall any Contributor be
159
+ liable to You for damages, including any direct, indirect, special,
160
+ incidental, or consequential damages of any character arising as a
161
+ result of this License or out of the use or inability to use the
162
+ Work (including but not limited to damages for loss of goodwill,
163
+ work stoppage, computer failure or malfunction, or any and all
164
+ other commercial damages or losses), even if such Contributor
165
+ has been advised of the possibility of such damages.
166
+
167
+ 9. Accepting Warranty or Additional Liability. While redistributing
168
+ the Work or Derivative Works thereof, You may choose to offer,
169
+ and charge a fee for, acceptance of support, warranty, indemnity,
170
+ or other liability obligations and/or rights consistent with this
171
+ License. However, in accepting such obligations, You may act only
172
+ on Your own behalf and on Your sole responsibility, not on behalf
173
+ of any other Contributor, and only if You agree to indemnify,
174
+ defend, and hold each Contributor harmless for any liability
175
+ incurred by, or claims asserted against, such Contributor by reason
176
+ of your accepting any such warranty or additional liability.
177
+
178
+ END OF TERMS AND CONDITIONS
179
+
180
+ APPENDIX: How to apply the Apache License to your work.
181
+
182
+ To apply the Apache License to your work, attach the following
183
+ boilerplate notice, with the fields enclosed by brackets "[]"
184
+ replaced with your own identifying information. (Don't include
185
+ the brackets!) The text should be enclosed in the appropriate
186
+ comment syntax for the file format. We also recommend that a
187
+ file or class name and description of purpose be included on the
188
+ same "printed page" as the copyright notice for easier
189
+ identification within third-party archives.
190
+
191
+ Copyright [yyyy] [name of copyright owner]
192
+
193
+ Licensed under the Apache License, Version 2.0 (the "License");
194
+ you may not use this file except in compliance with the License.
195
+ You may obtain a copy of the License at
196
+
197
+ http://www.apache.org/licenses/LICENSE-2.0
198
+
199
+ Unless required by applicable law or agreed to in writing, software
200
+ distributed under the License is distributed on an "AS IS" BASIS,
201
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202
+ See the License for the specific language governing permissions and
203
+ limitations under the License.
licenses/Charsiu-MIT.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2021 jzhu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
licenses/G2P-Apache-2.0.txt ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "{}"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright {yyyy} {name of copyright owner}
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
licenses/Whisper-MIT.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2022 OpenAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
model_manifest.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_name": "StreamPA",
3
+ "experiment_id": "M_stress_scalar_gate_capacity64",
4
+ "primary_seed": 1337,
5
+ "original_checkpoint_sha256": "870df8dbe3c787487f094e0c4d4ade6bed8fcc5d86b578c4edc57581e81023b4",
6
+ "scorer_sha256": "26b2f4a90b90e997ceb2eae9df2f09166a9173c1297c53ef26b4b843eb778583",
7
+ "config_sha256": "d508e880fff94cfc882109a1acc5122ea4c442bdc055114780d5c17b8ae1d026",
8
+ "parameter_count": 142258,
9
+ "official_paper_seeds": [
10
+ 1337,
11
+ 202601,
12
+ 202602,
13
+ 202603
14
+ ],
15
+ "source_model_class": "PCNStreamingScorer",
16
+ "charsiu_mode": "prefix_recompute",
17
+ "commit_rule": "legacy",
18
+ "source_config_sha256": "0aca7850b95ad2d522992493b6eabfb5810be9b7802c5557c355047a090602ab",
19
+ "data_manifest_sha256": "ce3c021836de4e8241bfc61f196dbda502b06da160080e70e0de76accbc6214c",
20
+ "source_files": {
21
+ "augment_slot_prosody_pcn.py": "e6ace1b6f909fd911f7a163b12363e9d33ef60fd46f25af4e2499e4c4097db2f",
22
+ "benchmark_ours_m_efficiency.py": "d7263131d9c84b398669b50e609d14fa3925c31d6f1d89d134cbca3e5dffb9e3",
23
+ "build_charsiu_seq_data.py": "2535957cce06f1816b7520ea1ffc1a35dd3815bda11a0e335cbcbb3c8f92ff2e",
24
+ "build_streaming_asr_gopt_data.py": "3bab451431f3386f293bd49760252d558e93ae92e706971a05394fe898a7fe39",
25
+ "build_streaming_charsiu_data.py": "3d6963e702d792b1967a24af8add08eaf812f30f02798589d60c50708f30822c",
26
+ "build_streaming_pcn_gopt_data.py": "4444775df9e88586247ed6233ff8fadb9a099dae36dd98271e8dd2dc7c6da43f",
27
+ "infer_streaming_pcn.py": "e005fd5612f0286f026641958f2259ed2774811ac0a7f00f05b329758de3c1d1",
28
+ "streaming_gopt.py": "3643ef2e07ff94dc7dc6e829b825680f73c9846d95a21f88189cb5ce6b0dab68",
29
+ "streaming_pcn_gopt.py": "1794fa024a698babbdfca90dfc9091d0a7ec051215461870c9f0b597ccee16fd",
30
+ "train_streaming_pcn.py": "8779d624c563e84acaa42e4b812835e672b385e7b3b03b20a5aa3a0afce22df6"
31
+ },
32
+ "deployment_changes": [
33
+ "caller EOF determines final time",
34
+ "reference phone-ratio filter removed",
35
+ "explicit overlength and invalid-prefix statuses",
36
+ "ASR-word presentation aggregation; raw slot outputs preserved"
37
+ ],
38
+ "score_scaling": {
39
+ "word_network_to_0_10": 5,
40
+ "utterance_network_to_0_10": 5,
41
+ "phone_network_to_0_2": 5,
42
+ "word_network_to_0_5": 2.5,
43
+ "utterance_network_to_0_5": 2.5,
44
+ "phone_network_to_0_5": 12.5
45
+ }
46
+ }
pyproject.toml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "streampa"
7
+ version = "1.0.0"
8
+ description = "Frozen StreamPA streaming pronunciation-assessment inference"
9
+ requires-python = ">=3.10,<3.11"
10
+ dependencies = ["torch==2.5.1", "transformers==4.46.3", "huggingface-hub==0.36.0", "safetensors==0.6.2", "numpy==1.26.4", "librosa==0.11.0", "soundfile==0.13.1", "g2p-en==2.1.0", "nltk==3.9.1"]
11
+
12
+ [tool.setuptools.packages.find]
13
+ include = ["streampa*"]
requirements-lock.txt ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Verified Python 3.10 / Linux CPU environment.
2
+ # Install with --extra-index-url https://download.pytorch.org/whl/cpu
3
+ audioread==3.1.0
4
+ certifi==2026.7.22
5
+ cffi==2.1.1
6
+ charset-normalizer==3.5.1
7
+ click==8.5.0
8
+ cloudpickle==3.1.2
9
+ decorator==5.3.1
10
+ Distance==0.1.3
11
+ exceptiongroup==1.3.1
12
+ filelock==3.32.6
13
+ fsspec==2026.7.0
14
+ g2p-en==2.1.0
15
+ hf-xet==1.6.0
16
+ huggingface-hub==0.36.0
17
+ idna==3.19
18
+ inflect==7.5.0
19
+ iniconfig==2.3.0
20
+ Jinja2==3.1.6
21
+ joblib==1.6.0
22
+ lazy-loader==0.5
23
+ librosa==0.11.0
24
+ llvmlite==0.49.0
25
+ MarkupSafe==3.0.3
26
+ more-itertools==11.1.0
27
+ mpmath==1.3.0
28
+ msgpack==1.2.2
29
+ networkx==3.4.2
30
+ nltk==3.9.1
31
+ numba==0.67.0
32
+ numpy==1.26.4
33
+ packaging==26.3
34
+ platformdirs==4.11.8
35
+ pluggy==1.6.0
36
+ pooch==1.9.0
37
+ pycparser==3.0
38
+ Pygments==2.21.0
39
+ pytest==8.4.2
40
+ PyYAML==6.0.3
41
+ regex==2026.9.10
42
+ requests==2.34.2
43
+ safetensors==0.6.2
44
+ scikit-learn==1.7.2
45
+ scipy==1.15.3
46
+ setuptools==79.0.1
47
+ soundfile==0.13.1
48
+ soxr==1.1.0
49
+ sympy==1.13.1
50
+ threadpoolctl==3.6.0
51
+ tokenizers==0.20.3
52
+ tomli==2.4.1
53
+ torch==2.5.1+cpu
54
+ tqdm==4.70.0
55
+ transformers==4.46.3
56
+ typeguard==4.6.0
57
+ typing_extensions==4.16.0
58
+ urllib3==2.7.0
scorer/config.json ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metadata_schema": "streaming_pcn_gopt_v2_stateful",
3
+ "pcn_type": "nbest_derived_phone_confusion_network",
4
+ "phone_dim": 40,
5
+ "seq_len": 72,
6
+ "prosody_dim": 14,
7
+ "prosody_norm_mean": [
8
+ 3.9681200981140137,
9
+ 204.3585205078125,
10
+ 24.05475616455078,
11
+ -0.20867417752742767,
12
+ 0.12271453440189362,
13
+ -4.253726005554199,
14
+ 1.724206805229187,
15
+ 0.004727904684841633,
16
+ 0.20122256875038147,
17
+ 10.172670364379883,
18
+ 0.3134472370147705,
19
+ 1.937715768814087,
20
+ 5.8537139892578125,
21
+ 17295600.0
22
+ ],
23
+ "prosody_norm_std": [
24
+ 2.0985183715820312,
25
+ 55.536617279052734,
26
+ 13.517866134643555,
27
+ 0.4277317225933075,
28
+ 0.06661950051784515,
29
+ 0.6672922372817993,
30
+ 0.378594309091568,
31
+ 0.009153330698609352,
32
+ 0.0010623399866744876,
33
+ 11.080301284790039,
34
+ 0.1449834257364273,
35
+ 0.9631871581077576,
36
+ 2.870692014694214,
37
+ 205320480.0
38
+ ],
39
+ "slot_prosody_dim": 17,
40
+ "slot_prosody": [
41
+ "slot_duration",
42
+ "slot_log_energy_mean",
43
+ "slot_log_energy_std",
44
+ "slot_log_energy_max",
45
+ "slot_f0_mean",
46
+ "slot_f0_std",
47
+ "slot_f0_max",
48
+ "slot_voiced_ratio",
49
+ "slot_position_in_word",
50
+ "word_phone_count",
51
+ "energy_relative_to_word_mean",
52
+ "duration_relative_to_word_mean",
53
+ "f0_relative_to_word_mean",
54
+ "is_vowel",
55
+ "lexical_stress_0",
56
+ "lexical_stress_1",
57
+ "lexical_stress_2"
58
+ ],
59
+ "slot_prosody_norm_mean": [
60
+ 0.039656512439250946,
61
+ -0.9386982917785645,
62
+ 0.12416643649339676,
63
+ -0.7695538997650146,
64
+ 49.076961517333984,
65
+ 1.3523738384246826,
66
+ 51.32339096069336,
67
+ 0.18721239268779755,
68
+ 0.13828140497207642,
69
+ 1.0300568342208862,
70
+ 4.231509587171445e-10,
71
+ 0.28040772676467896,
72
+ -2.2446879910376083e-08,
73
+ 0.11200845241546631,
74
+ 0.0,
75
+ 0.0,
76
+ 0.0
77
+ ],
78
+ "slot_prosody_norm_std": [
79
+ 0.11444349586963654,
80
+ 1.6153852939605713,
81
+ 0.31157857179641724,
82
+ 1.3436129093170166,
83
+ 92.65617370605469,
84
+ 4.188445568084717,
85
+ 97.01981353759766,
86
+ 0.3661966025829315,
87
+ 0.31229057908058167,
88
+ 1.9228776693344116,
89
+ 0.3923450708389282,
90
+ 0.5529012084007263,
91
+ 27.52008056640625,
92
+ 0.31510770320892334,
93
+ 0.0,
94
+ 0.0,
95
+ 0.0
96
+ ],
97
+ "utt_pooling_head": "gru_visible",
98
+ "fusion_mode": "scalar_gate",
99
+ "stress_branch": "detached",
100
+ "stress_grad_scale": 0.2,
101
+ "uses_state_projection": false,
102
+ "args": {
103
+ "lr": 0.001,
104
+ "n_epochs": 80,
105
+ "batch_size": 12,
106
+ "num_workers": 4,
107
+ "embed_dim": 64,
108
+ "depth": 3,
109
+ "heads": 4,
110
+ "gru_dim": 64,
111
+ "main_context_tokens": 16,
112
+ "tbptt_steps": 0,
113
+ "loss_w_phone": 1.0,
114
+ "loss_w_word": 1.0,
115
+ "loss_w_utt": 1.0,
116
+ "loss_w_asr": 0.5,
117
+ "loss_w_uncertainty": 0.2,
118
+ "loss_w_confidence": 0.2,
119
+ "loss_w_abstention": 0.2,
120
+ "loss_w_calibration": 0.1,
121
+ "loss_w_teacher_score": 0.8,
122
+ "loss_w_prefix_kd": 1.0,
123
+ "loss_w_rank": 0.2,
124
+ "loss_w_oracle_phone": 0.3,
125
+ "loss_w_oracle_word": 1.0,
126
+ "loss_w_oracle_utt_prefix": 0.2,
127
+ "loss_w_oracle_utt_final": 0.3,
128
+ "loss_w_stress_pearson": 0.2,
129
+ "loss_w_oracle_stress_pearson": 0.3,
130
+ "loss_w_teacher_stress_pearson": 0.1,
131
+ "loss_w_stress_rank": 0.1,
132
+ "loss_w_oracle_stress_rank": 0.1,
133
+ "loss_w_phone_stability": 0.01,
134
+ "loss_w_word_stability": 0.01,
135
+ "loss_w_utt_stability": 0.01,
136
+ "loss_w_commit_consistency": 0.0,
137
+ "loss_w_state_projection": 0.0,
138
+ "utt_dim_weights": [
139
+ 1.0,
140
+ 0.0,
141
+ 1.0,
142
+ 1.0,
143
+ 1.0
144
+ ],
145
+ "word_dim_weights": [
146
+ 1.0,
147
+ 3.0,
148
+ 1.0
149
+ ],
150
+ "teacher_word_dim_weights": [
151
+ 1.0,
152
+ 2.0,
153
+ 1.0
154
+ ],
155
+ "oracle_word_dim_weights": [
156
+ 1.0,
157
+ 5.0,
158
+ 1.0
159
+ ],
160
+ "soft_label_policy": "original",
161
+ "relaxed_min_gt_posterior": 0.05,
162
+ "relaxed_acoustic_scale": 0.3,
163
+ "relaxed_min_weight": 0.05,
164
+ "relaxed_max_weight": 0.5,
165
+ "utt_pooling_head": "gru_visible",
166
+ "fusion_mode": "scalar_gate",
167
+ "rank_margin": 0.02,
168
+ "stress_rank_margin": 0.02,
169
+ "stress_rank_max_items": 512,
170
+ "stress_loss_mask": "vowel",
171
+ "stress_voiced_threshold": 0.3,
172
+ "stress_branch": "detached",
173
+ "stress_grad_scale": 0.2,
174
+ "grad_clip_norm": 1.0,
175
+ "compile": false,
176
+ "tf32": true,
177
+ "seed": 1337
178
+ },
179
+ "preserve_unused_legacy_utt_head": true
180
+ }
scorer/inference_assets.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sample_rate": 16000,
3
+ "chunk_sec": 0.64,
4
+ "right_context_sec": 0.16,
5
+ "nbest": 5,
6
+ "beam_size": 8,
7
+ "max_new_tokens": 128,
8
+ "no_repeat_ngram_size": 0,
9
+ "language": "english",
10
+ "min_sil_frames": 4,
11
+ "commit_rule": "legacy",
12
+ "max_audio_sec": 30,
13
+ "filter": {
14
+ "max_new_tokens": 128,
15
+ "no_repeat_ngram_size": 0,
16
+ "strict_repeat_word_trigger": 64,
17
+ "strict_repeat_phone_trigger": 100,
18
+ "repeat_ngram_min_repeats": 4,
19
+ "repeat_max_ngram_size": 12,
20
+ "repeat_ngram_coverage": 0.6,
21
+ "repeat_token_ratio": 0.5,
22
+ "length_policy": "word_count and absolute phone_count only enable strict repetition checks; non-repetitive long hypotheses are retained",
23
+ "hard_rejection_policy": "reference-independent repeated hypotheses only; G2P or alignment failures return explicit invalid status"
24
+ },
25
+ "historical_reference_ratio_filter_disabled": true,
26
+ "lexical_stress_features": "zero as frozen builder",
27
+ "native_target_divisor": 5,
28
+ "score_scaling": {
29
+ "word_network_to_0_10": 5,
30
+ "utterance_network_to_0_10": 5,
31
+ "phone_network_to_0_2": 5,
32
+ "word_network_to_0_5": 2.5,
33
+ "utterance_network_to_0_5": 2.5,
34
+ "phone_network_to_0_5": 12.5
35
+ },
36
+ "clip_scores": false,
37
+ "historical_filter_metadata": {
38
+ "max_new_tokens": 128,
39
+ "no_repeat_ngram_size": 0,
40
+ "strict_repeat_word_trigger": 64,
41
+ "strict_repeat_phone_trigger": 100,
42
+ "max_phone_ratio": 3.0,
43
+ "repeat_ngram_min_repeats": 4,
44
+ "repeat_max_ngram_size": 12,
45
+ "repeat_ngram_coverage": 0.6,
46
+ "repeat_token_ratio": 0.5,
47
+ "length_policy": "word_count and absolute phone_count only enable strict repetition checks; non-repetitive long hypotheses are retained",
48
+ "hard_rejection_policy": "drop individual N-best hypotheses with detected repetition or ASR/reference phone ratio above max_phone_ratio; drop chunk only if all hypotheses are filtered"
49
+ }
50
+ }
streaming_gopt_best/best_audio_model.pth → scorer/model.safetensors RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:d3ab226af75d1dfd7dd71cbcf512a9441aa1200ba2079d34470f81159cd6b977
3
- size 190932
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:26b2f4a90b90e997ceb2eae9df2f09166a9173c1297c53ef26b4b843eb778583
3
+ size 578264
scripts/setup_assets.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Fetch fixed official third-party assets; validate hashes; no model execution."""
2
+ import argparse
3
+ import hashlib
4
+ import importlib.metadata
5
+ import json
6
+ from pathlib import Path
7
+ import urllib.request
8
+ import zipfile
9
+ from huggingface_hub import hf_hub_download
10
+
11
+ def sha(path):
12
+ h=hashlib.sha256()
13
+ with Path(path).open('rb') as f:
14
+ for chunk in iter(lambda:f.read(1048576),b''): h.update(chunk)
15
+ return h.hexdigest()
16
+
17
+ def setup(bundle, offline=False):
18
+ bundle=Path(bundle).resolve()
19
+ spec=json.loads((bundle/'assets/runtime_sources.json').read_text())
20
+ checks=[]
21
+ for row in spec['sources']:
22
+ dest=bundle/row['destination']
23
+ dest.parent.mkdir(parents=True,exist_ok=True)
24
+ if not dest.exists():
25
+ if offline: raise FileNotFoundError(f'Offline asset missing: {row["destination"]}')
26
+ if row['kind']=='hub':
27
+ source=Path(hf_hub_download(row['repo_id'],row['filename'],revision=row['revision'],token=False))
28
+ assert sha(source)==row['sha256'], row['destination']
29
+ dest.write_bytes(source.read_bytes())
30
+ else:
31
+ with urllib.request.urlopen(row['url'],timeout=120) as response:
32
+ content=response.read()
33
+ if hashlib.sha256(content).hexdigest()!=row['sha256']:
34
+ raise RuntimeError(f'Asset checksum failed: {row["destination"]}')
35
+ dest.write_bytes(content)
36
+ if sha(dest)!=row['sha256']:
37
+ raise RuntimeError(f'Asset checksum failed, not overwriting: {row["destination"]}')
38
+ if row['kind']=='zip':
39
+ target=(bundle/row['extract_to']).resolve()
40
+ with zipfile.ZipFile(dest) as archive:
41
+ for member in archive.infolist():
42
+ path=(target/member.filename).resolve()
43
+ if target!=path and target not in path.parents:
44
+ raise RuntimeError('Unsafe resource archive path')
45
+ if not member.is_dir():
46
+ content=archive.read(member)
47
+ if path.exists():
48
+ if path.read_bytes()!=content:
49
+ raise RuntimeError(f'Installed resource differs: {path.name}')
50
+ else:
51
+ path.parent.mkdir(parents=True,exist_ok=True)
52
+ path.write_bytes(content)
53
+ checks.append(dict(path=row['destination'],sha256=row['sha256'],status='PASS'))
54
+ inventory=json.loads((bundle/'assets/prepared_resource_hashes.json').read_text())
55
+ dist=importlib.metadata.distribution('g2p-en')
56
+ if dist.version!=inventory['g2p_en_version']: raise RuntimeError('G2P package version differs')
57
+ for row in inventory['g2p_files']:
58
+ path=Path(dist.locate_file(row['package_relative']))
59
+ if not path.is_file() or sha(path)!=row['sha256']:
60
+ raise RuntimeError(f'G2P resource differs: {row["package_relative"]}')
61
+ checks.append(dict(path=row['package_relative'],sha256=row['sha256'],status='PASS'))
62
+ for row in inventory['prepared_resources']:
63
+ path=bundle/row['path']
64
+ if not path.is_file() or sha(path)!=row['sha256']:
65
+ raise RuntimeError(f'Prepared resource differs: {row["path"]}')
66
+ print(json.dumps(dict(offline=offline,checks=checks),indent=2))
67
+ return checks
68
+
69
+ if __name__=='__main__':
70
+ p=argparse.ArgumentParser()
71
+ p.add_argument('--bundle-dir',type=Path,default=Path(__file__).resolve().parents[1])
72
+ p.add_argument('--offline',action='store_true')
73
+ a=p.parse_args()
74
+ setup(a.bundle_dir,a.offline)
scripts/verify_bundle.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Verify every published entity against the release SHA256SUMS."""
2
+ import argparse
3
+ import hashlib
4
+ from pathlib import Path
5
+
6
+ def verify(root):
7
+ root=Path(root).resolve(); count=0
8
+ for row in (root/'SHA256SUMS').read_text().splitlines():
9
+ digest,name=row.split(' ',1); path=(root/name).resolve()
10
+ if root not in path.parents: raise ValueError('Invalid manifest path')
11
+ h=hashlib.sha256()
12
+ with path.open('rb') as f:
13
+ for block in iter(lambda:f.read(1048576),b''): h.update(block)
14
+ if h.hexdigest()!=digest: raise RuntimeError(f'Checksum mismatch: {name}')
15
+ count+=1
16
+ print(f'PASS: {count} published files verified')
17
+ return count
18
+
19
+ if __name__=='__main__':
20
+ p=argparse.ArgumentParser(); p.add_argument('--bundle-dir',default=Path(__file__).resolve().parents[1])
21
+ verify(p.parse_args().bundle_dir)
streaming_gopt_best/config.json DELETED
@@ -1,42 +0,0 @@
1
- {
2
- "data_dir": "/DATA_2/guest/custom-gopt/data/streaming_asr_gopt_v6_asrconf",
3
- "input_dim": 47,
4
- "phn_num": 40,
5
- "seq_len": 692,
6
- "args": {
7
- "data_dir": "/DATA_2/guest/custom-gopt/data/streaming_asr_gopt_v6_asrconf",
8
- "exp_dir": "/DATA_2/guest/custom-gopt/exp/streaming-asr-gopt-v6-asrconf",
9
- "lr": 0.001,
10
- "n_epochs": 100,
11
- "depth": 3,
12
- "heads": 1,
13
- "batch_size": 64,
14
- "embed_dim": 24,
15
- "loss_w_phn": 1.0,
16
- "loss_w_word": 1.0,
17
- "loss_w_word_asr": 1.0,
18
- "loss_w_utt": 1.0,
19
- "model": "streaming_gopt",
20
- "noise": 0.0,
21
- "num_workers": 4,
22
- "device": "cuda",
23
- "main_context_tokens": "4,8,12,16",
24
- "right_context_tokens": "0,1,2,4",
25
- "compile": false,
26
- "tf32": false,
27
- "seed": 1337,
28
- "resume": false,
29
- "main_context_token_choices": [
30
- 4,
31
- 8,
32
- 12,
33
- 16
34
- ],
35
- "right_context_token_choices": [
36
- 0,
37
- 1,
38
- 2,
39
- 4
40
- ]
41
- }
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streaming_gopt_best/inference_assets.json DELETED
@@ -1,48 +0,0 @@
1
- {
2
- "sample_rate": 16000,
3
- "train_norm_mean": 0.0722440779209137,
4
- "train_norm_std": 0.25949764251708984,
5
- "feat_dim": 47,
6
- "phn_num": 40,
7
- "phn_dict": {
8
- "W": 0,
9
- "IY": 1,
10
- "K": 2,
11
- "AO": 3,
12
- "L": 4,
13
- "IH": 5,
14
- "T": 6,
15
- "B": 7,
16
- "EH": 8,
17
- "R": 9,
18
- "Z": 10,
19
- "OW": 11,
20
- "TH": 12,
21
- "F": 13,
22
- "AY": 14,
23
- "V": 15,
24
- "AH": 16,
25
- "N": 17,
26
- "UW": 18,
27
- "S": 19,
28
- "G": 20,
29
- "AA": 21,
30
- "M": 22,
31
- "P": 23,
32
- "NG": 24,
33
- "HH": 25,
34
- "EY": 26,
35
- "SH": 27,
36
- "AE": 28,
37
- "D": 29,
38
- "UH": 30,
39
- "AW": 31,
40
- "DH": 32,
41
- "ER": 33,
42
- "Y": 34,
43
- "JH": 35,
44
- "CH": 36,
45
- "OY": 37,
46
- "ZH": 38
47
- }
48
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streaming_gopt_best/result.csv DELETED
@@ -1,101 +0,0 @@
1
- epoch,phone_train_mse,phone_train_pcc,phone_val_mse,phone_val_pcc,learning rate,utt_train_mse_accuracy,utt_train_mse_completeness,utt_train_mse_fluency,utt_train_mse_prosodic,utt_train_mse_total,utt_train_pcc_accuracy,utt_train_pcc_completeness,utt_train_pcc_fluency,utt_train_pcc_prosodic,utt_train_pcc_total,utt_val_mse_accuracy,utt_val_mse_completeness,utt_val_mse_fluency,utt_val_mse_prosodic,utt_val_mse_total,utt_val_pcc_accuracy,utt_val_pcc_completeness,utt_val_pcc_fluency,utt_val_pcc_prosodic,utt_val_pcc_total,word_train_pcc_accuracy,word_train_pcc_stress,word_train_pcc_total,word_train_pcc_asr_accuracy,word_val_pcc_accuracy,word_val_pcc_stress,word_val_pcc_total,word_val_pcc_asr_accuracy
2
- 0.000000000000000000e+00,1.079137027263641357e-01,2.776732289780305396e-01,5.458179116249084473e-02,2.056475016002890233e-01,1.000000000000000021e-03,1.017732769250869751e-01,1.537960255518555641e-03,8.632741868495941162e-02,7.209369540214538574e-02,9.389597922563552856e-02,5.060659645574577814e-01,1.744086679858893388e-02,5.009421071735626896e-01,4.932015166782500182e-01,5.813221164796392726e-01,8.561448007822036743e-02,1.139749772846698761e-03,6.641924381256103516e-02,7.153119891881942749e-02,7.674410939216613770e-02,4.032423467488363000e-01,-1.592214327453594924e-02,3.815769695007495410e-01,3.394003035876998475e-01,5.042478848682950821e-01,2.077918562638139122e-01,-8.336524674171885696e-02,2.191634810191739335e-01,1.596240049224992019e-01,1.359882526136001468e-01,-4.588009800221485440e-02,1.449690180410702356e-01,1.804069662785112715e-01
3
- 1.000000000000000000e+00,1.041820421814918518e-01,3.706449166264469408e-01,6.219116970896720886e-02,2.582953758852352122e-01,1.000000000000000021e-03,7.506916671991348267e-02,3.650750964879989624e-03,6.011062115430831909e-02,5.140639096498489380e-02,6.998758018016815186e-02,6.370056414927510735e-01,1.298882550120022128e-01,6.780273015672545478e-01,6.862977747573038023e-01,6.796541662922360727e-01,8.756997436285018921e-02,3.653137944638729095e-03,7.288870215415954590e-02,6.140800565481185913e-02,8.417460322380065918e-02,5.261388264911209811e-01,2.443657795377397399e-02,5.871761168081823801e-01,5.965706019434455776e-01,5.679167708900907385e-01,3.640398602379311677e-01,-4.233877629281119354e-02,3.880548763609277252e-01,2.728305487899807535e-01,2.182483660645393453e-01,-3.853244682653233361e-02,2.504640631625094094e-01,3.203802817658301660e-01
4
- 2.000000000000000000e+00,9.281047433614730835e-02,4.388932832189844646e-01,5.085919052362442017e-02,2.917910224071572634e-01,1.000000000000000021e-03,6.204526871442794800e-02,3.228217363357543945e-03,4.249990358948707581e-02,4.041586443781852722e-02,5.738137662410736084e-02,7.214929956115374576e-01,8.182538551806139981e-02,7.771532765174712365e-01,7.594808830575937586e-01,7.377560843879427122e-01,6.918659806251525879e-02,3.128336509689688683e-03,4.416642710566520691e-02,4.611694440245628357e-02,6.303169578313827515e-02,5.695003532178497530e-01,-5.067768569970219732e-03,6.471075033146025390e-01,6.484407925864426048e-01,5.987705131988432239e-01,4.295590856406035218e-01,4.127377961734719075e-02,4.581967800386524403e-01,3.538432132168152222e-01,2.500769892836061659e-01,-1.171821163103803007e-02,2.867814875505260153e-01,3.951812826603934847e-01
5
- 3.000000000000000000e+00,8.795424550771713257e-02,4.852790553580234034e-01,5.026153847575187683e-02,3.126730051242129216e-01,1.000000000000000021e-03,5.290393158793449402e-02,1.651985454373061657e-03,3.355559706687927246e-02,3.485541418194770813e-02,4.882648214697837830e-02,7.594843870921196283e-01,6.719378746568728522e-02,8.178115626685859008e-01,8.000001848572846352e-01,7.757036692980765480e-01,6.167699769139289856e-02,1.421156455762684345e-03,4.079946875572204590e-02,4.082865267992019653e-02,5.512074753642082214e-02,6.063115601065953708e-01,-1.548446743561882055e-02,7.083793729119221005e-01,6.996366675591403350e-01,6.376530729514864326e-01,4.858310069896916716e-01,6.929232988970045193e-02,5.100691439367808222e-01,3.846799513993024444e-01,2.785472851702357477e-01,-3.475561394853065884e-03,3.115620678538462696e-01,4.184954436680822232e-01
6
- 4.000000000000000000e+00,8.721461892127990723e-02,5.109648368379621219e-01,6.383151561021804810e-02,3.174473789177553185e-01,1.000000000000000021e-03,4.725972935557365417e-02,1.288253813982009888e-03,3.230554983019828796e-02,3.321465849876403809e-02,4.065737873315811157e-02,7.754567118380922830e-01,6.929346740417351158e-02,8.312628993336449446e-01,8.169172738551149227e-01,7.957848608365545306e-01,6.013352796435356140e-02,9.462164598517119884e-04,3.752611950039863586e-02,3.895737975835800171e-02,5.430128425359725952e-02,6.190762454692686489e-01,1.694164467552703758e-03,7.283609325777327115e-01,7.194678905436201477e-01,6.520788335982493633e-01,5.206589800308554228e-01,1.233108496997321890e-01,5.402275814379471353e-01,3.854360042662868802e-01,2.983254935808805075e-01,2.548658286062590109e-02,3.280349747585073472e-01,4.151764133724160089e-01
7
- 5.000000000000000000e+00,8.810387551784515381e-02,5.233755684010888976e-01,6.833378225564956665e-02,3.205610361957397236e-01,1.000000000000000021e-03,4.402649030089378357e-02,1.182442763820290565e-03,3.257671743631362915e-02,2.821853384375572205e-02,3.860916569828987122e-02,7.844561945868420150e-01,8.341824281097018345e-02,8.422572964788933580e-01,8.295394734322586450e-01,8.070802518044921658e-01,6.067190691828727722e-02,7.594404160045087337e-04,4.596406221389770508e-02,3.856667503714561462e-02,5.295049399137496948e-02,6.282226133800322687e-01,1.692926155089146192e-02,7.419398580744197558e-01,7.319690681809026822e-01,6.642837027774701752e-01,5.339778616486737794e-01,1.488257401530594937e-01,5.520129563203034495e-01,3.729090532051128615e-01,3.039651271249513842e-01,4.959261266514947142e-02,3.319566011496995483e-01,4.006294076261598947e-01
8
- 6.000000000000000000e+00,8.414012193679809570e-02,5.381291814772770632e-01,6.494909524917602539e-02,3.196504956671062936e-01,1.000000000000000021e-03,4.297406971454620361e-02,1.028442755341529846e-03,2.753048017621040344e-02,2.733003720641136169e-02,3.911860659718513489e-02,7.904577456767357679e-01,8.890016920034679582e-02,8.449343388914654085e-01,8.328881381871791056e-01,8.106734163016174888e-01,6.023196130990982056e-02,6.536911241710186005e-04,3.779817745089530945e-02,3.825043886899948120e-02,5.370716378092765808e-02,6.276532593165649043e-01,3.025794644290185492e-02,7.427249721192606957e-01,7.321564399718462068e-01,6.630480505758957754e-01,5.454458282743140929e-01,1.487224212418345459e-01,5.636291401743753759e-01,3.761236095987761652e-01,3.047638127886744819e-01,4.767652687284456431e-02,3.318484113513537892e-01,3.933327346715422279e-01
9
- 7.000000000000000000e+00,8.397044241428375244e-02,5.454177449248397425e-01,6.567811965942382812e-02,3.296529190938821685e-01,1.000000000000000021e-03,4.575878381729125977e-02,8.211127133108675480e-04,2.928980439901351929e-02,3.056729398667812347e-02,4.057361558079719543e-02,7.909265393099840225e-01,7.771252355228436270e-02,8.451621372874504878e-01,8.331802795242353454e-01,8.122646093462853045e-01,6.321127712726593018e-02,4.775344568770378828e-04,3.779856115579605103e-02,4.165362194180488586e-02,5.752434208989143372e-02,6.332849796716126800e-01,3.639127957694833493e-02,7.445063607304056585e-01,7.304949047675072249e-01,6.655040270535693692e-01,5.536482511279675611e-01,1.309219956720971967e-01,5.684799286193154000e-01,3.703783991526826136e-01,3.100192455908276079e-01,3.533682433665723266e-02,3.363407503874519255e-01,3.893300762394926728e-01
10
- 8.000000000000000000e+00,7.932690531015396118e-02,5.582815030417074276e-01,5.237221717834472656e-02,3.340726441925265799e-01,1.000000000000000021e-03,4.299703985452651978e-02,8.145723841153085232e-04,2.827190607786178589e-02,2.760162949562072754e-02,3.740537166595458984e-02,7.971513246111363582e-01,4.942613349235330106e-02,8.506182705909921538e-01,8.374494236667009428e-01,8.179940128038997171e-01,5.951761454343795776e-02,4.477837937884032726e-04,3.352825716137886047e-02,3.847572952508926392e-02,5.108420550823211670e-02,6.266715202161995402e-01,5.474103983132304341e-02,7.435485424447159097e-01,7.338903823378927216e-01,6.652732645359187291e-01,5.742735421688209829e-01,1.349266156713314069e-01,5.865511050648093416e-01,3.641521699647172206e-01,3.124377730571293310e-01,4.111003383586240428e-02,3.384491038277656072e-01,3.718153188329691994e-01
11
- 9.000000000000000000e+00,7.904511690139770508e-02,5.650366056915427926e-01,4.784649610519409180e-02,3.360747481594478225e-01,1.000000000000000021e-03,4.097953066229820251e-02,7.262713043019175529e-04,2.636589109897613525e-02,2.707981504499912262e-02,3.672679513692855835e-02,8.011406049594625989e-01,8.184587861598646708e-02,8.525136531793285899e-01,8.391137622169869692e-01,8.216666105301617673e-01,6.167176365852355957e-02,3.881674201693385839e-04,3.504731878638267517e-02,3.860588744282722473e-02,5.474837496876716614e-02,6.288495700353141649e-01,6.697507963883096715e-02,7.402487613039686609e-01,7.330606577567296833e-01,6.682398542183080048e-01,5.746754602825498992e-01,7.563367138892368446e-02,5.882577255826275664e-01,3.789453452980684478e-01,3.127768395502930621e-01,1.541690944289706745e-02,3.394110095896446522e-01,3.878069671015842435e-01
12
- 1.000000000000000000e+01,7.689129561185836792e-02,5.776432205722596969e-01,5.536097660660743713e-02,3.413896982038953642e-01,1.000000000000000021e-03,4.202426597476005554e-02,6.829157355241477489e-04,3.145125880837440491e-02,3.008835576474666595e-02,3.676103428006172180e-02,8.051306298644963499e-01,1.043719686279931530e-01,8.540243584935583998e-01,8.405825732097117475e-01,8.262232117460068803e-01,6.519529968500137329e-02,3.890781954396516085e-04,4.766941443085670471e-02,4.384284839034080505e-02,5.613239854574203491e-02,6.316056430066069316e-01,3.294144611561136693e-02,7.459347545625676368e-01,7.356252546667766401e-01,6.703888185909948616e-01,5.841334619136506756e-01,1.479103288774953062e-01,5.959824190433531399e-01,3.707404388453034993e-01,3.155141152755229772e-01,5.384112108945592057e-02,3.406913478994477162e-01,3.788207564887718370e-01
13
- 1.100000000000000000e+01,7.516384124755859375e-02,5.914801923276814533e-01,4.760764539241790771e-02,3.483196833867099329e-01,1.000000000000000021e-03,4.065796732902526855e-02,7.364017073996365070e-04,2.592927962541580200e-02,2.613618224859237671e-02,3.549587726593017578e-02,8.087561591048159082e-01,7.958508800689907681e-02,8.556992770720274200e-01,8.415271552833636370e-01,8.285827151333120533e-01,5.945572257041931152e-02,3.861584118567407131e-04,3.543989360332489014e-02,3.745276108384132385e-02,5.191485211253166199e-02,6.229969702595520298e-01,6.281145466019341650e-02,7.397628520376408057e-01,7.312510635331408437e-01,6.618655838036759942e-01,6.043028196305714195e-01,1.060545625305443773e-01,6.171694270678337313e-01,3.595423438258240623e-01,3.149798370223247934e-01,2.745311107611971002e-02,3.418123225833971102e-01,3.633849619530142849e-01
14
- 1.200000000000000000e+01,7.663895934820175171e-02,5.997901646405072729e-01,6.157444044947624207e-02,3.468672964819129456e-01,1.000000000000000021e-03,3.920639306306838989e-02,7.200848194770514965e-04,2.613169886171817780e-02,2.673601917922496796e-02,3.415981680154800415e-02,8.116596138560332463e-01,7.243813638614614303e-02,8.570480618383445881e-01,8.442193388473763216e-01,8.330602093696122257e-01,6.306268274784088135e-02,3.563576610758900642e-04,3.395630046725273132e-02,3.661862760782241821e-02,5.546560510993003845e-02,6.304151407170361932e-01,9.210440100728198298e-02,7.433763895498277652e-01,7.354283789871293164e-01,6.671798055380051551e-01,5.979152664819351592e-01,1.571473732632979736e-01,6.082983081070993947e-01,3.693969070139038191e-01,3.118508839069054628e-01,5.883939534569676555e-02,3.370823225553667979e-01,3.799641527374392203e-01
15
- 1.300000000000000000e+01,7.256346195936203003e-02,6.098782952290799919e-01,5.524610355496406555e-02,3.542463643554487618e-01,1.000000000000000021e-03,3.843601420521736145e-02,8.357077604159712791e-04,2.614173851907253265e-02,2.665366418659687042e-02,3.454660996794700623e-02,8.144846763541699897e-01,7.492669135169920114e-02,8.570755867672109618e-01,8.449939293996406864e-01,8.335468067718763141e-01,6.179256364703178406e-02,4.695496463682502508e-04,3.769916668534278870e-02,3.874914720654487610e-02,5.201447010040283203e-02,6.225628455283348517e-01,9.648262583582849217e-02,7.424723665114357773e-01,7.330119067794130805e-01,6.598358610773447097e-01,6.110371157194449676e-01,1.552750881937589311e-01,6.246053003526816916e-01,3.665856355335136985e-01,3.189198594517477781e-01,5.958419476997803971e-02,3.459633092613857985e-01,3.805706792897113333e-01
16
- 1.400000000000000000e+01,7.195486128330230713e-02,6.169794118565439245e-01,4.993401467800140381e-02,3.392562008434108489e-01,1.000000000000000021e-03,4.383291304111480713e-02,7.470826385542750359e-04,2.761650644242763519e-02,2.762824855744838715e-02,4.176798462867736816e-02,8.168654544164080100e-01,8.989541314315742382e-02,8.568601612763651065e-01,8.443383740427758966e-01,8.353573513014010965e-01,6.755137443542480469e-02,4.235668457113206387e-04,3.518375381827354431e-02,3.951911628246307373e-02,6.108940392732620239e-02,6.087498558206331234e-01,7.872335351492198985e-02,7.336116943863905115e-01,7.258784055892653075e-01,6.511477998726845628e-01,6.364816304845191119e-01,1.601424668561550746e-01,6.481992154516424964e-01,3.693914464094895655e-01,3.083383534991637753e-01,5.489005947919788769e-02,3.322964769383936479e-01,3.704947441740806813e-01
17
- 1.500000000000000000e+01,7.075122743844985962e-02,6.323892591565722920e-01,5.910111218690872192e-02,3.447981679203572791e-01,1.000000000000000021e-03,3.858285024762153625e-02,7.073186570778489113e-04,2.751739509403705597e-02,2.719741687178611755e-02,3.248418867588043213e-02,8.215097414902410300e-01,8.740473396015147056e-02,8.617868267862002218e-01,8.467433481412670604e-01,8.414161078562124585e-01,6.167950481176376343e-02,3.493003605399280787e-04,3.442856296896934509e-02,3.846492990851402283e-02,5.255946889519691467e-02,6.165435109975533345e-01,1.490365179083074643e-01,7.385942566868495263e-01,7.251184387416174104e-01,6.612034657340111199e-01,6.239650975157681190e-01,1.682825843195471516e-01,6.347013315289149471e-01,3.780849372705800682e-01,3.048308048922340752e-01,6.156359888784206091e-02,3.311269076812738410e-01,3.888617018279006410e-01
18
- 1.600000000000000000e+01,6.941830366849899292e-02,6.367579718055026428e-01,5.368442833423614502e-02,3.582530429930391169e-01,1.000000000000000021e-03,3.857583552598953247e-02,7.032497669570147991e-04,2.830678969621658325e-02,3.184502571821212769e-02,3.400620073080062866e-02,8.241257037848728206e-01,3.477050389087385507e-02,8.658223416019931173e-01,8.504439691558483005e-01,8.453646623834510931e-01,5.808711051940917969e-02,3.656651824712753296e-04,3.896261751651763916e-02,4.351807758212089539e-02,5.077878013253211975e-02,6.306182923610124114e-01,1.320119897689596977e-01,7.469628697170999088e-01,7.343568750931779299e-01,6.737555269781565492e-01,6.605669086693081171e-01,1.107345599251453594e-01,6.701111607763260425e-01,3.850484142984801350e-01,3.240116505199102659e-01,2.097613555844252825e-02,3.468377814469588705e-01,3.931040748811758245e-01
19
- 1.700000000000000000e+01,6.827208399772644043e-02,6.481063655746305274e-01,5.909809842705726624e-02,3.475036295651883766e-01,1.000000000000000021e-03,3.632614016532897949e-02,7.615093491040170193e-04,2.656896598637104034e-02,2.723619155585765839e-02,3.201103582978248596e-02,8.297603619793432195e-01,9.376895544185308295e-02,8.629920386293501444e-01,8.485437188797493313e-01,8.490615288659018045e-01,6.571350246667861938e-02,4.412145353853702545e-04,3.586862981319427490e-02,3.940239176154136658e-02,5.732339620590209961e-02,6.227764555590322715e-01,1.378991925938969076e-01,7.401593588933328416e-01,7.280205074888045358e-01,6.610552541057019837e-01,6.447963468612175619e-01,1.666588143018238877e-01,6.560366075213918124e-01,3.752414580503733443e-01,3.106617599148195907e-01,6.066518750136679072e-02,3.327274566071386497e-01,3.845188215080038718e-01
20
- 1.800000000000000000e+01,6.528741866350173950e-02,6.577194496671199264e-01,5.303359031677246094e-02,3.426573666809243357e-01,1.000000000000000021e-03,3.519031032919883728e-02,8.660148014314472675e-04,2.426595240831375122e-02,2.568129636347293854e-02,3.062949888408184052e-02,8.334083089643693487e-01,9.549110851307869574e-02,8.655011270354117103e-01,8.519881670885902647e-01,8.522890235698877825e-01,6.542401015758514404e-02,5.303072975948452950e-04,3.759600967168807983e-02,3.885564953088760376e-02,5.690687149763107300e-02,6.160673859807292985e-01,1.228819312296807881e-01,7.393793767864327293e-01,7.311372807855845979e-01,6.573841244302103259e-01,6.635511890365026755e-01,1.782893507093815666e-01,6.744458129873126540e-01,3.759633985952385293e-01,2.978070946697530252e-01,5.052960950363397097e-02,3.214569181446781387e-01,3.780565556255954252e-01
21
- 1.900000000000000000e+01,6.402742117643356323e-02,6.663314603670023040e-01,5.399615690112113953e-02,3.502948722834527406e-01,1.000000000000000021e-03,3.565825149416923523e-02,7.032151916064321995e-04,2.377996407449245453e-02,2.467401139438152313e-02,3.069304488599300385e-02,8.364699034314022619e-01,4.329379913005938080e-02,8.699819300887646767e-01,8.539097249667736422e-01,8.561120357656288871e-01,5.949262902140617371e-02,3.949175297748297453e-04,3.383481875061988831e-02,3.783671557903289795e-02,5.248850211501121521e-02,6.211871966230475550e-01,9.049038679806505059e-02,7.436686035531492323e-01,7.310939059559175002e-01,6.640955940383881373e-01,6.752341535107614368e-01,1.555600875127526039e-01,6.839357817704022624e-01,3.592474331377061292e-01,3.135504575542004679e-01,3.731551735251175533e-02,3.361986741430235126e-01,3.632362839247926800e-01
22
- 2.000000000000000000e+01,6.391590833663940430e-02,6.702765494447086025e-01,5.623666942119598389e-02,3.398963422630594255e-01,5.000000000000000104e-04,3.360487893223762512e-02,7.457135943695902824e-04,2.449966594576835632e-02,2.608380652964115143e-02,2.952133491635322571e-02,8.410428875245757885e-01,7.426608414107375988e-02,8.710016255252929218e-01,8.555186001865857648e-01,8.590339066366601273e-01,6.533356010913848877e-02,4.211958730593323708e-04,3.688760101795196533e-02,3.923613578081130981e-02,5.684324353933334351e-02,6.204506197661368017e-01,1.465976489839573849e-01,7.432043748793344395e-01,7.304846104798988771e-01,6.633517185264909966e-01,6.816812835194285825e-01,1.847466358996510127e-01,6.925770051728732390e-01,3.633060944337104248e-01,3.069802425104836696e-01,5.826202973205892727e-02,3.283637256519282288e-01,3.659883887738401564e-01
23
- 2.100000000000000000e+01,6.346534937620162964e-02,6.756393246410333786e-01,5.631577968597412109e-02,3.463531266388009677e-01,5.000000000000000104e-04,3.620840236544609070e-02,6.411169306375086308e-04,2.427953667938709259e-02,2.642281912267208099e-02,2.981889992952346802e-02,8.435377919936687396e-01,6.254226783423402769e-02,8.728355680857047272e-01,8.578676027967189199e-01,8.610370873207828790e-01,6.033084169030189514e-02,3.083940246142446995e-04,3.329208493232727051e-02,3.785052523016929626e-02,5.135870352387428284e-02,6.260232389540949516e-01,1.727440049436962666e-01,7.438323497894350211e-01,7.325367970944491880e-01,6.640035333519742622e-01,6.862486985766825809e-01,1.899596652027064903e-01,6.942660647571701205e-01,3.646062845673220254e-01,3.127599055248252991e-01,5.709049394248971038e-02,3.329020664847050504e-01,3.689723508809851227e-01
24
- 2.200000000000000000e+01,6.295169889926910400e-02,6.780346181846128584e-01,5.676514282822608948e-02,3.432412128917491922e-01,5.000000000000000104e-04,3.258477523922920227e-02,6.418189150281250477e-04,2.285112440586090088e-02,2.355079539120197296e-02,2.924986369907855988e-02,8.461488834607181486e-01,7.821668715462797017e-02,8.737032069582920668e-01,8.588698701288256521e-01,8.648705321234475552e-01,6.416442990303039551e-02,3.368620236869901419e-04,3.700662776827812195e-02,3.842322528362274170e-02,5.764890834689140320e-02,6.239184557144704657e-01,1.329812942224762040e-01,7.445485726717759878e-01,7.299535929335742024e-01,6.655939715414124214e-01,6.738925058911863220e-01,1.866534561351652355e-01,6.809724404980760371e-01,3.550303821584157027e-01,3.087244545220975134e-01,6.268585859060217536e-02,3.303643966729859494e-01,3.610599724255625920e-01
25
- 2.300000000000000000e+01,6.340137869119644165e-02,6.819126628302820681e-01,5.921486020088195801e-02,3.443549204251065698e-01,5.000000000000000104e-04,3.249366953969001770e-02,7.036869646981358528e-04,2.305609732866287231e-02,2.333074621856212616e-02,2.801304310560226440e-02,8.481754688227125527e-01,7.420298712759899884e-02,8.748908580912736488e-01,8.602331467124194608e-01,8.657512753154646967e-01,6.462273001670837402e-02,3.948313533328473568e-04,3.498147428035736084e-02,3.754160553216934204e-02,5.454888567328453064e-02,6.222505577742677429e-01,1.112495009168680687e-01,7.422312879239346683e-01,7.316176496065041057e-01,6.625461393696500112e-01,6.879837139298451421e-01,1.884064663216354629e-01,6.975923975110001063e-01,3.598806180781239372e-01,3.131195083225753462e-01,5.908461815423164676e-02,3.338137094565032936e-01,3.638793502767964649e-01
26
- 2.400000000000000000e+01,6.068088486790657043e-02,6.878450946415769485e-01,4.875090345740318298e-02,3.495979572437574734e-01,5.000000000000000104e-04,3.169814869761466980e-02,6.767227896489202976e-04,2.255837433040142059e-02,2.353114634752273560e-02,2.739009819924831390e-02,8.510547445059198912e-01,5.374846872144546650e-02,8.749255968808360429e-01,8.601015198365780101e-01,8.678228506331330250e-01,6.364411115646362305e-02,3.961643087677657604e-04,3.456138074398040771e-02,3.772752359509468079e-02,5.551146715879440308e-02,6.160173881401913576e-01,8.228247847731386122e-02,7.416109201567839815e-01,7.295856202774859689e-01,6.587965295021460665e-01,7.094098410444467095e-01,1.810738551774272731e-01,7.160526811785619117e-01,3.710549099030631126e-01,3.097857026537553793e-01,5.953687827699413737e-02,3.321428938740144665e-01,3.750779033508894189e-01
27
- 2.500000000000000000e+01,6.031619012355804443e-02,6.903506270255110877e-01,5.405574291944503784e-02,3.447442788855238360e-01,2.500000000000000052e-04,3.316438943147659302e-02,6.500548915937542915e-04,2.216979861259460449e-02,2.290121093392372131e-02,2.793949656188488007e-02,8.542046975071778681e-01,6.361110056719258832e-02,8.765686321023247185e-01,8.621832627270408222e-01,8.711761400314569359e-01,6.540291756391525269e-02,3.573002177290618420e-04,3.609022498130798340e-02,3.782694414258003235e-02,5.669248849153518677e-02,6.204851417326243679e-01,7.425108800889966554e-02,7.429793642403799137e-01,7.313579265650005379e-01,6.604005397865415627e-01,7.091463938627056152e-01,1.755177398158697111e-01,7.160381087432259317e-01,3.739973357197518555e-01,3.136034318675789967e-01,4.697654178146810910e-02,3.331712712970533730e-01,3.770217931719293536e-01
28
- 2.600000000000000000e+01,5.995142832398414612e-02,6.925230946428559964e-01,5.355495586991310120e-02,3.480550134195181666e-01,2.500000000000000052e-04,3.069485351443290710e-02,6.226680707186460495e-04,2.242030017077922821e-02,2.348239347338676453e-02,2.665530703961849213e-02,8.558991322783168210e-01,6.722210060324336489e-02,8.778781881413928456e-01,8.635594894731659643e-01,8.724823635795103671e-01,6.362553685903549194e-02,3.200087521690875292e-04,3.666429966688156128e-02,3.818965703248977661e-02,5.505591258406639099e-02,6.189094838140793975e-01,8.073578155585253713e-02,7.420012235392176025e-01,7.307268868382146287e-01,6.581269510944628420e-01,7.080442795821242363e-01,1.849471162040889072e-01,7.149346957329287822e-01,3.646297491359525855e-01,3.114020355996068301e-01,6.005806856794900644e-02,3.318427496268828158e-01,3.685824861506054595e-01
29
- 2.700000000000000000e+01,6.004891544580459595e-02,6.944908849671526108e-01,5.587754026055335999e-02,3.438676260494581416e-01,2.500000000000000052e-04,3.003809042274951935e-02,6.549322861246764660e-04,2.234407328069210052e-02,2.300366759300231934e-02,2.640154585242271423e-02,8.583373243555245669e-01,7.155713566229512934e-02,8.784953218374323969e-01,8.636326825438849220e-01,8.734295978982050679e-01,6.402294337749481201e-02,3.640253853518515825e-04,3.432694450020790100e-02,3.740804642438888550e-02,5.422083288431167603e-02,6.161971631897648072e-01,8.228573713468846640e-02,7.423702622157385411e-01,7.297789570118994318e-01,6.577781285912499554e-01,7.158898295450024651e-01,1.826371018872875174e-01,7.228272105761712751e-01,3.651851656905684140e-01,3.116621158277260717e-01,5.258706168642443557e-02,3.320296904305072849e-01,3.672945495827650841e-01
30
- 2.800000000000000000e+01,5.985029786825180054e-02,6.933653109572712747e-01,5.290323868393898010e-02,3.477972857564285425e-01,2.500000000000000052e-04,3.010958060622215271e-02,6.784298457205295563e-04,2.205792628228664398e-02,2.261771820485591888e-02,2.600004151463508606e-02,8.590121304461194596e-01,7.584280348114019743e-02,8.782511458347288524e-01,8.643014307332648194e-01,8.752394966512057062e-01,6.669440120458602905e-02,3.805161977652460337e-04,3.770961984992027283e-02,3.835452720522880554e-02,5.673299357295036316e-02,6.193146975274594901e-01,6.284355805472434386e-02,7.423264996160044982e-01,7.310498206422872958e-01,6.590836649000640923e-01,7.148949942827074189e-01,1.761373511014341364e-01,7.235392770178671329e-01,3.689076861609350000e-01,3.142230032554600427e-01,4.580595596497590016e-02,3.336598231082767985e-01,3.704537116271839348e-01
31
- 2.900000000000000000e+01,6.027451157569885254e-02,6.952877317139389834e-01,5.719231069087982178e-02,3.498466440879555717e-01,2.500000000000000052e-04,2.996739745140075684e-02,6.906153284944593906e-04,2.196395210921764374e-02,2.332052588462829590e-02,2.610654383897781372e-02,8.599966075740608229e-01,6.384125756763842086e-02,8.788804356193581357e-01,8.649910539251590702e-01,8.754611535242440290e-01,6.195820868015289307e-02,3.826686297543346882e-04,3.450454771518707275e-02,3.754130378365516663e-02,5.339368060231208801e-02,6.210951529452348741e-01,7.290467448662243943e-02,7.438691092082123646e-01,7.319431625813266340e-01,6.602139442371556255e-01,7.062189224953392408e-01,1.978882261501348216e-01,7.141785287545380223e-01,3.597772425307489974e-01,3.119770970686542300e-01,6.165929141645252326e-02,3.336554498264133195e-01,3.609263518099656842e-01
32
- 3.000000000000000000e+01,5.975107476115226746e-02,6.979072263829914080e-01,5.740753188729286194e-02,3.478860044026280685e-01,1.250000000000000026e-04,2.965832501649856567e-02,6.463322788476943970e-04,2.169117704033851624e-02,2.342507243156433105e-02,2.658935450017452240e-02,8.616706941163232436e-01,8.684818374757541370e-02,8.792070951012203039e-01,8.654324593950811773e-01,8.772109937120813328e-01,6.756551563739776611e-02,3.524999774526804686e-04,3.672973066568374634e-02,4.049425944685935974e-02,5.925842374563217163e-02,6.156634660885346166e-01,7.274489335521647226e-02,7.397050040545084526e-01,7.273484004209838094e-01,6.546979110931987478e-01,7.174872316051424948e-01,2.001236838568846432e-01,7.250422641568589066e-01,3.675869928830769462e-01,3.167808567355248628e-01,5.998355715627732426e-02,3.365922374717836840e-01,3.681417042652462279e-01
33
- 3.100000000000000000e+01,5.989413708448410034e-02,6.996315708299917224e-01,5.842041969299316406e-02,3.440624748909637542e-01,1.250000000000000026e-04,2.961734123528003693e-02,6.601685890927910805e-04,2.166771516203880310e-02,2.242912910878658295e-02,2.569067478179931641e-02,8.634551951189386454e-01,7.373518933681830201e-02,8.794441878022366543e-01,8.659882946775833146e-01,8.779229845445655211e-01,6.907349079847335815e-02,3.751260228455066681e-04,3.653013333678245544e-02,3.888595849275588989e-02,5.839163437485694885e-02,6.150854278556651122e-01,5.802537093317107486e-02,7.416038716429277367e-01,7.289021462955227459e-01,6.544694563937497600e-01,7.150284744404634685e-01,1.958093039968815674e-01,7.224319784724553273e-01,3.575101864052698808e-01,3.111619973530036498e-01,5.537481546693692747e-02,3.316298114154574428e-01,3.588923688312974369e-01
34
- 3.200000000000000000e+01,5.988566949963569641e-02,6.992238174807317019e-01,5.906623601913452148e-02,3.437693362988800683e-01,1.250000000000000026e-04,2.921968512237071991e-02,6.518669542856514454e-04,2.243194915354251862e-02,2.250174246728420258e-02,2.535537071526050568e-02,8.634937917269943641e-01,7.241407914803683898e-02,8.800457878841222437e-01,8.668112739215135809e-01,8.787675865989853508e-01,6.810630857944488525e-02,3.643998352345079184e-04,3.903735056519508362e-02,3.912184387445449829e-02,5.827815830707550049e-02,6.137886526556087796e-01,5.542250585126605456e-02,7.402951479130426282e-01,7.279087452110757361e-01,6.535956841824600572e-01,7.211685484736051199e-01,2.004402482862068791e-01,7.284616385881582667e-01,3.627351777170784164e-01,3.146118776179099852e-01,5.612319413387305278e-02,3.336771553828679071e-01,3.634197948389671917e-01
35
- 3.300000000000000000e+01,5.856835097074508667e-02,7.027648290299935985e-01,5.547048524022102356e-02,3.456412281611375703e-01,1.250000000000000026e-04,2.878449857234954834e-02,6.364382570609450340e-04,2.215472981333732605e-02,2.266022749245166779e-02,2.541560120880603790e-02,8.653584049018316904e-01,7.378762485576129126e-02,8.803915105983948219e-01,8.672222542835896775e-01,8.795875652758127217e-01,6.736468523740768433e-02,3.489613591227680445e-04,3.795114904642105103e-02,3.924616053700447083e-02,5.776481330394744873e-02,6.131427290828318499e-01,6.886720737441570650e-02,7.408723291529872856e-01,7.279738433197148195e-01,6.535120331035212393e-01,7.287091148572059085e-01,1.953318361844819939e-01,7.354631804938983608e-01,3.657085513064761151e-01,3.124106198230188203e-01,5.519759918668131621e-02,3.316457071033748760e-01,3.667481801553459930e-01
36
- 3.400000000000000000e+01,5.956137180328369141e-02,7.015111903412459826e-01,5.840645357966423035e-02,3.472559838709036728e-01,1.250000000000000026e-04,2.853194996714591980e-02,6.652665906585752964e-04,2.143907174468040466e-02,2.226617559790611267e-02,2.497347444295883179e-02,8.664504678222918477e-01,7.810052549555511481e-02,8.808924968244327136e-01,8.675718060196130343e-01,8.806561384852223950e-01,6.732786446809768677e-02,3.809542104136198759e-04,3.673619404435157776e-02,3.906324505805969238e-02,5.760419741272926331e-02,6.123279002002739446e-01,5.472623831975714087e-02,7.406030033460383466e-01,7.286484246560003264e-01,6.535712251404711681e-01,7.182941983251204254e-01,1.935832419876346022e-01,7.257508294253484094e-01,3.560281604373304876e-01,3.137823674085809533e-01,5.510003893718258550e-02,3.338001031978982125e-01,3.562527470882380642e-01
37
- 3.500000000000000000e+01,5.860019847750663757e-02,7.042685941720621123e-01,5.681401118636131287e-02,3.426948570678647887e-01,6.250000000000000130e-05,2.852385118603706360e-02,6.606067763641476631e-04,2.189970575273036957e-02,2.214490994811058044e-02,2.511783875524997711e-02,8.671265816255088188e-01,6.056621990584185045e-02,8.810910783324238382e-01,8.679369290638075940e-01,8.806758001445005313e-01,6.550332158803939819e-02,3.746953443624079227e-04,3.482795879244804382e-02,3.776421770453453064e-02,5.584855005145072937e-02,6.110341124228617637e-01,6.315143251565817628e-02,7.419664169244968299e-01,7.294856629996301356e-01,6.534160308873315781e-01,7.239311916665248647e-01,1.959648329695103386e-01,7.307705664689683189e-01,3.615640951859294683e-01,3.089042672495866393e-01,5.472984329952629984e-02,3.291205437424626901e-01,3.613079785138656463e-01
38
- 3.600000000000000000e+01,5.800328776240348816e-02,7.058639554149160888e-01,5.480755493044853210e-02,3.457778184903980145e-01,6.250000000000000130e-05,2.823890186846256256e-02,6.616351893171668053e-04,2.135722525417804718e-02,2.201527170836925507e-02,2.483780682086944580e-02,8.680317355375335442e-01,7.052221606300879131e-02,8.811202254223819974e-01,8.680425841307216617e-01,8.813524163465211725e-01,6.680154800415039062e-02,3.719100786838680506e-04,3.627144917845726013e-02,3.855656832456588745e-02,5.701784789562225342e-02,6.110060303671205340e-01,6.047594143758559804e-02,7.416879238707400068e-01,7.291988567987940373e-01,6.538546130955125157e-01,7.258998137388515959e-01,1.937728568144377050e-01,7.325817537818388736e-01,3.635695842678671186e-01,3.106436235208112073e-01,5.457905856713080039e-02,3.309423285163805017e-01,3.634895597850825166e-01
39
- 3.700000000000000000e+01,5.821143090724945068e-02,7.060490856412066085e-01,5.624023824930191040e-02,3.451095352632748137e-01,6.250000000000000130e-05,2.808584831655025482e-02,6.540680187754333019e-04,2.130166999995708466e-02,2.194941602647304535e-02,2.463362924754619598e-02,8.682341968461753101e-01,6.500924821837479606e-02,8.817616510640799987e-01,8.685528059703553350e-01,8.816875118489447827e-01,6.636561453342437744e-02,3.663054085336625576e-04,3.565122187137603760e-02,3.786234930157661438e-02,5.651423335075378418e-02,6.104779852515527239e-01,6.361121036485652136e-02,7.411132242911566692e-01,7.284491089149587317e-01,6.527329296195616415e-01,7.267961672123041117e-01,2.003436210825304820e-01,7.334647103090474740e-01,3.609690281656690036e-01,3.109696475770241664e-01,5.698784088164550454e-02,3.308524113313969228e-01,3.598523409197552092e-01
40
- 3.800000000000000000e+01,5.858330428600311279e-02,7.056686596762939967e-01,5.770558491349220276e-02,3.433030890213782382e-01,6.250000000000000130e-05,2.818251028656959534e-02,6.572849233634769917e-04,2.158987522125244141e-02,2.188542298972606659e-02,2.477445080876350403e-02,8.683318048718714488e-01,6.912307268689021900e-02,8.816780630369920324e-01,8.686194430565205016e-01,8.819711959246643307e-01,6.794276088476181030e-02,3.768432652577757835e-04,3.758767247200012207e-02,3.849954158067703247e-02,5.828216671943664551e-02,6.102192081982115157e-01,5.582113165320360532e-02,7.409289530500461352e-01,7.279011496602600939e-01,6.528841149421040768e-01,7.225824821515871577e-01,2.009601693270930967e-01,7.290785916522167787e-01,3.584961730876087826e-01,3.099739849808434977e-01,5.843306431368194137e-02,3.301805421857078593e-01,3.573073511771043265e-01
41
- 3.900000000000000000e+01,5.797545984387397766e-02,7.068249875867739229e-01,5.639252811670303345e-02,3.423814455008474811e-01,6.250000000000000130e-05,2.825962379574775696e-02,6.672614836134016514e-04,2.137997560203075409e-02,2.201181091368198395e-02,2.472093515098094940e-02,8.688877439572811889e-01,7.045663515915893960e-02,8.818967518319136278e-01,8.687278933009655590e-01,8.821898320856362830e-01,6.889443099498748779e-02,3.906847268808633089e-04,3.601168841123580933e-02,3.854733332991600037e-02,5.851984769105911255e-02,6.075133286488839657e-01,4.874245942327867093e-02,7.411498427443522496e-01,7.275979376853332070e-01,6.509654718424938702e-01,7.300784090103903390e-01,2.012783311615001791e-01,7.370016376253487600e-01,3.628737269023813994e-01,3.108207783531933277e-01,5.479218483292316866e-02,3.306985008802184600e-01,3.614559856482376654e-01
42
- 4.000000000000000000e+01,5.780132487416267395e-02,7.076845682885440914e-01,5.582342669367790222e-02,3.447100529096328581e-01,3.125000000000000065e-05,2.790156006813049316e-02,6.595281884074211121e-04,2.118394710123538971e-02,2.181223779916763306e-02,2.448670379817485809e-02,8.693170685028146494e-01,6.852327820707168615e-02,8.821605652506244732e-01,8.689914506207102596e-01,8.825441761734389434e-01,6.704224646091461182e-02,3.740004613064229488e-04,3.573477268218994141e-02,3.825870901346206665e-02,5.731629207730293274e-02,6.093766484870757338e-01,4.931480233138274549e-02,7.411405513262409039e-01,7.274726165098253094e-01,6.522141273082791013e-01,7.315319166110029192e-01,2.013658223387820856e-01,7.382315810834915171e-01,3.636219105701390397e-01,3.116874921760072192e-01,5.699874742054832771e-02,3.315820495724637329e-01,3.624326583095542897e-01
43
- 4.100000000000000000e+01,5.828995257616043091e-02,7.066286048497344430e-01,5.706362426280975342e-02,3.459699185008684452e-01,3.125000000000000065e-05,2.797951176762580872e-02,6.603868096135556698e-04,2.129152789711952209e-02,2.179086208343505859e-02,2.451370097696781158e-02,8.692540251607590029e-01,7.082228896083442304e-02,8.820935766079378570e-01,8.691655499903775395e-01,8.825804557586213184e-01,6.761421263217926025e-02,3.823030274361371994e-04,3.566716611385345459e-02,3.842425718903541565e-02,5.755558609962463379e-02,6.106474249991158398e-01,5.239089712007670430e-02,7.410667829313017929e-01,7.282225875622945210e-01,6.529481295853148959e-01,7.287269169212345910e-01,2.018144693099795106e-01,7.355872323539961455e-01,3.601301513283056144e-01,3.128390640007575607e-01,5.681476265570255257e-02,3.325660584907124684e-01,3.581642467692969234e-01
44
- 4.200000000000000000e+01,5.767818540334701538e-02,7.083593697349319651e-01,5.567393824458122253e-02,3.450525812276888926e-01,3.125000000000000065e-05,2.789531648159027100e-02,6.614017183892428875e-04,2.144028805196285248e-02,2.214754559099674225e-02,2.443265542387962341e-02,8.698934244766330925e-01,6.995147792279096666e-02,8.823073802449288294e-01,8.692241960798464673e-01,8.828946757919553523e-01,6.766057014465332031e-02,3.737104416359215975e-04,3.752689063549041748e-02,3.932658955454826355e-02,5.757582560181617737e-02,6.091655170830136967e-01,5.521279379669469939e-02,7.410967425174687095e-01,7.276310310991033203e-01,6.519055576492363002e-01,7.321361008107423185e-01,2.008564024727396979e-01,7.385725644515125365e-01,3.631703024360398713e-01,3.104354000475741393e-01,5.580264291672666632e-02,3.302157017348057444e-01,3.622790181555390343e-01
45
- 4.300000000000000000e+01,5.759618058800697327e-02,7.085554611848238205e-01,5.555627495050430298e-02,3.450387041283258238e-01,3.125000000000000065e-05,2.773241326212882996e-02,6.654032622464001179e-04,2.112060785293579102e-02,2.173044160008430481e-02,2.436096593737602234e-02,8.700636425271700558e-01,6.696098733771610467e-02,8.824468771804726552e-01,8.695095127901067977e-01,8.830525099645826215e-01,6.669093668460845947e-02,3.802514693234115839e-04,3.605126589536666870e-02,3.825873881578445435e-02,5.703527480363845825e-02,6.093725491128805238e-01,5.349416532629409221e-02,7.415783275786098638e-01,7.283704175611198650e-01,6.525071638422642595e-01,7.303498572629313346e-01,2.002654578875795177e-01,7.371714364026081823e-01,3.619188686608916794e-01,3.099812941028922131e-01,5.565063753053853951e-02,3.299581036331036277e-01,3.605957982918240323e-01
46
- 4.400000000000000000e+01,5.822703987360000610e-02,7.075455458741118919e-01,5.765088647603988647e-02,3.462342105836703765e-01,3.125000000000000065e-05,2.777497842907905579e-02,6.628511473536491394e-04,2.117313444614410400e-02,2.174271829426288605e-02,2.438138052821159363e-02,8.700455257953249033e-01,6.796645807946677387e-02,8.824540086649047543e-01,8.695612044219637182e-01,8.831718854438014299e-01,6.752336025238037109e-02,3.799579862970858812e-04,3.667435050010681152e-02,3.855218365788459778e-02,5.765973031520843506e-02,6.095982785396706349e-01,5.466589094564063694e-02,7.405244689229176780e-01,7.274844330826566452e-01,6.515848106624280156e-01,7.295350220072881475e-01,2.059644071069134108e-01,7.364478398577973817e-01,3.621229527844233020e-01,3.128254244499993897e-01,5.804053561308113868e-02,3.322142502772474915e-01,3.604570526307017686e-01
47
- 4.500000000000000000e+01,5.769359692931175232e-02,7.088952219240789443e-01,5.611725896596908569e-02,3.457854467122016828e-01,1.562500000000000033e-05,2.767258509993553162e-02,6.651677540503442287e-04,2.108610421419143677e-02,2.170417644083499908e-02,2.434734068810939789e-02,8.704508903408876286e-01,6.714432446217231953e-02,8.826457559752397541e-01,8.697000307649652706e-01,8.833458586902458443e-01,6.731349974870681763e-02,3.842133446596562862e-04,3.603804111480712891e-02,3.825381025671958923e-02,5.746502429246902466e-02,6.087264313957002626e-01,5.315064640925604850e-02,7.408080425410550340e-01,7.277592053589144738e-01,6.514728543804696459e-01,7.327779654086312711e-01,2.024004856589385615e-01,7.395825228860876122e-01,3.616162917402771515e-01,3.116594983302576893e-01,5.662300786174526840e-02,3.312077718562869699e-01,3.598959023096113818e-01
48
- 4.600000000000000000e+01,5.745465308427810669e-02,7.093090052191836081e-01,5.534964427351951599e-02,3.458918012321102187e-01,1.562500000000000033e-05,2.762340381741523743e-02,6.656824261881411076e-04,2.107143960893154144e-02,2.172498032450675964e-02,2.431985363364219666e-02,8.705947439892899320e-01,6.757555349179884818e-02,8.827781726566783504e-01,8.697761021354575295e-01,8.835025464756910418e-01,6.718486547470092773e-02,3.804236184805631638e-04,3.601611778140068054e-02,3.843561187386512756e-02,5.741376802325248718e-02,6.077893411843272586e-01,5.001972933418522699e-02,7.405460099978279365e-01,7.271469445069219661e-01,6.504525764632010931e-01,7.360548475941092184e-01,2.017586109909301761e-01,7.427582127472494022e-01,3.626873048749004180e-01,3.125231373892896625e-01,5.544993939067584948e-02,3.317397689311892273e-01,3.605279768709976351e-01
49
- 4.700000000000000000e+01,5.755202844738960266e-02,7.094526245926545682e-01,5.603271350264549255e-02,3.436986831991284408e-01,1.562500000000000033e-05,2.761166542768478394e-02,6.623791414313018322e-04,2.107540890574455261e-02,2.169752866029739380e-02,2.426778897643089294e-02,8.706998947355369944e-01,6.624190698800845600e-02,8.827928573609269414e-01,8.697687837970961011e-01,8.835294958386371578e-01,6.708716601133346558e-02,3.838978882413357496e-04,3.574781119823455811e-02,3.820417821407318115e-02,5.716274306178092957e-02,6.069471741938499143e-01,5.111831078369336728e-02,7.407809113987623428e-01,7.273025784631999002e-01,6.501304704598123152e-01,7.336412752850877128e-01,2.038583258365815054e-01,7.401499917193310463e-01,3.608014601519458076e-01,3.102317670183311615e-01,5.707784324779167223e-02,3.296459424914170988e-01,3.588569573464798212e-01
50
- 4.800000000000000000e+01,5.773221328854560852e-02,7.091742106206258001e-01,5.671997740864753723e-02,3.434390243436385992e-01,1.562500000000000033e-05,2.763002924621105194e-02,6.727011641487479210e-04,2.109275013208389282e-02,2.168871462345123291e-02,2.432486228644847870e-02,8.707913166638922009e-01,6.690043756836742961e-02,8.827819345601858325e-01,8.698173358497038876e-01,8.835564698481661594e-01,6.732903420925140381e-02,3.897958667948842049e-04,3.587563335895538330e-02,3.841435164213180542e-02,5.726577341556549072e-02,6.071362488653184730e-01,5.433829709532146668e-02,7.409303220453330452e-01,7.277695362485639974e-01,6.503840816440107364e-01,7.320757418118554183e-01,2.053662484820551470e-01,7.386397963519978349e-01,3.624746603871766282e-01,3.096988676000239082e-01,5.772596753198275948e-02,3.292777611974421204e-01,3.606933941165191837e-01
51
- 4.900000000000000000e+01,5.744978040456771851e-02,7.093154408998256777e-01,5.561470240354537964e-02,3.444123957010851722e-01,1.562500000000000033e-05,2.764600142836570740e-02,6.590643315576016903e-04,2.105278149247169495e-02,2.165265567600727081e-02,2.437323331832885742e-02,8.708487731923589825e-01,6.608317857522315319e-02,8.829438927414984262e-01,8.700246898896746295e-01,8.837622376542761948e-01,6.782389432191848755e-02,3.755304787773638964e-04,3.631832078099250793e-02,3.838998079299926758e-02,5.780165642499923706e-02,6.078100862332325161e-01,5.935557787600220098e-02,7.407617383560607394e-01,7.274707542544325278e-01,6.510144586348578510e-01,7.331774862231902334e-01,2.021975615192862230e-01,7.396517237089234031e-01,3.613093074463315513e-01,3.106812137270248964e-01,5.521973921801121704e-02,3.298824469686564287e-01,3.589976688376887992e-01
52
- 5.000000000000000000e+01,5.744143947958946228e-02,7.096113973815068032e-01,5.578177794814109802e-02,3.440315274742876128e-01,7.812500000000000163e-06,2.758510597050189972e-02,6.609005504287779331e-04,2.105022780597209930e-02,2.167608402669429779e-02,2.424302324652671814e-02,8.709811809435680230e-01,6.677995588459902099e-02,8.829151597688368325e-01,8.699930227440740849e-01,8.838281586068192919e-01,6.783218681812286377e-02,3.785888548009097576e-04,3.638352081179618835e-02,3.856224194169044495e-02,5.775172635912895203e-02,6.077613343840500004e-01,5.516893543981719189e-02,7.409062808528776856e-01,7.277482362042895225e-01,6.509364047043794121e-01,7.337173362346266492e-01,2.039715474619706592e-01,7.402187135041840005e-01,3.617603782530501300e-01,3.102615198805290797e-01,5.667052811720077465e-02,3.296842553492516092e-01,3.594805250401529872e-01
53
- 5.100000000000000000e+01,5.746058002114295959e-02,7.096573657180469397e-01,5.589713528752326965e-02,3.443466027509430316e-01,7.812500000000000163e-06,2.756033092737197876e-02,6.627957336604595184e-04,2.108188718557357788e-02,2.164329215884208679e-02,2.425745688378810883e-02,8.710167152754775266e-01,6.569096697135820562e-02,8.829446448338443831e-01,8.700608138395168334e-01,8.837941146224719269e-01,6.732848286628723145e-02,3.802987630479037762e-04,3.573525324463844299e-02,3.827473148703575134e-02,5.732126533985137939e-02,6.076442981306482416e-01,5.461907718325954147e-02,7.409988985411296580e-01,7.278288417045507552e-01,6.508177928270638679e-01,7.343908303498809520e-01,2.035377593945188468e-01,7.409945037241421062e-01,3.624381373643129622e-01,3.106487420758040074e-01,5.555166539029344347e-02,3.299577067962531918e-01,3.602340987901400671e-01
54
- 5.200000000000000000e+01,5.738569423556327820e-02,7.099512803252361337e-01,5.574252083897590637e-02,3.441396789367732190e-01,7.812500000000000163e-06,2.754599787294864655e-02,6.607761024497449398e-04,2.102622576057910919e-02,2.166540734469890594e-02,2.420506067574024200e-02,8.711537022378423867e-01,6.543294074551236905e-02,8.830061080640903004e-01,8.700840736724950730e-01,8.839310295663087569e-01,6.759815663099288940e-02,3.784334985539317131e-04,3.612096607685089111e-02,3.848814219236373901e-02,5.745160952210426331e-02,6.075964732096398313e-01,5.593347406072308320e-02,7.409179484254615478e-01,7.276072011916628890e-01,6.509216219801829961e-01,7.347097594234488493e-01,2.026618912124049376e-01,7.412273077491132733e-01,3.618820826802758295e-01,3.101322328973821763e-01,5.558650150301541748e-02,3.295844654713537825e-01,3.596749711973725172e-01
55
- 5.300000000000000000e+01,5.726695433259010315e-02,7.099092009597289810e-01,5.506875365972518921e-02,3.448645051657324156e-01,7.812500000000000163e-06,2.753267809748649597e-02,6.609478732571005821e-04,2.107972465455532074e-02,2.165041863918304443e-02,2.422757446765899658e-02,8.711347038217133187e-01,6.577574063057256559e-02,8.830393593638441452e-01,8.701435287650705064e-01,8.838324106851745965e-01,6.722164154052734375e-02,3.794580115936696529e-04,3.561870753765106201e-02,3.819055855274200439e-02,5.719408392906188965e-02,6.070218216437966996e-01,5.198784401657766813e-02,7.408011083228955851e-01,7.275787722839508476e-01,6.501541018088694557e-01,7.370305291565372174e-01,2.022317206845467563e-01,7.435495241228671226e-01,3.627283076848499266e-01,3.114589174387702997e-01,5.477136767835608794e-02,3.306253956319393228e-01,3.602222168156140047e-01
56
- 5.400000000000000000e+01,5.749739706516265869e-02,7.099768000574888660e-01,5.629877001047134399e-02,3.435642449469926141e-01,7.812500000000000163e-06,2.749819681048393250e-02,6.618950865231454372e-04,2.104782499372959137e-02,2.163715288043022156e-02,2.419226616621017456e-02,8.712632372035862272e-01,6.457463255110587608e-02,8.830277765559811920e-01,8.700969804745376512e-01,8.839593674779178123e-01,6.745953857898712158e-02,3.796762612182646990e-04,3.572872281074523926e-02,3.828851878643035889e-02,5.735072493553161621e-02,6.070244758575673583e-01,5.501328047543069949e-02,7.407822708789688626e-01,7.273625368430606253e-01,6.504314565645372692e-01,7.338703054532845771e-01,2.037055377244740284e-01,7.404006892535789364e-01,3.608267107595114487e-01,3.097512729440159429e-01,5.595773492613229094e-02,3.293947202124622997e-01,3.586948945963912561e-01
57
- 5.500000000000000000e+01,5.744592472910881042e-02,7.099502447958720985e-01,5.605857446789741516e-02,3.440822106348935816e-01,3.906250000000000081e-06,2.750833705067634583e-02,6.614544545300304890e-04,2.101761102676391602e-02,2.164744585752487183e-02,2.418000809848308563e-02,8.712630698303130972e-01,6.575795292532818359e-02,8.830568487743437611e-01,8.701431496428014833e-01,8.840183839907495011e-01,6.754182279109954834e-02,3.790056507568806410e-04,3.599338233470916748e-02,3.844262659549713135e-02,5.744356289505958557e-02,6.071553680617974846e-01,5.538666302768226191e-02,7.405784465696630958e-01,7.272834916682324735e-01,6.502788928308976812e-01,7.344872764498233675e-01,2.039611062079114967e-01,7.410120574928570347e-01,3.622459033902148406e-01,3.102944620565963318e-01,5.621457463940952859e-02,3.297463352021089977e-01,3.599476293087465750e-01
58
- 5.600000000000000000e+01,5.746303126215934753e-02,7.099635005156946788e-01,5.614554136991500854e-02,3.439336098482917992e-01,3.906250000000000081e-06,2.751494385302066803e-02,6.622456712648272514e-04,2.102038636803627014e-02,2.164217457175254822e-02,2.418814785778522491e-02,8.712946808144153010e-01,6.554859784255236133e-02,8.830379431413794000e-01,8.701335894882702426e-01,8.840337577906892674e-01,6.765138357877731323e-02,3.801561833824962378e-04,3.606564179062843323e-02,3.847213834524154663e-02,5.756577849388122559e-02,6.070577713647951068e-01,5.566291200979119752e-02,7.405575932262146166e-01,7.272849178545600646e-01,6.502626324498993471e-01,7.340726998346212007e-01,2.033669902555134135e-01,7.406457469733201959e-01,3.616563648407477016e-01,3.100256766548040166e-01,5.593236919252329853e-02,3.295938089942700433e-01,3.593854559770280344e-01
59
- 5.700000000000000000e+01,5.748935043811798096e-02,7.099813197060538261e-01,5.631250515580177307e-02,3.437170825525101003e-01,3.906250000000000081e-06,2.751132473349571228e-02,6.638185004703700542e-04,2.102210000157356262e-02,2.163031511008739471e-02,2.418697997927665710e-02,8.713340250157509681e-01,6.594080996969724218e-02,8.830353768040084539e-01,8.701472339562361658e-01,8.840857498130265668e-01,6.779824197292327881e-02,3.823727893177419901e-04,3.607602044939994812e-02,3.841697052121162415e-02,5.771909281611442566e-02,6.070268507093916632e-01,5.523025315558896531e-02,7.405728137837412905e-01,7.272655890373751486e-01,6.502818643337041626e-01,7.338888727480074037e-01,2.034808955781119699e-01,7.404993973829826492e-01,3.612731567732452298e-01,3.100130420248641827e-01,5.618285865226124026e-02,3.295905327230008175e-01,3.589679277044617312e-01
60
- 5.800000000000000000e+01,5.745500326156616211e-02,7.100520055173937806e-01,5.618080496788024902e-02,3.439299963749520295e-01,3.906250000000000081e-06,2.748979255557060242e-02,6.636264733970165253e-04,2.101479843258857727e-02,2.162936888635158539e-02,2.416725456714630127e-02,8.714037647121626318e-01,6.638927770915006854e-02,8.830848436809125568e-01,8.701791992659164698e-01,8.841333273198243026e-01,6.774181127548217773e-02,3.816561074927449226e-04,3.600073605775833130e-02,3.844484314322471619e-02,5.765548348426818848e-02,6.069957855584394713e-01,5.484584515529485060e-02,7.405033353968931698e-01,7.272297042634101594e-01,6.501928909772668463e-01,7.344980635267722713e-01,2.035563248389018476e-01,7.410796593347169781e-01,3.620049125807568990e-01,3.102097996031332738e-01,5.611079887527249677e-02,3.297294828354042084e-01,3.595955216620245198e-01
61
- 5.900000000000000000e+01,5.758251622319221497e-02,7.099037073395471076e-01,5.662655457854270935e-02,3.439635165985814869e-01,3.906250000000000081e-06,2.749112620949745178e-02,6.636538892053067684e-04,2.101693116128444672e-02,2.163022197782993317e-02,2.416982501745223999e-02,8.714278858185701138e-01,6.632164256115566370e-02,8.830627070952667967e-01,8.701511792836306158e-01,8.841536428611110887e-01,6.779840588569641113e-02,3.819456615019589663e-04,3.601596131920814514e-02,3.840891271829605103e-02,5.769450217485427856e-02,6.071504341831778229e-01,5.515850220565964718e-02,7.403165391151330654e-01,7.270850253579197453e-01,6.502341220190568372e-01,7.335112982184727182e-01,2.046558771622793427e-01,7.401405102076468179e-01,3.614544687204442885e-01,3.105029894514958477e-01,5.667124680036145917e-02,3.300118707392660222e-01,3.589813260740831735e-01
62
- 6.000000000000000000e+01,5.750261992216110229e-02,7.100298328197944819e-01,5.636451765894889832e-02,3.438838045476164673e-01,1.953125000000000041e-06,2.748706191778182983e-02,6.626528920605778694e-04,2.101116254925727844e-02,2.163318917155265808e-02,2.416505478322505951e-02,8.714440609389956238e-01,6.643742194557779313e-02,8.830893986407003515e-01,8.701764642334249844e-01,8.841600853386872849e-01,6.775550544261932373e-02,3.810750204138457775e-04,3.611311316490173340e-02,3.846086561679840088e-02,5.765195190906524658e-02,6.070095771636930815e-01,5.501938157445027816e-02,7.403823023680670135e-01,7.271268339545726711e-01,6.501884122178752667e-01,7.338650128192699684e-01,2.041193825277973217e-01,7.404693941472164642e-01,3.614324956331061989e-01,3.102934198398368260e-01,5.633098881812795278e-02,3.297916667773376220e-01,3.590016348394912127e-01
63
- 6.100000000000000000e+01,5.747239291667938232e-02,7.100768114204669690e-01,5.626931041479110718e-02,3.439365447182763935e-01,1.953125000000000041e-06,2.747968956828117371e-02,6.619442719966173172e-04,2.100781723856925964e-02,2.162843756377696991e-02,2.416320890188217163e-02,8.714569874907842628e-01,6.659394519421914238e-02,8.831089530212642424e-01,8.701930727936474375e-01,8.841699136549296956e-01,6.778241693973541260e-02,3.806125023402273655e-04,3.609474375844001770e-02,3.844692185521125793e-02,5.768891796469688416e-02,6.069438787520539114e-01,5.535387902445150499e-02,7.403574306710092312e-01,7.270986131176008227e-01,6.501294715026701976e-01,7.342165450768297630e-01,2.039093186302871619e-01,7.408261259316137881e-01,3.616949636917873856e-01,3.103177510311216358e-01,5.614140965732253297e-02,3.297947445288641477e-01,3.592100906461591636e-01
64
- 6.200000000000000000e+01,5.746459960937500000e-02,7.100954651034809828e-01,5.624506995081901550e-02,3.439711506156754584e-01,1.953125000000000041e-06,2.747949771583080292e-02,6.624918896704912186e-04,2.100725285708904266e-02,2.163183130323886871e-02,2.416116371750831604e-02,8.714739594260987321e-01,6.672604597846470975e-02,8.831149008602924289e-01,8.702029745773181002e-01,8.841927019913112806e-01,6.782055646181106567e-02,3.808027831837534904e-04,3.615681082010269165e-02,3.848693892359733582e-02,5.771644786000251770e-02,6.069152541184574812e-01,5.572132985734255806e-02,7.403267779546696126e-01,7.270852823131191967e-01,6.500916139521180082e-01,7.342327062846002450e-01,2.042021921463162415e-01,7.408494357672560371e-01,3.614715809750129738e-01,3.102934626020606435e-01,5.644253990570220758e-02,3.297711858555714692e-01,3.590089602660738732e-01
65
- 6.300000000000000000e+01,5.744896084070205688e-02,7.101646034684171438e-01,5.623002350330352783e-02,3.438030499367885628e-01,1.953125000000000041e-06,2.747701667249202728e-02,6.622786750085651875e-04,2.100756950676441193e-02,2.162049338221549988e-02,2.416110970079898834e-02,8.714915366816392561e-01,6.672908403116362130e-02,8.831199580932652671e-01,8.702139938330905045e-01,8.841768239195370027e-01,6.778392195701599121e-02,3.816551761701703072e-04,3.604271262884140015e-02,3.843371570110321045e-02,5.768740922212600708e-02,6.067463913864807878e-01,5.468604846667494968e-02,7.403845391950052424e-01,7.271417171701070048e-01,6.500002166688594318e-01,7.346104551545374894e-01,2.041861597972399467e-01,7.412228067091590766e-01,3.616012191394945008e-01,3.102571322292139167e-01,5.626679479673205725e-02,3.297149303789014274e-01,3.591025463489887493e-01
66
- 6.400000000000000000e+01,5.737597122788429260e-02,7.102790380416147409e-01,5.594910308718681335e-02,3.440291630858124949e-01,1.953125000000000041e-06,2.745947800576686859e-02,6.628466653637588024e-04,2.100154012441635132e-02,2.161486633121967316e-02,2.414684183895587921e-02,8.715167208854792680e-01,6.663791033460179924e-02,8.831506117808006762e-01,8.702436333903951216e-01,8.842038666303311256e-01,6.763724237680435181e-02,3.813193761743605137e-04,3.602033108472824097e-02,3.839222714304924011e-02,5.756296962499618530e-02,6.066215904891890665e-01,5.462397042410631343e-02,7.403788276418387104e-01,7.271107596709052157e-01,6.499326042803451609e-01,7.350360101939639179e-01,2.037992325926081538e-01,7.416203556735097013e-01,3.614885406456637695e-01,3.104073318576248686e-01,5.628447374123765612e-02,3.298390288829440853e-01,3.589506423431298443e-01
67
- 6.500000000000000000e+01,5.742330849170684814e-02,7.102498495237604681e-01,5.615479126572608948e-02,3.438248638467049645e-01,9.765625000000000203e-07,2.746735513210296631e-02,6.624046945944428444e-04,2.100297436118125916e-02,2.161747589707374573e-02,2.415156923234462738e-02,8.715226440095005245e-01,6.662532191158315742e-02,8.831408454158657939e-01,8.702381230362054021e-01,8.842093549178463574e-01,6.771884113550186157e-02,3.814140218310058117e-04,3.604901954531669617e-02,3.842851519584655762e-02,5.762577056884765625e-02,6.066099091433172497e-01,5.480663671696575645e-02,7.403786493495110310e-01,7.271063989182965370e-01,6.499111084779034542e-01,7.347419887624648815e-01,2.040652269559434795e-01,7.413429098350969859e-01,3.615473024798027124e-01,3.101995896802399177e-01,5.639015446311485685e-02,3.296759007834815236e-01,3.590577368393563784e-01
68
- 6.600000000000000000e+01,5.743084847927093506e-02,7.102458390551315004e-01,5.619189888238906860e-02,3.437223281006636655e-01,9.765625000000000203e-07,2.746964804828166962e-02,6.623897934332489967e-04,2.100237645208835602e-02,2.161710336804389954e-02,2.415246516466140747e-02,8.715239695041208101e-01,6.680110964960517261e-02,8.831428916515681182e-01,8.702413473815729938e-01,8.842140637364832578e-01,6.778001040220260620e-02,3.814703086391091347e-04,3.608370944857597351e-02,3.843955323100090027e-02,5.767344683408737183e-02,6.065907863892668894e-01,5.480893479841097982e-02,7.403868191174977076e-01,7.271154265714514509e-01,6.499082967719681525e-01,7.345655244079198898e-01,2.041366339695062093e-01,7.411724055749353557e-01,3.615937466484192009e-01,3.100744881473868619e-01,5.643906446673701421e-02,3.295617429962663381e-01,3.591223140172120076e-01
69
- 6.700000000000000000e+01,5.742966383695602417e-02,7.102276910859203207e-01,5.616797134280204773e-02,3.438750019743646580e-01,9.765625000000000203e-07,2.746456302702426910e-02,6.620165659114718437e-04,2.100214920938014984e-02,2.161811105906963348e-02,2.414805628359317780e-02,8.715300259666258764e-01,6.661088202982859419e-02,8.831511863294048137e-01,8.702485664725656545e-01,8.842212554124229129e-01,6.773312389850616455e-02,3.811963542830199003e-04,3.603926673531532288e-02,3.844437748193740845e-02,5.762740597128868103e-02,6.066108230143272273e-01,5.494236575551991741e-02,7.403686133376564671e-01,7.271292363145139825e-01,6.499158466610417406e-01,7.345826176530317886e-01,2.041374430034333942e-01,7.411916060699529352e-01,3.615420531365333146e-01,3.101976754017236049e-01,5.635779141127397751e-02,3.296675423609912170e-01,3.590533129951267166e-01
70
- 6.800000000000000000e+01,5.744453147053718567e-02,7.101912353749495477e-01,5.621329694986343384e-02,3.439541669591849593e-01,9.765625000000000203e-07,2.746905013918876648e-02,6.624023662880063057e-04,2.100083045661449432e-02,2.161679789423942566e-02,2.414926886558532715e-02,8.715216710079438656e-01,6.686462762640484059e-02,8.831522530561179973e-01,8.702580736671434725e-01,8.842256963318463336e-01,6.779894232749938965e-02,3.813615185208618641e-04,3.608727455139160156e-02,3.845654800534248352e-02,5.767928063869476318e-02,6.066489740043111700e-01,5.514803987322123430e-02,7.403531417310654783e-01,7.271265485903766201e-01,6.499131376244264846e-01,7.345535053173876117e-01,2.041591845041441367e-01,7.411710204180917971e-01,3.615164223696791246e-01,3.102713116455638187e-01,5.637762968686108045e-02,3.297313494457086436e-01,3.590069624682669658e-01
71
- 6.900000000000000000e+01,5.741333588957786560e-02,7.102344719011565166e-01,5.610705539584159851e-02,3.438906163069733735e-01,9.765625000000000203e-07,2.746745012700557709e-02,6.620911299251019955e-04,2.099974639713764191e-02,2.161715179681777954e-02,2.414671331644058228e-02,8.715295877193216523e-01,6.683181535985072141e-02,8.831582617509405875e-01,8.702657265821421850e-01,8.842268641732424772e-01,6.775505840778350830e-02,3.812537470366805792e-04,3.609113395214080811e-02,3.846439346671104431e-02,5.765100196003913879e-02,6.065827827516634407e-01,5.499353293042178598e-02,7.403678755139304624e-01,7.271498666099009967e-01,6.498810361927637169e-01,7.346256213232769117e-01,2.040405239921606606e-01,7.412257716723893441e-01,3.614186606072066765e-01,3.101862189384682611e-01,5.631335244027101811e-02,3.296347005457743173e-01,3.589207643789073532e-01
72
- 7.000000000000000000e+01,5.741957575082778931e-02,7.102338759210974350e-01,5.613540112972259521e-02,3.438565519280359362e-01,4.882812500000000102e-07,2.746713533997535706e-02,6.622620276175439358e-04,2.100080437958240509e-02,2.161522209644317627e-02,2.414687536656856537e-02,8.715329194263845558e-01,6.680114083732613395e-02,8.831555895355329699e-01,8.702661176641476315e-01,8.842277415884156611e-01,6.776099652051925659e-02,3.813746734522283077e-04,3.607115894556045532e-02,3.845753893256187439e-02,5.765837803483009338e-02,6.065597806446760831e-01,5.491732931327965306e-02,7.403657343645365652e-01,7.271482386203614867e-01,6.498592251183369362e-01,7.346088142577709146e-01,2.040645957742745487e-01,7.412128134982870487e-01,3.613981219948474433e-01,3.101717848451308046e-01,5.628795248480193691e-02,3.296199044585383930e-01,3.588923755329502807e-01
73
- 7.100000000000000000e+01,5.743572488427162170e-02,7.102025293807017192e-01,5.619068816304206848e-02,3.438917688213767865e-01,4.882812500000000102e-07,2.746425755321979523e-02,6.623473600484430790e-04,2.099950797855854034e-02,2.161705493927001953e-02,2.414534427225589752e-02,8.715370933640044226e-01,6.680158793679501217e-02,8.831599715635095604e-01,8.702678064043369854e-01,8.842362622394025484e-01,6.778512895107269287e-02,3.813570365309715271e-04,3.609488159418106079e-02,3.846958279609680176e-02,5.767019465565681458e-02,6.066114257530637044e-01,5.517179878912516905e-02,7.403524103149407010e-01,7.271451906747401495e-01,6.498958894936933683e-01,7.344465778515022469e-01,2.040861550498316579e-01,7.410578372863244612e-01,3.613194215880365379e-01,3.102081560806959071e-01,5.626919462228825025e-02,3.296579241971322460e-01,3.588155066651922964e-01
74
- 7.200000000000000000e+01,5.742456763982772827e-02,7.102330874794131610e-01,5.615560337901115417e-02,3.438496383175498883e-01,4.882812500000000102e-07,2.746473066508769989e-02,6.623619119636714458e-04,2.099896781146526337e-02,2.161580696702003479e-02,2.414513006806373596e-02,8.715425118069819455e-01,6.680827397049549088e-02,8.831623669097378171e-01,8.702705559340625507e-01,8.842386380628706188e-01,6.778574734926223755e-02,3.813081129919737577e-04,3.610007464885711670e-02,3.846602514386177063e-02,5.767326802015304565e-02,6.065865652272027786e-01,5.515785854182109205e-02,7.403676238964812528e-01,7.271459364112643486e-01,6.498823077501467704e-01,7.345185652658234865e-01,2.040652932886340698e-01,7.411264224684612767e-01,3.614207807064758193e-01,3.101370057278158288e-01,5.628216519104396742e-02,3.295882582440780029e-01,3.589288847564161000e-01
75
- 7.300000000000000000e+01,5.744622275233268738e-02,7.102085440379439785e-01,5.624263361096382141e-02,3.438078231277241215e-01,4.882812500000000102e-07,2.746675536036491394e-02,6.622779183089733124e-04,2.099933102726936340e-02,2.161627635359764099e-02,2.414718270301818848e-02,8.715486005796245772e-01,6.681960083939397776e-02,8.831601373665650678e-01,8.702650369683572196e-01,8.842437072602662429e-01,6.783352047204971313e-02,3.813345101661980152e-04,3.611586242914199829e-02,3.846872225403785706e-02,5.771346017718315125e-02,6.066047430300115506e-01,5.530605698219152183e-02,7.403510049412492799e-01,7.271327409855227186e-01,6.498895821470151990e-01,7.343251359196801831e-01,2.042279057627584826e-01,7.409417212156231836e-01,3.613473336454958718e-01,3.101037521015050369e-01,5.636500249825535103e-02,3.295673234357141546e-01,3.588698875484411244e-01
76
- 7.400000000000000000e+01,5.745136737823486328e-02,7.101988652753689069e-01,5.626487359404563904e-02,3.437961959620443042e-01,4.882812500000000102e-07,2.746806852519512177e-02,6.624182569794356823e-04,2.100029215216636658e-02,2.161525748670101166e-02,2.414771541953086853e-02,8.715464962571461971e-01,6.673250778543272921e-02,8.831571289386703771e-01,8.702652558859645948e-01,8.842419306220477004e-01,6.784765422344207764e-02,3.814064257312566042e-04,3.609406203031539917e-02,3.846426680684089661e-02,5.771929025650024414e-02,6.065959654872017381e-01,5.517422321250689543e-02,7.403405030580366697e-01,7.271262946793806137e-01,6.498627731350653525e-01,7.343356287452170728e-01,2.042494631860750920e-01,7.409576015036694052e-01,3.613675009667304261e-01,3.101217650133613057e-01,5.629325226478007482e-02,3.295818792319797352e-01,3.588877992376497961e-01
77
- 7.500000000000000000e+01,5.744588002562522888e-02,7.102078285940360836e-01,5.624829977750778198e-02,3.437983046230574380e-01,2.441406250000000051e-07,2.746831253170967102e-02,6.624551606364548206e-04,2.099987678229808807e-02,2.161517180502414703e-02,2.414772287011146545e-02,8.715481559739700623e-01,6.672828455264281922e-02,8.831588453122196647e-01,8.702672689314630006e-01,8.842446283828105935e-01,6.786394119262695312e-02,3.813432704191654921e-04,3.610038384795188904e-02,3.846692293882369995e-02,5.772996321320533752e-02,6.065904907838698401e-01,5.520335012537037206e-02,7.403407249192688333e-01,7.271236230294804148e-01,6.498558244496220881e-01,7.343600900759987837e-01,2.042524123136491288e-01,7.409805332907912767e-01,3.614077350502166719e-01,3.101093339909245516e-01,5.630572561695176836e-02,3.295716581338060669e-01,3.589284385806786326e-01
78
- 7.600000000000000000e+01,5.743892490863800049e-02,7.102216325694971077e-01,5.622515082359313965e-02,3.437954216054376344e-01,2.441406250000000051e-07,2.746747434139251709e-02,6.626070244237780571e-04,2.100008539855480194e-02,2.161485888063907623e-02,2.414709888398647308e-02,8.715492001269232736e-01,6.670314663601538407e-02,8.831602269790401749e-01,8.702697340896236033e-01,8.842413607944816789e-01,6.783617287874221802e-02,3.813692019321024418e-04,3.608340770006179810e-02,3.846577182412147522e-02,5.770989507436752319e-02,6.065669487708768015e-01,5.507845098411880136e-02,7.403505736808677629e-01,7.271360778216400256e-01,6.498418034119605169e-01,7.344514811222639938e-01,2.042009962502181475e-01,7.410714216144047572e-01,3.614115257143830640e-01,3.101078123435152545e-01,5.626472263132843016e-02,3.295700648258561016e-01,3.589282743186844038e-01
79
- 7.700000000000000000e+01,5.743740871548652649e-02,7.102277645436181341e-01,5.621990188956260681e-02,3.437912222676542395e-01,2.441406250000000051e-07,2.746638841927051544e-02,6.626519025303423405e-04,2.099964395165443420e-02,2.161437086760997772e-02,2.414614520967006683e-02,8.715510605716333403e-01,6.668044542525251861e-02,8.831618085627179759e-01,8.702718690627626597e-01,8.842433024221280080e-01,6.783072650432586670e-02,3.813685907516628504e-04,3.608386218547821045e-02,3.846294432878494263e-02,5.770359560847282410e-02,6.065518996553731501e-01,5.512295738221845848e-02,7.403490945075877905e-01,7.271339920355295305e-01,6.498351631318796118e-01,7.344367821116366546e-01,2.042106800712302217e-01,7.410545454497475815e-01,3.613939652687481185e-01,3.100906819358428823e-01,5.629561459838820781e-02,3.295534443168421923e-01,3.589142486480900729e-01
80
- 7.800000000000000000e+01,5.742450430989265442e-02,7.102489533366833641e-01,5.617469176650047302e-02,3.437790764511438057e-01,2.441406250000000051e-07,2.746506966650485992e-02,6.624925299547612667e-04,2.100067958235740662e-02,2.161271870136260986e-02,2.414591051638126373e-02,8.715520732867861886e-01,6.664341673793017229e-02,8.831628856262063731e-01,8.702750003923352962e-01,8.842404304654249980e-01,6.780597567558288574e-02,3.813672810792922974e-04,3.604932129383087158e-02,3.845183923840522766e-02,5.768522620201110840e-02,6.065137423796700400e-01,5.495576474417412927e-02,7.403605034412954478e-01,7.271430670287354348e-01,6.498163814275317307e-01,7.345507906434630696e-01,2.041300058224560510e-01,7.411677319636353900e-01,3.614287691137362257e-01,3.100878057155768697e-01,5.622565621818903514e-02,3.295461832279625303e-01,3.589406211929877522e-01
81
- 7.900000000000000000e+01,5.741957202553749084e-02,7.102535173267034985e-01,5.615540221333503723e-02,3.438023976347557298e-01,2.441406250000000051e-07,2.746395580470561981e-02,6.624498637393116951e-04,2.100028656423091888e-02,2.161260321736335754e-02,2.414489351212978363e-02,8.715540603538532993e-01,6.665346977670373596e-02,8.831650306271450201e-01,8.702775371404991578e-01,8.842427695310736135e-01,6.780055165290832520e-02,3.813297662418335676e-04,3.604963421821594238e-02,3.845350816845893860e-02,5.768076702952384949e-02,6.065057029300869873e-01,5.494590809392143477e-02,7.403565822759506920e-01,7.271388317076141750e-01,6.498053591684698205e-01,7.345912936291569828e-01,2.041239682220660767e-01,7.412080624280539265e-01,3.614513459967580999e-01,3.101168658392585153e-01,5.621218204044639566e-02,3.295692281711170568e-01,3.589595529608528568e-01
82
- 8.000000000000000000e+01,5.742327868938446045e-02,7.102504219218946924e-01,5.617029592394828796e-02,3.437985082666789594e-01,1.220703125000000025e-07,2.746420912444591522e-02,6.624478264711797237e-04,2.100002393126487732e-02,2.161327749490737915e-02,2.414488419890403748e-02,8.715553793996473342e-01,6.668622610354618674e-02,8.831649061447780502e-01,8.702772064294299614e-01,8.842449672757642931e-01,6.780930608510971069e-02,3.813274670392274857e-04,3.605801984667778015e-02,3.845958784222602844e-02,5.768844112753868103e-02,6.065086159650444619e-01,5.495675849027614052e-02,7.403526602871517426e-01,7.271364830789397438e-01,6.498056284249777992e-01,7.345706335879287341e-01,2.041451938521659237e-01,7.411880511018625883e-01,3.614587231489704289e-01,3.101153737674690603e-01,5.622062298497036009e-02,3.295674922735567502e-01,3.589650043976567728e-01
83
- 8.100000000000000000e+01,5.742004513740539551e-02,7.102552233824797057e-01,5.615895241498947144e-02,3.438009832114546360e-01,1.220703125000000025e-07,2.746364288032054901e-02,6.624411325901746750e-04,2.099973335862159729e-02,2.161274664103984833e-02,2.414433844387531281e-02,8.715567418555276724e-01,6.667365819663925719e-02,8.831662958821379084e-01,8.702785747588579479e-01,8.842461137663830684e-01,6.780733168125152588e-02,3.813440271187573671e-04,3.605687990784645081e-02,3.845658153295516968e-02,5.768571048974990845e-02,6.065045349047678558e-01,5.493589447177506246e-02,7.403531421841038629e-01,7.271358950685630207e-01,6.498024614359654283e-01,7.345784323300510454e-01,2.041305808100165875e-01,7.411949400847568370e-01,3.614325347432615354e-01,3.101178326569315469e-01,5.622187147412986696e-02,3.295680858940626123e-01,3.589360490815998728e-01
84
- 8.200000000000000000e+01,5.742454528808593750e-02,7.102536211330112703e-01,5.617781728506088257e-02,3.437949748221078572e-01,1.220703125000000025e-07,2.746343612670898438e-02,6.624619127251207829e-04,2.099967002868652344e-02,2.161291614174842834e-02,2.414404414594173431e-02,8.715582469488831752e-01,6.667628739681512318e-02,8.831660680878352654e-01,8.702780360038786522e-01,8.842482640540055838e-01,6.781229376792907715e-02,3.813602670561522245e-04,3.605883195996284485e-02,3.845781832933425903e-02,5.768891796469688416e-02,6.065076667294944102e-01,5.495254475954052670e-02,7.403493774394225291e-01,7.271307410576480779e-01,6.498010496227788479e-01,7.345430041073197636e-01,2.041551692820713915e-01,7.411600160967464213e-01,3.614112487133256946e-01,3.101169881487820246e-01,5.624037693540302751e-02,3.295691186051292743e-01,3.589150673760931887e-01
85
- 8.300000000000000000e+01,5.741937085986137390e-02,7.102646833694312711e-01,5.616059526801109314e-02,3.437875116461047154e-01,1.220703125000000025e-07,2.746262215077877045e-02,6.624469533562660217e-04,2.099954150617122650e-02,2.161208540201187134e-02,2.414362318813800812e-02,8.715596380042260893e-01,6.665438315321364771e-02,8.831675816643849153e-01,8.702796044123495278e-01,8.842474870367829087e-01,6.780055910348892212e-02,3.813409712165594101e-04,3.605354949831962585e-02,3.845212981104850769e-02,5.768060684204101562e-02,6.064956414959886688e-01,5.493389466612151606e-02,7.403580775376392697e-01,7.271369333260118140e-01,6.497981526826565046e-01,7.345749001675088152e-01,2.041367756464162186e-01,7.411899318080075183e-01,3.614002741940437469e-01,3.101005109603698906e-01,5.623601058817087606e-02,3.295541032431022477e-01,3.589059645250776143e-01
86
- 8.400000000000000000e+01,5.742282792925834656e-02,7.102602077522777169e-01,5.617344006896018982e-02,3.437879448014057870e-01,1.220703125000000025e-07,2.746327593922615051e-02,6.624107481911778450e-04,2.099902927875518799e-02,2.161283418536186218e-02,2.414352260529994965e-02,8.715599722999042287e-01,6.668134927302638015e-02,8.831673466751756019e-01,8.702795956575404279e-01,8.842507511258999031e-01,6.781244277954101562e-02,3.813021467067301273e-04,3.606931120157241821e-02,3.845940902829170227e-02,5.769089236855506897e-02,6.065012723608875778e-01,5.499186535075765991e-02,7.403508380939906042e-01,7.271287527484887603e-01,6.497969210242429128e-01,7.345521057569089907e-01,2.041456915753170898e-01,7.411681356864782133e-01,3.614087352742270109e-01,3.101010336203619544e-01,5.624676560807435888e-02,3.295551977613382921e-01,3.589165827975167034e-01
87
- 8.500000000000000000e+01,5.742383003234863281e-02,7.102593869730534637e-01,5.617755278944969177e-02,3.437851166294035865e-01,6.103515625000000127e-08,2.746325172483921051e-02,6.624160450883209705e-04,2.099900133907794952e-02,2.161286771297454834e-02,2.414354868233203888e-02,8.715608062245510101e-01,6.668276563026212522e-02,8.831674927718244916e-01,8.702796529290203198e-01,8.842511316899783758e-01,6.781384348869323730e-02,3.813048533629626036e-04,3.606985881924629211e-02,3.845983371138572693e-02,5.769224092364311218e-02,6.065017000850139173e-01,5.500202172672521289e-02,7.403500074081836857e-01,7.271286242946808365e-01,6.497979597943042140e-01,7.345461560816259894e-01,2.041580107852390413e-01,7.411625852311527485e-01,3.614117454931655149e-01,3.100988984322044506e-01,5.625210006108104926e-02,3.295532419031205196e-01,3.589201405935464884e-01
88
- 8.600000000000000000e+01,5.742476135492324829e-02,7.102577166814763032e-01,5.618143454194068909e-02,3.437860673037613224e-01,6.103515625000000127e-08,2.746317721903324127e-02,6.624088273383677006e-04,2.099902741611003876e-02,2.161268331110477448e-02,2.414346486330032349e-02,8.715609745634425032e-01,6.668429221677837915e-02,8.831675716440425816e-01,8.702801914155418039e-01,8.842517808547284419e-01,6.781657040119171143e-02,3.813170769717544317e-04,3.606923297047615051e-02,3.845962882041931152e-02,5.769429728388786316e-02,6.065015667970146129e-01,5.501792494191445199e-02,7.403491660686467579e-01,7.271283221611252179e-01,6.497967066819320126e-01,7.345436121881204761e-01,2.041618529719605168e-01,7.411608221796346374e-01,3.614174993926673740e-01,3.100996006103592384e-01,5.625190901768166440e-02,3.295539167726508345e-01,3.589251923969645763e-01
89
- 8.700000000000000000e+01,5.742425099015235901e-02,7.102597402265301563e-01,5.618052184581756592e-02,3.437816513065331536e-01,6.103515625000000127e-08,2.746314555406570435e-02,6.624137749895453453e-04,2.099907957017421722e-02,2.161258272826671600e-02,2.414346486330032349e-02,8.715616197665645437e-01,6.668009172373261240e-02,8.831678667479145872e-01,8.702805211557190779e-01,8.842518682405621222e-01,6.781435012817382812e-02,3.813206567429006100e-04,3.606607764959335327e-02,3.845885768532752991e-02,5.769266933202743530e-02,6.065005489542699690e-01,5.500491741334506690e-02,7.403505796290379415e-01,7.271300146719492652e-01,6.497975534486524207e-01,7.345501588966292239e-01,2.041616479228390568e-01,7.411670201525287771e-01,3.614134888071269258e-01,3.100970349862665065e-01,5.625026578452416159e-02,3.295516220044625300e-01,3.589199517714117271e-01
90
- 8.800000000000000000e+01,5.742482095956802368e-02,7.102610341983937037e-01,5.618404969573020935e-02,3.437716071276898377e-01,6.103515625000000127e-08,2.746328897774219513e-02,6.624396773986518383e-04,2.099916152656078339e-02,2.161259576678276062e-02,2.414366602897644043e-02,8.715621148233181348e-01,6.668368526771051297e-02,8.831676130337948738e-01,8.702803624611001876e-01,8.842520066339106810e-01,6.781849265098571777e-02,3.813256043940782547e-04,3.606628254055976868e-02,3.845921531319618225e-02,5.769608914852142334e-02,6.064983040540150583e-01,5.501151616932171279e-02,7.403516565604084665e-01,7.271295040258479725e-01,6.497967397218882279e-01,7.345460186766975541e-01,2.041725718048502392e-01,7.411631172426720315e-01,3.614178419427707678e-01,3.100866926065189433e-01,5.625602693862640641e-02,3.295421033584158832e-01,3.589255757506658595e-01
91
- 8.900000000000000000e+01,5.742535740137100220e-02,7.102602700873654751e-01,5.618613958358764648e-02,3.437749806570415956e-01,6.103515625000000127e-08,2.746353484690189362e-02,6.624433444812893867e-04,2.099914476275444031e-02,2.161250449717044830e-02,2.414372749626636505e-02,8.715623620935524229e-01,6.667737659294606378e-02,8.831675821657406500e-01,8.702805849956876116e-01,8.842521383030135240e-01,6.781842559576034546e-02,3.813384973909705877e-04,3.606744483113288879e-02,3.845893591642379761e-02,5.769626796245574951e-02,6.065021882422694244e-01,5.501647321737686974e-02,7.403518650629383746e-01,7.271322890442554199e-01,6.497994543215112051e-01,7.345403284212455031e-01,2.041695695030512625e-01,7.411579896989215488e-01,3.614340780278993415e-01,3.100864449011108692e-01,5.625480940280294662e-02,3.295422640160611261e-01,3.589414591791941778e-01
92
- 9.000000000000000000e+01,5.742720514535903931e-02,7.102574562376572365e-01,5.619290471076965332e-02,3.437764460693469859e-01,3.051757812500000064e-08,2.746373228728771210e-02,6.624161615036427975e-04,2.099910192191600800e-02,2.161275967955589294e-02,2.414384670555591583e-02,8.715623132075172030e-01,6.668807637986462478e-02,8.831674153196664712e-01,8.702805237292446927e-01,8.842525469967530416e-01,6.782269477844238281e-02,3.813300572801381350e-04,3.607061877846717834e-02,3.846121206879615784e-02,5.769957974553108215e-02,6.065060219820423981e-01,5.503423987277752255e-02,7.403500209198955462e-01,7.271312575679095280e-01,6.498009542043783338e-01,7.345292694679929069e-01,2.041793018001358817e-01,7.411475698598882778e-01,3.614327498634438141e-01,3.100892356773357061e-01,5.625932627504737216e-02,3.295452059159525438e-01,3.589403043226604417e-01
93
- 9.100000000000000000e+01,5.742630735039710999e-02,7.102588910144457257e-01,5.618974566459655762e-02,3.437782352952818976e-01,3.051757812500000064e-08,2.746357209980487823e-02,6.623930530622601509e-04,2.099904604256153107e-02,2.161267399787902832e-02,2.414374239742755890e-02,8.715624175541218932e-01,6.668484338112633247e-02,8.831677245896590867e-01,8.702808713766587712e-01,8.842525653260127072e-01,6.782145798206329346e-02,3.813205112237483263e-04,3.607034683227539062e-02,3.846084699034690857e-02,5.769851058721542358e-02,6.065037941992372783e-01,5.502904275149276409e-02,7.403492967169220673e-01,7.271310026517361358e-01,6.497990451241880683e-01,7.345395487245528487e-01,2.041752937338662255e-01,7.411575976380556652e-01,3.614393885949500618e-01,3.100922308855101894e-01,5.625429539251478039e-02,3.295469858190657741e-01,3.589457473718164482e-01
94
- 9.200000000000000000e+01,5.742597207427024841e-02,7.102592754383779683e-01,5.618848279118537903e-02,3.437810267037063605e-01,3.051757812500000064e-08,2.746345847845077515e-02,6.623783847317099571e-04,2.099903672933578491e-02,2.161254547536373138e-02,2.414366416633129120e-02,8.715624646103242723e-01,6.668456966930118301e-02,8.831679259620964784e-01,8.702811981563622901e-01,8.842526408152533435e-01,6.782101094722747803e-02,3.813080256804823875e-04,3.606924414634704590e-02,3.846025466918945312e-02,5.769795551896095276e-02,6.065019717847847103e-01,5.502628692895356749e-02,7.403497489148200827e-01,7.271307707089539329e-01,6.497979019243056431e-01,7.345463889467864771e-01,2.041730743630215394e-01,7.411644707540356869e-01,3.614368745602211663e-01,3.100943990664665750e-01,5.625078781013140988e-02,3.295488505874840657e-01,3.589439006174725555e-01
95
- 9.300000000000000000e+01,5.742697045207023621e-02,7.102574923029804088e-01,5.619199946522712708e-02,3.437837338260280595e-01,3.051757812500000064e-08,2.746360935270786285e-02,6.624151137657463551e-04,2.099906280636787415e-02,2.161253057420253754e-02,2.414369955658912659e-02,8.715624958332319761e-01,6.668857907904449100e-02,8.831677699435963591e-01,8.702811425971618275e-01,8.842530038548164217e-01,6.782369315624237061e-02,3.813255170825868845e-04,3.607001155614852905e-02,3.846047818660736084e-02,5.770011991262435913e-02,6.065051917421470096e-01,5.502141280420194680e-02,7.403477283781759732e-01,7.271301766202132866e-01,6.497982940777305982e-01,7.345412190348534764e-01,2.041792869080393258e-01,7.411600235732921371e-01,3.614369759222857970e-01,3.100994729535130334e-01,5.625291258045726772e-02,3.295536287179154922e-01,3.589429192136726621e-01
96
- 9.400000000000000000e+01,5.742746219038963318e-02,7.102563154624900177e-01,5.619365721940994263e-02,3.437862046159803620e-01,3.051757812500000064e-08,2.746367827057838440e-02,6.624052766710519791e-04,2.099899388849735260e-02,2.161255665123462677e-02,2.414371073246002197e-02,8.715623925121640525e-01,6.669259301730738609e-02,8.831678098162296653e-01,8.702813134274873796e-01,8.842534607436839389e-01,6.782675534486770630e-02,3.813199873548001051e-04,3.607209771871566772e-02,3.846112638711929321e-02,5.770229920744895935e-02,6.065070089296641376e-01,5.503022576882060646e-02,7.403466347282283699e-01,7.271294141216424478e-01,6.497990071087068609e-01,7.345372821665852037e-01,2.041820663731753194e-01,7.411561034109079849e-01,3.614360982622374374e-01,3.101021480963429555e-01,5.625362064777472693e-02,3.295559569789580645e-01,3.589423428538700556e-01
97
- 9.500000000000000000e+01,5.742703750729560852e-02,7.102569368484444867e-01,5.619209259748458862e-02,3.437863875497129129e-01,1.525878906250000032e-08,2.746364288032054901e-02,6.623973604291677475e-04,2.099897712469100952e-02,2.161252126097679138e-02,2.414368651807308197e-02,8.715624576018268233e-01,6.669376552850886375e-02,8.831679109726302546e-01,8.702814666682135991e-01,8.842534967828880044e-01,6.782664358615875244e-02,3.813143412116914988e-04,3.607213124632835388e-02,3.846103325486183167e-02,5.770227313041687012e-02,6.065063963792194368e-01,5.502879930785751356e-02,7.403467771952495502e-01,7.271294226491348267e-01,6.497986774818259592e-01,7.345407597214546058e-01,2.041818029266593004e-01,7.411594022836662798e-01,3.614371247467591530e-01,3.101022643236751031e-01,5.625301273860582735e-02,3.295556554624058387e-01,3.589432466792510934e-01
98
- 9.600000000000000000e+01,5.742678791284561157e-02,7.102575520972506107e-01,5.619135499000549316e-02,3.437856280610490645e-01,1.525878906250000032e-08,2.746361866593360901e-02,6.624010857194662094e-04,2.099897339940071106e-02,2.161247096955776215e-02,2.414365112781524658e-02,8.715625138140986605e-01,6.669376107974200885e-02,8.831680326898636579e-01,8.702816169756135700e-01,8.842534472271667001e-01,6.782566010951995850e-02,3.813155053649097681e-04,3.607146814465522766e-02,3.846075013279914856e-02,5.770148709416389465e-02,6.065047930719577041e-01,5.502466650980317869e-02,7.403471140747229784e-01,7.271298053896527724e-01,6.497974098810830323e-01,7.345431741808966519e-01,2.041813274201347839e-01,7.411617321492262045e-01,3.614393388577165078e-01,3.101010506631156072e-01,5.625284969015263009e-02,3.295544392626322816e-01,3.589454730277931160e-01
99
- 9.700000000000000000e+01,5.742698535323143005e-02,7.102573362458866058e-01,5.619213730096817017e-02,3.437870707514761870e-01,1.525878906250000032e-08,2.746362797915935516e-02,6.623939843848347664e-04,2.099893055856227875e-02,2.161259949207305908e-02,2.414362877607345581e-02,8.715625979873579121e-01,6.669771919788446135e-02,8.831680724676759242e-01,8.702816760955561781e-01,8.842538026328752299e-01,6.782711297273635864e-02,3.813042712863534689e-04,3.607279434800148010e-02,3.846183791756629944e-02,5.770270153880119324e-02,6.065066086491071040e-01,5.503128305077718829e-02,7.403467665509644302e-01,7.271292686309098752e-01,6.497982853711653517e-01,7.345432199076017410e-01,2.041833803809668946e-01,7.411617869837417993e-01,3.614402063993495395e-01,3.101021552493902211e-01,5.625366139488278960e-02,3.295553474636763736e-01,3.589460570228465430e-01
100
- 9.800000000000000000e+01,5.742775276303291321e-02,7.102563257942547947e-01,5.619505420327186584e-02,3.437868536375007777e-01,1.525878906250000032e-08,2.746368013322353363e-02,6.624005036428570747e-04,2.099893055856227875e-02,2.161265723407268524e-02,2.414363436400890350e-02,8.715626216531162651e-01,6.669872117073184781e-02,8.831679648722633447e-01,8.702815725062246210e-01,8.842540431483839614e-01,6.782810389995574951e-02,3.813096263911575079e-04,3.607365116477012634e-02,3.846231102943420410e-02,5.770342797040939331e-02,6.065077478649176790e-01,5.503514446043885483e-02,7.403463647436743233e-01,7.271291605538280356e-01,6.497988977719499903e-01,7.345372687393231237e-01,2.041856672287047370e-01,7.411561993314802477e-01,3.614395982609147628e-01,3.101020264633129742e-01,5.625488237129971925e-02,3.295555820748442000e-01,3.589459024230869932e-01
101
- 9.900000000000000000e+01,5.742808803915977478e-02,7.102558540605663495e-01,5.619632825255393982e-02,3.437867991928307276e-01,1.525878906250000032e-08,2.746366523206233978e-02,6.624017842113971710e-04,2.099892869591712952e-02,2.161265537142753601e-02,2.414361946284770966e-02,8.715626273732197671e-01,6.669814984436445160e-02,8.831679477860365779e-01,8.702816305084354331e-01,8.842541294929460172e-01,6.782823801040649414e-02,3.813100629486143589e-04,3.607377037405967712e-02,3.846235945820808411e-02,5.770352855324745178e-02,6.065081304513683103e-01,5.503931070687841792e-02,7.403462395819607744e-01,7.271290619693382418e-01,6.497991553380486618e-01,7.345340126649899659e-01,2.041871967844338664e-01,7.411530658095542012e-01,3.614379600582254271e-01,3.101018571036791571e-01,5.625574519011593277e-02,3.295554763053956671e-01,3.589443249033947292e-01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streaming_gopt_best/test_metrics.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "phone_test_mse": 0.04919730871915817,
3
- "phone_test_pcc": 0.3975705055993251,
4
- "utt_test_mse": [
5
- 0.051985688507556915,
6
- 0.0016903950599953532,
7
- 0.03446277603507042,
8
- 0.03239584341645241,
9
- 0.04794545844197273
10
- ],
11
- "utt_test_pcc": [
12
- 0.651909193610124,
13
- 0.012690043750859805,
14
- 0.7249977587357453,
15
- 0.7332794084754206,
16
- 0.681940037784353
17
- ],
18
- "word_test_mse": [
19
- 0.06279397755861282,
20
- 0.0037991644348949194,
21
- 0.04641091078519821,
22
- 0.14399507641792297
23
- ],
24
- "word_test_pcc": [
25
- 0.4047142491023989,
26
- -0.0039119825110344765,
27
- 0.4122583381095728,
28
- 0.41746734262039764
29
- ],
30
- "word_score_names": [
31
- "accuracy",
32
- "stress",
33
- "total",
34
- "asr_accuracy"
35
- ],
36
- "best_epoch": 11
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streampa/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .pipeline import StreamPAPipeline, StreamPASession
2
+
3
+ __all__ = ['StreamPAPipeline', 'StreamPASession']
4
+ __version__ = '1.0.0'
streampa/charsiu_model.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MIT-licensed Charsiu frame classifier, extracted without numerical changes.
2
+ import torch
3
+ import torch.nn as nn
4
+ from transformers import Wav2Vec2ForCTC
5
+ from transformers.modeling_outputs import CausalLMOutput
6
+ class Wav2Vec2ForFrameClassification(Wav2Vec2ForCTC):
7
+
8
+ def forward(
9
+ self,
10
+ input_values,
11
+ attention_mask=None,
12
+ output_attentions=None,
13
+ output_hidden_states=None,
14
+ return_dict=None,
15
+ labels=None,
16
+ ):
17
+
18
+ return_dict = return_dict if return_dict is not None else self.config.use_return_dict
19
+
20
+ outputs = self.wav2vec2(
21
+ input_values,
22
+ attention_mask=attention_mask,
23
+ output_attentions=output_attentions,
24
+ output_hidden_states=output_hidden_states,
25
+ return_dict=return_dict,
26
+ )
27
+
28
+ hidden_states = outputs[0]
29
+ hidden_states = self.dropout(hidden_states)
30
+
31
+ logits = self.lm_head(hidden_states)
32
+
33
+ loss = None
34
+ if labels is not None:
35
+
36
+ if labels.max() >= self.config.vocab_size:
37
+ raise ValueError(f"Label values must be <= vocab_size: {self.config.vocab_size}")
38
+
39
+ # retrieve loss input_lengths from attention_mask
40
+ attention_mask = (
41
+ attention_mask if attention_mask is not None else torch.ones_like(input_values, dtype=torch.long)
42
+ )
43
+ # input_lengths = self._get_feat_extract_output_lengths(attention_mask.sum(-1)).to(torch.long)
44
+
45
+ loss = nn.functional.cross_entropy(logits.view(-1,logits.size(2)), labels.flatten(), reduction="mean")
46
+
47
+
48
+
49
+ if not return_dict:
50
+ output = (logits,) + outputs[2:]
51
+ return ((loss,) + output) if loss is not None else output
52
+
53
+ return CausalLMOutput(
54
+ loss=loss, logits=logits, hidden_states=outputs.hidden_states, attentions=outputs.attentions
55
+ )
streampa/frontend.py ADDED
@@ -0,0 +1,1556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frozen inference function extraction. See source provenance and licenses.
2
+ import gc
3
+ import math
4
+ import re
5
+ from collections import Counter
6
+ from itertools import groupby
7
+ import numpy as np
8
+ import librosa
9
+ import torch
10
+ from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq
11
+ EPS = 1e-8
12
+ EPS_TOKEN = '<eps>'
13
+
14
+ def normalize_phone(phone):
15
+ phone = phone.upper().strip()
16
+ phone = phone.replace('[', '').replace(']', '')
17
+ phone = re.sub(r'[_\d].*', '', phone)
18
+ return phone
19
+
20
+
21
+ def normalize_word(word):
22
+ word = word.upper().strip()
23
+ word = word.replace('[', '').replace(']', '')
24
+ word = re.sub(r"[^A-Z']", '', word)
25
+ return word
26
+
27
+
28
+ def monotonic_align(neg_log_probs, phone_ids):
29
+ frame_num = neg_log_probs.shape[0]
30
+ token_num = len(phone_ids)
31
+ if frame_num < token_num:
32
+ raise ValueError(f'Not enough frames ({frame_num}) to align {token_num} phones.')
33
+
34
+ dp = np.full((frame_num, token_num), np.inf, dtype=np.float32)
35
+ back = np.zeros((frame_num, token_num), dtype=np.int8)
36
+ dp[0, 0] = neg_log_probs[0, phone_ids[0]]
37
+
38
+ for t in range(1, frame_num):
39
+ max_token = min(t, token_num - 1)
40
+ for tok in range(max_token + 1):
41
+ stay_cost = dp[t - 1, tok]
42
+ move_cost = dp[t - 1, tok - 1] if tok > 0 else np.inf
43
+ if move_cost < stay_cost:
44
+ dp[t, tok] = move_cost + neg_log_probs[t, phone_ids[tok]]
45
+ back[t, tok] = 1
46
+ else:
47
+ dp[t, tok] = stay_cost + neg_log_probs[t, phone_ids[tok]]
48
+
49
+ path = np.zeros(frame_num, dtype=np.int32)
50
+ tok = token_num - 1
51
+ for t in range(frame_num - 1, -1, -1):
52
+ path[t] = tok
53
+ if t > 0 and back[t, tok] == 1:
54
+ tok -= 1
55
+ return path
56
+
57
+ def select_visible_frames(probs, keep_mask, audio_end, frame_step):
58
+ frame_limit = max(int(math.ceil(audio_end / max(frame_step, EPS))), 1)
59
+ frame_mask = np.zeros(len(probs), dtype=bool)
60
+ frame_mask[: min(frame_limit, len(probs))] = True
61
+ visible_mask = keep_mask & frame_mask
62
+ return np.flatnonzero(visible_mask), probs[visible_mask]
63
+
64
+ def softmax_1d(values):
65
+ arr = np.asarray(values, dtype=np.float64)
66
+ if arr.size == 0:
67
+ return arr.astype(np.float32)
68
+ pos_inf = np.isposinf(arr)
69
+ if np.any(pos_inf):
70
+ out = np.zeros_like(arr, dtype=np.float64)
71
+ out[pos_inf] = 1.0 / float(np.sum(pos_inf))
72
+ return out.astype(np.float32)
73
+ finite = np.isfinite(arr)
74
+ if not np.any(finite):
75
+ return np.full(arr.shape, 1.0 / float(arr.size), dtype=np.float32)
76
+ min_finite = np.min(arr[finite])
77
+ arr = np.where(finite, arr, min_finite - 50.0)
78
+ arr = arr - np.max(arr)
79
+ exp_arr = np.exp(arr)
80
+ return (exp_arr / np.clip(exp_arr.sum(), EPS, None)).astype(np.float32)
81
+
82
+
83
+ def entropy_np(probs):
84
+ probs = np.asarray(probs, dtype=np.float64)
85
+ probs = probs[probs > 0]
86
+ if probs.size == 0:
87
+ return 0.0
88
+ return float(-np.sum(probs * np.log(np.clip(probs, EPS, None))))
89
+
90
+
91
+ def top_margin(probs):
92
+ probs = np.asarray(probs, dtype=np.float32)
93
+ if probs.size == 0:
94
+ return 0.0, 0.0
95
+ top = np.sort(probs)[::-1]
96
+ top1 = float(top[0])
97
+ top2 = float(top[1]) if top.size > 1 else 0.0
98
+ return top1, float(top1 - top2)
99
+
100
+
101
+ def js_divergence(p, q):
102
+ p = np.asarray(p, dtype=np.float64)
103
+ q = np.asarray(q, dtype=np.float64)
104
+ p = p / np.clip(p.sum(), EPS, None)
105
+ q = q / np.clip(q.sum(), EPS, None)
106
+ m = 0.5 * (p + q)
107
+ kl_pm = np.sum(p * (np.log(np.clip(p, EPS, None)) - np.log(np.clip(m, EPS, None))))
108
+ kl_qm = np.sum(q * (np.log(np.clip(q, EPS, None)) - np.log(np.clip(m, EPS, None))))
109
+ return float(0.5 * (kl_pm + kl_qm))
110
+
111
+
112
+ def safe_json(value):
113
+ if isinstance(value, dict):
114
+ return {str(key): safe_json(val) for key, val in value.items()}
115
+ if isinstance(value, (list, tuple)):
116
+ return [safe_json(item) for item in value]
117
+ if isinstance(value, np.ndarray):
118
+ return safe_json(value.tolist())
119
+ if isinstance(value, (np.floating, np.integer)):
120
+ return value.item()
121
+ return value
122
+
123
+
124
+ def softmax_np_matrix(values):
125
+ arr = np.asarray(values, dtype=np.float64)
126
+ if arr.size == 0:
127
+ return arr.astype(np.float32)
128
+ arr = arr - np.max(arr, axis=-1, keepdims=True)
129
+ exp_arr = np.exp(arr)
130
+ return (exp_arr / np.clip(np.sum(exp_arr, axis=-1, keepdims=True), EPS, None)).astype(np.float32)
131
+
132
+
133
+ def audio_array_logits(audio, processor, model, sample_rate, device):
134
+ audio = np.asarray(audio, dtype=np.float32)
135
+ if hasattr(processor, 'audio_preprocess'):
136
+ inputs = processor.audio_preprocess(audio, sr=sample_rate)
137
+ inputs = torch.tensor(inputs, dtype=torch.float32).unsqueeze(0).to(device)
138
+ model_inputs = {'input_values': inputs}
139
+ else:
140
+ model_inputs = processor(audio, sampling_rate=sample_rate, return_tensors='pt')
141
+ model_inputs = {key: value.to(device) for key, value in model_inputs.items()}
142
+ with torch.no_grad():
143
+ logits = model(**model_inputs).logits[0].detach().cpu().numpy()
144
+ return softmax_np_matrix(logits), float(len(audio) / sample_rate)
145
+
146
+
147
+ def non_overlapping_occurrences(tokens, pattern):
148
+ positions = []
149
+ pattern_size = len(pattern)
150
+ cursor = 0
151
+ while cursor <= len(tokens) - pattern_size:
152
+ if tokens[cursor : cursor + pattern_size] == pattern:
153
+ positions.append(cursor)
154
+ cursor += pattern_size
155
+ else:
156
+ cursor += 1
157
+ return positions
158
+
159
+
160
+ def find_repeated_ngram(
161
+ words,
162
+ min_repeats,
163
+ max_ngram_size=12,
164
+ strict=False,
165
+ min_coverage=0.6,
166
+ dominant_token_ratio=0.5,
167
+ ):
168
+ if min_repeats <= 0:
169
+ return None
170
+ tokens = [str(word['text']) for word in words]
171
+ if not tokens:
172
+ return None
173
+
174
+ for ngram_size in range(1, min(max_ngram_size, len(tokens)) + 1):
175
+ span = ngram_size * min_repeats
176
+ for start in range(0, len(tokens) - span + 1):
177
+ pattern = tokens[start : start + ngram_size]
178
+ repeats = 1
179
+ cursor = start + ngram_size
180
+ while tokens[cursor : cursor + ngram_size] == pattern:
181
+ repeats += 1
182
+ cursor += ngram_size
183
+ if repeats >= min_repeats:
184
+ return {
185
+ 'start': int(start),
186
+ 'ngram_size': int(ngram_size),
187
+ 'repeats': int(repeats),
188
+ 'pattern': pattern,
189
+ 'detection': 'contiguous_cycle',
190
+ }
191
+
192
+ if not strict:
193
+ return None
194
+
195
+ token_counts = Counter(tokens)
196
+ dominant_token, dominant_count = token_counts.most_common(1)[0]
197
+ token_ratio = float(dominant_count) / float(len(tokens))
198
+ if dominant_count >= max(min_repeats * 2, 8) and token_ratio >= dominant_token_ratio:
199
+ return {
200
+ 'start': None,
201
+ 'ngram_size': 1,
202
+ 'repeats': int(dominant_count),
203
+ 'pattern': [dominant_token],
204
+ 'coverage': token_ratio,
205
+ 'detection': 'dominant_token',
206
+ }
207
+
208
+ max_size = min(max_ngram_size, len(tokens) // max(min_repeats, 1))
209
+ for ngram_size in range(max_size, 1, -1):
210
+ candidates = Counter(
211
+ tuple(tokens[start : start + ngram_size])
212
+ for start in range(0, len(tokens) - ngram_size + 1)
213
+ )
214
+ for pattern, raw_count in candidates.most_common():
215
+ if raw_count < min_repeats:
216
+ break
217
+ positions = non_overlapping_occurrences(tokens, list(pattern))
218
+ if len(positions) < min_repeats:
219
+ continue
220
+ coverage = float(len(positions) * ngram_size) / float(len(tokens))
221
+ if coverage >= min_coverage:
222
+ return {
223
+ 'start': int(positions[0]),
224
+ 'ngram_size': int(ngram_size),
225
+ 'repeats': int(len(positions)),
226
+ 'pattern': list(pattern),
227
+ 'coverage': coverage,
228
+ 'detection': 'high_coverage_periodicity',
229
+ }
230
+ return None
231
+
232
+
233
+ class PhoneMapper:
234
+ def __init__(self, lexicon, phn_dict):
235
+ self.lexicon = lexicon
236
+ self.phn_dict = phn_dict
237
+ self.cache = {}
238
+ self.fallback_phone = 'AH' if 'AH' in phn_dict else (next(iter(phn_dict)) if phn_dict else None)
239
+ self.g2p = None
240
+ try:
241
+ from g2p_en import G2p
242
+
243
+ self.g2p = G2p()
244
+ except Exception:
245
+ pass
246
+
247
+ def word_to_phones(self, word):
248
+ word = normalize_word(word)
249
+ if word in self.lexicon:
250
+ return list(self.lexicon[word]), 'lexicon'
251
+ if word in self.cache:
252
+ return self.cache[word]
253
+ if self.g2p is not None:
254
+ try:
255
+ phones = []
256
+ for raw_phone in self.g2p(word.lower()):
257
+ phone = normalize_phone(raw_phone)
258
+ if phone and phone in self.phn_dict:
259
+ phones.append(phone)
260
+ if phones:
261
+ result = (phones, 'g2p_en')
262
+ self.cache[word] = result
263
+ return result
264
+ except Exception:
265
+ self.g2p = None
266
+ if self.fallback_phone is None:
267
+ result = ([], 'unmapped')
268
+ else:
269
+ approx_len = max(1, int(math.ceil(len(re.sub(r'[^A-Z]', '', word)) / 3.0)))
270
+ result = ([self.fallback_phone] * approx_len, 'fallback_phone')
271
+ self.cache[word] = result
272
+ return result
273
+
274
+ def words_to_phone_sequence(self, words):
275
+ phones = []
276
+ phone_to_word = []
277
+ source_counts = Counter()
278
+ for word_idx, word in enumerate(words):
279
+ cur_phones, source = self.word_to_phones(word)
280
+ source_counts[source] += 1
281
+ for phone in cur_phones:
282
+ phones.append(phone)
283
+ phone_to_word.append(word_idx)
284
+ return phones, phone_to_word, dict(source_counts)
285
+
286
+
287
+ class WhisperNBestGenerator:
288
+ def __init__(self, model_name, language, device, nbest, beam_size, max_new_tokens, no_repeat_ngram_size):
289
+ self.processor = AutoProcessor.from_pretrained(model_name)
290
+ torch_dtype = torch.float16 if str(device).startswith('cuda') else torch.float32
291
+ self.model = AutoModelForSpeechSeq2Seq.from_pretrained(model_name, torch_dtype=torch_dtype)
292
+ self.model.to(device)
293
+ self.model.eval()
294
+ self.model_dtype = next(self.model.parameters()).dtype
295
+ self.language = language
296
+ self.device = device
297
+ self.nbest = int(nbest)
298
+ self.beam_size = max(int(beam_size), int(nbest))
299
+ self.max_new_tokens = int(max_new_tokens)
300
+ self.no_repeat_ngram_size = int(no_repeat_ngram_size)
301
+ self.forced_decoder_ids = None
302
+ if hasattr(self.processor, 'get_decoder_prompt_ids'):
303
+ try:
304
+ self.forced_decoder_ids = self.processor.get_decoder_prompt_ids(
305
+ language=language,
306
+ task='transcribe',
307
+ )
308
+ except Exception:
309
+ self.forced_decoder_ids = None
310
+
311
+ @torch.inference_mode()
312
+ def generate(self, audio, sample_rate, audio_end):
313
+ inputs = self.processor(
314
+ np.asarray(audio, dtype=np.float32),
315
+ sampling_rate=sample_rate,
316
+ return_tensors='pt',
317
+ return_attention_mask=True,
318
+ )
319
+ inputs = {key: value.to(self.device) for key, value in inputs.items()}
320
+ for key in ('input_features', 'input_values'):
321
+ if key in inputs and torch.is_floating_point(inputs[key]):
322
+ inputs[key] = inputs[key].to(dtype=self.model_dtype)
323
+ generate_kwargs = {
324
+ 'num_beams': self.beam_size,
325
+ 'num_return_sequences': self.nbest,
326
+ 'max_new_tokens': self.max_new_tokens,
327
+ 'return_dict_in_generate': True,
328
+ 'output_scores': True,
329
+ 'do_sample': False,
330
+ }
331
+ if self.no_repeat_ngram_size > 0:
332
+ generate_kwargs['no_repeat_ngram_size'] = self.no_repeat_ngram_size
333
+ if self.forced_decoder_ids is not None:
334
+ generate_kwargs['forced_decoder_ids'] = self.forced_decoder_ids
335
+ output = self.model.generate(**inputs, **generate_kwargs)
336
+ if torch.is_tensor(output):
337
+ sequences = output
338
+ sequence_scores = [0.0 for _ in range(int(sequences.shape[0]))]
339
+ transition_scores = []
340
+ else:
341
+ sequences = output.sequences
342
+ if getattr(output, 'sequences_scores', None) is not None:
343
+ sequence_scores = output.sequences_scores.detach().float().cpu().numpy().tolist()
344
+ else:
345
+ sequence_scores = [0.0 for _ in range(int(sequences.shape[0]))]
346
+ transition_scores = self._transition_scores(output)
347
+ texts = self.processor.batch_decode(sequences, skip_special_tokens=True)
348
+
349
+ rows = []
350
+ for rank, (text, sequence_score) in enumerate(zip(texts, sequence_scores)):
351
+ token_ids = sequences[rank].detach().cpu().tolist()
352
+ token_logprobs = (
353
+ transition_scores[rank]
354
+ if rank < len(transition_scores)
355
+ else [float(sequence_score) for _ in token_ids]
356
+ )
357
+ token_rows, word_rows = self._token_word_scores(token_ids, token_logprobs)
358
+ words = [row['word'] for row in word_rows]
359
+ length_norm = (
360
+ float(np.mean([row['logprob'] for row in token_rows]))
361
+ if token_rows
362
+ else float(sequence_score)
363
+ )
364
+ rows.append({
365
+ 'rank': int(rank),
366
+ 'text': ' '.join(words).lower(),
367
+ 'words': words,
368
+ 'logprob': float(length_norm),
369
+ 'sequence_score': float(sequence_score),
370
+ 'length_normalized_sequence_score': float(length_norm),
371
+ 'token_ids': [int(row['token_id']) for row in token_rows],
372
+ 'token_logprobs': [float(row['logprob']) for row in token_rows],
373
+ 'token_confidences': [float(row['confidence']) for row in token_rows],
374
+ 'word_token_ranges': [[int(row['token_start']), int(row['token_end'])] for row in word_rows],
375
+ 'word_logprobs': [float(row['logprob']) for row in word_rows],
376
+ 'word_confidences': [float(row['confidence']) for row in word_rows],
377
+ 'word_timestamps': estimate_word_timestamps(words, audio_end),
378
+ })
379
+ if str(self.device).startswith('cuda'):
380
+ torch.cuda.empty_cache()
381
+ gc.collect()
382
+ return rows
383
+
384
+ def _transition_scores(self, output):
385
+ if getattr(output, 'scores', None) is None:
386
+ return []
387
+ try:
388
+ sequences = output.sequences.detach().cpu()
389
+ step_scores = [score.detach().float().cpu() for score in output.scores]
390
+ if not step_scores:
391
+ return []
392
+ beam_indices = getattr(output, 'beam_indices', None)
393
+ if beam_indices is not None:
394
+ beam_indices = beam_indices.detach().cpu()
395
+ generated_steps = len(step_scores)
396
+ start = max(0, int(sequences.shape[1]) - generated_steps)
397
+ rows = []
398
+ for seq_idx in range(int(sequences.shape[0])):
399
+ cur = []
400
+ for step, logits in enumerate(step_scores):
401
+ token_pos = start + step
402
+ if token_pos >= int(sequences.shape[1]):
403
+ break
404
+ token_id = int(sequences[seq_idx, token_pos])
405
+ if token_id < 0 or token_id >= int(logits.shape[-1]):
406
+ cur.append(0.0)
407
+ continue
408
+ score_row = min(seq_idx, int(logits.shape[0]) - 1)
409
+ if beam_indices is not None and seq_idx < int(beam_indices.shape[0]) and token_pos < int(beam_indices.shape[1]):
410
+ candidate = int(beam_indices[seq_idx, token_pos])
411
+ if 0 <= candidate < int(logits.shape[0]):
412
+ score_row = candidate
413
+ log_probs = torch.log_softmax(logits[score_row], dim=-1)
414
+ cur.append(float(log_probs[token_id].item()))
415
+ rows.append(cur)
416
+ return rows
417
+ except Exception:
418
+ return []
419
+
420
+ def _token_word_scores(self, token_ids, token_logprobs):
421
+ tokenizer = self.processor.tokenizer
422
+ special_ids = set(getattr(tokenizer, 'all_special_ids', []) or [])
423
+ if len(token_logprobs) != len(token_ids):
424
+ aligned = [0.0 for _ in token_ids]
425
+ offset = max(0, len(token_ids) - len(token_logprobs))
426
+ for idx, value in enumerate(token_logprobs[-len(token_ids) :]):
427
+ pos = offset + idx
428
+ if pos < len(aligned):
429
+ aligned[pos] = float(value)
430
+ token_logprobs = aligned
431
+ token_rows = []
432
+ words = []
433
+ current = []
434
+ current_token_ids = []
435
+ current_logprobs = []
436
+ current_start = None
437
+
438
+ def flush():
439
+ nonlocal current, current_token_ids, current_logprobs, current_start
440
+ word = normalize_word(''.join(current))
441
+ if word:
442
+ avg_logprob = float(np.mean(current_logprobs)) if current_logprobs else 0.0
443
+ words.append({
444
+ 'word': word,
445
+ 'token_start': int(current_start if current_start is not None else 0),
446
+ 'token_end': int((current_token_ids[-1] + 1) if current_token_ids else 0),
447
+ 'logprob': avg_logprob,
448
+ 'confidence': float(np.clip(np.exp(avg_logprob), 0.0, 1.0)),
449
+ })
450
+ current = []
451
+ current_token_ids = []
452
+ current_logprobs = []
453
+ current_start = None
454
+
455
+ for pos, token_id in enumerate(token_ids):
456
+ if int(token_id) in special_ids:
457
+ continue
458
+ logprob = float(token_logprobs[pos]) if pos < len(token_logprobs) else 0.0
459
+ try:
460
+ text = tokenizer.decode([int(token_id)], skip_special_tokens=True, clean_up_tokenization_spaces=False)
461
+ except TypeError:
462
+ text = tokenizer.decode([int(token_id)], skip_special_tokens=True)
463
+ if not text:
464
+ continue
465
+ token_rows.append({
466
+ 'token_id': int(token_id),
467
+ 'logprob': logprob,
468
+ 'confidence': float(np.clip(np.exp(logprob), 0.0, 1.0)),
469
+ })
470
+ for char in text:
471
+ if char.isalpha() or char == "'":
472
+ if current_start is None:
473
+ current_start = pos
474
+ current.append(char)
475
+ current_token_ids.append(pos)
476
+ current_logprobs.append(logprob)
477
+ else:
478
+ flush()
479
+ flush()
480
+ return token_rows, words
481
+
482
+
483
+ def normalize_text_to_words(text):
484
+ tokens = []
485
+ for token in re.findall(r"[A-Za-z']+", text):
486
+ norm = normalize_word(token)
487
+ if norm:
488
+ tokens.append(norm)
489
+ return tokens
490
+
491
+
492
+ def estimate_word_timestamps(words, audio_end):
493
+ if not words:
494
+ return []
495
+ step = float(audio_end) / max(len(words), 1)
496
+ rows = []
497
+ for idx, word in enumerate(words):
498
+ rows.append({
499
+ 'word': word.lower(),
500
+ 'start': float(idx * step),
501
+ 'end': float(min(audio_end, (idx + 1) * step)),
502
+ 'source': 'duration_proportional_estimate',
503
+ })
504
+ return rows
505
+
506
+
507
+ def hypothesis_phone_rows(row, phone_mapper, phn_dict):
508
+ phones = []
509
+ phone_ids = []
510
+ phone_to_word = []
511
+ phone_confidences = []
512
+ source_counts = Counter()
513
+ word_confidences = row.get('word_confidences') or [1.0 for _ in row.get('words', [])]
514
+ for word_idx, word in enumerate(row.get('words', [])):
515
+ cur_phones, source = phone_mapper.word_to_phones(word)
516
+ source_counts[source] += 1
517
+ word_conf = float(word_confidences[word_idx]) if word_idx < len(word_confidences) else 1.0
518
+ word_conf = float(np.clip(word_conf, 0.0, 1.0))
519
+ for phone in cur_phones:
520
+ if phone not in phn_dict:
521
+ continue
522
+ phones.append(phone)
523
+ phone_ids.append(int(phn_dict[phone]))
524
+ phone_to_word.append(int(word_idx))
525
+ phone_confidences.append(word_conf)
526
+ return {
527
+ 'phones': phones,
528
+ 'phone_ids': phone_ids,
529
+ 'phone_to_word': phone_to_word,
530
+ 'phone_confidences': phone_confidences,
531
+ 'source_counts': dict(source_counts),
532
+ }
533
+
534
+
535
+ def align_hypothesis_with_charsiu(item, row, phone_mapper, phn_dict, phone_to_frame_id, audio_end):
536
+ hyp = hypothesis_phone_rows(row, phone_mapper, phn_dict)
537
+ words = row.get('words', [])
538
+ if not words or not hyp['phone_ids']:
539
+ row['word_timestamps'] = []
540
+ row['timestamp_source'] = 'empty_hypothesis'
541
+ row['phone_times'] = []
542
+ row['phone_acoustic_supports'] = []
543
+ return row
544
+
545
+ kept_indices, kept_probs = select_visible_frames(item['probs'], item['keep_mask'], audio_end, item['frame_step'])
546
+ frame_phone_ids = []
547
+ id_to_phone = {idx: phone for phone, idx in phn_dict.items()}
548
+ try:
549
+ for phone_idx in hyp['phone_ids']:
550
+ phone = id_to_phone[int(phone_idx)]
551
+ frame_phone_ids.append(phone_to_frame_id[phone])
552
+ if kept_probs.shape[0] < len(frame_phone_ids):
553
+ raise ValueError('not_enough_visible_frames_for_hypothesis')
554
+ path = monotonic_align(-np.log(np.clip(kept_probs, EPS, None)), frame_phone_ids)
555
+ phone_times = []
556
+ phone_acoustic_supports = []
557
+ for phone_pos, frame_phone_id in enumerate(frame_phone_ids):
558
+ local_frames = kept_indices[path == phone_pos]
559
+ if local_frames.size == 0:
560
+ raise ValueError('empty_aligned_phone_span')
561
+ start_time = float(local_frames.min() * item['frame_step'])
562
+ end_time = float((local_frames.max() + 1) * item['frame_step'])
563
+ support = float(np.mean(item['probs'][local_frames, frame_phone_id]))
564
+ phone_times.append((start_time, min(float(audio_end), end_time)))
565
+ phone_acoustic_supports.append(float(np.clip(support, 0.0, 1.0)))
566
+ word_rows = []
567
+ for word_idx, word in enumerate(words):
568
+ positions = [idx for idx, cur_word in enumerate(hyp['phone_to_word']) if cur_word == word_idx]
569
+ if not positions:
570
+ continue
571
+ start = min(phone_times[idx][0] for idx in positions)
572
+ end = max(phone_times[idx][1] for idx in positions)
573
+ word_rows.append({
574
+ 'word': word,
575
+ 'start': float(start),
576
+ 'end': float(end),
577
+ 'source': 'charsiu_hypothesis_alignment',
578
+ 'word_logprob': float(row.get('word_logprobs', [0.0] * len(words))[word_idx])
579
+ if word_idx < len(row.get('word_logprobs', []))
580
+ else 0.0,
581
+ 'word_confidence': float(row.get('word_confidences', [1.0] * len(words))[word_idx])
582
+ if word_idx < len(row.get('word_confidences', []))
583
+ else 1.0,
584
+ })
585
+ row['word_timestamps'] = word_rows
586
+ row['timestamp_source'] = 'charsiu_hypothesis_alignment'
587
+ row['phone_times'] = phone_times
588
+ row['phone_acoustic_supports'] = phone_acoustic_supports
589
+ except Exception as exc:
590
+ row['word_timestamps'] = estimate_word_timestamps(words, audio_end)
591
+ row['timestamp_source'] = 'duration_proportional_fallback'
592
+ row['timestamp_fallback_reason'] = repr(exc)
593
+ phone_times = []
594
+ phone_acoustic_supports = []
595
+ for phone_idx, word_idx in enumerate(hyp['phone_to_word']):
596
+ if word_idx < len(row['word_timestamps']):
597
+ word_time = row['word_timestamps'][word_idx]
598
+ phone_times.append((float(word_time['start']), float(word_time['end'])))
599
+ else:
600
+ phone_times.append((0.0, float(audio_end)))
601
+ phone_acoustic_supports.append(0.0)
602
+ row['phone_times'] = phone_times
603
+ row['phone_acoustic_supports'] = phone_acoustic_supports
604
+ row.update(hyp)
605
+ return row
606
+
607
+
608
+ def interval_overlap_ratio(a, b):
609
+ if a is None or b is None:
610
+ return 0.0
611
+ a0, a1 = a
612
+ b0, b1 = b
613
+ inter = max(0.0, min(float(a1), float(b1)) - max(float(a0), float(b0)))
614
+ union = max(float(a1), float(b1)) - min(float(a0), float(b0))
615
+ if union <= 0:
616
+ return 0.0
617
+ return inter / union
618
+
619
+
620
+ def align_token_sequences(
621
+ ref_tokens,
622
+ hyp_tokens,
623
+ mismatch_cost=1.0,
624
+ ref_times=None,
625
+ hyp_times=None,
626
+ hyp_confidences=None,
627
+ time_overlap_reward=0.0,
628
+ ):
629
+ n = len(ref_tokens)
630
+ m = len(hyp_tokens)
631
+ dp = np.zeros((n + 1, m + 1), dtype=np.float32)
632
+ back = {}
633
+ for i in range(1, n + 1):
634
+ dp[i, 0] = dp[i - 1, 0] + 1.0
635
+ back[(i, 0)] = (i - 1, 0, 'delete')
636
+ for j in range(1, m + 1):
637
+ dp[0, j] = dp[0, j - 1] + 1.0
638
+ back[(0, j)] = (0, j - 1, 'insert')
639
+ for i in range(1, n + 1):
640
+ for j in range(1, m + 1):
641
+ sub_cost = 0.0 if ref_tokens[i - 1] == hyp_tokens[j - 1] else mismatch_cost
642
+ if ref_times is not None and hyp_times is not None:
643
+ sub_cost -= float(time_overlap_reward) * interval_overlap_ratio(ref_times[i - 1], hyp_times[j - 1])
644
+ if hyp_confidences is not None:
645
+ sub_cost -= 0.1 * float(np.clip(hyp_confidences[j - 1], 0.0, 1.0))
646
+ sub_cost = max(0.0, sub_cost)
647
+ candidates = [
648
+ (dp[i - 1, j - 1] + sub_cost, i - 1, j - 1, 'match'),
649
+ (dp[i - 1, j] + 1.0, i - 1, j, 'delete'),
650
+ (dp[i, j - 1] + 1.0, i, j - 1, 'insert'),
651
+ ]
652
+ cost, prev_i, prev_j, op = min(candidates, key=lambda item: item[0])
653
+ dp[i, j] = cost
654
+ back[(i, j)] = (prev_i, prev_j, op)
655
+
656
+ pairs = []
657
+ i, j = n, m
658
+ while i > 0 or j > 0:
659
+ prev_i, prev_j, op = back[(i, j)]
660
+ if op == 'match':
661
+ pairs.append((i - 1, j - 1))
662
+ elif op == 'delete':
663
+ pairs.append((i - 1, None))
664
+ else:
665
+ pairs.append((None, j - 1))
666
+ i, j = prev_i, prev_j
667
+ pairs.reverse()
668
+ return pairs
669
+
670
+
671
+ def slot_top_phone(slot_counts, eps_index):
672
+ best_idx = None
673
+ best_mass = -1.0
674
+ for phone_idx, mass in slot_counts.items():
675
+ if phone_idx == eps_index:
676
+ continue
677
+ if mass > best_mass:
678
+ best_idx = phone_idx
679
+ best_mass = mass
680
+ return best_idx
681
+
682
+
683
+ def slot_time(slot):
684
+ mass = max(float(slot.get('time_mass', 0.0)), EPS)
685
+ if mass <= EPS:
686
+ return None
687
+ return float(slot.get('start_sum', 0.0) / mass), float(slot.get('end_sum', 0.0) / mass)
688
+
689
+
690
+ def add_slot_phone_mass(slot, phone_idx, phone_mass, eps_index, eps_mass=0.0, phone_time=None, confidence=1.0):
691
+ if phone_mass > 0:
692
+ slot['counts'][int(phone_idx)] += float(phone_mass)
693
+ if eps_mass > 0:
694
+ slot['counts'][eps_index] += float(eps_mass)
695
+ if phone_time is not None and phone_mass > 0:
696
+ slot['start_sum'] = slot.get('start_sum', 0.0) + float(phone_time[0]) * float(phone_mass)
697
+ slot['end_sum'] = slot.get('end_sum', 0.0) + float(phone_time[1]) * float(phone_mass)
698
+ slot['time_mass'] = slot.get('time_mass', 0.0) + float(phone_mass)
699
+ slot['confidence_sum'] = slot.get('confidence_sum', 0.0) + float(confidence) * max(float(phone_mass), 0.0)
700
+ slot['confidence_mass'] = slot.get('confidence_mass', 0.0) + max(float(phone_mass), 0.0)
701
+
702
+
703
+ def insert_slot(slots, insert_at, eps_index, prior_mass):
704
+ slot = {'counts': Counter(), 'start_sum': 0.0, 'end_sum': 0.0, 'time_mass': 0.0}
705
+ if prior_mass > 0:
706
+ slot['counts'][eps_index] += float(prior_mass)
707
+ slots.insert(insert_at, slot)
708
+ return slot
709
+
710
+
711
+ def build_pcn_from_hypotheses(hypotheses, phone_mapper, phn_dict):
712
+ eps_index = len(phn_dict)
713
+ phone_dim = len(phn_dict) + 1
714
+ hyp_logprobs = [float(row.get('length_normalized_sequence_score', row.get('logprob', 0.0))) for row in hypotheses]
715
+ hyp_weights = softmax_1d(hyp_logprobs)
716
+ id_to_phone = {idx: phone for phone, idx in phn_dict.items()}
717
+
718
+ hyp_phone_rows = []
719
+ for row, weight in zip(hypotheses, hyp_weights):
720
+ if 'phone_ids' not in row:
721
+ row = align_hypothesis_with_charsiu(
722
+ item={'probs': np.zeros((0, phone_dim), dtype=np.float32), 'keep_mask': np.zeros((0,), dtype=np.int32), 'frame_step': 0.02},
723
+ row=row,
724
+ phone_mapper=phone_mapper,
725
+ phn_dict=phn_dict,
726
+ phone_to_frame_id={},
727
+ audio_end=0.0,
728
+ )
729
+ phone_ids = [int(phone_idx) for phone_idx in row.get('phone_ids', [])]
730
+ phone_confidences = [float(x) for x in row.get('phone_confidences', [1.0] * len(phone_ids))]
731
+ phone_acoustic = [float(x) for x in row.get('phone_acoustic_supports', [0.0] * len(phone_ids))]
732
+ phone_times = row.get('phone_times', [None] * len(phone_ids))
733
+ hyp_phone_rows.append({
734
+ 'phones': row.get('phones', []),
735
+ 'phone_ids': phone_ids,
736
+ 'phone_to_word': list(row.get('phone_to_word', [])),
737
+ 'phone_confidences': phone_confidences,
738
+ 'phone_acoustic_supports': phone_acoustic,
739
+ 'phone_times': phone_times,
740
+ 'weight': float(weight),
741
+ 'source_counts': row.get('source_counts', {}),
742
+ })
743
+
744
+ slots = []
745
+ total_mass = 0.0
746
+ for hyp_idx, hyp in enumerate(hyp_phone_rows):
747
+ phone_ids = hyp['phone_ids']
748
+ weight = float(hyp['weight'])
749
+ if hyp_idx == 0:
750
+ for local_idx, phone_idx in enumerate(phone_ids):
751
+ slot = {'counts': Counter(), 'start_sum': 0.0, 'end_sum': 0.0, 'time_mass': 0.0}
752
+ quality = float(np.clip(
753
+ 0.5 * hyp['phone_confidences'][local_idx] + 0.5 * hyp['phone_acoustic_supports'][local_idx],
754
+ 0.0,
755
+ 1.0,
756
+ ))
757
+ add_slot_phone_mass(
758
+ slot,
759
+ phone_idx,
760
+ weight * quality,
761
+ eps_index,
762
+ eps_mass=weight * (1.0 - quality),
763
+ phone_time=hyp['phone_times'][local_idx] if local_idx < len(hyp['phone_times']) else None,
764
+ confidence=hyp['phone_confidences'][local_idx],
765
+ )
766
+ slots.append(slot)
767
+ total_mass += weight
768
+ continue
769
+
770
+ consensus_slot_ids = []
771
+ consensus_phone_ids = []
772
+ for slot_idx, slot in enumerate(slots):
773
+ top_idx = slot_top_phone(slot['counts'], eps_index)
774
+ if top_idx is not None:
775
+ consensus_slot_ids.append(slot_idx)
776
+ consensus_phone_ids.append(top_idx)
777
+ consensus_times = [slot_time(slots[slot_idx]) for slot_idx in consensus_slot_ids]
778
+ pairs = align_token_sequences(
779
+ consensus_phone_ids,
780
+ phone_ids,
781
+ ref_times=consensus_times,
782
+ hyp_times=hyp.get('phone_times'),
783
+ hyp_confidences=hyp.get('phone_confidences'),
784
+ time_overlap_reward=0.3,
785
+ )
786
+ cursor = 0
787
+ matched_slots = set()
788
+ for ref_pos, hyp_pos in pairs:
789
+ if ref_pos is None:
790
+ insert_at = consensus_slot_ids[cursor] if cursor < len(consensus_slot_ids) else len(slots)
791
+ slot = insert_slot(slots, insert_at, eps_index, total_mass)
792
+ quality = float(np.clip(
793
+ 0.5 * hyp['phone_confidences'][hyp_pos] + 0.5 * hyp['phone_acoustic_supports'][hyp_pos],
794
+ 0.0,
795
+ 1.0,
796
+ ))
797
+ add_slot_phone_mass(
798
+ slot,
799
+ phone_ids[hyp_pos],
800
+ weight * quality,
801
+ eps_index,
802
+ eps_mass=weight * (1.0 - quality),
803
+ phone_time=hyp['phone_times'][hyp_pos] if hyp_pos < len(hyp['phone_times']) else None,
804
+ confidence=hyp['phone_confidences'][hyp_pos],
805
+ )
806
+ cursor += 1
807
+ else:
808
+ slot_idx = consensus_slot_ids[ref_pos]
809
+ matched_slots.add(slot_idx)
810
+ cursor = ref_pos + 1
811
+ if hyp_pos is None:
812
+ slots[slot_idx]['counts'][eps_index] += weight
813
+ else:
814
+ quality = float(np.clip(
815
+ 0.5 * hyp['phone_confidences'][hyp_pos] + 0.5 * hyp['phone_acoustic_supports'][hyp_pos],
816
+ 0.0,
817
+ 1.0,
818
+ ))
819
+ add_slot_phone_mass(
820
+ slots[slot_idx],
821
+ phone_ids[hyp_pos],
822
+ weight * quality,
823
+ eps_index,
824
+ eps_mass=weight * (1.0 - quality),
825
+ phone_time=hyp['phone_times'][hyp_pos] if hyp_pos < len(hyp['phone_times']) else None,
826
+ confidence=hyp['phone_confidences'][hyp_pos],
827
+ )
828
+ for slot_idx, slot in enumerate(slots):
829
+ if slot_idx not in matched_slots and sum(slot['counts'].values()) < total_mass + weight - EPS:
830
+ if slot_top_phone(slot['counts'], eps_index) is not None:
831
+ slot['counts'][eps_index] += weight
832
+ total_mass += weight
833
+
834
+ if not slots:
835
+ slot = {'counts': Counter()}
836
+ slot['counts'][eps_index] = 1.0
837
+ slots = [slot]
838
+ total_mass = 1.0
839
+
840
+ cn_post = np.zeros((len(slots), phone_dim), dtype=np.float32)
841
+ top_phone_ids = []
842
+ slot_times = []
843
+ slot_confidences = []
844
+ for slot_idx, slot in enumerate(slots):
845
+ slot_mass = sum(slot['counts'].values())
846
+ if slot_mass < total_mass:
847
+ slot['counts'][eps_index] += total_mass - slot_mass
848
+ for phone_idx, mass in slot['counts'].items():
849
+ cn_post[slot_idx, int(phone_idx)] = float(mass) / max(float(total_mass), EPS)
850
+ cn_post[slot_idx] /= np.clip(cn_post[slot_idx].sum(), EPS, None)
851
+ top_phone_ids.append(slot_top_phone(slot['counts'], eps_index))
852
+ slot_times.append(slot_time(slot))
853
+ conf_mass = max(float(slot.get('confidence_mass', 0.0)), EPS)
854
+ slot_confidences.append(float(np.clip(slot.get('confidence_sum', 0.0) / conf_mass, 0.0, 1.0)))
855
+
856
+ return {
857
+ 'cn_post': cn_post,
858
+ 'top_phone_ids': top_phone_ids,
859
+ 'slot_times': slot_times,
860
+ 'slot_confidences': slot_confidences,
861
+ 'hyp_phone_rows': hyp_phone_rows,
862
+ 'hyp_weights': hyp_weights.tolist(),
863
+ 'id_to_phone': id_to_phone,
864
+ 'eps_index': eps_index,
865
+ }
866
+
867
+
868
+ def top_hyp_word_ids_for_slots(pcn):
869
+ slot_word_ids = np.zeros((len(pcn['top_phone_ids']),), dtype=np.int32) - 1
870
+ if not pcn['hyp_phone_rows']:
871
+ return slot_word_ids
872
+ top_hyp = pcn['hyp_phone_rows'][0]
873
+ hyp_phone_ids = list(top_hyp['phone_ids'])
874
+ hyp_phone_to_word = list(top_hyp['phone_to_word'])
875
+ consensus_slot_ids = []
876
+ consensus_phone_ids = []
877
+ for slot_idx, phone_idx in enumerate(pcn['top_phone_ids']):
878
+ if phone_idx is not None:
879
+ consensus_slot_ids.append(slot_idx)
880
+ consensus_phone_ids.append(phone_idx)
881
+ pairs = align_token_sequences(consensus_phone_ids, hyp_phone_ids)
882
+ for ref_pos, hyp_pos in pairs:
883
+ if ref_pos is None or hyp_pos is None:
884
+ continue
885
+ slot_idx = consensus_slot_ids[ref_pos]
886
+ if hyp_pos < len(hyp_phone_to_word):
887
+ slot_word_ids[slot_idx] = int(hyp_phone_to_word[hyp_pos])
888
+ return slot_word_ids
889
+
890
+
891
+ def pcn_stats(cn_post, top_phone_ids, prev_top_phone_ids, eps_index):
892
+ stats = np.zeros((cn_post.shape[0], 5), dtype=np.float32)
893
+ stable_prefix = 0
894
+ for idx, phone_idx in enumerate(top_phone_ids):
895
+ if idx < len(prev_top_phone_ids) and phone_idx == prev_top_phone_ids[idx]:
896
+ stable_prefix += 1
897
+ else:
898
+ break
899
+ prefix_stability = float(stable_prefix) / float(max(len(top_phone_ids), 1))
900
+ for idx, probs in enumerate(cn_post):
901
+ non_eps = np.delete(probs, eps_index)
902
+ top1, margin = top_margin(non_eps)
903
+ stats[idx] = np.array(
904
+ [
905
+ float(probs[eps_index]),
906
+ entropy_np(probs),
907
+ top1,
908
+ margin,
909
+ prefix_stability,
910
+ ],
911
+ dtype=np.float32,
912
+ )
913
+ return stats, prefix_stability
914
+
915
+
916
+ def validate_pcn(pcn):
917
+ cn_post = np.asarray(pcn['cn_post'], dtype=np.float32)
918
+ if not np.isfinite(cn_post).all():
919
+ raise ValueError('pcn_contains_nan_or_inf')
920
+ sums = cn_post.sum(axis=-1)
921
+ if not np.allclose(sums, 1.0, atol=1e-3):
922
+ raise ValueError(f'pcn_posterior_not_normalized:max_abs_err={float(np.max(np.abs(sums - 1.0)))}')
923
+ eps_index = int(pcn['eps_index'])
924
+ if eps_index < 0 or eps_index >= cn_post.shape[-1]:
925
+ raise ValueError('invalid_epsilon_index')
926
+ eps_probs = cn_post[:, eps_index]
927
+ if np.any(eps_probs < -1e-6) or np.any(eps_probs > 1.0 + 1e-6):
928
+ raise ValueError('epsilon_probability_out_of_range')
929
+ for row in pcn.get('hyp_phone_rows', []):
930
+ for value in row.get('phone_confidences', []):
931
+ if not np.isfinite(value) or value < -1e-6 or value > 1.0 + 1e-6:
932
+ raise ValueError('phone_confidence_out_of_range')
933
+ for value in pcn.get('slot_confidences', []):
934
+ if not np.isfinite(value) or value < -1e-6 or value > 1.0 + 1e-6:
935
+ raise ValueError('slot_confidence_out_of_range')
936
+
937
+
938
+ def cumulative_candidate_mask_from_asr(pcn_word_id, word_timestamps, commit_time, audio_end, is_final):
939
+ mask = np.zeros((pcn_word_id.shape[0],), dtype=np.float32)
940
+ committed_words = set()
941
+ for word_idx, word_time in enumerate(word_timestamps or []):
942
+ end_time = float(word_time.get('end', 0.0))
943
+ if end_time <= commit_time + 1e-6 or (is_final and end_time <= audio_end + 1e-6):
944
+ committed_words.add(int(word_idx))
945
+ for slot_idx, word_idx in enumerate(pcn_word_id.tolist()):
946
+ if int(word_idx) in committed_words:
947
+ mask[slot_idx] = 1.0
948
+ return mask
949
+
950
+
951
+ def align_previous_commits(prev_state, current_top_phone_ids):
952
+ if prev_state is None:
953
+ return {}, [], []
954
+ prev_top_phone_ids = prev_state.get('top_phone_ids', [])
955
+ prev_committed = set(int(x) for x in prev_state.get('committed_slots', []))
956
+ pairs = align_token_sequences(prev_top_phone_ids, current_top_phone_ids, mismatch_cost=1.0)
957
+ prev_to_current = {}
958
+ for prev_idx, cur_idx in pairs:
959
+ if prev_idx is None or cur_idx is None:
960
+ continue
961
+ if prev_idx < len(prev_top_phone_ids) and cur_idx < len(current_top_phone_ids):
962
+ if prev_top_phone_ids[prev_idx] == current_top_phone_ids[cur_idx]:
963
+ prev_to_current[int(prev_idx)] = int(cur_idx)
964
+ mapped_old = sorted(prev_to_current[idx] for idx in prev_committed if idx in prev_to_current)
965
+ dropped = sorted(idx for idx in prev_committed if idx not in prev_to_current)
966
+ return prev_to_current, mapped_old, dropped
967
+
968
+
969
+ def _top_word_timestamps(word_timestamps):
970
+ rows = word_timestamps or []
971
+ if rows and isinstance(rows[0], list):
972
+ rows = rows[0]
973
+ return rows if isinstance(rows, list) else []
974
+
975
+
976
+ def _commit_word_metadata(word_timestamps, word_idx):
977
+ rows = _top_word_timestamps(word_timestamps)
978
+ row = rows[word_idx] if 0 <= int(word_idx) < len(rows) and isinstance(rows[word_idx], dict) else {}
979
+ text = normalize_word(str(row.get('word', ''))).casefold()
980
+ try:
981
+ start = float(row.get('start'))
982
+ end = float(row.get('end'))
983
+ except (TypeError, ValueError):
984
+ start = None
985
+ end = None
986
+ return {'text': text, 'start': start, 'end': end}
987
+
988
+
989
+ def _commit_time_iou(left, right):
990
+ if any(item.get(key) is None for item in (left, right) for key in ('start', 'end')):
991
+ return 0.0
992
+ overlap = max(0.0, min(left['end'], right['end']) - max(left['start'], right['start']))
993
+ union = max(left['end'], right['end']) - min(left['start'], right['start'])
994
+ return overlap / union if union > 0 else 0.0
995
+
996
+
997
+ def _word_level_lineage(prev_state, pcn_word_id, current_to_prev, word_timestamps):
998
+ """Build deterministic one-to-one predecessor decisions without using supervision."""
999
+ current_positions = {
1000
+ word_idx: np.flatnonzero(pcn_word_id == word_idx)
1001
+ for word_idx in sorted(set(int(value) for value in pcn_word_id.tolist() if int(value) >= 0))
1002
+ }
1003
+ if prev_state is None:
1004
+ return current_positions, {
1005
+ word_idx: {
1006
+ 'candidate_prev_word_id': None,
1007
+ 'selected_prev_word_id': None,
1008
+ 'predecessor_word_ids': [],
1009
+ 'vote_counts': {},
1010
+ 'mapping_status': 'first_seen',
1011
+ 'split_competitor_count': 0,
1012
+ 'reliable': True,
1013
+ 'text_equal': False,
1014
+ 'time_iou': 0.0,
1015
+ }
1016
+ for word_idx in current_positions
1017
+ }
1018
+
1019
+ prev_word_ids = np.asarray(prev_state.get('pcn_word_id', []), dtype=np.int32)
1020
+ prev_word_states = prev_state.get('word_states', {})
1021
+ decisions = {}
1022
+ competitors_by_prev = {}
1023
+ for word_idx, positions in current_positions.items():
1024
+ votes = Counter()
1025
+ for position in positions.tolist():
1026
+ old_slot = int(current_to_prev[position]) if position < len(current_to_prev) else -1
1027
+ if 0 <= old_slot < len(prev_word_ids):
1028
+ prev_word_idx = int(prev_word_ids[old_slot])
1029
+ if prev_word_idx >= 0:
1030
+ votes[prev_word_idx] += 1
1031
+ strict = [key for key, count in votes.items() if count > len(positions) / 2.0]
1032
+ decision = {
1033
+ 'candidate_prev_word_id': None,
1034
+ 'selected_prev_word_id': None,
1035
+ 'predecessor_word_ids': sorted(int(key) for key in votes),
1036
+ 'vote_counts': {str(key): int(value) for key, value in sorted(votes.items())},
1037
+ 'mapping_status': 'unmatched_new' if not votes else 'ambiguous_no_majority',
1038
+ 'split_competitor_count': 0,
1039
+ 'reliable': not votes,
1040
+ 'text_equal': False,
1041
+ 'time_iou': 0.0,
1042
+ }
1043
+ if len(strict) == 1:
1044
+ prev_word_idx = int(strict[0])
1045
+ prev_meta = prev_word_states.get(prev_word_idx, {}).get('metadata', {})
1046
+ cur_meta = _commit_word_metadata(word_timestamps, word_idx)
1047
+ decision.update({
1048
+ 'candidate_prev_word_id': prev_word_idx,
1049
+ 'mapping_status': 'candidate',
1050
+ 'text_equal': bool(prev_meta.get('text') and cur_meta['text'] and prev_meta['text'] == cur_meta['text']),
1051
+ 'time_iou': float(_commit_time_iou(prev_meta, cur_meta)),
1052
+ })
1053
+ competitors_by_prev.setdefault(prev_word_idx, []).append((word_idx, decision))
1054
+ decisions[word_idx] = decision
1055
+
1056
+ # A strict-majority edge is unique for each current word. Selecting the
1057
+ # highest-weight edge for each predecessor is therefore the global maximum
1058
+ # weight one-to-one matching for this candidate graph.
1059
+ for prev_word_idx, competitors in sorted(competitors_by_prev.items()):
1060
+ for _, decision in competitors:
1061
+ decision['split_competitor_count'] = len(competitors)
1062
+ ranked = sorted(
1063
+ competitors,
1064
+ key=lambda item: (
1065
+ -int(item[1]['vote_counts'][str(prev_word_idx)]),
1066
+ -round(float(item[1]['time_iou']), 12),
1067
+ -int(item[1]['text_equal']),
1068
+ int(item[0]),
1069
+ ),
1070
+ )
1071
+ winner_key = (
1072
+ int(ranked[0][1]['vote_counts'][str(prev_word_idx)]),
1073
+ round(float(ranked[0][1]['time_iou']), 12),
1074
+ int(ranked[0][1]['text_equal']),
1075
+ )
1076
+ tied = [
1077
+ item for item in ranked
1078
+ if (
1079
+ int(item[1]['vote_counts'][str(prev_word_idx)]),
1080
+ round(float(item[1]['time_iou']), 12),
1081
+ int(item[1]['text_equal']),
1082
+ ) == winner_key
1083
+ ]
1084
+ if len(tied) == 1:
1085
+ _, winner = ranked[0]
1086
+ winner['selected_prev_word_id'] = int(prev_word_idx)
1087
+ winner['mapping_status'] = 'accepted'
1088
+ winner['reliable'] = True
1089
+ else:
1090
+ for _, decision in tied:
1091
+ decision['mapping_status'] = 'ambiguous_competition_tie'
1092
+ decision['reliable'] = False
1093
+ for _, loser in ranked[1:]:
1094
+ if loser['mapping_status'] == 'candidate':
1095
+ loser['mapping_status'] = 'split_new_successor'
1096
+ loser['reliable'] = True
1097
+ return current_positions, decisions
1098
+
1099
+
1100
+ def build_stateful_commit_masks(
1101
+ prev_state,
1102
+ pcn,
1103
+ pcn_word_id,
1104
+ word_timestamps,
1105
+ commit_time,
1106
+ audio_end,
1107
+ is_final,
1108
+ current_to_prev_override=None,
1109
+ commit_rule='legacy',
1110
+ ):
1111
+ if commit_rule not in {'legacy', 'merge_aware'}:
1112
+ raise ValueError(f"commit_rule must be 'legacy' or 'merge_aware', got {commit_rule!r}")
1113
+ word_timestamps = _top_word_timestamps(word_timestamps)
1114
+ candidate = cumulative_candidate_mask_from_asr(pcn_word_id, word_timestamps, commit_time, audio_end, is_final)
1115
+ if current_to_prev_override is None:
1116
+ prev_to_current, mapped_old_slots, dropped_or_revised = align_previous_commits(prev_state, pcn['top_phone_ids'])
1117
+ current_to_prev = np.zeros((len(pcn['top_phone_ids']),), dtype=np.int32) - 1
1118
+ for prev_idx, cur_idx in prev_to_current.items():
1119
+ if 0 <= cur_idx < current_to_prev.shape[0]:
1120
+ current_to_prev[cur_idx] = int(prev_idx)
1121
+ else:
1122
+ current_to_prev = np.asarray(current_to_prev_override, dtype=np.int32).copy()
1123
+ prev_to_current = {
1124
+ int(prev_idx): int(cur_idx)
1125
+ for cur_idx, prev_idx in enumerate(current_to_prev.tolist())
1126
+ if int(prev_idx) >= 0
1127
+ }
1128
+ prev_committed = set(int(value) for value in (prev_state or {}).get('committed_slots', []))
1129
+ mapped_old_slots = sorted(cur for prev, cur in prev_to_current.items() if prev in prev_committed)
1130
+ dropped_or_revised = sorted(prev for prev in prev_committed if prev not in prev_to_current)
1131
+ mapped_old_set = set(mapped_old_slots)
1132
+ cumulative = candidate.copy()
1133
+ for slot_idx in mapped_old_set:
1134
+ if 0 <= slot_idx < cumulative.shape[0]:
1135
+ cumulative[slot_idx] = 1.0
1136
+
1137
+ if commit_rule == 'legacy':
1138
+ new_mask = np.zeros_like(cumulative)
1139
+ for word_idx in sorted(set(int(value) for value in pcn_word_id.tolist() if int(value) >= 0)):
1140
+ positions = np.flatnonzero(pcn_word_id == word_idx)
1141
+ if positions.size == 0 or not bool(np.all(candidate[positions] > 0)):
1142
+ continue
1143
+ if any(int(position) in mapped_old_set for position in positions.tolist()):
1144
+ continue
1145
+ new_mask[positions] = 1.0
1146
+ committed_slots = sorted(np.flatnonzero(cumulative > 0).astype(int).tolist())
1147
+ new_slots = sorted(np.flatnonzero(new_mask > 0).astype(int).tolist())
1148
+ diagnostics = {
1149
+ 'mapped_old_slots': mapped_old_slots,
1150
+ 'new_slots': new_slots,
1151
+ 'dropped_or_revised_slots': dropped_or_revised,
1152
+ 'state_event_semantics': 'legacy_slot_any_v1',
1153
+ }
1154
+ next_state = {
1155
+ 'top_phone_ids': list(pcn['top_phone_ids']),
1156
+ 'committed_slots': committed_slots,
1157
+ }
1158
+ return cumulative.astype(np.float32), new_mask.astype(np.float32), current_to_prev, diagnostics, next_state
1159
+
1160
+ current_positions, lineage = _word_level_lineage(
1161
+ prev_state,
1162
+ np.asarray(pcn_word_id, dtype=np.int32),
1163
+ current_to_prev,
1164
+ word_timestamps,
1165
+ )
1166
+ prev_word_states = (prev_state or {}).get('word_states', {})
1167
+ next_logical_id = int((prev_state or {}).get('next_logical_word_id', 0))
1168
+ next_event_id = int((prev_state or {}).get('next_state_event_id', 0))
1169
+ next_word_states = {}
1170
+ word_event_rows = []
1171
+ new_mask = np.zeros_like(cumulative)
1172
+
1173
+ for word_idx, positions in sorted(current_positions.items()):
1174
+ decision = lineage[word_idx]
1175
+ selected_prev = decision['selected_prev_word_id']
1176
+ predecessor_ids = decision['predecessor_word_ids']
1177
+ predecessor_states = [prev_word_states.get(value, {}) for value in predecessor_ids]
1178
+ predecessor_committed = [bool(value.get('committed', False)) for value in predecessor_states]
1179
+ completed = bool(np.all(candidate[positions] > 0))
1180
+ split = int(decision['split_competitor_count']) > 1
1181
+ merge = len(predecessor_ids) > 1
1182
+ inherited_state = prev_word_states.get(selected_prev, {}) if selected_prev is not None else {}
1183
+ has_old_committed_slot = any(int(position) in mapped_old_set for position in positions.tolist())
1184
+ prev_word_ids_array = np.asarray((prev_state or {}).get('pcn_word_id', []), dtype=np.int32)
1185
+ prev_committed_slots = set(int(value) for value in (prev_state or {}).get('committed_slots', []))
1186
+ unassigned_committed_predecessors = 0
1187
+ for position in positions.tolist():
1188
+ old_slot = int(current_to_prev[position]) if position < len(current_to_prev) else -1
1189
+ if old_slot not in prev_committed_slots:
1190
+ continue
1191
+ if old_slot >= len(prev_word_ids_array) or int(prev_word_ids_array[old_slot]) < 0:
1192
+ unassigned_committed_predecessors += 1
1193
+ if unassigned_committed_predecessors:
1194
+ decision['mapping_reliable'] = False
1195
+ decision['reliable'] = False
1196
+ decision['mapping_status'] = 'lineage_broken_unassigned_committed_slot'
1197
+ legacy_emit = completed and not has_old_committed_slot
1198
+ mixed_merge = bool(
1199
+ merge
1200
+ and predecessor_committed
1201
+ and any(predecessor_committed)
1202
+ and not all(predecessor_committed)
1203
+ )
1204
+ partial_word_completed = bool(
1205
+ completed
1206
+ and selected_prev is not None
1207
+ and not inherited_state.get('committed', False)
1208
+ and not merge
1209
+ and bool(decision['reliable'])
1210
+ )
1211
+ # A3-Lite is intentionally minimal: reproduce the legacy mask exactly,
1212
+ # then add one event for a completed mixed committed/partial merge or
1213
+ # for a reliably inherited partial word that has now become complete.
1214
+ emit_event = bool(legacy_emit or (completed and mixed_merge) or partial_word_completed)
1215
+ committed = bool(completed or inherited_state.get('committed', False))
1216
+
1217
+ if selected_prev is not None and not (completed and mixed_merge):
1218
+ logical_id = int(inherited_state['logical_word_id'])
1219
+ else:
1220
+ logical_id = next_logical_id
1221
+ next_logical_id += 1
1222
+
1223
+ if completed and mixed_merge:
1224
+ event_reason = 'mixed_committed_partial_merge_completed'
1225
+ elif mixed_merge:
1226
+ event_reason = 'mixed_committed_partial_merge_partial'
1227
+ elif split:
1228
+ event_reason = 'split_completed' if completed else 'split_partial'
1229
+ elif merge:
1230
+ if completed:
1231
+ event_reason = 'merge_completed' if emit_event else 'merge_completed_legacy_suppressed'
1232
+ else:
1233
+ event_reason = 'merge_partial'
1234
+ elif unassigned_committed_predecessors and completed and not emit_event:
1235
+ event_reason = 'lineage_broken_completed_legacy_suppressed'
1236
+ elif selected_prev is not None and inherited_state.get('committed', False):
1237
+ event_reason = 'persistent_committed'
1238
+ elif selected_prev is not None:
1239
+ event_reason = 'partial_became_completed' if completed else 'partial_persistent'
1240
+ elif predecessor_ids:
1241
+ event_reason = 'ambiguous_completed' if completed else 'ambiguous_partial'
1242
+ else:
1243
+ event_reason = 'newly_completed' if completed else 'new_partial'
1244
+
1245
+ state_event_id = -1
1246
+ if emit_event:
1247
+ new_mask[positions] = 1.0
1248
+ state_event_id = next_event_id
1249
+ next_event_id += 1
1250
+ metadata = _commit_word_metadata(word_timestamps, word_idx)
1251
+ next_word_states[int(word_idx)] = {
1252
+ 'logical_word_id': int(logical_id),
1253
+ 'committed': bool(committed),
1254
+ 'state_event_id': int(state_event_id if state_event_id >= 0 else inherited_state.get('state_event_id', -1)),
1255
+ 'metadata': metadata,
1256
+ }
1257
+ word_event_rows.append({
1258
+ 'current_word_id': int(word_idx),
1259
+ 'logical_word_id': int(logical_id),
1260
+ 'state_event_id': int(state_event_id),
1261
+ 'completed': bool(completed),
1262
+ 'emit_state_event': bool(emit_event),
1263
+ 'event_reason': event_reason,
1264
+ 'mapping_status': decision['mapping_status'],
1265
+ 'mapping_reliable': bool(decision['reliable']),
1266
+ 'selected_prev_word_id': selected_prev,
1267
+ 'predecessor_word_ids': predecessor_ids,
1268
+ 'predecessor_committed': predecessor_committed,
1269
+ 'vote_counts': decision['vote_counts'],
1270
+ 'split_competitor_count': int(decision['split_competitor_count']),
1271
+ 'text': metadata['text'],
1272
+ 'text_equal': bool(decision['text_equal']),
1273
+ 'time_iou': float(decision['time_iou']),
1274
+ 'unassigned_committed_predecessor_slots': int(unassigned_committed_predecessors),
1275
+ })
1276
+
1277
+ committed_slots = sorted(np.flatnonzero(cumulative > 0).astype(int).tolist())
1278
+ new_slots = sorted(np.flatnonzero(new_mask > 0).astype(int).tolist())
1279
+ diagnostics = {
1280
+ 'mapped_old_slots': mapped_old_slots,
1281
+ 'new_slots': new_slots,
1282
+ 'dropped_or_revised_slots': dropped_or_revised,
1283
+ 'word_level_state_events': word_event_rows,
1284
+ 'state_event_semantics': 'word_predecessor_one_to_one_v1',
1285
+ }
1286
+ next_state = {
1287
+ 'top_phone_ids': list(pcn['top_phone_ids']),
1288
+ 'pcn_word_id': np.asarray(pcn_word_id, dtype=np.int32).tolist(),
1289
+ 'committed_slots': committed_slots,
1290
+ 'word_states': next_word_states,
1291
+ 'next_logical_word_id': int(next_logical_id),
1292
+ 'next_state_event_id': int(next_event_id),
1293
+ }
1294
+ return cumulative.astype(np.float32), new_mask.astype(np.float32), current_to_prev, diagnostics, next_state
1295
+
1296
+
1297
+ def acoustic_distribution_for_frames(frame_probs, phone_to_frame_id, phn_dict, phone_dim):
1298
+ out = np.zeros((phone_dim,), dtype=np.float32)
1299
+ if frame_probs.size == 0:
1300
+ return out
1301
+ mean_probs = frame_probs.mean(axis=0)
1302
+ for phone, phone_idx in phn_dict.items():
1303
+ frame_idx = phone_to_frame_id.get(phone)
1304
+ if frame_idx is not None and frame_idx < mean_probs.shape[0]:
1305
+ out[int(phone_idx)] = float(mean_probs[frame_idx])
1306
+ if out.sum() > 0:
1307
+ out /= out.sum()
1308
+ return out
1309
+
1310
+
1311
+ def build_acoustic_evidence(item, cn_post, top_phone_ids, phone_to_frame_id, phn_dict, audio_end):
1312
+ phone_dim = cn_post.shape[-1]
1313
+ acoustic_post = np.zeros_like(cn_post, dtype=np.float32)
1314
+ acoustic_stats = np.zeros((cn_post.shape[0], 4), dtype=np.float32)
1315
+ kept_indices, kept_probs = select_visible_frames(item['probs'], item['keep_mask'], audio_end, item['frame_step'])
1316
+ alignable = [
1317
+ (slot_idx, phone_idx)
1318
+ for slot_idx, phone_idx in enumerate(top_phone_ids)
1319
+ if phone_idx is not None
1320
+ ]
1321
+ if kept_probs.shape[0] < len(alignable) or not alignable:
1322
+ for slot_idx in range(cn_post.shape[0]):
1323
+ acoustic_stats[slot_idx, 3] = js_divergence(cn_post[slot_idx], acoustic_post[slot_idx])
1324
+ return acoustic_post, acoustic_stats, 0
1325
+
1326
+ id_to_phone = {idx: phone for phone, idx in phn_dict.items()}
1327
+ frame_phone_ids = [phone_to_frame_id[id_to_phone[phone_idx]] for _, phone_idx in alignable]
1328
+ try:
1329
+ path = monotonic_align(-np.log(np.clip(kept_probs, EPS, None)), frame_phone_ids)
1330
+ except Exception:
1331
+ for slot_idx in range(cn_post.shape[0]):
1332
+ acoustic_stats[slot_idx, 3] = js_divergence(cn_post[slot_idx], acoustic_post[slot_idx])
1333
+ return acoustic_post, acoustic_stats, 0
1334
+
1335
+ for local_idx, (slot_idx, _) in enumerate(alignable):
1336
+ tok_frames = kept_indices[path == local_idx]
1337
+ if tok_frames.size == 0:
1338
+ acoustic_stats[slot_idx, 3] = js_divergence(cn_post[slot_idx], acoustic_post[slot_idx])
1339
+ continue
1340
+ dist = acoustic_distribution_for_frames(
1341
+ item['probs'][tok_frames],
1342
+ phone_to_frame_id,
1343
+ phn_dict,
1344
+ phone_dim,
1345
+ )
1346
+ acoustic_post[slot_idx] = dist
1347
+ non_eps = dist[:-1]
1348
+ _, margin = top_margin(non_eps)
1349
+ duration = float(tok_frames.size * item['frame_step'])
1350
+ acoustic_stats[slot_idx] = np.array(
1351
+ [
1352
+ entropy_np(dist),
1353
+ margin,
1354
+ duration,
1355
+ js_divergence(cn_post[slot_idx], dist),
1356
+ ],
1357
+ dtype=np.float32,
1358
+ )
1359
+ return acoustic_post, acoustic_stats, int(kept_probs.shape[0])
1360
+
1361
+
1362
+ def compute_prosody(audio, sample_rate, audio_end, word_count, phone_count):
1363
+ audio = np.asarray(audio, dtype=np.float32)
1364
+ duration = float(max(audio_end, EPS))
1365
+ if audio.size == 0:
1366
+ return np.zeros((14,), dtype=np.float32)
1367
+ hop_length = max(int(0.01 * sample_rate), 1)
1368
+ frame_length = max(int(0.025 * sample_rate), hop_length)
1369
+ rms = librosa.feature.rms(y=audio, frame_length=frame_length, hop_length=hop_length)[0]
1370
+ log_energy = np.log(np.clip(rms, EPS, None))
1371
+ energy_threshold = np.percentile(rms, 20) if rms.size else 0.0
1372
+ silence = rms <= max(energy_threshold, EPS)
1373
+ silence_ratio = float(np.mean(silence)) if silence.size else 0.0
1374
+ pause_count, longest_pause = count_pauses(silence, hop_length / sample_rate)
1375
+ try:
1376
+ f0, voiced_flag, voiced_prob = librosa.pyin(
1377
+ audio,
1378
+ fmin=50,
1379
+ fmax=500,
1380
+ sr=sample_rate,
1381
+ frame_length=frame_length,
1382
+ hop_length=hop_length,
1383
+ )
1384
+ voiced = np.isfinite(f0)
1385
+ f0_values = f0[voiced]
1386
+ voiced_prob_mean = float(np.nanmean(voiced_prob)) if voiced_prob is not None else float(np.mean(voiced))
1387
+ except Exception:
1388
+ f0_values = np.array([], dtype=np.float32)
1389
+ voiced = np.zeros_like(rms, dtype=bool)
1390
+ voiced_prob_mean = 0.0
1391
+ f0_mean = float(np.mean(f0_values)) if f0_values.size else 0.0
1392
+ f0_std = float(np.std(f0_values)) if f0_values.size else 0.0
1393
+ f0_slope = slope(f0_values)
1394
+ energy_mean = float(np.mean(log_energy)) if log_energy.size else 0.0
1395
+ energy_std = float(np.std(log_energy)) if log_energy.size else 0.0
1396
+ energy_slope = slope(log_energy)
1397
+ voiced_duration = max(float(np.mean(voiced)) * duration, EPS) if voiced.size else EPS
1398
+ return np.array(
1399
+ [
1400
+ duration,
1401
+ f0_mean,
1402
+ f0_std,
1403
+ f0_slope,
1404
+ voiced_prob_mean,
1405
+ energy_mean,
1406
+ energy_std,
1407
+ energy_slope,
1408
+ silence_ratio,
1409
+ float(pause_count),
1410
+ float(longest_pause),
1411
+ float(word_count) / duration,
1412
+ float(phone_count) / duration,
1413
+ float(phone_count) / voiced_duration,
1414
+ ],
1415
+ dtype=np.float32,
1416
+ )
1417
+
1418
+
1419
+ def slot_prosody_feature_names():
1420
+ return [
1421
+ 'slot_duration',
1422
+ 'slot_log_energy_mean',
1423
+ 'slot_log_energy_std',
1424
+ 'slot_log_energy_max',
1425
+ 'slot_f0_mean',
1426
+ 'slot_f0_std',
1427
+ 'slot_f0_max',
1428
+ 'slot_voiced_ratio',
1429
+ 'slot_position_in_word',
1430
+ 'word_phone_count',
1431
+ 'energy_relative_to_word_mean',
1432
+ 'duration_relative_to_word_mean',
1433
+ 'f0_relative_to_word_mean',
1434
+ 'is_vowel',
1435
+ 'lexical_stress_0',
1436
+ 'lexical_stress_1',
1437
+ 'lexical_stress_2',
1438
+ ]
1439
+
1440
+
1441
+ def compute_slot_prosody(audio, sample_rate, slot_times, pcn_word_id, top_phone_ids, phn_dict):
1442
+ feature_names = slot_prosody_feature_names()
1443
+ slot_count = len(top_phone_ids)
1444
+ out = np.zeros((slot_count, len(feature_names)), dtype=np.float32)
1445
+ if slot_count <= 0:
1446
+ return out
1447
+ audio = np.asarray(audio, dtype=np.float32)
1448
+ id_to_phone = {int(idx): phone for phone, idx in phn_dict.items()}
1449
+ hop_length = max(int(0.01 * sample_rate), 1)
1450
+ frame_length = max(int(0.025 * sample_rate), hop_length)
1451
+ if audio.size:
1452
+ rms = librosa.feature.rms(y=audio, frame_length=frame_length, hop_length=hop_length)[0]
1453
+ log_energy = np.log(np.clip(rms, EPS, None))
1454
+ frame_times = librosa.frames_to_time(np.arange(rms.shape[0]), sr=sample_rate, hop_length=hop_length)
1455
+ else:
1456
+ log_energy = np.zeros((0,), dtype=np.float32)
1457
+ frame_times = np.zeros((0,), dtype=np.float32)
1458
+ try:
1459
+ f0, _, _ = librosa.pyin(
1460
+ audio,
1461
+ fmin=50,
1462
+ fmax=500,
1463
+ sr=sample_rate,
1464
+ frame_length=frame_length,
1465
+ hop_length=hop_length,
1466
+ )
1467
+ except Exception:
1468
+ f0 = np.zeros_like(log_energy, dtype=np.float32) * np.nan
1469
+ if f0 is None:
1470
+ f0 = np.zeros_like(log_energy, dtype=np.float32) * np.nan
1471
+ f0 = np.asarray(f0, dtype=np.float32)
1472
+ common_frames = min(frame_times.shape[0], log_energy.shape[0], f0.shape[0])
1473
+ frame_times = frame_times[:common_frames]
1474
+ log_energy = log_energy[:common_frames]
1475
+ f0 = f0[:common_frames]
1476
+
1477
+ durations = np.zeros((slot_count,), dtype=np.float32)
1478
+ energy_means = np.zeros((slot_count,), dtype=np.float32)
1479
+ f0_means = np.zeros((slot_count,), dtype=np.float32)
1480
+ pcn_word_id = np.asarray(pcn_word_id, dtype=np.int32)
1481
+ for slot_idx in range(slot_count):
1482
+ if slot_idx < len(slot_times) and slot_times[slot_idx] is not None:
1483
+ start, end = slot_times[slot_idx]
1484
+ start = float(start)
1485
+ end = float(max(end, start))
1486
+ else:
1487
+ start = 0.0
1488
+ end = 0.0
1489
+ durations[slot_idx] = max(end - start, 0.0)
1490
+ frame_mask = (frame_times >= start) & (frame_times <= end) if frame_times.size else np.zeros((0,), dtype=bool)
1491
+ energy_values = log_energy[frame_mask]
1492
+ f0_values = f0[frame_mask]
1493
+ f0_values = f0_values[np.isfinite(f0_values)]
1494
+ if energy_values.size:
1495
+ out[slot_idx, 1] = float(np.mean(energy_values))
1496
+ out[slot_idx, 2] = float(np.std(energy_values))
1497
+ out[slot_idx, 3] = float(np.max(energy_values))
1498
+ energy_means[slot_idx] = out[slot_idx, 1]
1499
+ if f0_values.size:
1500
+ out[slot_idx, 4] = float(np.mean(f0_values))
1501
+ out[slot_idx, 5] = float(np.std(f0_values))
1502
+ out[slot_idx, 6] = float(np.max(f0_values))
1503
+ out[slot_idx, 7] = float(f0_values.size / max(frame_mask.sum(), 1))
1504
+ f0_means[slot_idx] = out[slot_idx, 4]
1505
+ out[slot_idx, 0] = durations[slot_idx]
1506
+ word_idx = int(pcn_word_id[slot_idx]) if slot_idx < pcn_word_id.shape[0] else -1
1507
+ same_word = np.flatnonzero(pcn_word_id == word_idx) if word_idx >= 0 else np.asarray([slot_idx], dtype=np.int64)
1508
+ if same_word.size:
1509
+ position = np.flatnonzero(same_word == slot_idx)
1510
+ out[slot_idx, 8] = float(position[0] / max(same_word.size - 1, 1)) if position.size else 0.0
1511
+ out[slot_idx, 9] = float(same_word.size)
1512
+ phone_idx = top_phone_ids[slot_idx] if slot_idx < len(top_phone_ids) else None
1513
+ phone = id_to_phone.get(int(phone_idx), '') if phone_idx is not None else ''
1514
+ out[slot_idx, 13] = 1.0 if phone[:1].upper() in {'A', 'E', 'I', 'O', 'U'} else 0.0
1515
+
1516
+ for slot_idx in range(slot_count):
1517
+ word_idx = int(pcn_word_id[slot_idx]) if slot_idx < pcn_word_id.shape[0] else -1
1518
+ same_word = np.flatnonzero(pcn_word_id == word_idx) if word_idx >= 0 else np.asarray([slot_idx], dtype=np.int64)
1519
+ if same_word.size:
1520
+ word_energy = float(np.mean(energy_means[same_word]))
1521
+ word_duration = float(np.mean(durations[same_word]))
1522
+ word_f0 = float(np.mean(f0_means[same_word]))
1523
+ out[slot_idx, 10] = out[slot_idx, 1] - word_energy
1524
+ out[slot_idx, 11] = durations[slot_idx] / max(word_duration, EPS)
1525
+ out[slot_idx, 12] = out[slot_idx, 4] - word_f0
1526
+ return out.astype(np.float32)
1527
+
1528
+
1529
+ def slope(values):
1530
+ values = np.asarray(values, dtype=np.float32)
1531
+ if values.size < 2:
1532
+ return 0.0
1533
+ x = np.arange(values.size, dtype=np.float32)
1534
+ x = x - x.mean()
1535
+ y = values - values.mean()
1536
+ denom = float(np.sum(x * x))
1537
+ if denom <= EPS:
1538
+ return 0.0
1539
+ return float(np.sum(x * y) / denom)
1540
+
1541
+
1542
+ def count_pauses(silence_mask, frame_step):
1543
+ pause_count = 0
1544
+ longest = 0.0
1545
+ cur = 0
1546
+ for value in silence_mask.tolist():
1547
+ if value:
1548
+ cur += 1
1549
+ elif cur > 0:
1550
+ pause_count += 1
1551
+ longest = max(longest, cur * frame_step)
1552
+ cur = 0
1553
+ if cur > 0:
1554
+ pause_count += 1
1555
+ longest = max(longest, cur * frame_step)
1556
+ return pause_count, longest
streampa/infer.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ from pathlib import Path
4
+ from .pipeline import StreamPAPipeline
5
+
6
+ def main():
7
+ p=argparse.ArgumentParser(description='Frozen StreamPA raw-WAV inference')
8
+ p.add_argument('--audio',required=True)
9
+ p.add_argument('--bundle-dir',required=True)
10
+ p.add_argument('--device',default='cpu')
11
+ p.add_argument('--threads',type=int,default=2)
12
+ p.add_argument('--output',required=True)
13
+ a=p.parse_args()
14
+ result=StreamPAPipeline(a.bundle_dir,a.device,a.threads).score_file(a.audio)
15
+ dest=Path(a.output); dest.parent.mkdir(parents=True,exist_ok=True)
16
+ with dest.open('x',encoding='utf-8') as f: json.dump(result,f,indent=2,allow_nan=False)
17
+ print(json.dumps(dict(output=str(dest),prefixes=len(result['prefixes']),final_status=result['final']['status'])))
18
+
19
+ if __name__=='__main__': main()
streampa/pipeline.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reference-free deployment of the frozen StreamPA scorer and frontends."""
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import time
6
+ from itertools import groupby
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import soundfile as sf
11
+ import torch
12
+ from safetensors.torch import load_file
13
+ from transformers import Wav2Vec2CTCTokenizer, Wav2Vec2FeatureExtractor, Wav2Vec2Processor
14
+
15
+ from . import frontend as fe
16
+ from .charsiu_model import Wav2Vec2ForFrameClassification
17
+ from .streaming_pcn_gopt import PCNStreamingScorer
18
+
19
+
20
+ def read_json(path):
21
+ return json.loads(Path(path).read_text(encoding='utf-8-sig'))
22
+
23
+
24
+ def load_scorer(bundle, device='cpu'):
25
+ bundle = Path(bundle)
26
+ c = read_json(bundle / 'scorer/config.json')
27
+ a = c['args']
28
+ model = PCNStreamingScorer(phone_dim=c['phone_dim'], seq_len=c['seq_len'],
29
+ prosody_dim=c['prosody_dim'], embed_dim=a['embed_dim'], num_heads=a['heads'],
30
+ depth=a['depth'], gru_dim=a['gru_dim'], main_context_tokens=a['main_context_tokens'],
31
+ utt_pooling_head=c['utt_pooling_head'], fusion_mode=c['fusion_mode'],
32
+ slot_prosody_dim=c['slot_prosody_dim'], stress_branch=c['stress_branch'],
33
+ stress_grad_scale=c['stress_grad_scale'])
34
+ if c['preserve_unused_legacy_utt_head']:
35
+ model.utt_head = torch.nn.Sequential(torch.nn.LayerNorm(a['gru_dim']), torch.nn.Linear(a['gru_dim'], 5))
36
+ weights = load_file(str(bundle / 'scorer/model.safetensors'), device='cpu')
37
+ model.load_state_dict(weights, strict=True)
38
+ return model.to(device).eval(), c
39
+
40
+
41
+ class CharsiuFrontend:
42
+ def __init__(self, bundle, device, min_sil_frames):
43
+ self.device = device
44
+ self.min_sil_frames = min_sil_frames
45
+ tokenizer = Wav2Vec2CTCTokenizer.from_pretrained(str(bundle / 'charsiu/tokenizer'), local_files_only=True)
46
+ feature_extractor = Wav2Vec2FeatureExtractor(feature_size=1, sampling_rate=16000,
47
+ padding_value=0.0, do_normalize=True, return_attention_mask=False)
48
+ self.processor = Wav2Vec2Processor(feature_extractor=feature_extractor, tokenizer=tokenizer)
49
+ self.model = Wav2Vec2ForFrameClassification.from_pretrained(str(bundle / 'charsiu/model'),
50
+ local_files_only=True).to(device).eval()
51
+ self.phone_to_id = {}
52
+ for label, idx in tokenizer.get_vocab().items():
53
+ phone = fe.normalize_phone(label)
54
+ if phone and phone not in self.phone_to_id:
55
+ self.phone_to_id[phone] = int(idx)
56
+ self.sil_idx = tokenizer.convert_tokens_to_ids('[SIL]')
57
+
58
+ @torch.inference_mode()
59
+ def __call__(self, audio, audio_end):
60
+ if len(audio) > round(audio_end * 16000):
61
+ raise AssertionError('Charsiu received audio beyond the current boundary')
62
+ # Exact official audio_preprocess: Wav2Vec2Processor normalization, no text.
63
+ x = self.processor(audio, sampling_rate=16000, return_tensors='pt').input_values.to(self.device)
64
+ probs = fe.softmax_np_matrix(self.model(input_values=x).logits[0].cpu().numpy())
65
+ pred = probs.argmax(-1)
66
+ silence = []
67
+ for key, group in groupby(pred):
68
+ group = list(group)
69
+ silence.extend([-1] * len(group) if key == self.sil_idx and len(group) < self.min_sil_frames else group)
70
+ keep = np.asarray(silence) != self.sil_idx
71
+ if not keep.any(): keep[:] = True
72
+ return dict(probs=probs, keep_mask=keep, frame_step=len(audio)/16000/len(probs))
73
+
74
+
75
+ def filter_hypotheses(rows, mapper, config):
76
+ """Keep the historical repetition policy; no reference-length test."""
77
+ kept, rejected = [], []
78
+ for row in rows:
79
+ phones, _, sources = mapper.words_to_phone_sequence(row['words'])
80
+ strict = len(row['words']) > config['strict_repeat_word_trigger'] or len(phones) > config['strict_repeat_phone_trigger']
81
+ repeat = fe.find_repeated_ngram([{'text': w} for w in row['words']],
82
+ min_repeats=config['repeat_ngram_min_repeats'], max_ngram_size=config['repeat_max_ngram_size'],
83
+ strict=strict, min_coverage=config['repeat_ngram_coverage'], dominant_token_ratio=config['repeat_token_ratio'])
84
+ if repeat is not None:
85
+ rejected.append(dict(rank=row['rank'], reason='repetitive_asr_hypothesis', details=repeat))
86
+ elif sources.get('fallback_phone', 0) or sources.get('unmapped', 0):
87
+ rejected.append(dict(rank=row['rank'], reason='g2p_unavailable_for_word', sources=sources))
88
+ elif not row['words'] or not phones:
89
+ rejected.append(dict(rank=row['rank'], reason='empty_hypothesis'))
90
+ else:
91
+ kept.append(dict(row, asr_phone_count=len(phones), phone_source_counts=sources))
92
+ return kept, rejected
93
+
94
+
95
+ class StreamPAPipeline:
96
+ def __init__(self, bundle_dir, device='cpu', threads=2):
97
+ self.bundle_dir = Path(bundle_dir).resolve()
98
+ if device not in ('cpu', 'cuda', 'cuda:0'):
99
+ raise ValueError('Select cpu or one externally isolated CUDA device')
100
+ if device.startswith('cuda') and not torch.cuda.is_available():
101
+ raise RuntimeError('CUDA is not available')
102
+ self.device = device
103
+ torch.set_num_threads(threads)
104
+ torch.manual_seed(1337)
105
+ torch.backends.cuda.matmul.allow_tf32 = False
106
+ self.assets = read_json(self.bundle_dir / 'scorer/inference_assets.json')
107
+ if self.assets['sample_rate'] != 16000 or self.assets['commit_rule'] != 'legacy':
108
+ raise ValueError('Bundle is not the frozen 16-kHz legacy-commit configuration')
109
+ self.model, self.config = load_scorer(self.bundle_dir, device)
110
+ self.manifest = read_json(self.bundle_dir / 'model_manifest.json')
111
+ os.environ['NLTK_DATA'] = str(self.bundle_dir / 'assets/nltk_data')
112
+ import nltk
113
+ nltk.data.path[:] = [os.environ['NLTK_DATA']]
114
+ self.phn_dict = read_json(self.bundle_dir / 'assets/phn_dict.json')
115
+ self.mapper = fe.PhoneMapper(read_json(self.bundle_dir / 'assets/pronunciation_lexicon.json'), self.phn_dict)
116
+ if self.mapper.g2p is None:
117
+ raise RuntimeError('G2P runtime resources missing. Run scripts/setup_assets.py first.')
118
+ self.charsiu = CharsiuFrontend(self.bundle_dir, device, self.assets['min_sil_frames'])
119
+ missing = set(self.phn_dict) - set(self.charsiu.phone_to_id)
120
+ if missing:
121
+ raise RuntimeError(f'Charsiu label mapping missing: {sorted(missing)}')
122
+ self.asr = fe.WhisperNBestGenerator(str(self.bundle_dir / 'whisper'), self.assets['language'],
123
+ device, self.assets['nbest'], self.assets['beam_size'], self.assets['max_new_tokens'],
124
+ self.assets['no_repeat_ngram_size'])
125
+
126
+ @classmethod
127
+ def from_pretrained(cls, repo_id='faeea/StreamPA', revision=None, device='cpu', **kwargs):
128
+ if Path(repo_id).is_dir():
129
+ return cls(repo_id, device=device, **kwargs)
130
+ if revision is None:
131
+ raise ValueError('Specify a fixed Hub commit or release tag')
132
+ from huggingface_hub import snapshot_download
133
+ path = snapshot_download(repo_id, revision=revision)
134
+ # Optional assets are deliberately fetched only by the explicit setup script.
135
+ return cls(path, device=device, **kwargs)
136
+
137
+ def create_session(self):
138
+ return StreamPASession(self)
139
+
140
+ def score_file(self, audio_path, block_samples=4096):
141
+ session = self.create_session()
142
+ rows = []
143
+ with sf.SoundFile(audio_path) as f:
144
+ if f.samplerate != self.assets['sample_rate'] or f.channels != 1:
145
+ raise ValueError('Input must be mono 16000-Hz WAV; convert before starting a session')
146
+ while True:
147
+ block = f.read(block_samples, dtype='float32')
148
+ if len(block) == 0: break
149
+ rows.extend(session.append(block))
150
+ rows.append(session.finish())
151
+ return dict(model='StreamPA', seed=1337, prefixes=rows, final=rows[-1])
152
+
153
+ def make_batch(self, example):
154
+ n = len(example['cn_post']); limit = self.config['seq_len']
155
+ if n > limit: raise ValueError(f'{n} PCN slots exceeds supported {limit}')
156
+ batch = {}
157
+ for key in ['cn_post','cn_stats','acoustic_post','acoustic_stats','slot_prosody',
158
+ 'pcn_word_id','cumulative_commit_mask','new_commit_mask']:
159
+ src = np.asarray(example[key])
160
+ arr = np.full((limit,)+src.shape[1:], -1 if key=='pcn_word_id' else 0, dtype=src.dtype)
161
+ arr[:n] = src
162
+ batch['word_ids' if key=='pcn_word_id' else key] = arr[None]
163
+ batch['prosody'] = example['prosody'][None]
164
+ for key in ['prosody','slot_prosody']:
165
+ mean = np.asarray(self.config[key+'_norm_mean'],np.float32)
166
+ std = np.asarray(self.config[key+'_norm_std'],np.float32)
167
+ batch[key] = (batch[key]-mean)/np.clip(std,1e-6,None)
168
+ batch['visible_len'] = np.asarray([n],np.int64)
169
+ return {k:torch.from_numpy(v).to(self.device) for k,v in batch.items()}
170
+
171
+
172
+ class StreamPASession:
173
+ def __init__(self, pipeline):
174
+ self.pipeline = pipeline
175
+ self.reset()
176
+
177
+ def reset(self):
178
+ self.audio = np.empty(0,np.float32)
179
+ self.state = None
180
+ self.commit_state = None
181
+ self.previous_phones = []
182
+ self.next_step = 1
183
+ self.prefix_index = 0
184
+ self.closed = False
185
+ self.last_result = None
186
+
187
+ def append(self, audio_block):
188
+ if self.closed: raise RuntimeError('Session ended; reset before appending another recording')
189
+ block = np.asarray(audio_block,dtype=np.float32)
190
+ if block.ndim != 1 or not np.isfinite(block).all():
191
+ raise ValueError('Audio block must be finite mono samples')
192
+ if (len(self.audio)+len(block))/16000 > self.pipeline.assets['max_audio_sec']:
193
+ raise ValueError('Recording exceeds supported 30-second frontend limit; no audio was appended')
194
+ self.audio = np.concatenate([self.audio,block])
195
+ rows = []
196
+ while True:
197
+ commit = self.next_step * self.pipeline.assets['chunk_sec']
198
+ end_samples = round((commit+self.pipeline.assets['right_context_sec'])*16000)
199
+ if len(self.audio) < end_samples: break
200
+ rows.append(self._step(round(commit*16000),end_samples,False))
201
+ self.next_step += 1
202
+ return rows
203
+
204
+ def finish(self):
205
+ if self.closed: raise RuntimeError('Session already ended')
206
+ self.closed = True
207
+ return self._step(len(self.audio),len(self.audio),True)
208
+
209
+ def current(self):
210
+ return self.last_result
211
+
212
+ @torch.inference_mode()
213
+ def _step(self, commit_samples, end_samples, is_final):
214
+ started = time.perf_counter(); p = self.pipeline
215
+ audio = self.audio[:end_samples].copy()
216
+ assert len(audio) == end_samples <= len(self.audio)
217
+ commit, end = commit_samples/16000, end_samples/16000
218
+ base = dict(prefix_index=self.prefix_index,commit_time=commit,audio_end=end,
219
+ received_audio_end=len(self.audio)/16000,is_final=is_final,
220
+ lookahead_sec=end-commit,model='StreamPA',seed=1337,
221
+ checkpoint_sha256=p.manifest['original_checkpoint_sha256'],
222
+ input_audio_sha256=hashlib.sha256(audio.tobytes()).hexdigest(),
223
+ frontend_received_samples=dict(whisper=0,charsiu=0,prosody=0,pcn=0),
224
+ state_updated=False,new_committed_word_count=0,scores=None)
225
+ self.prefix_index += 1
226
+ def finish(status, **kw):
227
+ self.last_result=dict(base,status=status,wall_time_sec=time.perf_counter()-started,**kw)
228
+ return self.last_result
229
+ if end_samples == 0: return finish('empty_audio')
230
+ if end_samples < 400: return finish('audio_too_short')
231
+ if float(np.max(np.abs(audio))) < 1e-7: return finish('silence')
232
+ stages={}
233
+ t=time.perf_counter()
234
+ base['frontend_received_samples']['whisper']=end_samples
235
+ try: raw = p.asr.generate(audio,16000,end)
236
+ except Exception as exc: return finish('asr_failed',error=str(exc))
237
+ stages['whisper_sec']=time.perf_counter()-t
238
+ hypotheses, rejected = filter_hypotheses(raw,p.mapper,p.assets['filter'])
239
+ if not hypotheses: return finish('no_usable_hypothesis',rejected_hypotheses=rejected,stages=stages)
240
+ t=time.perf_counter()
241
+ try:
242
+ base['frontend_received_samples']['charsiu']=end_samples
243
+ item=p.charsiu(audio,end)
244
+ hypotheses=[fe.align_hypothesis_with_charsiu(item,dict(row),p.mapper,p.phn_dict,p.charsiu.phone_to_id,end) for row in hypotheses]
245
+ failures=[r.get('timestamp_fallback_reason') for r in hypotheses if r.get('timestamp_source')!='charsiu_hypothesis_alignment']
246
+ if failures: return finish('alignment_failed',reasons=failures,stages=stages)
247
+ base['frontend_received_samples']['pcn']=end_samples
248
+ pcn=fe.build_pcn_from_hypotheses(hypotheses,p.mapper,p.phn_dict)
249
+ fe.validate_pcn(pcn)
250
+ except Exception as exc: return finish('pcn_or_alignment_failed',error=str(exc),stages=stages)
251
+ stages['charsiu_alignment_pcn_sec']=time.perf_counter()-t
252
+ n=len(pcn['top_phone_ids'])
253
+ if n>p.config['seq_len']: return finish('unsupported_slot_length',slot_count=n,limit=p.config['seq_len'],stages=stages)
254
+ if n==0: return finish('no_scorable_slots',stages=stages)
255
+ ids=fe.top_hyp_word_ids_for_slots(pcn)
256
+ stats,_=fe.pcn_stats(pcn['cn_post'],pcn['top_phone_ids'],self.previous_phones,pcn['eps_index'])
257
+ acoustic,astats,frames=fe.build_acoustic_evidence(item,pcn['cn_post'],pcn['top_phone_ids'],p.charsiu.phone_to_id,p.phn_dict,end)
258
+ if frames==0: return finish('acoustic_alignment_failed',stages=stages)
259
+ t=time.perf_counter()
260
+ base['frontend_received_samples']['prosody']=end_samples
261
+ prosody=fe.compute_prosody(audio,16000,end,len(hypotheses[0]['words']),sum(x is not None for x in pcn['top_phone_ids']))
262
+ slot_prosody=fe.compute_slot_prosody(audio,16000,pcn['slot_times'],ids,pcn['top_phone_ids'],p.phn_dict)
263
+ stages['prosody_sec']=time.perf_counter()-t
264
+ cumulative,new,mapping,diagnostics,next_commit=fe.build_stateful_commit_masks(self.commit_state,pcn,ids,
265
+ hypotheses[0]['word_timestamps'],commit,end,is_final,commit_rule='legacy')
266
+ example=dict(cn_post=pcn['cn_post'],cn_stats=stats,acoustic_post=acoustic,acoustic_stats=astats,
267
+ prosody=prosody,slot_prosody=slot_prosody,pcn_word_id=ids,cumulative_commit_mask=cumulative,new_commit_mask=new)
268
+ batch=p.make_batch(example)
269
+ if any(not torch.isfinite(v).all() for v in batch.values()): return finish('nonfinite_features',stages=stages)
270
+ t=time.perf_counter()
271
+ before=None if self.state is None else self.state.clone()
272
+ out=p.model(**batch,prev_state=self.state)
273
+ if p.device.startswith('cuda'): torch.cuda.synchronize()
274
+ stages['scorer_sec']=time.perf_counter()-t
275
+ if any(not torch.isfinite(v).all() for v in out.values() if torch.is_tensor(v)):
276
+ return finish('nonfinite_scores',stages=stages)
277
+ count=len(set(int(x) for x in ids[new>0] if x>=0))
278
+ scorer_count=int(out['new_word_mask'].sum().item())
279
+ if scorer_count!=count:
280
+ raise AssertionError('Scorer GRU input count differs from distinct newly committed ASR words')
281
+ if count==0 and before is not None:
282
+ assert torch.equal(before,out['next_state']), 'GRU state changed without a new committed word'
283
+ self.state=out['next_state']
284
+ self.commit_state=next_commit
285
+ self.previous_phones=list(pcn['top_phone_ids'])
286
+ phone=out['phone_score'][0,:n,0].cpu().numpy()
287
+ word=out['word_scores'][0,:n].cpu().numpy()
288
+ utt=out['utt_scores'][0].cpu().numpy()
289
+ word_names=['accuracy','stress','total']
290
+ utt_names=['accuracy','completeness','fluency','prosodic','total']
291
+ slots=[]
292
+ reverse={v:k for k,v in p.phn_dict.items()}
293
+ for i in range(n):
294
+ ph=pcn['top_phone_ids'][i]
295
+ slots.append(dict(slot_index=i,asr_word_id=int(ids[i]),phone=reverse.get(ph,'<eps>'),
296
+ time=pcn['slot_times'][i],phone_network_raw=float(phone[i]),phone_0_2=float(phone[i]*5),
297
+ phone_0_5=float(phone[i]*12.5),word_network_raw=word[i].tolist(),
298
+ word_0_5=(word[i]*2.5).tolist(),is_committed=bool(cumulative[i]),is_new_commit=bool(new[i]),
299
+ valid_phone=ph is not None))
300
+ words=[]
301
+ for wid in sorted(set(int(v) for v in ids if v>=0)):
302
+ pos=np.flatnonzero(ids==wid)
303
+ values=word[pos].mean(0)
304
+ words.append(dict(asr_word_id=wid,text=hypotheses[0]['words'][wid],slot_indices=pos.tolist(),
305
+ scores_0_5=dict(zip(word_names,(values*2.5).tolist())),scores_network_raw=dict(zip(word_names,values.tolist())),
306
+ committed=bool(np.all(cumulative[pos]>0)),new_commit=bool(np.all(new[pos]>0)),
307
+ revisable=True,aggregation='arithmetic mean of visible slots assigned to this current ASR word'))
308
+ base['state_updated']=count>0
309
+ base['new_committed_word_count']=count
310
+ base['scores']=dict(utterance_network_raw=dict(zip(utt_names,utt.tolist())),
311
+ utterance_0_5={k:float(utt[j]*2.5) for j,k in enumerate(utt_names) if k!='completeness'},
312
+ completeness_status='untrained_not_displayed',clipped=False)
313
+ return finish('ok',slots=slots,words=words,visible_len=n,
314
+ cumulative_commit_mask=cumulative.tolist(),new_commit_mask=new.tolist(),mapped_old_slot=mapping.tolist(),
315
+ commit_diagnostics=diagnostics,stages=stages,rejected_hypotheses=rejected,
316
+ scorer_new_word_count=scorer_count,
317
+ state_sha256=hashlib.sha256(self.state.cpu().numpy().tobytes()).hexdigest(),
318
+ hypothesis_count=len(hypotheses))
streampa/stream.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ import json
3
+ from pathlib import Path
4
+ import soundfile as sf
5
+ from .pipeline import StreamPAPipeline
6
+
7
+ def main():
8
+ p=argparse.ArgumentParser(description='Append raw audio blocks and emit each StreamPA prefix')
9
+ p.add_argument('--audio',required=True)
10
+ p.add_argument('--bundle-dir',required=True)
11
+ p.add_argument('--device',default='cpu')
12
+ p.add_argument('--threads',type=int,default=2)
13
+ p.add_argument('--block-samples',type=int,default=1600)
14
+ p.add_argument('--output',required=True)
15
+ a=p.parse_args()
16
+ session=StreamPAPipeline(a.bundle_dir,a.device,a.threads).create_session()
17
+ dest=Path(a.output); dest.parent.mkdir(parents=True,exist_ok=True)
18
+ with sf.SoundFile(a.audio) as audio, dest.open('x',encoding='utf-8') as f:
19
+ if audio.samplerate!=16000 or audio.channels!=1: raise ValueError('Input must be mono 16000-Hz WAV')
20
+ while True:
21
+ block=audio.read(a.block_samples,dtype='float32')
22
+ if not len(block): break
23
+ for row in session.append(block):
24
+ f.write(json.dumps(row,allow_nan=False)+'\n'); f.flush()
25
+ f.write(json.dumps(session.finish(),allow_nan=False)+'\n')
26
+ print(json.dumps(dict(output=str(dest),prefixes=session.prefix_index)))
27
+
28
+ if __name__=='__main__': main()
streampa/streaming_gopt.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import math
4
+ import warnings
5
+
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+
12
+ def _no_grad_trunc_normal_(tensor, mean, std, a, b):
13
+ def norm_cdf(x):
14
+ return (1. + math.erf(x / math.sqrt(2.))) / 2.
15
+
16
+ if (mean < a - 2 * std) or (mean > b + 2 * std):
17
+ warnings.warn(
18
+ "mean is more than 2 std from [a, b] in nn.init.trunc_normal_. "
19
+ "The distribution of values may be incorrect.",
20
+ stacklevel=2,
21
+ )
22
+
23
+ with torch.no_grad():
24
+ l = norm_cdf((a - mean) / std)
25
+ u = norm_cdf((b - mean) / std)
26
+ tensor.uniform_(2 * l - 1, 2 * u - 1)
27
+ tensor.erfinv_()
28
+ tensor.mul_(std * math.sqrt(2.))
29
+ tensor.add_(mean)
30
+ tensor.clamp_(min=a, max=b)
31
+ return tensor
32
+
33
+
34
+ def trunc_normal_(tensor, mean=0., std=1., a=-2., b=2.):
35
+ return _no_grad_trunc_normal_(tensor, mean, std, a, b)
36
+
37
+
38
+ class MaskedAttention(nn.Module):
39
+ def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
40
+ super().__init__()
41
+ self.num_heads = num_heads
42
+ head_dim = dim // num_heads
43
+ self.scale = qk_scale or head_dim ** -0.5
44
+
45
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
46
+ self.attn_drop = nn.Dropout(attn_drop)
47
+ self.proj = nn.Linear(dim, dim)
48
+ self.proj_drop = nn.Dropout(proj_drop)
49
+
50
+ def forward(self, x, attn_mask):
51
+ bsz, seq_len, dim = x.shape
52
+ qkv = self.qkv(x).reshape(bsz, seq_len, 3, self.num_heads, dim // self.num_heads)
53
+ qkv = qkv.permute(2, 0, 3, 1, 4)
54
+ q, k, v = qkv[0], qkv[1], qkv[2]
55
+
56
+ attn = (q @ k.transpose(-2, -1)) * self.scale
57
+ attn = attn.masked_fill(~attn_mask.unsqueeze(1), torch.finfo(attn.dtype).min)
58
+ attn = attn.softmax(dim=-1)
59
+ attn = self.attn_drop(attn)
60
+
61
+ x = (attn @ v).transpose(1, 2).reshape(bsz, seq_len, dim)
62
+ x = self.proj(x)
63
+ x = self.proj_drop(x)
64
+ return x
65
+
66
+
67
+ class Mlp(nn.Module):
68
+ def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
69
+ super().__init__()
70
+ out_features = out_features or in_features
71
+ hidden_features = hidden_features or in_features
72
+ self.fc1 = nn.Linear(in_features, hidden_features)
73
+ self.act = act_layer()
74
+ self.fc2 = nn.Linear(hidden_features, out_features)
75
+ self.drop = nn.Dropout(drop)
76
+
77
+ def forward(self, x):
78
+ x = self.fc1(x)
79
+ x = self.act(x)
80
+ x = self.drop(x)
81
+ x = self.fc2(x)
82
+ x = self.drop(x)
83
+ return x
84
+
85
+
86
+ class StreamingBlock(nn.Module):
87
+ def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
88
+ act_layer=nn.GELU, norm_layer=nn.LayerNorm):
89
+ super().__init__()
90
+ self.norm1 = norm_layer(dim)
91
+ self.attn = MaskedAttention(
92
+ dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop,
93
+ )
94
+ self.norm2 = norm_layer(dim)
95
+ mlp_hidden_dim = int(dim * mlp_ratio)
96
+ self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
97
+
98
+ def forward(self, x, attn_mask):
99
+ x = x + self.attn(self.norm1(x), attn_mask)
100
+ x = x + self.mlp(self.norm2(x))
101
+ return x
102
+
103
+
104
+ class StreamingGOPT(nn.Module):
105
+ def __init__(self, embed_dim, num_heads, depth, input_dim, seq_len, phn_num, use_phone_embedding=True):
106
+ super().__init__()
107
+ self.input_dim = input_dim
108
+ self.embed_dim = embed_dim
109
+ self.seq_len = seq_len
110
+ self.phn_num = phn_num
111
+ self.use_phone_embedding = use_phone_embedding
112
+
113
+ self.blocks = nn.ModuleList([StreamingBlock(dim=embed_dim, num_heads=num_heads) for _ in range(depth)])
114
+ self.pos_embed = nn.Parameter(torch.zeros(1, self.seq_len + 5, self.embed_dim))
115
+ trunc_normal_(self.pos_embed, std=.02)
116
+
117
+ self.in_proj = nn.Linear(self.input_dim, embed_dim)
118
+ self.phn_proj = nn.Linear(self.phn_num, embed_dim)
119
+ self.cls_tokens = nn.Parameter(torch.zeros(1, 5, embed_dim))
120
+ trunc_normal_(self.cls_tokens, std=.02)
121
+
122
+ self.mlp_head_phn = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
123
+ self.mlp_head_word1 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
124
+ self.mlp_head_word2 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
125
+ self.mlp_head_word3 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
126
+ self.mlp_head_word4 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
127
+ self.mlp_head_utt1 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
128
+ self.mlp_head_utt2 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
129
+ self.mlp_head_utt3 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
130
+ self.mlp_head_utt4 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
131
+ self.mlp_head_utt5 = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
132
+
133
+ def build_streaming_mask(self, visible_lens, max_phone_tokens, main_context_tokens, right_context_tokens, device):
134
+ total_len = max_phone_tokens + 5
135
+ mask = torch.zeros((visible_lens.shape[0], total_len, total_len), dtype=torch.bool, device=device)
136
+ for row, visible_len in enumerate(visible_lens.tolist()):
137
+ visible_len = int(visible_len)
138
+ cls_start = visible_len
139
+ valid_len = visible_len + 5
140
+
141
+ for tok_idx in range(visible_len):
142
+ block_end = ((tok_idx // main_context_tokens) + 1) * main_context_tokens - 1
143
+ max_key = min(visible_len - 1, block_end + right_context_tokens)
144
+ mask[row, tok_idx, :max_key + 1] = True
145
+
146
+ for cls_offset in range(5):
147
+ query_idx = cls_start + cls_offset
148
+ mask[row, query_idx, :query_idx + 1] = True
149
+
150
+ for pad_idx in range(valid_len, total_len):
151
+ mask[row, pad_idx, pad_idx] = True
152
+
153
+ return mask
154
+
155
+ def pack_sequence(self, token_embed, phn_ids):
156
+ batch_size, max_phone_tokens, embed_dim = token_embed.shape
157
+ visible_mask = phn_ids >= 0
158
+ visible_lens = visible_mask.sum(dim=1)
159
+ total_len = max_phone_tokens + 5
160
+
161
+ packed = token_embed.new_zeros(batch_size, total_len, embed_dim)
162
+ cls_positions = torch.zeros(batch_size, 5, dtype=torch.long, device=token_embed.device)
163
+
164
+ cls_tokens = self.cls_tokens.expand(batch_size, -1, -1)
165
+ for row, visible_len in enumerate(visible_lens.tolist()):
166
+ visible_len = int(visible_len)
167
+ if visible_len > 0:
168
+ packed[row, :visible_len] = token_embed[row, :visible_len]
169
+ cls_start = visible_len
170
+ packed[row, cls_start:cls_start + 5] = cls_tokens[row]
171
+ cls_positions[row] = torch.arange(cls_start, cls_start + 5, device=token_embed.device)
172
+
173
+ packed = packed + self.pos_embed[:, :total_len]
174
+ return packed, visible_lens, cls_positions
175
+
176
+ def unpack_phone_states(self, hidden, visible_lens, max_phone_tokens):
177
+ phone_hidden = hidden.new_zeros(hidden.shape[0], max_phone_tokens, hidden.shape[-1])
178
+ for row, visible_len in enumerate(visible_lens.tolist()):
179
+ visible_len = int(visible_len)
180
+ if visible_len > 0:
181
+ phone_hidden[row, :visible_len] = hidden[row, :visible_len]
182
+ return phone_hidden
183
+
184
+ def forward(self, x, phn, main_context_tokens=8, right_context_tokens=2):
185
+ main_context_tokens = max(int(main_context_tokens), 1)
186
+ right_context_tokens = max(int(right_context_tokens), 0)
187
+
188
+ batch_size, max_phone_tokens, _ = x.shape
189
+ visible_mask = phn >= 0
190
+ x = self.in_proj(x) if self.embed_dim != self.input_dim else x
191
+
192
+ phn_clamped = torch.clamp(phn.long() + 1, min=0, max=self.phn_num - 1)
193
+ phn_one_hot = F.one_hot(phn_clamped, num_classes=self.phn_num).float()
194
+ phn_embed = self.phn_proj(phn_one_hot) * visible_mask.unsqueeze(-1)
195
+ token_embed = x + phn_embed if self.use_phone_embedding else x
196
+ packed, visible_lens, cls_positions = self.pack_sequence(token_embed, phn)
197
+
198
+ attn_mask = self.build_streaming_mask(
199
+ visible_lens=visible_lens,
200
+ max_phone_tokens=max_phone_tokens,
201
+ main_context_tokens=main_context_tokens,
202
+ right_context_tokens=right_context_tokens,
203
+ device=x.device,
204
+ )
205
+
206
+ hidden = packed
207
+ for blk in self.blocks:
208
+ hidden = blk(hidden, attn_mask)
209
+
210
+ phone_hidden = self.unpack_phone_states(hidden, visible_lens, max_phone_tokens)
211
+ batch_index = torch.arange(batch_size, device=x.device).unsqueeze(1)
212
+ cls_hidden = hidden[batch_index, cls_positions]
213
+
214
+ u1 = self.mlp_head_utt1(cls_hidden[:, 0])
215
+ u2 = self.mlp_head_utt2(cls_hidden[:, 1])
216
+ u3 = self.mlp_head_utt3(cls_hidden[:, 2])
217
+ u4 = self.mlp_head_utt4(cls_hidden[:, 3])
218
+ u5 = self.mlp_head_utt5(cls_hidden[:, 4])
219
+
220
+ p = self.mlp_head_phn(phone_hidden)
221
+ w1 = self.mlp_head_word1(phone_hidden)
222
+ w2 = self.mlp_head_word2(phone_hidden)
223
+ w3 = self.mlp_head_word3(phone_hidden)
224
+ w4 = self.mlp_head_word4(phone_hidden)
225
+ return u1, u2, u3, u4, u5, p, w1, w2, w3, w4
226
+
227
+
228
+ class StreamingGOPTNoPhn(StreamingGOPT):
229
+ def __init__(self, embed_dim, num_heads, depth, input_dim, seq_len, phn_num):
230
+ super().__init__(
231
+ embed_dim=embed_dim,
232
+ num_heads=num_heads,
233
+ depth=depth,
234
+ input_dim=input_dim,
235
+ seq_len=seq_len,
236
+ phn_num=phn_num,
237
+ use_phone_embedding=False,
238
+ )
streampa/streaming_pcn_gopt.py ADDED
@@ -0,0 +1,422 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import torch
4
+ import torch.nn as nn
5
+
6
+ from .streaming_gopt import StreamingBlock, trunc_normal_
7
+
8
+
9
+ def masked_mean(values, mask, dim, keepdim=False):
10
+ mask = mask.to(dtype=values.dtype)
11
+ while mask.dim() < values.dim():
12
+ mask = mask.unsqueeze(-1)
13
+ denom = mask.sum(dim=dim, keepdim=keepdim).clamp_min(1.0)
14
+ return (values * mask).sum(dim=dim, keepdim=keepdim) / denom
15
+
16
+
17
+ class ReliabilityGate(nn.Module):
18
+ def __init__(self, hidden_dim=16, output_dim=1):
19
+ super().__init__()
20
+ self.net = nn.Sequential(
21
+ nn.Linear(4, hidden_dim),
22
+ nn.GELU(),
23
+ nn.Linear(hidden_dim, int(output_dim)),
24
+ )
25
+
26
+ def forward(self, cn_stats, acoustic_stats):
27
+ # cn_stats: eps, entropy, top1, margin, prefix_stability
28
+ # acoustic_stats: entropy, margin, duration, PCN-Charsiu JS divergence
29
+ gate_input = torch.stack(
30
+ [
31
+ cn_stats[..., 1],
32
+ acoustic_stats[..., 0],
33
+ acoustic_stats[..., 3],
34
+ cn_stats[..., 4],
35
+ ],
36
+ dim=-1,
37
+ )
38
+ return torch.sigmoid(self.net(gate_input))
39
+
40
+
41
+ class PCNStreamingScorer(nn.Module):
42
+ """
43
+ Stateful streaming scorer for streaming_pcn_gopt_v2_stateful.
44
+
45
+ Local phone/word heads read all visible PCN slots. The sentence GRU is
46
+ updated only from slots selected by new_commit_mask. cumulative_commit_mask
47
+ is retained for losses/diagnostics and must not be used for GRU updates.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ phone_dim,
53
+ seq_len,
54
+ prosody_dim=14,
55
+ embed_dim=40,
56
+ num_heads=2,
57
+ depth=2,
58
+ pcn_embed_dim=16,
59
+ acoustic_embed_dim=16,
60
+ prosody_embed_dim=8,
61
+ gru_dim=32,
62
+ main_context_tokens=16,
63
+ dropout=0.0,
64
+ use_state_projection=False,
65
+ teacher_state_dim=128,
66
+ utt_pooling_head='gru',
67
+ fusion_mode='scalar_gate',
68
+ slot_prosody_dim=0,
69
+ slot_prosody_embed_dim=8,
70
+ stress_branch='none',
71
+ stress_grad_scale=0.2,
72
+ use_acoustic=True,
73
+ use_prosody=True,
74
+ use_uncertainty_stats=True,
75
+ ):
76
+ super().__init__()
77
+ self.phone_dim = int(phone_dim)
78
+ self.seq_len = int(seq_len)
79
+ self.prosody_dim = int(prosody_dim)
80
+ self.embed_dim = int(embed_dim)
81
+ self.gru_dim = int(gru_dim)
82
+ self.main_context_tokens = max(int(main_context_tokens), 1)
83
+ self.use_state_projection = bool(use_state_projection)
84
+ self.utt_pooling_head = str(utt_pooling_head)
85
+ self.fusion_mode = str(fusion_mode)
86
+ self.slot_prosody_dim = int(slot_prosody_dim or 0)
87
+ self.stress_branch = str(stress_branch)
88
+ self.stress_grad_scale = float(stress_grad_scale)
89
+ self.use_acoustic = bool(use_acoustic)
90
+ self.use_prosody = bool(use_prosody)
91
+ self.use_uncertainty_stats = bool(use_uncertainty_stats)
92
+
93
+ if self.utt_pooling_head not in {'gru', 'gru_visible', 'visible_committed'}:
94
+ raise ValueError("utt_pooling_head must be 'gru', 'gru_visible', or 'visible_committed'.")
95
+ if self.fusion_mode not in {'scalar_gate', 'concat_vector_gate', 'fixed_average'}:
96
+ raise ValueError("fusion_mode must be 'scalar_gate', 'concat_vector_gate', or 'fixed_average'.")
97
+ if self.stress_branch not in {'none', 'detached', 'gradscale'}:
98
+ raise ValueError("stress_branch must be 'none', 'detached', or 'gradscale'.")
99
+
100
+ if pcn_embed_dim != acoustic_embed_dim:
101
+ raise ValueError('pcn_embed_dim and acoustic_embed_dim must match for gated interpolation.')
102
+
103
+ self.pcn_proj = nn.Sequential(
104
+ nn.Linear(self.phone_dim + 5, pcn_embed_dim),
105
+ nn.LayerNorm(pcn_embed_dim),
106
+ nn.GELU(),
107
+ )
108
+ self.acoustic_proj = None
109
+ if self.use_acoustic:
110
+ self.acoustic_proj = nn.Sequential(
111
+ nn.Linear(self.phone_dim + 4, acoustic_embed_dim),
112
+ nn.LayerNorm(acoustic_embed_dim),
113
+ nn.GELU(),
114
+ )
115
+ self.prosody_proj = None
116
+ prosody_fused_dim = 0
117
+ if self.use_prosody:
118
+ prosody_fused_dim = int(prosody_embed_dim)
119
+ self.prosody_proj = nn.Sequential(
120
+ nn.Linear(self.prosody_dim, max(16, prosody_embed_dim * 2)),
121
+ nn.GELU(),
122
+ nn.Linear(max(16, prosody_embed_dim * 2), prosody_embed_dim),
123
+ nn.LayerNorm(prosody_embed_dim),
124
+ )
125
+ if self.use_acoustic and self.fusion_mode != 'fixed_average':
126
+ gate_dim = pcn_embed_dim if self.fusion_mode == 'concat_vector_gate' else 1
127
+ self.reliability_gate = ReliabilityGate(output_dim=gate_dim)
128
+ else:
129
+ self.reliability_gate = None
130
+ self.slot_prosody_proj = None
131
+ slot_prosody_fused_dim = 0
132
+ if self.slot_prosody_dim > 0:
133
+ slot_prosody_fused_dim = int(slot_prosody_embed_dim)
134
+ self.slot_prosody_proj = nn.Sequential(
135
+ nn.Linear(self.slot_prosody_dim, max(16, slot_prosody_fused_dim * 2)),
136
+ nn.GELU(),
137
+ nn.Linear(max(16, slot_prosody_fused_dim * 2), slot_prosody_fused_dim),
138
+ nn.LayerNorm(slot_prosody_fused_dim),
139
+ )
140
+ if not self.use_acoustic:
141
+ evidence_dim = pcn_embed_dim
142
+ elif self.fusion_mode == 'concat_vector_gate':
143
+ evidence_dim = pcn_embed_dim * 4
144
+ else:
145
+ evidence_dim = pcn_embed_dim
146
+ self.fused_proj = nn.Sequential(
147
+ nn.Linear(evidence_dim + prosody_fused_dim + slot_prosody_fused_dim, embed_dim),
148
+ nn.LayerNorm(embed_dim),
149
+ nn.Dropout(dropout),
150
+ )
151
+ self.pos_embed = nn.Parameter(torch.zeros(1, self.seq_len, self.embed_dim))
152
+ trunc_normal_(self.pos_embed, std=.02)
153
+ self.blocks = nn.ModuleList(
154
+ [
155
+ StreamingBlock(
156
+ dim=embed_dim,
157
+ num_heads=num_heads,
158
+ mlp_ratio=2.0,
159
+ qkv_bias=True,
160
+ drop=dropout,
161
+ attn_drop=dropout,
162
+ )
163
+ for _ in range(depth)
164
+ ]
165
+ )
166
+
167
+ self.word_pool_proj = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, embed_dim), nn.GELU())
168
+ self.sentence_gru = None
169
+ if self.utt_pooling_head != 'visible_committed':
170
+ self.sentence_gru = nn.GRU(input_size=embed_dim, hidden_size=gru_dim, batch_first=True)
171
+
172
+ self.phone_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
173
+ self.word_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 3))
174
+ self.stress_head = None
175
+ if self.stress_branch != 'none':
176
+ self.stress_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
177
+ self.asr_correct_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
178
+ self.uncertainty_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
179
+ self.confidence_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
180
+ self.abstention_head = nn.Sequential(nn.LayerNorm(embed_dim), nn.Linear(embed_dim, 1))
181
+ self.utt_head = None
182
+ if self.utt_pooling_head == 'gru':
183
+ self.utt_head = nn.Sequential(nn.LayerNorm(gru_dim), nn.Linear(gru_dim, 5))
184
+ self.utt_visible_head = None
185
+ if self.utt_pooling_head == 'gru_visible':
186
+ visible_dim = gru_dim + embed_dim + embed_dim + 2
187
+ self.utt_visible_head = nn.Sequential(nn.LayerNorm(visible_dim), nn.Linear(visible_dim, 5))
188
+ elif self.utt_pooling_head == 'visible_committed':
189
+ visible_dim = embed_dim + embed_dim + 2
190
+ self.utt_visible_head = nn.Sequential(nn.LayerNorm(visible_dim), nn.Linear(visible_dim, 5))
191
+ self.state_projection = (
192
+ nn.Sequential(nn.LayerNorm(gru_dim), nn.Linear(gru_dim, int(teacher_state_dim)))
193
+ if self.use_state_projection
194
+ else None
195
+ )
196
+
197
+ def build_local_causal_mask(self, valid_mask):
198
+ batch_size, seq_len = valid_mask.shape
199
+ idx = torch.arange(seq_len, device=valid_mask.device)
200
+ causal = idx.view(1, -1) <= idx.view(-1, 1)
201
+ local = idx.view(1, -1) >= (idx.view(-1, 1) - self.main_context_tokens + 1)
202
+ base = causal & local
203
+ mask = base.unsqueeze(0).expand(batch_size, -1, -1).clone()
204
+ key_valid = valid_mask.unsqueeze(1).expand(-1, seq_len, -1)
205
+ query_valid = valid_mask.unsqueeze(2).expand(-1, -1, seq_len)
206
+ mask = mask & key_valid & query_valid
207
+ diag = torch.eye(seq_len, dtype=torch.bool, device=valid_mask.device).unsqueeze(0)
208
+ return mask | diag
209
+
210
+ def encode_slots(self, cn_post, cn_stats, acoustic_post, acoustic_stats, prosody, valid_mask, slot_prosody=None):
211
+ if not self.use_uncertainty_stats:
212
+ cn_stats = cn_stats.clone()
213
+ acoustic_stats = acoustic_stats.clone()
214
+ cn_stats[..., 1] = 0.0 # PCN entropy
215
+ cn_stats[..., 3] = 0.0 # PCN margin
216
+ cn_stats[..., 4] = 0.0 # prefix stability
217
+ acoustic_stats[..., 0] = 0.0 # acoustic entropy
218
+ acoustic_stats[..., 1] = 0.0 # acoustic margin
219
+ acoustic_stats[..., 3] = 0.0 # PCN/Charsiu JS divergence
220
+ z_pcn = self.pcn_proj(torch.cat([cn_post, cn_stats], dim=-1))
221
+ if self.use_acoustic:
222
+ z_acoustic = self.acoustic_proj(torch.cat([acoustic_post, acoustic_stats], dim=-1))
223
+ if self.fusion_mode == 'fixed_average':
224
+ gate = cn_post.new_full((*cn_post.shape[:2], 1), 0.5)
225
+ interpolation = 0.5 * z_acoustic + 0.5 * z_pcn
226
+ else:
227
+ gate = self.reliability_gate(cn_stats, acoustic_stats)
228
+ interpolation = gate * z_acoustic + (1.0 - gate) * z_pcn
229
+ if self.fusion_mode == 'concat_vector_gate':
230
+ evidence = torch.cat([z_pcn, z_acoustic, z_acoustic - z_pcn, interpolation], dim=-1)
231
+ else:
232
+ evidence = interpolation
233
+ else:
234
+ gate = cn_post.new_zeros((*cn_post.shape[:2], 1))
235
+ evidence = z_pcn
236
+ fused_parts = [evidence]
237
+ if self.prosody_proj is not None:
238
+ z_prosody = self.prosody_proj(prosody).unsqueeze(1).expand(-1, cn_post.shape[1], -1)
239
+ fused_parts.append(z_prosody)
240
+ if self.slot_prosody_proj is not None:
241
+ if slot_prosody is None:
242
+ slot_prosody = cn_post.new_zeros(cn_post.shape[0], cn_post.shape[1], self.slot_prosody_dim)
243
+ fused_parts.append(self.slot_prosody_proj(slot_prosody))
244
+ token = self.fused_proj(torch.cat(fused_parts, dim=-1))
245
+ token = token + self.pos_embed[:, : token.shape[1]]
246
+ token = token * valid_mask.unsqueeze(-1).to(token.dtype)
247
+ return token, gate
248
+
249
+ def local_causal_transformer(self, token, valid_mask):
250
+ attn_mask = self.build_local_causal_mask(valid_mask)
251
+ hidden = token
252
+ for block in self.blocks:
253
+ hidden = block(hidden, attn_mask)
254
+ return hidden * valid_mask.unsqueeze(-1).to(hidden.dtype)
255
+
256
+ def pool_new_committed_words(self, hidden, new_commit_mask, word_ids=None):
257
+ batch_size, _, dim = hidden.shape
258
+ row_groups = []
259
+ max_words = 0
260
+ for row in range(batch_size):
261
+ active = torch.nonzero(new_commit_mask[row] > 0, as_tuple=False).squeeze(1).tolist()
262
+ groups = []
263
+ if active:
264
+ if word_ids is None:
265
+ groups = [[idx] for idx in active]
266
+ else:
267
+ cur_group = [active[0]]
268
+ cur_word = int(word_ids[row, active[0]].item())
269
+ for idx in active[1:]:
270
+ next_word = int(word_ids[row, idx].item())
271
+ if next_word == cur_word and next_word >= 0:
272
+ cur_group.append(idx)
273
+ else:
274
+ groups.append(cur_group)
275
+ cur_group = [idx]
276
+ cur_word = next_word
277
+ groups.append(cur_group)
278
+ row_groups.append(groups)
279
+ max_words = max(max_words, len(groups))
280
+
281
+ max_words = max(max_words, 1)
282
+ pooled_rows = []
283
+ pooled_masks = []
284
+ for row, groups in enumerate(row_groups):
285
+ pooled = hidden.new_zeros(max_words, dim)
286
+ mask = hidden.new_zeros(max_words)
287
+ for group_idx, positions in enumerate(groups):
288
+ pos = torch.tensor(positions, dtype=torch.long, device=hidden.device)
289
+ pooled[group_idx] = self.word_pool_proj(hidden[row, pos].mean(dim=0))
290
+ mask[group_idx] = 1.0
291
+ pooled_rows.append(pooled)
292
+ pooled_masks.append(mask)
293
+ return torch.stack(pooled_rows, dim=0), torch.stack(pooled_masks, dim=0)
294
+
295
+ def update_sentence_state(self, new_words, word_mask, prev_state):
296
+ batch_size = new_words.shape[0]
297
+ if prev_state is not None and prev_state.dim() == 2:
298
+ prev_state = prev_state.unsqueeze(0)
299
+ states = []
300
+ for row in range(batch_size):
301
+ if prev_state is None:
302
+ h0 = new_words.new_zeros(1, 1, self.gru_dim)
303
+ else:
304
+ h0 = prev_state[:, row : row + 1, :]
305
+ word_count = int(word_mask[row].sum().item())
306
+ if word_count <= 0:
307
+ states.append(h0)
308
+ continue
309
+ _, h_row = self.sentence_gru(new_words[row : row + 1, :word_count], h0)
310
+ states.append(h_row)
311
+ return torch.cat(states, dim=1)
312
+
313
+ def forward(
314
+ self,
315
+ cn_post,
316
+ cn_stats,
317
+ acoustic_post,
318
+ acoustic_stats,
319
+ prosody,
320
+ visible_len,
321
+ cumulative_commit_mask=None,
322
+ new_commit_mask=None,
323
+ commit_mask=None,
324
+ word_ids=None,
325
+ slot_prosody=None,
326
+ prev_state=None,
327
+ detach_next_state=False,
328
+ ):
329
+ batch_size, seq_len, _ = cn_post.shape
330
+ device = cn_post.device
331
+ token_idx = torch.arange(seq_len, device=device).unsqueeze(0)
332
+ valid_mask = token_idx < visible_len.to(device).unsqueeze(1)
333
+
334
+ if cumulative_commit_mask is None:
335
+ cumulative_commit_mask = commit_mask
336
+ if cumulative_commit_mask is None:
337
+ cumulative_commit_mask = torch.zeros((batch_size, seq_len), dtype=torch.bool, device=device)
338
+ if new_commit_mask is None:
339
+ # Legacy fallback: safe only for stateless/full-sentence use.
340
+ new_commit_mask = cumulative_commit_mask
341
+ cumulative_commit_mask = (cumulative_commit_mask > 0) & valid_mask
342
+ new_commit_mask = (new_commit_mask > 0) & valid_mask
343
+
344
+ token, gate = self.encode_slots(cn_post, cn_stats, acoustic_post, acoustic_stats, prosody, valid_mask, slot_prosody=slot_prosody)
345
+ slot_hidden = self.local_causal_transformer(token, valid_mask)
346
+ new_words, new_word_mask = self.pool_new_committed_words(slot_hidden, new_commit_mask, word_ids=word_ids)
347
+ if self.sentence_gru is None:
348
+ final_state = slot_hidden.new_zeros(1, batch_size, self.gru_dim)
349
+ else:
350
+ final_state = self.update_sentence_state(new_words, new_word_mask, prev_state)
351
+ next_state = final_state.detach() if detach_next_state else final_state
352
+ sentence_state = final_state[-1]
353
+
354
+ confidence_logit = self.confidence_head(slot_hidden)
355
+ abstention_logit = self.abstention_head(slot_hidden)
356
+ confidence = torch.sigmoid(confidence_logit)
357
+ abstention_probability = torch.sigmoid(abstention_logit)
358
+ if self.utt_pooling_head in {'gru_visible', 'visible_committed'}:
359
+ committed_pool = masked_mean(slot_hidden, cumulative_commit_mask, dim=1)
360
+ visible_pool = masked_mean(slot_hidden, valid_mask, dim=1)
361
+ mean_confidence = masked_mean(confidence, valid_mask, dim=1)
362
+ mean_abstention = masked_mean(abstention_probability, valid_mask, dim=1)
363
+ if self.utt_pooling_head == 'gru_visible':
364
+ utt_input = torch.cat([sentence_state, committed_pool, visible_pool, mean_confidence, mean_abstention], dim=-1)
365
+ else:
366
+ utt_input = torch.cat([committed_pool, visible_pool, mean_confidence, mean_abstention], dim=-1)
367
+ utt_scores = self.utt_visible_head(utt_input)
368
+ else:
369
+ utt_scores = self.utt_head(sentence_state)
370
+ base_word_scores = self.word_head(slot_hidden)
371
+ if self.stress_head is None:
372
+ word_scores = base_word_scores
373
+ else:
374
+ if self.stress_branch == 'detached':
375
+ stress_input = slot_hidden.detach()
376
+ else:
377
+ scale = float(self.stress_grad_scale)
378
+ stress_input = scale * slot_hidden + (1.0 - scale) * slot_hidden.detach()
379
+ stress_score = self.stress_head(stress_input)
380
+ word_scores = torch.cat([base_word_scores[..., 0:1], stress_score, base_word_scores[..., 2:3]], dim=-1)
381
+
382
+ output = {
383
+ 'phone_score': self.phone_head(slot_hidden),
384
+ 'word_scores': word_scores,
385
+ 'utt_scores': utt_scores,
386
+ 'asr_correct_logits': self.asr_correct_head(slot_hidden),
387
+ 'uncertainty_logits': self.uncertainty_head(slot_hidden),
388
+ 'confidence_logit': confidence_logit,
389
+ 'confidence': confidence,
390
+ 'abstention_logit': abstention_logit,
391
+ 'abstention_probability': abstention_probability,
392
+ 'reliability_gate': gate,
393
+ 'slot_hidden': slot_hidden,
394
+ 'new_word_states': new_words,
395
+ 'new_word_mask': new_word_mask,
396
+ 'word_states': new_words,
397
+ 'word_mask': new_word_mask,
398
+ 'sentence_state': sentence_state,
399
+ 'next_state': next_state,
400
+ 'cumulative_commit_mask': cumulative_commit_mask,
401
+ 'new_commit_mask': new_commit_mask,
402
+ }
403
+ if self.state_projection is not None:
404
+ output['state_projection'] = self.state_projection(sentence_state)
405
+ return output
406
+
407
+ @torch.inference_mode()
408
+ def stream_step(self, batch, prev_state=None):
409
+ return self.forward(
410
+ cn_post=batch['cn_post'],
411
+ cn_stats=batch['cn_stats'],
412
+ acoustic_post=batch['acoustic_post'],
413
+ acoustic_stats=batch['acoustic_stats'],
414
+ prosody=batch['prosody'],
415
+ visible_len=batch['visible_len'],
416
+ cumulative_commit_mask=batch.get('cumulative_commit_mask', batch.get('commit_mask')),
417
+ new_commit_mask=batch.get('new_commit_mask'),
418
+ word_ids=batch.get('pcn_word_id'),
419
+ slot_prosody=batch.get('slot_prosody'),
420
+ prev_state=prev_state,
421
+ detach_next_state=True,
422
+ )
tests/test_runtime.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Deterministic contract tests; real audio validation is documented separately."""
2
+ from types import SimpleNamespace
3
+ from pathlib import Path
4
+ import numpy as np
5
+ import pytest
6
+ import torch
7
+ from streampa.pipeline import StreamPASession, load_scorer
8
+ from streampa import frontend as fe
9
+
10
+ class ScheduleSession(StreamPASession):
11
+ def _step(self,commit_samples,end_samples,is_final):
12
+ row=dict(commit=commit_samples,end=end_samples,final=is_final,samples=self.audio[:end_samples].copy())
13
+ self.last_result=row
14
+ return row
15
+
16
+ def session():
17
+ return ScheduleSession(SimpleNamespace(assets={'max_audio_sec':30,'chunk_sec':.64,'right_context_sec':.16}))
18
+
19
+ def test_received_boundary_and_short_eof():
20
+ s=session()
21
+ assert s.append(np.zeros(12799,np.float32))==[]
22
+ r=s.append(np.ones(1,np.float32))[0]
23
+ assert r['commit']==10240 and r['end']==12800 and not r['final']
24
+ s.append(np.ones(100,np.float32))
25
+ r=s.finish()
26
+ assert r['end']==12900 and r['commit']==12900 and r['final']
27
+
28
+ def test_reset_and_new_session_do_not_share_state():
29
+ a=session(); b=session()
30
+ a.state=torch.ones(1,64); a.commit_state={'committed_slots':[0]}
31
+ a.append(np.ones(100)); a.reset()
32
+ assert a.state is None and a.commit_state is None and len(a.audio)==0
33
+ assert b.state is None and len(b.audio)==0
34
+
35
+ def test_suffix_is_not_in_current_input():
36
+ prefix=np.arange(12800,dtype=np.float32)
37
+ a=session(); b=session()
38
+ x=a.append(np.concatenate([prefix,np.ones(10000)]))[0]
39
+ y=b.append(np.concatenate([prefix,-np.ones(10000)]))[0]
40
+ assert np.array_equal(x['samples'],y['samples'])
41
+
42
+ def test_closed_empty_and_invalid_input():
43
+ s=session(); assert s.finish()['end']==0
44
+ with pytest.raises(RuntimeError): s.append(np.zeros(1))
45
+ s.reset()
46
+ with pytest.raises(ValueError): s.append(np.array([np.nan]))
47
+ with pytest.raises(ValueError): s.append(np.zeros((2,2)))
48
+ with pytest.raises(ValueError): s.append(np.zeros(480001))
49
+ assert len(s.audio)==0
50
+
51
+ def test_normalization_and_gap_mass():
52
+ assert fe.normalize_phone('AH0')=='AH'
53
+ assert fe.normalize_word('Hello!')=='HELLO'
54
+ post=np.array([[.6,.1,.3],[.2,.6,.2]],np.float32)
55
+ pcn={'cn_post':post,'eps_index':2}
56
+ fe.validate_pcn(pcn)
57
+ stats,_=fe.pcn_stats(post,[0,1],[],2)
58
+ assert np.allclose(stats[:,0],[.3,.2])
59
+ with pytest.raises(ValueError): fe.validate_pcn({'cn_post':post*0,'eps_index':2})
60
+
61
+ def test_partial_commit_and_legacy_persistence():
62
+ pcn={'top_phone_ids':[1,2],'slot_times':[(0,.3),(.3,.8)]}
63
+ ids=np.array([0,1],np.int32)
64
+ times=[{'word':'A','start':0,'end':.3},{'word':'B','start':.3,'end':.8}]
65
+ cum,new,_,_,state=fe.build_stateful_commit_masks(None,pcn,ids,times,.64,.8,False,commit_rule='legacy')
66
+ assert new.tolist()==[1.,0.] and cum.tolist()==[1.,0.]
67
+ cum,new,_,_,state=fe.build_stateful_commit_masks(state,pcn,ids,times,.8,.8,True,commit_rule='legacy')
68
+ assert new.tolist()==[0.,1.] and cum.tolist()==[1.,1.]
69
+ _,new,_,_,_=fe.build_stateful_commit_masks(state,pcn,ids,times,1,1,True,commit_rule='legacy')
70
+ assert new.sum()==0
71
+
72
+ def test_no_new_words_keeps_state_but_visible_path_remains_active():
73
+ torch.set_num_threads(2); torch.manual_seed(1337)
74
+ model,c=load_scorer(Path(__file__).resolve().parents[1])
75
+ n=c['seq_len']; d=c['phone_dim']
76
+ batch=dict(cn_post=torch.softmax(torch.randn(1,n,d),-1),cn_stats=torch.randn(1,n,5),
77
+ acoustic_post=torch.softmax(torch.randn(1,n,d),-1),acoustic_stats=torch.randn(1,n,4),
78
+ prosody=torch.randn(1,c['prosody_dim']),slot_prosody=torch.randn(1,n,c['slot_prosody_dim']),
79
+ visible_len=torch.tensor([4]),cumulative_commit_mask=torch.zeros(1,n),new_commit_mask=torch.zeros(1,n),
80
+ word_ids=torch.arange(n)[None])
81
+ state=torch.randn(1,1,64)
82
+ with torch.inference_mode():
83
+ first=model(**batch,prev_state=state)
84
+ batch['cn_stats'][:,:4]+=2
85
+ second=model(**batch,prev_state=state)
86
+ assert torch.equal(first['next_state'],state) and torch.equal(second['next_state'],state)
87
+ assert first['new_word_mask'].sum()==0
88
+ assert not torch.equal(first['utt_scores'],second['utt_scores'])
validation_summary.json ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "LOCAL_VALIDATION_PASS_HUB_VERIFICATION_REQUIRED",
3
+ "created_at": "2026-09-10T10:50:20.386964+00:00",
4
+ "python": "3.10.19",
5
+ "torch": "2.5.1+cpu",
6
+ "platform": "Ubuntu WSL2 on local Windows computer",
7
+ "device": "cpu",
8
+ "dtype": "float32",
9
+ "no_training": true,
10
+ "no_finetuning": true,
11
+ "checkpoint_values_unchanged": true,
12
+ "scorer_equivalence": {
13
+ "status": "PASS",
14
+ "atol": 1e-05,
15
+ "rtol": 1e-05,
16
+ "dtype": "float32",
17
+ "device": "cpu",
18
+ "prefixes": 4,
19
+ "server_torch": "2.3.1+cu121",
20
+ "local_torch": "2.5.1+cpu",
21
+ "max_abs": 3.814697265625e-06,
22
+ "max_mse": 1.655095813488414e-13
23
+ },
24
+ "frontend_real_audio": {
25
+ "model_load_sec": 5.285994583999999,
26
+ "platform": "local Ubuntu WSL2",
27
+ "device": "cpu",
28
+ "dtype": "float32",
29
+ "audios": [
30
+ {
31
+ "name": "real_short",
32
+ "duration_sec": 1.951,
33
+ "sha256": "cfc3105c2ac4db72dbd23ad052261ed812a8d1c51a6c98700d89f45835348cc0",
34
+ "prefixes": 3,
35
+ "valid": 2,
36
+ "updates": 2,
37
+ "statuses": [
38
+ "no_usable_hypothesis",
39
+ "ok",
40
+ "ok"
41
+ ],
42
+ "end_to_end_compute_sec": 162.954407101,
43
+ "scorer_compute_sec": 0.01154370499997981
44
+ },
45
+ {
46
+ "name": "real_medium",
47
+ "duration_sec": 2.889,
48
+ "sha256": "165bcc252817fac922eecba9447293fa04d79bb215e05c3eb7934061ed501a58",
49
+ "prefixes": 5,
50
+ "valid": 3,
51
+ "updates": 2,
52
+ "statuses": [
53
+ "no_usable_hypothesis",
54
+ "no_usable_hypothesis",
55
+ "ok",
56
+ "ok",
57
+ "ok"
58
+ ],
59
+ "end_to_end_compute_sec": 250.548563628,
60
+ "scorer_compute_sec": 0.007423772000038298
61
+ },
62
+ {
63
+ "name": "real_long",
64
+ "duration_sec": 4.387,
65
+ "sha256": "450eccef92e195845254f9915cb9b28ea6acffe0a2962526af435e29f330f397",
66
+ "prefixes": 7,
67
+ "valid": 4,
68
+ "updates": 2,
69
+ "statuses": [
70
+ "no_usable_hypothesis",
71
+ "no_usable_hypothesis",
72
+ "ok",
73
+ "no_usable_hypothesis",
74
+ "ok",
75
+ "ok",
76
+ "ok"
77
+ ],
78
+ "end_to_end_compute_sec": 383.971490372,
79
+ "scorer_compute_sec": 0.008973775000185924
80
+ }
81
+ ],
82
+ "status": "PASS",
83
+ "suffix_invariance": "PASS",
84
+ "reset": "PASS",
85
+ "empty_audio": "PASS",
86
+ "silence": "PASS",
87
+ "audio_source": "SpeechOcean762 CC BY 4.0 read speech; fixed availability/duration selection; interface tests only, no accuracy calculation"
88
+ },
89
+ "cli": {
90
+ "status": "PASS",
91
+ "stage": "cli",
92
+ "prefixes": 3,
93
+ "exact_single_stream_match": true,
94
+ "device": "cpu",
95
+ "dtype": "float32"
96
+ },
97
+ "offline": {
98
+ "status": "PASS",
99
+ "stage": "offline",
100
+ "prefixes": 3,
101
+ "exact_single_stream_match": true,
102
+ "device": "cpu",
103
+ "dtype": "float32"
104
+ },
105
+ "valid_prefix_causal_test": {
106
+ "status": "PASS",
107
+ "implementation": "actual fine-tuned Whisper, Charsiu, PCN/prosody and scorer",
108
+ "boundary_samples": 23040,
109
+ "received_samples": 24000,
110
+ "lookahead_seconds": 0.16,
111
+ "prefix_outputs_exact": true,
112
+ "state_hash_exact": true,
113
+ "actual_frontend_samples": {
114
+ "whisper": 23040,
115
+ "charsiu": 23040,
116
+ "prosody": 23040,
117
+ "pcn": 23040
118
+ },
119
+ "prior_history_reused": "one actually executed identical prefix, including original state; no saved prediction substituted for current inference",
120
+ "no_gpu": true
121
+ },
122
+ "failed_synthetic_test": {
123
+ "audio": "local SAPI Good morning",
124
+ "prefixes": 3,
125
+ "statuses": [
126
+ "no_usable_hypothesis",
127
+ "no_usable_hypothesis",
128
+ "no_usable_hypothesis"
129
+ ],
130
+ "reason": "frozen Whisper repetition policy; no false zero score emitted"
131
+ },
132
+ "untested": [
133
+ "native Windows",
134
+ "CUDA",
135
+ "assessment accuracy on new recordings",
136
+ "concurrent sessions"
137
+ ],
138
+ "hub_verification": "Only a release tag created after fresh-download validation marks a verified release."
139
+ }
{whisper_best_model → whisper}/added_tokens.json RENAMED
File without changes
{whisper_best_model → whisper}/config.json RENAMED
@@ -1,5 +1,4 @@
1
  {
2
- "_name_or_path": "/DATA_2/guest/custom-gopt/server_assets/models/whisper-base",
3
  "activation_dropout": 0.0,
4
  "activation_function": "gelu",
5
  "apply_spec_augment": false,
 
1
  {
 
2
  "activation_dropout": 0.0,
3
  "activation_function": "gelu",
4
  "apply_spec_augment": false,
{whisper_best_model → whisper}/generation_config.json RENAMED
File without changes
{whisper_best_model → whisper}/merges.txt RENAMED
File without changes
{whisper_best_model → whisper}/model.safetensors RENAMED
File without changes
{whisper_best_model → whisper}/normalizer.json RENAMED
File without changes
{whisper_best_model → whisper}/preprocessor_config.json RENAMED
File without changes