์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿง  TensorFlow๋กœ ๋š๋”ฑ! ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋งŒ๋“ค์–ด๋ณด๊ธฐ
๐Ÿค– AI / ๋จธ์‹ ๋Ÿฌ๋‹ ๊ฐœ๋ฐœ

๐Ÿง  TensorFlow๋กœ ๋š๋”ฑ! ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋งŒ๋“ค์–ด๋ณด๊ธฐ

๋”ฅ๋Ÿฌ๋‹์ด ์ฒ˜์Œ์ด์–ด๋„ ๊ดœ์ฐฎ์•„ โ€” ์ฝ”๋“œ ํ•œ ์ค„์”ฉ ๊ฐ™์ด ๋”ฐ๋ผ๊ฐ€ ๋ณด์ž ๐Ÿš€

๐Ÿ“ ํ…์ŠคํŠธ ์ž…๋ ฅ "์ด ์˜ํ™” ์ •๋ง ์žฌ๋ฐŒ์—ˆ์–ด์š”!" ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ โš™๏ธ ์ „์ฒ˜๋ฆฌ ํ† ํฐํ™” ํŒจ๋”ฉ ์ฒ˜๋ฆฌ ๋ฒกํ„ฐ ๋ณ€ํ™˜ ๐Ÿง  ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ Embedding ์ธต LSTM / Dense ์ธต Dropout ์ธต Softmax ์ถœ๋ ฅ โœ… ๋ถ„๋ฅ˜ ๊ฒฐ๊ณผ ๐Ÿ˜Š ๊ธ์ • : 94% ๐Ÿ˜ž ๋ถ€์ • : 6% ๊ฐ์„ฑ ๋ถ„์„ ์™„๋ฃŒ! TensorFlow ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ํŒŒ์ดํ”„๋ผ์ธ ์ž…๋ ฅ โ†’ ์ „์ฒ˜๋ฆฌ โ†’ ๋ชจ๋ธ ํ•™์Šต โ†’ ์˜ˆ์ธก ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ชจ๋ธ์„ ํ†ต๊ณผํ•˜๋ฉด์„œ ํ…์ŠคํŠธ์˜ ์˜๋ฏธ๋ฅผ ํ•™์Šตํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•ฉ๋‹ˆ๋‹ค
๐ŸŽฏ ํ…์ŠคํŠธ ๋ถ„๋ฅ˜, ์™œ ๋ฐฐ์›Œ์•ผ ํ• ๊นŒ?

์•ˆ๋…•! ์˜ค๋Š˜์€ TensorFlow๋ฅผ ์ด์šฉํ•ด์„œ ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์•ผ.
"ํ…์ŠคํŠธ ๋ถ„๋ฅ˜"๋ผ๊ณ  ํ•˜๋ฉด ๋ญ”๊ฐ€ ์–ด๋ ต๊ฒŒ ๋“ค๋ฆด ์ˆ˜ ์žˆ๋Š”๋ฐ, ์‚ฌ์‹ค ์šฐ๋ฆฌ ์ผ์ƒ์—์„œ ์—„์ฒญ ๋งŽ์ด ์“ฐ์ด๋Š” ๊ธฐ์ˆ ์ด์•ผ. ๐Ÿค”

์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ:

๐Ÿ“ง ์ŠคํŒธ ๋ฉ”์ผ ํ•„ํ„ฐ โ€” "์ด ๋ฉ”์ผ์ด ์ŠคํŒธ์ธ๊ฐ€, ์•„๋‹Œ๊ฐ€?"

๐ŸŽฌ ์˜ํ™” ๋ฆฌ๋ทฐ ๊ฐ์„ฑ ๋ถ„์„ โ€” "์ด ๋ฆฌ๋ทฐ๊ฐ€ ๊ธ์ •์ ์ธ๊ฐ€, ๋ถ€์ •์ ์ธ๊ฐ€?"

๐Ÿ“ฐ ๋‰ด์Šค ์นดํ…Œ๊ณ ๋ฆฌ ๋ถ„๋ฅ˜ โ€” "์ด ๊ธฐ์‚ฌ๊ฐ€ ์Šคํฌ์ธ ์ธ๊ฐ€, ์ •์น˜์ธ๊ฐ€, ๊ฒฝ์ œ์ธ๊ฐ€?"

๐Ÿ’ฌ ๊ณ ๊ฐ ๋ฌธ์˜ ์ž๋™ ๋ถ„๋ฅ˜ โ€” "์ด ๋ฌธ์˜๊ฐ€ ํ™˜๋ถˆ ์š”์ฒญ์ธ๊ฐ€, ๋ฐฐ์†ก ๋ฌธ์˜์ธ๊ฐ€?"

์ด ๋ชจ๋“  ๊ฒŒ ๋‹ค ํ…์ŠคํŠธ ๋ถ„๋ฅ˜(Text Classification)์•ผ!
ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„์„œ ๋ฏธ๋ฆฌ ์ •ํ•ด์ง„ ์นดํ…Œ๊ณ ๋ฆฌ ์ค‘ ํ•˜๋‚˜๋กœ ๋ถ„๋ฅ˜ํ•˜๋Š” ์ž‘์—…์ด์ง€.
๊ทธ๋ฆฌ๊ณ  ์ด๊ฑธ ๋”ฅ๋Ÿฌ๋‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด ์‚ฌ๋žŒ์ด ์ผ์ผ์ด ๊ทœ์น™์„ ์งœ์ง€ ์•Š์•„๋„ ๋ชจ๋ธ์ด ์•Œ์•„์„œ ํŒจํ„ด์„ ํ•™์Šตํ•ด์„œ ๋ถ„๋ฅ˜ํ•ด์ค˜. ์™„์ „ ์‹ ๊ธฐํ•˜์ง€? ๐Ÿ˜Ž

๐Ÿ’ก ์ด ๊ธ€์—์„œ ๋งŒ๋“ค ๊ฒƒ: IMDB ์˜ํ™” ๋ฆฌ๋ทฐ ๋ฐ์ดํ„ฐ์…‹์„ ์ด์šฉํ•ด์„œ ๋ฆฌ๋ทฐ๊ฐ€ ๊ธ์ •(Positive)์ธ์ง€ ๋ถ€์ •(Negative)์ธ์ง€ ๋ถ„๋ฅ˜ํ•˜๋Š” ์ด์ง„ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ TensorFlow/Keras๋กœ ๋งŒ๋“ค์–ด๋ณผ ๊ฑฐ์•ผ!

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…๋ถ€ํ„ฐ ์‹œ์ž‘!

์ฝ”๋“œ๋ฅผ ์งœ๊ธฐ ์ „์— ํ™˜๊ฒฝ๋ถ€ํ„ฐ ๋งž์ถฐ์•ผ๊ฒ ์ง€? ๊ฑฑ์ • ๋งˆ, ์—„์ฒญ ๊ฐ„๋‹จํ•ด.
์š”์ฆ˜์€ Google Colab์„ ์“ฐ๋ฉด ๋ณ„๋„ ์„ค์น˜ ์—†์ด ๋ฐ”๋กœ TensorFlow๋ฅผ ์“ธ ์ˆ˜ ์žˆ์–ด์„œ ์ง„์งœ ํŽธํ•ด. ๐Ÿ™Œ

๐Ÿ’ป ํ•„์š”ํ•œ ๊ฒƒ๋“ค
๋„๊ตฌ ๋ฒ„์ „ ์„ค๋ช…
Python 3.8 ์ด์ƒ ๊ธฐ๋ณธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด
TensorFlow 2.10 ์ด์ƒ ๋”ฅ๋Ÿฌ๋‹ ํ”„๋ ˆ์ž„์›Œํฌ (Keras ํฌํ•จ)
NumPy 1.21 ์ด์ƒ ์ˆ˜์น˜ ์—ฐ์‚ฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
Matplotlib 3.5 ์ด์ƒ ํ•™์Šต ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”
๐Ÿ“ฆ ์„ค์น˜ ๋ฐฉ๋ฒ•

๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ ์ž‘์—…ํ•œ๋‹ค๋ฉด ํ„ฐ๋ฏธ๋„์—์„œ ์ด๋ ‡๊ฒŒ ์ž…๋ ฅํ•ด:

pip install tensorflow numpy matplotlib

Google Colab์„ ์“ด๋‹ค๋ฉด? ์ด๋ฏธ ๋‹ค ์„ค์น˜๋˜์–ด ์žˆ์–ด! ๊ทธ๋ƒฅ ๋ฐ”๋กœ importํ•˜๋ฉด ๋ผ. ๐Ÿ‘

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

print(f"TensorFlow ๋ฒ„์ „: {tf.__version__}")
# ์ถœ๋ ฅ ์˜ˆ์‹œ: TensorFlow ๋ฒ„์ „: 2.13.0
โœ… ํŒ! TensorFlow 2.x ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” Keras๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด์„œ ๋”ฐ๋กœ ์„ค์น˜ํ•  ํ•„์š” ์—†์–ด. tf.keras๋กœ ๋ฐ”๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด!

๐Ÿ“Š IMDB ๋ฐ์ดํ„ฐ์…‹ โ€” ์šฐ๋ฆฌ์˜ ํ›ˆ๋ จ ์žฌ๋ฃŒ!

์˜ค๋Š˜ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ์…‹์€ IMDB Movie Reviews Dataset์ด์•ผ.
์ด๊ฑด ์˜ํ™” ๋ฆฌ๋ทฐ 50,000๊ฐœ๋กœ ๊ตฌ์„ฑ๋œ ๋ฐ์ดํ„ฐ์…‹์ธ๋ฐ, TensorFlow์— ๊ธฐ๋ณธ์œผ๋กœ ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด์„œ ๋”ฐ๋กœ ๋‹ค์šด๋ฐ›์„ ํ•„์š”๊ฐ€ ์—†์–ด. ์™„์ „ ํŽธํ•˜์ง€? ๐Ÿ˜„

