sentencepiece_unigram.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import json
  2. import os
  3. from typing import Iterator, List, Optional, Union, Tuple
  4. from tokenizers import AddedToken, Regex, Tokenizer, decoders, normalizers, pre_tokenizers, trainers
  5. from tokenizers.models import Unigram
  6. from .base_tokenizer import BaseTokenizer
  7. class SentencePieceUnigramTokenizer(BaseTokenizer):
  8. """SentencePiece Unigram Tokenizer
  9. Represents the Unigram algorithm, with the pretokenization used by SentencePiece
  10. """
  11. def __init__(
  12. self,
  13. vocab: Optional[List[Tuple[str, float]]] = None,
  14. replacement: str = "▁",
  15. add_prefix_space: bool = True,
  16. ):
  17. if vocab is not None:
  18. # Let Unigram(..) fail if only one of them is None
  19. tokenizer = Tokenizer(Unigram(vocab))
  20. else:
  21. tokenizer = Tokenizer(Unigram())
  22. tokenizer.normalizer = normalizers.Sequence(
  23. [normalizers.Nmt(), normalizers.NFKC(), normalizers.Replace(Regex(" {2,}"), " ")]
  24. )
  25. prepend_scheme = "always" if add_prefix_space else "never"
  26. tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
  27. tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
  28. parameters = {
  29. "model": "SentencePieceUnigram",
  30. "replacement": replacement,
  31. "add_prefix_space": add_prefix_space,
  32. }
  33. super().__init__(tokenizer, parameters)
  34. def train(
  35. self,
  36. files: Union[str, List[str]],
  37. vocab_size: int = 8000,
  38. show_progress: bool = True,
  39. special_tokens: Optional[List[Union[str, AddedToken]]] = None,
  40. initial_alphabet: Optional[List[str]] = None,
  41. unk_token: Optional[str] = None,
  42. ):
  43. """
  44. Train the model using the given files
  45. Args:
  46. files (:obj:`List[str]`):
  47. A list of path to the files that we should use for training
  48. vocab_size (:obj:`int`):
  49. The size of the final vocabulary, including all tokens and alphabet.
  50. show_progress (:obj:`bool`):
  51. Whether to show progress bars while training.
  52. special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
  53. A list of special tokens the model should know of.
  54. initial_alphabet (:obj:`List[str]`, `optional`):
  55. A list of characters to include in the initial alphabet, even
  56. if not seen in the training dataset.
  57. If the strings contain more than one character, only the first one
  58. is kept.
  59. unk_token (:obj:`str`, `optional`):
  60. The unknown token to be used by the model.
  61. """
  62. if special_tokens is None:
  63. special_tokens = []
  64. if initial_alphabet is None:
  65. initial_alphabet = []
  66. trainer = trainers.UnigramTrainer(
  67. vocab_size=vocab_size,
  68. special_tokens=special_tokens,
  69. show_progress=show_progress,
  70. initial_alphabet=initial_alphabet,
  71. unk_token=unk_token,
  72. )
  73. if isinstance(files, str):
  74. files = [files]
  75. self._tokenizer.train(files, trainer=trainer)
  76. def train_from_iterator(
  77. self,
  78. iterator: Union[Iterator[str], Iterator[Iterator[str]]],
  79. vocab_size: int = 8000,
  80. show_progress: bool = True,
  81. special_tokens: Optional[List[Union[str, AddedToken]]] = None,
  82. initial_alphabet: Optional[List[str]] = None,
  83. unk_token: Optional[str] = None,
  84. length: Optional[int] = None,
  85. ):
  86. """
  87. Train the model using the given iterator
  88. Args:
  89. iterator (:obj:`Union[Iterator[str], Iterator[Iterator[str]]]`):
  90. Any iterator over strings or list of strings
  91. vocab_size (:obj:`int`):
  92. The size of the final vocabulary, including all tokens and alphabet.
  93. show_progress (:obj:`bool`):
  94. Whether to show progress bars while training.
  95. special_tokens (:obj:`List[Union[str, AddedToken]]`, `optional`):
  96. A list of special tokens the model should know of.
  97. initial_alphabet (:obj:`List[str]`, `optional`):
  98. A list of characters to include in the initial alphabet, even
  99. if not seen in the training dataset.
  100. If the strings contain more than one character, only the first one
  101. is kept.
  102. unk_token (:obj:`str`, `optional`):
  103. The unknown token to be used by the model.
  104. length (:obj:`int`, `optional`):
  105. The total number of sequences in the iterator. This is used to
  106. provide meaningful progress tracking
  107. """
  108. if special_tokens is None:
  109. special_tokens = []
  110. if initial_alphabet is None:
  111. initial_alphabet = []
  112. trainer = trainers.UnigramTrainer(
  113. vocab_size=vocab_size,
  114. special_tokens=special_tokens,
  115. show_progress=show_progress,
  116. initial_alphabet=initial_alphabet,
  117. unk_token=unk_token,
  118. )
  119. self._tokenizer.train_from_iterator(
  120. iterator,
  121. trainer=trainer,
  122. length=length,
  123. )
  124. @staticmethod
  125. def from_spm(filename: str):
  126. try:
  127. import sys
  128. sys.path.append(".")
  129. import sentencepiece_model_pb2 as model # type: ignore[import]
  130. except Exception:
  131. raise Exception(
  132. "You don't seem to have the required protobuf file, in order to use this function you need to run `pip install protobuf` and `wget https://raw.githubusercontent.com/google/sentencepiece/master/python/src/sentencepiece/sentencepiece_model_pb2.py` for us to be able to read the intrinsics of your spm_file. `pip install sentencepiece` is not required."
  133. )
  134. m = model.ModelProto()
  135. m.ParseFromString(open(filename, "rb").read())
  136. precompiled_charsmap = m.normalizer_spec.precompiled_charsmap
  137. vocab = [(piece.piece, piece.score) for piece in m.pieces]
  138. unk_id = m.trainer_spec.unk_id
  139. model_type = m.trainer_spec.model_type
  140. byte_fallback = m.trainer_spec.byte_fallback
  141. if model_type != 1:
  142. raise Exception(
  143. "You're trying to run a `Unigram` model but you're file was trained with a different algorithm"
  144. )
  145. replacement = "▁"
  146. add_prefix_space = True
  147. tokenizer = Tokenizer(Unigram(vocab, unk_id, byte_fallback))
  148. if precompiled_charsmap:
  149. tokenizer.normalizer = normalizers.Sequence(
  150. [
  151. normalizers.Precompiled(precompiled_charsmap),
  152. normalizers.Replace(Regex(" {2,}"), " "),
  153. ]
  154. )
  155. else:
  156. tokenizer.normalizer = normalizers.Sequence([normalizers.Replace(Regex(" {2,}"), " ")])
  157. prepend_scheme = "always" if add_prefix_space else "never"
  158. tokenizer.pre_tokenizer = pre_tokenizers.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
  159. tokenizer.decoder = decoders.Metaspace(replacement=replacement, prepend_scheme=prepend_scheme)
  160. parameters = {
  161. "model": "SentencePieceUnigram",
  162. }
  163. obj = BaseTokenizer.__new__(SentencePieceUnigramTokenizer, tokenizer, parameters) # type: ignore[arg-type]
  164. BaseTokenizer.__init__(obj, tokenizer, parameters)
  165. return obj