๋ฉ˜ํ† ๋‹˜ ํ”ผ๋“œ๋ฐฑ: "llama-ccp + GGUF Qunatization"

LoRA ๋ณ‘ํ•ฉ -> llama-ccp ๋นŒ๋“œ -> GGUF (f16) ๋ณ€ํ™˜ -> Unsloth ์ถ”์ฒœ ์–‘์žํ™” UD-Q4_K_XL ๋˜๋Š” Q4_K_M -> mmproj Unsloth ๊ณต์‹์—์„œ ๊ฐ€์ ธ์˜ด LoRA ๊ฐ€ ๋น„์ „๋ ˆ์ด์–ด ๊ฑด๋“ค์ด์ง€์•Š์Œ -> ์„œ๋น™


ํ•œ๊ตญ ์ž‘๋ฌผ ํ•ด์ถฉ ํƒ์ง€๊ธฐ โ€” Qwen3.5-9B LoRA

unsloth/Qwen3.5-9B์„ ํŒŒ์ธํŠœ๋‹ํ•œ ๋น„์ „-์–ธ์–ด LoRA ์–ด๋Œ‘ํ„ฐ๋กœ, ์ž‘๋ฌผ ์‚ฌ์ง„์—์„œ ํ•œ๊ตญ ๋†์ž‘๋ฌผ ํ•ด์ถฉ์„ ์‹๋ณ„ํ•ฉ๋‹ˆ๋‹ค. ์žŽ, ๊ณผ์‹ค, ์‹๋ฌผ ์ „์ฒด ์‚ฌ์ง„์„ ๋„ฃ์œผ๋ฉด ํ•ด์ถฉ์˜ ํ•œ๊ตญ์–ด ์ด๋ฆ„์„ ์ถœ๋ ฅํ•˜๊ณ , ํ•ด์ถฉ์ด ์—†์œผ๋ฉด ์ •์ƒ์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

  • 19๊ฐœ ํด๋ž˜์Šค ๋ถ„๋ฅ˜๊ธฐ: ํ•ด์ถฉ 18์ข… + "์ •์ƒ"(ํ•ด์ถฉ ์—†์Œ)
  • ๋ฒ ์ด์Šค ๋ชจ๋ธ: unsloth/Qwen3.5-9B (๋น„์ „-์–ธ์–ด, ํ•˜์ด๋ธŒ๋ฆฌ๋“œ linear + self attention)
  • ์–ด๋Œ‘ํ„ฐ ์œ ํ˜•: LoRA (PEFT), rank 64, alpha 128
  • ์–ธ์–ด: ํ•œ๊ตญ์–ด
  • ํฌ๊ธฐ: ์–ด๋Œ‘ํ„ฐ ๊ฐ€์ค‘์น˜ 660 MB

์„ฑ๋Šฅ

Himedia-AI-01/pest-detection-korean์˜ ๊ฒ€์ฆ ์„ธํŠธ ์ „์ฒด 1,595๊ฐœ ์ƒ˜ํ”Œ๋กœ ํ‰๊ฐ€ํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

์ง€ํ‘œ ์ ์ˆ˜
์ •ํ™•๋„ (Accuracy) 91.36%
F1 (๋งคํฌ๋กœ) 90.32%
F1 (๊ฐ€์ค‘) 91.34%
์ •๋ฐ€๋„ (๋งคํฌ๋กœ) 90.88%
์žฌํ˜„์œจ (๋งคํฌ๋กœ) 91.01%
์ •๋ฐ€๋„ (๊ฐ€์ค‘) 92.40%
์žฌํ˜„์œจ (๊ฐ€์ค‘) 91.36%

ํ˜ผ๋™ ํ–‰๋ ฌ

Confusion Matrix

ํด๋ž˜์Šค๋ณ„ ์„ฑ๋Šฅ

ํด๋ž˜์Šค ์ •๋ฐ€๋„ ์žฌํ˜„์œจ F1 ์ƒ˜ํ”Œ ์ˆ˜
๋ฌด์žŽ๋ฒŒ 1.0000 1.0000 1.0000 22
๋‹ด๋ฐฐ๊ฑฐ์„ธ๋ฏธ๋‚˜๋ฐฉ 0.9787 0.9787 0.9787 47
๋จน๋…ธ๋ฆฐ์žฌ 0.9888 0.9670 0.9778 91
๋ฐฐ์ถ”ํฐ๋‚˜๋น„ 0.9658 0.9658 0.9658 117
๋‹ด๋ฐฐ๋‚˜๋ฐฉ 0.9429 0.9851 0.9635 67
์•Œ๋ฝ์ˆ˜์—ผ๋…ธ๋ฆฐ์žฌ 0.9741 0.9417 0.9576 120
ํ†ฑ๋‹ค๋ฆฌ๊ฐœ๋ฏธํ—ˆ๋ฆฌ๋…ธ๋ฆฐ์žฌ 1.0000 0.9078 0.9517 141
๊ฝƒ๋…ธ๋ž‘์ด์ฑ„๋ฒŒ๋ ˆ 0.9524 0.9302 0.9412 43
ํŒŒ๋ฐค๋‚˜๋ฐฉ 0.9500 0.9194 0.9344 124
ํฐ28์ ๋ฐ•์ด๋ฌด๋‹น๋ฒŒ๋ ˆ 0.9375 0.9184 0.9278 49
์ฉ๋ฉ๋‚˜๋ฌด๋…ธ๋ฆฐ์žฌ 0.8341 0.9884 0.9048 173
์ •์ƒ 0.8295 0.9932 0.9040 147
๋‹ด๋ฐฐ๊ฐ€๋ฃจ์ด 0.9730 0.8182 0.8889 44
๋ฐฐ์ถ”์ข€๋‚˜๋ฐฉ 0.8675 0.8889 0.8780 81
๋น„๋‹จ๋…ธ๋ฆฐ์žฌ 0.8333 0.8824 0.8571 34
๋ฒผ๋ฃฉ์žŽ๋ฒŒ๋ ˆ 0.9726 0.7136 0.8232 199
๋„๋‘‘๋‚˜๋ฐฉ 1.0000 0.6923 0.8182 13
๋ชฉํ™”๋ฐ”๋‘‘๋ช…๋‚˜๋ฐฉ 0.6667 0.9444 0.7816 36
๊ฒ€๊ฑฐ์„ธ๋ฏธ๋ฐค๋‚˜๋ฐฉ 0.6000 0.8571 0.7059 14

๐Ÿ“ˆ ๋ฒ ์ด์Šค๋ผ์ธ ๋น„๊ต: LoRA ์–ด๋Œ‘ํ„ฐ ์œ ๋ฌด์— ๋”ฐ๋ฅธ ์ฐจ์ด

LoRA ํŒŒ์ธํŠœ๋‹์ด ๋ชจ๋ธ์— ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ๊ฐ€๋ฅด์ณ ์ฃผ๋Š”์ง€ ์ •๋Ÿ‰์ ์œผ๋กœ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด, ๋˜‘๊ฐ™์€ ํ‰๊ฐ€ ํŒŒ์ดํ”„๋ผ์ธ์„ LoRA ์–ด๋Œ‘ํ„ฐ ์—†์ด ์›๋ณธ unsloth/Qwen3.5-9B ๋ฒ ์ด์Šค ๋ชจ๋ธ์—๋„ ๊ทธ๋Œ€๋กœ ๋Œ๋ ค ๋ดค์Šต๋‹ˆ๋‹ค โ€” ๊ฐ™์€ 1,595๊ฐœ ๊ฒ€์ฆ ์ƒ˜ํ”Œ(Himedia-AI-01/pest-detection-korean), ๊ฐ™์€ ์ „์ฒ˜๋ฆฌ(letterbox 512ร—512), ๊ฐ™์€ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ, ๊ฐ™์€ ๋””์ฝ”๋”ฉ ์„ค์ •.