๐Ÿ“Œ IMDB ๋ฐ์ดํ„ฐ์…‹ ๊ตฌ์„ฑ ๐ŸŽฌ ์ด ๋ฆฌ๋ทฐ ์ˆ˜: 50,000๊ฐœ
๐Ÿ“š ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ: 25,000๊ฐœ (๊ธ์ • 12,500๊ฐœ + ๋ถ€์ • 12,500๊ฐœ)
๐Ÿงช ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ: 25,000๊ฐœ (๊ธ์ • 12,500๊ฐœ + ๋ถ€์ • 12,500๊ฐœ)
๐Ÿท๏ธ ๋ ˆ์ด๋ธ”: 0 = ๋ถ€์ •(Negative), 1 = ๊ธ์ •(Positive)

๋ฐ์ดํ„ฐ๋Š” ์ด๋ฏธ ์ˆซ์ž๋กœ ์ธ์ฝ”๋”ฉ๋˜์–ด ์žˆ์–ด.
์ฆ‰, ๊ฐ ๋‹จ์–ด๊ฐ€ ์ •์ˆ˜ ์ธ๋ฑ์Šค๋กœ ๋ณ€ํ™˜๋œ ์ƒํƒœ์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด "the"๋Š” 1, "movie"๋Š” 17 ์ด๋Ÿฐ ์‹์œผ๋กœ.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ปดํ“จํ„ฐ๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ!

๐Ÿ“ฅ ๋ฐ์ดํ„ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
from tensorflow.keras.datasets import imdb

# ์ƒ์œ„ 10,000๊ฐœ ๋‹จ์–ด๋งŒ ์‚ฌ์šฉ (๋นˆ๋„ ๊ธฐ์ค€)
vocab_size = 10000

(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words=vocab_size
)

print(f"ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ ์ˆ˜: {len(train_data)}")
print(f"ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ˆ˜: {len(test_data)}")
print(f"์ฒซ ๋ฒˆ์งธ ๋ฆฌ๋ทฐ (์ˆซ์ž ์ธ์ฝ”๋”ฉ): {train_data[0][:20]}...")
print(f"์ฒซ ๋ฒˆ์งธ ๋ฆฌ๋ทฐ ๋ ˆ์ด๋ธ”: {train_labels[0]}")

# ์ถœ๋ ฅ:
# ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ ์ˆ˜: 25000
# ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ์ˆ˜: 25000
# ์ฒซ ๋ฒˆ์งธ ๋ฆฌ๋ทฐ (์ˆซ์ž ์ธ์ฝ”๋”ฉ): [1, 14, 22, 16, 43, 530, 973, ...]
# ์ฒซ ๋ฒˆ์งธ ๋ฆฌ๋ทฐ ๋ ˆ์ด๋ธ”: 1 (๊ธ์ •!)
๐Ÿ” ๋ฐ์ดํ„ฐ ํƒ์ƒ‰ โ€” ์‹ค์ œ ํ…์ŠคํŠธ๋กœ ๋ณต์›ํ•ด๋ณด๊ธฐ

์ˆซ์ž๋กœ๋งŒ ๋ณด๋ฉด ๋ญ”์ง€ ๋ชจ๋ฅด๋‹ˆ๊นŒ, ์‹ค์ œ ๋‹จ์–ด๋กœ ๋ณต์›ํ•ด์„œ ํ™•์ธํ•ด๋ณด์ž!

# ๋‹จ์–ด ์ธ๋ฑ์Šค ๋”•์…”๋„ˆ๋ฆฌ ๊ฐ€์ ธ์˜ค๊ธฐ
word_index = imdb.get_word_index()

# ์ธ๋ฑ์Šค โ†’ ๋‹จ์–ด ์—ญ๋ฐฉํ–ฅ ๋”•์…”๋„ˆ๋ฆฌ ๋งŒ๋“ค๊ธฐ
# ์‹ค์ œ ์ธ๋ฑ์Šค๋Š” 3์”ฉ ์˜คํ”„์…‹์ด ์žˆ์–ด (0=ํŒจ๋”ฉ, 1=์‹œ์ž‘, 2=์•Œ์ˆ˜์—†์Œ)
reverse_word_index = {value + 3: key for key, value in word_index.items()}
reverse_word_index[0] = "<PAD>"
reverse_word_index[1] = "<START>"
reverse_word_index[2] = "<UNK>"

# ์ฒซ ๋ฒˆ์งธ ๋ฆฌ๋ทฐ ๋ณต์›
def decode_review(encoded_review):
    return " ".join([reverse_word_index.get(i, "?") for i in encoded_review])

print(decode_review(train_data[0][:50]))
# ์ถœ๋ ฅ: <START> this film was just brilliant casting location scenery...
โœ… ์•Œ์•„๋‘๊ธฐ! IMDB ๋ฐ์ดํ„ฐ์…‹์—์„œ ๋‹จ์–ด ์ธ๋ฑ์Šค๋Š” 3๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด. 0, 1, 2๋Š” ๊ฐ๊ฐ ํŒจ๋”ฉ, ์‹œ์ž‘ ํ† ํฐ, ์•Œ ์ˆ˜ ์—†๋Š” ๋‹จ์–ด๋ฅผ ์œ„ํ•ด ์˜ˆ์•ฝ๋˜์–ด ์žˆ๊ฑฐ๋“ . ์ด ์˜คํ”„์…‹์„ ๋งž์ถฐ์ค˜์•ผ ์ œ๋Œ€๋กœ ๋ณต์›๋ผ!
๐Ÿ“ ๋ฆฌ๋ทฐ ๊ธธ์ด ๋ถ„ํฌ ํ™•์ธ

๊ฐ ๋ฆฌ๋ทฐ์˜ ๊ธธ์ด๊ฐ€ ๋‹ค ๋‹ฌ๋ผ. ์–ด๋–ค ๊ฑด ์งง๊ณ  ์–ด๋–ค ๊ฑด ์—„์ฒญ ๊ธธ์–ด.
์ด๊ฑธ ํ™•์ธํ•ด์•ผ ๋‚˜์ค‘์— ํŒจ๋”ฉ ๊ธธ์ด๋ฅผ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ์–ด!

lengths = [len(review) for review in train_data]
print(f"ํ‰๊ท  ๋ฆฌ๋ทฐ ๊ธธ์ด: {np.mean(lengths):.0f} ๋‹จ์–ด")
print(f"์ตœ๋Œ€ ๋ฆฌ๋ทฐ ๊ธธ์ด: {max(lengths)} ๋‹จ์–ด")
print(f"์ตœ์†Œ ๋ฆฌ๋ทฐ ๊ธธ์ด: {min(lengths)} ๋‹จ์–ด")
print(f"์ค‘์•™๊ฐ’: {np.median(lengths):.0f} ๋‹จ์–ด")

# ์ถœ๋ ฅ:
# ํ‰๊ท  ๋ฆฌ๋ทฐ ๊ธธ์ด: 238 ๋‹จ์–ด
# ์ตœ๋Œ€ ๋ฆฌ๋ทฐ ๊ธธ์ด: 2494 ๋‹จ์–ด
# ์ตœ์†Œ ๋ฆฌ๋ทฐ ๊ธธ์ด: 11 ๋‹จ์–ด
# ์ค‘์•™๊ฐ’: 178 ๋‹จ์–ด
ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ ๊ณผ์ • ํ•œ๋ˆˆ์— ๋ณด๊ธฐ โ‘  ์›๋ณธ ํ…์ŠคํŠธ "This movie was absolutely amazing!" โ†’ โ‘ก ํ† ํฐํ™” ["This", "movie", "was", "amazing"] โ†’ โ‘ข ์ •์ˆ˜ ์ธ์ฝ”๋”ฉ [14, 17, 43, 530, 22, ...] โ†“ โ‘ฃ ํŒจ๋”ฉ ์ฒ˜๋ฆฌ [0, 0, 14, 17, 43, 530, 22, ...] โ† โ‘ค ์ž„๋ฒ ๋”ฉ ๋ณ€ํ™˜ [[0.2, -0.5, 0.8], [0.1, 0.9, -0.3]...] โ† โ‘ฅ ๋ชจ๋ธ ํ•™์Šต! Dense, LSTM ๋“ฑ ๋ ˆ์ด์–ด ํ†ต๊ณผ โ†’ ๋ถ„๋ฅ˜ ๐Ÿ”‘ ํ•ต์‹ฌ ํฌ์ธํŠธ ํ…์ŠคํŠธ๋Š” ๊ทธ๋Œ€๋กœ ๋ชจ๋ธ์— ๋„ฃ์„ ์ˆ˜ ์—†์–ด! ์ˆซ์ž๋กœ ๋ณ€ํ™˜ โ†’ ๊ฐ™์€ ๊ธธ์ด๋กœ ํŒจ๋”ฉ โ†’ ๋ฒกํ„ฐ๋กœ ์ž„๋ฒ ๋”ฉ โ†’ ๊ทธ ๋‹ค์Œ ํ•™์Šต! ์ด ๊ณผ์ •์ด NLP์˜ ๊ธฐ๋ณธ ํŒŒ์ดํ”„๋ผ์ธ์ด์•ผ. ๊ผญ ๊ธฐ์–ตํ•ด๋‘์ž! ๐Ÿ’ช

โš™๏ธ ๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ โ€” ๋ชจ๋ธ์ด ๋จน์„ ์ˆ˜ ์žˆ๋Š” ํ˜•ํƒœ๋กœ!

๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ์€ ๋ชจ๋“  ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ํฌ๊ธฐ๊ฐ€ ๊ฐ™์•„์•ผ ํ•ด.
๊ทผ๋ฐ ๋ฆฌ๋ทฐ๋งˆ๋‹ค ๊ธธ์ด๊ฐ€ ๋‹ค๋ฅด์ž–์•„? ์–ด๋–ค ๊ฑด 20๋‹จ์–ด, ์–ด๋–ค ๊ฑด 2000๋‹จ์–ด.
์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒŒ ๋ฐ”๋กœ ํŒจ๋”ฉ(Padding)์ด์•ผ! ๐ŸŽฏ

๐Ÿ“ ์‹œํ€€์Šค ํŒจ๋”ฉ

