{ "cells": [ { "cell_type": "markdown", "id": "ordered-wrestling", "metadata": { "id": "ordered-wrestling" }, "source": [ "![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n", "
\n", "

Komputerowe wspomaganie tłumaczenia

\n", "

13,14. Korekta pisowni [laboratoria]

\n", "

Rafał Jaworski (2021)

\n", "
\n", "\n", "![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)" ] }, { "cell_type": "markdown", "id": "featured-afghanistan", "metadata": { "id": "featured-afghanistan" }, "source": [ "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." ] }, { "cell_type": "markdown", "id": "seventh-genre", "metadata": { "id": "seventh-genre" }, "source": [ "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:\n", "* interpunkcją\n", "* powtórzeniami wyrazów\n", "* stylistyką." ] }, { "cell_type": "markdown", "id": "sticky-society", "metadata": { "id": "sticky-society" }, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 9, "id": "familiar-terrace", "metadata": { "scrolled": true, "colab": { "base_uri": "https://localhost:8080/", "height": 341 }, "id": "familiar-terrace", "outputId": "1d74751b-6962-4d5a-ca3c-2d1e9f8f4b60" }, "outputs": [ { "output_type": "error", "ename": "FileNotFoundError", "evalue": "[Errno 2] No such file or directory: 'data/hunspell_pl.zip'", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mzipfile\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mZipFile\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mZipFile\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'data/hunspell_pl.zip'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mzipped_dictionary\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mzipped_dictionary\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'hunspell_pl.txt'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mdictionary_file\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mcount\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/usr/lib/python3.10/zipfile.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps)\u001b[0m\n\u001b[1;32m 1249\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1250\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1251\u001b[0;31m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mfp\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mio\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfilemode\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1252\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mOSError\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1253\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mfilemode\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mmodeDict\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'data/hunspell_pl.zip'" ] } ], "source": [ "from zipfile import ZipFile\n", "\n", "with ZipFile('data/hunspell_pl.zip') as zipped_dictionary:\n", " with zipped_dictionary.open('hunspell_pl.txt') as dictionary_file:\n", " count = 0\n", " for line_bytes in dictionary_file:\n", " count += 1\n", " if count >= 100000 and count <= 100020:\n", " line = line_bytes.decode('utf-8')\n", " print(line.rstrip())" ] }, { "cell_type": "markdown", "id": "dominant-insurance", "metadata": { "id": "dominant-insurance" }, "source": [ "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." ] }, { "cell_type": "markdown", "id": "single-brighton", "metadata": { "id": "single-brighton" }, "source": [ "Algorytm korekty pisowni na podstawie słownika powinien działać według następujących kroków:\n", "1. Wczytanie słownika do zbioru (set)\n", "2. Podział tekstu do korekty na słowa (podział po spacji)\n", "3. Dla każdego słowa wypisać, czy jest ono poprawne (znajduje się w słowniku) czy nie." ] }, { "cell_type": "markdown", "id": "needed-watson", "metadata": { "id": "needed-watson" }, "source": [ "### Ćwiczenie 1: Zaimplementuj podstawowy algorytm korekty pisowni według powyższych wytycznych." ] }, { "cell_type": "code", "execution_count": 8, "id": "economic-southeast", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "economic-southeast", "outputId": "3b304305-23e5-4ced-e65d-bdc6e1b06fbe" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "{'Kalendarz': True, 'jest': True, 'pełen': True, 'wydarzeń.': True, 'kalendarze': False, 'są': True, 'używane': True, 'codziennie.': True, 'albo': False, 'i': False, 'nie': False}\n" ] } ], "source": [ "def correct_text(text, dictionary_set):\n", " words_to_check = text.split()\n", " correction_results = {}\n", " for word in words_to_check:\n", " word_cleaned = word.strip(\",.!?\").lower()\n", " is_correct = word_cleaned in dictionary_set\n", " correction_results[word] = is_correct\n", "\n", " return correction_results\n", "\n", "dictionary_set = {\n", " 'kalendarz', 'jest', 'pełen', 'wydarzeń', 'są', 'używane', 'codziennie'\n", "}\n", "text_to_check = \"Kalendarz jest pełen wydarzeń. kalendarze są używane codziennie. albo i nie\"\n", "correction_results = correct_text(text_to_check, dictionary_set)\n", "print(correction_results)" ] }, { "cell_type": "markdown", "id": "endless-slide", "metadata": { "id": "endless-slide" }, "source": [ "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." ] }, { "cell_type": "markdown", "id": "adult-freight", "metadata": { "id": "adult-freight" }, "source": [ "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." ] }, { "cell_type": "markdown", "id": "everyday-things", "metadata": { "id": "everyday-things" }, "source": [ "Rozważmy następujący algorytm: dla danego słownika $D$ i błędnego słowa $w \\notin D$:\n", "1. Wygeneruj zbiór $L_1(w)$ wszystkich słów, których odległość Levenshteina od $w$ wynosi 1.\n", "2. Wyznacz zbiór $S_1(w)=L_1(w) \\cap D$\n", "3. Wyznacz zbiór $L_2(w)=\\bigcup_{v \\in L_1(w)} L_1(v)$\n", "4. Wyznacz zbiór $S_2(w)=L_2(w) \\cap D$\n", "5. Zwróć jako listę sugestii: $S_1 \\cup S_2$" ] }, { "cell_type": "markdown", "id": "industrial-convert", "metadata": { "id": "industrial-convert" }, "source": [ "### Ć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." ] }, { "cell_type": "code", "execution_count": 1, "id": "built-sally", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "built-sally", "outputId": "f52b9f96-8dfe-42be-dbc6-1ac1279aa9d0" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "{'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'}\n" ] } ], "source": [ "def L1(word):\n", " letters = 'abcdefghijklmnopqrstuvwxyz'\n", " n = len(word)\n", " results = set()\n", "\n", " for i in range(n):\n", " results.add(word[:i] + word[i+1:])\n", "\n", " for i in range(n):\n", " for c in letters:\n", " if c != word[i]:\n", " results.add(word[:i] + c + word[i+1:])\n", "\n", " for i in range(n + 1):\n", " for c in letters:\n", " results.add(word[:i] + c + word[i:])\n", "\n", " return results\n", "\n", "\n", "word = \"kot\"\n", "l1_words = L1(word)\n", "print(l1_words)" ] }, { "cell_type": "markdown", "id": "wireless-uncle", "metadata": { "id": "wireless-uncle" }, "source": [ "### Ćwiczenie 3: Napisz funkcję do generowania sugestii poprawek dla danego słowa według opisanego wcześniej algorytmu." ] }, { "cell_type": "code", "execution_count": 2, "id": "coordinated-cooperation", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "coordinated-cooperation", "outputId": "a19fcc88-b7c6-46d0-9a2c-f0b2a53f087a" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "{'cute', 'cut', 'cuts'}\n" ] } ], "source": [ "import string\n", "\n", "def generate_edits(word):\n", " letters = string.ascii_lowercase\n", " splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]\n", " deletes = [L + R[1:] for L, R in splits if R]\n", " transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]\n", " replaces = [L + c + R[1:] for L, R in splits if R for c in letters]\n", " inserts = [L + c + R for L, R in splits for c in letters]\n", " return set(deletes + transposes + replaces + inserts)\n", "\n", "def find_correct_words(words, dictionary):\n", " return set(word for word in words if word in dictionary)\n", "\n", "def generate_suggestions(word, dictionary):\n", " return find_correct_words(generate_edits(word), dictionary)\n", "\n", "dictionary = set([\"cat\", \"cot\", \"dog\", \"dot\", \"cute\", \"cuts\", \"cup\", \"cut\", \"cots\"])\n", "\n", "word = \"cutz\"\n", "suggestions = generate_suggestions(word, dictionary)\n", "print(suggestions)" ] } ], "metadata": { "author": "Rafał Jaworski", "email": "rjawor@amu.edu.pl", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "lang": "pl", "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" }, "subtitle": "13,14. Korekta pisowni", "title": "Komputerowe wspomaganie tłumaczenia", "year": "2021", "colab": { "provenance": [] } }, "nbformat": 4, "nbformat_minor": 5 }