AI/자연어처리

텍스트 전처리(정규 표현식(Regular Expression)

XZXXZX 2021. 7. 7. 14:35
728x90
반응형

1. 정규표현식 문법과 모듈 함수

파이썬에서는 정규 표현식 모듈 re를 진원한다. 이를 이용하면 특정 규치이 있는 텍스트 데이터를 빠르게 정제할 수 있다. 

 

1) 정규 표현식 문법

정규 표현식을 위해 사용되는 문법 중 특수 문자들은 아래와 같다.

특수문자 설명
. 한 개의 임의의 문자 ( 줄바꿈 문자인 \n은 제외
? 앞의 문자가 존재할 수도 있고, 존재하지 않을 수도 있다. (문자가 0개 또는 1개)
* 앞의 문자가 무한개로 존재할 수도 있고, 존재하지 않을 수도 있다.(문자가 0개 이상)
+ 앞의  문자가 최소 한 개 이상 존재합니다.(문자가 1개 이상)
^ 뒤의 문자로 문자열이 시작됩니다.
$ 앞의 문자로 문자열이 끝납니다.
{숫자} 숫자만큼 반복합니다.
{숫자1, 숫자2} 숫자1 이상 숫자2 이하만큼 반복합니다. ?, *, +를 이것으로 대체할 수 있습니다.
{숫자,} 숫자 이상만큼 반복합니다.
[] 대괄호 안의 문자들 중 한 개의 문자와 매치합니다. [amk]라고 한다면 a 또는 m 또는 k 중 하나라도 존재하면 매치를 의미합니다. [a-z]와 같이 범위를 지정할 수도 있습니다. [a-zA-z]는 알파벳 전체를 의미하는 범위이며, 문자열에 알파벳이 존재하면 매치를 의미합니다.
[^문자] 해당 문자를 제외한 문자를 매치합니다.
| A|B와 같이 쓰이며 A 또는 B의 의미를 가집니다.

정규 표현식 문법에는 역 슬래쉬(\)를 이용하여 자주 쓰이는 문자 규칙들이 있습니다.

문자 규칙 설명
\ 역 슬래쉬 문자 자체를 의미합니다.
\d 모든 숫자를 의미합니다. [0-9]와 의미가 동일합니다.
\D 숫자를 제외한 모든 문자를 의미합니다. [^0-9]와 의미가 동일합니다.
\s 공백을 의미합니다. [\t\n\r\f\v]와 의미가 동일합니다.
\S 공백을 제외한 문자를 의미합니다. [^\t\n\r\f\v]와 의미가 동일합니다.
\w 문자 또는 숫자를 의미합니다. [a-zA-Z0-9]와 의미가 동일합니다.
\W 문자 또는 숫자가 아닌 문자를 의미합니다. [^a-zA-Z0-9]와 의미가 동일합니다.

2) 정규 표현식 모듈 함수

정규표현식 모듈에서 지원하는 함수는 이와 같습니다.

모듈 함수 설명
re.compile() 정규표현식을 컴파일하는 함수다. 다시 말해, 파이썬에게 전해주는 역할을 한다. 찾고자 하는 패턴이 빈번한 경우에는 미리 컴파일해놓고 사용하면 속도와 편의성면에서 유리합니다.
re.search() 문자열 전체에 대해서 정규표현식과 매치되는지를 검색합니다.
re.match() 문자열의 처음이 정규표현식과 매치되는지를 검색합니다.
re.split() 정규 표현식을 기준으로 문자열을 분리하여 리스트로 리턴합니다.
re.findall() 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열을 찾아서 리스트로 리턴합니다. 만약, 매치되는 문자열이 없다면 빈리스트가 리턴됩니다.
re.finditer() 문자열에서 정규 표현식과 매치되는 모든 경우의 문자열에 대한 이터레이터 객체를 리턴합니다.
re.sub() 문자열에서 정규 표현식과 일치하는 부분에 대해서 다른 문자열로 대체합니다.