19๊ฐœ ํด๋ž˜์Šค ๋ผ๋ฒจ ์ค‘ ์–ด๋””์—๋„ ํ•ด๋‹นํ•˜์ง€ ์•Š๋Š” ์˜ˆ์ธก์€ UNKNOWN(off-vocabulary, OOV)์œผ๋กœ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์˜ค๋‹ต์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰ "์ด ๋ชจ๋ธ์ด ์‹ค์ œ๋กœ ์ •๋‹ต ํ•ด์ถฉ ์ด๋ฆ„์„ ์ถœ๋ ฅํ•ด ๋‚ด๋Š”๊ฐ€?"๋ฅผ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ ์ฃผ๋Š” ํ‰๊ฐ€์ž…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๊ฒฐ๊ณผ

์ง€ํ‘œ ๋ฒ ์ด์Šค Qwen3.5-9B
(LoRA ์—†์Œ)
LoRA ์ ์šฉ
(pest-detector-final)
ฮ”
์ •ํ™•๋„ (Accuracy) 9.47% 89.47% +80.00%p
F1 (๋งคํฌ๋กœ) 4.32% 89.44% +85.12%p
F1 (๊ฐ€์ค‘) 7.37% 90.38% +83.01%p
์ •๋ฐ€๋„ (๋งคํฌ๋กœ) 8.00% 90.88% +82.88%p
์žฌํ˜„์œจ (๋งคํฌ๋กœ) 5.47% 89.28% +83.81%p
OOV ์ถœ๋ ฅ 1,183 / 1,595 (74.2%) 33 / 1,595 (2.1%) โˆ’72.1%p

ํ˜ผ๋™ ํ–‰๋ ฌ ๋น„๊ต

๋ฒ ์ด์Šค ๋ชจ๋ธ (LoRA ์—†์Œ) โ€” ๊ฑฐ์˜ ๋ชจ๋“  ํ–‰์ด ์˜ค๋ฅธ์ชฝ ๋ UNKNOWN ์—ด๋กœ ๋ชฐ๋ฆฝ๋‹ˆ๋‹ค:

Baseline CM

LoRA ์–ด๋Œ‘ํ„ฐ ์ ์šฉ โ€” ์˜ˆ์ธก์ด ๋Œ€๊ฐ์„ ์— ์ง‘์ค‘๋˜๊ณ  UNKNOWN ์—ด์€ ๊ฑฐ์˜ ๋น„์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

LoRA CM


๋ฒ ์ด์Šค ๋ชจ๋ธ์˜ ์ถœ๋ ฅ ๋ถ„์„

๋ฒ ์ด์Šค ๋ชจ๋ธ์ด ๋‚ด๋†“์€ 1,183๊ฐœ์˜ OOV ์˜ˆ์ธก์„ ์ž์„ธํžˆ ๋œฏ์–ด๋ณด๋ฉด ์žฌ๋ฏธ์žˆ๋Š” ์‚ฌ์‹ค์ด ๋ณด์ž…๋‹ˆ๋‹ค: ์ด๊ฒƒ๋“ค์€ ์˜๋ฏธ ์—†๋Š” ๋ฌธ์ž์—ด์ด ์•„๋‹™๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„ ํ•œ๊ตญ์–ด๋กœ ๋ถ„๋ช…ํ•˜๊ฒŒ ์“ฐ์ธ, ์‹ค์žฌํ•˜๋Š” ํ•ด์ถฉ ์ด๋ฆ„๋“ค์ž…๋‹ˆ๋‹ค โ€” ๋ฒ ์ด์Šค ๋ชจ๋ธ์ด ๊ณค์ถฉ ์‚ฌ์ง„์ž„์„ ์ด๋ฏธ ์•Œ์•„๋ณด๊ณ  ์ด๋ฆ„๊นŒ์ง€ ๋ถ™์ด๋Š”๋ฐ, ๊ทธ ์ด๋ฆ„์ด ์šฐ๋ฆฌ๊ฐ€ ํ•™์Šต์— ์“ด 19๊ฐœ ํด๋ž˜์Šค์— ๋“ค์–ด ์žˆ์ง€ ์•Š์„ ๋ฟ์ž…๋‹ˆ๋‹ค.

๋ฒ ์ด์Šค ๋ชจ๋ธ์˜ ์ƒ์œ„ 20๊ฐœ OOV "๋‹จ์–ด" (์ฒซ ํ† ํฐ ๊ธฐ์ค€์œผ๋กœ ๋ฌถ์Œ):

๋นˆ๋„ ํ•œ๊ตญ์–ด ์„ค๋ช… ์‹ค์žฌ ์—ฌ๋ถ€
143 ๋ฐฉ์•„๋ฒŒ๋ ˆ wireworm / click beetle โœ… ์‹ค์žฌ ํ•ด์ถฉ (19๊ฐœ ๋ชฉ๋ก ์™ธ)
125 ํฐ๋ฐฐ๋‚˜๋ฐฉ white-bellied moth โœ… ์‹ค์žฌ ๋‚˜๋ฐฉ ๊ณ„์—ด
115 ๋ฐฉ์„ ์ถฉ nematode (์„ ์ถฉ) โœ… ์‹ค์žฌ ์‹๋ฌผ ํ•ด์ถฉ
94 ๋ฒผ๋ฉธ๊ตฌ rice planthopper โœ… ์‹ค์žฌ ๋ฒผ ํ•ด์ถฉ
76 ๋ฐค๋‚˜๋ฐฉ noctuid (๋ฐค๋‚˜๋ฐฉ๊ณผ) โœ… ์‹ค์žฌ ๋‚˜๋ฐฉ ๊ณ„์—ด
61 ๋‚˜๋ฐฉ "๋‚˜๋ฐฉ" (์ผ๋ฐ˜๋ช…) โš ๏ธ ์ผ๋ฐ˜๋ช…, ์ข… ๋ฏธ์ง€์ •
49 ๋ฐฐ์ถ”๋ฐฉ์•„๋ฒŒ๋ ˆ "๋ฐฐ์ถ” click beetle" โŒ ์ฐฝ์ž‘ ์กฐ์–ด
32 ์• ๊ธฐ๋ฐฐ์ถ”๋ฐฉ์•„๋ฒŒ๋ ˆ "์•„๊ธฐ ๋ฐฐ์ถ” click beetle" โŒ ์ฐฝ์ž‘ ์กฐ์–ด
30 ํฐ๋‚ ๊ฐœ๋‚˜๋ฐฉ "ํฐ ๋‚ ๊ฐœ ๋‚˜๋ฐฉ" โŒ ๊ทธ๋Ÿด๋“ฏํ•œ ์กฐํ•ฉ์–ด
25 ๋ฐฉ์•„์‡ ๋‚˜๋ฐฉ "๋ฐฉ์•„์‡  ๋‚˜๋ฐฉ" โŒ ์ฐฝ์ž‘ ์กฐ์–ด
21 ํฐ๊ฐ€๋ฃจ๋ณ‘ powdery mildew (ํฐ๊ฐ€๋ฃจ๋ณ‘) โœ… ์‹ค์žฌ (๋ณ‘ํ•ด, ํ•ด์ถฉ ์•„๋‹˜)
20 ์ง„๋”ง๋ฌผ aphid (์ง„๋”ง๋ฌผ) โœ… ์‹ค์žฌ ํ•ด์ถฉ โ€” 19๊ฐœ ์™ธ
20 ์• ๋ฒŒ๋ ˆ "์• ๋ฒŒ๋ ˆ" (์ผ๋ฐ˜๋ช…) โš ๏ธ ์ผ๋ฐ˜๋ช…
19 ํฐ์ ๋ฐ•์ด๊ฝƒ๋ฌด์ง€ white-spotted flower chafer โœ… ์‹ค์žฌ ๋”ฑ์ •๋ฒŒ๋ ˆ
18 ์žŽ๋ฒŒ๋ ˆ leaf beetle (์žŽ๋ฒŒ๋ ˆ๊ณผ) โš ๏ธ ๊ณผ๋ช…, ์ข… ๋ฏธ์ง€์ •
15 ๋ฐฉ์„ ์ž ๋ฐฉ์„ ์ถฉ์˜ ๋ณ€ํ˜• โŒ ์ฐฝ์ž‘ ์กฐ์–ด
11 ๋ฒผ์žŽ๋ฒŒ๋ ˆ rice leaf beetle โœ… ์‹ค์žฌ ํ•ด์ถฉ
10 ไธƒๆ˜Ÿ็“ข่™ซ "์น ์„ฑ๋ฌด๋‹น๋ฒŒ๋ ˆ" (์ค‘๊ตญ์–ด) โŒ ์ค‘๊ตญ์–ด ํ˜ผ์ž…
6 ๋Œ€๋‘๋‚˜๋ฐฉ "๋Œ€๋‘ ๋‚˜๋ฐฉ" โŒ ๊ทธ๋Ÿด๋“ฏํ•œ ์กฐํ•ฉ์–ด
6 ๋Œ€๋‘์žŽ๋ฒŒ๋ ˆ soybean leaf beetle โœ… ์‹ค์žฌ ํ•ด์ถฉ

์ „์ฒด 1,183๊ฐœ OOV ์›๋ฌธ ๋ชฉ๋ก์€ evaluation/baseline_off_vocab_list.json์— ์žˆ์Šต๋‹ˆ๋‹ค.


LoRA๊ฐ€ ์‹ค์ œ๋กœ ํ•˜๋Š” ์ผ

๋ฒ ์ด์Šค ๋ชจ๋ธ์€ ํ•œ๊ตญ ํ•ด์ถฉ์— ๋Œ€ํ•œ ์‚ฌ์ „ ์ง€์‹์ด ์ด๋ฏธ ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค โ€” ๊ณค์ถฉ ์‚ฌ์ง„์„ ๋ณด๊ณ  ํ•ด๋ถ€ํ•™์ ์œผ๋กœ ๊ทธ๋Ÿด๋“ฏํ•œ ์ด๋ฆ„์„ ๊ฝค ์ž์‹  ์žˆ๊ฒŒ ๋‚ด๋†“์Šต๋‹ˆ๋‹ค. ๋ชจ๋ธ์ด ๋ชจ๋ฅด๋Š” ๊ฑด ์ด๋ฒˆ ๊ณผ์ œ์˜ ์ •๋‹ต ๋ชฉ๋ก์— ๋“ค์–ด๊ฐ€๋Š” 19๊ฐœ ์ด๋ฆ„์ด ๋ฌด์—‡์ธ์ง€์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ LoRA์˜ ์—ญํ• ์€ "ํ•ด์ถฉ์ด ์–ด๋–ป๊ฒŒ ์ƒ๊ฒผ๋Š”์ง€"๋ฅผ ๊ฐ€๋ฅด์ณ ์ฃผ๋Š” ๊ฒŒ ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ์ง€์‹์€ ์ด๋ฏธ ์žˆ์Šต๋‹ˆ๋‹ค. LoRA๊ฐ€ ์‹ค์ œ๋กœ ํ•˜๋Š” ์ผ์€ vocabulary-locking (์–ดํœ˜ ๊ณ ์ •) โ€” ๋ชจ๋ธ์˜ ์ถœ๋ ฅ ๋ถ„ํฌ๋ฅผ "19์ง€์„ ๋‹ค ๋ฉ”๋‰ด"๋กœ ์ขํ˜€ ์ฃผ๋Š” ์ผ์ž…๋‹ˆ๋‹ค.

๋™์ž‘ ๋ฒ ์ด์Šค ๋ชจ๋ธ + LoRA ์–ด๋Œ‘ํ„ฐ
ํ•ด์ถฉ ์ธ์‹ ์ด๋ฏธ ์ถฉ๋ถ„ (์‹ค์žฌ ํ•ด์ถฉ ์ด๋ฆ„ ์ƒ์„ฑ) ์—ฌ์ „ํžˆ ์ถฉ๋ถ„
์ถœ๋ ฅ ์–ดํœ˜ ๊ฐœ๋ฐฉํ˜• โ€” ๋ชจ๋“  ํ•œ๊ตญ์–ด ํ•ด์ถฉ ์ด๋ฆ„ ํ•™์Šต๋œ 19๊ฐœ ํด๋ž˜์Šค๋กœ ๊ณ ์ •
OOV ์ถœ๋ ฅ ๋น„์œจ 74.2% 2.1%
CoT(์‚ฌ๊ณ  ์—ฐ์‡„) ํ† ํฐ ์ถœ๋ ฅ ์žฆ์Œ (<think>โ€ฆ</think>) ์ œ๊ฑฐ๋จ
ํด๋ž˜์Šค๋ณ„ F1 ๋Œ€๋ถ€๋ถ„ 0.00 (์ถ”์ธก์ด ๊ฑฐ์˜ ์•ˆ ๋งž์Œ) ๋ชจ๋“  ํด๋ž˜์Šค โ‰ฅ 0.70, ๋Œ€๋ถ€๋ถ„ โ‰ฅ 0.90

๋ฐ”๋กœ ์ด๊ฒƒ์ด 9B ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฒ ์ด์Šค ๋ชจ๋ธ์— 660 MB์งœ๋ฆฌ LoRA ๊ฐ€์ค‘์น˜๋งŒ์œผ๋กœ ์ •ํ™•๋„๊ฐ€ 9%์—์„œ 91%๋กœ ๋›ด ์ด์œ ์ž…๋‹ˆ๋‹ค. 90์–ต ๊ฐœ ํŒŒ๋ผ๋ฏธํ„ฐ์— ํ•ด์ถฉ์— ๋Œ€ํ•œ ์ง€์‹์„ ์ƒˆ๋กœ ์ƒˆ๊ฒจ ๋„ฃ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, 19๊ฐœ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณ ๋ฅด๋Š” ์ข์€ ์„ ํ˜ธ๋งŒ ํ•™์Šต์‹œํ‚ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๊ฑด vocabulary alignment ๋ฌธ์ œ์ด๊ณ , low-rank adaptation(LoRA)์ด ๊ฐ€์žฅ ์ž˜ ํ•ด๋‚ด๋Š” ์ข…๋ฅ˜์˜ ์ผ์ž…๋‹ˆ๋‹ค.


๋ฐฉ๋ฒ•๋ก  ์ฃผ์„

์œ„์˜ ๋ฒ ์ด์Šค๋ผ์ธ ์ˆ˜์น˜๋Š” 1,595๊ฐœ ์ƒ˜ํ”Œ ์ „์ฒด๋ฅผ ๋Œ€์ƒ์œผ๋กœ ๊ณ„์‚ฐํ•œ ๊ฐ’์ด๋ฉฐ, OOV ์˜ˆ์ธก์€ ์˜ค๋‹ต์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€๋Ÿฌ ์ด๋ ‡๊ฒŒ ๋งž์ถ˜ ๊ฒƒ์ž…๋‹ˆ๋‹ค โ€” "์›๋ณธ ๋ฒ ์ด์Šค ๋ชจ๋ธ์ด ์‹ค์ œ๋กœ ์ •๋‹ต์„ ์–ผ๋งˆ๋‚˜ ์ž์ฃผ ๋‚ด๋†“๋Š”๊ฐ€?"๋ฅผ ์žˆ๋Š” ๊ทธ๋Œ€๋กœ ๋ณด์—ฌ ์ฃผ๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.

์ฐธ๊ณ ๋กœ OOV ์ƒ˜ํ”Œ์„ ์ ์ˆ˜ ๊ณ„์‚ฐ์—์„œ ์ œ์™ธํ•˜๋ฉด(๋ฒ ์ด์Šค ๋ชจ๋ธ์ด ์œ ํšจํ•œ ํด๋ž˜์Šค ์ด๋ฆ„์„ ๋ฑ‰์€ 412๊ฐœ ์ƒ˜ํ”Œ๋งŒ ๋‚จ๊ธฐ๋ฉด) ๋ฒ ์ด์Šค ๋ชจ๋ธ๋„ ์ •ํ™•๋„ 36.65% / F1 ๊ฐ€์ค‘ 27.13%๊นŒ์ง€ ์˜ฌ๋ผ๊ฐ‘๋‹ˆ๋‹ค. ๋‹ค๋งŒ ์ด ์ˆ˜์น˜๋Š” "๋ชจ๋ธ์ด ์–ดํœ˜ ์•ˆ์—์„œ ๋‹ต์„ ๋‚ธ ๊ฒฝ์šฐ"๋งŒ ๋”ฐ๋กœ ๋–ผ์–ด ๋‚ธ ๊ฒƒ์ด๋ฉฐ, ์• ์ดˆ์— 74%๋Š” ์–ดํœ˜ ์•ˆ์—์„œ ๋‹ต์„ ๋‚ด์ง€๋„ ๋ชปํ•ฉ๋‹ˆ๋‹ค.


