pytorch

[pytorch] 정규 표현식(Regular Expression) | 특수 문자(metacharacters)| 모듈 함수 (module functions) | 정규 표현식을 이용한 토큰화 (RegexpTokenizer)

독립성이 강한 ISFP 2024. 11. 13. 17:42
728x90
반응형

정규 표현식(Regular Expression)

정규 표현식은 텍스트 전처리에서 유용한 도구로, 특정 규칙에 따라 문자열을 검색하고 조작할 수 있는 패턴 매칭 기술입니다.

파이썬에서는 re 모듈을 통해 정규 표현식을 지원하며, 이를 사용하여 텍스트 데이터를 신속하게 정제하고 처리할 수 있습니다. 이번 포스팅에서는 정규 표현식의 문법, 주요 함수 및 실습 예제를 살펴보겠습니다.


정규 표현식(Regular Expression) 실습

 

정규 표현식의 특수 문자(metacharacters)

자주 사용하는 특수 문자
자주 사용하는 문자 규칙

. 기호 

. 기호는 한 개의 임의의 문자를 나타냅니다.(줄 바꿈 문자 제외)

따라서 패턴 "p.n"은 'p'로 시작하고, 그다음에 임의의 한 글자가 오며, 'n'으로 끝나는 문자열과 일치합니다.

import re

re = re.compile("p.n")

# 이 문자열은 'p'로 시작하고 중간에 임의의 문자 'a'가 있으며 'n'으로 끝나므로 패턴과 일치합니다.
print(re.search("pan"))  

# 이 문자열은 'p'와 'n' 사이에 두 글자 ('io')가 있어 패턴과 일치하지 않습니다.
print(re.search("pion")) 

# 이 문자열은 'p'로 시작하지 않으므로 패턴과 일치하지 않습니다.
print(re.search("car"))  

-------------출력---------------
<re.Match object; span=(0, 3), match='pan'>
None
None

 

 

? 기호

? 기호는 바로 앞에 있는 문자가 0개 또는 1개 존재할 수 있음을 나타냅니다.
따라서 패턴 "ab?c"는 'a'로 시작하고, 그다음에 'b'가 0개 또는 1개 올 수 있으며, 마지막에 'c'로 끝나는 문자열과 일치합니다.

import re

# 패턴 "ab?c"를 컴파일하여 r에 저장합니다.
r = re.compile("ab?c")

# 문자열 "abc"는 'a'와 'c' 사이에 'b'가 하나 있어 패턴과 일치합니다.
print(r.search("abc"))   

# 문자열 "ac"는 'a'와 'c' 사이에 'b'가 없어도 패턴과 일치합니다.
print(r.search("ac"))   

# 문자열 "abbc"는 'a'와 'c' 사이에 'b'가 두 개 있으므로 패턴과 일치하지 않습니다.
print(r.search("abbc")) 

# 문자열 "aXc"는 'a'와 'c' 사이에 'b'가 아닌 다른 문자가 있으므로 패턴과 일치하지 않습니다.
print(r.search("aXc"))  

# -------------출력---------------
# <re.Match object; span=(0, 3), match='abc'>
# <re.Match object; span=(0, 2), match='ac'>
# None
# None

 

 

* 기호

* 기호는 바로 앞에 있는 문자가 0개 이상 있을 수 있음을 나타냅니다.
따라서 패턴 "ab*c"는 'a'로 시작하고, 그다음에 'b'가 0개 이상 올 수 있으며, 마지막에 'c'로 끝나는 문자열과 일치합니다.

import re

# 패턴 "ab*c"를 컴파일하여 r에 저장합니다.
r = re.compile("ab*c")

# 문자열 "ac"는 'a'와 'c' 사이에 'b'가 없으므로 패턴과 일치합니다.
print(r.search("ac"))  

# 문자열 "abbc"는 'a'와 'c' 사이에 'b'가 두 개 있으므로 패턴과 일치합니다.
print(r.search("abbc")) 

# 문자열 "aXc"는 'a'와 'c' 사이에 'b'가 아닌 다른 문자가 있으므로 패턴과 일치하지 않습니다.
print(r.search("aXc"))  

