Skip to content

Latest commit

 

History

History
287 lines (245 loc) · 10.1 KB

220119.md

File metadata and controls

287 lines (245 loc) · 10.1 KB

Day 100

파이썬으로 챗봇 만들기

Chapter 2

챗봇 개발에 유용한 자연어 처리 기능들

토크화(Tokenization)

토큰화는 텍스트를 의미 있는 부분으로 나누는 NLP의 기본적인 개념 중 하나이다.

# 예제
doc = nlp('Brexit is the impending withdrawl of the U.K. from the European Union')
for token in doc:
    print(token.text)
**수행결과**
Brexit
is
the
impending
withdrawl
of
the
U.K.
from
the
European
Union

spaCy의 토큰화 결과가 만족스럽지 않다면, add_special_case 메소드를 이용해서 나만의 규칙을 추가할 수 있다.

정규 표현식(Regular expressions)

정규 표현식은 패턴 매칭이 가능하므로 처리하고 있는 데이터가 정확하거나 부정확하다는 것을 확인할 수 있다.

# 예제
import re

sentence1 = 'Book me a metro from Airport Station to Hong Kong Station.'
sentence2 = 'Book me a cab from Hong Kong Airport to AsiaWorld-Expo.'

from_to = re.compile('.* from (.*) to (.*)')
to_from = re.compile('.* to (.*) from (.*)')

from_to_match = from_to.match(sentence2)
to_from_match = to_from.match(sentence2)

if from_to_match and from_to_match.groups():
    _from = from_to_match.groups()[0]
    _to = from_to_match.groups()[1]
    print('from_to pattern matched correctly. Printing values')
    print('From : {}, To : {}'.format(_from, _to))
    
elif to_from_match and to_from_match.groups():
    _to = to_from_match.groups()[0]
    _from = to_from_match.groups()[1]
    print('to_from pattern matched correctly. Printing values')
    print('From : {}, To : {}'.format(_from, _to))
**수행결과**
from_to pattern matched correctly. Printing values
From : Hong Kong Airport, To : AsiaWorld-Expo.

정규 표현식 추가 정리

정규 표현식에서 사용하는 메타 문자

. ^ $ * + ? { } [ ] \ | ( )

문자 클래스 [ ]

[abc]

문자 클래스로 만들어진 정규식은 "[ ] 사이의 문자들과 매치"라는 의미를 갖는다.
예를 들어 [abc]라면 a, b, c 중에 한 개의 문자와 매치를 의미한다. a, before, delete와 매치하면 a는 문자 a가 있으므로 매치, before은 문자 b가 있으므로 매치, delete는 a, b, c 중 어느 하나도 포함하고 있지 않으므로 매치되지 않는다.

[ ] 안의 두 문자 사이에 "-"를 사용하면 두 문자 사이의 범위를 의미한다. [a-c]는 [abc]와 같은 의미를 갖는다.

문자 클래스 안에 ^ 메타 문자를 사용할 경우 not의 의미를 가져 [^0-9]의 경우 숫자가 아닌 문자만 매치되게된다.

별도의 표기법이 몇 가지 존재한다.

  • \d : 숫자와 매치, [0-9]와 동일
  • \D : 숫자가 아닌 것과 매치 [^0-9]와 동일
  • \s : whitespace 문자와 매치, [ \t\n\r\f\v]와 동일
  • \S : whitespace 문자가 아닌 것과 매치
  • \w : 문자 + 숫자와 매치, [a-zA-Z0-9_]와 동일
  • \W : 문자 + 숫자가 아닌 문자와 매치

Dot (.)

a.b

줄바꿈 문자인 \n을 제외한 모든 문자와 매치됨을 의미한다. a.b는 a와 b 사이에 어떤 문자가 들어가도 모두 매치된다는 의미로, aab, a0b는 매치되고, abc는 a와 b 사이에 문자가 없으므로 매치되지 않는다.

a[.]b

[.]와 같이 문자 클래스 내에 Dot 메타 문자가 사용된다면 이것은 문자 .을 의미한다.

반복 (*)

ca*t

*바로 앞에 있는 문자 a가 0부터 무한대로 반복될 수 있다는 의미이다.

ct, cat, caaat 모두 a가 0번 이상 반복되어 매치된다.

반복 (+)

ca+t

+앞의 문자가 최소 1번 이상 반복될 때 사용한다.

ct는 a가 0번 반복되어 매치되지 않지만, cat, caat는 매치된다.

반복 ({m,n}, ?)
반복 횟수가 m부터 n까지 매치된다. 생략된 m은 0과 동일하며, 생략된 n은 무한대의 의미를 갖는다.

ca{2}t

위의 정규식은 "c + a(반드시 2번 반복) + t"의 의미를 갖는다.
cat는 a가 1번만 반복되어 매치되지 않고, caat는 a가 2번 반복되어 매치된다.

ca{2, 5}t

"c + a(2~5회 반복) + t"의 의미를 갖는다.
cat과 caaaaaat는 범위를 벗어나 매치되지 않고, caat와 caaaaat는 매치된다.

ab?c

? 메타 문자는 {0, 1}을 의미한다. 즉, "a + b(있어도 되고 없어도 된다) + c"의 의미를 갖는다.
abc와 ac 둘 다 매치가 된다.

re.compile()

import re
p = re.compile('ab*')

re.compile을 사용하여 정규 표현식을 컴파일한다.

