extended-wer-normalizer 0.1.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.
- extended_wer_normalizer-0.1.0/.claude/settings.local.json +7 -0
- extended_wer_normalizer-0.1.0/.gitignore +10 -0
- extended_wer_normalizer-0.1.0/.python-version +1 -0
- extended_wer_normalizer-0.1.0/PKG-INFO +111 -0
- extended_wer_normalizer-0.1.0/README.md +88 -0
- extended_wer_normalizer-0.1.0/main.py +6 -0
- extended_wer_normalizer-0.1.0/pyproject.toml +40 -0
- extended_wer_normalizer-0.1.0/src/extended_wer_normalizer/__init__.py +83 -0
- extended_wer_normalizer-0.1.0/src/extended_wer_normalizer/transforms.py +400 -0
- extended_wer_normalizer-0.1.0/tests/test_normalization.py +405 -0
- extended_wer_normalizer-0.1.0/uv.lock +452 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: extended-wer-normalizer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: jiwer-compatible WER normalizer with number, email, URL, filler, and symbol normalization for voice AI evaluation
|
|
5
|
+
Project-URL: Homepage, https://github.com/ai-coustics/extended-wer-normalizer
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: asr,jiwer,normalization,speech recognition,voice ai,wer
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: jiwer>=3.0
|
|
20
|
+
Requires-Dist: num2words>=0.5
|
|
21
|
+
Requires-Dist: whisper-normalizer>=0.1.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# extended-wer-normalizer
|
|
25
|
+
|
|
26
|
+
jiwer-compatible text normalizer for Word Error Rate (WER) evaluation in voice AI.
|
|
27
|
+
|
|
28
|
+
Extends [jiwer](https://github.com/jitsi/jiwer)'s built-in transforms with normalizations that matter for real-world ASR evaluation: phone numbers, emails, URLs, currency, percentages, ordinals, filler words, and stuttering.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install extended-wer-normalizer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from extended_wer_normalizer import normalize_for_wer
|
|
40
|
+
|
|
41
|
+
normalize_for_wer("Call 0176 or email info@example.com, it costs $5.99")
|
|
42
|
+
# → "call 0 1 7 6 or email info at example dot com it costs five dollars ninety nine cents"
|
|
43
|
+
|
|
44
|
+
normalize_for_wer("Um, 1st place goes to Dr. Smith with 50% accuracy")
|
|
45
|
+
# → "first place goes to doctor smith with fifty percent accuracy"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## jiwer integration
|
|
49
|
+
|
|
50
|
+
Every normalization is a `jiwer.AbstractTransform` subclass — compose them freely:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import jiwer
|
|
54
|
+
from extended_wer_normalizer.transforms import NormalizeEmails, ExpandDigitRuns
|
|
55
|
+
|
|
56
|
+
pipeline = jiwer.Compose([
|
|
57
|
+
NormalizeEmails(),
|
|
58
|
+
ExpandDigitRuns(),
|
|
59
|
+
jiwer.ToLowerCase(),
|
|
60
|
+
jiwer.RemovePunctuation(),
|
|
61
|
+
jiwer.ReduceToListOfListOfWords(),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
wer = jiwer.wer("info at example dot com", "info@example.com", hypothesis_transform=pipeline)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use the pre-built pipeline directly with `jiwer.wer`:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import jiwer
|
|
71
|
+
from extended_wer_normalizer import english_wer_pipeline
|
|
72
|
+
|
|
73
|
+
wer = jiwer.wer(reference, hypothesis, reference_transform=english_wer_pipeline, hypothesis_transform=english_wer_pipeline)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Available transforms
|
|
77
|
+
|
|
78
|
+
| Transform | Example |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `ExpandDigitRuns` | `"0176"` → `"0 1 7 6"` |
|
|
81
|
+
| `DigitWordsToChars` | `"zero one seven"` → `"0 1 7"` |
|
|
82
|
+
| `WhisperEnglishNormalize` | lowercase, punctuation, contractions, compound numbers |
|
|
83
|
+
| `WhisperBasicNormalize` | language-agnostic basic normalization |
|
|
84
|
+
| `FinalDigitWordCleanup` | residual digit-word sweep after compound resolution |
|
|
85
|
+
| `NormalizeEmails` | `"user@example.com"` → `"user at example dot com"` |
|
|
86
|
+
| `NormalizeURLs` | `"https://example.com/path"` → `"example dot com"` |
|
|
87
|
+
| `NormalizeCurrency` | `"$5.99"` → `"five dollars ninety nine cents"` |
|
|
88
|
+
| `NormalizePercentages` | `"50%"` → `"fifty percent"` |
|
|
89
|
+
| `NormalizeOrdinals` | `"1st"` → `"first"`, `"15th"` → `"fifteenth"` |
|
|
90
|
+
| `ExpandAbbreviations` | `"Dr."` → `"doctor"`, `"vs."` → `"versus"` |
|
|
91
|
+
| `NormalizeSymbols` | `"cats & dogs"` → `"cats and dogs"` |
|
|
92
|
+
| `RemoveFillerWords` | removes `um`, `uh`, `hmm`, `er`, `ah`, … |
|
|
93
|
+
| `CollapseRepetitions` | `"I I I think"` → `"I think"` |
|
|
94
|
+
|
|
95
|
+
## Pipeline design
|
|
96
|
+
|
|
97
|
+
The English pipeline applies transforms in an order that ensures idempotence and correct interaction with Whisper's `EnglishTextNormalizer`:
|
|
98
|
+
|
|
99
|
+
1. **Pre-Whisper**: email, URL, symbol, abbreviation (patterns Whisper would mangle)
|
|
100
|
+
2. **Digit preparation**: expand digit runs, convert digit words
|
|
101
|
+
3. **Core**: `WhisperEnglishNormalize` (lowercase, punctuation, contractions, compound numbers → digits)
|
|
102
|
+
4. **Post-Whisper**: digit run expansion, digit word cleanup, currency, percentage, ordinal (patterns Whisper preserves or compacts)
|
|
103
|
+
5. **Cleanup**: filler words, repetition collapse
|
|
104
|
+
|
|
105
|
+
## Non-English
|
|
106
|
+
|
|
107
|
+
For non-English text, pass `language` to get Whisper's `BasicTextNormalizer`:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
normalize_for_wer("Das kostet fünf Euro", language="de")
|
|
111
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# extended-wer-normalizer
|
|
2
|
+
|
|
3
|
+
jiwer-compatible text normalizer for Word Error Rate (WER) evaluation in voice AI.
|
|
4
|
+
|
|
5
|
+
Extends [jiwer](https://github.com/jitsi/jiwer)'s built-in transforms with normalizations that matter for real-world ASR evaluation: phone numbers, emails, URLs, currency, percentages, ordinals, filler words, and stuttering.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install extended-wer-normalizer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from extended_wer_normalizer import normalize_for_wer
|
|
17
|
+
|
|
18
|
+
normalize_for_wer("Call 0176 or email info@example.com, it costs $5.99")
|
|
19
|
+
# → "call 0 1 7 6 or email info at example dot com it costs five dollars ninety nine cents"
|
|
20
|
+
|
|
21
|
+
normalize_for_wer("Um, 1st place goes to Dr. Smith with 50% accuracy")
|
|
22
|
+
# → "first place goes to doctor smith with fifty percent accuracy"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## jiwer integration
|
|
26
|
+
|
|
27
|
+
Every normalization is a `jiwer.AbstractTransform` subclass — compose them freely:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import jiwer
|
|
31
|
+
from extended_wer_normalizer.transforms import NormalizeEmails, ExpandDigitRuns
|
|
32
|
+
|
|
33
|
+
pipeline = jiwer.Compose([
|
|
34
|
+
NormalizeEmails(),
|
|
35
|
+
ExpandDigitRuns(),
|
|
36
|
+
jiwer.ToLowerCase(),
|
|
37
|
+
jiwer.RemovePunctuation(),
|
|
38
|
+
jiwer.ReduceToListOfListOfWords(),
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
wer = jiwer.wer("info at example dot com", "info@example.com", hypothesis_transform=pipeline)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use the pre-built pipeline directly with `jiwer.wer`:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import jiwer
|
|
48
|
+
from extended_wer_normalizer import english_wer_pipeline
|
|
49
|
+
|
|
50
|
+
wer = jiwer.wer(reference, hypothesis, reference_transform=english_wer_pipeline, hypothesis_transform=english_wer_pipeline)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Available transforms
|
|
54
|
+
|
|
55
|
+
| Transform | Example |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `ExpandDigitRuns` | `"0176"` → `"0 1 7 6"` |
|
|
58
|
+
| `DigitWordsToChars` | `"zero one seven"` → `"0 1 7"` |
|
|
59
|
+
| `WhisperEnglishNormalize` | lowercase, punctuation, contractions, compound numbers |
|
|
60
|
+
| `WhisperBasicNormalize` | language-agnostic basic normalization |
|
|
61
|
+
| `FinalDigitWordCleanup` | residual digit-word sweep after compound resolution |
|
|
62
|
+
| `NormalizeEmails` | `"user@example.com"` → `"user at example dot com"` |
|
|
63
|
+
| `NormalizeURLs` | `"https://example.com/path"` → `"example dot com"` |
|
|
64
|
+
| `NormalizeCurrency` | `"$5.99"` → `"five dollars ninety nine cents"` |
|
|
65
|
+
| `NormalizePercentages` | `"50%"` → `"fifty percent"` |
|
|
66
|
+
| `NormalizeOrdinals` | `"1st"` → `"first"`, `"15th"` → `"fifteenth"` |
|
|
67
|
+
| `ExpandAbbreviations` | `"Dr."` → `"doctor"`, `"vs."` → `"versus"` |
|
|
68
|
+
| `NormalizeSymbols` | `"cats & dogs"` → `"cats and dogs"` |
|
|
69
|
+
| `RemoveFillerWords` | removes `um`, `uh`, `hmm`, `er`, `ah`, … |
|
|
70
|
+
| `CollapseRepetitions` | `"I I I think"` → `"I think"` |
|
|
71
|
+
|
|
72
|
+
## Pipeline design
|
|
73
|
+
|
|
74
|
+
The English pipeline applies transforms in an order that ensures idempotence and correct interaction with Whisper's `EnglishTextNormalizer`:
|
|
75
|
+
|
|
76
|
+
1. **Pre-Whisper**: email, URL, symbol, abbreviation (patterns Whisper would mangle)
|
|
77
|
+
2. **Digit preparation**: expand digit runs, convert digit words
|
|
78
|
+
3. **Core**: `WhisperEnglishNormalize` (lowercase, punctuation, contractions, compound numbers → digits)
|
|
79
|
+
4. **Post-Whisper**: digit run expansion, digit word cleanup, currency, percentage, ordinal (patterns Whisper preserves or compacts)
|
|
80
|
+
5. **Cleanup**: filler words, repetition collapse
|
|
81
|
+
|
|
82
|
+
## Non-English
|
|
83
|
+
|
|
84
|
+
For non-English text, pass `language` to get Whisper's `BasicTextNormalizer`:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
normalize_for_wer("Das kostet fünf Euro", language="de")
|
|
88
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[tool.hatch.build.targets.wheel]
|
|
6
|
+
packages = ["src/extended_wer_normalizer"]
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "extended-wer-normalizer"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "jiwer-compatible WER normalizer with number, email, URL, filler, and symbol normalization for voice AI evaluation"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
keywords = ["wer", "speech recognition", "asr", "normalization", "jiwer", "voice ai"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
26
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"jiwer>=3.0",
|
|
30
|
+
"whisper-normalizer>=0.1.12",
|
|
31
|
+
"num2words>=0.5",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/ai-coustics/extended-wer-normalizer"
|
|
36
|
+
|
|
37
|
+
[dependency-groups]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=9.0.3",
|
|
40
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""extended-wer-normalizer: jiwer-compatible WER normalization for voice AI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import jiwer
|
|
6
|
+
|
|
7
|
+
from .transforms import (
|
|
8
|
+
CollapseRepetitions,
|
|
9
|
+
DigitWordsToChars,
|
|
10
|
+
ExpandAbbreviations,
|
|
11
|
+
ExpandDigitRuns,
|
|
12
|
+
FinalDigitWordCleanup,
|
|
13
|
+
NormalizeCurrency,
|
|
14
|
+
NormalizeEmails,
|
|
15
|
+
NormalizeOrdinals,
|
|
16
|
+
NormalizePercentages,
|
|
17
|
+
NormalizeSymbols,
|
|
18
|
+
NormalizeURLs,
|
|
19
|
+
RemoveFillerWords,
|
|
20
|
+
WhisperBasicNormalize,
|
|
21
|
+
WhisperEnglishNormalize,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"normalize_for_wer",
|
|
26
|
+
"english_wer_pipeline",
|
|
27
|
+
"CollapseRepetitions",
|
|
28
|
+
"DigitWordsToChars",
|
|
29
|
+
"ExpandAbbreviations",
|
|
30
|
+
"ExpandDigitRuns",
|
|
31
|
+
"FinalDigitWordCleanup",
|
|
32
|
+
"NormalizeCurrency",
|
|
33
|
+
"NormalizeEmails",
|
|
34
|
+
"NormalizeOrdinals",
|
|
35
|
+
"NormalizePercentages",
|
|
36
|
+
"NormalizeSymbols",
|
|
37
|
+
"NormalizeURLs",
|
|
38
|
+
"RemoveFillerWords",
|
|
39
|
+
"WhisperBasicNormalize",
|
|
40
|
+
"WhisperEnglishNormalize",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
english_wer_pipeline = jiwer.Compose([
|
|
44
|
+
# Pre-WhisperEN: handle patterns WhisperEN would mangle or remove
|
|
45
|
+
NormalizeEmails(), # must be first: WhisperEN strips @ and dots in emails
|
|
46
|
+
NormalizeURLs(), # WhisperEN strips slashes and scheme prefixes
|
|
47
|
+
NormalizeSymbols(), # WhisperEN removes & and + as punctuation
|
|
48
|
+
ExpandAbbreviations(), # expand Dr./Mr. before WhisperEN lowercases
|
|
49
|
+
# Core Whisper normalization (lowercase, punctuation, contractions, compound numbers)
|
|
50
|
+
ExpandDigitRuns(), # preserve leading zeros before WhisperEN sees e.g. "0176"
|
|
51
|
+
DigitWordsToChars(), # "zero one seven" → "0 1 7" before compound resolution
|
|
52
|
+
WhisperEnglishNormalize(),
|
|
53
|
+
# Post-WhisperEN: handle patterns WhisperEN preserves as-is or converts to compact form
|
|
54
|
+
# (WhisperEN preserves $5.99, 50%, 1st; and converts spoken forms back to compact)
|
|
55
|
+
ExpandDigitRuns(), # split any remaining digit runs (e.g. from compound numbers)
|
|
56
|
+
FinalDigitWordCleanup(), # final digit-word sweep — must run before currency/% so
|
|
57
|
+
# it doesn't convert "nine" inside "ninety nine cents"
|
|
58
|
+
NormalizeCurrency(), # $5.99 → five dollars ninety nine cents
|
|
59
|
+
NormalizePercentages(), # 50% → fifty percent
|
|
60
|
+
NormalizeOrdinals(), # 1st → first
|
|
61
|
+
RemoveFillerWords(),
|
|
62
|
+
CollapseRepetitions(),
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def normalize_for_wer(text: str, language: str = "en") -> str:
|
|
67
|
+
"""Normalize text for WER comparison.
|
|
68
|
+
|
|
69
|
+
English pipeline applies, in order:
|
|
70
|
+
email → URL → symbol → currency → percentage → ordinal → abbreviation
|
|
71
|
+
→ digit-run expansion → digit-word-to-char → Whisper EnglishTextNormalizer
|
|
72
|
+
→ digit-run expansion → final digit cleanup → filler removal → repetition collapse
|
|
73
|
+
|
|
74
|
+
All number representations collapse to space-separated single digits, so
|
|
75
|
+
"0176", "zero one seven six", and "21" all compare correctly.
|
|
76
|
+
|
|
77
|
+
For non-English text, Whisper's BasicTextNormalizer is applied instead.
|
|
78
|
+
"""
|
|
79
|
+
if language == "en":
|
|
80
|
+
result = english_wer_pipeline([text])
|
|
81
|
+
return result[0].strip()
|
|
82
|
+
from whisper_normalizer.basic import BasicTextNormalizer
|
|
83
|
+
return BasicTextNormalizer()(text).strip()
|