Skip to content

Commit 5c26e5d

Browse files
Add Vietnamese TN support for Money and Range semiotic classes (#304)
* Add Vietnamese TN support for Money and Range semiotic classes - Add money.py tagger and verbalizer for Vietnamese currency handling - Add range.py tagger for numerical range processing - Add supporting data files for money (currency, currency_minor, per_unit) - Add quantity abbreviations and time units data - Update existing taggers and verbalizers for integration - Add comprehensive test cases for money and range functionality - Update tokenize_and_classify to include new semiotic classes Signed-off-by: folivoramanh <palasek182@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * modify illogical test cases Signed-off-by: folivoramanh <palasek182@gmail.com> * refractor and simplify word and punctuation to avoid hardcoding Signed-off-by: folivoramanh <palasek182@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refractor code money range Signed-off-by: folivoramanh <palasek182@gmail.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: folivoramanh <palasek182@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 39704ac commit 5c26e5d

File tree

24 files changed

+892
-117
lines changed

24 files changed

+892
-117
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
$ đô la
2+
đô la đô la
3+
ơ rô
4+
¥ yên nhật
5+
won
6+
đồng
7+
đ đồng
8+
đồng đồng
9+
£ bảng anh
10+
rupee
11+
¢ xu
12+
franc pháp
13+
cent xu
14+
cents xu
15+
CHF franc thụy sĩ
16+
FF franc pháp
17+
JPY yên nhật
18+
KRW won hàn quốc
19+
CNY nhân dân tệ
20+
USD đô la
21+
usd đô la
22+
SGD đô la singapore
23+
MYR ringgit malaysia
24+
THB baht thái lan
25+
IDR rupiah indonesia
26+
PHP peso philippines
27+
AUD đô la úc
28+
CAD đô la canada
29+
NZD đô la new zealand
30+
HKD đô la hồng kông
31+
TWD đô la đài loan
32+
ff franc pháp
33+
chf franc thụy sĩ
34+
jpy yên nhật
35+
krw won hàn quốc
36+
cny nhân dân tệ
37+
usd đô la
38+
vnd đồng
39+
vnđ đồng
40+
sgd đô la singapore
41+
myr ringgit malaysia
42+
thb baht thái lan
43+
idr rupiah indonesia
44+
php peso philippines
45+
aud đô la úc
46+
cad đô la canada
47+
nzd đô la new zealand
48+
hkd đô la hồng kông
49+
twd đô la đài loan
50+
VND đồng
51+
VNĐ đồng
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$ xu
2+
xu
3+
£ penny
4+
¢ xu
5+
cent xu
6+
cents xu
7+
pence penny
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/giờ trên giờ
2+
/g trên giờ
3+
/h trên giờ
4+
/ngày trên ngày
5+
/d trên ngày
6+
/tuần trên tuần
7+
/tháng trên tháng
8+
/năm trên năm
9+
/phút trên phút
10+
/p trên phút
11+
/giây trên giây
12+
/s trên giây
13+
/lần một lần
14+
/cái một cái
15+
/chiếc một chiếc
16+
/kg một ki lô gam
17+
/g một gam
18+
/cm một xăng ti mét
19+
/m một mét
20+
/km một ki lô mét
21+
/cm² một xăng ti mét vuông
22+
/m² một mét vuông
23+
/m2 một mét vuông
24+
/m³ một mét khối
25+
/m3 một mét khối
26+
/l một lít
27+
/ml một mi li lít
28+
/người một người
29+
/chỗ một chỗ
30+
/bài một bài
31+
/trang một trang
32+
/từ một từ
33+
/đồng một đồng
34+
/KB một kilobyte
35+
/GB một gigabyte
36+
/MB một megabyte
37+
/TB một terabyte
38+
/tấn một tấn
39+
/đêm một đêm
40+
/buổi một buổi
41+
/ca một ca
42+
/dự án một dự án
43+
/lớp một lớp
44+
/khóa một khóa
45+
/suất một suất
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
k nghìn
2+
K nghìn
3+
tr triệu
4+
TR triệu
5+
Tr triệu
6+
t tỷ
7+
T tỷ
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
h giờ
2+
g giờ
3+
p phút
4+
s giây

nemo_text_processing/text_normalization/vi/graph_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
NEMO_GRAPH = pynini.union(NEMO_ALNUM, NEMO_PUNCT).optimize()
4444

4545
NEMO_SIGMA = pynini.closure(NEMO_CHAR)
46+
NEMO_COMMA = ","
47+
NEMO_COMMA_VI = "phẩy"
4648

4749
delete_space = pynutil.delete(pynini.closure(NEMO_WHITE_SPACE))
4850
delete_zero_or_one_space = pynutil.delete(pynini.closure(NEMO_WHITE_SPACE, 0, 1))

nemo_text_processing/text_normalization/vi/taggers/cardinal.py

Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,83 +56,132 @@ def __init__(self, deterministic: bool = True):
5656
+ pynini.union(self.special_digits, pynini.union("2", "3", "6", "7", "8", "9") @ self.digit),
5757
)
5858