์žฌํ˜„

git clone https://github.com/pfox1995/pest-hyperparameter-search.git
cd pest-hyperparameter-search

# ๋ฒ ์ด์Šค๋ผ์ธ (LoRA ์—†์Œ)
python eval_baseline.py --save-dir ./hf_eval_baseline --label baseline

# LoRA ์–ด๋Œ‘ํ„ฐ ์ ์šฉ (์ด ๋ชจ๋ธ)
python eval_v8.py --adapter pfox1995/pest-detector-final \
                  --save-dir ./hf_eval_v8 --label v8

๋ชจ๋“  ํ‰๊ฐ€ ์•„ํ‹ฐํŒฉํŠธ โ€” ํ˜ผ๋™ ํ–‰๋ ฌ PNG, metrics.json, ํด๋ž˜์Šค๋ณ„ ๋ถ„์„, ์ „์ฒด ์›๋ณธ ์˜ˆ์ธก, OOV ๋ชฉ๋ก โ€” ์€ github.com/pfox1995/pest-hyperparameter-search/tree/main/evaluation์— ์žˆ์Šต๋‹ˆ๋‹ค.


ํด๋ž˜์Šค ๋ชฉ๋ก

๋ชจ๋ธ์ด ์ธ์‹ํ•˜๋Š” 19๊ฐœ ํ•œ๊ตญ์–ด ๋ผ๋ฒจ(ํ•ด์ถฉ 18์ข… + "์ •์ƒ"):

๊ฒ€๊ฑฐ์„ธ๋ฏธ๋ฐค๋‚˜๋ฐฉ, ๊ฝƒ๋…ธ๋ž‘์ด์ฑ„๋ฒŒ๋ ˆ, ๋‹ด๋ฐฐ๊ฐ€๋ฃจ์ด, ๋‹ด๋ฐฐ๊ฑฐ์„ธ๋ฏธ๋‚˜๋ฐฉ, ๋‹ด๋ฐฐ๋‚˜๋ฐฉ, ๋„๋‘‘๋‚˜๋ฐฉ, ๋จน๋…ธ๋ฆฐ์žฌ, ๋ชฉํ™”๋ฐ”๋‘‘๋ช…๋‚˜๋ฐฉ, ๋ฌด์žŽ๋ฒŒ, ๋ฐฐ์ถ”์ข€๋‚˜๋ฐฉ, ๋ฐฐ์ถ”ํฐ๋‚˜๋น„, ๋ฒผ๋ฃฉ์žŽ๋ฒŒ๋ ˆ, ๋น„๋‹จ๋…ธ๋ฆฐ์žฌ, ์ฉ๋ฉ๋‚˜๋ฌด๋…ธ๋ฆฐ์žฌ, ์•Œ๋ฝ์ˆ˜์—ผ๋…ธ๋ฆฐ์žฌ, ์ •์ƒ, ํฐ28์ ๋ฐ•์ด๋ฌด๋‹น๋ฒŒ๋ ˆ, ํ†ฑ๋‹ค๋ฆฌ๊ฐœ๋ฏธํ—ˆ๋ฆฌ๋…ธ๋ฆฐ์žฌ, ํŒŒ๋ฐค๋‚˜๋ฐฉ


ํ•™์Šต ์„ธ๋ถ€ ์‚ฌํ•ญ

  • ๋ฒ ์ด์Šค ๋ชจ๋ธ: unsloth/Qwen3.5-9B (Qwen3-Next ๋น„์ „-์–ธ์–ด, ํ•˜์ด๋ธŒ๋ฆฌ๋“œ Gated DeltaNet + self-attention)
  • ์–ด๋Œ‘ํ„ฐ: LoRA, rank r=64, alpha 128. ํƒ€๊ฒŸ ์ •๊ทœ์‹์€ model.layers.*์™€ language_model.layers.* ๋ชจ๋“ˆ ๊ฒฝ๋กœ๋ฅผ ๋ชจ๋‘ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค (self_attn, linear_attn, mlp โ€” q/k/v/o/gate/up/down/in_proj_*/out_proj).
  • ๋ฐ์ดํ„ฐ์…‹: Himedia-AI-01/pest-detection-korean โ€” ํ•œ๊ตญ ๋†์ž‘๋ฌผ ํ•ด์ถฉ ์ด๋ฏธ์ง€, 19๊ฐœ ํด๋ž˜์Šค (ํ•ด์ถฉ 18์ข… + "์ •์ƒ")
  • ์ตœ์  ์ฒดํฌํฌ์ธํŠธ: step 850, eval_loss = 0.0232
  • ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰: ์ตœ์ข… ํ•™์Šต ์ „์— Optuna TPE๋กœ proxy 50 trial + full 10 trial ์ˆ˜ํ–‰
  • ํ•˜๋“œ์›จ์–ด: RunPod A6000 (48 GB VRAM)
  • ํ”„๋ ˆ์ž„์›Œํฌ: Unsloth + PEFT + TRL/SFTTrainer, bf16

ํ•™์Šต ์ฝ”๋“œ์™€ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰ ์ฝ”๋“œ๋Š” pfox1995/pest-hyperparameter-search์— ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ›ก๏ธ ํ•™์Šต ์•ˆ์ •์„ฑ๊ณผ ์ฒดํฌํฌ์ธํŠธ ์„ ํƒ

์ด ์–ด๋Œ‘ํ„ฐ๋Š” ํ•œ ๋ฒˆ์˜ ํ•™์Šต ์ค‘ eval_loss๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์•˜๋˜ step 850 ์ง€์ ์˜ LoRA ๊ฐ€์ค‘์น˜์ž…๋‹ˆ๋‹ค. ๋ฐ”๋กœ ์ด์–ด์ง„ step 940~950 ๊ตฌ๊ฐ„์—์„œ gradient๊ฐ€ ํญ๋ฐœํ•ด eval_loss๊ฐ€ 0.023์—์„œ 0.58๊นŒ์ง€ ํŠ€์–ด ์˜ค๋ฅด๊ธด ํ–ˆ์ง€๋งŒ, HuggingFace Trainer์˜ load_best_model_at_end=True ๋•๋ถ„์— ํ•™์Šต์ด ๋๋‚  ๋•Œ ์ž๋™์œผ๋กœ step 850 ๊ฐ€์ค‘์น˜๋กœ ๋˜๋Œ์•„์™”์Šต๋‹ˆ๋‹ค. ๊ณต๊ฐœ๋œ ์–ด๋Œ‘ํ„ฐ๋Š” 'ํญ๋ฐœ ์ดํ›„'๊ฐ€ ์•„๋‹ˆ๋ผ **'ํญ๋ฐœ ์ง์ „, eval_loss๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์•˜๋˜ ์ˆœ๊ฐ„'**์˜ ์Šค๋ƒ…์ƒท์ž…๋‹ˆ๋‹ค.

ํ•™์Šต ๊ถค์  (์ฃผ์š” ์Šคํ…)

์‹ค์ œ ํ•™์Šต ๋กœ๊ทธ(training_artifacts/checkpoint-950/trainer_state.json)์—์„œ ๋ฝ‘์•„๋‚ธ ๊ด€์ธก๊ฐ’์ž…๋‹ˆ๋‹ค.

