정규표현식 기본 규칙

NLP에서 활용하는 정규표현식 기초

자연어 처리에서 텍스트 전처리와 패턴 매칭에 필수적인 정규표현식의 기본 규칙을 알아본다.

NLP
저자

Kwangmin Kim

공개

2025년 01월 03일

1 정규표현식 기초

1.1 정규표현식이란?

  • 토큰화같은 텍스트 전처리에 필수적으로 사용되는 도구
  • 정규표현식(Regular Expression)은 특정한 패턴을 가진 문자열을 검색, 매칭, 치환하는 데 사용되는 형식 언어
  • 자연어 처리(NLP)에서 텍스트 전처리 단계에서 중요한 도구로 활용

1.2 기본 문자 매칭

  1. 일반 문자: 문자 자체를 매칭
  2. 메타 문자: 특별한 의미를 가진 문자들 (., ^, $, *, +, ? 등)

1.3 기본 패턴 규칙

  1. .: 임의의 한 문자와 매칭
    • 예시: a.c는 “abc”, “adc”, “a3c” 등과 매칭됨
  2. ^: 문자열의 시작
    • 예시: ^Hello는 “Hello World”와 매칭되지만 “World Hello”와는 매칭되지 않음
  3. $: 문자열의 끝
    • 예시: world$는 “Hello world”와 매칭되지만 “world hello”와는 매칭되지 않음
  4. *: 앞의 요소가 0번 이상 반복
    • 예시: ab*c는 “ac”, “abc”, “abbc”, “abbbc” 등과 매칭됨
  5. +: 앞의 요소가 1번 이상 반복
    • 예시: ab+c는 “abc”, “abbc”, “abbbc” 등과 매칭되지만 “ac”와는 매칭되지 않음
  6. ?: 앞의 요소가 0번 또는 1번 등장
    • 예시: colou?r는 “color”와 “colour” 모두와 매칭됨
  7. {n}: 앞의 요소가 정확히 n번 반복
    • 예시: a{3}는 “aaa”와 매칭됨
  8. {n,} - 앞의 요소가 n번 이상 반복
    • 예시: a{2,}는 “aa”, “aaa”, “aaaa” 등과 매칭됨
  9. {n,m} - 앞의 요소가 n번 이상 m번 이하 반복
    • 예시: a{1,3}는 “a”, “aa”, “aaa”와 매칭됨