59+
hundred_word = self.magnitudes["hundred"]
60+
linh_word = self.magnitudes["linh"]
61+
5962
self.hundreds_pattern = pynini.union(
60-
self.single_digit + insert_space + pynutil.insert(self.magnitudes["hundred"]) + pynutil.delete("00"),
63+
# X00: một trăm, hai trăm, etc.
64+
self.single_digit + insert_space + pynutil.insert(hundred_word) + pynutil.delete("00"),
65+
# X0Y: một trăm linh một, hai trăm linh năm, etc.
6166
self.single_digit
6267
+ insert_space
63-
+ pynutil.insert(self.magnitudes["hundred"])
68+
+ pynutil.insert(hundred_word)
6469
+ pynutil.delete("0")
6570
+ insert_space
66-
+ pynutil.insert(self.magnitudes["linh"])
71+
+ pynutil.insert(linh_word)
6772
+ insert_space
6873
+ self.linh_digits,
69-
self.single_digit
70-
+ insert_space
71-
+ pynutil.insert(self.magnitudes["hundred"])
72-
+ insert_space
73-
+ self.two_digit,
74+
# XYZ: một trăm hai mười ba, etc.
75+
self.single_digit + insert_space + pynutil.insert(hundred_word) + insert_space + self.two_digit,
7476
)
7577

7678
self.hundreds = pynini.closure(NEMO_DIGIT, 3, 3) @ self.hundreds_pattern
7779

80+
# Build magnitude patterns (thousands, millions, billions)
7881
self.thousand = self._build_magnitude_pattern("thousand", 4, 6, 3)
7982
self.million = self._build_magnitude_pattern("million", 7, 9, 6, self.thousand)
8083
self.billion = self._build_magnitude_pattern("billion", 10, 12, 9, self.million)
8184

85+
# Handle dot-separated numbers: 1.000, 1.000.000, etc.
86+
delete_dot = pynutil.delete(".")
87+
dot_patterns = []
88+
89+
# Thousand with dots: 1.000
90+
dot_patterns.append(
91+
pynini.compose(
92+
(NEMO_DIGIT - "0") + pynini.closure(NEMO_DIGIT, 0, 2) + delete_dot + NEMO_DIGIT**3, self.thousand
93+
)
94+
)
95+
96+
# Million with dots: 1.000.000
97+
dot_patterns.append(
98+
pynini.compose(
99+
(NEMO_DIGIT - "0")
100+
+ pynini.closure(NEMO_DIGIT, 0, 2)
101+
+ delete_dot
102+
+ NEMO_DIGIT**3
103+
+ delete_dot
104+
+ NEMO_DIGIT**3,
105+
self.million,
106+
)
107+
)
108+
109+
# Billion with dots: 1.000.000.000
110+
dot_patterns.append(
111+
pynini.compose(
112+
(NEMO_DIGIT - "0")
113+
+ pynini.closure(NEMO_DIGIT, 0, 2)
114+
+ delete_dot
115+
+ NEMO_DIGIT**3
116+
+ delete_dot
117+
+ NEMO_DIGIT**3
118+
+ delete_dot
119+
+ NEMO_DIGIT**3,
120+
self.billion,
121+
)
122+
)
123+
82124
self.graph = pynini.union(
83-
self.billion, self.million, self.thousand, self.hundreds, self.two_digit, self.single_digit, self.zero
125+
self.billion,
126+
self.million,
127+
self.thousand,
128+
self.hundreds,
129+
self.two_digit,
130+
self.single_digit,
131+
self.zero,
132+
*dot_patterns,
84133
).optimize()
85134

86135
self.single_digits_graph = self.single_digit | self.zero
87136
self.graph_with_and = self.graph
88137