2. 정규 표현식 실습

 

1) .기호

.은 한 개의 임의의 문자를 나타낸다. 예를 들어서 정규 표현식이 a.c라고 한다면 a와 c 사이에는 어떤 1개의 문자라도 올 수 있다. 즉 akc, azc, avc, a5c, a!c와 같은 형태는 모두 a.c의 정규표현식과 매치된다.

import re
r = re.compile('a.c')
print(r.search('kkk')) # 결과 출력 X
print(r.search('abc'))
None
<re.Match object; span=(0, 3), match='abc'>

2) ?기호

?는 ? 앞의 문자가 존재할 수 도 잇고, 존재하지 않을 수도 있는 경우를 나타냅니다. 예를 들어서 정규 표현식이 ab?c라고 합시다. 이경우 이 정규 표현식에서의 b는 있다고 취급할 수도 있고, 없다고 취급할 수도 있습니다. 즉, abc와 ac 모두 매치할 수 있습니다.

r = re.compile('ab?c')
print(r.search('abbc')) # 결과 출력 X
print(r.search('abc'))
print(r.search('ac'))
None
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 2), match='ac'>

두번째와 세번째 경우만 b가 있는경우 b가 없는 경우를 판단하여 매치 시켜준다.

3) *기호

*은 바로 앞의 문자가 0개 이상일 경우를 나타냅니다. 앞의 문자는 존재하지 않을 수도 있으며, 또는 여러 개일 수도 있습니다. 예를 들어서 정규표현식이 ab*c라고 한다면 ac, abc, abbc, abbbc등과 매치할 수 있으며 b의 갯수는 무수히 많아도 상관 없다.

import re
r = re.compile('ab*c')
print(r.search('a')) # 결과 출력 x
print(r.search('ac'))
print(r.search('abc'))
print(r.search('abbbbbc'))
None
<re.Match object; span=(0, 2), match='ac'>
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 7), match='abbbbbc'>

4) +기호

+는 *와 유사하다. 하지만 다른 점은 앞의 문자가 최소 1개 이상이어야 한다는 점이다.예시로 정규 표현식이 ab+c라고 한다면, ac는 매치되지 않는다. 하지만 abc,abbbc등과 매치할 수 있으며 b의 갯수는 무수히 많을 수 있다.

import re
r = re.compile('ab+c')
print(r.search('ac')) # 결과 출력x
print(r.search('abc'))
print(r.search('abbbbbbc'))
None
<re.Match object; span=(0, 3), match='abc'>
<re.Match object; span=(0, 8), match='abbbbbbc'>

5) ^기호

^는 시작되는 글자를 지정한다. 정규표현식이 ^a라면 a로 시작되는 문자열만을 찾아낸다.

import re
r = re.compile('^a')
print(r.search('bbc'))
print(r.search('ab'))
None
<re.Match object; span=(0, 1), match='a'>

6) {숫자} 기호

문자에 해당 기호를 붙이면, 해당 문자를 숫자만큼 반복한 것이다. 예시로 정규표현식이 ab{2}c 라면 a와 c 사이에 b가 존재하면서 b가 2개인 문자열에 대해서 매치합니다.

import re
r = re.compile('ab{2}c')
print(r.search('ac'))# 결과 출력X
print(r.search('abc'))#결과 출력X
print(r.search('abbc'))
print(r.search('abbbbbbc'))
None
None
<re.Match object; span=(0, 4), match='abbc'>
None

7) {숫자1, 숫자2} 기호

문자에 해당 기호를 붙이면, 해당 문자를 숫자1 이상 숫자2 이하만큼 반복한다. 예시로 정규 표현식이 ab{2,8}c라면 a와 c 사이에 b가 존재하면서 b는 2개 이상 8개 이하인 문자열에 대해서 매치한다.