1.4 문자 클래스

  1. [abc]: a, b, c 중 하나와 매칭
    • 예시: [abc]at는 “aat”, “bat”, “cat”과 매칭됨
  2. [^abc]: ^ 문자는 문자 클래스 [ ] 내에서 사용될 때 부정(negation)을 의미하며, a, b, c를 제외한 문자와 매칭
    • 예시: [^abc]at는 “dat”, “eat”, “fat” 등과 매칭되지만 “aat”, “bat”, “cat”과는 매칭되지 않음
  3. [a-z]: - 문자는 문자 클래스 [ ] 내에서 사용될 때 범위(range)를 의미하며, a부터 z까지의 소문자와 매칭
    • 예시: [a-z]oo는 “foo”, “zoo” 등과 매칭됨
  4. [A-Z]: A부터 Z까지의 대문자와 매칭
    • 예시: [A-Z]ello는 “Hello”, “Jello” 등과 매칭됨
  5. [0-9] - 숫자와 매칭
    • 예시: page[0-9]는 “page0”, “page1” 등과 매칭됨
  6. [_]: 문자 클래스 [ ] 내에서 사용될 때 문자 클래스를 의미하며, 언더바(_)와 매칭
    • 예시 ```python import re # [^a-zA-Z0-9_] 패턴은 영문자, 숫자, 언더스코어를 제외한 모든 문자와 일치 text = “Hello_world! This-is a sample@123.” cleaned = re.sub(r’[^a-zA-Z0-9_]‘,’ ’, text) # 특수문자만 공백으로 됨 print(cleaned) # “Hello_world This is a sample 123”

1.5 미리 정의된 문자 클래스

  1. \d - 숫자와 매칭 (=[0-9])
    • 예시: \d\d\d는 “123”, “456” 등 세 자리 숫자와 매칭됨
  2. \D - 숫자가 아닌 문자와 매칭 (=[^0-9])
    • 예시: \D\D\D는 “abc”, “XYZ” 등 숫자가 아닌 세 문자와 매칭됨
  3. \w - 단어 문자와 매칭 (=[a-zA-Z0-9_])
    • 예시: \w+는 단어 문자가 1회 이상 반복되어야 한다.

    • “hello123”, “Python_3”, “user_name”, “variable42”, “data_science2023” 등과 매칭됨

    • 예시:

      import re
      text = "hello123 @special! Python_3"
      words = re.findall(r'\w+', text)
      print(words)  # ['hello123', 'Python_3']
  4. \W - 단어 문자가 아닌 문자와 매칭
    • 예시: \W+는 “!@#”, ” ” 등과 매칭됨
  5. \s - 공백 문자와 매칭 (스페이스, 탭, 줄바꿈 등)
    • 예시: hello\sworld는 “hello world”와 매칭됨
  6. \S - 공백이 아닌 문자와 매칭
    • 예시: \S+는 “hello”, “world” 등 공백이 없는 문자열과 매칭됨

1.6 그룹과 참조

  1. (...) - 그룹화 및 캡처
    • 예시: (ab)+는 “ab”, “abab”, “ababab” 등과 매칭됨
    • 예시: (a+b+)는 “ab”, “aab”, “abb”, “aaabbb” 등 a가 하나 이상, b가 하나 이상 연속된 패턴과 매칭됨
    • 예시: (a{2}b{2})는 “aabb”와 같이 정확히 a가 2번, b가 2번 반복되는 패턴과 매칭됨
    • 예시: (a{2,3}b{2,3})는 “aabb”, “aaabb”, “aabbb”, “aaabbb” 등 a가 2~3번, b가 2~3번 반복되는 패턴과 매칭됨
  2. (?:...) - 그룹화만 하고 캡처하지 않음 (non-capturing group)
    • 일반 그룹 (...)은 패턴을 그룹화하고 매칭된 부분을 메모리에 저장(캡처)함
    • 반면 (?:...)는 패턴을 그룹화하지만 매칭된 부분을 메모리에 저장하지 않음
    • 단순히 패턴을 묶어서 처리하고 싶을 때 사용하며, 나중에 참조할 필요가 없을 때 메모리 효율을 위해 사용함
    • 예시: (?:ab)+c는 “abc”, “ababc”, “abababc” 등과 매칭됨
    • 예시: (?:ab)+는 “ab”가 1회 이상 반복됨
    • 예시: (?:ab)+c는 “ab”가 1회 이상 반복된 후 “c”가 오는 패턴
    • 예시: ab는 정확히 “ab”라는 문자열과 매칭
    • 예시: (ab)는 “ab”를 하나의 단위로 그룹화하고 캡처함
    • 예시: (ab)+c는 “ab”가 1회 이상 반복된 후 “c”가 오는 패턴
    • 주요 차이점:
      1. 패턴 자체의 차이:
        • (?:ab)+: “ab”의 반복만 매치
        • (ab)+c: “ab”의 반복 + “c”를 매치
      2. 캡처 여부:
        • (?:ab)+: 비캡처 그룹이므로 매치된 내용을 캡처하지 않음
        • (ab)+c: 캡처 그룹이므로 마지막으로 매치된 “ab”를 캡처함
      3. 매치되는 문자열:
        • (?:ab)+: “ab”, “abab”, “ababab”…
        • (ab)+c: “abc”, “ababc”, “abababc”…
    import re
    
    pattern = r'(?:ab)+'
    strings = ['ab', 'abab', 'ababab', 'a', 'abc']
    
    for s in strings:
        match = re.fullmatch(pattern, s)
        if match:
            print(f"'{s}' 매치됨")
            print(f"  그룹: {match.groups()}")  # 빈 튜플 () - 캡처된 그룹 없음
        else:
            print(f"'{s}' 매치되지 않음")
    # 결과
    # 'ab' 매치됨
    #   그룹: ()
    # 'abab' 매치됨
    #   그룹: ()
    # 'ababab' 매치됨
    #   그룹: ()
    # 'a' 매치되지 않음
    # 'abc' 매치되지 않음
    
    pattern = r'(ab)+c'
    strings = ['abc', 'ababc', 'abababc', 'ab', 'c']
    
    for s in strings:
        match = re.fullmatch(pattern, s)
        if match:
            print(f"'{s}' 매치됨")
            print(f"  그룹: {match.groups()}")  # ('ab',) - 마지막 'ab'가 캡처됨
        else:
            print(f"'{s}' 매치되지 않음")
    
    # 결과:
    # 
    # 'abc' 매치됨
    #   그룹: ('ab',)
    # 'ababc' 매치됨
    #   그룹: ('ab',)
    # 'abababc' 매치됨
    #   그룹: ('ab',)
    # 'ab' 매치되지 않음
    # 'c' 매치되지 않음
  3. \1, \2, ... - 이전에 캡처된 그룹을 참조하는 역참조(backreference) 기능
    • \1: 첫 번째 캡처 그룹과 동일한 내용을 의미
    • \2: 두 번째 캡처 그룹과 동일한 내용을 의미
    • \3: 세 번째 캡처 그룹과 동일한 내용을 의미
    • 예시: (\w+) \1
      • 첫 번째 캡처 그룹(단어)이 그대로 반복되는 패턴
      • 매치됨: “hello hello”, “python python”
      • 매치되지 않음: “hello world”, “python java”
    • 예시: (\w+) (\w+) \2
      • 첫 번째 단어 다음에 두 번째 단어가 나오고, 그 다음에 두 번째 단어가 반복되는 패턴
      • 매치됨: “hello world world”, “python java java”
      • 매치되지 않음: “hello hello world”, “python python java”
    • 예시: (\w+) (\w+) (\w+) \3
      • 세 개의 단어가 나오고, 그 다음에 세 번째 단어가 반복되는 패턴
      • 매치됨: “apple banana cherry cherry”, “one two three three”
      • 매치되지 않음: “apple banana cherry banana”, “one two three one” ## 경계 매칭
  4. \b: 단어 경계
    • 예시: \bcat\b는 “The cat sat” 문장에서 “cat”과 매칭되지만 “category”의 일부와는 매칭되지 않음
  5. \B: 단어 경계가 아닌 위치
    • 예시: \Bcat\B는 “location”의 “cat”과 매칭되지만 독립된 단어 “cat”과는 매칭되지 않음

1.7 선택과 대안

  1. | - 대안 패턴 (OR 연산자)
    • 예시: cat|dog는 “cat”과 “dog” 모두와 매칭됨 ### NLP에서의 정규표현식 활용 예시
  • 정규표현식은 자연어 처리(NLP)에서 다양한 텍스트 전처리 및 정보 추출 작업에 활용

1.7.0.1 이메일 주소 추출

import re

text = "연락처: john.doe@example.com, support@company.co.kr, help-desk@org.net"

# 이메일 패턴
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'

# 모든 이메일 주소 추출
emails = re.findall(email_pattern, text)
print(emails)
# 출력: ['john.doe@example.com', 'support@company.co.kr', 'help-desk@org.net']

1.7.0.2 URL 추출

import re

text = "참고 링크는 https://example.com/page, http://test.co.kr 그리고 www.resources.org/docs 입니다."

# URL 패턴
url_pattern = r'https?://[^\s]+|www\.[^\s]+'

# 모든 URL 추출
urls = re.findall(url_pattern, text)
print(urls)
# 출력: ['https://example.com/page', 'http://test.co.kr', 'www.resources.org/docs']

1.7.0.3 문장 토큰화

import re

text = "안녕하세요. 정규표현식을 배우고 있습니다! 어렵지만 유용하죠? 화이팅..."

# 문장 경계 패턴 (마침표, 느낌표, 물음표 뒤에 공백)
sentence_pattern = r'[.!?]+\s+'

# 문장으로 분리
sentences = re.split(sentence_pattern, text)
print(sentences)
# 출력: ['안녕하세요', '정규표현식을 배우고 있습니다', '어렵지만 유용하죠', '화이팅...']

1.7.0.4 해시태그 추출 (소셜 미디어 분석)

import re

tweet = "오늘 날씨가 좋네요 #날씨 #봄 #산책 @친구 같이 가자!"

# 해시태그 패턴
hashtag_pattern = r'#\w+'

# 모든 해시태그 추출
hashtags = re.findall(hashtag_pattern, tweet)
print(hashtags)
# 출력: ['#날씨', '#봄', '#산책']

1.7.0.5 날짜 형식 표준화

import re

dates = [
    "2023-05-15", 
    "05/15/2023", 
    "15.05.2023",
    "May 15, 2023"
]

# 다양한 날짜 패턴 처리
for date in dates:
    # YYYY-MM-DD 패턴
    if re.match(r'^\d{4}-\d{2}-\d{2}$', date):
        print(f"{date} -> 이미 표준 형식")
    
    # MM/DD/YYYY 패턴
    elif re.match(r'^\d{2}/\d{2}/\d{4}$', date):
        m, d, y = re.findall(r'(\d{2})/(\d{2})/(\d{4})', date)[0]
        print(f"{date} -> {y}-{m}-{d}")
    
    # DD.MM.YYYY 패턴
    elif re.match(r'^\d{2}\.\d{2}\.\d{4}$', date):
        d, m, y = re.findall(r'(\d{2})\.(\d{2})\.(\d{4})', date)[0]
        print(f"{date} -> {y}-{m}-{d}")
    
    # 월 이름 패턴
    elif re.match(r'^[A-Za-z]+ \d{1,2}, \d{4}$', date):
        month_names = {"January": "01", "February": "02", "March": "03", "April": "04", 
                      "May": "05", "June": "06", "July": "07", "August": "08", 
                      "September": "09", "October": "10", "November": "11", "December": "12"}
        
        match = re.match(r'^([A-Za-z]+) (\d{1,2}), (\d{4})$', date)
        if match:
            month, day, year = match.groups()
            month_num = month_names.get(month, "00")
            day = day.zfill(2)  # 한 자리 숫자면 앞에 0 추가
            print(f"{date} -> {year}-{month_num}-{day}")

1.7.0.6 개체명 추출

  • 간단한 규칙 기반으로 정확한 원하는 답을 얻을 순 없지만 규칙을 정밀하게 보완하면 어느 정도 후보군을 추릴 수 있다.
  • 이런 규칙 기반의 텍스트 추출을 할 때 정규표현식이 필수적으로 사용된다.)
  • 예를 들어 아래의 인명을 추출하는 것은 다음과 같은 추가 조건을 추가하면 좋다.
    • 사전 기반 접근법: 흔한 한국 성씨 목록을 사용해 필터링
    • 문맥 분석: 이름 앞뒤로 나오는 “씨”, “님”, “대표” 등의 호칭 고려
    • 기계학습 방식 (ex.NER, Named Entity Recognition 모델 사용)
import re

text = """
김철수 대표는 삼성전자에서 5년간 근무했으며, 현재 서울특별시 강남구에 거주 중입니다.
그는 한국대학교를 졸업했고, 현재 7,500,000원의 월급을 받고 있습니다.
"""

# 인명 패턴 (성+이름) : 성이 2~4글자, 이름이 2~4글자 또는 2~5글자
person_pattern = r'[가-힣]{2,4}\s[가-힣]{2,4}|[가-힣]{2,5}'
persons = re.findall(person_pattern, text)
print("인명:", persons) # 인명: ['김철수', '대표는', '현재', '서울특별시', '강남구에', '거주', '현재']

# 기관명 패턴
org_pattern = r'[가-힣a-zA-Z0-9]+[대학교|회사|전자|은행|그룹]'
orgs = re.findall(org_pattern, text)
print("기관:", orgs)

# 금액 패턴
money_pattern = r'[0-9,]+원'
money = re.findall(money_pattern, text)
print("금액:", money)

# 지역 패턴
location_pattern = r'[가-힣]+[시|도|군|구](\s[가-힣]+[시|도|군|구])?'
locations = re.findall(location_pattern, text)
print("지역:", locations)

1.7.0.7 텍스트 정제 (HTML 태그 제거)

import re

html = "<p>안녕하세요. <b>정규표현식</b>으로 <span style='color:red'>HTML 태그</span>를 제거합니다.</p>"

# HTML 태그 제거 패턴
clean_text = re.sub(r'<[^>]+>', '', html)
print(clean_text)
# 출력: 안녕하세요. 정규표현식으로 HTML 태그를 제거합니다.

1.7.0.8 특수문자 정규화

import re

text = "텍스트 정제 작업: 특수문자(!@#$%^&*)를 제거하거나 변환합니다."

# 특수문자 제거
clean_text = re.sub(r'[^\w\s]', '', text)
print(clean_text)
# 출력: 텍스트 정제 작업 특수문자를 제거하거나 변환합니다

1.7.0.9 단어 빈도 분석

import re
from collections import Counter

text = """
자연어 처리(NLP)는 컴퓨터가 인간의 언어를 이해하고 처리하는 기술입니다.
자연어 처리는 텍스트 분석, 기계 번역, 감성 분석 등 다양한 분야에 활용됩니다.
"""

# 단어 추출 (한글, 영문, 숫자)
words = re.findall(r'\b[가-힣a-zA-Z0-9]+\b', text)
word_counts = Counter(words)
print(word_counts.most_common(5))

1.7.0.10 전화번호 추출 및 정규화

import re

text = "연락처 목록: 010-1234-5678, 02)987-6543, 01099998888, +82-10-5555-1234"

# 전화번호 패턴
phone_pattern = r'(\+\d{1,3}[-\s]?)?(\d{2,3}[-\)\s]?)?(\d{3,4}[-\s]?)(\d{4})'
phones = re.findall(phone_pattern, text)

# 전화번호 정규화
normalized_phones = []
for phone in phones:
    country, area, first, last = phone
    normalized = f"{area.replace(')', '')}-{first.replace('-', '')}-{last}"
    normalized_phones.append(normalized)

print(normalized_phones)
# 출력: ['010-1234-5678', '02-987-6543', '010-9999-8888', '10-5555-1234']
  • 이러한 정규표현식 활용은 NLP 파이프라인의 전처리 단계에서 텍스트 정제, 토큰화, 정보 추출에 매우 유용하다
  • 하지만 복잡한 언어 패턴 분석에는 한계가 있어 고급 NLP 작업에는 딥러닝 모델과 함께 사용되는 경우가 많다.

Subscribe

Enjoy this blog? Get notified of new posts by email: