Sirius16 commited on
Commit
4dc60af
·
verified ·
1 Parent(s): 5cf7da8

Upload 28 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/BrainAI.png filter=lfs diff=lfs merge=lfs -text
37
+ static/DSC[[:space:]]UI.png filter=lfs diff=lfs merge=lfs -text
38
+ static/foto[[:space:]]sampel/Hilmy.jpg filter=lfs diff=lfs merge=lfs -text
39
+ static/foto[[:space:]]sampel/Icha.png filter=lfs diff=lfs merge=lfs -text
40
+ static/foto[[:space:]]sampel/Jason.jpg filter=lfs diff=lfs merge=lfs -text
41
+ static/foto[[:space:]]sampel/Lil[[:space:]]Bah[[:space:]]Lil.jpg filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /code
4
+
5
+ COPY requirements.txt .
6
+
7
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
8
+
9
+ RUN useradd -m -u 1000 user
10
+ USER user
11
+ ENV HOME=/home/user \
12
+ PATH=/home/user/.local/bin:$PATH
13
+
14
+ WORKDIR $HOME/app
15
+
16
+ COPY --chown=user . $HOME/app
17
+
18
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import torch
4
+ import torch.nn.functional as F
5
+ from flask import Flask, render_template, request, jsonify
6
+ from torchvision import transforms
7
+ from PIL import Image
8
+
9
+ from convnext_mlp import ConvNextMLP
10
+ from convnext_kan import ConvNextKAN
11
+
12
+ app = Flask(__name__)
13
+
14
+ CLASSES = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
15
+ IMAGENET_MEAN = [0.485, 0.456, 0.406]
16
+ IMAGENET_STD = [0.229, 0.224, 0.225]
17
+
18
+ inference_transform = transforms.Compose([
19
+ transforms.Resize(256),
20
+ transforms.CenterCrop(224),
21
+ transforms.ToTensor(),
22
+ transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD),
23
+ ])
24
+
25
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
26
+
27
+ def load_model_weights(model, path):
28
+ if os.path.exists(path):
29
+ model.load_state_dict(torch.load(path, map_location=device))
30
+ print(f"Pth file of {path} successfully loaded!")
31
+ else:
32
+ print(f"Pth file of {path} not found. Please check the path.")
33
+
34
+ model.eval()
35
+ return model.to(device)
36
+
37
+ FREEZE_BACKBONE = True
38
+ UNFREEZE_LAST_STAGE = False
39
+
40
+ model_mlp = ConvNextMLP(
41
+ num_classes=10,
42
+ head_depth=3,
43
+ hidden_dim_1=512,
44
+ hidden_dim_2=512,
45
+ head_style="rakyan",
46
+ freeze_backbone=FREEZE_BACKBONE,
47
+ unfreeze_last_stage=UNFREEZE_LAST_STAGE
48
+ )
49
+ model_mlp = load_model_weights(model_mlp, "weights/convnext_mlp_cifar10.pth")
50
+
51
+ model_kan = ConvNextKAN(
52
+ num_classes=10,
53
+ head_depth=3,
54
+ hidden_dim_1=512,
55
+ hidden_dim_2=512,
56
+ head_style="rakyan",
57
+ freeze_backbone=FREEZE_BACKBONE,
58
+ unfreeze_last_stage=UNFREEZE_LAST_STAGE
59
+ )
60
+ model_kan = load_model_weights(model_kan, "weights/convnext_kan_cifar10.pth")
61
+
62
+ @app.route('/')
63
+ def home():
64
+ return render_template('index.html')
65
+
66
+ @app.route('/api/predict', methods=['POST'])
67
+ def predict():
68
+ if 'file' not in request.files:
69
+ return jsonify({"status": "error", "message": "No file"}), 400
70
+
71
+ file = request.files['file']
72
+ img_bytes = file.read()
73
+
74
+ try:
75
+ image = Image.open(io.BytesIO(img_bytes)).convert('RGB')
76
+ tensor_img = inference_transform(image).unsqueeze(0).to(device)
77
+
78
+ with torch.no_grad():
79
+ out_1 = model_mlp(tensor_img)
80
+ prob_1 = F.softmax(out_1, dim=1)
81
+ conf_1, idx_1 = torch.max(prob_1, 1)
82
+
83
+ out_2 = model_kan(tensor_img)
84
+ prob_2 = F.softmax(out_2, dim=1)
85
+ conf_2, idx_2 = torch.max(prob_2, 1)
86
+
87
+ p1_list = prob_1[0].tolist()
88
+ p2_list = prob_2[0].tolist()
89
+
90
+ all_p1 = [{"class": CLASSES[i], "confidence": round(p1_list[i] * 100, 2)} for i in range(10)]
91
+ all_p2 = [{"class": CLASSES[i], "confidence": round(p2_list[i] * 100, 2)} for i in range(10)]
92
+
93
+ all_p1.sort(key=lambda x: x['confidence'], reverse=True)
94
+ all_p2.sort(key=lambda x: x['confidence'], reverse=True)
95
+
96
+ is_outlier = False
97
+ if conf_1.item() < 0.35 and conf_2.item() < 0.35:
98
+ is_outlier = True
99
+
100
+ return jsonify({
101
+ "status": "success",
102
+ "is_outlier": is_outlier,
103
+ "model_1": {
104
+ "class": CLASSES[idx_1.item()],
105
+ "confidence": round(conf_1.item() * 100, 2),
106
+ "all_probs": all_p1
107
+ },
108
+ "model_2": {
109
+ "class": CLASSES[idx_2.item()],
110
+ "confidence": round(conf_2.item() * 100, 2),
111
+ "all_probs": all_p2
112
+ }
113
+ })
114
+ except Exception as e:
115
+ return jsonify({"status": "error", "message": str(e)})
116
+
117
+ if __name__ == '__main__':
118
+ app.run(host='0.0.0.0', port=7860)
convnext_kan.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torchvision.models import convnext_tiny, ConvNeXt_Tiny_Weights
4
+ from kan import KANLinear
5
+
6
+ class ConvNeXtFeatureExtractor(nn.Module):
7
+ def __init__(
8
+ self,
9
+ freeze_backbone: bool = True,
10
+ unfreeze_last_stage: bool = False,
11
+ ):
12
+ super().__init__()
13
+ weights = ConvNeXt_Tiny_Weights.DEFAULT
14
+ self.backbone = convnext_tiny(weights=weights)
15
+
16
+ if freeze_backbone:
17
+ for p in self.backbone.parameters():
18
+ p.requires_grad = False
19
+
20
+ if unfreeze_last_stage:
21
+ for p in self.backbone.features[-1].parameters():
22
+ p.requires_grad = True
23
+
24
+ self.output_dim = self.backbone.classifier[2].in_features
25
+ self.backbone.classifier = nn.Identity()
26
+
27
+ def forward(self, x):
28
+ x = self.backbone(x)
29
+ x = x.view(x.size(0), -1)
30
+ return x
31
+
32
+ class KANHead(nn.Module):
33
+ def __init__(
34
+ self,
35
+ input_dim: int,
36
+ num_classes: int,
37
+ head_depth: int = 2,
38
+ hidden_dim_1: int = 512,
39
+ hidden_dim_2: int = 256,
40
+ head_style: str = "standard",
41
+ ):
42
+ super().__init__()
43
+ self.head_depth = head_depth
44
+ self.head_style = head_style
45
+
46
+ if head_style == "rakyan":
47
+ if head_depth == 2:
48
+ self.kan1 = KANLinear(input_dim, hidden_dim_1)
49
+ self.bn1 = nn.BatchNorm1d(hidden_dim_1)
50
+ self.act1 = nn.ReLU(inplace=True)
51
+ self.kan2 = KANLinear(hidden_dim_1, num_classes)
52
+
53
+ elif head_depth == 3:
54
+ self.kan1 = KANLinear(input_dim, hidden_dim_1)
55
+ self.bn1 = nn.BatchNorm1d(hidden_dim_1)
56
+ self.act1 = nn.ReLU(inplace=True)
57
+ self.drop1 = nn.Dropout(p=0.3)
58
+
59
+ self.kan2 = KANLinear(hidden_dim_1, hidden_dim_2)
60
+ self.bn2 = nn.BatchNorm1d(hidden_dim_2)
61
+ self.act2 = nn.ReLU(inplace=True)
62
+ self.drop2 = nn.Dropout(p=0.3)
63
+
64
+ self.kan3 = KANLinear(hidden_dim_2, num_classes)
65
+ else:
66
+ raise ValueError("head_depth must be 2 or 3")
67
+
68
+ elif head_style == "standard":
69
+ if head_depth == 2:
70
+ self.pre_norm = nn.LayerNorm(input_dim)
71
+ self.kan1 = KANLinear(input_dim, hidden_dim_1)
72
+ self.norm1 = nn.LayerNorm(hidden_dim_1)
73
+ self.kan2 = KANLinear(hidden_dim_1, num_classes)
74
+
75
+ elif head_depth == 3:
76
+ self.pre_norm = nn.LayerNorm(input_dim)
77
+ self.kan1 = KANLinear(input_dim, hidden_dim_1)
78
+ self.norm1 = nn.LayerNorm(hidden_dim_1)
79
+ self.kan2 = KANLinear(hidden_dim_1, hidden_dim_2)
80
+ self.norm2 = nn.LayerNorm(hidden_dim_2)
81
+ self.kan3 = KANLinear(hidden_dim_2, num_classes)
82
+ else:
83
+ raise ValueError("head_depth must be 2 or 3")
84
+ else:
85
+ raise ValueError("head_style must be 'standard' or 'rakyan'")
86
+
87
+ def forward(self, x):
88
+ if self.head_style == "rakyan":
89
+ x = self.kan1(x); x = self.bn1(x); x = self.act1(x); x = self.drop1(x)
90
+
91
+ if self.head_depth == 2:
92
+ x = self.kan2(x)
93
+ else:
94
+ x = self.kan2(x); x = self.bn2(x); x = self.act2(x); x = self.drop2(x)
95
+ x = self.kan3(x)
96
+ return x
97
+
98
+ x = self.pre_norm(x)
99
+ x = self.kan1(x)
100
+ x = self.norm1(x)
101
+
102
+ if self.head_depth == 2:
103
+ x = self.kan2(x)
104
+ else:
105
+ x = self.kan2(x)
106
+ x = self.norm2(x)
107
+ x = self.kan3(x)
108
+
109
+ return x
110
+
111
+ class ConvNextKAN(nn.Module):
112
+ def __init__(
113
+ self,
114
+ num_classes: int = 10,
115
+ head_depth: int = 2,
116
+ hidden_dim_1: int = 512,
117
+ hidden_dim_2: int = 256,
118
+ freeze_backbone: bool = True,
119
+ unfreeze_last_stage: bool = False,
120
+ head_style: str = "standard"
121
+ ):
122
+ super().__init__()
123
+ self.feature_extractor = ConvNeXtFeatureExtractor(
124
+ freeze_backbone=freeze_backbone,
125
+ unfreeze_last_stage=unfreeze_last_stage
126
+ )
127
+
128
+ self.head = KANHead(
129
+ input_dim=self.feature_extractor.output_dim,
130
+ num_classes=num_classes,
131
+ head_depth=head_depth,
132
+ hidden_dim_1=hidden_dim_1,
133
+ hidden_dim_2=hidden_dim_2,
134
+ head_style=head_style
135
+ )
136
+
137
+ def forward(self, x):
138
+ features = self.feature_extractor(x)
139
+ out = self.head(features)
140
+ return out
convnext_mlp.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torchvision.models import convnext_tiny, ConvNeXt_Tiny_Weights
4
+
5
+ class ConvNeXtFeatureExtractor(nn.Module):
6
+ def __init__(
7
+ self,
8
+ freeze_backbone: bool = True,
9
+ unfreeze_last_stage: bool = False,
10
+ ):
11
+ super().__init__()
12
+ weights = ConvNeXt_Tiny_Weights.DEFAULT
13
+ self.backbone = convnext_tiny(weights=weights)
14
+
15
+ if freeze_backbone:
16
+ for p in self.backbone.parameters():
17
+ p.requires_grad = False
18
+
19
+ if unfreeze_last_stage:
20
+ for p in self.backbone.features[-1].parameters():
21
+ p.requires_grad = True
22
+
23
+ self.output_dim = self.backbone.classifier[2].in_features
24
+ self.backbone.classifier = nn.Identity()
25
+
26
+ def forward(self, x):
27
+ x = self.backbone(x)
28
+ x = x.view(x.size(0), -1)
29
+ return x
30
+
31
+ class MLPHead(nn.Module):
32
+ def __init__(
33
+ self,
34
+ input_dim: int,
35
+ num_classes: int,
36
+ head_depth: int = 2,
37
+ hidden_dim_1: int = 512,
38
+ hidden_dim_2: int = 256,
39
+ dropout: float = 0.1,
40
+ head_style: str = "standard",
41
+ ):
42
+ super().__init__()
43
+
44
+ if head_style == "rakyan":
45
+ if head_depth == 2:
46
+ self.net = nn.Sequential(
47
+ nn.Linear(input_dim, hidden_dim_1),
48
+ nn.BatchNorm1d(hidden_dim_1),
49
+ nn.ReLU(inplace=True),
50
+ nn.Dropout(p=0.3),
51
+ nn.Linear(hidden_dim_1, num_classes),
52
+ )
53
+ elif head_depth == 3:
54
+ self.net = nn.Sequential(
55
+ nn.Linear(input_dim, hidden_dim_1),
56
+ nn.BatchNorm1d(hidden_dim_1),
57
+ nn.ReLU(inplace=True),
58
+ nn.Dropout(p=0.3),
59
+ nn.Linear(hidden_dim_1, hidden_dim_2),
60
+ nn.BatchNorm1d(hidden_dim_2),
61
+ nn.ReLU(inplace=True),
62
+ nn.Dropout(p=0.3),
63
+ nn.Linear(hidden_dim_2, num_classes),
64
+ )
65
+ else:
66
+ raise ValueError("head_depth must be 2 or 3")
67
+
68
+ elif head_style == "standard":
69
+ if head_depth == 2:
70
+ self.net = nn.Sequential(
71
+ nn.LayerNorm(input_dim),
72
+ nn.Linear(input_dim, hidden_dim_1),
73
+ nn.LayerNorm(hidden_dim_1),
74
+ nn.GELU(),
75
+ nn.Dropout(dropout),
76
+ nn.Linear(hidden_dim_1, num_classes),
77
+ )
78
+ elif head_depth == 3:
79
+ self.net = nn.Sequential(
80
+ nn.LayerNorm(input_dim),
81
+ nn.Linear(input_dim, hidden_dim_1),
82
+ nn.LayerNorm(hidden_dim_1),
83
+ nn.GELU(),
84
+ nn.Dropout(dropout),
85
+ nn.Linear(hidden_dim_1, hidden_dim_2),
86
+ nn.LayerNorm(hidden_dim_2),
87
+ nn.GELU(),
88
+ nn.Dropout(dropout),
89
+ nn.Linear(hidden_dim_2, num_classes),
90
+ )
91
+ else:
92
+ raise ValueError("head_depth must be 2 or 3")
93
+ else:
94
+ raise ValueError("head_style must be 'standard' or 'rakyan'")
95
+
96
+ def forward(self, x):
97
+ return self.net(x)
98
+
99
+ class ConvNextMLP(nn.Module):
100
+ def __init__(
101
+ self,
102
+ num_classes: int = 10,
103
+ head_depth: int = 2,
104
+ hidden_dim_1: int = 512,
105
+ hidden_dim_2: int = 256,
106
+ dropout: float = 0.1,
107
+ freeze_backbone: bool = True,
108
+ unfreeze_last_stage: bool = False,
109
+ head_style: str = "standard"
110
+ ):
111
+ super().__init__()
112
+ self.feature_extractor = ConvNeXtFeatureExtractor(
113
+ freeze_backbone=freeze_backbone,
114
+ unfreeze_last_stage=unfreeze_last_stage
115
+ )
116
+
117
+ self.head = MLPHead(
118
+ input_dim=self.feature_extractor.output_dim,
119
+ num_classes=num_classes,
120
+ head_depth=head_depth,
121
+ hidden_dim_1=hidden_dim_1,
122
+ hidden_dim_2=hidden_dim_2,
123
+ dropout=dropout,
124
+ head_style=head_style
125
+ )
126
+
127
+ def forward(self, x):
128
+ features = self.feature_extractor(x)
129
+ out = self.head(features)
130
+ return out
kan.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import math
5
+
6
+ class KANLinear(nn.Module):
7
+ def __init__(
8
+ self,
9
+ in_features: int,
10
+ out_features: int,
11
+ grid_size: int = 5,
12
+ spline_order: int = 3,
13
+ scale_noise: float = 0.1,
14
+ scale_base: float = 1.0,
15
+ scale_spline: float = 1.0,
16
+ enable_standalone_scale_spline: bool = True,
17
+ base_activation: nn.Module = nn.SiLU(),
18
+ grid_eps: float = 0.02,
19
+ grid_range: list = [-1, 1],
20
+ ):
21
+ super(KANLinear, self).__init__()
22
+ self.in_features = in_features
23
+ self.out_features = out_features
24
+ self.grid_size = grid_size
25
+ self.spline_order = spline_order
26
+ self.grid_eps = grid_eps
27
+
28
+ h = (grid_range[1] - grid_range[0]) / grid_size
29
+
30
+ grid = (
31
+ (
32
+ torch.arange(-spline_order, grid_size + spline_order + 1) * h
33
+ + grid_range[0]
34
+ )
35
+ .expand(in_features, -1)
36
+ .contiguous()
37
+ )
38
+ self.register_buffer("grid", grid)
39
+
40
+ self.base_weight = nn.Parameter(torch.Tensor(out_features, in_features))
41
+ self.spline_weight = nn.Parameter(
42
+ torch.Tensor(out_features, in_features, grid_size + spline_order)
43
+ )
44
+
45
+ if enable_standalone_scale_spline:
46
+ self.spline_scaler = nn.Parameter(
47
+ torch.Tensor(out_features, in_features)
48
+ )
49
+ else:
50
+ self.register_parameter("spline_scaler", None)
51
+
52
+ self.scale_noise = scale_noise
53
+ self.scale_base = scale_base
54
+ self.scale_spline = scale_spline
55
+ self.enable_standalone_scale_spline = enable_standalone_scale_spline
56
+ self.base_activation = base_activation
57
+
58
+ self.reset_parameters()
59
+
60
+ def reset_parameters(self):
61
+ nn.init.kaiming_uniform_(self.base_weight, a=math.sqrt(5) * self.scale_base)
62
+ with torch.no_grad():
63
+ noise = (
64
+ (
65
+ torch.rand(self.grid_size + 1, self.in_features, self.out_features)
66
+ - 1 / 2
67
+ )
68
+ * self.scale_noise
69
+ / self.grid_size
70
+ )
71
+
72
+ self.spline_weight.data.copy_(
73
+ (self.scale_spline if not self.enable_standalone_scale_spline else 1.0)
74
+ * self.curve2coeff(
75
+ self.grid.T[self.spline_order : -self.spline_order],
76
+ noise,
77
+ )
78
+ )
79
+ if self.enable_standalone_scale_spline:
80
+ nn.init.kaiming_uniform_(self.spline_scaler, a=math.sqrt(5) * self.scale_spline)
81
+
82
+ def b_splines(self, x: torch.Tensor):
83
+ assert x.dim() == 2 and x.size(1) == self.in_features
84
+
85
+ grid = self.grid
86
+ x = x.unsqueeze(-1)
87
+ bases = ((x >= grid[:, :-1]) & (x < grid[:, 1:])).to(x.dtype)
88
+
89
+ for k in range(1, self.spline_order + 1):
90
+ bases = (
91
+ (x - grid[:, : -(k + 1)])
92
+ / (grid[:, k:-1] - grid[:, : -(k + 1)])
93
+ * bases[:, :, :-1]
94
+ ) + (
95
+ (grid[:, k + 1 :] - x)
96
+ / (grid[:, k + 1 :] - grid[:, 1:(-k)])
97
+ * bases[:, :, 1:]
98
+ )
99
+
100
+ assert bases.size() == (x.size(0), self.in_features, self.grid_size + self.spline_order)
101
+ return bases.contiguous()
102
+
103
+ def curve2coeff(self, x: torch.Tensor, y: torch.Tensor):
104
+ assert x.dim() == 2 and x.size(1) == self.in_features
105
+ assert y.size() == (x.size(0), self.in_features, self.out_features)
106
+
107
+ A = self.b_splines(x).transpose(0, 1)
108
+ B = y.transpose(0, 1)
109
+
110
+ solution = torch.linalg.lstsq(A, B).solution
111
+ result = solution.permute(2, 0, 1)
112
+
113
+ assert result.size() == (self.out_features, self.in_features, self.grid_size + self.spline_order)
114
+ return result.contiguous()
115
+
116
+ def forward(self, x: torch.Tensor):
117
+ assert x.dim() == 2 and x.size(1) == self.in_features
118
+
119
+ base_output = F.linear(self.base_activation(x), self.base_weight)
120
+
121
+ spline_output = F.linear(
122
+ self.b_splines(x).view(x.size(0), -1),
123
+ self.spline_weight.view(self.out_features, -1),
124
+ )
125
+
126
+ if self.enable_standalone_scale_spline:
127
+ spline_output = spline_output * self.spline_scaler.unsqueeze(0).mean(dim=2)
128
+
129
+ return base_output + spline_output
kan1.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+ import math
5
+
6
+ class KANLinear(nn.Module):
7
+ def __init__(
8
+ self,
9
+ in_features,
10
+ out_features,
11
+ grid_size=5,
12
+ spline_order=3,
13
+ scale_noise=0.1,
14
+ scale_base= 1.0,
15
+ scale_spline=1.0,
16
+ enable_standalone_scale_spline=True,
17
+ base_activation=nn.SiLU,
18
+ grid_eps=0.02,
19
+ grid_range=[-1, 1],
20
+ ):
21
+ super(KANLinear, self).__init__()
22
+ self.in_features = in_features
23
+ self.out_features = out_features
24
+ self.grid_size = grid_size
25
+ self.spline_order = spline_order
26
+
27
+ h = (grid_range[1] - grid_range[0]) / grid_size
28
+ grid = (
29
+ (
30
+ torch.arange(-spline_order, grid_size + spline_order + 1) * h
31
+ + grid_range[0]
32
+ )
33
+ .expand(in_features, -1)
34
+ .contiguous()
35
+ )
36
+ self.register_buffer("grid", grid)
37
+
38
+ self.base_weight = nn.Parameter(torch.Tensor(out_features, in_features))
39
+ self.spline_weight = nn.Parameter(
40
+ torch.Tensor(out_features, in_features, grid_size + spline_order)
41
+ )
42
+ if enable_standalone_scale_spline:
43
+ self.spline_scaler = nn.Parameter(
44
+ torch.Tensor(out_features, in_features)
45
+ )
46
+
47
+ self.scale_noise = scale_noise
48
+ self.scale_base = scale_base
49
+ self.scale_spline = scale_spline
50
+ self.enable_standalone_scale_spline = enable_standalone_scale_spline
51
+ self.base_activation = base_activation()
52
+ self.grid_eps = grid_eps
53
+ self.reset_parameters()
54
+
55
+ def reset_parameters(self):
56
+ nn.init.kaiming_uniform_(self.base_weight, a=math.sqrt(5) * self.scale_base)
57
+ with torch.no_grad():
58
+ noise = (
59
+ (
60
+ torch.rand(self.grid_size + 1, self.in_features, self.out_features)
61
+ - 1 / 2
62
+ )
63
+ * self.scale_noise
64
+ / self.grid_size
65
+ )
66
+ self.spline_weight.data.copy_(
67
+ (self.scale_spline if not self.enable_standalone_scale_spline else 1.0)
68
+ * self.curve2coeff(
69
+ self.grid.T[self.spline_order : -self.spline_order],
70
+ noise,
71
+ )
72
+ )
73
+ if self.enable_standalone_scale_spline:
74
+ nn.init.kaiming_uniform_(self.spline_scaler, a=math.sqrt(5) * self.scale_spline)
75
+
76
+ def b_splines(self, x):
77
+ assert x.dim() == 2 and x.size(1) == self.in_features
78
+ grid = self.grid
79
+ x = x.unsqueeze(-1)
80
+ bases = ((x >= grid[:, :-1]) & (x < grid[:, 1:])).to(x.dtype)
81
+ for k in range(1, self.spline_order + 1):
82
+ bases = (
83
+ (x - grid[:, : -(k + 1)])
84
+ / (grid[:, k:-1] - grid[:, : -(k + 1)])
85
+ * bases[:, :, :-1]
86
+ ) + (
87
+ (grid[:, k + 1 :] - x)
88
+ / (grid[:, k + 1 :] - grid[:, 1:(-k)])
89
+ * bases[:, :, 1:]
90
+ )
91
+ return bases.contiguous()
92
+
93
+ def curve2coeff(self, x, y):
94
+ A = self.b_splines(x).transpose(0, 1)
95
+ B = y.transpose(0, 1)
96
+ solution = torch.linalg.lstsq(A, B).solution
97
+ result = solution.permute(2, 0, 1)
98
+ return result.contiguous()
99
+
100
+ @property
101
+ def scaled_spline_weight(self):
102
+ return self.spline_weight * (
103
+ self.spline_scaler.unsqueeze(-1)
104
+ if self.enable_standalone_scale_spline
105
+ else 1.0
106
+ )
107
+
108
+ def forward(self, x):
109
+ if x.dim() != 2 or x.size(1) != self.in_features:
110
+ x = x.view(x.size(0), -1)
111
+ base_output = F.linear(self.base_activation(x), self.base_weight)
112
+ spline_output = F.linear(
113
+ self.b_splines(x).view(x.size(0), -1),
114
+ self.scaled_spline_weight.view(self.out_features, -1),
115
+ )
116
+ return base_output + spline_output
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Flask
2
+ torch
3
+ torchvision
4
+ Pillow
resnet_kan.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from torchvision.models import resnet50, ResNet50_Weights
3
+ from kan1 import KANLinear
4
+
5
+ class ResNetKAN(nn.Module):
6
+ def __init__(self, num_classes=10, freeze_backbone=True):
7
+ super().__init__()
8
+ weights = ResNet50_Weights.DEFAULT
9
+ self.resnet = resnet50(weights=weights)
10
+ if freeze_backbone:
11
+ for p in self.resnet.parameters():
12
+ p.requires_grad = False
13
+ for p in self.resnet.layer3.parameters():
14
+ p.requires_grad = True
15
+ for p in self.resnet.layer4.parameters():
16
+ p.requires_grad = True
17
+ num_features = self.resnet.fc.in_features
18
+ self.resnet.fc = nn.Identity()
19
+ self.kan1 = KANLinear(num_features, 512)
20
+ self.bn1 = nn.BatchNorm1d(512)
21
+ self.act1 = nn.ReLU()
22
+ self.kan2 = KANLinear(512, 512)
23
+ self.bn2 = nn.BatchNorm1d(512)
24
+ self.act2 = nn.ReLU()
25
+ self.kan3 = KANLinear(512, num_classes)
26
+
27
+ def forward(self, x):
28
+ x = self.resnet(x)
29
+ x = x.view(x.size(0), -1)
30
+ x = self.kan1(x)
31
+ x = self.bn1(x)
32
+ x = self.act1(x)
33
+ x = self.kan2(x)
34
+ x = self.bn2(x)
35
+ x = self.act2(x)
36
+ x = self.kan3(x)
37
+ return x
resnet_mlp.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ from torchvision.models import resnet50, ResNet50_Weights
3
+
4
+ class MLPHead(nn.Module):
5
+ def __init__(self, in_features, hidden_dim, num_classes):
6
+ super().__init__()
7
+ self.net = nn.Sequential(
8
+ nn.Linear(in_features, hidden_dim),
9
+ nn.BatchNorm1d(hidden_dim),
10
+ nn.ReLU(inplace=True),
11
+ nn.Linear(hidden_dim, hidden_dim),
12
+ nn.BatchNorm1d(hidden_dim),
13
+ nn.ReLU(inplace=True),
14
+ nn.Linear(hidden_dim, num_classes),
15
+ )
16
+
17
+ def forward(self, x):
18
+ return self.net(x)
19
+
20
+ class ResNetMLP(nn.Module):
21
+ def __init__(self, num_classes=10, freeze_backbone=True, hidden_dim=512):
22
+ super().__init__()
23
+ weights = ResNet50_Weights.DEFAULT
24
+ self.resnet = resnet50(weights=weights)
25
+ if freeze_backbone:
26
+ for p in self.resnet.parameters():
27
+ p.requires_grad = False
28
+ for p in self.resnet.layer3.parameters():
29
+ p.requires_grad = True
30
+ for p in self.resnet.layer4.parameters():
31
+ p.requires_grad = True
32
+ num_features = self.resnet.fc.in_features
33
+ self.resnet.fc = nn.Identity()
34
+ self.mlp_head = MLPHead(
35
+ in_features=num_features,
36
+ hidden_dim=hidden_dim,
37
+ num_classes=num_classes,
38
+ )
39
+
40
+ def forward(self, x):
41
+ x = self.resnet(x)
42
+ x = x.view(x.size(0), -1)
43
+ x = self.mlp_head(x)
44
+ return x
static/BrainAI.png ADDED

