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.
Files changed (71) hide show
  1. {libz-0.4 → libz-0.6}/PKG-INFO +1 -1
  2. {libz-0.4 → libz-0.6}/libz/DLG/__init__.py +15 -15
  3. libz-0.6/libz/DLG/prac1.py +137 -0
  4. libz-0.6/libz/DLG/prac2.py +172 -0
  5. libz-0.6/libz/DLG/prac3.py +183 -0
  6. libz-0.6/libz/DLG/prac4.py +164 -0
  7. libz-0.6/libz/DLG/prac5.py +269 -0
  8. libz-0.6/libz/DLG/prac6.py +253 -0
  9. libz-0.6/libz/DLG/prac7.py +194 -0
  10. libz-0.6/libz/DLG/prac8.py +231 -0
  11. {libz-0.4 → libz-0.6}/libz/DLG/prac9.py +46 -7
  12. {libz-0.4 → libz-0.6}/libz/__init__.py +1 -1
  13. {libz-0.4 → libz-0.6}/libz.egg-info/PKG-INFO +1 -1
  14. {libz-0.4 → libz-0.6}/pyproject.toml +1 -1
  15. libz-0.4/libz/DLG/prac1.py +0 -71
  16. libz-0.4/libz/DLG/prac2.py +0 -108
  17. libz-0.4/libz/DLG/prac3.py +0 -108
  18. libz-0.4/libz/DLG/prac4.py +0 -105
  19. libz-0.4/libz/DLG/prac5.py +0 -99
  20. libz-0.4/libz/DLG/prac6.py +0 -119
  21. libz-0.4/libz/DLG/prac7.py +0 -110
  22. libz-0.4/libz/DLG/prac8.py +0 -141
  23. {libz-0.4 → libz-0.6}/README.md +0 -0
  24. {libz-0.4 → libz-0.6}/libz/AWT/__init__.py +0 -0
  25. {libz-0.4 → libz-0.6}/libz/AWT/prac1.py +0 -0
  26. {libz-0.4 → libz-0.6}/libz/AWT/prac10.py +0 -0
  27. {libz-0.4 → libz-0.6}/libz/AWT/prac2.py +0 -0
  28. {libz-0.4 → libz-0.6}/libz/AWT/prac3.py +0 -0
  29. {libz-0.4 → libz-0.6}/libz/AWT/prac4.py +0 -0
  30. {libz-0.4 → libz-0.6}/libz/AWT/prac5.py +0 -0
  31. {libz-0.4 → libz-0.6}/libz/AWT/prac6.py +0 -0
  32. {libz-0.4 → libz-0.6}/libz/AWT/prac7.py +0 -0
  33. {libz-0.4 → libz-0.6}/libz/AWT/prac8.py +0 -0
  34. {libz-0.4 → libz-0.6}/libz/AWT/prac9.py +0 -0
  35. {libz-0.4 → libz-0.6}/libz/BDA/__init__.py +0 -0
  36. {libz-0.4 → libz-0.6}/libz/BDA/prac1.py +0 -0
  37. {libz-0.4 → libz-0.6}/libz/BDA/prac10.py +0 -0
  38. {libz-0.4 → libz-0.6}/libz/BDA/prac2.py +0 -0
  39. {libz-0.4 → libz-0.6}/libz/BDA/prac3.py +0 -0
  40. {libz-0.4 → libz-0.6}/libz/BDA/prac4.py +0 -0
  41. {libz-0.4 → libz-0.6}/libz/BDA/prac5.py +0 -0
  42. {libz-0.4 → libz-0.6}/libz/BDA/prac6.py +0 -0
  43. {libz-0.4 → libz-0.6}/libz/BDA/prac7.py +0 -0
  44. {libz-0.4 → libz-0.6}/libz/BDA/prac8.py +0 -0
  45. {libz-0.4 → libz-0.6}/libz/BDA/prac9.py +0 -0
  46. {libz-0.4 → libz-0.6}/libz/BIO/__init__.py +0 -0
  47. {libz-0.4 → libz-0.6}/libz/BIO/prac1.py +0 -0
  48. {libz-0.4 → libz-0.6}/libz/BIO/prac10.py +0 -0
  49. {libz-0.4 → libz-0.6}/libz/BIO/prac2.py +0 -0
  50. {libz-0.4 → libz-0.6}/libz/BIO/prac3.py +0 -0
  51. {libz-0.4 → libz-0.6}/libz/BIO/prac4.py +0 -0
  52. {libz-0.4 → libz-0.6}/libz/BIO/prac5.py +0 -0
  53. {libz-0.4 → libz-0.6}/libz/BIO/prac6.py +0 -0
  54. {libz-0.4 → libz-0.6}/libz/BIO/prac7.py +0 -0
  55. {libz-0.4 → libz-0.6}/libz/BIO/prac8.py +0 -0
  56. {libz-0.4 → libz-0.6}/libz/BIO/prac9.py +0 -0
  57. {libz-0.4 → libz-0.6}/libz/DL/__init__.py +0 -0
  58. {libz-0.4 → libz-0.6}/libz/DL/prac1.py +0 -0
  59. {libz-0.4 → libz-0.6}/libz/DL/prac2.py +0 -0
  60. {libz-0.4 → libz-0.6}/libz/DL/prac3.py +0 -0
  61. {libz-0.4 → libz-0.6}/libz/DL/prac4.py +0 -0
  62. {libz-0.4 → libz-0.6}/libz/DL/prac5.py +0 -0
  63. {libz-0.4 → libz-0.6}/libz/DL/prac6.py +0 -0
  64. {libz-0.4 → libz-0.6}/libz/DL/prac7.py +0 -0
  65. {libz-0.4 → libz-0.6}/libz/DL/prac8.py +0 -0
  66. {libz-0.4 → libz-0.6}/libz/DL/prac9.py +0 -0
  67. {libz-0.4 → libz-0.6}/libz/_core.py +0 -0
  68. {libz-0.4 → libz-0.6}/libz.egg-info/SOURCES.txt +0 -0
  69. {libz-0.4 → libz-0.6}/libz.egg-info/dependency_links.txt +0 -0
  70. {libz-0.4 → libz-0.6}/libz.egg-info/top_level.txt +0 -0
  71. {libz-0.4 → libz-0.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libz
3
- Version: 0.4
3
+ Version: 0.6
4
4
  Summary: A lightweight Python utility toolkit.
5
5
  Requires-Python: >=3.7
6
6
  Description-Content-Type: text/markdown
@@ -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))