오늘은 예제는 맞췄는데 시간, 메모리 초과가 뜬 문제를 가져왔습니다. 

정답이 뭔지 몰라서 더욱 답답한.. 하지만 DP를 이용한 방식, Two Pointer를 이용한 방식 모두 예제는 맞춰서.. 

나중에 좀 더 코딩을 잘하는(?) 제가 참고하도록 기록해놓겠습니다.

 

문제

https://www.acmicpc.net/problem/10066

 

10066번: 팰린드롬

알파벳 소문자로만 이루어진 문자열이 주어진다. 부분문자열의 "등장값" 이란 그 부분문자열이 등장하는 횟수와 부분문자열의 길이를 곱한 값이다. 문자열이 주어졌을 때, 팰린드롬이면서 가장

www.acmicpc.net

 

코드 및 풀이

 

우선 'bacab'과 같이 팰린드롬인 문자열의 조건은, 

1) 시작점의 문자열과 끝점의 문자열이 같고, ('bacab'의 경우 양끝이 'b'로 같습니다)

2) 시작점, 끝점을 제외한 문자열 역시 팰린드롬입니다. ('bacab'의 경우 양끝을 제외한 'aca'역시 팰린드롬입니다) 

 

아래는 문제에 대한 풀이입니다.

 

1. Two Pointer를 이용한 방식

s='abacaba'
result={}
answer=[]
for i in range(len(s)):
  for j in range(0,2):
    left,right=i,i+j
    while left>=0 and right<len(s) and s[left]==s[right]: 
        left-=1 
        right+=1
        if s[left+1:right] not in result.keys():
            result[s[left+1:right]]=1
        else:
            result[s[left+1:right]]+=1
for i in result:
    answer.append(len(i)*result[i])
print(max(answer))

1) 팰린드롬을 Two Pointer로 구하는 원리는 아래를 참고하시면 될 것 같습니다.

https://byumm315.tistory.com/entry/%EB%A6%AC%ED%8A%B8%EC%BD%94%EB%93%9C-5-%EA%B0%80%EC%9E%A5-%EA%B8%B4-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC-%EB%B6%80%EB%B6%84-%EB%AC%B8%EC%9E%90%EC%97%B4

 

[리트코드 #5] 가장 긴 팰린드롬 부분 문자열

오늘은 '파이썬 알고리즘 인터뷰'를 참고했습니다. http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9791189909178 파이썬 알고리즘 인터뷰 - 교보문고 95가지 알고리즘 문제..

byumm315.tistory.com

 

2) 팰린드롬을 구하며 result 딕셔너리에 키(key)를 저장했습니다. 이때, 딕셔너리에 값(value)이 존재하면 1을 더했고 존재하지 않으면 키(key)를 만들고 값을 1로 지정했습니다. result 는 아래와 같습니다.

 

{'a': 4, 'aba': 2, 'abacaba': 1, 'aca': 1, 'b': 2, 'bacab': 1, 'c': 1}

 

3) for문을 이용해 result에서 키(key)의 길이와 해당하는 값(value)을 곱해 answer 리스트에 저장했습니다.

이후 가장 큰 값을 출력했습니다. answer 리스트는 아래와 같습니다.

 

[4, 2, 6, 1, 3, 5, 7]

 

4) 정답은 7입니다.

 

2. DP(다이나믹 프로그래밍)를 이용한 방식

s='abacaba'
n=len(s)
dp = [[0] * n for _ in range(n)]
answer=[]
answer_1=[]

for num_len in range(n):
    for start in range(n - num_len):
        end = start + num_len
        # 시작점과 끝점이 같다면 글자수가 1개이므로 무조건 팰린드롬
        if start == end:
            dp[start][end] =1
            answer.append(s[start:end+1])
        # 시작점의 글자와 끝점의 글자가 같다면
        elif s[start] == s[end]:
        	# 두 글자짜리 문자열이라면 무조건 팰린드롬
            if start + 1 == end: 
                dp[start][end] = 1
                answer.append(s[start:end+1])
            # 가운데 문자열이 팰린드롬이라면 팰린드롬
            elif dp[start+1][end-1] == 1: 
                dp[start][end] = 1
                answer.append(s[start:end+1])
                
from collections import Counter
for i in Counter(answer):
    answer_1.append(len(i)*Counter(answer)[i])
print(max(answer_1))

1) 문자열 길이의 행과 열을 가진 DP Table dp를 만듭니다. 

2) 이중for을 통해 시작점과 끝점사이 길이가 0일때부터 n일때까지 범위를 지정합니다. 

따라서 길이가 0인 부분문자열부터 n인 부분문자열까지 확장하는 방식으로 팰린드롬을 찾습니다.

 

이것은 크기가 작은 부분문자열부터 팰린드롬 유무를 확인하고 문자열의 크기를 조금씩 크게 만들면서 팰린드롬 유무를 확인한다는 점에서 DPBottom - Up 방식과 유사합니다.

 

DP에서 문제가 팰린드롬 유무를 찾기라면 작은 문제를 풀면서 큰 문제의 해답을 찾는다는 점에서 유사합니다.

 

2-1) 예를 들면, 길이가 7'abacaba'가 팰린드롬인지 알려면, 양 끝점을 봅니다.

양 끝점이 'a'로 같기 때문에, 팰린드롬일 가능성이 있습니다.

 

이제, 양 끝점을 제외한 나머지 문자열'bacab'가 팰린드롬인지 알아야 됩니다.

그런데, 우리는 이미 앞에서 길이가 5인 부분 문자열에 있는 팰린드롬들을 구했습니다.

 

따라서 'bacab'가 팰린드롬임을 알 수 있습니다. 

가운데에 있는 'bacab'가 팰린드롬이기 때문에, 'abacaba' 역시 팰린드롬입니다.

 

3) 문자열 안에 있는 부분문자열이 팰린드롬인 경우, dp[시작점][끝점]1을 부여합니다.

아래의 그림의 0번째 줄을 예로 들면, 0번째 원소와 2번째 원소가 1입니다. 즉, 시작위치가 0, 끝위치가 2인 원소 'aba'가 팰린드롬임을 의미합니다.

4) 문자열 안에 있는 부분문자열이 팰린드롬인 경우, 해당 문자열을 answer 리스트에 저장합니다.

answer 리스트는 다음과 같습니다.

 

['a', 'b', 'a', 'c', 'a', 'b', 'a', 'aba', 'aca', 'aba', 'bacab', 'abacaba']

 

5) answer 리스트 안에 있는 중복 문자열과 빈도수를 구하기 위해 collections에서 Counter를 임포트합니다.

문자열의 길이와 빈도수를 곱한 후, 가장 큰 값을 출력합니다. Counter 함수를 적용한 결과는 다음과 같습니다.

 

Counter({'a': 4, 'aba': 2, 'abacaba': 1, 'aca': 1, 'b': 2, 'bacab': 1, 'c': 1})

 

6) 정답은 7입니다.

 

참고

https://velog.io/@himi/%EB%B0%B1%EC%A4%80-10942.-%ED%8C%B0%EB%A6%B0%EB%93%9C%EB%A1%AC

 

[파이썬] 백준 10942. 팰린드롬?

왜 동적 프로그래밍 문제인지를 이해하는 것부터 어려움을 겪었던 문제였다!

velog.io

 

오늘은 '파이썬 알고리즘 인터뷰'를 참고했습니다.

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9791189909178 

 

파이썬 알고리즘 인터뷰 - 교보문고

95가지 알고리즘 문제 풀이로 완성하는 코딩 테스트 | [이 책의 구성] [1부 코딩 인터뷰] 1장, ‘코딩 인터뷰’에서는 코딩 테스트에 대한 소개와 어떻게 하면 시험을 잘 치를 수 있을지, 문제 풀이

www.kyobobook.co.kr

 

