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

Modelowanie języka

\n", "

1. Język i jego zapis [wykład]

\n", "

Filip Graliński (2022)

\n", "
\n", "\n", "![Logo 2](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech2.jpg)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Język — różne perspektywy\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Słowo wstępne\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "W matematyce istnieją dwa spojrzenia na rzeczywistość: ciągłe i dyskretne.\n", "\n", "Otaczająca nas rzeczywistość fizyczna jest z natury ciągła\n", "(przynajmniej jeśli nie operujemy w mikroskali), lecz język\n", "jest dyskretnym wyłomem w ciągłej rzeczywistości.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Lingwistyka matematyczna\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Przypomnijmy sobie definicję języka przyjętą w lingwistyce\n", "matematycznej, w kontekście, na przykład, teorii automatów.\n", "\n", "****Alfabetem**** nazywamy skończony zbiór symboli.\n", "\n", "****Łańcuchem**** (****napisem****) nad alfabetem $\\Sigma$ nazywamy dowolny, skończony,\n", "ciąg złożony z symboli z $\\Sigma$.\n", "\n", "****Językiem**** nazywamy dowolny, skończony bądź nieskończony, zbiór łańcuchów.\n", "\n", "W tym formalnym ujęciu językami są na przykład następujące zbiory:\n", "\n", "- $\\{\\mathit{poniedziałek},\\mathit{wtorek},\\mathit{środa},\\mathit{czwartek},\\mathit{piątek},\\mathit{sobota},\\mathit{niedziela}\\}$\n", "- $\\{\\mathit{ab},\\mathit{abb},\\mathit{abbb},\\mathit{abbbb},\\ldots\\}$\n", "\n", "To podejście, z jednej strony oczywiście nie do końca się pokrywa się z potocznym\n", "rozumieniem słowa *język*, z drugiej kojarzy nam się z takimi\n", "narzędziami informatyki jak wyrażenia regularne, automaty skończenie\n", "stanowe czy gramatyki języków programowania.\n", "\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abbb" ] } ], "source": [ "import regex as re\n", "rx = re.compile(r'ab+')\n", "\n", "rx.search('żabbba').group(0)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ] } ], "source": [ "import rstr\n", "\n", "rstr.xeger(r'ab+')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Ujęcie probabilistyczne\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Na tym wykładzie przyjmiemy inną perspektywą, częściowo ciągłą, opartą\n", "na probabilistyce. Język będziemy definiować poprzez ****rozkład\n", "prawdopodobieństwa****: sensownym wypowiedziom czy tekstom będziemy\n", "przypisywać stosunkowe wysokie prawdopodobieństwo, „ułomnym” tekstom — niższe (być może zerowe).\n", "\n", "Na ogół nie mamy jednak do czynienia z językiem jako takim tylko z\n", "jego przybliżeniami, ****modelami**** (model może być lepszy lub gorszy,\n", "ale przynajmniej powinien być użyteczny…). Formalnie $M$ nazywamy\n", "modelem języka (nad skończonym alfabetem $\\Sigma$), jeśli określa dyskretny rozkład prawdopodobieństwa $P_M$:\n", "\n", "$$P_M \\colon \\Sigma^{*} \\rightarrow [0,1].$$\n", "\n", "Rzecz jasna, skoro mamy do czynienia z rozkładem prawdopodobieństwa, to:\n", "\n", "$$\\sum_{\\alpha \\in \\Sigma^{*}} P_M(\\alpha) = 1.$$\n", "\n", "Jeśli $M$ ma być modelem języka polskiego, oczekiwalibyśmy, że dla\n", "napisów:\n", "\n", "- $z_1$ — *W tym stanie rzeczy pan Ignacy coraz częściej myślał o Wokulskim.*\n", "- $z_2$ — *Po wypełniony zbiornik pełny i należne kwotę, usłyszała w attendant*\n", "- $z_3$ — *xxxxyźźźźźit backspace hoooooooooop x y z*\n", "\n", "zachodzić będzie:\n", "\n", "$$ P_M(z_1) > P_M(z_2) > P_M(z_3). $$\n", "\n", "****Pytanie**** Jakiej konkretnie wartości prawdopodobieństwa\n", "spodziewalibyśmy się dla zdania *Dzisiaj rano kupiłem w piekarni sześć bułek*\n", "dla sensownego modelu języka polskiego?\n", "\n", "Moglibyśmy sprowadzić tę definicję języka do tej „dyskretnej”, tzn.\n", "moglibyśmy przyjąć, że łańcuch $\\alpha$ należy do języka wyznaczonego\n", "przez model $M$, jeśli $P_M(\\alpha) > 0$.\n", "\n", "****Pytanie**** Czy moglibyśmy w ten sposób opisać język nieskończony? Czy może istnieć\n", "dyskretny rozkład prawdopodobieństwa dla nieskończonego zbioru?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Co jest symbolem?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Model języka daje rozkład prawdopodobieństwa nad zbiorem łańcuchów\n", "opartym na skończonym alfabecie, tj. zbiorze symboli. W praktyce\n", "alfabet nie musi być zgodny z potocznym czy językoznawczym rozumieniem\n", "tego słowa. To znaczy alfabet może być zbiorem znaków (liter), ale\n", "modelować język możemy też przyjmując inny typ symboli: sylaby,\n", "morfemy (cząstki wyrazów) czy po prostu całe wyrazy.\n", "\n", "Powinniśmy przy tym pamiętać, że, koniec końców, w pamięci komputera\n", "wszelkiego rodzaju łańcuchy są zapisywane jako ciągi zer i jedynek — bitów.\n", "Omówmy pokrótce techniczną stronę modelowania języka.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Kodowanie znaków\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Cóż może być prostszego od pliku tekstowego?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " Ala ma kota.\n", "\n", "Komputer nic nie wie o literach.\n", "\n", "… w rzeczywistości operuje tylko na liczbach …\n", "\n", "… czy raczej na zerach i jedynkach …\n", "\n", "… a tak naprawdę na ciągłym sygnale elektrycznym …\n", "\n", "![img](./01_Jezyk/digitalsignal.jpg)\n", "\n", "… zera i jedynki są w naszej głowie …\n", "\n", "… co jest dziwne, *naprawdę* dziwne …\n", "\n", "… ale nikt normalny się tym nie przejmuje.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Jak zakodować literę?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zakodowanie pikseli składających się na kształtu (****glyfu****) litery A\n", "*oczywiście* nie jest dobrym pomysłem.\n", "\n", "![img](./01_Jezyk/raster.png)\n", "\n", "Nie, potrzebujemy *arbitralnego* kodowania dla wszystkich możliwych\n", "kształtów litery A (*w naszych głowach*): A, $\\mathcal{A}$,\n", "$\\mathbb{A}$, $\\mathfrak{A}$ powinny otrzymać ten sam kod, powiedzmy 65\n", "(binarnie: 1000001).\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### ASCII\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ASCII to 7-bitowy (****nie**** 8-bitowy!) system kodowania znaków.\n", "\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "40: (\n", "41: )\n", "42: *\n", "43: +\n", "44: ,\n", "45: -\n", "46: .\n", "47: /\n", "48: 0\n", "49: 1\n", "50: 2\n", "51: 3\n", "52: 4\n", "53: 5\n", "54: 6\n", "55: 7\n", "56: 8\n", "57: 9\n", "58: :\n", "59: ;\n", "60: <\n", "61: =\n", "62: >\n", "63: ?\n", "64: @\n", "65: A\n", "66: B\n", "67: C\n", "68: D\n", "69: E\n", "70: F\n", "71: G\n", "72: H\n", "73: I\n", "74: J\n", "75: K\n", "76: L\n", "77: M\n", "78: N\n", "79: O\n", "80: P\n", "81: Q\n", "82: R\n", "83: S\n", "84: T\n", "85: U\n", "86: V\n", "87: W\n", "88: X\n", "89: Y\n", "90: Z\n", "91: [\n", "92: \\\n", "93: ]\n", "94: ^\n", "95: _\n", "96: `\n", "97: a\n", "98: b\n", "99: c\n", "100: d\n", "101: e\n", "102: f\n", "103: g\n", "104: h\n", "105: i\n", "106: j\n", "107: k\n", "108: l\n", "109: m\n", "110: n\n", "111: o\n", "112: p\n", "113: q\n", "114: r\n", "115: s\n", "116: t\n", "117: u\n", "118: v\n", "119: w\n", "120: x\n", "121: y\n", "122: z\n", "123: {\n", "124: |\n", "125: }\n", "126: ~\n", "127: \n" ] } ], "source": [ "for code in range(40, 128):\n", " print(f'{code}: {chr(code)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Jak zejść na poziom bitów?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Linux — wiersz poleceń\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Linux command line:\n", "\n", " $ echo 'Ala ma kota' > file.txt\n", " $ hexdump -C file.txt\n", " 00000000 41 6c 61 20 6d 61 20 6b 6f 74 61 0a |Ala ma kota.|\n", " 0000000c\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Edytor tekstu (Emacs)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![img](./01_Jezyk/hexl-mode.png)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Uwaga!\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- kiedy dzieje się coś dziwnego, sprawdź, co tak *naprawdę* jest w pliku\n", "- ASCII jest 7-bitowym kodowaniem (128 znaków)\n", " - choć zazwyczaj uzupełnionym (ang. *padded*) do 8 bitów\n", " - nie mów plik *plik ASCII*, kiedy masz na myśli *prosty/czysty plik tekstowy* (ang. *plain text file*)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Higiena plików tekstowych\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Piekło końca wiersza\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![img](./01_Jezyk/dante.jpg)\n", "\n", "Więcej na [https://re-research.pl/pl/post/2017-01-28-00042-anatomia-pliku-tekstowego-2.html](https://re-research.pl/pl/post/2017-01-28-00042-anatomia-pliku-tekstowego-2.html)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Dobre rady\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- żadnych niepotrzebnych spacji na końcu wiersza\n", "\n", "- żadnych niepotrzebnych pustych wierszy na końcu pliku\n", "\n", "- … ale ostatni wiersz powinien zakończyć się znakiem końca wiersza\n", "\n", "- nie używać znaków tabulacji (zamiast tego 4 spacje)\n", " - wyjątek: pliki TSV\n", " - wyjątek: pliki Makefile\n", "\n", "- uwaga na niestandardowe spacje i dziwne znaki o zerowej długości\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Unikod\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "ASCII obejmuje 128 znaków: litery alfabetu łacińskiego (właściwie angielskiego),\n", "cyfry, znaki interpunkcyjne, znaki specjalne itd.\n", "\n", "Co z pozostałymi znakami? Polskimi ogonkami, czeskimi haczykami,\n", "francuskimi akcentami, cyrylicą, koreańskim alfabetem, chińskimi\n", "znakami, rongorongo?\n", "\n", "워싱턴, 부산, 삼성\n", "\n", "Rozwiązaniem jest Unikod (ang. *Unicode*) system, który przypisuje\n", "znakom używanym przez ludzkość liczby (kody, ang. *code points*).\n", "\n", "| Znak|Kod ASCII|Kod Unikodowy|\n", "|---|---|---|\n", "| 9|57|57|\n", "| a|97|97|\n", "| ą|-|261|\n", "| ł|-|322|\n", "| $\\aleph$|-|1488|\n", "| ặ|-|7861|\n", "| ☣|-|9763|\n", "| 😇|-|128519|\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### UTF-8\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Kody znaków są pojęciem abstrakcyjnym. Potrzebujemy konkretnego ****kodowania****\n", "by zamienić kody w sekwencję bajtów. Najpopularniejszym kodowaniem jest UTF-8.\n", "\n", "W kodowaniu UTF-8 znaki zapisywane za pomocą 1, 2, 3, 4, 5 lub 6 bajtów\n", "(w praktyce — raczej to 4 bajtów).\n", "\n", "| Znak|Kod Unikodowy|Szesnastkowo|UTF-8 (binarnie)|\n", "|---|---|---|---|\n", "| 9|57|U+0049|01001001|\n", "| a|97|U+0061|01100001|\n", "| ą|261|U+0105|11000100:10000101|\n", "| ł|322|U+0142|11000101:10000010|\n", "| $\\aleph$|1488|U+05D0|11010111:10010000|\n", "| ặ|7861|U+1EB7|11100001:10111010:10110111|\n", "| ☣|9763|U+2623|11100010:10011000:10100011|\n", "| 😇|128519|U+1f607|11110000:10011111:10011000:10000111|\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### UTF-8 — ogólny schemat zamiany kodu na bajty\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- 0x00 do 0x7F – 0xxxxxxx,\n", "- 0x80 do 0x7FF – 110xxxxx 10xxxxxx\n", "- 0x800 do 0xFFFF — 1110xxxx 10xxxxxx 10xxxxxx\n", "- 0x10000 do 0x1FFFFF – 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n", "- 0x200000 do 0x3FFFFFF – 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx\n", "- 0x4000000 do 0x7FFFFFFF – 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx\n", "\n", "Symbol x oznacza znaczący bit.\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### *Źdźbło* to ile bajtów w UTF-8?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Jeśli wczytać jako wiersz w języku C, 11 bajtów!\n", "\n", "![img](./01_Jezyk/zdzblo.png)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dlaczego UTF-8 jest doskonałym systemem kodowania?\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- wstecznie kompatybilny z ASCII\n", " - plik ASCII jest poprawnym plikiem UTF-8\n", "- nie zajmuje dużo miejsca\n", " - chyba że w tekście jest dużo „dziwnych” znaków\n", "- proste grepowanie działa\n", " - `grep UAM text-in-utf8.txt` zadziała\n", " - ale nawet nie próbuj: `grep SRPOL text-in-utf16.txt`\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Porady\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- zawsze używaj UTF-8\n", " - bądź asertywny! jeśli w pracy każą używać czegoś innego — rezygnuj z pracy\n", " - **NIE** używaj innych unikodowych kodowań: UTF-16, UTF-32, UCS-2\n", " - **NIE** używaj nieunikodowych systemów kodowania\n", " - ISO-8859-2, Windows-1250, Mazovia, IEA Świerk, …\n", "- uwaga na pułapki UTF-8\n", " - ustalenie długości napisu w znakach wymaga przejścia znak po znaku\n", " - jeśli napis w kodowaniu UTF-8 zajmuje 9 bajtów, ile to znaków?\n", " 3, 4, 5, 6, 7, 8 lub 9!\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### **NIE** używaj sekwencji BOM\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![img](./01_Jezyk/evil-bom.png)\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Unikod/UTF-8 a języki programowania\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Pamiętaj, żeby być konsekwentnym!\n", "\n", "- kodowanie kodu źródłowego (literały!)\n", " - czasami podawane na początku pliku\n", " - … albo brane z ustawień *locale*\n", " - … albo — domyślnie — UTF-8 (w nowszych językach programowania)\n", "- kodowanie standardowego wejścia/wyjścia i plików\n", "- jak sekwencje bajtów są interpretowane w czasie działania programu?\n", " - *Źdźbło* jest łańcuchem złożonym z 6 czy 9 elementów??\n", " - 9 bajtów\n", " - 6 kodów\n", " - `\"Źdźbło\"[1]` …\n", " - `d`\n", " - … albo śmieci\n", "\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Python 2\n", "\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#!/usr/bin/python2\n", "# -*- coding: utf-8 -*-\n", "import sys\n", "for line in sys.stdin:\n", " line = line.decode('utf-8').rstrip()\n", " if \"źdźbło\".decode('utf-8') in line:\n", " print len(line), ' ', line" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Python3\n", "\n" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "#!/usr/bin/python3\n", "import sys\n", "for line in sys.stdin:\n", " line = line.strip()\n", " if \"źdźbło\" in line:\n", " print(len(line), ' ', line)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Uwaga: zakładając, że zmienna środowiskowa `LANG` jest ustawiona na UTF-8.\n", "\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "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.10.9" }, "org": null }, "nbformat": 4, "nbformat_minor": 1 }