Step eval_loss grad_norm ์ƒํƒœ
25 0.0465 3.20 warmup ๋๋‚œ ์งํ›„ (์ฒซ ํ‰๊ฐ€)
125 0.0332 โ€” ์ˆ˜๋ ด ๊ตฌ๊ฐ„ ์ง„์ž…
200 0.0305 10.84 ์ •์ƒ ๋ฒ”์œ„ (clipping ๋ฐœ๋™)
325 0.0254 0.36 ๊ฐ€์žฅ ์•ˆ์ •์ ์ธ ๊ตฌ๊ฐ„
450 0.0282 โ€” ์•ˆ์ •์ ์œผ๋กœ ์ˆ˜๋ ด ์ค‘
850 0.0232 โ€” ๐Ÿ† eval_loss ์ตœ์ €์  โ€” ์ด ์ฒดํฌํฌ์ธํŠธ๊ฐ€ ์ตœ์ข… ์–ด๋Œ‘ํ„ฐ๋กœ ์ฑ„ํƒ๋จ
900 0.0239 2,710 grad_norm ๊ธ‰๋“ฑ ์กฐ์ง
920 โ€” 2,333 ๋ถˆ์•ˆ์ •ํ•œ ์ƒํƒœ ์ง€์†
925 0.0358 โ€” eval_loss ๋ฐ˜๋“ฑ ์‹œ์ž‘
940 โ€” 505,148 ๐Ÿ’ฅ gradient ํญ๋ฐœ ๋ณธ๊ฒฉํ™”
950 0.5803 โ€” ๐Ÿ’ฅ ํ•™์Šต ๋ฐœ์‚ฐ (์ตœ์ข… ์Šคํ…)

best_metric: 0.023164400830864906, best_global_step: 850 (trainer_state.json ์ƒ๋‹จ ํ•„๋“œ์— Trainer๊ฐ€ ์ž์ฒด ๊ธฐ๋กํ•œ ๊ณต์‹ ๊ฐ’)

ํ•™์Šต ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๊ฑธ์–ด ๋‘” 7๋‹จ ์•ˆ์ „์žฅ์น˜

train_final.py๋Š” ํ•™์Šต์ด ๋ถˆ์•ˆ์ •ํ•ด์งˆ ๊ฐ€๋Šฅ์„ฑ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ์—ผ๋‘์— ๋‘๊ณ  ์ง  ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ 7๊ฐœ ์•ˆ์ „์žฅ์น˜๊ฐ€ ๋™์‹œ์— ๊ฑธ๋ ค ์žˆ์Šต๋‹ˆ๋‹ค:

# ์„ค์ • ๊ฐ’ ์—ญํ• 
1 max_grad_norm 1.0 ๋งค ์Šคํ…๋งˆ๋‹ค gradient L2 norm์„ 1.0์œผ๋กœ clipping. ํญ๋ฐœ์ด backprop์— ์‹ค๋ฆฌ์ง€ ๋ชปํ•˜๊ฒŒ ๋ง‰์Œ.
2 warmup_ratio 0.03 ์ „์ฒด ์Šคํ…์˜ 3%๋ฅผ warmup์œผ๋กœ ์จ์„œ ์ดˆ๊ธฐ LR์ด ํŠ€๋Š” ์ถฉ๊ฒฉ์„ ์™„ํ™”.
3 lr_scheduler_type linear LR์„ ์„ ํ˜•์œผ๋กœ ์ค„์—ฌ ๋‚˜๊ฐ โ†’ ํ›„๋ฐ˜์œผ๋กœ ๊ฐˆ์ˆ˜๋ก ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ž‘์•„์ง.
4 optim adamw_torch adamw_8bit๋ณด๋‹ค ์ˆ˜์น˜์ ์œผ๋กœ ์•ˆ์ • (์ด์ „ ์‹คํ—˜์—์„œ 8bit optimizer๊ฐ€ step 29์—์„œ ๋ฐœ์‚ฐํ•œ ์ด๋ ฅ์ด ์žˆ์Œ).
5 use_rslora True rank-stabilized LoRA โ€” ์Šค์ผ€์ผ์„ alpha/โˆšr๋กœ ์ •๊ทœํ™”ํ•ด rank-64์—์„œ๋„ ์ˆ˜๋ ด์ด ์•ˆ์ •์ .
6 save_steps / eval_steps 25 / 25 25 ์Šคํ…๋งˆ๋‹ค ์ฒดํฌํฌ์ธํŠธ + ํ‰๊ฐ€. ํญ๋ฐœ ์ง์ „์˜ ์•ˆ์ • ๊ตฌ๊ฐ„์„ ์ด˜์ด˜ํ•˜๊ฒŒ ๋‚จ๊ฒจ ์คŒ.
7 load_best_model_at_end True ํ•™์Šต์ด ๋๋‚  ๋•Œ eval_loss๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์•˜๋˜ ์ฒดํฌํฌ์ธํŠธ๋กœ ์ž๋™ ๋ณต์›.

์ด ์ค‘ #1๊ณผ #7์ด ์ด๋ฒˆ ํ•™์Šต์˜ ๊ฒฐ์ •์  ์•ˆ์ „๋ง์œผ๋กœ ์ž‘๋™ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • #1 (max_grad_norm=1.0) โ€” step 940์—์„œ gradient๊ฐ€ 505,148๊นŒ์ง€ ํŠ€์–ด ์˜ฌ๋ž์„ ๋•Œ, ์‹ค์ œ ๊ฐ€์ค‘์น˜ ์—…๋ฐ์ดํŠธ์— ๋ฐ˜์˜๋˜๋Š” norm์€ 1.0์œผ๋กœ ๋ˆŒ๋Ÿฌ ์คฌ์Šต๋‹ˆ๋‹ค. ๋•๋ถ„์— eval_loss๊ฐ€ 0.58 ์„ ์—์„œ ๋ฉˆ์ถ˜ ๊ฒƒ์ด๋ฉฐ, clipping์ด ์—†์—ˆ๋‹ค๋ฉด ๊ฐ€์ค‘์น˜ ์ž์ฒด๊ฐ€ NaN์œผ๋กœ ๋ฐœ์‚ฐํ–ˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ํฝ๋‹ˆ๋‹ค.
  • #7 (load_best_model_at_end=True) โ€” trainer.train()์ด ๋๋‚˜๋Š” ์ˆœ๊ฐ„ Trainer๊ฐ€ ๋‚ด๋ถ€ log_history๋ฅผ ํ›‘์–ด์„œ metric_for_best_model="eval_loss"๊ฐ€ ๊ฐ€์žฅ ๋‚ฎ์•˜๋˜ step 850 ์ฒดํฌํฌ์ธํŠธ๋ฅผ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค. ํ•™์Šต ์Šคํฌ๋ฆฝํŠธ์—์„œ ๋ณ„๋„ ์ž‘์—…์„ ํ•˜์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ๋ณต์›๋ฉ๋‹ˆ๋‹ค.

์™œ ํ›„๋ฐ˜์— ํญ๋ฐœํ–ˆ๋Š”๊ฐ€ โ€” ๋ฒ„๊ทธ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ์„ธํŒ…์˜ ํŠน์„ฑ