ํŒจ๋”ฉ์€ ์งง์€ ์‹œํ€€์Šค์— 0์„ ์ฑ„์›Œ์„œ ๊ธธ์ด๋ฅผ ๋งž์ถ”๋Š” ๊ฑฐ์•ผ.
๋ฐ˜๋Œ€๋กœ ๋„ˆ๋ฌด ๊ธด ์‹œํ€€์Šค๋Š” ์ž˜๋ผ๋‚ด๊ธฐ๋„ ํ•ด. ์ด๊ฑธ ํŠธ๋Ÿฐ์ผ€์ด์…˜(Truncation)์ด๋ผ๊ณ  ํ•ด.

from tensorflow.keras.preprocessing.sequence import pad_sequences

# ์ตœ๋Œ€ ๊ธธ์ด ์„ค์ • (ํ‰๊ท  ๊ธธ์ด ๊ทผ์ฒ˜๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒŒ ์ผ๋ฐ˜์ )
max_length = 256

# ํŒจ๋”ฉ ์ฒ˜๋ฆฌ
# padding='post': ๋’ค์— 0์„ ์ฑ„์›€
# truncating='post': ๋’ค๋ฅผ ์ž˜๋ผ๋ƒ„
train_data_padded = pad_sequences(
    train_data,
    maxlen=max_length,
    padding='post',
    truncating='post'
)

test_data_padded = pad_sequences(
    test_data,
    maxlen=max_length,
    padding='post',
    truncating='post'
)

print(f"ํŒจ๋”ฉ ํ›„ ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ shape: {train_data_padded.shape}")
print(f"ํŒจ๋”ฉ ํ›„ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ shape: {test_data_padded.shape}")

# ์ถœ๋ ฅ:
# ํŒจ๋”ฉ ํ›„ ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ shape: (25000, 256)
# ํŒจ๋”ฉ ํ›„ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ shape: (25000, 256)
๐Ÿ’ก padding='pre' vs 'post'? - pre: ์•ž์— 0์„ ์ฑ„์›€ โ†’ [0, 0, 0, 14, 17, 43]
- post: ๋’ค์— 0์„ ์ฑ„์›€ โ†’ [14, 17, 43, 0, 0, 0]
LSTM ๊ณ„์—ด ๋ชจ๋ธ์€ pre๊ฐ€ ๋” ์ข‹์„ ๋•Œ๋„ ์žˆ๊ณ , ๋‹จ์ˆœ Dense ๋ชจ๋ธ์€ ์–ด๋А ์ชฝ์ด๋“  ํฌ๊ฒŒ ์ฐจ์ด ์—†์–ด. ์‹คํ—˜ํ•ด๋ณด๋Š” ๊ฒŒ ์ตœ๊ณ !
๐Ÿท๏ธ ๋ ˆ์ด๋ธ” ๋ฐฐ์—ด ๋ณ€ํ™˜
import numpy as np

# ๋ ˆ์ด๋ธ”์„ numpy ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
train_labels = np.array(train_labels)
test_labels = np.array(test_labels)

print(f"ํ›ˆ๋ จ ๋ ˆ์ด๋ธ” shape: {train_labels.shape}")
print(f"๊ธ์ • ๋ฆฌ๋ทฐ ์ˆ˜: {np.sum(train_labels == 1)}")
print(f"๋ถ€์ • ๋ฆฌ๋ทฐ ์ˆ˜: {np.sum(train_labels == 0)}")

# ์ถœ๋ ฅ:
# ํ›ˆ๋ จ ๋ ˆ์ด๋ธ” shape: (25000,)
# ๊ธ์ • ๋ฆฌ๋ทฐ ์ˆ˜: 12500
# ๋ถ€์ • ๋ฆฌ๋ทฐ ์ˆ˜: 12500

์™„๋ฒฝํ•˜๊ฒŒ ๊ท ํ˜• ์žกํžŒ ๋ฐ์ดํ„ฐ์…‹์ด์•ผ! ๊ธ์ • 50%, ๋ถ€์ • 50%.
์ด๋Ÿฐ ๊ฒฝ์šฐ์—” ํด๋ž˜์Šค ๋ถˆ๊ท ํ˜• ๋ฌธ์ œ๋ฅผ ๊ฑฑ์ • ์•ˆ ํ•ด๋„ ๋ผ. ๐Ÿ‘Œ

๐Ÿ”€ ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ

ํ›ˆ๋ จ ์ค‘์— ๋ชจ๋ธ์ด ์ž˜ ํ•™์Šต๋˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋ ค๋ฉด ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ(Validation Data)๊ฐ€ ํ•„์š”ํ•ด.
ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ์˜ ์ผ๋ถ€๋ฅผ ๋–ผ์–ด์„œ ๊ฒ€์ฆ์šฉ์œผ๋กœ ์“ฐ์ž!

# ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ์˜ 20%๋ฅผ ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉ
val_size = 5000

x_val = train_data_padded[:val_size]
y_val = train_labels[:val_size]

x_train = train_data_padded[val_size:]
y_train = train_labels[val_size:]

print(f"์‹ค์ œ ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ: {x_train.shape}")
print(f"๊ฒ€์ฆ ๋ฐ์ดํ„ฐ: {x_val.shape}")

# ์ถœ๋ ฅ:
# ์‹ค์ œ ํ›ˆ๋ จ ๋ฐ์ดํ„ฐ: (20000, 256)
# ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ: (5000, 256)

๐Ÿ—๏ธ ๋ชจ๋ธ ์„ค๊ณ„ โ€” ๋“œ๋””์–ด ์ง„์งœ ๋”ฅ๋Ÿฌ๋‹!

์ด์ œ ์ง„์งœ ์žฌ๋ฐŒ๋Š” ๋ถ€๋ถ„์ด์•ผ! ๐ŸŽ‰
ํ…์ŠคํŠธ ๋ถ„๋ฅ˜์— ์“ธ ์ˆ˜ ์žˆ๋Š” ๋ชจ๋ธ ๊ตฌ์กฐ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์žˆ๋Š”๋ฐ, ์˜ค๋Š˜์€ ์„ธ ๊ฐ€์ง€๋ฅผ ์†Œ๊ฐœํ• ๊ฒŒ.
๊ฐ๊ฐ ์žฅ๋‹จ์ ์ด ์žˆ์œผ๋‹ˆ๊นŒ ์ƒํ™ฉ์— ๋งž๊ฒŒ ๊ณจ๋ผ ์“ฐ๋ฉด ๋ผ!

๐Ÿ”ต ๋ชจ๋ธ 1: ๋‹จ์ˆœ Dense ๋ชจ๋ธ (๊ธฐ๋ณธ๊ธฐ ๋‹ค์ง€๊ธฐ)

๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ๊ตฌ์กฐ์•ผ. Embedding ๋ ˆ์ด์–ด๋กœ ๋‹จ์–ด๋ฅผ ๋ฒกํ„ฐ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ ,
GlobalAveragePooling์œผ๋กœ ํ‰๊ท ์„ ๋‚ด์„œ Dense ๋ ˆ์ด์–ด๋กœ ๋ถ„๋ฅ˜ํ•ด.

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
    Embedding, GlobalAveragePooling1D, Dense, Dropout
)

embedding_dim = 64  # ๊ฐ ๋‹จ์–ด๋ฅผ 64์ฐจ์› ๋ฒกํ„ฐ๋กœ ํ‘œํ˜„

model_dense = Sequential([
    # ์ž„๋ฒ ๋”ฉ ๋ ˆ์ด์–ด: ๋‹จ์–ด ์ธ๋ฑ์Šค โ†’ ๋ฐ€์ง‘ ๋ฒกํ„ฐ
    Embedding(
        input_dim=vocab_size,      # ์–ดํœ˜ ํฌ๊ธฐ (10,000)
        output_dim=embedding_dim,  # ์ž„๋ฒ ๋”ฉ ์ฐจ์› (64)
        input_length=max_length    # ์ž…๋ ฅ ์‹œํ€€์Šค ๊ธธ์ด (256)
    ),
    
    # ์‹œํ€€์Šค ์ „์ฒด๋ฅผ ํ‰๊ท  ๋‚ด์„œ ๊ณ ์ • ํฌ๊ธฐ ๋ฒกํ„ฐ๋กœ ๋งŒ๋“ค๊ธฐ
    GlobalAveragePooling1D(),
    
    # ์™„์ „ ์—ฐ๊ฒฐ ๋ ˆ์ด์–ด
    Dense(64, activation='relu'),
    Dropout(0.3),  # ๊ณผ์ ํ•ฉ ๋ฐฉ์ง€
    
    # ์ถœ๋ ฅ ๋ ˆ์ด์–ด (์ด์ง„ ๋ถ„๋ฅ˜ โ†’ sigmoid)
    Dense(1, activation='sigmoid')
])

model_dense.summary()
๐Ÿ“‹ ๋ชจ๋ธ ๊ตฌ์กฐ ์š”์•ฝ ๐Ÿ”น Embedding(10000, 64, 256) โ†’ ์ถœ๋ ฅ: (None, 256, 64)
๐Ÿ”น GlobalAveragePooling1D โ†’ ์ถœ๋ ฅ: (None, 64)
๐Ÿ”น Dense(64, relu) โ†’ ์ถœ๋ ฅ: (None, 64)
๐Ÿ”น Dropout(0.3) โ†’ ์ถœ๋ ฅ: (None, 64)
๐Ÿ”น Dense(1, sigmoid) โ†’ ์ถœ๋ ฅ: (None, 1)

์ด ํŒŒ๋ผ๋ฏธํ„ฐ: ์•ฝ 650,000๊ฐœ
๐ŸŸข ๋ชจ๋ธ 2: LSTM ๋ชจ๋ธ (์ˆœ์„œ ์ •๋ณด ํ™œ์šฉ)

LSTM(Long Short-Term Memory)์€ ํ…์ŠคํŠธ์˜ ์ˆœ์„œ ์ •๋ณด๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ชจ๋ธ์ด์•ผ.
"๋‚˜๋Š” ์˜ํ™”๋ฅผ ์ข‹์•„ํ•ด"์™€ "๋‚˜๋Š” ์˜ํ™”๋ฅผ ์‹ซ์–ดํ•ด"๋Š” ๋‹จ์–ด ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•˜์ž–์•„?
LSTM์€ ์ด๋Ÿฐ ์ˆœ์„œ ์˜์กด์„ฑ์„ ์ž˜ ํฌ์ฐฉํ•ด!