문제는 아래와 같습니다.

 

https://leetcode.com/problems/longest-palindromic-substring/

 

Longest Palindromic Substring - LeetCode

Level up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared for your next interview.

leetcode.com

 

풀이 과정

 

저는 투포인터가 중앙을 중심으로 확장하는 형태로 풀이했습니다. 'babab'를 통해 가장 긴 팰린드롭을 구하는 풀이를 설명드리겠습니다.우선 [그림1]과 같이 길이가 2, 3인 두 개의 투포인터를 'babab'에 배치합니다.

[그림 1]

여기서 A와 같이 투포인터가 가리키는 시작점과 끝점의 원소가 같다면, 팰린드롭을 의미합니다. [그림 1]에서 'bab', 'aba' 가 있습니다.

 

하지만 'aba' (A)의 경우, 다른 팰린드롭과 다르게 양옆에 문자열이 있어서 양옆으로 투포인터를 확장할 수 있습니다. 확장하면 'babab'가 됩니다. 이 역시 양끝이 서로 같기 때문에 팰린드롭입니다. 

 

우리는 가장 긴 팰린드롭을 구해야되기 때문에, 'babab' 안에 있는 팰린드롭에서 가장 긴 길이의 투포인터를 구합니다.

 

코드

 

def answer(self,s):

	#팰린드롬 판별 및 투 포인터 확장
	def expand(left,right):
		while left>=0 and right<len(s) and s[left]==s[right]: #팰린드롬인 경우
    		left-=1 #투 포인터 양옆으로 확장
       		right+=1
    	return s[left+1:right]
	
    #해당 사항이 없을 때 빠르게 리턴
	if len(s)<2 or s==s[::-1]:
		return s
	
    result=''
	
    #슬라이딩 윈도우 우측으로 이동
	for i in range(len(s)-1):
		result=max(result,expand(i,i+1),expand(i,i+2),key=len)
 	return result

오늘은 저의 학위논문 CODE를 올려봅니다.

논문 분야는 텍스트 마이닝으로, 크게 감성분석, 토픽모델링, 대응분석으로 구성됩니다.

대응분석은 SAS로 수행했고 감성분석, 토픽모델링은 Google Colab 환경에서 Python 3.0으로 진행했습니다.

 

당시 학위논문을 쓰면서 텍스트 마이닝에 흥미를 더욱 가지며 졸업 후, Word Embedding과 Deep Learning으로 공부 영역을 확장했습니다. 현재도, 텍스트 마이닝, NLP 는 가장 관심있는 분야입니다.

 

그럼, 토픽모델링 CODE를 올려보도록 하겠습니다.

 

목차

1. 데이터 불러오기 및 형태소 분석

2. 단어 전처리

2.1. 형태소 분석에서 분할된 단어 하나로 합치기

2.2. 너무 많이 나오는 단어 제거하기

2.3. 의미없는 단어 제거 및 한 글자인 경우 제거하기

3. 기사별로 단어들을 합치기

4. 빈도수가 높은 단어, 너무 낮은 단어 제거하기

5. Gensim 패키지를 이용해 토픽모델링하기

5.1. 토픽수 구하기

5.2. 토픽모델링하기

5.3. 기사별 토픽 할당하기

 

1. 데이터 불러오기 및 형태소 분석

from google.colab import drive
drive.mount('/content/drive')
import pandas as pd
link = pd.read_excel('/content/drive/MyDrive/방역 기사 추출.xlsx')
link.head()

#한글제외 제거
import re
hangul = re.compile('[^ ㄱ-ㅣ가-힣]+') # 한글과 띄어쓰기를 제외한 모든 글자 
for i in range(len(link)):
  link.iloc[i,0] = hangul.sub(' ', link.iloc[i,0]) # 한글과 띄어쓰기를 제외한 모든 부분을 제거
  
#Komoran 이용하여 형태소 분석 수행
!apt-get update 
!apt-get install g++ openjdk-8-jdk python-dev python3-dev 
!pip3 install JPype1-py3 
!pip3 install konlpy 
!JAVA_HOME="C:\Users\tyumi\Downloads"
from konlpy.tag import * # class  
komoran=Komoran()
for i in range(0,len(link)):
  link.iloc[i,0]=re.sub('\n|\r','',link.iloc[i,0])

text_1=[]
article=[]
index=[]
paper=[]
title=[]
date=[]
for j in range(0,len(link)):
  ex_pos=list(komoran.pos(link.iloc[j,0]))
  for (text, tclass) in ex_pos : # ('형태소', 'NNG') 
    if tclass == 'VA' or tclass == 'NNG' or tclass == 'NNP' or  tclass == 'MAG' : 
      text_1.append(text) 
      index+=[j]
      article+=[link.iloc[j,0]]
      paper+=[link.iloc[j,3]]
      title+=[link.iloc[j,4]]
      date+=[link.iloc[j,2]]
link=pd.DataFrame({'Index':index,'Phrase':text_1,'Text':article,'Paper':paper,'Title':title,'Date':date})

[link 데이터 프레임 예시]

2. 단어 전처리

 

2.1. 형태소 분석에서 분할된 단어 하나로 합치기

 

