
위 이미지는 Dall-E가 생성한 이미지입니다.
https://chat.openai.com/share/88ade769-05af-484b-af1f-f2b5334119fe
갓 GPT가 프로젝트 완수율을 30분 만에 50%p정도 높여주었다.
문득 생각난 아이디어를 구현해달라고 하니 정말 놀라운 속도로 만들어주었다. 개선점은 많지만 수정마저도 chatGPT가 다 해주니 정말 편하다~ 작은 기능 하나를 추가하려고 해도 심리적인 부담감이 있어 진행 속도가 더디곤 했는데 이젠 정말 아이디어만 있으면 뭐든 만들 수 있는 세상이다. 개발자도 이전과 요구 역량이 많이 달라질 듯싶다. 이전에는 '코딩 좀 한다'의 기준이 내가 원하는 기능을 100% 구현할 줄 아는 능력이었다면 지금은 전체적인 숲을 볼 줄 아는 능력, 기획력, 툴을 활용해 만들 수 있는 기능인지를 판단하는 능력 등이 더 중요하지 않을까? 물론 chatGPT에 대한 의존도가 너무 높아지면 그것도 문제이지만, chatGPT가 다 해준다해서 코딩 실력이 퇴화한다거나 할 걱정은 아직 없다. 퇴화할 실력이 별로 없어서 그런가?..
그것도 맞지만 내가 파이썬 코드를 10줄을 짜서 구현한 내용을 한 줄로 짜는 chatGPT의 코드를 보면 참.. 배우는게 많다. 새로운 함수도 알게되고 효율적으로 코드를 작성하고 주석을 다는 방법 등 나한테 많은 것을 알려주는 고마운 스승이다.
각설하고 프로젝트 진행 상황이다.
사용자가 어떤 과정으로 서비스를 사용하는지 순서대로 정리한 것
- 사용자가 웹에서 'I want paper about cat'를 입력한다.
- 사용자가 입력한 문장에서 'cat'이라는 키워드를 추출한다.
- 'cat'과 유사한 단어를 문장유사도검색을 통해 'kitten', 'turkish angora' 등과 같이 찾아낸다.
- DB에서 'cat', 'kitten', 'turkish angora'가 포함된 데이터를 찾는다.
- 사용자에게 웹으로 출력 결과를 보여준다.
1.1. 데이터 준비:
nltk 라이브러리 같은 자연어 처리 도구를 사용할 수 있습니다.1.2. 데이터베이스 설계:
papers 테이블에 id, paper_id, sentence 컬럼을 만들 수 있습니다.1.3. 데이터 삽입:
2.1. BERT 모델 Fine-Tuning:
2.2. 유의어 추출:
3.1. 검색 쿼리 구성:
LIKE 연산자나 정규 표현식을 사용하여 유의어를 포함하는 문장을 찾을 수 있습니다.3.2. 데이터 검색:
4.1. 웹 인터페이스 개발:
4.2. 결과 표시:
chatGPT 유저라면 알겠지만 포맷까지 그대로 복사해온 것이다. 하지만 위 링크에서 볼 수 있듯 내 프로젝트 경험과 기획, 그리고 같은 팀원의 조언까지 짬뽕된 궁극의 로드맵이다.
arxive의 pubmed 데이터셋으로 kaggle에 공개되어 있는 논문 데이터셋이다.
원본데이터는 txt 파일이고 json 형식으로 되어 있었다. 왜 json으로 저장하지 않았는지는 의문이지만 꽤 번거로운 과정을 거쳐 pandas DataFrame 형식으로 변환했다.
변환 코드는 아래와 같다.
# .txt to .json
filename="test.txt"
with open(filename,"r") as f :
lines = f.readlines()
lines = [line.strip() for line in lines]
import json
dict_collection = [json.loads(line) for line in lines]
import collections
super_dict = collections.defaultdict(list)
for d in dict_collection:
for k, v in d.items(): # d.items() in Python 3+
super_dict[k].append(v)
# .json to DataFrame
df = pd.DataFrame(super_dict)
df1 = df[['article_id','article_text','abstract_text']] # 필요 열만 추출해서 저장했다.
df1.to_csv('data1.csv') # csv 파일로 저장
article_id, article_text, abstract_text만 가져왔다. 이 데이터셋에서 유일하게 아쉬운 점은 article title이 부재한다는 것이다. 물론 논문이 id로 특정되기는 하지만 자연어 title에도 포함된 정보가 꽤 많기 때문에 아쉬운 점이다. 크롤링은 이전 포스팅에서 언급한 것과 같이 사이트의 크롤링 정책 때문에 불가하다.
논문을 쓸 때 논문의 전체 내용보다는 일부분만 참조하는 경우가 많다. 그래서 embedding을 통해 논문을 검색할 때도 유사도 계산 결과 상위권의 논문들이 검색어와 전혀 관련 없는 경우가 간간이 있었다. 이런 점을 보완하기 위해 문장 단위로 한 행에 잘라 DB에 저장한 다음 쿼리를 통해 끄집어 오는 방식을 차용했다. My SQL은 나에게는 인터페이스가 조금 불편해서 SQLite3 라이브러리를 활용해 파이썬에서 DB를 생성하고 데이터를 삽입/삭제하는 방식을 채택했다.
import sqlite3
# 데이터베이스 연결 생성
conn = sqlite3.connect('papers.db')
# 커서 생성
cursor = conn.cursor()
# papers 테이블 생성
cursor.execute('''
CREATE TABLE IF NOT EXISTS papers (
paper_id TEXT PRIMARY KEY,
abstract TEXT
)
''')
# sentences 테이블 생성
cursor.execute('''
CREATE TABLE IF NOT EXISTS sentences (
sentence_id INTEGER PRIMARY KEY AUTOINCREMENT,
paper_id TEXT,
sentence_text TEXT,
FOREIGN KEY (paper_id) REFERENCES papers (paper_id)
)
''')
# 변경사항 커밋 및 연결 종료
conn.commit()
conn.close()
GodPT가 논리적 데이터베이스 설계까지 해줬다. 갓피티의 조언에 따라 두 개의 테이블(papers, sentneces)을 생성한 뒤 검색은 sentences에서 하고 결과는 papers를 통해 논문의 제목, 저자, 저널명, DOI, 발행 연도 등을 끌고올 수 있게 설계를 해줬다.
최고다 GPT쨩!! ❤️❤️❤️❤️❤️❤️
# 예시 데이터
papers_data = [
(1, '논문 제목 1', '2023-01-01'),
(2, '논문 제목 2', '2023-01-02')
]
sentences_data = [
(1, '논문 1의 첫 번째 문장입니다.'),
(1, '논문 1의 두 번째 문장입니다.'),
(2, '논문 2의 첫 번째 문장입니다.'),
(2, 'This is second sentece of paper no. 2.'),
(2, 'This is third sentence of research paper no.2.')
]
# papers 데이터
df = pd.read_csv('/data.csv')
# 'article_id' 열의 중복 제거
df_unique = df_cleaned.drop_duplicates(subset='article_id')
# senteces 데이터
def sentence_boundary_detection(text):
# 영어 문장 경계 인식기를 로드합니다
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
# 텍스트를 문장 단위로 분할합니다
return tokenizer.tokenize(text)
# 리스트 컴프리헨션을 사용하여 결과 생성
sentence_result_list = [(article_id, sentence.strip())
for article_id, text in zip(df_unique['article_id'], df_unique['article_text'])
for sentence in sentence_boundary_detection(text)]
# 데이터베이스에 데이터 삽입
conn = sqlite3.connect('papers.db')
cursor = conn.cursor()
cursor.executemany('INSERT INTO papers (paper_id, abstract) VALUES (?, ?)', result_list)
cursor.executemany('INSERT INTO sentences (paper_id, sentence_text) VALUES (?, ?)', sentence_result_list)
conn.commit()
conn.close()
예시 데이터의 형식에 맞춰서 진짜 데이터셋을 입력하기만 하면 끝이다. senteces 데이터에는 punctuation에 따라 문장 분할을 해주는 추가 작업을 해주었는데 성능이 안좋아서 빼버릴 예정이다. 최종적으로는 문장 단위가 아니라 의미적인 면에서 같은 얘기를 하고 있는 chunk 단위로
import sqlite3
# 데이터베이스 연결
conn = sqlite3.connect('papers.db')
cursor = conn.cursor()
# '논문' 키워드를 포함하는 문장 조회
keyword = '%cardi%'
cursor.execute('SELECT * FROM sentences WHERE sentence_text LIKE ?', (keyword,))
# 조회 결과 출력
sentences = cursor.fetchall()
for sentence in sentences:
print(sentence)
# 연결 종료
conn.close()
좀 더 괜찮은 예제를 넣어보고 싶었는데 그냥 '논문'이라고 넣어봤다. 결과가 너무 많이 나와서 뭔가 조치를 취해야 할 것 같다.
또 당연하게도 '논문'이라는 글자가 문자 그대로 들어가있어야 검색이 되므로 유사어, 어근추출 등의 추가 작업이 필요하다.
BERT를 fine tuning해서 내 데이터에 딱 맞는 유의어 추출기를 만들고 싶었지만 빠른 완성을 위해 nltk라이브러리를 사용했다.
# nltk 설치
!pip install nltk
# 라이브러리 임포트
import nltk
nltk.download('wordnet')
nltk.download('omw-1.4')
from nltk.corpus import wordnet as wn
def find_synonyms(word, count=5):
synonyms = set()
for syn in wn.synsets(word):
for lemma in syn.lemmas():
synonyms.add(lemma.name())
if len(synonyms) >= count:
return list(synonyms)[:count]
return list(synonyms)
# 'paper'에 대한 유의어 찾기
synonyms = find_synonyms('paper')
print(synonyms)
실행 결과는 아래와 같다.
['theme', 'report', 'newspaper', 'composition', 'paper']
최종적으로 데이터 조회 + 유의어 검색을 합쳐서 유의어 쿼리를 조회하는 코드이다.
import sqlite3
from nltk.corpus import wordnet as wn
def find_synonyms(word, count=5):
synonyms = set()
for syn in wn.synsets(word):
for lemma in syn.lemmas():
synonyms.add(lemma.name().replace('_', ' '))
if len(synonyms) >= count:
return list(synonyms)[:count]
return list(synonyms)
def find_sentences_with_synonyms(synonyms):
conn = sqlite3.connect('papers.db')
cursor = conn.cursor()
# 유의어를 포함하는 문장을 찾기 위한 쿼리 구성
query = "SELECT * FROM sentences WHERE " + " OR ".join(["sentence_text LIKE ?" for _ in synonyms])
params = ['%' + synonym + '%' for synonym in synonyms]
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
return results
# 'paper'에 대한 유의어 찾기
synonyms = find_synonyms('disease')
# 유의어를 포함하는 문장 찾기
sentences = find_sentences_with_synonyms(synonyms)
for sentence in sentences:
print(sentence)
파이썬 라이브러리인 Flask를 활용해 뚝딱 만들었다.
먼저 설치 코드이다.
pip install Flask
!는 붙여도 되고 안 붙여도 되는데 커맨드라인에 입력해야 실행되는 코드라면 !가 필수이고 pip는 ipynb자체에 입력해도 실행이 된다.
app.py라는 파일을 생성하고 다음 코드를 작성한다.
from flask import Flask, render_template, request
import sqlite3
app = Flask(__name__)
def get_search_results(keyword):
conn = sqlite3.connect('papers.db')
cursor = conn.cursor()
# 검색 쿼리 실행
query = '''
SELECT sentences.sentence_text, papers.title
FROM sentences
JOIN papers ON sentences.paper_id = papers.paper_id
WHERE sentences.sentence_text LIKE ?
'''
cursor.execute(query, ('%' + keyword + '%',))
results = cursor.fetchall()
conn.close()
return results
@app.route('/', methods=['GET', 'POST'])
def index():
search_results = []
if request.method == 'POST':
keyword = request.form['keyword']
search_results = get_search_results(keyword)
return render_template('index.html', search_results=search_results)
if __name__ == '__main__':
app.run(debug=True)
Flask 애플리케이션과 같은 디렉토리에 templates라는 폴더를 만들고, 그 안에 index.html 파일을 생성한다.
<!DOCTYPE html>
<html>
<head>
<title>논문 검색</title>
</head>
<body>
<h1>논문 검색</h1>
<form method="post">
<input type="text" name="keyword" placeholder="검색어 입력">
<button type="submit">검색</button>
</form>
<hr>
{% if search_results %}
<h2>검색 결과</h2>
<ul>
{% for sentence, title in search_results %}
<li><strong>{{ title }}</strong>: {{ sentence }}</li>
{% endfor %}
</ul>
{% else %}
<p>검색 결과가 없습니다.</p>
{% endif %}
</body>
</html>
이제 다음 명령어를 수행하면 웹이 실행된다.
!python app.py
네트워크 문제로 실행이 안된다...
이렇게 쉬울 리 없지... 아쉬운대로 갓피티가 만들어준 html 화면을 첨부한다.
streamlit을 활용하면 더욱 이쁘고 완성도 있게 만들어준다고 하니 사용해볼 예정이다.