from tensorflow.keras.layers import LSTM, Bidirectional

model_lstm = Sequential([
    Embedding(
        input_dim=vocab_size,
        output_dim=embedding_dim,
        input_length=max_length
    ),
    
    # Bidirectional LSTM: ์•ž๋’ค ๋ฐฉํ–ฅ ๋ชจ๋‘ ์ฒ˜๋ฆฌ
    Bidirectional(LSTM(64, return_sequences=False)),
    
    Dense(64, activation='relu'),
    Dropout(0.4),
    
    Dense(1, activation='sigmoid')
])

model_lstm.summary()
๐Ÿ’ก Bidirectional์ด ๋ญ์•ผ? ์ผ๋ฐ˜ LSTM์€ ์™ผ์ชฝโ†’์˜ค๋ฅธ์ชฝ์œผ๋กœ๋งŒ ์ฝ์–ด. ๊ทผ๋ฐ Bidirectional์€ ์™ผ์ชฝโ†’์˜ค๋ฅธ์ชฝ, ์˜ค๋ฅธ์ชฝโ†’์™ผ์ชฝ ๋‘ ๋ฐฉํ–ฅ์œผ๋กœ ์ฝ์–ด์„œ ๋ฌธ๋งฅ์„ ๋” ์ž˜ ํŒŒ์•…ํ•ด! ์„ฑ๋Šฅ์ด ๋ณดํ†ต ๋” ์ข‹์•„. ๐Ÿ˜Ž
๐ŸŸ  ๋ชจ๋ธ 3: Conv1D ๋ชจ๋ธ (๋น ๋ฅด๊ณ  ๊ฐ•๋ ฅ!)

1D ํ•ฉ์„ฑ๊ณฑ ์‹ ๊ฒฝ๋ง(Conv1D)์€ ์ด๋ฏธ์ง€ ๋ถ„๋ฅ˜์—์„œ ์“ฐ๋Š” CNN์„ ํ…์ŠคํŠธ์— ์ ์šฉํ•œ ๊ฑฐ์•ผ.
ํŠน์ • ํŒจํ„ด(n-gram)์„ ํšจ์œจ์ ์œผ๋กœ ํฌ์ฐฉํ•˜๊ณ , LSTM๋ณด๋‹ค ํ›ˆ๋ จ ์†๋„๊ฐ€ ๋นจ๋ผ!

from tensorflow.keras.layers import Conv1D, MaxPooling1D, GlobalMaxPooling1D

model_conv = Sequential([
    Embedding(
        input_dim=vocab_size,
        output_dim=embedding_dim,
        input_length=max_length
    ),
    
    # 1D ํ•ฉ์„ฑ๊ณฑ: 3๊ฐœ ๋‹จ์–ด์”ฉ ํŒจํ„ด ํฌ์ฐฉ
    Conv1D(128, 5, activation='relu'),
    MaxPooling1D(pool_size=2),
    
    Conv1D(64, 3, activation='relu'),
    GlobalMaxPooling1D(),
    
    Dense(64, activation='relu'),
    Dropout(0.3),
    
    Dense(1, activation='sigmoid')
])

model_conv.summary()
๋ชจ๋ธ ์žฅ์  ๋‹จ์  ์ถ”์ฒœ ์ƒํ™ฉ
Dense ๋น ๋ฆ„, ๋‹จ์ˆœ ์ˆœ์„œ ์ •๋ณด ๋ฌด์‹œ ๋น ๋ฅธ ๋ฒ ์ด์Šค๋ผ์ธ
LSTM ์ˆœ์„œ ์ •๋ณด ํ™œ์šฉ ๋А๋ฆผ, ๋ฉ”๋ชจ๋ฆฌ ๋งŽ์ด ์”€ ๊ธด ํ…์ŠคํŠธ, ๋ฌธ๋งฅ ์ค‘์š”
Conv1D ๋น ๋ฆ„, ํŒจํ„ด ํฌ์ฐฉ ์šฐ์ˆ˜ ์žฅ๊ฑฐ๋ฆฌ ์˜์กด์„ฑ ์•ฝํ•จ ์งง์€ ํ…์ŠคํŠธ, ์†๋„ ์ค‘์š”
Dense ๋ชจ๋ธ ๋ ˆ์ด์–ด ๊ตฌ์กฐ ์‹œ๊ฐํ™” ๐Ÿ“ฅ ์ž…๋ ฅ (256๊ฐœ ์ •์ˆ˜) Embedding ๋ ˆ์ด์–ด 10,000 ๋‹จ์–ด โ†’ 64์ฐจ์› ๋ฒกํ„ฐ (256ร—64 ํ–‰๋ ฌ) GlobalAveragePooling1D 256ร—64 โ†’ 64 (์‹œํ€€์Šค ํ‰๊ท ) Dense(64) + Dropout(0.3) 64 ๋‰ด๋Ÿฐ, ReLU ํ™œ์„ฑํ™” Dense(1) + Sigmoid 0~1 ์‚ฌ์ด ํ™•๋ฅ ๊ฐ’ ์ถœ๋ ฅ ๐Ÿ˜Š ๊ธ์ • / ๐Ÿ˜ž ๋ถ€์ • ์ถœ๋ ฅ: (None, 256, 64) ์ถœ๋ ฅ: (None, 64) ์ถœ๋ ฅ: (None, 64) ์ถœ๋ ฅ: (None, 1)

๐Ÿ”ฅ ๋ชจ๋ธ ์ปดํŒŒ์ผ & ํ›ˆ๋ จ โ€” ํ•™์Šต ์‹œ์ž‘!

๋ชจ๋ธ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ ์ปดํŒŒ์ผํ•˜๊ณ  ํ›ˆ๋ จ์‹œ์ผœ์•ผ ํ•ด!
์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ๋Š” ์†์‹ค ํ•จ์ˆ˜, ์˜ตํ‹ฐ๋งˆ์ด์ €, ํ‰๊ฐ€ ์ง€ํ‘œ๋ฅผ ์„ค์ •ํ•ด.

โš™๏ธ ๋ชจ๋ธ ์ปดํŒŒ์ผ
# Conv1D ๋ชจ๋ธ๋กœ ์ง„ํ–‰ (์†๋„์™€ ์„ฑ๋Šฅ ๊ท ํ˜•์ด ์ข‹์•„์„œ!)
model = model_conv

model.compile(
    optimizer='adam',                    # Adam ์˜ตํ‹ฐ๋งˆ์ด์ €
    loss='binary_crossentropy',          # ์ด์ง„ ๋ถ„๋ฅ˜ ์†์‹ค ํ•จ์ˆ˜
    metrics=['accuracy']                 # ์ •ํ™•๋„ ์ธก์ •
)

print("๋ชจ๋ธ ์ปดํŒŒ์ผ ์™„๋ฃŒ! โœ…")
๐Ÿค” ์™œ ์ด ์„ค์ •๋“ค์„ ์“ฐ๋Š” ๊ฑฐ์•ผ? ๐Ÿ“Œ optimizer='adam'
Adam(Adaptive Moment Estimation)์€ ํ•™์Šต๋ฅ ์„ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•ด์ฃผ๋Š” ์Šค๋งˆํŠธํ•œ ์˜ตํ‹ฐ๋งˆ์ด์ €์•ผ. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ Adam์ด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ข‹์€ ์„ฑ๋Šฅ์„ ๋‚ด.

๐Ÿ“Œ loss='binary_crossentropy'
์ด์ง„ ๋ถ„๋ฅ˜(0 ๋˜๋Š” 1)์— ์‚ฌ์šฉํ•˜๋Š” ์†์‹ค ํ•จ์ˆ˜์•ผ. ๋ชจ๋ธ์˜ ์˜ˆ์ธก์ด ์‹ค์ œ ๋ ˆ์ด๋ธ”๊ณผ ์–ผ๋งˆ๋‚˜ ๋‹ค๋ฅธ์ง€ ์ธก์ •ํ•ด. ๊ฐ’์ด ๋‚ฎ์„์ˆ˜๋ก ์ข‹์•„!

๐Ÿ“Œ metrics=['accuracy']
ํ›ˆ๋ จ ์ค‘์— ์ •ํ™•๋„๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•ด. ์ „์ฒด ์˜ˆ์ธก ์ค‘ ๋งž์€ ๋น„์œจ์ด์•ผ.
๐Ÿ“š ์ฝœ๋ฐฑ ์„ค์ • โ€” ํ›ˆ๋ จ์„ ๋” ์Šค๋งˆํŠธํ•˜๊ฒŒ!

์ฝœ๋ฐฑ(Callback)์€ ํ›ˆ๋ จ ์ค‘์— ํŠน์ • ์กฐ๊ฑด์—์„œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜์•ผ.
๋‘ ๊ฐ€์ง€ ์ค‘์š”ํ•œ ์ฝœ๋ฐฑ์„ ์„ค์ •ํ•ด๋ณด์ž!

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

callbacks = [
    # ๊ฒ€์ฆ ์†์‹ค์ด ๊ฐœ์„ ๋˜์ง€ ์•Š์œผ๋ฉด ์กฐ๊ธฐ ์ข…๋ฃŒ
    EarlyStopping(
        monitor='val_loss',    # ๊ฒ€์ฆ ์†์‹ค ๋ชจ๋‹ˆํ„ฐ๋ง
        patience=3,            # 3 ์—ํฌํฌ ๋™์•ˆ ๊ฐœ์„  ์—†์œผ๋ฉด ์ข…๋ฃŒ
        restore_best_weights=True  # ์ตœ๊ณ  ์„ฑ๋Šฅ ๊ฐ€์ค‘์น˜๋กœ ๋ณต์›
    ),
    
    # ์ตœ๊ณ  ์„ฑ๋Šฅ ๋ชจ๋ธ ์ €์žฅ
    ModelCheckpoint(
        filepath='best_model.keras',
        monitor='val_accuracy',
        save_best_only=True,
        verbose=1
    )
]

