Download code/dna_motif_tokenizer.py from nancyH/supp_tokenizer_bench: direct link, hf CLI and curl.
- Browser
- Download file 4.3 kB
-
https://huggingface.co/nancyH/supp_tokenizer_bench/resolve/main/code/dna_motif_tokenizer.py
- Command line
-
hf download hf://nancyH/supp_tokenizer_bench/code/dna_motif_tokenizer.py
-
curl -L -o dna_motif_tokenizer.py https://huggingface.co/nancyH/supp_tokenizer_bench/resolve/main/code/dna_motif_tokenizer.py
4.3 kB
| """Wrapper for the canonical DNAMotifTokenizer (HF: Anonymous-843q0u4q08/DNAMotifTokenizer). | |
| Exposes a `tokenizers.Tokenizer`-compatible interface (.encode, .get_vocab) so | |
| motif_coverage_eval.py can consume it like the other tokenizers. | |
| """ | |
| import itertools | |
| import pickle | |
| import random | |
| from os.path import join | |
| from pathlib import Path | |
| class TrieNode: | |
| def __init__(self): | |
| self.children = {} | |
| self.is_end_of_word = False | |
| self.features = [] | |
| class Trie: | |
| def __init__(self): | |
| self.root = TrieNode() | |
| self.lookup_table = {} | |
| def insert(self, word, features=None): | |
| current_node = self.root | |
| for char in word: | |
| if char not in current_node.children: | |
| current_node.children[char] = TrieNode() | |
| current_node = current_node.children[char] | |
| current_node.is_end_of_word = True | |
| if features: | |
| current_node.features.append(features) | |
| def search(self, word): | |
| current_node = self.root | |
| for char in word: | |
| if char not in current_node.children: | |
| return False | |
| current_node = current_node.children[char] | |
| if current_node.is_end_of_word: | |
| if len(current_node.features) > 0: | |
| return current_node.features | |
| return True | |
| return False | |
| class _Encoding: | |
| def __init__(self, tokens): | |
| self.tokens = tokens | |
| self.offsets = None | |
| class DNAMotifTokenizer: | |
| """Canonical greedy-longest-match tokenizer with +1/+2 lookahead.""" | |
| MAXLEN = 12 | |
| def __init__(self, tokenizer_dir, seed=0): | |
| tokenizer_dir = Path(tokenizer_dir) | |
| with open(tokenizer_dir / "motifs_hardcode_trie.pkl", "rb") as f: | |
| self.trie = pickle.load(f) | |
| self.k1 = ["A", "T", "C", "G", "N"] | |
| self.k3 = ["".join(t) for t in itertools.product("ATCG", repeat=3)] | |
| self.lookup = {} | |
| with open(tokenizer_dir / "motifs_dedup.txt") as f: | |
| for line in f: | |
| seg, name = line.strip().split(maxsplit=1) | |
| self.lookup[seg] = name | |
| self.vocab = {} | |
| with open(tokenizer_dir / "vocab_dedup.txt") as f: | |
| for i, line in enumerate(f): | |
| tok = line.strip() | |
| if tok: | |
| self.vocab[tok] = i | |
| self._rng = random.Random(seed) | |
| def get_vocab(self): | |
| return dict(self.vocab) | |
| def get_vocab_size(self): | |
| return len(self.vocab) | |
| def _tokenize_at(self, seg, i): | |
| candidates = [] | |
| for l in range(4, self.MAXLEN + 1): | |
| segment = seg[i:i + l] | |
| if self.trie.search(segment): | |
| candidates.append(segment) | |
| if candidates: | |
| best = self._rng.choice(candidates) | |
| return best, len(best) | |
| # Fallback: 3-mer then 1-mer | |
| for l in range(3, 0, -1): | |
| segment = seg[i:i + l] | |
| if l == 3 and segment in self.k3: | |
| return segment, 3 | |
| if l == 1 and segment in self.k1: | |
| return segment, 1 | |
| # Last resort: emit single char even if unknown (e.g. 'N' outside k1 — but k1 has N) | |
| return seg[i:i + 1], 1 | |
| def _tokenize_seq(self, seg): | |
| tokens = [] | |
| i = 0 | |
| while i < len(seg): | |
| best_token, best_score = self._tokenize_at(seg, i) | |
| best_i = i | |
| next_pos = i + len(best_token) | |
| offsets = [1, 2] if len(best_token) > 1 else [] | |
| for shift in offsets: | |
| i_shifted = i + shift | |
| if i_shifted < len(seg): | |
| tok_, score_ = self._tokenize_at(seg, i_shifted) | |
| next_pos_ = i_shifted + len(tok_) | |
| if score_ > best_score: | |
| best_token, best_score, best_i, next_pos = tok_, score_, i_shifted, next_pos_ | |
| for skip in range(best_i - i): | |
| tokens.append(seg[i + skip]) | |
| tokens.append(best_token) | |
| i = next_pos | |
| return tokens | |
| def encode(self, text, add_special_tokens=False): | |
| return _Encoding(self._tokenize_seq(text)) | |
| def encode_batch(self, texts, add_special_tokens=False): | |
| return [_Encoding(self._tokenize_seq(t)) for t in texts] | |