Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 4 additions & 22 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,43 +10,25 @@ jobs:
fail-fast: false
matrix:
version:
- '1.5'
- '1.6'
- '1'
- 'nightly'
os:
- ubuntu-latest
- windows-latest
- macos-latest
arch:
- x64
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: python dependencies
run: |
python -m pip install --upgrade pip
pip install pyod
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@v1
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: actions/cache@v1
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
- uses: julia-actions/cache@v1 # https://github.com/julia-actions/cache
- uses: julia-actions/julia-buildpkg@v1
env:
PYTHON: python
- uses: julia-actions/julia-runtest@v1
- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v2
with:
file: lcov.info
files: lcov.info
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
*.jl.cov
*.jl.mem
/Manifest.toml

.CondaPkg/*
5 changes: 5 additions & 0 deletions CondaPkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[deps]
pyod = "=1"

[deps.llvmlite]
channel = "numba"
8 changes: 4 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name = "OutlierDetectionPython"
uuid = "2449c660-d36c-460e-a68b-92ab3c865b3e"
authors = ["David Muhr <muhrdavid@gmail.com> and contributors"]
version = "0.1.2"
version = "0.2.0"

[deps]
OutlierDetectionInterface = "1722ece6-f894-4ffc-b6be-6ca1174e2011"
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"

[compat]
OutlierDetectionInterface = "0.1"
PyCall = "1.92"
julia = "1"
PythonCall = "0.9"
julia = "1.6"

[extras]
OutlierDetectionTest = "66620973-d34b-445b-a614-4040704cad69"
Expand Down
6 changes: 6 additions & 0 deletions src/OutlierDetectionPython.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
module OutlierDetectionPython
using OutlierDetectionInterface
const OD = OutlierDetectionInterface
using PythonCall
const numpy = PythonCall.pynew()

function __init__()
PythonCall.pycopy!(numpy, pyimport("numpy"))
end

include("utils.jl")
include("models.jl")
Expand Down
78 changes: 73 additions & 5 deletions src/models.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,58 @@ end
$(make_docs_link("cof"))
"""
@pymodel mutable struct COFDetector <: UnsupervisedDetector
n_neighbors::Integer = 5::(_ > 0)
n_neighbors::Integer = 20::(_ > 0)
method::String = "fast"::(_ in ("fast", "memory"))
end

""" COPODDetector()
""" CDDetector(whitening = true,
rule_of_thumb = false)
$(make_docs_link("cd"))
"""
@pymodel mutable struct CDDetector <: UnsupervisedDetector
whitening::Bool = true
rule_of_thumb::Bool = false
end

""" COPODDetector(n_jobs = 1)
$(make_docs_link("copod"))
"""
@pymodel mutable struct COPODDetector <: UnsupervisedDetector end
@pymodel mutable struct COPODDetector <: UnsupervisedDetector
n_jobs::Integer = 1::(_ >= -1)
end

""" ECODDetector(n_jobs = 1)
$(make_docs_link("ecod"))
"""
@pymodel mutable struct ECODDetector <: UnsupervisedDetector
n_jobs = 1
end

""" GMMDetector(n_components=1,
covariance_type="full",
tol=0.001,
reg_covar=1e-06,
max_iter=100,
n_init=1,
init_params="kmeans",
weights_init=None,
means_init=None,
precisions_init=None,
random_state=None,
warm_start=False)
$(make_docs_link("gmm"))
"""
@pymodel mutable struct GMMDetector <: UnsupervisedDetector
n_components::Integer = 1::(_ > 0)
covariance_type::String = "full"::(_ in ("full", "tied", "diag", "spherical"))
tol::Real = 0.001::(_ > 0)
reg_covar::Real = 1e-06::(_ >= 0)
max_iter::Integer = 100::(_ > 0)
n_init::Integer = 1::(_ > 0)
init_params::String = "kmeans"::(_ in ("kemans", "random"))
random_state::Union{Nothing,Integer} = nothing
warm_start::Bool = false
end

""" HBOSDetector(n_bins = 10,
alpha = 0.1,
Expand All @@ -53,7 +97,6 @@ end
max_samples = "auto",
max_features = 1.0
bootstrap = false,
behaviour = "new",
random_state = nothing,
verbose = 0,
n_jobs = 1)
Expand All @@ -64,12 +107,37 @@ $(make_docs_link("iforest"))
max_samples::Union{String,Real} = "auto"
max_features::Real = 1.0
bootstrap::Bool = false
behaviour::String = "new"
random_state::Union{Nothing,Integer} = nothing
verbose::Integer = 0::(0 <= _ <= 2)
n_jobs::Integer = 1::(_ >= -1)
end

""" INNEDetector(n_estimators=200,
max_samples="auto",
random_state=None)
$(make_docs_link("inne"))
"""
@pymodel mutable struct INNEDetector <: UnsupervisedDetector
n_estimators::Integer = 200::(_ > 0)
max_samples::Union{Integer,Real,String} = "auto"
random_state::Union{Nothing,Integer} = nothing
end

""" KDEDetector(bandwidth=1.0,
algorithm="auto",
leaf_size=30,
metric="minkowski",
metric_params=None)
$(make_docs_link("kde"))
"""
@pymodel mutable struct KDEDetector <: UnsupervisedDetector
bandwidth::Real = 1.0::(_ > 0)
algorithm::String = "auto"::(_ in ("auto", "ball_tree", "kd_tree", "brute"))
leaf_size::Integer = 30::(_ > 0)
metric::String = "minkowski"::(_ in ("cityblock", "cosine", "euclidean", "l1", "l2", "manhatten", "braycurtis", "canberra", "chebyshev", "correlation", "dice", "hamming", "jaccard", "kulsinski", "mahalanobis", "matching", "minkowski", "rogerstanimoto", "russellrao", "seuclidean", "sokalmichener", "sokalsneath", "sqeuclidean", "yule"))
metric_params::Union{Nothing,Any} = nothing
end

""" KNNDetector(n_neighbors = 5,
method = "largest",
radius = 1.0,
Expand Down
13 changes: 6 additions & 7 deletions src/utils.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using PyCall:PyObject, PyReverseDims, pyimport_conda

struct PyModel <: DetectorModel
pyobject::PyObject
pyobject::Py
end

# lazily import a python model
Expand All @@ -10,7 +9,7 @@ pyod_import(name::String) = () -> begin
module_name = lowercase(raw_name)
# names are not consistently uppercased
model_name = raw_name == "IForest" ? raw_name : uppercase(raw_name)
getproperty(pyimport_conda("pyod.models.$(module_name)", "pyod", "conda-forge"), model_name)
getproperty(pyimport("pyod.models.$(module_name)"), model_name)
end
pyod_import(name::Symbol) = pyod_import(String(name))

Expand All @@ -22,12 +21,12 @@ function pyod_fit(modelname, params)
pymodelname = String(modelname)
quote
function OD.fit(model::$modelname, X::Data; verbosity)::Fit
Xt = PyReverseDims(X) # from column-major to row-major
Xt = numpy.array(PermutedDimsArray(X, (2,1))) # from column-major to row-major
# load the underlying python model with key-word arguments
detector = pyod_import($pymodelname)()($((Expr(:kw, p, :(model.$p)) for p in params)...))
detector.fit(Xt)
# the underlying python model is out model
return PyModel(detector), detector.decision_scores_
return PyModel(detector), pyconvert(Array, detector.decision_scores_)
end
end
end
Expand All @@ -36,9 +35,9 @@ end
function pyod_score(modelname)
quote
function OD.transform(_::$modelname, model::DetectorModel, X::Data)::Scores
Xt = PyReverseDims(X) # change from column-major to row-major
Xt = numpy.array(PermutedDimsArray(X, (2,1))) # from column-major to row-major
scores_test = model.pyobject.decision_function(Xt)
return scores_test
return pyconvert(Vector, scores_test)
end
end
end
Expand Down