forked from bfijalkowski/KWT-2024
342 lines
16 KiB
Plaintext
342 lines
16 KiB
Plaintext
{
|
|
"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",
|
|
"<div class=\"alert alert-block alert-info\">\n",
|
|
"<h1> Komputerowe wspomaganie tłumaczenia </h1>\n",
|
|
"<h2> 13,14. <i>Korekta pisowni</i> [laboratoria]</h2>\n",
|
|
"<h3>Rafał Jaworski (2021)</h3>\n",
|
|
"</div>\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<ipython-input-9-cf46e3e07935>\u001b[0m in \u001b[0;36m<cell line: 3>\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
|
|
} |