print("์ฝœ๋ฐฑ ์„ค์ • ์™„๋ฃŒ! ๐ŸŽฏ")
โœ… EarlyStopping์˜ ๋งˆ๋ฒ•! ๊ณผ์ ํ•ฉ(Overfitting)์„ ๋ฐฉ์ง€ํ•˜๋Š” ํ•ต์‹ฌ ๊ธฐ๋ฒ•์ด์•ผ. ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ ์„ฑ๋Šฅ์ด ๋” ์ด์ƒ ์ข‹์•„์ง€์ง€ ์•Š์œผ๋ฉด ํ›ˆ๋ จ์„ ์ž๋™์œผ๋กœ ๋ฉˆ์ถฐ์ค˜. ๋ถˆํ•„์š”ํ•œ ํ›ˆ๋ จ ์‹œ๊ฐ„๋„ ์ค„์ด๊ณ , ์ตœ๊ณ  ์„ฑ๋Šฅ ์ง€์ ์—์„œ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด!
๐Ÿš€ ๋ชจ๋ธ ํ›ˆ๋ จ ์‹œ์ž‘!
history = model.fit(
    x_train, y_train,
    epochs=20,              # ์ตœ๋Œ€ 20 ์—ํฌํฌ
    batch_size=512,         # ๋ฐฐ์น˜ ํฌ๊ธฐ
    validation_data=(x_val, y_val),
    callbacks=callbacks,
    verbose=1
)

# ํ›ˆ๋ จ ์ถœ๋ ฅ ์˜ˆ์‹œ:
# Epoch 1/20
# 40/40 [==============================] - 8s 180ms/step
# loss: 0.6931 - accuracy: 0.5012 - val_loss: 0.6928 - val_accuracy: 0.5024
# ...
# Epoch 8/20
# 40/40 [==============================] - 6s 150ms/step
# loss: 0.2341 - accuracy: 0.9123 - val_loss: 0.2876 - val_accuracy: 0.8834
# Epoch 11/20 - EarlyStopping ๋ฐœ๋™!
# ํ›ˆ๋ จ ์กฐ๊ธฐ ์ข…๋ฃŒ (val_loss ๊ฐœ์„  ์—†์Œ)
๐Ÿ“ˆ ํ›ˆ๋ จ ๊ฒฐ๊ณผ ์‹œ๊ฐํ™”

ํ›ˆ๋ จ์ด ๋๋‚˜๋ฉด ํ•™์Šต ๊ณก์„ ์„ ๊ทธ๋ ค์„œ ๋ชจ๋ธ์ด ์ž˜ ํ•™์Šต๋๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž!

import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# ์ •ํ™•๋„ ๊ทธ๋ž˜ํ”„
axes[0].plot(history.history['accuracy'], label='ํ›ˆ๋ จ ์ •ํ™•๋„', color='#7eb8d4')
axes[0].plot(history.history['val_accuracy'], label='๊ฒ€์ฆ ์ •ํ™•๋„', color='#f0a060')
axes[0].set_title('๋ชจ๋ธ ์ •ํ™•๋„', fontsize=14, fontweight='bold')
axes[0].set_xlabel('์—ํฌํฌ')
axes[0].set_ylabel('์ •ํ™•๋„')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# ์†์‹ค ๊ทธ๋ž˜ํ”„
axes[1].plot(history.history['loss'], label='ํ›ˆ๋ จ ์†์‹ค', color='#7eb8d4')
axes[1].plot(history.history['val_loss'], label='๊ฒ€์ฆ ์†์‹ค', color='#f0a060')
axes[1].set_title('๋ชจ๋ธ ์†์‹ค', fontsize=14, fontweight='bold')
axes[1].set_xlabel('์—ํฌํฌ')
axes[1].set_ylabel('์†์‹ค')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('training_history.png', dpi=150, bbox_inches='tight')
plt.show()

๐Ÿ“Š ๋ชจ๋ธ ํ‰๊ฐ€ โ€” ์–ผ๋งˆ๋‚˜ ์ž˜ ๋งž์ถ”๋‚˜?

ํ›ˆ๋ จ์ด ๋๋‚ฌ์œผ๋ฉด ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋กœ ์ตœ์ข… ์„ฑ๋Šฅ์„ ํ‰๊ฐ€ํ•ด์•ผ ํ•ด!
ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋Š” ํ›ˆ๋ จ ์ค‘์— ํ•œ ๋ฒˆ๋„ ๋ณธ ์  ์—†๋Š” ๋ฐ์ดํ„ฐ๋ผ์„œ ์ง„์งœ ์‹ค๋ ฅ์„ ์•Œ ์ˆ˜ ์žˆ์–ด. ๐ŸŽฏ

๐Ÿงช ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ ํ‰๊ฐ€
test_loss, test_accuracy = model.evaluate(
    test_data_padded, test_labels,
    verbose=1
)

print(f"\n๐Ÿ“Š ์ตœ์ข… ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ")
print(f"ํ…Œ์ŠคํŠธ ์†์‹ค: {test_loss:.4f}")
print(f"ํ…Œ์ŠคํŠธ ์ •ํ™•๋„: {test_accuracy:.4f} ({test_accuracy*100:.2f}%)")

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
# 782/782 [==============================] - 3s 4ms/step
# ํ…Œ์ŠคํŠธ ์†์‹ค: 0.2934
# ํ…Œ์ŠคํŠธ ์ •ํ™•๋„: 0.8876 (88.76%)
๐Ÿ” ์ƒ์„ธ ๋ถ„๋ฅ˜ ๋ฆฌํฌํŠธ

์ •ํ™•๋„ ํ•˜๋‚˜๋งŒ ๋ณด๋Š” ๊ฑด ๋ถ€์กฑํ•ด. ๋” ์ž์„ธํ•œ ๋ถ„์„์„ ํ•ด๋ณด์ž!

from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns

# ์˜ˆ์ธก๊ฐ’ ์ƒ์„ฑ
y_pred_prob = model.predict(test_data_padded)
y_pred = (y_pred_prob > 0.5).astype(int).flatten()

# ๋ถ„๋ฅ˜ ๋ฆฌํฌํŠธ
print(classification_report(
    test_labels, y_pred,
    target_names=['๋ถ€์ •(Negative)', '๊ธ์ •(Positive)']
))

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
#                    precision  recall  f1-score  support
# ๋ถ€์ •(Negative)       0.89      0.88      0.89     12500
# ๊ธ์ •(Positive)       0.88      0.89      0.89     12500
# accuracy                               0.89     25000
# macro avg            0.89      0.89      0.89     25000

# ํ˜ผ๋™ ํ–‰๋ ฌ
cm = confusion_matrix(test_labels, y_pred)
print(f"\nํ˜ผ๋™ ํ–‰๋ ฌ:")
print(f"์ง„์งœ ๋ถ€์ • โ†’ ๋ถ€์ • ์˜ˆ์ธก: {cm[0][0]:,}๊ฐœ (True Negative)")
print(f"์ง„์งœ ๋ถ€์ • โ†’ ๊ธ์ • ์˜ˆ์ธก: {cm[0][1]:,}๊ฐœ (False Positive)")
print(f"์ง„์งœ ๊ธ์ • โ†’ ๋ถ€์ • ์˜ˆ์ธก: {cm[1][0]:,}๊ฐœ (False Negative)")
print(f"์ง„์งœ ๊ธ์ • โ†’ ๊ธ์ • ์˜ˆ์ธก: {cm[1][1]:,}๊ฐœ (True Positive)")
๐Ÿ“Œ ํ‰๊ฐ€ ์ง€ํ‘œ ์ดํ•ดํ•˜๊ธฐ ๐ŸŽฏ Precision (์ •๋ฐ€๋„): "๊ธ์ •์ด๋ผ๊ณ  ์˜ˆ์ธกํ•œ ๊ฒƒ ์ค‘ ์‹ค์ œ๋กœ ๊ธ์ •์ธ ๋น„์œจ"
โ†’ ๋†’์„์ˆ˜๋ก ๊ฑฐ์ง“ ๊ธ์ •(False Positive)์ด ์ ์–ด

๐ŸŽฏ Recall (์žฌํ˜„์œจ): "์‹ค์ œ ๊ธ์ • ์ค‘ ๊ธ์ •์ด๋ผ๊ณ  ์˜ˆ์ธกํ•œ ๋น„์œจ"
โ†’ ๋†’์„์ˆ˜๋ก ๋†“์น˜๋Š” ๊ธ์ •(False Negative)์ด ์ ์–ด

๐ŸŽฏ F1-Score: Precision๊ณผ Recall์˜ ์กฐํ™” ํ‰๊ท 
โ†’ ๋‘ ์ง€ํ‘œ๋ฅผ ๊ท ํ˜• ์žˆ๊ฒŒ ๋ฐ˜์˜ํ•œ ์ข…ํ•ฉ ์ ์ˆ˜

๐ŸŽฌ ์‹ค์ œ ๋ฆฌ๋ทฐ๋กœ ์˜ˆ์ธกํ•ด๋ณด๊ธฐ!

์ด์ œ ์ง„์งœ ์žฌ๋ฐŒ๋Š” ๋ถ€๋ถ„! ์ง์ ‘ ์˜ํ™” ๋ฆฌ๋ทฐ๋ฅผ ์จ์„œ ๋ชจ๋ธ์ด ๊ธ์ •/๋ถ€์ •์„ ๋งž์ถ”๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ด๋ณด์ž! ๐Ÿฟ

