From cce887985b9662fb729279ae64ff76b4bb7c342e Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Thu, 14 May 2026 01:59:01 +0530 Subject: [PATCH 01/10] Hi TN: fraction class improvements with lexical mappings for special cases (#420) * Fix: fraction class improvements with lexical mappings Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * move common fraction mappings to TSV and add ASCII digit support Signed-off-by: Shreyas Pawar * Jenkins date conflict resolved Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/fraction/common_fractions.tsv | 10 ++++++++ .../text_normalization/hi/taggers/fraction.py | 25 ++++++++++++++----- .../hi/verbalizers/fraction.py | 4 +-- .../test_cases_fraction.txt | 6 ++++- 5 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/fraction/common_fractions.tsv diff --git a/Jenkinsfile b/Jenkinsfile index 24ac047eb..d9c3a5984 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/05-13-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/fraction/common_fractions.tsv b/nemo_text_processing/text_normalization/hi/data/fraction/common_fractions.tsv new file mode 100644 index 000000000..5e44cb502 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/fraction/common_fractions.tsv @@ -0,0 +1,10 @@ +१/२ आधा +१/३ तिहाई +२/३ दो तिहाई +१/४ चौथाई +३/४ तीन चौथाई +1/2 आधा +1/3 तिहाई +2/3 दो तिहाई +1/4 चौथाई +3/4 तीन चौथाई \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/fraction.py b/nemo_text_processing/text_normalization/hi/taggers/fraction.py index b5528deba..8b72b25b2 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/fraction.py +++ b/nemo_text_processing/text_normalization/hi/taggers/fraction.py @@ -26,18 +26,18 @@ ) from nemo_text_processing.text_normalization.hi.utils import get_abs_path -HI_ONE_HALF = "१/२" # 1/2 -HI_ONE_QUARTER = "१/४" # 1/4 -HI_THREE_QUARTERS = "३/४" # 3/4 +HI_ONE_HALF = "१/२" +HI_ONE_QUARTER = "१/४" +HI_THREE_QUARTERS = "३/४" class FractionFst(GraphFst): """ Finite state transducer for classifying fraction "२३ ४/६" -> - fraction { integer: "तेईस" numerator: "चार" denominator: "छः"} + fraction { integer: "तेईस" numerator: "चार" denominator: "छह"} ४/६" -> - fraction { numerator: "चार" denominator: "छः"} + fraction { numerator: "चार" denominator: "छह"} Args: @@ -54,13 +54,16 @@ def __init__(self, cardinal, deterministic: bool = True): self.optional_graph_negative = pynini.closure( pynutil.insert("negative: ") + pynini.cross("-", "\"true\"") + pynutil.insert(NEMO_SPACE), 0, 1 ) + self.integer = pynutil.insert("integer_part: \"") + cardinal_graph + pynutil.insert("\"") + self.numerator = ( pynutil.insert("numerator: \"") + cardinal_graph + pynini.cross(pynini.union("/", NEMO_SPACE + "/" + NEMO_SPACE), "\"") + pynutil.insert(NEMO_SPACE) ) + self.denominator = pynutil.insert("denominator: \"") + cardinal_graph + pynutil.insert("\"") dedh_dhai_graph = pynini.string_map( @@ -77,6 +80,15 @@ def __init__(self, cardinal, deterministic: bool = True): paune_numbers = paune + pynini.cross(NEMO_SPACE + HI_THREE_QUARTERS, "") paune_graph = pynutil.insert(HI_PAUNE) + pynutil.insert(NEMO_SPACE) + paune_numbers + common_fraction_map = pynini.string_file(get_abs_path("data/fraction/common_fractions.tsv")) + + graph_common_fraction = ( + pynutil.insert("morphosyntactic_features: \"") + + common_fraction_map + + pynutil.insert("\"") + + pynutil.insert(NEMO_SPACE) + ) + graph_dedh_dhai = ( pynutil.insert("morphosyntactic_features: \"") + dedh_dhai_graph @@ -114,10 +126,11 @@ def __init__(self, cardinal, deterministic: bool = True): weighted_graph = ( final_graph + | pynutil.add_weight(graph_common_fraction, -0.3) | pynutil.add_weight(graph_dedh_dhai, -0.2) + | pynutil.add_weight(graph_paune, -0.2) | pynutil.add_weight(graph_savva, -0.1) | pynutil.add_weight(graph_sadhe, -0.1) - | pynutil.add_weight(graph_paune, -0.2) ) self.graph = weighted_graph diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/fraction.py b/nemo_text_processing/text_normalization/hi/verbalizers/fraction.py index a07c41eae..66d944ea7 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/fraction.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/fraction.py @@ -21,8 +21,8 @@ class FractionFst(GraphFst): """ Finite state transducer for verbalizing fraction - e.g. fraction { integer: "तेईस" numerator: "चार" denominator: "छः" }-> तेईस चार बटा छः - e.g. fraction { numerator: "चार" denominator: "छः" } -> चार बटा छः + e.g. fraction { integer: "तेईस" numerator: "चार" denominator: "छह" }-> तेईस और चार बटा छह + e.g. fraction { numerator: "चार" denominator: "छह" } -> चार बटा छह Args: diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_fraction.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_fraction.txt index 4184ae9ee..6778978b7 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_fraction.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_fraction.txt @@ -20,4 +20,8 @@ १०००००००००००००००/८~एक पद्म बटा आठ 100000000000000000/412~एक शंख बटा चार सौ बारह २ २/७~दो और दो बटा सात -120 75/90~एक सौ बीस और पचहत्तर बटा नब्बे \ No newline at end of file +120 75/90~एक सौ बीस और पचहत्तर बटा नब्बे +१/२~आधा +१/३~तिहाई +1/4~चौथाई +3/4~तीन चौथाई \ No newline at end of file From 0daa8a0d74914080e95fd0b4ea95229a14c36d5e Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Thu, 28 May 2026 19:19:15 +0530 Subject: [PATCH 02/10] Hi TN date class accuracy improvement (#418) * date class accuracy improvement Signed-off-by: Shreyas Pawar * Jenkins file date update for Hi TN Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address Reviewer feedback regarding date tagger, tsv files and inclusion of more test cases Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * hi-tn-date: minor formatting fix Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * date tagger modification according to feedback removed support for d-mm-yyyy, dd-m-yyyy and mm-yyyy Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * replace open()+union() with string_map for suffix and prefix unions Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../text_normalization/hi/data/date/days.tsv | 66 +++---- .../hi/data/date/months.tsv | 28 +-- .../hi/data/date/prefixes.tsv | 7 +- .../text_normalization/hi/taggers/date.py | 173 ++++++++++++++---- .../hi/taggers/tokenize_and_classify.py | 2 +- .../test_cases_date.txt | 25 ++- 6 files changed, 205 insertions(+), 96 deletions(-) diff --git a/nemo_text_processing/text_normalization/hi/data/date/days.tsv b/nemo_text_processing/text_normalization/hi/data/date/days.tsv index 633e2aec0..6df0fa3d4 100644 --- a/nemo_text_processing/text_normalization/hi/data/date/days.tsv +++ b/nemo_text_processing/text_normalization/hi/data/date/days.tsv @@ -1,40 +1,9 @@ -०१ एक -०२ दो -०३ तीन -०४ चार -०५ पाँच -०६ छः -०७ सात -०८ आठ -०९ नौ -१० दस -११ ग्यारह -१२ बारह -१३ तेरह -१४ चौदह -१५ पंद्रह -१६ सोलह -१७ सत्रह -१८ अठारह -१९ उन्नीस -२० बीस -२१ इक्कीस -२२ बाईस -२३ तेईस -२४ चौबीस -२५ पच्चीस -२६ छब्बीस -२७ सत्ताईस -२८ अट्ठाईस -२९ उनतीस -३० तीस -३१ इकतीस 01 एक 02 दो 03 तीन 04 चार 05 पाँच -06 छः +06 छह 07 सात 08 आठ 09 नौ @@ -59,4 +28,35 @@ 28 अट्ठाईस 29 उनतीस 30 तीस -31 इकतीस \ No newline at end of file +31 इकतीस +०१ एक +०२ दो +०३ तीन +०४ चार +०५ पाँच +०६ छह +०७ सात +०८ आठ +०९ नौ +१० दस +११ ग्यारह +१२ बारह +१३ तेरह +१४ चौदह +१५ पंद्रह +१६ सोलह +१७ सत्रह +१८ अठारह +१९ उन्नीस +२० बीस +२१ इक्कीस +२२ बाईस +२३ तेईस +२४ चौबीस +२५ पच्चीस +२६ छब्बीस +२७ सत्ताईस +२८ अट्ठाईस +२९ उनतीस +३० तीस +३१ इकतीस \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/date/months.tsv b/nemo_text_processing/text_normalization/hi/data/date/months.tsv index af770dafc..3667f07cf 100644 --- a/nemo_text_processing/text_normalization/hi/data/date/months.tsv +++ b/nemo_text_processing/text_normalization/hi/data/date/months.tsv @@ -1,17 +1,5 @@ -०१ जनवरी -०२ फ़रवरी -०३ मार्च -०४ अप्रैल -०५ मई -०६ जून -०७ जुलाई -०८ अगस्त -०९ सितंबर -१० अक्टूबर -११ नवंबर -१२ दिसंबर 01 जनवरी -02 फ़रवरी +02 फरवरी 03 मार्च 04 अप्रैल 05 मई @@ -21,4 +9,16 @@ 09 सितंबर 10 अक्टूबर 11 नवंबर -12 दिसंबर \ No newline at end of file +12 दिसंबर +०१ जनवरी +०२ फरवरी +०३ मार्च +०४ अप्रैल +०५ मई +०६ जून +०७ जुलाई +०८ अगस्त +०९ सितंबर +१० अक्टूबर +११ नवंबर +१२ दिसंबर \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/date/prefixes.tsv b/nemo_text_processing/text_normalization/hi/data/date/prefixes.tsv index d4c1ca0b1..6166ec327 100644 --- a/nemo_text_processing/text_normalization/hi/data/date/prefixes.tsv +++ b/nemo_text_processing/text_normalization/hi/data/date/prefixes.tsv @@ -1,3 +1,4 @@ -सन् -सन -साल \ No newline at end of file +सन् +सन +साल +दशक \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/date.py b/nemo_text_processing/text_normalization/hi/taggers/date.py index da917f3de..6f43e5d6e 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/date.py +++ b/nemo_text_processing/text_normalization/hi/taggers/date.py @@ -33,23 +33,31 @@ teens_ties = pynini.union(teens_ties_hi, teens_ties_en) teens_and_ties = pynutil.add_weight(teens_ties, -0.1) -# Read suffixes from file into a list with open(get_abs_path("data/date/suffixes.tsv"), "r", encoding="utf-8") as f: - suffixes_list = f.read().splitlines() + suffix_union = pynini.string_map([line.rstrip("\n") for line in f if line.strip()]) + with open(get_abs_path("data/date/prefixes.tsv"), "r", encoding="utf-8") as f: - prefixes_list = f.read().splitlines() + prefix_union = pynini.string_map([line.rstrip("\n") for line in f if line.strip()]) + +verbalized_hundreds = teens_ties_hi.project("output") +verbalized_unit = pynini.union(verbalized_hundreds, digit.project("output")) + +verbalized_year_sou = ( + verbalized_hundreds + pynini.accep(" सौ") + pynini.closure(pynini.accep(" ") + verbalized_unit, 0, 1) +) -# Create union of suffixes and prefixes -suffix_union = pynini.union(*suffixes_list) -prefix_union = pynini.union(*prefixes_list) +pad_latin = pynini.union(*[pynini.cross(str(i), f"0{i}") for i in range(1, 10)]) +pad_devanagari = pynini.union(*[pynini.cross(d, f"०{d}") for d in "१२३४५६७८९"]) class DateFst(GraphFst): """ Finite state transducer for classifying date, e.g. "०१-०४-२०२४" -> date { day: "एक" month: "अप्रैल" year: "दो हज़ार चौबीस" } - "०४-०१-२०२४" -> date { month: "अप्रैल" day: "एक" year: "दो हज़ार चौबीस" } - + "६ मार्च, २०१०" -> date { day: "छह" month: "मार्च" year: "दो हज़ार दस" } + "३१ मई, १९९० ई." -> date { day: "इकतीस" month: "मई" year: "उन्नीस सौ नब्बे" era: "ईसवी" } + "उन्नीस सौ बीस में" -> date { era: "उन्नीस सौ बीस में" } + "02-07-1970" -> date { day: "दो" month: "जुलाई" year: "उन्नीस सौ सत्तर" } Args: cardinal: cardinal GraphFst @@ -68,52 +76,137 @@ def __init__(self, cardinal: GraphFst): ) cardinal_graph = pynini.union( - digit, teens_and_ties, cardinal.graph_hundreds, graph_year_thousands, graph_year_hundreds_as_thousands + digit, + teens_and_ties, + cardinal.graph_hundreds, + graph_year_thousands, + graph_year_hundreds_as_thousands, ) graph_year = pynini.union(graph_year_thousands, graph_year_hundreds_as_thousands) + graph_year_era = pynini.union( + graph_year_thousands, + graph_year_hundreds_as_thousands, + cardinal.graph_hundreds, + ) + delete_dash = pynutil.delete("-") - delete_slash = pynutil.delete("/") + delete_comma = pynutil.delete(",") + delete_space = pynutil.delete(" ") + delete_optional_space = pynini.closure(pynutil.delete(" "), 0, 1) + delete_comma_sep = delete_comma + delete_optional_space + + day_num_padded = pynini.union( + days, + teens_and_ties, + ) - days_graph = pynutil.insert("day: \"") + days + pynutil.insert("\"") + insert_space + day_num_bare = pynini.union( + pynini.compose(pad_latin, days), + pynini.compose(pad_devanagari, days), + ) - months_graph = pynutil.insert("month: \"") + months + pynutil.insert("\"") + insert_space + days_graph_padded = pynutil.insert("day: \"") + day_num_padded + pynutil.insert("\"") + insert_space + days_graph_bare = pynutil.insert("day: \"") + day_num_bare + pynutil.insert("\"") + insert_space - years_graph = pynutil.insert("year: \"") + graph_year + pynutil.insert("\"") + insert_space + month_name_acceptor = pynini.project(months, "output") + + months_numeric_padded = months + + months_numeric_bare = pynini.union( + pynini.compose(pad_latin, months), + pynini.compose(pad_devanagari, months), + ) - graph_dd_mm = days_graph + delete_dash + months_graph + months_graph_numeric_padded = ( + pynutil.insert("month: \"") + months_numeric_padded + pynutil.insert("\"") + insert_space + ) + + months_fst_padded = pynini.union(months_numeric_padded, month_name_acceptor) + months_graph_padded = pynutil.insert("month: \"") + months_fst_padded + pynutil.insert("\"") + insert_space - graph_mm_dd = months_graph + delete_dash + days_graph + months_fst_bare = pynini.union(months_numeric_bare, month_name_acceptor) + months_graph_bare = pynutil.insert("month: \"") + months_fst_bare + pynutil.insert("\"") + insert_space - graph_mm_dd += pynutil.insert(" preserve_order: true ") + month_name_graph = pynutil.insert("month: \"") + month_name_acceptor + pynutil.insert("\"") + insert_space + + years_graph = pynutil.insert("year: \"") + graph_year + pynutil.insert("\"") + insert_space - # Graph for era era_graph = pynutil.insert("era: \"") + year_suffix + pynutil.insert("\"") + insert_space range_graph = pynini.cross("-", "से") - # Graph for year century_number = pynini.compose(pynini.closure(NEMO_ALL_DIGIT, 1), cardinal_graph) + pynini.accep("वीं") century_text = pynutil.insert("era: \"") + century_number + pynutil.insert("\"") + insert_space - # Updated logic to use suffix_union year_number = graph_year + suffix_union year_text = pynutil.insert("era: \"") + year_number + pynutil.insert("\"") + insert_space - # Updated logic to use prefix_union - year_prefix = pynutil.insert("era: \"") + prefix_union + insert_space + graph_year + pynutil.insert("\"") + year_prefix = pynutil.insert("era: \"") + prefix_union + pynini.accep(" ") + graph_year + pynutil.insert("\"") - delete_separator = pynini.union(delete_dash, delete_slash) - graph_dd_mm_yyyy = days_graph + delete_separator + months_graph + delete_separator + years_graph + year_prefix_suffix = ( + pynutil.insert("era: \"") + + prefix_union + + pynini.accep(" ") + + graph_year + + suffix_union + + pynutil.insert("\"") + ) - graph_mm_dd_yyyy = months_graph + delete_separator + days_graph + delete_separator + years_graph + graph_verbalized_year_suffix = ( + pynutil.insert("era: \"") + verbalized_year_sou + suffix_union + pynutil.insert("\"") + insert_space + ) - graph_mm_dd_yyyy += pynutil.insert(" preserve_order: true ") + graph_verbalized_year_bare = ( + pynutil.insert("era: \"") + verbalized_year_sou + pynutil.insert("\"") + insert_space + ) - graph_mm_yyyy = months_graph + delete_dash + insert_space + years_graph + graph_verbalized_year_prefix = ( + pynutil.insert("era: \"") + prefix_union + pynini.accep(" ") + verbalized_year_sou + pynutil.insert("\"") + ) - graph_year_suffix = era_graph + graph_verbalized_year_prefix_suffix = ( + pynutil.insert("era: \"") + + prefix_union + + pynini.accep(" ") + + verbalized_year_sou + + suffix_union + + pynutil.insert("\"") + ) + + graph_dd_mm = days_graph_padded + delete_dash + months_graph_padded + + graph_d_m = days_graph_bare + delete_dash + months_graph_bare + + graph_dd_mm_yyyy = days_graph_padded + delete_dash + months_graph_padded + delete_dash + years_graph + + graph_d_m_yyyy = days_graph_bare + delete_dash + months_graph_bare + delete_dash + years_graph + + graph_dd_month = days_graph_padded + delete_space + months_graph_numeric_padded + + graph_dd_month_comma_yyyy = ( + days_graph_padded + delete_space + months_graph_padded + delete_comma_sep + years_graph + ) + + graph_dd_month_comma_yyyy_era = ( + days_graph_padded + delete_space + months_graph_padded + delete_comma_sep + years_graph + era_graph + ) + + graph_month_comma_yyyy = months_graph_padded + delete_comma_sep + years_graph + + graph_month_comma_yyyy_era = months_graph_padded + delete_comma_sep + years_graph + era_graph + + graph_month_name_yyyy = month_name_graph + delete_space + years_graph + + graph_year_era_only = ( + pynutil.insert("era: \"") + + graph_year_era + + insert_space + + year_suffix + + pynutil.insert("\"") + + insert_space + ) graph_range = ( pynutil.insert("era: \"") @@ -126,21 +219,31 @@ def __init__(self, cardinal: GraphFst): + pynutil.insert(" preserve_order: true ") ) - # default assume dd_mm_yyyy + graph_year_suffix = era_graph final_graph = ( - pynutil.add_weight(graph_dd_mm, -0.001) - | graph_mm_dd + pynutil.add_weight(graph_dd_month_comma_yyyy_era, -0.003) + | pynutil.add_weight(graph_month_comma_yyyy_era, -0.003) | pynutil.add_weight(graph_dd_mm_yyyy, -0.001) - | graph_mm_dd_yyyy - | pynutil.add_weight(graph_mm_yyyy, -0.2) - | pynutil.add_weight(graph_year_suffix, -0.001) + | pynutil.add_weight(graph_d_m_yyyy, -0.001) + | pynutil.add_weight(graph_dd_month_comma_yyyy, -0.001) + | pynutil.add_weight(graph_dd_mm, -0.001) + | pynutil.add_weight(graph_d_m, -0.001) + | pynutil.add_weight(graph_dd_month, -0.001) + | pynutil.add_weight(graph_month_name_yyyy, -0.2) + | pynutil.add_weight(graph_month_comma_yyyy, -0.2) + | pynutil.add_weight(graph_year_era_only, -0.005) | pynutil.add_weight(graph_range, -0.005) + | pynutil.add_weight(graph_year_suffix, -0.001) | pynutil.add_weight(century_text, -0.001) - | pynutil.add_weight(year_text, -0.001) + | pynutil.add_weight(graph_verbalized_year_prefix_suffix, -0.012) + | pynutil.add_weight(graph_verbalized_year_prefix, -0.011) + | pynutil.add_weight(graph_verbalized_year_suffix, -0.010) + | pynutil.add_weight(graph_verbalized_year_bare, -0.009) + | pynutil.add_weight(year_prefix_suffix, -0.010) | pynutil.add_weight(year_prefix, -0.009) + | pynutil.add_weight(year_text, -0.001) ) self.final_graph = final_graph.optimize() - self.fst = self.add_tokens(self.final_graph) diff --git a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py index 3e1ded4b1..88cb04727 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py +++ b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py @@ -121,7 +121,7 @@ def __init__( pynutil.add_weight(whitelist_graph, 1.01) | pynutil.add_weight(cardinal_graph, 1.1) | pynutil.add_weight(decimal_graph, 1.1) - | pynutil.add_weight(fraction_graph, 1.1) + | pynutil.add_weight(fraction_graph, 1.05) | pynutil.add_weight(date_graph, 1.1) | pynutil.add_weight(time_graph, 1.1) | pynutil.add_weight(measure_graph, 1.1) diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_date.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_date.txt index 86f1f6678..ac12e6af6 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_date.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_date.txt @@ -1,20 +1,19 @@ -06-05~छः मई +06-05~छह मई ३१-०६~इकतीस जून 02-01~दो जनवरी ०४-०१~चार जनवरी -01-10~एक अक्टूबर +01-10~एक अक्टूबर १२-०७~बारह जुलाई -02-27~फ़रवरी सत्ताईस -०४-०३~चार मार्च +०४-०३~चार मार्च 25-03-2020~पच्चीस मार्च दो हज़ार बीस ३०-०५-२०७०~तीस मई दो हज़ार सत्तर -12-07-1970~बारह जुलाई उन्नीस सौ सत्तर ०९-१२-२१०१~नौ दिसंबर इक्कीस सौ एक 23-08-2024~तेईस अगस्त दो हज़ार चौबीस -१०-२९-२०००~अक्टूबर उनतीस दो हज़ार -11-14-1100~नवंबर चौदह ग्यारह सौ -०३-२०१०~मार्च दो हज़ार दस -11-2024~नवंबर दो हज़ार चौबीस +३ मार्च~तीन मार्च +६ मार्च, २०१०~छह मार्च दो हज़ार दस +३१ मई, १९९० ई.~इकतीस मई उन्नीस सौ नब्बे ईसवी +मार्च, २०२४~मार्च दो हज़ार चौबीस +जनवरी, १९९० ई.~जनवरी उन्नीस सौ नब्बे ईसवी २०७०~दो हज़ार सत्तर 2024~दो हज़ार चौबीस १२० ई. पू.~एक सौ बीस ईसा पूर्व @@ -31,4 +30,10 @@ सन 1999~सन उन्नीस सौ निन्यानबे सन् १९२०~सन् उन्नीस सौ बीस साल 1971~साल उन्नीस सौ इकहत्तर -१९२०-२६ तक~उन्नीस सौ बीस से छब्बीस तक \ No newline at end of file +सन 1999 में~सन उन्नीस सौ निन्यानबे में +सन् उन्नीस सौ बीस~सन् उन्नीस सौ बीस +सन उन्नीस सौ बीस में~सन उन्नीस सौ बीस में +१९२०-२६ तक~उन्नीस सौ बीस से छब्बीस तक +2-7-1970~दो जुलाई उन्नीस सौ सत्तर +02-07-1970~दो जुलाई उन्नीस सौ सत्तर +12-07-1970~बारह जुलाई उन्नीस सौ सत्तर \ No newline at end of file From 42bf4aa320610fb6cc0c4a10abfd18c30c6a2ab2 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Tue, 2 Jun 2026 19:46:58 +0530 Subject: [PATCH 03/10] Hi TN money class bug fix (#424) * Hi TN money class bug fix Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added test cases with decimal portion having more than two digits for Indian Currency Signed-off-by: Shreyas Pawar --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/money/currency_singular.tsv | 9 ++ .../hi/data/money/major_minor_currencies.tsv | 3 +- .../text_normalization/hi/taggers/money.py | 122 +++++++++++++++--- .../hi/verbalizers/money.py | 98 +++++++------- .../test_cases_money.txt | 10 ++ 6 files changed, 177 insertions(+), 67 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/money/currency_singular.tsv diff --git a/Jenkinsfile b/Jenkinsfile index d9c3a5984..8b0511d06 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/05-13-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/05-29-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/money/currency_singular.tsv b/nemo_text_processing/text_normalization/hi/data/money/currency_singular.tsv new file mode 100644 index 000000000..af8d793f2 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/money/currency_singular.tsv @@ -0,0 +1,9 @@ +₹ रुपया +£ पाउंड +₩ वॉन +$ डॉलर +₺ लीरा +৳ टका +¥ येन +₦ नाइरा +€ यूरो \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/money/major_minor_currencies.tsv b/nemo_text_processing/text_normalization/hi/data/money/major_minor_currencies.tsv index cf62891d1..a9186acc3 100644 --- a/nemo_text_processing/text_normalization/hi/data/money/major_minor_currencies.tsv +++ b/nemo_text_processing/text_normalization/hi/data/money/major_minor_currencies.tsv @@ -1,4 +1,5 @@ रुपए पैसे +रुपया पैसे पाउंड पेंस वॉन जिओन डॉलर सेंट @@ -6,4 +7,4 @@ टका पैसे येन सेन नाइरा कोबो -यूरो सेंट +यूरो सेंट \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/money.py b/nemo_text_processing/text_normalization/hi/taggers/money.py index 01e46352f..16f389ae7 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/money.py +++ b/nemo_text_processing/text_normalization/hi/taggers/money.py @@ -16,18 +16,18 @@ from pynini.lib import pynutil from nemo_text_processing.text_normalization.hi.graph_utils import GraphFst, insert_space -from nemo_text_processing.text_normalization.hi.utils import get_abs_path +from nemo_text_processing.text_normalization.hi.utils import get_abs_path, load_labels currency_graph = pynini.string_file(get_abs_path("data/money/currency.tsv")) +currency_singular_graph = pynini.string_file(get_abs_path("data/money/currency_singular.tsv")) class MoneyFst(GraphFst): """ Finite state transducer for classifying money, suppletive aware, e.g. - ₹५० -> money { money { currency_maj: "रुपए" integer_part: "पचास" } - ₹५०.५० -> money { currency_maj: "रुपए" integer_part: "पचास" fractional_part: "पचास" currency_min: "centiles" } - ₹०.५० -> money { currency_maj: "रुपए" integer_part: "शून्य" fractional_part: "पचास" currency_min: "centiles" } - Note that the 'centiles' string is a placeholder to handle by the verbalizer by applying the corresponding minor currency denomination + ₹५० -> money { currency_maj: "रुपए" integer_part: "पचास" } + ₹५०.५० -> money { currency_maj: "रुपए" integer_part: "पचास" fractional_part: "पचास" currency_min: "पैसे" } + ₹०.५० -> { money { currency_maj: "रुपए" integer_part: "शून्य" fractional_part: "पचास" currency_min: "पैसे" } Args: cardinal: CardinalFst @@ -41,30 +41,118 @@ def __init__(self, cardinal: GraphFst): cardinal_graph = cardinal.final_graph + _en_to_hi_digit = pynini.string_file(get_abs_path("data/ordinal/en_to_hi_digit.tsv")) + _deva_to_ascii = pynini.invert(_en_to_hi_digit) + deva_to_ascii = pynini.closure(_deva_to_ascii | pynini.union(*"0123456789"), 1) + + _ascii_digit = pynini.union(*"0123456789") + _ascii_nonzero = pynini.union(*"123456789") + _deva_nonzero = pynini.union(*"१२३४५६७८९") + _any_digit = _ascii_digit | pynini.union(*"०१२३४५६७८९") + _any_nonzero = _ascii_nonzero | _deva_nonzero + optional_graph_negative = pynini.closure( - pynutil.insert("negative: ") + pynini.cross("-", "\"true\"") + insert_space, + pynutil.insert("negative: ") + pynini.cross("-", '"true"') + insert_space, 0, 1, ) + currency_major = pynutil.insert('currency_maj: "') + currency_graph + pynutil.insert('"') + currency_major_singular = pynutil.insert('currency_maj: "') + currency_singular_graph + pynutil.insert('"') + + one = pynini.union("1", "१") + integer_one = pynutil.insert('integer_part: "') + (one @ cardinal_graph) + pynutil.insert('"') integer = pynutil.insert('integer_part: "') + cardinal_graph + pynutil.insert('"') - fraction = pynutil.insert('fractional_part: "') + cardinal_graph + pynutil.insert('"') - currency_minor = pynutil.insert('currency_min: "') + pynutil.insert("centiles") + pynutil.insert('"') - graph_major_only = optional_graph_negative + currency_major + insert_space + integer - graph_major_and_minor = ( + strip_trailing_zeros = pynini.closure(_ascii_digit) + _ascii_nonzero + pynini.closure(pynutil.delete("0")) + canonicalise = ( + (pynutil.delete("0") + _ascii_nonzero) + | (_ascii_nonzero + pynutil.insert("0")) + | (_ascii_nonzero + _ascii_digit) + ) + two_digits_fractional_part = deva_to_ascii @ strip_trailing_zeros @ canonicalise + + fraction = ( + pynutil.insert('fractional_part: "') + (two_digits_fractional_part @ cardinal_graph) + pynutil.insert('"') + ) + + optional_delete_fractional_zeros = pynini.closure( + pynutil.delete(".") + pynini.closure(pynutil.delete("0") | pynutil.delete("०"), 1), + 0, + 1, + ) + + has_3plus_sig_digits = _any_digit + _any_digit + _any_nonzero + pynini.closure(_any_digit) + single_digit = _any_digit @ cardinal.single_digits_graph + decimal_digits = ( + pynutil.insert('fractional_part: "') + + single_digit + + pynini.closure(insert_space + single_digit) + + pynutil.insert('"') + ) + guarded_decimal_digits = has_3plus_sig_digits @ decimal_digits + + graph_decimal_path = ( optional_graph_negative + currency_major + insert_space - + integer + + pynutil.insert('integer_part: "') + + cardinal_graph + + pynutil.insert('"') + pynini.cross(".", " ") - + fraction + + guarded_decimal_digits + ).optimize() + + graph_major_only_singular = ( + optional_graph_negative + + currency_major_singular + insert_space - + currency_minor - ) + + integer_one + + optional_delete_fractional_zeros + ).optimize() + + graph_major_only = ( + optional_graph_negative + currency_major + insert_space + integer + optional_delete_fractional_zeros + ).optimize() + + maj_labels = load_labels(get_abs_path("data/money/currency.tsv")) + maj_singular_labels = load_labels(get_abs_path("data/money/currency_singular.tsv")) + maj_to_min = dict(load_labels(get_abs_path("data/money/major_minor_currencies.tsv"))) + + def _build_major_and_minor(sym_maj_labels, int_graph): + result = None + for sym, maj in sym_maj_labels: + min_name = maj_to_min.get(maj) + if not min_name: + continue - graph_currencies = graph_major_only | graph_major_and_minor + curr_maj = pynutil.insert('currency_maj: "') + pynini.cross(sym, maj) + pynutil.insert('"') + curr_min = pynutil.insert('currency_min: "') + pynutil.insert(min_name) + pynutil.insert('"') + + g = ( + optional_graph_negative + + curr_maj + + insert_space + + int_graph + + pynini.cross(".", " ") + + fraction + + insert_space + + curr_min + ).optimize() + + result = g if result is None else pynini.union(result, g).optimize() + + return result + + graph_major_and_minor = _build_major_and_minor(maj_labels, integer) + graph_major_and_minor_singular = _build_major_and_minor(maj_singular_labels, integer_one) + + graph_currencies = ( + pynutil.add_weight(graph_major_only_singular | graph_major_and_minor_singular, -0.001) + | pynutil.add_weight(graph_decimal_path, -0.0005) + | graph_major_only + | graph_major_and_minor + ) graph = graph_currencies.optimize() - final_graph = self.add_tokens(graph) - self.fst = final_graph + self.fst = self.add_tokens(graph) diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/money.py b/nemo_text_processing/text_normalization/hi/verbalizers/money.py index 048140295..1e5da99e4 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/money.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/money.py @@ -15,26 +15,16 @@ import pynini from pynini.lib import pynutil -major_minor_currencies = { - "रुपए": "पैसे", - "पाउंड": "पेंस", - "वॉन": "जिओन", - "डॉलर": "सेंट", - "लीरा": "कुरस", - "टका": "पैसे", - "येन": "सेन", - "नाइरा": "कोबो", - "यूरो": "सेंट", -} from nemo_text_processing.text_normalization.hi.graph_utils import NEMO_NOT_QUOTE, NEMO_SPACE, GraphFst +from nemo_text_processing.text_normalization.hi.utils import get_abs_path, load_labels class MoneyFst(GraphFst): """ Finite state transducer for verbalizing money, e.g. - money { integer_part: "बारह" currency_maj: "रुपए" } -> बारह रुपए - money { integer_part: "बारह" currency_maj: "रुपए" fractional_part: "पचास" currency_min: "centiles" } -> बारह रुपए पचास पैसे - money { currency_maj: "रुपए" integer_part: "शून्य" fractional_part: "पचास" currency_min: "centiles" } -> पचास पैसे + money { currency_maj: "रुपए" integer_part: "बारह" } } -> बारह रुपए + money { currency_maj: "रुपए" integer_part: "बारह" fractional_part: "पचास" currency_min: "पैसे" } -> बारह रुपए पचास पैसे + money { currency_maj: "रुपए" integer_part: "शून्य" fractional_part: "पचास" currency_min: "पैसे" } -> पचास पैसे Args: cardinal: CardinalFst @@ -46,55 +36,67 @@ class MoneyFst(GraphFst): def __init__(self): super().__init__(name="money", kind="verbalize") - currency_major = pynutil.delete('currency_maj: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') + sp = pynini.accep(NEMO_SPACE) + currency_major = pynutil.delete('currency_maj: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') integer_part = pynutil.delete('integer_part: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') - fractional_part = ( pynutil.delete('fractional_part: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') ) - # Handles major denominations only - graph_major_only = integer_part + pynini.accep(NEMO_SPACE) + currency_major + currency_minor = pynutil.delete('currency_min: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') + + graph_major_only = integer_part + sp + currency_major + + all_major_names = [maj for maj, _ in load_labels(get_abs_path("data/money/major_minor_currencies.tsv"))] - # Handles both major and minor denominations major_minor_graphs = [] + minor_only_graphs = [] + + for major in all_major_names: + graph_major_slot = pynutil.delete('currency_maj: "') + pynutil.delete(major) + pynutil.delete('"') + + major_minor_graphs.append( + graph_major_slot + + sp + + integer_part + + pynutil.insert(NEMO_SPACE) + + pynutil.insert(major) + + sp + + fractional_part + + sp + + currency_minor + ) - # Handles minor denominations only - minor_graphs = [] - - # Logic for handling minor denominations - for major, minor in major_minor_currencies.items(): - graph_major = pynutil.delete('currency_maj: "') + pynini.accep(major) + pynutil.delete('"') - graph_minor = pynutil.delete('currency_min: "') + pynini.cross("centiles", minor) + pynutil.delete('"') - graph_major_minor_partial = ( - integer_part - + pynini.accep(NEMO_SPACE) - + graph_major - + pynini.accep(NEMO_SPACE) + minor_only_graphs.append( + graph_major_slot + + sp + + pynutil.delete('integer_part: "शून्य"') + + sp + fractional_part - + pynini.accep(NEMO_SPACE) - + graph_minor + + sp + + currency_minor ) - major_minor_graphs.append(graph_major_minor_partial) - graph_minor_partial = ( - pynutil.delete('integer_part: "शून्य"') - + pynutil.delete(NEMO_SPACE) - + pynutil.delete('currency_maj: "') + graph_major_minor = pynini.union(*major_minor_graphs) + graph_minor_only = pynini.union(*minor_only_graphs) + + decimal_graphs = [] + for major in all_major_names: + decimal_graphs.append( + pynutil.delete('currency_maj: "') + pynutil.delete(major) + pynutil.delete('"') - + pynutil.delete(NEMO_SPACE) + + sp + + integer_part + + sp + + pynutil.insert(" दशमलव ") + fractional_part - + pynini.accep(NEMO_SPACE) - + graph_minor + + pynutil.insert(NEMO_SPACE) + + pynutil.insert(major) ) - minor_graphs.append(graph_minor_partial) - - graph_major_minor = pynini.union(*major_minor_graphs) - graph_minor_only = pynini.union(*minor_graphs) + graph_decimal_money = pynini.union(*decimal_graphs) - graph = graph_major_only | graph_major_minor | pynutil.add_weight(graph_minor_only, -0.1) + graph = graph_major_only | graph_major_minor | pynutil.add_weight(graph_minor_only, -0.1) | graph_decimal_money - delete_tokens = self.delete_tokens(graph) - self.fst = delete_tokens.optimize() + self.fst = self.delete_tokens(graph).optimize() diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt index 0b199ff37..26b292a17 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt @@ -116,3 +116,13 @@ $९.९९~नौ डॉलर निन्यानबे सेंट ₦१०.२७~दस नाइरा सत्ताईस कोबो €200.90~दो सौ यूरो नब्बे सेंट €१२३४.७५~एक हज़ार दो सौ चौंतीस यूरो पचहत्तर सेंट +$1.12~एक डॉलर बारह सेंट +$1.123~एक दशमलव एक दो तीन डॉलर +$1.1234~एक दशमलव एक दो तीन चार डॉलर +₹2.2000~दो रुपए बीस पैसे +$1.2000~एक डॉलर बीस सेंट +₹1.500~एक रुपया पचास पैसे +₹5.00~पाँच रुपए +₹१~एक रुपया +₹२.१२३~दो दशमलव एक दो तीन रुपए +₹१.१२३४~एक दशमलव एक दो तीन चार रुपए \ No newline at end of file From 205fff62e6cff751833b4e3fbeccc150be38fc3c Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Thu, 4 Jun 2026 02:18:32 +0530 Subject: [PATCH 04/10] Hi TN Electronic: Fix partial TSV matching, chemical formula logic, and relative file paths (#425) * Fix partial TSV matching, added support for chemical formulas, and relative file paths Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Refactor electronic class with dynamic symbol matrix and chemical rules. Replaced hardcoded TSVs and Python symbol rules with a data-driven symbol_classes.tsv and dynamic elements.tsv. Fixed greedy over-tagging of standard English words and added Unicode support for complex chemical ions without breaking URL hyphen logic. Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/electronic/elements.tsv | 132 ++++++++++++++++++ .../hi/data/electronic/symbol_classes.tsv | 16 +++ .../hi/data/electronic/symbols.tsv | 5 +- .../hi/taggers/electronic.py | 123 ++++++++-------- .../hi/verbalizers/electronic.py | 111 +++++++-------- .../test_cases_electronic.txt | 11 +- 7 files changed, 275 insertions(+), 125 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/elements.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/symbol_classes.tsv diff --git a/Jenkinsfile b/Jenkinsfile index 8b0511d06..204ae8c60 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/05-29-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-02-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/elements.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/elements.tsv new file mode 100644 index 000000000..be4610634 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/elements.tsv @@ -0,0 +1,132 @@ +Ac +Ag +Al +Am +An +Ar +As +At +Au +B +Ba +Be +Bh +Bi +Bk +Br +Bu +Bz +C +Ca +Cd +Ce +Cf +Cl +Cm +Cn +Co +Cp +Cr +Cs +Cu +D +Db +Ds +Dy +En +Er +Es +Et +Eu +F +Fe +Fl +Fm +Fr +Ga +Gd +Ge +H +He +Hf +Hg +Ho +Hs +I +In +Ir +K +Kr +La +Li +Ln +Lr +Lu +Lv +M +Mc +Md +Me +Mg +Mn +Mo +Mt +N +Na +Nb +Nd +Ne +Nh +Ni +No +Np +O +Og +Os +P +Pa +Pb +Pd +Ph +Pm +Po +Pr +Pt +Pu +R +Ra +Rb +Re +Rf +Rg +Rh +Rn +Ru +S +Sb +Sc +Se +Sg +Si +Sm +Sn +Sr +T +Ta +Tb +Tc +Te +Th +Ti +Tl +Tm +Ts +U +V +W +X +Xe +Y +Yb +Zn +Zr \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/symbol_classes.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/symbol_classes.tsv new file mode 100644 index 000000000..cf17c8756 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/electronic/symbol_classes.tsv @@ -0,0 +1,16 @@ +. email,url,unix,windows +- email,url,unix,windows,chem +_ email,url,unix,windows +/ url,unix +$ unix +\ windows +( windows,chem +) windows,chem ++ url,chem +– chem +# url +? url +& url += url +% url +: url,windows \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv index fe8881ae8..e720f5338 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/symbols.tsv @@ -30,4 +30,7 @@ $ डॉलर \[ ओपन स्क्वेर ब्रेकेट \] क्लोज़ स्क्वेर ब्रेकेट { ओपन कर्ली ब्रेकेट -} क्लोज़ कर्ली ब्रेकेट \ No newline at end of file +} क्लोज़ कर्ली ब्रेकेट +– माइनस +⁻ माइनस +⁺ प्लस \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/electronic.py b/nemo_text_processing/text_normalization/hi/taggers/electronic.py index 7807117e6..a309ec089 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/taggers/electronic.py @@ -26,7 +26,7 @@ class ElectronicFst(GraphFst): e.g. kumar@gmail.com -> tokens { electronic { username: "kumar" domain: "gmail.com" } } e.g. https://google.com/ -> tokens { electronic { protocol: "https" domain: "google.com/" } } e.g. C:\\Users\\HP\\Desktop -> tokens { electronic { path: "C:\\Users\\HP\\Desktop" } } - e.g. 192.168.1.1 -> tokens { electronic { ip: "192.168.1.1" } } + e.g. 192.168.1.1 -> tokens { electronic { domain: "192.168.1.1" } } """ @@ -36,11 +36,31 @@ def __init__(self, deterministic: bool = True): subscript_digit = pynini.project( pynini.string_file(get_abs_path("data/electronic/subscript_digit.tsv")), "input" ) - alphanumeric = NEMO_ALPHA | NEMO_DIGIT | NEMO_HI_DIGIT | subscript_digit - # email - username_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") | pynini.accep("_") + symbol_dict = {"email": [], "url": [], "unix": [], "windows": [], "chem": []} + + with open(get_abs_path("data/electronic/symbol_classes.tsv"), "r", encoding="utf-8") as f: + for line in f: + if not line.strip(): + continue + parts = line.strip().split("\t") + if len(parts) == 2: + sym = parts[0] + classes = parts[1].split(",") + for c in classes: + if c in symbol_dict: + symbol_dict[c].append(sym) + + email_symbols = pynini.union(*symbol_dict["email"]) + url_symbols = pynini.union(*symbol_dict["url"]) + unix_symbols = pynini.union(*symbol_dict["unix"]) + win_symbols = pynini.union(*symbol_dict["windows"]) + chemical_symbols = pynini.union(*symbol_dict["chem"]) + + unix_segment_syms = pynini.union(*[s for s in symbol_dict["unix"] if s != "/"]) + + username_chars = NEMO_ALPHA | NEMO_DIGIT | email_symbols username = pynutil.insert("username: \"") + pynini.closure(username_chars, 1) + pynutil.insert("\"") domain_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep(".") | pynini.accep("-") @@ -48,7 +68,6 @@ def __init__(self, deterministic: bool = True): email_graph = username + pynini.cross("@", "") + domain - # url: protocol handling for https://, http://, www., and combined forms protocol_start = pynini.cross("https://", "https") | pynini.cross("http://", "http") protocol_end = pynini.cross("www.", "www") protocol = ( @@ -61,36 +80,13 @@ def __init__(self, deterministic: bool = True): + pynutil.insert("\"") ) - url_path_chars = alphanumeric | pynini.union( - pynini.accep("."), - pynini.accep("-"), - pynini.accep("_"), - pynini.accep("/"), - pynini.accep("#"), - pynini.accep("?"), - pynini.accep("&"), - pynini.accep("="), - pynini.accep("%"), - pynini.accep("+"), - pynini.accep(":"), - ) + url_path_chars = alphanumeric | url_symbols url_path = pynini.closure(url_path_chars, 1) - url_domain = pynutil.insert(" domain: \"") + url_path + pynutil.insert("\"") - url_graph = protocol + url_domain - # file paths: Windows (C:\...), Unix (/...), and backslash-prefixed (\...) drive_letter = NEMO_ALPHA - windows_path_chars = alphanumeric | pynini.union( - pynini.accep("\\"), - pynini.accep("."), - pynini.accep("-"), - pynini.accep("_"), - pynini.accep(" "), - pynini.accep("("), - pynini.accep(")"), - ) + windows_path_chars = alphanumeric | win_symbols | pynini.accep(" ") windows_path = ( pynutil.insert("path: \"") + drive_letter @@ -100,24 +96,16 @@ def __init__(self, deterministic: bool = True): + pynutil.insert("\"") ) - unix_path_chars = alphanumeric | pynini.union( - pynini.accep("/"), - pynini.accep("."), - pynini.accep("-"), - pynini.accep("_"), - pynini.accep("$"), - ) - unix_path = ( - pynutil.insert("path: \"") + pynini.accep("/") + pynini.closure(unix_path_chars, 1) + pynutil.insert("\"") - ) + unix_path_chars = alphanumeric | unix_symbols + unix_segment_chars = alphanumeric | unix_segment_syms + unix_segment = pynini.closure(unix_segment_chars, 1) - backslash_path_chars = alphanumeric | pynini.union( - pynini.accep("\\"), - pynini.accep("."), - pynini.accep("-"), - pynini.accep("_"), - pynini.accep(" "), - ) + abs_unix_path = pynini.accep("/") + pynini.closure(unix_path_chars, 1) + rel_unix_path = unix_segment + pynini.accep("/") + pynini.closure(unix_path_chars, 0) + + unix_path = pynutil.insert("path: \"") + (abs_unix_path | rel_unix_path) + pynutil.insert("\"") + + backslash_path_chars = alphanumeric | unix_segment_syms | pynini.accep("\\") | pynini.accep(" ") backslash_path = ( pynutil.insert("path: \"") + pynini.accep("\\") @@ -125,12 +113,10 @@ def __init__(self, deterministic: bool = True): + pynutil.insert("\"") ) - # ip addresses: exactly 4 dot-separated octets ip_octet = pynini.closure(NEMO_DIGIT, 1, 3) dot_octet = pynini.accep(".") + ip_octet ip_address = pynutil.insert("domain: \"") + ip_octet + pynini.closure(dot_octet, 3, 3) + pynutil.insert("\"") - # domains: simple TLD-based (abc.com) and government/education suffixes (.gov.in, .ac.in) domain_segment_chars = NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") domain_segment = pynini.closure(domain_segment_chars, 1) @@ -144,7 +130,6 @@ def __init__(self, deterministic: bool = True): pynutil.insert("domain: \"") + domain_body + pynini.closure(pynini.accep("/"), 0, 1) + pynutil.insert("\"") ) - # file extensions: e.g. report.pdf, data.csv known_extensions = pynini.project( pynini.string_file(get_abs_path("data/electronic/file_extensions.tsv")), "input" ) @@ -154,27 +139,41 @@ def __init__(self, deterministic: bool = True): pynutil.insert("domain: \"") + filename_stem + pynini.accep(".") + known_extensions + pynutil.insert("\"") ) - # chemical formulas with subscript digits: e.g. H₂O, CO₂ - chemical_chars = NEMO_ALPHA | subscript_digit - chemical_formula = ( - pynutil.insert("domain: \"") + NEMO_ALPHA + pynini.closure(chemical_chars, 1) + pynutil.insert("\"") - ) + elements = pynini.project(pynini.string_file(get_abs_path("data/electronic/elements.tsv")), "input") + + chem_number = pynini.closure(NEMO_DIGIT | subscript_digit, 1) + + chem_block = elements + pynini.closure(chem_number, 0, 1) + + chem_sequence_chars = chem_block | chemical_symbols | chem_number + + raw_chemical = pynini.closure(chemical_symbols) + chem_block + pynini.closure(chem_sequence_chars) + + any_chem = pynini.closure(chem_sequence_chars) + has_open = any_chem + pynini.accep("(") + any_chem + no_open = pynini.difference(any_chem, has_open) + ends_with_close = any_chem + pynini.accep(")") + + unbalanced_trailing = pynini.intersect(no_open, ends_with_close) + valid_chemical = pynini.difference(raw_chemical, unbalanced_trailing).optimize() + + chemical_formula = pynutil.insert("domain: \"") + valid_chemical + pynutil.insert("\"") - # alphanumeric codes: strings containing both letters and digits, - # optionally separated by hyphens, e.g. IELF004, N95, GSAT-18, F-35B alnum_seg = pynini.closure(NEMO_ALPHA | NEMO_DIGIT, 1) - alphanumeric_pattern = alnum_seg + pynini.closure(pynini.accep("-") + alnum_seg) + separator = pynini.accep("-") | pynini.accep(".") + alphanumeric_pattern = alnum_seg + pynini.closure(separator + alnum_seg) + + alnum_hyp_dot_sigma = pynini.closure(NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") | pynini.accep(".")) + + contains_alpha = alnum_hyp_dot_sigma + NEMO_ALPHA + alnum_hyp_dot_sigma + contains_digit = alnum_hyp_dot_sigma + NEMO_DIGIT + alnum_hyp_dot_sigma - alnum_hyp_sigma = pynini.closure(NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-")) - contains_alpha = alnum_hyp_sigma + NEMO_ALPHA + alnum_hyp_sigma - contains_digit = alnum_hyp_sigma + NEMO_DIGIT + alnum_hyp_sigma alphanumeric_code_fst = pynini.intersect( pynini.intersect(alphanumeric_pattern, contains_alpha), contains_digit ).optimize() alphanumeric_code = pynutil.insert("domain: \"") + alphanumeric_code_fst + pynutil.insert("\"") - # Weights use 3 tiers: structurally unambiguous (1.0), moderately general (1.1), greedy (1.2) graph = ( pynutil.add_weight(url_graph, 1.0) | pynutil.add_weight(email_graph, 1.0) diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py index 124e1c60b..398c79fef 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py @@ -30,10 +30,10 @@ class ElectronicFst(GraphFst): Uses a phonetic-first approach with letter-by-letter fallback. Examples: - electronic { username: "kumar" domain: "gmail.com" } -> "कुमार एट जीमेल डॉट कॉम" + electronic { username: "kumar" domain: "gmail.com" } -> "के यू एम ए आर एट जीमेल डॉट कॉम" electronic { protocol: "https" domain: "google.com/" } -> "एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गूगल डॉट कॉम फॉरवर्ड स्लैश" - electronic { path: "C:\\Users\\HP" } -> "सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी" - electronic { ip: "192.168.1.1" } -> "एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक" + electronic { path: "C:\\Users\\HP\\Desktop" } -> "सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डेस्कटॉप" + electronic { domain: "192.168.1.1" } -> "एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक" Args: deterministic: if True will provide a single transduction option, @@ -43,7 +43,6 @@ class ElectronicFst(GraphFst): def __init__(self, deterministic: bool = True): super().__init__(name="electronic", kind="verbalize", deterministic=deterministic) - # Load data files symbols_graph = pynini.string_file(get_abs_path("data/electronic/symbols.tsv")).optimize() domain_graph = pynini.string_file(get_abs_path("data/electronic/domain.tsv")).optimize() server_name_graph = pynini.string_file(get_abs_path("data/electronic/server_name.tsv")).optimize() @@ -51,94 +50,90 @@ def __init__(self, deterministic: bool = True): latin_to_hindi_graph = pynini.string_file(get_abs_path("data/address/letters.tsv")) latin_to_hindi_graph = capitalized_input_graph(latin_to_hindi_graph).optimize() - # Digit mappings - use telephone number mappings for ASCII digits ascii_digit_graph = pynini.string_file(get_abs_path("data/telephone/number.tsv")).optimize() hindi_digit_graph = pynini.string_file(get_abs_path("data/numbers/digit.tsv")).optimize() hindi_zero_graph = pynini.string_file(get_abs_path("data/numbers/zero.tsv")).optimize() subscript_digit_graph = pynini.string_file(get_abs_path("data/electronic/subscript_digit.tsv")).optimize() digit_verbalization = ascii_digit_graph | hindi_digit_graph | hindi_zero_graph | subscript_digit_graph - # Combined phonetic word graph: server names + common words - phonetic_word = server_name_graph | common_words_graph + protocol_graph = pynini.string_file(get_abs_path("data/electronic/protocols.tsv")).optimize() - # ============ CHARACTER VERBALIZATION ============ - # Single character to Hindi verbalization with space insertion - char_to_hindi = pynutil.add_weight(latin_to_hindi_graph, 1.0) | pynutil.add_weight( # Letter mapping - digit_verbalization, 1.0 - ) # Digit mapping - char_with_space = char_to_hindi + insert_space + single_letter = latin_to_hindi_graph + insert_space + single_digit = digit_verbalization + insert_space + single_symbol = symbols_graph + insert_space - # ============ SYMBOL VERBALIZATION ============ - symbol_to_hindi = symbols_graph + insert_space + single_non_alpha = pynutil.add_weight(single_symbol, 1.0) | pynutil.add_weight(single_digit, 1.0) - # ============ DOMAIN VERBALIZATION ============ - # Domain extension verbalization (.com -> डॉट कॉम) - domain_ext_verbalization = pynini.cross(".", "डॉट ") + domain_graph + insert_space + def make_alpha_run_verbalizer(tsv_graphs): + phonetic = pynini.union(*[pynutil.add_weight(g + insert_space, w) for g, w in tsv_graphs]) + literal = pynutil.add_weight(pynini.closure(single_letter, 1), 1.1) + return phonetic | literal - # ============ PROTOCOL VERBALIZATION ============ - protocol_graph = pynini.string_file(get_abs_path("data/electronic/protocols.tsv")).optimize() - protocol_verbalization = protocol_graph + insert_space + def make_content(alpha_run_verb, non_alpha_sep=None): + if non_alpha_sep is None: + non_alpha_sep = single_non_alpha + mandatory_sep = pynini.closure(non_alpha_sep, 1) + return ( + pynini.closure(non_alpha_sep, 0) + + pynini.closure(alpha_run_verb + mandatory_sep, 0) + + pynini.closure(alpha_run_verb, 0, 1) + + pynini.closure(non_alpha_sep, 0) + ) - # ============ FIELD EXTRACTION ============ - # Extract username field delete_username_tag = pynutil.delete("username: \"") delete_domain_tag = pynutil.delete("domain: \"") delete_protocol_tag = pynutil.delete("protocol: \"") delete_path_tag = pynutil.delete("path: \"") delete_quote = pynutil.delete("\"") - # Username verbalization: letter-by-letter with symbol handling - username_content = pynini.closure( - pynutil.add_weight(phonetic_word + insert_space, 0.9) - | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1), - 1, + username_alpha_run = make_alpha_run_verbalizer( + [ + (server_name_graph, 0.85), + (domain_graph, 0.87), + (common_words_graph, 0.90), + ] ) - - username_graph = ( - delete_username_tag + username_content + delete_quote + delete_space + pynutil.insert("एट ") # @ symbol + username_content = make_content(username_alpha_run) + username_graph = delete_username_tag + username_content + delete_quote + delete_space + pynutil.insert("एट ") + + domain_alpha_run = make_alpha_run_verbalizer( + [ + (server_name_graph, 0.85), + (domain_graph, 0.87), + (common_words_graph, 0.90), + ] ) - # Domain verbalization - domain_content = pynini.closure( - pynutil.add_weight(phonetic_word + insert_space, 0.9) - | pynutil.add_weight(domain_ext_verbalization, 0.95) - | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1), - 1, + domain_alpha_run = make_alpha_run_verbalizer( + [ + (server_name_graph, 0.85), + (domain_graph, 0.87), + (common_words_graph, 0.90), + ] ) + domain_content = pynutil.add_weight(make_content(domain_alpha_run), 1.0) + domain_only_graph = delete_domain_tag + domain_content + delete_quote - # Protocol verbalization - protocol_only_graph = delete_protocol_tag + protocol_verbalization + delete_quote + delete_space + protocol_only_graph = delete_protocol_tag + protocol_graph + insert_space + delete_quote + delete_space - # Path verbalization (Windows/Unix file paths) - path_content = pynini.closure( - pynutil.add_weight(common_words_graph + insert_space, 0.9) - | pynutil.add_weight(symbol_to_hindi, 1.0) - | pynutil.add_weight(char_with_space, 1.1), - 1, + path_alpha_run = make_alpha_run_verbalizer( + [ + (domain_graph, 0.87), + (common_words_graph, 0.90), + ] ) - + path_content = make_content(path_alpha_run) path_graph = delete_path_tag + path_content + delete_quote - # IP address verbalization (digit by digit) - ip_char = pynutil.add_weight(symbols_graph + insert_space, 1.0) | pynutil.add_weight( - digit_verbalization + insert_space, 1.0 - ) + ip_char = single_symbol | single_digit ip_content = pynini.closure(ip_char, 1) - ip_graph = delete_domain_tag + ip_content + delete_quote - # ============ COMBINED GRAPH ============ - # Email: username + domain email_full = username_graph + domain_only_graph - - # URL with protocol: protocol + domain url_full = protocol_only_graph + domain_only_graph - # Combined final graph graph = ( pynutil.add_weight(url_full, 1.0) | pynutil.add_weight(email_full, 1.01) diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt index 85b34c4a3..f9a175794 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt @@ -53,8 +53,13 @@ C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्ल 255.255.255.0~दो पाँच पाँच डॉट दो पाँच पाँच डॉट दो पाँच पाँच डॉट शून्य आईपी पता है 192.168.1.1~आईपी पता है एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक आईपी एड्रेस 10.0.0.1~आईपी एड्रेस एक शून्य डॉट शून्य डॉट शून्य डॉट एक -ip address 192.168.1.1~आई पी एड्रेस एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक -ip address 10.0.0.1~आई पी एड्रेस एक शून्य डॉट शून्य डॉट शून्य डॉट एक +ip address 192.168.1.1~ip address एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक +ip address 10.0.0.1~ip address एक शून्य डॉट शून्य डॉट शून्य डॉट एक report.pdf~आर ई पी ओ आर टी डॉट पी डी एफ photo.jpg~पी एच ओ टी ओ डॉट जे पी जी -data.csv~डेटा डॉट सी एस वी \ No newline at end of file +data.csv~डेटा डॉट सी एस वी +robinson.org~आर ओ बी आई एन एस ओ एन डॉट ऑर्ग +anand@gmail.com~ए एन ए एन डी एट जीमेल डॉट कॉम +Al₂(SO₄)₃~ए एल दो ओपन ब्रेकेट एस ओ चार क्लोज़ ब्रेकेट तीन +C₂H₄~सी दो एच चार +home/desktop~होम फॉरवर्ड स्लैश डेस्कटॉप \ No newline at end of file From 88c2421db7a13ce2084a23c33949027277d4975c Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Wed, 10 Jun 2026 03:51:46 +0530 Subject: [PATCH 05/10] Remove single capital letter units from Measure class (#429) Signed-off-by: Shreyas Pawar --- Jenkinsfile | 2 +- .../text_normalization/hi/data/measure/unit.tsv | 12 +----------- .../data_text_normalization/test_cases_measure.txt | 6 +----- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 204ae8c60..33b361cf5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-02-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-04-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/measure/unit.tsv b/nemo_text_processing/text_normalization/hi/data/measure/unit.tsv index 4065bc86b..d236dd51b 100644 --- a/nemo_text_processing/text_normalization/hi/data/measure/unit.tsv +++ b/nemo_text_processing/text_normalization/hi/data/measure/unit.tsv @@ -1,6 +1,5 @@ °C डिग्री सेल्सियस °F डिग्री फारेनहाइट -K केल्विन g ग्राम kg किलोग्राम mg मिलीग्राम @@ -72,7 +71,6 @@ dl² वर्ग डेसीलीटर dal² वर्ग डेकालीटर dl³ घन डेसीलीटर dal³ घन डेकालीटर -L लीटर kL किलोलीटर mL मिलीलीटर mL² वर्ग मिलीलीटर @@ -99,12 +97,10 @@ mm³ घन मिलीमीटर qt क्वार्ट gal गैलन pt पिंट -W वाट MW मेगावाट KW किलोवाट b बिट Mb मेगाबिट -B बाइट GB गीगाबाइट KB किलोबाइट TB टेराबाइट @@ -114,11 +110,7 @@ EB एक्साबाइट ZB जेटाबाइट YB योटाबाइट BB ब्रोन्टोबाइट -C कूलंब -V वोल्ट Pa पास्कल -A ऐंपीयर -J जूल s सेकंड hr घंटा h घंटे @@ -131,7 +123,6 @@ doz दर्जन Hz हर्ट्ज़ GHz गीगाहर्ट्ज़ KHz किलोहर्ट्ज़ -N न्यूटन dB डेसीबल yr साल hp हॉर्सपॉवर @@ -151,5 +142,4 @@ mi/hr मील प्रति घंटा mi/min मील प्रति मिनट ₹/ac रुपए प्रति एकड़ x बाई -X बाई -* बाई +* बाई \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_measure.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_measure.txt index 6d0cef9b1..6afd66b7f 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_measure.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_measure.txt @@ -26,10 +26,6 @@ २५.४ °C~पच्चीस दशमलव चार डिग्री सेल्सियस 22 °F~बाईस डिग्री फारेनहाइट २२.५ °F~बाईस दशमलव पाँच डिग्री फारेनहाइट -7 K~सात केल्विन -७.२२ K~सात दशमलव दो दो केल्विन -5 L~पाँच लीटर -५.४ L~पाँच दशमलव चार लीटर 50 ml~पचास मिलीलीटर ५०.५ ml~पचास दशमलव पाँच मिलीलीटर 19 qt~उन्नीस क्वार्ट @@ -70,4 +66,4 @@ ५ yr~पाँच साल 1.5 yr~डेढ़ साल २.५ yr~ढाई साल -3.5 yr~साढ़े तीन साल +3.5 yr~साढ़े तीन साल \ No newline at end of file From 317783ebd8bc656ccef98964ca37390b43ce3bc4 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Tue, 23 Jun 2026 03:53:36 +0530 Subject: [PATCH 06/10] Hi TN: Implement Serial tagger (#440) * Hi TN Serial: Implement SerialFst tagger for Devanagari-numeric mixtures, number chains, and mathematical powers Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Address feedback for Hindi Serial Tagger PR Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/serial/chars.tsv | 68 ++++++++ .../hi/data/serial/power_special.tsv | 4 + .../hi/data/serial/special_symbols.tsv | 4 + .../text_normalization/hi/taggers/serial.py | 153 ++++++++++++++++++ .../hi/taggers/tokenize_and_classify.py | 10 +- .../test_cases_serial.txt | 21 +++ tests/nemo_text_processing/hi/test_serial.py | 33 ++++ .../hi/test_sparrowhawk_normalization.sh | 8 +- 9 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/serial/chars.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/serial/power_special.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/serial/special_symbols.tsv create mode 100644 nemo_text_processing/text_normalization/hi/taggers/serial.py create mode 100644 tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt create mode 100644 tests/nemo_text_processing/hi/test_serial.py diff --git a/Jenkinsfile b/Jenkinsfile index 33b361cf5..3d7a538ed 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-04-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-17-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/serial/chars.tsv b/nemo_text_processing/text_normalization/hi/data/serial/chars.tsv new file mode 100644 index 000000000..d7c9c39e8 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/serial/chars.tsv @@ -0,0 +1,68 @@ +अ +आ +इ +ई +उ +ऊ +ऋ +ए +ऐ +ओ +औ +ऑ +ा +ि +ी +ु +ू +ृ +े +ै +ो +ौ +ॉ +ं +ः +ँ +क +ख +ग +घ +ङ +च +छ +ज +झ +ञ +ट +ठ +ड +ढ +ण +त +थ +द +ध +न +प +फ +ब +भ +म +य +र +ल +व +श +ष +स +ह +क़ +ख़ +ग़ +ज़ +ड़ +ढ़ +फ़ +य़ +् \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/serial/power_special.tsv b/nemo_text_processing/text_normalization/hi/data/serial/power_special.tsv new file mode 100644 index 000000000..64583f947 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/serial/power_special.tsv @@ -0,0 +1,4 @@ +^2 स्क्वेर्ड +^२ स्क्वेर्ड +^3 क्यूब +^३ क्यूब \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/serial/special_symbols.tsv b/nemo_text_processing/text_normalization/hi/data/serial/special_symbols.tsv new file mode 100644 index 000000000..c96a15bd6 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/serial/special_symbols.tsv @@ -0,0 +1,4 @@ +# हैशटैग +% प्रतिशत +& एंड +@ एट \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/serial.py b/nemo_text_processing/text_normalization/hi/taggers/serial.py new file mode 100644 index 000000000..d7433e583 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/taggers/serial.py @@ -0,0 +1,153 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.hi.graph_utils import ( + NEMO_ALPHA, + NEMO_DIGIT, + NEMO_NOT_SPACE, + NEMO_SIGMA, + GraphFst, + convert_space, +) +from nemo_text_processing.text_normalization.hi.utils import get_abs_path + + +class SerialFst(GraphFst): + """ + Finite state transducer for classifying serial strings in Hindi. + Handles Devanagari-numeric mixtures, complex delimited number chains, + symbols, and powers. Supports both ASCII (0-9) and Devanagari (०-९) digits. + + e.g. कोविड-19 -> tokens { name: "कोविड-उन्नीस" } + e.g. 5जी -> tokens { name: "पाँच जी" } + e.g. ३जी -> tokens { name: "तीन जी" } + e.g. 2^2 -> tokens { name: "दो स्क्वेर्ड" } + e.g. 2^4 -> tokens { name: "दो टु द पावर चार" } + e.g. 1-800-555 -> tokens { name: "एक-आठ सौ-पाँच सौ पचपन" } + + Note: Pure Latin-alpha + digit patterns (A12, B-60) are intentionally + excluded here so they fall through to the electronic classifier. + """ + + def __init__( + self, + cardinal: GraphFst, + deterministic: bool = True, + ): + super().__init__(name="serial", kind="classify", deterministic=deterministic) + + digit_graph = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) + zero_graph = pynini.string_file(get_abs_path("data/numbers/zero.tsv")) + + devanagari_digits = pynini.project( + pynini.union(digit_graph, zero_graph), + "input", + ).optimize() + + any_digit = pynini.union(NEMO_DIGIT, devanagari_digits).optimize() + + limited_cardinal_graph = ( + cardinal.digit | cardinal.zero | cardinal.teens_and_ties | cardinal.graph_hundreds + ).optimize() + num_graph = limited_cardinal_graph + + symbols_graph = pynini.string_file(get_abs_path("data/serial/special_symbols.tsv")).optimize() + + devanagari_chars = pynini.string_file(get_abs_path("data/serial/chars.tsv")).optimize() + + letter_graph = pynini.string_file(get_abs_path("data/address/letters.tsv")) + latin_letters = letter_graph + pynini.closure(pynutil.insert(" ") + letter_graph) + latin_letters = latin_letters.optimize() + + devanagari_word = pynini.closure(devanagari_chars, 2).optimize() + + delimiter = (pynini.accep("-") | pynini.accep("/") | pynini.accep(" ")).optimize() + + alphas = (latin_letters | devanagari_word).optimize() + segment = (alphas | num_graph | symbols_graph).optimize() + + serial_core = segment + pynini.closure(delimiter + segment, 1) + serial_core = serial_core.optimize() + + serial_graph = serial_core + + all_alphas = pynini.union(NEMO_ALPHA, devanagari_chars).optimize() + + insert_space_alpha_digit = pynini.cdrewrite(pynutil.insert(" "), all_alphas, any_digit, NEMO_SIGMA) + insert_space_digit_alpha = pynini.cdrewrite(pynutil.insert(" "), any_digit, all_alphas, NEMO_SIGMA) + space_inserter = pynini.compose(insert_space_alpha_digit, insert_space_digit_alpha).optimize() + + glued_serial = pynini.compose(space_inserter, serial_core).optimize() + serial_graph = pynini.union(serial_graph, glued_serial).optimize() + + power_special = pynutil.add_weight( + pynini.string_file(get_abs_path("data/serial/power_special.tsv")), -1.0 + ).optimize() + + power_generic = pynutil.add_weight( + (pynutil.delete("^") + pynutil.insert(" टु द पावर ") + num_graph), 1.0 + ).optimize() + + power_suffix = pynini.union(power_special, power_generic).optimize() + power_graph = num_graph + power_suffix + serial_graph = pynini.union(serial_graph, power_graph).optimize() + + serial_graph = pynini.compose(pynini.closure(NEMO_NOT_SPACE, 2), serial_graph).optimize() + + pure_word_slash = pynini.closure(NEMO_ALPHA, 1) + pynini.accep("/") + pynini.closure(NEMO_ALPHA, 1) + + dimension_pattern = ( + pynini.closure(any_digit, 1) + (pynini.accep("x") | pynini.accep("X")) + pynini.closure(any_digit, 1) + ) + + _opt_delim = pynini.closure(pynini.accep("-") | pynini.accep(" "), 0, 1) + latin_alphanum = (pynini.closure(NEMO_ALPHA, 1) + _opt_delim + pynini.closure(any_digit, 1)) | ( + pynini.closure(any_digit, 1) + _opt_delim + pynini.closure(NEMO_ALPHA, 1) + ) + + ordinal_suffixes = pynini.project( + pynini.union( + pynini.string_file(get_abs_path("data/ordinal/suffixes.tsv")), + pynini.string_file(get_abs_path("data/ordinal/suffixes_map.tsv")), + ), + "input", + ).optimize() + ordinal_pattern = pynini.closure(any_digit, 1) + ordinal_suffixes + + date_year_suffix = pynini.project( + pynini.string_file(get_abs_path("data/date/year_suffix.tsv")), + "input", + ).optimize() + date_suffixes = pynini.project( + pynini.string_file(get_abs_path("data/date/suffixes.tsv")), + "input", + ).optimize() + date_pattern = ( + pynini.closure(any_digit, 1) + + pynini.closure(pynini.accep("-") + pynini.closure(any_digit, 1), 0) + + pynini.accep(" ") + + pynini.union(date_year_suffix, date_suffixes) + ) + + exclusions = pure_word_slash | dimension_pattern | latin_alphanum | ordinal_pattern | date_pattern + accepted_inputs = pynini.difference(NEMO_SIGMA, exclusions).optimize() + + serial_graph = pynini.compose(accepted_inputs, serial_graph).optimize() + + self.graph = serial_graph.optimize() + graph = pynutil.insert('name: "') + convert_space(self.graph).optimize() + pynutil.insert('"') + self.fst = graph.optimize() diff --git a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py index 88cb04727..75663ca24 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py +++ b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py @@ -35,6 +35,7 @@ from nemo_text_processing.text_normalization.hi.taggers.money import MoneyFst from nemo_text_processing.text_normalization.hi.taggers.ordinal import OrdinalFst from nemo_text_processing.text_normalization.hi.taggers.punctuation import PunctuationFst +from nemo_text_processing.text_normalization.hi.taggers.serial import SerialFst from nemo_text_processing.text_normalization.hi.taggers.telephone import TelephoneFst from nemo_text_processing.text_normalization.hi.taggers.time import TimeFst from nemo_text_processing.text_normalization.hi.taggers.whitelist import WhiteListFst @@ -111,12 +112,18 @@ def __init__( punctuation = PunctuationFst(deterministic=deterministic) punct_graph = punctuation.fst + word = WordFst(punctuation=punctuation, deterministic=deterministic) + word_graph = word.fst + telephone = TelephoneFst() telephone_graph = telephone.fst electronic = ElectronicFst(deterministic=deterministic) electronic_graph = electronic.fst + serial = SerialFst(cardinal=cardinal, deterministic=deterministic) + serial_graph = serial.fst + classify = ( pynutil.add_weight(whitelist_graph, 1.01) | pynutil.add_weight(cardinal_graph, 1.1) @@ -129,10 +136,9 @@ def __init__( | pynutil.add_weight(telephone_graph, 1.1) | pynutil.add_weight(ordinal_graph, 1.1) | pynutil.add_weight(electronic_graph, 1.1) + | pynutil.add_weight(serial_graph, 1.11) ) - word_graph = WordFst(punctuation=punctuation, deterministic=deterministic).fst - punct = pynutil.insert("tokens { ") + pynutil.add_weight(punct_graph, weight=2.1) + pynutil.insert(" }") punct = pynini.closure( pynini.union( diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt new file mode 100644 index 000000000..4c3880fb9 --- /dev/null +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt @@ -0,0 +1,21 @@ +कोविड-19~कोविड-उन्नीस +कोविड-१९~कोविड-उन्नीस +5जी~पाँच जी +५जी~पाँच जी +2^2~दो स्क्वेर्ड +२^२~दो स्क्वेर्ड +1-800-555~एक-आठ सौ-पाँच सौ पचपन +3जी~तीन जी +4जी~चार जी +कोरोना-2~कोरोना-दो +अग्नि-5~अग्नि-पाँच +ओमिक्रॉन-2~ओमिक्रॉन-दो +3^2~तीन स्क्वेर्ड +2^3~दो क्यूब +5^3~पाँच क्यूब +४^५~चार टु द पावर पाँच +99-1~निन्यानबे-एक +10-20-30~दस-बीस-तीस +1-800-999~एक-आठ सौ-नौ सौ निन्यानबे +पृथ्वी-4~पृथ्वी-चार +ब्रह्मोस-1~ब्रह्मोस-एक \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/test_serial.py b/tests/nemo_text_processing/hi/test_serial.py new file mode 100644 index 000000000..43da54b17 --- /dev/null +++ b/tests/nemo_text_processing/hi/test_serial.py @@ -0,0 +1,33 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from parameterized import parameterized + +from nemo_text_processing.text_normalization.normalize import Normalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestSerial: + normalizer = Normalizer( + input_case='cased', lang='hi', cache_dir=CACHE_DIR, overwrite_cache=False, post_process=True + ) + + @parameterized.expand(parse_test_case_file('hi/data_text_normalization/test_cases_serial.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False, punct_post_process=True) + assert pred == expected diff --git a/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh b/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh index e8057a126..974dac331 100644 --- a/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh +++ b/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh @@ -52,10 +52,10 @@ testTNDecimal() { # runtest $input #} -#testTNSerial() { -# input=$PROJECT_DIR/hi/data_text_normalization/test_cases_serial.txt -# runtest $input -#} +testTNSerial() { + input=$PROJECT_DIR/hi/data_text_normalization/test_cases_serial.txt + runtest $input +} #testTNRoman() { # input=$PROJECT_DIR/en/data_text_normalization/test_cases_roman.txt From 9203a96f5c815302c32938328af5c37132eac381 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Tue, 30 Jun 2026 01:36:35 +0530 Subject: [PATCH 07/10] Hi TN: Implement Roman semiotic class (#442) * Hi TN Roman class implementation Signed-off-by: Shreyas Pawar * Refactor Roman FST to utilize shared serial Devanagari chars Signed-off-by: Shreyas Pawar * Add __init__.py for serial and roman data folders Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update roman_graph weight to 1.07 and other minor changes Signed-off-by: Shreyas Pawar * Feedback changes Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/ordinal/suffixes_map.tsv | 3 +- .../hi/data/roman/__init__.py | 13 ++ .../data/roman/roman_ordinal_exceptions.tsv | 10 ++ .../hi/data/roman/roman_to_spoken.tsv | 100 +++++++++++++ .../hi/data/serial/__init__.py | 13 ++ .../text_normalization/hi/taggers/roman.py | 138 ++++++++++++++++++ .../hi/taggers/tokenize_and_classify.py | 5 + .../hi/verbalizers/roman.py | 98 +++++++++++++ .../hi/verbalizers/verbalize.py | 5 + .../test_cases_roman.txt | 23 +++ tests/nemo_text_processing/hi/test_roman.py | 36 +++++ .../hi/test_sparrowhawk_normalization.sh | 8 +- 13 files changed, 447 insertions(+), 7 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/roman/__init__.py create mode 100644 nemo_text_processing/text_normalization/hi/data/roman/roman_ordinal_exceptions.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/roman/roman_to_spoken.tsv create mode 100644 nemo_text_processing/text_normalization/hi/data/serial/__init__.py create mode 100644 nemo_text_processing/text_normalization/hi/taggers/roman.py create mode 100644 nemo_text_processing/text_normalization/hi/verbalizers/roman.py create mode 100644 tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt create mode 100644 tests/nemo_text_processing/hi/test_roman.py diff --git a/Jenkinsfile b/Jenkinsfile index 3d7a538ed..039504611 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-17-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-25-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/ordinal/suffixes_map.tsv b/nemo_text_processing/text_normalization/hi/data/ordinal/suffixes_map.tsv index 77139cff5..2abb5c492 100644 --- a/nemo_text_processing/text_normalization/hi/data/ordinal/suffixes_map.tsv +++ b/nemo_text_processing/text_normalization/hi/data/ordinal/suffixes_map.tsv @@ -1,2 +1 @@ -वे वें - +वे वें \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/roman/__init__.py b/nemo_text_processing/text_normalization/hi/data/roman/__init__.py new file mode 100644 index 000000000..4fc25d0d3 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/roman/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/hi/data/roman/roman_ordinal_exceptions.tsv b/nemo_text_processing/text_normalization/hi/data/roman/roman_ordinal_exceptions.tsv new file mode 100644 index 000000000..b298e13e6 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/roman/roman_ordinal_exceptions.tsv @@ -0,0 +1,10 @@ +Iला पहला +Iली पहली +IIरा दूसरा +IIरी दूसरी +IIIरा तीसरा +IIIरी तीसरी +IVथा चौथा +IVथी चौथी +VIठा छठा +VIठी छठी \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/roman/roman_to_spoken.tsv b/nemo_text_processing/text_normalization/hi/data/roman/roman_to_spoken.tsv new file mode 100644 index 000000000..69b760196 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/roman/roman_to_spoken.tsv @@ -0,0 +1,100 @@ +I एक +II दो +III तीन +IV चार +V पाँच +VI छह +VII सात +VIII आठ +IX नौ +X दस +XI ग्यारह +XII बारह +XIII तेरह +XIV चौदह +XV पंद्रह +XVI सोलह +XVII सत्रह +XVIII अठारह +XIX उन्नीस +XX बीस +XXI इक्कीस +XXII बाईस +XXIII तेईस +XXIV चौबीस +XXV पच्चीस +XXVI छब्बीस +XXVII सत्ताईस +XXVIII अट्ठाईस +XXIX उनतीस +XXX तीस +XXXI इकतीस +XXXII बत्तीस +XXXIII तैंतीस +XXXIV चौंतीस +XXXV पैंतीस +XXXVI छत्तीस +XXXVII सैंतीस +XXXVIII अड़तीस +XXXIX उनचालीस +XL चालीस +XLI इकतालीस +XLII बयालीस +XLIII तैंतालीस +XLIV चौंतालीस +XLV पैंतालीस +XLVI छियालीस +XLVII सैंतालीस +XLVIII अड़तालीस +XLIX उनचास +L पचास +LI इक्यावन +LII बावन +LIII तिरपन +LIV चौवन +LV पचपन +LVI छप्पन +LVII सत्तावन +LVIII अट्ठावन +LIX उनसठ +LX साठ +LXI इकसठ +LXII बासठ +LXIII तिरसठ +LXIV चौंसठ +LXV पैंसठ +LXVI छियासठ +LXVII सड़सठ +LXVIII अड़सठ +LXIX उनहत्तर +LXX सत्तर +LXXI इकहत्तर +LXXII बहत्तर +LXXIII तिहत्तर +LXXIV चौहत्तर +LXXV पचहत्तर +LXXVI छिहत्तर +LXXVII सतहत्तर +LXXVIII अठहत्तर +LXXIX उनासी +LXXX अस्सी +LXXXI इक्यासी +LXXXII बयासी +LXXXIII तिरासी +LXXXIV चौरासी +LXXXV पचासी +LXXXVI छियासी +LXXXVII सत्तासी +LXXXVIII अट्ठासी +LXXXIX नवासी +XC नब्बे +XCI इक्यानवे +XCII बानवे +XCIII तिरानवे +XCIV चौरानवे +XCV पचानवे +XCVI छियानवे +XCVII सत्तानवे +XCVIII अट्ठानवे +XCIX निन्यानवे +C एक सौ \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/serial/__init__.py b/nemo_text_processing/text_normalization/hi/data/serial/__init__.py new file mode 100644 index 000000000..4fc25d0d3 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/serial/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/hi/taggers/roman.py b/nemo_text_processing/text_normalization/hi/taggers/roman.py new file mode 100644 index 000000000..ea8d259fe --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/taggers/roman.py @@ -0,0 +1,138 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.hi.graph_utils import GraphFst, convert_space, insert_space +from nemo_text_processing.text_normalization.hi.utils import get_abs_path, load_labels + + +class RomanFst(GraphFst): + """ + Finite state transducer for classifying Roman numerals in Hindi text. + e.g. भास्कर-II -> tokens { roman { key_cardinal: "भास्कर" integer: "II" } } + e.g. कक्षा XII -> tokens { roman { key_cardinal: "कक्षा" integer: "XII" } } + e.g. XIIवीं कक्षा -> tokens { roman { integer: "XII" default_ordinal: "बारहवीं" key_cardinal: "कक्षा" } } + e.g. IVथी कक्षा -> tokens { roman { integer: "IV" default_ordinal: "चौथी" key_cardinal: "कक्षा" } } + + 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="roman", kind="classify", deterministic=deterministic) + + roman_graph = pynini.string_file(get_abs_path("data/roman/roman_to_spoken.tsv")).optimize() + roman_numeral_only = pynini.project(roman_graph, "input").optimize() + + devanagari_chars = pynini.project( + pynini.string_file(get_abs_path("data/serial/chars.tsv")), "input" + ).optimize() + + devanagari_word = pynini.closure(devanagari_chars, 1).optimize() + + devanagari_phrase = ( + devanagari_word + pynini.closure((pynini.accep(" ") | pynini.accep("-")) + devanagari_word) + ).optimize() + + separator = (pynini.accep("-") | pynini.accep(" ")).optimize() + + key_before_numeral = ( + pynutil.insert("preserve_order: true ") + + pynutil.insert('key_cardinal: "') + + convert_space(devanagari_phrase) + + pynutil.insert('"') + + pynutil.delete(separator) + + insert_space + + pynutil.insert('integer: "') + + roman_numeral_only + + pynutil.insert('"') + ).optimize() + + numeral_before_key = ( + pynutil.insert("preserve_order: true ") + + pynutil.insert('integer: "') + + roman_numeral_only + + pynutil.insert('"') + + pynutil.delete(separator) + + insert_space + + pynutil.insert('key_cardinal: "') + + convert_space(devanagari_phrase) + + pynutil.insert('"') + ).optimize() + + roman_rows = load_labels(get_abs_path("data/roman/roman_to_spoken.tsv")) + numerals_by_len_desc = sorted((n for n, _ in roman_rows), key=len, reverse=True) + + exception_rows = load_labels(get_abs_path("data/roman/roman_ordinal_exceptions.tsv")) + exception_fused_set = {fused for fused, _ in exception_rows} + + suffix_rows_raw = load_labels(get_abs_path("data/ordinal/suffixes.tsv")) + load_labels( + get_abs_path("data/ordinal/suffixes_map.tsv") + ) + + exception_graphs = [] + for fused, spoken_word in exception_rows: + matched_numeral = next(c for c in numerals_by_len_desc if fused.startswith(c)) + exception_graphs.append( + pynutil.insert('integer: "' + matched_numeral + '"') + + insert_space + + pynutil.insert('default_ordinal: "' + spoken_word + '"') + + pynutil.delete(fused) + ) + glued_ordinal_exceptions_graph = pynini.union(*exception_graphs).optimize() + + regular_row_graphs = [] + for numeral, spoken in roman_rows: + for row in suffix_rows_raw: + + suffix_input = row[0] + suffix_output = row[1] if len(row) > 1 else row[0] + + fused = numeral + suffix_input + if fused in exception_fused_set: + continue + spoken_ordinal = spoken + suffix_output + regular_row_graphs.append( + pynutil.insert('integer: "' + numeral + '"') + + insert_space + + pynutil.insert('default_ordinal: "' + spoken_ordinal + '"') + + pynutil.delete(fused) + ) + glued_ordinal_regular_graph = pynini.union(*regular_row_graphs).optimize() + + roman_glued_ordinal_fields = pynini.union( + pynutil.add_weight(glued_ordinal_exceptions_graph, -0.1), + glued_ordinal_regular_graph, + ).optimize() + + roman_glued_ordinal = ( + pynutil.insert("preserve_order: true ") + + roman_glued_ordinal_fields + + pynini.closure( + pynutil.delete(" ") + + insert_space + + pynutil.insert('key_cardinal: "') + + convert_space(devanagari_phrase) + + pynutil.insert('"'), + 0, + 1, + ) + ).optimize() + + graph = pynini.union(key_before_numeral, numeral_before_key, roman_glued_ordinal).optimize() + + self.fst = self.add_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py index 75663ca24..0e8d7fc73 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py +++ b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py @@ -35,6 +35,7 @@ from nemo_text_processing.text_normalization.hi.taggers.money import MoneyFst from nemo_text_processing.text_normalization.hi.taggers.ordinal import OrdinalFst from nemo_text_processing.text_normalization.hi.taggers.punctuation import PunctuationFst +from nemo_text_processing.text_normalization.hi.taggers.roman import RomanFst from nemo_text_processing.text_normalization.hi.taggers.serial import SerialFst from nemo_text_processing.text_normalization.hi.taggers.telephone import TelephoneFst from nemo_text_processing.text_normalization.hi.taggers.time import TimeFst @@ -115,6 +116,9 @@ def __init__( word = WordFst(punctuation=punctuation, deterministic=deterministic) word_graph = word.fst + roman = RomanFst(deterministic=deterministic) + roman_graph = roman.fst + telephone = TelephoneFst() telephone_graph = telephone.fst @@ -137,6 +141,7 @@ def __init__( | pynutil.add_weight(ordinal_graph, 1.1) | pynutil.add_weight(electronic_graph, 1.1) | pynutil.add_weight(serial_graph, 1.11) + | pynutil.add_weight(roman_graph, 1.1) ) punct = pynutil.insert("tokens { ") + pynutil.add_weight(punct_graph, weight=2.1) + pynutil.insert(" }") diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/roman.py b/nemo_text_processing/text_normalization/hi/verbalizers/roman.py new file mode 100644 index 000000000..c28084a77 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/verbalizers/roman.py @@ -0,0 +1,98 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pynini +from pynini.lib import pynutil + +from nemo_text_processing.text_normalization.hi.graph_utils import ( + NEMO_NOT_QUOTE, + GraphFst, + delete_zero_or_one_space, + insert_space, +) +from nemo_text_processing.text_normalization.hi.utils import get_abs_path + + +class RomanFst(GraphFst): + """ + Finite state transducer for verbalizing Roman numerals in Hindi. + roman { preserve_order: true key_cardinal: "भास्कर" integer: "II" } -> भास्कर दो + roman { preserve_order: true key_cardinal: "कक्षा" integer: "XII" } -> कक्षा बारह + roman { preserve_order: true integer: "XII" default_ordinal: "बारहवीं" key_cardinal: "कक्षा" } -> बारहवीं कक्षा + roman { preserve_order: true integer: "IV" default_ordinal: "चौथी" key_cardinal: "कक्षा" } -> चौथी कक्षा + + 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="roman", kind="verbalize", deterministic=deterministic) + + roman_to_spoken = pynini.string_file(get_abs_path("data/roman/roman_to_spoken.tsv")).optimize() + + key_cardinal = ( + pynutil.delete('key_cardinal: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') + ).optimize() + + integer = (pynutil.delete('integer: "') + roman_to_spoken + pynutil.delete('"')).optimize() + + default_ordinal = ( + pynutil.delete('default_ordinal: "') + pynini.closure(NEMO_NOT_QUOTE, 1) + pynutil.delete('"') + ).optimize() + + ignore_integer = ( + pynutil.delete('integer: "') + pynutil.delete(pynini.closure(NEMO_NOT_QUOTE, 1)) + pynutil.delete('"') + ).optimize() + + drop_preserve_order = pynini.closure( + delete_zero_or_one_space + + pynutil.delete("preserve_order:") + + delete_zero_or_one_space + + pynutil.delete("true") + + delete_zero_or_one_space, + 0, + 1, + ).optimize() + + key_first = ( + drop_preserve_order + + key_cardinal + + delete_zero_or_one_space + + insert_space + + integer + + drop_preserve_order + ).optimize() + + numeral_first = ( + drop_preserve_order + + integer + + delete_zero_or_one_space + + insert_space + + key_cardinal + + drop_preserve_order + ).optimize() + + glued_ordinal = ( + drop_preserve_order + + ignore_integer + + delete_zero_or_one_space + + default_ordinal + + pynini.closure(delete_zero_or_one_space + insert_space + key_cardinal, 0, 1) + + drop_preserve_order + ).optimize() + + graph = pynini.union(key_first, numeral_first, glued_ordinal).optimize() + + self.fst = self.delete_tokens(graph).optimize() diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py b/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py index e0fb8d8b5..bd6ca4b5b 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/verbalize.py @@ -21,6 +21,7 @@ from nemo_text_processing.text_normalization.hi.verbalizers.measure import MeasureFst from nemo_text_processing.text_normalization.hi.verbalizers.money import MoneyFst from nemo_text_processing.text_normalization.hi.verbalizers.ordinal import OrdinalFst +from nemo_text_processing.text_normalization.hi.verbalizers.roman import RomanFst from nemo_text_processing.text_normalization.hi.verbalizers.telephone import TelephoneFst from nemo_text_processing.text_normalization.hi.verbalizers.time import TimeFst from nemo_text_processing.text_normalization.hi.verbalizers.whitelist import WhiteListFst @@ -70,6 +71,9 @@ def __init__(self, deterministic: bool = True): electronic = ElectronicFst(deterministic=deterministic) electronic_graph = electronic.fst + roman = RomanFst(deterministic=deterministic) + roman_graph = roman.fst + whitelist_graph = WhiteListFst(deterministic=deterministic).fst graph = ( @@ -84,6 +88,7 @@ def __init__(self, deterministic: bool = True): | whitelist_graph | telephone_graph | electronic_graph + | roman_graph ) self.fst = graph diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt new file mode 100644 index 000000000..00f697a89 --- /dev/null +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt @@ -0,0 +1,23 @@ +भास्कर-II~भास्कर दो +चंद्रयान-III~चंद्रयान तीन +अग्नि-IV~अग्नि चार +श्रेणी-II~श्रेणी दो +कक्षा XII~कक्षा बारह +अध्याय IV~अध्याय चार +भाग III~भाग तीन +खंड V~खंड पाँच +विश्व युद्ध II~विश्व युद्ध दो +विश्व युद्ध-II~विश्व युद्ध दो +प्रथम पंचवर्षीय योजना-I~प्रथम पंचवर्षीय योजना एक +राष्ट्रीय राजमार्ग-IV~राष्ट्रीय राजमार्ग चार +रोहिणी आर एस-I~रोहिणी आर एस एक +पीएसएलवी सी-IV~पीएसएलवी सी चार +ISRO मिशन-III~आई एस आर ओ मिशन तीन +कक्षा XII की परीक्षा~कक्षा बारह की परीक्षा +XIIवीं कक्षा की परीक्षा~बारहवीं कक्षा की परीक्षा +भाग II का सारांश~भाग दो का सारांश +अध्याय IV के प्रश्न~अध्याय चार के प्रश्न +IVथी कक्षा के विद्यार्थी~चौथी कक्षा के विद्यार्थी +XC विद्यार्थी~नब्बे विद्यार्थी +LIII वा गणतंत्र दिन~तिरपन वा गणतंत्र दिन +भाग-XCIX~भाग निन्यानवे \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/test_roman.py b/tests/nemo_text_processing/hi/test_roman.py new file mode 100644 index 000000000..041b88fd1 --- /dev/null +++ b/tests/nemo_text_processing/hi/test_roman.py @@ -0,0 +1,36 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import pytest +from parameterized import parameterized + +from nemo_text_processing.inverse_text_normalization.inverse_normalize import InverseNormalizer +from nemo_text_processing.text_normalization.normalize import Normalizer + +from ..utils import CACHE_DIR, parse_test_case_file + + +class TestRoman: + normalizer = Normalizer( + input_case='cased', lang='hi', cache_dir=CACHE_DIR, overwrite_cache=False, post_process=False + ) + inverse_normalizer = InverseNormalizer(lang='hi', cache_dir=CACHE_DIR, overwrite_cache=False) + + @parameterized.expand(parse_test_case_file('hi/data_text_normalization/test_cases_roman.txt')) + @pytest.mark.run_only_on('CPU') + @pytest.mark.unit + def test_norm(self, test_input, expected): + pred = self.normalizer.normalize(test_input, verbose=False) + assert pred.strip() == expected.strip() diff --git a/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh b/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh index 974dac331..74e1cf9c9 100644 --- a/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh +++ b/tests/nemo_text_processing/hi/test_sparrowhawk_normalization.sh @@ -57,10 +57,10 @@ testTNSerial() { runtest $input } -#testTNRoman() { -# input=$PROJECT_DIR/en/data_text_normalization/test_cases_roman.txt -# runtest $input -#} +testTNRoman() { + input=$PROJECT_DIR/hi/data_text_normalization/test_cases_roman.txt + runtest $input +} testTNElectronic() { input=$PROJECT_DIR/hi/data_text_normalization/test_cases_electronic.txt From 8205ab26c1b5ee925c4d4a1cb9e0109748c40894 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Mon, 13 Jul 2026 19:35:50 +0530 Subject: [PATCH 08/10] reducing uncommon entries in electronic class tsv data files (#458) Signed-off-by: Shreyas Pawar --- Jenkinsfile | 2 +- .../hi/data/electronic/common_words.tsv | 106 +----------------- .../hi/data/electronic/domain.tsv | 18 +-- .../hi/data/electronic/file_extensions.tsv | 19 +--- .../hi/data/electronic/server_name.tsv | 41 +------ .../test_cases_electronic.txt | 18 +-- 6 files changed, 16 insertions(+), 188 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 039504611..a6abeb361 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-25-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-10-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv index ef3cf696f..a9f8e937d 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv @@ -14,17 +14,8 @@ page पेज pages पेजेस user यूज़र users यूज़र्स -Users यूज़र्स -User यूज़र -Desktop डेस्कटॉप -Documents डॉक्युमेंट्स -Downloads डाउनलोड्स -Music म्यूज़िक -Pictures पिक्चर्स -Videos वीडियोज़ admin एडमिन app ऐप -faq एफ ए क्यू help हेल्प terms टर्म्स privacy प्राइवेसी @@ -44,9 +35,6 @@ video वीडियो videos वीडियोज़ desktop डेस्कटॉप documents डॉक्युमेंट्स -master मास्टर -blob ब्लॉब -tree ट्री tests टेस्ट्स test टेस्ट config कॉन्फ़िग @@ -55,110 +43,20 @@ profile प्रोफ़ाइल account अकाउंट web वेब email ई मेल -laptop लैपटॉप mobile मोबाइल phone फोन phones फोन्स online ऑनलाइन -courses कोर्सेज़ -learn लर्न -learning लर्निंग -university यूनिवर्सिटी -academy अकेडमी domain डोमेन domains डोमेन्स -analysis अनैलिसिस -play प्ले -maps मैप्स -drive ड्राइव -cloud क्लाउड -services सर्विसेज़ -india इंडिया -screenshot स्क्रीनशॉट -zoom ज़ूम -audacity ऑडेसिटी -coursera कोर्सेरा -apache अपाची -kernel कर्नल -bin बिन -var वार -home होम -activate एक्टिवेट -sites साइट्स -available अवेलेबल -enabled इनेबल्ड -backups बैकअप्स -temp टेम्प -secure सेक्योर -real रियल data डेटा -work वर्क -survey सर्वे -files फाइल्स file फ़ाइल -chapter चैप्टर -python पाइथन +files फाइल्स audio ऑडियो -sample सैंपल -templates टेम्पलेट्स -impress इंप्रेस -office ऑफिस -libreoffice लिब्रे ऑफिस -express एक्सप्रेस -scribe स्क्राइब -transcription ट्रांसक्रिप्शन software सॉफ्टवेयर -teams टीम्स -school स्कूल -space स्पेस -green ग्रीन -brown ब्राउन -white व्हाइट -black ब्लैक homepage होमपेज content कंटेन्ट default डिफ़ॉल्ट -foodhealth फूड हेल्थ -workflows वर्कफ्लोज़ world वर्ल्ड list लिस्ट -and एंड -LICENSE लाइसेंस -license लाइसेंस -tag टैग -blogs ब्लॉग्स -bath बाथ -ward वार्ड -banks बैंक्स -Audio ऑडियो -dean डीन -rice राइस -honda होंडा -ford फोर्ड -house हाउस -bharat भरत -rich रिच -cook कुक -lane लेन -knight नाइट -moody मूडी -wise वाइज़ -shields शील्ड्स -puppy पप्पी -recipe रेसिपी -hall हॉल -mason मेसन -king किंग -fry फ्राई -flowers फ्लावर्स -assam आसाम -grace ग्रेस -bishop बिशप -woods वुड्स -brewer ब्रूअर -cannon कैनन -saute सौटे -pope पोप -robin रॉबिन -price प्राइस -address एड्रेस \ No newline at end of file +license लाइसेंस \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv index 2b4f70f03..bdb1a902d 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv @@ -3,36 +3,22 @@ org ऑर्ग net नेट edu ई डी यू gov जी ओ वी -biz बिज़ -info इन्फो in इन co सी ओ io आई ओ ai ए आई uk यू के us यू एस -ru आर यू -de डी ई -fr एफ आर -jp जे पी -cn सी एन au ए यू ca सी ए -br बी आर ac ए सी res आर ई एस nic एन आई सी ernet ई आर नेट -mil एम आई एल -int आई एन टी tv टी वी me एम ई tech टेक dev डी ई वी app ऐप -xyz एक्स वाई ज़ेड -online ऑनलाइन -store स्टोर -blog ब्लॉग -site साइट -pro प्रो \ No newline at end of file +biz बिज़ +info इन्फो \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv index 2c261a062..e768c3838 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv @@ -12,7 +12,6 @@ pptx पी पी टी एक्स csv सी एस वी txt टी एक्स टी html एच टी एम एल -htm एच टी एम xml एक्स एम एल json जे एस ओ एन css सी एस एस @@ -30,22 +29,6 @@ mkv एम के वी mov एम ओ वी wav डब्ल्यू ए वी svg एस वी जी -ico आई सी ओ apk ए पी के exe ई एक्स ई -dmg डी एम जी -iso आई एस ओ -sql एस क्यू एल -log एल ओ जी -bak बी ए के -JPG जे पी जी -JPEG जे पी ई जी -PNG पी एन जी -PDF पी डी एफ -DOC डी ओ सी -DOCX डी ओ सी एक्स -CSV सी एस वी -TXT टी एक्स टी -HTML एच टी एम एल -MP3 एम पी तीन -MP4 एम पी चार \ No newline at end of file +sql एस क्यू एल \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv index 8d986bfd8..d0029ee5c 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv @@ -22,43 +22,4 @@ nvidia एनविडिया intel इंटेल adobe अडोब wordpress वर्डप्रेस -blogger ब्लॉगर -mentalfloss मेंटल फ्लॉस -placekitten प्लेस किटन -dummyimage डमी इमेज -reliablesoft रिलाएबल सॉफ्ट -ebay ई बे -moz मोज़ -mozilla मॉज़िला -genius जीनियस -groupon ग्रुप ऑन -gutenberg गुटेनबर्ग -recipepuppy रेसिपी पप्पी -buyagift बाय अ गिफ्ट -webmd वेब एम डी -researchgate रिसर्च गेट -afternic आफ्टर निक -hipolabs हिपो लैब्स -licindia एल आई सी इंडिया -placeimg प्लेस आई एम जी -codecademy कोड कैडेमी -skillshop स्किलशॉप -skillshare स्किल शेयर -udemy यूडेमी -masterclass मास्टरक्लास -amity एमिटी -sharda शारदा -universities यूनिवर्सिटीज़ -mcdonald मैक्डॉनल्ड -southmountaincc साउथ माउन्टेन सी सी -academyart अकेडमी आर्ट -bryanuniversity ब्रायन यूनिवर्सिटी -centralaz सेंट्रल ए ज़ेड -alaska अलास्का -phoenix फीनिक्स -phoenixcollege फीनिक्स कॉलेज -maricopa मैरीकोपा -prescott प्रेसकॉट -azwestern ए ज़ेड वेस्टर्न -poetry पोएट्री -harvard हावर्ड \ No newline at end of file +blogger ब्लॉगर \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt index f9a175794..fd1bf459d 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt @@ -5,7 +5,7 @@ google.com~गूगल डॉट कॉम kumaar.org~के यू एम ए ए आर डॉट ऑर्ग kumaar.info~के यू एम ए ए आर डॉट इन्फो kumar@gmail.com~के यू एम ए आर एट जीमेल डॉट कॉम -robin@hotmail.com~रॉबिन एट हॉटमेल डॉट कॉम +robin@hotmail.com~आर ओ बी आई एन एट हॉटमेल डॉट कॉम kapil@live.com~के ए पी आई एल एट लाइव डॉट कॉम sneha@live.com~एस एन ई एच ए एट लाइव डॉट कॉम mayank@google.com~एम ए वाई ए एन के एट गूगल डॉट कॉम @@ -18,7 +18,7 @@ kristen11@hotmail.com~के आर आई एस टी ई एन एक ए dsmith@yahoo.com~डी एस एम आई टी एच एट याहू डॉट कॉम hgarza@gmail.com~एच जी ए आर ज़ेड ए एट जीमेल डॉट कॉम qhill@yahoo.com~क्यू एच आई एल एल एट याहू डॉट कॉम -green-turner.org~ग्रीन हाइफ़न टी यू आर एन ई आर डॉट ऑर्ग +green-turner.org~जी आर ई ई एन हाइफ़न टी यू आर एन ई आर डॉट ऑर्ग sharma-badami.com~एस एच ए आर एम ए हाइफ़न बी ए डी ए एम आई डॉट कॉम osborne-gross.com~ओ एस बी ओ आर एन ई हाइफ़न जी आर ओ एस एस डॉट कॉम lucero-stevenson.net~एल यू सी ई आर ओ हाइफ़न एस टी ई वी ई एन एस ओ एन डॉट नेट @@ -29,7 +29,7 @@ https://amazon.com/~एच टी टी पी एस कोलन फॉरव www.google.com~डब्ल्यू डब्ल्यू डब्ल्यू डॉट गूगल डॉट कॉम https://www.ndtv.com~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट एन डी टी वी डॉट कॉम https://www.rbi.org.in/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट आर बी आई डॉट ऑर्ग डॉट इन फॉरवर्ड स्लैश -https://www.amity.edu~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट एमिटी डॉट ई डी यू +https://www.amity.edu~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट ए एम आई टी वाई डॉट ई डी यू https://example.com/blog/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश ब्लॉग फॉरवर्ड स्लैश https://example.com/about.html~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश अबाउट डॉट एच टी एम एल https://example.com/search.php~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश सर्च डॉट पी एच पी @@ -38,13 +38,13 @@ http://gcu.edu~एच टी टी पी कोलन फॉरवर्ड http://pima.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश पी आई एम ए डॉट ई डी यू bamu.nic.in/~बी ए एम यू डॉट एन आई सी डॉट इन फॉरवर्ड स्लैश bieap.gov.in/~बी आई ई ए पी डॉट जी ओ वी डॉट इन फॉरवर्ड स्लैश -www.sharda.ac.in~डब्ल्यू डब्ल्यू डब्ल्यू डॉट शारदा डॉट ए सी डॉट इन -C:\Users\HP\Desktop\~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डेस्कटॉप बैकवर्ड स्लैश -C:\Users\HP\Downloads\~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डाउनलोड्स बैकवर्ड स्लैश -C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डॉक्युमेंट्स बैकवर्ड स्लैश ज़ेड ओ ओ एम +www.sharda.ac.in~डब्ल्यू डब्ल्यू डब्ल्यू डॉट एस एच ए आर डी ए डॉट ए सी डॉट इन +C:\Users\HP\Desktop\~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ई एस के टी ओ पी बैकवर्ड स्लैश +C:\Users\HP\Downloads\~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ओ डब्ल्यू एन एल ओ ए डी एस बैकवर्ड स्लैश +C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ओ सी यू एम ई एन टी एस बैकवर्ड स्लैश ज़ेड ओ ओ एम /home/desktop~फॉरवर्ड स्लैश होम फॉरवर्ड स्लैश डेस्कटॉप -/etc/apache~फॉरवर्ड स्लैश ई टी सी फॉरवर्ड स्लैश अपाची -/var/www~फॉरवर्ड स्लैश वार फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू +/etc/apache~फॉरवर्ड स्लैश ई टी सी फॉरवर्ड स्लैश ए पी ए सी एच ई +/var/www~फॉरवर्ड स्लैश वी ए आर फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू 192.168.1.1~एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक 10.0.0.1~एक शून्य डॉट शून्य डॉट शून्य डॉट एक 83.54.245.61~आठ तीन डॉट पाँच चार डॉट दो चार पाँच डॉट छह एक From 7b4356ce895a8ed40e0c6f207c26fc557ed9b1a8 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Wed, 29 Jul 2026 01:57:18 +0530 Subject: [PATCH 09/10] Hi TN: Address, Electronic, Serial, and Cardinal FST Optimization and Bug Fix (#461) * Hi TN Address, Electronic, Serial, and Cardinal FST Optimizations and Bug Fixes Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review feedback changes Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feedback review changes for address and electronic Signed-off-by: Shreyas Pawar * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Signed-off-by: Shreyas Pawar Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- Jenkinsfile | 2 +- .../hi/data/address/context.tsv | 3 +- .../hi/data/address/en_to_hi_mapping.tsv | 2 - .../hi/data/electronic/common_words.tsv | 62 --------- .../hi/data/electronic/domain.tsv | 48 +++---- .../hi/data/electronic/file_extensions.tsv | 68 ++++----- .../hi/data/electronic/protocols.tsv | 10 +- .../hi/data/electronic/server_name.tsv | 25 ---- .../text_normalization/hi/taggers/cardinal.py | 38 +++++- .../hi/taggers/electronic.py | 24 +--- .../text_normalization/hi/taggers/measure.py | 129 +++++++++--------- .../text_normalization/hi/taggers/serial.py | 35 +++-- .../hi/taggers/tokenize_and_classify.py | 10 +- .../hi/verbalizers/electronic.py | 81 ++++------- .../test_cases_address.txt | 67 +++++---- .../test_cases_cardinal.txt | 11 ++ .../test_cases_decimal.txt | 4 + .../test_cases_electronic.txt | 106 +++++++------- .../test_cases_money.txt | 18 ++- .../test_cases_roman.txt | 2 +- .../test_cases_serial.txt | 12 +- .../test_cases_word.txt | 6 +- 22 files changed, 366 insertions(+), 397 deletions(-) delete mode 100644 nemo_text_processing/text_normalization/hi/data/address/en_to_hi_mapping.tsv delete mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv delete mode 100644 nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv diff --git a/Jenkinsfile b/Jenkinsfile index a6abeb361..fd1611f6e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,7 +28,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/04-23-26-0' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-10-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-28-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/address/context.tsv b/nemo_text_processing/text_normalization/hi/data/address/context.tsv index 9faadaa3b..d57bfd7d3 100644 --- a/nemo_text_processing/text_normalization/hi/data/address/context.tsv +++ b/nemo_text_processing/text_normalization/hi/data/address/context.tsv @@ -44,5 +44,4 @@ वेस्ट सामने पीछे -वीया -आर डी \ No newline at end of file +वीया \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/address/en_to_hi_mapping.tsv b/nemo_text_processing/text_normalization/hi/data/address/en_to_hi_mapping.tsv deleted file mode 100644 index 15929b547..000000000 --- a/nemo_text_processing/text_normalization/hi/data/address/en_to_hi_mapping.tsv +++ /dev/null @@ -1,2 +0,0 @@ -street स्ट्रीट -southern सदर्न \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv deleted file mode 100644 index a9f8e937d..000000000 --- a/nemo_text_processing/text_normalization/hi/data/electronic/common_words.tsv +++ /dev/null @@ -1,62 +0,0 @@ -about अबाउट -blog ब्लॉग -home होम -index इंडेक्स -login लॉगिन -register रजिस्टर -search सर्च -tags टैग्स -category केटेगरी -categories केटेगरीज़ -post पोस्ट -posts पोस्ट्स -page पेज -pages पेजेस -user यूज़र -users यूज़र्स -admin एडमिन -app ऐप -help हेल्प -terms टर्म्स -privacy प्राइवेसी -contact कॉन्टैक्ट -main मेन -explore एक्सप्लोर -wiki विकी -docs डॉक्स -download डाउनलोड -downloads डाउनलोड्स -upload अपलोड -uploads अपलोड्स -photos फ़ोटोज़ -images इमेजेज़ -music म्यूज़िक -video वीडियो -videos वीडियोज़ -desktop डेस्कटॉप -documents डॉक्युमेंट्स -tests टेस्ट्स -test टेस्ट -config कॉन्फ़िग -settings सेटिंग्स -profile प्रोफ़ाइल -account अकाउंट -web वेब -email ई मेल -mobile मोबाइल -phone फोन -phones फोन्स -online ऑनलाइन -domain डोमेन -domains डोमेन्स -data डेटा -file फ़ाइल -files फाइल्स -audio ऑडियो -software सॉफ्टवेयर -homepage होमपेज -content कंटेन्ट -default डिफ़ॉल्ट -world वर्ल्ड -list लिस्ट -license लाइसेंस \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv index bdb1a902d..dccb5dd90 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/domain.tsv @@ -1,24 +1,24 @@ -com कॉम -org ऑर्ग -net नेट -edu ई डी यू -gov जी ओ वी -in इन -co सी ओ -io आई ओ -ai ए आई -uk यू के -us यू एस -au ए यू -ca सी ए -ac ए सी -res आर ई एस -nic एन आई सी -ernet ई आर नेट -tv टी वी -me एम ई -tech टेक -dev डी ई वी -app ऐप -biz बिज़ -info इन्फो \ No newline at end of file +com +org +net +edu +gov +in +co +io +ai +uk +us +au +ca +ac +res +nic +ernet +tv +me +tech +dev +app +biz +info diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv index e768c3838..7283febf5 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/file_extensions.tsv @@ -1,34 +1,34 @@ -jpg जे पी जी -jpeg जे पी ई जी -png पी एन जी -gif जी आई एफ -pdf पी डी एफ -doc डी ओ सी -docx डी ओ सी एक्स -xls एक्स एल एस -xlsx एक्स एल एस एक्स -ppt पी पी टी -pptx पी पी टी एक्स -csv सी एस वी -txt टी एक्स टी -html एच टी एम एल -xml एक्स एम एल -json जे एस ओ एन -css सी एस एस -js जे एस -py पी वाई -java जावा -cpp सी पी पी -zip ज़िप -rar आर ए आर -tar टी ए आर -mp3 एम पी तीन -mp4 एम पी चार -avi ए वी आई -mkv एम के वी -mov एम ओ वी -wav डब्ल्यू ए वी -svg एस वी जी -apk ए पी के -exe ई एक्स ई -sql एस क्यू एल \ No newline at end of file +jpg +jpeg +png +gif +pdf +doc +docx +xls +xlsx +ppt +pptx +csv +txt +html +xml +json +css +js +py +java +cpp +zip +rar +tar +mp3 +mp4 +avi +mkv +mov +wav +svg +apk +exe +sql \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/protocols.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/protocols.tsv index 627781003..dd897a4b6 100644 --- a/nemo_text_processing/text_normalization/hi/data/electronic/protocols.tsv +++ b/nemo_text_processing/text_normalization/hi/data/electronic/protocols.tsv @@ -1,5 +1,5 @@ -https एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश -http एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश -www डब्ल्यू डब्ल्यू डब्ल्यू डॉट -httpswww एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट -httpwww एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट \ No newline at end of file +https https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश +http http कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश +www www डॉट +httpswww https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश www डॉट +httpwww http कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश www डॉट diff --git a/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv b/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv deleted file mode 100644 index d0029ee5c..000000000 --- a/nemo_text_processing/text_normalization/hi/data/electronic/server_name.tsv +++ /dev/null @@ -1,25 +0,0 @@ -gmail जीमेल -yahoo याहू -hotmail हॉटमेल -outlook आउटलुक -live लाइव -google गूगल -microsoft माइक्रोसॉफ्ट -facebook फ़ेसबुक -twitter ट्विटर -instagram इंस्टाग्राम -linkedin लिंक्डइन -youtube यूट्यूब -amazon अमेज़ोन -wikipedia विकिपीडिया -github गिटहब -reddit रेडिट -netflix नेटफ्लिक्स -spotify स्पॉटिफाई -apple एप्पल -samsung सैमसंग -nvidia एनविडिया -intel इंटेल -adobe अडोब -wordpress वर्डप्रेस -blogger ब्लॉगर \ No newline at end of file diff --git a/nemo_text_processing/text_normalization/hi/taggers/cardinal.py b/nemo_text_processing/text_normalization/hi/taggers/cardinal.py index c29ccaa59..dd4611010 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/cardinal.py +++ b/nemo_text_processing/text_normalization/hi/taggers/cardinal.py @@ -18,6 +18,7 @@ from nemo_text_processing.text_normalization.hi.graph_utils import ( NEMO_ALL_DIGIT, NEMO_ALL_ZERO, + NEMO_DIGIT, GraphFst, insert_space, ) @@ -348,11 +349,46 @@ def create_larger_number_graph(digit_graph, suffix, zeros_counts, sub_graph): ) cardinal_with_leading_zeros = pynutil.add_weight(cardinal_with_leading_zeros, 0.5) + # Handle large numbers written with digit-group separators. + delete_separator = pynutil.delete(",") + two_digits = NEMO_ALL_DIGIT + NEMO_ALL_DIGIT + three_digits = NEMO_ALL_DIGIT + NEMO_ALL_DIGIT + NEMO_ALL_DIGIT + # Indian grouping: 1-2 leading digits, groups of 2, final group of 3. + indian_grouping = ( + pynini.closure(NEMO_ALL_DIGIT, 1, 2) + + pynini.closure(delete_separator + two_digits) + + delete_separator + + three_digits + ) + # International grouping: 1-3 leading digits, one or more groups of 3. + western_grouping = pynini.closure(NEMO_ALL_DIGIT, 1, 3) + pynini.closure(delete_separator + three_digits, 1) + strip_separators = (indian_grouping | western_grouping).optimize() + cardinal_with_separators = pynini.compose(strip_separators, graph_without_leading_zeros).optimize() + # Full graph including leading zeros - for standalone cardinal matching - final_graph = graph_without_leading_zeros | cardinal_with_leading_zeros + final_graph = graph_without_leading_zeros | cardinal_with_leading_zeros | cardinal_with_separators optional_minus_graph = pynini.closure(pynutil.insert("negative: ") + pynini.cross("-", "\"true\" "), 0, 1) + # --- Centralized logic for Address & Serial classes --- + # 1-3 digit groups read as cardinals, 4+ digits read digit-by-digit + limited_cardinal_graph = (self.digit | self.zero | self.teens_and_ties | self.graph_hundreds).optimize() + + any_digit = pynini.union( + NEMO_DIGIT, + pynini.project( + pynini.union( + pynini.string_file(get_abs_path("data/numbers/digit.tsv")), + pynini.string_file(get_abs_path("data/numbers/zero.tsv")), + ), + "input", + ), + ).optimize() + + digitwise_4plus = pynini.compose(any_digit**4 + pynini.closure(any_digit), self.single_digits_graph).optimize() + + self.code_num_graph = (limited_cardinal_graph | digitwise_4plus).optimize() + self.final_graph = final_graph.optimize() final_graph = optional_minus_graph + pynutil.insert("integer: \"") + self.final_graph + pynutil.insert("\"") final_graph = self.add_tokens(final_graph) diff --git a/nemo_text_processing/text_normalization/hi/taggers/electronic.py b/nemo_text_processing/text_normalization/hi/taggers/electronic.py index a309ec089..e1b93835e 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/taggers/electronic.py @@ -22,7 +22,7 @@ class ElectronicFst(GraphFst): """ Finite state transducer for classifying electronic: as URLs, email addresses, file paths, - IP addresses, domains, chemical formulas, and alphanumeric codes. + IP addresses, domains, and chemical formulas. e.g. kumar@gmail.com -> tokens { electronic { username: "kumar" domain: "gmail.com" } } e.g. https://google.com/ -> tokens { electronic { protocol: "https" domain: "google.com/" } } e.g. C:\\Users\\HP\\Desktop -> tokens { electronic { path: "C:\\Users\\HP\\Desktop" } } @@ -157,22 +157,13 @@ def __init__(self, deterministic: bool = True): unbalanced_trailing = pynini.intersect(no_open, ends_with_close) valid_chemical = pynini.difference(raw_chemical, unbalanced_trailing).optimize() - chemical_formula = pynutil.insert("domain: \"") + valid_chemical + pynutil.insert("\"") + # Recognise a chemical formula only when it uses subscript notation + chem_sigma = pynini.closure(NEMO_ALPHA | NEMO_DIGIT | subscript_digit | chemical_symbols) + contains_subscript = chem_sigma + subscript_digit + chem_sigma + valid_chemical = pynini.intersect(valid_chemical, contains_subscript).optimize() - alnum_seg = pynini.closure(NEMO_ALPHA | NEMO_DIGIT, 1) - separator = pynini.accep("-") | pynini.accep(".") - alphanumeric_pattern = alnum_seg + pynini.closure(separator + alnum_seg) - - alnum_hyp_dot_sigma = pynini.closure(NEMO_ALPHA | NEMO_DIGIT | pynini.accep("-") | pynini.accep(".")) - - contains_alpha = alnum_hyp_dot_sigma + NEMO_ALPHA + alnum_hyp_dot_sigma - contains_digit = alnum_hyp_dot_sigma + NEMO_DIGIT + alnum_hyp_dot_sigma - - alphanumeric_code_fst = pynini.intersect( - pynini.intersect(alphanumeric_pattern, contains_alpha), contains_digit - ).optimize() - - alphanumeric_code = pynutil.insert("domain: \"") + alphanumeric_code_fst + pynutil.insert("\"") + # Chemical formulas carry a dedicated tag so the verbalizer can spell element + chemical_formula = pynutil.insert("fragment_id: \"") + valid_chemical + pynutil.insert("\"") graph = ( pynutil.add_weight(url_graph, 1.0) @@ -184,7 +175,6 @@ def __init__(self, deterministic: bool = True): | pynutil.add_weight(combined_domain, 1.1) | pynutil.add_weight(file_with_extension, 1.1) | pynutil.add_weight(chemical_formula, 1.2) - | pynutil.add_weight(alphanumeric_code, 1.2) ) self.graph = graph.optimize() diff --git a/nemo_text_processing/text_normalization/hi/taggers/measure.py b/nemo_text_processing/text_normalization/hi/taggers/measure.py index e18111696..83dc1be4e 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/measure.py +++ b/nemo_text_processing/text_normalization/hi/taggers/measure.py @@ -28,12 +28,13 @@ HI_SADHE, HI_SAVVA, HYPHEN, - INPUT_LOWER_CASED, LOWERCASE_X, + MIN_NEG_WEIGHT, NEMO_CHAR, NEMO_DIGIT, NEMO_HI_DIGIT, NEMO_NOT_SPACE, + NEMO_SIGMA, NEMO_SPACE, NEMO_WHITE_SPACE, ONE_POINT_FIVE, @@ -50,11 +51,15 @@ from nemo_text_processing.text_normalization.hi.utils import get_abs_path digit = pynini.string_file(get_abs_path("data/numbers/digit.tsv")) -# Load both Hindi (Devanagari) and English (Arabic) number mappings -teens_ties_hi = pynini.string_file(get_abs_path("data/numbers/teens_and_ties.tsv")) -teens_ties_en = pynini.string_file(get_abs_path("data/numbers/teens_and_ties_en.tsv")) -teens_ties = pynini.union(teens_ties_hi, teens_ties_en) -teens_and_ties = pynutil.add_weight(teens_ties, -0.1) + +# Shared Address Maps +zero = pynini.string_file(get_abs_path("data/numbers/zero.tsv")) +telephone_number = pynini.string_file(get_abs_path("data/telephone/number.tsv")) +states_map = pynini.string_file(get_abs_path("data/address/states.tsv")) +cities_map = pynini.string_file(get_abs_path("data/address/cities.tsv")) +special_characters_map = pynini.string_file(get_abs_path("data/address/special_characters.tsv")) +letters_map = pynini.string_file(get_abs_path("data/address/letters.tsv")) +context_map = pynini.string_file(get_abs_path("data/address/context.tsv")) class MeasureFst(GraphFst): @@ -71,32 +76,27 @@ class MeasureFst(GraphFst): for False multiple transduction are generated (used for audio-based normalization) """ - def get_structured_address_graph(self, ordinal: GraphFst, input_case: str): + def get_structured_address_graph(self, cardinal: GraphFst, ordinal: GraphFst, input_case: str): """ Minimal address tagger for state/city + pincode patterns only. - Highly optimized for performance. Examples: "मुंबई ८८४४०४" -> "मुंबई आठ आठ चार चार शून्य चार" "गोवा १२३४५६" -> "गोवा एक दो तीन चार पाँच छह" + "100 फीट रोड, चेन्नई" -> "एक सौ फीट रोड, चेन्नई" """ # State/city keywords - states = pynini.string_file(get_abs_path("data/address/states.tsv")) - cities = pynini.string_file(get_abs_path("data/address/cities.tsv")) - state_city_names = pynini.union(states, cities).optimize() - - # Digit mappings - num_token = ( - digit - | pynini.string_file(get_abs_path("data/numbers/zero.tsv")) - | pynini.string_file(get_abs_path("data/telephone/number.tsv")) - ).optimize() - # Pincode (6 digits) + state_city_names = pynini.union(states_map, cities_map).optimize() + + # Digit mappings (shared maps loaded once at module level) + num_token = (digit | zero | telephone_number).optimize() + + # Pincode (6 digits) -> always digit-by-digit (length >= 4) pincode = (num_token + pynini.closure(insert_space + num_token, 5, 5)).optimize() - # Street number (1-4 digits) - street_num = (num_token + pynini.closure(insert_space + num_token, 0, 3)).optimize() + # Street number: Use centralized digit-by-digit logic from cardinal + street_num = cardinal.code_num_graph # Text: words with trailing separator (comma? + space) any_digit = pynini.union(NEMO_HI_DIGIT, NEMO_DIGIT).optimize() @@ -107,7 +107,9 @@ def get_structured_address_graph(self, ordinal: GraphFst, input_case: str): # Separator: optional comma followed by mandatory space sep = pynini.closure(pynini.accep(COMMA), 0, 1) + pynini.accep(NEMO_SPACE) word_with_sep = word + sep - text = pynini.closure(word_with_sep, 0, 5).optimize() + # Consume inline address numbers using the same 1-3 (cardinal) / 4+ (digit-by-digit) rule + num_with_sep = street_num + sep + text = pynini.closure(pynini.union(word_with_sep, num_with_sep), 0, 5).optimize() # Pattern: [street_num + sep]? text state/city [space pincode] pattern = ( @@ -124,34 +126,26 @@ def get_structured_address_graph(self, ordinal: GraphFst, input_case: str): ) return pynutil.add_weight(graph, 1.0).optimize() - def get_address_graph(self, ordinal: GraphFst, input_case: str): + def get_address_graph(self, cardinal: GraphFst, ordinal: GraphFst, serial: GraphFst, input_case: str): """ - Address tagger that converts digits/hyphens/slashes character-by-character - when address context keywords are present. - English words and ordinals are converted to Hindi transliterations. + Address tagger that fires when address context keywords are present. Examples: - "७०० ओक स्ट्रीट" -> "सात शून्य शून्य ओक स्ट्रीट" - "६६-४ पार्क रोड" -> "छह छह हाइफ़न चार पार्क रोड" + "७०० ओक स्ट्रीट" -> "सात सौ ओक स्ट्रीट" + "६६-४ पार्क रोड" -> "छियासठ हाइफ़न चार पार्क रोड" + "593988" (6-digit pincode) -> "पाँच नौ तीन नौ आठ आठ" + "32A नाज़ प्लाज़ा" -> "बत्तीस ए नाज़ प्लाज़ा" """ + # Retain internal weights of ordinal graph ordinal_graph = ordinal.graph # Alphanumeric to word mappings (digits, special characters, telephone digits) - char_to_word = ( - digit - | pynini.string_file(get_abs_path("data/numbers/zero.tsv")) - | pynini.string_file(get_abs_path("data/address/special_characters.tsv")) - | pynini.string_file(get_abs_path("data/telephone/number.tsv")) + char_to_word = (digit | zero | special_characters_map | telephone_number).optimize() + letter_to_word = capitalized_input_graph(letters_map) + # Identity acceptor for keywords (Devanagari/English) to prevent unintended rewrites/transliteration + address_keywords = pynini.project( + capitalized_input_graph(context_map), + "input", ).optimize() - letter_to_word = pynini.string_file(get_abs_path("data/address/letters.tsv")) - letter_to_word = capitalized_input_graph(letter_to_word) - address_keywords_hi = pynini.string_file(get_abs_path("data/address/context.tsv")) - - # English address keywords with Hindi translation (case-insensitive) - en_to_hi_map = pynini.string_file(get_abs_path("data/address/en_to_hi_mapping.tsv")) - if input_case != INPUT_LOWER_CASED: - en_to_hi_map = capitalized_input_graph(en_to_hi_map) - address_keywords_en = pynini.project(en_to_hi_map, "input") - address_keywords = pynini.union(address_keywords_hi, address_keywords_en) # Alphanumeric processing: treat digits, letters, and -/ as convertible tokens single_digit = pynini.union(NEMO_DIGIT, NEMO_HI_DIGIT).optimize() @@ -162,24 +156,37 @@ def get_address_graph(self, ordinal: GraphFst, input_case: str): NEMO_CHAR, pynini.union(NEMO_WHITE_SPACE, convertible_char, pynini.accep(COMMA)) ).optimize() - # Token processors with weights: prefer ordinals and known English→Hindi words - # Delete space before comma to avoid Sparrowhawk "sil" issue - comma_processor = pynutil.add_weight(delete_space + pynini.accep(COMMA), 0.0) - ordinal_processor = pynutil.add_weight(insert_space + ordinal_graph, -5.0) - english_word_processor = pynutil.add_weight(insert_space + en_to_hi_map, -3.0) - letter_processor = pynutil.add_weight(insert_space + pynini.compose(single_letter, letter_to_word), 0.5) - digit_char_processor = pynutil.add_weight(insert_space + pynini.compose(convertible_char, char_to_word), 0.0) - other_word_processor = pynutil.add_weight(insert_space + pynini.closure(non_space_char, 1), 0.1) + comma_processor = delete_space + pynini.accep(COMMA) + ordinal_processor = insert_space + ordinal_graph + latin_word = single_letter + pynini.closure(single_letter, 1) + english_word_processor = insert_space + latin_word + letter_processor = insert_space + pynini.compose(single_letter, letter_to_word) + special_char_processor = insert_space + pynini.compose(special_chars, char_to_word) + other_word_processor = insert_space + pynini.closure(non_space_char, 1) + + code_processor = insert_space + serial.mixed_alphanum_graph + + # Pure numeric runs: 1-3 digits read as cardinal, 4+ digits read digit-by-digit + number_run = pynini.compose(pynini.closure(single_digit, 1), cardinal.code_num_graph).optimize() + + # A positive weight of 0.5 penalizes multiple uses of this arc. This forces the FST to consume all contiguous digits as ONE run (cost 0.5) instead of splitting "625" into "6" and "25" (cost 1.0). + number_run_processor = insert_space + number_run + + # A tiny positive penalty prevents multiple uses of this arc, forcing the FST to consume contiguous digits as ONE run instead of aggressively splitting them. + number_run_processor = insert_space + number_run token_processor = ( - ordinal_processor - | english_word_processor - | letter_processor - | digit_char_processor - | pynini.accep(NEMO_SPACE) + pynini.accep(NEMO_SPACE) | comma_processor - | other_word_processor + | special_char_processor + | code_processor + | pynutil.add_weight(ordinal_processor, MIN_NEG_WEIGHT) + | pynutil.add_weight(number_run_processor, 0.5) # Keeps numbers together + | letter_processor + | english_word_processor + | pynutil.add_weight(other_word_processor, 0.1) # Keeps Hindi words together ).optimize() + full_string_processor = pynini.closure(token_processor, 1).optimize() # Window-based context matching around address keywords for robust detection @@ -201,9 +208,9 @@ def get_address_graph(self, ordinal: GraphFst, input_case: str): + address_graph + pynutil.insert('" } preserve_order: true') ) - return pynutil.add_weight(graph, 1.05).optimize() + return graph.optimize() - def __init__(self, cardinal: GraphFst, decimal: GraphFst, ordinal: GraphFst, input_case: str): + def __init__(self, cardinal: GraphFst, decimal: GraphFst, ordinal: GraphFst, serial: GraphFst, input_case: str): super().__init__(name="measure", kind="classify") cardinal_graph = ( @@ -451,8 +458,8 @@ def __init__(self, cardinal: GraphFst, decimal: GraphFst, ordinal: GraphFst, inp + pynutil.insert("\"") ) - address_graph = self.get_address_graph(ordinal, input_case) - structured_address_graph = self.get_structured_address_graph(ordinal, input_case) + address_graph = self.get_address_graph(cardinal, ordinal, serial, input_case) + structured_address_graph = self.get_structured_address_graph(cardinal, ordinal, input_case) graph = ( pynutil.add_weight(graph_decimal, 0.1) diff --git a/nemo_text_processing/text_normalization/hi/taggers/serial.py b/nemo_text_processing/text_normalization/hi/taggers/serial.py index d7433e583..3f2244dd8 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/serial.py +++ b/nemo_text_processing/text_normalization/hi/taggers/serial.py @@ -20,6 +20,7 @@ NEMO_DIGIT, NEMO_NOT_SPACE, NEMO_SIGMA, + TO_LOWER, GraphFst, convert_space, ) @@ -38,9 +39,9 @@ class SerialFst(GraphFst): e.g. 2^2 -> tokens { name: "दो स्क्वेर्ड" } e.g. 2^4 -> tokens { name: "दो टु द पावर चार" } e.g. 1-800-555 -> tokens { name: "एक-आठ सौ-पाँच सौ पचपन" } - - Note: Pure Latin-alpha + digit patterns (A12, B-60) are intentionally - excluded here so they fall through to the electronic classifier. + e.g. B-60 -> tokens { name: "बी-साठ" } + e.g. A12 -> tokens { name: "ए बारह" } + e.g. FY2024 -> tokens { name: "एफ वाई दो शून्य दो चार" } """ def __init__( @@ -60,16 +61,15 @@ def __init__( any_digit = pynini.union(NEMO_DIGIT, devanagari_digits).optimize() - limited_cardinal_graph = ( - cardinal.digit | cardinal.zero | cardinal.teens_and_ties | cardinal.graph_hundreds - ).optimize() - num_graph = limited_cardinal_graph + # Fetch centralized 1-3 vs 4+ digit logic from cardinal + num_graph = cardinal.code_num_graph symbols_graph = pynini.string_file(get_abs_path("data/serial/special_symbols.tsv")).optimize() devanagari_chars = pynini.string_file(get_abs_path("data/serial/chars.tsv")).optimize() letter_graph = pynini.string_file(get_abs_path("data/address/letters.tsv")) + letter_graph = (letter_graph | pynini.compose(TO_LOWER, letter_graph)).optimize() latin_letters = letter_graph + pynini.closure(pynutil.insert(" ") + letter_graph) latin_letters = latin_letters.optimize() @@ -94,6 +94,16 @@ def __init__( glued_serial = pynini.compose(space_inserter, serial_core).optimize() serial_graph = pynini.union(serial_graph, glued_serial).optimize() + # Reusable mixed alphanumeric-code graph + code_join_char = pynini.union(all_alphas, any_digit, pynini.accep("-"), pynini.accep("/")) + has_letter = pynini.closure(code_join_char) + all_alphas + pynini.closure(code_join_char) + has_digit = pynini.closure(code_join_char) + any_digit + pynini.closure(code_join_char) + mixed_code_only = pynini.intersect( + pynini.intersect(pynini.closure(code_join_char, 1), has_letter), has_digit + ).optimize() + + self.mixed_alphanum_graph = pynini.compose(mixed_code_only, serial_graph).optimize() + power_special = pynutil.add_weight( pynini.string_file(get_abs_path("data/serial/power_special.tsv")), -1.0 ).optimize() @@ -110,15 +120,14 @@ def __init__( pure_word_slash = pynini.closure(NEMO_ALPHA, 1) + pynini.accep("/") + pynini.closure(NEMO_ALPHA, 1) + letter_join_char = NEMO_ALPHA | pynini.accep("-") | pynini.accep("/") + contains_latin_letter = pynini.closure(letter_join_char) + NEMO_ALPHA + pynini.closure(letter_join_char) + pure_latin_word = pynini.intersect(pynini.closure(letter_join_char, 1), contains_latin_letter).optimize() + dimension_pattern = ( pynini.closure(any_digit, 1) + (pynini.accep("x") | pynini.accep("X")) + pynini.closure(any_digit, 1) ) - _opt_delim = pynini.closure(pynini.accep("-") | pynini.accep(" "), 0, 1) - latin_alphanum = (pynini.closure(NEMO_ALPHA, 1) + _opt_delim + pynini.closure(any_digit, 1)) | ( - pynini.closure(any_digit, 1) + _opt_delim + pynini.closure(NEMO_ALPHA, 1) - ) - ordinal_suffixes = pynini.project( pynini.union( pynini.string_file(get_abs_path("data/ordinal/suffixes.tsv")), @@ -143,7 +152,7 @@ def __init__( + pynini.union(date_year_suffix, date_suffixes) ) - exclusions = pure_word_slash | dimension_pattern | latin_alphanum | ordinal_pattern | date_pattern + exclusions = pure_word_slash | pure_latin_word | dimension_pattern | ordinal_pattern | date_pattern accepted_inputs = pynini.difference(NEMO_SIGMA, exclusions).optimize() serial_graph = pynini.compose(accepted_inputs, serial_graph).optimize() diff --git a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py index 0e8d7fc73..04124635e 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py +++ b/nemo_text_processing/text_normalization/hi/taggers/tokenize_and_classify.py @@ -100,7 +100,12 @@ def __init__( ordinal = OrdinalFst(cardinal=cardinal, deterministic=deterministic) ordinal_graph = ordinal.fst - measure = MeasureFst(cardinal=cardinal, decimal=decimal, ordinal=ordinal, input_case=input_case) + serial = SerialFst(cardinal=cardinal, deterministic=deterministic) + serial_graph = serial.fst + + measure = MeasureFst( + cardinal=cardinal, decimal=decimal, ordinal=ordinal, serial=serial, input_case=input_case + ) measure_graph = measure.fst money = MoneyFst(cardinal=cardinal) @@ -125,9 +130,6 @@ def __init__( electronic = ElectronicFst(deterministic=deterministic) electronic_graph = electronic.fst - serial = SerialFst(cardinal=cardinal, deterministic=deterministic) - serial_graph = serial.fst - classify = ( pynutil.add_weight(whitelist_graph, 1.01) | pynutil.add_weight(cardinal_graph, 1.1) diff --git a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py index 398c79fef..29dcf81d9 100644 --- a/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py +++ b/nemo_text_processing/text_normalization/hi/verbalizers/electronic.py @@ -16,6 +16,7 @@ from pynini.lib import pynutil from nemo_text_processing.text_normalization.hi.graph_utils import ( + NEMO_ALPHA, GraphFst, capitalized_input_graph, delete_space, @@ -27,13 +28,15 @@ class ElectronicFst(GraphFst): """ Finite state transducer for verbalizing electronic addresses. - Uses a phonetic-first approach with letter-by-letter fallback. + English words and letters are kept verbatim (Latin script); only digits and + symbols are read out in Hindi. Examples: - electronic { username: "kumar" domain: "gmail.com" } -> "के यू एम ए आर एट जीमेल डॉट कॉम" - electronic { protocol: "https" domain: "google.com/" } -> "एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गूगल डॉट कॉम फॉरवर्ड स्लैश" - electronic { path: "C:\\Users\\HP\\Desktop" } -> "सी कोलन बैकवर्ड स्लैश यूज़र्स बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डेस्कटॉप" + electronic { username: "kumar" domain: "gmail.com" } -> "kumar एट gmail डॉट com" + electronic { protocol: "https" domain: "google.com/" } -> "https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश google डॉट com फॉरवर्ड स्लैश" + electronic { path: "C:\\Users\\HP\\Desktop" } -> "C कोलन बैकवर्ड स्लैश Users बैकवर्ड स्लैश HP बैकवर्ड स्लैश Desktop" electronic { domain: "192.168.1.1" } -> "एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक" + electronic { fragment_id: "C₂H₄" } -> "सी दो एच चार" Args: deterministic: if True will provide a single transduction option, @@ -44,11 +47,6 @@ def __init__(self, deterministic: bool = True): super().__init__(name="electronic", kind="verbalize", deterministic=deterministic) symbols_graph = pynini.string_file(get_abs_path("data/electronic/symbols.tsv")).optimize() - domain_graph = pynini.string_file(get_abs_path("data/electronic/domain.tsv")).optimize() - server_name_graph = pynini.string_file(get_abs_path("data/electronic/server_name.tsv")).optimize() - common_words_graph = pynini.string_file(get_abs_path("data/electronic/common_words.tsv")).optimize() - latin_to_hindi_graph = pynini.string_file(get_abs_path("data/address/letters.tsv")) - latin_to_hindi_graph = capitalized_input_graph(latin_to_hindi_graph).optimize() ascii_digit_graph = pynini.string_file(get_abs_path("data/telephone/number.tsv")).optimize() hindi_digit_graph = pynini.string_file(get_abs_path("data/numbers/digit.tsv")).optimize() @@ -58,25 +56,30 @@ def __init__(self, deterministic: bool = True): protocol_graph = pynini.string_file(get_abs_path("data/electronic/protocols.tsv")).optimize() - single_letter = latin_to_hindi_graph + insert_space single_digit = digit_verbalization + insert_space single_symbol = symbols_graph + insert_space single_non_alpha = pynutil.add_weight(single_symbol, 1.0) | pynutil.add_weight(single_digit, 1.0) - def make_alpha_run_verbalizer(tsv_graphs): - phonetic = pynini.union(*[pynutil.add_weight(g + insert_space, w) for g, w in tsv_graphs]) - literal = pynutil.add_weight(pynini.closure(single_letter, 1), 1.1) - return phonetic | literal + # A run of Latin letters is preserved verbatim; digits and symbols verbalize in Hindi. + alpha_run = pynini.closure(NEMO_ALPHA, 1) + insert_space - def make_content(alpha_run_verb, non_alpha_sep=None): + # Chemical formulas are spelled out letter-by-letter (element symbols are + # abbreviations, not words), while digits and symbols verbalize in Hindi. + latin_to_hindi_graph = capitalized_input_graph( + pynini.string_file(get_abs_path("data/address/letters.tsv")) + ).optimize() + chem_char = (latin_to_hindi_graph + insert_space) | single_digit | single_symbol + chem_content = pynini.closure(chem_char, 1) + + def make_content(non_alpha_sep=None): if non_alpha_sep is None: non_alpha_sep = single_non_alpha mandatory_sep = pynini.closure(non_alpha_sep, 1) return ( pynini.closure(non_alpha_sep, 0) - + pynini.closure(alpha_run_verb + mandatory_sep, 0) - + pynini.closure(alpha_run_verb, 0, 1) + + pynini.closure(alpha_run + mandatory_sep, 0) + + pynini.closure(alpha_run, 0, 1) + pynini.closure(non_alpha_sep, 0) ) @@ -84,48 +87,17 @@ def make_content(alpha_run_verb, non_alpha_sep=None): delete_domain_tag = pynutil.delete("domain: \"") delete_protocol_tag = pynutil.delete("protocol: \"") delete_path_tag = pynutil.delete("path: \"") + delete_fragment_id_tag = pynutil.delete("fragment_id: \"") delete_quote = pynutil.delete("\"") - username_alpha_run = make_alpha_run_verbalizer( - [ - (server_name_graph, 0.85), - (domain_graph, 0.87), - (common_words_graph, 0.90), - ] - ) - username_content = make_content(username_alpha_run) - username_graph = delete_username_tag + username_content + delete_quote + delete_space + pynutil.insert("एट ") - - domain_alpha_run = make_alpha_run_verbalizer( - [ - (server_name_graph, 0.85), - (domain_graph, 0.87), - (common_words_graph, 0.90), - ] - ) - - domain_alpha_run = make_alpha_run_verbalizer( - [ - (server_name_graph, 0.85), - (domain_graph, 0.87), - (common_words_graph, 0.90), - ] - ) - - domain_content = pynutil.add_weight(make_content(domain_alpha_run), 1.0) - - domain_only_graph = delete_domain_tag + domain_content + delete_quote + general_content = make_content() + username_graph = delete_username_tag + general_content + delete_quote + delete_space + pynutil.insert("एट ") + domain_only_graph = delete_domain_tag + general_content + delete_quote protocol_only_graph = delete_protocol_tag + protocol_graph + insert_space + delete_quote + delete_space + path_graph = delete_path_tag + general_content + delete_quote - path_alpha_run = make_alpha_run_verbalizer( - [ - (domain_graph, 0.87), - (common_words_graph, 0.90), - ] - ) - path_content = make_content(path_alpha_run) - path_graph = delete_path_tag + path_content + delete_quote + chem_graph = delete_fragment_id_tag + chem_content + delete_quote ip_char = single_symbol | single_digit ip_content = pynini.closure(ip_char, 1) @@ -140,6 +112,7 @@ def make_content(alpha_run_verb, non_alpha_sep=None): | pynutil.add_weight(path_graph, 1.02) | pynutil.add_weight(ip_graph, 1.03) | pynutil.add_weight(domain_only_graph, 1.04) + | pynutil.add_weight(chem_graph, 1.04) ) delete_tokens = self.delete_tokens(graph) diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_address.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_address.txt index 9989fa75c..d0554ce30 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_address.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_address.txt @@ -1,47 +1,44 @@ -700 ओक स्ट्रीट~सात शून्य शून्य ओक स्ट्रीट -११ जंगल रोड~एक एक जंगल रोड -301 पार्क एवेन्यू~तीन शून्य एक पार्क एवेन्यू -गली नंबर १७ जीएकगढ़~गली नंबर एक सात जीएकगढ़ -अदनान अपार्टमेंट फ्लैट नंबर 55~अदनान अपार्टमेंट फ्लैट नंबर पाँच पाँच +700 ओक स्ट्रीट~सात सौ ओक स्ट्रीट +११ जंगल रोड~ग्यारह जंगल रोड +301 पार्क एवेन्यू~तीन सौ एक पार्क एवेन्यू +गली नंबर १७ जीएकगढ़~गली नंबर सत्रह जीएकगढ़ +अदनान अपार्टमेंट फ्लैट नंबर 55~अदनान अपार्टमेंट फ्लैट नंबर पचपन प्लॉट नंबर ८ बालाजी मार्केट~प्लॉट नंबर आठ बालाजी मार्केट -शॉप नंबर 109 9 और 10 डिवाइडिंग रोड सेक्टर 10 फरीदाबाद~शॉप नंबर एक शून्य नौ नौ और एक शून्य डिवाइडिंग रोड सेक्टर एक शून्य फरीदाबाद -बूथ ७०, सेक्टर ८, चंडीगढ़~बूथ सात शून्य, सेक्टर आठ, चंडीगढ़ -2221 Southern Street~दो दो दो एक सदर्न स्ट्रीट -७०० ओक स्ट्रीट~सात शून्य शून्य ओक स्ट्रीट -625 स्कूल स्ट्रीट~छह दो पाँच स्कूल स्ट्रीट +शॉप नंबर 109 9 और 10 डिवाइडिंग रोड सेक्टर 10 फरीदाबाद~शॉप नंबर एक सौ नौ नौ और दस डिवाइडिंग रोड सेक्टर दस फरीदाबाद +बूथ ७०, सेक्टर ८, चंडीगढ़~बूथ सत्तर, सेक्टर आठ, चंडीगढ़ +७०० ओक स्ट्रीट~सात सौ ओक स्ट्रीट +625 स्कूल स्ट्रीट~छह सौ पच्चीस स्कूल स्ट्रीट १४७० एस वाशिंगटन स्ट्रीट~एक चार सात शून्य एस वाशिंगटन स्ट्रीट -506 स्टेट रोड~पाँच शून्य छह स्टेट रोड -६६-४ पार्कहर्स्ट आर डी~छह छह हाइफ़न चार पार्कहर्स्ट आर डी -579 ट्रॉय-शेंक्टाडी रोड~पाँच सात नौ ट्रॉय हाइफ़न शेंक्टाडी रोड +506 स्टेट रोड~पाँच सौ छह स्टेट रोड +579 ट्रॉय-शेंक्टाडी रोड~पाँच सौ उनासी ट्रॉय हाइफ़न शेंक्टाडी रोड ७८३० - ई वेटरन्स पार्कवे, कोलंबस, जी ए ३१९०९~सात आठ तीन शून्य हाइफ़न ई वेटरन्स पार्कवे, कोलंबस, जी ए तीन एक नौ शून्य नौ -66-4, पार्कहर्स्ट रोड~छह छह हाइफ़न चार, पार्कहर्स्ट रोड -८४०/१, १०० फीट रोड, मेट्रो पिलर ५६-५७, इंदिरानगर, बैंगलोर~आठ चार शून्य बटा एक, एक शून्य शून्य फीट रोड, मेट्रो पिलर पाँच छह हाइफ़न पाँच सात, इंदिरानगर, बैंगलोर -17-18, राजलक्ष्मी नगर, 7th क्रॉस स्ट्रीट, 100 फीट बाईपास रोड, वेलाचेरी, चेन्नई~एक सात हाइफ़न एक आठ, राजलक्ष्मी नगर, सेवंथ क्रॉस स्ट्रीट, एक शून्य शून्य फीट बाईपास रोड, वेलाचेरी, चेन्नई +66-4, पार्कहर्स्ट रोड~छियासठ हाइफ़न चार, पार्कहर्स्ट रोड +८४०/१, १०० फीट रोड, मेट्रो पिलर ५६-५७, इंदिरानगर, बैंगलोर~आठ सौ चालीस बटा एक, एक सौ फीट रोड, मेट्रो पिलर छप्पन हाइफ़न सत्तावन, इंदिरानगर, बैंगलोर +17-18, राजलक्ष्मी नगर, 7th क्रॉस स्ट्रीट, 100 फीट बाईपास रोड, वेलाचेरी, चेन्नई~सत्रह हाइफ़न अठारह, राजलक्ष्मी नगर, सेवंथ क्रॉस स्ट्रीट, एक सौ फीट बाईपास रोड, वेलाचेरी, चेन्नई ४/५ न्यू म्युनिसिपल मार्केट रोड नंबर ५ और ६ सेन्टाक्रूज़ वेस्ट~चार बटा पाँच न्यू म्युनिसिपल मार्केट रोड नंबर पाँच और छह सेन्टाक्रूज़ वेस्ट -16/17 4th फ्लोर जवाहर नगर मटरू मंदिर रोड नंबर 2~एक छह बटा एक सात फ़ोर्थ फ्लोर जवाहर नगर मटरू मंदिर रोड नंबर दो -५/३०४ सिक्का कॉम्प्लेक्स विकास मार्ग एक्सटेंशन~पाँच बटा तीन शून्य चार सिक्का कॉम्प्लेक्स विकास मार्ग एक्सटेंशन -21/2 2nd फ्लोर 1st मेन रोड गांधी नगर~दो एक बटा दो सेकंड फ्लोर फ़र्स्ट मेन रोड गांधी नगर -नंबर २२/१८ ३rd फ्लोर सराय बोउ अली शू मार्केट~नंबर दो दो बटा एक आठ थर्ड फ्लोर सराय बोउ अली शू मार्केट -14/3, मथुरा रोड~एक चार बटा तीन, मथुरा रोड -यूनिट ३ १st फ्लोर नंबर ३७ सोलेमान खतर स्ट्रीट~यूनिट तीन फ़र्स्ट फ्लोर नंबर तीन सात सोलेमान खतर स्ट्रीट -1st फ्लोर नंबर 52 नॉर्थ अबूज़र स्ट्रीट खान ए अंसारी स्ट्रीट शरीयती स्ट्रीट 16617~फ़र्स्ट फ्लोर नंबर पाँच दो नॉर्थ अबूज़र स्ट्रीट खान ए अंसारी स्ट्रीट शरीयती स्ट्रीट एक छह छह एक सात -२०६ जय कॉम कॉम्प्लेक्स १st पोखरन रोड~दो शून्य छह जय कॉम कॉम्प्लेक्स फ़र्स्ट पोखरन रोड -नंबर 36 2nd फ्लोर सुपर 8 फेज 1 एकबतन टाउन तेहरान 13947~नंबर तीन छह सेकंड फ्लोर सुपर आठ फेज एक एकबतन टाउन तेहरान एक तीन नौ चार सात -२nd फ्लोर नंबर ८०८ आजादी स्ट्रीट~सेकंड फ्लोर नंबर आठ शून्य आठ आजादी स्ट्रीट -2nd फ्लोर नंबर 15 बिफ़ोर कांदि स्ट्रीट नॉर्थ सोहरावर्दी स्ट्रीट 15669~सेकंड फ्लोर नंबर एक पाँच बिफ़ोर कांदि स्ट्रीट नॉर्थ सोहरावर्दी स्ट्रीट एक पाँच छह छह नौ -यूनिट ४ नंबर २५ २nd गोलहा स्ट्रीट काशनी स्ट्रीट नूर स्क्वेर~यूनिट चार नंबर दो पाँच सेकंड गोलहा स्ट्रीट काशनी स्ट्रीट नूर स्क्वेर -ईस्ट 3rd फ्लोर नंबर 70 नेक्स्ट दो तोहीद इंस्टीट्यूट परचम स्ट्रीट~ईस्ट थर्ड फ्लोर नंबर सात शून्य नेक्स्ट दो तोहीद इंस्टीट्यूट परचम स्ट्रीट +16/17 4th फ्लोर जवाहर नगर मटरू मंदिर रोड नंबर 2~सोलह बटा सत्रह फ़ोर्थ फ्लोर जवाहर नगर मटरू मंदिर रोड नंबर दो +५/३०४ सिक्का कॉम्प्लेक्स विकास मार्ग एक्सटेंशन~पाँच बटा तीन सौ चार सिक्का कॉम्प्लेक्स विकास मार्ग एक्सटेंशन +21/2 2nd फ्लोर 1st मेन रोड गांधी नगर~इक्कीस बटा दो सेकंड फ्लोर फ़र्स्ट मेन रोड गांधी नगर +नंबर २२/१८ ३rd फ्लोर सराय बोउ अली शू मार्केट~नंबर बाईस बटा अठारह थर्ड फ्लोर सराय बोउ अली शू मार्केट +14/3, मथुरा रोड~चौदह बटा तीन, मथुरा रोड +यूनिट ३ १st फ्लोर नंबर ३७ सोलेमान खतर स्ट्रीट~यूनिट तीन फ़र्स्ट फ्लोर नंबर सैंतीस सोलेमान खतर स्ट्रीट +1st फ्लोर नंबर 52 नॉर्थ अबूज़र स्ट्रीट खान ए अंसारी स्ट्रीट शरीयती स्ट्रीट 16617~फ़र्स्ट फ्लोर नंबर बावन नॉर्थ अबूज़र स्ट्रीट खान ए अंसारी स्ट्रीट शरीयती स्ट्रीट एक छह छह एक सात +२०६ जय कॉम कॉम्प्लेक्स १st पोखरन रोड~दो सौ छह जय कॉम कॉम्प्लेक्स फ़र्स्ट पोखरन रोड +नंबर 36 2nd फ्लोर सुपर 8 फेज 1 एकबतन टाउन तेहरान 13947~नंबर छत्तीस सेकंड फ्लोर सुपर आठ फेज एक एकबतन टाउन तेहरान एक तीन नौ चार सात +२nd फ्लोर नंबर ८०८ आजादी स्ट्रीट~सेकंड फ्लोर नंबर आठ सौ आठ आजादी स्ट्रीट +2nd फ्लोर नंबर 15 बिफ़ोर कांदि स्ट्रीट नॉर्थ सोहरावर्दी स्ट्रीट 15669~सेकंड फ्लोर नंबर पंद्रह बिफ़ोर कांदि स्ट्रीट नॉर्थ सोहरावर्दी स्ट्रीट एक पाँच छह छह नौ +यूनिट ४ नंबर २५ २nd गोलहा स्ट्रीट काशनी स्ट्रीट नूर स्क्वेर~यूनिट चार नंबर पच्चीस सेकंड गोलहा स्ट्रीट काशनी स्ट्रीट नूर स्क्वेर +ईस्ट 3rd फ्लोर नंबर 70 नेक्स्ट दो तोहीद इंस्टीट्यूट परचम स्ट्रीट~ईस्ट थर्ड फ्लोर नंबर सत्तर नेक्स्ट दो तोहीद इंस्टीट्यूट परचम स्ट्रीट ३rd फ्लोर नंबर ५ हमेदन एली अपोज़िट लाले पार्क नॉर्थ कारगर स्ट्रीट~थर्ड फ्लोर नंबर पाँच हमेदन एली अपोज़िट लाले पार्क नॉर्थ कारगर स्ट्रीट 4th फ्लोर नंबर 1124 जमहोरी स्ट्रीट~फ़ोर्थ फ्लोर नंबर एक एक दो चार जमहोरी स्ट्रीट ५th फ्लोर नंबर ७/१ १३th एली शाहिद अराबली स्ट्रीट~फ़िफ्थ फ्लोर नंबर सात बटा एक थर्टींथ एली शाहिद अराबली स्ट्रीट -11, 80 फीट रोड, इंडियन ऑयल पेट्रोल पंप, कोरमंगला 6th ब्लॉक, बैंगलोर के सामने~एक एक, आठ शून्य फीट रोड, इंडियन ऑयल पेट्रोल पंप, कोरमंगला सिक्स्थ ब्लॉक, बैंगलोर के सामने -२१/११, जे ब्लॉक, ६th एवेन्यू मेन रोड, अन्ना नगर पूर्व, चेन्नई~दो एक बटा एक एक, जे ब्लॉक, सिक्स्थ एवेन्यू मेन रोड, अन्ना नगर पूर्व, चेन्नई -32A नाज़ प्लाज़ा मेरिस रोड~तीन दो ए नाज़ प्लाज़ा मेरिस रोड -२१४ बी गोविंद पूरी स्ट्रीट नंबर २~दो एक चार बी गोविंद पूरी स्ट्रीट नंबर दो -4362 16वीं एवेन्यू एसडब्ल्यू, देवदार रैपिड्स, आई ए 52404~चार तीन छह दो सोलहवीं एवेन्यू एसडब्ल्यू, देवदार रैपिड्स, आई ए बावन हज़ार चार सौ चार +11, 80 फीट रोड, इंडियन ऑयल पेट्रोल पंप, कोरमंगला 6th ब्लॉक, बैंगलोर के सामने~ग्यारह, अस्सी फीट रोड, इंडियन ऑयल पेट्रोल पंप, कोरमंगला सिक्स्थ ब्लॉक, बैंगलोर के सामने +२१/११, जे ब्लॉक, ६th एवेन्यू मेन रोड, अन्ना नगर पूर्व, चेन्नई~इक्कीस बटा ग्यारह, जे ब्लॉक, सिक्स्थ एवेन्यू मेन रोड, अन्ना नगर पूर्व, चेन्नई +32A नाज़ प्लाज़ा मेरिस रोड~बत्तीस ए नाज़ प्लाज़ा मेरिस रोड +२१४ बी गोविंद पूरी स्ट्रीट नंबर २~दो सौ चौदह बी गोविंद पूरी स्ट्रीट नंबर दो +२५१३ ५३ एवेन्यू, मुंबई, महाराष्ट्र ४००००१~दो पाँच एक तीन तिरेपन एवेन्यू, मुंबई, महाराष्ट्र चार शून्य शून्य शून्य शून्य एक अमरावती ६५५९३०~अमरावती छह पाँच पाँच नौ तीन शून्य शिमला, हिमाचल प्रदेश 593988~शिमला, हिमाचल प्रदेश पाँच नौ तीन नौ आठ आठ -२७०४४० डॉसन आर डी, अल्बानी, जीए ३१७०७~दो सात शून्य चार चार शून्य डॉसन आर डी, अल्बानी, जीए तीन एक सात शून्य सात रांची, झारखंड 736557~रांची, झारखंड सात तीन छह पाँच पाँच सात कोहिमा, नागालैंड ४४८३७७~कोहिमा, नागालैंड चार चार आठ तीन सात सात मुंबई, महाराष्ट्र 839488~मुंबई, महाराष्ट्र आठ तीन नौ चार आठ आठ diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_cardinal.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_cardinal.txt index d607992d7..050310f9f 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_cardinal.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_cardinal.txt @@ -148,3 +148,14 @@ ०७३~शून्य सात तीन 0001~शून्य शून्य शून्य एक ०००~शून्य शून्य शून्य +3,24,50,000~तीन करोड़ चौबीस लाख पचास हज़ार +२,१२,१५,०००~दो करोड़ बारह लाख पंद्रह हज़ार +32,450,000~तीन करोड़ चौबीस लाख पचास हज़ार +४,९९,९९,०००~चार करोड़ निन्यानबे लाख निन्यानबे हज़ार +5,50,00,000~पाँच करोड़ पचास लाख +32,45,000~बत्तीस लाख पैंतालीस हज़ार +५,५६,३२०~पाँच लाख छप्पन हज़ार तीन सौ बीस +1,23,456~एक लाख तेईस हज़ार चार सौ छप्पन +12,345~बारह हज़ार तीन सौ पैंतालीस +११,२२०~ग्यारह हज़ार दो सौ बीस +1,00,00,000~एक करोड़ \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_decimal.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_decimal.txt index 3582aff50..03b01de2f 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_decimal.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_decimal.txt @@ -18,3 +18,7 @@ १०००००००००००००.०००३~एक नील दशमलव शून्य शून्य शून्य तीन 1000000000000000.008~एक पद्म दशमलव शून्य शून्य आठ १०००००००००००००००००.४१२~एक शंख दशमलव चार एक दो +१९२.१६८~एक सौ बानबे दशमलव एक छह आठ +192.168~एक सौ बानबे दशमलव एक छह आठ +99.99~निन्यानबे दशमलव नौ नौ +९९.९९~निन्यानबे दशमलव नौ नौ diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt index fd1bf459d..3265724a3 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_electronic.txt @@ -1,50 +1,50 @@ -gmail.com~जीमेल डॉट कॉम -yahoo.com~याहू डॉट कॉम -hotmail.com~हॉटमेल डॉट कॉम -google.com~गूगल डॉट कॉम -kumaar.org~के यू एम ए ए आर डॉट ऑर्ग -kumaar.info~के यू एम ए ए आर डॉट इन्फो -kumar@gmail.com~के यू एम ए आर एट जीमेल डॉट कॉम -robin@hotmail.com~आर ओ बी आई एन एट हॉटमेल डॉट कॉम -kapil@live.com~के ए पी आई एल एट लाइव डॉट कॉम -sneha@live.com~एस एन ई एच ए एट लाइव डॉट कॉम -mayank@google.com~एम ए वाई ए एन के एट गूगल डॉट कॉम -charu@yahoo.com~सी एच ए आर यू एट याहू डॉट कॉम -john20@yahoo.com~जे ओ एच एन दो शून्य एट याहू डॉट कॉम -vivaan62@gmail.com~वी आई वी ए ए एन छह दो एट जीमेल डॉट कॉम -viaan15@kumaar.com~वी आई ए ए एन एक पाँच एट के यू एम ए ए आर डॉट कॉम -ltaa12@gmail.com~एल टी ए ए एक दो एट जीमेल डॉट कॉम -kristen11@hotmail.com~के आर आई एस टी ई एन एक एक एट हॉटमेल डॉट कॉम -dsmith@yahoo.com~डी एस एम आई टी एच एट याहू डॉट कॉम -hgarza@gmail.com~एच जी ए आर ज़ेड ए एट जीमेल डॉट कॉम -qhill@yahoo.com~क्यू एच आई एल एल एट याहू डॉट कॉम -green-turner.org~जी आर ई ई एन हाइफ़न टी यू आर एन ई आर डॉट ऑर्ग -sharma-badami.com~एस एच ए आर एम ए हाइफ़न बी ए डी ए एम आई डॉट कॉम -osborne-gross.com~ओ एस बी ओ आर एन ई हाइफ़न जी आर ओ एस एस डॉट कॉम -lucero-stevenson.net~एल यू सी ई आर ओ हाइफ़न एस टी ई वी ई एन एस ओ एन डॉट नेट -https://google.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गूगल डॉट कॉम फॉरवर्ड स्लैश -https://github.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश गिटहब डॉट कॉम फॉरवर्ड स्लैश -https://wikipedia.org/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश विकिपीडिया डॉट ऑर्ग फॉरवर्ड स्लैश -https://amazon.com/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश अमेज़ोन डॉट कॉम फॉरवर्ड स्लैश -www.google.com~डब्ल्यू डब्ल्यू डब्ल्यू डॉट गूगल डॉट कॉम -https://www.ndtv.com~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट एन डी टी वी डॉट कॉम -https://www.rbi.org.in/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट आर बी आई डॉट ऑर्ग डॉट इन फॉरवर्ड स्लैश -https://www.amity.edu~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू डॉट ए एम आई टी वाई डॉट ई डी यू -https://example.com/blog/~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश ब्लॉग फॉरवर्ड स्लैश -https://example.com/about.html~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश अबाउट डॉट एच टी एम एल -https://example.com/search.php~एच टी टी पी एस कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ई एक्स ए एम पी एल ई डॉट कॉम फॉरवर्ड स्लैश सर्च डॉट पी एच पी -http://ati.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ए टी आई डॉट ई डी यू -http://gcu.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश जी सी यू डॉट ई डी यू -http://pima.edu~एच टी टी पी कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश पी आई एम ए डॉट ई डी यू -bamu.nic.in/~बी ए एम यू डॉट एन आई सी डॉट इन फॉरवर्ड स्लैश -bieap.gov.in/~बी आई ई ए पी डॉट जी ओ वी डॉट इन फॉरवर्ड स्लैश -www.sharda.ac.in~डब्ल्यू डब्ल्यू डब्ल्यू डॉट एस एच ए आर डी ए डॉट ए सी डॉट इन -C:\Users\HP\Desktop\~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ई एस के टी ओ पी बैकवर्ड स्लैश -C:\Users\HP\Downloads\~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ओ डब्ल्यू एन एल ओ ए डी एस बैकवर्ड स्लैश -C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्लैश यू एस ई आर एस बैकवर्ड स्लैश एच पी बैकवर्ड स्लैश डी ओ सी यू एम ई एन टी एस बैकवर्ड स्लैश ज़ेड ओ ओ एम -/home/desktop~फॉरवर्ड स्लैश होम फॉरवर्ड स्लैश डेस्कटॉप -/etc/apache~फॉरवर्ड स्लैश ई टी सी फॉरवर्ड स्लैश ए पी ए सी एच ई -/var/www~फॉरवर्ड स्लैश वी ए आर फॉरवर्ड स्लैश डब्ल्यू डब्ल्यू डब्ल्यू +gmail.com~gmail डॉट com +yahoo.com~yahoo डॉट com +hotmail.com~hotmail डॉट com +google.com~google डॉट com +kumaar.org~kumaar डॉट org +kumaar.info~kumaar डॉट info +kumar@gmail.com~kumar एट gmail डॉट com +robin@hotmail.com~robin एट hotmail डॉट com +kapil@live.com~kapil एट live डॉट com +sneha@live.com~sneha एट live डॉट com +mayank@google.com~mayank एट google डॉट com +charu@yahoo.com~charu एट yahoo डॉट com +john20@yahoo.com~john दो शून्य एट yahoo डॉट com +vivaan62@gmail.com~vivaan छह दो एट gmail डॉट com +viaan15@kumaar.com~viaan एक पाँच एट kumaar डॉट com +ltaa12@gmail.com~ltaa एक दो एट gmail डॉट com +kristen11@hotmail.com~kristen एक एक एट hotmail डॉट com +dsmith@yahoo.com~dsmith एट yahoo डॉट com +hgarza@gmail.com~hgarza एट gmail डॉट com +qhill@yahoo.com~qhill एट yahoo डॉट com +green-turner.org~green हाइफ़न turner डॉट org +sharma-badami.com~sharma हाइफ़न badami डॉट com +osborne-gross.com~osborne हाइफ़न gross डॉट com +lucero-stevenson.net~lucero हाइफ़न stevenson डॉट net +https://google.com/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश google डॉट com फॉरवर्ड स्लैश +https://github.com/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश github डॉट com फॉरवर्ड स्लैश +https://wikipedia.org/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश wikipedia डॉट org फॉरवर्ड स्लैश +https://amazon.com/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश amazon डॉट com फॉरवर्ड स्लैश +www.google.com~www डॉट google डॉट com +https://www.ndtv.com~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश www डॉट ndtv डॉट com +https://www.rbi.org.in/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश www डॉट rbi डॉट org डॉट in फॉरवर्ड स्लैश +https://www.amity.edu~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश www डॉट amity डॉट edu +https://example.com/blog/~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश example डॉट com फॉरवर्ड स्लैश blog फॉरवर्ड स्लैश +https://example.com/about.html~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश example डॉट com फॉरवर्ड स्लैश about डॉट html +https://example.com/search.php~https कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश example डॉट com फॉरवर्ड स्लैश search डॉट php +http://ati.edu~http कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश ati डॉट edu +http://gcu.edu~http कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश gcu डॉट edu +http://pima.edu~http कोलन फॉरवर्ड स्लैश फॉरवर्ड स्लैश pima डॉट edu +bamu.nic.in/~bamu डॉट nic डॉट in फॉरवर्ड स्लैश +bieap.gov.in/~bieap डॉट gov डॉट in फॉरवर्ड स्लैश +www.sharda.ac.in~www डॉट sharda डॉट ac डॉट in +C:\Users\HP\Desktop~C कोलन बैकवर्ड स्लैश Users बैकवर्ड स्लैश HP बैकवर्ड स्लैश Desktop +C:\Users\HP\Downloads~C कोलन बैकवर्ड स्लैश Users बैकवर्ड स्लैश HP बैकवर्ड स्लैश Downloads +C:\Users\HP\Documents\Zoom~C कोलन बैकवर्ड स्लैश Users बैकवर्ड स्लैश HP बैकवर्ड स्लैश Documents बैकवर्ड स्लैश Zoom +/home/desktop~फॉरवर्ड स्लैश home फॉरवर्ड स्लैश desktop +/etc/apache~फॉरवर्ड स्लैश etc फॉरवर्ड स्लैश apache +/var/www~फॉरवर्ड स्लैश var फॉरवर्ड स्लैश www 192.168.1.1~एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक 10.0.0.1~एक शून्य डॉट शून्य डॉट शून्य डॉट एक 83.54.245.61~आठ तीन डॉट पाँच चार डॉट दो चार पाँच डॉट छह एक @@ -55,11 +55,11 @@ C:\Users\HP\Documents\Zoom~सी कोलन बैकवर्ड स्ल आईपी एड्रेस 10.0.0.1~आईपी एड्रेस एक शून्य डॉट शून्य डॉट शून्य डॉट एक ip address 192.168.1.1~ip address एक नौ दो डॉट एक छह आठ डॉट एक डॉट एक ip address 10.0.0.1~ip address एक शून्य डॉट शून्य डॉट शून्य डॉट एक -report.pdf~आर ई पी ओ आर टी डॉट पी डी एफ -photo.jpg~पी एच ओ टी ओ डॉट जे पी जी -data.csv~डेटा डॉट सी एस वी -robinson.org~आर ओ बी आई एन एस ओ एन डॉट ऑर्ग -anand@gmail.com~ए एन ए एन डी एट जीमेल डॉट कॉम +report.pdf~report डॉट pdf +photo.jpg~photo डॉट jpg +data.csv~data डॉट csv +robinson.org~robinson डॉट org +anand@gmail.com~anand एट gmail डॉट com Al₂(SO₄)₃~ए एल दो ओपन ब्रेकेट एस ओ चार क्लोज़ ब्रेकेट तीन C₂H₄~सी दो एच चार -home/desktop~होम फॉरवर्ड स्लैश डेस्कटॉप \ No newline at end of file +home/desktop~home फॉरवर्ड स्लैश desktop \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt index 26b292a17..e5f157872 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_money.txt @@ -125,4 +125,20 @@ $1.2000~एक डॉलर बीस सेंट ₹5.00~पाँच रुपए ₹१~एक रुपया ₹२.१२३~दो दशमलव एक दो तीन रुपए -₹१.१२३४~एक दशमलव एक दो तीन चार रुपए \ No newline at end of file +₹१.१२३४~एक दशमलव एक दो तीन चार रुपए +₹3,24,50,000~तीन करोड़ चौबीस लाख पचास हज़ार रुपए +₹32,450,000~तीन करोड़ चौबीस लाख पचास हज़ार रुपए +₹5,50,00,000~पाँच करोड़ पचास लाख रुपए +₹12,54,000~बारह लाख चौवन हज़ार रुपए +₹1,00,000~एक लाख रुपए +₹2,148~दो हज़ार एक सौ अड़तालीस रुपए +₹99,999~निन्यानबे हज़ार नौ सौ निन्यानबे रुपए +₹३,२४,५०,०००~तीन करोड़ चौबीस लाख पचास हज़ार रुपए +₹३२,४५०,०००~तीन करोड़ चौबीस लाख पचास हज़ार रुपए +₹२,१२,१५,०००~दो करोड़ बारह लाख पंद्रह हज़ार रुपए +₹५,५०,००,०००~पाँच करोड़ पचास लाख रुपए +₹१२,५४,०००~बारह लाख चौवन हज़ार रुपए +₹५,५६,३२०~पाँच लाख छप्पन हज़ार तीन सौ बीस रुपए +₹१,००,०००~एक लाख रुपए +₹२,१४८~दो हज़ार एक सौ अड़तालीस रुपए +₹९९,९९९~निन्यानबे हज़ार नौ सौ निन्यानबे रुपए \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt index 00f697a89..340c754ed 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_roman.txt @@ -12,7 +12,7 @@ राष्ट्रीय राजमार्ग-IV~राष्ट्रीय राजमार्ग चार रोहिणी आर एस-I~रोहिणी आर एस एक पीएसएलवी सी-IV~पीएसएलवी सी चार -ISRO मिशन-III~आई एस आर ओ मिशन तीन +ISRO मिशन-III~ISRO मिशन तीन कक्षा XII की परीक्षा~कक्षा बारह की परीक्षा XIIवीं कक्षा की परीक्षा~बारहवीं कक्षा की परीक्षा भाग II का सारांश~भाग दो का सारांश diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt index 4c3880fb9..4a105554d 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_serial.txt @@ -18,4 +18,14 @@ 10-20-30~दस-बीस-तीस 1-800-999~एक-आठ सौ-नौ सौ निन्यानबे पृथ्वी-4~पृथ्वी-चार -ब्रह्मोस-1~ब्रह्मोस-एक \ No newline at end of file +ब्रह्मोस-1~ब्रह्मोस-एक +Q1~क्यू एक +A10~ए दस +A12~ए बारह +B-60~बी-साठ +ABC-123~ए बी सी-एक सौ तेईस +FY2024~एफ वाई दो शून्य दो चार +H2O~एच दो ओ +CO2~सी ओ दो +ABCDE1234F~ए बी सी डी ई एक दो तीन चार एफ +F16~एफ सोलह \ No newline at end of file diff --git a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_word.txt b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_word.txt index e9649919e..e7a284e9f 100644 --- a/tests/nemo_text_processing/hi/data_text_normalization/test_cases_word.txt +++ b/tests/nemo_text_processing/hi/data_text_normalization/test_cases_word.txt @@ -13,4 +13,8 @@ टाटा~टाटा ~ झ~झ -संगीत~संगीत \ No newline at end of file +संगीत~संगीत +This is a sentence.~This is a sentence. +google~google +mera email hai~mera email hai +user@~user@ \ No newline at end of file From a1147ed483b037405c4c76dd4fa90a449346d6c5 Mon Sep 17 00:00:00 2001 From: Shreyas Pawar Date: Wed, 29 Jul 2026 19:07:18 +0530 Subject: [PATCH 10/10] Hi tn v3: staging to main error fixed (#466) Signed-off-by: Shreyas Pawar --- Jenkinsfile | 2 +- .../hi/data/fraction/__init__.py | 13 +++++++++++++ .../text_normalization/hi/taggers/measure.py | 4 ---- .../text_normalization/hi/taggers/punctuation.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 nemo_text_processing/text_normalization/hi/data/fraction/__init__.py diff --git a/Jenkinsfile b/Jenkinsfile index 514e80d22..fd58716ec 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -29,7 +29,7 @@ pipeline { MR_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/03-12-24-1' JA_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/10-17-24-1' KO_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-04-25-6' - HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-28-26-0' + HI_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/07-29-26-0' DEFAULT_TN_CACHE='/home/jenkins/TestData/text_norm/ci/grammars/06-08-23-0' } stages { diff --git a/nemo_text_processing/text_normalization/hi/data/fraction/__init__.py b/nemo_text_processing/text_normalization/hi/data/fraction/__init__.py new file mode 100644 index 000000000..4fc25d0d3 --- /dev/null +++ b/nemo_text_processing/text_normalization/hi/data/fraction/__init__.py @@ -0,0 +1,13 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/nemo_text_processing/text_normalization/hi/taggers/measure.py b/nemo_text_processing/text_normalization/hi/taggers/measure.py index 83dc1be4e..67043b727 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/measure.py +++ b/nemo_text_processing/text_normalization/hi/taggers/measure.py @@ -34,7 +34,6 @@ NEMO_DIGIT, NEMO_HI_DIGIT, NEMO_NOT_SPACE, - NEMO_SIGMA, NEMO_SPACE, NEMO_WHITE_SPACE, ONE_POINT_FIVE, @@ -169,9 +168,6 @@ def get_address_graph(self, cardinal: GraphFst, ordinal: GraphFst, serial: Graph # Pure numeric runs: 1-3 digits read as cardinal, 4+ digits read digit-by-digit number_run = pynini.compose(pynini.closure(single_digit, 1), cardinal.code_num_graph).optimize() - # A positive weight of 0.5 penalizes multiple uses of this arc. This forces the FST to consume all contiguous digits as ONE run (cost 0.5) instead of splitting "625" into "6" and "25" (cost 1.0). - number_run_processor = insert_space + number_run - # A tiny positive penalty prevents multiple uses of this arc, forcing the FST to consume contiguous digits as ONE run instead of aggressively splitting them. number_run_processor = insert_space + number_run diff --git a/nemo_text_processing/text_normalization/hi/taggers/punctuation.py b/nemo_text_processing/text_normalization/hi/taggers/punctuation.py index 14c9a1a55..ea9cfc72e 100644 --- a/nemo_text_processing/text_normalization/hi/taggers/punctuation.py +++ b/nemo_text_processing/text_normalization/hi/taggers/punctuation.py @@ -1,3 +1,17 @@ +# Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import sys from unicodedata import category