for i in range(0,len(link)):
  if str(link.iloc[i,1])=='코로나':
    if str(link.iloc[i+1,1])=='바이러스':
      link.iloc[i,1]=''
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='미래':
    if str(link.iloc[i+1,1])=='통합':
        if str(link.iloc[i+2,1])=='당':
          link.iloc[i,1]='미래통합당'
          link.iloc[i+1,1]=''
          link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='자유':
    if str(link.iloc[i+1,1])=='한국당':
      link.iloc[i,1]='자유한국당'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='사회':
    if str(link.iloc[i+1,1])=='거리':
      if str(link.iloc[i+2,1])=='두기':
        link.iloc[i,1]='거리두기'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='거리':
    if str(link.iloc[i+1,1])=='두기':
      link.iloc[i,1]='거리두기'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='중앙':
    if str(link.iloc[i+1,1])=='방역':
      if str(link.iloc[i+2,1])=='대책':
        if str(link.iloc[i+3,1])=='본부':
          link.iloc[i,1]=''
          link.iloc[i+1,1]=''
          link.iloc[i+2,1]=''
          link.iloc[i+3,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='보건':
    if str(link.iloc[i+1,1])=='복지':
      if str(link.iloc[i+2,1])=='가족부':
        link.iloc[i,1]=''
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='보건':
    if str(link.iloc[i+1,1])=='복지부':
      link.iloc[i,1]='보건복지부'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='박':
    if str(link.iloc[i+1,1])=='능':
      if str(link.iloc[i+2,1])=='후':
        link.iloc[i,1]='박능후'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='정은':
    if str(link.iloc[i+1,1])=='경':
      link.iloc[i,1]='정은경'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='문':
    if str(link.iloc[i+1,1])=='대통령':
      link.iloc[i,1]='문재인'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='공공':
    if str(link.iloc[i+1,1])=='의대':
      link.iloc[i,1]='공공의대'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='공공':
    if str(link.iloc[i+1,1])=='의료':
      link.iloc[i,1]='공공의료'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='소상':
    if str(link.iloc[i+1,1])=='공인':
      link.iloc[i,1]='소상공인'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='자가':
    if str(link.iloc[i+1,1])=='격리':
      link.iloc[i,1]='자가격리'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='자가':
    if str(link.iloc[i+1,1])=='진단':
      link.iloc[i,1]='자가진단'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='사랑':
    if str(link.iloc[i+1,1])=='제일':
      if str(link.iloc[i+2,1])=='교회':
        link.iloc[i,1]='사랑제일교회'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='세계':
    if str(link.iloc[i+1,1])=='보건':
      if str(link.iloc[i+2,1])=='기구':
        link.iloc[i,1]='세계보건기구'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='박':
    if str(link.iloc[i+1,1])=='시장':
      link.iloc[i,1]='박원순'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='우':
    if str(link.iloc[i+1,1])=='한':
      link.iloc[i,1]='우한'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='강':
    if str(link.iloc[i+1,1])=='대변인':
      link.iloc[i,1]='강경화'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='한국':
    if str(link.iloc[i+1,1])=='발':
      link.iloc[i,1]='한국발'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='검':
    if str(link.iloc[i+1,1])=='체':
      link.iloc[i,1]='검체'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='강경':
    if str(link.iloc[i+1,1])=='외교부':
      if str(link.iloc[i+2,1])=='장관':
        link.iloc[i,1]='강경화'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
    elif str(link.iloc[i+1,1])=='외교':
      if str(link.iloc[i+2,1])=='장관':
        link.iloc[i,1]='강경화'
        link.iloc[i+1,1]=''
        link.iloc[i+2,1]=''
    elif str(link.iloc[i+1,1])=='장관':
      link.iloc[i,1]='강경화'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='국':
    if str(link.iloc[i+1,1])=='격':
      link.iloc[i,1]='국격'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='차':
    if str(link.iloc[i+1,1])=='벽':
      link.iloc[i,1]='차벽'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='중국':
    if str(link.iloc[i+1,1])=='발':
      link.iloc[i,1]='중국발'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='국민':
    if str(link.iloc[i+1,1])=='힘':
      link.iloc[i,1]='국민의힘'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='개천절':
    if str(link.iloc[i+1,1])=='집회':
      link.iloc[i,1]='개천절집회'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='광화문':
    if str(link.iloc[i+1,1])=='집회':
      link.iloc[i,1]='광화문집회'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='엄마':
    if str(link.iloc[i+1,1])=='부대':
      link.iloc[i,1]='엄마부대'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='전광훈':
    if str(link.iloc[i+1,1])=='목사':
      link.iloc[i,1]='전광훈목사'
      link.iloc[i+1,1]=''
for i in range(0,len(link)):
  if str(link.iloc[i,1])=='베이':
    link.iloc[i,1]='후베이'
  elif str(link.iloc[i,1])=='신천지예수교':
    link.iloc[i,1]='신천지'
  elif str(link.iloc[i,1])=='민주당':
    link.iloc[i,1]='더불어민주당'
  elif str(link.iloc[i,1])=='신천지예수교 증거장막성전':
    link.iloc[i,1]='신천지'
  elif str(link.iloc[i,1])=='의과대학':
    link.iloc[i,1]='의대'
  elif str(link.iloc[i,1])=='대한의사협회':
    link.iloc[i,1]='의협'
  elif str(link.iloc[i,1])=='중동호흡기증후군':
    link.iloc[i,1]='메르스'
  elif str(link.iloc[i,1])=='비대':
    link.iloc[i,1]='비대면'
  elif str(link.iloc[i,1])=='우하':
    link.iloc[i,1]='우한'

2.2. 너무 많이 나오는 단어 제거하기

for i in range(0,len(link)):
  if str(link.iloc[i,1])=='뉴시스':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='국민일보':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='연합뉴스':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='중대본':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='질본':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='질병관리본부':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='질병관리본부장':
    link.iloc[i,1]=''  
  elif str(link.iloc[i,1])=='코로나바이러스':
    link.iloc[i,1]=''  
  elif str(link.iloc[i,1])=='코로나':
    link.iloc[i,1]=''  
  elif str(link.iloc[i,1])=='신종':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='이날':
    link.iloc[i,1]=''
  elif str(link.iloc[i,1])=='기자':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='매일일보':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='의원':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='대표':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='장관':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='총리':
    link.iloc[i,1]=''        
  elif str(link.iloc[i,1])=='위원장':
    link.iloc[i,1]=''

2.3. 의미없는 단어 제거 및 한 글자인 경우 제거하기

from google.colab import drive
drive.mount('/content/drive')
import pandas as pd
dd = pd.read_excel('/content/drive/MyDrive/제거문자.xlsx')

def find_index(data, target):
  res = []
  lis = data
  while True:
    try:
      res.append(lis.index(target) + (res[-1]+1 if len(res)!=0 else 0)) #+1의 이유 : 0부터 시작이니까
      lis = data[res[-1]+1:]
    except:
      break     
  return res
aa=[]
for i in range(0,len(dd)):
  aa+=find_index(link.iloc[:,1].tolist(),dd.iloc[i,0])
for j in range(0,len(aa)):
  link.iloc[aa[j],1]=''

ii=[]
for i in range(0,len(link)):
  if link.iloc[i,1]!='':
    ii+=[i]
link_1=link.iloc[ii,:]

for i in range(0,len(link_1)):
  if len(link_1.iloc[i,1])==1:
    link_1.iloc[i,1]=''
ii=[]
for i in range(0,len(link_1)):
  if link_1.iloc[i,1]!='':
    ii+=[i]
link_2=link_1.iloc[ii,:]
link_2

[link_2 데이터프레임 예시]

3. 기사별로 단어들을 합치기

!pip3 install gensim
import gensim
link_2=link_2.reset_index(drop=True)
grouped =link_2.groupby(['Index'])
for key, group in grouped:
  globals()['Phrase_{}'.format(key)]=list(group.Phrase)
  globals()['Index_{}'.format(key)]=list(group.Index)[0]
  globals()['Text_{}'.format(key)]=list(group.Text)[0]  
  globals()['Title_{}'.format(key)]=list(group.Title)[0]  
  globals()['Paper_{}'.format(key)]=list(group.Paper)[0]  
  globals()['Date_{}'.format(key)]=list(group.Date)[0] 
Phrase=[]
Index=[]
Text=[]
Title=[]
Paper=[]
Date=[]
for key in range(470):
  Phrase.append(globals()['Phrase_{}'.format(key)])
  Index.append(globals()['Index_{}'.format(key)])
  Text.append(globals()['Text_{}'.format(key)])
  Title.append(globals()['Title_{}'.format(key)])
  Paper.append(globals()['Paper_{}'.format(key)])
  Date.append(globals()['Date_{}'.format(key)])
link_3=pd.DataFrame({'Index':Index,'Phrase':Phrase,'Text':Text,'Title':Title,'Paper':Paper,'Date':Date})
link_3

[link_3 데이터프레임 예시]

4. 빈도수가 높은 단어, 너무 낮은 단어 제거하기

from gensim import corpora
tokenized_doc = link_3.Phrase
dictionary = corpora.Dictionary(tokenized_doc)
dictionary.filter_extremes(no_below=5,no_above=0.8)
text=tokenized_doc
corpus = [dictionary.doc2bow(text) for text in tokenized_doc]

5. Gensim 패키지를 이용해 토픽모델링하기

5.1. 토픽수 구하기

#토픽모델링-토픽수
per=[]
import matplotlib.pyplot as plt
from gensim import models
for i in range(1,11):
  ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics = i, id2word=dictionary, passes=500,random_state=2500,gamma_threshold=0.01,alpha='auto')
  cm = models.CoherenceModel(model=ldamodel, corpus=corpus, coherence='u_mass')
  coherence = cm.get_coherence()
  per.append(coherence)
limit=11; start=1; step=1;
x = range(start, limit, step)
plt.plot(x, per)
plt.xlabel("Num Topics")
plt.ylabel("Coherence score")
plt.legend(("per"), loc='best')
plt.show()

[토픽 수 그래프]

5.2. 토픽모델링하기

NUM_TOPICS = 3 
ldamodel = gensim.models.ldamodel.LdaModel(corpus, num_topics = NUM_TOPICS, id2word=dictionary, passes=500,random_state=2500,gamma_threshold=0.01,alpha='auto')
topics = ldamodel.print_topics(num_words=20)
for topic in topics:
    print(topic)

