data preprocessing codes
Browse files
ai_hub_CC_QA_data_preprocessing_multithread_for_hf.py
ADDED
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from tqdm import tqdm
|
2 |
+
import os
|
3 |
+
import pandas as pd
|
4 |
+
import json
|
5 |
+
import time
|
6 |
+
import re
|
7 |
+
import threading
|
8 |
+
from concurrent.futures import ThreadPoolExecutor
|
9 |
+
from transformers import AutoTokenizer
|
10 |
+
import torch
|
11 |
+
from enum import Enum
|
12 |
+
import traceback
|
13 |
+
# ignore warning
|
14 |
+
import warnings
|
15 |
+
warnings.filterwarnings("ignore")
|
16 |
+
os.environ['HF_TOKEN'] = "YOUR HUGGINGFACE ACCESS TOKEN"
|
17 |
+
os.environ["HUGGINGFACEHUB_API_TOKEN"] = "YOUR HUGGINGFACE ACCESS TOKEN"
|
18 |
+
|
19 |
+
''' directory tree
|
20 |
+
.root
|
21 |
+
├── Training
|
22 |
+
│ ├── cs
|
23 |
+
│ ├── dasan
|
24 |
+
│ ├── finance
|
25 |
+
│ └── health
|
26 |
+
└── Validation
|
27 |
+
├── cs
|
28 |
+
├── dasan
|
29 |
+
├── finance
|
30 |
+
└── health
|
31 |
+
'''
|
32 |
+
|
33 |
+
root_dir = os.path.join('SET YOUR DATA DIR PATH TO root')
|
34 |
+
is_train = "Training" # Set to Validation if you want to preprocess validation data
|
35 |
+
|
36 |
+
## 데이터 종류 설정
|
37 |
+
# cs (고객 상담), dasan (다산콜센터), finance (금융/보험), health (질병관리본부)
|
38 |
+
category = ['dasan', 'finance', 'health', 'cs']
|
39 |
+
|
40 |
+
filedirpaths = []
|
41 |
+
for c in category:
|
42 |
+
filedirpaths.append(os.path.join(root_dir, is_train, c))
|
43 |
+
filepaths = []
|
44 |
+
for fdp in filedirpaths:
|
45 |
+
filenames = os.listdir(fdp)
|
46 |
+
for filename in filenames:
|
47 |
+
filepaths.append(os.path.join(fdp,filename))
|
48 |
+
filepaths = list(filter(lambda x:'.json' in x, filepaths)) # json 파일만 추출
|
49 |
+
|
50 |
+
# 전처리된 파일들을 chat-template-formatting하여 데이터프레임으로 저장
|
51 |
+
ddf_columns = ['category','seq_id','dialogue']
|
52 |
+
dialogue_df = pd.DataFrame(
|
53 |
+
data=[['' for _ in ddf_columns]],
|
54 |
+
columns=ddf_columns
|
55 |
+
)
|
56 |
+
dialogue_df_lock = threading.Lock() # 멀티 스레딩 시, 해당 객체로의 동시 접근을 방지
|
57 |
+
|
58 |
+
def data_preprocessing(filepath):
|
59 |
+
ddf_columns = ['category','seq_id','dialogue']
|
60 |
+
with open(os.path.join('SET PATH of short_sentences.json'), 'r', encoding='utf-8') as f:
|
61 |
+
shorts_to_filter = json.load(f)
|
62 |
+
|
63 |
+
def set_private_token(text):
|
64 |
+
# 정규 표현식 패턴: 2개 이상 반복되는 'x', 'X', 'o', 'O', '0', '-', 'ㅇ'
|
65 |
+
pattern = r"(x{2,}|X{2,}|o{2,}|O{2,}|0{2,}|\-{2,}|ㅇ{2,})"
|
66 |
+
# 해당 패턴을 '<|private|>'로 대체
|
67 |
+
return re.sub(pattern, '<|private|>', text)
|
68 |
+
|
69 |
+
def merge_continuous_role(conversation):
|
70 |
+
merged_conversation = []
|
71 |
+
current_entry = None
|
72 |
+
|
73 |
+
for entry in conversation:
|
74 |
+
# 현재 entry가 비어있으면 새로운 entry로 초기화
|
75 |
+
if current_entry is None:
|
76 |
+
current_entry = entry
|
77 |
+
# 이전 entry와 role이 같으면 content를 합침
|
78 |
+
elif current_entry['role'] == entry['role']:
|
79 |
+
current_entry['content'] += ' ' + entry['content']
|
80 |
+
# role이 다르면, 현재까지의 entry를 결과에 추가하고 새로운 entry로 초기화
|
81 |
+
else:
|
82 |
+
merged_conversation.append(current_entry)
|
83 |
+
current_entry = entry
|
84 |
+
|
85 |
+
# 마지막 entry 추가
|
86 |
+
if current_entry is not None:
|
87 |
+
merged_conversation.append(current_entry)
|
88 |
+
return merged_conversation
|
89 |
+
|
90 |
+
try:
|
91 |
+
# print(filepath)
|
92 |
+
global dialogue_df
|
93 |
+
|
94 |
+
tokenizer = AutoTokenizer.from_pretrained("song9/CC-KuLLM3-LoRA")
|
95 |
+
savepath = os.path.join(f"{filepath.split('.json')[0]}.tsv")
|
96 |
+
columns = ['domain','category','id','speaker','sequence','cust_intent','couns_intent','QA','cust_q','couns_q','cust_a','couns_a','entities','dictionary','knowlegde_base']
|
97 |
+
|
98 |
+
### 아래 if문에서 else문의 과정(json파일들을 읽어서 하나의 dataframe으로 결합하는 과정)이 가장 시간이 오래 걸린다.
|
99 |
+
# # else문은 대략 8시간이 소요된다. 한 번만 dataframe을 저장하면 아래 로직들은 금방 끝난다.
|
100 |
+
if os.path.exists(savepath): # 이미 dialogue json을 tsv파일로 변환한게 있으면 그 파일을 사용
|
101 |
+
df = pd.read_csv(savepath, sep='\t')
|
102 |
+
# print(f"✅ {savepath} tsv file read")
|
103 |
+
else: # json 파일을 dataFrame으로 파싱하는 과정
|
104 |
+
df = pd.DataFrame(data=[['0' for _ in range(len(columns))]],columns=columns)
|
105 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
106 |
+
data = json.load(f)
|
107 |
+
for d in tqdm(data):
|
108 |
+
df = pd.concat([df, pd.DataFrame(data=[d.values()], columns=columns)], axis=0)
|
109 |
+
df = df.iloc[1:]
|
110 |
+
df.reset_index(inplace=True, drop=True)
|
111 |
+
|
112 |
+
df.to_csv(savepath, sep='\t', index=False)
|
113 |
+
# print(f"✅ {savepath} tsv file saved")
|
114 |
+
|
115 |
+
# Preprocessing
|
116 |
+
# sequence가 1인 row는 제거
|
117 |
+
# sequence 컬럼을 int로 변환
|
118 |
+
df['sequence'] = df['sequence'].astype(int)
|
119 |
+
# sequence가 1인 대화 id -> 아래서 확인하는 걸로 변경
|
120 |
+
# one_seq_id = pd.DataFrame(df.groupby('id')['sequence'].max()).query('id == 1').reset_index()['id'].tolist()
|
121 |
+
# df = df[~df['id'].isin(one_seq_id)]
|
122 |
+
|
123 |
+
temp_dialogue_df = pd.DataFrame(
|
124 |
+
data=[['' for _ in ddf_columns]],
|
125 |
+
columns=ddf_columns
|
126 |
+
)
|
127 |
+
|
128 |
+
# system_prompt = '당신은 고객의 전화에 응대하는 전화 상담원입니다.\n 당신은 비도덕적이거나, 성적이거나, 불법적이거나 또는 사회 통념적으로 허용되지 않는 발언은 하지 않습니다.\n 고객에게 친절하게 대화하며, 고객의 응답에 가능한 정확하고 예의 바르게 응답함으로써 최대한 도와주려고 노력합니다.\n 고객의 질문을 이해하지 못했다면, 어떤 부분을 이해하지 못했는지 설명하고 고객에게 구체적인 질문을 요구합니다.\n 당신은 고객과 전화로 소통하기 때문에 답변이 간결해야 합니다. 거짓 정보를 발언하지 않도록 주의합니다.'
|
129 |
+
# sys_pattern = r'<<SYS>>\n.*\n<</SYS>>'
|
130 |
+
|
131 |
+
# 여러 대화들이 하나의 dataframe에 섞여있는데 이를 분리하여 각각의 json파일로 저장
|
132 |
+
# 모든 대화들을 chat dataset formatting하여 json 파일로 저장.
|
133 |
+
# 같은 시퀀스 id별로 df_prep를 나눠 하나의 대화.json파일로 저장
|
134 |
+
|
135 |
+
for seq_id in tqdm(df['id'].unique()):
|
136 |
+
# print(seq_id)
|
137 |
+
sequence_df = df[df['id']==seq_id] # 같은 seq_id의 대화들만 있는 dataframe
|
138 |
+
if len(sequence_df) < 9: # 고객-상담원 turn이 5번 이상인 대화들만 처리
|
139 |
+
continue
|
140 |
+
# print(sequence_df[['cust_q','couns_q','cust_a','couns_a']])
|
141 |
+
seq_category = os.path.dirname(filepath).split('/')[-1] # 해당 대화의 카테고리 (cs, finance, health, dasan)
|
142 |
+
category_dirpath = os.path.join(root_dir,is_train,f'{seq_category}-preprocessed')
|
143 |
+
if not os.path.exists(category_dirpath):
|
144 |
+
os.mkdir(category_dirpath)
|
145 |
+
savepath_processed_json = os.path.join(category_dirpath,f'{seq_category}_{seq_id}.json') # 대화 1개별로 1개의 json파일에 저장할 예정
|
146 |
+
# if os.path.exists(savepath_processed_json):
|
147 |
+
# with open(savepath_processed_json, 'r', encoding='utf-8') as f:
|
148 |
+
# dialogue = json.load(f)
|
149 |
+
# # print(f"✅ {savepath_processed_json} json file read")
|
150 |
+
# else:
|
151 |
+
# 혹시 모르니 sequence_df를 'sequence'컬럼을 기준으로 오름차순 정렬해줌.
|
152 |
+
sequence_df = sequence_df.sort_values('sequence', ascending=True)
|
153 |
+
sequence_df.reset_index(inplace=True, drop=True)
|
154 |
+
dialogue = []
|
155 |
+
past_role = ''
|
156 |
+
# dialogue = dialogue + [{'role':'system', 'content':'당신은 "위드마인드"에서 만든 "전화 상담원"입니다.\n 당신은 비도덕적이거나, 성적이거나, 불법적이거나 또는 사회 통념적으로 허용되지 않는 발언은 하지 않습니다.\n 고객에게 친절하게 대화하며, 고객의 응답에 가능한 정확하고 예의 바르게 응답함으로써 최대한 도와주려고 노력합니다.\n 고객의 질문을 이해하지 못했다면, 어떤 부분을 이해하지 못했는지 설명하고 고객에게 구체적인 질문을 요구합니다.\n 당신은 고객과 전화로 소통하기 때문에 답변이 간결해야 합니다. 거짓 정보를 발언하지 않도록 주의합니다.'}]
|
157 |
+
for i, row in sequence_df.iterrows():
|
158 |
+
# print(f"{i}행, {list(filter(lambda x: not pd.isna(x), sequence_df.loc[i,['cust_q','cust_a','couns_q','couns_a']].tolist()))[0][:10]}")
|
159 |
+
if row['speaker'] == '상담사':
|
160 |
+
role = 'assistant'
|
161 |
+
else:
|
162 |
+
role = 'user'
|
163 |
+
# print(f"role = {role}, past_role = {past_role}")
|
164 |
+
c = ''.join(filter(lambda x: not pd.isna(x), sequence_df.loc[i,['cust_q','cust_a','couns_q','couns_a']].tolist())).strip()
|
165 |
+
c = set_private_token(c)
|
166 |
+
if past_role == role: # 이전 발화자와 현재 발화자가 같으면 하나의 발화로 연결
|
167 |
+
# print(f"seq_id={i} : {role}이 2번 말함. <|interjection|> 토큰 추가")
|
168 |
+
# dialogue = dialogue + [{'role':"assistant" if role=="user" else "user", 'content':"<|interjection|>"}]
|
169 |
+
# 같은 발화자가 2번 연속 말하면 이전의 발화에 이어붙임
|
170 |
+
dialogue[-1]['content'] = dialogue[-1]['content'] + ' ' + c
|
171 |
+
past_role = role
|
172 |
+
continue
|
173 |
+
past_role = role
|
174 |
+
dialogue = dialogue + [{'role':role, 'content':c}]
|
175 |
+
# 만약 대화가 gpt(counselor)부터 시작한다면 human부터 시작하도록 해야 한다.
|
176 |
+
if dialogue[0].get('role') == 'assistant':
|
177 |
+
dialogue.insert(0,{'role':'user', 'content':'<|startofcall|>'})
|
178 |
+
else: # 고객이 대화 시작이어도 startofcall 토큰 추가
|
179 |
+
dialogue[0]['content'] = '<|startofcall|>' + dialogue[0]['content']
|
180 |
+
# 만약 마지막 대화가 고객이라면 고객의 발화를 제거한다.
|
181 |
+
if dialogue[-1].get('role') == 'user':
|
182 |
+
dialogue = dialogue[:-1]
|
183 |
+
|
184 |
+
# 고객, 상담원의 대화에서 길이가 9 미만이고 & short_sentence 구문이 포함되어 있으면 해당 대화는 제거함.
|
185 |
+
is_short = False
|
186 |
+
new_dialogue = []
|
187 |
+
dialogue_copy = dialogue.copy()
|
188 |
+
for i, turn in enumerate(dialogue_copy):
|
189 |
+
if i < 2:
|
190 |
+
new_dialogue.append(turn)
|
191 |
+
continue
|
192 |
+
cur_role = turn['role']
|
193 |
+
content = turn['content']
|
194 |
+
if i == len(dialogue_copy)-2:
|
195 |
+
new_dialogue.append(turn)
|
196 |
+
continue
|
197 |
+
if sum([x in content for x in shorts_to_filter]) > 0 and len(content)<9:
|
198 |
+
# print(content , '- is short')
|
199 |
+
is_short = True
|
200 |
+
continue
|
201 |
+
if is_short:
|
202 |
+
is_short = False
|
203 |
+
if cur_role != new_dialogue[-1]['role']:
|
204 |
+
new_dialogue.append(turn)
|
205 |
+
else:
|
206 |
+
new_dialogue[-1]['content'] += " " + content
|
207 |
+
else:
|
208 |
+
if cur_role != new_dialogue[-1]['role']:
|
209 |
+
new_dialogue.append(turn)
|
210 |
+
else:
|
211 |
+
new_dialogue[-1]['content'] += " " + content
|
212 |
+
|
213 |
+
new_dialogue = merge_continuous_role(new_dialogue) # 연속되는 role이 있는지 재확인한다.
|
214 |
+
|
215 |
+
dialogue = [
|
216 |
+
{
|
217 |
+
'dialogue':new_dialogue
|
218 |
+
}
|
219 |
+
]
|
220 |
+
|
221 |
+
with open(savepath_processed_json, 'w', encoding='utf-8') as f:
|
222 |
+
json.dump(dialogue, f, ensure_ascii=False)
|
223 |
+
# print(f"✅ {savepath_processed_json} json file saved")
|
224 |
+
|
225 |
+
# dialogue를 chat-template formatting 하여 dialogue_df에 저장
|
226 |
+
chat_template_formatted_dialogue = tokenizer.apply_chat_template(dialogue[0]['dialogue'], tokenize=False, )
|
227 |
+
# 맨 앞의 bos 토큰은 제거해준다.
|
228 |
+
# chat_template_formatted_dialogue = chat_template_formatted_dialogue[3:]
|
229 |
+
# chat_template_formatted_dialogue = re.sub(sys_pattern, f'<<SYS>>\n{system_prompt}\n<</SYS>>', chat_template_formatted_dialogue)
|
230 |
+
temp_dialogue_df = pd.concat([temp_dialogue_df, pd.DataFrame(
|
231 |
+
data=[[seq_category, seq_id, chat_template_formatted_dialogue]],
|
232 |
+
columns=ddf_columns
|
233 |
+
)])
|
234 |
+
|
235 |
+
# print(f"✅ seq_id별 json file saved")
|
236 |
+
temp_dialogue_df = temp_dialogue_df.iloc[1:]
|
237 |
+
if not os.path.exists(os.path.join(root_dir,is_train,'temp_tsvs')):
|
238 |
+
os.mkdir(os.path.join(root_dir,is_train,'temp_tsvs'))
|
239 |
+
tddf_savepath = os.path.join(root_dir,is_train,'temp_tsvs',os.path.basename(filepath).replace('.json','.tsv'))
|
240 |
+
temp_dialogue_df.to_csv(tddf_savepath, sep='\t', index=False)
|
241 |
+
# print(f"✅ {tddf_savepath} tsv file saved")
|
242 |
+
# Lock을 사용하여 dialogue_df에 안전하게 접근
|
243 |
+
with dialogue_df_lock:
|
244 |
+
dialogue_df = pd.concat([dialogue_df, temp_dialogue_df], axis=0)
|
245 |
+
except Exception as e:
|
246 |
+
print(f"에러 타입: {type(e).__name__}") # 예외의 타입
|
247 |
+
print(f"에러 메시지: {e}") # 예외 메시지
|
248 |
+
traceback.print_exc() # 전체 스택 트레이스 출력
|
249 |
+
|
250 |
+
# 멀티 쓰레딩을 사용하여 파일 처리
|
251 |
+
with ThreadPoolExecutor(max_workers=15) as executor: # 최대 스레드 수를 적절히 설정
|
252 |
+
executor.map(data_preprocessing, filepaths)
|
253 |
+
|
254 |
+
dialogue_df = dialogue_df.iloc[1:]
|
255 |
+
savepath_dialogue_df = os.path.join(root_dir, is_train, 'train_no_short.tsv')
|
256 |
+
dialogue_df.to_csv(savepath_dialogue_df, sep='\t', index=False)
|
257 |
+
print('✅dialogue_df 저장 완료')
|
ai_hub_multi_subject_conversations_data_preprocessing_multithread_for_hf.py
ADDED
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from tqdm import tqdm
|
2 |
+
import os
|
3 |
+
import pandas as pd
|
4 |
+
import json
|
5 |
+
import time
|
6 |
+
import re
|
7 |
+
import threading
|
8 |
+
from concurrent.futures import ThreadPoolExecutor
|
9 |
+
from transformers import AutoTokenizer
|
10 |
+
import torch
|
11 |
+
from enum import Enum
|
12 |
+
import traceback
|
13 |
+
# ignore warning
|
14 |
+
import warnings
|
15 |
+
warnings.filterwarnings("ignore")
|
16 |
+
os.environ['HF_TOKEN'] = "SET YOUR HUGGINGFACE ACCESS TOKEN"
|
17 |
+
os.environ["HUGGINGFACEHUB_API_TOKEN"] = "SET YOUR HUGGINGFACE ACCESS TOKEN"
|
18 |
+
|
19 |
+
is_train = "Training" # set to "Validation" if you want to preprocess validation data
|
20 |
+
root_dir = os.path.join('SET TO YOUR root PATH',is_train)
|
21 |
+
|
22 |
+
''' directory tree
|
23 |
+
.root
|
24 |
+
├── Training
|
25 |
+
│ ├── TL_1_shopping
|
26 |
+
│ │ ├── 01AS문의
|
27 |
+
│ │ │ ├─── shopping1_0601.json
|
28 |
+
│ │ │ ├─── shopping1_0602.json
|
29 |
+
│ │ │ └─── ...
|
30 |
+
│ │ ├── 02제품사용문의
|
31 |
+
│ │ ├── 03주문결제
|
32 |
+
│ │ ├── 04배송
|
33 |
+
│ │ ├── 05환불반품교환
|
34 |
+
│ │ ├── 06이벤트
|
35 |
+
│ │ └── 07온오프라인안내
|
36 |
+
│ ├── TL_2_civil_complaint
|
37 |
+
│ │ └── 04민원신고
|
38 |
+
│ └── TL_3_education
|
39 |
+
│ └── 03비용환불문의
|
40 |
+
└── Validation
|
41 |
+
├── VL_1_shopping
|
42 |
+
│ ├── 01AS문의
|
43 |
+
│ ├── 02제품사용문의
|
44 |
+
│ ├── 03주문결제
|
45 |
+
│ ├── 04배송
|
46 |
+
│ ├── 05환불반품교환
|
47 |
+
│ ├── 06이벤트
|
48 |
+
│ └── 07온오프라인안내
|
49 |
+
├── VL_2_civil_complaint
|
50 |
+
│ └── 04민원신고
|
51 |
+
└── VL_3_education
|
52 |
+
└── 03비용환불문의
|
53 |
+
'''
|
54 |
+
category = ['shopping','civil_complaint','education']
|
55 |
+
category_dir_names = []
|
56 |
+
if is_train=='Training':
|
57 |
+
for i,c in enumerate(category):
|
58 |
+
category_dir_names.append(f"TL_{i+1}_{c}")
|
59 |
+
else:
|
60 |
+
for i,c in enumerate(category):
|
61 |
+
category_dir_names.append(f"VL_{i+1}_{c}")
|
62 |
+
|
63 |
+
if not os.path.exists(os.path.join(root_dir, 'preprocessed')):
|
64 |
+
os.mkdir(os.path.join(root_dir, 'preprocessed'))
|
65 |
+
sub_category_dirpaths = []
|
66 |
+
for cdn in category_dir_names:
|
67 |
+
tmp = [os.path.join(root_dir, cdn, x) for x in os.listdir(os.path.join(root_dir,cdn))]
|
68 |
+
tmp_preprocessed = list(map(lambda x : os.path.join(root_dir,'preprocessed',os.path.basename(x)),tmp))
|
69 |
+
sub_category_dirpaths += tmp
|
70 |
+
for dirpath in tmp_preprocessed:
|
71 |
+
if not os.path.exists(dirpath):
|
72 |
+
os.mkdir(dirpath)
|
73 |
+
dialogue_df_cols=[
|
74 |
+
'seq_id', # 대화의 고유 id
|
75 |
+
'size', # 대화의 총 글자수
|
76 |
+
'category', # 대화의 주제
|
77 |
+
'sequence', # 발화 개수
|
78 |
+
'dialogue', # 대화 텍스트
|
79 |
+
]
|
80 |
+
dialogue_df = pd.DataFrame(
|
81 |
+
data=[["" for _ in dialogue_df_cols]],
|
82 |
+
columns=dialogue_df_cols
|
83 |
+
)
|
84 |
+
dialogue_df_lock = threading.Lock()
|
85 |
+
error_files = []
|
86 |
+
error_files_lock = threading.Lock()
|
87 |
+
def data_preprocessing(sub_category_dirpath):
|
88 |
+
try:
|
89 |
+
global error_files
|
90 |
+
global dialogue_df
|
91 |
+
global root_dir
|
92 |
+
is_train = 'Training'
|
93 |
+
sub_category_preprocessed_dir = os.path.join(root_dir, 'preprocessed', os.path.basename(sub_category_dirpath))
|
94 |
+
tokenizer = AutoTokenizer.from_pretrained("song9/CC-KuLLM3-LoRA")
|
95 |
+
dialogue_df_cols=[
|
96 |
+
'seq_id', # 대화의 고유 id
|
97 |
+
'size', # 대화의 총 글자수
|
98 |
+
'category', # 대화의 주제
|
99 |
+
'sequence', # 발화 개수
|
100 |
+
'dialogue', # 대화 텍스트
|
101 |
+
]
|
102 |
+
with open(os.path.join('SET PATH of short_sentences.json'), 'r', encoding='utf-8') as f:
|
103 |
+
shorts_to_filter = json.load(f)
|
104 |
+
|
105 |
+
role_dict = {'A.':'assistant','B.':'user', 'A':'assistant','B':'user'}
|
106 |
+
def set_private_token(text):
|
107 |
+
return re.sub(r'#@.*?#','<|private|>',text)
|
108 |
+
|
109 |
+
def merge_continuous_role(conversation):
|
110 |
+
merged_conversation = []
|
111 |
+
current_entry = None
|
112 |
+
|
113 |
+
for entry in conversation:
|
114 |
+
# 현재 entry가 비어있으면 새로운 entry로 초기화
|
115 |
+
if current_entry is None:
|
116 |
+
current_entry = entry
|
117 |
+
# 이전 entry와 role이 같으면 content를 합침
|
118 |
+
elif current_entry['role'] == entry['role']:
|
119 |
+
current_entry['content'] += ' ' + entry['content']
|
120 |
+
# role이 다르면, 현재까지의 entry를 결과에 추가하고 새로운 entry로 초기화
|
121 |
+
else:
|
122 |
+
merged_conversation.append(current_entry)
|
123 |
+
current_entry = entry
|
124 |
+
|
125 |
+
# 마지막 entry 추가
|
126 |
+
if current_entry is not None:
|
127 |
+
merged_conversation.append(current_entry)
|
128 |
+
return merged_conversation
|
129 |
+
|
130 |
+
temp_dialogue_df = pd.DataFrame(
|
131 |
+
data=[['' for _ in dialogue_df_cols]],
|
132 |
+
columns=dialogue_df_cols
|
133 |
+
)
|
134 |
+
json_decoding_error_files = []
|
135 |
+
for i,filename in tqdm(enumerate(os.listdir(sub_category_dirpath))):
|
136 |
+
filepath = os.path.join(sub_category_dirpath, filename)
|
137 |
+
try:
|
138 |
+
with open(filepath, 'r', encoding='utf-8') as f:
|
139 |
+
data = json.load(f)
|
140 |
+
except json.decoder.JSONDecodeError as e:
|
141 |
+
# print(filepath)
|
142 |
+
json_decoding_error_files.append(filepath)
|
143 |
+
traceback.print_exc()
|
144 |
+
continue
|
145 |
+
seq_id = data['info'][0]['id']
|
146 |
+
file_size = data['info'][0]['size']
|
147 |
+
data_annotations = data['info'][0]['annotations']
|
148 |
+
if data_annotations['speaker_type'] != '1:1':
|
149 |
+
# print("spearker type이 1:1이 아님")
|
150 |
+
continue
|
151 |
+
seq_category = data_annotations['subject']
|
152 |
+
seq_category = seq_category.replace('/', '_')
|
153 |
+
seq_length = len(data_annotations['lines'])
|
154 |
+
dialogue = []
|
155 |
+
past_role = ''
|
156 |
+
for line in data_annotations['lines']:
|
157 |
+
role = line['text'][:1]
|
158 |
+
role = role_dict[role]
|
159 |
+
c = line['text'][1:] # A , B 는 제거
|
160 |
+
if c.startswith('.'):
|
161 |
+
c = c[1:]
|
162 |
+
c = set_private_token(c)
|
163 |
+
if past_role == role:
|
164 |
+
dialogue[-1]['content'] = dialogue[-1]['content'] + ' ' + c
|
165 |
+
past_role = role
|
166 |
+
continue
|
167 |
+
past_role = role
|
168 |
+
dialogue = dialogue + [{'role':role,'content':c}]
|
169 |
+
if dialogue[0].get('role') == 'assistant':
|
170 |
+
dialogue.insert(0, {'role':'user','content':'<|startofcall|>'})
|
171 |
+
else:
|
172 |
+
dialogue[0]['content'] = '<|startofcall|>' + dialogue[0]['content']
|
173 |
+
if dialogue[-1].get('role') == 'user':
|
174 |
+
dialogue = dialogue[:-1]
|
175 |
+
is_short = False
|
176 |
+
new_dialogue = []
|
177 |
+
dialogue_copy = dialogue.copy()
|
178 |
+
for i, turn in enumerate(dialogue_copy):
|
179 |
+
if i < 2:
|
180 |
+
new_dialogue.append(turn)
|
181 |
+
continue
|
182 |
+
cur_role = turn['role']
|
183 |
+
content = turn['content']
|
184 |
+
if i == len(dialogue_copy)-2:
|
185 |
+
new_dialogue.append(turn)
|
186 |
+
continue
|
187 |
+
if sum([x in content for x in shorts_to_filter]) > 0 and len(content)<9:
|
188 |
+
# print(content , '- is short')
|
189 |
+
is_short = True
|
190 |
+
continue
|
191 |
+
if is_short:
|
192 |
+
is_short = False
|
193 |
+
if cur_role != new_dialogue[-1]['role']:
|
194 |
+
new_dialogue.append(turn)
|
195 |
+
else:
|
196 |
+
new_dialogue[-1]['content'] += " " + content
|
197 |
+
else:
|
198 |
+
if cur_role != new_dialogue[-1]['role']:
|
199 |
+
new_dialogue.append(turn)
|
200 |
+
else:
|
201 |
+
new_dialogue[-1]['content'] += " " + content
|
202 |
+
|
203 |
+
new_dialogue = merge_continuous_role(new_dialogue) # 연속되는 role이 있는지 재확인한다.
|
204 |
+
|
205 |
+
dialogue = [
|
206 |
+
{
|
207 |
+
'dialogue':new_dialogue
|
208 |
+
}
|
209 |
+
]
|
210 |
+
|
211 |
+
# 1개 file 완료.
|
212 |
+
savepath_processed_json = os.path.join(sub_category_preprocessed_dir,f'{seq_category}_{seq_id}.json')
|
213 |
+
with open(savepath_processed_json, 'w', encoding='utf-8') as f:
|
214 |
+
json.dump(dialogue, f, ensure_ascii=False)
|
215 |
+
# print(f"✅ {savepath_processed_json} json file saved")
|
216 |
+
|
217 |
+
# dialogue를 chat-template formatting 하여 dialogue_df에 저장
|
218 |
+
chat_template_formatted_dialogue = tokenizer.apply_chat_template(dialogue[0]['dialogue'], tokenize=False)
|
219 |
+
# 맨 앞의 bos 토큰은 제거해준다.
|
220 |
+
chat_template_formatted_dialogue = chat_template_formatted_dialogue[3:]
|
221 |
+
# chat_template_formatted_dialogue = re.sub(sys_pattern, f'<<SYS>>\n{system_prompt}\n<</SYS>>', chat_template_formatted_dialogue)
|
222 |
+
temp_dialogue_df = pd.concat([temp_dialogue_df, pd.DataFrame(
|
223 |
+
data=[[seq_id, file_size, seq_category, seq_length, chat_template_formatted_dialogue]],
|
224 |
+
columns=dialogue_df_cols
|
225 |
+
)])
|
226 |
+
temp_dialogue_df = temp_dialogue_df.iloc[1:]
|
227 |
+
if not os.path.exists(os.path.join(root_dir,'temp_tsvs')):
|
228 |
+
os.mkdir(os.path.join(root_dir,'temp_tsvs'))
|
229 |
+
tddf_savepath = os.path.join(root_dir,'temp_tsvs',os.path.basename(sub_category_dirpath) + '.tsv')
|
230 |
+
temp_dialogue_df.to_csv(tddf_savepath, sep='\t', index=False)
|
231 |
+
# print(f"✅ {tddf_savepath} tsv file saved")
|
232 |
+
# Lock을 사용하여 dialogue_df에 안전하게 접근
|
233 |
+
with dialogue_df_lock:
|
234 |
+
dialogue_df = pd.concat([dialogue_df, temp_dialogue_df], axis=0)
|
235 |
+
except Exception as e:
|
236 |
+
print(f"에러 타입: {type(e).__name__}") # 예외의 타입
|
237 |
+
print(f"에러 메시지: {e}") # 예외 메시지
|
238 |
+
traceback.print_exc() # 전체 스택 트레이스 출력
|
239 |
+
finally:
|
240 |
+
with error_files_lock:
|
241 |
+
error_files += json_decoding_error_files
|
242 |
+
|
243 |
+
# 멀티 쓰레딩을 사용하여 파일 처리
|
244 |
+
with ThreadPoolExecutor(max_workers=8) as executor: # 최대 스레드 수를 적절히 설정
|
245 |
+
executor.map(data_preprocessing, sub_category_dirpaths)
|
246 |
+
|
247 |
+
dialogue_df = dialogue_df.iloc[1:]
|
248 |
+
savepath_dialogue_df = os.path.join(root_dir, f'ai_hub_multi_subjects_conversations_{is_train}.tsv')
|
249 |
+
dialogue_df.to_csv(savepath_dialogue_df, sep='\t', index=False)
|
250 |
+
print('✅dialogue_df 저장 완료')
|
251 |
+
print('⚠️ error_file 개수:', len(error_files))
|
252 |
+
if len(error_files)>0:
|
253 |
+
print('⚠️ error_file 이름:')
|
254 |
+
for ef in error_files:
|
255 |
+
print(ef)
|
256 |
+
|
257 |
+
|