Git LFS Details

  • SHA256: 0a76af116739256f0c3f403b292ec14e27bb7ae6cad62d3795659f9263597084
  • Pointer size: 132 Bytes
  • Size of remote file: 1.87 MB
static/DSC UI.png ADDED

Git LFS Details

  • SHA256: d0655fa1573a1cf2cdd18ada05739b4435783a1fdc9fc14071ce075b6cfee06b
  • Pointer size: 131 Bytes
  • Size of remote file: 183 kB
static/foto sampel/Ahdi.jpg ADDED
static/foto sampel/Autistic.77.jpg ADDED
static/foto sampel/Autistic.8.jpg ADDED
static/foto sampel/Hilmy.jpg ADDED

Git LFS Details

  • SHA256: ce309d0a135ed792dcde8b04f58b7f45cb7bc0da73fa5f8d0af5e0158f127109
  • Pointer size: 131 Bytes
  • Size of remote file: 161 kB
static/foto sampel/Icha.png ADDED

Git LFS Details

  • SHA256: bf97d7a141411d091f1bd1cbd947486d0575ac78066b6d9f4ae4e6a1a6ff6a64
  • Pointer size: 131 Bytes
  • Size of remote file: 867 kB
static/foto sampel/Jason.jpg ADDED

Git LFS Details

  • SHA256: 68b88f412c5672bd5bf37a0031630df6617b25797c995af658f35bf51495e222
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
static/foto sampel/Jokowi.jpg ADDED
static/foto sampel/Koh Owi.jpg ADDED
static/foto sampel/Lil Bah Lil.jpg ADDED