def predict_sentiment(review_text, model, word_index, max_len=256):
    """
    ํ…์ŠคํŠธ ๋ฆฌ๋ทฐ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๊ฐ์„ฑ์„ ์˜ˆ์ธกํ•˜๋Š” ํ•จ์ˆ˜
    """
    # 1. ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ
    review_text = review_text.lower()
    words = review_text.split()
    
    # 2. ๋‹จ์–ด โ†’ ์ •์ˆ˜ ์ธ์ฝ”๋”ฉ
    # ๋‹จ์–ด ์ธ๋ฑ์Šค์— ์—†๋Š” ๋‹จ์–ด๋Š” 2(UNK)๋กœ ์ฒ˜๋ฆฌ
    encoded = [word_index.get(word, 2) + 3 for word in words]
    
    # 3. ํŒจ๋”ฉ
    from tensorflow.keras.preprocessing.sequence import pad_sequences
    padded = pad_sequences([encoded], maxlen=max_len, padding='post')
    
    # 4. ์˜ˆ์ธก
    prediction = model.predict(padded, verbose=0)[0][0]
    
    # 5. ๊ฒฐ๊ณผ ์ถœ๋ ฅ
    sentiment = "๐Ÿ˜Š ๊ธ์ • (Positive)" if prediction > 0.5 else "๐Ÿ˜ž ๋ถ€์ • (Negative)"
    confidence = prediction if prediction > 0.5 else 1 - prediction
    
    print(f"๋ฆฌ๋ทฐ: {review_text[:80]}...")
    print(f"์˜ˆ์ธก: {sentiment}")
    print(f"ํ™•์‹ ๋„: {confidence*100:.1f}%")
    print("-" * 50)
    
    return prediction

# ํ…Œ์ŠคํŠธํ•ด๋ณด๊ธฐ!
test_reviews = [
    "This movie was absolutely brilliant! The acting was superb and the story was captivating.",
    "Terrible film. Waste of time and money. The plot made no sense at all.",
    "It was okay, nothing special but not bad either. Average movie experience.",
    "One of the best movies I have ever seen! Highly recommend to everyone!"
]

for review in test_reviews:
    predict_sentiment(review, model, word_index)

# ์ถœ๋ ฅ ์˜ˆ์‹œ:
# ๋ฆฌ๋ทฐ: this movie was absolutely brilliant! the acting was superb...
# ์˜ˆ์ธก: ๐Ÿ˜Š ๊ธ์ • (Positive)
# ํ™•์‹ ๋„: 97.3%
# --------------------------------------------------
# ๋ฆฌ๋ทฐ: terrible film. waste of time and money. the plot made no...
# ์˜ˆ์ธก: ๐Ÿ˜ž ๋ถ€์ • (Negative)
# ํ™•์‹ ๋„: 94.8%
# --------------------------------------------------
โœ… ํ•œ๊ตญ์–ด ๋ฆฌ๋ทฐ๋„ ๋ถ„๋ฅ˜ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? IMDB๋Š” ์˜์–ด ๋ฐ์ดํ„ฐ๋ผ์„œ ํ•œ๊ตญ์–ด๋Š” ๋ฐ”๋กœ ์•ˆ ๋ผ. ํ•œ๊ตญ์–ด ํ…์ŠคํŠธ ๋ถ„๋ฅ˜๋ฅผ ํ•˜๋ ค๋ฉด ํ•œ๊ตญ์–ด ๋ฐ์ดํ„ฐ์…‹(๋„ค์ด๋ฒ„ ์˜ํ™” ๋ฆฌ๋ทฐ ๋“ฑ)์„ ์‚ฌ์šฉํ•˜๊ณ , KoNLPy ๊ฐ™์€ ํ•œ๊ตญ์–ด ํ˜•ํƒœ์†Œ ๋ถ„์„๊ธฐ๋กœ ํ† ํฐํ™”ํ•ด์•ผ ํ•ด. ๋‚˜์ค‘์— ๋„์ „ํ•ด๋ด! ๐Ÿ‡ฐ๐Ÿ‡ท

โšก ์„ฑ๋Šฅ ๋” ์˜ฌ๋ฆฌ๋Š” ๊ฟ€ํŒ๋“ค!

๊ธฐ๋ณธ ๋ชจ๋ธ๋กœ 88~89% ์ •ํ™•๋„๋ฅผ ๋‹ฌ์„ฑํ–ˆ๋Š”๋ฐ, ๋” ์˜ฌ๋ฆฌ๊ณ  ์‹ถ๋‹ค๋ฉด?
์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

๐ŸŒŸ ํŒ 1: ์‚ฌ์ „ ํ›ˆ๋ จ๋œ ์ž„๋ฒ ๋”ฉ ์‚ฌ์šฉ (GloVe, Word2Vec)

์ฒ˜์Œ๋ถ€ํ„ฐ ์ž„๋ฒ ๋”ฉ์„ ํ•™์Šตํ•˜๋Š” ๋Œ€์‹ , ์ด๋ฏธ ์ˆ˜์‹ญ์–ต ๊ฐœ์˜ ํ…์ŠคํŠธ๋กœ ํ•™์Šต๋œ
GloVe๋‚˜ Word2Vec ์ž„๋ฒ ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ์˜ฌ๋ผ!

import numpy as np

# GloVe ์ž„๋ฒ ๋”ฉ ๋กœ๋“œ (glove.6B.100d.txt ํŒŒ์ผ ํ•„์š”)
def load_glove_embeddings(glove_path, word_index, embedding_dim=100):
    embeddings_index = {}
    
    with open(glove_path, encoding='utf-8') as f:
        for line in f:
            values = line.split()
            word = values[0]
            coefs = np.asarray(values[1:], dtype='float32')
            embeddings_index[word] = coefs
    
    print(f"GloVe ๋‹จ์–ด ์ˆ˜: {len(embeddings_index):,}")
    
    # ์ž„๋ฒ ๋”ฉ ํ–‰๋ ฌ ์ƒ์„ฑ
    embedding_matrix = np.zeros((vocab_size, embedding_dim))
    
    for word, i in word_index.items():
        if i < vocab_size:
            embedding_vector = embeddings_index.get(word)
            if embedding_vector is not None:
                embedding_matrix[i] = embedding_vector
    
    return embedding_matrix

# ์‚ฌ์ „ ํ›ˆ๋ จ ์ž„๋ฒ ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋ธ
from tensorflow.keras.layers import Embedding

# embedding_matrix = load_glove_embeddings('glove.6B.100d.txt', word_index)

model_pretrained = Sequential([
    Embedding(
        input_dim=vocab_size,
        output_dim=100,
        # weights=[embedding_matrix],  # ์‚ฌ์ „ ํ›ˆ๋ จ ๊ฐ€์ค‘์น˜
        # trainable=False,             # ์ž„๋ฒ ๋”ฉ ๊ณ ์ • (ํŒŒ์ธํŠœ๋‹ ์•ˆ ํ•จ)
        input_length=max_length
    ),
    Conv1D(128, 5, activation='relu'),
    GlobalMaxPooling1D(),
    Dense(64, activation='relu'),
    Dropout(0.3),
    Dense(1, activation='sigmoid')
])
๐ŸŒŸ ํŒ 2: ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํŠœ๋‹
# ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ๋“ค
hyperparams_to_try = {
    'embedding_dim': [32, 64, 128, 256],
    'max_length': [128, 256, 512],
    'batch_size': [128, 256, 512],
    'learning_rate': [0.001, 0.0005, 0.0001],
    'dropout_rate': [0.2, 0.3, 0.4, 0.5],
    'dense_units': [32, 64, 128]
}

# Keras Tuner๋ฅผ ์‚ฌ์šฉํ•œ ์ž๋™ ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ ํƒ์ƒ‰
# pip install keras-tuner
import keras_tuner as kt

def build_model(hp):
    model = Sequential([
        Embedding(
            vocab_size,
            hp.Int('embedding_dim', min_value=32, max_value=256, step=32),
            input_length=max_length
        ),
        Conv1D(
            hp.Int('conv_filters', min_value=64, max_value=256, step=64),
            5, activation='relu'
        ),
        GlobalMaxPooling1D(),
        Dense(
            hp.Int('dense_units', min_value=32, max_value=128, step=32),
            activation='relu'
        ),
        Dropout(hp.Float('dropout', min_value=0.2, max_value=0.5, step=0.1)),
        Dense(1, activation='sigmoid')
    ])
    
    model.compile(
        optimizer=tf.keras.optimizers.Adam(
            hp.Float('learning_rate', min_value=1e-4, max_value=1e-2, sampling='log')
        ),
        loss='binary_crossentropy',
        metrics=['accuracy']
    )
    return model
๐ŸŒŸ ํŒ 3: ํ…์ŠคํŠธ ์ฆ๊ฐ• (Data Augmentation)
import random

def augment_text(text_tokens, augment_prob=0.1):
    """
    ๊ฐ„๋‹จํ•œ ํ…์ŠคํŠธ ์ฆ๊ฐ• ๊ธฐ๋ฒ•
    - ๋žœ๋ค ๋‹จ์–ด ์‚ญ์ œ
    - ๋žœ๋ค ๋‹จ์–ด ๊ต์ฒด
    """
    augmented = []
    for token in text_tokens:
        rand = random.random()
        if rand < augment_prob:
            # 10% ํ™•๋ฅ ๋กœ ๋‹จ์–ด ์‚ญ์ œ
            continue
        augmented.append(token)
    return augmented

# ๋™์˜์–ด ๊ต์ฒด (nlpaug ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ)
# pip install nlpaug
# import nlpaug.augmenter.word as naw
# aug = naw.SynonymAug(aug_src='wordnet')
# augmented_text = aug.augment("This movie was great!")
๐ŸŒŸ ํŒ 4: BERT ๊ฐ™์€ ํŠธ๋žœ์Šคํฌ๋จธ ๋ชจ๋ธ ํ™œ์šฉ

์ตœ๊ณ  ์„ฑ๋Šฅ์„ ์›ํ•œ๋‹ค๋ฉด? BERT(Bidirectional Encoder Representations from Transformers)๋ฅผ ์จ๋ด!
TensorFlow Hub์—์„œ ์‚ฌ์ „ ํ›ˆ๋ จ๋œ BERT๋ฅผ ๋ฐ”๋กœ ๊ฐ€์ ธ๋‹ค ์“ธ ์ˆ˜ ์žˆ์–ด.

import tensorflow_hub as hub
import tensorflow_text as text

