number_normalizer.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. # coding=utf-8
  2. # Copyright 2023 The HuggingFace Inc. team.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. """English Normalizer class for CLVP."""
  16. import re
  17. class EnglishNormalizer:
  18. def __init__(self):
  19. # List of (regular expression, replacement) pairs for abbreviations:
  20. self._abbreviations = [
  21. (re.compile("\\b%s\\." % x[0], re.IGNORECASE), x[1])
  22. for x in [
  23. ("mrs", "misess"),
  24. ("mr", "mister"),
  25. ("dr", "doctor"),
  26. ("st", "saint"),
  27. ("co", "company"),
  28. ("jr", "junior"),
  29. ("maj", "major"),
  30. ("gen", "general"),
  31. ("drs", "doctors"),
  32. ("rev", "reverend"),
  33. ("lt", "lieutenant"),
  34. ("hon", "honorable"),
  35. ("sgt", "sergeant"),
  36. ("capt", "captain"),
  37. ("esq", "esquire"),
  38. ("ltd", "limited"),
  39. ("col", "colonel"),
  40. ("ft", "fort"),
  41. ]
  42. ]
  43. self.ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
  44. self.teens = [
  45. "ten",
  46. "eleven",
  47. "twelve",
  48. "thirteen",
  49. "fourteen",
  50. "fifteen",
  51. "sixteen",
  52. "seventeen",
  53. "eighteen",
  54. "nineteen",
  55. ]
  56. self.tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
  57. def number_to_words(self, num: int) -> str:
  58. """
  59. Converts numbers(`int`) to words(`str`).
  60. Please note that it only supports upto - "'nine hundred ninety-nine quadrillion, nine hundred ninety-nine
  61. trillion, nine hundred ninety-nine billion, nine hundred ninety-nine million, nine hundred ninety-nine
  62. thousand, nine hundred ninety-nine'" or `number_to_words(999_999_999_999_999_999)`.
  63. """
  64. if num == 0:
  65. return "zero"
  66. elif num < 0:
  67. return "minus " + self.number_to_words(abs(num))
  68. elif num < 10:
  69. return self.ones[num]
  70. elif num < 20:
  71. return self.teens[num - 10]
  72. elif num < 100:
  73. return self.tens[num // 10] + ("-" + self.number_to_words(num % 10) if num % 10 != 0 else "")
  74. elif num < 1000:
  75. return (
  76. self.ones[num // 100] + " hundred" + (" " + self.number_to_words(num % 100) if num % 100 != 0 else "")
  77. )
  78. elif num < 1_000_000:
  79. return (
  80. self.number_to_words(num // 1000)
  81. + " thousand"
  82. + (", " + self.number_to_words(num % 1000) if num % 1000 != 0 else "")
  83. )
  84. elif num < 1_000_000_000:
  85. return (
  86. self.number_to_words(num // 1_000_000)
  87. + " million"
  88. + (", " + self.number_to_words(num % 1_000_000) if num % 1_000_000 != 0 else "")
  89. )
  90. elif num < 1_000_000_000_000:
  91. return (
  92. self.number_to_words(num // 1_000_000_000)
  93. + " billion"
  94. + (", " + self.number_to_words(num % 1_000_000_000) if num % 1_000_000_000 != 0 else "")
  95. )
  96. elif num < 1_000_000_000_000_000:
  97. return (
  98. self.number_to_words(num // 1_000_000_000_000)
  99. + " trillion"
  100. + (", " + self.number_to_words(num % 1_000_000_000_000) if num % 1_000_000_000_000 != 0 else "")
  101. )
  102. elif num < 1_000_000_000_000_000_000:
  103. return (
  104. self.number_to_words(num // 1_000_000_000_000_000)
  105. + " quadrillion"
  106. + (
  107. ", " + self.number_to_words(num % 1_000_000_000_000_000)
  108. if num % 1_000_000_000_000_000 != 0
  109. else ""
  110. )
  111. )
  112. else:
  113. return "number out of range"
  114. def convert_to_ascii(self, text: str) -> str:
  115. """
  116. Converts unicode to ascii
  117. """
  118. return text.encode("ascii", "ignore").decode("utf-8")
  119. def _expand_dollars(self, m: str) -> str:
  120. """
  121. This method is used to expand numerical dollar values into spoken words.
  122. """
  123. match = m.group(1)
  124. parts = match.split(".")
  125. if len(parts) > 2:
  126. return match + " dollars" # Unexpected format
  127. dollars = int(parts[0]) if parts[0] else 0
  128. cents = int(parts[1]) if len(parts) > 1 and parts[1] else 0
  129. if dollars and cents:
  130. dollar_unit = "dollar" if dollars == 1 else "dollars"
  131. cent_unit = "cent" if cents == 1 else "cents"
  132. return "%s %s, %s %s" % (dollars, dollar_unit, cents, cent_unit)
  133. elif dollars:
  134. dollar_unit = "dollar" if dollars == 1 else "dollars"
  135. return "%s %s" % (dollars, dollar_unit)
  136. elif cents:
  137. cent_unit = "cent" if cents == 1 else "cents"
  138. return "%s %s" % (cents, cent_unit)
  139. else:
  140. return "zero dollars"
  141. def _remove_commas(self, m: str) -> str:
  142. """
  143. This method is used to remove commas from sentences.
  144. """
  145. return m.group(1).replace(",", "")
  146. def _expand_decimal_point(self, m: str) -> str:
  147. """
  148. This method is used to expand '.' into spoken word ' point '.
  149. """
  150. return m.group(1).replace(".", " point ")
  151. def _expand_ordinal(self, num: str) -> str:
  152. """
  153. This method is used to expand ordinals such as '1st', '2nd' into spoken words.
  154. """
  155. ordinal_suffixes = {1: "st", 2: "nd", 3: "rd"}
  156. num = int(num.group(0)[:-2])
  157. if 10 <= num % 100 and num % 100 <= 20:
  158. suffix = "th"
  159. else:
  160. suffix = ordinal_suffixes.get(num % 10, "th")
  161. return self.number_to_words(num) + suffix
  162. def _expand_number(self, m: str) -> str:
  163. """
  164. This method acts as a preprocessing step for numbers between 1000 and 3000 (same as the original repository,
  165. link :
  166. https://github.com/neonbjb/tortoise-tts/blob/4003544b6ff4b68c09856e04d3eff9da26d023c2/tortoise/utils/tokenizer.py#L86)
  167. """
  168. num = int(m.group(0))
  169. if num > 1000 and num < 3000:
  170. if num == 2000:
  171. return "two thousand"
  172. elif num > 2000 and num < 2010:
  173. return "two thousand " + self.number_to_words(num % 100)
  174. elif num % 100 == 0:
  175. return self.number_to_words(num // 100) + " hundred"
  176. else:
  177. return self.number_to_words(num)
  178. else:
  179. return self.number_to_words(num)
  180. def normalize_numbers(self, text: str) -> str:
  181. """
  182. This method is used to normalize numbers within a text such as converting the numbers to words, removing
  183. commas, etc.
  184. """
  185. text = re.sub(re.compile(r"([0-9][0-9\,]+[0-9])"), self._remove_commas, text)
  186. text = re.sub(re.compile(r"£([0-9\,]*[0-9]+)"), r"\1 pounds", text)
  187. text = re.sub(re.compile(r"\$([0-9\.\,]*[0-9]+)"), self._expand_dollars, text)
  188. text = re.sub(re.compile(r"([0-9]+\.[0-9]+)"), self._expand_decimal_point, text)
  189. text = re.sub(re.compile(r"[0-9]+(st|nd|rd|th)"), self._expand_ordinal, text)
  190. text = re.sub(re.compile(r"[0-9]+"), self._expand_number, text)
  191. return text
  192. def expand_abbreviations(self, text: str) -> str:
  193. """
  194. Expands the abbreviate words.
  195. """
  196. for regex, replacement in self._abbreviations:
  197. text = re.sub(regex, replacement, text)
  198. return text
  199. def collapse_whitespace(self, text: str) -> str:
  200. """
  201. Removes multiple whitespaces
  202. """
  203. return re.sub(re.compile(r"\s+"), " ", text)
  204. def __call__(self, text):
  205. """
  206. Converts text to ascii, numbers / number-like quantities to their spelt-out counterparts and expands
  207. abbreviations
  208. """
  209. text = self.convert_to_ascii(text)
  210. text = text.lower()
  211. text = self.normalize_numbers(text)
  212. text = self.expand_abbreviations(text)
  213. text = self.collapse_whitespace(text)
  214. text = text.replace('"', "")
  215. return text