Git LFS Details

  • SHA256: 03457ac9a6748f65e4c3ef56ce1d8ab9d36c701ed4df08252a40f88a53308d81
  • Pointer size: 131 Bytes
  • Size of remote file: 989 kB
static/foto sampel/Non_Autistic.29.jpg ADDED
static/foto sampel/Non_Autistic.8.jpg ADDED
static/foto sampel/Prabs.jpg ADDED
templates/index.html ADDED
@@ -0,0 +1,927 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ConvNeXt CIFAR10 - Research Deployment</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap');
9
+
10
+ * {
11
+ margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ font-family: 'Plus Jakarta Sans', sans-serif;
15
+ }
16
+
17
+ html {
18
+ scroll-behavior: smooth;
19
+ }
20
+
21
+ body {
22
+ background-color: #f4f7fe;
23
+ color: #1e293b;
24
+ display: flex;
25
+ min-height: 100vh;
26
+ overflow-x: hidden;
27
+ }
28
+
29
+ .sidebar {
30
+ width: 280px;
31
+ background: #ffffff;
32
+ height: 100vh;
33
+ position: fixed;
34
+ left: 0;
35
+ top: 0;
36
+ box-shadow: 4px 0 24px rgba(0,0,0,0.04);
37
+ display: flex;
38
+ flex-direction: column;
39
+ z-index: 1000;
40
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
41
+ }
42
+
43
+ .sidebar-header {
44
+ padding: 40px 30px;
45
+ text-align: center;
46
+ display: flex;
47
+ justify-content: center;
48
+ align-items: center;
49
+ }
50
+
51
+ .sidebar-logo {
52
+ font-size: 24px;
53
+ font-weight: 800;
54
+ background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
55
+ -webkit-background-clip: text;
56
+ -webkit-text-fill-color: transparent;
57
+ letter-spacing: -0.5px;
58
+ }
59
+
60
+ .nav-menu {
61
+ list-style: none;
62
+ padding: 0 20px;
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 10px;
66
+ }
67
+
68
+ .nav-item {
69
+ display: block;
70
+ }
71
+
72
+ .nav-link {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 16px;
76
+ padding: 16px 20px;
77
+ text-decoration: none;
78
+ color: #64748b;
79
+ font-weight: 600;
80
+ font-size: 15px;
81
+ border-radius: 16px;
82
+ transition: all 0.3s ease;
83
+ }
84
+
85
+ .nav-link:hover, .nav-link.active {
86
+ background: #f1f5f9;
87
+ color: #4f46e5;
88
+ }
89
+
90
+ .nav-link svg {
91
+ width: 20px;
92
+ height: 20px;
93
+ stroke: currentColor;
94
+ stroke-width: 2.5;
95
+ fill: none;
96
+ }
97
+
98
+ .main-wrapper {
99
+ flex: 1;
100
+ margin-left: 280px;
101
+ display: flex;
102
+ flex-direction: column;
103
+ transition: margin-left 0.3s ease;
104
+ }
105
+
106
+ .hamburger-btn {
107
+ display: none;
108
+ position: fixed;
109
+ top: 20px;
110
+ left: 20px;
111
+ z-index: 2000;
112
+ background: #ffffff;
113
+ border: none;
114
+ width: 45px;
115
+ height: 45px;
116
+ border-radius: 12px;
117
+ box-shadow: 0 4px 12px rgba(0,0,0,0.08);
118
+ cursor: pointer;
119
+ align-items: center;
120
+ justify-content: center;
121
+ }
122
+
123
+ .hamburger-btn svg {
124
+ width: 24px;
125
+ height: 24px;
126
+ stroke: #1e293b;
127
+ stroke-width: 2.5;
128
+ }
129
+
130
+ .hero-banner {
131
+ background: linear-gradient(120deg, #4f46e5 0%, #8b5cf6 100%);
132
+ padding: 80px 60px;
133
+ color: white;
134
+ position: relative;
135
+ overflow: hidden;
136
+ border-bottom-left-radius: 40px;
137
+ border-bottom-right-radius: 40px;
138
+ margin: 20px;
139
+ display: flex;
140
+ flex-direction: column;
141
+ align-items: center;
142
+ justify-content: center;
143
+ text-align: center;
144
+ }
145
+
146
+ .hero-title {
147
+ font-size: 42px;
148
+ font-weight: 800;
149
+ margin-bottom: 12px;
150
+ letter-spacing: -1px;
151
+ position: relative;
152
+ z-index: 2;
153
+ }
154
+
155
+ .hero-subtitle {
156
+ font-size: 18px;
157
+ color: #e0e7ff;
158
+ font-weight: 500;
159
+ position: relative;
160
+ z-index: 2;
161
+ }
162
+
163
+ .container {
164
+ max-width: 1200px;
165
+ margin: 0 auto;
166
+ padding: 40px;
167
+ width: 100%;
168
+ }
169
+
170
+ .dashboard-grid {
171
+ display: grid;
172
+ grid-template-columns: 1fr;
173
+ gap: 40px;
174
+ }
175
+
176
+ .card-panel {
177
+ background: #ffffff;
178
+ border-radius: 30px;
179
+ padding: 45px;
180
+ box-shadow: 0 10px 40px rgba(112, 144, 176, 0.08);
181
+ border: 1px solid rgba(226, 232, 240, 0.8);
182
+ position: relative;
183
+ overflow: hidden;
184
+ }
185
+
186
+ .support-heading {
187
+ font-size: 16px;
188
+ font-weight: 700;
189
+ color: #718096;
190
+ text-transform: uppercase;
191
+ letter-spacing: 2px;
192
+ text-align: center;
193
+ margin-bottom: 40px;
194
+ }
195
+
196
+ .support-logos-container {
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ gap: 50px;
201
+ }
202
+
203
+ .support-item {
204
+ display: flex;
205
+ flex-direction: column;
206
+ align-items: center;
207
+ gap: 20px;
208
+ flex: 1;
209
+ }
210
+
211
+ .support-img {
212
+ height: 75px;
213
+ object-fit: contain;
214
+ }
215
+
216
+ .support-divider {
217
+ width: 1px;
218
+ height: 100px;
219
+ background-color: #cbd5e0;
220
+ }
221
+
222
+ .support-text {
223
+ font-size: 15px;
224
+ font-weight: 600;
225
+ color: #718096;
226
+ text-align: center;
227
+ }
228
+
229
+ .section-header {
230
+ display: flex;
231
+ align-items: center;
232
+ gap: 15px;
233
+ margin-bottom: 35px;
234
+ }
235
+
236
+ .section-icon {
237
+ width: 48px;
238
+ height: 48px;
239
+ background: #eef2ff;
240
+ border-radius: 14px;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ color: #4f46e5;
245
+ }
246
+
247
+ .section-icon svg {
248
+ width: 24px;
249
+ height: 24px;
250
+ stroke: currentColor;
251
+ stroke-width: 2.5;
252
+ fill: none;
253
+ }
254
+
255
+ .section-titles h2 {
256
+ font-size: 24px;
257
+ font-weight: 800;
258
+ color: #1e293b;
259
+ }
260
+
261
+ .section-titles p {
262
+ color: #64748b;
263
+ font-size: 14px;
264
+ margin-top: 4px;
265
+ font-weight: 500;
266
+ }
267
+
268
+ .pills-grid {
269
+ display: flex;
270
+ flex-wrap: wrap;
271
+ gap: 15px;
272
+ }
273
+
274
+ .pill {
275
+ background: #f8fafc;
276
+ border: 2px solid transparent;
277
+ padding: 12px 24px;
278
+ border-radius: 16px;
279
+ font-weight: 700;
280
+ font-size: 15px;
281
+ color: #475569;
282
+ cursor: pointer;
283
+ display: flex;
284
+ align-items: center;
285
+ gap: 10px;
286
+ transition: all 0.3s;
287
+ }
288
+
289
+ .pill:hover {
290
+ border-color: #4f46e5;
291
+ background: #eef2ff;
292
+ color: #4f46e5;
293
+ transform: translateY(-3px);
294
+ box-shadow: 0 10px 20px rgba(79, 70, 229, 0.1);
295
+ }
296
+
297
+ .upload-zone {
298
+ border: 3px dashed #cbd5e0;
299
+ border-radius: 24px;
300
+ background: #f8fafc;
301
+ padding: 70px 20px;
302
+ text-align: center;
303
+ cursor: pointer;
304
+ transition: all 0.3s ease;
305
+ display: flex;
306
+ flex-direction: column;
307
+ align-items: center;
308
+ justify-content: center;
309
+ }
310
+
311
+ .upload-zone:hover {
312
+ border-color: #4f46e5;
313
+ background: #f5f3ff;
314
+ }
315
+
316
+ #upload-prompt {
317
+ display: flex;
318
+ flex-direction: column;
319
+ align-items: center;
320
+ justify-content: center;
321
+ width: 100%;
322
+ }
323
+
324
+ .upload-icon {
325
+ width: 70px;
326
+ height: 70px;
327
+ background: #ffffff;
328
+ border-radius: 20px;
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: center;
332
+ margin-bottom: 20px;
333
+ box-shadow: 0 10px 25px rgba(0,0,0,0.06);
334
+ color: #4f46e5;
335
+ transition: transform 0.3s;
336
+ }
337
+
338
+ .upload-zone:hover .upload-icon {
339
+ transform: scale(1.1);
340
+ }
341
+
342
+ .upload-text {
343
+ font-size: 20px;
344
+ font-weight: 800;
345
+ color: #1e293b;
346
+ }
347
+
348
+ .upload-subtext {
349
+ font-size: 14px;
350
+ color: #64748b;
351
+ margin-top: 8px;
352
+ font-weight: 500;
353
+ }
354
+
355
+ #preview-wrapper {
356
+ display: none;
357
+ flex-direction: column;
358
+ align-items: center;
359
+ width: 100%;
360
+ }
361
+
362
+ #preview-img {
363
+ max-height: 300px;
364
+ border-radius: 16px;
365
+ box-shadow: 0 15px 30px rgba(0,0,0,0.1);
366
+ margin-bottom: 25px;
367
+ object-fit: cover;
368
+ }
369
+
370
+ .btn-outline {
371
+ background: #ffffff;
372
+ border: 2px solid #e2e8f0;
373
+ padding: 12px 30px;
374
+ border-radius: 16px;
375
+ font-weight: 700;
376
+ font-size: 15px;
377
+ color: #475569;
378
+ cursor: pointer;
379
+ transition: all 0.3s;
380
+ }
381
+
382
+ .btn-outline:hover {
383
+ border-color: #4f46e5;
384
+ color: #4f46e5;
385
+ background: #f5f3ff;
386
+ }
387
+
388
+ .results-container {
389
+ display: none;
390
+ margin-top: 40px;
391
+ }
392
+
393
+ .cards-grid {
394
+ display: grid;
395
+ grid-template-columns: 1fr 1fr;
396
+ gap: 30px;
397
+ }
398
+
399
+ .result-card {
400
+ background: #ffffff;
401
+ border: 1px solid #e2e8f0;
402
+ border-radius: 24px;
403
+ padding: 30px;
404
+ box-shadow: 0 4px 20px rgba(0,0,0,0.03);
405
+ position: relative;
406
+ }
407
+
408
+ .result-card::before {
409
+ content: '';
410
+ position: absolute;
411
+ top: 0;
412
+ left: 0;
413
+ width: 100%;
414
+ height: 6px;
415
+ background: linear-gradient(90deg, #4f46e5, #8b5cf6);
416
+ border-top-left-radius: 24px;
417
+ border-top-right-radius: 24px;
418
+ }
419
+
420
+ .rc-label {
421
+ font-size: 13px;
422
+ font-weight: 800;
423
+ color: #64748b;
424
+ text-transform: uppercase;
425
+ letter-spacing: 1.5px;
426
+ margin-bottom: 12px;
427
+ }
428
+
429
+ .rc-value {
430
+ font-size: 32px;
431
+ font-weight: 800;
432
+ color: #1e293b;
433
+ margin-bottom: 8px;
434
+ }
435
+
436
+ .rc-conf {
437
+ font-size: 14px;
438
+ color: #059669;
439
+ font-weight: 700;
440
+ background: #d1fae5;
441
+ padding: 6px 14px;
442
+ border-radius: 10px;
443
+ display: inline-flex;
444
+ align-items: center;
445
+ gap: 6px;
446
+ }
447
+
448
+ .rc-bars {
449
+ margin-top: 30px;
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: 12px;
453
+ }
454
+
455
+ .bar-item {
456
+ display: flex;
457
+ align-items: center;
458
+ font-size: 13px;
459
+ font-weight: 700;
460
+ color: #475569;
461
+ }
462
+
463
+ .bar-label { width: 90px; }
464
+ .bar-bg { flex: 1; height: 10px; background: #f1f5f9; border-radius: 5px; margin: 0 15px; overflow: hidden; }
465
+ .bar-fill { height: 100%; background: #4f46e5; border-radius: 5px; transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1); }
466
+ .bar-val { width: 45px; text-align: right; }
467
+
468
+ .interpretation-alert {
469
+ margin-top: 30px;
470
+ padding: 25px;
471
+ border-radius: 20px;
472
+ font-size: 15px;
473
+ line-height: 1.6;
474
+ display: flex;
475
+ align-items: flex-start;
476
+ gap: 20px;
477
+ box-shadow: 0 10px 25px rgba(0,0,0,0.03);
478
+ }
479
+
480
+ .alert-icon { font-size: 28px; line-height: 1; }
481
+ .alert-content strong { display: block; margin-bottom: 6px; font-size: 16px; font-weight: 800; }
482
+
483
+ .loader {
484
+ display: none;
485
+ text-align: center;
486
+ padding: 60px;
487
+ }
488
+
489
+ .spinner {
490
+ width: 50px;
491
+ height: 50px;
492
+ border: 5px solid #eef2ff;
493
+ border-top: 5px solid #4f46e5;
494
+ border-radius: 50%;
495
+ animation: spin 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
496
+ margin: 0 auto 20px;
497
+ }
498
+
499
+ @keyframes spin { 100% { transform: rotate(360deg); } }
500
+
501
+ .modal {
502
+ display: none;
503
+ position: fixed;
504
+ top: 0; left: 0; width: 100%; height: 100%;
505
+ background: rgba(15, 23, 42, 0.4);
506
+ backdrop-filter: blur(8px);
507
+ z-index: 9999;
508
+ align-items: center;
509
+ justify-content: center;
510
+ opacity: 0;
511
+ transition: opacity 0.3s;
512
+ }
513
+
514
+ .modal.show { opacity: 1; }
515
+
516
+ .modal-content {
517
+ background: white;
518
+ padding: 40px;
519
+ border-radius: 30px;
520
+ max-width: 420px;
521
+ width: 90%;
522
+ text-align: center;
523
+ position: relative;
524
+ transform: translateY(20px);
525
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
526
+ box-shadow: 0 25px 50px rgba(0,0,0,0.1);
527
+ }
528
+
529
+ .modal.show .modal-content { transform: translateY(0); }
530
+
531
+ .modal-close {
532
+ position: absolute;
533
+ top: 20px; right: 25px;
534
+ font-size: 28px;
535
+ color: #94a3b8;
536
+ cursor: pointer;
537
+ transition: color 0.3s;
538
+ background: none;
539
+ border: none;
540
+ }
541
+
542
+ .modal-close:hover { color: #ef4444; }
543
+
544
+ .modal-emoji { font-size: 60px; margin-bottom: 20px; }
545
+ .modal-title { font-size: 24px; font-weight: 800; color: #1e293b; margin-bottom: 12px; }
546
+ .modal-desc { font-size: 15px; color: #475569; line-height: 1.7; font-weight: 500;}
547
+
548
+ @media (max-width: 1024px) {
549
+ .sidebar { transform: translateX(-100%); }
550
+ .sidebar.active { transform: translateX(0); }
551
+ .main-wrapper { margin-left: 0; }
552
+ .hamburger-btn { display: flex; }
553
+ .hero-banner { padding: 80px 30px 60px; margin: 0; border-radius: 0; border-bottom-left-radius: 30px; border-bottom-right-radius: 30px;}
554
+ .container { padding: 30px 20px; }
555
+ }
556
+
557
+ @media (max-width: 768px) {
558
+ .cards-grid { grid-template-columns: 1fr; }
559
+ .hero-title { font-size: 32px; }
560
+ .support-logos-container { flex-direction: column; gap: 30px; }
561
+ .support-divider { width: 80%; height: 1px; }
562
+ }
563
+ </style>
564
+ </head>
565
+ <body>
566
+
567
+ <button class="hamburger-btn" id="menu-toggle">
568
+ <svg viewBox="0 0 24 24">
569
+ <line x1="3" y1="12" x2="21" y2="12"></line>
570
+ <line x1="3" y1="6" x2="21" y2="6"></line>
571
+ <line x1="3" y1="18" x2="21" y2="18"></line>
572
+ </svg>
573
+ </button>
574
+
575
+ <aside class="sidebar" id="sidebar">
576
+ <div class="sidebar-header">
577
+ <div class="sidebar-logo">ConvNeXt</div>
578
+ </div>
579
+ <ul class="nav-menu">
580
+ <li class="nav-item">
581
+ <a href="#beranda" class="nav-link active" onclick="closeSidebar()">
582
+ <svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>
583
+ Beranda
584
+ </a>
585
+ </li>
586
+ <li class="nav-item">
587
+ <a href="#dukungan" class="nav-link" onclick="closeSidebar()">
588
+ <svg viewBox="0 0 24 24"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>
589
+ Dukungan
590
+ </a>
591
+ </li>
592
+ <li class="nav-item">
593
+ <a href="#dataset" class="nav-link" onclick="closeSidebar()">
594
+ <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="3" y1="9" x2="21" y2="9"></line><line x1="9" y1="21" x2="9" y2="9"></line></svg>
595
+ Data CIFAR-10
596
+ </a>
597
+ </li>
598
+ <li class="nav-item">
599
+ <a href="#deteksi" class="nav-link" onclick="closeSidebar()">
600
+ <svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
601
+ Deteksi Objek
602
+ </a>
603
+ </li>
604
+ </ul>
605
+ </aside>
606
+
607
+ <div class="main-wrapper">
608
+ <header class="hero-banner" id="beranda">
609
+ <h1 class="hero-title">Deployment ConvNeXt pada CIFAR10</h1>
610
+ <div class="hero-subtitle">Renzie (2206825630)</div>
611
+ </header>
612
+
613
+ <main class="container">
614
+ <div class="dashboard-grid">
615
+
616
+ <div class="card-panel" id="dukungan">
617
+ <h3 class="support-heading">PENELITIAN INI DIDUKUNG OLEH</h3>
618
+ <div class="support-logos-container">
619
+ <div class="support-item">
620
+ <img src="/static/DSC UI.png" class="support-img" alt="DSC">
621
+ <span class="support-text">Data Science Center, FMIPA UI</span>
622
+ </div>
623
+ <div class="support-divider"></div>
624
+ <div class="support-item">
625
+ <img src="/static/BrainAI.png" class="support-img" alt="BrainAI">
626
+ <span class="support-text">BrainAI Lab, Departemen Matematika FMIPA UI</span>
627
+ </div>
628
+ </div>
629
+ </div>
630
+
631
+ <div class="card-panel" id="dataset">
632
+ <div class="section-header">
633
+ <div class="section-icon">
634
+ <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
635
+ </div>
636
+ <div class="section-titles">
637
+ <h2>Dataset CIFAR-10</h2>
638
+ <p>Jelajahi 10 kelas objek visual yang menjadi acuan pengenalan citra.</p>
639
+ </div>
640
+ </div>
641
+ <div class="pills-grid">
642
+ <div class="pill" onclick="showClass('Pesawat', '✈️', 'Mencakup pesawat komersial, jet tempur, dan pesawat baling-baling.')">✈️ Pesawat</div>
643
+ <div class="pill" onclick="showClass('Mobil', '🚗', 'Kendaraan roda empat untuk penumpang seperti sedan dan SUV.')">🚗 Mobil</div>
644
+ <div class="pill" onclick="showClass('Burung', '🐦', 'Berbagai spesies burung liar maupun peliharaan.')">🐦 Burung</div>
645
+ <div class="pill" onclick="showClass('Kucing', '🐱', 'Mamalia famili Felidae, mencakup berbagai ras domestik.')">🐱 Kucing</div>
646
+ <div class="pill" onclick="showClass('Rusa', '🦌', 'Hewan mamalia pemamah biak dari famili Cervidae.')">🦌 Rusa</div>
647
+ <div class="pill" onclick="showClass('Anjing', '🐶', 'Mamalia karnivora yang telah didomestikasi.')">🐶 Anjing</div>
648
+ <div class="pill" onclick="showClass('Katak', '🐸', 'Amfibi tak berekor yang pandai melompat.')">🐸 Katak</div>
649
+ <div class="pill" onclick="showClass('Kuda', '🐴', 'Mamalia berkuku satu untuk berkuda atau pekerja beban.')">🐴 Kuda</div>
650
+ <div class="pill" onclick="showClass('Kapal', '🚢', 'Kendaraan air ukuran besar dan kecil.')">🚢 Kapal</div>
651
+ <div class="pill" onclick="showClass('Truk', '🚚', 'Kendaraan bermotor besar pengangkut barang.')">🚚 Truk</div>
652
+ </div>
653
+ </div>
654
+
655
+ <div class="card-panel" id="deteksi">
656
+ <div class="section-header">
657
+ <div class="section-icon">
658
+ <svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
659
+ </div>
660
+ <div class="section-titles">
661
+ <h2>Deteksi Objek ConvNeXt</h2>
662
+ <p>Unggah citra untuk dianalisis oleh arsitektur ConvNeXt-MLP dan ConvNeXt-KAN.</p>
663
+ </div>
664
+ </div>
665
+
666
+ <div class="upload-zone" id="drop-zone">
667
+ <div id="upload-prompt">
668
+ <div class="upload-icon">
669
+ <svg width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
670
+ </div>
671
+ <div class="upload-text">Klik atau seret gambar ke sini</div>
672
+ <div class="upload-subtext">Format didukung: JPG, PNG (Max 5MB)</div>
673
+ </div>
674
+
675
+ <div id="preview-wrapper">
676
+ <img id="preview-img" src="#" alt="Preview">
677
+ <button class="btn-outline" id="btn-change">Ganti Gambar</button>
678
+ </div>
679
+ <input type="file" id="file-input" accept="image/jpeg, image/png" hidden>
680
+ </div>
681
+
682
+ <div class="loader" id="loader">
683
+ <div class="spinner"></div>
684
+ <div style="color: #64748b; font-size: 15px; font-weight: 700;">Mengekstrak Fitur Citra...</div>
685
+ </div>
686
+
687
+ <div class="results-container" id="results">
688
+ <div class="cards-grid">
689
+ <div class="result-card">
690
+ <div class="rc-label">Model 1: ConvNeXt-MLP</div>
691
+ <div class="rc-value" id="mlp-name">-</div>
692
+ <div class="rc-conf">
693
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
694
+ <span id="mlp-conf">-</span>
695
+ </div>
696
+ <div class="rc-bars" id="mlp-probs"></div>
697
+ </div>
698
+
699
+ <div class="result-card">
700
+ <div class="rc-label">Model 2: ConvNeXt-KAN</div>
701
+ <div class="rc-value" id="kan-name">-</div>
702
+ <div class="rc-conf">
703
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
704
+ <span id="kan-conf">-</span>
705
+ </div>
706
+ <div class="rc-bars" id="kan-probs"></div>
707
+ </div>
708
+ </div>
709
+
710
+ <div class="interpretation-alert" id="inter-box">
711
+ <div class="alert-icon" id="inter-emoji"></div>
712
+ <div class="alert-content">
713
+ <strong id="inter-title"></strong>
714
+ <span id="inter-text"></span>
715
+ </div>
716
+ </div>
717
+ </div>
718
+
719
+ </div>
720
+ </div>
721
+ </main>
722
+ </div>
723
+
724
+ <div class="modal" id="infoModal">
725
+ <div class="modal-content">
726
+ <button class="modal-close" onclick="closeModal('infoModal')">&times;</button>
727
+ <div class="modal-emoji" id="m-emoji"></div>
728
+ <div class="modal-title" id="m-title"></div>
729
+ <div class="modal-desc" id="m-desc"></div>
730
+ </div>
731
+ </div>
732
+
733
+ <script>
734
+ const menuToggle = document.getElementById('menu-toggle');
735
+ const sidebar = document.getElementById('sidebar');
736
+
737
+ menuToggle.addEventListener('click', () => {
738
+ sidebar.classList.toggle('active');
739
+ });
740
+
741
+ function closeSidebar() {
742
+ if (window.innerWidth <= 1024) {
743
+ sidebar.classList.remove('active');
744
+ }
745
+ }
746
+
747
+ const navLinks = document.querySelectorAll('.nav-link');
748
+ navLinks.forEach(link => {
749
+ link.addEventListener('click', function() {
750
+ navLinks.forEach(l => l.classList.remove('active'));
751
+ this.classList.add('active');
752
+ });
753
+ });
754
+
755
+ const dropZone = document.getElementById('drop-zone');
756
+ const fileInput = document.getElementById('file-input');
757
+ const uploadPrompt = document.getElementById('upload-prompt');
758
+ const previewWrapper = document.getElementById('preview-wrapper');
759
+ const previewImg = document.getElementById('preview-img');
760
+ const btnChange = document.getElementById('btn-change');
761
+ const loader = document.getElementById('loader');
762
+ const results = document.getElementById('results');
763
+
764
+ dropZone.addEventListener('click', (e) => {
765
+ if (e.target !== btnChange) fileInput.click();
766
+ });
767
+
768
+ btnChange.addEventListener('click', (e) => {
769
+ e.stopPropagation();
770
+ fileInput.click();
771
+ });
772
+
773
+ fileInput.addEventListener('change', (e) => {
774
+ if (e.target.files[0]) handleFile(e.target.files[0]);
775
+ });
776
+
777
+ dropZone.addEventListener('dragover', (e) => {
778
+ e.preventDefault();
779
+ dropZone.style.borderColor = '#4f46e5';
780
+ dropZone.style.backgroundColor = '#f5f3ff';
781
+ });
782
+
783
+ dropZone.addEventListener('dragleave', () => {
784
+ dropZone.style.borderColor = '#cbd5e0';
785
+ dropZone.style.backgroundColor = '#f8fafc';
786
+ });
787
+
788
+ dropZone.addEventListener('drop', (e) => {
789
+ e.preventDefault();
790
+ dropZone.style.borderColor = '#cbd5e0';
791
+ dropZone.style.backgroundColor = '#f8fafc';
792
+ if (e.dataTransfer.files.length) {
793
+ fileInput.files = e.dataTransfer.files;
794
+ handleFile(e.dataTransfer.files[0]);
795
+ }
796
+ });
797
+
798
+ function handleFile(file) {
799
+ if (file.type === 'image/jpeg' || file.type === 'image/png') {
800
+ const reader = new FileReader();
801
+ reader.onload = (e) => {
802
+ previewImg.src = e.target.result;
803
+ uploadPrompt.style.display = 'none';
804
+ previewWrapper.style.display = 'flex';
805
+ dropZone.style.borderStyle = 'solid';
806
+ dropZone.style.padding = '30px 20px';
807
+ processUpload(file);
808
+ };
809
+ reader.readAsDataURL(file);
810
+ }
811
+ }
812
+
813
+ function translateClass(enClass) {
814
+ const mapId = {
815
+ 'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing',
816
+ 'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda',
817
+ 'ship': 'Kapal', 'truck': 'Truk'
818
+ };
819
+ return mapId[enClass] || enClass;
820
+ }
821
+
822
+ function renderProbs(containerId, probs) {
823
+ const container = document.getElementById(containerId);
824
+ container.innerHTML = '';
825
+
826
+ probs.slice(0, 5).forEach(p => {
827
+ let color = '#4f46e5';
828
+ if(p.confidence > 70) color = '#10b981';
829
+ else if(p.confidence < 15) color = '#94a3b8';
830
+
831
+ container.innerHTML += `
832
+ <div class="bar-item">
833
+ <div class="bar-label">${translateClass(p.class)}</div>
834
+ <div class="bar-bg"><div class="bar-fill" style="width: ${p.confidence}%; background: ${color}"></div></div>
835
+ <div class="bar-val">${p.confidence}%</div>
836
+ </div>
837
+ `;
838
+ });
839
+ }
840
+
841
+ function processUpload(file) {
842
+ results.style.display = 'none';
843
+ loader.style.display = 'block';
844
+
845
+ const formData = new FormData();
846
+ formData.append('file', file);
847
+
848
+ fetch('/api/predict', { method: 'POST', body: formData })
849
+ .then(res => res.json())
850
+ .then(data => {
851
+ loader.style.display = 'none';
852
+
853
+ if (data.status === 'error') {
854
+ alert("Kesalahan server.");
855
+ return;
856
+ }
857
+
858
+ results.style.display = 'block';
859
+
860
+ const c1 = translateClass(data.model_1.class);
861
+ const c2 = translateClass(data.model_2.class);
862
+
863
+ document.getElementById('mlp-name').innerText = c1;
864
+ document.getElementById('mlp-conf').innerText = data.model_1.confidence + '%';
865
+ document.getElementById('kan-name').innerText = c2;
866
+ document.getElementById('kan-conf').innerText = data.model_2.confidence + '%';
867
+
868
+ renderProbs('mlp-probs', data.model_1.all_probs);
869
+ renderProbs('kan-probs', data.model_2.all_probs);
870
+
871
+ const interBox = document.getElementById('inter-box');
872
+ const emoji = document.getElementById('inter-emoji');
873
+ const title = document.getElementById('inter-title');
874
+ const text = document.getElementById('inter-text');
875
+
876
+ if (data.is_outlier) {
877
+ interBox.style.background = '#fffbeb';
878
+ interBox.style.border = '1px solid #fde68a';
879
+ interBox.style.color = '#b45309';
880
+ title.innerText = 'Unidentified Object';
881
+ text.innerText = 'Confidence score for this image is low across both models, indicating it may not belong to any known class in the CIFAR-10 dataset.';
882
+ } else if(c1 === c2) {
883
+ interBox.style.background = '#f0fdf4';
884
+ interBox.style.border = '1px solid #bbf7d0';
885
+ interBox.style.color = '#15803d';
886
+ title.innerText = 'Consistent Prediction';
887
+ text.innerText = `Both architectures confidently classify the image as ${c1}.`;
888
+ } else {
889
+ interBox.style.background = '#fef2f2';
890
+ interBox.style.border = '1px solid #fecaca';
891
+ interBox.style.color = '#b91c1c';
892
+ title.innerText = 'Inconsistent Prediction';
893
+ text.innerText = `Model ConvNeXt-MLP sees the object as ${c1}, while ConvNeXt-KAN is inclined towards ${c2}.`;
894
+ }
895
+
896
+ setTimeout(() => {
897
+ results.scrollIntoView({ behavior: 'smooth', block: 'start' });
898
+ }, 100);
899
+ })
900
+ .catch(err => {
901
+ loader.style.display = 'none';
902
+ alert("Gagal koneksi ke server.");
903
+ });
904
+ }
905
+
906
+ function showClass(title, emoji, desc) {
907
+ document.getElementById('m-title').innerText = title;
908
+ document.getElementById('m-emoji').innerText = emoji;
909
+ document.getElementById('m-desc').innerText = desc;
910
+
911
+ const modal = document.getElementById('infoModal');
912
+ modal.style.display = 'flex';
913
+ setTimeout(() => modal.classList.add('show'), 10);
914
+ }
915
+
916
+ function closeModal(id) {
917
+ const modal = document.getElementById(id);
918
+ modal.classList.remove('show');
919
+ setTimeout(() => modal.style.display = 'none', 300);
920
+ }
921
+
922
+ window.onclick = function(event) {
923
+ if (event.target.classList.contains('modal')) closeModal(event.target.id);
924
+ }
925
+ </script>
926
+ </body>
927
+ </html>
weights/convnext_kan_cifar10.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7e53e3ea740d68a17d49ef7943e8fe55a1eb9f5d96d68e8348fe819b8682eb6f
3
+ size 137881637
weights/convnext_mlp_cifar10.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9098c7362c43b1c55fa39b51f5250b02feda11d6a61a485a2977466a9c4c92ae
3
+ size 114020323
weights/resnet_kan_cifar10_run1.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:177e2529170a03a07ea951ed06d57091bcecc08b4dafc06b78e0a20e4c9c9486
3
+ size 147162506
weights/tesresnet_mlp_cifar10_run1.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:faaf2c4e7327148a6cf95c0fa01216cbf592cf786087691374c19c155b8763d4
3
+ size 99648044