# TensorFlow Hub์—์„œ BERT ๋ชจ๋ธ ๋กœ๋“œ
bert_preprocess_model = hub.KerasLayer(
    "https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3"
)
bert_encoder = hub.KerasLayer(
    "https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/4",
    trainable=True  # ํŒŒ์ธํŠœ๋‹
)

# BERT ๊ธฐ๋ฐ˜ ๋ถ„๋ฅ˜ ๋ชจ๋ธ
text_input = tf.keras.layers.Input(shape=(), dtype=tf.string, name='text')
preprocessed_text = bert_preprocess_model(text_input)
outputs = bert_encoder(preprocessed_text)

# ํ’€๋ง๋œ ์ถœ๋ ฅ ์‚ฌ์šฉ
net = outputs['pooled_output']
net = tf.keras.layers.Dropout(0.1)(net)
net = tf.keras.layers.Dense(1, activation='sigmoid', name='classifier')(net)

model_bert = tf.keras.Model(text_input, net)

# BERT๋Š” ๋ณดํ†ต 90% ์ด์ƒ์˜ ์ •ํ™•๋„๋ฅผ ๋‹ฌ์„ฑํ•ด!
โš ๏ธ ์ฃผ์˜! BERT๋Š” ์—„์ฒญ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ ๊ทธ๋งŒํผ ๋ฌด๊ฑฐ์›Œ. GPU ์—†์ด๋Š” ํ›ˆ๋ จ์ด ๋งค์šฐ ๋А๋ฆด ์ˆ˜ ์žˆ์–ด. Google Colab์˜ GPU๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, ์ž‘์€ BERT ๋ณ€ํ˜•(DistilBERT, ALBERT)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•ด!
๋ชจ๋ธ๋ณ„ ์„ฑ๋Šฅ ๋น„๊ต (IMDB ๊ธฐ์ค€) ๊ฐ ๋ชจ๋ธ์˜ ํ…Œ์ŠคํŠธ ์ •ํ™•๋„ ๋น„๊ต 80% 85% 90% 95% 100% 85% Dense ๊ธฐ๋ณธ ๋ชจ๋ธ 88% Conv1D ํ•ฉ์„ฑ๊ณฑ ๋ชจ๋ธ 87% LSTM ์ˆœํ™˜ ๋ชจ๋ธ 91% GloVe+Conv ์‚ฌ์ „ํ›ˆ๋ จ ์ž„๋ฒ ๋”ฉ 94% BERT ํŠธ๋žœ์Šคํฌ๋จธ * ์ˆ˜์น˜๋Š” ์ผ๋ฐ˜์ ์ธ ์ฐธ๊ณ ๊ฐ’์ด๋ฉฐ ๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ, ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์–ด์š”

๐Ÿ’พ ๋ชจ๋ธ ์ €์žฅ & ๋ฐฐํฌ โ€” ๋งŒ๋“  ๊ฑธ ์จ๋จน์ž!

์—ด์‹ฌํžˆ ๋งŒ๋“  ๋ชจ๋ธ, ์ €์žฅํ•ด๋‘๊ณ  ๋‚˜์ค‘์— ๋˜ ์“ฐ๊ฑฐ๋‚˜ ์„œ๋น„์Šค์— ๋ฐฐํฌํ•ด์•ผ๊ฒ ์ง€?
TensorFlow๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ €์žฅ ๋ฐฉ์‹์„ ์ง€์›ํ•ด!

๐Ÿ’พ ๋ชจ๋ธ ์ €์žฅ ๋ฐฉ๋ฒ•
# ๋ฐฉ๋ฒ• 1: SavedModel ํ˜•์‹ (๊ถŒ์žฅ)
model.save('text_classifier_model')
# ํด๋” ๊ตฌ์กฐ๋กœ ์ €์žฅ๋จ

# ๋ฐฉ๋ฒ• 2: HDF5 ํ˜•์‹ (.h5)
model.save('text_classifier_model.h5')

# ๋ฐฉ๋ฒ• 3: ๊ฐ€์ค‘์น˜๋งŒ ์ €์žฅ
model.save_weights('model_weights.h5')

# ๋ชจ๋ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
loaded_model = tf.keras.models.load_model('text_classifier_model')
print("๋ชจ๋ธ ๋กœ๋“œ ์™„๋ฃŒ! โœ…")
๐ŸŒ Flask๋กœ ๊ฐ„๋‹จํ•œ API ๋งŒ๋“ค๊ธฐ

๋ชจ๋ธ์„ ์›น API๋กœ ๋ฐฐํฌํ•˜๋ฉด ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด!
Flask๋กœ ๊ฐ„๋‹จํ•œ REST API๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

from flask import Flask, request, jsonify
import tensorflow as tf
import numpy as np
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.datasets import imdb

app = Flask(__name__)

# ๋ชจ๋ธ ๋ฐ ์„ค์ • ๋กœ๋“œ
model = tf.keras.models.load_model('text_classifier_model')
word_index = imdb.get_word_index()
MAX_LENGTH = 256

@app.route('/predict', methods=['POST'])
def predict():
    data = request.get_json()
    review_text = data.get('text', '')
    
    if not review_text:
        return jsonify({'error': 'ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!'}), 400
    
    # ์ „์ฒ˜๋ฆฌ
    words = review_text.lower().split()
    encoded = [word_index.get(word, 2) + 3 for word in words]
    padded = pad_sequences([encoded], maxlen=MAX_LENGTH, padding='post')
    
    # ์˜ˆ์ธก
    prediction = float(model.predict(padded, verbose=0)[0][0])
    sentiment = 'positive' if prediction > 0.5 else 'negative'
    confidence = prediction if prediction > 0.5 else 1 - prediction
    
    return jsonify({
        'text': review_text,
        'sentiment': sentiment,
        'confidence': round(confidence * 100, 2),
        'raw_score': round(prediction, 4)
    })

if __name__ == '__main__':
    app.run(debug=True, port=5000)

# ์‚ฌ์šฉ ์˜ˆ์‹œ:
# curl -X POST http://localhost:5000/predict \
#   -H "Content-Type: application/json" \
#   -d '{"text": "This movie was absolutely amazing!"}'
#
# ์‘๋‹ต:
# {
#   "text": "This movie was absolutely amazing!",
#   "sentiment": "positive",
#   "confidence": 97.3,
#   "raw_score": 0.9730
# }
โœ… ์‹ค์ œ ์„œ๋น„์Šค ๋ฐฐํฌ ํŒ! Flask ๋Œ€์‹  FastAPI๋ฅผ ์“ฐ๋ฉด ๋” ๋น ๋ฅด๊ณ  ์ž๋™ ๋ฌธ์„œํ™”๋„ ๋ผ์„œ ์ข‹์•„.
ํด๋ผ์šฐ๋“œ ๋ฐฐํฌ๋Š” Google Cloud Run, AWS Lambda, Heroku ๋“ฑ์„ ํ™œ์šฉํ•ด๋ด!
๋ชจ๋ธ์ด ํฌ๋‹ค๋ฉด TensorFlow Serving์ด๋‚˜ TensorFlow Lite๋กœ ์ตœ์ ํ™”ํ•˜๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•ด๋ด.

๐Ÿ“‹ ์ „์ฒด ์ฝ”๋“œ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ํ•˜๋‚˜์˜ ์™„์„ฑ๋œ ์ฝ”๋“œ๋กœ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ!
์ด๊ฑธ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ Google Colab์— ๋ถ™์—ฌ๋„ฃ์œผ๋ฉด ๋ฐ”๋กœ ์‹คํ–‰๋ผ. ๐ŸŽ‰

"""
TensorFlow ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ - ์™„์„ฑ ์ฝ”๋“œ
IMDB ์˜ํ™” ๋ฆฌ๋ทฐ ๊ฐ์„ฑ ๋ถ„์„ (๊ธ์ •/๋ถ€์ •)
"""

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
    Embedding, Conv1D, GlobalMaxPooling1D,
    Dense, Dropout
)
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint

# ============================================
# 1. ์„ค์ •๊ฐ’
# ============================================
VOCAB_SIZE = 10000    # ์–ดํœ˜ ํฌ๊ธฐ
MAX_LENGTH = 256      # ์ตœ๋Œ€ ์‹œํ€€์Šค ๊ธธ์ด
EMBEDDING_DIM = 64    # ์ž„๋ฒ ๋”ฉ ์ฐจ์›
BATCH_SIZE = 512      # ๋ฐฐ์น˜ ํฌ๊ธฐ
EPOCHS = 20           # ์ตœ๋Œ€ ์—ํฌํฌ
VAL_SIZE = 5000       # ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ ํฌ๊ธฐ

print(f"TensorFlow ๋ฒ„์ „: {tf.__version__}")

# ============================================
# 2. ๋ฐ์ดํ„ฐ ๋กœ๋“œ ๋ฐ ์ „์ฒ˜๋ฆฌ
# ============================================
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words=VOCAB_SIZE
)

# ํŒจ๋”ฉ
train_data_padded = pad_sequences(train_data, maxlen=MAX_LENGTH, padding='post')
test_data_padded = pad_sequences(test_data, maxlen=MAX_LENGTH, padding='post')

# ๋ ˆ์ด๋ธ” ๋ณ€ํ™˜
train_labels = np.array(train_labels)
test_labels = np.array(test_labels)

# ๊ฒ€์ฆ ๋ฐ์ดํ„ฐ ๋ถ„๋ฆฌ
x_val = train_data_padded[:VAL_SIZE]
y_val = train_labels[:VAL_SIZE]
x_train = train_data_padded[VAL_SIZE:]
y_train = train_labels[VAL_SIZE:]

print(f"ํ›ˆ๋ จ: {x_train.shape}, ๊ฒ€์ฆ: {x_val.shape}, ํ…Œ์ŠคํŠธ: {test_data_padded.shape}")

# ============================================
# 3. ๋ชจ๋ธ ๊ตฌ์„ฑ
# ============================================
model = Sequential([
    Embedding(VOCAB_SIZE, EMBEDDING_DIM, input_length=MAX_LENGTH),
    Conv1D(128, 5, activation='relu'),
    GlobalMaxPooling1D(),
    Dense(64, activation='relu'),
    Dropout(0.3),
    Dense(1, activation='sigmoid')
])

