1"""
214. ์ค์ ํ
์คํธ ๋ถ๋ฅ ํ๋ก์ ํธ
3
4๊ฐ์ฑ ๋ถ์์ ์ํ ํ
์คํธ ๋ถ๋ฅ ํ์ดํ๋ผ์ธ ๊ตฌํ
5"""
6
7import torch
8import torch.nn as nn
9import torch.nn.functional as F
10from torch.utils.data import DataLoader, Dataset
11import numpy as np
12import matplotlib.pyplot as plt
13from collections import Counter
14import re
15import math
16
17print("=" * 60)
18print("์ค์ ํ
์คํธ ๋ถ๋ฅ ํ๋ก์ ํธ")
19print("=" * 60)
20
21device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
22print(f"Device: {device}")
23
24
25# ============================================
26# 1. ํ
์คํธ ์ ์ฒ๋ฆฌ
27# ============================================
28print("\n[1] ํ
์คํธ ์ ์ฒ๋ฆฌ")
29print("-" * 40)
30
31def simple_tokenizer(text):
32 """๊ฐ๋จํ ํ ํฌ๋์ด์ """
33 text = text.lower()
34 text = re.sub(r'[^\w\s]', '', text)
35 return text.split()
36
37# ํ
์คํธ
38sample = "This is a SAMPLE sentence! With punctuation."
39tokens = simple_tokenizer(sample)
40print(f"์๋ฌธ: {sample}")
41print(f"ํ ํฐ: {tokens}")
42
43
44# ============================================
45# 2. ์ดํ ๊ตฌ์ถ
46# ============================================
47print("\n[2] ์ดํ ๊ตฌ์ถ")
48print("-" * 40)
49
50class Vocabulary:
51 """ํ
์คํธ ์ดํ ์ฌ์ """
52 def __init__(self, min_freq=2):
53 self.word2idx = {'<pad>': 0, '<unk>': 1}
54 self.idx2word = {0: '<pad>', 1: '<unk>'}
55 self.word_freq = Counter()
56 self.min_freq = min_freq
57
58 def build(self, texts, tokenizer):
59 """์ดํ ๊ตฌ์ถ"""
60 for text in texts:
61 tokens = tokenizer(text)
62 self.word_freq.update(tokens)
63
64 idx = len(self.word2idx)
65 for word, freq in self.word_freq.items():
66 if freq >= self.min_freq and word not in self.word2idx:
67 self.word2idx[word] = idx
68 self.idx2word[idx] = word
69 idx += 1
70
71 print(f"์ด ๋จ์ด ์: {len(self.word_freq)}")
72 print(f"์ดํ ํฌ๊ธฐ (min_freq={self.min_freq}): {len(self.word2idx)}")
73
74 def encode(self, text, tokenizer, max_len=None):
75 """ํ
์คํธ๋ฅผ ์ธ๋ฑ์ค๋ก ๋ณํ"""
76 tokens = tokenizer(text)
77 indices = [self.word2idx.get(t, self.word2idx['<unk>']) for t in tokens]
78 if max_len:
79 if len(indices) > max_len:
80 indices = indices[:max_len]
81 else:
82 indices = indices + [self.word2idx['<pad>']] * (max_len - len(indices))
83 return indices
84
85 def __len__(self):
86 return len(self.word2idx)
87
88
89# ============================================
90# 3. ์ํ ๋ฐ์ดํฐ์
91# ============================================
92print("\n[3] ์ํ ๋ฐ์ดํฐ์
์์ฑ")
93print("-" * 40)
94
95# ๊ฐ์ฑ ๋ถ์์ฉ ์ํ ๋ฐ์ดํฐ
96positive_samples = [
97 "This movie is absolutely amazing and wonderful",
98 "I love this product it is fantastic",
99 "Great experience highly recommended",
100 "Excellent quality and fast delivery",
101 "Best purchase I have ever made",
102 "Wonderful service and friendly staff",
103 "I am very happy with this item",
104 "Perfect product exactly what I needed",
105 "Amazing value for the price",
106 "Outstanding performance and quality",
107 "This is the best thing ever",
108 "Incredible movie I loved every minute",
109 "Superb quality and great design",
110 "Highly satisfied with my purchase",
111 "Fantastic product works perfectly",
112] * 50 # 750 samples
113
114negative_samples = [
115 "Terrible product do not buy",
116 "Worst experience of my life",
117 "Very disappointed with the quality",
118 "Complete waste of money",
119 "Poor customer service",
120 "The product broke after one day",
121 "I hate this movie it was boring",
122 "Never buying from here again",
123 "Awful quality and slow delivery",
124 "Extremely bad experience",
125 "This is the worst product ever",
126 "Horrible movie total waste of time",
127 "Very poor quality disappointed",
128 "Bad product not recommended",
129 "Terrible service will not return",
130] * 50 # 750 samples
131
132texts = positive_samples + negative_samples
133labels = [1] * len(positive_samples) + [0] * len(negative_samples)
134
135# ์
ํ
136indices = np.random.permutation(len(texts))
137texts = [texts[i] for i in indices]
138labels = [labels[i] for i in indices]
139
140print(f"์ด ์ํ ์: {len(texts)}")
141print(f"๊ธ์ : {sum(labels)}, ๋ถ์ : {len(labels) - sum(labels)}")
142
143# ์ดํ ๊ตฌ์ถ
144vocab = Vocabulary(min_freq=2)
145vocab.build(texts, simple_tokenizer)
146
147
148# ============================================
149# 4. PyTorch Dataset
150# ============================================
151print("\n[4] PyTorch Dataset")
152print("-" * 40)
153
154class TextDataset(Dataset):
155 def __init__(self, texts, labels, vocab, tokenizer, max_len=50):
156 self.texts = texts
157 self.labels = labels
158 self.vocab = vocab
159 self.tokenizer = tokenizer
160 self.max_len = max_len
161
162 def __len__(self):
163 return len(self.texts)
164
165 def __getitem__(self, idx):
166 text = self.texts[idx]
167 label = self.labels[idx]
168 encoded = self.vocab.encode(text, self.tokenizer, self.max_len)
169 return torch.tensor(encoded, dtype=torch.long), torch.tensor(label, dtype=torch.long)
170
171# ๋ฐ์ดํฐ ๋ถํ
172train_size = int(0.8 * len(texts))
173train_texts, test_texts = texts[:train_size], texts[train_size:]
174train_labels, test_labels = labels[:train_size], labels[train_size:]
175
176train_dataset = TextDataset(train_texts, train_labels, vocab, simple_tokenizer)
177test_dataset = TextDataset(test_texts, test_labels, vocab, simple_tokenizer)
178
179train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
180test_loader = DataLoader(test_dataset, batch_size=32)
181
182print(f"Train: {len(train_dataset)}, Test: {len(test_dataset)}")
183
184# ์ํ ํ์ธ
185sample_x, sample_y = train_dataset[0]
186print(f"์ํ ์
๋ ฅ shape: {sample_x.shape}")
187print(f"์ํ ๋ผ๋ฒจ: {sample_y.item()}")
188
189
190# ============================================
191# 5. ๊ธฐ๋ณธ ํ
์คํธ ๋ถ๋ฅ๊ธฐ (์๋ฒ ๋ฉ + ํ๊ท )
192# ============================================
193print("\n[5] ๊ธฐ๋ณธ ํ
์คํธ ๋ถ๋ฅ๊ธฐ")
194print("-" * 40)
195
196class SimpleClassifier(nn.Module):
197 """์๋ฒ ๋ฉ ํ๊ท ๊ธฐ๋ฐ ๋ถ๋ฅ๊ธฐ"""
198 def __init__(self, vocab_size, embed_dim, num_classes):
199 super().__init__()
200 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
201 self.fc = nn.Linear(embed_dim, num_classes)
202
203 def forward(self, x):
204 # x: (batch, seq_len)
205 embedded = self.embedding(x) # (batch, seq, embed)
206 # ํ๊ท ํ๋ง (ํจ๋ฉ ์ ์ธ)
207 mask = (x != 0).unsqueeze(-1).float()
208 pooled = (embedded * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
209 return self.fc(pooled)
210
211simple_model = SimpleClassifier(len(vocab), embed_dim=64, num_classes=2)
212print(f"SimpleClassifier ํ๋ผ๋ฏธํฐ: {sum(p.numel() for p in simple_model.parameters()):,}")
213
214
215# ============================================
216# 6. LSTM ๋ถ๋ฅ๊ธฐ
217# ============================================
218print("\n[6] LSTM ๋ถ๋ฅ๊ธฐ")
219print("-" * 40)
220
221class LSTMClassifier(nn.Module):
222 """์๋ฐฉํฅ LSTM ๋ถ๋ฅ๊ธฐ"""
223 def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes,
224 num_layers=2, dropout=0.5):
225 super().__init__()
226 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
227
228 self.lstm = nn.LSTM(
229 embed_dim, hidden_dim,
230 num_layers=num_layers,
231 batch_first=True,
232 bidirectional=True,
233 dropout=dropout if num_layers > 1 else 0
234 )
235
236 self.fc = nn.Sequential(
237 nn.Dropout(dropout),
238 nn.Linear(hidden_dim * 2, hidden_dim),
239 nn.ReLU(),
240 nn.Dropout(dropout),
241 nn.Linear(hidden_dim, num_classes)
242 )
243
244 def forward(self, x):
245 embedded = self.embedding(x)
246 output, (h_n, c_n) = self.lstm(embedded)
247
248 # ์๋ฐฉํฅ ๋ง์ง๋ง ์๋ ์ํ ๊ฒฐํฉ
249 forward_last = h_n[-2]
250 backward_last = h_n[-1]
251 combined = torch.cat([forward_last, backward_last], dim=1)
252
253 return self.fc(combined)
254
255lstm_model = LSTMClassifier(len(vocab), embed_dim=64, hidden_dim=128, num_classes=2)
256print(f"LSTMClassifier ํ๋ผ๋ฏธํฐ: {sum(p.numel() for p in lstm_model.parameters()):,}")
257
258
259# ============================================
260# 7. Transformer ๋ถ๋ฅ๊ธฐ
261# ============================================
262print("\n[7] Transformer ๋ถ๋ฅ๊ธฐ")
263print("-" * 40)
264
265class PositionalEncoding(nn.Module):
266 def __init__(self, d_model, max_len=512, dropout=0.1):
267 super().__init__()
268 self.dropout = nn.Dropout(dropout)
269
270 pe = torch.zeros(max_len, d_model)
271 position = torch.arange(0, max_len).unsqueeze(1).float()
272 div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
273
274 pe[:, 0::2] = torch.sin(position * div_term)
275 pe[:, 1::2] = torch.cos(position * div_term)
276 pe = pe.unsqueeze(0)
277
278 self.register_buffer('pe', pe)
279
280 def forward(self, x):
281 x = x + self.pe[:, :x.size(1)]
282 return self.dropout(x)
283
284
285class TransformerClassifier(nn.Module):
286 """Transformer ์ธ์ฝ๋ ๊ธฐ๋ฐ ๋ถ๋ฅ๊ธฐ"""
287 def __init__(self, vocab_size, embed_dim, num_heads, num_layers,
288 num_classes, max_len=512, dropout=0.1):
289 super().__init__()
290 self.embed_dim = embed_dim
291 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
292 self.pos_encoder = PositionalEncoding(embed_dim, max_len, dropout)
293
294 encoder_layer = nn.TransformerEncoderLayer(
295 d_model=embed_dim,
296 nhead=num_heads,
297 dim_feedforward=embed_dim * 4,
298 dropout=dropout,
299 batch_first=True
300 )
301 self.transformer = nn.TransformerEncoder(encoder_layer, num_layers)
302
303 self.fc = nn.Sequential(
304 nn.Dropout(dropout),
305 nn.Linear(embed_dim, num_classes)
306 )
307
308 def forward(self, x):
309 # ํจ๋ฉ ๋ง์คํฌ ์์ฑ
310 padding_mask = (x == 0)
311
312 # ์๋ฒ ๋ฉ + ์ค์ผ์ผ๋ง + ์์น ์ธ์ฝ๋ฉ
313 embedded = self.embedding(x) * math.sqrt(self.embed_dim)
314 embedded = self.pos_encoder(embedded)
315
316 # Transformer ์ธ์ฝ๋
317 output = self.transformer(embedded, src_key_padding_mask=padding_mask)
318
319 # ํ๊ท ํ๋ง (ํจ๋ฉ ์ ์ธ)
320 mask = (~padding_mask).unsqueeze(-1).float()
321 pooled = (output * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
322
323 return self.fc(pooled)
324
325transformer_model = TransformerClassifier(
326 len(vocab), embed_dim=64, num_heads=4, num_layers=2, num_classes=2
327)
328print(f"TransformerClassifier ํ๋ผ๋ฏธํฐ: {sum(p.numel() for p in transformer_model.parameters()):,}")
329
330
331# ============================================
332# 8. ํ์ต ํจ์
333# ============================================
334print("\n[8] ํ์ต ํ์ดํ๋ผ์ธ")
335print("-" * 40)
336
337def train_epoch(model, loader, criterion, optimizer, device):
338 model.train()
339 total_loss = 0
340 correct = 0
341 total = 0
342
343 for texts, labels in loader:
344 texts, labels = texts.to(device), labels.to(device)
345
346 optimizer.zero_grad()
347 outputs = model(texts)
348 loss = criterion(outputs, labels)
349 loss.backward()
350
351 # ๊ธฐ์ธ๊ธฐ ํด๋ฆฌํ (RNN์ ์ค์)
352 torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
353
354 optimizer.step()
355
356 total_loss += loss.item()
357 pred = outputs.argmax(dim=1)
358 correct += (pred == labels).sum().item()
359 total += labels.size(0)
360
361 return total_loss / len(loader), 100. * correct / total
362
363
364def evaluate(model, loader, criterion, device):
365 model.eval()
366 total_loss = 0
367 correct = 0
368 total = 0
369
370 with torch.no_grad():
371 for texts, labels in loader:
372 texts, labels = texts.to(device), labels.to(device)
373 outputs = model(texts)
374 loss = criterion(outputs, labels)
375
376 total_loss += loss.item()
377 pred = outputs.argmax(dim=1)
378 correct += (pred == labels).sum().item()
379 total += labels.size(0)
380
381 return total_loss / len(loader), 100. * correct / total
382
383
384def train_model(model, train_loader, test_loader, epochs=10, lr=1e-3):
385 model = model.to(device)
386 criterion = nn.CrossEntropyLoss()
387 optimizer = torch.optim.Adam(model.parameters(), lr=lr)
388
389 history = {'train_loss': [], 'train_acc': [], 'test_loss': [], 'test_acc': []}
390
391 for epoch in range(epochs):
392 train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer, device)
393 test_loss, test_acc = evaluate(model, test_loader, criterion, device)
394
395 history['train_loss'].append(train_loss)
396 history['train_acc'].append(train_acc)
397 history['test_loss'].append(test_loss)
398 history['test_acc'].append(test_acc)
399
400 if (epoch + 1) % 5 == 0 or epoch == 0:
401 print(f"Epoch {epoch+1:2d}: Train Loss={train_loss:.4f}, Acc={train_acc:.1f}% | "
402 f"Test Loss={test_loss:.4f}, Acc={test_acc:.1f}%")
403
404 return history
405
406
407# ============================================
408# 9. ๋ชจ๋ธ ๋น๊ต ํ์ต
409# ============================================
410print("\n[9] ๋ชจ๋ธ ๋น๊ต ํ์ต")
411print("-" * 40)
412
413# ๋ชจ๋ธ ์ฌ์์ฑ (ํ์ต ์ ์ํ)
414models = {
415 'Simple': SimpleClassifier(len(vocab), embed_dim=64, num_classes=2),
416 'LSTM': LSTMClassifier(len(vocab), embed_dim=64, hidden_dim=128, num_classes=2),
417 'Transformer': TransformerClassifier(len(vocab), embed_dim=64, num_heads=4,
418 num_layers=2, num_classes=2)
419}
420
421results = {}
422for name, model in models.items():
423 print(f"\n--- {name} ํ์ต ---")
424 history = train_model(model, train_loader, test_loader, epochs=15)
425 results[name] = history
426 print(f"{name} ์ต์ข
ํ
์คํธ ์ ํ๋: {history['test_acc'][-1]:.1f}%")
427
428
429# ============================================
430# 10. ๊ฒฐ๊ณผ ์๊ฐํ
431# ============================================
432print("\n[10] ๊ฒฐ๊ณผ ์๊ฐํ")
433print("-" * 40)
434
435fig, axes = plt.subplots(1, 2, figsize=(12, 4))
436
437# ์ ํ๋
438for name, history in results.items():
439 axes[0].plot(history['test_acc'], label=f"{name} (final={history['test_acc'][-1]:.1f}%)")
440axes[0].set_xlabel('Epoch')
441axes[0].set_ylabel('Test Accuracy (%)')
442axes[0].set_title('Model Comparison - Accuracy')
443axes[0].legend()
444axes[0].grid(True, alpha=0.3)
445
446# ์์ค
447for name, history in results.items():
448 axes[1].plot(history['test_loss'], label=name)
449axes[1].set_xlabel('Epoch')
450axes[1].set_ylabel('Test Loss')
451axes[1].set_title('Model Comparison - Loss')
452axes[1].legend()
453axes[1].grid(True, alpha=0.3)
454
455plt.tight_layout()
456plt.savefig('text_classification_comparison.png', dpi=100)
457plt.close()
458print("๊ทธ๋ํ ์ ์ฅ: text_classification_comparison.png")
459
460
461# ============================================
462# 11. ์ถ๋ก ํจ์
463# ============================================
464print("\n[11] ์ถ๋ก ํ
์คํธ")
465print("-" * 40)
466
467def predict_sentiment(model, text, vocab, tokenizer, device):
468 """ํ
์คํธ ๊ฐ์ฑ ์์ธก"""
469 model.eval()
470 encoded = vocab.encode(text, tokenizer, max_len=50)
471 tensor = torch.tensor(encoded).unsqueeze(0).to(device)
472
473 with torch.no_grad():
474 output = model(tensor)
475 prob = F.softmax(output, dim=1)
476 pred = output.argmax(dim=1).item()
477
478 sentiment = 'Positive' if pred == 1 else 'Negative'
479 confidence = prob[0, pred].item()
480
481 return sentiment, confidence
482
483# ํ
์คํธ ๋ฌธ์ฅ
484test_sentences = [
485 "This product is amazing and I love it",
486 "Terrible quality waste of money",
487 "It's okay nothing special",
488 "Best purchase ever highly recommended",
489 "Very disappointed will not buy again",
490]
491
492# LSTM ๋ชจ๋ธ๋ก ์์ธก
493lstm_model = models['LSTM']
494print("\nLSTM ๋ชจ๋ธ ์์ธก:")
495for sentence in test_sentences:
496 sentiment, conf = predict_sentiment(lstm_model, sentence, vocab, simple_tokenizer, device)
497 print(f" [{sentiment:8s}] ({conf*100:5.1f}%) {sentence}")
498
499
500# ============================================
501# 12. Attention ์๊ฐํ
502# ============================================
503print("\n[12] Attention ๊ฐ์ค์น ๋ถ์")
504print("-" * 40)
505
506class AttentionLSTM(nn.Module):
507 """Attention์ด ์๋ LSTM"""
508 def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
509 super().__init__()
510 self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
511 self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True, bidirectional=True)
512 self.attention = nn.Linear(hidden_dim * 2, 1)
513 self.fc = nn.Linear(hidden_dim * 2, num_classes)
514
515 def forward(self, x, return_attention=False):
516 embedded = self.embedding(x)
517 output, _ = self.lstm(embedded)
518
519 # Attention
520 attn_weights = torch.softmax(self.attention(output).squeeze(-1), dim=1)
521 context = (output * attn_weights.unsqueeze(-1)).sum(dim=1)
522
523 logits = self.fc(context)
524
525 if return_attention:
526 return logits, attn_weights
527 return logits
528
529attn_model = AttentionLSTM(len(vocab), embed_dim=64, hidden_dim=128, num_classes=2)
530print(f"AttentionLSTM ํ๋ผ๋ฏธํฐ: {sum(p.numel() for p in attn_model.parameters()):,}")
531
532# ํ์ต
533print("\nAttentionLSTM ํ์ต:")
534attn_model = attn_model.to(device)
535history = train_model(attn_model, train_loader, test_loader, epochs=10)
536
537
538# Attention ์๊ฐํ
539def visualize_attention(model, text, vocab, tokenizer, device):
540 model.eval()
541 tokens = tokenizer(text)
542 encoded = vocab.encode(text, tokenizer, max_len=len(tokens))
543 tensor = torch.tensor(encoded).unsqueeze(0).to(device)
544
545 with torch.no_grad():
546 logits, attn = model(tensor, return_attention=True)
547 pred = logits.argmax(dim=1).item()
548 prob = F.softmax(logits, dim=1)[0, pred].item()
549
550 attn = attn[0].cpu().numpy()[:len(tokens)]
551 sentiment = 'Positive' if pred == 1 else 'Negative'
552
553 return tokens, attn, sentiment, prob
554
555# ์๊ฐํ
556sample_text = "This movie is absolutely amazing and wonderful"
557tokens, attn, sentiment, prob = visualize_attention(attn_model, sample_text, vocab, simple_tokenizer, device)
558
559print(f"\n๋ฌธ์ฅ: {sample_text}")
560print(f"์์ธก: {sentiment} ({prob*100:.1f}%)")
561print("\nAttention ๊ฐ์ค์น:")
562for token, weight in zip(tokens, attn):
563 bar = 'โ' * int(weight * 50)
564 print(f" {token:12s} {weight:.3f} {bar}")
565
566
567# ============================================
568# ์ ๋ฆฌ
569# ============================================
570print("\n" + "=" * 60)
571print("ํ
์คํธ ๋ถ๋ฅ ์ ๋ฆฌ")
572print("=" * 60)
573
574summary = """
575ํ
์คํธ ๋ถ๋ฅ ํ์ดํ๋ผ์ธ:
576 1. ํ ํฐํ: ํ
์คํธ โ ๋จ์ด ๋ฆฌ์คํธ
577 2. ์ดํ ๊ตฌ์ถ: ๋จ์ด โ ์ธ๋ฑ์ค ๋งคํ
578 3. ์ธ์ฝ๋ฉ: ํ
์คํธ โ ํ
์
579 4. ๋ชจ๋ธ: ์๋ฒ ๋ฉ โ ์ธ์ฝ๋ โ ๋ถ๋ฅ
580
581๋ชจ๋ธ ๋น๊ต:
582 - Simple (์๋ฒ ๋ฉ ํ๊ท ): ๋น ๋ฆ, ๊ฐ๋จ
583 - LSTM: ์์ ์ ๋ณด ํ์ฉ, ์์ ์
584 - Transformer: ๋ณ๋ ฌํ, ๊ธด ์ํ์ค
585
586ํต์ฌ ์ฝ๋:
587 # ์ดํ ๊ตฌ์ถ
588 vocab = Vocabulary(min_freq=2)
589 vocab.build(texts, tokenizer)
590
591 # LSTM ๋ถ๋ฅ๊ธฐ
592 lstm = nn.LSTM(embed_dim, hidden_dim, bidirectional=True)
593 _, (h_n, _) = lstm(embedded)
594 combined = torch.cat([h_n[-2], h_n[-1]], dim=1)
595
596 # Transformer ๋ถ๋ฅ๊ธฐ
597 encoder = nn.TransformerEncoder(encoder_layer, num_layers)
598 output = encoder(embedded, src_key_padding_mask=padding_mask)
599
600ํ์ต ํ:
601 - ๊ธฐ์ธ๊ธฐ ํด๋ฆฌํ (RNN์ ํ์)
602 - Dropout (๊ณผ์ ํฉ ๋ฐฉ์ง)
603 - ์ ์ ํ ํจ๋ฉ ์ฒ๋ฆฌ
604
605๋ค์ ๋จ๊ณ:
606 - HuggingFace Transformers
607 - BERT/GPT ํ์ธํ๋
608 - ๋๊ท๋ชจ ๋ฐ์ดํฐ์
(IMDb)
609"""
610print(summary)
611print("=" * 60)