LoRA rank=64์— dropout=0.0์œผ๋กœ 9B์งœ๋ฆฌ ๋ฒ ์ด์Šค ๋ชจ๋ธ์„ ํŒŒ์ธํŠœ๋‹ํ•˜๋ฉด, ์—ํญ ํ›„๋ฐ˜๋ถ€์— eval_loss๊ฐ€ ํ•œ ๋ฒˆ ์ˆ˜๋ ดํ•œ ๋’ค ๋‹ค์‹œ ๋ฌด๋„ˆ์ง€๋Š” ํŒจํ„ด์ด ๊ฑฐ์˜ ๊ทœ์น™์ ์œผ๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์›์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

  1. loss๊ฐ€ ๊ทน๋„๋กœ ์ž‘์•„์ง โ€” step 850 ๋ถ€๊ทผ์—์„œ train loss๊ฐ€ 0.024 ์ˆ˜์ค€์ด๋ผ๋Š” ๊ฑด ์‚ฌ์‹ค์ƒ ํ•™์Šต ๋ฐ์ดํ„ฐ๋ฅผ ๊ฑฐ์˜ ๋‹ค ์™ธ์šด ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ์ด ๊ตฌ๊ฐ„์—์„œ๋Š” Adam์˜ 2์ฐจ ๋ชจ๋ฉ˜ํŠธ(๋ถ„์‚ฐ ์ถ”์ •์น˜)๊ฐ€ ๋งค์šฐ ์ž‘์•„์ง€๋Š”๋ฐ, ์ด๋•Œ ๋ฐฐ์น˜ ํ•˜๋‚˜๋ผ๋„ ๊ธฐ์กด ๋ถ„ํฌ์™€ ๋‹ค๋ฅธ ๋ฐฉํ–ฅ์„ ๊ฐ€๋ฆฌํ‚ค๋ฉด ์‹คํšจ LR(effective LR)์ด ํญ๋ฐœ์ ์œผ๋กœ ์ปค์ง‘๋‹ˆ๋‹ค.
  2. LoRA rank-64 + dropout 0 ์กฐํ•ฉ์€ capacity ์—ฌ์œ ๊ฐ€ ํผ โ€” ๋‚จ์€ capacity๊ฐ€ "์ด๋ฏธ ์ž˜ ๋งž์ถ˜ ์ƒ˜ํ”Œ์„ ๋” ์ •๋ฐ€ํ•˜๊ฒŒ ๋งž์ถ”๋ ค๋Š”" ์ชฝ์œผ๋กœ ์ ๋ฆฌ๋ฉด์„œ overfit ๋ฐฉํ–ฅ์˜ gradient๋ฅผ ๋งŒ๋“ค์–ด ๋ƒ…๋‹ˆ๋‹ค.
  3. linear ์Šค์ผ€์ค„๋Ÿฌ์˜ ํ•œ๊ณ„ โ€” step 940 ์‹œ์ ์—๋„ LR์ด 4.24e-5๋กœ ์—ฌ์ „ํžˆ ๊ฝค ๋†’๊ณ , cosine ์Šค์ผ€์ค„์ฒ˜๋Ÿผ ํ›„๋ฐ˜์— ๊ธ‰๊ฒฉํžˆ ๋–จ์–ด์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ „๋žต์€ "gradient ํญ๋ฐœ ์ž์ฒด๋ฅผ ๋ง‰๋Š” ๊ฒƒ"์ด ์•„๋‹ˆ๋ผ **"ํญ๋ฐœ์ด ์ผ์–ด๋‚˜๊ธฐ ์ „์˜ ์ตœ์  ์ง€์ ์„ ์žก์•„ ๋‘๋Š” ๊ฒƒ"**์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด ์–ด๋Œ‘ํ„ฐ๋Š” ์ •ํ™•ํžˆ ๊ทธ๋Ÿฐ ์›์น™์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ์Šต๋‹ˆ๋‹ค.

์ €์žฅ๋œ ์ฒดํฌํฌ์ธํŠธ๊ฐ€ "์™„์„ฑ๋œ" ์–ด๋Œ‘ํ„ฐ๋กœ ํƒ€๋‹นํ•œ ์ด์œ 

  • โœ… eval_loss ์ตœ์ € ์ง€์  โ€” step 850์€ ์ „์ฒด log_history ์ค‘ eval_loss๊ฐ€ ์ตœ์†Œ(0.02316)๋ฅผ ๊ธฐ๋กํ•œ ์Šคํ…์ž…๋‹ˆ๋‹ค.
  • โœ… ์ถฉ๋ถ„ํžˆ ์ˆ˜๋ ด๋œ ์ƒํƒœ โ€” step 300๋ถ€ํ„ฐ step 900๊นŒ์ง€ ์•ฝ 600 ์Šคํ… ๋™์•ˆ eval_loss๊ฐ€ 0.023~0.030 ๊ตฌ๊ฐ„์—์„œ ์•ˆ์ •์ ์œผ๋กœ ์ง„๋™ํ–ˆ์Šต๋‹ˆ๋‹ค โ€” ๊ณผ์ ํ•ฉ ์ง์ „์˜ ์ž˜ ๋ฌด๋ฅด์ต์€ ์ˆ˜๋ ด ๊ตฌ๊ฐ„์ž…๋‹ˆ๋‹ค.
  • โœ… ๊ฒ€์ฆ ์„ฑ๋Šฅ์œผ๋กœ ๊ต์ฐจ ํ™•์ธ โ€” 1,595๊ฐœ ๊ฒ€์ฆ ์ƒ˜ํ”Œ ์ „์ฒด์—์„œ 89.47% ์ •ํ™•๋„, F1 macro 89.44% (evaluation/metrics.json ์ฐธ์กฐ).
  • โœ… ๋ฒ ์ด์Šค๋ผ์ธ ๋Œ€๋น„ +80%p โ€” LoRA ์—†์ด ๋Œ๋ฆฐ ๋™์ผ ๋ชจ๋ธ์€ 9.47%์— ๊ทธ์นฉ๋‹ˆ๋‹ค (์ž์„ธํ•œ ๋น„๊ต๋Š” ์œ„์˜ "๋ฒ ์ด์Šค๋ผ์ธ ๋น„๊ต" ์„น์…˜ ์ฐธ๊ณ ).

"ํ•™์Šต์ด ์ค‘๊ฐ„์— ๊ผฌ์˜€์œผ๋‹ˆ ์ด ์–ด๋Œ‘ํ„ฐ๋„ ๋ถ€์‹คํ•œ ๊ฒƒ ์•„๋‹Œ๊ฐ€?"๋ผ๋Š” ์˜๋ฌธ์ด ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ, ์‹ค์€ ๊ทธ ๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. ์˜คํžˆ๋ ค load_best_model_at_end๊ฐ€ ๊ฑธ๋ ค ์žˆ๋Š” ์ƒํ™ฉ์—์„œ ํญ๋ฐœ์ด ๋ฐœ์ƒํ–ˆ๋‹ค๋Š” ์‚ฌ์‹ค ์ž์ฒด๊ฐ€ ์šด์˜ ์•ˆ์ „๋ง์ด ์„ค๊ณ„๋Œ€๋กœ ์ž‘๋™ํ–ˆ๋‹ค๋Š” ์ฆ๊ฑฐ์ž…๋‹ˆ๋‹ค. "ํ•™์Šต์„ ๋๊นŒ์ง€ ๋Œ๋ฆฐ ์–ด๋Œ‘ํ„ฐ"๊ฐ€ ๋” ์ข‹์€ ์–ด๋Œ‘ํ„ฐ๋ผ๋Š” ๋ณด์žฅ๋„ ์—†์Šต๋‹ˆ๋‹ค โ€” ์˜คํžˆ๋ ค eval_loss๊ฐ€ ๋‹ค์‹œ ์˜ฌ๋ผ๊ฐ€ ๋ฒ„๋ฆฐ overfit ์ƒํƒœ์ธ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค.

ํ–ฅํ›„ ๊ฐœ์„  โ€” ๋” ๊น”๋”ํ•˜๊ฒŒ ์ข…๋ฃŒํ•˜๋ ค๋ฉด

๋‹ค์Œ ํ•™์Šต์—์„œ๋Š” transformers.EarlyStoppingCallback์„ ๋ถ™์ด๋ฉด ํญ๋ฐœ์ด ์ผ์–ด๋‚˜๊ธฐ ์ „์— ํ•™์Šต์„ ์กฐ๊ธฐ ์ข…๋ฃŒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

from transformers import EarlyStoppingCallback

trainer = SFTTrainer(
    ...,
    callbacks=[EarlyStoppingCallback(
        early_stopping_patience=3,      # 3ํšŒ ์—ฐ์† eval_loss ๊ฐœ์„ ์ด ์—†์œผ๋ฉด ์ข…๋ฃŒ
        early_stopping_threshold=0.001, # ๊ฐœ์„ ์œผ๋กœ ์ธ์ •ํ•  ์ตœ์†Œ ๋ณ€ํ™”๋Ÿ‰
    )],
)

