genDWD 0.1.0__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.
- gendwd-0.1.0/LICENSE +21 -0
- gendwd-0.1.0/PKG-INFO +90 -0
- gendwd-0.1.0/README.md +71 -0
- gendwd-0.1.0/genDWD.egg-info/PKG-INFO +90 -0
- gendwd-0.1.0/genDWD.egg-info/SOURCES.txt +11 -0
- gendwd-0.1.0/genDWD.egg-info/dependency_links.txt +1 -0
- gendwd-0.1.0/genDWD.egg-info/requires.txt +2 -0
- gendwd-0.1.0/genDWD.egg-info/top_level.txt +1 -0
- gendwd-0.1.0/gendwd/__init__.py +1 -0
- gendwd-0.1.0/gendwd/gendwd.py +361 -0
- gendwd-0.1.0/pyproject.toml +3 -0
- gendwd-0.1.0/setup.cfg +4 -0
- gendwd-0.1.0/setup.py +19 -0
gendwd-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thoriq Al Mahdi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gendwd-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genDWD
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generalized Distance Weighted Discrimination (DWD) with sGS-ADDM
|
|
5
|
+
Author: Thoriq Al Mahdi
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: scipy
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# genDWD: Generalized Distance Weighted Discrimination
|
|
21
|
+
|
|
22
|
+
**genDWD** is a high-performance Python implementation of the Generalized Distance Weighted Discrimination (DWD) algorithm.
|
|
23
|
+
|
|
24
|
+
While **Support Vector Machines (SVM)** are widely used, they often suffer from the "data piling" phenomenon in **HDLLS (High-Dimension, Low Large Sample)** settings, where many data points project onto the same location on the decision boundary, leading to poor generalization. DWD was specifically developed to overcome this by accounting for the relative distance of all data points, providing better robustness in high-dimensional spaces.
|
|
25
|
+
|
|
26
|
+
Originally formulated for binary classification, this implementation extends the algorithm's utility by featuring built-in **Multiclass classification** support, enabling its application to complex, real-world datasets with multiple categories. It is designed to handle these tasks efficiently using the sGS-ADMM framework for large-scale optimization.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Research Basis
|
|
31
|
+
|
|
32
|
+
This implementation is strictly constructed based on the numerical procedures described in the following scientific paper:
|
|
33
|
+
|
|
34
|
+
> **"Fast algorithms for large scale generalized distance weighted discrimination"**
|
|
35
|
+
>
|
|
36
|
+
> — *Xin Yee Lam, J.S. Marron, Defeng Sun, and Kim-Chuan Toh (September 5, 2018)*
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
- **Automatic Detection**: Automatically switches between Binary and Multiclass (One-vs-One) logic.
|
|
41
|
+
- **Adaptive Solvers**: Dynamically selects between Cholesky, SMW, or PSQMR based on $n$ and $d$.
|
|
42
|
+
- **sGS-ADMM Framework**: Ensures fast convergence using Symmetric Gauss-Seidel ADMM.
|
|
43
|
+
- **Penalty Tuning**: Automatic $C$ parameter calculation using median inter-class distance.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
1. Install the package:
|
|
48
|
+
```bash
|
|
49
|
+
pip install genDWD
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Implementation Guide
|
|
53
|
+
|
|
54
|
+
To use the `genDWD` class in your project, follow the instructions below. The model follows the standard `.fit()` and `.predict()` pattern.
|
|
55
|
+
|
|
56
|
+
### 1. Basic Initialization
|
|
57
|
+
You can customize the model during initialization. The parameter `C='auto'` is recommended as it calculates the penalty based on the dataset's geometry.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from gendwd import genDWD
|
|
61
|
+
|
|
62
|
+
model = genDWD(C='auto')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Model Training and Prediction
|
|
66
|
+
|
|
67
|
+
The model follows the familiar `.fit()` and `.predict()` workflow. It automatically handles both binary and multiclass data.
|
|
68
|
+
|
|
69
|
+
#### Training the Model
|
|
70
|
+
To train the model, pass your feature matrix `X` and label vector `y`. The algorithm will automatically detect if it should use a binary or multiclass (One-vs-One) strategy.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import numpy as np
|
|
74
|
+
|
|
75
|
+
# Sample Data: 100 samples, 10 features
|
|
76
|
+
X_train = np.random.randn(100, 10)
|
|
77
|
+
# Labels can be integers, strings, or floats
|
|
78
|
+
y_train = np.random.choice(['Class_A', 'Class_B'], size=100)
|
|
79
|
+
|
|
80
|
+
# Train the model
|
|
81
|
+
model.fit(X_train, y_train)
|
|
82
|
+
|
|
83
|
+
# New data for prediction
|
|
84
|
+
X_new = np.random.randn(5, 10)
|
|
85
|
+
|
|
86
|
+
# Get predicted class labels
|
|
87
|
+
predictions = model.predict(X_new)
|
|
88
|
+
|
|
89
|
+
print(f"Predicted Labels: {predictions}")
|
|
90
|
+
```
|
gendwd-0.1.0/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# genDWD: Generalized Distance Weighted Discrimination
|
|
2
|
+
|
|
3
|
+
**genDWD** is a high-performance Python implementation of the Generalized Distance Weighted Discrimination (DWD) algorithm.
|
|
4
|
+
|
|
5
|
+
While **Support Vector Machines (SVM)** are widely used, they often suffer from the "data piling" phenomenon in **HDLLS (High-Dimension, Low Large Sample)** settings, where many data points project onto the same location on the decision boundary, leading to poor generalization. DWD was specifically developed to overcome this by accounting for the relative distance of all data points, providing better robustness in high-dimensional spaces.
|
|
6
|
+
|
|
7
|
+
Originally formulated for binary classification, this implementation extends the algorithm's utility by featuring built-in **Multiclass classification** support, enabling its application to complex, real-world datasets with multiple categories. It is designed to handle these tasks efficiently using the sGS-ADMM framework for large-scale optimization.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Core Research Basis
|
|
12
|
+
|
|
13
|
+
This implementation is strictly constructed based on the numerical procedures described in the following scientific paper:
|
|
14
|
+
|
|
15
|
+
> **"Fast algorithms for large scale generalized distance weighted discrimination"**
|
|
16
|
+
>
|
|
17
|
+
> — *Xin Yee Lam, J.S. Marron, Defeng Sun, and Kim-Chuan Toh (September 5, 2018)*
|
|
18
|
+
|
|
19
|
+
## Key Features
|
|
20
|
+
|
|
21
|
+
- **Automatic Detection**: Automatically switches between Binary and Multiclass (One-vs-One) logic.
|
|
22
|
+
- **Adaptive Solvers**: Dynamically selects between Cholesky, SMW, or PSQMR based on $n$ and $d$.
|
|
23
|
+
- **sGS-ADMM Framework**: Ensures fast convergence using Symmetric Gauss-Seidel ADMM.
|
|
24
|
+
- **Penalty Tuning**: Automatic $C$ parameter calculation using median inter-class distance.
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
1. Install the package:
|
|
29
|
+
```bash
|
|
30
|
+
pip install genDWD
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Implementation Guide
|
|
34
|
+
|
|
35
|
+
To use the `genDWD` class in your project, follow the instructions below. The model follows the standard `.fit()` and `.predict()` pattern.
|
|
36
|
+
|
|
37
|
+
### 1. Basic Initialization
|
|
38
|
+
You can customize the model during initialization. The parameter `C='auto'` is recommended as it calculates the penalty based on the dataset's geometry.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from gendwd import genDWD
|
|
42
|
+
|
|
43
|
+
model = genDWD(C='auto')
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 2. Model Training and Prediction
|
|
47
|
+
|
|
48
|
+
The model follows the familiar `.fit()` and `.predict()` workflow. It automatically handles both binary and multiclass data.
|
|
49
|
+
|
|
50
|
+
#### Training the Model
|
|
51
|
+
To train the model, pass your feature matrix `X` and label vector `y`. The algorithm will automatically detect if it should use a binary or multiclass (One-vs-One) strategy.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
import numpy as np
|
|
55
|
+
|
|
56
|
+
# Sample Data: 100 samples, 10 features
|
|
57
|
+
X_train = np.random.randn(100, 10)
|
|
58
|
+
# Labels can be integers, strings, or floats
|
|
59
|
+
y_train = np.random.choice(['Class_A', 'Class_B'], size=100)
|
|
60
|
+
|
|
61
|
+
# Train the model
|
|
62
|
+
model.fit(X_train, y_train)
|
|
63
|
+
|
|
64
|
+
# New data for prediction
|
|
65
|
+
X_new = np.random.randn(5, 10)
|
|
66
|
+
|
|
67
|
+
# Get predicted class labels
|
|
68
|
+
predictions = model.predict(X_new)
|
|
69
|
+
|
|
70
|
+
print(f"Predicted Labels: {predictions}")
|
|
71
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: genDWD
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generalized Distance Weighted Discrimination (DWD) with sGS-ADDM
|
|
5
|
+
Author: Thoriq Al Mahdi
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy
|
|
11
|
+
Requires-Dist: scipy
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: classifier
|
|
14
|
+
Dynamic: description
|
|
15
|
+
Dynamic: description-content-type
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
Dynamic: requires-dist
|
|
18
|
+
Dynamic: summary
|
|
19
|
+
|
|
20
|
+
# genDWD: Generalized Distance Weighted Discrimination
|
|
21
|
+
|
|
22
|
+
**genDWD** is a high-performance Python implementation of the Generalized Distance Weighted Discrimination (DWD) algorithm.
|
|
23
|
+
|
|
24
|
+
While **Support Vector Machines (SVM)** are widely used, they often suffer from the "data piling" phenomenon in **HDLLS (High-Dimension, Low Large Sample)** settings, where many data points project onto the same location on the decision boundary, leading to poor generalization. DWD was specifically developed to overcome this by accounting for the relative distance of all data points, providing better robustness in high-dimensional spaces.
|
|
25
|
+
|
|
26
|
+
Originally formulated for binary classification, this implementation extends the algorithm's utility by featuring built-in **Multiclass classification** support, enabling its application to complex, real-world datasets with multiple categories. It is designed to handle these tasks efficiently using the sGS-ADMM framework for large-scale optimization.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Core Research Basis
|
|
31
|
+
|
|
32
|
+
This implementation is strictly constructed based on the numerical procedures described in the following scientific paper:
|
|
33
|
+
|
|
34
|
+
> **"Fast algorithms for large scale generalized distance weighted discrimination"**
|
|
35
|
+
>
|
|
36
|
+
> — *Xin Yee Lam, J.S. Marron, Defeng Sun, and Kim-Chuan Toh (September 5, 2018)*
|
|
37
|
+
|
|
38
|
+
## Key Features
|
|
39
|
+
|
|
40
|
+
- **Automatic Detection**: Automatically switches between Binary and Multiclass (One-vs-One) logic.
|
|
41
|
+
- **Adaptive Solvers**: Dynamically selects between Cholesky, SMW, or PSQMR based on $n$ and $d$.
|
|
42
|
+
- **sGS-ADMM Framework**: Ensures fast convergence using Symmetric Gauss-Seidel ADMM.
|
|
43
|
+
- **Penalty Tuning**: Automatic $C$ parameter calculation using median inter-class distance.
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
1. Install the package:
|
|
48
|
+
```bash
|
|
49
|
+
pip install genDWD
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Implementation Guide
|
|
53
|
+
|
|
54
|
+
To use the `genDWD` class in your project, follow the instructions below. The model follows the standard `.fit()` and `.predict()` pattern.
|
|
55
|
+
|
|
56
|
+
### 1. Basic Initialization
|
|
57
|
+
You can customize the model during initialization. The parameter `C='auto'` is recommended as it calculates the penalty based on the dataset's geometry.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from gendwd import genDWD
|
|
61
|
+
|
|
62
|
+
model = genDWD(C='auto')
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Model Training and Prediction
|
|
66
|
+
|
|
67
|
+
The model follows the familiar `.fit()` and `.predict()` workflow. It automatically handles both binary and multiclass data.
|
|
68
|
+
|
|
69
|
+
#### Training the Model
|
|
70
|
+
To train the model, pass your feature matrix `X` and label vector `y`. The algorithm will automatically detect if it should use a binary or multiclass (One-vs-One) strategy.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import numpy as np
|
|
74
|
+
|
|
75
|
+
# Sample Data: 100 samples, 10 features
|
|
76
|
+
X_train = np.random.randn(100, 10)
|
|
77
|
+
# Labels can be integers, strings, or floats
|
|
78
|
+
y_train = np.random.choice(['Class_A', 'Class_B'], size=100)
|
|
79
|
+
|
|
80
|
+
# Train the model
|
|
81
|
+
model.fit(X_train, y_train)
|
|
82
|
+
|
|
83
|
+
# New data for prediction
|
|
84
|
+
X_new = np.random.randn(5, 10)
|
|
85
|
+
|
|
86
|
+
# Get predicted class labels
|
|
87
|
+
predictions = model.predict(X_new)
|
|
88
|
+
|
|
89
|
+
print(f"Predicted Labels: {predictions}")
|
|
90
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gendwd
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .gendwd import genDWD
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.linalg import cho_factor, cho_solve
|
|
3
|
+
from scipy.spatial.distance import cdist
|
|
4
|
+
|
|
5
|
+
class genDWD:
|
|
6
|
+
def __init__(self, C='auto', q=1, maxIter=2000):
|
|
7
|
+
|
|
8
|
+
self.C = C
|
|
9
|
+
self.q = q
|
|
10
|
+
self.maxIter = maxIter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
self.is_multiclass = False
|
|
15
|
+
self.classes_ = None
|
|
16
|
+
self.w_ = None
|
|
17
|
+
self.beta_ = None
|
|
18
|
+
self.history_ = None
|
|
19
|
+
self.models_ = None
|
|
20
|
+
|
|
21
|
+
def _Newton_Method(self, a, q, sigma, s_old, max_iter=20, tol=1e-8):
|
|
22
|
+
s = np.maximum(s_old, 1e-10)
|
|
23
|
+
inv_sigma = 1.0 / sigma
|
|
24
|
+
c1 = q * (q + 2) * inv_sigma
|
|
25
|
+
c2 = q * (q + 1) * inv_sigma
|
|
26
|
+
|
|
27
|
+
for i in range(max_iter):
|
|
28
|
+
s_safe = np.minimum(s, 1e10)
|
|
29
|
+
s_q1 = s_safe**(q + 1)
|
|
30
|
+
s_q2 = s_safe**(q + 2)
|
|
31
|
+
|
|
32
|
+
numerator = c1 + a * s_q1
|
|
33
|
+
denominator = c2 + s_q2
|
|
34
|
+
|
|
35
|
+
s_next = s * (numerator / (denominator + 1e-15))
|
|
36
|
+
s_next = np.maximum(1e-10, s_next)
|
|
37
|
+
|
|
38
|
+
if np.max(np.abs(s_next - s)) < tol:
|
|
39
|
+
return s_next
|
|
40
|
+
|
|
41
|
+
s = s_next
|
|
42
|
+
return s
|
|
43
|
+
|
|
44
|
+
def _penalty_parameter(self, X, y, expon=1, rmzeroFea=True, scaleFea=True):
|
|
45
|
+
dim, n = X.shape
|
|
46
|
+
if rmzeroFea:
|
|
47
|
+
normX = np.sqrt(np.sum(X**2, axis=1))
|
|
48
|
+
nzrow = np.where(normX > 0)[0]
|
|
49
|
+
if len(nzrow) < len(normX):
|
|
50
|
+
X = X[nzrow, :]
|
|
51
|
+
dim = X.shape[0]
|
|
52
|
+
|
|
53
|
+
if scaleFea:
|
|
54
|
+
if dim > 0.5 * n:
|
|
55
|
+
normX = np.sqrt(np.sum(X**2, axis=1))
|
|
56
|
+
if np.max(normX) > 2 * np.min(normX):
|
|
57
|
+
if dim > 3 * n:
|
|
58
|
+
scaling_vec = 1.0 / np.maximum(1, np.sqrt(normX))
|
|
59
|
+
else:
|
|
60
|
+
scaling_vec = 1.0 / np.maximum(1, normX)
|
|
61
|
+
X = X * scaling_vec[:, np.newaxis]
|
|
62
|
+
|
|
63
|
+
positive = np.where(y == 1)[0]
|
|
64
|
+
negative = np.where(y == -1)[0]
|
|
65
|
+
|
|
66
|
+
limit = 100 if (dim > 1e4 and n > 1e4) else 200
|
|
67
|
+
|
|
68
|
+
if len(positive) > limit:
|
|
69
|
+
positive = np.random.choice(positive, limit, replace=False)
|
|
70
|
+
if len(negative) > limit:
|
|
71
|
+
negative = np.random.choice(negative, limit, replace=False)
|
|
72
|
+
|
|
73
|
+
posX = X[:, positive].T
|
|
74
|
+
negX = X[:, negative].T
|
|
75
|
+
|
|
76
|
+
ddist = cdist(negX, posX, metric='euclidean')
|
|
77
|
+
dd = np.median(ddist)
|
|
78
|
+
|
|
79
|
+
if expon == 1:
|
|
80
|
+
const = np.log(n) * (max(1000, dim)**(1/3))
|
|
81
|
+
else:
|
|
82
|
+
const = 10 * np.log(n) * (max(1000, dim)**(1/3))
|
|
83
|
+
|
|
84
|
+
C = (10**(expon + 1)) * max(1, const / (dd**(expon + 1)))
|
|
85
|
+
return C
|
|
86
|
+
|
|
87
|
+
def _matvec_A(self, x, Z, y, const):
|
|
88
|
+
dim = Z.shape[0]
|
|
89
|
+
n = Z.shape[1]
|
|
90
|
+
|
|
91
|
+
x = x.reshape(-1, 1)
|
|
92
|
+
w_part = x[:dim, :]
|
|
93
|
+
beta_part = x[dim:, :]
|
|
94
|
+
|
|
95
|
+
Ztw = Z.T @ w_part
|
|
96
|
+
ZZtw = Z @ Ztw
|
|
97
|
+
y_vec = y.reshape(-1, 1)
|
|
98
|
+
Zy_beta = Z @ (y_vec * beta_part)
|
|
99
|
+
|
|
100
|
+
upper = ZZtw + (const * w_part) + Zy_beta
|
|
101
|
+
lower_left = y_vec.T @ Ztw
|
|
102
|
+
lower_right = (y_vec.T @ y_vec) * beta_part
|
|
103
|
+
lower = lower_left + lower_right
|
|
104
|
+
|
|
105
|
+
return np.vstack((upper, lower))
|
|
106
|
+
|
|
107
|
+
def _psqmr_solver(self, Z, y, rhs, x0, const, tol=1e-6, max_iter=100):
|
|
108
|
+
r = rhs - self._matvec_A(x0, Z, y, const)
|
|
109
|
+
q = r.copy()
|
|
110
|
+
tau = np.linalg.norm(q)
|
|
111
|
+
theta = 0.0
|
|
112
|
+
d = np.zeros_like(x0)
|
|
113
|
+
x = x0.copy()
|
|
114
|
+
|
|
115
|
+
for k in range(1, max_iter + 1):
|
|
116
|
+
Aq = self._matvec_A(q, Z, y, const)
|
|
117
|
+
num = r.T @ r
|
|
118
|
+
den = q.T @ Aq
|
|
119
|
+
alpha_k = num / (den + 1e-12)
|
|
120
|
+
|
|
121
|
+
r_next = r - alpha_k * Aq
|
|
122
|
+
theta_next = np.linalg.norm(r_next) / (tau + 1e-12)
|
|
123
|
+
c_k = 1.0 / np.sqrt(1 + theta_next**2)
|
|
124
|
+
|
|
125
|
+
tau_next = tau * theta_next * c_k
|
|
126
|
+
d = (c_k**2 * theta**2) * d + (c_k**2 * alpha_k) * q
|
|
127
|
+
x = x + d
|
|
128
|
+
|
|
129
|
+
if tau_next < tol:
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
beta_k = (r_next.T @ r_next) / (num + 1e-12)
|
|
133
|
+
q = r_next + beta_k * q
|
|
134
|
+
r, theta, tau = r_next, theta_next, tau_next
|
|
135
|
+
|
|
136
|
+
return x
|
|
137
|
+
|
|
138
|
+
def _binary_fit(self, X, y, C_val):
|
|
139
|
+
|
|
140
|
+
X = X.T
|
|
141
|
+
dim, n = X.shape
|
|
142
|
+
|
|
143
|
+
if C_val == 'auto':
|
|
144
|
+
C_val = self._penalty_parameter(X, y)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
Zscale = np.linalg.norm(X, 'fro')
|
|
148
|
+
Z = X * y.reshape(1, -1) / Zscale
|
|
149
|
+
|
|
150
|
+
sigma = min(10 * C_val, n)**self.q
|
|
151
|
+
normZ = 1 + np.sqrt(np.max(np.sum(Z * Z, axis=0)))
|
|
152
|
+
|
|
153
|
+
r = np.ones((n, 1))
|
|
154
|
+
wbeta = np.zeros((dim+1, 1))
|
|
155
|
+
u = np.zeros((dim, 1))
|
|
156
|
+
xi = np.zeros((n, 1))
|
|
157
|
+
alpha = np.zeros((n, 1))
|
|
158
|
+
p = np.zeros((dim, 1))
|
|
159
|
+
tau = 1.618
|
|
160
|
+
const = 1.0
|
|
161
|
+
tol = 1e-5
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
use_psqmr = (dim > 3000 and n > 3000)
|
|
166
|
+
use_smw = (dim > 3000 and n <= 3000)
|
|
167
|
+
use_cholesky = not use_psqmr and not use_smw
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
if use_cholesky:
|
|
172
|
+
A1 = Z @ Z.T + const * np.eye(dim)
|
|
173
|
+
A2 = Z @ y.reshape(-1, 1)
|
|
174
|
+
A3 = A2.T
|
|
175
|
+
A4 = y.reshape(1, -1) @ y.reshape(-1, 1)
|
|
176
|
+
A = np.block([[A1, A2], [A3, A4]])
|
|
177
|
+
A += 1e-9 * np.eye(A.shape[0])
|
|
178
|
+
A_factor = cho_factor(A)
|
|
179
|
+
elif use_smw:
|
|
180
|
+
D_inv = 1.0 / const
|
|
181
|
+
norm_y_sq = np.sum(y**2)
|
|
182
|
+
J_utama = np.eye(n) + D_inv * (Z.T @ Z)
|
|
183
|
+
J_factor = cho_factor(J_utama)
|
|
184
|
+
y_vec = y.flatten()
|
|
185
|
+
J_inv_y = cho_solve(J_factor, y_vec)
|
|
186
|
+
denom_smw = 1.0 + (1.0 / norm_y_sq) * (y_vec @ J_inv_y) - 1.0
|
|
187
|
+
inv_D_hat = np.concatenate([np.full(dim, D_inv), [1.0 / norm_y_sq]])
|
|
188
|
+
|
|
189
|
+
def solver_system(r_val, xi_val, alpha_val, u_val, p_val, sigma, wbeta):
|
|
190
|
+
tmp = r_val - xi_val + alpha_val / sigma
|
|
191
|
+
rhs1 = Z @ tmp + const * u_val + p_val / sigma
|
|
192
|
+
rhs2 = y.T @ tmp
|
|
193
|
+
rhs = np.vstack((rhs1, rhs2))
|
|
194
|
+
|
|
195
|
+
if use_cholesky:
|
|
196
|
+
sol = cho_solve(A_factor, rhs)
|
|
197
|
+
elif use_smw:
|
|
198
|
+
h_hat = inv_D_hat.reshape(-1, 1) * rhs
|
|
199
|
+
norm_y = np.sqrt(norm_y_sq)
|
|
200
|
+
Ut_h_hat = np.zeros((n + 1, 1))
|
|
201
|
+
Ut_h_hat[:n] = Z.T @ h_hat[:dim] + y.reshape(-1, 1) * h_hat[dim:]
|
|
202
|
+
Ut_h_hat[n] = norm_y * h_hat[dim:]
|
|
203
|
+
v = Ut_h_hat.flatten()
|
|
204
|
+
J_inv_v = np.zeros(n + 1)
|
|
205
|
+
J_inv_v[:n] = cho_solve(J_factor, v[:n])
|
|
206
|
+
J_inv_v[n] = -v[n]
|
|
207
|
+
y_bar = np.append(y_vec / norm_y, 1.0)
|
|
208
|
+
y_bar_t_J_inv_v = y_bar @ J_inv_v
|
|
209
|
+
y_bar_t_J_inv_y_bar = y_bar @ np.append(J_inv_y / norm_y, -1.0)
|
|
210
|
+
H_inv_Ut_h_hat = J_inv_v - (y_bar_t_J_inv_v / (1.0 + y_bar_t_J_inv_y_bar)) * np.append(J_inv_y / norm_y, -1.0)
|
|
211
|
+
U_res = np.zeros((dim + 1, 1))
|
|
212
|
+
U_res[:dim] = Z @ H_inv_Ut_h_hat[:n].reshape(-1, 1)
|
|
213
|
+
U_res[dim:] = y.reshape(1, -1) @ H_inv_Ut_h_hat[:n].reshape(-1, 1) + norm_y * H_inv_Ut_h_hat[n]
|
|
214
|
+
sol = h_hat - inv_D_hat.reshape(-1, 1) * U_res
|
|
215
|
+
elif use_psqmr:
|
|
216
|
+
sol = self._psqmr_solver(Z, y, rhs, wbeta, const, tol=1e-4, max_iter=50)
|
|
217
|
+
return sol[:dim], sol[dim:]
|
|
218
|
+
|
|
219
|
+
for iter in range(1, self.maxIter + 1):
|
|
220
|
+
rold, wbetaold, uold = r.copy(), wbeta.copy(), u.copy()
|
|
221
|
+
xiold, alphaold, pold = xi.copy(), alpha.copy(), p.copy()
|
|
222
|
+
|
|
223
|
+
w, beta = solver_system(rold, xiold, alphaold, uold, pold, sigma, wbetaold)
|
|
224
|
+
|
|
225
|
+
ZTwpbetay = Z.T @ w + beta * y.reshape(-1, 1)
|
|
226
|
+
cc = ZTwpbetay + xiold - alphaold / sigma
|
|
227
|
+
r = self._Newton_Method(cc, self.q, sigma, rold)
|
|
228
|
+
r = np.maximum(r, 0)
|
|
229
|
+
|
|
230
|
+
doublecompute_measure = normZ * np.linalg.norm(r - rold) * (iter**1.5)
|
|
231
|
+
if doublecompute_measure > 10 or iter < 50:
|
|
232
|
+
w, beta = solver_system(rold, xiold, alphaold, uold, pold, sigma, wbetaold)
|
|
233
|
+
ZTwpbetay = Z.T @ w + beta * y.reshape(-1, 1)
|
|
234
|
+
|
|
235
|
+
uinput = w - pold / (const * sigma)
|
|
236
|
+
u_norm = np.linalg.norm(uinput)
|
|
237
|
+
u = Zscale * uinput / max(Zscale, u_norm)
|
|
238
|
+
|
|
239
|
+
xiinput = r - ZTwpbetay + (alphaold - C_val) / sigma
|
|
240
|
+
xi = np.maximum(xiinput, 0)
|
|
241
|
+
|
|
242
|
+
Rp = ZTwpbetay + xi - r
|
|
243
|
+
alpha = alphaold - tau * sigma * Rp
|
|
244
|
+
p = pold - (tau * sigma * const) * (w - u)
|
|
245
|
+
|
|
246
|
+
alpha_f = alpha.flatten()
|
|
247
|
+
xi_f = xi.flatten()
|
|
248
|
+
r_f = r.flatten()
|
|
249
|
+
y_f = y.flatten()
|
|
250
|
+
rexpon1 = np.maximum(r_f, 1e-10)**(self.q + 1)
|
|
251
|
+
|
|
252
|
+
eta_c1 = np.abs(np.dot(y_f, alpha_f))
|
|
253
|
+
eta_c2 = np.abs(np.dot(xi_f, (C_val - alpha_f)))
|
|
254
|
+
term_comp3_a = np.linalg.norm(alpha_f * rexpon1 - self.q)
|
|
255
|
+
term_comp3_b = np.linalg.norm(alpha_f - self.q / rexpon1)**2
|
|
256
|
+
eta_c3 = min(term_comp3_a, term_comp3_b)
|
|
257
|
+
eta_c = max(eta_c1, eta_c2, eta_c3) / (1 + C_val)
|
|
258
|
+
|
|
259
|
+
eta_p1 = np.linalg.norm(Rp)
|
|
260
|
+
eta_p2 = np.linalg.norm(w - u)
|
|
261
|
+
eta_p3 = max(np.linalg.norm(w) - Zscale, 0)
|
|
262
|
+
eta_p = max(eta_p1, eta_p2, eta_p3) / (1 + C_val)
|
|
263
|
+
|
|
264
|
+
eta_d = max(np.linalg.norm(np.minimum(0, alpha_f)),
|
|
265
|
+
np.linalg.norm(np.maximum(alpha_f - C_val, 0))) / (1 + C_val)
|
|
266
|
+
|
|
267
|
+
primobj = np.sum(r_f / rexpon1) + np.sum(C_val * xi_f) + 1e-8
|
|
268
|
+
kappa = ((self.q + 1) / self.q) * (self.q**(1 / (self.q + 1)))
|
|
269
|
+
term_dual1 = kappa * np.sum(np.maximum(0, alpha_f)**(self.q / (self.q + 1)))
|
|
270
|
+
term_dual2 = np.linalg.norm(Z @ alpha.reshape(-1, 1))
|
|
271
|
+
|
|
272
|
+
dualobj = term_dual1 - term_dual2
|
|
273
|
+
eta_gap = abs(primobj - dualobj) / (1 + abs(primobj) + abs(dualobj))
|
|
274
|
+
|
|
275
|
+
kondisi1 = max(eta_p, eta_d)
|
|
276
|
+
kondisi2 = min(eta_gap, eta_c)
|
|
277
|
+
kondisi3 = max(eta_gap, eta_c)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
if kondisi1 < tol and kondisi2 < np.sqrt(tol) and kondisi3 < 0.05:
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
if iter % 20 == 0:
|
|
285
|
+
ratio = max(eta_p, 0.2 * tol) / max(eta_d, 0.2 * tol)
|
|
286
|
+
if ratio > 5:
|
|
287
|
+
sigma = min(sigma * 2.2, 1e6)
|
|
288
|
+
elif 1 / ratio > 5:
|
|
289
|
+
sigma = max(sigma / 1.65, 1e-3)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
w = w / Zscale
|
|
294
|
+
|
|
295
|
+
return w, beta
|
|
296
|
+
|
|
297
|
+
def fit(self, X, y):
|
|
298
|
+
self.classes_ = np.sort(np.unique(y))
|
|
299
|
+
|
|
300
|
+
if len(self.classes_) == 2:
|
|
301
|
+
# Mode Biner
|
|
302
|
+
self.is_multiclass = False
|
|
303
|
+
self.c_neg, self.c_pos = self.classes_[0], self.classes_[1]
|
|
304
|
+
y_bin = np.where(y == self.c_pos, 1, -1)
|
|
305
|
+
|
|
306
|
+
res = self._binary_fit(X, y_bin, self.C)
|
|
307
|
+
|
|
308
|
+
self.w_, self.beta_ = res
|
|
309
|
+
|
|
310
|
+
elif len(self.classes_) > 2:
|
|
311
|
+
# Mode Multi-kelas (One-vs-One)
|
|
312
|
+
self.is_multiclass = True
|
|
313
|
+
self.models_ = {}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
for i in range(len(self.classes_)):
|
|
317
|
+
for j in range(i + 1, len(self.classes_)):
|
|
318
|
+
c_i = self.classes_[i]
|
|
319
|
+
c_j = self.classes_[j]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
mask = (y == c_i) | (y == c_j)
|
|
324
|
+
y_mask = np.where(y[mask] == c_i, -1, 1)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
res = self._binary_fit(X[mask], y_mask, self.C)
|
|
328
|
+
|
|
329
|
+
w_bin, b_bin = res
|
|
330
|
+
|
|
331
|
+
self.models_[(c_i, c_j)] = (w_bin, b_bin)
|
|
332
|
+
else:
|
|
333
|
+
raise ValueError("Data label 'y' hanya memiliki 1 kelas. Tidak dapat diklasifikasi.")
|
|
334
|
+
|
|
335
|
+
return self
|
|
336
|
+
|
|
337
|
+
def predict(self, X):
|
|
338
|
+
if not self.is_multiclass:
|
|
339
|
+
# Prediksi Biner
|
|
340
|
+
proyeksi = np.dot(X, self.w_) + self.beta_
|
|
341
|
+
y_pred_bin = np.where(proyeksi > 0, 1, -1).flatten()
|
|
342
|
+
return np.where(y_pred_bin == 1, self.c_pos, self.c_neg)
|
|
343
|
+
|
|
344
|
+
else:
|
|
345
|
+
# Prediksi Multi-kelas (Voting / Akumulasi Skor)
|
|
346
|
+
n_samples = X.shape[0]
|
|
347
|
+
n_classes = len(self.classes_)
|
|
348
|
+
accumulated_scores = np.zeros((n_samples, n_classes), dtype=float)
|
|
349
|
+
|
|
350
|
+
for (c_i, c_j), (w, b) in self.models_.items():
|
|
351
|
+
score = np.dot(X, w) + b
|
|
352
|
+
score = score.reshape(-1)
|
|
353
|
+
|
|
354
|
+
idx_i = np.where(self.classes_ == c_i)[0][0]
|
|
355
|
+
idx_j = np.where(self.classes_ == c_j)[0][0]
|
|
356
|
+
|
|
357
|
+
accumulated_scores[:, idx_j] += score
|
|
358
|
+
accumulated_scores[:, idx_i] -= score
|
|
359
|
+
|
|
360
|
+
final_indices = np.argmax(accumulated_scores, axis=1)
|
|
361
|
+
return self.classes_[final_indices]
|
gendwd-0.1.0/setup.cfg
ADDED
gendwd-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="genDWD",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
author="Thoriq Al Mahdi",
|
|
7
|
+
description="Generalized Distance Weighted Discrimination (DWD) with sGS-ADDM",
|
|
8
|
+
long_description=open("README.md").read(),
|
|
9
|
+
long_description_content_type="text/markdown",
|
|
10
|
+
packages=find_packages(),
|
|
11
|
+
install_requires=[
|
|
12
|
+
"numpy",
|
|
13
|
+
"scipy",
|
|
14
|
+
],
|
|
15
|
+
classifiers=[
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
],
|
|
19
|
+
)
|