{ "cells": [ { "cell_type": "markdown", "id": "f4066037-c828-47a8-95c6-f5a127f4323f", "metadata": {}, "source": [ "# **자연어 처리 바이블**\n", "- **[정규표현식 시작하기](https://wikidocs.net/4308) | [정규표현식 고급편](https://wikidocs.net/4309)**\n", "\n", "```java\n", "pip install konlpy\n", "pip install pororo\n", "bash <(curl -s https://raw.githubusercontent.com/konlpy/konlpy/master/scripts/mecab.sh)\n", "```\n", "## **1 구문분석**\n", "### **Tokeninzer**" ] }, { "cell_type": "code", "execution_count": 1, "id": "20a7fad8-d7b6-43d1-b0d8-a2923570f4e0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('아이', 'NNG'),\n", " ('들', 'XSN'),\n", " ('이', 'JKS'),\n", " ('케이크', 'NNG'),\n", " ('를', 'JKO'),\n", " ('먹', 'VV'),\n", " ('었', 'EP'),\n", " ('다', 'EC')]" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from konlpy.tag import Kkma\n", "from konlpy.tag import Okt\n", "from konlpy.tag import Mecab\n", "Mecab().pos(\"아이들이 케이크를 먹었다\")" ] }, { "cell_type": "code", "execution_count": 2, "id": "2ebd5a48-9263-4486-807d-3055c0ae054e", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "2021-08-10 14:59:12.802288: I tensorflow/stream_executor/platform/default/dso_loader.cc:53] Successfully opened dynamic library libcudart.so.11.0\n" ] }, { "data": { "text/plain": [ "[('마이클 제프리 조던', 'PERSON'),\n", " ('(', 'O'),\n", " ('영어', 'CIVILIZATION'),\n", " (':', 'O'),\n", " (' ', 'O'),\n", " ('Michael Jeffrey Jordan', 'PERSON'),\n", " (',', 'O'),\n", " (' ', 'O'),\n", " ('1963년 2월 17일 ~', 'DATE'),\n", " (' ', 'O'),\n", " (')', 'O'),\n", " ('농구선수', 'CIVILIZATION'),\n", " ('이다.', 'O')]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from pororo import Pororo\n", "Pororo.available_models(\"collocation\")\n", "ner = Pororo(task=\"ner\", lang=\"ko\")\n", "ner(\"마이클 제프리 조던(영어: Michael Jeffrey Jordan, 1963년 2월 17일 ~ )농구선수이다.\")" ] }, { "cell_type": "markdown", "id": "43012df7-b15c-444f-83fb-9e8ed9888f8b", "metadata": { "tags": [] }, "source": [ "## **2 NLTK 를 활용한 구조 구문분석**\n", "### **01 NLTK 패키지를 활용한 규칙기반 구조 구문분석**" ] }, { "cell_type": "code", "execution_count": 3, "id": "5a911e10-2f54-4f03-8f55-9941d867bed7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import nltk\n", "grammar = nltk.CFG.fromstring(\"\"\"\n", "S -> NP VP\n", "NP -> NN XSN JK | NN JK\n", "VP -> NP VP | VV EP EF\n", "NN -> '아이' | '케이크'\n", "XSN -> '들'\n", "JK -> '이' | '를'\n", "VV -> '먹'\n", "EP -> '었'\n", "EF -> '다'\n", "\"\"\")\n", "parser = nltk.ChartParser(grammar)\n", "parser" ] }, { "cell_type": "code", "execution_count": 4, "id": "9c78f377-ef5f-4515-801f-4cb1b69a1986", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "아이, 들, 이, 케이크, 를, 먹, 었, 다\n", "(S\n", " (NP (NN 아이) (XSN 들) (JK 이))\n", " (VP (NP (NN 케이크) (JK 를)) (VP (VV 먹) (EP 었) (EF 다))))\n" ] } ], "source": [ "texts = \"아이들이 케이크를 먹었다\"\n", "from konlpy.tag import Mecab\n", "tokens = Mecab().pos(\"아이들이 케이크를 먹었다\")\n", "sentence = [_[0] for _ in tokens]\n", "print(\", \".join(sentence))\n", "for tree in parser.parse(sentence):\n", " print(tree)" ] }, { "cell_type": "markdown", "id": "b68bba96-309b-4db0-baba-2ec94a3d48ee", "metadata": { "tags": [] }, "source": [ "### **02 Spacy 를 이용한 의존 구문 분석**\n", "```r\n", "! pip install spacy\n", "! python -m spacy download en\n", "```\n", "- Spacy 모델은 문장을 token들로 구성된 document로 처리한다.\n", "- 각 token에는 품사, 의존 관계, 개체명 정보 등이 태깅된다.\n", " - token.text: token 문자열\n", " - token.dep_: token과 token의 지배소 간의 의존 관계 유형\n", " - token.head: 지배소 token\n", "\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "85238316-ded0-4c37-83c9-df23fd748efe", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The det cat\n", "fat amod cat\n", "cat nsubj sat\n", "sat ROOT sat\n", "on prep sat\n", "the det mat\n", "mat pobj on\n" ] } ], "source": [ "import spacy\n", "# 영어 multi-task 통계 모델\n", "nlp = spacy.load('en_core_web_sm')\n", "doc = nlp('The fat cat sat on the mat')\n", "for token in doc:\n", " print(token.text, token.dep_, token.head.text)" ] }, { "cell_type": "code", "execution_count": 6, "id": "00ca7ba9-5cf5-4057-ac1b-2c1cca01adb4", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n", " The\n", " DET\n", "\n", "\n", "\n", " fat\n", " ADJ\n", "\n", "\n", "\n", " cat\n", " NOUN\n", "\n", "\n", "\n", " sat\n", " VERB\n", "\n", "\n", "\n", " on\n", " ADP\n", "\n", "\n", "\n", " the\n", " DET\n", "\n", "\n", "\n", " mat\n", " NOUN\n", "\n", "\n", "\n", " \n", " \n", " det\n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " amod\n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " nsubj\n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " prep\n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " det\n", " \n", " \n", "\n", "\n", "\n", " \n", " \n", " pobj\n", " \n", " \n", "\n", "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from spacy import displacy\n", "# Jupyter, Colab 등에서 동작\n", "displacy.render(doc, style='dep', jupyter=True)" ] }, { "cell_type": "markdown", "id": "be6cf80e-1dc0-4982-a79d-4f895e0eb662", "metadata": { "tags": [] }, "source": [ "## **3 단어 의미 중의성 문제의 해결**\n", "### **01 Lesk 알고리즘을 이용한 단어 중의성 해소**" ] }, { "cell_type": "code", "execution_count": 7, "id": "ac590c72-a23f-4ac1-a3a6-786c8b42fe85", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[nltk_data] Downloading package wordnet to /home/buffet/nltk_data...\n", "[nltk_data] Package wordnet is already up-to-date!\n", "[nltk_data] Downloading package punkt to /home/buffet/nltk_data...\n", "[nltk_data] Package punkt is already up-to-date!\n", "[nltk_data] Downloading package stopwords to /home/buffet/nltk_data...\n", "[nltk_data] Package stopwords is already up-to-date!\n" ] } ], "source": [ "import nltk\n", "nltk.download('wordnet')\n", "nltk.download('punkt')\n", "nltk.download('stopwords')\n", "from nltk.corpus import wordnet \n", "from nltk import word_tokenize\n", "from nltk.corpus import stopwords\n", "import sys" ] }, { "cell_type": "code", "execution_count": 8, "id": "96b7edcd-20d9-48a8-bf84-f46f2d054750", "metadata": {}, "outputs": [], "source": [ "# 단어와 문장에 나타난 단어에 대해 Best Sense 추출\n", "def disambiguate(word, sentence, stopwords):\n", " # Best sense 를 얻기위한 Lesk 알고리즘을 작성\n", " word_senses = wordnet.synsets(word)\n", " # Assume that first sense is most freq\n", " best_sense = word_senses[0]\n", " max_overlap = 0\n", " context = set(word_tokenize(sentence))\n", " for sense in word_senses:\n", " signature = tokenized_gloss(sense)\n", " overlap = compute_overlap(signature, context, stopwords)\n", " if overlap > max_overlap:\n", " max_overlap = overlap\n", " best_sense = sense\n", " return best_sense" ] }, { "cell_type": "code", "execution_count": 9, "id": "9778ed04-f0d2-497a-9e76-d303bea62aa1", "metadata": {}, "outputs": [], "source": [ "# sense의 definition에 대한 모든 token 추출\n", "def tokenized_gloss(sense):\n", " tokens = set(word_tokenize(sense.definition()))\n", " for example in sense.examples():\n", " tokens.union(set(word_tokenize(example)))\n", " return tokens" ] }, { "cell_type": "code", "execution_count": 10, "id": "e3d4dd9b-5d8d-4329-9439-3e80df5675aa", "metadata": {}, "outputs": [], "source": [ "# 겹치는 단어의 비교\n", "def compute_overlap(signature, context, stopwords):\n", " gloss = signature.difference(stopwords)\n", " return len(gloss.intersection(context))" ] }, { "cell_type": "code", "execution_count": 11, "id": "a7fd3619-8004-4bf1-ae7c-3c6512da48d1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Word : eat\n", "Sense : eat.v.02\n", "Definition : eat a meal; take a meal\n", "Sentence : They eat a meal\n", "{'take', 'a', 'meal', 'eat', ';'}\n", "2\n", "Best sense: Synset('eat.v.02')\n" ] } ], "source": [ "# NLTK에서 지정한 영어 불용어 처리\n", "# ex) i, my, they\n", "stopwords = set(stopwords.words('english'))\n", "sentence = (\"They eat a meal\")\n", "context = set(word_tokenize(sentence))\n", "word = 'eat'\n", "print(\"Word :\", word)\n", "\n", "syn = wordnet.synsets('eat')[1]\n", "print(\"Sense :\", syn.name())\n", "print(\"Definition :\", syn.definition())\n", "print(\"Sentence :\", sentence)\n", "\n", "signature = tokenized_gloss(syn)\n", "print(signature)\n", "print(compute_overlap(signature, context, stopwords))\n", "print(\"Best sense: \", disambiguate(word, sentence, stopwords))" ] }, { "cell_type": "markdown", "id": "879bbb73-979c-4d57-9d72-10b45be21fa7", "metadata": { "tags": [] }, "source": [ "## **4 NLTK를 이용한 개체명 인식**\n", "### **01 Lesk 알고리즘을 이용한 단어 중의성 해소**" ] }, { "cell_type": "code", "execution_count": 12, "id": "513567ee-8d32-4b99-a939-af4eecce140e", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[nltk_data] Downloading package punkt to /home/buffet/nltk_data...\n", "[nltk_data] Package punkt is already up-to-date!\n", "[nltk_data] Downloading package words to /home/buffet/nltk_data...\n", "[nltk_data] Package words is already up-to-date!\n", "[nltk_data] Downloading package averaged_perceptron_tagger to\n", "[nltk_data] /home/buffet/nltk_data...\n", "[nltk_data] Package averaged_perceptron_tagger is already up-to-\n", "[nltk_data] date!\n", "[nltk_data] Downloading package maxent_ne_chunker to\n", "[nltk_data] /home/buffet/nltk_data...\n", "[nltk_data] Package maxent_ne_chunker is already up-to-date!\n" ] }, { "data": { "text/plain": [ "True" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# -*- coding:utf-8 -*-\n", "import nltk\n", "nltk.download('punkt')\n", "nltk.download('words')\n", "nltk.download('averaged_perceptron_tagger')\n", "nltk.download('maxent_ne_chunker')" ] }, { "cell_type": "code", "execution_count": 13, "id": "f7d352b0-f395-4211-a54a-2b23c8bcc46a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['Prime', 'Minister', 'Boris', 'Johnson', 'had', 'previously', 'said', 'the', 'UK', 'would', 'leave', 'by', '31', 'October', '.']\n" ] } ], "source": [ "sentence = \"Prime Minister Boris Johnson had previously said the UK would leave by 31 October.\"\n", "tokens = nltk.word_tokenize(sentence)\n", "print(tokens)" ] }, { "cell_type": "code", "execution_count": 14, "id": "50d4df3e-c603-46ca-ba82-7e057439878c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[('Prime', 'NNP'), ('Minister', 'NNP'), ('Boris', 'NNP'), ('Johnson', 'NNP'), ('had', 'VBD'), ('previously', 'RB'), ('said', 'VBD'), ('the', 'DT'), ('UK', 'NNP'), ('would', 'MD'), ('leave', 'VB'), ('by', 'IN'), ('31', 'CD'), ('October', 'NNP'), ('.', '.')]\n" ] } ], "source": [ "tagged = nltk.pos_tag(tokens)\n", "print(tagged)" ] }, { "cell_type": "code", "execution_count": 15, "id": "c55206cb-a2c3-4888-a512-0c2764c4dd1f", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(S\n", " Prime/NNP\n", " Minister/NNP\n", " (PERSON Boris/NNP Johnson/NNP)\n", " had/VBD\n", " previously/RB\n", " said/VBD\n", " the/DT\n", " (ORGANIZATION UK/NNP)\n", " would/MD\n", " leave/VB\n", " by/IN\n", " 31/CD\n", " October/NNP\n", " ./.)\n" ] } ], "source": [ "entities = nltk.chunk.ne_chunk(tagged)\n", "print(entities)" ] }, { "cell_type": "markdown", "id": "d4ab7376-af74-41e9-9cfe-b22dd5c8c4a6", "metadata": { "tags": [] }, "source": [ "## **5 N-Gram 언어 모델로 문장 생서하기**\n", "### **01 Lesk 알고리즘을 이용한 단어 중의성 해소**" ] }, { "cell_type": "code", "execution_count": 22, "id": "2ca635bc-9846-4af8-bf28-b61ec1a793ae", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('마이클/NNP', '제프리/NNP', '조던/NNP'),\n", " ('제프리/NNP', '조던/NNP', '(/SSO'),\n", " ('조던/NNP', '(/SSO', 'Michael/SL'),\n", " ('(/SSO', 'Michael/SL', 'Jeffrey/SL'),\n", " ('Michael/SL', 'Jeffrey/SL', 'Jordan/SL'),\n", " ('Jeffrey/SL', 'Jordan/SL', ')/SSC'),\n", " ('Jordan/SL', ')/SSC', '농구/NNG'),\n", " (')/SSC', '농구/NNG', '선수/NNG'),\n", " ('농구/NNG', '선수/NNG', '이/VCP'),\n", " ('선수/NNG', '이/VCP', '다/EF'),\n", " ('이/VCP', '다/EF', './SF')]" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "texts = \"마이클 제프리 조던(Michael Jeffrey Jordan) 농구선수이다.\"\n", "\n", "from konlpy.tag import Mecab\n", "from nltk.util import ngrams\n", "tokens = Mecab().pos(texts)\n", "tokens = [\"/\".join(_) for _ in tokens]\n", "\n", "# 토큰을 N-gram의 형태로 바꾸어준다. \n", "# ngrams 함수의 두 번째 인자로 N값을 지정할 수 있다.\n", "trigram = ngrams(tokens, 3)\n", "[_ for _ in trigram]" ] }, { "cell_type": "code", "execution_count": 23, "id": "0f719e33-83ad-45cb-88c8-43a73752c702", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bigrams with padding: \n", "('', '마이클/NNP')\n", "('마이클/NNP', '제프리/NNP')\n", "('제프리/NNP', '조던/NNP')\n", "('조던/NNP', '(/SSO')\n", "('(/SSO', 'Michael/SL')\n", "('Michael/SL', 'Jeffrey/SL')\n", "('Jeffrey/SL', 'Jordan/SL')\n", "('Jordan/SL', ')/SSC')\n", "(')/SSC', '농구/NNG')\n", "('농구/NNG', '선수/NNG')\n", "('선수/NNG', '이/VCP')\n", "('이/VCP', '다/EF')\n", "('다/EF', './SF')\n", "('./SF', '')\n" ] } ], "source": [ "# padding 을 통해 입력 데이터에 문장의 시작과 끝을 알리는 토큰을 추가\n", "bigram = ngrams(\n", " tokens, 2,\n", " pad_left=True, left_pad_symbol=\"\",\n", " pad_right=True, right_pad_symbol=\"\"\n", ")\n", "print(\"bigrams with padding: \")\n", "for b in bigram:\n", " print(b)" ] }, { "cell_type": "code", "execution_count": 25, "id": "2a118520-8336-4550-b668-2173ee746cb1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "데이터셋: [['9976970', '아 더빙.. 진짜 짜증나네요 목소리', '0'], ['3819312', '흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나', '1'], ['10265843', '너무재밓었다그래서보는것을추천한다', '0'], ['9045019', '교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정', '0'], ['6483659', '사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 던스트가 너무나도 이뻐보였다', '1'], ['5403919', '막 걸음마 뗀 3세부터 초등학교 1학년생인 8살용영화.ㅋㅋㅋ...별반개도 아까움.', '0'], ['7797314', '원작의 긴장감을 제대로 살려내지못했다.', '0'], ['9443947', '별 반개도 아깝다 욕나온다 이응경 길용우 연기생활이몇년인지..정말 발로해도 그것보단 낫겟다 납치.감금만반복반복..이드라마는 가족도없다 연기못하는사람만모엿네', '0'], ['7156791', '액션이 없는데도 재미 있는 몇안되는 영화', '1'], ['5912145', '왜케 평점이 낮은건데? 꽤 볼만한데.. 헐리우드식 화려함에만 너무 길들여져 있나?', '1']]\n", "텍스트 데이터: ['아 더빙.. 진짜 짜증나네요 목소리', '흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나', '너무재밓었다그래서보는것을추천한다', '교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정', '사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 던스트가 너무나도 이뻐보였다']\n", "문장 개수: 150000\n" ] } ], "source": [ "# 다운로드 받은 데이터셋을 읽고 인덱스와 라벨을 제외한 텍스트 부분만 가져온다.\n", "# codecs 패키지는 대용량 파일을 조금씩 읽을 수 있게 해준다. \n", "import codecs\n", "with codecs.open(\"data/ratings_train.txt\", encoding='utf-8') as f:\n", " data = [line.split('\\t') for line in f.read().splitlines()] # \\n 제외\n", " data = data[1:] # header 제외\n", "\n", "# 총 15만개의 문장으로 이루어진 데이터셋임을 알 수 있다.\n", "docs = [row[1] for row in data] # 텍스트 부분만 가져옴\n", "print(f\"데이터셋: {data[:10]}\\n텍스트 데이터: {docs[:5]}\\n문장 개수: {len(docs)}\") " ] }, { "cell_type": "code", "execution_count": 28, "id": "ec51c69e-23bb-400e-b1f2-4e5238b73fcc", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|███████████████████████████████████████████████████████████████████████| 150000/150000 [00:01<00:00, 95103.93it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[('', '아'), ('아', ' '), (' ', '더'), ('더', '빙'), ('빙', '.')]\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "from tqdm import tqdm\n", "# 토큰화한 텍스트 데이터의 bigram을 모두 리스트에 추가한다.\n", "sentences = []\n", "for _ in tqdm(docs):\n", " tokens = [\"/\".join(_) for _ in _]\n", " bigram = ngrams(tokens, 2, pad_left=True, pad_right=True, left_pad_symbol=\"\", right_pad_symbol=\"\")\n", " sentences += [t for t in bigram]\n", "print(sentences[:5])" ] } ], "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.11" } }, "nbformat": 4, "nbformat_minor": 5 }