# -------------출력---------------
# <re.Match object; span=(0, 2), match='ac'>
# <re.Match object; span=(0, 4), match='abbc'>
# None

 

+ 기호

+ 기호는 바로 앞에 있는 문자가 1개 이상 있어야 함을 나타냅니다.
따라서 패턴 "ab+c"는 'a'로 시작하고, 그 다음에 'b'가 1개 이상 있어야 하며, 마지막에 'c'로 끝나는 문자열과 일치합니다.

import re

# 패턴 "ab+c"를 컴파일하여 r에 저장합니다.
r = re.compile("ab+c")

# 문자열 "ac"는 'a'와 'c' 사이에 'b'가 없으므로 패턴과 일치하지 않습니다.
print(r.search("ac"))  

# 문자열 "abc"는 'a'와 'c' 사이에 'b'가 하나 있으므로 패턴과 일치합니다.
print(r.search("abc"))

# 문자열 "aXc"는 'a'와 'c' 사이에 'b'가 아니라 'X'가 있으므로 패턴과 일치하지 않습니다.
print(r.search("aXc"))   

# -------------출력---------------
# None
# <re.Match object; span=(0, 3), match='abc'>
# None

 

^ 기호

^ 기호는 문자열의 시작 부분을 나타냅니다.
따라서 패턴 "^hello"는 문자열이 "hello"로 시작할 때만 일치합니다.

import re

# 패턴 "^hello"를 컴파일하여 r에 저장합니다.
r = re.compile("^hello")

print(r.search("hello world"))  # 문자열이 "hello"로 시작하므로 매치
print(r.search("hello"))        # 문자열이 "hello"로 시작하므로 매치
print(r.search("hellooo"))      # 문자열이 "hello"로 시작하므로 매치
print(r.search("world hello"))  # 문자열이 "hello"로 시작하지 않으므로 매치되지 않음
print(r.search("hi there"))     # 문자열이 "hello"로 시작하지 않으므로 매치되지 않음

# -------------출력---------------
# <re.Match object; span=(0, 5), match='hello'>
# <re.Match object; span=(0, 5), match='hello'>
# <re.Match object; span=(0, 5), match='hello'>
# None
# None

 

 

$ 기호

$ 기호는 문자열의 끝 부분을 나타냅니다.
따라서 패턴 "end$"는 문자열이 "end"로 끝날 때만 일치합니다.

import re

# 패턴 "end$"를 컴파일하여 r에 저장합니다.
r = re.compile("end$")

print(r.search("the end"))       # 문자열이 "end"로 끝나므로 매치
print(r.search("end"))            # 문자열이 "end"로 끝나므로 매치
print(r.search("ending"))         # 문자열이 "end"로 끝나지 않으므로 매치되지 않음
print(r.search("the end of it"))  # 문자열이 "end"로 끝나지 않으므로 매치되지 않음
print(r.search("this is the end")) # 문자열이 "end"로 끝나므로 매치

# -------------예상 출력---------------
# <re.Match object; span=(4, 7), match='end'>
# <re.Match object; span=(0, 3), match='end'>
# None
# None
# <re.Match object; span=(12, 15), match='end'>

 

{숫자} 기호

{숫자} 기호는 바로 앞에 있는 문자가 지정된 횟수만큼 연속으로 나타나야 함을 의미합니다.
따라서 패턴 "a{3}"는 'a'가 정확히 3번 연속으로 나타날 때만 일치합니다.

import re

# 패턴 "a{3}"를 컴파일하여 r에 저장합니다.
r = re.compile("a{3}")

# 문자열 "aaa"는 'a'가 3개 연속으로 있어 패턴과 일치합니다.
print(r.search("aaa"))     

# 문자열 "aa"는 'a'가 2개뿐이므로 패턴과 일치하지 않습니다.
print(r.search("aa"))      

# 문자열 "aaaa"는 'a'가 4개 연속이지만, 첫 번째 3개가 패턴과 일치합니다.
print(r.search("aaaa"))    

# 문자열 "a"는 'a'가 1개뿐이므로 패턴과 일치하지 않습니다.
print(r.search("a"))       