문자열 검색
컴파일된 패턴 객체는 4가지 메소드를 제공한다.

Method 목적
match() 문자열의 처음부터 정규식과 매치되는지 조사한다.
search() 문자열 전체를 검색하여 정규식과 매치되는지 조사한다.
findall() 정규식과 매치되는 모든 문자열(substring)을 리스트로 돌려준다.
finditer() 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 돌려준다.

match

import re
p = re.compile('[a-z]+')

m = p.match('python')   # match 객체 반환
n = p.match('3 python') # None 반환

"python"은 [a-z]+에 부합되므로 match 객체를 반환하고 "3 python"은 부합되지 않아 None을 반환한다.

# 기본 형태
p = re.compile('[a-z]+')
m = p.mathc("python")

# 축약 형태
m = re.match('[a-z]+', "python")

search

m = p.search("python")      # <re.Match object; span=(0, 6), match='python'>
n = p.search("3 python")    # <re.Match object; span=(2, 8), match='python'>

"3 python"의 첫 번째 문자는 "3"이지만 search는 문자열의 처음부터 검색하는 것이 아니라 문자열 전체를 검색하기 때문에 "3" 이후의 "python" 문자열과 매치된다.

findall

result = p.findall("life is too short")
print(result) # ['life', 'is', 'too', 'short']

"life is too short" 문자열의 'life', 'is', 'too', 'short' 단어를 각각 [a-z]+ 정규식과 매치해서 리스트로 반환한다.

*finditer

result = p.finditer("life is too short")
print(result)   # <callable_iterator object at ~>
for r in result: print(r)   
# <re.Match object; span=(0, 4), match='life'>
# <re.Match object; span=(5, 7), match='is'>
# <re.Match object; span=(8, 11), match='too'>
# <re.Match object; span=(12, 17), match='short'>

finditer은 findall과 동일하지만 그 결과로 반복 가능한 객체를 돌려준다. 반복 가능한 객체가 포함하는 각각의 요소는 match 객체이다.

match 객체의 메소드

method 목적
group() 매치된 문자열을 돌려준다.
start() 매치된 문자열의 시작 위치를 돌려준다.
end() 매치된 문자열의 끝 위치를 돌려준다.
span() 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다.
m = p.match("python")
m.group()   # 'python'
m.start()   # 0
m.end()     # 6
m.span()    # (0, 6)

컴파일 옵션

  • DOTALL(S) : "."이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다.
  • IGNORECASE(I) : 대소문자에 관계없이 매치할 수 있도록 한다.
  • MULTILINE(M) : 여러줄과 매치할 수 있도록 한다. (^, $ 메타문자 사용과 관계가 있는 옵션이다.)
  • VERBOSE(X) : verbose 모드를 사용할 수 있도록 한다. (정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게 된다.)

옵션을 사용할 때 re.DOTALL처럼 전체 옵션 이름을 써도 되고, re.S처럼 약어를 써도 된다.

DOTALL, S

p = re.compile('a.b')
m = p.match('a\nb') # None

p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb') # match 객체 반환

보통 여러 줄로 이루어진 문자열에서 \n에 상관없이 검색할 때 많이 사용한다.

IGNORECASE, I

p = re.compile('[a-z]+', re.I)
p.match('python')   # 매치
p.match('Python')   # 매치
p.match('PYTHON')   # 매치

[a-z]+ 정규식은 소문자만을 의미하지만 대소문자 구별 없이 매치되는 것을 확인할 수 있다.

MULTILINE, M
메타 문자 ^, $와 연관된 옵션이다. ^는 문자열의 처음을 의미하고 $는 문자열의 마지막을 의미한다. ^python인 경우 python으로 문자열이 시작해야 매치되고, python$의 경우 문자열이 python으로 끝나야 매치된다.

p = re.compile("^python\s\w+")

data = """python one
life is too short
python two
you need python
python three"""

print(p.findall(data))

여기서 사용한 정규식은 python이라는 문자열로 시작하고 그 뒤에 whitespace, 그 뒤에 단어가 와야한다는 의미이다.
실행하면 ['python one']이 출력된다.
^ 메타 문자에 의해 python이라는 문자열을 사용한 첫 번째 줄만 매치된 것이다.
^ 메타문자를 문자열 전체의 처음이 아니라 각 라인의 처음으로 인식시키고 싶을 때 사용하는 옵션이 re.MULTILINE, re.M이다.

p = re.compile("^python\s\w+", re.MULTILINE)

data = """python one
life is too short
python two
you need python
python three"""

print(p.findall(data))

이렇게 수정하면 ['python one', 'python two', 'python three']가 출력된다. 즉, re.MULTILINE은 ^, $ 메타 문자를 문자열의 각 줄마다 적용해 주는 것이다.

VERBOSE, X

charref = re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE)

re.VERBOSE 옵션을 사용하면 문자열에 사용된 whitespace는 컴파일할 때 제거된다(단 []안에 사용한 whitespace는 제외).

백슬래시
\section이라는 문자열을 찾고 싶은데 그냥 넣으면 [ \t\n\r\f\v]ection으로 인식해버린다. 그래서 \\section으로 사용해야 하는데 컴파일할 때 \section이 전달되어 버리는데 이를 막기위해서는 문자열 앞에 r을 붙여주면 된다. 그러면 정규식을 Raw String임을 알려주는 것이 된다.

p = re.compile(r'\\section')