5.3. 기사별 토픽 할당하기

#토픽모델링-문서
def make_topictable_per_doc(ldamodel, corpus):
    topic_table = pd.DataFrame()
    for i, topic_list in enumerate(ldamodel[corpus]):
        doc = topic_list[0] if ldamodel.per_word_topics else topic_list            
        doc = sorted(doc, key=lambda x: (x[1]), reverse=True)
        for j, (topic_num, prop_topic) in enumerate(doc): 
            if j == 0:  
                topic_table = topic_table.append(pd.Series([int(topic_num), round(prop_topic,4), topic_list]), ignore_index=True)
            else:
                break
    return(topic_table)
topictable = make_topictable_per_doc(ldamodel, corpus)
topictable = topictable.reset_index() 
a=topictable.iloc[:,0]
b=topictable.iloc[:,1]
c=topictable.iloc[:,2]
link_3['Topic']=b.tolist()
link_3['Frequency']=c.tolist()

[기사별 토픽 할당 결과]

6. 최종 결과

주제 주제명 주제비중
주제1 코로나 19 발생 초기 정부 대응에 대한 국내외 평가 50%
주제2 코로나 19 장기화로 인한 정부의 방역 정책 논란 24%
주제3 정부의 집회 대응에 대한 논란 26%

오늘은 LSTM에 대해서 알아봅시다. '밑바닥부터 시작하는 딥러닝2' 책을 공부한 내용으로 설명했습니다.

 

http://www.yes24.com/Product/Goods/72173703

 

밑바닥부터 시작하는 딥러닝 2 - YES24

직접 구현하면서 배우는 본격 딥러닝 입문서 이번에는 순환 신경망과 자연어 처리다! 이 책은 『밑바닥부터 시작하는 딥러닝』에서 다루지 못했던 순환 신경망(RNN)을 자연어 처리와 시계열 데

www.yes24.com

 

 

목차는 아래와 같습니다.

 

1. LSTM 이란?

2. LSTM 대략적인 설명

3. LSTM 구현

 


1. LSTM 이란?

 

LSTM은 RNN의 기울기 소실 문제를 방지하는 신경망입니다. RNN의 Hidden Layer에 기억 셀, Input/Output/Forget Gate를 추가한 형태입니다. 

 

LSTM과 RNN의 차이는 [그림 1]로 설명하겠습니다.

 

RNN의 Hidden Layer에는 Hidden State인 Ht-1과 Input인 Xt, 가중치인 Wt로 구성되어 있습니다. 이들은 Hidden Layer 안에서 선형결합과 하이퍼볼릭 탄젠트 함수를 거친 후 Ht를 내보냅니다.

 

하지만 LSTM의 Hidden Layer에는 기억 셀인 Ct-1과 Input/Output/Forget Gate가 있습니다. 기억 셀은 데이터를 LSTM 계층 내에서만 주고받습니다. 즉, LSTM 계층 내에서만 이동하고 다른 계층으로 출력하지는 않습니다. 또한 기억 셀 Ct에는 시각 t에서의 LSTM의 기억이 저장돼 있습니다. 이것은 과거로부터 시각 t까지에 필요한 모든 정보가 저장돼 있다고 가정합니다. 이 기억을 바탕으로 다음 시각의 LSTM에 Hidden State Ht를 출력합니다. 

 

[그림 1] RNN과 LSTM의 차이

 


 

2. LSTM 대략적인 설명

 

지금부터 LSTM의 게이트에서 일어나는 과정을 대략적으로 설명해보겠습니다.

[그림 2] LSTM 구조

 

1) o : Output Gate

 

Output Gate는 다음 Hidden State Ht의 출력을 담당하는 게이트입니다. Output Gate에서 Hidden State Ht-1과 Input Xt를 연산한 것을 tanh(Ct)에 곱합니다. 이를 통해 tanh(Ct)에서 얼만큼의 양을 Ht로 출력시킬지 결정합니다.

 

계산식은 [식 1]과 같습니다. 가중치와 Xt, Ht-1간의 선형결합에 시그모이드 함수를 씌운 형태입니다. [식 2]는 Output Gate 결과에 tanh(Ct)를 곱해 Hidden State Ht로 생성하는 과정입니다.

 

[식 1] Output Gate
[식 2] Hidden State Ht 구하는 방식

 

2) f : Forget Gate

 

Forget Gate는 이전 단계에서의 기억 셀 Ct-1에서 불필요한 기억을 잊게 해주는 게이트입니다. Forget Gate에서 Ht-1과 Xt를 연산한 것을 Ct-1에 곱합니다. 이를 통해 Ct-1에서 얼만큼의 양을 지워서 Ct로 연산할지 결정합니다.

 

계산식은 [식 3]와 같습니다. [식 4]는 Forget Gate 결과에 Ct-1를 곱해 기억 셀 Ct로 생성하는 과정입니다.

 

[식 3] Forget Gate
[식 5] 기억 셀 Ct를 구하는 방식

 

3) g : 기억 셀에 추가하는 새로운 기억

 

새로 기억해야 할 정보를 기억셀에 추가하기 위해 [그림 2]에서와 같이 tanh 노드를 추가합니다. [그림 2]에서 tanh노드와 이전 단계의 기억 셀 Ct-1을 더하면서 기억 셀에 새로운 '정보'가 추가됩니다.

 

계산식은 [식 5]와 같습니다. 이 g가 Ct-1에 더해짐으로써 새로운 기억이 생깁니다.

 

[식 5] 기억 셀에 추가하는 새로운 기억 g 계산

 

4) i  : Input Gate

 

Input Gate는 g의 각 원소가 새로 추가되는 기억으로써의 가치가 얼마나 큰지를 판단합니다. 새 기억 g를 모두 수용하지 않고, 적절히 부분적으로 선택하는 것이 이 게이트의 목표입니다.

 

계산식은 [식 6]과 같습니다.

 

[식 6] Input Gate&amp;amp;amp;amp;amp;amp;nbsp;

 

앞에서 구한 i, g, f를 이용해서 이전 단계에서의 기억 셀 Ct-1을 Ct로 변환하는 과정은 [식 7]과 같습니다.

 

[식 7] 기억 셀 Ct를 구하는 방식

 

[그림 2] LSTM 구조

 

 

정리하자면, [그림 2]에서 LSTM은 RNN과 다르게 필요한 정보(기억)를 선별해서 저장하는 과정을 거칩니다.

, 이전 단계에서의 기억셀 Ct-1에 f를 적용해 일부 기억을 잊고, g와 i의 곱을 더해 새로운 기억을 추가합니다. 이후 tanh()을 씌우고 o를 적용해 어느 정도의 데이터를 Hidden State Ht로 출력할지 결정합니다.  

 


 

3. LSTM 구현

 

[식 8]은 LSTM에서 수행하는 계산입니다. [식 8]의 위에서 네 개의 수식은 XWx+hWh+b 형태의 아핀 변환입니다. 이것을 하나의 식으로 정리해 계산할 수 있습니다. 그 과정은 [그림 3]과 같습니다. 

 

[식 8] LSTM 수식

 

[그림 3] 각 식의 가중치들을 모아 4개의 식을 단 한번의 아핀 변환으로 계산

 

[그림 3]은 각 식의 가중치들을 모아 4개의 식을 단 한번의 아핀 변환으로 계산해 A 행렬을 구합니다. 이후 Slice를 이용해 활성화 함수를 적용해서 f, g, i, o 를 생성합니다. 이와 같이 계산하면 원래 개별적으로 총 4번을 수행하던 아핀 변환을 1번의 계산으로 끝마칠 수 있습니다. 이것을 반영해 LSTM을 표현하면 [그림 4]와 같습니다.

 

[그림 4] 4개분의 가중치를 모아 아핀 변환을 수행하는 LSTM의 계산 그래프

 