89-
self.fst = self.add_tokens(
90-
pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1)
91-
+ pynutil.insert("integer: \"")
92-
+ self.graph
93-
+ pynutil.insert("\"")
94-
).optimize()
138+
# Build final FST with optional negative and integer wrapper
139+
negative = pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1)
140+
final_graph = negative + pynutil.insert("integer: \"") + self.graph + pynutil.insert("\"")
141+
self.fst = self.add_tokens(final_graph).optimize()
95142

96143
def _build_magnitude_pattern(self, name, min_digits, max_digits, zero_count, prev_pattern=None):
97144
magnitude_word = self.magnitudes[name]
145+
linh_word = self.magnitudes["linh"]
98146

99147
patterns = []
100148
for digits in range(min_digits, max_digits + 1):
101149
leading_digits = digits - zero_count
102-
leading_fst = {1: self.single_digit, 2: self.two_digit, 3: self.hundreds_pattern}.get(
103-
leading_digits, self.hundreds_pattern
104-
)
150+
151+
# Choose leading pattern based on digit count
152+
if leading_digits == 1:
153+
leading_fst = self.single_digit
154+
elif leading_digits == 2:
155+
leading_fst = self.two_digit
156+
else: # 3 digits
157+
leading_fst = self.hundreds_pattern
105158

106159
prefix = leading_fst + insert_space + pynutil.insert(magnitude_word)
160+
digit_patterns = []
107161

108-
digit_patterns = [prefix + pynutil.delete("0" * zero_count)]
162+
# Case 1: All trailing zeros (e.g., 1000 -> một nghìn)
163+
digit_patterns.append(prefix + pynutil.delete("0" * zero_count))
109164

165+
# Case 2: Has lower magnitude (e.g., 1001000 -> một triệu một nghìn)
110166
if prev_pattern:
111167
digit_patterns.append(prefix + insert_space + prev_pattern)
112168

113-
trailing_patterns = []
169+
# Case 3: Trailing patterns with linh (e.g., 1001 -> một nghìn linh một)
114170
for trailing_zeros in range(zero_count):
115171
remaining_digits = zero_count - trailing_zeros
172+
trailing_prefix = prefix + pynutil.delete("0" * trailing_zeros)
173+
116174
if remaining_digits == 1:
117-
trailing_patterns.append(
118-
prefix
119-
+ pynutil.delete("0" * trailing_zeros)
120-
+ insert_space
121-
+ pynutil.insert(self.magnitudes["linh"])
122-
+ insert_space
123-
+ self.linh_digits
175+
digit_patterns.append(
176+
trailing_prefix + insert_space + pynutil.insert(linh_word) + insert_space + self.linh_digits
124177
)
125178
elif remaining_digits == 2:
126-
trailing_patterns.append(
127-
prefix + pynutil.delete("0" * trailing_zeros) + insert_space + self.two_digit
128-
)
179+
digit_patterns.append(trailing_prefix + insert_space + self.two_digit)
129180
elif remaining_digits == 3:
130-
trailing_patterns.append(
131-
prefix + pynutil.delete("0" * trailing_zeros) + insert_space + self.hundreds_pattern
132-
)
133-
digit_patterns.extend(trailing_patterns)
181+
digit_patterns.append(trailing_prefix + insert_space + self.hundreds_pattern)
134182

135183
if name == "million" and digits == 7:
184+
# Handle patterns like 1001001 -> một triệu một nghìn linh một
136185
digit_patterns.extend(
137186
[
138187
prefix
@@ -143,7 +192,7 @@ def _build_magnitude_pattern(self, name, min_digits, max_digits, zero_count, pre
143192
+ pynutil.insert(self.magnitudes["thousand"])
144193
+ pynutil.delete("00")
145194
+ insert_space
146-
+ pynutil.insert(self.magnitudes["linh"])
195+
+ pynutil.insert(linh_word)
147196
+ insert_space
148197
+ self.linh_digits,
149198
prefix
@@ -154,12 +203,13 @@ def _build_magnitude_pattern(self, name, min_digits, max_digits, zero_count, pre
154203
+ pynutil.insert(self.magnitudes["thousand"])
155204
+ pynutil.delete("00")
156205
+ insert_space
157-
+ pynutil.insert(self.magnitudes["linh"])
206+
+ pynutil.insert(linh_word)
158207
+ insert_space
159208
+ self.linh_digits,
160209
]
161210
)
162211
elif name == "billion" and digits == 10:
212+
# Handle patterns like 1001001001
163213
digit_patterns.append(
164214
prefix
165215
+ pynutil.delete("00")

0 commit comments

Comments
 (0)