텍스트 데이터를 전처리하다보면, 정규 표현식은 아주 유용한 도구로서 사용된다.
1. 정규 표현식 문법과 모듈 함수
1-1) 정규 표현식 문법
정규 표현식을 위해 사용되는 문법 중 특수 문자들은 아래와 같다.
정규 표현식 문법에는 역 슬래쉬(\)를 이용하여 자주 쓰이는 문자 규칙들이 있다.
1-2) 정규 표현식 모듈 함수
정규표현식 모듈에서 지원하는 함수는 다음과 같다.
2. 정규 표현식 실습
2-1) .기호
.은 한 개의 임의의 문자를 나타낸다. 예를 들어 정규표현식이 a.c라고 하면, a와 c 사이에는 어떤 1개의 문자라도 올 수 있다.
import re
r=re.compile("a.c")
r.search("kkk") # 아무런 결과도 출력되지 않는다.
r.search("abc")
<_sre.SRE_Match object; span=(0, 3), match='abc'>
위의 코드는 search의 입력을 ㅗ들어오는 문자열에 정규표현식 패턴 a.c이 존재하는지를 확인하는 코드이다.
2-2) ?기호
?는 ?앞의 문자가 존재할 수도 있고, 존재하지 않을 수도 있는 경우를 나타낸다.
import re
r=re.compile("ab?c")
r.search("abbc") # 아무런 결과도 출력되지 않는다.
r.search("abc")
<_sre.SRE_Match object; span=(0, 3), match='abc'>
b가 있는 것으로 판단하여 abc를 매치하는 것을 볼 수 있다.
r.search("ac")
<_sre.SRE_Match object; span=(0, 2), match='ac'>
b가 없는 것으로 판단하여 ac를 매치하는 것을 볼 수 있다.
2-3) *기호
*은 바로 앞의 문자가 0개 이상일 경우를 나타낸다. 앞의 문자는 존재하지 않을 수도 있으며, 또는 여러 개일수도 있다.
import re
r=re.compile("ab*c")
r.search("a") # 아무런 결과도 출력되지 않는다.
r.search("ac")
<_sre.SRE_Match object; span=(0, 2), match='ac'>
r.search("abbbbc")
<_sre.SRE_Match object; span=(0, 6), match='abbbbc'>
2-4) +기호
+는 *와 유사하다. 하지만 다른 점은 앞의 문자가 최소 1개 이상이어야 한다는 점이다.
import re
r=re.compile("ab+c")
r.search("ac") # 아무런 결과도 출력되지 않는다.
r.search("abc")
<_sre.SRE_Match object; span=(0, 3), match='abc'>
2-5) ^기호
^는 시작되는 글자를 지정한다. 가령 정규표현식이 ^a라면 a로 시작되는 문자열만을 찾아낸다.
import re
r=re.compile("^a")
r.search("bbc") # 아무런 결과도 출력되지 않는다.
r.search("ab")
<_sre.SRE_Match object; span=(0, 1), match='a'>
2-6) {숫자}기호
문자에 해당 기호를 붙이면, 해당 문자를 숫자만큼 반복한 것을 나타낸다.
import re
r=re.compile("ab{2}c")
r.search("ac") # 아무런 결과도 출력되지 않는다.
r.search("abc") # 아무런 결과도 출력되지 않는다.
r.search("abbc")
<_sre.SRE_Match object; span=(0, 4), match='abbc'>
r.search("abbbbbc") # 아무런 결과도 출력되지 않는다.
2-7) {숫자1, 숫자2} 기호
문자에 해당 기호를 붙이면, 해당 문자를 숫자1 이상 숫자2 이하만큼 반복한다.
import re
r=re.compile("ab{2,8}c")
r.search("ac") # 아무런 결과도 출력되지 않는다.
r.search("ac") # 아무런 결과도 출력되지 않는다.
r.search("abc") # 아무런 결과도 출력되지 않는다.
r.search("abbc")
<_sre.SRE_Match object; span=(0, 4), match='abbc'>
r.search("abbbbbbbbbc") # 아무런 결과도 출력되지 않는다.
2-8) {숫자,} 기호
문자에 해당 기호를 붙이면 해당 문자를 숫자 이상만큼 반복한다.
import re
r=re.compile("a{2,}bc")
r.search("bc") # 아무런 결과도 출력되지 않는다.
r.search("aa") # 아무런 결과도 출력되지 않는다.
r.search("aabc")
<_sre.SRE_Match object; span=(0, 4), match='aabc'>
r.search("aaaaaaaabc")
<_sre.SRE_Match object; span=(0, 10), match='aaaaaaaabc'>
2-9) [ ] 기호
[ ]안에 문자들을 넣으면 그 문자들 중 한 개의 문자와 매치라는 의미를 가진다.
import re
r=re.compile("[abc]") # [abc]는 [a-c]와 같다.
r.search("zzz") # 아무런 결과도 출력되지 않는다.
r.search("a")
<_sre.SRE_Match object; span=(0, 1), match='a'>
2-10) [^문자] 기호
[^문자]는 5)에서 설명한 ^와는 완전히 다른 의미로 쓰인다. 여기서는 ^ 기호 뒤에 붙은 문자들을 제외한 모든 문자를 매치하는 역할을 한다. 예를 들어서 [^abc]라는 정규 표현식이 있다면, a 또는 b 또는 c가 들어간 문자열을 제외한 모든 문자열을 매치한다.
import re
r=re.compile("[^abc]")
r.search("a") # 아무런 결과도 출력되지 않는다.
r.search("ab") # 아무런 결과도 출력되지 않는다.
r.search("b") # 아무런 결과도 출력되지 않는다.
r.search("d")
<_sre.SRE_Match object; span=(0, 1), match='d'>
3. 정규 표현식 모듈 함수 예제
지금까지 정규 표현식 문법에 대한 이해를 위해 정규 표현식 모듈 함수 중에서 re.compile()과 re.search()를 사용해본다.
3-1) re.match() 와 re.search()의 차이
search()가 정규 표현식 전체에 대해서 문자열이 매치하는지를 본다면, match()는 문자열의 첫 부분부터 정규 표현식과 매치하는지를 확인한다. 문자열 중간에 찾을 패턴이 있더라도, match함수는 문자열의 시작에서 패턴이 일치하지 않으면 찾지 않는다.
import re
r=re.compile("ab.")
r.search("kkkabc")
<_sre.SRE_Match object; span=(3, 6), match='abc'>
r.match("kkkabc") #아무런 결과도 출력되지 않는다.
r.match("abckkk")
<_sre.SRE_Match object; span=(0, 3), match='abc'>
3-2) re.split()
split() 함수는 입력된 정규 표현식을 기준으로 문자열들을 분리하여 리스트로 리턴한다.
NLP에서 가장 많이 사용되는 정규 표현식 함수 중 하나로 토큰화에 유용하게 쓰인다.
import re
text="사과 딸기 수박 메론 바나나"
re.split(" ",text)
['사과', '딸기', '수박', '메론', '바나나']
위의 예제의 경우 텍스트로부터 공백을 기준으로 문자열 분리를 수행하였다.
결과로는 리스트를 리턴하는 모습을 볼 수 있다.
import re
text="""사과
딸기
수박
메론
바나나"""
re.split("\n",text)
['사과', '딸기', '수박', '메론', '바나나']
import re
text="사과+딸기+수박+메론+바나나"
re.split("\+",text)
['사과', '딸기', '수박', '메론', '바나나']
이와 유사하게 줄바꿈이나 다른 정규 표현식을 기준으로 텍스트를 분리할 수 있다.
3-3) re.findall()
findall()함수는 정규 표현식과 매치되는 모든 문자열들을 리스트로 리턴한다.
단, 매치되는 문자열이 없다면 빈 리스트를 리턴한다.
import re
text="이름 : 김철수
전화번호 : 010 - 1234 - 1234
나이 : 30
성별 : 남"""
re.findall("\d+",text)
['010', '1234', '1234', '30']
정규 표현식으로 숫자를 입력하자, 전체 데이터에서 숫자만 찾아내 리스트로 리턴하는 것을 볼 수 있다.
3-4)re.sub()
sub() 함수는 정규 표현식 패턴과 일치하는 문자열을 찾아 다른 문자열로 대체할 수 있다.
import re
text="Regular expression : A regular expression, regex or regexp[1] (sometimes called a rational expression)[2][3] is,
in theoretical computer science and formal language theory, a sequence of characters that define a search pattern."
re.sub('[^a-zA-Z]',' ',text)
'Regular expression A regular expression regex or regexp sometimes called a rational expression
is in theoretical computer science and formal language theory a sequence of characters that define a search pattern '
위와 같은 경우, 영어 문장에 각주 등과 같은 이유로 특수 문자가 섞여있다. 자연어 처리를 위해 특수 문자를 제거하고 싶다면 알파벳 외의 문자는 공백으로 처리하는 등의 사용 용도로 쓸 수 있다.
5. 정규 표현식 텍스트 전처리 예제
import re
text = """100 John PROF
101 James STUD
102 Mac STUD"""
re.split('\s+', text)
'\s+'는 공백을 찾아내는 정규표현식이다.
1뒤에 붙은 +는 최소 1개 이상의 패턴을 찾아낸다는 의미이며 s는 공백을 의미하기 때문에 최소 1개 이상의 공백인 패턴을 찾아낸다.
split은 주어진 정규표현식을 기준으로 분리하므로 결과는 아래와 같다.
['100', 'John', 'PROF', '101', 'James', 'STUD', '102', 'Mac', 'STUD']
다음은 해당 데이터로부터 숫자만을 뽑아온다고 해본다.
re.findall('\d+',text)
여기서 \d는 숫자에 해당되는 정규표현식이다.
+를 붙였으므로 최소 1개 이상의 숫자에 해당하는 값을 의미한다.
findall은 해당 정규 표현식에 일치하는 값을 찾아내는 메소드이다.
['100', '101', '102]
텍스트로부터 대문자인 행의 값만 가져오고 싶다고 한다면, 정규 표현식에 대문자를 기준으로 매치시키면 된다.
하지만 정규표현식에 대문자라는 기준만을 넣을 경우 문자열을 가져오는 것이 아니라 모든 대문자 각각을 갖고 오게 된다.
re.findall('[A-Z]',text)
['J', 'P', 'R', 'O', 'F', 'J', 'S', 'T', 'U', 'D', 'M', 'S', 'T', 'U', 'D']
이 경우, 여러 가지 방법이 있겠지만 대문자가 연속적으로 4번 등장하는 경우로 조건을 추가한다.
re.findall('[A-Z]{4}',text)
['PROF', 'STUD', 'STUD']
이름의 경우에는 대문자와 소문자가 섞여있는 상황이다. 이름처럼 처음에 대문자가 등장하고, 그 후에 소문자가 여러 번 등장 하는 경우에 대한 매치를 해본다.
re.findall('[A-Z][a-z]+',text)
['John', 'James', 'Mac']
import re
letters_only = re.sub('[^a-zA-Z]', ' ', text)
위 코드는 영문자가 아닌 전부 공백으로 치환한다.
6. 정규 표현식을 이용한 토큰화
NLTK에서는 정규 표현식을 사용해서 단어 토큰화를 수행하는 RegexpTokenizer를 지원한다.
RegexpTokenizer()에서 괄호 안에 원하는 정규 표현식을 넣어서 토큰화를 수행하는 것이다.
import nltk
from nltk.tokenize import RegexpTokenizer
tokenizer=RegexpTokenizer("[\w]+")
print(tokenizer.tokenize("Don't be fooled by the dark sounding name, Mr. Jone's Orphanage is as
cheery as cheery goes for a pastry shop"))
['Don', 't', 'be', 'fooled', 'by', 'the', 'dark', 'sounding', 'name', 'Mr', 'Jone', 's',
'Orphanage', 'is', 'as', 'cheery', 'as', 'cheery', 'goes', 'for', 'a', 'pastry', 'shop']
tokenizer=RegexpTokenizer("[\w]+")에서 \+는 문자 또는 숫자가 1개 이상인 경우를 인식하는 코드이다.
이 코드는 문장에서 구두점을 제외하고, 단어들만을 가지고 토큰화를 수행한다.
RegexpTokenizer()에서 괄호 안에 토큰을 나누기 위한 기준을 입력 할 수도 있다.
import nltk
from nltk.tokenize import RegexpTokenizer
tokenizer=RegexpTokenizer("[\s]+", gaps=True)
print(tokenizer.tokenize("Don't be fooled by the dark sounding name, Mr. Jone's Orphanage is
as cheery as cheery goes for a pastry shop"))
cheery goes for a pastry shop"))
["Don't", 'be', 'fooled', 'by', 'the', 'dark', 'sounding', 'name,', 'Mr.', "Jone's", 'Orphanage',
'is', 'as', 'cheery', 'as', 'cheery', 'goes', 'for', 'a', 'pastry', 'shop']
위 코드에서 gaps=True는 해당 정규 표현식을 토큰으로 나누기 위한 기준으로 사용한다는 의미이다.
gaps=True라는 부분을 기재하지 않는다면, 결과는 공백들만 나오게 된다.
*정리:
1. 파이썬에서는 정규 표현식 모듈 re를 지원하고 있다.
2. 특정 규칙이 있는 텍스트 데이터를 빠르게 정제할 수 있다.
3. 코드를 많이 작성해보던, 응용을 해보던, 외우던 해야 할 듯하다.
'Deep learning > NLP(자연어처리)' 카테고리의 다른 글
Bag of Words(BoW) (0) | 2020.03.03 |
---|---|
카운트 기반의 단어 표현(Count based word Representation) (0) | 2020.03.02 |
어간 추출(Stemming) and 표제어 추출(Lemmatization) (0) | 2020.03.01 |
정제(Cleaning) and 정규화(Normalization) (0) | 2020.03.01 |
토큰화 (0) | 2020.03.01 |