안녕하세요, 개발자 여러분!
오늘은 스택오버플로우에서 접수된 흥미로운 NLP(자연어 처리) 문제, 즉 음성 파일에서 전사된 텍스트로부터 특정 활동과 시간 범위를 추출하는 방법에 대해 다루고자 합니다. 이는 단순히 키워드를 찾는 것을 넘어, 텍스트의 의미를 이해하고 구조화된 정보를 뽑아내는 고난이도 NLP 과제입니다. 사용자가 겪고 있는 문제와 같이, ‘나는 오전 7시 30분에 출발해서 8시에 도착했고, 8시부터 10시까지 일했다’와 같은 문장에서 ‘7시 30분부터 8시까지 이동/출발’, ‘8시부터 10시까지 일함’과 같은 정보를 추출해야 할 때, 어떤 접근 방식이 필요한지 자세히 알아보겠습니다.
1. 문제 발생 상황
문제의 핵심은 다음과 같습니다. 개발자는 Python을 사용하여 오디오 파일에서 텍스트를 전사(transcribe)했습니다. 이제 이 전사된 텍스트에서 특정 활동(예: 이동, 작업)과 그 활동이 이루어진 시간 범위를 추출해야 합니다.
예시 텍스트: “I left at seven-thirty in the morning, arrived at eight, and worked from eight to ten.”
요구되는 추출 결과:
- trip/left from seven-thirty to eight
- worked from eight to ten
사용자는 다양한 방법을 시도했지만 원하는 결과에 근접하지 못했다고 밝혔으며, 어디서부터 시작해야 할지 막막해하고 있습니다. 이는 텍스트에서 시간 정보를 포함한 복합적인 의미 단위(semantic units)를 뽑아내야 하는 전형적인 정보 추출(Information Extraction) 문제입니다.
2. 명확한 문제 원인 (왜 어려운가?)
이 문제가 단순한 문자열 검색이나 기본적인 토큰화를 넘어 복잡하게 느껴지는 이유는 다음과 같습니다.
- 자연어의 복잡성과 모호성: 시간 표현이 ‘seven-thirty’, ‘eight’, ‘ten’과 같이 다양하게 나타나며, 활동(‘left’, ‘arrived’, ‘worked’)과 직접적인 서술어-시간 형태가 아닌 경우도 많습니다. 예를 들어, ‘left at seven-thirty’와 ‘arrived at eight’는 별개의 이벤트처럼 보이지만, ‘trip/left from seven-thirty to eight’라는 하나의 활동 범위로 묶여야 합니다.
- 의미론적 이해 필요: 단순히 단어를 매칭하는 것을 넘어, 문장 내에서 동사(활동)와 시간 표현 사이의 의미적, 문법적 관계를 이해해야 합니다. ‘from X to Y’와 같은 명시적인 패턴 외에 ‘at X’와 ‘at Y’처럼 분리된 시간 정보를 결합하여 하나의 기간으로 추론해야 하는 경우도 있습니다.
- 다양한 시간 표현: ‘오전’, ‘오후’, ‘아침’, ‘저녁’ 등의 시간대 지시어나 숫자(8시), 텍스트(여덟 시) 등 시간 표현 방식이 다양할 수록 처리 로직이 복잡해집니다.
이러한 문제들을 해결하기 위해서는 기본적인 텍스트 처리 기법을 넘어, 개체명 인식(Named Entity Recognition, NER), 의존성 파싱(Dependency Parsing)과 같은 고급 NLP 기법 또는 정교한 규칙 기반 시스템이 필요합니다.
3. 해결 방법 및 코드 예시
텍스트에서 특정 활동과 시간 범위를 추출하는 데에는 여러 접근 방식이 있지만, 여기서는 실용적인 시작점이 될 수 있는 두 가지 주요 방법을 제시하고 코드 예시를 통해 설명하겠습니다.
3.1. 정규표현식(Regex)을 이용한 규칙 기반 추출
특정 패턴이 명확하게 반복되는 경우, 정규표현식은 매우 강력하고 효율적인 도구입니다. 특히 ‘from X to Y’와 같이 명확한 시간 범위 지시자가 있을 때 유용합니다.
import re
text = "I left at seven-thirty in the morning, arrived at eight, and worked from eight to ten."
# 1. "worked from X to Y" 패턴 추출
# 이 예시에서는 'worked'라는 동사와 'from ... to ...' 패턴을 직접 매칭합니다.
# 시간 표현은 'seven-thirty', 'eight', 'ten'과 같은 영어 단어나 숫자가 올 수 있으므로,
# 더 유연한 패턴(예: [\w\s-]+)을 사용합니다.
pattern_worked_time = r"(worked)\s+from\s+([\w\s-]+)\s+to\s+([\w\s-]+)"
matches_worked = re.findall(pattern_worked_time, text, re.IGNORECASE)
results = []
for match in matches_worked:
activity, start_time, end_time = match
results.append(f"{activity} from {start_time.strip()} to {end_time.strip()}")
# 2. "left at X, arrived at Y" 패턴을 통한 'trip/left' 정보 추출 (더 복잡한 추론 필요)
# 이 부분은 단순 정규표현식만으로는 어렵고, 여러 번의 매칭과 조합이 필요합니다.
# 여기서는 'left at'과 'arrived at' 패턴을 먼저 찾은 다음, 이를 연결하는 방식으로 접근합니다.
pattern_left_at = r"left\s+at\s+([\w\s-]+)"
pattern_arrived_at = r"arrived\s+at\s+([\w\s-]+)"
match_left = re.search(pattern_left_at, text, re.IGNORECASE)
match_arrived = re.search(pattern_arrived_at, text, re.IGNORECASE)
if match_left and match_arrived:
start_time_left = match_left.group(1).strip()
end_time_arrived = match_arrived.group(1).strip()
results.append(f"trip/left from {start_time_left} to {end_time_arrived}")
print("--- 정규표현식 추출 결과 ---")
for r in results:
print(r)
정규표현식의 장단점:
- 장점: 특정 패턴에 대해 빠르고 정확하며 구현이 간단합니다.
- 단점: 패턴이 불규칙하거나 복잡해지면 정규표현식 자체가 매우 복잡해지고 유지보수가 어려워집니다. 모든 가능한 시간 표현(오후 3시, 15:00, 3 PM 등)에 대응하기 어렵습니다.
3.2. SpaCy를 활용한 개체명 인식(NER) 및 의존성 파싱
더 복잡하고 유연한 정보 추출을 위해서는 SpaCy와 같은 강력한 NLP 라이브러리를 활용하는 것이 좋습니다. SpaCy는 텍스트를 토큰화하고, 품사를 태깅하며, 개체명을 인식하고, 문장의 의존성 구조를 파악하는 등 다양한 기능을 제공합니다.
import spacy
# 영어 모델 로드 (최초 실행 시 'python -m spacy download en_core_web_sm' 필요)
try:
nlp = spacy.load("en_core_web_sm")
except OSError:
print("SpaCy 'en_core_web_sm' 모델을 다운로드합니다.")
import subprocess
subprocess.run(["python", "-m", "spacy", "download", "en_core_web_sm"])
nlp = spacy.load("en_core_web_sm")
text = "I left at seven-thirty in the morning, arrived at eight, and worked from eight to ten."
doc = nlp(text)
extracted_segments = []
# 1. 시간 개체명(TIME) 추출
print("--- SpaCy 개체명(NER) 추출 결과 ---")
time_entities = [(ent.text, ent.start_char, ent.end_char) for ent in doc.ents if ent.label_ == "TIME"]
for ent_text, _, _ in time_entities:
print(f"시간 엔티티: {ent_text}")
# 2. 활동 동사 식별 및 시간 정보와 연결 (규칙 기반)
# "worked from X to Y" 패턴 처리
for token in doc:
if token.lemma_ == "work" and token.pos_ == "VERB":
# 'worked' 동사 주변에서 'from X to Y' 패턴 찾기
# 간단한 규칙: 'worked' 뒤에 'from'이 오고 그 뒤에 'to'로 연결된 두 개의 시간 엔티티를 찾습니다.
# 실제 구현에서는 의존성 파싱을 더 정교하게 활용해야 합니다.
start_time_token = None
end_time_token = None
# 'from'과 'to'를 기준으로 시간 찾기
for j in range(token.i + 1, len(doc)):
if doc[j].lower_ == "from":
# 'from' 뒤의 시간 찾기
for k in range(j + 1, len(doc)):
if doc[k].ent_type_ == "TIME":
start_time_token = doc[k]
break
elif doc[j].lower_ == "to" and start_time_token:
# 'to' 뒤의 시간 찾기
for k in range(j + 1, len(doc)):
if doc[k].ent_type_ == "TIME":
end_time_token = doc[k]
break
if start_time_token and end_time_token:
break
if start_time_token and end_time_token:
extracted_segments.append(f"worked from {start_time_token.text} to {end_time_token.text}")
# "left at X, arrived at Y" 패턴 처리
# 'left'와 'arrived' 동사를 찾고, 각각에 연결된 'at' 전치사를 통해 시간을 추출합니다.
# 그리고 이 두 시간 정보를 결합합니다.
left_time = None
arrived_time = None
for token in doc:
if token.lemma_ == "leave" and token.pos_ == "VERB": # 'left'의 원형은 'leave'
# 'at' 전치사를 찾아 시간 엔티티와 연결
for child in token.children:
if child.lower_ == "at":
for grandchild in child.children:
if grandchild.ent_type_ == "TIME":
left_time = grandchild.text
break
if left_time: break # 시간을 찾았으면 다음 토큰으로 넘어감
if token.lemma_ == "arrive" and token.pos_ == "VERB":
for child in token.children:
if child.lower_ == "at":
for grandchild in child.children:
if grandchild.ent_type_ == "TIME":
arrived_time = grandchild.text
break
if arrived_time: break # 시간을 찾았으면 다음 토큰으로 넘어감
if left_time and arrived_time:
extracted_segments.append(f"trip/left from {left_time} to {arrived_time}")
print("\n--- SpaCy 기반 추출 결과 ---")
for segment in extracted_segments:
print(segment)
SpaCy 활용의 장단점:
- 장점: 텍스트의 구조와 의미를 더 깊이 이해하여 유연하고 견고한 추출이 가능합니다. 다양한 시간 표현과 동사를 처리하는 데 유리합니다.
- 단점: 초기 학습 및 구현이 정규표현식보다 복잡할 수 있습니다. 특히 복잡한 논리나 추론이 필요한 경우, 규칙을 정교하게 설계하거나 커스텀 NER 모델을 훈련해야 할 수도 있습니다.
4. 향후 예방 및 활용을 위한 팁
이러한 정보 추출 시스템을 더욱 견고하고 확장 가능하게 만들려면 다음 팁들을 고려해 볼 수 있습니다.
- 규칙 기반과 머신러닝의 결합: 간단하고 명확한 패턴은 정규표현식으로 처리하고, 복잡하거나 모호한 패턴은 SpaCy의 규칙 기반 매처(Rule-based Matcher)나 커스텀 NER 모델, 또는 더 나아가 딥러닝 기반의 정보 추출 모델(예: BERT, GPT 등의 파인튜닝)을 활용하는 하이브리드 접근 방식을 고려하세요.
- 데이터 증강 및 레이블링: 추출하고자 하는 패턴이 다양하고 복잡하다면, 실제 데이터에 대한 충분한 레이블링 작업을 통해 감독 학습(Supervised Learning) 모델을 훈련하는 것이 장기적으로 더 나은 성능을 보장합니다.
- 도메인 특화: 특정 도메인(예: 회의록, 의료 기록 등)에서만 사용되는 특수한 시간 표현이나 활동 동사가 있다면, 해당 도메인에 특화된 모델이나 규칙을 개발하여 정확도를 높일 수 있습니다.
- 지속적인 평가 및 개선: 추출된 결과를 정기적으로 평가하고, 시스템의 약점을 파악하여 규칙을 개선하거나 모델을 재훈련하는 과정을 반복해야 합니다.
텍스트에서 의미 있는 정보를 추출하는 것은 NLP의 핵심 과제 중 하나입니다. 제시된 방법들을 바탕으로 여러분의 프로젝트에 맞는 최적의 솔루션을 찾아 성공적으로 구현하시길 바랍니다!
![[에러 해결] [Tkinter] Label 위젯의 텍스트 오버플로우 및 글자수 제한 방법 Tkinter 계산기 Label 텍스트 오버플로우 이미지](https://i.sstatic.net/0brSWl1C.png)
![[에러 해결] [Tkinter ScrolledText] 모든 텍스트 삭제 시 폰트 크기 축소 문제 해결 방법 [에러 해결] [Tkinter ScrolledText] 모든 텍스트 삭제 시 폰트 크기 축소 문제 해결 방법](https://dev-error.com/wp-content/plugins/contextual-related-posts/default.png)