diff --git a/deeppavlov/models/spelling_correction/levenshtein/levenshtein_searcher.py b/deeppavlov/models/spelling_correction/levenshtein/levenshtein_searcher.py index 3c05d6848c..dfebaf228b 100644 --- a/deeppavlov/models/spelling_correction/levenshtein/levenshtein_searcher.py +++ b/deeppavlov/models/spelling_correction/levenshtein/levenshtein_searcher.py @@ -23,8 +23,8 @@ class LevenshteinSearcher: """ - Класс для поиска близких слов - в соответствии с расстоянием Левенштейна + A class for searching for close words + according to the Levenshtein distance """ @@ -42,7 +42,7 @@ def __init__(self, alphabet, dictionary, operation_costs=None, else: raise ValueError("Euristics should be non-negative integer or None") if isinstance(dictionary, Trie): - # словарь передан уже в виде бора + # the dictionary has already been transmitted in the form of a Trie self.dictionary = dictionary else: self.dictionary = make_trie(alphabet, dictionary, make_cashed=True, @@ -70,15 +70,15 @@ def search(self, word, d, allow_spaces=True, return_cost=True): def _trie_search(self, word, d, transducer=None, allow_spaces=True, return_cost=True): """ - Находит все слова в префиксном боре, расстояние до которых - в соответствии с заданным преобразователем не превышает d + Finds all words in the Trie, the distance to which in + accordance with the specified converter does not exceed d """ if transducer is None: - # разобраться с пробелами + # deals with spaces transducer = self.transducer.inverse() allow_spaces &= self.allow_spaces trie = self.dictionary - # инициализация переменных + # variable initialization used_agenda_keys = set() agenda = SortedListWithKey(key=(lambda x: x[1])) h = self.h_func(word, trie.root) @@ -87,7 +87,7 @@ def _trie_search(self, word, d, transducer=None, agenda.add((key, value)) answer = dict() k = 0 - # очередь с приоритетом с промежуточными результатами + # priority queue with intermediate results while len(agenda) > 0: key, value = agenda.pop(0) if key in used_agenda_keys: @@ -95,8 +95,8 @@ def _trie_search(self, word, d, transducer=None, used_agenda_keys.add(key) low, pos, index = key cost, g, h = value - # g --- текущая стоимость, h --- нижняя оценка будущей стоимости - # cost = g + h --- нижняя оценка суммарной стоимости + # g --- current value, h--- lower estimate of future value + # cost = g + h --- lower estimate of the total cost k += 1 max_upperside_length = min(len(word) - pos, transducer.max_up_length) for upperside_length in range(max_upperside_length + 1): @@ -106,7 +106,7 @@ def _trie_search(self, word, d, transducer=None, continue for curr_low, curr_cost in transducer.operation_costs[curr_up].items(): new_g = g + curr_cost - if new_g > d: # если g > d, то h можно не вычислять + if new_g > d: # if g > d, then h can not be calculated continue if curr_low == " ": if allow_spaces and trie.is_final(index): @@ -137,13 +137,13 @@ def _trie_search(self, word, d, transducer=None, def _precompute_euristics(self): """ - Предвычисляет будущие символы и стоимости операций с ними - для h-эвристики + Precalculates future symbols and the cost of operations with them + for h-heuristics """ if self.euristics is None: return - # вычисление минимальной стоимости операции, - # приводящей к появлению ('+') или исчезновению ('-') данного символа + # calculation of the minimum cost of the operation, + # leading to the appearance ('+') or disappearance ('-') of this symbol removal_costs = {a: np.inf for a in self.alphabet} insertion_costs = {a: np.inf for a in self.alphabet} if self.allow_spaces: @@ -161,13 +161,13 @@ def _precompute_euristics(self): insertion_cost = cost / len(low) for a in low: insertion_costs[a] = min(insertion_costs[a], insertion_cost) - # предвычисление возможных будущих символов в узлах дерева - # precompute_future_symbols(self.dictionary, self.euristics, self.allow_spaces) - # предвычисление стоимостей потери символа в узлах дерева + # precomputation of possible future symbols in tree nodes + # precompute_future_symbols(self.dictionary, self.heuristics, self.allow_spaces) + # precomputing the cost of symbol loss in tree nodes self._absense_costs_by_node = _precompute_absense_costs( self.dictionary, removal_costs, insertion_costs, self.euristics, self.allow_spaces) - # массив для сохранения эвристик + # array for storing heuristics self._temporary_euristics = [dict() for i in range(len(self.dictionary))] def _define_h_function(self): @@ -178,35 +178,35 @@ def _define_h_function(self): def _euristic_h_function(self, suffix, index): """ - Вычисление h-эвристики из работы Hulden,2009 для текущей вершины словаря + Calculation of the h-heuristic from Hulden, 2009 for the current vertex of the dictionary - Аргументы: + Arguments: ---------- suffix : string - непрочитанный суффикс входного слова + the unread suffix of the input word index : int - индекс текущего узла в словаре + index of the current node in the dictionary - Возвращает: + Returns: ----------- - cost : float - оценка снизу для стоимости замены, - приводящей к входному слову с суффиксом suffix, - если прочитанный префикс слова без опечатки - привёл в вершину с номером index + const : float + bottom estimate for replacement cost, + leading to an input word with the suffix suffix, + if the read prefix is a word without a typo + led to the vertex with the index number """ if self.euristics > 0: suffix = suffix[:self.euristics] - # кэширование результатов + # caching of results index_temporary_euristics = self._temporary_euristics[index] cost = index_temporary_euristics.get(suffix, None) if cost is not None: return cost - # извлечение нужных данных из массивов + # extracting the necessary data from arrays absense_costs = self._absense_costs_by_node[index] data = self.dictionary.data[index] costs = np.zeros(dtype=np.float64, shape=(self.euristics,)) - # costs[j] --- оценка штрафа при предпросмотре вперёд на j символов + # costs[j] --- penalty estimate when previewing forward by j characters for i, a in enumerate(suffix): costs[i:] += absense_costs[a][i:] cost = max(costs) @@ -229,28 +229,28 @@ def _minimal_replacement_cost(self, first, second): def _precompute_absense_costs(dictionary, removal_costs, insertion_costs, n, allow_spaces=False): """ - Вычисляет минимальную стоимость появления нового символа в узлах словаря - в соответствии со штрафами из costs + Calculates the minimum cost of the appearance of a new character in the dictionary nodes + according to the penalties from costs - Аргументы: + Arguments: --------------- - dictionary : Trie - словарь, хранящийся в виде ациклического автомата + dictionary : Tkey + a dictionary stored as an acyclic automaton removal_costs : dict - штрафы за удаление символов + penalties for deleting characters insertion_costs : dict - штрафы за вставку символов + penalties for inserting characters n : int - глубина ``заглядывания вперёд'' в словаре + the depth of `looking ahead" in the dictionary - Возвращает + Returns --------------- - answer : list of dicts, len(answer)=len(dictionary) - answer[i][a][j] равно минимальному штрафу за появление символа a - в j-ой позиции в вершине с номером i + answer : list of diets, en(answer)=len(dictionary) + answer[i][a][j] is equal to the minimum penalty for the appearance of the symbol a + in the j-th position at the vertex with the number i """ answer = [dict() for node in dictionary.data] if n == 0: @@ -259,7 +259,7 @@ def _precompute_absense_costs(dictionary, removal_costs, insertion_costs, n, if allow_spaces: curr_alphabet += [' '] for l, (costs_in_node, node) in enumerate(zip(answer, dictionary.data)): - # определение минимальной стоимости удаления символов + # determining the minimum cost of deleting characters curr_node_removal_costs = np.empty(dtype=np.float64, shape=(n,)) if len(node[0]) > 0: curr_node_removal_costs[0] = min(removal_costs[symbol] for symbol in node[0]) @@ -271,7 +271,7 @@ def _precompute_absense_costs(dictionary, removal_costs, insertion_costs, n, curr_node_removal_costs[j] = min(curr_node_removal_costs[j - 1], curr_cost) else: curr_node_removal_costs[:] = np.inf - # определение минимальной стоимости вставки + # determining the minimum insertion cost for a in curr_alphabet: curr_symbol_costs = np.empty(dtype=np.float64, shape=(n,)) curr_symbol_costs.fill(insertion_costs[a]) @@ -286,21 +286,21 @@ def _precompute_absense_costs(dictionary, removal_costs, insertion_costs, n, class SegmentTransducer: """ - Класс, реализующий взвешенный конечный преобразователь, - осуществляющий замены из заданного списка операций + A class implementing a weighted finite converter, + performing substitutions from a given list of operations - Аргументы: + Arguments: ---------- alphabet : list - алфавит + alphabet - operation_costs : dict or None(optional, default=None) - словарь вида {(up,low) : cost} + operation_costs : doctor None(optional, default=None) + dictionary of the form {(up,low) : cost} allow_spaces : bool(optional, default=False) - разрешены ли элементы трансдукции, содержащие пробел - (используется только если явно не заданы operation costs - и они равны значению по умолчанию) + are the transduction elements containing a space allowed + (used only if operation costs are not explicitly specified + and they are equal to the default value) """ @@ -316,26 +316,26 @@ def __init__(self, alphabet, operation_costs=None, allow_spaces=False): self._make_maximal_key_lengths() # self.maximal_value_lengths = {} # for up, probs in self.operation_costs.items(): - # СЛИШКОМ МНОГО ВЫЗОВОВ, НАДО КАК-ТО ЗАПОМНИТЬ - # МАКСИМАЛЬНЫЕ ДЛИНЫ КЛЮЧЕЙ ПРИ ОБРАЩЕНИИ + # THERE ARE TOO MANY CALLS, YOU NEED TO REMEMBER SOMEHOW + # MAXIMUM KEY LENGTHS WHEN ACCESSING # max_low_length = max(len(low) for low in probs) if (len(probs) > 0) else -1 # self.maximal_value_lengths[up] = self.maximal_key_length def get_operation_cost(self, up, low): """ - Возвращает стоимость элементарной трансдукции up->low - или np.inf, если такой элементарной трансдукции нет + Returns the cost of elementary transduction up->low + or np.inf, if there is no such elementary transduction - Аргументы: + Arguments: ---------- up, low : string - элементы элементарной трансдукции + elements of elementary transduction - Возвращает: + Returns: ----------- - cost : float - стоимость элементарной трансдукции up->low - (np.inf, если такая трансдукция отсутствует) + const : float + the cost of elementary transduction up->low + (np.inf, if there is no such transduction) """ up_costs = self.operation_costs.get(up, None) if up_costs is None: @@ -345,9 +345,9 @@ def get_operation_cost(self, up, low): def inverse(self): """ - Строит пробразователь, задающий обратное конечное преобразование + Build a converter specifying the inverse finite transformation """ - # УПРОСТИТЬ ОБРАЩЕНИЕ!!! + # SIMPLIFY HANDLING!!! inversed_transducer = SegmentTransducer(self.alphabet, operation_costs=dict()) inversed_transducer.operation_costs = self._reversed_operation_costs inversed_transducer._reversed_operation_costs = self.operation_costs @@ -359,29 +359,29 @@ def inverse(self): def distance(self, first, second, return_transduction=False): """ - Вычисляет трансдукцию минимальной стоимости, - отображающую first в second + Calculates the minimum cost transduction, + displaying first in second - Аргументы: + Arguments: ----------- first : string second : string - Верхний и нижний элементы трансдукции + Upper and lower transduction elements return_transduction : bool (optional, default=False) - следует ли возвращать трансдукцию минимального веса - (см. возвращаемое значение) + should the minimum weight transduction be returned + (see return value) - Возвращает: + Returns: ----------- - (final_cost, transductions) : tuple(float, list) - если return_transduction=True, то возвращает - минимальную стоимость трансдукции, переводящей first в second - и список трансдукций с данной стоимостью - - final_cost : float - если return_transduction=False, то возвращает - минимальную стоимость трансдукции, переводящей first в second + (final_cost, conversions) : tuple(float, list) + if return_transduction=True, then returns + the minimum cost of the transduction that converts first to second + and a list of transductions with a given cost + + final_const : float + if return_transduction=False, it returns + the minimum cost of the transduction that converts first to second """ if return_transduction: add_pred = (lambda x, y: (y == np.inf or x < y)) @@ -404,13 +404,13 @@ def distance(self, first, second, return_transduction=False): def transduce(self, first, second, threshold): """ - Возвращает все трансдукции, переводящие first в second, - чья стоимость не превышает threshold + Returns all transductions that translate first to second, + whose value does not exceed threshold - Возвращает: + Returns: ---------- result : list - список вида [(трансдукция, стоимость)] + a list of the form [(transduction, cost)] """ add_pred = (lambda x, y: x <= threshold) clear_pred = (lambda x, y: False) @@ -424,20 +424,20 @@ def transduce(self, first, second, threshold): def lower_transductions(self, word, max_cost, return_cost=True): """ - Возвращает все трансдукции с верхним элементом word, - чья стоимость не превышает max_cost + Returns all transductions with the top word element, + whose cost does not exceed max_cost - ` Возвращает: + ` Returns: ---------- result : list - список вида [(трансдукция, стоимость)], если return_cost=True - список трансдукций, если return_cost=False - список отсортирован в порядке возрастания стоимости трансдукции + a list of the form [(transduction, cost)] if return_cost=True + the list of transductions, if return_cost=False + the list is sorted in ascending order of the cost of transduction """ prefixes = [[] for i in range(len(word) + 1)] prefixes[0].append(((), 0.0)) for pos in range(len(prefixes)): - # вставки + # inserts prefixes[pos] = self._perform_insertions(prefixes[pos], max_cost) max_upperside_length = min(len(word) - pos, self.max_up_length) for upperside_length in range(1, max_upperside_length + 1): @@ -479,40 +479,40 @@ def upper_transductions(self, word, max_cost, return_cost=True): def _fill_levenshtein_table(self, first, second, update_func, add_pred, clear_pred, threshold=None): """ - Функция, динамически заполняющая таблицу costs стоимости трансдукций, - costs[i][j] --- минимальная стоимость трансдукции, - переводящей first[:i] в second[:j] + A function that dynamically fills in the costs table of the cost of transductions, + costs[i][j] --- minimum cost of transduction, + translating first[:i] to second[:j] - Аргументы: + Arguments: ---------- first, second : string - Верхний и нижний элементы трансдукции + Upper and lower transduction elements update_func : callable, float*float -> bool - update_func(x, y) возвращает новое значение в ячейке таблицы costs, - если старое значение --- y, а потенциально новое значение --- x - везде update_func = min + update_func(x, y) returns a new value in a cell of the costs table, + if the old value is --- y, and the potentially new value is--- x + everywhere update_func = min add_pred : callable : float*float -> bool - add_pred(x, y) возвращает, производится ли добавление - нового элемента p стоимости x в ячейку backtraces[i][j] - в зависимости от значения costs[i][j]=y и текущей стоимости x + add_pred(x, y) returns whether an addition is being made + a new element p of value x in the cell backtraces[i][j] + depending on the value of costs[i][j]=y and the current cost of x clear_pred : callable : float*float -> bool - clear_pred(x, y) возвращает, производится ли очистка - ячейки backtraces[i][j] в зависимости от значения costs[i][j]=y - и текущей стоимости x элемента p, добавляемого в эту ячейку + clear_pred(x, y) returns whether the cleanup is being performed + cells backtraces[i][j] depending on the value of costs[i][j]=y + and the current cost x of the element p being added to this cell - Возвращает: + Returns: ----------- costs : array, dtype=float, shape=(len(first)+1, len(second)+1) - массив, в ячейке с индексами i, j которого хранится - минимальная стоимость трансдукции, переводящей first[:i] в second[:j] + an array in a cell with indexes i, j of which is stored + the minimum cost of the transduction that translates first[:i] to second[:j] backtraces : array, dtype=list, shape=(len(first)+1, len(second)+1) - массив, в ячейке с индексами i, j которого хранятся - обратные ссылки на предыдущую ячейку в оптимальной трансдукции, - приводящей в ячейку backtraces[i][j] + an array in a cell with indexes i, j of which are stored + backlinks to the previous cell in optimal transduction, + leading to the cell backtraces[i][j] """ m, n = len(first), len(second) - # если threshold=None, то в качестве порога берётся удвоенная стоимость - # трансдукции, отображающей символы на одинаковых позициях друг в друга + # if threshold=None, then double the cost is taken as the threshold + # a transduction that maps symbols at the same positions into each other if threshold is None: threshold = 0.0 for a, b in zip(first, second): @@ -524,7 +524,7 @@ def _fill_levenshtein_table(self, first, second, update_func, add_pred, clear_pr for b in second[m:]: threshold += self.get_operation_cost('', b) threshold *= 2 - # инициализация возвращаемых массивов + # initialization of returned arrays costs = np.zeros(shape=(m + 1, n + 1), dtype=np.float64) costs[:] = np.inf backtraces = [None] * (m + 1) @@ -542,7 +542,7 @@ def _fill_levenshtein_table(self, first, second, update_func, add_pred, clear_pr if costs[i][j] > threshold: continue if len(backtraces[i][j]) == 0 and i + j > 0: - continue # не нашлось обратных ссылок + continue # no backlinks found for j_right in range((j if i_right > i else j + 1), min(j + max_low_length, n) + 1): low = second[j: j_right] @@ -560,8 +560,8 @@ def _fill_levenshtein_table(self, first, second, update_func, add_pred, clear_pr def _make_reversed_operation_costs(self): """ - Заполняет массив _reversed_operation_costs - на основе имеющегося массива operation_costs + Populates the _reversed_operation_costs array + based on the existing operation_costs array """ _reversed_operation_costs = dict() for up, costs in self.operation_costs.items(): @@ -573,10 +573,10 @@ def _make_reversed_operation_costs(self): def _make_maximal_key_lengths(self): """ - Вычисляет максимальную длину элемента low - в элементарной трансдукции (up, low) для каждого up - и максимальную длину элемента up - в элементарной трансдукции (up, low) для каждого low + Calculates the maximum length of the element low + in the elementary transduction (up, low) for each up + and the maximum length of the up element + in the elementary transduction (up, low) for each low """ self.max_up_length = \ (max(len(up) for up in self.operation_costs) @@ -594,27 +594,27 @@ def _make_maximal_key_lengths(self): def _backtraces_to_transductions(self, first, second, backtraces, threshold, return_cost=False): """ - Восстанавливает трансдукции по таблице обратных ссылок + Restores transductions from the backlink table - Аргументы: + Arguments: ---------- first, second : string - верхние и нижние элементы трансдукции + upper and lower transduction elements backtraces : array-like, dtype=list, shape=(len(first)+1, len(second)+1) - таблица обратных ссылок + backlink table threshold : float - порог для отсева трансдукций, - возвращаются только трансдукции стоимостью <= threshold + threshold for elimination by transduction, + only transductions worth <= threshold are returned return_cost : bool (optional, default=False) - если True, то вместе с трансдукциями возвращается их стоимость + if True, then their cost is returned along with the transductions - Возвращает: + Returns: ----------- result : list - список вида [(трансдукция, стоимость)], если return_cost=True - и вида [трансдукция], если return_cost=False, - содержащий все трансдукции, переводящие first в second, - чья стоимость не превышает threshold + a list of the form [(transduction, cost)] if return_cost=True + and of the form [transduction] if return_cost=False, + containing all the transductions that translate first to second, + whose value does not exceed threshold """ m, n = len(first), len(second) agenda = [None] * (m + 1) @@ -640,20 +640,20 @@ def _backtraces_to_transductions(self, first, second, backtraces, threshold, ret def _perform_insertions(self, initial, max_cost): """ - возвращает все трансдукции стоимости <= max_cost, - которые можно получить из элементов initial + returns all value transductions <= max_cost, + which can be obtained from the initial elements - Аргументы: + Arguments: ---------- initial : list of tuples - список исходных трансдукций вида [(трансдукция, стоимость)] - max_cost : float - максимальная стоимость трансдукции + list of initial transductions of the form [(transduction, cost)] + max_const : float + maximum cost of transduction - Возвращает: + Returns: ----------- final : list of tuples - финальный список трансдукций вида [(трансдукция, стоимость)] + the final list of transductions of the form [(transduction, cost)] """ queue = list(initial) final = initial @@ -681,10 +681,10 @@ def _make_default_operation_costs(self, allow_spaces=False): if allow_spaces: current_costs[" "] = 1.0 self.operation_costs[a] = current_costs - # транспозиции + # transpositions for a, b in itertools.permutations(self.alphabet, 2): self.operation_costs[a + b] = {b + a: 1.0} - # пробелы + # spaces if allow_spaces: self.operation_costs[" "] = {c: 1.0 for c in self.alphabet} self.operation_costs[" "][""] = 1.0