libz 0.4__tar.gz → 0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {libz-0.4 → libz-0.6}/PKG-INFO +1 -1
- {libz-0.4 → libz-0.6}/libz/DLG/__init__.py +15 -15
- libz-0.6/libz/DLG/prac1.py +137 -0
- libz-0.6/libz/DLG/prac2.py +172 -0
- libz-0.6/libz/DLG/prac3.py +183 -0
- libz-0.6/libz/DLG/prac4.py +164 -0
- libz-0.6/libz/DLG/prac5.py +269 -0
- libz-0.6/libz/DLG/prac6.py +253 -0
- libz-0.6/libz/DLG/prac7.py +194 -0
- libz-0.6/libz/DLG/prac8.py +231 -0
- {libz-0.4 → libz-0.6}/libz/DLG/prac9.py +46 -7
- {libz-0.4 → libz-0.6}/libz/__init__.py +1 -1
- {libz-0.4 → libz-0.6}/libz.egg-info/PKG-INFO +1 -1
- {libz-0.4 → libz-0.6}/pyproject.toml +1 -1
- libz-0.4/libz/DLG/prac1.py +0 -71
- libz-0.4/libz/DLG/prac2.py +0 -108
- libz-0.4/libz/DLG/prac3.py +0 -108
- libz-0.4/libz/DLG/prac4.py +0 -105
- libz-0.4/libz/DLG/prac5.py +0 -99
- libz-0.4/libz/DLG/prac6.py +0 -119
- libz-0.4/libz/DLG/prac7.py +0 -110
- libz-0.4/libz/DLG/prac8.py +0 -141
- {libz-0.4 → libz-0.6}/README.md +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/__init__.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac1.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac10.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac2.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac3.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac4.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac5.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac6.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac7.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac8.py +0 -0
- {libz-0.4 → libz-0.6}/libz/AWT/prac9.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/__init__.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac1.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac10.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac2.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac3.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac4.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac5.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac6.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac7.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac8.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BDA/prac9.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/__init__.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac1.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac10.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac2.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac3.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac4.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac5.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac6.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac7.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac8.py +0 -0
- {libz-0.4 → libz-0.6}/libz/BIO/prac9.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/__init__.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac1.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac2.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac3.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac4.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac5.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac6.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac7.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac8.py +0 -0
- {libz-0.4 → libz-0.6}/libz/DL/prac9.py +0 -0
- {libz-0.4 → libz-0.6}/libz/_core.py +0 -0
- {libz-0.4 → libz-0.6}/libz.egg-info/SOURCES.txt +0 -0
- {libz-0.4 → libz-0.6}/libz.egg-info/dependency_links.txt +0 -0
- {libz-0.4 → libz-0.6}/libz.egg-info/top_level.txt +0 -0
- {libz-0.4 → libz-0.6}/setup.cfg +0 -0
{libz-0.4 → libz-0.6}/PKG-INFO
RENAMED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
from libz._core import register
|
|
2
|
-
|
|
3
|
-
TITLES = {
|
|
4
|
-
1: "Deep feed forward (any CSV)",
|
|
5
|
-
2: "Regularization and optimization (any CSV)",
|
|
6
|
-
3: "Semi-supervised learning (any text CSV)",
|
|
7
|
-
4: "Multitasking (any CSV)",
|
|
8
|
-
5: "CNN-based handwriting recognition (any image folder / MNIST)",
|
|
9
|
-
6: "CNN - unsupervised feature learning (any image folder / digits)",
|
|
10
|
-
7: "Recurrent neural network (any text CSV / IMDB)",
|
|
11
|
-
8: "Recursive neural network (tree) (any text CSV / sample)",
|
|
12
|
-
9: "PCA vs linear autoencoder (any numeric CSV / synthetic)",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
register(__name__, globals(), TITLES)
|
|
1
|
+
from libz._core import register
|
|
2
|
+
|
|
3
|
+
TITLES = {
|
|
4
|
+
1: "Deep feed forward (any CSV)",
|
|
5
|
+
2: "Regularization and optimization (any CSV)",
|
|
6
|
+
3: "Semi-supervised learning (any text CSV)",
|
|
7
|
+
4: "Multitasking (any CSV)",
|
|
8
|
+
5: "CNN-based handwriting recognition (any image folder / CSV / MNIST)",
|
|
9
|
+
6: "CNN - unsupervised feature learning (any image folder / CSV / digits)",
|
|
10
|
+
7: "Recurrent neural network (any text CSV / IMDB)",
|
|
11
|
+
8: "Recursive neural network (tree) (any text CSV / sample)",
|
|
12
|
+
9: "PCA vs linear autoencoder (any numeric CSV / synthetic)",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
register(__name__, globals(), TITLES)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Deep Feed Forward Neural Network - works with any CSV classification dataset
|
|
2
|
+
# The target column and the feature columns are picked automatically.
|
|
3
|
+
# If accuracy is low, you are asked which columns to use and the model is retrained.
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from sklearn.model_selection import train_test_split
|
|
7
|
+
from sklearn.preprocessing import StandardScaler, LabelEncoder
|
|
8
|
+
from sklearn.metrics import classification_report, confusion_matrix
|
|
9
|
+
from tensorflow.keras.models import Sequential
|
|
10
|
+
from tensorflow.keras.layers import Input, Dense, Dropout
|
|
11
|
+
from tensorflow.keras.callbacks import EarlyStopping
|
|
12
|
+
|
|
13
|
+
MIN_ACCURACY = 0.70 # below this you get to choose the columns yourself
|
|
14
|
+
TARGET_NAMES = {"target", "label", "labels", "class", "output", "outcome", "result", "y"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------- helpers ----------
|
|
18
|
+
def read_csv(path):
|
|
19
|
+
"""Read a CSV with any separator (, ; tab |) and encoding."""
|
|
20
|
+
with open(path, encoding="latin-1") as f:
|
|
21
|
+
first_line = f.readline()
|
|
22
|
+
sep = max([",", ";", "\t", "|"], key=first_line.count)
|
|
23
|
+
try:
|
|
24
|
+
df = pd.read_csv(path, sep=sep)
|
|
25
|
+
except UnicodeDecodeError:
|
|
26
|
+
df = pd.read_csv(path, sep=sep, encoding="latin-1")
|
|
27
|
+
df = df.loc[:, ~df.columns.astype(str).str.startswith("Unnamed")]
|
|
28
|
+
return df.dropna(axis=1, how="all")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_id(col):
|
|
32
|
+
name = str(col.name).lower()
|
|
33
|
+
return (name in ("id", "index") or name.endswith("_id")
|
|
34
|
+
or (pd.api.types.is_integer_dtype(col) and col.nunique() == len(col)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def guess_target(df):
|
|
38
|
+
for c in df.columns:
|
|
39
|
+
if str(c).lower() in TARGET_NAMES:
|
|
40
|
+
return c
|
|
41
|
+
return [c for c in df.columns if not is_id(df[c]) and df[c].nunique() >= 2][-1]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def make_labels(col):
|
|
45
|
+
"""Numeric columns with many values become High / Low (split at the median)."""
|
|
46
|
+
if pd.api.types.is_numeric_dtype(col) and col.nunique() > 10:
|
|
47
|
+
col = pd.Series(np.where(col >= col.median(), "High", "Low"), index=col.index)
|
|
48
|
+
encoder = LabelEncoder()
|
|
49
|
+
return encoder.fit_transform(col.astype(str)), [str(c) for c in encoder.classes_]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def make_features(X):
|
|
53
|
+
"""Drop ID / constant / free-text columns, fill missing values, one-hot encode."""
|
|
54
|
+
keep = [c for c in X.columns
|
|
55
|
+
if not is_id(X[c]) and X[c].nunique() > 1
|
|
56
|
+
and (pd.api.types.is_numeric_dtype(X[c]) or X[c].nunique() <= 50)]
|
|
57
|
+
X = X[keep].copy()
|
|
58
|
+
for c in X.columns:
|
|
59
|
+
if pd.api.types.is_numeric_dtype(X[c]):
|
|
60
|
+
X[c] = X[c].fillna(X[c].median())
|
|
61
|
+
else:
|
|
62
|
+
X[c] = X[c].fillna(X[c].mode()[0])
|
|
63
|
+
return pd.get_dummies(X, drop_first=True, dtype=int), keep
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ask_columns(df, target, dropped):
|
|
67
|
+
print(f"\nAccuracy is below {MIN_ACCURACY:.0%}. You can choose the columns yourself.")
|
|
68
|
+
print("Columns:", list(df.columns))
|
|
69
|
+
new_target = input(f"Target column (Enter to keep '{target}'): ").strip() or target
|
|
70
|
+
if new_target not in df.columns:
|
|
71
|
+
print(f"'{new_target}' not found, keeping '{target}'.")
|
|
72
|
+
new_target = target
|
|
73
|
+
drop = [c.strip() for c in input("Columns to leave out, comma separated (Enter for none): ").split(",")]
|
|
74
|
+
drop = [c for c in drop if c in df.columns and c != new_target]
|
|
75
|
+
changed = new_target != target or set(drop) != set(dropped)
|
|
76
|
+
return new_target, drop, changed
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------- 1. Load dataset ----------
|
|
80
|
+
path = input("Enter CSV file path: ").strip().strip('"')
|
|
81
|
+
df = read_csv(path)
|
|
82
|
+
print(df.head())
|
|
83
|
+
print("Shape:", df.shape)
|
|
84
|
+
|
|
85
|
+
target = guess_target(df)
|
|
86
|
+
dropped = []
|
|
87
|
+
|
|
88
|
+
# ---------- 2. Train (repeats if accuracy is low and you choose other columns) ----------
|
|
89
|
+
while True:
|
|
90
|
+
data = df.dropna(subset=[target]).drop_duplicates()
|
|
91
|
+
y, class_names = make_labels(data[target])
|
|
92
|
+
X, used = make_features(data.drop(columns=[target] + dropped))
|
|
93
|
+
print(f"\nTarget column: {target} -> classes {class_names}")
|
|
94
|
+
print("Feature columns used:", used)
|
|
95
|
+
|
|
96
|
+
stratify = y if np.bincount(y).min() >= 2 else None
|
|
97
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
98
|
+
X, y, test_size=0.2, random_state=42, stratify=stratify
|
|
99
|
+
)
|
|
100
|
+
scaler = StandardScaler()
|
|
101
|
+
X_train = scaler.fit_transform(X_train)
|
|
102
|
+
X_test = scaler.transform(X_test)
|
|
103
|
+
|
|
104
|
+
# Deep feed forward network
|
|
105
|
+
model = Sequential([
|
|
106
|
+
Input(shape=(X_train.shape[1],)),
|
|
107
|
+
Dense(128, activation="relu"),
|
|
108
|
+
Dropout(0.3),
|
|
109
|
+
Dense(64, activation="relu"),
|
|
110
|
+
Dropout(0.2),
|
|
111
|
+
Dense(32, activation="relu"),
|
|
112
|
+
Dense(len(class_names), activation="softmax"),
|
|
113
|
+
])
|
|
114
|
+
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
115
|
+
model.fit(X_train, y_train, epochs=50, batch_size=32, validation_split=0.2, verbose=0,
|
|
116
|
+
callbacks=[EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True)])
|
|
117
|
+
|
|
118
|
+
test_acc = model.evaluate(X_test, y_test, verbose=0)[1]
|
|
119
|
+
print(f"Testing Accuracy: {test_acc * 100:.2f}%")
|
|
120
|
+
if test_acc >= MIN_ACCURACY:
|
|
121
|
+
break
|
|
122
|
+
target, dropped, changed = ask_columns(df, target, dropped)
|
|
123
|
+
if not changed:
|
|
124
|
+
break
|
|
125
|
+
|
|
126
|
+
# ---------- 3. Final results ----------
|
|
127
|
+
model.summary()
|
|
128
|
+
train_acc = model.evaluate(X_train, y_train, verbose=0)[1]
|
|
129
|
+
y_pred = model.predict(X_test, verbose=0).argmax(axis=1)
|
|
130
|
+
|
|
131
|
+
print(f"\nTraining Accuracy: {train_acc * 100:.2f}%")
|
|
132
|
+
print(f"Testing Accuracy: {test_acc * 100:.2f}%")
|
|
133
|
+
print("\nClassification Report:")
|
|
134
|
+
print(classification_report(y_test, y_pred, labels=range(len(class_names)),
|
|
135
|
+
target_names=class_names, zero_division=0))
|
|
136
|
+
print("Confusion Matrix:")
|
|
137
|
+
print(confusion_matrix(y_test, y_pred))
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Regularization and Optimization - works with any CSV classification dataset
|
|
2
|
+
# Compares: no regularization vs L2 + Dropout + Early Stopping,
|
|
3
|
+
# and the optimizers Adam, SGD and RMSprop.
|
|
4
|
+
# The target column and the feature columns are picked automatically.
|
|
5
|
+
# If accuracy is low, you are asked which columns to use and the models are retrained.
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
from sklearn.model_selection import train_test_split
|
|
10
|
+
from sklearn.preprocessing import StandardScaler, LabelEncoder
|
|
11
|
+
from sklearn.metrics import classification_report, confusion_matrix
|
|
12
|
+
from tensorflow.keras.models import Sequential
|
|
13
|
+
from tensorflow.keras.layers import Input, Dense, Dropout
|
|
14
|
+
from tensorflow.keras.regularizers import l2
|
|
15
|
+
from tensorflow.keras.callbacks import EarlyStopping
|
|
16
|
+
|
|
17
|
+
MIN_ACCURACY = 0.70 # below this you get to choose the columns yourself
|
|
18
|
+
TARGET_NAMES = {"target", "label", "labels", "class", "output", "outcome", "result", "y"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ---------- helpers ----------
|
|
22
|
+
def read_csv(path):
|
|
23
|
+
"""Read a CSV with any separator (, ; tab |) and encoding."""
|
|
24
|
+
with open(path, encoding="latin-1") as f:
|
|
25
|
+
first_line = f.readline()
|
|
26
|
+
sep = max([",", ";", "\t", "|"], key=first_line.count)
|
|
27
|
+
try:
|
|
28
|
+
df = pd.read_csv(path, sep=sep)
|
|
29
|
+
except UnicodeDecodeError:
|
|
30
|
+
df = pd.read_csv(path, sep=sep, encoding="latin-1")
|
|
31
|
+
df = df.loc[:, ~df.columns.astype(str).str.startswith("Unnamed")]
|
|
32
|
+
return df.dropna(axis=1, how="all")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_id(col):
|
|
36
|
+
name = str(col.name).lower()
|
|
37
|
+
return (name in ("id", "index") or name.endswith("_id")
|
|
38
|
+
or (pd.api.types.is_integer_dtype(col) and col.nunique() == len(col)))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def guess_target(df):
|
|
42
|
+
for c in df.columns:
|
|
43
|
+
if str(c).lower() in TARGET_NAMES:
|
|
44
|
+
return c
|
|
45
|
+
return [c for c in df.columns if not is_id(df[c]) and df[c].nunique() >= 2][-1]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def make_labels(col):
|
|
49
|
+
"""Numeric columns with many values become High / Low (split at the median)."""
|
|
50
|
+
if pd.api.types.is_numeric_dtype(col) and col.nunique() > 10:
|
|
51
|
+
col = pd.Series(np.where(col >= col.median(), "High", "Low"), index=col.index)
|
|
52
|
+
encoder = LabelEncoder()
|
|
53
|
+
return encoder.fit_transform(col.astype(str)), [str(c) for c in encoder.classes_]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def make_features(X):
|
|
57
|
+
"""Drop ID / constant / free-text columns, fill missing values, one-hot encode."""
|
|
58
|
+
keep = [c for c in X.columns
|
|
59
|
+
if not is_id(X[c]) and X[c].nunique() > 1
|
|
60
|
+
and (pd.api.types.is_numeric_dtype(X[c]) or X[c].nunique() <= 50)]
|
|
61
|
+
X = X[keep].copy()
|
|
62
|
+
for c in X.columns:
|
|
63
|
+
if pd.api.types.is_numeric_dtype(X[c]):
|
|
64
|
+
X[c] = X[c].fillna(X[c].median())
|
|
65
|
+
else:
|
|
66
|
+
X[c] = X[c].fillna(X[c].mode()[0])
|
|
67
|
+
return pd.get_dummies(X, drop_first=True, dtype=int), keep
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def ask_columns(df, target, dropped):
|
|
71
|
+
print(f"\nBest accuracy is below {MIN_ACCURACY:.0%}. You can choose the columns yourself.")
|
|
72
|
+
print("Columns:", list(df.columns))
|
|
73
|
+
new_target = input(f"Target column (Enter to keep '{target}'): ").strip() or target
|
|
74
|
+
if new_target not in df.columns:
|
|
75
|
+
print(f"'{new_target}' not found, keeping '{target}'.")
|
|
76
|
+
new_target = target
|
|
77
|
+
drop = [c.strip() for c in input("Columns to leave out, comma separated (Enter for none): ").split(",")]
|
|
78
|
+
drop = [c for c in drop if c in df.columns and c != new_target]
|
|
79
|
+
changed = new_target != target or set(drop) != set(dropped)
|
|
80
|
+
return new_target, drop, changed
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_model(n_inputs, n_classes, regularize, optimizer):
|
|
84
|
+
reg = l2(0.001) if regularize else None
|
|
85
|
+
drop = 0.3 if regularize else 0.0
|
|
86
|
+
model = Sequential([
|
|
87
|
+
Input(shape=(n_inputs,)),
|
|
88
|
+
Dense(128, activation="relu", kernel_regularizer=reg),
|
|
89
|
+
Dropout(drop),
|
|
90
|
+
Dense(64, activation="relu", kernel_regularizer=reg),
|
|
91
|
+
Dropout(drop),
|
|
92
|
+
Dense(n_classes, activation="softmax"),
|
|
93
|
+
])
|
|
94
|
+
model.compile(optimizer=optimizer, loss="sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
95
|
+
return model
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
EXPERIMENTS = [
|
|
99
|
+
("No regularization (Adam)", False, "adam"),
|
|
100
|
+
("L2 + Dropout + EarlyStop (Adam)", True, "adam"),
|
|
101
|
+
("L2 + Dropout + EarlyStop (SGD)", True, "sgd"),
|
|
102
|
+
("L2 + Dropout + EarlyStop (RMSprop)", True, "rmsprop"),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
# ---------- 1. Load dataset ----------
|
|
106
|
+
path = input("Enter CSV file path: ").strip().strip('"')
|
|
107
|
+
df = read_csv(path)
|
|
108
|
+
print(df.head())
|
|
109
|
+
print("Shape:", df.shape)
|
|
110
|
+
|
|
111
|
+
target = guess_target(df)
|
|
112
|
+
dropped = []
|
|
113
|
+
|
|
114
|
+
# ---------- 2. Train all experiments (repeats if accuracy is low and you choose other columns) ----------
|
|
115
|
+
while True:
|
|
116
|
+
data = df.dropna(subset=[target]).drop_duplicates()
|
|
117
|
+
y, class_names = make_labels(data[target])
|
|
118
|
+
X, used = make_features(data.drop(columns=[target] + dropped))
|
|
119
|
+
print(f"\nTarget column: {target} -> classes {class_names}")
|
|
120
|
+
print("Feature columns used:", used)
|
|
121
|
+
|
|
122
|
+
stratify = y if np.bincount(y).min() >= 2 else None
|
|
123
|
+
X_train, X_test, y_train, y_test = train_test_split(
|
|
124
|
+
X, y, test_size=0.2, random_state=42, stratify=stratify
|
|
125
|
+
)
|
|
126
|
+
scaler = StandardScaler()
|
|
127
|
+
X_train = scaler.fit_transform(X_train)
|
|
128
|
+
X_test = scaler.transform(X_test)
|
|
129
|
+
|
|
130
|
+
results, histories, models = [], {}, {}
|
|
131
|
+
for name, regularize, optimizer in EXPERIMENTS:
|
|
132
|
+
print("Training:", name)
|
|
133
|
+
model = build_model(X_train.shape[1], len(class_names), regularize, optimizer)
|
|
134
|
+
callbacks = [EarlyStopping(monitor="val_loss", patience=5, restore_best_weights=True)] if regularize else []
|
|
135
|
+
history = model.fit(X_train, y_train, epochs=50, batch_size=32,
|
|
136
|
+
validation_split=0.2, callbacks=callbacks, verbose=0)
|
|
137
|
+
train_acc = model.evaluate(X_train, y_train, verbose=0)[1]
|
|
138
|
+
test_acc = model.evaluate(X_test, y_test, verbose=0)[1]
|
|
139
|
+
results.append([name, train_acc * 100, test_acc * 100, len(history.history["loss"])])
|
|
140
|
+
histories[name] = history
|
|
141
|
+
models[name] = model
|
|
142
|
+
|
|
143
|
+
results_df = pd.DataFrame(results, columns=["Model", "Train Acc", "Test Acc", "Epochs"])
|
|
144
|
+
best_acc = results_df["Test Acc"].max() / 100
|
|
145
|
+
print(f"Best Test Accuracy: {best_acc * 100:.2f}%")
|
|
146
|
+
if best_acc >= MIN_ACCURACY:
|
|
147
|
+
break
|
|
148
|
+
target, dropped, changed = ask_columns(df, target, dropped)
|
|
149
|
+
if not changed:
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
# ---------- 3. Compare results ----------
|
|
153
|
+
print("\nComparison:")
|
|
154
|
+
print(results_df.round(2).to_string(index=False))
|
|
155
|
+
|
|
156
|
+
plt.figure(figsize=(9, 5))
|
|
157
|
+
for name, history in histories.items():
|
|
158
|
+
plt.plot(history.history["val_loss"], label=name)
|
|
159
|
+
plt.title("Validation Loss: Regularization and Optimizers")
|
|
160
|
+
plt.xlabel("Epoch")
|
|
161
|
+
plt.ylabel("Validation Loss")
|
|
162
|
+
plt.legend()
|
|
163
|
+
plt.grid(True)
|
|
164
|
+
plt.show()
|
|
165
|
+
|
|
166
|
+
best = results_df.sort_values("Test Acc", ascending=False).iloc[0]["Model"]
|
|
167
|
+
y_pred = models[best].predict(X_test, verbose=0).argmax(axis=1)
|
|
168
|
+
print(f"\nBest model: {best}")
|
|
169
|
+
print(classification_report(y_test, y_pred, labels=range(len(class_names)),
|
|
170
|
+
target_names=class_names, zero_division=0))
|
|
171
|
+
print("Confusion Matrix:")
|
|
172
|
+
print(confusion_matrix(y_test, y_pred))
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Semi-Supervised Learning (self-training with pseudo-labels) - works with any text CSV
|
|
2
|
+
# Only part of the training data keeps its labels. The model labels the rest itself
|
|
3
|
+
# (only predictions it is confident about) and retrains on the bigger dataset.
|
|
4
|
+
# The text column (longest average text) and the label column are picked automatically.
|
|
5
|
+
# If the semi-supervised accuracy is low, you are asked which columns to use and it retrains.
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pandas as pd
|
|
8
|
+
from sklearn.model_selection import train_test_split
|
|
9
|
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
|
10
|
+
from sklearn.preprocessing import LabelEncoder
|
|
11
|
+
from sklearn.metrics import classification_report, confusion_matrix
|
|
12
|
+
from tensorflow.keras.models import Sequential
|
|
13
|
+
from tensorflow.keras.layers import Input, Dense, Dropout
|
|
14
|
+
from tensorflow.keras.callbacks import EarlyStopping
|
|
15
|
+
|
|
16
|
+
MIN_ACCURACY = 0.70 # below this you get to choose the columns yourself
|
|
17
|
+
TARGET_NAMES = {"target", "label", "labels", "class", "output", "outcome", "result", "y"}
|
|
18
|
+
LABELED_RATIO = 0.3 # fraction of the training data that keeps its labels
|
|
19
|
+
THRESHOLD = 0.90 # confidence needed to accept a pseudo-label
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---------- helpers ----------
|
|
23
|
+
def read_csv(path):
|
|
24
|
+
"""Read a CSV with any separator (, ; tab |) and encoding."""
|
|
25
|
+
with open(path, encoding="latin-1") as f:
|
|
26
|
+
first_line = f.readline()
|
|
27
|
+
sep = max([",", ";", "\t", "|"], key=first_line.count)
|
|
28
|
+
try:
|
|
29
|
+
df = pd.read_csv(path, sep=sep)
|
|
30
|
+
except UnicodeDecodeError:
|
|
31
|
+
df = pd.read_csv(path, sep=sep, encoding="latin-1")
|
|
32
|
+
df = df.loc[:, ~df.columns.astype(str).str.startswith("Unnamed")]
|
|
33
|
+
return df.dropna(axis=1, how="all")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def pick_column(df, prompt):
|
|
37
|
+
while True:
|
|
38
|
+
col = input(prompt).strip()
|
|
39
|
+
if col in df.columns:
|
|
40
|
+
return col
|
|
41
|
+
print(f"'{col}' not found. Columns: {list(df.columns)}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def guess_text_label(df):
|
|
45
|
+
"""Text = non-numeric column with the longest average text; label = a target-like column."""
|
|
46
|
+
text_cols = [c for c in df.columns if not pd.api.types.is_numeric_dtype(df[c])]
|
|
47
|
+
if not text_cols or len(df.columns) < 2:
|
|
48
|
+
print("Could not find a text column and a label column automatically.")
|
|
49
|
+
print("Columns:", list(df.columns))
|
|
50
|
+
text_col = pick_column(df, "Text column: ")
|
|
51
|
+
label_col = pick_column(df, "Label column: ")
|
|
52
|
+
return text_col, label_col
|
|
53
|
+
|
|
54
|
+
text_col = max(text_cols, key=lambda c: df[c].dropna().astype(str).str.len().mean())
|
|
55
|
+
others = [c for c in df.columns if c != text_col]
|
|
56
|
+
label_col = next((c for c in others if str(c).lower() in TARGET_NAMES), None)
|
|
57
|
+
if label_col is None:
|
|
58
|
+
label_col = next((c for c in others if 2 <= df[c].nunique() <= 20), others[0])
|
|
59
|
+
return text_col, label_col
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ask_columns(df, text_col, label_col):
|
|
63
|
+
print(f"\nAccuracy is below {MIN_ACCURACY:.0%}. You can choose the columns yourself.")
|
|
64
|
+
print("Columns:", list(df.columns))
|
|
65
|
+
new_text = input(f"Text column (Enter to keep '{text_col}'): ").strip() or text_col
|
|
66
|
+
if new_text not in df.columns:
|
|
67
|
+
print(f"'{new_text}' not found, keeping '{text_col}'.")
|
|
68
|
+
new_text = text_col
|
|
69
|
+
new_label = input(f"Label column (Enter to keep '{label_col}'): ").strip() or label_col
|
|
70
|
+
if new_label not in df.columns:
|
|
71
|
+
print(f"'{new_label}' not found, keeping '{label_col}'.")
|
|
72
|
+
new_label = label_col
|
|
73
|
+
if new_text == new_label:
|
|
74
|
+
print("Text and label columns must be different, keeping the previous choice.")
|
|
75
|
+
new_text, new_label = text_col, label_col
|
|
76
|
+
changed = new_text != text_col or new_label != label_col
|
|
77
|
+
return new_text, new_label, changed
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def train_model(X, y, n_classes):
|
|
81
|
+
model = Sequential([
|
|
82
|
+
Input(shape=(X.shape[1],)),
|
|
83
|
+
Dense(64, activation="relu"),
|
|
84
|
+
Dropout(0.3),
|
|
85
|
+
Dense(32, activation="relu"),
|
|
86
|
+
Dropout(0.3),
|
|
87
|
+
Dense(n_classes, activation="softmax"),
|
|
88
|
+
])
|
|
89
|
+
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
|
|
90
|
+
# Shuffle first: validation_split takes the LAST 20%, which would otherwise be only pseudo-labels
|
|
91
|
+
order = np.random.permutation(len(X))
|
|
92
|
+
X, y = X[order], y[order]
|
|
93
|
+
model.fit(X, y, epochs=20, batch_size=32, validation_split=0.2, verbose=0,
|
|
94
|
+
callbacks=[EarlyStopping(monitor="val_loss", patience=3, restore_best_weights=True)])
|
|
95
|
+
return model
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ---------- 1. Load dataset ----------
|
|
99
|
+
path = input("Enter CSV file path: ").strip().strip('"')
|
|
100
|
+
df = read_csv(path)
|
|
101
|
+
print(df.head())
|
|
102
|
+
print("Shape:", df.shape)
|
|
103
|
+
|
|
104
|
+
text_col, label_col = guess_text_label(df)
|
|
105
|
+
|
|
106
|
+
# ---------- 2. Train (repeats if accuracy is low and you choose other columns) ----------
|
|
107
|
+
while True:
|
|
108
|
+
# Clean and encode
|
|
109
|
+
data = df[[text_col, label_col]].dropna().drop_duplicates()
|
|
110
|
+
encoder = LabelEncoder()
|
|
111
|
+
y = encoder.fit_transform(data[label_col].astype(str))
|
|
112
|
+
class_names = [str(c) for c in encoder.classes_]
|
|
113
|
+
n_classes = len(class_names)
|
|
114
|
+
print(f"\nText column: {text_col} | Label column: {label_col} -> classes {class_names}")
|
|
115
|
+
print(data[label_col].value_counts())
|
|
116
|
+
|
|
117
|
+
stratify = y if np.bincount(y).min() >= 2 else None
|
|
118
|
+
X_train_text, X_test_text, y_train, y_test = train_test_split(
|
|
119
|
+
data[text_col].astype(str), y, test_size=0.2, random_state=42, stratify=stratify
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# TF-IDF features
|
|
123
|
+
vectorizer = TfidfVectorizer(max_features=5000, stop_words="english")
|
|
124
|
+
X_train = vectorizer.fit_transform(X_train_text).toarray().astype("float32")
|
|
125
|
+
X_test = vectorizer.transform(X_test_text).toarray().astype("float32")
|
|
126
|
+
|
|
127
|
+
# Split training data into labelled and unlabelled parts
|
|
128
|
+
stratify = y_train if np.bincount(y_train).min() >= 2 else None
|
|
129
|
+
X_lab, X_unlab, y_lab, _ = train_test_split(
|
|
130
|
+
X_train, y_train, train_size=LABELED_RATIO, random_state=42, stratify=stratify
|
|
131
|
+
)
|
|
132
|
+
print("Labelled samples:", len(X_lab))
|
|
133
|
+
print("Unlabelled samples:", len(X_unlab))
|
|
134
|
+
|
|
135
|
+
# Baseline: supervised model trained on the labelled part only
|
|
136
|
+
baseline = train_model(X_lab, y_lab, n_classes)
|
|
137
|
+
baseline_acc = baseline.evaluate(X_test, y_test, verbose=0)[1]
|
|
138
|
+
print(f"\nBaseline (labelled data only) Test Accuracy: {baseline_acc * 100:.2f}%")
|
|
139
|
+
|
|
140
|
+
# Self-training: add confident pseudo-labels and retrain
|
|
141
|
+
model = baseline
|
|
142
|
+
X_cur, y_cur, pool = X_lab, y_lab, X_unlab
|
|
143
|
+
|
|
144
|
+
for round_no in range(1, 4):
|
|
145
|
+
probs = model.predict(pool, verbose=0)
|
|
146
|
+
confident = probs.max(axis=1) >= THRESHOLD
|
|
147
|
+
if confident.sum() == 0:
|
|
148
|
+
print("No more confident predictions.")
|
|
149
|
+
break
|
|
150
|
+
|
|
151
|
+
X_cur = np.vstack([X_cur, pool[confident]])
|
|
152
|
+
y_cur = np.concatenate([y_cur, probs[confident].argmax(axis=1)])
|
|
153
|
+
pool = pool[~confident]
|
|
154
|
+
print(f"Round {round_no}: added {confident.sum()} pseudo-labels "
|
|
155
|
+
f"(training size {len(X_cur)}, {len(pool)} unlabelled left)")
|
|
156
|
+
|
|
157
|
+
model = train_model(X_cur, y_cur, n_classes)
|
|
158
|
+
|
|
159
|
+
ssl_acc = model.evaluate(X_test, y_test, verbose=0)[1]
|
|
160
|
+
print(f"Semi-Supervised Test Accuracy: {ssl_acc * 100:.2f}%")
|
|
161
|
+
if ssl_acc >= MIN_ACCURACY:
|
|
162
|
+
break
|
|
163
|
+
text_col, label_col, changed = ask_columns(df, text_col, label_col)
|
|
164
|
+
if not changed:
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
# ---------- 3. Compare ----------
|
|
168
|
+
print(f"\nBaseline Test Accuracy: {baseline_acc * 100:.2f}%")
|
|
169
|
+
print(f"Semi-Supervised Test Accuracy: {ssl_acc * 100:.2f}%")
|
|
170
|
+
|
|
171
|
+
if ssl_acc > baseline_acc:
|
|
172
|
+
print("Semi-supervised learning improved the model.")
|
|
173
|
+
elif ssl_acc < baseline_acc:
|
|
174
|
+
print("Baseline model performed better.")
|
|
175
|
+
else:
|
|
176
|
+
print("Both models performed equally.")
|
|
177
|
+
|
|
178
|
+
y_pred = model.predict(X_test, verbose=0).argmax(axis=1)
|
|
179
|
+
print("\nClassification Report (semi-supervised model):")
|
|
180
|
+
print(classification_report(y_test, y_pred, labels=range(len(class_names)),
|
|
181
|
+
target_names=class_names, zero_division=0))
|
|
182
|
+
print("Confusion Matrix:")
|
|
183
|
+
print(confusion_matrix(y_test, y_pred))
|