16 KiB
Komputerowe wspomaganie tłumaczenia
13,14. Korekta pisowni [laboratoria]
Rafał Jaworski (2021)
Współczesne programy typu CAT nie mogą obyć się bez korektora pisowni. Na bieżąco kontrolują one pisownię wyrazów po stronie docelowej, czyli tam, gdzie tłumacz wpisuje tłumaczenie. Jest to niezwykle istotne w sytuacji, gdy język docelowy nie jest dla tłumacza językiem ojczystym. Co więcej, badania wykazują, iż korekta pisowni wydatnie zmniejsza liczbę błędów w każdych scenariuszach.
Co poprawia korekta pisowni? Słowa. Tylko lub aż słowa. Program dokonujący korekty pisowni przegląda tekst słowo po słowie i sprawdza, czy należy ono do słownika. Jeśli nie, sygnalizowany jest błąd oraz, jeśli to możliwe, podawane sugestie poprawy. Co istotne, korektor pisowni nie zajmuje się szeregiem błędów, które mieszczą się w dziedzinie korekty gramatycznej, w tym:
- interpunkcją
- powtórzeniami wyrazów
- stylistyką.
Aby zaimplementować korektor pisowni bez wątpienia potrzebny jest słownik. Skorzystajmy ze słownika, który znajdziemy w folderze data, pochodzącego z narzędzia Hunspell. Jest on spakowany - użyjmy techniki czytania z archiwum zip bez rozpakowywania. Poniższy kod wypisze fragment ze środka słownika.
from zipfile import ZipFile
with ZipFile('data/hunspell_pl.zip') as zipped_dictionary:
with zipped_dictionary.open('hunspell_pl.txt') as dictionary_file:
count = 0
for line_bytes in dictionary_file:
count += 1
if count >= 100000 and count <= 100020:
line = line_bytes.decode('utf-8')
print(line.rstrip())
[0;31m---------------------------------------------------------------------------[0m [0;31mFileNotFoundError[0m Traceback (most recent call last) [0;32m<ipython-input-9-cf46e3e07935>[0m in [0;36m<cell line: 3>[0;34m()[0m [1;32m 1[0m [0;32mfrom[0m [0mzipfile[0m [0;32mimport[0m [0mZipFile[0m[0;34m[0m[0;34m[0m[0m [1;32m 2[0m [0;34m[0m[0m [0;32m----> 3[0;31m [0;32mwith[0m [0mZipFile[0m[0;34m([0m[0;34m'data/hunspell_pl.zip'[0m[0;34m)[0m [0;32mas[0m [0mzipped_dictionary[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 4[0m [0;32mwith[0m [0mzipped_dictionary[0m[0;34m.[0m[0mopen[0m[0;34m([0m[0;34m'hunspell_pl.txt'[0m[0;34m)[0m [0;32mas[0m [0mdictionary_file[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [1;32m 5[0m [0mcount[0m [0;34m=[0m [0;36m0[0m[0;34m[0m[0;34m[0m[0m [0;32m/usr/lib/python3.10/zipfile.py[0m in [0;36m__init__[0;34m(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps)[0m [1;32m 1249[0m [0;32mwhile[0m [0;32mTrue[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [1;32m 1250[0m [0;32mtry[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;32m-> 1251[0;31m [0mself[0m[0;34m.[0m[0mfp[0m [0;34m=[0m [0mio[0m[0;34m.[0m[0mopen[0m[0;34m([0m[0mfile[0m[0;34m,[0m [0mfilemode[0m[0;34m)[0m[0;34m[0m[0;34m[0m[0m [0m[1;32m 1252[0m [0;32mexcept[0m [0mOSError[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [1;32m 1253[0m [0;32mif[0m [0mfilemode[0m [0;32min[0m [0mmodeDict[0m[0;34m:[0m[0;34m[0m[0;34m[0m[0m [0;31mFileNotFoundError[0m: [Errno 2] No such file or directory: 'data/hunspell_pl.zip'
Miejmy na uwadze, że powyższy słownik zawiera tylko formy podstawowe słowa, np. zawiera słowo "kalendarz", ale nie zawiera "kalendarze", "kalendarza", "kalendarzy" itp.
Algorytm korekty pisowni na podstawie słownika powinien działać według następujących kroków:
- Wczytanie słownika do zbioru (set)
- Podział tekstu do korekty na słowa (podział po spacji)
- Dla każdego słowa wypisać, czy jest ono poprawne (znajduje się w słowniku) czy nie.
Ćwiczenie 1: Zaimplementuj podstawowy algorytm korekty pisowni według powyższych wytycznych.
def correct_text(text, dictionary_set):
words_to_check = text.split()
correction_results = {}
for word in words_to_check:
word_cleaned = word.strip(",.!?").lower()
is_correct = word_cleaned in dictionary_set
correction_results[word] = is_correct
return correction_results
dictionary_set = {
'kalendarz', 'jest', 'pełen', 'wydarzeń', 'są', 'używane', 'codziennie'
}
text_to_check = "Kalendarz jest pełen wydarzeń. kalendarze są używane codziennie. albo i nie"
correction_results = correct_text(text_to_check, dictionary_set)
print(correction_results)
{'Kalendarz': True, 'jest': True, 'pełen': True, 'wydarzeń.': True, 'kalendarze': False, 'są': True, 'używane': True, 'codziennie.': True, 'albo': False, 'i': False, 'nie': False}
To jednak oczywiście nie wszystko. Do tej pory mamy funkcjonalność sygnalizowania słów błędnych, ale każdy dobry korektor pisowni potrafi podać sugestie poprawek. W tym celu musimy stawić czoła następującemu problemowi - wygenerowanie listy słów podobnych do danego słowa błędnego, które znajdują się w słowniku.
W pierwszej kolejności musimy zdefiniować podobieństwo między wyrazami. Posłuży do tego dobrze nam znana odległość Levenshteina - wyrazy podobne to takie, dla których dystans Levenshteina jest niewielki (np. równy 1 lub 2). Teraz brakuje tylko algorytmu wyszukiwania wyrazów w danym słowniku, które znajdują się niedaleko (w sensie Levenshteina) danego błędnego słowa.
Rozważmy następujący algorytm: dla danego słownika $D$ i błędnego słowa $w \notin D$:
- Wygeneruj zbiór $L_1(w)$ wszystkich słów, których odległość Levenshteina od $w$ wynosi 1.
- Wyznacz zbiór $S_1(w)=L_1(w) \cap D$
- Wyznacz zbiór $L_2(w)=\bigcup_{v \in L_1(w)} L_1(v)$
- Wyznacz zbiór $S_2(w)=L_2(w) \cap D$
- Zwróć jako listę sugestii: $S_1 \cup S_2$
Ćwiczenie 2: Napisz funkcję do generowania zbioru $L_1(w)$ - wszystkich słów znajdujących się w odległości Levenshteina 1 od danego słowa w.
def L1(word):
letters = 'abcdefghijklmnopqrstuvwxyz'
n = len(word)
results = set()
for i in range(n):
results.add(word[:i] + word[i+1:])
for i in range(n):
for c in letters:
if c != word[i]:
results.add(word[:i] + c + word[i+1:])
for i in range(n + 1):
for c in letters:
results.add(word[:i] + c + word[i:])
return results
word = "kot"
l1_words = L1(word)
print(l1_words)
{'ykot', 'tot', 'kfot', 'kgot', 'kotv', 'koxt', 'koto', 'hkot', 'kotc', 'dot', 'kjt', 'kolt', 'koti', 'kzot', 'kott', 'kat', 'kotx', 'kit', 'kpt', 'kowt', 'koo', 'kxt', 'koz', 'koyt', 'koa', 'kon', 'klot', 'iot', 'kotp', 'kozt', 'jot', 'klt', 'yot', 'krt', 'kort', 'ikot', 'kodt', 'kost', 'koit', 'fot', 'ekot', 'kdot', 'kbt', 'kotz', 'krot', 'xot', 'kotb', 'cot', 'rkot', 'ktot', 'kotl', 'kote', 'kop', 'kou', 'kots', 'kt', 'kmt', 'kox', 'dkot', 'kof', 'koc', 'fkot', 'ot', 'koat', 'kzt', 'kok', 'kuot', 'kos', 'sot', 'mkot', 'mot', 'koi', 'tkot', 'kotf', 'kmot', 'kut', 'kor', 'xkot', 'kotg', 'koft', 'pot', 'nkot', 'aot', 'rot', 'khot', 'ktt', 'kow', 'koq', 'jkot', 'kkot', 'kotw', 'kdt', 'kojt', 'kcot', 'kotm', 'okot', 'kct', 'akot', 'kst', 'kod', 'kwt', 'uot', 'kotn', 'kxot', 'ko', 'koot', 'koet', 'keot', 'gkot', 'kota', 'zkot', 'kft', 'koh', 'kjot', 'kgt', 'kotr', 'qkot', 'kht', 'pkot', 'koth', 'koty', 'kbot', 'kiot', 'koe', 'kvot', 'eot', 'hot', 'bkot', 'kog', 'kwot', 'kotq', 'not', 'kpot', 'kotj', 'kqt', 'kob', 'vkot', 'kvt', 'komt', 'ckot', 'lot', 'kaot', 'kol', 'koy', 'koj', 'qot', 'ket', 'kobt', 'bot', 'koct', 'koqt', 'wot', 'skot', 'kogt', 'got', 'kom', 'koht', 'kovt', 'kout', 'oot', 'knt', 'kov', 'kotk', 'kont', 'wkot', 'kqot', 'vot', 'kyt', 'kopt', 'kkt', 'lkot', 'ukot', 'kotu', 'knot', 'kyot', 'kokt', 'kotd', 'zot', 'ksot'}
Ćwiczenie 3: Napisz funkcję do generowania sugestii poprawek dla danego słowa według opisanego wcześniej algorytmu.
import string
def generate_edits(word):
letters = string.ascii_lowercase
splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
deletes = [L + R[1:] for L, R in splits if R]
transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
inserts = [L + c + R for L, R in splits for c in letters]
return set(deletes + transposes + replaces + inserts)
def find_correct_words(words, dictionary):
return set(word for word in words if word in dictionary)
def generate_suggestions(word, dictionary):
return find_correct_words(generate_edits(word), dictionary)
dictionary = set(["cat", "cot", "dog", "dot", "cute", "cuts", "cup", "cut", "cots"])
word = "cutz"
suggestions = generate_suggestions(word, dictionary)
print(suggestions)
{'cute', 'cut', 'cuts'}