diff --git a/wyk/03_Tfidf.ipynb b/wyk/03_Tfidf.ipynb new file mode 100644 index 0000000..56b4000 --- /dev/null +++ b/wyk/03_Tfidf.ipynb @@ -0,0 +1,2341 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Wyszukiwarka - szybka i sensowna" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Roboczy przykład\n", + "\n", + "Zakładamy, że mamy pewną kolekcję dokumentów $D = {d_1, \\ldots, d_N}$. ($N$ - liczba dokumentów w kolekcji)." + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Podobno jest kot w butach." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "{-# LANGUAGE OverloadedStrings #-}\n", + "\n", + "import Data.Text hiding(map, filter, zip)\n", + "import Prelude hiding(words, take)\n", + "\n", + "collectionD :: [Text]\n", + "collectionD = [\"Ala ma kota.\", \"Podobno jest kot w butach.\", \"Ty chyba masz kota!\", \"But chyba zgubiłem.\"]\n", + "\n", + "-- Operator (!!) zwraca element listy o podanym indeksie\n", + "-- (Przy większych listach będzie nieefektywne, ale nie będziemy komplikować)\n", + "collectionD !! 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Wydobycie tekstu\n", + "\n", + "Przykładowe narzędzia:\n", + "\n", + "* pdftotext\n", + "* antiword\n", + "* Tesseract OCR\n", + "* Apache Tika - uniwersalne narzędzie do wydobywania tekstu z różnych formatów\n", + "\n", + "## Normalizacja tekstu\n", + "\n", + "Cokolwiek robimy z tekstem, najpierw musimy go _znormalizować_." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Tokenizacja\n", + "\n", + "Po pierwsze musimy podzielić tekst na _tokeny_, czyli wyrazapodobne jednostki.\n", + "Może po prostu podzielić po spacjach?" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ma" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kota." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenizeStupidly :: Text -> [Text]\n", + "-- words to funkcja z Data.Text, która dzieli po spacjach\n", + "tokenizeStupidly = words\n", + "\n", + "tokenizeStupidly $ Prelude.head collectionD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A, trzeba _chociaż_ odsunąć znaki interpunkcyjne. Najprościej użyć wyrażenia regularnego. Warto użyć [unikodowych własności](https://en.wikipedia.org/wiki/Unicode_character_property) znaków i konstrukcji `\\p{...}`. " + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ma" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kota" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "{-# LANGUAGE QuasiQuotes #-}\n", + "\n", + "import Text.Regex.PCRE.Heavy\n", + "\n", + "tokenize :: Text -> [Text]\n", + "tokenize = map fst . scan [re|[\\p{L}0-9]+|\\p{P}|]\n", + "\n", + "tokenize $ Prelude.head collectionD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cała kolekcja stokenizowana:" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ma" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kota" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Podobno" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "jest" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kot" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "w" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "butach" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Ty" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "chyba" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "masz" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kota" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "!" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "But" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "chyba" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "zgubiłem" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "map tokenize collectionD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Problemy z tokenizacją\n", + "\n", + "##### Język angielski" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "I" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "use" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "a" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "data" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "-" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "base" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"I use a data-base\"" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "I" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "use" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "a" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "database" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"I use a database\"" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "I" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "use" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "a" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "data" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "base" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"I use a data base\"" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "I" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "don" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "t" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "like" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Python" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"I don't like Python\"" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0018" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "555" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "555" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "122" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"+0018 555 555 122\"" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "0018555555122" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"+0018555555122\"" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Which" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "one" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "is" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "better" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + ":" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "C" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "or" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "C" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "#" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "?" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"Which one is better: C++ or C#?\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Inne języki?" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Rechtsschutzversicherungsgesellschaften" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "wie" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "die" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "HUK" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "-" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Coburg" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "machen" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "es" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "bereits" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "seit" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "geraumer" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Zeit" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "vor" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + ":" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"Rechtsschutzversicherungsgesellschaften wie die HUK-Coburg machen es bereits seit geraumer Zeit vor:\"" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "今日波兹南是贸易" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "、" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "工业及教育的中心" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "。" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "波兹南是波兰第五大的城市及第四大的工业中心" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "," + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "波兹南亦是大波兰省的行政首府" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "。" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "也舉辦有不少展覽會" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "。" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "是波蘭西部重要的交通中心都市" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "。" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"今日波兹南是贸易、工业及教育的中心。波兹南是波兰第五大的城市及第四大的工业中心,波兹南亦是大波兰省的行政首府。也舉辦有不少展覽會。是波蘭西部重要的交通中心都市。\"" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "l" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ordinateur" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tokenize \"l'ordinateur\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Lematyzacja" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "_Lematyzacja_ to sprowadzenie do formy podstawowej (_lematu_), np. \"krześle\" do \"krzesło\", \"zrobimy\" do \"zrobić\" dla języka polskiego, \"chairs\" do \"chair\", \"made\" do \"make\" dla języka angielskiego." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lematyzacja dla języka polskiego jest bardzo trudna, praktycznie nie sposób wykonać ją regułowo, po prostu musimy się postarać o bardzo obszerny _słownik form fleksyjnych_.\n", + "\n", + "Na potrzeby tego wykładu stwórzmy sobie mały słownik form fleksyjnych w postaci tablicy asocjacyjnej (haszującej)." + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Use head
Found:
collectionD !! 0
Why Not:
head collectionD
" + ], + "text/plain": [ + "Line 22: Use head\n", + "Found:\n", + "collectionD !! 0\n", + "Why not:\n", + "head collectionD" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "but" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "butami" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "mieć" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kot" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "." + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import Data.Map as Map hiding(take, map, filter)\n", + "\n", + "mockInflectionDictionary :: Map Text Text\n", + "mockInflectionDictionary = Map.fromList [\n", + " (\"kota\", \"kot\"),\n", + " (\"butach\", \"but\"),\n", + " (\"masz\", \"mieć\"),\n", + " (\"ma\", \"mieć\"),\n", + " (\"buta\", \"but\"),\n", + " (\"zgubiłem\", \"zgubić\")]\n", + "\n", + "lemmatizeWord :: Map Text Text -> Text -> Text\n", + "lemmatizeWord dict w = findWithDefault w w dict\n", + "\n", + "lemmatizeWord mockInflectionDictionary \"butach\"\n", + "-- a tego nie ma w naszym słowniczku, więc zwracamy to samo\n", + "lemmatizeWord mockInflectionDictionary \"butami\"\n", + "\n", + "lemmatize :: Map Text Text -> [Text] -> [Text]\n", + "lemmatize dict = map (lemmatizeWord dict)\n", + "\n", + "lemmatize mockInflectionDictionary $ tokenize $ collectionD !! 0 \n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie**: Nawet w naszym słowniczku mamy problemy z niejednoznacznością lematyzacji. Jakie?\n", + "\n", + "Obszerny słownik form fleksyjnych dla języka polskiego: http://zil.ipipan.waw.pl/PoliMorf?action=AttachFile&do=view&target=PoliMorf-0.6.7.tab.gz" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stemowanie\n", + "\n", + "Stemowanie (rdzeniowanie) obcina wyraz do _rdzenia_ niekoniecznie będącego sensownym wyrazem, np. \"krześle\" może być rdzeniowane do \"krześl\", \"krześ\" albo \"krzes\", \"zrobimy\" do \"zrobi\".\n", + "\n", + "* stemowanie nie jest tak dobrze określone jak lematyzacja (można robić na wiele sposobów)\n", + "* bardziej podatne na metody regułowe (choć dla polskiego i tak trudno)\n", + "* dla angielskiego istnieją znane algorytmy stemowania, np. [algorytm Portera](https://tartarus.org/martin/PorterStemmer/def.txt)\n", + "* zob. też [program Snowball](https://snowballstem.org/) z regułami dla wielu języków\n", + "\n", + "Prosty stemmer \"dla ubogich\" dla języka polskiego to obcinanie do sześciu znaków." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "zrobim" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "komput" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "butach" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "poorMansStemming :: Text -> Text\n", + "poorMansStemming = take 6\n", + "\n", + "poorMansStemming \"zrobimy\"\n", + "poorMansStemming \"komputerami\"\n", + "poorMansStemming \"butach\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### _Stop words_\n", + "\n", + "Często wyszukiwarki pomijają krótkie, częste i nieniosące znaczenia słowa - _stop words_ (_słowa przestankowe_)." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "True" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "isStopWord :: Text -> Bool\n", + "isStopWord \"w\" = True\n", + "isStopWord \"jest\" = True\n", + "isStopWord \"że\" = True\n", + "-- przy okazji możemy pozbyć się znaków interpunkcyjnych\n", + "isStopWord w = w ≈ [re|^\\p{P}+$|]\n", + "\n", + "isStopWord \"kot\"\n", + "isStopWord \"!\"\n" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ma" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kota" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "removeStopWords :: [Text] -> [Text]\n", + "removeStopWords = filter (not . isStopWord)\n", + "\n", + "removeStopWords $ tokenize $ Prelude.head collectionD " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie**: Jakim zapytaniom usuwanie _stop words_ może szkodzić? Podać przykłady dla języka polskiego i angielskiego. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Normalizacja - różności\n", + "\n", + "W skład normalizacji może też wchodzić:\n", + "\n", + "* poprawianie błędów literowych\n", + "* sprowadzanie do małych liter (lower-casing czy raczej case-folding)\n", + "* usuwanie znaków diakrytycznych\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "żdźbło" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "toLower \"ŻDŹBŁO\"" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "źdźbło" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "toCaseFold \"ŹDŹBŁO\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Pytanie:** Kiedy _case-folding_ da inny wynik niż _lower-casing_? Jakie to ma praktyczne znaczenie?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Normalizacja jako całościowy proces\n", + "\n", + "Najważniejsza zasada: dokumenty w naszej kolekcji powinny być normalizowane w dokładnie taki sposób, jak zapytania.\n", + "\n", + "Efektem normalizacji jest zamiana dokumentu na ciąg _termów_ (ang. _terms_), czyli znormalizowanych wyrazów.\n", + "\n", + "Innymi słowy po normalizacji dokument $d_i$ traktujemy jako ciąg termów $t_i^1,\\dots,t_i^{|d_i|}$." + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "but" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "chyba" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "zgubić" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "normalize :: Text -> [Text]\n", + "normalize = removeStopWords . map toLower . lemmatize mockInflectionDictionary . tokenize\n", + "\n", + "normalize $ collectionD !! 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Zbiór wszystkich termów w kolekcji dokumentów nazywamy słownikiem (ang. _vocabulary_), nie mylić ze słownikiem jako strukturą danych w Pythonie (_dictionary_).\n", + "\n", + "$$V = \\bigcup_{i=1}^N \\{t_i^1,\\dots,t_i^{|d_i|}\\}$$\n", + "\n", + "(To zbiór, więc liczymy bez powtórzeń!)" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "fromList [\"ala\",\"but\",\"chyba\",\"kot\",\"mie\\263\",\"podobno\",\"ty\",\"zgubi\\263\"]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import Data.Set as Set hiding(map)\n", + "\n", + "getVocabulary :: [Text] -> Set Text \n", + "getVocabulary = Set.unions . map (Set.fromList . normalize) \n", + "\n", + "getVocabulary collectionD" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Jak wyszukiwarka może być szybka?\n", + "\n", + "_Odwrócony indeks_ (ang. _inverted index_) pozwala wyszukiwarce szybko szukać w milionach dokumentów. Odwrócoy indeks to prostu... indeks, jaki znamy z książek (mapowanie słów na numery stron/dokumentów).\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Use tuple-section
Found:
\\ t -> (t, ix)
Why Not:
(, ix)
" + ], + "text/plain": [ + "Line 4: Use tuple-section\n", + "Found:\n", + "\\ t -> (t, ix)\n", + "Why not:\n", + "(, ix)" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "fromList [(\"chyba\",2),(\"kot\",2),(\"mie\\263\",2),(\"ty\",2)]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "collectionDNormalized = map normalize collectionD\n", + "\n", + "documentToPostings :: ([Text], Int) -> Set (Text, Int)\n", + "documentToPostings (d, ix) = Set.fromList $ map (\\t -> (t, ix)) d\n", + "\n", + "documentToPostings (collectionDNormalized !! 2, 2) \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Use zipWith
Found:
map documentToPostings $ zip coll [0 .. ]
Why Not:
zipWith (curry documentToPostings) coll [0 .. ]
" + ], + "text/plain": [ + "Line 2: Use zipWith\n", + "Found:\n", + "map documentToPostings $ zip coll [0 .. ]\n", + "Why not:\n", + "zipWith (curry documentToPostings) coll [0 .. ]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "fromList [(\"ala\",0),(\"but\",1),(\"but\",3),(\"chyba\",2),(\"chyba\",3),(\"kot\",0),(\"kot\",1),(\"kot\",2),(\"mie\\263\",0),(\"mie\\263\",2),(\"podobno\",1),(\"ty\",2),(\"zgubi\\263\",3)]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "collectionToPostings :: [[Text]] -> Set (Text, Int)\n", + "collectionToPostings coll = Set.unions $ map documentToPostings $ zip coll [0..]\n", + "\n", + "collectionToPostings collectionDNormalized" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Eta reduce
Found:
updateInvertedIndex (t, ix) invIndex\n", + " = insertWith (++) t [ix] invIndex
Why Not:
updateInvertedIndex (t, ix) = insertWith (++) t [ix]
" + ], + "text/plain": [ + "Line 2: Eta reduce\n", + "Found:\n", + "updateInvertedIndex (t, ix) invIndex\n", + " = insertWith (++) t [ix] invIndex\n", + "Why not:\n", + "updateInvertedIndex (t, ix) = insertWith (++) t [ix]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "fromList [(\"ala\",[0]),(\"but\",[1,3]),(\"chyba\",[2,3]),(\"kot\",[0,1,2]),(\"mie\\263\",[0,2]),(\"podobno\",[1]),(\"ty\",[2]),(\"zgubi\\263\",[3])]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "updateInvertedIndex :: (Text, Int) -> Map Text [Int] -> Map Text [Int]\n", + "updateInvertedIndex (t, ix) invIndex = insertWith (++) t [ix] invIndex\n", + "\n", + "getInvertedIndex :: [[Text]] -> Map Text [Int]\n", + "getInvertedIndex = Prelude.foldr updateInvertedIndex Map.empty . Set.toList . collectionToPostings\n", + "\n", + "getInvertedIndex collectionDNormalized" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Relewantność\n", + "\n", + "Potrafimy szybko przeszukiwać znormalizowane dokumenty, ale które dokumenty są ważne (_relewantne_) względem potrzeby informacyjnej użytkownika?\n", + "\n", + "### Zapytania boole'owskie\n", + "\n", + "* `pizzeria Poznań dowóz` to `pizzeria AND Poznań AND dowóz` czy `pizzera OR POZNAŃ OR dowóz`\n", + "* `(pizzeria OR pizza OR tratoria) AND Poznań AND dowóz\n", + "* `pizzeria AND Poznań AND dowóz AND NOT golonka`\n", + "\n", + "Jak domyślnie interpretować zapytanie?\n", + "\n", + "* jako zapytanie AND -- być może za mało dokumentów\n", + "* rozwiązanie pośrednie?\n", + "* jako zapytanie OR -- być może za dużo dokumentów\n", + "\n", + "Możemy jakieś miary dopasowania dokumentu do zapytania, żeby móc posortować dokumenty...\n", + "\n", + "### Mierzenie dopasowania dokumentu do zapytania\n", + "\n", + "Potrzebujemy jakieś funkcji $\\sigma : Q x D \\rightarrow \\mathbb{R}$. \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Musimy jakoś zamienić dokumenty na liczby, tj. dokumenty na wektory liczb, a całą kolekcję na macierz.\n", + "\n", + "Po pierwsze ponumerujmy wszystkie termy ze słownika." + ] + }, + { + "cell_type": "code", + "execution_count": 115, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "fromList [(0,\"ala\"),(1,\"but\"),(2,\"chyba\"),(3,\"kot\"),(4,\"mie\\263\"),(5,\"podobno\"),(6,\"ty\"),(7,\"zgubi\\263\")]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "fromList [(\"ala\",0),(\"but\",1),(\"chyba\",2),(\"kot\",3),(\"mie\\263\",4),(\"podobno\",5),(\"ty\",6),(\"zgubi\\263\",7)]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ala" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "2" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "voc = getVocabulary collectionD\n", + "\n", + "vocD :: Map Int Text\n", + "vocD = Map.fromList $ zip [0..] $ Set.toList voc\n", + "\n", + "invvocD :: Map Text Int\n", + "invvocD = Map.fromList $ zip (Set.toList voc) [0..]\n", + "\n", + "vocD\n", + "\n", + "invvocD\n", + "\n", + "vocD ! 0\n", + "invvocD ! \"chyba\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Napiszmy funkcję, która _wektoryzuje_ znormalizowany dokument.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 125, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Redundant $
Found:
map (\\ i -> count (v ! i) doc) $ [0 .. (vecSize - 1)]
Why Not:
map (\\ i -> count (v ! i) doc) [0 .. (vecSize - 1)]
Redundant bracket
Found:
(collectionDNormalized !! 2)
Why Not:
collectionDNormalized !! 2
" + ], + "text/plain": [ + "Line 2: Redundant $\n", + "Found:\n", + "map (\\ i -> count (v ! i) doc) $ [0 .. (vecSize - 1)]\n", + "Why not:\n", + "map (\\ i -> count (v ! i) doc) [0 .. (vecSize - 1)]Line 9: Redundant bracket\n", + "Found:\n", + "(collectionDNormalized !! 2)\n", + "Why not:\n", + "collectionDNormalized !! 2" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "ty" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "chyba" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "mieć" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "kot" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[0.0,0.0,1.0,1.0,1.0,0.0,1.0,0.0]" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "vectorize :: Int -> Map Int Text -> [Text] -> [Double]\n", + "vectorize vecSize v doc = map (\\i -> count (v ! i) doc) $ [0..(vecSize-1)]\n", + " where count t doc \n", + " | t `elem` doc = 1.0\n", + " | otherwise = 0.0\n", + " \n", + "vocSize = Set.size voc\n", + "\n", + "(collectionDNormalized !! 2)\n", + "vectorize vocSize vocD (collectionDNormalized !! 2)\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + " ![image](./macierz.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Jak inaczej uwzględnić częstość wyrazów?\n", + "\n", + "
\n", + " $\n", + " \\newcommand{\\idf}{\\mathop{\\rm idf}\\nolimits}\n", + " \\newcommand{\\tf}{\\mathop{\\rm tf}\\nolimits}\n", + " \\newcommand{\\df}{\\mathop{\\rm df}\\nolimits}\n", + " \\newcommand{\\tfidf}{\\mathop{\\rm tfidf}\\nolimits}\n", + " $\n", + "
\n", + "\n", + "* $\\tf_{t,d}$\n", + "\n", + "* $1+\\log(\\tf_{t,d})$\n", + "\n", + "* $0.5 + \\frac{0.5 \\times \\tf_{t,d}}{max_t(\\tf_{t,d})}$" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + " $\n", + " \\newcommand{\\idf}{\\mathop{\\rm idf}\\nolimits}\n", + " \\newcommand{\\tf}{\\mathop{\\rm tf}\\nolimits}\n", + " \\newcommand{\\df}{\\mathop{\\rm df}\\nolimits}\n", + " \\newcommand{\\tfidf}{\\mathop{\\rm tfidf}\\nolimits}\n", + " $\n", + "
\n", + "\n", + "### Odwrotna częstość dokumentowa\n", + "\n", + "Czy wszystkie wyrazy są tak samo ważne?\n", + "\n", + "**NIE.** Wyrazy pojawiające się w wielu dokumentach są mniej ważne.\n", + "\n", + "Aby to uwzględnić, przemnażamy frekwencję wyrazu przez _odwrotną\n", + " częstość w dokumentach_ (_inverse document frequency_):\n", + "\n", + "$$\\idf_t = \\log \\frac{N}{\\df_t},$$\n", + "\n", + "gdzie:\n", + "\n", + "* $\\idf_t$ - odwrotna częstość wyrazu $t$ w dokumentach\n", + "\n", + "* $N$ - liczba dokumentów w kolekcji\n", + "\n", + "* $\\df_f$ - w ilu dokumentach wystąpił wyraz $t$?\n", + "\n", + "#### Dlaczego idf?\n", + "\n", + "term $t$ wystąpił...\n", + "\n", + "* w 1 dokumencie, $\\idf_t = \\log N/1 = \\log N$\n", + "* 2 razy w kolekcji, $\\idf_t = \\log N/2$ lub $\\log N$\n", + "* 3 razy w kolekcji, $\\idf_t = \\log N/(N/2) = \\log 2$\n", + "* we wszystkich dokumentach, $\\idf_t = \\log N/N = \\log 1 = 0$\n", + "\n", + "#### Co z tego wynika?\n", + "\n", + "Zamiast $\\tf_{t,d}$ będziemy w wektorach rozpatrywać wartości:\n", + "\n", + "$$\\tfidf_{t,d} = \\tf_{t,d} \\times \\idf_{t}$$\n", + "\n", + "Teraz zdefiniujemy _overlap score measure_:\n", + "\n", + "$$\\sigma(q,d) = \\sum_{t \\in q} \\tfidf_{t,d}$$\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Haskell", + "language": "haskell", + "name": "haskell" + }, + "language_info": { + "codemirror_mode": "ihaskell", + "file_extension": ".hs", + "mimetype": "text/x-haskell", + "name": "haskell", + "pygments_lexer": "Haskell", + "version": "8.10.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/wyk/macierz.png b/wyk/macierz.png new file mode 100644 index 0000000..e0a1012 Binary files /dev/null and b/wyk/macierz.png differ