# 문자열 "aabaa"는 연속된 'a'가 2개뿐이므로 패턴과 일치하지 않습니다.
print(r.search("aabaa"))  

# 문자열 "bcadaaa"는 'a'가 3개 연속으로 나타나는 부분이 있어 패턴과 일치합니다.
print(r.search("bcadaaa"))  

# -------------예상 출력---------------
# <re.Match object; span=(0, 3), match='aaa'>
# None
# <re.Match object; span=(0, 3), match='aaa'>
# None
# None
# <re.Match object; span=(4, 7), match='aaa'>

 

 

{숫자1, 숫자2} 기호

{숫자1, 숫자2} 기호는 바로 앞에 있는 문자가 최소 숫자 1번, 최대 숫자 2번까지 연속으로 나타날 수 있음을 의미합니다.
따라서 패턴 "a{2,4}"는 'a'가 최소 2번에서 최대 4번까지 연속으로 나타날 때 일치합니다.

import re

# 패턴 "a{2,4}"를 컴파일하여 r에 저장합니다.
r = re.compile("a{2,4}")

# 문자열 "aa"는 'a'가 2개 연속으로 있어 패턴과 일치합니다.
print(r.search("aa"))           

# 문자열 "aaaa"는 'a'가 4개 연속으로 있어 패턴과 일치합니다.
print(r.search("aaaa"))     

# 문자열 "aaaaa"는 'a'가 5개 연속으로 있지만, 패턴은 최대 4개까지만 허용하므로 첫 4개의 'a'만 매치됩니다.
print(r.search("aaaaa"))    

# 문자열 "a"는 'a'가 1개뿐이므로 패턴과 일치하지 않습니다.
print(r.search("a"))       

# 문자열 "baaac"는 'a'가 연속으로 3개 있어 패턴과 일치합니다.
print(r.search("baaac"))  

# -------------출력---------------
# <re.Match object; span=(0, 2), match='aa'>
# <re.Match object; span=(0, 4), match='aaaa'>
# <re.Match object; span=(0, 4), match='aaaa'>
# None
# <re.Match object; span=(1, 4), match='aaa'>

 

{숫자, } 기호

{숫자, } 기호는 바로 앞에 있는 문자가 최소 숫자 번 이상 연속으로 나타나야 함을 의미합니다.
따라서 패턴 "a{2,}"는 'a'가 최소 2번 이상 연속으로 나타날 때 일치합니다.

import re

# 패턴 "a{2,}"를 컴파일하여 r에 저장합니다.
r = re.compile("a{2,}")

# 문자열 "aa"는 'a'가 2개 연속으로 있어 패턴과 일치합니다.
print(r.search("aa"))        # 'a'가 2개 있으므로 매치

# 문자열 "aaaa"는 'a'가 4개 연속으로 있어 패턴과 일치합니다.
print(r.search("aaaa"))      # 'a'가 4개 있으므로 매치

# 문자열 "a"는 'a'가 1개뿐이므로 패턴과 일치하지 않습니다.
print(r.search("a"))         # 'a'가 1개 있으므로 매치되지 않음

# 문자열 "bbaaa"는 'a'가 3개 연속으로 나타나는 부분이 있어 패턴과 일치합니다.
print(r.search("bbaaa"))     # 'a'가 3개 있으므로 매치

# 문자열 "bb"는 'a'가 없으므로 패턴과 일치하지 않습니다.
print(r.search("bb"))        # 'a'가 없으므로 매치되지 않음

# -------------출력---------------
# <re.Match object; span=(0, 2), match='aa'>
# <re.Match object; span=(0, 4), match='aaaa'>
# None
# <re.Match object; span=(2, 5), match='aaa'>
# None

 

[] 기호

[] 기호는 대괄호 안에 있는 문자들 중 하나와 일치하는 경우를 찾습니다.
"[abc]"는 'a', 'b', 또는 'c' 중 하나의 문자와 매치됩니다.

import re

# 패턴 "[abc]"를 컴파일하여 r에 저장합니다.
r = re.compile("[abc]")

# 문자열 "apple"에는 'a'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("apple"))      