import re
r = re.compile('ab{2,8}c')
print(r.search('ac'))# 결과 출력x
print(r.search('abc'))# 결과 출력x
print(r.search('abbc')) # b2개
print(r.search('abbbbbbbbc')) # b8개
print(r.search('abbbbbbbbbc'))#결과 출력 x  b9개
None
None
<re.Match object; span=(0, 4), match='abbc'>
<re.Match object; span=(0, 10), match='abbbbbbbbc'>
None

8) {숫자,} 기호

문자에 해당 기호를 붙이면 해당 문자를 숫자 이상 만큼 반복한다. 예시로 정규표현식이 a{2,}bc라면 뒤에 bc가 붙으면서 a의 갯수가 2개 이상인 경우인 문자열과 매치된다. 또한 만약 {0,}을 쓴다면 *와 동일한 의미가 되며, {1,}을 쓴다면 +와 동일한 의미다.

import re
r = re.compile('a{2,}bc')
print(r.search('bc')) # 결과 출력X
print(r.search('aa')) # 결과 출력x
print(r.search('aabc')) # a 2개
print(r.search('aaaaaaaabc')) # a 8개
None
None
<re.Match object; span=(0, 4), match='aabc'>
<re.Match object; span=(0, 10), match='aaaaaaaabc'>

9) [] 기호

[]안에 문자들을 넣으면 그 문자들 중 한 개의 문자와 매치라는 의미를 가진다. 예시로 정규 표현식이 [abc]라면 a 또는 b 또는 c가 들어가 있는 문자열과 매치된다. 범위를 지정하는 것도 가능하다. [a-zA-Z]는 알파벳 전부를 의미하며, [0-9]는 숫자 전부를 의미한다.

# 알파벳 소문자 abc에 대해서 정규표현식 만들기
import re
r = re.compile('[abc]') # [abc]는 [a-c]와 같음
print(r.search('zzz')) # 결과 출력 X
print(r.search('a'))
print(r.search('aaaaaaa'))
print(r.search('baac'))
None
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='a'>
<re.Match object; span=(0, 1), match='b'>
# 알파벳 소문자에 대해서 범위 지정하여 정규표현식 만들기
import re
r = re.compile('[a-z]')
print(r.search('AAA'))# 결과 출력x
print(r.search('aBC'))
print(r.search('111'))#결과 출력x
None
<re.Match object; span=(0, 1), match='a'>
None

10) [^문자] 기호

[^문자]는 5)에서 말한 것과 같이 ^와는 다른 의미로 쓰이게 된다. ^기호 뒤에 문자들을 제외한 모든 문자를 매치하는 역할을 한다. 예시로 [^abc]라는 정규 표현식이라면 a 또는 b 또는 c가 들어간 문자열을 제외한 모든 문자열을 매치한다.

# [^문자]
import re
r = re.compile('[^abc]')
print(r.search('a')) # 결과출력 x
print(r.search('ab'))# 결과출력 x
print(r.search('b')) # 결과출력 x
print(r.search('d'))
print(r.search('1'))
None
None
None
<re.Match object; span=(0, 1), match='d'>
<re.Match object; span=(0, 1), match='1'>

3. 정규 표현식 모듈 함수 예제

1) re.match()dhk re.search()의 차이

search()가 정규 표현식 전체에 대해서 문자열이 매치하는지를 본다면, match()는 문자열의 첫 부분부터 정규표현식과 매치하는지를 확인한다. 문자열 중간에 찾을 패턴이 있다고 하더라도, match 함수는 문자열의 시작에서 패턴이 일치하지 않다면 찾지 않는다.

# re.match()와 re.search()의 차이
import re
r = re.compile('ab.')
print(r.search('kkkabc')) # 문자열의 중간에 있더라도 정규표현식을 만족함
print(r.match('kkkabc'))# 문자열의 첫부분에서 정규표현식을 만족하지 않아 결과 출력 x
print(r.match('abckkk')) # 문자열의 첫부분에서 정규표현식을 만족함
<re.Match object; span=(3, 6), match='abc'>
None
<re.Match object; span=(0, 3), match='abc'>