์ด๋ฒˆ ํ•™์Šต์— ์ด ์ฝœ๋ฐฑ์ด ๊ฑธ๋ ค ์žˆ์—ˆ๋‹ค๋ฉด step 900์ฏค์—์„œ ํ•™์Šต์ด ์ž๋™์œผ๋กœ ๋๋‚˜์„œ step 940์˜ ํญ๋ฐœ์€ ์•„์˜ˆ ์ผ์–ด๋‚˜์ง€ ์•Š์•˜์„ ๊ฒ๋‹ˆ๋‹ค. ์ €์žฅ๋˜๋Š” ์ตœ์ข… ์–ด๋Œ‘ํ„ฐ ์ž์ฒด๋Š” ๊ฐ™์ง€๋งŒ(step 850) GPU ์‹œ๊ฐ„๊ณผ ์ „๋ ฅ์„ ์•ฝ 6% ์•„๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•œ๋ˆˆ์— ์ •๋ฆฌ

์งˆ๋ฌธ ๋‹ต
gradient clipping์ด ๊ฑธ๋ ค ์žˆ์—ˆ๋Š”๊ฐ€? โœ… max_grad_norm=1.0
์‹ค์ œ๋กœ ํญ๋ฐœ์ด ์ผ์–ด๋‚ฌ๋Š”๊ฐ€? โœ… step 940, grad_norm 505,148
ํญ๋ฐœํ•œ ์ƒํƒœ์˜ ์–ด๋Œ‘ํ„ฐ๊ฐ€ ์—…๋กœ๋“œ๋๋Š”๊ฐ€? โŒ load_best_model_at_end=True ๋•๋ถ„์— step 850 ๋ฒ„์ „์œผ๋กœ ๋ณต์›๋จ
์—…๋กœ๋“œ๋œ ์–ด๋Œ‘ํ„ฐ์˜ ์„ฑ๋Šฅ์€? โœ… 89.47% ์ •ํ™•๋„ / F1 macro 89.44% (1,595๊ฐœ ๊ฒ€์ฆ ์ƒ˜ํ”Œ ์ „์ฒด)
ํ•™์Šต์„ ๋๊นŒ์ง€ ๋Œ๋ ค์•ผ ํ–ˆ๋Š”๊ฐ€? โŒ ์ด๋ฏธ step 850์ด eval_loss ์ตœ์ €์ . ๊ทธ ์ดํ›„๋Š” overfit ๋ฐ ํญ๋ฐœ ๊ตฌ๊ฐ„

์‚ฌ์šฉ๋ฒ•

๋น ๋ฅธ ์‹œ์ž‘ (์ถ”๋ก )

import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
from peft import PeftModel
from PIL import Image

BASE    = "unsloth/Qwen3.5-9B"
ADAPTER = "pfox1995/pest-detector-final"

# ๋ฒ ์ด์Šค ๋ชจ๋ธ ๋กœ๋“œ + ์–ด๋Œ‘ํ„ฐ ์—ฐ๊ฒฐ
model = AutoModelForImageTextToText.from_pretrained(
    BASE, dtype=torch.bfloat16, device_map="cuda",
).eval()
model = PeftModel.from_pretrained(model, ADAPTER)
processor = AutoProcessor.from_pretrained(BASE)

# ์ด๋ฏธ์ง€ ์ค€๋น„
image = Image.open("pest.jpg").convert("RGB")

# ํ•™์Šต ์‹œ ์‚ฌ์šฉํ•œ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ
SYSTEM_MSG = (
    "๋‹น์‹ ์€ ์ž‘๋ฌผ ํ•ด์ถฉ ์‹๋ณ„ ์ „๋ฌธ๊ฐ€์ž…๋‹ˆ๋‹ค. "
    "์‚ฌ์ง„์„ ๋ณด๊ณ  ํ•ด์ถฉ์˜ ์ด๋ฆ„๋งŒ ํ•œ๊ตญ์–ด๋กœ ๋‹ตํ•˜์„ธ์š”. "
    'ํ•ด์ถฉ์ด ์—†์œผ๋ฉด "์ •์ƒ"์ด๋ผ๊ณ ๋งŒ ๋‹ตํ•˜์„ธ์š”. '
    "๋ถ€๊ฐ€ ์„ค๋ช… ์—†์ด ์ด๋ฆ„๋งŒ ์ถœ๋ ฅํ•˜์„ธ์š”."
)

messages = [
    {"role": "system", "content": [{"type": "text", "text": SYSTEM_MSG}]},
    {"role": "user", "content": [
        {"type": "image", "image": image},
        {"type": "text", "text": "์ด ์‚ฌ์ง„์— ์žˆ๋Š” ํ•ด์ถฉ์˜ ์ด๋ฆ„์„ ์•Œ๋ ค์ฃผ์„ธ์š”."},
    ]},
]

tmpl = processor.apply_chat_template(messages, add_generation_prompt=True)
inputs = processor(image, tmpl, add_special_tokens=False, return_tensors="pt").to("cuda")

with torch.inference_mode():
    out = model.generate(
        **inputs,
        max_new_tokens=16,
        do_sample=False,
        use_cache=True,
        stop_strings=["\n", "<|im_end|>"],
        tokenizer=processor.tokenizer,
    )

prediction = processor.decode(
    out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True
).strip()
print(prediction)   # ์˜ˆ: "๋ฐฐ์ถ”ํฐ๋‚˜๋น„"

๋น ๋ฅธ ์„œ๋น™์„ ์œ„ํ•œ ์–ด๋Œ‘ํ„ฐ ๋ณ‘ํ•ฉ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” LoRA ๊ฐ€์ค‘์น˜๋ฅผ ๋ฒ ์ด์Šค ๋ชจ๋ธ์— ํ•œ ๋ฒˆ๋งŒ ๋ณ‘ํ•ฉํ•ด ๋‘๋ฉด, ์ถ”๋ก  ์‹œ PEFT๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ฒŒ ๋˜์–ด forward pass๊ฐ€ ์•ฝ 20% ๋นจ๋ผ์ง‘๋‹ˆ๋‹ค.

from transformers import AutoModelForImageTextToText, AutoProcessor
from peft import PeftModel
import torch

model = AutoModelForImageTextToText.from_pretrained(
    "unsloth/Qwen3.5-9B", dtype=torch.bfloat16, device_map="cuda",
)
model = PeftModel.from_pretrained(model, "pfox1995/pest-detector-final")
model = model.merge_and_unload()   # LoRA๋ฅผ ๋ฒ ์ด์Šค ๊ฐ€์ค‘์น˜์— ๋ณ‘ํ•ฉ
model.save_pretrained("./pest-detector-merged", safe_serialization=True)
AutoProcessor.from_pretrained("unsloth/Qwen3.5-9B").save_pretrained("./pest-detector-merged")

๋ณ‘ํ•ฉ ํ›„์—๋Š” ์„œ๋น™ ์‹œ peft ์—†์ด ์ˆœ์ˆ˜ transformers๋งŒ์œผ๋กœ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


HTTP API๋กœ ์„œ๋น™ํ•˜๊ธฐ

๋ฐ”๋กœ ์“ธ ์ˆ˜ ์žˆ๋Š” FastAPI ์„œ๋ฒ„๊ฐ€ pfox1995/pest-hyperparameter-search/serving/์— ์ค€๋น„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

์ตœ์†Œ ๋ฐฐํฌ ์ ˆ์ฐจ

git clone https://github.com/pfox1995/pest-hyperparameter-search.git
cd pest-hyperparameter-search/serving

pip install -r requirements.txt
python merge_adapter.py                               # ์•ฝ 2๋ถ„, 1ํšŒ๋งŒ ์‹คํ–‰
uvicorn serve:app --host 0.0.0.0 --port 8000          # ์›Œ๋ฐ์—…์ด ๋๋‚˜๋ฉด ์ค€๋น„ ์™„๋ฃŒ

ํ•˜๋“œ์›จ์–ด ๊ถŒ์žฅ ์‚ฌํ•ญ

GPU VRAM ๋น„๊ณ 
RTX A40 48 GB ๊ถŒ์žฅ. RunPod ์ŠคํŒŸ ๊ธฐ์ค€ $0.22/์‹œ. bf16 ์‹คํ–‰๊ณผ ๋™์‹œ ์š”์ฒญ ์ฒ˜๋ฆฌ์— ์ถฉ๋ถ„ํ•œ ์—ฌ์œ ๊ฐ€ ์žˆ์Œ.
RTX A6000 48 GB VRAM์€ ๋™์ผ, ํด๋Ÿญ์ด ์•ฝ 20% ๋น ๋ฆ„, ๊ฐ€๊ฒฉ์€ ๋” ๋น„์Œˆ.
RTX A5000 24 GB bf16์ด ๋น ๋“ฏํ•˜๊ฒŒ ๋“ค์–ด๊ฐ, ๋‹จ์ผ ์ŠคํŠธ๋ฆผ ์ „์šฉ.
A100 80 GB 80 GB 9B ๋ชจ๋ธ์—๋Š” ๊ณผํ•œ ์‚ฌ์–‘ โ€” ๊ฑด๋„ˆ๋›ฐ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

API ํ˜ธ์ถœ

curl -X POST http://your-pod:8000/predict \
  -H "Content-Type: application/json" \
  -d '{"image_base64": "'$(base64 -w0 pest.jpg)'"}'

์‘๋‹ต ์˜ˆ์‹œ:

{
  "pest": "๋ฐฐ์ถ”ํฐ๋‚˜๋น„",
  "raw": "๋ฐฐ์ถ”ํฐ๋‚˜๋น„",
  "known_class": true,
  "latency_ms": 980
}

์„œ๋ฒ„๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค์Œ ํ”„๋กœ๋•์…˜์šฉ ์ตœ์ ํ™”๋ฅผ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • stop_strings=["\n"]๋กœ ์ฒซ ์ค„๋ฐ”๊ฟˆ์—์„œ generate๋ฅผ ์ค‘๋‹จ (chat template์˜ trailing ํ† ํฐ ๋ฐฉ์ง€)
  • ์žฌํ˜„์„ฑ๊ณผ ์บ์‹œ ํšจ์œจ์„ ์œ„ํ•œ greedy decoding + ์ด๋ฏธ์ง€ ํ•ด์‹œ ๊ธฐ๋ฐ˜ LRU ์บ์‹œ
  • ํ•™์Šต ์‹œ ์ž…๋ ฅ ๋ถ„ํฌ์— ๋งž์ถ”๊ธฐ ์œ„ํ•œ 768 px letterbox resize
  • no-grad ์ถ”๋ก ์„ ์œ„ํ•œ torch.inference_mode()

Dockerfile๊ณผ ์Šค์ผ€์ผ๋ง ๊ฐ€์ด๋“œ๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๋ฐฐํฌ ๋ฌธ์„œ๋Š” ์„œ๋น™ README๋ฅผ ์ฐธ๊ณ ํ•˜์„ธ์š”.

์žฌํ˜„ (์ฐธ๊ณ ์šฉ)

git clone https://github.com/pfox1995/pest-hyperparameter-search.git
cd pest-hyperparameter-search

# 7๋‹จ ์•ˆ์ „์žฅ์น˜๊ฐ€ ๋ชจ๋‘ ๊ฑธ๋ฆฐ ํ•™์Šต
python3 train_final.py \
  --epochs 1 \
  --save-strategy steps --save-steps 25 --eval-steps 25

training_artifacts/final_train.log์— ํ•™์Šต ๋กœ๊ทธ ์›๋ณธ์ด ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ์žˆ๊ณ , training_artifacts/checkpoint-{850,925,950}/trainer_state.json์—์„œ ๊ฐ ๊ตฌ๊ฐ„์˜ loss / grad_norm / ํ•™์Šต๋ฅ  ๊ถค์ ์„ ์ง์ ‘ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


ํ•œ๊ณ„ ๋ฐ ์ฃผ์˜ ์‚ฌํ•ญ

  • ํ•œ๊ตญ์–ด ์ „์šฉ ์ถœ๋ ฅ. ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋ชจ๋ธ์—๊ฒŒ ํ•œ๊ตญ์–ด ํ•ด์ถฉ ์ด๋ฆ„๋งŒ ์ถœ๋ ฅํ•˜๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์‹œ์Šคํ…œ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์“ฐ๋ฉด ์ถœ๋ ฅ ํ˜•์‹์ด ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ณ ์ •๋œ 19๊ฐœ ํด๋ž˜์Šค ์–ดํœ˜. ๋ชจ๋ธ์€ ํ•™์Šต ์„ธํŠธ์— ์žˆ๋Š” ๋ผ๋ฒจ๋งŒ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค. 19๊ฐœ ํด๋ž˜์Šค๋ฅผ ๋ฒ—์–ด๋‚œ ๋“œ๋ฌธ ํ•ด์ถฉ์€ ์•Œ๋ ค์ง„ 19๊ฐœ ์ค‘ ํ•˜๋‚˜๋กœ ์ž˜๋ชป ๋ถ„๋ฅ˜๋ฉ๋‹ˆ๋‹ค.
  • ๋ฒ ์ด์Šค ๋ชจ๋ธ ํŠน์ด์  โ€” ์ƒ์„ฑ์ด EOS์—์„œ ํ•ญ์ƒ ๋ฉˆ์ถ”์ง€ ์•Š์Œ. ์ถ”๋ก  ์‹œ ๋ชจ๋ธ์ด ํ•ด์ถฉ ์ด๋ฆ„ ๋’ค์— chat template ํ† ํฐ(์˜ˆ: \nassistant)์„ ๋ง๋ถ™์ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. generate()์— stop_strings=["\n"] ์ธ์ˆ˜๋ฅผ ์ฃผ๋ฉด ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ œ๊ณต๋œ ์„œ๋น™ ์„œ๋ฒ„๋Š” ์ด๋ฅผ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋น„์ „ ํƒ€์›Œ ๋ฏผ๊ฐ๋„. ์žฅ๋ณ€ ๊ธฐ์ค€ 768 px ์ดํ•˜ ์ด๋ฏธ์ง€๋กœ ํ•™์Šตํ•œ ๋ชจ๋ธ์ž…๋‹ˆ๋‹ค. ๋„ˆ๋ฌด ํฐ ์ด๋ฏธ์ง€๋‚˜ ๋„ˆ๋ฌด ์ž‘์€ ์ด๋ฏธ์ง€๋Š” ์„ฑ๋Šฅ์ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋‹ˆ 512~768 px ๋ฒ”์œ„๋กœ letterbox resizeํ•ด์„œ ์“ฐ์‹œ๊ธธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ธ์šฉ

์ด ๋ชจ๋ธ์„ ์‚ฌ์šฉํ•˜์‹ค ๊ฒฝ์šฐ ๋‹ค์Œ์„ ์ธ์šฉํ•ด ์ฃผ์„ธ์š”:

@misc{pest-detector-final-2026,
  author = {pfox1995},
  title = {Korean Pest Detector โ€” Qwen3.5-9B LoRA Adapter},
  year = {2026},
  publisher = {Hugging Face},
  url = {https://huggingface.co/pfox1995/pest-detector-final},
}

๋งํฌ


Unsloth๋กœ 2๋ฐฐ ๋น ๋ฅด๊ฒŒ, VRAM์„ 70% ์•„๊ปด์„œ ํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค. PEFT ๊ธฐ๋ฐ˜์˜ LoRA ์–ด๋Œ‘ํ„ฐ์ž…๋‹ˆ๋‹ค.

Downloads last month
131
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Model tree for pfox1995/pest-detector-final

Finetuned
Qwen/Qwen3.5-9B
Adapter
(50)
this model

Evaluation results

  • Accuracy on Himedia-AI-01/pest-detection-korean
    self-reported
    0.914
  • F1 (macro) on Himedia-AI-01/pest-detection-korean
    self-reported
    0.903
  • F1 (weighted) on Himedia-AI-01/pest-detection-korean
    self-reported
    0.913
  • Precision (macro) on Himedia-AI-01/pest-detection-korean
    self-reported
    0.909
  • Recall (macro) on Himedia-AI-01/pest-detection-korean
    self-reported
    0.910