chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:10 +08:00
commit c397331b1e
3684 changed files with 990993 additions and 0 deletions
@@ -0,0 +1 @@
@@ -0,0 +1,350 @@
import json
import re
import string
from collections import defaultdict, namedtuple
from typing import Dict, List, Optional, Set, Tuple
from unicodedata import category
import logging
EOS_TYPE = "EOS"
PUNCT_TYPE = "PUNCT"
PLAIN_TYPE = "PLAIN"
Instance = namedtuple("Instance", "token_type un_normalized normalized")
known_types = [
"PLAIN",
"DATE",
"CARDINAL",
"LETTERS",
"VERBATIM",
"MEASURE",
"DECIMAL",
"ORDINAL",
"DIGIT",
"MONEY",
"TELEPHONE",
"ELECTRONIC",
"FRACTION",
"TIME",
"ADDRESS",
]
def _load_kaggle_text_norm_file(file_path: str) -> List[Instance]:
"""
https://www.kaggle.com/richardwilliamsproat/text-normalization-for-english-russian-and-polish
Loads text file in the Kaggle Google text normalization file format: <semiotic class>\t<unnormalized text>\t<`self` if trivial class or normalized text>
E.g.
PLAIN Brillantaisia <self>
PLAIN is <self>
PLAIN a <self>
PLAIN genus <self>
PLAIN of <self>
PLAIN plant <self>
PLAIN in <self>
PLAIN family <self>
PLAIN Acanthaceae <self>
PUNCT . sil
<eos> <eos>
Args:
file_path: file path to text file
Returns: flat list of instances
"""
res = []
with open(file_path, "r") as fp:
for line in fp:
parts = line.strip().split("\t")
if parts[0] == "<eos>":
res.append(Instance(token_type=EOS_TYPE, un_normalized="", normalized=""))
else:
l_type, l_token, l_normalized = parts
l_token = l_token.lower()
l_normalized = l_normalized.lower()
if l_type == PLAIN_TYPE:
res.append(
Instance(token_type=l_type, un_normalized=l_token, normalized=l_token)
)
elif l_type != PUNCT_TYPE:
res.append(
Instance(token_type=l_type, un_normalized=l_token, normalized=l_normalized)
)
return res
def load_files(file_paths: List[str], load_func=_load_kaggle_text_norm_file) -> List[Instance]:
"""
Load given list of text files using the `load_func` function.
Args:
file_paths: list of file paths
load_func: loading function
Returns: flat list of instances
"""
res = []
for file_path in file_paths:
res.extend(load_func(file_path=file_path))
return res
def clean_generic(text: str) -> str:
"""
Cleans text without affecting semiotic classes.
Args:
text: string
Returns: cleaned string
"""
text = text.strip()
text = text.lower()
return text
def evaluate(
preds: List[str], labels: List[str], input: Optional[List[str]] = None, verbose: bool = True
) -> float:
"""
Evaluates accuracy given predictions and labels.
Args:
preds: predictions
labels: labels
input: optional, only needed for verbosity
verbose: if true prints [input], golden labels and predictions
Returns accuracy
"""
acc = 0
nums = len(preds)
for i in range(nums):
pred_norm = clean_generic(preds[i])
label_norm = clean_generic(labels[i])
if pred_norm == label_norm:
acc = acc + 1
else:
if input:
print(f"inpu: {json.dumps(input[i])}")
print(f"gold: {json.dumps(label_norm)}")
print(f"pred: {json.dumps(pred_norm)}")
return acc / nums
def training_data_to_tokens(
data: List[Instance], category: Optional[str] = None
) -> Dict[str, Tuple[List[str], List[str]]]:
"""
Filters the instance list by category if provided and converts it into a map from token type to list of un_normalized and normalized strings
Args:
data: list of instances
category: optional semiotic class category name
Returns Dict: token type -> (list of un_normalized strings, list of normalized strings)
"""
result = defaultdict(lambda: ([], []))
for instance in data:
if instance.token_type != EOS_TYPE:
if category is None or instance.token_type == category:
result[instance.token_type][0].append(instance.un_normalized)
result[instance.token_type][1].append(instance.normalized)
return result
def training_data_to_sentences(data: List[Instance]) -> Tuple[List[str], List[str], List[Set[str]]]:
"""
Takes instance list, creates list of sentences split by EOS_Token
Args:
data: list of instances
Returns (list of unnormalized sentences, list of normalized sentences, list of sets of categories in a sentence)
"""
# split data at EOS boundaries
sentences = []
sentence = []
categories = []
sentence_categories = set()
for instance in data:
if instance.token_type == EOS_TYPE:
sentences.append(sentence)
sentence = []
categories.append(sentence_categories)
sentence_categories = set()
else:
sentence.append(instance)
sentence_categories.update([instance.token_type])
un_normalized = [
" ".join([instance.un_normalized for instance in sentence]) for sentence in sentences
]
normalized = [
" ".join([instance.normalized for instance in sentence]) for sentence in sentences
]
return un_normalized, normalized, categories
def post_process_punctuation(text: str) -> str:
"""
Normalized quotes and spaces
Args:
text: text
Returns: text with normalized spaces and quotes
"""
text = (
text.replace("( ", "(")
.replace(" )", ")")
.replace("{ ", "{")
.replace(" }", "}")
.replace("[ ", "[")
.replace(" ]", "]")
.replace(" ", " ")
.replace("", '"')
.replace("", "'")
.replace("»", '"')
.replace("«", '"')
.replace("\\", "")
.replace("", '"')
.replace("´", "'")
.replace("", "'")
.replace("", '"')
.replace("", "'")
.replace("`", "'")
.replace("- -", "--")
)
for punct in "!,.:;?":
text = text.replace(f" {punct}", punct)
return text.strip()
def pre_process(text: str) -> str:
"""
Optional text preprocessing before normalization (part of TTS TN pipeline)
Args:
text: string that may include semiotic classes
Returns: text with spaces around punctuation marks
"""
space_both = "[]"
for punct in space_both:
text = text.replace(punct, " " + punct + " ")
# remove extra space
text = re.sub(r" +", " ", text)
return text
def load_file(file_path: str) -> List[str]:
"""
Loads given text file with separate lines into list of string.
Args:
file_path: file path
Returns: flat list of string
"""
res = []
with open(file_path, "r") as fp:
for line in fp:
res.append(line)
return res
def write_file(file_path: str, data: List[str]):
"""
Writes out list of string to file.
Args:
file_path: file path
data: list of string
"""
with open(file_path, "w") as fp:
for line in data:
fp.write(line + "\n")
def post_process_punct(input: str, normalized_text: str, add_unicode_punct: bool = False):
"""
Post-processing of the normalized output to match input in terms of spaces around punctuation marks.
After NN normalization, Moses detokenization puts a space after
punctuation marks, and attaches an opening quote "'" to the word to the right.
E.g., input to the TN NN model is "12 test' example",
after normalization and detokenization -> "twelve test 'example" (the quote is considered to be an opening quote,
but it doesn't match the input and can cause issues during TTS voice generation.)
The current function will match the punctuation and spaces of the normalized text with the input sequence.
"12 test' example" -> "twelve test 'example" -> "twelve test' example" (the quote was shifted to match the input).
Args:
input: input text (original input to the NN, before normalization or tokenization)
normalized_text: output text (output of the TN NN model)
add_unicode_punct: set to True to handle unicode punctuation marks as well as default string.punctuation (increases post processing time)
"""
# in the post-processing WFST graph "``" are repalced with '"" quotes (otherwise single quotes "`" won't be handled correctly)
# this function fixes spaces around them based on input sequence, so here we're making the same double quote replacement
# to make sure these new double quotes work with this function
if "``" in input and "``" not in normalized_text:
input = input.replace("``", '"')
input = [x for x in input]
normalized_text = [x for x in normalized_text]
punct_marks = [x for x in string.punctuation if x in input]
if add_unicode_punct:
punct_unicode = [
chr(i)
for i in range(sys.maxunicode)
if category(chr(i)).startswith("P") and chr(i) not in punct_default and chr(i) in input
]
punct_marks = punct_marks.extend(punct_unicode)
for punct in punct_marks:
try:
equal = True
if input.count(punct) != normalized_text.count(punct):
equal = False
idx_in, idx_out = 0, 0
while punct in input[idx_in:]:
idx_out = normalized_text.index(punct, idx_out)
idx_in = input.index(punct, idx_in)
def _is_valid(idx_out, idx_in, normalized_text, input):
"""Check if previous or next word match (for cases when punctuation marks are part of
semiotic token, i.e. some punctuation can be missing in the normalized text)"""
return (
idx_out > 0
and idx_in > 0
and normalized_text[idx_out - 1] == input[idx_in - 1]
) or (
idx_out < len(normalized_text) - 1
and idx_in < len(input) - 1
and normalized_text[idx_out + 1] == input[idx_in + 1]
)
if not equal and not _is_valid(idx_out, idx_in, normalized_text, input):
idx_in += 1
continue
if idx_in > 0 and idx_out > 0:
if normalized_text[idx_out - 1] == " " and input[idx_in - 1] != " ":
normalized_text[idx_out - 1] = ""
elif normalized_text[idx_out - 1] != " " and input[idx_in - 1] == " ":
normalized_text[idx_out - 1] += " "
if idx_in < len(input) - 1 and idx_out < len(normalized_text) - 1:
if normalized_text[idx_out + 1] == " " and input[idx_in + 1] != " ":
normalized_text[idx_out + 1] = ""
elif normalized_text[idx_out + 1] != " " and input[idx_in + 1] == " ":
normalized_text[idx_out] = normalized_text[idx_out] + " "
idx_out += 1
idx_in += 1
except:
logging.debug(f"Skipping post-processing of {''.join(normalized_text)} for '{punct}'")
normalized_text = "".join(normalized_text)
return re.sub(r" +", " ", normalized_text)
@@ -0,0 +1 @@
@@ -0,0 +1 @@
@@ -0,0 +1,9 @@
.com punkt com
.uk punkt uk
.fr punkt fr
.net dot net
.br punkt br
.in punkt in
.ru punkt ru
.de punkt de
.it punkt it
1 .com punkt com
2 .uk punkt uk
3 .fr punkt fr
4 .net dot net
5 .br punkt br
6 .in punkt in
7 .ru punkt ru
8 .de punkt de
9 .it punkt it
@@ -0,0 +1,12 @@
gmail g mail
nvidia
outlook
hotmail
yahoo
live
yandex
orange
wanadoo
web
comcast
aol
1 gmail g mail
2 nvidia
3 outlook
4 hotmail
5 yahoo
6 live
7 yandex
8 orange
9 wanadoo
10 web
11 comcast
12 aol
@@ -0,0 +1,20 @@
. punkt
: doppelpunkt
- bindestrich
_ unterstrich
! ausrufezeichen
# raute
$ dollar zeichen
% prozent zeichen
& und
' apostroph
* asterisk
+ plus
/ slash
= gleichheitszeichen
? fragezeichen
^ zirkumflex
{ linke klammer auf
} rechte klammer zu
~ tilde
, komma
1 . punkt
2 : doppelpunkt
3 - bindestrich
4 _ unterstrich
5 ! ausrufezeichen
6 # raute
7 $ dollar zeichen
8 % prozent zeichen
9 & und
10 ' apostroph
11 * asterisk
12 + plus
13 / slash
14 = gleichheitszeichen
15 ? fragezeichen
16 ^ zirkumflex
17 { linke klammer auf
18 } rechte klammer zu
19 ~ tilde
20 , komma
@@ -0,0 +1,30 @@
halb zwei
drittel drei
viertel vier
fünftel fünf
sechstel sechs
siebtel sieben
achtel acht
neuntel neun
zehntel zehn
elftel elf
zwölftel zwölf
dreizehntel dreizehn
vierzehntel vierzehn
fünfzehntel fünfzehn
sechzehntel sechzehn
siebzehntel siebzehn
achtzehntel achtzehn
neunzehntel neunzehn
zwanzigstel zwanzig
dreißigstel dreißig
vierzigstel vierzig
fünfzigstel fünfzig
sechzigstel sechzig
siebzigstel siebzig
achtzigstel achtzig
neunzigstel neunzig
hundertstel hundert
tausendstel tausend
millionstel million
milliardstel milliarde
1 halb zwei
2 drittel drei
3 viertel vier
4 fünftel fünf
5 sechstel sechs
6 siebtel sieben
7 achtel acht
8 neuntel neun
9 zehntel zehn
10 elftel elf
11 zwölftel zwölf
12 dreizehntel dreizehn
13 vierzehntel vierzehn
14 fünfzehntel fünfzehn
15 sechzehntel sechzehn
16 siebzehntel siebzehn
17 achtzehntel achtzehn
18 neunzehntel neunzehn
19 zwanzigstel zwanzig
20 dreißigstel dreißig
21 vierzigstel vierzig
22 fünfzigstel fünfzig
23 sechzigstel sechzig
24 siebzigstel siebzig
25 achtzigstel achtzig
26 neunzigstel neunzig
27 hundertstel hundert
28 tausendstel tausend
29 millionstel million
30 milliardstel milliarde
@@ -0,0 +1,82 @@
% prozent
f fahrenheit
c celsius
°C grad celsius
°F grad fahrenheit
K kelvin
km kilometer
m meter
cm zentimeter
mm millimeter
μm mikrometer
nm nanometer
dm dezimeter
pm pikometer
hm hektometer
ha hektar
mi meile
m² quadrat meter -0.0001
km² quadrat kilometer -0.0001
mm² quadrat millimeter -0.0001
cm² quadrat zentimeter -0.0001
m³ kubik meter -0.0001
km³ kubik kilometer -0.0001
mm³ kubik millimeter -0.0001
cm³ kubik zentimeter -0.0001
m2 quadrat meter
km2 quadrat kilometer
mm2 quadrat millimeter
cm2 quadrat zentimeter
m3 kubik meter
km3 kubik kilometer
mm3 kubik millimeter
cm3 kubik zentimeter
ft fuß
g gramm
µg mikrogramm
mg milligramm
kg kilogramm
lb pfund
oz unze
cwt zentner
gr korn
dr drachne
μg mikrogramm
pg petagramm
h stunde
s sekunde
min minute
ds decisekunde
ms millisekunde
μs mikrosekunde
hz hertz
kw kilowatt
kwh kilowattstunde
ghz gigahertz
khz kilohertz
mhz megahertz
v volt
mc megacoulomb
mA milliampere
A ampere
tw terawatt
mv millivolt
mw megawatt
gw gigawatt
ω ohm
db dezibel
gb gigabyte
kb kilobit
pb petabit
mb megabyte
kb kilobyte
tb terabyte
kv kilovolt
mv megavolt
kn kilonewton
ml milliliter
l liter
ma megaampere
bar bar
kcal kilokalorie
cal kalorie
1 % prozent
2 f fahrenheit
3 c celsius
4 °C grad celsius
5 °F grad fahrenheit
6 K kelvin
7 km kilometer
8 m meter
9 cm zentimeter
10 mm millimeter
11 μm mikrometer
12 nm nanometer
13 dm dezimeter
14 pm pikometer
15 hm hektometer
16 ha hektar
17 mi meile
18 quadrat meter -0.0001
19 km² quadrat kilometer -0.0001
20 mm² quadrat millimeter -0.0001
21 cm² quadrat zentimeter -0.0001
22 kubik meter -0.0001
23 km³ kubik kilometer -0.0001
24 mm³ kubik millimeter -0.0001
25 cm³ kubik zentimeter -0.0001
26 m2 quadrat meter
27 km2 quadrat kilometer
28 mm2 quadrat millimeter
29 cm2 quadrat zentimeter
30 m3 kubik meter
31 km3 kubik kilometer
32 mm3 kubik millimeter
33 cm3 kubik zentimeter
34 ft fuß
35 g gramm
36 µg mikrogramm
37 mg milligramm
38 kg kilogramm
39 lb pfund
40 oz unze
41 cwt zentner
42 gr korn
43 dr drachne
44 μg mikrogramm
45 pg petagramm
46 h stunde
47 s sekunde
48 min minute
49 ds decisekunde
50 ms millisekunde
51 μs mikrosekunde
52 hz hertz
53 kw kilowatt
54 kwh kilowattstunde
55 ghz gigahertz
56 khz kilohertz
57 mhz megahertz
58 v volt
59 mc megacoulomb
60 mA milliampere
61 A ampere
62 tw terawatt
63 mv millivolt
64 mw megawatt
65 gw gigawatt
66 ω ohm
67 db dezibel
68 gb gigabyte
69 kb kilobit
70 pb petabit
71 mb megabyte
72 kb kilobyte
73 tb terabyte
74 kv kilovolt
75 mv megavolt
76 kn kilonewton
77 ml milliliter
78 l liter
79 ma megaampere
80 bar bar
81 kcal kilokalorie
82 cal kalorie
@@ -0,0 +1,10 @@
meile meilen
unze unzen
drachne drachnen
stunde stunden
sekunde sekunden
minute minuten
minute minuten
bit bits
byte bytes
kalorie kalorien
1 meile meilen
2 unze unzen
3 drachne drachnen
4 stunde stunden
5 sekunde sekunden
6 minute minuten
7 minute minuten
8 bit bits
9 byte bytes
10 kalorie kalorien
@@ -0,0 +1,25 @@
€ euro
$ dollar
$ us dollar
£ pfund
₩ won
nzd neuseeland dollar
rs rupie
chf schweizer franken
dkk dänische krone
fim finnische mark
aed dirham
¥ yen
czk tschechische krone
mro ouguiya
pkr pakistanische rupie
crc colon
hkd hong kong dollar
npr nepalesische rupee
awg aruba florin
nok norwegische krone
tzs tansania schilling
sek schwedisch krone
cyp zypern pfund
dm d-mark
dm deutsche mark
1 euro
2 $ dollar
3 $ us dollar
4 £ pfund
5 won
6 nzd neuseeland dollar
7 rs rupie
8 chf schweizer franken
9 dkk dänische krone
10 fim finnische mark
11 aed dirham
12 ¥ yen
13 czk tschechische krone
14 mro ouguiya
15 pkr pakistanische rupie
16 crc colon
17 hkd hong kong dollar
18 npr nepalesische rupee
19 awg aruba florin
20 nok norwegische krone
21 tzs tansania schilling
22 sek schwedisch krone
23 cyp zypern pfund
24 dm d-mark
25 dm deutsche mark
@@ -0,0 +1,3 @@
$ cent
€ cent
£ pence
1 $ cent
2 cent
3 £ pence
@@ -0,0 +1,3 @@
$ cent
€ cent
£ penny
1 $ cent
2 cent
3 £ penny
@@ -0,0 +1,13 @@
jan januar
feb februar
mär märz
apr april
mai mai
jun juni
jul juli
aug august
sep september
sept september
okt oktober
nov november
dez dezember
1 jan januar
2 feb februar
3 mär märz
4 apr april
5 mai mai
6 jun juni
7 jul juli
8 aug august
9 sep september
10 sept september
11 okt oktober
12 nov november
13 dez dezember
@@ -0,0 +1,24 @@
1 januar
2 februar
3 märz
4 april
5 mai
6 juni
7 juli
8 august
9 september
10 oktober
11 november
12 dezember
01 januar
02 februar
03 märz
04 april
05 mai
06 juni
07 juli
08 august
09 september
10 oktober
11 november
12 dezember
1 1 januar
2 2 februar
3 3 märz
4 4 april
5 5 mai
6 6 juni
7 7 juli
8 8 august
9 9 september
10 10 oktober
11 11 november
12 12 dezember
13 01 januar
14 02 februar
15 03 märz
16 04 april
17 05 mai
18 06 juni
19 07 juli
20 08 august
21 09 september
22 10 oktober
23 11 november
24 12 dezember
@@ -0,0 +1,8 @@
zwei 2
drei 3
vier 4
fünf 5
sechs 6
sieben 7
acht 8
neun 9
1 zwei 2
2 drei 3
3 vier 4
4 fünf 5
5 sechs 6
6 sieben 7
7 acht 8
8 neun 9
@@ -0,0 +1,3 @@
eine 1
ein 1
eins 1 -0.0001
1 eine 1
2 ein 1
3 eins 1 -0.0001
@@ -0,0 +1,12 @@
million
millionen
milliarde
milliarden
billion
billionen
billiarde
billiarden
trillion
trillionen
trilliarde
trilliarde
1 million
2 millionen
3 milliarde
4 milliarden
5 billion
6 billionen
7 billiarde
8 billiarden
9 trillion
10 trillionen
11 trilliarde
12 trilliarde
@@ -0,0 +1,10 @@
zehn 10
elf 11
zwölf 12
dreizehn 13
vierzehn 14
fünfzehn 15
sechzehn 16
siebzehn 17
achtzehn 18
neunzehn 19
1 zehn 10
2 elf 11
3 zwölf 12
4 dreizehn 13
5 vierzehn 14
6 fünfzehn 15
7 sechzehn 16
8 siebzehn 17
9 achtzehn 18
10 neunzehn 19
@@ -0,0 +1,8 @@
zwanzig 2
dreißig 3
vierzig 4
fünfzig 5
sechzig 6
siebzig 7
achtzig 8
neunzig 9
1 zwanzig 2
2 dreißig 3
3 vierzig 4
4 fünfzig 5
5 sechzig 6
6 siebzig 7
7 achtzig 8
8 neunzig 9
@@ -0,0 +1 @@
null 0
1 null 0
@@ -0,0 +1,9 @@
ers eins
zwei zwei
drit drei
vier vier
fünf fünf
sechs sechs
sieb sieben
ach acht
neun neun
1 ers eins
2 zwei zwei
3 drit drei
4 vier vier
5 fünf fünf
6 sechs sechs
7 sieb sieben
8 ach acht
9 neun neun
@@ -0,0 +1,4 @@
hunderts hundert
tausends tausend
millions million
milliards milliarde
1 hunderts hundert
2 tausends tausend
3 millions million
4 milliards milliarde
@@ -0,0 +1,8 @@
zwanzigs zwanzig
dreißigs dreißig
vierzigs vierzig
fünfzigs fünfzig
sechzigs sechzig
siebzigs siebzig
achtzigs achtzig
neunzigs neunzig
1 zwanzigs zwanzig
2 dreißigs dreißig
3 vierzigs vierzig
4 fünfzigs fünfzig
5 sechzigs sechzig
6 siebzigs siebzig
7 achtzigs achtzig
8 neunzigs neunzig
@@ -0,0 +1,12 @@
1 12
2 1
3 2
4 3
5 4
6 5
7 6
8 7
9 8
10 9
11 10
12 11
1 1 12
2 2 1
3 3 2
4 4 3
5 5 4
6 6 5
7 7 6
8 8 7
9 9 8
10 10 9
11 11 10
12 12 11
@@ -0,0 +1,13 @@
12 0
1 13
2 14
3 15
4 16
5 17
6 18
7 19
8 20
9 21
10 22
11 23
12 24
1 12 0
2 1 13
3 2 14
4 3 15
5 4 16
6 5 17
7 6 18
8 7 19
9 8 20
10 9 21
11 10 22
12 11 23
13 12 24
@@ -0,0 +1,59 @@
1 59
2 58
3 57
4 56
5 55
6 54
7 53
8 52
9 51
10 50
11 49
12 48
13 47
14 46
15 45
16 44
17 43
18 42
19 41
20 40
21 39
22 38
23 37
24 36
25 35
26 34
27 33
28 32
29 31
30 30
31 29
32 28
33 27
34 26
35 25
36 24
37 23
38 22
39 21
40 20
41 19
42 18
43 17
44 16
45 15
46 14
47 13
48 12
49 11
50 10
51 9
52 8
53 7
54 6
55 5
56 4
57 3
58 2
59 1
1 1 59
2 2 58
3 3 57
4 4 56
5 5 55
6 6 54
7 7 53
8 8 52
9 9 51
10 10 50
11 11 49
12 12 48
13 13 47
14 14 46
15 15 45
16 16 44
17 17 43
18 18 42
19 19 41
20 20 40
21 21 39
22 22 38
23 23 37
24 24 36
25 25 35
26 26 34
27 27 33
28 28 32
29 29 31
30 30 30
31 31 29
32 32 28
33 33 27
34 34 26
35 35 25
36 36 24
37 37 23
38 38 22
39 39 21
40 40 20
41 41 19
42 42 18
43 43 17
44 44 16
45 45 15
46 46 14
47 47 13
48 48 12
49 49 11
50 50 10
51 51 9
52 52 8
53 53 7
54 54 6
55 55 5
56 56 4
57 57 3
58 58 2
59 59 1
@@ -0,0 +1,7 @@
cst c s t
cet c e t
pst p s t
est e s t
pt p t
et e t
gmt g m t
1 cst c s t
2 cet c e t
3 pst p s t
4 est e s t
5 pt p t
6 et e t
7 gmt g m t
@@ -0,0 +1,7 @@
z.B. zum beispiel
d.h. dass heißt
Dr. doktor
Mr. mister
Mrs. misses
Ms. miss
Nr. nummer
1 z.B. zum beispiel
2 d.h. dass heißt
3 Dr. doktor
4 Mr. mister
5 Mrs. misses
6 Ms. miss
7 Nr. nummer
@@ -0,0 +1 @@
@@ -0,0 +1,215 @@
from collections import defaultdict
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_DIGIT,
DAMO_SIGMA,
GraphFst,
delete_space,
insert_space,
)
from pynini.lib import pynutil
AND = "und"
def get_ties_digit(digit_path: str, tie_path: str) -> "pynini.FstLike":
"""
getting all inverse normalizations for numbers between 21 - 100
Args:
digit_path: file to digit tsv
tie_path: file to tie tsv, e.g. 20, 30, etc.
Returns:
res: fst that converts numbers to their verbalization
"""
digits = defaultdict(list)
ties = defaultdict(list)
for k, v in load_labels(digit_path):
digits[v].append(k)
digits["1"] = ["ein"]
for k, v in load_labels(tie_path):
ties[v].append(k)
d = []
for i in range(21, 100):
s = str(i)
if s[1] == "0":
continue
for di in digits[s[1]]:
for ti in ties[s[0]]:
word = di + f" {AND} " + ti
d.append((word, s))
res = pynini.string_map(d)
return res
class CardinalFst(GraphFst):
"""
Finite state transducer for classifying cardinals, e.g.
"101" -> cardinal { integer: "ein hundert und zehn" }
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = False):
super().__init__(name="cardinal", kind="classify", deterministic=deterministic)
graph_zero = pynini.string_file(get_abs_path("data/numbers/zero.tsv")).invert()
graph_digit_no_one = pynini.string_file(get_abs_path("data/numbers/digit.tsv")).invert()
graph_one = pynini.string_file(get_abs_path("data/numbers/ones.tsv")).invert()
graph_digit = graph_digit_no_one | graph_one
self.digit = (graph_digit | graph_zero).optimize()
graph_teen = pynini.string_file(get_abs_path("data/numbers/teen.tsv")).invert()
graph_ties = pynini.string_file(get_abs_path("data/numbers/ties.tsv")).invert()
# separator = "."
def tens_no_zero():
return (
pynutil.delete("0") + graph_digit
| get_ties_digit(
get_abs_path("data/numbers/digit.tsv"), get_abs_path("data/numbers/ties.tsv")
).invert()
| graph_teen
| (graph_ties + pynutil.delete("0"))
)
def hundred_non_zero():
return (graph_digit_no_one + insert_space | pynini.cross("1", "ein ")) + pynutil.insert(
"hundert"
) + (
pynini.closure(insert_space + pynutil.insert(AND, weight=0.0001), 0, 1)
+ insert_space
+ tens_no_zero()
| pynutil.delete("00")
) | pynutil.delete(
"0"
) + tens_no_zero()
def thousand():
return (
hundred_non_zero() + insert_space + pynutil.insert("tausend")
| pynutil.delete("000")
) + (insert_space + hundred_non_zero() | pynutil.delete("000"))
optional_plural_quantity_en = pynini.closure(pynutil.insert("en", weight=-0.0001), 0, 1)
optional_plural_quantity_n = pynini.closure(pynutil.insert("n", weight=-0.0001), 0, 1)
graph_million = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("million")
+ optional_plural_quantity_en,
pynutil.delete("000"),
)
graph_billion = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("milliarde")
+ optional_plural_quantity_n,
pynutil.delete("000"),
)
graph_trillion = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("billion")
+ optional_plural_quantity_en,
pynutil.delete("000"),
)
graph_quadrillion = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("billiarde")
+ optional_plural_quantity_n,
pynutil.delete("000"),
)
graph_quintillion = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("trillion")
+ optional_plural_quantity_en,
pynutil.delete("000"),
)
graph_sextillion = pynini.union(
hundred_non_zero()
+ insert_space
+ pynutil.insert("trilliarde")
+ optional_plural_quantity_n,
pynutil.delete("000"),
)
graph = pynini.union(
graph_sextillion
+ insert_space
+ graph_quintillion
+ insert_space
+ graph_quadrillion
+ insert_space
+ graph_trillion
+ insert_space
+ graph_billion
+ insert_space
+ graph_million
+ insert_space
+ thousand()
)
fix_syntax = [
("eins tausend", "ein tausend"),
("eins millionen", "eine million"),
("eins milliarden", "eine milliarde"),
("eins billionen", "eine billion"),
("eins billiarden", "eine billiarde"),
]
fix_syntax = pynini.union(*[pynini.cross(*x) for x in fix_syntax])
self.graph = (
((DAMO_DIGIT - "0" + pynini.closure(DAMO_DIGIT, 0)) - "0" - "1")
@ pynini.cdrewrite(pynini.closure(pynutil.insert("0")), "[BOS]", "", DAMO_SIGMA)
@ DAMO_DIGIT**24
@ graph
@ pynini.cdrewrite(delete_space, "[BOS]", "", DAMO_SIGMA)
@ pynini.cdrewrite(delete_space, "", "[EOS]", DAMO_SIGMA)
@ pynini.cdrewrite(pynini.cross(" ", " "), "", "", DAMO_SIGMA)
@ pynini.cdrewrite(fix_syntax, "[BOS]", "", DAMO_SIGMA)
)
self.graph |= graph_zero | pynini.cross("1", "eins")
# self.graph = pynini.cdrewrite(pynutil.delete(separator), "", "", DAMO_SIGMA) @ self.graph
self.graph = self.graph.optimize()
self.graph_hundred_component_at_least_one_none_zero_digit = (
((DAMO_DIGIT - "0" + pynini.closure(DAMO_DIGIT, 0)) - "0" - "1")
@ pynini.cdrewrite(pynini.closure(pynutil.insert("0")), "[BOS]", "", DAMO_SIGMA)
@ DAMO_DIGIT**3
@ hundred_non_zero()
) | pynini.cross("1", "eins")
self.graph_hundred_component_at_least_one_none_zero_digit = (
self.graph_hundred_component_at_least_one_none_zero_digit.optimize()
)
self.two_digit_non_zero = (
pynini.closure(DAMO_DIGIT, 1, 2)
@ self.graph_hundred_component_at_least_one_none_zero_digit
)
optional_minus_graph = pynini.closure(
pynutil.insert("negative: ") + pynini.cross("-", '"true" '), 0, 1
)
final_graph = (
optional_minus_graph + pynutil.insert('integer: "') + self.graph + pynutil.insert('"')
)
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,130 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_CHAR,
DAMO_DIGIT,
TO_LOWER,
GraphFst,
insert_space,
)
from pynini.lib import pynutil
graph_teen = pynini.invert(pynini.string_file(get_abs_path("data/numbers/teen.tsv"))).optimize()
graph_digit = pynini.invert(pynini.string_file(get_abs_path("data/numbers/digit.tsv"))).optimize()
ties_graph = pynini.invert(pynini.string_file(get_abs_path("data/numbers/ties.tsv"))).optimize()
delete_leading_zero = (pynutil.delete("0") | (DAMO_DIGIT - "0")) + DAMO_DIGIT
def get_year_graph(cardinal: GraphFst) -> "pynini.FstLike":
"""
Returns year verbalizations as fst
< 2000 neunzehn (hundert) (vier und zwanzig), >= 2000 regular cardinal
**00 ** hundert
Args:
delete_leading_zero: removed leading zero
cardinal: cardinal GraphFst
"""
year_gt_2000 = (pynini.union("21", "20") + DAMO_DIGIT**2) @ cardinal.graph
graph_two_digit = delete_leading_zero @ cardinal.two_digit_non_zero
hundred = pynutil.insert("hundert")
graph_double_double = (
(pynini.accep("1") + DAMO_DIGIT) @ graph_two_digit
+ insert_space
+ pynini.closure(hundred + insert_space, 0, 1)
+ graph_two_digit
)
# for 20**
graph_double_double |= pynini.accep("20") @ graph_two_digit + insert_space + graph_two_digit
graph = (
graph_double_double
| (pynini.accep("1") + DAMO_DIGIT) @ graph_two_digit
+ insert_space
+ pynutil.delete("00")
+ hundred
| year_gt_2000
)
return graph
class DateFst(GraphFst):
"""
Finite state transducer for classifying date, e.g.
"01.04.2010" -> date { day: "erster" month: "april" year: "zwei tausend zehn" preserve_order: true }
"1994" -> date { year: "neunzehn vier und neuzig" }
"1900" -> date { year: "neunzehn hundert" }
Args:
cardinal: cardinal GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal: GraphFst, deterministic: bool):
super().__init__(name="date", kind="classify", deterministic=deterministic)
month_abbr_graph = load_labels(get_abs_path("data/months/abbr_to_name.tsv"))
number_to_month = pynini.string_file(get_abs_path("data/months/numbers.tsv")).optimize()
month_graph = pynini.union(*[x[1] for x in month_abbr_graph]).optimize()
month_abbr_graph = pynini.string_map(month_abbr_graph)
month_abbr_graph = (
pynutil.add_weight(month_abbr_graph, weight=0.0001)
| ((TO_LOWER + pynini.closure(DAMO_CHAR)) @ month_abbr_graph)
) + pynini.closure(pynutil.delete(".", weight=-0.0001), 0, 1)
self.month_abbr = month_abbr_graph
month_graph |= (TO_LOWER + pynini.closure(DAMO_CHAR)) @ month_graph
# jan.-> januar, Jan-> januar, januar-> januar
month_graph |= month_abbr_graph
numbers = cardinal.graph_hundred_component_at_least_one_none_zero_digit
optional_leading_zero = delete_leading_zero | DAMO_DIGIT
# 01, 31, 1
digit_day = optional_leading_zero @ pynini.union(*[str(x) for x in range(1, 32)]) @ numbers
day = (pynutil.insert('day: "') + digit_day + pynutil.insert('"')).optimize()
digit_month = optional_leading_zero @ pynini.union(*[str(x) for x in range(1, 13)])
number_to_month = digit_month @ number_to_month
digit_month @= numbers
month_name = (pynutil.insert('month: "') + month_graph + pynutil.insert('"')).optimize()
month_number = (
pynutil.insert('month: "')
+ (pynutil.add_weight(digit_month, weight=0.0001) | number_to_month)
+ pynutil.insert('"')
).optimize()
# prefer cardinal over year
year = pynutil.add_weight(get_year_graph(cardinal=cardinal), weight=0.001)
self.year = year
year_only = pynutil.insert('year: "') + year + pynutil.insert('"')
graph_dmy = (
day
+ pynutil.delete(".")
+ pynini.closure(pynutil.delete(" "), 0, 1)
+ insert_space
+ month_name
+ pynini.closure(pynini.accep(" ") + year_only, 0, 1)
)
separators = ["."]
for sep in separators:
year_optional = pynini.closure(pynini.cross(sep, " ") + year_only, 0, 1)
new_graph = day + pynini.cross(sep, " ") + month_number + year_optional
graph_dmy |= new_graph
dash = "-"
day_optional = pynini.closure(pynini.cross(dash, " ") + day, 0, 1)
graph_ymd = year_only + pynini.cross(dash, " ") + month_number + day_optional
final_graph = graph_dmy + pynutil.insert(" preserve_order: true")
final_graph |= year_only
final_graph |= graph_ymd
self.final_graph = final_graph.optimize()
self.fst = self.add_tokens(self.final_graph).optimize()
@@ -0,0 +1,82 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import GraphFst, insert_space
from pynini.lib import pynutil
quantities = pynini.string_file(get_abs_path("data/numbers/quantities.tsv"))
def get_quantity(
decimal: "pynini.FstLike", cardinal_up_to_hundred: "pynini.FstLike"
) -> "pynini.FstLike":
"""
Returns FST that transforms either a cardinal or decimal followed by a quantity into a numeral,
e.g. 1 million -> integer_part: "eine" quantity: "million"
e.g. 1.4 million -> integer_part: "eins" fractional_part: "vier" quantity: "million"
Args:
decimal: decimal FST
cardinal_up_to_hundred: cardinal FST
"""
numbers = cardinal_up_to_hundred
res = (
pynutil.insert('integer_part: "')
+ numbers
+ pynutil.insert('"')
+ pynini.accep(" ")
+ pynutil.insert('quantity: "')
+ quantities
+ pynutil.insert('"')
)
res |= (
decimal
+ pynini.accep(" ")
+ pynutil.insert('quantity: "')
+ quantities
+ pynutil.insert('"')
)
return res
class DecimalFst(GraphFst):
"""
Finite state transducer for classifying decimal, e.g.
-11,4006 billion -> decimal { negative: "true" integer_part: "elf" fractional_part: "vier null null sechs" quantity: "billion" preserve_order: true }
1 billion -> decimal { integer_part: "eins" quantity: "billion" preserve_order: true }
Args:
cardinal: CardinalFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal: GraphFst, deterministic: bool = True):
super().__init__(name="decimal", kind="classify", deterministic=deterministic)
graph_digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")).invert()
graph_digit |= pynini.string_file(get_abs_path("data/numbers/zero.tsv")).invert()
graph_digit |= pynini.cross("1", "eins")
self.graph = graph_digit + pynini.closure(insert_space + graph_digit).optimize()
point = pynutil.delete(",")
optional_graph_negative = pynini.closure(
pynutil.insert("negative: ") + pynini.cross("-", '"true" '), 0, 1
)
self.graph_fractional = (
pynutil.insert('fractional_part: "') + self.graph + pynutil.insert('"')
)
self.graph_integer = (
pynutil.insert('integer_part: "') + cardinal.graph + pynutil.insert('"')
)
final_graph_wo_sign = self.graph_integer + point + insert_space + self.graph_fractional
self.final_graph_wo_negative = final_graph_wo_sign | get_quantity(
final_graph_wo_sign, cardinal.graph_hundred_component_at_least_one_none_zero_digit
)
final_graph = optional_graph_negative + self.final_graph_wo_negative
final_graph += pynutil.insert(" preserve_order: true")
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,64 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_ALPHA,
DAMO_DIGIT,
GraphFst,
insert_space,
)
from pynini.lib import pynutil
class ElectronicFst(GraphFst):
"""
Finite state transducer for classifying electronic: email addresses
e.g. "abc@hotmail.com" -> electronic { username: "abc" domain: "hotmail.com" preserve_order: true }
e.g. "www.abc.com/123" -> electronic { protocol: "www." domain: "abc.com/123" preserve_order: true }
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="electronic", kind="classify", deterministic=deterministic)
dot = pynini.accep(".")
accepted_common_domains = [
x[0] for x in load_labels(get_abs_path("data/electronic/domain.tsv"))
]
accepted_common_domains = pynini.union(*accepted_common_domains)
accepted_symbols = [x[0] for x in load_labels(get_abs_path("data/electronic/symbols.tsv"))]
accepted_symbols = pynini.union(*accepted_symbols) - dot
accepted_characters = pynini.closure(DAMO_ALPHA | DAMO_DIGIT | accepted_symbols)
# email
username = (
pynutil.insert('username: "')
+ accepted_characters
+ pynutil.insert('"')
+ pynini.cross("@", " ")
)
domain_graph = accepted_characters + dot + accepted_characters
domain_graph = pynutil.insert('domain: "') + domain_graph + pynutil.insert('"')
domain_common_graph = (
pynutil.insert('domain: "')
+ accepted_characters
+ accepted_common_domains
+ pynini.closure(
(accepted_symbols | dot) + pynini.closure(accepted_characters, 1), 0, 1
)
+ pynutil.insert('"')
)
graph = (username + domain_graph) | domain_common_graph
# url
protocol_start = pynini.accep("https://") | pynini.accep("http://")
protocol_end = pynini.accep("www.")
protocol = protocol_start | protocol_end | (protocol_start + protocol_end)
protocol = pynutil.insert('protocol: "') + protocol + pynutil.insert('"')
graph |= protocol + insert_space + (domain_graph | domain_common_graph)
self.graph = graph
final_graph = self.add_tokens(self.graph + pynutil.insert(" preserve_order: true"))
self.fst = final_graph.optimize()
@@ -0,0 +1,42 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import GraphFst
from pynini.lib import pynutil
class FractionFst(GraphFst):
"""
Finite state transducer for classifying fraction
"23 4/6" ->
fraction { integer: "drei und zwanzig" numerator: "vier" denominator: "sechs" preserve_order: true }
Args:
cardinal: cardinal GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal, deterministic: bool = True):
super().__init__(name="fraction", kind="classify", deterministic=deterministic)
cardinal_graph = cardinal.graph
self.optional_graph_negative = pynini.closure(
pynutil.insert("negative: ") + pynini.cross("-", '"true" '), 0, 1
)
self.integer = pynutil.insert('integer_part: "') + cardinal_graph + pynutil.insert('"')
self.numerator = (
pynutil.insert('numerator: "')
+ cardinal_graph
+ pynini.cross(pynini.union("/", " / "), '" ')
)
self.denominator = pynutil.insert('denominator: "') + cardinal_graph + pynutil.insert('"')
self.graph = (
self.optional_graph_negative
+ pynini.closure(self.integer + pynini.accep(" "), 0, 1)
+ self.numerator
+ self.denominator
)
graph = self.graph + pynutil.insert(" preserve_order: true")
final_graph = self.add_tokens(graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,185 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_ALPHA,
DAMO_DIGIT,
DAMO_NON_BREAKING_SPACE,
DAMO_SIGMA,
GraphFst,
convert_space,
insert_space,
)
from pynini.examples import plurals
from pynini.lib import pynutil
unit_singular = pynini.string_file(get_abs_path("data/measure/measurements.tsv"))
suppletive = pynini.string_file(get_abs_path("data/measure/suppletive.tsv"))
def singular_to_plural():
# plural endung n/en maskuline Nomen mit den Endungen e, ent, and, ant, ist, or
_n = DAMO_SIGMA + pynini.union("e") + pynutil.insert("n")
_en = (
DAMO_SIGMA
+ pynini.union(
"ent", "and", "ant", "ist", "or", "ion", "ik", "heit", "keit", "schaft", "tät", "ung"
)
+ pynutil.insert("en")
)
_nen = DAMO_SIGMA + pynini.union("in") + (pynutil.insert("e") | pynutil.insert("nen"))
_fremd = DAMO_SIGMA + pynini.union("ma", "um", "us") + pynutil.insert("en")
# maskuline Nomen mit den Endungen eur, ich, ier, ig, ling, ör
_e = DAMO_SIGMA + pynini.union("eur", "ich", "ier", "ig", "ling", "ör") + pynutil.insert("e")
_s = DAMO_SIGMA + pynini.union("a", "i", "o", "u", "y") + pynutil.insert("s")
graph_plural = plurals._priority_union(
suppletive, pynini.union(_n, _en, _nen, _fremd, _e, _s), DAMO_SIGMA
).optimize()
return graph_plural
class MeasureFst(GraphFst):
"""
Finite state transducer for classifying measure, e.g.
"2,4 oz" -> measure { cardinal { integer_part: "zwei" fractional_part: "vier" units: "unzen" preserve_order: true } }
"1 oz" -> measure { cardinal { integer: "zwei" units: "unze" preserve_order: true } }
"1 million oz" -> measure { cardinal { integer: "eins" quantity: "million" units: "unze" preserve_order: true } }
This class also converts words containing numbers and letters
e.g. "a-8" —> "a acht"
e.g. "1,2-a" —> "ein komma zwei a"
Args:
cardinal: CardinalFst
decimal: DecimalFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(
self, cardinal: GraphFst, decimal: GraphFst, fraction: GraphFst, deterministic: bool = True
):
super().__init__(name="measure", kind="classify", deterministic=deterministic)
cardinal_graph = cardinal.graph
graph_unit_singular = convert_space(unit_singular)
graph_unit_plural = graph_unit_singular @ pynini.cdrewrite(
convert_space(suppletive), "", "[EOS]", DAMO_SIGMA
)
optional_graph_negative = pynini.closure("-", 0, 1)
graph_unit_denominator = (
pynini.cross("/", "pro") + pynutil.insert(DAMO_NON_BREAKING_SPACE) + graph_unit_singular
)
optional_unit_denominator = pynini.closure(
pynutil.insert(DAMO_NON_BREAKING_SPACE) + graph_unit_denominator,
0,
1,
)
unit_plural = (
pynutil.insert('units: "')
+ (graph_unit_plural + (optional_unit_denominator) | graph_unit_denominator)
+ pynutil.insert('"')
)
unit_singular_graph = (
pynutil.insert('units: "')
+ ((graph_unit_singular + optional_unit_denominator) | graph_unit_denominator)
+ pynutil.insert('"')
)
subgraph_decimal = (
decimal.fst + insert_space + pynini.closure(pynutil.delete(" "), 0, 1) + unit_plural
)
subgraph_cardinal = (
(optional_graph_negative + (pynini.closure(DAMO_DIGIT) - "1")) @ cardinal.fst
+ insert_space
+ pynini.closure(pynutil.delete(" "), 0, 1)
+ unit_plural
)
subgraph_cardinal |= (
(optional_graph_negative + pynini.accep("1"))
@ cardinal.fst
@ pynini.cdrewrite(pynini.cross("eins", "ein"), "", "", DAMO_SIGMA)
+ insert_space
+ pynini.closure(pynutil.delete(" "), 0, 1)
+ unit_singular_graph
)
subgraph_fraction = (
fraction.fst + insert_space + pynini.closure(pynutil.delete(" "), 0, 1) + unit_plural
)
cardinal_dash_alpha = (
pynutil.insert('cardinal { integer: "')
+ cardinal_graph
+ pynutil.delete("-")
+ pynutil.insert('" } units: "')
+ pynini.closure(DAMO_ALPHA, 1)
+ pynutil.insert('"')
)
alpha_dash_cardinal = (
pynutil.insert('units: "')
+ pynini.closure(DAMO_ALPHA, 1)
+ pynutil.delete("-")
+ pynutil.insert('"')
+ pynutil.insert(' cardinal { integer: "')
+ cardinal_graph
+ pynutil.insert('" }')
)
decimal_dash_alpha = (
pynutil.insert("decimal { ")
+ decimal.final_graph_wo_negative
+ pynutil.delete("-")
+ pynutil.insert(' } units: "')
+ pynini.closure(DAMO_ALPHA, 1)
+ pynutil.insert('"')
)
decimal_times = (
pynutil.insert("decimal { ")
+ decimal.final_graph_wo_negative
+ pynutil.insert(' } units: "')
+ pynini.union("x", "X")
+ pynutil.insert('"')
)
cardinal_times = (
pynutil.insert('cardinal { integer: "')
+ cardinal_graph
+ pynutil.insert('" } units: "')
+ pynini.union("x", "X")
+ pynutil.insert('"')
)
alpha_dash_decimal = (
pynutil.insert('units: "')
+ pynini.closure(DAMO_ALPHA, 1)
+ pynutil.delete("-")
+ pynutil.insert('"')
+ pynutil.insert(" decimal { ")
+ decimal.final_graph_wo_negative
+ pynutil.insert(" }")
)
final_graph = (
subgraph_decimal
| subgraph_cardinal
| cardinal_dash_alpha
| alpha_dash_cardinal
| decimal_dash_alpha
| decimal_times
| alpha_dash_decimal
| subgraph_fraction
| cardinal_times
)
final_graph += pynutil.insert(" preserve_order: true")
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,160 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_ALPHA,
DAMO_DIGIT,
DAMO_SIGMA,
GraphFst,
convert_space,
insert_space,
)
from pynini.lib import pynutil
min_singular = pynini.string_file(get_abs_path("data/money/currency_minor_singular.tsv"))
min_plural = pynini.string_file(get_abs_path("data/money/currency_minor_plural.tsv"))
maj_singular = pynini.string_file((get_abs_path("data/money/currency.tsv")))
class MoneyFst(GraphFst):
"""
Finite state transducer for classifying money, e.g.
"€1" -> money { currency_maj: "euro" integer_part: "ein"}
"€1,000" -> money { currency_maj: "euro" integer_part: "ein" }
"€1,001" -> money { currency_maj: "euro" integer_part: "eins" fractional_part: "null null eins"}
"£1,4" -> money { integer_part: "ein" currency_maj: "pfund" fractional_part: "vierzig" preserve_order: true}
-> money { integer_part: "ein" currency_maj: "pfund" fractional_part: "vierzig" currency_min: "pence" preserve_order: true}
"£0,01" -> money { fractional_part: "ein" currency_min: "penny" preserve_order: true}
"£0,01 million" -> money { currency_maj: "pfund" integer_part: "null" fractional_part: "null eins" quantity: "million"}
Args:
cardinal: CardinalFst
decimal: DecimalFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal: GraphFst, decimal: GraphFst, deterministic: bool = True):
super().__init__(name="money", kind="classify", deterministic=deterministic)
cardinal_graph = cardinal.graph
graph_decimal_final = decimal.fst
maj_singular_labels = load_labels(get_abs_path("data/money/currency.tsv"))
maj_singular_graph = convert_space(maj_singular)
maj_plural_graph = maj_singular_graph
graph_maj_singular = (
pynutil.insert('currency_maj: "') + maj_singular_graph + pynutil.insert('"')
)
graph_maj_plural = (
pynutil.insert('currency_maj: "') + maj_plural_graph + pynutil.insert('"')
)
optional_delete_fractional_zeros = pynini.closure(
pynutil.delete(",") + pynini.closure(pynutil.delete("0"), 1), 0, 1
)
graph_integer_one = (
pynutil.insert('integer_part: "') + pynini.cross("1", "ein") + pynutil.insert('"')
)
# only for decimals where third decimal after comma is non-zero or with quantity
decimal_delete_last_zeros = (
pynini.closure(DAMO_DIGIT, 1)
+ pynini.accep(",")
+ pynini.closure(DAMO_DIGIT, 2)
+ (DAMO_DIGIT - "0")
+ pynini.closure(pynutil.delete("0"))
)
decimal_with_quantity = DAMO_SIGMA + DAMO_ALPHA
graph_decimal = (
graph_maj_plural
+ insert_space
+ (decimal_delete_last_zeros | decimal_with_quantity) @ graph_decimal_final
)
graph_integer = (
pynutil.insert('integer_part: "')
+ ((DAMO_SIGMA - "1") @ cardinal_graph)
+ pynutil.insert('"')
)
graph_integer_only = graph_maj_singular + insert_space + graph_integer_one
graph_integer_only |= graph_maj_plural + insert_space + graph_integer
graph = (graph_integer_only + optional_delete_fractional_zeros) | graph_decimal
# remove trailing zeros of non zero number in the first 2 digits and fill up to 2 digits
# e.g. 2000 -> 20, 0200->02, 01 -> 01, 10 -> 10
# not accepted: 002, 00, 0,
two_digits_fractional_part = (
pynini.closure(DAMO_DIGIT) + (DAMO_DIGIT - "0") + pynini.closure(pynutil.delete("0"))
) @ (
(pynutil.delete("0") + (DAMO_DIGIT - "0"))
| ((DAMO_DIGIT - "0") + pynutil.insert("0"))
| ((DAMO_DIGIT - "0") + DAMO_DIGIT)
)
graph_min_singular = pynutil.insert(' currency_min: "') + min_singular + pynutil.insert('"')
graph_min_plural = pynutil.insert(' currency_min: "') + min_plural + pynutil.insert('"')
# format ** euro ** cent
decimal_graph_with_minor = None
for curr_symbol, _ in maj_singular_labels:
preserve_order = pynutil.insert(" preserve_order: true")
integer_plus_maj = (
graph_integer + insert_space + pynutil.insert(curr_symbol) @ graph_maj_plural
)
integer_plus_maj |= (
graph_integer_one + insert_space + pynutil.insert(curr_symbol) @ graph_maj_singular
)
# non zero integer part
integer_plus_maj = (pynini.closure(DAMO_DIGIT) - "0") @ integer_plus_maj
graph_fractional_one = two_digits_fractional_part @ pynini.cross("1", "ein")
graph_fractional_one = (
pynutil.insert('fractional_part: "') + graph_fractional_one + pynutil.insert('"')
)
graph_fractional = (
two_digits_fractional_part
@ (pynini.closure(DAMO_DIGIT, 1, 2) - "1")
@ cardinal.two_digit_non_zero
)
graph_fractional = (
pynutil.insert('fractional_part: "') + graph_fractional + pynutil.insert('"')
)
fractional_plus_min = (
graph_fractional + insert_space + pynutil.insert(curr_symbol) @ graph_min_plural
)
fractional_plus_min |= (
graph_fractional_one
+ insert_space
+ pynutil.insert(curr_symbol) @ graph_min_singular
)
decimal_graph_with_minor_curr = (
integer_plus_maj + pynini.cross(",", " ") + fractional_plus_min
)
decimal_graph_with_minor_curr |= pynutil.add_weight(
integer_plus_maj
+ pynini.cross(",", " ")
+ pynutil.insert('fractional_part: "')
+ two_digits_fractional_part @ cardinal.two_digit_non_zero
+ pynutil.insert('"'),
weight=0.0001,
)
decimal_graph_with_minor_curr |= pynutil.delete("0,") + fractional_plus_min
decimal_graph_with_minor_curr = (
pynutil.delete(curr_symbol) + decimal_graph_with_minor_curr + preserve_order
)
decimal_graph_with_minor = (
decimal_graph_with_minor_curr
if decimal_graph_with_minor is None
else pynini.union(decimal_graph_with_minor, decimal_graph_with_minor_curr)
)
final_graph = graph | decimal_graph_with_minor
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,37 @@
# Adapted from https://github.com/google/TextNormalizationCoveringGrammars
# Russian minimally supervised number grammar.
import pynini
from fun_text_processing.text_normalization.en.graph_utils import DAMO_DIGIT, GraphFst
from pynini.lib import pynutil
class OrdinalFst(GraphFst):
"""
Finite state transducer for classifying cardinals, e.g.
"2." -> ordinal { integer: "zwei" } }
"2tes" -> ordinal { integer: "zwei" } }
Args:
cardinal: cardinal GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal: GraphFst, deterministic=False):
super().__init__(name="ordinal", kind="classify", deterministic=deterministic)
cardinal_graph = cardinal.graph
endings = ["ter", "tes", "tem", "te", "ten"]
self.graph = (
(
pynini.closure(DAMO_DIGIT | pynini.accep("."))
+ pynutil.delete(
pynutil.add_weight(pynini.union(*endings), weight=0.0001) | pynini.accep(".")
)
)
@ cardinal_graph
).optimize()
final_graph = pynutil.insert('integer: "') + self.graph + pynutil.insert('"')
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,66 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import DAMO_DIGIT, GraphFst, insert_space
from pynini.lib import pynutil
class TelephoneFst(GraphFst):
"""
Finite state transducer for classifying telephone, which includes country code, number part and extension
E.g
"+49 1234-1233" -> telephone { country_code: "plus neun und vierzig" number_part: "eins zwei drei vier eins zwei drei drei" preserve_order: true }
"(012) 1234-1233" -> telephone { country_code: "null eins zwei" number_part: "eins zwei drei vier eins zwei drei drei" preserve_order: true }
(0**)
Args:
cardinal: cardinal GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal: GraphFst, deterministic: bool = True):
super().__init__(name="telephone", kind="classify", deterministic=deterministic)
graph_zero = pynini.invert(
pynini.string_file(get_abs_path("data/numbers/zero.tsv"))
).optimize()
graph_digit_no_zero = pynini.invert(
pynini.string_file(get_abs_path("data/numbers/digit.tsv"))
).optimize() | pynini.cross("1", "eins")
graph_digit = graph_digit_no_zero | graph_zero
numbers_with_single_digits = pynini.closure(graph_digit + insert_space) + graph_digit
two_digit_and_zero = (DAMO_DIGIT**2 @ cardinal.two_digit_non_zero) | graph_zero
# def add_space_after_two_digit():
# return pynini.closure(two_digit_and_zero + insert_space) + (
# two_digit_and_zero
# )
country_code = pynini.closure(pynini.cross("+", "plus "), 0, 1) + two_digit_and_zero
country_code |= (
pynutil.delete("(")
+ graph_zero
+ insert_space
+ numbers_with_single_digits
+ pynutil.delete(")")
)
country_code |= graph_zero + insert_space + numbers_with_single_digits
country_code = pynutil.insert('country_code: "') + country_code + pynutil.insert('"')
del_separator = pynini.cross(pynini.union("-", " "), " ")
# numbers_with_two_digits = pynini.closure(graph_digit + insert_space) + add_space_after_two_digit() + pynini.closure(insert_space + graph_digit)
# numbers = numbers_with_two_digits + pynini.closure(del_separator + numbers_with_two_digits, 0, 1)
numbers = numbers_with_single_digits + pynini.closure(
del_separator + numbers_with_single_digits, 0, 1
)
number_length = pynini.closure((DAMO_DIGIT | pynini.union("-", " ", ")", "(")), 7)
number_part = pynini.compose(number_length, numbers)
number = pynutil.insert('number_part: "') + number_part + pynutil.insert('"')
graph = country_code + pynini.accep(" ") + number
self.graph = graph
final_graph = self.add_tokens(self.graph + pynutil.insert(" preserve_order: true"))
self.fst = final_graph.optimize()
@@ -0,0 +1,94 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_DIGIT,
GraphFst,
convert_space,
insert_space,
)
from pynini.lib import pynutil
class TimeFst(GraphFst):
"""
Finite state transducer for classifying time, e.g.
"02:15 Uhr est" -> time { hours: "2" minutes: "15" zone: "e s t"}
"2 Uhr" -> time { hours: "2" }
"09:00 Uhr" -> time { hours: "2" }
"02:15:10 Uhr" -> time { hours: "2" minutes: "15" seconds: "10"}
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="time", kind="classify", deterministic=deterministic)
final_suffix = pynutil.delete(" ") + pynutil.delete("Uhr") | pynutil.delete("uhr")
time_zone_graph = pynini.string_file(get_abs_path("data/time/time_zone.tsv"))
labels_hour = [str(x) for x in range(0, 25)]
labels_minute_single = [str(x) for x in range(1, 10)]
labels_minute_double = [str(x) for x in range(10, 60)]
delete_leading_zero_to_double_digit = (
pynutil.delete("0") | (DAMO_DIGIT - "0")
) + DAMO_DIGIT
graph_hour = pynini.union(*labels_hour)
graph_minute_single = pynini.union(*labels_minute_single)
graph_minute_double = pynini.union(*labels_minute_double)
final_graph_hour_only = pynutil.insert('hours: "') + graph_hour + pynutil.insert('"')
final_graph_hour = (
pynutil.insert('hours: "')
+ delete_leading_zero_to_double_digit @ graph_hour
+ pynutil.insert('"')
)
final_graph_minute = (
pynutil.insert('minutes: "')
+ (pynutil.delete("0") + graph_minute_single | graph_minute_double)
+ pynutil.insert('"')
)
final_graph_second = (
pynutil.insert('seconds: "')
+ (pynutil.delete("0") + graph_minute_single | graph_minute_double)
+ pynutil.insert('"')
)
final_time_zone_optional = pynini.closure(
pynini.accep(" ")
+ pynutil.insert('zone: "')
+ convert_space(time_zone_graph)
+ pynutil.insert('"'),
0,
1,
)
# 02:30 Uhr
graph_hm = (
final_graph_hour
+ pynutil.delete(":")
+ (pynutil.delete("00") | (insert_space + final_graph_minute))
+ final_suffix
+ final_time_zone_optional
)
# 10:30:05 Uhr,
graph_hms = (
final_graph_hour
+ pynutil.delete(":")
+ (pynini.cross("00", ' minutes: "0"') | (insert_space + final_graph_minute))
+ pynutil.delete(":")
+ (pynini.cross("00", ' seconds: "0"') | (insert_space + final_graph_second))
+ final_suffix
+ final_time_zone_optional
+ pynutil.insert(" preserve_order: true")
)
# 2 Uhr est
graph_h = final_graph_hour_only + final_suffix + final_time_zone_optional
final_graph = (graph_hm | graph_h | graph_hms).optimize()
final_graph = self.add_tokens(final_graph)
self.fst = final_graph.optimize()
@@ -0,0 +1,145 @@
import os
import pynini
from fun_text_processing.text_normalization.de.taggers.cardinal import CardinalFst
from fun_text_processing.text_normalization.de.taggers.date import DateFst
from fun_text_processing.text_normalization.de.taggers.decimal import DecimalFst
from fun_text_processing.text_normalization.de.taggers.electronic import ElectronicFst
from fun_text_processing.text_normalization.de.taggers.fraction import FractionFst
from fun_text_processing.text_normalization.de.taggers.measure import MeasureFst
from fun_text_processing.text_normalization.de.taggers.money import MoneyFst
from fun_text_processing.text_normalization.de.taggers.ordinal import OrdinalFst
from fun_text_processing.text_normalization.de.taggers.telephone import TelephoneFst
from fun_text_processing.text_normalization.de.taggers.time import TimeFst
from fun_text_processing.text_normalization.de.taggers.whitelist import WhiteListFst
from fun_text_processing.text_normalization.de.taggers.word import WordFst
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_CHAR,
DAMO_DIGIT,
GraphFst,
delete_extra_space,
delete_space,
generator_main,
)
from fun_text_processing.text_normalization.en.taggers.punctuation import PunctuationFst
from pynini.lib import pynutil
import logging
class ClassifyFst(GraphFst):
"""
Final class that composes all other classification grammars. This class can process an entire sentence, that is lower cased.
For deployment, this grammar will be compiled and exported to OpenFst Finate State Archiv (FAR) File.
More details to deployment at NeMo/tools/text_processing_deployment.
Args:
input_case: accepting either "lower_cased" or "cased" input.
deterministic: if True will provide a single transduction option,
for False multiple options (used for audio-based normalization)
cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache.
overwrite_cache: set to True to overwrite .far files
whitelist: path to a file with whitelist replacements
"""
def __init__(
self,
input_case: str,
deterministic: bool = False,
cache_dir: str = None,
overwrite_cache: bool = False,
whitelist: str = None,
):
super().__init__(name="tokenize_and_classify", kind="classify", deterministic=deterministic)
far_file = None
if cache_dir is not None and cache_dir != "None":
os.makedirs(cache_dir, exist_ok=True)
whitelist_file = os.path.basename(whitelist) if whitelist else ""
far_file = os.path.join(
cache_dir, f"_{input_case}_de_tn_{deterministic}_deterministic{whitelist_file}.far"
)
if not overwrite_cache and far_file and os.path.exists(far_file):
self.fst = pynini.Far(far_file, mode="r")["tokenize_and_classify"]
no_digits = pynini.closure(pynini.difference(DAMO_CHAR, DAMO_DIGIT))
self.fst_no_digits = pynini.compose(self.fst, no_digits).optimize()
logging.info(f"ClassifyFst.fst was restored from {far_file}.")
else:
logging.info(f"Creating ClassifyFst grammars. This might take some time...")
self.cardinal = CardinalFst(deterministic=deterministic)
cardinal_graph = self.cardinal.fst
self.ordinal = OrdinalFst(cardinal=self.cardinal, deterministic=deterministic)
ordinal_graph = self.ordinal.fst
self.decimal = DecimalFst(cardinal=self.cardinal, deterministic=deterministic)
decimal_graph = self.decimal.fst
self.fraction = FractionFst(cardinal=self.cardinal, deterministic=deterministic)
fraction_graph = self.fraction.fst
self.measure = MeasureFst(
cardinal=self.cardinal,
decimal=self.decimal,
fraction=self.fraction,
deterministic=deterministic,
)
measure_graph = self.measure.fst
self.date = DateFst(cardinal=self.cardinal, deterministic=deterministic)
date_graph = self.date.fst
word_graph = WordFst(deterministic=deterministic).fst
self.time = TimeFst(deterministic=deterministic)
time_graph = self.time.fst
self.telephone = TelephoneFst(cardinal=self.cardinal, deterministic=deterministic)
telephone_graph = self.telephone.fst
self.electronic = ElectronicFst(deterministic=deterministic)
electronic_graph = self.electronic.fst
self.money = MoneyFst(
cardinal=self.cardinal, decimal=self.decimal, deterministic=deterministic
)
money_graph = self.money.fst
self.whitelist = WhiteListFst(
input_case=input_case, deterministic=deterministic, input_file=whitelist
)
whitelist_graph = self.whitelist.fst
punct_graph = PunctuationFst(deterministic=deterministic).fst
classify = (
pynutil.add_weight(whitelist_graph, 1.01)
| pynutil.add_weight(time_graph, 1.1)
| pynutil.add_weight(measure_graph, 1.1)
| pynutil.add_weight(cardinal_graph, 1.1)
| pynutil.add_weight(fraction_graph, 1.1)
| pynutil.add_weight(date_graph, 1.1)
| pynutil.add_weight(ordinal_graph, 1.1)
| pynutil.add_weight(decimal_graph, 1.1)
| pynutil.add_weight(money_graph, 1.1)
| pynutil.add_weight(telephone_graph, 1.1)
| pynutil.add_weight(electronic_graph, 1.1)
)
classify |= pynutil.add_weight(word_graph, 100)
punct = (
pynutil.insert("tokens { ")
+ pynutil.add_weight(punct_graph, weight=1.1)
+ pynutil.insert(" }")
)
token = pynutil.insert("tokens { ") + classify + pynutil.insert(" }")
token_plus_punct = (
pynini.closure(punct + pynutil.insert(" "))
+ token
+ pynini.closure(pynutil.insert(" ") + punct)
)
graph = token_plus_punct + pynini.closure(
pynutil.add_weight(delete_extra_space, 1.1) + token_plus_punct
)
graph = delete_space + graph + delete_space
self.fst = graph.optimize()
no_digits = pynini.closure(pynini.difference(DAMO_CHAR, DAMO_DIGIT))
self.fst_no_digits = pynini.compose(self.fst, no_digits).optimize()
if far_file:
generator_main(far_file, {"tokenize_and_classify": self.fst})
logging.info(f"ClassifyFst grammars are saved to {far_file}.")
@@ -0,0 +1,52 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import GraphFst, convert_space
from pynini.lib import pynutil
class WhiteListFst(GraphFst):
"""
Finite state transducer for classifying whitelist, e.g.
"Mr." -> tokens { name: "mister" }
This class has highest priority among all classifier grammars. Whitelisted tokens are defined and loaded from "data/whitelist.tsv".
Args:
input_case: accepting either "lower_cased" or "cased" input.
deterministic: if True will provide a single transduction option,
for False multiple options (used for audio-based normalization)
input_file: path to a file with whitelist replacements
"""
def __init__(self, input_case: str, deterministic: bool = True, input_file: str = None):
super().__init__(name="whitelist", kind="classify", deterministic=deterministic)
def _get_whitelist_graph(input_case, file):
whitelist = load_labels(file)
if input_case == "lower_cased":
whitelist = [[x[0].lower()] + x[1:] for x in whitelist]
graph = pynini.string_map(whitelist)
return graph
graph = _get_whitelist_graph(input_case, get_abs_path("data/whitelist.tsv"))
if not deterministic and input_case != "lower_cased":
graph |= pynutil.add_weight(
_get_whitelist_graph("lower_cased", get_abs_path("data/whitelist.tsv")),
weight=0.0001,
)
if input_file:
whitelist_provided = _get_whitelist_graph(input_case, input_file)
if not deterministic:
graph |= whitelist_provided
else:
graph = whitelist_provided
if not deterministic:
units_graph = _get_whitelist_graph(
input_case, file=get_abs_path("data/measure/measurements.tsv")
)
graph |= units_graph
self.graph = graph
self.final_graph = convert_space(self.graph).optimize()
self.fst = (pynutil.insert('name: "') + self.final_graph + pynutil.insert('"')).optimize()
@@ -0,0 +1,19 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import DAMO_NOT_SPACE, GraphFst
from pynini.lib import pynutil
class WordFst(GraphFst):
"""
Finite state transducer for classifying word.
e.g. sleep -> tokens { name: "sleep" }
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="word", kind="classify")
word = pynutil.insert('name: "') + pynini.closure(DAMO_NOT_SPACE, 1) + pynutil.insert('"')
self.fst = word.optimize()
@@ -0,0 +1,34 @@
import csv
import os
import logging
def get_abs_path(rel_path):
"""
Get absolute path
Args:
rel_path: relative path to this file
Returns absolute path
"""
abs_path = os.path.dirname(os.path.abspath(__file__)) + os.sep + rel_path
if not os.path.exists(abs_path):
logging.warning(f"{abs_path} does not exist")
return abs_path
def load_labels(abs_path):
"""
loads relative path file as dictionary
Args:
abs_path: absolute path
Returns dictionary of mappings
"""
label_tsv = open(abs_path, encoding="utf-8")
labels = list(csv.reader(label_tsv, delimiter="\t"))
return labels
@@ -0,0 +1,28 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import DAMO_NOT_QUOTE, GraphFst
from pynini.lib import pynutil
class CardinalFst(GraphFst):
"""
Finite state transducer for verbalizing cardinals
e.g. cardinal { integer: "zwei" } -> "zwei"
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="cardinal", kind="verbalize", deterministic=deterministic)
optional_sign = pynini.closure(pynini.cross('negative: "true" ', "minus "), 0, 1)
self.optional_sign = optional_sign
integer = pynini.closure(DAMO_NOT_QUOTE, 1)
self.integer = pynutil.delete(' "') + integer + pynutil.delete('"')
integer = pynutil.delete("integer:") + self.integer
self.numbers = integer
graph = optional_sign + self.numbers
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,55 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
DAMO_SIGMA,
GraphFst,
delete_preserve_order,
)
from pynini.lib import pynutil
class DateFst(GraphFst):
"""
Finite state transducer for verbalizing date, e.g.
date { day: "vier" month: "april" year: "zwei tausend zwei" } -> "vierter april zwei tausend zwei"
date { day: "vier" month: "mai" year: "zwei tausend zwei" } -> "vierter mai zwei tausend zwei"
Args:
ordinal: ordinal verbalizer GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, ordinal: GraphFst, deterministic: bool = True):
super().__init__(name="date", kind="verbalize", deterministic=deterministic)
day_cardinal = (
pynutil.delete('day: "') + pynini.closure(DAMO_NOT_QUOTE, 1) + pynutil.delete('"')
)
day = day_cardinal @ pynini.cdrewrite(
ordinal.ordinal_stem, "", "[EOS]", DAMO_SIGMA
) + pynutil.insert("ter")
months_names = pynini.union(
*[x[1] for x in load_labels(get_abs_path("data/months/abbr_to_name.tsv"))]
)
month = pynutil.delete('month: "') + pynini.closure(DAMO_NOT_QUOTE, 1) + pynutil.delete('"')
final_month = month @ months_names
final_month |= month @ pynini.difference(DAMO_SIGMA, months_names) @ pynini.cdrewrite(
ordinal.ordinal_stem, "", "[EOS]", DAMO_SIGMA
) + pynutil.insert("ter")
year = pynutil.delete('year: "') + pynini.closure(DAMO_NOT_QUOTE, 1) + pynutil.delete('"')
# day month year
graph_dmy = (
day + pynini.accep(" ") + final_month + pynini.closure(pynini.accep(" ") + year, 0, 1)
)
graph_dmy |= final_month + pynini.accep(" ") + year
self.graph = graph_dmy | year
final_graph = self.graph + delete_preserve_order
delete_tokens = self.delete_tokens(final_graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,57 @@
import pynini
from fun_text_processing.text_normalization.de.taggers.decimal import quantities
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
GraphFst,
delete_preserve_order,
insert_space,
)
from pynini.lib import pynutil
class DecimalFst(GraphFst):
"""
Finite state transducer for classifying decimal, e.g.
decimal { negative: "true" integer_part: "elf" fractional_part: "vier null sechs" quantity: "billionen" } -> minus elf komma vier null sechs billionen
decimal { integer_part: "eins" quantity: "billion" } -> eins billion
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="decimal", kind="classify", deterministic=deterministic)
delete_space = pynutil.delete(" ")
self.optional_sign = pynini.closure(
pynini.cross('negative: "true"', "minus ") + delete_space, 0, 1
)
self.integer = (
pynutil.delete('integer_part: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
self.fractional_default = (
pynutil.delete('fractional_part: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
self.fractional = pynutil.insert(" komma ") + self.fractional_default
self.quantity = (
delete_space
+ insert_space
+ pynutil.delete('quantity: "')
+ quantities
+ pynutil.delete('"')
)
self.optional_quantity = pynini.closure(self.quantity, 0, 1)
graph = self.optional_sign + (
self.integer + self.quantity
| self.integer + delete_space + self.fractional + self.optional_quantity
)
self.numbers = graph
graph += delete_preserve_order
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,64 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
DAMO_SIGMA,
GraphFst,
delete_preserve_order,
insert_space,
)
from pynini.lib import pynutil
class ElectronicFst(GraphFst):
"""
Finite state transducer for verbalizing electronic
e.g. electronic { username: "abc" domain: "hotmail.com" } -> "a b c at hotmail punkt com"
-> "a b c at h o t m a i l punkt c o m"
-> "a b c at hotmail punkt c o m"
-> "a b c at h o t m a i l punkt com"
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="electronic", kind="verbalize", deterministic=deterministic)
graph_digit_no_zero = pynini.invert(
pynini.string_file(get_abs_path("data/numbers/digit.tsv"))
).optimize() | pynini.cross("1", "eins")
graph_zero = pynini.invert(
pynini.string_file(get_abs_path("data/numbers/zero.tsv"))
).optimize()
graph_digit = graph_digit_no_zero | graph_zero
graph_symbols = pynini.string_file(get_abs_path("data/electronic/symbols.tsv")).optimize()
server_common = pynini.string_file(get_abs_path("data/electronic/server_name.tsv"))
domain_common = pynini.string_file(get_abs_path("data/electronic/domain.tsv"))
def add_space_after_char():
return pynini.closure(DAMO_NOT_QUOTE - pynini.accep(" ") + insert_space) + (
DAMO_NOT_QUOTE - pynini.accep(" ")
)
verbalize_characters = pynini.cdrewrite(graph_symbols | graph_digit, "", "", DAMO_SIGMA)
user_name = pynutil.delete('username: "') + add_space_after_char() + pynutil.delete('"')
user_name @= verbalize_characters
convert_defaults = (
pynutil.add_weight(DAMO_NOT_QUOTE, weight=0.0001) | domain_common | server_common
)
domain = convert_defaults + pynini.closure(insert_space + convert_defaults)
domain @= verbalize_characters
domain = pynutil.delete('domain: "') + domain + pynutil.delete('"')
protocol = (
pynutil.delete('protocol: "')
+ add_space_after_char() @ pynini.cdrewrite(graph_symbols, "", "", DAMO_SIGMA)
+ pynutil.delete('"')
)
self.graph = (pynini.closure(protocol + pynini.accep(" "), 0, 1) + domain) | (
user_name + pynini.accep(" ") + pynutil.insert("at ") + domain
)
delete_tokens = self.delete_tokens(self.graph + delete_preserve_order)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,68 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
DAMO_SIGMA,
GraphFst,
delete_preserve_order,
insert_space,
)
from pynini.lib import pynutil
class FractionFst(GraphFst):
"""
Finite state transducer for verbalizing fraction
e.g. fraction { integer: "drei" numerator: "eins" denominator: "zwei" }-> drei ein halb
e.g. fraction { numerator: "vier" denominator: "zwei" } -> vier halbe
e.g. fraction { numerator: "drei" denominator: "vier" } -> drei viertel
Args:
ordinal: ordinal GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, ordinal: GraphFst, deterministic: bool = True):
super().__init__(name="fraction", kind="verbalize", deterministic=deterministic)
optional_sign = pynini.closure(
pynini.cross('negative: "true"', "minus ") + pynutil.delete(" "), 0, 1
)
change_one = pynini.cdrewrite(
pynutil.add_weight(pynini.cross("eins", "ein"), weight=-0.0001),
"[BOS]",
"[EOS]",
DAMO_SIGMA,
)
change_numerator_two = pynini.cdrewrite(
pynini.cross("zweitel", "halbe"), "[BOS]", "[EOS]", DAMO_SIGMA
)
integer = pynutil.delete('integer_part: "') + change_one + pynutil.delete('" ')
numerator = pynutil.delete('numerator: "') + change_one + pynutil.delete('" ')
denominator = (
pynutil.delete('denominator: "')
+ pynini.closure(DAMO_NOT_QUOTE)
@ (
pynini.cdrewrite(
pynini.closure(ordinal.ordinal_stem, 0, 1), "", "[EOS]", DAMO_SIGMA
)
+ pynutil.insert("tel")
)
@ change_numerator_two
+ pynutil.delete('"')
)
integer += insert_space + pynini.closure(pynutil.insert("und ", weight=0.001), 0, 1)
denominator_one_half = pynini.cdrewrite(
pynini.cross("ein halbe", "ein halb"), "[BOS]", "[EOS]", DAMO_SIGMA
)
fraction_default = (numerator + insert_space + denominator) @ denominator_one_half
self.graph = optional_sign + pynini.closure(integer, 0, 1) + fraction_default
graph = self.graph + delete_preserve_order
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,41 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
GraphFst,
delete_extra_space,
delete_preserve_order,
)
from pynini.lib import pynutil
class MeasureFst(GraphFst):
"""
Finite state transducer for verbalizing measure, e.g.
measure { cardinal { integer: "zwei" units: "unzen" } } -> "zwei unzen"
measure { cardinal { integer_part: "zwei" quantity: "millionen" units: "unzen" } } -> "zwei millionen unzen"
Args:
decimal: decimal GraphFst
cardinal: cardinal GraphFst
fraction: fraction GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(
self, decimal: GraphFst, cardinal: GraphFst, fraction: GraphFst, deterministic: bool
):
super().__init__(name="measure", kind="verbalize", deterministic=deterministic)
unit = pynutil.delete('units: "') + pynini.closure(DAMO_NOT_QUOTE) + pynutil.delete('"')
graph_decimal = decimal.fst
graph_cardinal = cardinal.fst
graph_fraction = fraction.fst
graph = (graph_cardinal | graph_decimal | graph_fraction) + pynini.accep(" ") + unit
graph |= unit + delete_extra_space + (graph_cardinal | graph_decimal)
graph += delete_preserve_order
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,77 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
GraphFst,
delete_preserve_order,
)
from pynini.lib import pynutil
class MoneyFst(GraphFst):
"""
Finite state transducer for verbalizing money, e.g.
money { currency_maj: "euro" integer_part: "ein"} -> "ein euro"
money { currency_maj: "euro" integer_part: "eins" fractional_part: "null null eins"} -> "eins komma null null eins euro"
money { integer_part: "ein" currency_maj: "pfund" fractional_part: "vierzig" preserve_order: true} -> "ein pfund vierzig"
money { integer_part: "ein" currency_maj: "pfund" fractional_part: "vierzig" currency_min: "pence" preserve_order: true} -> "ein pfund vierzig pence"
money { fractional_part: "ein" currency_min: "penny" preserve_order: true} -> "ein penny"
money { currency_maj: "pfund" integer_part: "null" fractional_part: "null eins" quantity: "million"} -> "null komma null eins million pfund"
Args:
decimal: GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, decimal: GraphFst, deterministic: bool = True):
super().__init__(name="money", kind="verbalize", deterministic=deterministic)
keep_space = pynini.accep(" ")
maj = (
pynutil.delete('currency_maj: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
min = (
pynutil.delete('currency_min: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
fractional_part = (
pynutil.delete('fractional_part: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
integer_part = (
pynutil.delete('integer_part: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
optional_add_and = pynini.closure(pynutil.insert("und "), 0, 1)
# *** currency_maj
graph_integer = integer_part + keep_space + maj
# *** currency_maj + (***) | ((und) *** current_min)
graph_integer_with_minor = (
integer_part
+ keep_space
+ maj
+ keep_space
+ (fractional_part | (optional_add_and + fractional_part + keep_space + min))
+ delete_preserve_order
)
# *** komma *** currency_maj
graph_decimal = decimal.fst + keep_space + maj
# *** current_min
graph_minor = fractional_part + keep_space + min + delete_preserve_order
graph = graph_integer | graph_integer_with_minor | graph_decimal | graph_minor
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,45 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
DAMO_SIGMA,
GraphFst,
)
from pynini.lib import pynutil
class OrdinalFst(GraphFst):
"""
Finite state transducer for verbalizing roman numerals
e.g. ordinal { integer: "vier" } } -> "vierter"
-> "viertes" ...
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="ordinal", kind="verbalize", deterministic=deterministic)
graph_digit = pynini.string_file(get_abs_path("data/ordinals/digit.tsv")).invert()
graph_ties = pynini.string_file(get_abs_path("data/ordinals/ties.tsv")).invert()
graph_thousands = pynini.string_file(get_abs_path("data/ordinals/thousands.tsv")).invert()
graph = (
pynutil.delete('integer: "') + pynini.closure(DAMO_NOT_QUOTE, 1) + pynutil.delete('"')
)
suffixes = pynini.union("ten", "tem", "ter", "tes", "te")
convert_rest = pynutil.insert(suffixes, weight=0.01)
self.ordinal_stem = graph_digit | graph_ties | graph_thousands
suffix = pynini.cdrewrite(
pynini.closure(self.ordinal_stem, 0, 1) + convert_rest,
"",
"[EOS]",
DAMO_SIGMA,
).optimize()
self.graph = pynini.compose(graph, suffix)
self.suffix = suffix
delete_tokens = self.delete_tokens(self.graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,40 @@
import pynini
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_NOT_QUOTE,
GraphFst,
delete_preserve_order,
)
from pynini.lib import pynutil
class TelephoneFst(GraphFst):
"""
Finite state transducer for verbalizing telephone, e.g.
telephone { country_code: "plus neun und vierzig" number_part: "null eins eins eins null null null" }
-> "plus neun und vierzig null eins eins eins null null null"
Args:
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="telephone", kind="verbalize", deterministic=deterministic)
country_code = (
pynutil.delete('country_code: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
number_part = (
pynutil.delete('number_part: "')
+ pynini.closure(DAMO_NOT_QUOTE, 1)
+ pynutil.delete('"')
)
self.graph = country_code + pynini.accep(" ") + number_part
graph = self.graph + delete_preserve_order
delete_tokens = self.delete_tokens(graph)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,126 @@
import pynini
from fun_text_processing.text_normalization.de.utils import get_abs_path, load_labels
from fun_text_processing.text_normalization.en.graph_utils import (
DAMO_DIGIT,
DAMO_SIGMA,
GraphFst,
convert_space,
delete_preserve_order,
)
from pynini.lib import pynutil
class TimeFst(GraphFst):
"""
Finite state transducer for verbalizing electronic, e.g.
time { hours: "2" minutes: "15"} -> "zwei uhr fünfzehn"
time { minutes: "15" hours: "2" } -> "viertel nach zwei"
time { minutes: "15" hours: "2" } -> "fünfzehn nach zwei"
time { hours: "14" minutes: "15"} -> "vierzehn uhr fünfzehn"
time { minutes: "15" hours: "14" } -> "viertel nach zwei"
time { minutes: "15" hours: "14" } -> "fünfzehn nach drei"
time { minutes: "45" hours: "14" } -> "viertel vor drei"
Args:
cardinal_tagger: cardinal_tagger tagger GraphFst
deterministic: if True will provide a single transduction option,
for False multiple transduction are generated (used for audio-based normalization)
"""
def __init__(self, cardinal_tagger: GraphFst, deterministic: bool = True):
super().__init__(name="time", kind="verbalize", deterministic=deterministic)
# add weight so when using inverse text normalization this conversion is depriotized
night_to_early = pynutil.add_weight(
pynini.invert(
pynini.string_file(get_abs_path("data/time/hour_to_night.tsv"))
).optimize(),
weight=0.0001,
)
hour_to = pynini.invert(
pynini.string_file(get_abs_path("data/time/hour_to.tsv"))
).optimize()
minute_to = pynini.invert(
pynini.string_file(get_abs_path("data/time/minute_to.tsv"))
).optimize()
time_zone_graph = pynini.invert(
convert_space(
pynini.union(*[x[1] for x in load_labels(get_abs_path("data/time/time_zone.tsv"))])
)
)
graph_zero = pynini.invert(
pynini.string_file(get_abs_path("data/numbers/zero.tsv"))
).optimize()
number_verbalization = graph_zero | cardinal_tagger.two_digit_non_zero
hour = pynutil.delete('hours: "') + pynini.closure(DAMO_DIGIT, 1) + pynutil.delete('"')
hour_verbalized = hour @ number_verbalization @ pynini.cdrewrite(
pynini.cross("eins", "ein"), "[BOS]", "[EOS]", DAMO_SIGMA
) + pynutil.insert(" uhr")
minute = pynutil.delete('minutes: "') + pynini.closure(DAMO_DIGIT, 1) + pynutil.delete('"')
zone = pynutil.delete('zone: "') + time_zone_graph + pynutil.delete('"')
optional_zone = pynini.closure(pynini.accep(" ") + zone, 0, 1)
second = pynutil.delete('seconds: "') + pynini.closure(DAMO_DIGIT, 1) + pynutil.delete('"')
graph_hms = (
hour_verbalized
+ pynini.accep(" ")
+ minute @ number_verbalization
+ pynutil.insert(" minuten")
+ pynini.accep(" ")
+ second @ number_verbalization
+ pynutil.insert(" sekunden")
+ optional_zone
)
graph_hms @= pynini.cdrewrite(
pynini.cross("eins minuten", "eine minute")
| pynini.cross("eins sekunden", "eine sekunde"),
pynini.union(" ", "[BOS]"),
"",
DAMO_SIGMA,
)
min_30 = [str(x) for x in range(1, 31)]
min_30 = pynini.union(*min_30)
min_29 = [str(x) for x in range(1, 30)]
min_29 = pynini.union(*min_29)
graph_h = hour_verbalized
graph_hm = hour_verbalized + pynini.accep(" ") + minute @ number_verbalization
graph_m_past_h = (
minute @ min_30 @ (number_verbalization | pynini.cross("15", "viertel"))
+ pynini.accep(" ")
+ pynutil.insert("nach ")
# + hour @ number_verbalization
+ hour
@ pynini.cdrewrite(night_to_early, "[BOS]", "[EOS]", DAMO_SIGMA)
@ number_verbalization
)
graph_m30_h = (
minute @ pynini.cross("30", "halb")
+ pynini.accep(" ")
+ hour
@ pynini.cdrewrite(night_to_early, "[BOS]", "[EOS]", DAMO_SIGMA)
@ hour_to
@ number_verbalization
)
graph_m_to_h = (
minute @ minute_to @ min_29 @ (number_verbalization | pynini.cross("15", "viertel"))
+ pynini.accep(" ")
+ pynutil.insert("vor ")
+ hour
@ pynini.cdrewrite(night_to_early, "[BOS]", "[EOS]", DAMO_SIGMA)
@ hour_to
@ number_verbalization
)
self.graph = (
graph_hms
| graph_h
| graph_hm
| pynutil.add_weight(graph_m_past_h, weight=0.0001)
| pynutil.add_weight(graph_m30_h, weight=0.0001)
| pynutil.add_weight(graph_m_to_h, weight=0.0001)
) + optional_zone
delete_tokens = self.delete_tokens(self.graph + delete_preserve_order)
self.fst = delete_tokens.optimize()
@@ -0,0 +1,64 @@
from fun_text_processing.text_normalization.de.taggers.cardinal import CardinalFst as CardinalTagger
from fun_text_processing.text_normalization.de.verbalizers.cardinal import CardinalFst
from fun_text_processing.text_normalization.de.verbalizers.date import DateFst
from fun_text_processing.text_normalization.de.verbalizers.decimal import DecimalFst
from fun_text_processing.text_normalization.de.verbalizers.electronic import ElectronicFst
from fun_text_processing.text_normalization.de.verbalizers.fraction import FractionFst
from fun_text_processing.text_normalization.de.verbalizers.measure import MeasureFst
from fun_text_processing.text_normalization.de.verbalizers.money import MoneyFst
from fun_text_processing.text_normalization.de.verbalizers.ordinal import OrdinalFst
from fun_text_processing.text_normalization.de.verbalizers.telephone import TelephoneFst
from fun_text_processing.text_normalization.de.verbalizers.time import TimeFst
from fun_text_processing.text_normalization.en.graph_utils import GraphFst
from fun_text_processing.text_normalization.en.verbalizers.whitelist import WhiteListFst
class VerbalizeFst(GraphFst):
"""
Composes other verbalizer grammars.
For deployment, this grammar will be compiled and exported to OpenFst Finate State Archiv (FAR) File.
More details to deployment at NeMo/tools/text_processing_deployment.
Args:
deterministic: if True will provide a single transduction option,
for False multiple options (used for audio-based normalization)
"""
def __init__(self, deterministic: bool = True):
super().__init__(name="verbalize", kind="verbalize", deterministic=deterministic)
cardinal_tagger = CardinalTagger(deterministic=deterministic)
cardinal = CardinalFst(deterministic=deterministic)
cardinal_graph = cardinal.fst
ordinal = OrdinalFst(deterministic=deterministic)
ordinal_graph = ordinal.fst
decimal = DecimalFst(deterministic=deterministic)
decimal_graph = decimal.fst
fraction = FractionFst(ordinal=ordinal, deterministic=deterministic)
fraction_graph = fraction.fst
date = DateFst(ordinal=ordinal)
date_graph = date.fst
measure = MeasureFst(
cardinal=cardinal, decimal=decimal, fraction=fraction, deterministic=deterministic
)
measure_graph = measure.fst
electronic = ElectronicFst(deterministic=deterministic)
electronic_graph = electronic.fst
whitelist_graph = WhiteListFst(deterministic=deterministic).fst
money_graph = MoneyFst(decimal=decimal).fst
telephone_graph = TelephoneFst(deterministic=deterministic).fst
time_graph = TimeFst(cardinal_tagger=cardinal_tagger, deterministic=deterministic).fst
graph = (
cardinal_graph
| measure_graph
| decimal_graph
| ordinal_graph
| date_graph
| electronic_graph
| money_graph
| fraction_graph
| whitelist_graph
| telephone_graph
| time_graph
)
self.fst = graph
@@ -0,0 +1,61 @@
import os
import pynini
from fun_text_processing.text_normalization.de.verbalizers.verbalize import VerbalizeFst
from fun_text_processing.text_normalization.en.graph_utils import (
GraphFst,
delete_extra_space,
delete_space,
generator_main,
)
from fun_text_processing.text_normalization.en.verbalizers.word import WordFst
from pynini.lib import pynutil
import logging
class VerbalizeFinalFst(GraphFst):
"""
Finite state transducer that verbalizes an entire sentence
Args:
deterministic: if True will provide a single transduction option,
for False multiple options (used for audio-based normalization)
cache_dir: path to a dir with .far grammar file. Set to None to avoid using cache.
overwrite_cache: set to True to overwrite .far files
"""
def __init__(
self, deterministic: bool = True, cache_dir: str = None, overwrite_cache: bool = False
):
super().__init__(name="verbalize_final", kind="verbalize", deterministic=deterministic)
far_file = None
if cache_dir is not None and cache_dir != "None":
os.makedirs(cache_dir, exist_ok=True)
far_file = os.path.join(
cache_dir, f"de_tn_{deterministic}_deterministic_verbalizer.far"
)
if not overwrite_cache and far_file and os.path.exists(far_file):
self.fst = pynini.Far(far_file, mode="r")["verbalize"]
logging.info(f"VerbalizeFinalFst graph was restored from {far_file}.")
else:
verbalize = VerbalizeFst(deterministic=deterministic).fst
word = WordFst(deterministic=deterministic).fst
types = verbalize | word
graph = (
pynutil.delete("tokens")
+ delete_space
+ pynutil.delete("{")
+ delete_space
+ types
+ delete_space
+ pynutil.delete("}")
)
graph = delete_space + pynini.closure(graph + delete_extra_space) + graph + delete_space
self.fst = graph.optimize()
if far_file:
generator_main(far_file, {"verbalize": self.fst})
logging.info(f"VerbalizeFinalFst grammars are saved to {far_file}.")
@@ -0,0 +1,3 @@
from fun_text_processing.text_normalization.en.taggers.tokenize_and_classify import ClassifyFst
from fun_text_processing.text_normalization.en.verbalizers.verbalize import VerbalizeFst
from fun_text_processing.text_normalization.en.verbalizers.verbalize_final import VerbalizeFinalFst
@@ -0,0 +1,384 @@
from argparse import ArgumentParser
from typing import List
import regex as re
from fun_text_processing.text_normalization.data_loader_utils import (
EOS_TYPE,
Instance,
load_files,
training_data_to_sentences,
)
"""
This file is for evaluation purposes.
filter_loaded_data() cleans data (list of instances) for text normalization. Filters and cleaners can be specified for each semiotic class individually.
For example, normalized text should only include characters and whitespace characters but no punctuation.
Cardinal unnormalized instances should contain at least one integer and all other characters are removed.
"""
class Filter:
"""
Filter class
Args:
class_type: semiotic class used in dataset
process_func: function to transform text
filter_func: function to filter text
"""
def __init__(self, class_type: str, process_func: object, filter_func: object):
self.class_type = class_type
self.process_func = process_func
self.filter_func = filter_func
def filter(self, instance: Instance) -> bool:
"""
filter function
Args:
filters given instance with filter function
Returns: True if given instance fulfills criteria or does not belong to class type
"""
if instance.token_type != self.class_type:
return True
return self.filter_func(instance)
def process(self, instance: Instance) -> Instance:
"""
process function
Args:
processes given instance with process function
Returns: processed instance if instance belongs to expected class type or original instance
"""
if instance.token_type != self.class_type:
return instance
return self.process_func(instance)
def filter_cardinal_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_cardinal_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
un_normalized = re.sub(r"[^0-9]", "", un_normalized)
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_ordinal_1(instance: Instance) -> bool:
ok = re.search(r"(st|nd|rd|th)\s*$", instance.un_normalized)
return ok
def process_ordinal_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
un_normalized = re.sub(r"[,\s]", "", un_normalized)
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_decimal_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_decimal_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
un_normalized = re.sub(r",", "", un_normalized)
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_measure_1(instance: Instance) -> bool:
ok = True
return ok
def process_measure_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
un_normalized = re.sub(r",", "", un_normalized)
un_normalized = re.sub(r"m2", "", un_normalized)
un_normalized = re.sub(r"(\d)([^\d.\s])", r"\1 \2", un_normalized)
normalized = re.sub(r"[^a-z\s]", "", normalized)
normalized = re.sub(r"per ([a-z\s]*)s$", r"per \1", normalized)
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_money_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_money_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
un_normalized = re.sub(r",", "", un_normalized)
un_normalized = re.sub(r"a\$", r"$", un_normalized)
un_normalized = re.sub(r"us\$", r"$", un_normalized)
un_normalized = re.sub(r"(\d)m\s*$", r"\1 million", un_normalized)
un_normalized = re.sub(r"(\d)bn?\s*$", r"\1 billion", un_normalized)
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_time_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_time_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
un_normalized = re.sub(r": ", ":", un_normalized)
un_normalized = re.sub(r"(\d)\s?a\s?m\s?", r"\1 a.m.", un_normalized)
un_normalized = re.sub(r"(\d)\s?p\s?m\s?", r"\1 p.m.", un_normalized)
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_plain_1(instance: Instance) -> bool:
ok = True
return ok
def process_plain_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_punct_1(instance: Instance) -> bool:
ok = True
return ok
def process_punct_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_date_1(instance: Instance) -> bool:
ok = True
return ok
def process_date_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
un_normalized = re.sub(r",", "", un_normalized)
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_letters_1(instance: Instance) -> bool:
ok = True
return ok
def process_letters_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_verbatim_1(instance: Instance) -> bool:
ok = True
return ok
def process_verbatim_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_digit_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_digit_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_telephone_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_telephone_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_electronic_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_electronic_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_fraction_1(instance: Instance) -> bool:
ok = re.search(r"[0-9]", instance.un_normalized)
return ok
def process_fraction_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
def filter_address_1(instance: Instance) -> bool:
ok = True
return ok
def process_address_1(instance: Instance) -> Instance:
un_normalized = instance.un_normalized
normalized = instance.normalized
normalized = re.sub(r"[^a-z ]", "", normalized)
return Instance(
token_type=instance.token_type, un_normalized=un_normalized, normalized=normalized
)
filters = []
filters.append(
Filter(class_type="CARDINAL", process_func=process_cardinal_1, filter_func=filter_cardinal_1)
)
filters.append(
Filter(class_type="ORDINAL", process_func=process_ordinal_1, filter_func=filter_ordinal_1)
)
filters.append(
Filter(class_type="DECIMAL", process_func=process_decimal_1, filter_func=filter_decimal_1)
)
filters.append(
Filter(class_type="MEASURE", process_func=process_measure_1, filter_func=filter_measure_1)
)
filters.append(Filter(class_type="MONEY", process_func=process_money_1, filter_func=filter_money_1))
filters.append(Filter(class_type="TIME", process_func=process_time_1, filter_func=filter_time_1))
filters.append(Filter(class_type="DATE", process_func=process_date_1, filter_func=filter_date_1))
filters.append(Filter(class_type="PLAIN", process_func=process_plain_1, filter_func=filter_plain_1))
filters.append(Filter(class_type="PUNCT", process_func=process_punct_1, filter_func=filter_punct_1))
filters.append(
Filter(class_type="LETTERS", process_func=process_letters_1, filter_func=filter_letters_1)
)
filters.append(
Filter(class_type="VERBATIM", process_func=process_verbatim_1, filter_func=filter_verbatim_1)
)
filters.append(Filter(class_type="DIGIT", process_func=process_digit_1, filter_func=filter_digit_1))
filters.append(
Filter(class_type="TELEPHONE", process_func=process_telephone_1, filter_func=filter_telephone_1)
)
filters.append(
Filter(
class_type="ELECTRONIC", process_func=process_electronic_1, filter_func=filter_electronic_1
)
)
filters.append(
Filter(class_type="FRACTION", process_func=process_fraction_1, filter_func=filter_fraction_1)
)
filters.append(
Filter(class_type="ADDRESS", process_func=process_address_1, filter_func=filter_address_1)
)
filters.append(Filter(class_type=EOS_TYPE, process_func=lambda x: x, filter_func=lambda x: True))
def filter_loaded_data(data: List[Instance], verbose: bool = False) -> List[Instance]:
"""
Filters list of instances
Args:
data: list of instances
Returns: filtered and transformed list of instances
"""
updates_instances = []
for instance in data:
updated_instance = False
for fil in filters:
if tl.class_type == instance.token_type and tl.filter(instance):
instance = fil.process(instance)
updated_instance = True
if updated_instance:
if verbose:
print(instance)
updates_instances.append(instance)
return updates_instances
def parse_args():
parser = ArgumentParser()
parser.add_argument(
"--input", help="input file path", type=str, default="./en_with_types/output-00001-of-00100"
)
parser.add_argument("--verbose", help="print filtered instances", action="store_true")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
file_path = args.input
print("Loading training data: " + file_path)
instance_list = load_files([file_path]) # List of instances
filtered_instance_list = filter_loaded_data(instance_list, args.verbose)
training_data_to_sentences(filtered_instance_list)
@@ -0,0 +1 @@
@@ -0,0 +1,14 @@
st Street
street Street
expy Expressway
fwy Freeway
hwy Highway
dr Drive
ct Court
ave Avenue
av Avenue
cir Circle
blvd Boulevard
alley Alley
way Way
jct Junction
1 st Street
2 street Street
3 expy Expressway
4 fwy Freeway
5 hwy Highway
6 dr Drive
7 ct Court
8 ave Avenue
9 av Avenue
10 cir Circle
11 blvd Boulevard
12 alley Alley
13 way Way
14 jct Junction
@@ -0,0 +1,52 @@
Alabama AL
Alaska AK
Arizona AZ
Arkansas AR
California CA
Colorado CO
Connecticut CT
Delaware DE
Florida FL
Georgia GA
Hawaii HI
Idaho ID
Illinois IL
Indiana IN
Indiana IND
Iowa IA
Kansas KS
Kentucky KY
Louisiana LA
Maine ME
Maryland MD
Massachusetts MA
Michigan MI
Minnesota MN
Mississippi MS
Missouri MO
Montana MT
Nebraska NE
Nevada NV
New Hampshire NH
New Jersey NJ
New Mexico NM
New York NY
North Carolina NC
North Dakota ND
Ohio OH
Oklahoma OK
Oregon OR
Pennsylvania PA
Rhode Island RI
South Carolina SC
South Dakota SD
Tennessee TN
Tennessee TENN
Texas TX
Utah UT
Vermont VT
Virginia VA
Washington WA
West Virginia WV
Wisconsin WI
Wyoming WY
1 Alabama AL
2 Alaska AK
3 Arizona AZ
4 Arkansas AR
5 California CA
6 Colorado CO
7 Connecticut CT
8 Delaware DE
9 Florida FL
10 Georgia GA
11 Hawaii HI
12 Idaho ID
13 Illinois IL
14 Indiana IN
15 Indiana IND
16 Iowa IA
17 Kansas KS
18 Kentucky KY
19 Louisiana LA
20 Maine ME
21 Maryland MD
22 Massachusetts MA
23 Michigan MI
24 Minnesota MN
25 Mississippi MS
26 Missouri MO
27 Montana MT
28 Nebraska NE
29 Nevada NV
30 New Hampshire NH
31 New Jersey NJ
32 New Mexico NM
33 New York NY
34 North Carolina NC
35 North Dakota ND
36 Ohio OH
37 Oklahoma OK
38 Oregon OR
39 Pennsylvania PA
40 Rhode Island RI
41 South Carolina SC
42 South Dakota SD
43 Tennessee TN
44 Tennessee TENN
45 Texas TX
46 Utah UT
47 Vermont VT
48 Virginia VA
49 Washington WA
50 West Virginia WV
51 Wisconsin WI
52 Wyoming WY
@@ -0,0 +1,31 @@
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
eighteen
nineteen
twenty
twenty one
twenty two
twenty three
twenty four
twenty five
twenty six
twenty seven
twenty eight
twenty nine
thirty
thirty one
1 one
2 two
3 three
4 four
5 five
6 six
7 seven
8 eight
9 nine
10 ten
11 eleven
12 twelve
13 thirteen
14 fourteen
15 fifteen
16 sixteen
17 seventeen
18 eighteen
19 nineteen
20 twenty
21 twenty one
22 twenty two
23 twenty three
24 twenty four
25 twenty five
26 twenty six
27 twenty seven
28 twenty eight
29 twenty nine
30 thirty
31 thirty one
@@ -0,0 +1,12 @@
jan january
feb february
mar march
apr april
jun june
jul july
aug august
sep september
sept september
oct october
nov november
dec december
1 jan january
2 feb february
3 mar march
4 apr april
5 jun june
6 jul july
7 aug august
8 sep september
9 sept september
10 oct october
11 nov november
12 dec december
@@ -0,0 +1,12 @@
january
february
march
april
may
june
july
august
september
october
november
december
1 january
2 february
3 march
4 april
5 may
6 june
7 july
8 august
9 september
10 october
11 november
12 december
@@ -0,0 +1,24 @@
1 january
2 february
3 march
4 april
5 may
6 june
7 july
8 august
9 september
10 october
11 november
12 december
01 january
02 february
03 march
04 april
05 may
06 june
07 july
08 august
09 september
10 october
11 november
12 december
1 1 january
2 2 february
3 3 march
4 4 april
5 5 may
6 6 june
7 7 july
8 8 august
9 9 september
10 10 october
11 11 november
12 12 december
13 01 january
14 02 february
15 03 march
16 04 april
17 05 may
18 06 june
19 07 july
20 08 august
21 09 september
22 10 october
23 11 november
24 12 december
@@ -0,0 +1,16 @@
A. D AD
A.D AD
a. d AD
a.d AD
a. d. AD
a.d. AD
B. C BC
B.C BC
b. c BC
b.c BC
A. D. AD
A.D. AD
B. C. BC
B.C. BC
b. c. BC
b.c. BC
1 A. D AD
2 A.D AD
3 a. d AD
4 a.d AD
5 a. d. AD
6 a.d. AD
7 B. C BC
8 B.C BC
9 b. c BC
10 b.c BC
11 A. D. AD
12 A.D. AD
13 B. C. BC
14 B.C. BC
15 b. c. BC
16 b.c. BC
@@ -0,0 +1,12 @@
.com dot com
.org dot org
.gov dot gov
.uk dot UK
.fr dot FR
.net dot net
.br dot BR
.in dot IN
.ru dot RU
.de dot DE
.it dot IT
.jpg dot jpeg
1 .com dot com
2 .org dot org
3 .gov dot gov
4 .uk dot UK
5 .fr dot FR
6 .net dot net
7 .br dot BR
8 .in dot IN
9 .ru dot RU
10 .de dot DE
11 .it dot IT
12 .jpg dot jpeg
@@ -0,0 +1,21 @@
. dot
- dash
_ underscore
! exclamation mark
# number sign
$ dollar sign
% percent sign
& ampersand
' quote
* asterisk
+ plus
/ slash
= equal sign
? question mark
^ circumflex
` right single quote
{ left brace
| vertical bar
} right brace
~ tilde
, comma
1 . dot
2 - dash
3 _ underscore
4 ! exclamation mark
5 # number sign
6 $ dollar sign
7 % percent sign
8 & ampersand
9 ' quote
10 * asterisk
11 + plus
12 / slash
13 = equal sign
14 ? question mark
15 ^ circumflex
16 ` right single quote
17 { left brace
18 | vertical bar
19 } right brace
20 ~ tilde
21 , comma
@@ -0,0 +1,8 @@
+ plus
- minus
/ divided
÷ divided
: divided
× times
* times
· times
1 + plus
2 - minus
3 / divided
4 ÷ divided
5 : divided
6 × times
7 * times
8 · times
@@ -0,0 +1,127 @@
amu atomic mass unit
bar bar
° degree
º degree
°c degree Celsius
°C degree Celsius
ºc degree Celsius
ºC degree Celsius
℃ degree Celsius
cm2 square centimeter
cm² square centimeter
cm3 cubic centimeter
cm³ cubic centimeter
cm centimeter
cwt hundredweight
db decibel
dm3 cubic decimeter
dm³ cubic decimeter
dm decimeter
ds decisecond
°f degree Fahrenheit
°F degree Fahrenheit
℉ degree Fahrenheit
ft foot
ghz gigahertz
gw gigawatt
gwh gigawatt hour
hz hertz
" inch
kbps kilobit per second
kcal kilo calory
kgf kilogram force
kg kilogram
khz kilohertz
km2 square kilometer
km² square kilometer
km3 cubic kilometer
km³ cubic kilometer
km kilometer
kpa kilopascal
kwh kilowatt hour
kw kilowatt
kW kilowatt
lb pound
lbs pound
m2 square meter
m² square meter
m3 cubic meter
m³ cubic meter
mbps megabit per second
mg milligram
mhz megahertz
mi2 square mile
mi² square mile
mi3 cubic mile
mi³ cubic mile
cu mi cubic mile
mi mile
min minute
ml milliliter
mm2 square millimeter
mm² square millimeter
mol mole
mpa megapascal
mph mile per hour
ng nanogram
nm nanometer
ns nanosecond
oz ounce
pa pascal
% percent
rad radian
rpm revolution per minute
sq ft square foot
sq mi square mile
sv sievert
tb terabyte
tj terajoule
tl teraliter
v volt
yd yard
μg microgram
μm micrometer
μs microsecond
ω ohm
atm ATM
au AU
bq BQ
cc CC
cd CD
da DA
eb EB
ev EV
f F
gb GB
g G
gl GL
gpa GPA
gy GY
ha HA
h H
hl HL
hp GP
hs HS
kb KB
kl KL
kn KN
kt KT
kv KV
lm LM
ma MA
mA MA
mb MB
mc MC
mf MF
m M
mm MM
ms MS
mv MV
mw MW
pb PB
pg PG
ps PS
s S
tb TB
tb YB
zb ZB
Can't render this file because it contains an unexpected character in line 127 and column 6.
@@ -0,0 +1,43 @@
atm atmosphere
bq becquerel
cd candela
da dalton
eb exabyte
f degree Fahrenheit
gb gigabyte
g gram
gl gigaliter
ha hectare
h hour
hl hectoliter
hp horsepower
hp horsepower
kb kilobit
kb kilobyte
ma megaampere
mA megaampere
ma milliampere
mA milliampere
mb megabyte
mc megacoulomb
mf megafarad
m meter
m minute
mm millimeter
mm millimeter
mm millimeter
ms megasecond
ms mega siemens
ms millisecond
mv millivolt
mV millivolt
mw megawatt
mW megawatt
pb petabyte
pg petagram
ps petasecond
s second
tb terabyte
tb terabyte
yb yottabyte
zb zettabyte
1 atm atmosphere
2 bq becquerel
3 cd candela
4 da dalton
5 eb exabyte
6 f degree Fahrenheit
7 gb gigabyte
8 g gram
9 gl gigaliter
10 ha hectare
11 h hour
12 hl hectoliter
13 hp horsepower
14 hp horsepower
15 kb kilobit
16 kb kilobyte
17 ma megaampere
18 mA megaampere
19 ma milliampere
20 mA milliampere
21 mb megabyte
22 mc megacoulomb
23 mf megafarad
24 m meter
25 m minute
26 mm millimeter
27 mm millimeter
28 mm millimeter
29 ms megasecond
30 ms mega siemens
31 ms millisecond
32 mv millivolt
33 mV millivolt
34 mw megawatt
35 mW megawatt
36 pb petabyte
37 pg petagram
38 ps petasecond
39 s second
40 tb terabyte
41 tb terabyte
42 yb yottabyte
43 zb zettabyte
@@ -0,0 +1,39 @@
$ dollar
$ us dollar
US$ us dollar
฿ Thai Baht
£ pound
€ euro
₩ won
nzd new zealand dollar
rs rupee
chf swiss franc
dkk danish kroner
fim finnish markka
aed arab emirates dirham
¥ yen
czk czech koruna
mro mauritanian ouguiya
pkr pakistani rupee
crc costa rican colon
hk$ hong kong dollar
npr nepalese rupee
awg aruban florin
nok norwegian kroner
tzs tanzanian shilling
sek swedish kronor
cyp cypriot pound
r real
sar saudi riyal
cve cape verde escudo
rsd serbian dinar
dm german mark
shp saint helena pounds
php philippine peso
cad canadian dollar
ssp south sudanese pound
scr seychelles rupee
mvr maldivian rufiyaa
DH dirham
Dh dirham
Dhs. dirham
1 $ dollar
2 $ us dollar
3 US$ us dollar
4 ฿ Thai Baht
5 £ pound
6 euro
7 won
8 nzd new zealand dollar
9 rs rupee
10 chf swiss franc
11 dkk danish kroner
12 fim finnish markka
13 aed arab emirates dirham
14 ¥ yen
15 czk czech koruna
16 mro mauritanian ouguiya
17 pkr pakistani rupee
18 crc costa rican colon
19 hk$ hong kong dollar
20 npr nepalese rupee
21 awg aruban florin
22 nok norwegian kroner
23 tzs tanzanian shilling
24 sek swedish kronor
25 cyp cypriot pound
26 r real
27 sar saudi riyal
28 cve cape verde escudo
29 rsd serbian dinar
30 dm german mark
31 shp saint helena pounds
32 php philippine peso
33 cad canadian dollar
34 ssp south sudanese pound
35 scr seychelles rupee
36 mvr maldivian rufiyaa
37 DH dirham
38 Dh dirham
39 Dhs. dirham
@@ -0,0 +1,4 @@
$ cents
US$ cents
€ cents
£ pence
1 $ cents
2 US$ cents
3 cents
4 £ pence
@@ -0,0 +1,3 @@
$ cent
€ cent
£ penny
1 $ cent
2 cent
3 £ penny
@@ -0,0 +1,2 @@
/ea each
/dozen
1 /ea each
2 /dozen
@@ -0,0 +1,9 @@
one 1
two 2
three 3
four 4
five 5
six 6
seven 7
eight 8
nine 9
1 one 1
2 two 2
3 three 3
4 four 4
5 five 5
6 six 6
7 seven 7
8 eight 8
9 nine 9
@@ -0,0 +1,18 @@
¼ 1/4
½ 1/2
¾ 3/4
⅐ 1/7
⅑ 1/9
⅒ 1/10
⅓ 1/3
⅔ 2/3
⅕ 1/5
⅖ 2/5
⅗ 3/5
⅘ 4/5
⅙ 1/6
⅚ 5/6
⅛ 1/8
⅜ 3/8
⅝ 5/8
⅞ 7/8
1 ¼ 1/4
2 ½ 1/2
3 ¾ 3/4
4 1/7
5 1/9
6 1/10
7 1/3
8 2/3
9 1/5
10 2/5
11 3/5
12 4/5
13 1/6
14 5/6
15 1/8
16 3/8
17 5/8
18 7/8
@@ -0,0 +1 @@
hundred
1 hundred
@@ -0,0 +1,10 @@
M million
MLN million
m million
mln million
B billion
b billion
BN billion
bn billion
K thousand
k thousand
1 M million
2 MLN million
3 m million
4 mln million
5 B billion
6 b billion
7 BN billion
8 bn billion
9 K thousand
10 k thousand
@@ -0,0 +1,10 @@
ten 10
eleven 11
twelve 12
thirteen 13
fourteen 14
fifteen 15
sixteen 16
seventeen 17
eighteen 18
nineteen 19
1 ten 10
2 eleven 11
3 twelve 12
4 thirteen 13
5 fourteen 14
6 fifteen 15
7 sixteen 16
8 seventeen 17
9 eighteen 18
10 nineteen 19
@@ -0,0 +1,22 @@
thousand
million
billion
trillion
quadrillion
quintillion
sextillion
septillion
octillion
nonillion
decillion
undecillion
duodecillion
tredecillion
quattuordecillion
quindecillion
sexdecillion
septendecillion
octodecillion
novemdecillion
vigintillion
centillion
1 thousand
2 million
3 billion
4 trillion
5 quadrillion
6 quintillion
7 sextillion
8 septillion
9 octillion
10 nonillion
11 decillion
12 undecillion
13 duodecillion
14 tredecillion
15 quattuordecillion
16 quindecillion
17 sexdecillion
18 septendecillion
19 octodecillion
20 novemdecillion
21 vigintillion
22 centillion
@@ -0,0 +1,8 @@
twenty 2
thirty 3
forty 4
fifty 5
sixty 6
seventy 7
eighty 8
ninety 9
1 twenty 2
2 thirty 3
3 forty 4
4 fifty 5
5 sixty 6
6 seventy 7
7 eighty 8
8 ninety 9
@@ -0,0 +1 @@
zero 0
1 zero 0
@@ -0,0 +1,9 @@
first one
second two
third three
fourth four
fifth five
sixth sixth
seventh seven
eighth eight
ninth nine
1 first one
2 second two
3 third three
4 fourth four
5 fifth five
6 sixth sixth
7 seventh seven
8 eighth eight
9 ninth nine

Some files were not shown because too many files have changed in this diff Show More