2) split() 함수는 이력된 정규 표현식을 기준으로 문자열들을 분리하여 리스트로 리턴한다. 토큰화에 유용하게 활용되기에 자연어 처리에 있어서 가장 많이 사용되는 정규 표현식 함수 중 하나이다. 

import re
text = '사과 딸기 수박 메론 바나나'
re.split(' ', text) # 공백을 기준으로 문자열 분리
['사과', '딸기', '수박', '메론', '바나나']
import re
text = """사과
딸기
수박
메론
바나나"""
re.split('\n', text) # 줄바꿈을 기준으로 문자열 분리
import re
text = '사과+딸기+수박+메론+바나나'
re.split("\+",text) # +기호를 기준으로 문자열 분리
['사과', '딸기', '수박', '메론', '바나나']

3) re.findall()

findall() 함수는 정규 표현식과 매치되는 모든 문자열들을 리스트로 리턴한다. 단, 매치되는 문자열이 없다면 빈 리스트를 리턴한다.

import re
text = """이름 : 김철수
전화번호 : 010 - 1234 - 1234
나이 : 30
성별 : 남"""
print(re.findall('\d+', text)) # 숫자만 찾아내서 리스트로 리턴
print(re.findall("\d+", '문자열입니다.')) # 문자열입니다. 안에 숫자가 없기에 빈리스트로 리턴

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"""  

print(re.split('\s+', text)) # 최소 1개 이상의 공백인 패턴을 기준으로 split
print(re.findall('\d+', text)) # 숫자로 구성된 데이터뽑아옴
print(re.findall('[A-Z]', text)) # 정규표현식에 대문자라는 기준만 넣은 경우 문자열을 가져오는 것이 아닌 모든 대문자 각각을 가져온다.
print(re.findall('[A-Z]{4}', text)) # 대문자가 연속으로 4번 등장하는 경우
print(re.findall('[A-Z][a-z]+', text)) # 대문자와 소문자가 섞여있는 상황에서 이름에 대한 행의 값을 가져오려면 처음에 대문자가 등장하고 그후에 소문자가 여러번 등장하는경우
['100', 'John', 'PROF', '101', 'James', 'STUD', '102', 'Mac', 'STUD']
['100', '101', '102']
['J', 'P', 'R', 'O', 'F', 'J', 'S', 'T', 'U', 'D', 'M', 'S', 'T', 'U', 'D']
['PROF', 'STUD', 'STUD']
['John', 'James', 'Mac']

6. 정규 표현식을 이용한 토큰화

NLTK에서는 정규 표현식을 사용해서 단어 토큰화를 수행하는 RegexpTokenizer를 지원한다. RegxpTokenizer()에서 괄호 안에 원하는 정규 표현식을 넣어서 토큰화를 수행한다.

import nltk
from nltk.tokenize import RegexpTokenizer
 #문자 또는 숫자가 1개 이상인 경우를 인식하는 코드이다. 문장에서 구두점을 제외하고 단어들만을 가지고 토큰화를 수행한다.
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']
import nltk
from nltk.tokenize import RegexpTokenizer
 # gaps = True는 정규 표현식을 토큰으로 나누기 위한 기준으로 사용한다는 것이다. gaps = True라는 부분을 기재하지 않는다면, 토큰화의 결과는 공백만 나오게된다. 
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"))
["Don't", 'be', 'fooled', 'by', 'the', 'dark', 'sounding', 'name,', 'Mr.', "Jone's", 'Orphanage', 'is', 'as', 'cheery', 'as', 'cheery', 'goes', 'for', 'a', 'pastry', 'shop']

참고문헌 : https://wikidocs.net/21703

728x90
반응형