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
plik_zip = 'data/hunspell_pl.zip'
plik_txt = 'hunspell_pl.txt'
with ZipFile(plik_zip) as zipped_dictionary:
with zipped_dictionary.open(plik_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())
kalecząc kaledonidy kaledoński kalefaktor kalejdofon kalejdoskop kalejdoskopowość kalejdoskopowy kaleka kaleki kalema kalendarium kalendarz kalendarzowy kalendarzyk kalendy kalenica kalenicowy kalepin kalesonki kalesony
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.
import zipfile
def load_dictionary(zip_filepath, dictionary_filename):
with zipfile.ZipFile(zip_filepath, 'r') as zip_ref:
with zip_ref.open(dictionary_filename) as file:
words = set(word.strip().decode('utf-8') for word in file.readlines())
return words
def correct_text(text, dictionary):
words = text.split()
results = []
for word in words:
if word in dictionary:
results.append(f"'{word}' - ok")
else:
results.append(f"'{word}' - nok")
return results
dictionary = load_dictionary(plik_zip, plik_txt)
text_to_check = "To jest przykładowy tekst do sprawdzenia pisowni."
results = correct_text(text_to_check, dictionary)
for result in results:
print(result)
'To' - nok 'jest' - ok 'przykładowy' - ok 'tekst' - ok 'do' - ok 'sprawdzenia' - nok 'pisowni.' - nok
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(w):
letters = 'abcdefghijklmnopqrstuvwxyz'
splits = [(w[:i], w[i:]) for i in range(len(w) + 1)]
deletes = [L + R[1:] for L, R in splits if R]
inserts = [L + c + R for L, R in splits for c in letters]
replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
return set(deletes + inserts + replaces)
word = "przykład"
L1_set = L1(word)
print(L1_set)
{'przykbad', 'przykłay', 'przukład', 'crzykład', 'przykqład', 'wprzykład', 'przykładi', 'przykłtad', 'yprzykład', 'przykłsd', 'przykłvad', 'przxykład', 'przykzład', 'przykładg', 'pyrzykład', 'przyoład', 'prjykład', 'przeykład', 'przykkład', 'przikład', 'przyksad', 'sprzykład', 'pozykład', 'przyxkład', 'przyiład', 'pzrzykład', 'pwrzykład', 'jprzykład', 'przykła', 'grzykład', 'przykvad', 'przykładh', 'przykłae', 'przcykład', 'przmykład', 'plrzykład', 'przyckład', 'wrzykład', 'prvykład', 'erzykład', 'pzzykład', 'jrzykład', 'przykvład', 'przykpad', 'przmkład', 'przykłkd', 'przyeład', 'przyzład', 'przykłas', 'xprzykład', 'przykcład', 'przdkład', 'przykeład', 'przytkład', 'przykłap', 'przvkład', 'zrzykład', 'przynład', 'przfkład', 'przykłrd', 'mrzykład', 'przyktład', 'przyskład', 'frzykład', 'przykłnad', 'przykłaj', 'przykłqd', 'przyhkład', 'purzykład', 'przwykład', 'przykłod', 'przbykład', 'ptrzykład', 'przykłao', 'przykłal', 'porzykład', 'perzykład', 'przrykład', 'przkkład', 'przykłag', 'pqzykład', 'przykław', 'przykfład', 'prcykład', 'przyktad', 'praykład', 'przlykład', 'przykładk', 'tprzykład', 'drzykład', 'ppzykład', 'pruzykład', 'przyfład', 'przykmad', 'przymład', 'przykhad', 'prznykład', 'mprzykład', 'przgkład', 'przyzkład', 'pxrzykład', 'przykładm', 'przykłpd', 'przykzad', 'przykyad', 'przwkład', 'qrzykład', 'pirzykład', 'przekład', 'przykłado', 'srzykład', 'przykłabd', 'przhkład', 'iprzykład', 'przykłwad', 'przgykład', 'xrzykład', 'rrzykład', 'przykładz', 'pvzykład', 'prgzykład', 'pryzykład', 'przbkład', 'przyaład', 'fprzykład', 'przykłgd', 'prhykład', 'przykoad', 'nrzykład', 'przhykład', 'pezykład', 'przykładl', 'przsykład', 'przykłgad', 'przykłamd', 'przykłhd', 'prszykład', 'przyrład', 'przykaład', 'przykłads', 'phzykład', 'przykłayd', 'nprzykład', 'przydład', 'przykuad', 'pvrzykład', 'przyekład', 'przykładc', 'przykładr', 'lrzykład', 'orzykład', 'prrzykład', 'przybkład', 'przykdład', 'brzykład', 'przykłjad', 'prznkład', 'przykłlad', 'przykładw', 'zprzykład', 'przyhład', 'przykwład', 'przakład', 'przykmład', 'przykłaid', 'ptzykład', 'przyykład', 'eprzykład', 'pfrzykład', 'pgzykład', 'przykłkad', 'przyjład', 'pgrzykład', 'pkzykład', 'przykłxad', 'dprzykład', 'gprzykład', 'pfzykład', 'przykłmd', 'przykiad', 'rprzykład', 'przypład', 'przykłakd', 'przykłfd', 'przyuład', 'pmrzykład', 'przykłsad', 'prhzykład', 'przpykład', 'przyyład', 'prgykład', 'przykładx', 'przykłade', 'przvykład', 'przykłhad', 'przykłzd', 'przykłnd', 'prsykład', 'przqkład', 'vrzykład', 'przykłld', 'przykoład', 'przjykład', 'psrzykład', 'pyzykład', 'przykłmad', 'pryykład', 'przylład', 'przykłid', 'prqzykład', 'przykłiad', 'przykłazd', 'przykłbd', 'przykłaf', 'przckład', 'pmzykład', 'prjzykład', 'przykłatd', 'hprzykład', 'pxzykład', 'kprzykład', 'przykładp', 'prczykład', 'pdzykład', 'prmzykład', 'przykłaz', 'przuykład', 'przykłxd', 'prziykład', 'przykbład', 'przykłau', 'priykład', 'pszykład', 'przzkład', 'przykładt', 'proykład', 'przrkład', 'przymkład', 'pzykład', 'prezykład', 'przykłald', 'uprzykład', 'przqykład', 'przykłafd', 'prvzykład', 'przykłapd', 'przykłand', 'przykyład', 'przfykład', 'pizykład', 'przykwad', 'hrzykład', 'przykławd', 'przykładu', 'pczykład', 'prazykład', 'prlzykład', 'przykłud', 'przlkład', 'przykłada', 'przykjład', 'prnykład', 'przyikład', 'przywkład', 'pwzykład', 'przyknad', 'prfykład', 'przykłbad', 'parzykład', 'przykłdad', 'prztkład', 'pjrzykład', 'przykrad', 'przzykład', 'przyvkład', 'przjkład', 'puzykład', 'pjzykład', 'pcrzykład', 'przykładb', 'przykłaqd', 'przykpład', 'prxzykład', 'prpykład', 'przykłab', 'prdzykład', 'przylkład', 'prmykład', 'przykładf', 'irzykład', 'yrzykład', 'przynkład', 'przyakład', 'pnrzykład', 'przykłqad', 'pruykład', 'przokład', 'przyvład', 'przpkład', 'prozykład', 'pbzykład', 'przykiład', 'prwykład', 'przykłak', 'pbrzykład', 'przykładq', 'przykłfad', 'przykjad', 'przyukład', 'przykłyad', 'przykłtd', 'przdykład', 'przykad', 'przykłard', 'prpzykład', 'przykłdd', 'przykłai', 'preykład', 'prtzykład', 'przykłwd', 'przykłax', 'przoykład', 'przykłcad', 'cprzykład', 'przyxład', 'krzykład', 'przykłead', 'przykłaud', 'przydkład', 'przykłvd', 'przykłcd', 'przykłah', 'przykłyd', 'przykłaa', 'pprzykład', 'prztykład', 'aprzykład', 'przykłagd', 'przypkład', 'przyrkład', 'przxkład', 'przykłoad', 'przykłasd', 'prwzykład', 'przykłd', 'przyklład', 'prxykład', 'przykłam', 'przykłaod', 'vprzykład', 'przygkład', 'rzykład', 'przykłady', 'prdykład', 'przaykład', 'prbykład', 'przykłaxd', 'przkład', 'przykłajd', 'przykłac', 'przyfkład', 'prtykład', 'przykead', 'przybład', 'lprzykład', 'przykgad', 'prrykład', 'przyknład', 'przykłaq', 'qprzykład', 'przykłpad', 'pnzykład', 'pqrzykład', 'plzykład', 'przykqad', 'oprzykład', 'przykxad', 'przyokład', 'przytład', 'przykkad', 'przykcad', 'przykłuad', 'przykłrad', 'przykłaed', 'przkykład', 'przykładj', 'przykłjd', 'przysład', 'przykładd', 'przykład', 'prlykład', 'przykaad', 'przykxład', 'przykłahd', 'przykładn', 'pazykład', 'prfzykład', 'przyksład', 'przyqład', 'przyqkład', 'prqykład', 'przyład', 'prnzykład', 'przykgład', 'przykdad', 'przykłed', 'przykłar', 'przycład', 'przykłan', 'przykłaad', 'przykłat', 'przskład', 'pkrzykład', 'przykłav', 'trzykład', 'przygład', 'przykładv', 'prbzykład', 'pdrzykład', 'przykrład', 'prkzykład', 'przykfad', 'przykłacd', 'przykuład', 'przyklad', 'arzykład', 'przykłavd', 'przyjkład', 'przywład', 'prykład', 'prizykład', 'urzykład', 'prkykład', 'bprzykład', 'przykłzad', 'przykhład', 'phrzykład'}
Ćwiczenie 3: Napisz funkcję do generowania sugestii poprawek dla danego słowa według opisanego wcześniej algorytmu.
def generate_suggestions(text, dictionary):
words = text.split()
results = []
for word in words:
if word in dictionary:
results.append(f"'{word}' - ok")
else:
suggestions = L1(word)
valid_suggestions = [s for s in suggestions if s in dictionary]
if valid_suggestions:
suggestion_str = ", ".join(valid_suggestions)
results.append(f"'{word}' - nok (sugerowane poprawki: {suggestion_str})")
else:
results.append(f"'{word}' - nok (brak sugestii)")
return results
text_to_check = "To jest przykładowy tekst do sprawdzenia pisowni."
results = generate_suggestions(text_to_check, dictionary)
for result in results:
print(result)
'To' - nok (sugerowane poprawki: T, Tb, Tt, Tot, o, no, to, oo, Tc, Tl, Ta, co, do, po, bo, Th, ko, Te, eo, go, Tm, ho, Tob, Tom, Ti, Tr) 'jest' - ok 'przykładowy' - ok 'tekst' - ok 'do' - ok 'sprawdzenia' - nok (brak sugestii) 'pisowni.' - nok (sugerowane poprawki: pisownia)