werpy 3.2.0__tar.gz → 3.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {werpy-3.2.0 → werpy-3.3.0}/CHANGELOG.md +15 -0
- {werpy-3.2.0 → werpy-3.3.0}/PKG-INFO +6 -2
- {werpy-3.2.0 → werpy-3.3.0}/README.md +1 -1
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/conf.py +1 -1
- {werpy-3.2.0 → werpy-3.3.0}/meson.build +1 -1
- {werpy-3.2.0 → werpy-3.3.0}/pyproject.toml +6 -2
- {werpy-3.2.0 → werpy-3.3.0}/werpy/__init__.py +1 -1
- {werpy-3.2.0 → werpy-3.3.0}/werpy/metrics.pyx +180 -6
- {werpy-3.2.0 → werpy-3.3.0}/werpy/wer.py +6 -6
- {werpy-3.2.0 → werpy-3.3.0}/werpy/wers.py +5 -5
- {werpy-3.2.0 → werpy-3.3.0}/.circleci/config.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-example-summary-results-word-error-rate-breakdown.png +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-example-summaryp-results-word-error-rate-breakdown.png +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/assets/images/werpy-logo-word-error-rate.png +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/dependabot.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/bandit.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/codacy.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/codeql.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/mypy.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/pylint.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/release-test.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/release.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/reuse.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/ruff.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/scorecards.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.github/workflows/wheels.yml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.gitignore +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.pylintrc +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/.readthedocs.yaml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/CITATION.cff +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/CODE_OF_CONDUCT.md +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/CONTRIBUTING.md +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/LICENSE +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/LICENSES/BSD-3-Clause.txt +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/REUSE.toml +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/SECURITY.md +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/benchmarks/speed_comparison_librispeech_full.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/Makefile +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/make.bat +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/requirements.in +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/requirements.txt +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/index.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/installation.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/modules.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/images/werpy-example-summary-results-word-error-rate-breakdown.png +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/images/werpy-example-summaryp-results-word-error-rate-breakdown.png +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/index.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/normalization.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/summarization.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/weighted-word-error-rate.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/docs/source/usage/word-error-rate.rst +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_normalize.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_summary.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_summaryp.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_wer.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_werp.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_werps.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/tests/test_wers.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/errorhandler.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/normalize.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/py.typed +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/summary.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/summaryp.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/werp.py +0 -0
- {werpy-3.2.0 → werpy-3.3.0}/werpy/werps.py +0 -0
|
@@ -4,6 +4,21 @@
|
|
|
4
4
|
This changelog file outlines a chronologically ordered list of the changes made on this project.
|
|
5
5
|
It is organized by version and release date followed by a list of Enhancements, New Features, Bug Fixes, and/or Breaking Changes.
|
|
6
6
|
|
|
7
|
+
## Version 3.3.0
|
|
8
|
+
|
|
9
|
+
**Released:** December 19, 2025
|
|
10
|
+
**Tag:** v3.3.0
|
|
11
|
+
|
|
12
|
+
### Enhancements
|
|
13
|
+
|
|
14
|
+
- Implemented ultra-fast WER-only path with space-optimized 2-row dynamic programming algorithm and batch buffer reuse. Added four new functions (`calculations_wer_only()`, `_calculations_wer_only_reuse_ptr()`, `_metrics_batch_wer_only()`, `metrics_wer_only()`) that eliminate backtrace overhead and use O(n) memory instead of O(m×n). This optimization uses pointer swapping instead of value copying and reuses DP buffers across entire batches, providing significant performance gains for `wer()` and `wers()` functions that only need the WER metric without error counts or word lists.
|
|
15
|
+
|
|
16
|
+
- Fixed portability issue in WER-only batch processing by replacing platform-dependent `int*` pointers with guaranteed 32-bit `cnp.int32_t*` pointers. This ensures correct behavior on all platforms where `sizeof(int)` may differ from 4 bytes, while also removing unnecessary type casts for cleaner code that follows NumPy/Cython best practices.
|
|
17
|
+
|
|
18
|
+
- Expanded benchmarking support by adding optional third-party WER libraries (`pywer`, `evaluate`, `universal-edit-distance`, `torchmetrics`) to `pyproject.toml` under the `benchmarks` extra. Updated `benchmark_synthetic_data_local.py` to safely import optional dependencies, ensure all benchmark functions are always defined, and enforce consistent numeric return types. This fixes static analysis warnings, prevents runtime errors when optional packages are missing, and enables more comprehensive and reliable cross-package performance comparisons.
|
|
19
|
+
|
|
20
|
+
- Standardized all Levenshtein dynamic programming buffers and memoryviews to use cnp.int32_t instead of platform-dependent int. This ensures strict dtype alignment with NumPy int32 arrays, removes undefined behavior on platforms where sizeof(int) != 4, and improves type safety without impacting performance.
|
|
21
|
+
|
|
7
22
|
## Version 3.2.0
|
|
8
23
|
|
|
9
24
|
**Released:** December 15, 2025
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: werpy
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.3.0
|
|
4
4
|
Summary: A powerful yet lightweight Python package to calculate and analyze the Word Error Rate (WER).
|
|
5
5
|
Keywords: werpy,wer,word error rate,asr,automatic speech recognition,stt,speech-to-text,levenshtein distance,text similarity,nlp,accuracy metrics
|
|
6
6
|
Author-Email: Ross Armstrong <ross.armstrong@analyticsinmotion.com>
|
|
@@ -68,6 +68,10 @@ Provides-Extra: benchmarks
|
|
|
68
68
|
Requires-Dist: datasets>=4.4.1; extra == "benchmarks"
|
|
69
69
|
Requires-Dist: werx>=0.3.1; extra == "benchmarks"
|
|
70
70
|
Requires-Dist: jiwer>=4.0.0; extra == "benchmarks"
|
|
71
|
+
Requires-Dist: pywer>=0.1.1; extra == "benchmarks"
|
|
72
|
+
Requires-Dist: evaluate>=0.4.6; extra == "benchmarks"
|
|
73
|
+
Requires-Dist: universal-edit-distance>=0.4.3; extra == "benchmarks"
|
|
74
|
+
Requires-Dist: torchmetrics; extra == "benchmarks"
|
|
71
75
|
Description-Content-Type: text/markdown
|
|
72
76
|
|
|
73
77
|
|
|
@@ -85,7 +89,7 @@ Description-Content-Type: text/markdown
|
|
|
85
89
|
| | |
|
|
86
90
|
| --- | --- |
|
|
87
91
|
| Meta | [](https://www.python.org/downloads/) [](https://github.com/psf/black) [](https://werpy.readthedocs.io/en/latest/?badge=latest) [](https://www.analyticsinmotion.com) |
|
|
88
|
-
| License | [](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE) [](https://app.fossa.com/projects/git
|
|
92
|
+
| License | [](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE) [](https://app.fossa.com/projects/git+github.com%2Fanalyticsinmotion%2Fwerpy?ref=badge_small) [](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
|
|
89
93
|
| Security | [](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml) [](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml) [](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml) |
|
|
90
94
|
| Testing | [](https://www.codefactor.io/repository/github/analyticsinmotion/werpy) [](https://dl.circleci.com/status-badge/redirect/gh/analyticsinmotion/werpy/tree/main) [](https://codecov.io/gh/analyticsinmotion/werpy) |
|
|
91
95
|
| Package | [](https://pypi.org/project/werpy/) [](https://pypi.org/project/werpy/) [](https://pepy.tech/project/werpy) [](https://pypi.org/project/werpy/) |
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| | |
|
|
14
14
|
| --- | --- |
|
|
15
15
|
| Meta | [](https://www.python.org/downloads/) [](https://github.com/psf/black) [](https://werpy.readthedocs.io/en/latest/?badge=latest) [](https://www.analyticsinmotion.com) |
|
|
16
|
-
| License | [](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE) [](https://app.fossa.com/projects/git
|
|
16
|
+
| License | [](https://github.com/analyticsinmotion/werpy/blob/main/LICENSE) [](https://app.fossa.com/projects/git+github.com%2Fanalyticsinmotion%2Fwerpy?ref=badge_small) [](https://api.reuse.software/info/github.com/analyticsinmotion/werpy) |
|
|
17
17
|
| Security | [](https://github.com/analyticsinmotion/werpy/actions/workflows/codeql.yml) [](https://github.com/analyticsinmotion/werpy/actions/workflows/codacy.yml) [](https://github.com/analyticsinmotion/werpy/actions/workflows/bandit.yml) |
|
|
18
18
|
| Testing | [](https://www.codefactor.io/repository/github/analyticsinmotion/werpy) [](https://dl.circleci.com/status-badge/redirect/gh/analyticsinmotion/werpy/tree/main) [](https://codecov.io/gh/analyticsinmotion/werpy) |
|
|
19
19
|
| Package | [](https://pypi.org/project/werpy/) [](https://pypi.org/project/werpy/) [](https://pepy.tech/project/werpy) [](https://pypi.org/project/werpy/) |
|
|
@@ -19,7 +19,7 @@ from datetime import datetime
|
|
|
19
19
|
project = "werpy"
|
|
20
20
|
copyright = f'{datetime.now().year} <a href="https://www.analyticsinmotion.com">Analytics in Motion</a>'
|
|
21
21
|
author = "Ross Armstrong"
|
|
22
|
-
release = "3.
|
|
22
|
+
release = "3.3.0"
|
|
23
23
|
|
|
24
24
|
# -- General configuration ---------------------------------------------------
|
|
25
25
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
@@ -9,7 +9,7 @@ requires = [
|
|
|
9
9
|
|
|
10
10
|
[project]
|
|
11
11
|
name = 'werpy'
|
|
12
|
-
version = '3.
|
|
12
|
+
version = '3.3.0'
|
|
13
13
|
description = 'A powerful yet lightweight Python package to calculate and analyze the Word Error Rate (WER).'
|
|
14
14
|
readme = 'README.md'
|
|
15
15
|
requires-python = '>=3.10'
|
|
@@ -75,4 +75,8 @@ benchmarks = [
|
|
|
75
75
|
"datasets>=4.4.1",
|
|
76
76
|
"werx>=0.3.1",
|
|
77
77
|
"jiwer>=4.0.0",
|
|
78
|
-
|
|
78
|
+
"pywer>=0.1.1",
|
|
79
|
+
"evaluate>=0.4.6",
|
|
80
|
+
"universal-edit-distance>=0.4.3",
|
|
81
|
+
"torchmetrics",
|
|
82
|
+
]
|
|
@@ -55,13 +55,13 @@ cpdef cnp.ndarray calculations(object reference, object hypothesis):
|
|
|
55
55
|
# SAFETY: All cells are explicitly initialized below (row 0, col 0, then DP loop).
|
|
56
56
|
# Allocate the (m+1) x (n+1) DP matrix without zero-initialization to avoid
|
|
57
57
|
# redundant memory writes. Boundary conditions are initialized explicitly.
|
|
58
|
-
cdef
|
|
58
|
+
cdef cnp.int32_t[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
|
|
59
59
|
|
|
60
60
|
# Initialize first column and first row (boundary conditions)
|
|
61
61
|
for i in range(m + 1):
|
|
62
|
-
ldm[i, 0] = <
|
|
62
|
+
ldm[i, 0] = <cnp.int32_t>i
|
|
63
63
|
for j in range(n + 1):
|
|
64
|
-
ldm[0, j] = <
|
|
64
|
+
ldm[0, j] = <cnp.int32_t>j
|
|
65
65
|
|
|
66
66
|
# Fill the Levenshtein distance matrix
|
|
67
67
|
# Compute edit distances using a branch-free inner loop and manual minimum
|
|
@@ -181,13 +181,13 @@ cpdef cnp.ndarray calculations_fast(object reference, object hypothesis):
|
|
|
181
181
|
cdef int cost, del_cost, ins_cost, sub_cost, best
|
|
182
182
|
|
|
183
183
|
# Allocate the (m+1) x (n+1) DP matrix without zero-initialization
|
|
184
|
-
cdef
|
|
184
|
+
cdef cnp.int32_t[:, :] ldm = np.empty((m + 1, n + 1), dtype=np.int32)
|
|
185
185
|
|
|
186
186
|
# Initialize first column and first row (boundary conditions)
|
|
187
187
|
for i in range(m + 1):
|
|
188
|
-
ldm[i, 0] = <
|
|
188
|
+
ldm[i, 0] = <cnp.int32_t>i
|
|
189
189
|
for j in range(n + 1):
|
|
190
|
-
ldm[0, j] = <
|
|
190
|
+
ldm[0, j] = <cnp.int32_t>j
|
|
191
191
|
|
|
192
192
|
# Fill the Levenshtein distance matrix
|
|
193
193
|
for i in range(1, m + 1):
|
|
@@ -268,3 +268,177 @@ cpdef object metrics_fast(object reference, object hypothesis):
|
|
|
268
268
|
if isinstance(reference, (list, np.ndarray)) and isinstance(hypothesis, (list, np.ndarray)):
|
|
269
269
|
return _metrics_batch_fast(list(reference), list(hypothesis))
|
|
270
270
|
return calculations_fast(reference, hypothesis)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
@cython.boundscheck(False)
|
|
274
|
+
@cython.wraparound(False)
|
|
275
|
+
cpdef cnp.ndarray calculations_wer_only(object reference, object hypothesis):
|
|
276
|
+
"""
|
|
277
|
+
WER-only fast path - 2-row DP (O(n) memory), no backtrace.
|
|
278
|
+
Returns only [wer, ld, m] without error counts or word tracking.
|
|
279
|
+
|
|
280
|
+
This is the fastest path for pure WER calculation, using space-optimized
|
|
281
|
+
Wagner-Fischer algorithm with rolling 2-row buffer instead of full matrix.
|
|
282
|
+
|
|
283
|
+
Returns (3,) float64 array: [wer, ld, m]
|
|
284
|
+
"""
|
|
285
|
+
cdef list reference_word = reference.split()
|
|
286
|
+
cdef list hypothesis_word = hypothesis.split()
|
|
287
|
+
|
|
288
|
+
cdef Py_ssize_t m = len(reference_word)
|
|
289
|
+
cdef Py_ssize_t n = len(hypothesis_word)
|
|
290
|
+
|
|
291
|
+
cdef Py_ssize_t i, j
|
|
292
|
+
cdef int cost, del_cost, ins_cost, sub_cost, best, ld
|
|
293
|
+
cdef double wer
|
|
294
|
+
|
|
295
|
+
cdef cnp.ndarray prev_arr = np.empty(n + 1, dtype=np.int32)
|
|
296
|
+
cdef cnp.ndarray curr_arr = np.empty(n + 1, dtype=np.int32)
|
|
297
|
+
|
|
298
|
+
cdef cnp.int32_t[:] prev = prev_arr
|
|
299
|
+
cdef cnp.int32_t[:] curr = curr_arr
|
|
300
|
+
|
|
301
|
+
for j in range(n + 1):
|
|
302
|
+
prev[j] = <cnp.int32_t>j
|
|
303
|
+
|
|
304
|
+
for i in range(1, m + 1):
|
|
305
|
+
curr[0] = <cnp.int32_t>i
|
|
306
|
+
for j in range(1, n + 1):
|
|
307
|
+
cost = 0 if reference_word[i - 1] == hypothesis_word[j - 1] else 1
|
|
308
|
+
|
|
309
|
+
del_cost = prev[j] + 1
|
|
310
|
+
ins_cost = curr[j - 1] + 1
|
|
311
|
+
sub_cost = prev[j - 1] + cost
|
|
312
|
+
|
|
313
|
+
best = del_cost
|
|
314
|
+
if ins_cost < best:
|
|
315
|
+
best = ins_cost
|
|
316
|
+
if sub_cost < best:
|
|
317
|
+
best = sub_cost
|
|
318
|
+
|
|
319
|
+
curr[j] = best
|
|
320
|
+
|
|
321
|
+
prev, curr = curr, prev
|
|
322
|
+
|
|
323
|
+
ld = prev[n]
|
|
324
|
+
wer = (<double>ld) / m if m > 0 else 0.0
|
|
325
|
+
|
|
326
|
+
return np.array([wer, <double>ld, <double>m], dtype=np.float64)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@cython.boundscheck(False)
|
|
330
|
+
@cython.wraparound(False)
|
|
331
|
+
cdef inline void _calculations_wer_only_reuse_ptr(
|
|
332
|
+
object reference,
|
|
333
|
+
object hypothesis,
|
|
334
|
+
cnp.int32_t* prev,
|
|
335
|
+
cnp.int32_t* curr,
|
|
336
|
+
double* out3,
|
|
337
|
+
) except *:
|
|
338
|
+
"""
|
|
339
|
+
Internal WER-only DP using caller-provided buffers and pointer swap (no copying).
|
|
340
|
+
Writes: out3[0]=wer, out3[1]=ld, out3[2]=m
|
|
341
|
+
|
|
342
|
+
This implementation uses true pointer swapping instead of copying values,
|
|
343
|
+
eliminating O(n) copy overhead per outer iteration.
|
|
344
|
+
"""
|
|
345
|
+
cdef list reference_word = reference.split()
|
|
346
|
+
cdef list hypothesis_word = hypothesis.split()
|
|
347
|
+
|
|
348
|
+
cdef Py_ssize_t m = len(reference_word)
|
|
349
|
+
cdef Py_ssize_t n = len(hypothesis_word)
|
|
350
|
+
|
|
351
|
+
cdef Py_ssize_t i, j
|
|
352
|
+
cdef int cost, del_cost, ins_cost, sub_cost, best, ld
|
|
353
|
+
cdef cnp.int32_t* tmp
|
|
354
|
+
|
|
355
|
+
# Initialize base row: prev[j] = j for j=0..n
|
|
356
|
+
for j in range(n + 1):
|
|
357
|
+
prev[j] = j
|
|
358
|
+
|
|
359
|
+
for i in range(1, m + 1):
|
|
360
|
+
curr[0] = i
|
|
361
|
+
for j in range(1, n + 1):
|
|
362
|
+
cost = 0 if reference_word[i - 1] == hypothesis_word[j - 1] else 1
|
|
363
|
+
|
|
364
|
+
del_cost = prev[j] + 1
|
|
365
|
+
ins_cost = curr[j - 1] + 1
|
|
366
|
+
sub_cost = prev[j - 1] + cost
|
|
367
|
+
|
|
368
|
+
best = del_cost
|
|
369
|
+
if ins_cost < best:
|
|
370
|
+
best = ins_cost
|
|
371
|
+
if sub_cost < best:
|
|
372
|
+
best = sub_cost
|
|
373
|
+
|
|
374
|
+
curr[j] = best
|
|
375
|
+
|
|
376
|
+
# Swap prev and curr pointers (zero-cost operation)
|
|
377
|
+
tmp = prev
|
|
378
|
+
prev = curr
|
|
379
|
+
curr = tmp
|
|
380
|
+
|
|
381
|
+
ld = prev[n]
|
|
382
|
+
out3[0] = (<double>ld) / m if m > 0 else 0.0
|
|
383
|
+
out3[1] = <double>ld
|
|
384
|
+
out3[2] = <double>m
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@cython.boundscheck(False)
|
|
388
|
+
@cython.wraparound(False)
|
|
389
|
+
cdef cnp.ndarray _metrics_batch_wer_only(list references, list hypotheses):
|
|
390
|
+
"""
|
|
391
|
+
Fast batch processing for WER-only calculations with buffer reuse and pointer swapping.
|
|
392
|
+
|
|
393
|
+
Eliminates repeated buffer allocations by reusing prev/curr arrays across all pairs
|
|
394
|
+
in the batch, sized to the maximum hypothesis length. Uses true pointer swapping
|
|
395
|
+
instead of value copying for optimal performance.
|
|
396
|
+
|
|
397
|
+
Returns (n, 3) float64 array where each row contains:
|
|
398
|
+
[wer, ld, m]
|
|
399
|
+
"""
|
|
400
|
+
cdef Py_ssize_t n_pairs = len(references)
|
|
401
|
+
cdef Py_ssize_t idx
|
|
402
|
+
|
|
403
|
+
cdef cnp.ndarray out = np.empty((n_pairs, 3), dtype=np.float64)
|
|
404
|
+
|
|
405
|
+
# Find max hypothesis token length to size buffers once
|
|
406
|
+
cdef Py_ssize_t max_n = 0
|
|
407
|
+
cdef Py_ssize_t this_n
|
|
408
|
+
cdef object h
|
|
409
|
+
cdef list h_words
|
|
410
|
+
for idx in range(n_pairs):
|
|
411
|
+
h = hypotheses[idx]
|
|
412
|
+
h_words = h.split()
|
|
413
|
+
this_n = len(h_words)
|
|
414
|
+
if this_n > max_n:
|
|
415
|
+
max_n = this_n
|
|
416
|
+
|
|
417
|
+
# Allocate reusable DP buffers once for the entire batch
|
|
418
|
+
cdef cnp.ndarray prev_arr = np.empty(max_n + 1, dtype=np.int32)
|
|
419
|
+
cdef cnp.ndarray curr_arr = np.empty(max_n + 1, dtype=np.int32)
|
|
420
|
+
|
|
421
|
+
# Get raw pointers for zero-cost swapping
|
|
422
|
+
cdef cnp.int32_t* prev = <cnp.int32_t*>cnp.PyArray_DATA(prev_arr)
|
|
423
|
+
cdef cnp.int32_t* curr = <cnp.int32_t*>cnp.PyArray_DATA(curr_arr)
|
|
424
|
+
|
|
425
|
+
# Process each pair using shared buffers, writing directly to output rows
|
|
426
|
+
cdef double* out_row
|
|
427
|
+
for idx in range(n_pairs):
|
|
428
|
+
out_row = <double*>cnp.PyArray_DATA(out) + (idx * 3)
|
|
429
|
+
_calculations_wer_only_reuse_ptr(references[idx], hypotheses[idx], prev, curr, out_row)
|
|
430
|
+
|
|
431
|
+
return out
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
cpdef object metrics_wer_only(object reference, object hypothesis):
|
|
435
|
+
"""
|
|
436
|
+
WER-only metrics entry point (fastest path).
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
- strings: (3,) float64 array [wer, ld, m]
|
|
440
|
+
- sequences: (n, 3) float64 array, one row per pair
|
|
441
|
+
"""
|
|
442
|
+
if isinstance(reference, (list, np.ndarray)) and isinstance(hypothesis, (list, np.ndarray)):
|
|
443
|
+
return _metrics_batch_wer_only(list(reference), list(hypothesis))
|
|
444
|
+
return calculations_wer_only(reference, hypothesis)
|
|
@@ -12,7 +12,7 @@ This module defines the following function:
|
|
|
12
12
|
|
|
13
13
|
import numpy as np
|
|
14
14
|
from .errorhandler import error_handler
|
|
15
|
-
from .metrics import
|
|
15
|
+
from .metrics import metrics_wer_only
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def wer(reference, hypothesis) -> float | np.float64 | None:
|
|
@@ -57,17 +57,17 @@ def wer(reference, hypothesis) -> float | np.float64 | None:
|
|
|
57
57
|
"""
|
|
58
58
|
try:
|
|
59
59
|
error_handler(reference, hypothesis)
|
|
60
|
-
result =
|
|
60
|
+
result = metrics_wer_only(reference, hypothesis)
|
|
61
61
|
except (ValueError, AttributeError, ZeroDivisionError) as err:
|
|
62
62
|
print(f"{type(err).__name__}: {str(err)}")
|
|
63
63
|
return None
|
|
64
64
|
|
|
65
|
-
# Batch: (n,
|
|
65
|
+
# Batch: (n, 3) float64, columns [wer, ld, m]
|
|
66
66
|
if isinstance(result, np.ndarray) and result.ndim == 2:
|
|
67
|
-
den = np.sum(result[:, 2])
|
|
68
|
-
return float(np.sum(result[:, 1]) / den) if den else 0.0
|
|
67
|
+
den = np.sum(result[:, 2]) # m column
|
|
68
|
+
return float(np.sum(result[:, 1]) / den) if den else 0.0 # ld column
|
|
69
69
|
|
|
70
|
-
# Single: (
|
|
70
|
+
# Single: (3,) float64, WER is at index 0
|
|
71
71
|
if isinstance(result, np.ndarray) and getattr(result, "ndim", 0) == 0:
|
|
72
72
|
result = result.item()
|
|
73
73
|
|
|
@@ -11,7 +11,7 @@ This module defines the following function:
|
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
from .errorhandler import error_handler
|
|
14
|
-
from .metrics import
|
|
14
|
+
from .metrics import metrics_wer_only
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def wers(reference, hypothesis):
|
|
@@ -50,16 +50,16 @@ def wers(reference, hypothesis):
|
|
|
50
50
|
"""
|
|
51
51
|
try:
|
|
52
52
|
error_handler(reference, hypothesis)
|
|
53
|
-
result =
|
|
53
|
+
result = metrics_wer_only(reference, hypothesis)
|
|
54
54
|
except (ValueError, AttributeError, ZeroDivisionError) as err:
|
|
55
55
|
print(f"{type(err).__name__}: {str(err)}")
|
|
56
56
|
return None
|
|
57
57
|
|
|
58
|
-
# Batch: (n,
|
|
58
|
+
# Batch: (n, 3) float64, columns [wer, ld, m]
|
|
59
59
|
if isinstance(result, np.ndarray) and result.ndim == 2:
|
|
60
|
-
return result[:, 0].tolist()
|
|
60
|
+
return result[:, 0].tolist() # Return wer column
|
|
61
61
|
|
|
62
|
-
# Single: (
|
|
62
|
+
# Single: (3,) float64, WER is at index 0
|
|
63
63
|
if isinstance(result, np.ndarray) and getattr(result, "ndim", 0) == 0:
|
|
64
64
|
result = result.item()
|
|
65
65
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|