model.compile(
    optimizer='adam',
    loss='binary_crossentropy',
    metrics=['accuracy']
)

model.summary()

# ============================================
# 4. ์ฝœ๋ฐฑ ์„ค์ •
# ============================================
callbacks = [
    EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True),
    ModelCheckpoint('best_model.keras', monitor='val_accuracy', save_best_only=True)
]

# ============================================
# 5. ๋ชจ๋ธ ํ›ˆ๋ จ
# ============================================
history = model.fit(
    x_train, y_train,
    epochs=EPOCHS,
    batch_size=BATCH_SIZE,
    validation_data=(x_val, y_val),
    callbacks=callbacks,
    verbose=1
)

# ============================================
# 6. ๋ชจ๋ธ ํ‰๊ฐ€
# ============================================
test_loss, test_accuracy = model.evaluate(test_data_padded, test_labels)
print(f"\nโœ… ํ…Œ์ŠคํŠธ ์ •ํ™•๋„: {test_accuracy*100:.2f}%")

# ============================================
# 7. ํ•™์Šต ๊ณก์„  ์‹œ๊ฐํ™”
# ============================================
fig, axes = plt.subplots(1, 2, figsize=(12, 4))

axes[0].plot(history.history['accuracy'], label='ํ›ˆ๋ จ', color='#7eb8d4')
axes[0].plot(history.history['val_accuracy'], label='๊ฒ€์ฆ', color='#f0a060')
axes[0].set_title('์ •ํ™•๋„')
axes[0].legend()

axes[1].plot(history.history['loss'], label='ํ›ˆ๋ จ', color='#7eb8d4')
axes[1].plot(history.history['val_loss'], label='๊ฒ€์ฆ', color='#f0a060')
axes[1].set_title('์†์‹ค')
axes[1].legend()

plt.tight_layout()
plt.show()

# ============================================
# 8. ์‹ค์ œ ์˜ˆ์ธก
# ============================================
word_index = imdb.get_word_index()

def predict_sentiment(text):
    words = text.lower().split()
    encoded = [word_index.get(w, 2) + 3 for w in words]
    padded = pad_sequences([encoded], maxlen=MAX_LENGTH, padding='post')
    score = model.predict(padded, verbose=0)[0][0]
    label = "๐Ÿ˜Š ๊ธ์ •" if score > 0.5 else "๐Ÿ˜ž ๋ถ€์ •"
    conf = score if score > 0.5 else 1 - score
    print(f"[{label}] ํ™•์‹ ๋„: {conf*100:.1f}% | {text[:60]}")

predict_sentiment("This movie was absolutely brilliant and amazing!")
predict_sentiment("Terrible film, complete waste of time and money.")

๐ŸŽ“ ๋งˆ๋ฌด๋ฆฌ & ๋‹ค์Œ ๋‹จ๊ณ„๋กœ!

์™€, ์—ฌ๊ธฐ๊นŒ์ง€ ๋”ฐ๋ผ์™”๋‹ค๋ฉด ์ง„์งœ ๋Œ€๋‹จํ•ด! ๐Ÿ‘
์˜ค๋Š˜ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๊ฑธ ์ •๋ฆฌํ•ด๋ณด์ž.

โœ… ์˜ค๋Š˜ ๋ฐฐ์šด ๊ฒƒ๋“ค ๐Ÿ”น ํ…์ŠคํŠธ ๋ถ„๋ฅ˜๊ฐ€ ๋ฌด์—‡์ด๊ณ  ์–ด๋””์— ์“ฐ์ด๋Š”์ง€
๐Ÿ”น IMDB ๋ฐ์ดํ„ฐ์…‹ ๋กœ๋“œ ๋ฐ ํƒ์ƒ‰
๐Ÿ”น ํ…์ŠคํŠธ ์ „์ฒ˜๋ฆฌ (ํ† ํฐํ™”, ํŒจ๋”ฉ)
๐Ÿ”น ์„ธ ๊ฐ€์ง€ ๋ชจ๋ธ ๊ตฌ์กฐ (Dense, LSTM, Conv1D)
๐Ÿ”น ๋ชจ๋ธ ์ปดํŒŒ์ผ, ํ›ˆ๋ จ, ์ฝœ๋ฐฑ ์„ค์ •
๐Ÿ”น ๋ชจ๋ธ ํ‰๊ฐ€ ๋ฐ ์„ฑ๋Šฅ ์ง€ํ‘œ ํ•ด์„
๐Ÿ”น ์‹ค์ œ ํ…์ŠคํŠธ๋กœ ์˜ˆ์ธกํ•˜๊ธฐ
๐Ÿ”น ์„ฑ๋Šฅ ๊ฐœ์„  ๋ฐฉ๋ฒ• (GloVe, BERT ๋“ฑ)
๐Ÿ”น ๋ชจ๋ธ ์ €์žฅ ๋ฐ API ๋ฐฐํฌ
๐Ÿš€ ๋‹ค์Œ์— ๋„์ „ํ•ด๋ณผ ๊ฒƒ๋“ค
๐Ÿ‡ฐ๐Ÿ‡ท ํ•œ๊ตญ์–ด
๊ฐ์„ฑ ๋ถ„์„
๐Ÿ“ฐ ๋‹ค์ค‘ ํด๋ž˜์Šค
๋ถ„๋ฅ˜
๐Ÿค— HuggingFace
Transformers
โšก TF Lite
๋ชจ๋ฐ”์ผ ๋ฐฐํฌ

ํ…์ŠคํŠธ ๋ถ„๋ฅ˜๋Š” NLP(์ž์—ฐ์–ด ์ฒ˜๋ฆฌ)์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ํƒœ์Šคํฌ์•ผ.
์ด๊ฑธ ๋งˆ์Šคํ„ฐํ•˜๋ฉด ๊ฐ์„ฑ ๋ถ„์„, ์ŠคํŒธ ํ•„ํ„ฐ, ๋‰ด์Šค ๋ถ„๋ฅ˜, ์ฑ—๋ด‡ ์˜๋„ ๋ถ„๋ฅ˜ ๋“ฑ
๋‹ค์–‘ํ•œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ๋ฐ”๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ช

๐Ÿ’ก ์žฌ๋Šฅ๋„ท ํ™œ์šฉ ํŒ! ์ด๋Ÿฐ ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ์„ ๋งŒ๋“œ๋Š” ๊ธฐ์ˆ ์„ ์ตํ˜”๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์—์„œ AI/๋จธ์‹ ๋Ÿฌ๋‹ ๊ด€๋ จ ํ”„๋ฆฌ๋žœ์„œ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•ด๋ณผ ์ˆ˜๋„ ์žˆ์–ด! ๊ฐ์„ฑ ๋ถ„์„ API ๊ฐœ๋ฐœ, ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๋ชจ๋ธ ๊ตฌ์ถ• ๋“ฑ์˜ ์„œ๋น„์Šค๋Š” ์ˆ˜์š”๊ฐ€ ๊ฝค ์žˆ๊ฑฐ๋“ . ๋ฐฐ์šด ๊ฑธ ์‹ค์ œ ์ˆ˜์ต์œผ๋กœ ์—ฐ๊ฒฐํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ! ๐ŸŽฏ
๐Ÿ“š ๋” ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?
์ฃผ์ œ ์ถ”์ฒœ ๋ฆฌ์†Œ์Šค
TensorFlow ๊ณต์‹ ํŠœํ† ๋ฆฌ์–ผ tensorflow.org/tutorials
Keras ๋ฌธ์„œ keras.io
HuggingFace ํŠธ๋žœ์Šคํฌ๋จธ huggingface.co/docs
ํ•œ๊ตญ์–ด NLP (KoNLPy) konlpy.org
๋”ฅ๋Ÿฌ๋‹ ๋…ผ๋ฌธ ์ฝ๊ธฐ arxiv.org/cs.CL

๋”ฅ๋Ÿฌ๋‹์€ ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์ง€์ง€๋งŒ, ์ง์ ‘ ์ฝ”๋“œ๋ฅผ ์งœ๊ณ  ์‹คํ—˜ํ•ด๋ณด๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ์ œ์ผ ๋นจ๋ผ!
์˜ค๋Š˜ ๋ฐฐ์šด ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ๋Œ๋ ค๋ณด๊ณ , ํŒŒ๋ผ๋ฏธํ„ฐ๋„ ๋ฐ”๊ฟ”๋ณด๊ณ , ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ์…‹์—๋„ ์ ์šฉํ•ด๋ด.
๊ทธ๋Ÿฌ๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ AI ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด์žˆ์„ ๊ฑฐ์•ผ! ๐ŸŒŸ

โœ… ๋งˆ์ง€๋ง‰ ๊ฟ€ํŒ! ๋ชจ๋ธ ์„ฑ๋Šฅ์ด ์ƒ๊ฐ๋ณด๋‹ค ์•ˆ ๋‚˜์˜จ๋‹ค๊ณ  ํฌ๊ธฐํ•˜์ง€ ๋งˆ! ๋ฐ์ดํ„ฐ ์ „์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ์„ ํ•˜๊ฑฐ๋‚˜, ํ•˜์ดํผํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์กฐ๊ธˆ์”ฉ ๋ฐ”๊ฟ”๋ณด๊ฑฐ๋‚˜, ๋” ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ง‘ํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์–ด. ๋จธ์‹ ๋Ÿฌ๋‹์€ ์‹คํ—˜์˜ ์—ฐ์†์ด์•ผ! ๐Ÿ”ฌ
๐Ÿค– TensorFlow ๐Ÿ“ ํ…์ŠคํŠธ ๋ถ„๋ฅ˜ ๐Ÿง  ๋”ฅ๋Ÿฌ๋‹ ๐Ÿ’ฌ NLP ๐ŸŽฌ ๊ฐ์„ฑ ๋ถ„์„ ๐Ÿ Python โšก Keras ๐Ÿ”ฅ ๋จธ์‹ ๋Ÿฌ๋‹
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.