ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [n421] Count-based Representation
    AI 부트캠프 2021. 12. 24. 09:47

    자연어 처리

    자연어 처리 용어

    • 말뭉치(Corpus) : 텍스트 데이터
    • 문서(Document) : 말뭉치가 여러 개인 문장들의 집합
    • 문장(Sentence) : 단어와 형태소 등으로 구성된 문자열
    • 어휘 집합(Vocabulary) : 문서와 문장을 단어/형태소와 같은 토큰 단위로 나눈 집합

     

    자연어 (Natural Language)

    우리가 일반적으로 사용하는 언어를 자연어라고 한다. 표준어가 아닐 수도 있고, 우리가 일상에서 사용하는 모든 언어를 가리킨다. 자연어 처리(Natural Language Processing, NLP)는 컴퓨터가 사용할 수 있게 처리하는 기술을 뜻한다.

     

     

    Text Preprocessing (텍스트 전처리)

    차원의 저주 (Curse of Dimensionality)

    "특성의 개수가 선형적으로 늘어날 때 동일한 설명력을 가지기 위해 필요한 인스턴스의 수는 지수적으로 증가한다.
    즉 동일한 개수의 인스턴스를 가지는 데이터셋의 차원이 늘어날수록 설명력이 떨어지게 된다.”

    자연어 처리에서 전체 데이터셋의 feature는 말뭉치에 존재하는 단어의 종류이다. 그러므로 차원의 저주에서 벗어나기 위해서는 이 단어의 종류를 줄여야 한다.

    예를 들어, 대문자를 전부 소문자를 통일 하거나, 정규식을 사용하여 구두점을 없애는 등의 처리를 할 수 있다.

    이 경우 AmazonbasicsAmazonBasics는 같대소문자가 다르기 때문에 다른 브랜드, 즉 다른 카테고리로 인식된다. 이를 모두 소문자화 하여 한 카테고리로 만들 수 있다.

    # 데이터를 모두 소문자로 변환
    df['brand'] = df['brand'].apply(lambda x: x.lower())
    df['brand'].value_counts()

     

    토큰화

    import spacy
    from spacy.tokenizer import Tokenizer
    
    nlp = spacy.load("en_core_web_sm")
    
    tokenizer = Tokenizer(nlp.vocab)
    sent1 = "I am a student."
    sent2 = "J is the alphabet that follows i."
    sent3 = "Is she a student trying to become a data scientist?"
    
    sent_lst = [sent1, sent2, sent3]
    
    total_tokens = []
    
    for i, sent in enumerate(tokenizer.pipe(sent_lst)):
        for token in sent:
          print(i, token)

    다음과 같이 결과가 나온다. 즉, tokenizer.pipe(sent_lst) 에는 object가 담겨져 있고,
    i와 sent에는 인덱스와 문장이 각각 담겨져 있다.

    0 I am a student.
    1 J is the alphabet that follows i.
    2 Is she a student trying to become a data scientist?

    그래서 코드와 같이 for token in sent를 한 뒤 print(i, token)을 하면 왼쪽 사진과 같은 결과가 나온다. 

    이를 토큰화 하기 위해서 token.text를 아래와 같이 사용한다.

     

     

    sent1 = "I am a student."
    sent2 = "J is the alphabet that follows i."
    sent3 = "Is she a student trying to become a data scientist?"
    
    sent_lst = [sent1, sent2, sent3]
    
    total_tokens = []
    
    for i, sent in enumerate(tokenizer.pipe(sent_lst)):
        sent_token = [token.text for token in sent]
        total_tokens.extend(sent_token)
        print(f"sent {i} : {sent_token}")
    
    token_set = set(total_tokens)
    
    print(f""" 
    total_tokens : {token_set}
    #_of_total_tokens : {len(token_set)}
    """)

     

    문서 단어 행렬 (Document-Term Matrix, DTM)

    def word2idx(sent, total):
        sent_token = sent.split()
        return [1 if word in sent_token else 0 for word in total]
    
    sent1_idx = word2idx(sent1, token_set)
    sent2_idx = word2idx(sent2, token_set)
    sent3_idx = word2idx(sent3, token_set)
    sent1_idx은 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]와 같은 형태이다. 이를 데이터 프레임 안에 삽입하여 DTM 형태로 만든다.
    import pandas as pd
    
    dtm = pd.DataFrame([sent1_idx, sent2_idx, sent3_idx], columns=set(total_tokens))
    dtm
     
     
    dtm의 shape은 (3, 19)이다. 즉 모든 토큰의 수는 19개이다.
    하지만 희소 행렬 (Sparse) 이므로 모델에서 높은 성능을 보장하기 어렵다. 즉, 차원의 저주에서 벗어나기 어렵다!
    이를 위해서 차원을 줄이는 과정 (정규 표현식, 대소문자 통일 등)을 통해 차원을 줄여야 한다.

     

    정규 표현식 (Regex)

    문자열을 다루기 위해 매우 중요하므로 연습이 필요하다. 

     

    [정규표현식] re 모듈

    정규표현식  regular expression 특정한 패턴과 일치하는 문자열을 '검색', '치환', '제거' 하는 기능을 지원 정규표현식의 도움없이 패턴을 찾는 작업(Rule 기반)은 불완전 하거나, 작업의 cost가 높음 e.

    da-journal.com

     

    Python RegEx

    W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

    www.w3schools.com

     

    07-2 정규 표현식 시작하기

    [TOC] ## 정규 표현식의 기초, 메타 문자 정규 표현식에서 사용하는 메타 문자(meta characters)에는 다음과 같은 것이 있다. > ※ 메타 문자란 원래 ...

    wikidocs.net

     

    정규 표현식을 이용하여 모든 대문자를 소문자로 변경하고 구두점이나 공백을 제거하자.

    import re
    
    def lower_and_regex(sentence):
        """
        모든 대문자를 소문자로 변경 후
        정규식을 이용하여 알파벳 소문자 이외의 구두점 삭제
        """
        sentence = sentence.lower()
        sentence = re.sub(r"[^a-z ]", "", sentence)
        return sentence
    
    prep_sent_lst = [lower_and_regex(sent) for sent in sent_lst]
    print(prep_sent_lst)

     

    변경하기 전에 sent_lst는 아래와 같은 형태였다.

    ['I am a student.', 'J is the alphabet that follows i.', 'Is she a student trying to become a data scientist?']

     

    이를 함수 lower_and_regex를 통해 소문자로 변경 및 구두점 삭제하였다.

    ['i am a student', 'j is the alphabet that follows i', 'is she a student trying to become a data scientist']

    이렇게 변경된 문장들의 리스트 prep_sent_lst를 다시 토큰화 시켜준다.

    for i, prep_sent in enumerate(tokenizer.pipe(prep_sent_lst)):
        sent_token_prep = [token.text for token in prep_sent]
        total_tokens_prep.extend(sent_token_prep)
        print(f"sent {i} : {sent_token_prep}")
    
    token_set_prep = set(total_tokens_prep)
    
    print(f""" 
    total_tokens : {token_set_prep}
    num_of_total_tokens : {len(token_set_prep)}
    """)

    total_tokens : {'student', 'i', 'is', 'scientist', 'alphabet', 'that', 'a', 'to', 'the', 'she', 'follows', 'become', 'data', 'j', 'am', 'trying'}
    num_of_total_tokens : 16

    간단하게 함수화 하면 다음과 같다.

    def tokenize(text):
        """text 문자열을 의미있는 단어 단위로 list에 저장합니다.
        Args:
            text (str): 토큰화 할 문자열
        Returns:
            list: 토큰이 저장된 리스트
        """
        # 정규식 적용
        tokens = re.sub(r"[^a-zA-Z0-9 ]", "", text)
    
        # 소문자로 치환
        tokens = tokens.lower().split()
        
        return tokens

     

    불용어 (Stop words) 처리 

    oh, and, but 와 같은 단어들을 stop words라 한다. 예를 들어 제품 리뷰를 할 때 의미 없는 I, you, and, a 와 같은 단어를 제거하기 위함이다. 의미 없는 단어들을 제거 함으로써 차원의 저주 위험에서 조금 벗어날 수 있다. 

    불용어 예시는 다음과 같다.

      {'could', 'to', 'others', 'herein', 'cannot', 'whereafter', 'through', 'hers',
      'while', 'being', 'a', '‘m', 'together', 'noone', "'ve", 'became', 'seems', 
      'somewhere', 'why', 'did', 'an', 'have', 'something', 'regarding', 'among',
      'as', 'show', 'from', "n't", 'of', 'which', 'less', 'thus', 'himself', 'after', 
      'whose', 'whole', '‘ll', 'full', 'once', 'only', 'ever', 'becomes', 'whereupon', 
      'nevertheless', 'every', 'up', 'him', 'myself', 'alone', 'somehow', 'few', 'on', 
      'must', 'done', 'the', 'serious', 'fifteen', 'now', 'hereby', 'therefore', 'my'}

     

    통계적 트리밍 (Trimming)

    모든 불용어를 제거하는 방법이 아닌, 통계적으로 많이 등장하거나 너무 적은 토큰을 제거하는 방식이다. 

     

    어간 추출 (Stemming)

    어간 추출은 -ed, -ing, -s와 같이 어간에 붙은 부분을 제거하여 단어의 의미가 포함된 부분만 남기는 것을 의미한다.

    예를 들어,  argue, argued, arguing, argus의 어간은 argu이다. 

    Stemming 방법은 Porter, Snowball, Dawson 등 알고리즘으로 잘 정립되어 있다. 

    from nltk.stem import PorterStemmer
    
    ps = PorterStemmer()
    
    words = ["wolf", "wolves"]
    
    for word in words:
        print(ps.stem(word))

    결과 wolf, wolv이다.

    Porter 알고리즘은 단순히 단어의 끝 부분을 자르기 때문에 사전에 없는 단어도 많이 나온다. 

     

    표제어 추출 (Lemmatization)

    명사의 복수형은 단수형으로, 동사는 타동사로 변환된다. 많은 연산이 필요해서 다소 오래 걸린다.

    lem = "The social wolf. Wolves are complex."
    
    nlp = spacy.load("en_core_web_sm")
    
    doc = nlp(lem)
    
    # wolf, wolve가 어떤 Lemma로 추출되는지 확인해 보세요
    for token in doc:
        print(token.text, "  ", token.lemma_)
    The    the
    social    social
    wolf    wolf
    .    .
    Wolves    wolf
    are    be
    complex    complex
    .    .

     

    등장 횟수 기반의 단어 표현 (Count-based Representation)

    컴퓨터가 계산 할 수 있도록 벡터화(Vectorization)하는 과정이다. Count-based Representation은 단어가 문서에서 몇 번 등장하는가 그 횟수를 기반으로 문서를 벡터화한다. 벡터화된 문서는 문서-단어 행렬 (Document-Term Matrix, DTM) 형태로 나타난다. 각 row에는 문서가 있고, 각 컬럼에는 단어가 있는 행렬이다. 

     

    Bag of Words(BoW)

    • 단순하게 모든 단어를 순서나 문맥 상관 없이 Bag에 담아 빈도수만 고려하는 방식이다. 
    • 단어 순서를 무시해서 문맥 파악을 할 수 없다.

    # 예제로 사용할 text를 선언합니다. 
    text = """In information retrieval, tf–idf or TFIDF, short for term frequency–inverse document frequency, is a numerical statistic that is intended to reflect how important a word is to a document in a collection or corpus.
    It is often used as a weighting factor in searches of information retrieval, text mining, and user modeling.
    The tf–idf value increases proportionally to the number of times a word appears in the document and is offset by the number of documents in the corpus that contain the word,
    which helps to adjust for the fact that some words appear more frequently in general.
    tf–idf is one of the most popular term-weighting schemes today.
    A survey conducted in 2015 showed that 83% of text-based recommender systems in digital libraries use tf–idf."""
    # spacy의 언어모델을 이용하여 token화된 단어들을 확인합니다. 
    doc = nlp(text)
    print([token.lemma_ for token in doc if (token.is_stop != True) and (token.is_punct != True)])

    아래와 같은 결과가 나온다.

    ['information', 'retrieval', 'tf', 'idf', 'TFIDF', 'short', 'term', 'frequency', 'inverse', 'document', 'frequency', 'numerical', 'statistic', 'intend', 'reflect', 'important', 'word', 'document', 'collection', 'corpus', '\n', 'weight', 'factor', 'search', 'information', 'retrieval', 'text', 'mining', 'user', 'modeling', '\n', 'tf', 'idf', 'value', 'increase', 'proportionally', 'number', 'time', 'word', 'appear', 'document', 'offset', 'number', 'document', 'corpus', 'contain', 'word', '\n', 'help', 'adjust', 'fact', 'word', 'appear', 'frequently', 'general', '\n', 'tf', 'idf', 'popular', 'term', 'weight', 'scheme', 'today', '\n', 'survey', 'conduct', '2015', 'show', '83', 'text', 'base', 'recommender', 'system', 'digital', 'library', 'use', 'tf', 'idf']

     

    마존 리뷰 데이터에 CountVectorizer 적용하기

    from sklearn.feature_extraction.text import CountVectorizer
    
    count_vect = CountVectorizer(stop_words='english', max_features=100)
    
    # Fit 후 dtm을 만듭니다.(문서, 단어마다 tf-idf 값을 계산합니다)
    dtm_count_amazon = count_vect.fit_transform(df['reviews.text'])
    
    dtm_count_amazon = pd.DataFrame(dtm_count_amazon.todense(), columns=count_vect.get_feature_names())
    dtm_count_amazon

     

     

    TF-IDF

    • Term Frequence Inverse Document Frequency
    • 다른 문서에서 잘 나오지 않지만 이 문서에서는 여러번 언급된다면 이 문서를 대표할 수 있는 단어가 된다. 
    • 각 단어별로 문서와의 연관성을 알고자 할 때 사용한다.
    • TF는 문서에서 단어가 몇번 출현했는지를 나타낸다.
    • 단어가 많이 출현 할 수록 문서와 연관이 높다 (중요하다)라고 가정한다.
    • a, is 와 같은 Stopword는 문서와 연관이 없지만 자주 출현한다.
    IDF Score = log(문장 개수/단어가 출현한 문장 개수)

     

     

    TfidfVectorizer 적용하기

    # TF-IDF vectorizer. 테이블을 작게 만들기 위해 max_features=15로 제한하였습니다.
    tfidf = TfidfVectorizer(stop_words='english', max_features=15)
    
    # Fit 후 dtm을 만듭니다.(문서, 단어마다 tf-idf 값을 계산합니다)
    dtm_tfidf = tfidf.fit_transform(sentences_lst)
    
    dtm_tfidf = pd.DataFrame(dtm_tfidf.todense(), columns=tfidf.get_feature_names())
    dtm_tfidf

    vect = CountVectorizer(stop_words='english', max_features=15)
    dtm_count_vs_tfidf = vect.fit_transform(sentences_lst)
    dtm_count_vs_tfidf = pd.DataFrame(dtm_count_vs_tfidf.todense(), columns=vect.get_feature_names())
    dtm_count_vs_tfidf

     

    문서 유사도 

    코사인 유사도는 가장 많이 쓰이는 유사도 측정 방법이다.

    코사인 유사도는 두 벡터가 이루는 각의 코사인 값을 이용하여 구할 수 있는 유사도이다.

    • 두 벡터(문서)가
      • 완전히 같을 경우 1
      • 90도의 각을 이루면 0
      • 완전히 반대방향을 이루면 -1

    댓글