File size: 3,948 Bytes
ab331fa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
""" 

Model.py - DO NOT RUN THIS SCRIPT!



Run this script, prep/data_prep.py and the notebooks through pipeline.py

"""

import json
import torch
import numpy as np
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parent.parent

torch.manual_seed(42)

DATA_DIR = Path(__file__).resolve().parent / 'prep'

assert (DATA_DIR / 'prepared_data.npz').exists(), f"Missing {DATA_DIR / 'prepared_data.npz'}"
assert (DATA_DIR / 'label_vocab.json').exists(), f"Missing {DATA_DIR / 'label_vocab.json'}"

data = np.load(DATA_DIR / 'prepared_data.npz')

X_train = data['X_train']
X_test = data['X_test']
y_train = data['y_train']
y_test = data['y_test']

with open(DATA_DIR / 'label_vocab.json', encoding='utf-8') as f:
    VOCAB = json.load(f)

    
DIM = X_train.shape[1]
NUM_LABELS = len(VOCAB)

class SkillData(Dataset):
    def __init__(self, X, y) -> torch.tensor:
        self.X = torch.tensor(X, dtype=torch.float32)
        self.y = torch.tensor(y, dtype=torch.float32)
        
    def __len__(self) -> len:
        return len(self.X)
    
    def __getitem__(self, idx) -> int:
        return self.X[idx], self.y[idx]
    
train_ds = SkillData(X_train, y_train)
test_ds = SkillData(X_test, y_test)

train_loader = DataLoader(train_ds, batch_size=64, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=64, shuffle=False)

class SkillClassifier(nn.Module):
    def __init__(self, input_dim, num_labels, hidden_dim=32, dropout=0.3) -> None:
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.ReLU(),
            nn.Dropout(dropout),
            nn.Linear(hidden_dim, num_labels)       
        )
        
    def forward(self, x) -> float:
        return self.net(x)
    
jobanalyze_6k = SkillClassifier(DIM, NUM_LABELS)

total_params = sum(p.numel() for p in jobanalyze_6k.parameters())
trainable_params = sum(p.numel() for p in jobanalyze_6k.parameters() if p.requires_grad)

print(f"Total Parameters: {total_params:,}")
print(f"Trainable Parameters: {trainable_params:,}")

pos_counts = y_train.sum(axis=0)
neg_counts = len(y_train) - pos_counts
pos_weight = torch.tensor(neg_counts / (pos_counts + 1e-6), dtype=torch.float32)
pos_weight = torch.clamp(pos_weight, max=10.0)

criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
optimizer = torch.optim.Adam(jobanalyze_6k.parameters(), lr=1e-3, weight_decay=1e-4)

EPOCHS = 300
history = {'train_loss' : [], 'test_loss' : []}

for epoch in range(1, EPOCHS + 1):
    jobanalyze_6k.train()
    train_losses = []
    for xb, yb in train_loader:
        optimizer.zero_grad()
        logits = jobanalyze_6k(xb)
        loss = criterion(logits, yb)
        loss.backward()
        optimizer.step()
        train_losses.append(loss.item())
        
    jobanalyze_6k.eval()
    test_losses = []
    with torch.no_grad():
        for xb, yb in test_loader:
            logits = jobanalyze_6k(xb)
            loss = criterion(logits, yb)
            test_losses.append(loss.item())
            
    train_loss = sum(train_losses) / len(train_losses)
    test_loss = sum(test_losses) / len(test_losses)
    history['train_loss'].append(train_loss)
    history['test_loss'].append(test_loss)
    
    if epoch % 10 == 0 or epoch == 1:
        print(f"Epoch {epoch:3d} | train_loss {train_loss:.4f} | test_loss {test_loss:.4f}")
        
out_dir = REPO_ROOT / 'model_out'
out_dir.mkdir(parents=True, exist_ok=True)

torch.save(jobanalyze_6k.state_dict(), out_dir / 'skill_classifier.pt')
with open(out_dir / 'training_history.json', 'w') as f:
    json.dump(history, f)       
    
print("\n Saved Model")
print(f"Final train_loss : {history['train_loss'][-1]:.4f} | "
      f"Final test_loss : {history['test_loss'][-1]:.4f}")