[그림 4]에서와 같이 처음 4개의 아핀 변환을 한 번에 수행합니다. 그리고 Slice 노드를 통해 4개의 결과를 꺼냅니다. Slice는 아핀 변환의 결과를 균등하게 네 조각으로 나눠서 꺼내주는 노드입니다. Slice 다음에는 활성화 함수를 거쳐 계산을 수행해 기억 셀 Ct, Hidden State Ht를 구합니다.  

 

4. LSTM 구현[파이썬]

 

코드는 '밑바닥부터 시작하는 딥러닝2'의 'Common/time_layers.py' 에 있습니다.

https://github.com/oreilly-japan/deep-learning-from-scratch-2/blob/master/common/time_layers.py

 

*그림은 밑바닥부터 시작하는 딥러닝2를 참고했습니다*

http://www.yes24.com/Product/Goods/72173703

 

밑바닥부터 시작하는 딥러닝 2 - YES24

직접 구현하면서 배우는 본격 딥러닝 입문서 이번에는 순환 신경망과 자연어 처리다! 이 책은 『밑바닥부터 시작하는 딥러닝』에서 다루지 못했던 순환 신경망(RNN)을 자연어 처리와 시계열 데

www.yes24.com

[목차]

1. 정의

2. 알고리즘

   2.1 순전파

   2.2 역전파

3. 기울기 소실 문제

 

1. 정의

 

RNN(Recurrent Neural Network)로, 은닉층에서 나온 결과값이 다시 은닉층으로 돌아가 새로운 입력값과 연산을 수행하는 순환구조입니다. 완전연결층(Fully Connected Layer), CNN(Convolutional Neural Network)은 은닉층에서 나온 결과값이 출력층 방향으로 이동하지만, RNN은 은닉층으로 되돌아가 순환한다는 점에서 큰 차이가 있습니다. 

 

RNN은 [그림 1]과 같은 순환구조를 형성합니다.

 

[그림 1] RNN의 순환구조

 

[그림 1]은 RNN 순환구조를 펼친 형태로 입력값{x0, x1,..., xt}를 순차적으로 입력했을 때 각 입력에 대응해 {h0,...,ht}가 출력됩니다. 이때 RNN계층은 모두 같은 계층이고 출력{h0,..,hk,.,ht}를 각각 k 시점에서의 은닉 상태라고 합니다. 

 

이러한 RNN의 구조는 시계열 데이터, 텍스트 데이터에 적합합니다.

 

가령 'You say goodbye and I say hello'에서 각 단어의 다음에 나올 단어를 예측하고자 할때, 문장에서 임베딩 된 단어를 순차적으로 RNN 에 입력합니다.

 

이후 각 입력에 대응하는 은닉상태로 다음에 나올 단어를 예측합니다. 여기서 은닉상태는 해당 시점까지의 단어 정보를 나타냅니다.

 

순전파와 역전파를 반복하며 모형을 학습시켜 최종적으로 각 단어 다음에 나올 단어를 완벽하게 예측하는 은닉상태를 갖는 것이 RNN의 목적 중 하나입니다. 

 

[그림 2]는 'You say goodbye and I say hello'에서 각 단어 다음에 나올 단어를 예측하며 학습시킨 RNN 언어모델입니다. 

 

[그림 2] RNN의 언어 모델

 

[그림 2]에서 You 라는 단어가 Embedding 계층을 거친 후 RNN에 입력되면, you의 은닉상태 h_you를 출력하고 다음에 올 단어

 

say를 예측합니다. 은닉상태 h_you다음 RNN 계층으로 들어가 say라는 단어가 임베딩된 형태와 연산합니다. 여기서 h_you는 과거의 정보 'you'를 응집한 은닉 상태입니다.

 

다음 RNN 계층에서 나온 say의 은닉상태 h_say는 과거의 정보 'You say'를 응집한 은닉 상태입니다. 이후 h_say를 이용해 다음에 올 단어 goodbye를 예측합니다.

 

이 과정이 RNN 학습, 순전파 과정입니다. 이후 역전파 과정을 거치며 예측 단어와 실제 단어를 비교하면서 가중치와 은닉상태를 업그레이드합니다.

 

2. 알고리즘

   2.1 순전파

 

[그림 3] RNN 순전파 과정

 

[그림3]은 RNN의 순전파 과정입니다. ('You say goodbye and I say hello'에서 다음에 나올 단어를 예측하며 RNN 계층을 학습하는 과정입니다.) 

 

1) say의 은닉상태인 h_sayhello가 임베딩된 벡터(x_hello)RNN계층에 들어간 후 일련의 과정을 통해 hello의 은닉상태인 h_helllo y_hello를 출력합니다.

2) 출력된 은닉상태는 다음 RNN 계층의 입력으로 이동합니다.

3) h_hellohello를 비롯해 say, I 와 같이 그동안 이동해온 단어들의 정보를 갖고있습니다.

4) y_helloh_hello가 변형된 형태로, hello의 다음 단어를 예측한 결과입니다.

 

여기서 일련의 과정은 [그림4]의 식으로, 모형으로 나타내면 [그림3]의 오른쪽 직사각형입니다.

 

[그림 4] RNN 순전파 수식

1) 가중치와 편향인 Wh, Wx, bRNN계층별로 동일합니다.

2) [그림 4]를 보면 h_hello를 구하기 위해 h_sayWh를 곱하고 hello를 임베딩한 단어벡터(x_hello)Wx를 곱한 후 편향(b)을 더했습니다.

3) 이후 하이퍼볼릭탄젠트 함수(tanh)를 씌웠습니다. 

 

   2.2 역전파

 

[그림 5] RNN의 역전파

1) RNN에서 오차역전파는 RNN 계층에서 출력된 결과와 Y값의 차이를 반영해 가중치와 은닉상태를 업그레이드하는 과정입니다.

2) 예를 들면, [그림5]에서 Softmax 이후 나오는 단어들의 예측 확률과 실제값의 오차를 Cross Entropy로 구합니다.

3) 이후 오차를 미분해 기울기{dh_hello, dh_row, dh_say, dw_x, dx_hello, dw_h}를 구하고 각 가중치에 반영해 업그레이드하는 식으로 오차역전파가 진행됩니다.

4) 오차를 미분하는 과정에서 y에 대해 미분하는 과정이 필요합니다.

5) y에 대해 미분하는 식을 나타내면 [그림6]과 같습니다.

 

[그림 6] RNN 역전파 수식

 

3. 기울기 소실 문제

 

1) RNN에서 역전파가 진행되면서 기울기가 소실되는 문제가 발생합니다.

2) 우선 오차역전파를 통해 은닉상태, 가중치, 편향{h, W, b}은 기울기/미분값이 반영되며 업그레이드 됩니다.

3) RNN은 기울기/미분값을 구하는 과정이 [그림 7], [그림 8]과 같이 복잡합니다.

4) 만약 RNN 계층이 많다면, 오차역전파가 진행될수록 기울기가 소실되는 문제가 발생합니다.

 

수식으로 증명하면서 기울기가 어떻게 소실되는지 보여드리겠습니다.

 

[그림 7] 기울기 전개식 (1)

 

1) [그림 7]은 h_roww_h로 미분하는 과정입니다.

2) Say의 은닉상태인 h_say는 이전 단계의 RNN계층인 you, say, goodbye, and, I 의 영향을 받습니다.

3) 따라서 h_roww_h로 미분하려면 h_you, h_say, h_goodbye, h_and, h_I를 모두 반영해야 됩니다.

4) [그림 7]을 정리하면 [그림 8]과 같습니다.

 

[그림 8] 기울기 전개식 (2)

 

1) [그림 8]에서 h_roww_h로 미분하면 가중치, 은닉상태를 계속 곱하고 더하는 패턴이 보이는 것을 알 수 있습니다.