# 문자열 "banana"에는 'b'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("banana"))    

# 문자열 "cherry"에는 'c'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("cherry"))    

# 문자열 "kiwi"에는 'a', 'b', 'c' 중 어느 것도 포함되어 있지 않으므로 패턴과 일치하지 않습니다.
print(r.search("kiwi"))       

# -------------출력---------------
# <re.Match object; span=(0, 1), match='a'>
# <re.Match object; span=(0, 1), match='b'>
# <re.Match object; span=(0, 1), match='c'>
# None
# None

 

"[a-z]"와 같이 사용하면 소문자 알파벳 전체(a부터 z까지) 중 하나의 문자와 매치됩니다.

import re

# 'a', 'b', 또는 'c' 중 하나의 문자와 매치됩니다.
r = re.compile("[a-z]")

print(r.search("apple"))      # 'a'가 있으므로 매치
print(r.search("banana"))     # 'b'가 있으므로 매치
print(r.search("cherry"))     # 'c'가 있으므로 매치

# -------------출력---------------
# <re.Match object; span=(0, 1), match='a'>
# <re.Match object; span=(0, 1), match='b'>
# <re.Match object; span=(0, 1), match='c'>

 

[^문자] 기호

 

[^문자] 기호는 대괄호 안에 있는 문자들을 제외한 나머지 문자와 일치합니다.
예를 들어, "[^abc]"는 'a', 'b', 'c'가 아닌 모든 문자와 매치됩니다.

import re

# 패턴 "[^abc]"를 컴파일하여 r에 저장합니다.
r = re.compile("[^abc]")

# 문자열 "apple"에는 'a', 'b', 'c'가 아닌 문자 'p'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("apple"))      

# 문자열 "banana"에는 'a', 'b', 'c'가 아닌 문자 'n'이 포함되어 있으므로 패턴과 일치합니다.
print(r.search("banana"))    

# 문자열 "cherry"에는 'a', 'b', 'c'가 아닌 문자 'h'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("cherry"))     

# 문자열 "abc"에는 'a', 'b', 'c'로만 이루어져 있으므로 패턴과 일치하지 않습니다.
print(r.search("abc"))       

# 문자열 "A"에는 'a', 'b', 'c'가 아닌 대문자 'A'가 포함되어 있으므로 패턴과 일치합니다.
print(r.search("A"))         

# 문자열 "b"는 'a', 'b', 'c' 중 하나로만 이루어져 있으므로 패턴과 일치하지 않습니다.
print(r.search("b"))         

# -------------예상 출력---------------
# <re.Match object; span=(1, 2), match='p'>
# <re.Match object; span=(2, 3), match='n'>
# <re.Match object; span=(1, 2), match='h'>
# None
# <re.Match object; span=(0, 1), match='A'>
# None

 


정규 표현식(regular expression) 모듈 함수

자주 사용하는 모듈 험수

match 함수

match 함수는 문자열의 시작 부분이 패턴과 일치하는지 확인합니다.
즉, 문자열의 첫 번째 문자가 패턴과 일치해야 매치됩니다.

import re

# 패턴 "[^abc]"를 컴파일하여 r에 저장합니다.
r = re.compile("[^abc]")

# 문자열 "apple"은 'a'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("apple"))      

# 문자열 "banana"는 'b'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("banana"))     

# 문자열 "cherry"는 'c'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("cherry"))     

# 문자열 "abc"는 'a'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("abc"))      

# 문자열 "A"는 대문자 'A'로 시작하며, 'a', 'b', 'c'가 아니므로 패턴과 일치합니다.
print(r.match("A"))         

# 문자열 "b"는 'b'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("b"))     

# 문자열 "C"는 대문자 'C'로 시작하므로 패턴과 일치하지 않습니다.
print(r.match("C"))         

# -------------예상 출력---------------
# None
# None
# None
# None
# <re.Match object; span=(0, 1), match='A'>
# None
# None

split 함수  

split 함수는 지정된 패턴을 기준으로 문자열을 분리하여 리스트로 반환합니다.

 

 

