Views
No views yet
이 모델은 Google의 강력한 소형 언어 모델인 Gemma-3-12B-it을 기반으로, 한국어 범죄 사건 보고서 분석 태스크에 맞게 미세 조정(Fine-tuning)되었습니다.
# 주요 기능
사건 재구성 (Context Generation): 사건 보고서의 내용과 사실 관계를 분석하여 당시 상황을 논리적으로 재구성합니다.
사건 유형 분류 (Kind Classification): 재구성된 내용을 바탕으로 사건의 종류를 정확히 분류하여 제시합니다.
# 학습 전략
(QLoRA)모델 학습에는 QLoRA (Quantized Low-Rank Adaptation) 기법이 적용되어, 높은 메모리 효율성과 빠른 학습 속도를 달성했습니다.
양자화: 4-bit NF4 양자화 (BitsAndBytes)
PEFT: LoRA 적용 (Rank $r=16$, $\alpha=32$)
최적화: adamw_torch, Learning Rate $2e-4$, Cosine Scheduler
필수 라이브러리 및 버전이 모델의 학습 및 사용을 위해서는 다음 라이브러리 버전이 권장됩니다.
라이브러리 버전
transformers : 4.57.3
accelerate : 1.12.0
bitsandbytes : 0.48.2
peft : 0.15.2
torch : 2.9.01from peft import prepare_model_for_kbit_training, LoraConfig, TaskType
2from transformers import AutoModelForCausalLM, BitsAndBytesConfig, AutoTokenizer, TrainingArguments, Trainer
3
4from peft import PeftModel
5
6
7base_model = "google/gemma-3-12b-it"
8
9tokenizer = AutoTokenizer.from_pretrained(base_model)
10tokenizer.add_special_tokens({
11 "additional_special_tokens": ["<start_of_turn>", "<end_of_turn>"]
12})
13
14model = AutoModelForCausalLM.from_pretrained(
15 base_model,
16 torch_dtype=torch.bfloat16,
17 device_map="auto"
18)
19model.config.pad_token_id = tokenizer.pad_token_id
20model.config.bos_token_id = tokenizer.bos_token_id
21model.config.eos_token_id = tokenizer.eos_token_id
22
23model.resize_token_embeddings(len(tokenizer))
24
25model = PeftModel.from_pretrained(model, f"/lora_adapter")
26
27
28question = """다음 사건 보고서를 통해서 당시 범죄 사건을 재구성해주고, 사건 유형을 분류해줘.
29
30[사건 보고서]
312024년 4월 25일, 서울 숭실대입구역 인근에서 이삿짐 화물차가 인도로 돌진해 60대 남성을 부상시킨 사건이 발생하였다.
32현장 조사 결과, 화물차 운전자 김민수(35)는 사전에 브레이크 패드를 마모시키고 브레이크액에 물을 섞어 고장을 유발한 것으로 확인되었다.
33사고 직후 김은 차 내부에 숨겨진 금고에서 현금 200만 원과 가죽 가방, 스마트폰을 꺼내 상가 안경원 직원에게 전달하고, 은행 계좌로 송금하였다.
34경찰은 차량 GPS 기록, 브레이크 패드 마모 상태, CCTV 영상 조작 여부를 조사 중이며, 김은 교통사고처리특례법에 따라 치상 혐의로 입건되었다.
35현재 수사는 김의 은행 송금 기록과 금고 내용물 확보를 통해 범죄 동기와 범행 방법을 파악 중이다. 사건은 아직 결말이 내려지지 않았으며, 추가 증거 확보가 필요하다."""
36
37
38messages = [
39 {"role": "system", "content": """당신은 범행분석 AI 모델입니다. 질문에 정확하게 답변하세요."""},
40 {"role": "user", "content": question}
41]
42
43inputs = tokenizer.apply_chat_template(
44 messages,
45 add_generation_prompt=True,
46 return_tensors="pt",
47 return_dict=True,
48).to(model.device)
49
50model.eval()
51outputs = model.generate(
52 **inputs,
53 max_new_tokens=1024 * 2,
54 temperature=0.1,
55 do_sample=True,
56)
57
58generated = outputs[0][inputs["input_ids"].shape[-1] :]
59answer = tokenizer.decode(generated, skip_special_tokens=True)
60print('사건 재구성 : ', answer)
61
62
63사건 재구성 : 2024년 4월 25일 오후 1시 30분, 서울 숭실대입구역 인근에서 발생한 이삿짐 화물차 사고는 단순한 교통사고가 아니라, 사전에 계획된 절도 사건의 일환이었다. 화물차 운전자인 김민수(35)는 3개월 전부터 이웃 상가 주인인 이영호(45)와 비밀스러운 거래를 진행해왔다. 이영호는 상가 내 고가의 전자제품과 귀중품을 보유하고 있었으며, 김은 이영호가 자주 이용하는 이삿짐 화물차를 이용해 상가를 털 계획을 세웠다.
64
65김은 먼저 화물차의 브레이크 패드를 고의로 마모시키고, 브레이크액에 물을 섞어 브레이크가 작동하지 않도록 조작했다. 또한, 차 내부에 숨겨진 작은 금고에 이영호의 귀중품을 숨겨 두었다. 4월 25일, 김은 이삿짐을 싣고 상가로 출발했다. 상가 앞에 도착하자, 김은 브레이크를 고의로 밟아 차가 급정거하도록 유도했다. 브레이크가 작동하지 않아 차는 인도 쪽으로 돌진했고, 60대 남성이 인도에서 횡단보도를 건너던 중 차에 치여 다리를 다쳤다.
66
67사고 직후 김은 즉시 차를 멈추고, 현장에 남아 있던 상가 직원에게 “브레이크가 고장 나서 사고가 났다”며 사과했다. 동시에, 김은 차 내부에 숨겨진 금고를 열어 이영호의 귀중품을 꺼내 상가 직원에게 전달했다. 이영호는 이미 김이 사전에 연락을 취해 “이삿짐이 위험하다”며 상가를 비우도록 유도했고, 김은 그 틈을 타 상가를 털었다.
68
69김은 사고 직후 차를 다른 도로로 이동시켜, 경찰이 도착하기 전에 현장을 빠져나갔다. 그는 차 내부에 남겨진 금고를 다시 잠그고, 차를 다른 지역으로 이동시켜 증거를 숨겼다. 이후, 김은 상가 직원에게 “사고가 발생해 보험금을 청구할 수 있다”며, 사고로 인한 손해를 보상받을 수 있다고 속였다.
70
71경찰은 사고 현장을 조사하던 중, 차 내부에 숨겨진 금고를 발견하고, 김이 사전에 브레이크를 고의로 고장낸 사실을 확인했다. 또한, 김이 사고 직후 상가 직원에게 귀중품을 전달한 CCTV 영상이 확보되었다. 김은 교통사고처리특례법에 따라 치상 혐의로 입건되었으며, 현재 경찰은 김의 은행 계좌와 차 내부에 숨겨진 금고의 내용물을 조사 중이다. 사건은 아직 수사 단계이며, 김이 실제로 이영호의 귀중품을 훔쳐 다른 지역으로 이동시킨 사실이 확인되면, 절도 혐의로 추가 기소가 가능할 전망이다.
72사건 유형 : 절도base_model = "./gemma-3-12b-it"
tokenizer = AutoTokenizer.from_pretrained(base_model)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model = AutoModelForCausalLM.from_pretrained(
base_model,
quantization_config=bnb_config,
torch_dtype=torch.bfloat16,
device_map="auto"
)
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
model2 = get_peft_model(model, lora_config)def format_prompt(ex, max_length=1775):
global max_length_cal, row
title = '다음 사건 보고서를 통해서 당시 사건을 재구성해주고, 사건 유형을 분류해줘\n'
question = ex["report"]
answer = ex["context"]
kind = f'\n사건 유형 : {ex["kind"]}'
prompt = f"""<start_of_turn>system
당신은 범행분석 AI 모델입니다.
질문에 정확하게 답변하세요.
<end_of_turn>
<start_of_turn>user
{title}
[사건 보고서]
{question}\n<end_of_turn>\n<start_of_turn>model\n"""
prompt_ids = tokenizer(prompt, add_special_tokens=False)["input_ids"]
model_part = f"""{answer} {kind}<end_of_turn>"""
answer_ids = tokenizer(model_part, add_special_tokens=False)["input_ids"]
input_ids = prompt_ids + answer_ids
labels = [-100] * len(prompt_ids) + answer_ids
attention_mask = [1] * len(input_ids)
pad_len = max_length - len(input_ids)
input_ids += [tokenizer.pad_token_id] * pad_len
attention_mask += [0] * pad_len
labels += [-100] * pad_len
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels,
}training_args = TrainingArguments(
output_dir=model_path,
per_device_train_batch_size=16,
gradient_accumulation_steps=2,
num_train_epochs=4,
learning_rate=2e-4,
bf16=True,
fp16=False,
gradient_checkpointing = False,
logging_steps=5,
eval_steps=300,
save_strategy="steps",
save_steps=300,
save_total_limit=2,
report_to="none",
lr_scheduler_type="cosine",
warmup_ratio=0.05,
optim="adamw_torch"
)[4208/4208 24:18:13, Epoch 4/4]
Step Training Loss
5 2.421700
10 2.201100
15 2.120400
20 1.976900
25 1.791000
30 1.637100
35 1.536300
40 1.449400
45 1.359500
50 1.303600
55 1.247800
60 1.221700
65 1.207600
70 1.198400
75 1.159500
80 1.147800
85 1.102900
90 1.118900
95 1.070000
100 1.108500
200 1.035400
300 0.959200
400 0.936700
500 0.907200
600 0.905200
700 0.896200
800 0.891700
900 0.875500
1000 0.881300
2000 0.790400
3100 0.706300
3200 0.647300
3305 0.646800
3400 0.643700
3500 0.647800
3600 0.645000
3700 0.629200
3800 0.650200
3900 0.643800
3905 0.655300
3910 0.618900
3915 0.644300
3920 0.654000
3925 0.635700
3930 0.638100
3935 0.637000
3940 0.639900
3945 0.656600
3950 0.659600
3955 0.639200
3960 0.647100
3965 0.651800
3970 0.643500
3975 0.648100
3980 0.657600
3985 0.653300
3990 0.656000
3995 0.658200
4000 0.645500
4100 0.636100
4105 0.661700
4110 0.639600
4115 0.646400
4120 0.653800
4125 0.661900
4130 0.652300
4135 0.644100
4140 0.649600
4145 0.637300
4150 0.644700
4155 0.638500
4160 0.644800
4165 0.652200
4170 0.632500
4175 0.645700
4180 0.645900
4185 0.646300
4190 0.648200
4195 0.658500
4200 0.664600
4205 0.646900
Evaluation Result :
{'eval_loss': 0.8163620829582214, 'eval_runtime': 66.0619, 'eval_samples_per_second': 5.147, 'eval_steps_per_second': 0.651, 'epoch': 4.0}
Best checkpoint :
3910 0.6189(주)인정보
홈페이지 : http://www.ijbinfo.com
정보통신산업진흥원의 지원을 받아서 진행했습니다.(주)인정보
주소 : 서울시 금천구 가산동 60-5 갑을그레이트밸리A동 805호
연락처 : TEL : 02-3397-7765 FAX : 02-3397-7769 E-mail : sales@injungbo.co.kr
담당자 : 장형원(chyungwon@ijbinfo.com)