목차 1. NLP 란 (링크 : https://summerorange.tistory.com/entry/1-%EC%9E%90%EC%97%B0%EC%96%B4-%EC%B2%98%EB%A6%AC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C) 2. 정규표현식을 익히자 3. 확률 4. 선형대수 5. 미적분 6. 텍스트 전처리 7. 분류 8. 텍스트 유사도 9. Bert & Gpt 10. ChatBot |
한줄평: Text처리에는 정규표현식이 쵝오👍
정규표현식이란?
Python에서 원하는 텍스트만 추출할 때 꼭 써야 하는 게 정규표현식입니다. 파이썬 뿐만 아니라 JAVA, C, JavaScript, PHP, Rust, C++, 에도 지원합니다. 파이썬 이외에도 자주 썼던 경우는, 저 같은 경우는 vim입니다. vim에서 텍스트 대체는 생각보다 자주 쓸 일이 있어서 정규 표현식으로 짜서 쓰는 경우가 있습니다.
정규 표현식을 도대체 왜 쓰는지?
정규 표현식의 장점이란 강력한 텍스트 찾기 기능입니다.
파이썬 코드로 텍스트 대체할 때, 리스트 구조 혹은 딕셔너리 구조 만들어서 원소 바꾸기를 한다? 이렇게 하는 순간 코드도 길어지고... 그 for ~ in range 루프의 메모리 잡아먹기ㅋㅋㅋ큐ㅠㅠ 정규 표현식으로 짜면 코드가 간결해집니다.
또한, 컴파일 하면 속도도 빨라지는데, 요즘 성능의 컴퓨터로는 속도가 빨라지는지 솔직히 모르겠ㅋㅋㅋ 데이터가 작아서 그런지 0.000001139 정도 빨라지는 것 같은데... 만약 데이터가 엄청나게 크다면 이 속도는 유의미하게 중요합니다.
핫하게 사용되는 영역
문자열 데이터의 처리 String Processing, 웹 스크래핑 Web Scraping, 검색 엔진 Search Engines,
사실 전반적으로 사용됩니다. 사용자 인풋 값은 string 형태일 때가 많으니 제약조건을 걸 때에도 사용하고, 서버 관련 코드 짤 때에도 리눅스 vim 등등 텍스트 대체에서도 정규표현식이 필요합니다.
정규 표현식의 단점?
한줄평: 문법이 어렵다.
일단 외계어 같습니다. 어셈블리 보다 더 모호한 것 같다는 느낌이 있습니다. 힘들게 익혀두어도 안 쓰면 잊어먹습니다.
정규 표현식은 언제부터 쓰였는지?
상당히 역사가 깊습니다.
컴퓨터의 언어의 발전이 B언어 -> C언어 -> C++ ... 컴퓨터 언어와 같이 태어났다고 보시면 됩니다. 1950년대 정도는 지금과 같은 개인용 컴퓨터는 없었고, 기업이나 국가에서만 사용되는 거대한 서버용 컴퓨터가 맨 처음 등장했습니다. 즉 UNIX 컴퓨터. UNIX에서 텍스트 처리를 할 때 사용한 개념이 정규 표현식 입니다. 특정 문자열이 들어있는 폴더나 파일을 찾아줘. 해당 내용을 찾아줘. 등 텍스트 검색할 때 사용했습니다. 그 땐 이렇게 사용자가 보기 편한 화면이 없었으니까, 텍스트 검색이 필수 였습니다. 대부분 Command Line...
이 때 두 가지 구문을 사용했는데 하나는 POSIX, 다른 하나는 Perl.
Perl 구문을 현재 Java, Javascript, Python , .NET 등에서 채택해서 사용하고 있습니다. 예를 들면 .+, .*
Python 에서 정규표현식이란?
Python에는 정규표현식 내장 라이브러리 (re)가 있습니다.
import re
data = "오늘은 비가 내립니다"
re.search("^오늘은.*니다$", data)
# 결과
# <re.Match object; span=(0, 11), match='오늘은 비가 내립니다'>
다음과 같이 re를 import해서 사용할 수 있습니다.
Python 정규표현식 함수
사용 예시들은 모두 컴파일을 하지 않은 간단한 예시입니다.
함수 | 사용 예시 | 리턴 값 |
search | # 패턴 검색 text = "The quick brown fox jumps over the lazy dog" pattern = "fox" result = re.search(pattern, text)
|
문자열에서 해당하는 객체 리턴 |
findall | # 여러 패턴 일치 text = "apple, banana, cherry" patterns = ["apple", "cherry"] results = re.findall("|".join(patterns), text)
# ['apple', 'cherry'] |
문자열에서 해당하는 텍스트 리스트로 리턴 |
sub | # 패턴 대체 text = "The quick brown fox jumps over the lazy dog"
pattern = "fox"
# The quick brown cat jumps over the lazy dog |
문자열에서 해당 문자열을 다른 문자열로 대체함 |
split |
#스페이스 기준 자르기
txt = "여기 스페인에 비가 내립니다"
x = re.split("\s", txt)
print(x)
# ['여기', '스페인에', '비가', '내립니다'] |
문자열에서 해당하는 매치를 split한 리스트를 리턴 |
함수 중, 자주 쓰는 건 finall, sub 이 두 개이다.
Python 정규표현식 메타문자(MetaCharacters)
메타문자(MetaCharacters) 는 특별한 의미를 가진 문자를 의미함.
메타문자 | 의미 | 예시 |
[] | 문자열 셋트 | [a-zA-Z] : 영어 문자 [0-9] : 숫자 [abc] : a, b, c중에 하나라도 있으면 매칭 [^abc] : a, b, c는 제외하고 알파벳 매칭 [0-5][6-9] 06, 49 등의 두 자리를 매칭 [ㄱ-ㅎ] 한글 [+] : 정말로 + 를 매칭해줌 "5+8=13" 일 경우 ['+'] 리스트로 반환 받을 수 있음 예시: (re.findall("[+]", "5+8=13") |
\ | 또다른 메타 의미가 있는 문자를 의미함 | \s : 띄어쓰기 |
. | 모든 문자를 의미 | 호.이 : 호랑이, 호랭이, 호윤이, 호돌이, 호둘이, 호순이, 호호이, 호.이, 호@이 모두 포함 |
^ | 시작하는 문자 | ^Hello : Hello로 시작하는 문자. hello는 안됨. 대소문자 구분 |
$ | 끝나는 문자 | world$ : world로 끝나는 문자. 역시 대소문자 구분 |
* | 0 이상 |
x = re.findall("he.*", "he")
x
#['he'] |
+ | 1 이상 |
x = re.findall("he.+", "hello World")
x
#['hello World'] |
? | 0 또는 1번 |
x = re.findall("he.?", "hello")
x
#['hel'] |
{} | 구체적인 숫자를 설정할 수 있음 |
x = re.findall("he.{3}", "hello")
x #['hello']
|
| | 또는 |
x = re.findall("그|그녀", "그는 현재 스페인 마드리드에 있다")
x #['그']
|
() | 그룹 |
그룹의 경우 예시는 하단에,
그룹의 바깥쪽이 먼저, 안쪽이 그 다음으로 숫자가 매겨지며.
data = '''
123456-1234567
12345-123456
123456-12345678
990313-3900000
'''
pattern = re.compile(r"((\d{6})[-]\d{7})")
result = pattern.search(data)
print(result.group(1)) # 123456-1234567
print(result.group(2)) #123456
다음과 같다.
result2 = pattern.findall(data)
# ---- result below -----
result2
[('123456-1234567', '123456'),
('123456-1234567', '123456'),
('990313-3900000', '990313')]
특수 배열 문자(Special Sequence)
메타 문자 중에 \ 라는 표시는 특수 문자를 가리킵니다.
특히 검색할 때 \speak 등으로 검색하면 \speak 을 검색하는 것이 아니라 \s를 특별 문자로 취급받아서 peak 가 검색됩니다. 이런 점을 방지하기 위해서 r'\\speak' 등으로 앞에 r이 붙으며. r은 "raw string"을 의미합니다. 즉, r'\\speak'는 '\speak' 를 검색해달라는 뜻
특수 배열문자 리스트는 하단에...
문자 | 의미 | 예시 |
\A | 문자열 시작하는 문자를 의미 | "\A그녀는" |
\b | 문자열에서 시작하거나 끝나는 단어를 의미 | x = re.findall(r"\b시장", "예전에 당나귀가 나그네와 함께 여행을 떠났었는데, 둘은 전에 시장에서") |
\B | 문자열에서 시작하거나 끝나는 단어가 아닌 것 | x = re.findall(r"\B전에", "예전에 당나귀가 나그네와 함께 여행을 떠났었는데, 둘은 전에 시장에서") x = re.findall(r"\B장에", "예전에 당나귀가 나그네와 함께 여행을 떠났었는데, 둘은 전에 시장에서") |
\d | 숫자를 의미 | x = re.findall("\d", "오늘 산 초밥은 1,5000원") # ['1', '5', '0', '0', '0'] |
\D | 숫자가 아닌 것을 의미 | x = re.findall("\D", "오늘 산 초밥은 1,5000원") # ['오', '늘', ' ', '산', ' ', '초', '밥', '은', ' ', ',', '원'] |
\s | 공백문자를 의미 | x = re.findall("\s", "오늘 산 초밥은 1,5000원") # [' ', ' ', ' '] |
\S | 공백문자가 아닌 것 |
x = re.findall("\S", "오늘 산 초밥은 1,5000원")
# ['오', '늘', '산', '초', '밥', '은', '1', ',', '5', '0', '0', '0', '원'] |
\w | 문자나 숫자를 의미 |
x = re.findall("\w", "오늘 산 초밥은 1,5000원")
# ['오', '늘', '산', '초', '밥', '은', '1', '5', '0', '0', '0', '원'] |
\W | 문자나 숫자가 아닌 것 |
x = re.findall("\W", "오늘 산 초밥은 1,5000원")
# [' ', ' ', ' ', ','] |
\Z | 문자열에서 끝나는 문자를 의미 |
x = re.findall("원\Z", "오늘 산 초밥은 1,5000원")
# ['원'] |
\\ | 일반 문자 \ 와 매칭 |
매타 문자가 아니라는 걸 표기
|
이 중에서 실제로 자주 사용하는 것은,
\d, \s, \w 가 되겠네요.
주의해야 할 건 \s .... 자꾸 string이라고 착각하게 되어서ㅋㅋㅠㅠ 헷갈리는... 공백문자...
Python 정규표현식 컴파일 및 옵션
지금까지 예제에선 정규표현식을 컴파일 한 후에 함수를 사용하는 것보다, 컴파일 없이 사용했습니다. 만약 여러 가지 텍스트를 같은 패턴으로 한 번에 컴파일을 하면 좀 더 빨라진다는 이점이 있습니다.
컴파일 옵션 (= 축약 코드) | 의미 | 예시 |
re.DOTALL (= re.S) | 줄바꿈 문자 등도 포함 |
data = '''오늘은 참 바쁜 하루였다
어제까지만 해도 주말이었는데, 눈을 떠
보니 오늘이 왔다
어제오늘내일'''
pattern = re.compile('다.어', re.S)
result = pattern.findall(data)
# ['다\n어', '다\n어'] |
re.IGNORECASE (= re.I) | 대소문자 구분 없음 |
data = '''Hello, Eveyone
It is nice to Meet You Again
Today we are going to discuss about Global Climate Change
and I want you to discuss
Any Opion is Okay Just say what you want
Is it Okay?'''
pattern = re.compile('you', re.I)
result = pattern.findall(data)
# ['You', 'you', 'you'] |
re.MULTILINE (= re.M) | ''' 문자 문자 문자'''와 같이 여러 라인이 있는 경우 고려 |
pattern = re.compile("^It\s\w+", re.M)
result = pattern.findall(data)
# ['It is'] |
re.VERBOSE (= re.X) | 정규식에 줄 단위의 주석을 달아서 만들 수 있음 |
a = re.compile(r"""\d+ # the integral part
\. # the decimal point
\d* # some fractional digits""", re.X)
result = a.findall("000.324 는 23.123 의 x축과 y축의")
|
정규표현식에서 Greedy 방법을 제어
Greedy 방법이란, 뜻 그대로 탐욕스러운 찾는 방법인데 조금이라도 맞는다면 정보를 다 긁어모으는 방법을 그리디 방법이라고 합니다.
. (마침표) -> 모든 문자를 의미
? -> 끝을 의미함
예를 들어, [1. 단어] 하는 이유는 ~ [2. 단어단어] 사회생활을 하면서 ~ [3. 단어단더] 여러 프로젝트를 하면서 ~ 와 같은 텍스트에서 [ ] 안에 있는 부분만 추출하고 싶습니다.
하지만, 그리디 방법에서는 [] 안에 것 이외에 다른 텍스트도 한꺼번에 가지고 올 수 있습니다.
[1. 단어 ] <- 닫힘은 무시하고 맨 뒤까지의 [3. 단어단더 ] <- 여기까지 멈춰서 1~3 사이의 텍스트 모두 긁어오는 게 그리디 방법입니다.
제어하기 위해서 ? 를 넣어줍니다.
text = '''
[1. 단어] 하는 이유는 ~
[2. 단어단어] 이라고 말하면서 ~
[3. 단어단더] 여러 프로젝트를 하면서 ~
'''
result = re.findall(r'\[.+?\]', text)
result #['[1. 단어]', '[2. 단어단어]', '[3. 단어단더]']
와 같이 greedy 검색을 피해 깔끔하게 열고 닫는 것을 지정할 수 있습니다.
여기 까지 정리했지만, 사실 이것보다 더 많은 옵션들.. 그리고 다른 방법들이 있습니다.
어디에서 볼 수 있냐면 python 공식 문서 입니다.
마무리
주로 python에서의 정규표현식을 정리했는데, vim도 거의 비슷합니다. 일부는 좀 다르게 짜야겠지만요! 정리하면서... 정규 표현식 컴파일 하지 않고 짠 코드들이 마음에 걸리네요(아직까진 잘 돌아갑디다...ㅋㅋㅋ) 원래 예정 일자 안에 포스팅을 올렸어야 했지만, 건강이 좋지 않아서 쉬었습니다. 환절기에 건강조심하시고, 정리한 글들이 도움이 되었으면 좋겠습니다. GoodLuck🤞
Reference
1. 정규 표현식 Group 과 관련한 이름 설정 및 참조와 관련하여 유용한 자료를 볼 수 있는 곳
2. 파이썬 정규 표현식 공식 document
https://docs.python.org/3/library/re.html#re.compile
3. 파이썬 정규표현식 참고자료
https://www.w3schools.com/python/python_regex.asp
'NLP-writing' 카테고리의 다른 글
미적분 - 경사 하강법(gradient descent) (0) | 2023.04.27 |
---|---|
Linear Algebra 선형대수학 with NLP (0) | 2023.04.09 |
3. Probability - 베이지안Bayesian (0) | 2023.03.26 |
1. 자연어 처리란 무엇일까? (0) | 2023.02.25 |
[글또] NLP writing - 일정 (0) | 2023.02.11 |