2) 만약 RNN 계층이 여러 개라면, 오차역전파 과정이 진행되며 기울기가 곱하고 더하는 과정이 반복될 것입니다.

3) 여기에서 기울기가 감소하는 문제가 발생합니다.

4) 그렇게 되면 오차역전파 과정이 꽤 진행되었을 때 업데이트할 기울기가 아주 작은 문제가 발생할 수 있습니다.

 

이것을 기울기 소실이라고 하며, 이를 해결하기 위해 LSTM을 이용합니다.

 

LSTM은 다음 시간에....

 

참고자료: https://ratsgo.github.io/natural%20language%20processing/2017/03/09/rnnlstm/

 

RNN과 LSTM을 이해해보자! · ratsgo's blog

이번 포스팅에서는 Recurrent Neural Networks(RNN)과 RNN의 일종인 Long Short-Term Memory models(LSTM)에 대해 알아보도록 하겠습니다. 우선 두 알고리즘의 개요를 간략히 언급한 뒤 foward, backward compute pass를 천천

ratsgo.github.io

 

오늘은 저의 학위논문 CODE를 올려봅니다.

논문 분야는 텍스트 마이닝으로, 크게 감성분석, 토픽모델링, 대응분석으로 구성됩니다.

대응분석은 SAS로 수행했고 감성분석, 토픽모델링은 Google Colab 환경에서 Python 3.0으로 진행했습니다.

 

당시 학위논문을 쓰면서 텍스트 마이닝에 흥미를 더욱 가지며 졸업 후, Word Embedding과 Deep Learning으로 공부 영역을 확장했습니다. 현재도, 텍스트 마이닝, NLP 는 가장 관심있는 분야입니다.

 

그럼, 감성분석 CODE과 토픽모델링 CODE를 올려보도록 하겠습니다.

 

목차

1. 필요한 패키지 설치

2. 감성 사전 불러오기(감성사전은 분석하는 텍스트가 '뉴스 기사'인 점을 고려해 Bing 감성사전을 번역해 활용했습니다)

3. 감성 사전 품사 추출하기

4. 방역 기사 불러오기

5. 방역 기사 품사 추출하기

6. 기사에 감성을 적용해, 감성 점수 구하기

6-1. 방역 기사 속 품사들과 사전 속 품사를 매치하여 감성 점수를 구하기

6-2. 기사별로 감성 점수를 합치기

 

 

1. 필요한 패키지 설치

!apt-get update 
!apt-get install g++ openjdk-8-jdk python-dev python3-dev 
!pip3 install JPype1-py3 
!pip3 install konlpy 
!JAVA_HOME="C:\Users\tyumi\Downloads"

 

2. 감성 사전 불러오기

from google.colab import files
myfile = files.upload()
import io
import pandas as pd
dict_0 = pd.read_excel(io.BytesIO(myfile['Bing 번역 최종.xlsx']))
dict_0.head()

감성사전 예시

 

3. 감성 사전 품사 추출하기

from konlpy.tag import * # class  
komoran=Komoran()
index=[]
words=[]
score=[]
text_1=[]
for j in range(0,len(dict_0)):
    ex_pos=komoran.pos(dict_0.iloc[j,0])
    for (text, tclass) in ex_pos : # ('형태소', 'NNG') 
        if tclass == 'VV' or tclass == 'VA' or tclass == 'NNG' or tclass=='XPN' or tclass == 'NNP'or tclass=='VX' or tclass == 'VCP' or tclass == 'VCN' or tclass == 'MAG' or tclass == 'XR': 
            index+=[j]
            words+=[dict_0.iloc[j,0]]
            score+=[dict_0.iloc[j,1]]
            text_1.append(text)
dict_1=pd.DataFrame({'Index':index,'Text':text_1,'Words':words,'Score':score})

품사 추출한 감성사전 예시

 

4. 방역 기사 불러오기

from google.colab import files
myfile = files.upload()
import io
import pandas as pd
article_0 = pd.read_excel(io.BytesIO(myfile['방역 기사 추출.xlsx']))
article_0.head()

방역 기사 예시

 

5. 방역 기사 품사 추출하기

import re
index=[]
paper=[]
text_1=[]
text_2=[]
title=[]
for i in range(0,len(article_0)):
  article_0.iloc[i,0]=re.sub('\n|\r','',article_0.iloc[i,0]) #문장에서 필요없는 문구 삭제
  ex_pos=komoran.pos(article_0.iloc[i,0]) #komoran 품사 태그 적용
  for (text, tclass) in ex_pos : # ('형태소', 'NNG') 
      if tclass == 'VV' or tclass == 'VA' or tclass == 'NNG' or tclass == 'NNP'or tclass=='XPN' or tclass=='VX' or tclass == 'VCP' or tclass == 'VCN' or tclass == 'MAG' or tclass == 'XR': 
          index+=[i]
          paper+=[article_0.iloc[i,3]]
          text_1+=[article_0.iloc[i,0]]
          text_2.append(text)
          title+=[article_0.iloc[i,4]]
article_1=pd.DataFrame({'Index':index,'Text':text_2,'Article':text_1,'Paper':paper,'Title':title})

품사 추출된 방역 기사 예시

 

6. 기사에 감성을 적용해, 감성 점수 구하기

6-1. 방역 기사 속 품사들과 사전 속 품사를 매치하여 감성 점수를 구하기

article_1.loc[:,'Score']=[0]*len(article_1)
dict_list=list(dict_1.loc[:,'Text'])
article_list=list(article_1.loc[:,'Text'])
for i in range(len(dict_list)):
  equal_list = list(filter(lambda x: (article_list[x]==dict_list[i]) and (len(article_list[x])>=2), range(len(article_list))))
  for j in equal_list:
    article_1.loc[j,'Score']=dict_1.loc[i,'Score']

품사별 감성 점수 예시

 

6-2. 기사별로 감성 점수를 합치기

article_0['Score']=article_1['Score'].groupby(article_1['Index']).sum()
article_0

기사별 감성 점수 예시

 

하나의 노드로부터 시작해 차례대로 모든 노드들을 한 번씩 방문하는 그래프 탐색에는 크게 깊이 우선 탐색(DFS)과 너비 우선 탐색(BFS)가 있습니다. 

 

오늘은 DFS에 대해서 알아보겠습니다.

DFS는 루트 노드나 임의의 노드에서 시작하여 최대로 진입할 수 있는 깊이까지 탐색하고 다시 돌아와 다른 노드로 같은 방식으로 탐색합니다.

 

이 그림을 통해 DFS의 원리를 알아보겠습니다. 노드 1에서 시작해서 차례대로 노드 7까지 순회합니다. 진행되는 방향을 보면 한 가지의 끝까지 탐색하고, 그 다음 가지를 순차적으로 탐색합니다. 전체적으로 위에서 아래로 깊게 탐색이 진행되는 것을 볼 수 있습니다. 

 

그림을 자세히 설명하자면 1부터 시작해서 막다른 곳에 다다를 때까지 연속으로 진행되는 탐색이 총 3번에 걸쳐 진행됩니다.

1->2->5->6까지 진행하고 그 다음 5로 되돌아갔다가 다음번 탐색은 7->3, 다시 되돌아 나가 마지막으로 시작 지점까지 올라가서 4를 탐색하고 종료합니다. 즉, 최종 결과는 1->2->5->6->7->3->4 입니다.

 

예를 들면, 미로에서 한 루트로 계속 가다가 막다른 길이면 바로 직전 갈림길로 돌아가 길을 찾는 것이라고 할 수 있습니다. DFS는 전체 정점을 맹목적으로 탐색할 때 사용합니다. DFS의 구현은 재귀 구조, 스택을 이용합니다.

 

 

1. DFS를 재귀 구조로 구현하는 방법을 알아보겠습니다.

 

Python CODE

graph_list = {1: set([2, 3, 4]),
              2: set([5]),
              3: set([5]),
              4: set([]),
              5: set([6, 7]),
              6: set([]),
              7: set([3])}

root_node = 1

def recursive_dfs(v,discovered=[]):
  discovered.append(v)
  for w in graph_list[v]:
    if w not in discovered:
      discovered=recursive_dfs(w,discovered)
  return discovered
recursive_dfs(1)

graph_list는 앞의 그래프를 딕셔너리로 나타낸 것입니다.

 

재귀 구조로 구현하는 과정은 아래와 같습니다.(CODE 해석)

 

1) 시작 노드를 방문한 후, 방문 기록(discovered)에 삽입합니다.

2) 시작 노드와 인접한 노드를 w로 지정합니다.

 

3) 재귀함수 recursive_dfs()에 w와 discovered를 삽입합니다.[이 때, w가 여러 개인 경우 가장 작은 w를 넣습니다]

 

    재귀함수에서 일어나는 과정은 아래와 같습니다.

 

    3-1) w 노드를 방문한 후, discovered에 삽입합니다.

    3-2) w 노드와 인접한 노드들 중 discovered에 없고(방문한 적이 없고) 가장 작은 노드를 선택합니다.

    3-3) 이것을 새로운 w노드로 지정합니다.

    3-4) 재귀함수 recursive_dfs()에 w와 discovered를 삽입합니다.

 

4) 모든 노드를 방문할 때 까지 3-1~3-4 과정을 반복합니다.

 

    4-1) 이 때, 중간에 끊기는 경우가 있습니다. 바로 3-2)에서 w노드와 인접한 노드가 없거나 모두 방문한 경우입니다.

   4-2) 이런 경우 직전 w노드로 돌아가서 해당 w노드와 인접하면서 방문한 적이 없는 노드를 새로 방문하고 3-1에서 다시 시작합니다.

           [예를 들면, 앞의 그림에서 1->2->5->6 에서 끊김을 알 수 있습니다. 이때 5 노드로 다시 돌아가서 방문한 적이 없는 7을

           새로 방문하는 것입니다. 이후 7은 w노드가 되고 discovered에 삽입됩니다.]

 

5) 완성한 discovered가 DFS로 구한 경로입니다.

 

 

2. DFS를 Stack으로 구현하는 방법을 알아보겠습니다.

 

Python CODE

graph_list = {1: set([2, 3, 4]),
              2: set([5]),
              3: set([5]),
              4: set([]),
              5: set([6, 7]),
              6: set([]),
              7: set([3])}

root_node = 1

def iterative_dfs(start_v):
  discovered=[]
  stack=[start_v]
  while stack:
    v=stack.pop()
    if v not in discovered:
      discovered.append(v)
      for w in graph_list[v]:
        stack.append(w)
  return discovered
iterative_dfs(1)

 

Stack으로 구현하는 과정은 아래와 같습니다.(CODE 해석)

 

1) 시작 노드를 방문한 후, Stack에 삽입합니다.

 

2) 가장 최근에 Stack에 삽입된 원소를 꺼내서 v로 지정합니다.

 

3) v가 한번도 방문한 적이 없는 노드인 경우 방문기록(discovered)에 삽입합니다.

 

     3-1) v가 방문한 적이 있는 노드인 경우 2 과정으로 돌아가서 계산합니다.

 

4) v와 인접한 노드들을 차례로 Stack에 삽입합니다. 

 

5) 2~4 과정을 Stack이 비어있을 때까지 반복합니다.

 

 

참고

https://velog.io/@sohi_5/algorithmDFS

 

[자료구조와 알고리즘] DFS (깊이 우선 탐색)

그래프 탐색 중 깊이 우선 탐색의 개념과 과정 설명 및 문제 적용

velog.io

 

http://www.kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9791189909178 

 

파이썬 알고리즘 인터뷰 - 교보문고

95가지 알고리즘 문제 풀이로 완성하는 코딩 테스트 | [이 책의 구성] [1부 코딩 인터뷰] 1장, ‘코딩 인터뷰’에서는 코딩 테스트에 대한 소개와 어떻게 하면 시험을 잘 치를 수 있을지, 문제 풀이

www.kyobobook.co.kr

 

그래프의 각 정점을 방문하는 그래프 탐색에는 크게 깊이 우선 탐색(DFS)과 너비 우선 탐색(BFS)가 있습니다. 

 

오늘은 BFS에 대해서 알아보겠습니다.

 

BFS는 임의의 정점에서 시작해서 인접한 정점을 먼저 탐색하는 방법으로, 시작 정점으로부터 가까운 정점을 먼저 방문하고 멀리 떨어져 있는 정점을 나중에 방문하는 순회 방법입니다.

 

두 정점 사이의 최단 경로 혹은 임의의 경로를 찾고 싶을 때 이 방법을 선택합니다.

 

BFS의 특징은 어떤 정점을 방문했었는지 여부를 반드시 검사 해야 하며 이를 지키지 않을 경우 무한루프에 빠질 위험이 있습니다. 따라서 방문한 정점들을 차례로 저장한 후 꺼낼 수 있는 자료 구조인 큐(Queue)를 사용합니다.

BFS 알고리즘을 먼저 보여드린 후, Python으로 구현한 코드를 제시하겠습니다.

 

1. BFS 알고리즘 - 순서

 

  1. 시작 정점을 방문한 후 방문된 정점을 큐에 삽입합니다.(방문한 정점 체크)
  2. 시작 정점의 이웃 정점을 모두 방문할때까지 방문된 정점을 큐에 삽입하는 과정을 반복합니다.
  3. 큐에서 꺼낸 정점을 방문합니다.
  4. 큐에서 꺼낸 정점과 인접한 정점들을 모두 방문합니다.
  5. 이 때, 인접한 정점 중 새로 방문되는 점이 있다면, 큐에 삽입합니다.
  6. 만약, 인접한 정점 중 새로 방문되는 점이 없다면, 큐에 삽입하는 과정 없이 [과정 3]으로 돌아갑니다.
  7. 큐가 빈 공간이 될 때까지 [과정 3~6] 을 반복합니다.

 

2. BFS 알고리즘 - 예시

 

우선 그래프는 [그림 1]과 같습니다.

왼쪽은 그래프 그림, 오른쪽은 그래프 수식입니다. (수식에서 1: set([3,4])는 1은 3과 4를 가리킴을 의미합니다)

 

[그림 1]

아래는 BFS가 진행되는 과정입니다.

과정은 (1)부터 (6)으로 구성됩니다. 초록색 동그라미는 현재 머무는 정점, 파란색 동그라미는 이미 방문한 정점을 의미합니다.

(1) 먼저, 시작점을 정합니다. 저희는 1로 정했습니다. 시작점 1을 큐와 방문기록에 삽입합니다.

(2) 1을 큐에서 뺍니다. 이후 1에서 인접한 접점 3과 4에 방문합니다. 방문한 점을 큐와 방문기록에 삽입합니다.

(3) 3을 큐에서 뺍니다. 이후 3에서 인접한 접점 1과 5에 방문합니다.

    하지만 1은 이미 방문한 점이기 때문에 넘어가고, 5에 방문합니다. 5를 큐와 방문기록에 삽입합니다.

(4) 4를 큐에서 뺍니다. 이후 4에서 인접한 접점 1에 방문합니다.

      하지만 1은 이미 방문한 점이기 때문에 통과하고 다음 단계인 (5)로 넘어갑니다.

(5) 5를 큐에서 뺍니다. 이후 5에서 인접한 접점 2와 6에 방문합니다. 2와 6을 큐와 방문기록에 삽입합니다.

(6) 방문기록은 최단이동경로와도 같습니다. 완성했습니다.

 

3. BFS 알고리즘 - CODE

 

앞의 예시를 파이썬으로 구현하면 아래와 같습니다.

graph_list = {1: set([3, 4]),
              2: set([3, 4, 5]),
              3: set([1, 5]),
              4: set([1]),
              5: set([2, 6]),
              6: set([3, 5])}
root_node = 1

from collections import deque

def iterative_bfs(graph, start_v):
    discovered=[start_v]
    queue = [start_v]
    while queue:
        v = queue.pop(0)
        for w in graph[v]:
          if w not in discovered:
            discovered.append(w)
            queue.append(w)
    return discovered
  
print(iterative_bfs(graph_list, root_node))

출처

https://gmlwjd9405.github.io/2018/08/15/algorithm-bfs.html

 

[알고리즘] 너비 우선 탐색(BFS)이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

이 게시글은 '밑바닥부터 시작하는 딥러닝2'을 공부한 내용 바탕으로 정리했습니다.

http://www.yes24.com/Product/Goods/72173703

 

밑바닥부터 시작하는 딥러닝 2 - YES24

직접 구현하면서 배우는 본격 딥러닝 입문서 이번에는 순환 신경망과 자연어 처리다! 이 책은 『밑바닥부터 시작하는 딥러닝』에서 다루지 못했던 순환 신경망(RNN)을 자연어 처리와 시계열 데

www.yes24.com

RNN을 본격적으로 설명하기 전에 언어모델로서 RNN이 왜 필요한지 설명드리겠습니다.

 

1과 2에서 언어모델을 설명한 후, 3에서 언어모델로 적합한 기법이 RNN임을 강조하며 필요성을 설명드리겠습니다.

언어모델을 이미 아는 분이시면 3으로 바로 넘어가주세요 :)

 

순서는 아래와 같습니다.

 

 

1. 언어모델이란?

2. 수식으로 표현한 언어모델

3. 언어모델 관점, RNN의 필요성

 

 

1. 언어모델이란?

 

 

언어모델은 문장에 대해서, 그 문장이 얼마나 자연스러운지를 확률로 평가하는 것입니다.

예를 들면, '나는 학교에 간다'라는 문장에는 높은 확률을 출력하고 '나는 학교에 갈매기'에는 낮은 확률을 출력하는 것이 일종의 언어모델입니다.

 

언어모델은 기계번역과 음성인식에 대표적으로 이용됩니다. 하지만 새로운 문장을 생성하는 용도로도 사용할 수 있습니다. 왜냐하면 언어모델은 단어 순서가 얼마나 자연스러운지를 확률적으로 평가할 수 있기 때문입니다. 따라서 해당 확률분포에 따라 다음 순서에 올 적합한 단어를 샘플링할 수 있습니다.

 

이제 언어모델을 수식으로 설명하겠습니다.

 

 

2. 수식으로 표현한 언어모델

 

 

w1,...,wm이라는 m개의 단어로 된 문장이 있을 때, 단어가 w1,...,wm순서로 등장할 확률을 P(w1,...,wm)이라고 합니다. 

이 확률은 동시확률이라고도 합니다. 우리는 이 확률을 구해서 문장의 자연스러운 정도를 계산합니다.

 

동시확률 P(w1,...,wm)은 사후 확률을 사용해 [식 1]과 같이 분해하여 쓸 수 있습니다. [식 1]의 사후확률은 타깃 단어보다 왼쪽에 있는 모든 단어를 조건으로 했을 때의 확률입니다. 이러한 사후확률을 총곱해서 나온 것이 언어모델의 동시확률입니다.

[식 1]

 

 

3. 언어모델 관점, RNN의 필요성

 

 

Word2Vec의 CBOW 모델을 언어 모델에 적용했을 때 나타나는 문제점(단어의 순서를 반영하지 않는 점)과 신경 확률론적 언어모델(NPLM)에서 제안한 모델의 문제점(단어가 늘어날수록 가중치가 증가하는 점)을 해결하기 위해 등장한 것이 RNN 언어모델입니다.

 

 

1) CBOW 모델을 이용한 언어모델

 

 

CBOW 모델을 억지로 언어모델에 적용하려면 맥락크기를 특정 값으로 한정하여 근사적으로 나타냅니다. [식 2]는 맥락을 왼쪽 2개의 단어로 한정합니다. 

[식 2]

맥락 크기를 특정 값으로 한정하여 나타내면, 맥락 바깥의 정보는 반영되지 않습니다.

 

그리고 문제점이 더 있습니다. 바로 CBOW 모델에서는 맥락 안의 단어 순서가 무시된다는 점입니다.

 

만약 맥락크기가 2일때,  'I say goodbye, you say hello' 문장에서 'you' 예측에는 어떻게 CBOW 모델을 사용할까요? 맥락인 'say, goodbye'를 이용하고 [그림 1]과 같습니다. 

[그림 1]

[그림 1]에서 모델의 은닉층에서는 'say'와 'goodbye' 단어 벡터들이 더해지므로 맥락의 단어 순서는 무시됩니다. 

 

 

2) NPLM(Neural Probabilistic Language Model) 에서 제안한 언어모델

 

 

[그림 2]

맥락의 단어 벡터 순서를 고려하기 위해 [그림 2]와 같이 은닉층에서 단어 벡터를 연결하는 방식이 있습니다.  하지만 이 방식을 취하면, 맥락크기에 비례해서 가중치가 늘어나는 문제점이 생깁니다.

 

 

3) RNN의 필요성

 

 

RNN은 맥락이 아무리 길어도 그 맥락의 정보를 기억하는 구조로, 순서가 있는 시계열 데이터에도 유연하게 대응할 수 있습니다. 따라서 주어진 문장의 정보를 습득하며 학습한 후, 다음에 올 단어를 예측할 수 있습니다. 

오늘은 문제를 주어진 시간 안에 다 풀지 못하고 다른 분들이 올려주신 코드를 리뷰하게 되었습니다.

코드에서 사용되는 함수가 유용하고 알고리즘이 간단하다는 점에서 향후 문제를 풀 때 도움이 될 것 같습니다.

그래서 기록용으로 게시글을 올렸습니다.

문제는 아래와 같습니다.

https://programmers.co.kr/learn/courses/30/lessons/43165

 

코딩테스트 연습 - 타겟 넘버

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+1+1+1 = 3 +1-1+1+1+

programmers.co.kr

 

코드는 다음과 같습니다.

출처: https://programmers.co.kr/learn/courses/30/lessons/43165/solution_groups?language=python3 

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

from itertools import product
def solution(numbers, target):
    l = [(x, -x) for x in numbers]
    s=list(map(sum,product(*l)))
    return s.count(target)
solution([1,1,1,1,1],3)

 

 

풀이과정

설명을 위해 중간에 print 함수를 추가했습니다.

from itertools import product
def solution(numbers, target):
    l = [(x, -x) for x in numbers]
    s = list(product(*l))
    ss=list(map(sum,product(*l)))
    print(l)
    print(s)
    print(ss)
    return ss.count(target)
solution([1,1,1,1,1],3)

 [1,1,1,1,1]을 numbers로, 3을 target으로 할 때, solution 함수의 print(l), print(s), print(ss), 결과값은 아래와 같습니다.

  • print(l)을 통해 numbers의 각 숫자와 마이너스 형태를 취한 숫자를 튜플 형태로 나타냈습니다.
  • list(product(*l))을 통해 (1,1,1,1,1) 부터 (-1,-1,-1,-1,-1) 까지 1과 -1로 구성할 수 있는 배열을 튜플 형태로 나타냈습니다.
  • 마지막으로 map과 sum 함수를 이용해 앞에서 구한 배열을 합쳤습니다. 예를 들면, (1,1,1,1,1)은 5가 되는 것입니다.
  • 결과값으로 ss에서 target인 개수를 반환했습니다.

+ Recent posts