"\s+": 하나 이상의 공백(띄어쓰기, 탭, 줄 바꿈 등)을 기준으로 문자열을 분리합니다.

  • \s는 공백 문자(띄어쓰기, 탭, 줄 바꿈 등)를 의미하고,
  • +는 그 문자가 하나 이상 연속으로 나올 때를 뜻합니다.

 

import re

text1 = "사과 딸기 수박 메론 바나나"
result1 = re.split(r"\s+", text1)  # 공백 기준으로 분리
print("공백 기준 분리:", result1)

> 공백 기준 분리: ['사과', '딸기', '수박', '메론', '바나나']

 

 

"\n" : 줄 바꿈을 기준으로 문자열을 분리합니다.

 

text2 = """사과
            딸기
            수박
            메론
            바나나"""
result2 = re.split(r"\n", text2)  # 줄바꿈 기준으로 분리
print("줄바꿈 기준 분리:", result2)

> 줄바꿈 기준 분리: ['사과', '딸기', '수박', '메론', '바나나']

 

"\+":  + 를 기준으로 문자열을 분리합니다.

text3 = "사과+딸기+수박+메론+바나나"
result3 = re.split(r"\+", text3)  # '+' 기준으로 분리
print("더하기 기호 기준 분리:", result3)

> 더하기 기호 기준 분리: ['사과', '딸기', '수박', '메론', '바나나']

 

"공백, 줄 바꿈, +"를 기준으로 문자열을 분리합니다.

text4 = "사과+딸기 수박\n메론 바나나"
result4 = re.split(r"[+\s\n]+", text4)  # 공백, 줄바꿈, '+' 기준으로 분리
print("여러 구분자 기준 분리:", result4)

> 여러 구분자 기준 분리: ['사과', '딸기', '수박', '메론', '바나나']

findall 함수  

findall 함수는 지정된 패턴과 일치하는 모든 문자열을 찾아 리스트로 반환합니다.

 

숫자를 찾는 정규 표현식

text1 = """이름: 김철수
전화번호: 010-1234-5678
나이: 30세
성별: 남자"""

# 숫자를 모두 찾아서 리스트로 반환합니다.
numbers = re.findall(r'\d+', text1)  # \d+는 하나 이상의 숫자를 의미
print("찾은 숫자들:", numbers)

> 찾은 숫자들: ['010', '1234', '5678', '30']

 

이메일 주소를 찾는 정규 표현식

text2 = "제 이메일은 example@example.com입니다. 그리고 다른 이메일은 test123@gmail.com입니다."

# 이메일 주소를 모두 찾아서 리스트로 반환합니다.
emails = re.findall(r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}', text2)
print("찾은 이메일 주소들:", emails)

> 찾은 이메일 주소들: ['example@example.com', 'test123@gmail.com']

 

특정 단어를 찾는 정규 표현식

text3 = "사과, 딸기, 수박, 메론, 바나나가 있습니다."

# '사과'라는 단어를 모두 찾아서 리스트로 반환합니다.
fruits = re.findall(r'사과', text3)
print("찾은 단어들:", fruits)

> 찾은 단어들: ['사과']

 

대문자 알파벳을 찾는 정규 표현식

# 대문자 알파벳을 찾는 정규 표현식
text4 = "ABCdefGHIjklMNO"

# 대문자 알파벳을 모두 찾아서 리스트로 반환합니다.
uppercase_letters = re.findall(r'[A-Z]', text4)
print("찾은 대문자 알파벳:", uppercase_letters)

> 찾은 대문자 알파벳: ['A', 'B', 'C', 'G', 'H', 'I', 'M', 'N', 'O']

sub 함수  

sub 함수는 지정된 패턴과 일치하는 부분을 다른 문자열로 대체합니다.

첫 번째 매개변수는 대체할 문자열, 두 번째 매개변수는 대상 문자열입니다.

 

문자열에서 모든 숫자를 제거

text1 = "전화번호: 010-1234-5678, 나이: 30세"
# 모든 숫자를 공백으로 대체합니다.
result1 = re.sub(r'\d+', '', text1)
print("숫자 제거:", result1)

> 숫자 제거: 전화번호: --, 나이: 세

 

특정 단어를 다른 단어로 대체

