본문 바로가기
카테고리 없음

SVM을 활용한 뉴스 기사 분석

by JDeoks 2023. 5. 1.

0. 목표

 

어절 단위로 형태소분석 되어있는 텍스트파일로 corpus 만든 후 TF-IDF 작성

column은 ㄱㄴㄷ순으로 오름차순 정렬할 것

각 문서를 벡터로 변환한 후 SVM 모델로 카테고리 분류

 

 

1. 카테고리 값을 string으로 갖는 list category list 생성

 

.txt 파일들의 이름이 사전식 정렬을 하면 제대로 정렬되지 않아서 natural sort 함수를 사용했다.

 

# cell 1

import re

def atoi(text):
    return int(text) if text.isdigit() else text

def natural_keys(text):
    '''
    alist.sort(key=natural_keys) sorts in human order
    http://nedbatchelder.com/blog/200712/human_sorting.html
    (See Toothy's implementation in the comments)
    '''
    return [ atoi(c) for c in re.split(r'(\d+)', text) ]

 

lexical order sort

 

natural sort

출처: 

https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside

 

How to correctly sort a string with a number inside?

I have a list of strings containing numbers and I cannot find a good way to sort them. For example I get something like this: something1 something12 something17 something2 something25 something29 ...

stackoverflow.com

 

path에 제일 상위 디렉토리를 입력하고 하위 폴더들의 이름을 읽어 category list에 저장한 후 정렬한다.

# cell 2

import os

def readCategory(directory):
    """
    카테고리를 포함하는 폴더의 directory를 입력받아 오름차순으로 정렬 후 카테고리 list를 반환
     ex) readCategory('2022_Fall_Student_Data/8/Corpus/Input_Data')

        args:
            directory`string`: 읽을 파일의 디렉토리
        return:
            category`str list`: 카테고리이름 list
    """
    folder_list = os.listdir(directory)
    category = []
    # 카테고리 string 저장할 list

    for i in folder_list:
        if not i.endswith('.DS_Store'):
            category.append(i)
        # .DS_Store 빼고 category에 추가

    category.sort(key=natural_keys)

    return category
    

category = readCategory('/Users/Input_Data')
print(category)

cell 2

# cell 3

def readFileName(directory):
    """
    읽을 파일의 디렉토리를 입력받아 DataFrame으로 반환
     ex) readFileName('2022_Fall_Student_Data/8/Corpus/Input_Data')

        arg:
            directory`string`: 읽을 파일의 디렉토리
        return:
            fileName2D`str 2D list`: 읽은 파일명 저장된 table (row: categoryIdx, col: 파일 이름)
    """

    fileName2D = []
    # row: categoryIdx, column: 해당 카테고리의 파일 이름

    for categoryIdx in range(len(category)):
        path = directory+'/'+category[categoryIdx]
        file_list = os.listdir(path)
        #해당 카테고리 안에 있는 파일명이 담긴 string list

        txt_list = []
        for i in file_list:
            if i.endswith('.txt') and not i.endswith('.DS_Store'):
                #파일 형식이 .txt로 끝나는 파일 이름만 배열에 추가
                txt_list.append(i)

        txt_list.sort(key=natural_keys)
        # natural sort
        fileName2D.append(txt_list)

    # print("카테고리마다의 파일 개수")
    # for i in fileName2D:
    #     print(len(i), end=" ")
    
    return fileName2D
    
print(fileName2D)

cell 3

 

 

 

2. 파일 읽은 후 DTM DataFrame 생성

 

8_(POS)child_1.txt

다음으로는 리스트의 각 파일을 읽어 중복되지 않게 리스트에 추가하여 corpus를 만들어야 한다.

 

문서는 위와 같이 생겼는데, 형태소 분석이 되어있는 '\t' 기준 뒷 부분만 사용할 것이다.

명사류 형태소는 NNG, NNP만 고려할 것이기 때문에 '+'를 기준으로 뒷 부분은 버린다.

 

# cell 4

import pandas as pd
import numpy as np

def makeTrainDTM(directory, category, fileName2D):

    DTM = pd.DataFrame(columns=range(1))
    DTM.columns = ['category']
    # term table

    docNum = 0
    #문서 번호

    for categoryIdx in range(len(category)):

        for fileIdx in range(len(fileName2D[categoryIdx])):
            file = open(directory+'/'+category[categoryIdx]+'/'+fileName2D[categoryIdx][fileIdx], "r")

            lines = file.readlines()
            #파일을 한 줄씩 읽어서 string으로 list에 저장

            docRow = "Doc"+str(docNum)
            #row 이름 ex) Doc3

            DTM.loc[docRow] = [0 for i in range(len(DTM.columns))]
            # 새로운 row를 만들어 0으로 채움
            # ex)Doc3 = [0, 0, 0, 0](열 개수만큼 0을 만들어 list로 반환)

            DTM.loc[docRow, 'category'] = categoryIdx
            # 해당 문서의 카테고리 값에 categoryIdx 저장
            
            for line in lines:
                morp = line.split('\t')[-1].split('+')[0].strip('\n')
                # '\t'를 기준으로 뒤, '+'를 기준으로 앞의 string을 잘라 list에 저장
                
                if morp == '':
                    continue
                # 빈 string이면 무시

                if morp in DTM.columns:
                    DTM.loc[docRow,morp] += 1
                else:
                    DTM[morp] = 0
                    DTM.loc[docRow,morp] += 1
                # DTM columns에서 중복 확인, 중복 있으면 값만 1 증가시킴, 없으면 열 추가 후 값 1 대입 

            docNum += 1
    return DTM
    #28분
     
DTM = makeTrainDTM('Input_Data', category, fileName2D)
DTM

 

파일 경로에 파일명을 붙일 때 파일명 앞에 /를 붙여야 제대로 된 디렉토리 주소가 된다.

파일을 한 줄씩 읽어서 string list로 저장해주는 .readlines()를 사용하였는데,

.readlines() 함수는 '\n'까지 스트링에 저장해주기 때문에 strip('\n')으로 삭제했다.

 

DTM DataFrame을 만들때 column을 하나 만들어주었는데

빈 DataFrame을 생성했을 때 행과 열이 둘 다 없으면 데이터를 추가하거나 수정할 수 없기 때문이다.
최소한 열 정보라도 갖고 있어야 행 데이터를 추가할 수 있기 때문에 첫 column인 category를 추가해주었다.

 

TF

제일 앞에 있는 column은 카테고리를 표시하는데, train 시킬 때 label의 역할을 할 것이다.

매 문서마다 새로운 row를 만들어주면서

corpus에 동일한 형태소가 없으면 column을 추가하는 방식이다.

그래서 대략적으로 왼쪽 위부터 계단식으로 내려오는 형태를 띄는 걸 볼 수 있다.

 

코드:

https://github.com/JeongDeok/NLP_Assignment2