text2 = "사과, 딸기, 수박, 사과가 있습니다."
# '사과'를 '포도'로 대체합니다.
result2 = re.sub(r'사과', '포도', text2)
print("단어 대체:", result2)

> 단어 대체: 포도, 딸기, 수박, 포도가 있습니다.

 

특수 문자를 제거

text3 = "안녕하세요! 여기는 Python@OpenAI입니다."
# 특수 문자를 공백으로 대체합니다.
result3 = re.sub(r'[^a-zA-Z0-9가-힣\s]', ' ', text3)
print("특수 문자 제거:", result3)

> 특수 문자 제거: 안녕하세요  여기는 Python OpenAI입니다

 

다중 공백을 하나의 공백으로 대체

text4 = "사과   딸기    수박  메론    바나나"
# 다중 공백을 하나의 공백으로 대체합니다.
result4 = re.sub(r'\s+', ' ', text4)
print("다중 공백 제거:", result4)

> 다중 공백 제거: 사과 딸기 수박 메론 바나나

정규 표현식을 이용한 토큰화 (RegexpTokenizer)

`RegexpTokenizer`는 `nltk.tokenize` 모듈의 클래스 중 하나로, 

사용자가 지정한 정규 표현식(regular expression, regex)을 기준으로 텍스트를 토큰화하는 도구입니다. 

일반적인 단어 분리 방식이 아닌, 특정 패턴에 따라 유연하게 텍스트를 분리하거나 원하는 요소만 추출할 수 있도록 해줍니다. 

`RegexpTokenizer`의 주요 기능

- 패턴 기반 토큰화

`RegexpTokenizer`는 특정 패턴을 입력받아, 그 패턴과 일치하는 부분을 토큰으로 추출하거나(기본 설정), 그 패턴을 기준으로 텍스트를 나누는 기능을 수행합니다.


- 정규 표현식 사용

일반적인 공백, 구두점 등을 기반으로 하지 않고, 정규 표현식을 사용해 더 복잡하고 유연한 조건을 적용할 수 있습니다.


- gaps 옵션

`gaps=True`로 설정하면 패턴에 해당하는 부분을 기준으로 텍스트를 분리하고,

`gaps=False`로 설정하면 일치하는 부분 자체를 토큰으로 추출합니다.


`RegexpTokenizer`의 사용법

from nltk.tokenize import RegexpTokenizer

# 예제 텍스트
text = "Hello there! How's everything going? Call me at 123-456-7890."

# 문자와 아포스트로피 포함된 단어를 추출하는 토크나이저
tokenizer1 = RegexpTokenizer(r"[\w']+")
print("단어 토큰화 결과:", tokenizer1.tokenize(text))

# 공백을 기준으로 텍스트를 나누는 토크나이저 (공백을 제외하고 모든 텍스트를 토큰으로 반환)
tokenizer2 = RegexpTokenizer(r"\s+", gaps=True)
print("공백 기준 분리 결과:", tokenizer2.tokenize(text))

# 구두점과 숫자만 추출하는 토크나이저
tokenizer3 = RegexpTokenizer(r"[,.!?0-9]+")
print("구두점 및 숫자 추출 결과:", tokenizer3.tokenize(text))

-----------출력-----------
단어 토큰화 결과: ['Hello', 'there', "How's", 'everything', 'going', 'Call', 'me', 'at', '123', '456', '7890']
공백 기준 분리 결과: ['Hello', 'there!', "How's", 'everything', 'going?', 'Call', 'me', 'at', '123-456-7890.']
구두점 및 숫자 추출 결과: ['!', '?', '123', '456', '7890.']

 

- `tokenizer1`은 단어(`\w`)와 아포스트로피(`'`)가 포함된 토큰을 추출합니다.
- `tokenizer2`는 공백(`\s+`)을 기준으로 텍스트를 분리하는데, `gaps=True`로 설정되어 공백을 제외한 모든 텍스트가 토큰으로 반환됩니다.
- `tokenizer3`는 `, . ! ?` 등의 구두점이나 숫자(`0-9`)에 해당하는 패턴을 토큰으로 추출합니다.

728x90
반응형