xplia 1.0.1__py3-none-any.whl
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.
- venv/Lib/site-packages/_distutils_hack/__init__.py +222 -0
- venv/Lib/site-packages/_distutils_hack/override.py +1 -0
- venv/Lib/site-packages/pip/__init__.py +13 -0
- venv/Lib/site-packages/pip/__main__.py +24 -0
- venv/Lib/site-packages/pip/__pip-runner__.py +50 -0
- venv/Lib/site-packages/pip/_internal/__init__.py +19 -0
- venv/Lib/site-packages/pip/_internal/build_env.py +311 -0
- venv/Lib/site-packages/pip/_internal/cache.py +292 -0
- venv/Lib/site-packages/pip/_internal/cli/__init__.py +4 -0
- venv/Lib/site-packages/pip/_internal/cli/autocompletion.py +171 -0
- venv/Lib/site-packages/pip/_internal/cli/base_command.py +236 -0
- venv/Lib/site-packages/pip/_internal/cli/cmdoptions.py +1074 -0
- venv/Lib/site-packages/pip/_internal/cli/command_context.py +27 -0
- venv/Lib/site-packages/pip/_internal/cli/main.py +79 -0
- venv/Lib/site-packages/pip/_internal/cli/main_parser.py +134 -0
- venv/Lib/site-packages/pip/_internal/cli/parser.py +294 -0
- venv/Lib/site-packages/pip/_internal/cli/progress_bars.py +68 -0
- venv/Lib/site-packages/pip/_internal/cli/req_command.py +508 -0
- venv/Lib/site-packages/pip/_internal/cli/spinners.py +159 -0
- venv/Lib/site-packages/pip/_internal/cli/status_codes.py +6 -0
- venv/Lib/site-packages/pip/_internal/commands/__init__.py +132 -0
- venv/Lib/site-packages/pip/_internal/commands/cache.py +222 -0
- venv/Lib/site-packages/pip/_internal/commands/check.py +54 -0
- venv/Lib/site-packages/pip/_internal/commands/completion.py +121 -0
- venv/Lib/site-packages/pip/_internal/commands/configuration.py +282 -0
- venv/Lib/site-packages/pip/_internal/commands/debug.py +199 -0
- venv/Lib/site-packages/pip/_internal/commands/download.py +147 -0
- venv/Lib/site-packages/pip/_internal/commands/freeze.py +108 -0
- venv/Lib/site-packages/pip/_internal/commands/hash.py +59 -0
- venv/Lib/site-packages/pip/_internal/commands/help.py +41 -0
- venv/Lib/site-packages/pip/_internal/commands/index.py +139 -0
- venv/Lib/site-packages/pip/_internal/commands/inspect.py +92 -0
- venv/Lib/site-packages/pip/_internal/commands/install.py +778 -0
- venv/Lib/site-packages/pip/_internal/commands/list.py +368 -0
- venv/Lib/site-packages/pip/_internal/commands/search.py +174 -0
- venv/Lib/site-packages/pip/_internal/commands/show.py +189 -0
- venv/Lib/site-packages/pip/_internal/commands/uninstall.py +113 -0
- venv/Lib/site-packages/pip/_internal/commands/wheel.py +183 -0
- venv/Lib/site-packages/pip/_internal/configuration.py +381 -0
- venv/Lib/site-packages/pip/_internal/distributions/__init__.py +21 -0
- venv/Lib/site-packages/pip/_internal/distributions/base.py +39 -0
- venv/Lib/site-packages/pip/_internal/distributions/installed.py +23 -0
- venv/Lib/site-packages/pip/_internal/distributions/sdist.py +150 -0
- venv/Lib/site-packages/pip/_internal/distributions/wheel.py +34 -0
- venv/Lib/site-packages/pip/_internal/exceptions.py +733 -0
- venv/Lib/site-packages/pip/_internal/index/__init__.py +2 -0
- venv/Lib/site-packages/pip/_internal/index/collector.py +505 -0
- venv/Lib/site-packages/pip/_internal/index/package_finder.py +1029 -0
- venv/Lib/site-packages/pip/_internal/index/sources.py +223 -0
- venv/Lib/site-packages/pip/_internal/locations/__init__.py +467 -0
- venv/Lib/site-packages/pip/_internal/locations/_distutils.py +173 -0
- venv/Lib/site-packages/pip/_internal/locations/_sysconfig.py +213 -0
- venv/Lib/site-packages/pip/_internal/locations/base.py +81 -0
- venv/Lib/site-packages/pip/_internal/main.py +12 -0
- venv/Lib/site-packages/pip/_internal/metadata/__init__.py +127 -0
- venv/Lib/site-packages/pip/_internal/metadata/_json.py +84 -0
- venv/Lib/site-packages/pip/_internal/metadata/base.py +688 -0
- venv/Lib/site-packages/pip/_internal/metadata/importlib/__init__.py +4 -0
- venv/Lib/site-packages/pip/_internal/metadata/importlib/_compat.py +55 -0
- venv/Lib/site-packages/pip/_internal/metadata/importlib/_dists.py +224 -0
- venv/Lib/site-packages/pip/_internal/metadata/importlib/_envs.py +188 -0
- venv/Lib/site-packages/pip/_internal/metadata/pkg_resources.py +270 -0
- venv/Lib/site-packages/pip/_internal/models/__init__.py +2 -0
- venv/Lib/site-packages/pip/_internal/models/candidate.py +34 -0
- venv/Lib/site-packages/pip/_internal/models/direct_url.py +237 -0
- venv/Lib/site-packages/pip/_internal/models/format_control.py +80 -0
- venv/Lib/site-packages/pip/_internal/models/index.py +28 -0
- venv/Lib/site-packages/pip/_internal/models/installation_report.py +53 -0
- venv/Lib/site-packages/pip/_internal/models/link.py +581 -0
- venv/Lib/site-packages/pip/_internal/models/scheme.py +31 -0
- venv/Lib/site-packages/pip/_internal/models/search_scope.py +132 -0
- venv/Lib/site-packages/pip/_internal/models/selection_prefs.py +51 -0
- venv/Lib/site-packages/pip/_internal/models/target_python.py +110 -0
- venv/Lib/site-packages/pip/_internal/models/wheel.py +92 -0
- venv/Lib/site-packages/pip/_internal/network/__init__.py +2 -0
- venv/Lib/site-packages/pip/_internal/network/auth.py +561 -0
- venv/Lib/site-packages/pip/_internal/network/cache.py +69 -0
- venv/Lib/site-packages/pip/_internal/network/download.py +186 -0
- venv/Lib/site-packages/pip/_internal/network/lazy_wheel.py +210 -0
- venv/Lib/site-packages/pip/_internal/network/session.py +519 -0
- venv/Lib/site-packages/pip/_internal/network/utils.py +96 -0
- venv/Lib/site-packages/pip/_internal/network/xmlrpc.py +60 -0
- venv/Lib/site-packages/pip/_internal/operations/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/operations/build/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/operations/build/build_tracker.py +124 -0
- venv/Lib/site-packages/pip/_internal/operations/build/metadata.py +39 -0
- venv/Lib/site-packages/pip/_internal/operations/build/metadata_editable.py +41 -0
- venv/Lib/site-packages/pip/_internal/operations/build/metadata_legacy.py +74 -0
- venv/Lib/site-packages/pip/_internal/operations/build/wheel.py +37 -0
- venv/Lib/site-packages/pip/_internal/operations/build/wheel_editable.py +46 -0
- venv/Lib/site-packages/pip/_internal/operations/build/wheel_legacy.py +102 -0
- venv/Lib/site-packages/pip/_internal/operations/check.py +187 -0
- venv/Lib/site-packages/pip/_internal/operations/freeze.py +255 -0
- venv/Lib/site-packages/pip/_internal/operations/install/__init__.py +2 -0
- venv/Lib/site-packages/pip/_internal/operations/install/editable_legacy.py +46 -0
- venv/Lib/site-packages/pip/_internal/operations/install/wheel.py +740 -0
- venv/Lib/site-packages/pip/_internal/operations/prepare.py +743 -0
- venv/Lib/site-packages/pip/_internal/pyproject.py +179 -0
- venv/Lib/site-packages/pip/_internal/req/__init__.py +92 -0
- venv/Lib/site-packages/pip/_internal/req/constructors.py +506 -0
- venv/Lib/site-packages/pip/_internal/req/req_file.py +552 -0
- venv/Lib/site-packages/pip/_internal/req/req_install.py +874 -0
- venv/Lib/site-packages/pip/_internal/req/req_set.py +119 -0
- venv/Lib/site-packages/pip/_internal/req/req_uninstall.py +650 -0
- venv/Lib/site-packages/pip/_internal/resolution/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/resolution/base.py +20 -0
- venv/Lib/site-packages/pip/_internal/resolution/legacy/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/resolution/legacy/resolver.py +600 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/base.py +141 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/candidates.py +555 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/factory.py +730 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/found_candidates.py +155 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/provider.py +255 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/reporter.py +80 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/requirements.py +165 -0
- venv/Lib/site-packages/pip/_internal/resolution/resolvelib/resolver.py +299 -0
- venv/Lib/site-packages/pip/_internal/self_outdated_check.py +242 -0
- venv/Lib/site-packages/pip/_internal/utils/__init__.py +0 -0
- venv/Lib/site-packages/pip/_internal/utils/_jaraco_text.py +109 -0
- venv/Lib/site-packages/pip/_internal/utils/_log.py +38 -0
- venv/Lib/site-packages/pip/_internal/utils/appdirs.py +52 -0
- venv/Lib/site-packages/pip/_internal/utils/compat.py +63 -0
- venv/Lib/site-packages/pip/_internal/utils/compatibility_tags.py +165 -0
- venv/Lib/site-packages/pip/_internal/utils/datetime.py +11 -0
- venv/Lib/site-packages/pip/_internal/utils/deprecation.py +120 -0
- venv/Lib/site-packages/pip/_internal/utils/direct_url_helpers.py +87 -0
- venv/Lib/site-packages/pip/_internal/utils/egg_link.py +72 -0
- venv/Lib/site-packages/pip/_internal/utils/encoding.py +36 -0
- venv/Lib/site-packages/pip/_internal/utils/entrypoints.py +84 -0
- venv/Lib/site-packages/pip/_internal/utils/filesystem.py +153 -0
- venv/Lib/site-packages/pip/_internal/utils/filetypes.py +27 -0
- venv/Lib/site-packages/pip/_internal/utils/glibc.py +88 -0
- venv/Lib/site-packages/pip/_internal/utils/hashes.py +151 -0
- venv/Lib/site-packages/pip/_internal/utils/inject_securetransport.py +35 -0
- venv/Lib/site-packages/pip/_internal/utils/logging.py +348 -0
- venv/Lib/site-packages/pip/_internal/utils/misc.py +735 -0
- venv/Lib/site-packages/pip/_internal/utils/models.py +39 -0
- venv/Lib/site-packages/pip/_internal/utils/packaging.py +57 -0
- venv/Lib/site-packages/pip/_internal/utils/setuptools_build.py +146 -0
- venv/Lib/site-packages/pip/_internal/utils/subprocess.py +260 -0
- venv/Lib/site-packages/pip/_internal/utils/temp_dir.py +246 -0
- venv/Lib/site-packages/pip/_internal/utils/unpacking.py +257 -0
- venv/Lib/site-packages/pip/_internal/utils/urls.py +62 -0
- venv/Lib/site-packages/pip/_internal/utils/virtualenv.py +104 -0
- venv/Lib/site-packages/pip/_internal/utils/wheel.py +136 -0
- venv/Lib/site-packages/pip/_internal/vcs/__init__.py +15 -0
- venv/Lib/site-packages/pip/_internal/vcs/bazaar.py +112 -0
- venv/Lib/site-packages/pip/_internal/vcs/git.py +526 -0
- venv/Lib/site-packages/pip/_internal/vcs/mercurial.py +163 -0
- venv/Lib/site-packages/pip/_internal/vcs/subversion.py +324 -0
- venv/Lib/site-packages/pip/_internal/vcs/versioncontrol.py +705 -0
- venv/Lib/site-packages/pip/_internal/wheel_builder.py +355 -0
- venv/Lib/site-packages/pip/_vendor/__init__.py +120 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/__init__.py +18 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/_cmd.py +61 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/adapter.py +137 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/cache.py +65 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/__init__.py +9 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/file_cache.py +188 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/caches/redis_cache.py +39 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/compat.py +32 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/controller.py +439 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/filewrapper.py +111 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/heuristics.py +139 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/serialize.py +190 -0
- venv/Lib/site-packages/pip/_vendor/cachecontrol/wrapper.py +33 -0
- venv/Lib/site-packages/pip/_vendor/certifi/__init__.py +4 -0
- venv/Lib/site-packages/pip/_vendor/certifi/__main__.py +12 -0
- venv/Lib/site-packages/pip/_vendor/certifi/core.py +108 -0
- venv/Lib/site-packages/pip/_vendor/chardet/__init__.py +115 -0
- venv/Lib/site-packages/pip/_vendor/chardet/big5freq.py +386 -0
- venv/Lib/site-packages/pip/_vendor/chardet/big5prober.py +47 -0
- venv/Lib/site-packages/pip/_vendor/chardet/chardistribution.py +261 -0
- venv/Lib/site-packages/pip/_vendor/chardet/charsetgroupprober.py +106 -0
- venv/Lib/site-packages/pip/_vendor/chardet/charsetprober.py +147 -0
- venv/Lib/site-packages/pip/_vendor/chardet/cli/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/chardet/cli/chardetect.py +112 -0
- venv/Lib/site-packages/pip/_vendor/chardet/codingstatemachine.py +90 -0
- venv/Lib/site-packages/pip/_vendor/chardet/codingstatemachinedict.py +19 -0
- venv/Lib/site-packages/pip/_vendor/chardet/cp949prober.py +49 -0
- venv/Lib/site-packages/pip/_vendor/chardet/enums.py +85 -0
- venv/Lib/site-packages/pip/_vendor/chardet/escprober.py +102 -0
- venv/Lib/site-packages/pip/_vendor/chardet/escsm.py +261 -0
- venv/Lib/site-packages/pip/_vendor/chardet/eucjpprober.py +102 -0
- venv/Lib/site-packages/pip/_vendor/chardet/euckrfreq.py +196 -0
- venv/Lib/site-packages/pip/_vendor/chardet/euckrprober.py +47 -0
- venv/Lib/site-packages/pip/_vendor/chardet/euctwfreq.py +388 -0
- venv/Lib/site-packages/pip/_vendor/chardet/euctwprober.py +47 -0
- venv/Lib/site-packages/pip/_vendor/chardet/gb2312freq.py +284 -0
- venv/Lib/site-packages/pip/_vendor/chardet/gb2312prober.py +47 -0
- venv/Lib/site-packages/pip/_vendor/chardet/hebrewprober.py +316 -0
- venv/Lib/site-packages/pip/_vendor/chardet/jisfreq.py +325 -0
- venv/Lib/site-packages/pip/_vendor/chardet/johabfreq.py +2382 -0
- venv/Lib/site-packages/pip/_vendor/chardet/johabprober.py +47 -0
- venv/Lib/site-packages/pip/_vendor/chardet/jpcntx.py +238 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langbulgarianmodel.py +4649 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langgreekmodel.py +4397 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langhebrewmodel.py +4380 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langhungarianmodel.py +4649 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langrussianmodel.py +5725 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langthaimodel.py +4380 -0
- venv/Lib/site-packages/pip/_vendor/chardet/langturkishmodel.py +4380 -0
- venv/Lib/site-packages/pip/_vendor/chardet/latin1prober.py +147 -0
- venv/Lib/site-packages/pip/_vendor/chardet/macromanprober.py +162 -0
- venv/Lib/site-packages/pip/_vendor/chardet/mbcharsetprober.py +95 -0
- venv/Lib/site-packages/pip/_vendor/chardet/mbcsgroupprober.py +57 -0
- venv/Lib/site-packages/pip/_vendor/chardet/mbcssm.py +661 -0
- venv/Lib/site-packages/pip/_vendor/chardet/metadata/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/chardet/metadata/languages.py +352 -0
- venv/Lib/site-packages/pip/_vendor/chardet/resultdict.py +16 -0
- venv/Lib/site-packages/pip/_vendor/chardet/sbcharsetprober.py +162 -0
- venv/Lib/site-packages/pip/_vendor/chardet/sbcsgroupprober.py +88 -0
- venv/Lib/site-packages/pip/_vendor/chardet/sjisprober.py +105 -0
- venv/Lib/site-packages/pip/_vendor/chardet/universaldetector.py +362 -0
- venv/Lib/site-packages/pip/_vendor/chardet/utf1632prober.py +225 -0
- venv/Lib/site-packages/pip/_vendor/chardet/utf8prober.py +82 -0
- venv/Lib/site-packages/pip/_vendor/chardet/version.py +9 -0
- venv/Lib/site-packages/pip/_vendor/colorama/__init__.py +7 -0
- venv/Lib/site-packages/pip/_vendor/colorama/ansi.py +102 -0
- venv/Lib/site-packages/pip/_vendor/colorama/ansitowin32.py +277 -0
- venv/Lib/site-packages/pip/_vendor/colorama/initialise.py +121 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/__init__.py +1 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/ansi_test.py +76 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/ansitowin32_test.py +294 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/initialise_test.py +189 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/isatty_test.py +57 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/utils.py +49 -0
- venv/Lib/site-packages/pip/_vendor/colorama/tests/winterm_test.py +131 -0
- venv/Lib/site-packages/pip/_vendor/colorama/win32.py +180 -0
- venv/Lib/site-packages/pip/_vendor/colorama/winterm.py +195 -0
- venv/Lib/site-packages/pip/_vendor/distlib/__init__.py +23 -0
- venv/Lib/site-packages/pip/_vendor/distlib/compat.py +1116 -0
- venv/Lib/site-packages/pip/_vendor/distlib/database.py +1350 -0
- venv/Lib/site-packages/pip/_vendor/distlib/index.py +508 -0
- venv/Lib/site-packages/pip/_vendor/distlib/locators.py +1300 -0
- venv/Lib/site-packages/pip/_vendor/distlib/manifest.py +393 -0
- venv/Lib/site-packages/pip/_vendor/distlib/markers.py +152 -0
- venv/Lib/site-packages/pip/_vendor/distlib/metadata.py +1076 -0
- venv/Lib/site-packages/pip/_vendor/distlib/resources.py +358 -0
- venv/Lib/site-packages/pip/_vendor/distlib/scripts.py +437 -0
- venv/Lib/site-packages/pip/_vendor/distlib/util.py +1932 -0
- venv/Lib/site-packages/pip/_vendor/distlib/version.py +739 -0
- venv/Lib/site-packages/pip/_vendor/distlib/wheel.py +1082 -0
- venv/Lib/site-packages/pip/_vendor/distro/__init__.py +54 -0
- venv/Lib/site-packages/pip/_vendor/distro/__main__.py +4 -0
- venv/Lib/site-packages/pip/_vendor/distro/distro.py +1399 -0
- venv/Lib/site-packages/pip/_vendor/idna/__init__.py +44 -0
- venv/Lib/site-packages/pip/_vendor/idna/codec.py +112 -0
- venv/Lib/site-packages/pip/_vendor/idna/compat.py +13 -0
- venv/Lib/site-packages/pip/_vendor/idna/core.py +400 -0
- venv/Lib/site-packages/pip/_vendor/idna/idnadata.py +2151 -0
- venv/Lib/site-packages/pip/_vendor/idna/intranges.py +54 -0
- venv/Lib/site-packages/pip/_vendor/idna/package_data.py +2 -0
- venv/Lib/site-packages/pip/_vendor/idna/uts46data.py +8600 -0
- venv/Lib/site-packages/pip/_vendor/msgpack/__init__.py +57 -0
- venv/Lib/site-packages/pip/_vendor/msgpack/exceptions.py +48 -0
- venv/Lib/site-packages/pip/_vendor/msgpack/ext.py +193 -0
- venv/Lib/site-packages/pip/_vendor/msgpack/fallback.py +1010 -0
- venv/Lib/site-packages/pip/_vendor/packaging/__about__.py +26 -0
- venv/Lib/site-packages/pip/_vendor/packaging/__init__.py +25 -0
- venv/Lib/site-packages/pip/_vendor/packaging/_manylinux.py +301 -0
- venv/Lib/site-packages/pip/_vendor/packaging/_musllinux.py +136 -0
- venv/Lib/site-packages/pip/_vendor/packaging/_structures.py +61 -0
- venv/Lib/site-packages/pip/_vendor/packaging/markers.py +304 -0
- venv/Lib/site-packages/pip/_vendor/packaging/requirements.py +146 -0
- venv/Lib/site-packages/pip/_vendor/packaging/specifiers.py +802 -0
- venv/Lib/site-packages/pip/_vendor/packaging/tags.py +487 -0
- venv/Lib/site-packages/pip/_vendor/packaging/utils.py +136 -0
- venv/Lib/site-packages/pip/_vendor/packaging/version.py +504 -0
- venv/Lib/site-packages/pip/_vendor/pkg_resources/__init__.py +3361 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/__init__.py +566 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/__main__.py +53 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/android.py +210 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/api.py +223 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/macos.py +91 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/unix.py +223 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/version.py +4 -0
- venv/Lib/site-packages/pip/_vendor/platformdirs/windows.py +255 -0
- venv/Lib/site-packages/pip/_vendor/pygments/__init__.py +82 -0
- venv/Lib/site-packages/pip/_vendor/pygments/__main__.py +17 -0
- venv/Lib/site-packages/pip/_vendor/pygments/cmdline.py +668 -0
- venv/Lib/site-packages/pip/_vendor/pygments/console.py +70 -0
- venv/Lib/site-packages/pip/_vendor/pygments/filter.py +71 -0
- venv/Lib/site-packages/pip/_vendor/pygments/filters/__init__.py +940 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatter.py +124 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/__init__.py +158 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/_mapping.py +23 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/bbcode.py +108 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/groff.py +170 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/html.py +989 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/img.py +645 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/irc.py +154 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/latex.py +521 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/other.py +161 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/pangomarkup.py +83 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/rtf.py +146 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/svg.py +188 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal.py +127 -0
- venv/Lib/site-packages/pip/_vendor/pygments/formatters/terminal256.py +338 -0
- venv/Lib/site-packages/pip/_vendor/pygments/lexer.py +943 -0
- venv/Lib/site-packages/pip/_vendor/pygments/lexers/__init__.py +362 -0
- venv/Lib/site-packages/pip/_vendor/pygments/lexers/_mapping.py +559 -0
- venv/Lib/site-packages/pip/_vendor/pygments/lexers/python.py +1198 -0
- venv/Lib/site-packages/pip/_vendor/pygments/modeline.py +43 -0
- venv/Lib/site-packages/pip/_vendor/pygments/plugin.py +88 -0
- venv/Lib/site-packages/pip/_vendor/pygments/regexopt.py +91 -0
- venv/Lib/site-packages/pip/_vendor/pygments/scanner.py +104 -0
- venv/Lib/site-packages/pip/_vendor/pygments/sphinxext.py +217 -0
- venv/Lib/site-packages/pip/_vendor/pygments/style.py +197 -0
- venv/Lib/site-packages/pip/_vendor/pygments/styles/__init__.py +103 -0
- venv/Lib/site-packages/pip/_vendor/pygments/token.py +213 -0
- venv/Lib/site-packages/pip/_vendor/pygments/unistring.py +153 -0
- venv/Lib/site-packages/pip/_vendor/pygments/util.py +330 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/__init__.py +322 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/actions.py +217 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/common.py +432 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/core.py +6115 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/diagram/__init__.py +656 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/exceptions.py +299 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/helpers.py +1100 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/results.py +796 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/testing.py +331 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/unicode.py +361 -0
- venv/Lib/site-packages/pip/_vendor/pyparsing/util.py +284 -0
- venv/Lib/site-packages/pip/_vendor/pyproject_hooks/__init__.py +23 -0
- venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_compat.py +8 -0
- venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_impl.py +330 -0
- venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/__init__.py +18 -0
- venv/Lib/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py +353 -0
- venv/Lib/site-packages/pip/_vendor/requests/__init__.py +182 -0
- venv/Lib/site-packages/pip/_vendor/requests/__version__.py +14 -0
- venv/Lib/site-packages/pip/_vendor/requests/_internal_utils.py +50 -0
- venv/Lib/site-packages/pip/_vendor/requests/adapters.py +538 -0
- venv/Lib/site-packages/pip/_vendor/requests/api.py +157 -0
- venv/Lib/site-packages/pip/_vendor/requests/auth.py +315 -0
- venv/Lib/site-packages/pip/_vendor/requests/certs.py +24 -0
- venv/Lib/site-packages/pip/_vendor/requests/compat.py +67 -0
- venv/Lib/site-packages/pip/_vendor/requests/cookies.py +561 -0
- venv/Lib/site-packages/pip/_vendor/requests/exceptions.py +141 -0
- venv/Lib/site-packages/pip/_vendor/requests/help.py +131 -0
- venv/Lib/site-packages/pip/_vendor/requests/hooks.py +33 -0
- venv/Lib/site-packages/pip/_vendor/requests/models.py +1034 -0
- venv/Lib/site-packages/pip/_vendor/requests/packages.py +16 -0
- venv/Lib/site-packages/pip/_vendor/requests/sessions.py +833 -0
- venv/Lib/site-packages/pip/_vendor/requests/status_codes.py +128 -0
- venv/Lib/site-packages/pip/_vendor/requests/structures.py +99 -0
- venv/Lib/site-packages/pip/_vendor/requests/utils.py +1094 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/__init__.py +26 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/compat/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/compat/collections_abc.py +6 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/providers.py +133 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/reporters.py +43 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/resolvers.py +547 -0
- venv/Lib/site-packages/pip/_vendor/resolvelib/structs.py +170 -0
- venv/Lib/site-packages/pip/_vendor/rich/__init__.py +177 -0
- venv/Lib/site-packages/pip/_vendor/rich/__main__.py +274 -0
- venv/Lib/site-packages/pip/_vendor/rich/_cell_widths.py +451 -0
- venv/Lib/site-packages/pip/_vendor/rich/_emoji_codes.py +3610 -0
- venv/Lib/site-packages/pip/_vendor/rich/_emoji_replace.py +32 -0
- venv/Lib/site-packages/pip/_vendor/rich/_export_format.py +76 -0
- venv/Lib/site-packages/pip/_vendor/rich/_extension.py +10 -0
- venv/Lib/site-packages/pip/_vendor/rich/_fileno.py +24 -0
- venv/Lib/site-packages/pip/_vendor/rich/_inspect.py +270 -0
- venv/Lib/site-packages/pip/_vendor/rich/_log_render.py +94 -0
- venv/Lib/site-packages/pip/_vendor/rich/_loop.py +43 -0
- venv/Lib/site-packages/pip/_vendor/rich/_null_file.py +69 -0
- venv/Lib/site-packages/pip/_vendor/rich/_palettes.py +309 -0
- venv/Lib/site-packages/pip/_vendor/rich/_pick.py +17 -0
- venv/Lib/site-packages/pip/_vendor/rich/_ratio.py +160 -0
- venv/Lib/site-packages/pip/_vendor/rich/_spinners.py +482 -0
- venv/Lib/site-packages/pip/_vendor/rich/_stack.py +16 -0
- venv/Lib/site-packages/pip/_vendor/rich/_timer.py +19 -0
- venv/Lib/site-packages/pip/_vendor/rich/_win32_console.py +662 -0
- venv/Lib/site-packages/pip/_vendor/rich/_windows.py +72 -0
- venv/Lib/site-packages/pip/_vendor/rich/_windows_renderer.py +56 -0
- venv/Lib/site-packages/pip/_vendor/rich/_wrap.py +56 -0
- venv/Lib/site-packages/pip/_vendor/rich/abc.py +33 -0
- venv/Lib/site-packages/pip/_vendor/rich/align.py +311 -0
- venv/Lib/site-packages/pip/_vendor/rich/ansi.py +240 -0
- venv/Lib/site-packages/pip/_vendor/rich/bar.py +94 -0
- venv/Lib/site-packages/pip/_vendor/rich/box.py +517 -0
- venv/Lib/site-packages/pip/_vendor/rich/cells.py +154 -0
- venv/Lib/site-packages/pip/_vendor/rich/color.py +622 -0
- venv/Lib/site-packages/pip/_vendor/rich/color_triplet.py +38 -0
- venv/Lib/site-packages/pip/_vendor/rich/columns.py +187 -0
- venv/Lib/site-packages/pip/_vendor/rich/console.py +2633 -0
- venv/Lib/site-packages/pip/_vendor/rich/constrain.py +37 -0
- venv/Lib/site-packages/pip/_vendor/rich/containers.py +167 -0
- venv/Lib/site-packages/pip/_vendor/rich/control.py +225 -0
- venv/Lib/site-packages/pip/_vendor/rich/default_styles.py +190 -0
- venv/Lib/site-packages/pip/_vendor/rich/diagnose.py +37 -0
- venv/Lib/site-packages/pip/_vendor/rich/emoji.py +96 -0
- venv/Lib/site-packages/pip/_vendor/rich/errors.py +34 -0
- venv/Lib/site-packages/pip/_vendor/rich/file_proxy.py +57 -0
- venv/Lib/site-packages/pip/_vendor/rich/filesize.py +89 -0
- venv/Lib/site-packages/pip/_vendor/rich/highlighter.py +232 -0
- venv/Lib/site-packages/pip/_vendor/rich/json.py +140 -0
- venv/Lib/site-packages/pip/_vendor/rich/jupyter.py +101 -0
- venv/Lib/site-packages/pip/_vendor/rich/layout.py +443 -0
- venv/Lib/site-packages/pip/_vendor/rich/live.py +375 -0
- venv/Lib/site-packages/pip/_vendor/rich/live_render.py +113 -0
- venv/Lib/site-packages/pip/_vendor/rich/logging.py +289 -0
- venv/Lib/site-packages/pip/_vendor/rich/markup.py +246 -0
- venv/Lib/site-packages/pip/_vendor/rich/measure.py +151 -0
- venv/Lib/site-packages/pip/_vendor/rich/padding.py +141 -0
- venv/Lib/site-packages/pip/_vendor/rich/pager.py +34 -0
- venv/Lib/site-packages/pip/_vendor/rich/palette.py +100 -0
- venv/Lib/site-packages/pip/_vendor/rich/panel.py +308 -0
- venv/Lib/site-packages/pip/_vendor/rich/pretty.py +994 -0
- venv/Lib/site-packages/pip/_vendor/rich/progress.py +1702 -0
- venv/Lib/site-packages/pip/_vendor/rich/progress_bar.py +224 -0
- venv/Lib/site-packages/pip/_vendor/rich/prompt.py +376 -0
- venv/Lib/site-packages/pip/_vendor/rich/protocol.py +42 -0
- venv/Lib/site-packages/pip/_vendor/rich/region.py +10 -0
- venv/Lib/site-packages/pip/_vendor/rich/repr.py +149 -0
- venv/Lib/site-packages/pip/_vendor/rich/rule.py +130 -0
- venv/Lib/site-packages/pip/_vendor/rich/scope.py +86 -0
- venv/Lib/site-packages/pip/_vendor/rich/screen.py +54 -0
- venv/Lib/site-packages/pip/_vendor/rich/segment.py +739 -0
- venv/Lib/site-packages/pip/_vendor/rich/spinner.py +137 -0
- venv/Lib/site-packages/pip/_vendor/rich/status.py +132 -0
- venv/Lib/site-packages/pip/_vendor/rich/style.py +796 -0
- venv/Lib/site-packages/pip/_vendor/rich/styled.py +42 -0
- venv/Lib/site-packages/pip/_vendor/rich/syntax.py +948 -0
- venv/Lib/site-packages/pip/_vendor/rich/table.py +1002 -0
- venv/Lib/site-packages/pip/_vendor/rich/terminal_theme.py +153 -0
- venv/Lib/site-packages/pip/_vendor/rich/text.py +1307 -0
- venv/Lib/site-packages/pip/_vendor/rich/theme.py +115 -0
- venv/Lib/site-packages/pip/_vendor/rich/themes.py +5 -0
- venv/Lib/site-packages/pip/_vendor/rich/traceback.py +756 -0
- venv/Lib/site-packages/pip/_vendor/rich/tree.py +251 -0
- venv/Lib/site-packages/pip/_vendor/six.py +998 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/__init__.py +608 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/_asyncio.py +94 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/_utils.py +76 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/after.py +51 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/before.py +46 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/before_sleep.py +71 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/nap.py +43 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/retry.py +272 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/stop.py +103 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/tornadoweb.py +59 -0
- venv/Lib/site-packages/pip/_vendor/tenacity/wait.py +228 -0
- venv/Lib/site-packages/pip/_vendor/tomli/__init__.py +11 -0
- venv/Lib/site-packages/pip/_vendor/tomli/_parser.py +691 -0
- venv/Lib/site-packages/pip/_vendor/tomli/_re.py +107 -0
- venv/Lib/site-packages/pip/_vendor/tomli/_types.py +10 -0
- venv/Lib/site-packages/pip/_vendor/typing_extensions.py +3072 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/__init__.py +102 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/_collections.py +337 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/_version.py +2 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/connection.py +572 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/connectionpool.py +1132 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_appengine_environ.py +36 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/bindings.py +519 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/_securetransport/low_level.py +397 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/appengine.py +314 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/ntlmpool.py +130 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/pyopenssl.py +518 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/securetransport.py +921 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/contrib/socks.py +216 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py +323 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/fields.py +274 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py +98 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/packages/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/__init__.py +0 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/makefile.py +51 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/packages/backports/weakref_finalize.py +155 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/packages/six.py +1076 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py +537 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/request.py +170 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/response.py +879 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/__init__.py +49 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/connection.py +149 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/proxy.py +57 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/queue.py +22 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/request.py +137 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/response.py +107 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/retry.py +620 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_.py +495 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/ssl_match_hostname.py +159 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/ssltransport.py +221 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/timeout.py +271 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/url.py +435 -0
- venv/Lib/site-packages/pip/_vendor/urllib3/util/wait.py +152 -0
- venv/Lib/site-packages/pip/_vendor/webencodings/__init__.py +342 -0
- venv/Lib/site-packages/pip/_vendor/webencodings/labels.py +231 -0
- venv/Lib/site-packages/pip/_vendor/webencodings/mklabels.py +59 -0
- venv/Lib/site-packages/pip/_vendor/webencodings/tests.py +153 -0
- venv/Lib/site-packages/pip/_vendor/webencodings/x_user_defined.py +325 -0
- venv/Lib/site-packages/pip/py.typed +4 -0
- venv/Lib/site-packages/pkg_resources/__init__.py +3296 -0
- venv/Lib/site-packages/pkg_resources/_vendor/__init__.py +0 -0
- venv/Lib/site-packages/pkg_resources/_vendor/appdirs.py +608 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/__init__.py +36 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_adapters.py +170 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_common.py +104 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_compat.py +98 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_itertools.py +35 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/_legacy.py +121 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/abc.py +137 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/readers.py +122 -0
- venv/Lib/site-packages/pkg_resources/_vendor/importlib_resources/simple.py +116 -0
- venv/Lib/site-packages/pkg_resources/_vendor/jaraco/__init__.py +0 -0
- venv/Lib/site-packages/pkg_resources/_vendor/jaraco/context.py +213 -0
- venv/Lib/site-packages/pkg_resources/_vendor/jaraco/functools.py +525 -0
- venv/Lib/site-packages/pkg_resources/_vendor/jaraco/text/__init__.py +599 -0
- venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/__init__.py +4 -0
- venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/more.py +4316 -0
- venv/Lib/site-packages/pkg_resources/_vendor/more_itertools/recipes.py +698 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/__about__.py +26 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/__init__.py +25 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/_manylinux.py +301 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/_musllinux.py +136 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/_structures.py +61 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/markers.py +304 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/requirements.py +146 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/specifiers.py +802 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/tags.py +487 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/utils.py +136 -0
- venv/Lib/site-packages/pkg_resources/_vendor/packaging/version.py +504 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/__init__.py +331 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/actions.py +207 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/common.py +424 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/core.py +5814 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/diagram/__init__.py +642 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/exceptions.py +267 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/helpers.py +1088 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/results.py +760 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/testing.py +331 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/unicode.py +352 -0
- venv/Lib/site-packages/pkg_resources/_vendor/pyparsing/util.py +235 -0
- venv/Lib/site-packages/pkg_resources/_vendor/zipp.py +329 -0
- venv/Lib/site-packages/pkg_resources/extern/__init__.py +76 -0
- venv/Lib/site-packages/setuptools/__init__.py +247 -0
- venv/Lib/site-packages/setuptools/_deprecation_warning.py +7 -0
- venv/Lib/site-packages/setuptools/_distutils/__init__.py +24 -0
- venv/Lib/site-packages/setuptools/_distutils/_collections.py +56 -0
- venv/Lib/site-packages/setuptools/_distutils/_functools.py +20 -0
- venv/Lib/site-packages/setuptools/_distutils/_macos_compat.py +12 -0
- venv/Lib/site-packages/setuptools/_distutils/_msvccompiler.py +572 -0
- venv/Lib/site-packages/setuptools/_distutils/archive_util.py +280 -0
- venv/Lib/site-packages/setuptools/_distutils/bcppcompiler.py +408 -0
- venv/Lib/site-packages/setuptools/_distutils/ccompiler.py +1220 -0
- venv/Lib/site-packages/setuptools/_distutils/cmd.py +436 -0
- venv/Lib/site-packages/setuptools/_distutils/command/__init__.py +25 -0
- venv/Lib/site-packages/setuptools/_distutils/command/_framework_compat.py +55 -0
- venv/Lib/site-packages/setuptools/_distutils/command/bdist.py +157 -0
- venv/Lib/site-packages/setuptools/_distutils/command/bdist_dumb.py +144 -0
- venv/Lib/site-packages/setuptools/_distutils/command/bdist_rpm.py +615 -0
- venv/Lib/site-packages/setuptools/_distutils/command/build.py +153 -0
- venv/Lib/site-packages/setuptools/_distutils/command/build_clib.py +208 -0
- venv/Lib/site-packages/setuptools/_distutils/command/build_ext.py +787 -0
- venv/Lib/site-packages/setuptools/_distutils/command/build_py.py +407 -0
- venv/Lib/site-packages/setuptools/_distutils/command/build_scripts.py +173 -0
- venv/Lib/site-packages/setuptools/_distutils/command/check.py +151 -0
- venv/Lib/site-packages/setuptools/_distutils/command/clean.py +76 -0
- venv/Lib/site-packages/setuptools/_distutils/command/config.py +377 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install.py +814 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install_data.py +84 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install_egg_info.py +91 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install_headers.py +45 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install_lib.py +238 -0
- venv/Lib/site-packages/setuptools/_distutils/command/install_scripts.py +61 -0
- venv/Lib/site-packages/setuptools/_distutils/command/py37compat.py +31 -0
- venv/Lib/site-packages/setuptools/_distutils/command/register.py +319 -0
- venv/Lib/site-packages/setuptools/_distutils/command/sdist.py +531 -0
- venv/Lib/site-packages/setuptools/_distutils/command/upload.py +205 -0
- venv/Lib/site-packages/setuptools/_distutils/config.py +139 -0
- venv/Lib/site-packages/setuptools/_distutils/core.py +291 -0
- venv/Lib/site-packages/setuptools/_distutils/cygwinccompiler.py +364 -0
- venv/Lib/site-packages/setuptools/_distutils/debug.py +5 -0
- venv/Lib/site-packages/setuptools/_distutils/dep_util.py +96 -0
- venv/Lib/site-packages/setuptools/_distutils/dir_util.py +243 -0
- venv/Lib/site-packages/setuptools/_distutils/dist.py +1286 -0
- venv/Lib/site-packages/setuptools/_distutils/errors.py +127 -0
- venv/Lib/site-packages/setuptools/_distutils/extension.py +248 -0
- venv/Lib/site-packages/setuptools/_distutils/fancy_getopt.py +470 -0
- venv/Lib/site-packages/setuptools/_distutils/file_util.py +249 -0
- venv/Lib/site-packages/setuptools/_distutils/filelist.py +371 -0
- venv/Lib/site-packages/setuptools/_distutils/log.py +80 -0
- venv/Lib/site-packages/setuptools/_distutils/msvc9compiler.py +832 -0
- venv/Lib/site-packages/setuptools/_distutils/msvccompiler.py +695 -0
- venv/Lib/site-packages/setuptools/_distutils/py38compat.py +8 -0
- venv/Lib/site-packages/setuptools/_distutils/py39compat.py +22 -0
- venv/Lib/site-packages/setuptools/_distutils/spawn.py +109 -0
- venv/Lib/site-packages/setuptools/_distutils/sysconfig.py +558 -0
- venv/Lib/site-packages/setuptools/_distutils/text_file.py +287 -0
- venv/Lib/site-packages/setuptools/_distutils/unixccompiler.py +401 -0
- venv/Lib/site-packages/setuptools/_distutils/util.py +513 -0
- venv/Lib/site-packages/setuptools/_distutils/version.py +358 -0
- venv/Lib/site-packages/setuptools/_distutils/versionpredicate.py +175 -0
- venv/Lib/site-packages/setuptools/_entry_points.py +86 -0
- venv/Lib/site-packages/setuptools/_imp.py +82 -0
- venv/Lib/site-packages/setuptools/_importlib.py +47 -0
- venv/Lib/site-packages/setuptools/_itertools.py +23 -0
- venv/Lib/site-packages/setuptools/_path.py +29 -0
- venv/Lib/site-packages/setuptools/_reqs.py +19 -0
- venv/Lib/site-packages/setuptools/_vendor/__init__.py +0 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/__init__.py +1047 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_adapters.py +68 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_collections.py +30 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_compat.py +71 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_functools.py +104 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_itertools.py +73 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_meta.py +48 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_metadata/_text.py +99 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/__init__.py +36 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_adapters.py +170 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_common.py +104 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_compat.py +98 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_itertools.py +35 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/_legacy.py +121 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/abc.py +137 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/readers.py +122 -0
- venv/Lib/site-packages/setuptools/_vendor/importlib_resources/simple.py +116 -0
- venv/Lib/site-packages/setuptools/_vendor/jaraco/__init__.py +0 -0
- venv/Lib/site-packages/setuptools/_vendor/jaraco/context.py +213 -0
- venv/Lib/site-packages/setuptools/_vendor/jaraco/functools.py +525 -0
- venv/Lib/site-packages/setuptools/_vendor/jaraco/text/__init__.py +599 -0
- venv/Lib/site-packages/setuptools/_vendor/more_itertools/__init__.py +4 -0
- venv/Lib/site-packages/setuptools/_vendor/more_itertools/more.py +3824 -0
- venv/Lib/site-packages/setuptools/_vendor/more_itertools/recipes.py +620 -0
- venv/Lib/site-packages/setuptools/_vendor/ordered_set.py +488 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/__about__.py +26 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/__init__.py +25 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/_manylinux.py +301 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/_musllinux.py +136 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/_structures.py +61 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/markers.py +304 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/requirements.py +146 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/specifiers.py +802 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/tags.py +487 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/utils.py +136 -0
- venv/Lib/site-packages/setuptools/_vendor/packaging/version.py +504 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/__init__.py +331 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/actions.py +207 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/common.py +424 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/core.py +5814 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/diagram/__init__.py +642 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/exceptions.py +267 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/helpers.py +1088 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/results.py +760 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/testing.py +331 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/unicode.py +352 -0
- venv/Lib/site-packages/setuptools/_vendor/pyparsing/util.py +235 -0
- venv/Lib/site-packages/setuptools/_vendor/tomli/__init__.py +11 -0
- venv/Lib/site-packages/setuptools/_vendor/tomli/_parser.py +691 -0
- venv/Lib/site-packages/setuptools/_vendor/tomli/_re.py +107 -0
- venv/Lib/site-packages/setuptools/_vendor/tomli/_types.py +10 -0
- venv/Lib/site-packages/setuptools/_vendor/typing_extensions.py +2296 -0
- venv/Lib/site-packages/setuptools/_vendor/zipp.py +329 -0
- venv/Lib/site-packages/setuptools/archive_util.py +213 -0
- venv/Lib/site-packages/setuptools/build_meta.py +511 -0
- venv/Lib/site-packages/setuptools/command/__init__.py +12 -0
- venv/Lib/site-packages/setuptools/command/alias.py +78 -0
- venv/Lib/site-packages/setuptools/command/bdist_egg.py +457 -0
- venv/Lib/site-packages/setuptools/command/bdist_rpm.py +40 -0
- venv/Lib/site-packages/setuptools/command/build.py +146 -0
- venv/Lib/site-packages/setuptools/command/build_clib.py +101 -0
- venv/Lib/site-packages/setuptools/command/build_ext.py +383 -0
- venv/Lib/site-packages/setuptools/command/build_py.py +368 -0
- venv/Lib/site-packages/setuptools/command/develop.py +193 -0
- venv/Lib/site-packages/setuptools/command/dist_info.py +142 -0
- venv/Lib/site-packages/setuptools/command/easy_install.py +2312 -0
- venv/Lib/site-packages/setuptools/command/editable_wheel.py +844 -0
- venv/Lib/site-packages/setuptools/command/egg_info.py +763 -0
- venv/Lib/site-packages/setuptools/command/install.py +139 -0
- venv/Lib/site-packages/setuptools/command/install_egg_info.py +63 -0
- venv/Lib/site-packages/setuptools/command/install_lib.py +122 -0
- venv/Lib/site-packages/setuptools/command/install_scripts.py +70 -0
- venv/Lib/site-packages/setuptools/command/py36compat.py +134 -0
- venv/Lib/site-packages/setuptools/command/register.py +18 -0
- venv/Lib/site-packages/setuptools/command/rotate.py +64 -0
- venv/Lib/site-packages/setuptools/command/saveopts.py +22 -0
- venv/Lib/site-packages/setuptools/command/sdist.py +210 -0
- venv/Lib/site-packages/setuptools/command/setopt.py +149 -0
- venv/Lib/site-packages/setuptools/command/test.py +251 -0
- venv/Lib/site-packages/setuptools/command/upload.py +17 -0
- venv/Lib/site-packages/setuptools/command/upload_docs.py +213 -0
- venv/Lib/site-packages/setuptools/config/__init__.py +35 -0
- venv/Lib/site-packages/setuptools/config/_apply_pyprojecttoml.py +377 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/__init__.py +34 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/error_reporting.py +318 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/extra_validations.py +36 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_exceptions.py +51 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/fastjsonschema_validations.py +1035 -0
- venv/Lib/site-packages/setuptools/config/_validate_pyproject/formats.py +259 -0
- venv/Lib/site-packages/setuptools/config/expand.py +462 -0
- venv/Lib/site-packages/setuptools/config/pyprojecttoml.py +493 -0
- venv/Lib/site-packages/setuptools/config/setupcfg.py +762 -0
- venv/Lib/site-packages/setuptools/dep_util.py +25 -0
- venv/Lib/site-packages/setuptools/depends.py +176 -0
- venv/Lib/site-packages/setuptools/discovery.py +600 -0
- venv/Lib/site-packages/setuptools/dist.py +1222 -0
- venv/Lib/site-packages/setuptools/errors.py +58 -0
- venv/Lib/site-packages/setuptools/extension.py +148 -0
- venv/Lib/site-packages/setuptools/extern/__init__.py +76 -0
- venv/Lib/site-packages/setuptools/glob.py +167 -0
- venv/Lib/site-packages/setuptools/installer.py +104 -0
- venv/Lib/site-packages/setuptools/launch.py +36 -0
- venv/Lib/site-packages/setuptools/logging.py +36 -0
- venv/Lib/site-packages/setuptools/monkey.py +165 -0
- venv/Lib/site-packages/setuptools/msvc.py +1703 -0
- venv/Lib/site-packages/setuptools/namespaces.py +107 -0
- venv/Lib/site-packages/setuptools/package_index.py +1126 -0
- venv/Lib/site-packages/setuptools/py34compat.py +13 -0
- venv/Lib/site-packages/setuptools/sandbox.py +530 -0
- venv/Lib/site-packages/setuptools/unicode_utils.py +42 -0
- venv/Lib/site-packages/setuptools/version.py +6 -0
- venv/Lib/site-packages/setuptools/wheel.py +222 -0
- venv/Lib/site-packages/setuptools/windows_support.py +29 -0
- xplia/__init__.py +72 -0
- xplia/api/__init__.py +432 -0
- xplia/api/fastapi_app.py +453 -0
- xplia/cli.py +321 -0
- xplia/compliance/__init__.py +39 -0
- xplia/compliance/ai_act.py +538 -0
- xplia/compliance/compliance_checker.py +511 -0
- xplia/compliance/compliance_report.py +236 -0
- xplia/compliance/expert_review/__init__.py +18 -0
- xplia/compliance/expert_review/evaluation_criteria.py +209 -0
- xplia/compliance/expert_review/integration.py +270 -0
- xplia/compliance/expert_review/trust_expert_evaluator.py +379 -0
- xplia/compliance/explanation_rights.py +45 -0
- xplia/compliance/formatters/__init__.py +35 -0
- xplia/compliance/formatters/csv_formatter.py +179 -0
- xplia/compliance/formatters/html_formatter.py +689 -0
- xplia/compliance/formatters/html_trust_formatter.py +147 -0
- xplia/compliance/formatters/json_formatter.py +107 -0
- xplia/compliance/formatters/pdf_formatter.py +641 -0
- xplia/compliance/formatters/pdf_trust_formatter.py +309 -0
- xplia/compliance/formatters/trust_formatter_mixin.py +267 -0
- xplia/compliance/formatters/xml_formatter.py +173 -0
- xplia/compliance/gdpr.py +803 -0
- xplia/compliance/hipaa.py +134 -0
- xplia/compliance/report_base.py +205 -0
- xplia/compliance/report_generator.py +820 -0
- xplia/compliance/translations.py +299 -0
- xplia/core/__init__.py +98 -0
- xplia/core/base.py +391 -0
- xplia/core/config.py +297 -0
- xplia/core/factory.py +416 -0
- xplia/core/model_adapters/__init__.py +47 -0
- xplia/core/model_adapters/base.py +160 -0
- xplia/core/model_adapters/pytorch_adapter.py +339 -0
- xplia/core/model_adapters/sklearn_adapter.py +215 -0
- xplia/core/model_adapters/tensorflow_adapter.py +280 -0
- xplia/core/model_adapters/xgboost_adapter.py +295 -0
- xplia/core/optimizations.py +322 -0
- xplia/core/performance/__init__.py +57 -0
- xplia/core/performance/cache_manager.py +502 -0
- xplia/core/performance/memory_optimizer.py +465 -0
- xplia/core/performance/parallel_executor.py +327 -0
- xplia/core/registry.py +1234 -0
- xplia/explainers/__init__.py +70 -0
- xplia/explainers/__init__updated.py +62 -0
- xplia/explainers/adaptive/__init__.py +24 -0
- xplia/explainers/adaptive/explainer_selector.py +405 -0
- xplia/explainers/adaptive/explanation_quality.py +395 -0
- xplia/explainers/adaptive/fusion_strategies.py +297 -0
- xplia/explainers/adaptive/meta_explainer.py +320 -0
- xplia/explainers/adversarial/__init__.py +21 -0
- xplia/explainers/adversarial/adversarial_xai.py +678 -0
- xplia/explainers/anchor_explainer.py +1769 -0
- xplia/explainers/attention_explainer.py +996 -0
- xplia/explainers/bayesian/__init__.py +8 -0
- xplia/explainers/bayesian/bayesian_explainer.py +127 -0
- xplia/explainers/bias/__init__.py +17 -0
- xplia/explainers/bias/advanced_bias_detection.py +934 -0
- xplia/explainers/calibration/__init__.py +26 -0
- xplia/explainers/calibration/audience_adapter.py +376 -0
- xplia/explainers/calibration/audience_profiles.py +372 -0
- xplia/explainers/calibration/calibration_metrics.py +299 -0
- xplia/explainers/calibration/explanation_calibrator.py +460 -0
- xplia/explainers/causal/__init__.py +19 -0
- xplia/explainers/causal/causal_inference.py +669 -0
- xplia/explainers/certified/__init__.py +21 -0
- xplia/explainers/certified/certified_explanations.py +619 -0
- xplia/explainers/continual/__init__.py +8 -0
- xplia/explainers/continual/continual_explainer.py +102 -0
- xplia/explainers/counterfactual_explainer.py +804 -0
- xplia/explainers/counterfactuals/__init__.py +17 -0
- xplia/explainers/counterfactuals/advanced_counterfactuals.py +259 -0
- xplia/explainers/expert_evaluator.py +538 -0
- xplia/explainers/feature_importance_explainer.py +376 -0
- xplia/explainers/federated/__init__.py +17 -0
- xplia/explainers/federated/federated_xai.py +664 -0
- xplia/explainers/generative/__init__.py +15 -0
- xplia/explainers/generative/generative_explainer.py +243 -0
- xplia/explainers/gradient_explainer.py +3590 -0
- xplia/explainers/graph/__init__.py +26 -0
- xplia/explainers/graph/gnn_explainer.py +638 -0
- xplia/explainers/graph/molecular_explainer.py +438 -0
- xplia/explainers/lime_explainer.py +1580 -0
- xplia/explainers/llm/__init__.py +23 -0
- xplia/explainers/llm/llm_explainability.py +737 -0
- xplia/explainers/metalearning/__init__.py +15 -0
- xplia/explainers/metalearning/metalearning_explainer.py +276 -0
- xplia/explainers/moe/__init__.py +8 -0
- xplia/explainers/moe/moe_explainer.py +108 -0
- xplia/explainers/multimodal/__init__.py +30 -0
- xplia/explainers/multimodal/base.py +262 -0
- xplia/explainers/multimodal/diffusion_explainer.py +608 -0
- xplia/explainers/multimodal/foundation_model_explainer.py +323 -0
- xplia/explainers/multimodal/registry.py +139 -0
- xplia/explainers/multimodal/text_image_explainer.py +381 -0
- xplia/explainers/multimodal/vision_language_explainer.py +608 -0
- xplia/explainers/nas/__init__.py +5 -0
- xplia/explainers/nas/nas_explainer.py +65 -0
- xplia/explainers/neuralodes/__init__.py +5 -0
- xplia/explainers/neuralodes/neuralode_explainer.py +64 -0
- xplia/explainers/neurosymbolic/__init__.py +8 -0
- xplia/explainers/neurosymbolic/neurosymbolic_explainer.py +97 -0
- xplia/explainers/partial_dependence_explainer.py +509 -0
- xplia/explainers/privacy/__init__.py +21 -0
- xplia/explainers/privacy/differential_privacy_xai.py +624 -0
- xplia/explainers/quantum/__init__.py +5 -0
- xplia/explainers/quantum/quantum_explainer.py +79 -0
- xplia/explainers/recommender/__init__.py +8 -0
- xplia/explainers/recommender/recsys_explainer.py +124 -0
- xplia/explainers/reinforcement/__init__.py +15 -0
- xplia/explainers/reinforcement/rl_explainer.py +173 -0
- xplia/explainers/shap_explainer.py +2238 -0
- xplia/explainers/streaming/__init__.py +19 -0
- xplia/explainers/streaming/streaming_xai.py +703 -0
- xplia/explainers/timeseries/__init__.py +15 -0
- xplia/explainers/timeseries/timeseries_explainer.py +252 -0
- xplia/explainers/trust/__init__.py +24 -0
- xplia/explainers/trust/confidence_report.py +368 -0
- xplia/explainers/trust/fairwashing.py +489 -0
- xplia/explainers/trust/uncertainty.py +453 -0
- xplia/explainers/unified_explainer.py +566 -0
- xplia/explainers/unified_explainer_utils.py +309 -0
- xplia/integrations/__init__.py +3 -0
- xplia/integrations/mlflow_integration.py +331 -0
- xplia/integrations/wandb_integration.py +375 -0
- xplia/plugins/__init__.py +36 -0
- xplia/plugins/example_visualizer.py +11 -0
- xplia/utils/__init__.py +18 -0
- xplia/utils/performance.py +119 -0
- xplia/utils/validation.py +109 -0
- xplia/visualizations/__init__.py +31 -0
- xplia/visualizations/base.py +256 -0
- xplia/visualizations/boxplot_chart.py +224 -0
- xplia/visualizations/charts_impl.py +65 -0
- xplia/visualizations/gauge_chart.py +211 -0
- xplia/visualizations/gradient_viz.py +117 -0
- xplia/visualizations/heatmap_chart.py +173 -0
- xplia/visualizations/histogram_chart.py +176 -0
- xplia/visualizations/line_chart.py +100 -0
- xplia/visualizations/pie_chart.py +134 -0
- xplia/visualizations/radar_chart.py +154 -0
- xplia/visualizations/registry.py +76 -0
- xplia/visualizations/sankey_chart.py +190 -0
- xplia/visualizations/scatter_chart.py +252 -0
- xplia/visualizations/table_chart.py +263 -0
- xplia/visualizations/treemap_chart.py +216 -0
- xplia/visualizations.py +535 -0
- xplia/visualizers/__init__.py +87 -0
- xplia/visualizers/base_visualizer.py +294 -0
- xplia-1.0.1.dist-info/METADATA +685 -0
- xplia-1.0.1.dist-info/RECORD +870 -0
- xplia-1.0.1.dist-info/WHEEL +5 -0
- xplia-1.0.1.dist-info/entry_points.txt +2 -0
- xplia-1.0.1.dist-info/licenses/LICENSE +21 -0
- xplia-1.0.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1769 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Explainer basé sur Anchor pour XPLIA
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
Ce module implémente l'explainer Anchor qui génère des règles d'ancrage
|
|
6
|
+
pour expliquer les prédictions d'un modèle.
|
|
7
|
+
|
|
8
|
+
Les règles d'ancrage sont des règles SI-ALORS qui suffisent à "ancrer" une prédiction,
|
|
9
|
+
c'est-à-dire que si les conditions de la règle sont satisfaites, la prédiction
|
|
10
|
+
reste presque certainement la même, indépendamment des autres caractéristiques.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pandas as pd
|
|
16
|
+
import os
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import time
|
|
20
|
+
import traceback
|
|
21
|
+
import datetime
|
|
22
|
+
from functools import lru_cache
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from typing import List, Dict, Any, Union, Optional, Tuple, Callable
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
|
|
27
|
+
from ..core.base import ExplainerBase, register_explainer
|
|
28
|
+
from ..core.explanation import (
|
|
29
|
+
ExplanationResult,
|
|
30
|
+
FeatureImportance,
|
|
31
|
+
ModelMetadata,
|
|
32
|
+
ExplainabilityMethod,
|
|
33
|
+
AudienceLevel
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AnchorExplainerConfig:
|
|
38
|
+
"""Configuration pour l'AnchorExplainer permettant un paramétrage complet."""
|
|
39
|
+
# Paramètres généraux
|
|
40
|
+
feature_names: Optional[List[str]] = None
|
|
41
|
+
categorical_features: Optional[List[int]] = None
|
|
42
|
+
class_names: Optional[List[str]] = None
|
|
43
|
+
discretize_continuous: bool = True
|
|
44
|
+
|
|
45
|
+
# Paramètres d'optimisation
|
|
46
|
+
use_cache: bool = True
|
|
47
|
+
cache_size: int = 128
|
|
48
|
+
use_gpu: bool = True
|
|
49
|
+
compute_quality_metrics: bool = True
|
|
50
|
+
|
|
51
|
+
# Paramètres d'ancrage
|
|
52
|
+
threshold: float = 0.95
|
|
53
|
+
tau: float = 0.15
|
|
54
|
+
batch_size: int = 100
|
|
55
|
+
coverage_samples: int = 10000
|
|
56
|
+
beam_size: int = 5
|
|
57
|
+
max_anchor_size: Optional[int] = None
|
|
58
|
+
stop_on_first: bool = False
|
|
59
|
+
binary_cache_size: int = 10000
|
|
60
|
+
cache_margin: int = 1000
|
|
61
|
+
|
|
62
|
+
# Paramètres de narrative
|
|
63
|
+
narrative_audiences: List[str] = field(default_factory=lambda: ["technical"])
|
|
64
|
+
supported_languages: List[str] = field(default_factory=lambda: ["en", "fr"])
|
|
65
|
+
|
|
66
|
+
# Paramètres de conformité
|
|
67
|
+
verify_compliance: bool = True
|
|
68
|
+
compliance_rules: Dict[str, Any] = field(default_factory=dict)
|
|
69
|
+
|
|
70
|
+
# Paramètres d'échantillonnage pour les explications de groupes
|
|
71
|
+
sampling_method: str = "random" # 'random', 'kmeans', 'stratified'
|
|
72
|
+
max_instances: int = 10
|
|
73
|
+
|
|
74
|
+
@register_explainer
|
|
75
|
+
class AnchorExplainer(ExplainerBase):
|
|
76
|
+
"""
|
|
77
|
+
Explainer qui génère des règles d'ancrage pour expliquer les prédictions d'un modèle.
|
|
78
|
+
|
|
79
|
+
Les règles d'ancrage sont des règles SI-ALORS qui suffisent à "ancrer" une prédiction,
|
|
80
|
+
c'est-à-dire que si les conditions de la règle sont satisfaites, la prédiction
|
|
81
|
+
reste presque certainement la même, indépendamment des autres caractéristiques.
|
|
82
|
+
|
|
83
|
+
Cette implémentation utilise la bibliothèque Anchor originale.
|
|
84
|
+
|
|
85
|
+
Attributes:
|
|
86
|
+
_model: Modèle à expliquer
|
|
87
|
+
_feature_names: Noms des caractéristiques
|
|
88
|
+
_categorical_features: Indices des caractéristiques catégorielles
|
|
89
|
+
_class_names: Noms des classes (pour les modèles de classification)
|
|
90
|
+
_explainer: Instance de l'explainer Anchor
|
|
91
|
+
_metadata: Métadonnées du modèle
|
|
92
|
+
_discretizer: Discrétiseur pour les caractéristiques continues
|
|
93
|
+
_logger: Logger pour les messages de débogage
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, model, config=None, **kwargs):
|
|
97
|
+
"""
|
|
98
|
+
Initialise l'explainer Anchor avec des fonctionnalités avancées.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
model: Modèle à expliquer
|
|
102
|
+
config: Configuration via AnchorExplainerConfig
|
|
103
|
+
**kwargs: Arguments additionnels pour configuration
|
|
104
|
+
feature_names: Liste des noms des caractéristiques
|
|
105
|
+
categorical_features: Liste des indices des caractéristiques catégorielles
|
|
106
|
+
class_names: Liste des noms des classes
|
|
107
|
+
discretize_continuous: Si True, discrétise les caractéristiques continues
|
|
108
|
+
use_cache: Utiliser le cache pour les explications répétées
|
|
109
|
+
cache_size: Taille du cache LRU
|
|
110
|
+
use_gpu: Utiliser le GPU si disponible
|
|
111
|
+
compute_quality_metrics: Calculer les métriques de qualité
|
|
112
|
+
verify_compliance: Vérifier la conformité réglementaire
|
|
113
|
+
narrative_audiences: Audiences pour les narratives
|
|
114
|
+
supported_languages: Langues supportées
|
|
115
|
+
"""
|
|
116
|
+
super().__init__(model=model, **kwargs)
|
|
117
|
+
|
|
118
|
+
# Initialiser la configuration
|
|
119
|
+
if config is None:
|
|
120
|
+
self._config = AnchorExplainerConfig()
|
|
121
|
+
elif isinstance(config, dict):
|
|
122
|
+
self._config = AnchorExplainerConfig(**config)
|
|
123
|
+
else:
|
|
124
|
+
self._config = config
|
|
125
|
+
|
|
126
|
+
# Surcharger la configuration avec les paramètres spécifiques
|
|
127
|
+
for key, value in kwargs.items():
|
|
128
|
+
if hasattr(self._config, key):
|
|
129
|
+
setattr(self._config, key, value)
|
|
130
|
+
|
|
131
|
+
# Initialiser les attributs de base
|
|
132
|
+
self._feature_names = self._config.feature_names
|
|
133
|
+
self._categorical_features = self._config.categorical_features or []
|
|
134
|
+
self._class_names = self._config.class_names
|
|
135
|
+
self._discretize_continuous = self._config.discretize_continuous
|
|
136
|
+
|
|
137
|
+
# Explainer Anchor
|
|
138
|
+
self._explainer = None
|
|
139
|
+
|
|
140
|
+
# Discrétiseur pour les caractéristiques continues
|
|
141
|
+
self._discretizer = None
|
|
142
|
+
|
|
143
|
+
# Métadonnées du modèle
|
|
144
|
+
self._metadata = None
|
|
145
|
+
|
|
146
|
+
# Logger
|
|
147
|
+
self._logger = logging.getLogger(__name__)
|
|
148
|
+
|
|
149
|
+
# Détecter le type de framework (TensorFlow, PyTorch, etc.)
|
|
150
|
+
self._framework = self._extract_model_type()
|
|
151
|
+
|
|
152
|
+
# Initialiser la gestion du GPU si demandée
|
|
153
|
+
if self._config.use_gpu:
|
|
154
|
+
self._initialize_gpu()
|
|
155
|
+
|
|
156
|
+
# Initialiser le vérificateur de conformité réglementaire
|
|
157
|
+
if self._config.verify_compliance:
|
|
158
|
+
self._initialize_compliance_checker()
|
|
159
|
+
else:
|
|
160
|
+
self._compliance_checker = None
|
|
161
|
+
|
|
162
|
+
# Tracer l'initialisation
|
|
163
|
+
self._logger.info(f"AnchorExplainer initialisé avec le framework '{self._framework}'")
|
|
164
|
+
if self._compliance_checker:
|
|
165
|
+
self._logger.info("Vérificateur de conformité initialisé")
|
|
166
|
+
if self._config.use_gpu:
|
|
167
|
+
self._logger.info("Support GPU activé")
|
|
168
|
+
|
|
169
|
+
def _initialize_gpu(self):
|
|
170
|
+
"""
|
|
171
|
+
Initialise la configuration GPU pour le framework détecté.
|
|
172
|
+
Cette méthode configure le GPU pour TensorFlow/PyTorch si disponible.
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
if self._framework == 'tensorflow':
|
|
176
|
+
# Configuration GPU pour TensorFlow
|
|
177
|
+
try:
|
|
178
|
+
import tensorflow as tf
|
|
179
|
+
gpus = tf.config.list_physical_devices('GPU')
|
|
180
|
+
if gpus:
|
|
181
|
+
# Configuration de la croissance dynamique de la mémoire
|
|
182
|
+
self._set_gpu_memory_growth(gpus)
|
|
183
|
+
self._logger.info(f"GPU TensorFlow configuré: {len(gpus)} GPU(s) disponible(s)")
|
|
184
|
+
else:
|
|
185
|
+
self._logger.info("Aucun GPU TensorFlow détecté, utilisation du CPU")
|
|
186
|
+
except ImportError:
|
|
187
|
+
self._logger.warning("TensorFlow non disponible pour la configuration GPU")
|
|
188
|
+
|
|
189
|
+
elif self._framework == 'pytorch':
|
|
190
|
+
# Configuration GPU pour PyTorch
|
|
191
|
+
try:
|
|
192
|
+
import torch
|
|
193
|
+
if torch.cuda.is_available():
|
|
194
|
+
# Réserver la mémoire au fur et à mesure
|
|
195
|
+
torch.cuda.set_per_process_memory_fraction(0.9) # Réserver 90% de la mémoire GPU
|
|
196
|
+
torch.cuda.empty_cache()
|
|
197
|
+
self._logger.info(f"GPU PyTorch configuré: {torch.cuda.device_count()} GPU(s) disponible(s)")
|
|
198
|
+
else:
|
|
199
|
+
self._logger.info("Aucun GPU PyTorch détecté, utilisation du CPU")
|
|
200
|
+
except ImportError:
|
|
201
|
+
self._logger.warning("PyTorch non disponible pour la configuration GPU")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
self._logger.warning(f"Erreur lors de l'initialisation GPU: {str(e)}")
|
|
204
|
+
|
|
205
|
+
def _set_gpu_memory_growth(self, gpus):
|
|
206
|
+
"""
|
|
207
|
+
Configure la croissance dynamique de la mémoire GPU pour TensorFlow
|
|
208
|
+
afin d'éviter les erreurs OOM (Out of Memory).
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
gpus: Liste des GPU physiques disponibles (tf.config.list_physical_devices)
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
import tensorflow as tf
|
|
215
|
+
for gpu in gpus:
|
|
216
|
+
tf.config.experimental.set_memory_growth(gpu, True)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
self._logger.warning(f"Erreur lors de la configuration de la croissance mémoire GPU: {str(e)}")
|
|
219
|
+
|
|
220
|
+
@contextmanager
|
|
221
|
+
def _maybe_use_gpu_context(self, use_gpu=None):
|
|
222
|
+
"""
|
|
223
|
+
Context manager pour gérer l'utilisation du GPU selon le framework.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
use_gpu: Si True, utilise le GPU si disponible. Si None, utilise la valeur de configuration.
|
|
227
|
+
|
|
228
|
+
Yields:
|
|
229
|
+
Contexte GPU approprié pour le framework
|
|
230
|
+
"""
|
|
231
|
+
# Déterminer si on utilise le GPU
|
|
232
|
+
if use_gpu is None:
|
|
233
|
+
use_gpu = self._config.use_gpu
|
|
234
|
+
|
|
235
|
+
if not use_gpu:
|
|
236
|
+
# Si GPU non demandé, simplement passer
|
|
237
|
+
yield
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# Sauvegarder le device/contexte actuel
|
|
241
|
+
original_device = None
|
|
242
|
+
device_changed = False
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
# Utiliser le GPU selon le framework
|
|
246
|
+
if self._framework == 'tensorflow':
|
|
247
|
+
try:
|
|
248
|
+
import tensorflow as tf
|
|
249
|
+
# Vérifier si GPU est disponible
|
|
250
|
+
gpus = tf.config.list_physical_devices('GPU')
|
|
251
|
+
if gpus:
|
|
252
|
+
# Utiliser le premier GPU
|
|
253
|
+
with tf.device('/GPU:0'):
|
|
254
|
+
yield
|
|
255
|
+
else:
|
|
256
|
+
# Pas de GPU disponible
|
|
257
|
+
yield
|
|
258
|
+
except ImportError:
|
|
259
|
+
# TensorFlow non disponible
|
|
260
|
+
yield
|
|
261
|
+
|
|
262
|
+
elif self._framework == 'pytorch':
|
|
263
|
+
try:
|
|
264
|
+
import torch
|
|
265
|
+
if torch.cuda.is_available():
|
|
266
|
+
# Sauvegarder le device actuel
|
|
267
|
+
original_device = torch.cuda.current_device()
|
|
268
|
+
# Utiliser le premier GPU
|
|
269
|
+
torch.cuda.set_device(0)
|
|
270
|
+
device_changed = True
|
|
271
|
+
yield
|
|
272
|
+
else:
|
|
273
|
+
# Pas de GPU disponible
|
|
274
|
+
yield
|
|
275
|
+
except ImportError:
|
|
276
|
+
# PyTorch non disponible
|
|
277
|
+
yield
|
|
278
|
+
else:
|
|
279
|
+
# Autre framework non supporté pour GPU
|
|
280
|
+
yield
|
|
281
|
+
finally:
|
|
282
|
+
# Restaurer le device original si nécessaire
|
|
283
|
+
if device_changed and original_device is not None:
|
|
284
|
+
try:
|
|
285
|
+
import torch
|
|
286
|
+
torch.cuda.set_device(original_device)
|
|
287
|
+
torch.cuda.empty_cache() # Libérer la mémoire GPU
|
|
288
|
+
except:
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
def _initialize_compliance_checker(self):
|
|
292
|
+
"""
|
|
293
|
+
Initialise le vérificateur de conformité réglementaire.
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
from ..compliance.compliance_checker import ComplianceChecker
|
|
297
|
+
self._compliance_checker = ComplianceChecker(
|
|
298
|
+
rules=self._config.compliance_rules,
|
|
299
|
+
explainer_type="AnchorExplainer"
|
|
300
|
+
)
|
|
301
|
+
self._logger.info("Vérificateur de conformité initialisé avec succès")
|
|
302
|
+
except ImportError:
|
|
303
|
+
self._logger.warning("Module de conformité non disponible")
|
|
304
|
+
self._compliance_checker = None
|
|
305
|
+
except Exception as e:
|
|
306
|
+
self._logger.warning(f"Impossible d'initialiser le vérificateur de conformité: {str(e)}")
|
|
307
|
+
self._compliance_checker = None
|
|
308
|
+
|
|
309
|
+
def explain_instance(self, instance, **kwargs) -> ExplanationResult:
|
|
310
|
+
"""
|
|
311
|
+
Génère une explication basée sur des règles d'ancrage pour une instance spécifique.
|
|
312
|
+
Cette version améliorée inclut le cache, la gestion GPU, les métriques de qualité,
|
|
313
|
+
les narratives multilingues et la vérification de conformité réglementaire.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
instance: Instance à expliquer (array, liste, dict ou pandas.Series)
|
|
317
|
+
**kwargs: Paramètres additionnels
|
|
318
|
+
data_type: Type de données ('tabular', 'text', 'image')
|
|
319
|
+
threshold: Seuil de précision pour l'ancrage (défaut: selon config)
|
|
320
|
+
tau: Paramètre de relaxation (défaut: selon config)
|
|
321
|
+
beam_size: Taille du faisceau pour la recherche (défaut: selon config)
|
|
322
|
+
max_anchor_size: Taille maximum de l'ancrage (défaut: selon config)
|
|
323
|
+
stop_on_first: Arrêter à la première explication valide (défaut: selon config)
|
|
324
|
+
binary_cache_size: Taille du cache binaire (défaut: selon config)
|
|
325
|
+
cache_margin: Marge du cache (défaut: selon config)
|
|
326
|
+
feature_names: Noms des caractéristiques (défaut: ceux de l'initialisation)
|
|
327
|
+
audience_level: Niveau d'audience (TECHNICAL, BUSINESS, PUBLIC, ALL)
|
|
328
|
+
language: Code de langue ('en', 'fr', etc.) (défaut: 'en')
|
|
329
|
+
use_gpu: Utiliser le GPU si disponible (défaut: selon config)
|
|
330
|
+
use_cache: Utiliser le cache pour les explications répétées (défaut: selon config)
|
|
331
|
+
compute_quality_metrics: Calculer les métriques de qualité (défaut: selon config)
|
|
332
|
+
verify_compliance: Vérifier la conformité réglementaire (défaut: selon config)
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
ExplanationResult: Résultat standardisé de l'explication
|
|
336
|
+
"""
|
|
337
|
+
# Paramètres de base
|
|
338
|
+
audience_level = kwargs.get('audience_level', AudienceLevel.TECHNICAL)
|
|
339
|
+
data_type = kwargs.get('data_type', 'tabular')
|
|
340
|
+
language = kwargs.get('language', 'en')
|
|
341
|
+
use_cache = kwargs.get('use_cache', self._config.use_cache)
|
|
342
|
+
|
|
343
|
+
# Initialiser la mesure du temps d'exécution
|
|
344
|
+
start_time = time.time()
|
|
345
|
+
execution_metadata = {
|
|
346
|
+
'start_time': datetime.datetime.now().isoformat(),
|
|
347
|
+
'data_type': data_type,
|
|
348
|
+
'audience_level': str(audience_level),
|
|
349
|
+
'language': language,
|
|
350
|
+
'from_cache': False,
|
|
351
|
+
'framework': getattr(self, '_framework', 'unknown')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
# Audit de l'action
|
|
356
|
+
self.add_audit_record("explain_instance", {
|
|
357
|
+
"data_type": data_type,
|
|
358
|
+
"audience_level": audience_level.value if isinstance(audience_level, AudienceLevel) else audience_level,
|
|
359
|
+
"language": language,
|
|
360
|
+
"use_cache": use_cache,
|
|
361
|
+
"use_gpu": kwargs.get('use_gpu', self._config.use_gpu)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
# 1. Vérifier si une explication en cache est disponible
|
|
365
|
+
explanation_data = None
|
|
366
|
+
if use_cache:
|
|
367
|
+
cache_key = self._get_cache_key(instance, **kwargs)
|
|
368
|
+
if cache_key:
|
|
369
|
+
self._logger.debug(f"Recherche dans le cache avec la clé: {cache_key[:10]}...")
|
|
370
|
+
# Récupérer du cache ou calculer si nécessaire
|
|
371
|
+
explanation_data = self._get_cached_explanation(cache_key, instance, **kwargs)
|
|
372
|
+
if explanation_data and explanation_data.get('metadata', {}).get('from_cache', False):
|
|
373
|
+
self._logger.info(f"Explication trouvée dans le cache: {cache_key[:10]}...")
|
|
374
|
+
execution_metadata['from_cache'] = True
|
|
375
|
+
|
|
376
|
+
# 2. Si pas de cache ou non trouvée, calculer l'explication
|
|
377
|
+
if not use_cache or not explanation_data or 'error' in explanation_data:
|
|
378
|
+
explanation_data = self._compute_explanation_cached(instance, **kwargs)
|
|
379
|
+
|
|
380
|
+
# 3. Convertir le résultat au format ExplanationResult
|
|
381
|
+
if not explanation_data:
|
|
382
|
+
raise ValueError("Échec du calcul de l'explication, résultat vide")
|
|
383
|
+
|
|
384
|
+
if 'error' in explanation_data:
|
|
385
|
+
# Gérer les erreurs
|
|
386
|
+
error_msg = explanation_data.get('error', "Erreur inconnue lors de l'explication")
|
|
387
|
+
self._logger.error(f"Erreur lors de l'explication: {error_msg}")
|
|
388
|
+
raise ValueError(error_msg)
|
|
389
|
+
|
|
390
|
+
# 4. Extraire les informations pour créer l'ExplanationResult
|
|
391
|
+
# Recréer les objets FeatureImportance à partir des dictionnaires si nécessaire
|
|
392
|
+
feature_importances_data = explanation_data.get('feature_importances', [])
|
|
393
|
+
feature_importances = []
|
|
394
|
+
|
|
395
|
+
for fi_data in feature_importances_data:
|
|
396
|
+
if isinstance(fi_data, dict):
|
|
397
|
+
feature_importances.append(FeatureImportance(
|
|
398
|
+
feature_name=fi_data.get('feature_name', ""),
|
|
399
|
+
importance=fi_data.get('importance', 0.0),
|
|
400
|
+
local_value=fi_data.get('local_value')
|
|
401
|
+
))
|
|
402
|
+
else:
|
|
403
|
+
feature_importances.append(fi_data) # Déjà un objet FeatureImportance
|
|
404
|
+
|
|
405
|
+
# Récupérer les règles d'ancrage et autres informations
|
|
406
|
+
anchor_rules = explanation_data.get('anchor_rules', [])
|
|
407
|
+
prediction = explanation_data.get('prediction')
|
|
408
|
+
narratives = explanation_data.get('narratives', {})
|
|
409
|
+
|
|
410
|
+
# Extraire la narrative appropriée selon le niveau d'audience et la langue
|
|
411
|
+
explanation_narrative = None
|
|
412
|
+
if language in narratives:
|
|
413
|
+
lang_narratives = narratives[language]
|
|
414
|
+
audience_key = audience_level.lower() if isinstance(audience_level, AudienceLevel) else str(audience_level).lower()
|
|
415
|
+
|
|
416
|
+
if audience_key in lang_narratives:
|
|
417
|
+
explanation_narrative = lang_narratives[audience_key]
|
|
418
|
+
elif 'technical' in lang_narratives: # Fallback sur technical
|
|
419
|
+
explanation_narrative = lang_narratives['technical']
|
|
420
|
+
|
|
421
|
+
# Préparer les métadonnées enrichies
|
|
422
|
+
metadata = {
|
|
423
|
+
'data_type': data_type,
|
|
424
|
+
'audience_level': str(audience_level),
|
|
425
|
+
'language': language,
|
|
426
|
+
'execution_time_ms': int((time.time() - start_time) * 1000)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
# Ajouter les métriques d'ancrage aux métadonnées
|
|
430
|
+
if 'anchor_metrics' in explanation_data:
|
|
431
|
+
metadata.update(explanation_data['anchor_metrics'])
|
|
432
|
+
|
|
433
|
+
# Ajouter les métriques de qualité si disponibles
|
|
434
|
+
if 'quality_metrics' in explanation_data:
|
|
435
|
+
metadata['quality'] = explanation_data['quality_metrics']
|
|
436
|
+
|
|
437
|
+
# Ajouter les résultats de conformité si disponibles
|
|
438
|
+
if 'compliance' in explanation_data:
|
|
439
|
+
metadata['compliance'] = explanation_data['compliance']
|
|
440
|
+
|
|
441
|
+
# Ajouter les métadonnées d'exécution
|
|
442
|
+
metadata.update(execution_metadata)
|
|
443
|
+
|
|
444
|
+
# 5. Créer et retourner l'ExplanationResult final
|
|
445
|
+
result = ExplanationResult(
|
|
446
|
+
method=ExplainabilityMethod.ANCHOR,
|
|
447
|
+
feature_importances=feature_importances,
|
|
448
|
+
anchor_rules=anchor_rules,
|
|
449
|
+
metadata=metadata,
|
|
450
|
+
audience_level=audience_level,
|
|
451
|
+
explanation_narrative=explanation_narrative
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
return result
|
|
455
|
+
|
|
456
|
+
except Exception as e:
|
|
457
|
+
# Enregistrer l'erreur
|
|
458
|
+
self._logger.error(f"Erreur lors de l'explication Anchor: {str(e)}")
|
|
459
|
+
self._logger.debug(traceback.format_exc())
|
|
460
|
+
|
|
461
|
+
# Mettre à jour les métadonnées d'exécution avec l'erreur
|
|
462
|
+
execution_metadata['error'] = str(e)
|
|
463
|
+
execution_metadata['traceback'] = traceback.format_exc()
|
|
464
|
+
execution_metadata['execution_time_ms'] = int((time.time() - start_time) * 1000)
|
|
465
|
+
|
|
466
|
+
# Retourner un résultat d'erreur
|
|
467
|
+
return ExplanationResult(
|
|
468
|
+
method=ExplainabilityMethod.ANCHOR,
|
|
469
|
+
feature_importances=[],
|
|
470
|
+
metadata=execution_metadata,
|
|
471
|
+
audience_level=audience_level
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
def explain(self, X, y=None, **kwargs) -> ExplanationResult:
|
|
475
|
+
"""
|
|
476
|
+
Génère des explications basées sur des règles d'ancrage pour un ensemble de données.
|
|
477
|
+
Pour les explications par ancrage, cette méthode sélectionne un échantillon
|
|
478
|
+
représentatif et génère des règles d'ancrage pour chaque instance.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
X: Données d'entrée à expliquer
|
|
482
|
+
y: Valeurs cibles réelles (optionnel)
|
|
483
|
+
**kwargs: Paramètres additionnels
|
|
484
|
+
max_instances: Nombre maximum d'instances à expliquer
|
|
485
|
+
sampling_strategy: Stratégie d'échantillonnage ('random', 'stratified', 'kmeans')
|
|
486
|
+
data_type: Type de données ('tabular', 'text', 'image')
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
ExplanationResult: Résultat standardisé de l'explication
|
|
490
|
+
"""
|
|
491
|
+
# Paramètres
|
|
492
|
+
audience_level = kwargs.get('audience_level', AudienceLevel.TECHNICAL)
|
|
493
|
+
max_instances = kwargs.get('max_instances', 5)
|
|
494
|
+
sampling_strategy = kwargs.get('sampling_strategy', 'random')
|
|
495
|
+
data_type = kwargs.get('data_type', 'tabular')
|
|
496
|
+
|
|
497
|
+
# Conversion des données en format approprié
|
|
498
|
+
if isinstance(X, pd.DataFrame):
|
|
499
|
+
feature_names = X.columns.tolist()
|
|
500
|
+
X_values = X.values
|
|
501
|
+
else:
|
|
502
|
+
X_values = np.array(X)
|
|
503
|
+
feature_names = kwargs.get('feature_names', self._feature_names) or \
|
|
504
|
+
[f"feature_{i}" for i in range(X_values.shape[1])]
|
|
505
|
+
|
|
506
|
+
# Échantillonner des instances représentatives
|
|
507
|
+
sampled_indices = self._sample_instances(X_values, y, max_instances, sampling_strategy)
|
|
508
|
+
sampled_instances = X_values[sampled_indices]
|
|
509
|
+
|
|
510
|
+
# Tracer l'action
|
|
511
|
+
self.add_audit_record("explain", {
|
|
512
|
+
"n_samples": X_values.shape[0],
|
|
513
|
+
"n_features": X_values.shape[1],
|
|
514
|
+
"audience_level": audience_level.value if isinstance(audience_level, AudienceLevel) else audience_level,
|
|
515
|
+
"max_instances": max_instances,
|
|
516
|
+
"sampling_strategy": sampling_strategy,
|
|
517
|
+
"data_type": data_type
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
# Générer des règles d'ancrage pour chaque instance échantillonnée
|
|
522
|
+
all_anchor_rules = []
|
|
523
|
+
all_anchor_metrics = []
|
|
524
|
+
all_feature_importances = []
|
|
525
|
+
|
|
526
|
+
for instance in sampled_instances:
|
|
527
|
+
# Utiliser explain_instance pour chaque instance
|
|
528
|
+
instance_result = self.explain_instance(
|
|
529
|
+
instance,
|
|
530
|
+
feature_names=feature_names,
|
|
531
|
+
audience_level=audience_level,
|
|
532
|
+
data_type=data_type,
|
|
533
|
+
**kwargs
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Collecter les résultats
|
|
537
|
+
all_anchor_rules.append(instance_result.raw_explanation["anchor_rules"])
|
|
538
|
+
all_anchor_metrics.append(instance_result.raw_explanation["anchor_metrics"])
|
|
539
|
+
all_feature_importances.append(instance_result.feature_importances)
|
|
540
|
+
|
|
541
|
+
# Agréger les importances de caractéristiques
|
|
542
|
+
aggregated_importances = self._aggregate_feature_importances(all_feature_importances, feature_names)
|
|
543
|
+
|
|
544
|
+
# Extraire les métadonnées du modèle
|
|
545
|
+
if not self._metadata:
|
|
546
|
+
self._extract_metadata()
|
|
547
|
+
|
|
548
|
+
# Créer le résultat d'explication
|
|
549
|
+
result = ExplanationResult(
|
|
550
|
+
method=ExplainabilityMethod.ANCHOR,
|
|
551
|
+
model_metadata=self._metadata,
|
|
552
|
+
feature_importances=aggregated_importances,
|
|
553
|
+
raw_explanation={
|
|
554
|
+
"sampled_instances": sampled_instances.tolist(),
|
|
555
|
+
"sampled_indices": sampled_indices.tolist(),
|
|
556
|
+
"all_anchor_rules": all_anchor_rules,
|
|
557
|
+
"all_anchor_metrics": all_anchor_metrics,
|
|
558
|
+
"feature_names": feature_names,
|
|
559
|
+
"data_type": data_type
|
|
560
|
+
},
|
|
561
|
+
audience_level=audience_level
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return result
|
|
565
|
+
|
|
566
|
+
except Exception as e:
|
|
567
|
+
self._logger.error(f"Erreur lors de la génération des règles d'ancrage: {str(e)}")
|
|
568
|
+
raise RuntimeError(f"Échec de l'explication par ancrage: {str(e)}")
|
|
569
|
+
|
|
570
|
+
def _initialize_explainer(self, X, data_type='tabular'):
|
|
571
|
+
"""
|
|
572
|
+
Initialise l'explainer Anchor selon le type de données.
|
|
573
|
+
|
|
574
|
+
Args:
|
|
575
|
+
X: Données d'entrée
|
|
576
|
+
data_type: Type de données ('tabular', 'text', 'image')
|
|
577
|
+
"""
|
|
578
|
+
try:
|
|
579
|
+
import anchor
|
|
580
|
+
from anchor import anchor_tabular, anchor_text, anchor_image
|
|
581
|
+
except ImportError:
|
|
582
|
+
raise ImportError("Le package Anchor est requis pour cette méthode. "
|
|
583
|
+
"Installez-le avec 'pip install anchor-exp'.")
|
|
584
|
+
|
|
585
|
+
if data_type == 'tabular':
|
|
586
|
+
# Initialiser le discrétiseur si nécessaire
|
|
587
|
+
if self._discretize_continuous and self._discretizer is None:
|
|
588
|
+
self._discretizer = anchor_tabular.AnchorTabularExplainer.discretizer(
|
|
589
|
+
X,
|
|
590
|
+
self._categorical_features,
|
|
591
|
+
self._feature_names
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
# Créer l'explainer pour données tabulaires
|
|
595
|
+
self._explainer = anchor_tabular.AnchorTabularExplainer(
|
|
596
|
+
class_names=self._class_names,
|
|
597
|
+
feature_names=self._feature_names,
|
|
598
|
+
train_data=X,
|
|
599
|
+
categorical_names={i: [] for i in self._categorical_features},
|
|
600
|
+
discretizer=self._discretizer
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
elif data_type == 'text':
|
|
604
|
+
# Créer l'explainer pour données textuelles
|
|
605
|
+
self._explainer = anchor_text.AnchorText(
|
|
606
|
+
nlp=None, # Utiliser spaCy par défaut
|
|
607
|
+
class_names=self._class_names
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
elif data_type == 'image':
|
|
611
|
+
# Créer l'explainer pour images
|
|
612
|
+
self._explainer = anchor_image.AnchorImage(
|
|
613
|
+
class_names=self._class_names
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
else:
|
|
617
|
+
raise ValueError(f"Type de données non supporté: {data_type}")
|
|
618
|
+
|
|
619
|
+
# Définir la fonction de prédiction
|
|
620
|
+
self._explainer.predictor = self._get_prediction_function()
|
|
621
|
+
|
|
622
|
+
def _get_prediction_function(self):
|
|
623
|
+
"""
|
|
624
|
+
Crée une fonction de prédiction pour l'explainer Anchor.
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
function: Fonction de prédiction
|
|
628
|
+
"""
|
|
629
|
+
# Déterminer le type de modèle
|
|
630
|
+
model_type = self._get_model_type()
|
|
631
|
+
|
|
632
|
+
# Créer une fonction de prédiction appropriée
|
|
633
|
+
def predict_fn(instances):
|
|
634
|
+
# Pour les modèles scikit-learn et similaires
|
|
635
|
+
if model_type in ['sklearn', 'xgboost', 'lightgbm', 'catboost']:
|
|
636
|
+
if hasattr(self._model, 'predict'):
|
|
637
|
+
return self._model.predict(instances)
|
|
638
|
+
else:
|
|
639
|
+
raise ValueError("Le modèle ne possède pas de méthode 'predict'.")
|
|
640
|
+
|
|
641
|
+
# Pour TensorFlow/Keras
|
|
642
|
+
elif model_type == 'tensorflow':
|
|
643
|
+
import tensorflow as tf
|
|
644
|
+
# Convertir en tensor si nécessaire
|
|
645
|
+
if not isinstance(instances, tf.Tensor):
|
|
646
|
+
instances = tf.convert_to_tensor(instances, dtype=tf.float32)
|
|
647
|
+
# Faire la prédiction
|
|
648
|
+
preds = self._model(instances)
|
|
649
|
+
# Convertir en classes si nécessaire
|
|
650
|
+
if len(preds.shape) > 1 and preds.shape[1] > 1:
|
|
651
|
+
return tf.argmax(preds, axis=1).numpy()
|
|
652
|
+
else:
|
|
653
|
+
return tf.round(preds).numpy()
|
|
654
|
+
|
|
655
|
+
# Pour PyTorch
|
|
656
|
+
elif model_type == 'pytorch':
|
|
657
|
+
import torch
|
|
658
|
+
# Mettre le modèle en mode évaluation
|
|
659
|
+
self._model.eval()
|
|
660
|
+
# Convertir en tensor si nécessaire
|
|
661
|
+
if not isinstance(instances, torch.Tensor):
|
|
662
|
+
instances = torch.tensor(instances, dtype=torch.float32)
|
|
663
|
+
# Désactiver le calcul de gradient
|
|
664
|
+
with torch.no_grad():
|
|
665
|
+
# Faire la prédiction
|
|
666
|
+
preds = self._model(instances)
|
|
667
|
+
# Convertir en classes
|
|
668
|
+
if preds.shape[1] > 1:
|
|
669
|
+
return torch.argmax(preds, dim=1).numpy()
|
|
670
|
+
else:
|
|
671
|
+
return torch.round(preds).numpy()
|
|
672
|
+
|
|
673
|
+
# Pour les autres types de modèles
|
|
674
|
+
else:
|
|
675
|
+
# Essayer d'appeler predict
|
|
676
|
+
if hasattr(self._model, 'predict'):
|
|
677
|
+
return self._model.predict(instances)
|
|
678
|
+
else:
|
|
679
|
+
# Essayer d'appeler le modèle directement
|
|
680
|
+
try:
|
|
681
|
+
return self._model(instances)
|
|
682
|
+
except:
|
|
683
|
+
raise ValueError("Impossible de faire des prédictions avec le modèle fourni.")
|
|
684
|
+
|
|
685
|
+
return predict_fn
|
|
686
|
+
|
|
687
|
+
def _explain_tabular_instance(self, instance, **kwargs):
|
|
688
|
+
"""
|
|
689
|
+
Génère une explication pour une instance tabulaire.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
instance: Instance à expliquer
|
|
693
|
+
**kwargs: Paramètres pour l'explainer Anchor
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
object: Explication générée par Anchor
|
|
697
|
+
"""
|
|
698
|
+
return self._explainer.explain_instance(
|
|
699
|
+
instance,
|
|
700
|
+
threshold=kwargs.get('threshold', 0.95),
|
|
701
|
+
tau=kwargs.get('tau', 0.15),
|
|
702
|
+
batch_size=kwargs.get('batch_size', 100),
|
|
703
|
+
coverage_samples=kwargs.get('coverage_samples', 10000),
|
|
704
|
+
beam_size=kwargs.get('beam_size', 5),
|
|
705
|
+
max_anchor_size=kwargs.get('max_anchor_size'),
|
|
706
|
+
stop_on_first=kwargs.get('stop_on_first', False),
|
|
707
|
+
binary_cache_size=kwargs.get('binary_cache_size', 10000),
|
|
708
|
+
cache_margin=kwargs.get('cache_margin', 1000),
|
|
709
|
+
verbose=kwargs.get('verbose', False),
|
|
710
|
+
verbose_every=kwargs.get('verbose_every', 1)
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
def _explain_text_instance(self, text, **kwargs):
|
|
714
|
+
"""
|
|
715
|
+
Génère une explication pour une instance textuelle.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
text: Texte à expliquer
|
|
719
|
+
**kwargs: Paramètres pour l'explainer Anchor
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
object: Explication générée par Anchor
|
|
723
|
+
"""
|
|
724
|
+
return self._explainer.explain_instance(
|
|
725
|
+
text,
|
|
726
|
+
threshold=kwargs.get('threshold', 0.95),
|
|
727
|
+
use_unk=kwargs.get('use_unk', True),
|
|
728
|
+
use_proba=kwargs.get('use_proba', False),
|
|
729
|
+
sample_proba=kwargs.get('sample_proba', 0.5),
|
|
730
|
+
top_n=kwargs.get('top_n', 100),
|
|
731
|
+
temperature=kwargs.get('temperature', 1.0),
|
|
732
|
+
beam_size=kwargs.get('beam_size', 5)
|
|
733
|
+
)
|
|
734
|
+
|
|
735
|
+
def _explain_image_instance(self, image, **kwargs):
|
|
736
|
+
"""
|
|
737
|
+
Génère une explication pour une image.
|
|
738
|
+
|
|
739
|
+
Args:
|
|
740
|
+
image: Image à expliquer
|
|
741
|
+
**kwargs: Paramètres pour l'explainer Anchor
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
object: Explication générée par Anchor
|
|
745
|
+
"""
|
|
746
|
+
segmentation_fn = kwargs.get('segmentation_fn')
|
|
747
|
+
if segmentation_fn is None:
|
|
748
|
+
# Utiliser une segmentation par défaut si non fournie
|
|
749
|
+
try:
|
|
750
|
+
from skimage.segmentation import felzenszwalb
|
|
751
|
+
segmentation_fn = lambda x: felzenszwalb(x, scale=100, sigma=0.5, min_size=50)
|
|
752
|
+
except ImportError:
|
|
753
|
+
raise ImportError("scikit-image est requis pour la segmentation d'image. "
|
|
754
|
+
"Installez-le avec 'pip install scikit-image'.")
|
|
755
|
+
|
|
756
|
+
return self._explainer.explain_instance(
|
|
757
|
+
image,
|
|
758
|
+
segmentation_fn,
|
|
759
|
+
threshold=kwargs.get('threshold', 0.95),
|
|
760
|
+
p_sample=kwargs.get('p_sample', 0.5),
|
|
761
|
+
**{k: v for k, v in kwargs.items() if k not in ['threshold', 'p_sample', 'segmentation_fn']}
|
|
762
|
+
)
|
|
763
|
+
|
|
764
|
+
def _extract_anchor_rules(self, explanation, feature_names):
|
|
765
|
+
"""
|
|
766
|
+
Extrait les règles d'ancrage à partir de l'explication.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
explanation: Explication générée par Anchor
|
|
770
|
+
feature_names: Noms des caractéristiques
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
list: Liste des règles d'ancrage
|
|
774
|
+
"""
|
|
775
|
+
rules = []
|
|
776
|
+
|
|
777
|
+
# Pour les explications tabulaires
|
|
778
|
+
if hasattr(explanation, 'names'):
|
|
779
|
+
for rule in explanation.names():
|
|
780
|
+
rules.append(rule)
|
|
781
|
+
# Pour les explications textuelles
|
|
782
|
+
elif hasattr(explanation, 'exp_map'):
|
|
783
|
+
for pos in explanation.exp_map['names']:
|
|
784
|
+
rules.append(pos)
|
|
785
|
+
# Pour les explications d'images
|
|
786
|
+
elif hasattr(explanation, 'segments'):
|
|
787
|
+
rules = [f"segment_{i}" for i in explanation.segments()]
|
|
788
|
+
# Fallback
|
|
789
|
+
else:
|
|
790
|
+
try:
|
|
791
|
+
# Essayer d'extraire les règles directement
|
|
792
|
+
rules = explanation.names()
|
|
793
|
+
except:
|
|
794
|
+
self._logger.warning("Impossible d'extraire les règles d'ancrage. Format d'explication non reconnu.")
|
|
795
|
+
rules = []
|
|
796
|
+
|
|
797
|
+
return rules
|
|
798
|
+
|
|
799
|
+
def _extract_anchor_metrics(self, explanation):
|
|
800
|
+
"""
|
|
801
|
+
Extrait les métriques de l'explication.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
explanation: Explication générée par Anchor
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
dict: Métriques de l'explication
|
|
808
|
+
"""
|
|
809
|
+
metrics = {}
|
|
810
|
+
|
|
811
|
+
# Métriques communes
|
|
812
|
+
if hasattr(explanation, 'precision'):
|
|
813
|
+
metrics['precision'] = float(explanation.precision)
|
|
814
|
+
if hasattr(explanation, 'coverage'):
|
|
815
|
+
metrics['coverage'] = float(explanation.coverage)
|
|
816
|
+
|
|
817
|
+
# Métriques spécifiques aux données tabulaires
|
|
818
|
+
if hasattr(explanation, 'anchor'):
|
|
819
|
+
metrics['anchor_size'] = len(explanation.anchor)
|
|
820
|
+
|
|
821
|
+
# Métriques spécifiques aux textes
|
|
822
|
+
if hasattr(explanation, 'exp_map'):
|
|
823
|
+
if 'precision' in explanation.exp_map:
|
|
824
|
+
metrics['precision'] = float(explanation.exp_map['precision'])
|
|
825
|
+
if 'coverage' in explanation.exp_map:
|
|
826
|
+
metrics['coverage'] = float(explanation.exp_map['coverage'])
|
|
827
|
+
|
|
828
|
+
# Métriques spécifiques aux images
|
|
829
|
+
if hasattr(explanation, 'segments'):
|
|
830
|
+
metrics['num_segments'] = len(explanation.segments())
|
|
831
|
+
|
|
832
|
+
return metrics
|
|
833
|
+
|
|
834
|
+
def _convert_rules_to_importances(self, anchor_rules, feature_names):
|
|
835
|
+
"""
|
|
836
|
+
Convertit les règles d'ancrage en importances de caractéristiques.
|
|
837
|
+
|
|
838
|
+
Args:
|
|
839
|
+
anchor_rules: Règles d'ancrage
|
|
840
|
+
feature_names: Noms des caractéristiques
|
|
841
|
+
|
|
842
|
+
Returns:
|
|
843
|
+
list: Liste d'objets FeatureImportance
|
|
844
|
+
"""
|
|
845
|
+
importances = []
|
|
846
|
+
feature_counts = {}
|
|
847
|
+
|
|
848
|
+
# Compter les occurrences des caractéristiques dans les règles
|
|
849
|
+
for rule in anchor_rules:
|
|
850
|
+
# Extraire les noms des caractéristiques de la règle
|
|
851
|
+
for feature in feature_names:
|
|
852
|
+
if feature in rule:
|
|
853
|
+
feature_counts[feature] = feature_counts.get(feature, 0) + 1
|
|
854
|
+
|
|
855
|
+
# Convertir les comptages en importances
|
|
856
|
+
total_counts = sum(feature_counts.values()) if feature_counts else 1
|
|
857
|
+
for feature, count in feature_counts.items():
|
|
858
|
+
importances.append(FeatureImportance(
|
|
859
|
+
feature_name=feature,
|
|
860
|
+
importance=count / total_counts,
|
|
861
|
+
std=0.0, # Pas d'écart-type pour les règles d'ancrage
|
|
862
|
+
additional_info={
|
|
863
|
+
'occurrence_count': count,
|
|
864
|
+
'total_rules': len(anchor_rules)
|
|
865
|
+
}
|
|
866
|
+
))
|
|
867
|
+
|
|
868
|
+
# Ajouter les caractéristiques non utilisées avec importance nulle
|
|
869
|
+
for feature in feature_names:
|
|
870
|
+
if feature not in feature_counts:
|
|
871
|
+
importances.append(FeatureImportance(
|
|
872
|
+
feature_name=feature,
|
|
873
|
+
importance=0.0,
|
|
874
|
+
std=0.0,
|
|
875
|
+
additional_info={
|
|
876
|
+
'occurrence_count': 0,
|
|
877
|
+
'total_rules': len(anchor_rules)
|
|
878
|
+
}
|
|
879
|
+
))
|
|
880
|
+
|
|
881
|
+
# Trier par importance décroissante
|
|
882
|
+
importances.sort(key=lambda x: x.importance, reverse=True)
|
|
883
|
+
|
|
884
|
+
return importances
|
|
885
|
+
|
|
886
|
+
def _aggregate_feature_importances(self, all_importances, feature_names):
|
|
887
|
+
"""
|
|
888
|
+
Agrège les importances de caractéristiques de plusieurs instances.
|
|
889
|
+
|
|
890
|
+
Args:
|
|
891
|
+
all_importances: Liste de listes d'objets FeatureImportance
|
|
892
|
+
feature_names: Noms des caractéristiques
|
|
893
|
+
|
|
894
|
+
Returns:
|
|
895
|
+
list: Liste agrégée d'objets FeatureImportance
|
|
896
|
+
"""
|
|
897
|
+
# Initialiser un dictionnaire pour stocker les importances agrégées
|
|
898
|
+
aggregated = {feature: {'sum': 0.0, 'count': 0, 'values': []} for feature in feature_names}
|
|
899
|
+
|
|
900
|
+
# Collecter toutes les importances par caractéristique
|
|
901
|
+
for importances in all_importances:
|
|
902
|
+
for imp in importances:
|
|
903
|
+
feature = imp.feature_name
|
|
904
|
+
if feature in aggregated:
|
|
905
|
+
aggregated[feature]['sum'] += imp.importance
|
|
906
|
+
aggregated[feature]['count'] += 1
|
|
907
|
+
aggregated[feature]['values'].append(imp.importance)
|
|
908
|
+
|
|
909
|
+
# Calculer les importances moyennes et les écarts-types
|
|
910
|
+
result = []
|
|
911
|
+
for feature, data in aggregated.items():
|
|
912
|
+
if data['count'] > 0:
|
|
913
|
+
mean_importance = data['sum'] / data['count']
|
|
914
|
+
std_importance = np.std(data['values']) if len(data['values']) > 1 else 0.0
|
|
915
|
+
|
|
916
|
+
result.append(FeatureImportance(
|
|
917
|
+
feature_name=feature,
|
|
918
|
+
importance=mean_importance,
|
|
919
|
+
std=float(std_importance),
|
|
920
|
+
additional_info={
|
|
921
|
+
'count': data['count'],
|
|
922
|
+
'min': float(min(data['values'])) if data['values'] else 0.0,
|
|
923
|
+
'max': float(max(data['values'])) if data['values'] else 0.0
|
|
924
|
+
}
|
|
925
|
+
))
|
|
926
|
+
else:
|
|
927
|
+
# Caractéristique non utilisée
|
|
928
|
+
result.append(FeatureImportance(
|
|
929
|
+
feature_name=feature,
|
|
930
|
+
importance=0.0,
|
|
931
|
+
std=0.0,
|
|
932
|
+
additional_info={
|
|
933
|
+
'count': 0,
|
|
934
|
+
'min': 0.0,
|
|
935
|
+
'max': 0.0
|
|
936
|
+
}
|
|
937
|
+
))
|
|
938
|
+
|
|
939
|
+
# Trier par importance décroissante
|
|
940
|
+
result.sort(key=lambda x: x.importance, reverse=True)
|
|
941
|
+
|
|
942
|
+
return result
|
|
943
|
+
|
|
944
|
+
def _sample_instances(self, X, y=None, max_instances=5, strategy='random'):
|
|
945
|
+
"""
|
|
946
|
+
Échantillonne des instances représentatives à partir des données.
|
|
947
|
+
|
|
948
|
+
Args:
|
|
949
|
+
X: Données d'entrée
|
|
950
|
+
y: Valeurs cibles réelles (optionnel)
|
|
951
|
+
max_instances: Nombre maximum d'instances à sélectionner
|
|
952
|
+
strategy: Stratégie d'échantillonnage ('random', 'stratified', 'kmeans')
|
|
953
|
+
|
|
954
|
+
Returns:
|
|
955
|
+
numpy.ndarray: Indices des instances sélectionnées
|
|
956
|
+
"""
|
|
957
|
+
n_samples = X.shape[0]
|
|
958
|
+
max_instances = min(max_instances, n_samples)
|
|
959
|
+
|
|
960
|
+
if strategy == 'random':
|
|
961
|
+
# Échantillonnage aléatoire
|
|
962
|
+
indices = np.random.choice(n_samples, max_instances, replace=False)
|
|
963
|
+
|
|
964
|
+
elif strategy == 'stratified' and y is not None:
|
|
965
|
+
# Échantillonnage stratifié par classe
|
|
966
|
+
try:
|
|
967
|
+
from sklearn.model_selection import StratifiedShuffleSplit
|
|
968
|
+
except ImportError:
|
|
969
|
+
self._logger.warning("scikit-learn est requis pour l'échantillonnage stratifié. "
|
|
970
|
+
"Utilisation de l'échantillonnage aléatoire.")
|
|
971
|
+
return np.random.choice(n_samples, max_instances, replace=False)
|
|
972
|
+
|
|
973
|
+
sss = StratifiedShuffleSplit(n_splits=1, test_size=max_instances/n_samples, random_state=42)
|
|
974
|
+
for _, test_idx in sss.split(X, y):
|
|
975
|
+
indices = test_idx
|
|
976
|
+
if len(indices) > max_instances:
|
|
977
|
+
indices = indices[:max_instances]
|
|
978
|
+
break
|
|
979
|
+
|
|
980
|
+
elif strategy == 'kmeans':
|
|
981
|
+
# Échantillonnage basé sur le clustering k-means
|
|
982
|
+
try:
|
|
983
|
+
from sklearn.cluster import KMeans
|
|
984
|
+
from sklearn.preprocessing import StandardScaler
|
|
985
|
+
except ImportError:
|
|
986
|
+
self._logger.warning("scikit-learn est requis pour l'échantillonnage k-means. "
|
|
987
|
+
"Utilisation de l'échantillonnage aléatoire.")
|
|
988
|
+
return np.random.choice(n_samples, max_instances, replace=False)
|
|
989
|
+
|
|
990
|
+
# Normaliser les données
|
|
991
|
+
scaler = StandardScaler()
|
|
992
|
+
X_scaled = scaler.fit_transform(X)
|
|
993
|
+
|
|
994
|
+
# Appliquer k-means
|
|
995
|
+
kmeans = KMeans(n_clusters=max_instances, random_state=42, n_init=10)
|
|
996
|
+
clusters = kmeans.fit_predict(X_scaled)
|
|
997
|
+
|
|
998
|
+
# Sélectionner l'instance la plus proche de chaque centroide
|
|
999
|
+
indices = []
|
|
1000
|
+
for i in range(max_instances):
|
|
1001
|
+
cluster_points = np.where(clusters == i)[0]
|
|
1002
|
+
if len(cluster_points) > 0:
|
|
1003
|
+
# Trouver le point le plus proche du centroide
|
|
1004
|
+
centroid = kmeans.cluster_centers_[i]
|
|
1005
|
+
distances = np.linalg.norm(X_scaled[cluster_points] - centroid, axis=1)
|
|
1006
|
+
closest_idx = cluster_points[np.argmin(distances)]
|
|
1007
|
+
indices.append(closest_idx)
|
|
1008
|
+
|
|
1009
|
+
# Compléter si nécessaire
|
|
1010
|
+
if len(indices) < max_instances:
|
|
1011
|
+
remaining = np.setdiff1d(np.arange(n_samples), indices)
|
|
1012
|
+
additional = np.random.choice(remaining, max_instances - len(indices), replace=False)
|
|
1013
|
+
indices = np.concatenate([indices, additional])
|
|
1014
|
+
else:
|
|
1015
|
+
# Par défaut, échantillonnage aléatoire
|
|
1016
|
+
indices = np.random.choice(n_samples, max_instances, replace=False)
|
|
1017
|
+
|
|
1018
|
+
return indices
|
|
1019
|
+
|
|
1020
|
+
def _extract_metadata(self):
|
|
1021
|
+
"""
|
|
1022
|
+
Extrait les métadonnées du modèle.
|
|
1023
|
+
"""
|
|
1024
|
+
# Déterminer le type de modèle
|
|
1025
|
+
model_type = self._get_model_type()
|
|
1026
|
+
|
|
1027
|
+
# Déterminer le type de tâche (classification ou régression)
|
|
1028
|
+
task_type = self._get_task_type()
|
|
1029
|
+
|
|
1030
|
+
# Créer les métadonnées du modèle
|
|
1031
|
+
self._metadata = ModelMetadata(
|
|
1032
|
+
model_type=model_type,
|
|
1033
|
+
framework=model_type, # Utiliser le type comme framework pour simplifier
|
|
1034
|
+
task_type=task_type,
|
|
1035
|
+
feature_names=self._feature_names,
|
|
1036
|
+
target_names=self._class_names,
|
|
1037
|
+
model_parameters={
|
|
1038
|
+
'categorical_features': self._categorical_features,
|
|
1039
|
+
'discretize_continuous': self._discretize_continuous
|
|
1040
|
+
}
|
|
1041
|
+
)
|
|
1042
|
+
|
|
1043
|
+
def _get_model_type(self):
|
|
1044
|
+
"""
|
|
1045
|
+
Détermine le type de modèle (framework).
|
|
1046
|
+
|
|
1047
|
+
Returns:
|
|
1048
|
+
str: Type de modèle (sklearn, tensorflow, pytorch, etc.)
|
|
1049
|
+
"""
|
|
1050
|
+
model_module = self._model.__module__.split('.')[0].lower()
|
|
1051
|
+
|
|
1052
|
+
if model_module in ['sklearn', 'scikit']:
|
|
1053
|
+
return 'sklearn'
|
|
1054
|
+
elif model_module in ['tensorflow', 'tf', 'keras']:
|
|
1055
|
+
return 'tensorflow'
|
|
1056
|
+
elif model_module in ['torch', 'pytorch']:
|
|
1057
|
+
return 'pytorch'
|
|
1058
|
+
elif model_module in ['xgboost']:
|
|
1059
|
+
return 'xgboost'
|
|
1060
|
+
elif model_module in ['lightgbm']:
|
|
1061
|
+
return 'lightgbm'
|
|
1062
|
+
elif model_module in ['catboost']:
|
|
1063
|
+
return 'catboost'
|
|
1064
|
+
else:
|
|
1065
|
+
return 'unknown'
|
|
1066
|
+
|
|
1067
|
+
def _get_task_type(self):
|
|
1068
|
+
"""
|
|
1069
|
+
Détermine le type de tâche (classification ou régression).
|
|
1070
|
+
|
|
1071
|
+
Returns:
|
|
1072
|
+
str: Type de tâche ('classification' ou 'regression')
|
|
1073
|
+
"""
|
|
1074
|
+
# Vérifier les méthodes disponibles sur le modèle
|
|
1075
|
+
if hasattr(self._model, 'predict_proba'):
|
|
1076
|
+
return 'classification'
|
|
1077
|
+
elif hasattr(self._model, '_estimator_type'):
|
|
1078
|
+
return self._model._estimator_type
|
|
1079
|
+
else:
|
|
1080
|
+
# Par défaut, supposer que c'est une classification
|
|
1081
|
+
return 'classification'
|
|
1082
|
+
|
|
1083
|
+
def _get_cache_key(self, instance, **kwargs):
|
|
1084
|
+
"""
|
|
1085
|
+
Génère une clé de cache unique pour une instance et des paramètres d'explication.
|
|
1086
|
+
|
|
1087
|
+
Args:
|
|
1088
|
+
instance: Instance à expliquer
|
|
1089
|
+
**kwargs: Paramètres d'explication
|
|
1090
|
+
|
|
1091
|
+
Returns:
|
|
1092
|
+
str: Clé de cache unique ou None si impossible de générer
|
|
1093
|
+
"""
|
|
1094
|
+
try:
|
|
1095
|
+
# Convertir l'instance en structure serializable
|
|
1096
|
+
if isinstance(instance, pd.DataFrame):
|
|
1097
|
+
instance_data = instance.to_dict('records')
|
|
1098
|
+
elif isinstance(instance, pd.Series):
|
|
1099
|
+
instance_data = instance.to_dict()
|
|
1100
|
+
elif isinstance(instance, dict):
|
|
1101
|
+
instance_data = instance
|
|
1102
|
+
elif isinstance(instance, (list, np.ndarray)):
|
|
1103
|
+
instance_data = instance
|
|
1104
|
+
# Convertir les ndarray en listes
|
|
1105
|
+
if isinstance(instance_data, np.ndarray):
|
|
1106
|
+
instance_data = instance_data.tolist()
|
|
1107
|
+
else:
|
|
1108
|
+
self._logger.warning(f"Type d'instance non supporté pour le cache: {type(instance)}")
|
|
1109
|
+
return None
|
|
1110
|
+
|
|
1111
|
+
# Extraire les paramètres pertinents pour la clé de cache
|
|
1112
|
+
cache_params = {
|
|
1113
|
+
'data_type': kwargs.get('data_type', 'tabular'),
|
|
1114
|
+
'threshold': kwargs.get('threshold', self._config.threshold),
|
|
1115
|
+
'tau': kwargs.get('tau', self._config.tau),
|
|
1116
|
+
'max_anchor_size': kwargs.get('max_anchor_size', self._config.max_anchor_size),
|
|
1117
|
+
'beam_size': kwargs.get('beam_size', self._config.beam_size),
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
# Créer une chaîne unique pour l'instance et les paramètres
|
|
1121
|
+
cache_data = {
|
|
1122
|
+
'instance': instance_data,
|
|
1123
|
+
'params': cache_params
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
# Générer un hash SHA-256 comme clé de cache
|
|
1127
|
+
cache_key = hashlib.sha256(json.dumps(cache_data, sort_keys=True).encode()).hexdigest()
|
|
1128
|
+
|
|
1129
|
+
return cache_key
|
|
1130
|
+
|
|
1131
|
+
except Exception as e:
|
|
1132
|
+
self._logger.warning(f"Erreur lors de la génération de la clé de cache: {str(e)}")
|
|
1133
|
+
return None
|
|
1134
|
+
|
|
1135
|
+
@lru_cache(maxsize=128)
|
|
1136
|
+
def _compute_explanation(self, instance_hash, instance_serialized, **kwargs):
|
|
1137
|
+
"""
|
|
1138
|
+
Calcule l'explication pour une instance avec gestion du cache LRU.
|
|
1139
|
+
Cette méthode est décorée avec lru_cache pour mémoriser les résultats.
|
|
1140
|
+
|
|
1141
|
+
Args:
|
|
1142
|
+
instance_hash: Hash de l'instance (pour le cache)
|
|
1143
|
+
instance_serialized: Représentation sérialisée de l'instance
|
|
1144
|
+
**kwargs: Paramètres d'explication
|
|
1145
|
+
|
|
1146
|
+
Returns:
|
|
1147
|
+
dict: Résultat d'explication
|
|
1148
|
+
"""
|
|
1149
|
+
# Désérialiser l'instance
|
|
1150
|
+
instance = json.loads(instance_serialized)
|
|
1151
|
+
|
|
1152
|
+
# Convertir en format approprié selon le type de données
|
|
1153
|
+
data_type = kwargs.get('data_type', 'tabular')
|
|
1154
|
+
|
|
1155
|
+
if data_type == 'tabular':
|
|
1156
|
+
# Reconvertir en array numpy
|
|
1157
|
+
instance_array = np.array(instance)
|
|
1158
|
+
elif data_type == 'text':
|
|
1159
|
+
# String pour texte
|
|
1160
|
+
instance_array = instance
|
|
1161
|
+
elif data_type == 'image':
|
|
1162
|
+
# Reconvertir en array numpy pour image
|
|
1163
|
+
instance_array = np.array(instance)
|
|
1164
|
+
else:
|
|
1165
|
+
raise ValueError(f"Type de données non supporté: {data_type}")
|
|
1166
|
+
|
|
1167
|
+
# Calculer l'explication via la méthode adéquate selon le type de données
|
|
1168
|
+
# Déléguer à la méthode complète
|
|
1169
|
+
return self._compute_explanation_cached(instance_array, **kwargs)
|
|
1170
|
+
|
|
1171
|
+
def _get_cached_explanation(self, cache_key, instance, **kwargs):
|
|
1172
|
+
"""
|
|
1173
|
+
Récupère une explication du cache ou la calcule si nécessaire.
|
|
1174
|
+
|
|
1175
|
+
Args:
|
|
1176
|
+
cache_key: Clé de cache
|
|
1177
|
+
instance: Instance à expliquer
|
|
1178
|
+
**kwargs: Paramètres d'explication
|
|
1179
|
+
|
|
1180
|
+
Returns:
|
|
1181
|
+
dict: Résultat d'explication
|
|
1182
|
+
"""
|
|
1183
|
+
try:
|
|
1184
|
+
# Sérialiser l'instance pour le cache
|
|
1185
|
+
if isinstance(instance, pd.DataFrame):
|
|
1186
|
+
serialized_instance = json.dumps(instance.to_dict('records')[0])
|
|
1187
|
+
elif isinstance(instance, pd.Series):
|
|
1188
|
+
serialized_instance = json.dumps(instance.to_dict())
|
|
1189
|
+
elif isinstance(instance, dict):
|
|
1190
|
+
serialized_instance = json.dumps(instance)
|
|
1191
|
+
elif isinstance(instance, (list, np.ndarray)):
|
|
1192
|
+
if isinstance(instance, np.ndarray):
|
|
1193
|
+
serialized_instance = json.dumps(instance.tolist())
|
|
1194
|
+
else:
|
|
1195
|
+
serialized_instance = json.dumps(instance)
|
|
1196
|
+
else:
|
|
1197
|
+
raise ValueError(f"Type d'instance non supporté pour le cache: {type(instance)}")
|
|
1198
|
+
|
|
1199
|
+
# Appeler la méthode cachée
|
|
1200
|
+
explanation = self._compute_explanation(cache_key, serialized_instance, **kwargs)
|
|
1201
|
+
|
|
1202
|
+
# Marquer comme provenant du cache
|
|
1203
|
+
if explanation and 'metadata' in explanation:
|
|
1204
|
+
explanation['metadata']['from_cache'] = True
|
|
1205
|
+
|
|
1206
|
+
return explanation
|
|
1207
|
+
|
|
1208
|
+
except Exception as e:
|
|
1209
|
+
self._logger.warning(f"Erreur lors de la récupération du cache: {str(e)}")
|
|
1210
|
+
# En cas d'erreur, calculer directement
|
|
1211
|
+
return self._compute_explanation_cached(instance, **kwargs)
|
|
1212
|
+
|
|
1213
|
+
def _compute_explanation_quality_metrics(self, anchor_rules, explanation=None, instance=None, prediction=None):
|
|
1214
|
+
"""
|
|
1215
|
+
Calcule les métriques de qualité de l'explication par ancrage.
|
|
1216
|
+
|
|
1217
|
+
Args:
|
|
1218
|
+
anchor_rules: Règles d'ancrage générées
|
|
1219
|
+
explanation: Objet d'explication original (optionnel)
|
|
1220
|
+
instance: Instance expliquée (optionnel)
|
|
1221
|
+
prediction: Prédiction du modèle (optionnel)
|
|
1222
|
+
|
|
1223
|
+
Returns:
|
|
1224
|
+
dict: Métriques de qualité de l'explication
|
|
1225
|
+
"""
|
|
1226
|
+
metrics = {}
|
|
1227
|
+
|
|
1228
|
+
try:
|
|
1229
|
+
# 1. Précision de l'ancrage (directement depuis l'objet explanation)
|
|
1230
|
+
if explanation and hasattr(explanation, 'precision'):
|
|
1231
|
+
metrics['precision'] = float(explanation.precision)
|
|
1232
|
+
else:
|
|
1233
|
+
metrics['precision'] = None
|
|
1234
|
+
|
|
1235
|
+
# 2. Couverture de l'ancrage (directement depuis l'objet explanation)
|
|
1236
|
+
if explanation and hasattr(explanation, 'coverage'):
|
|
1237
|
+
metrics['coverage'] = float(explanation.coverage)
|
|
1238
|
+
else:
|
|
1239
|
+
metrics['coverage'] = None
|
|
1240
|
+
|
|
1241
|
+
# 3. Nombre de conditions dans la règle d'ancrage
|
|
1242
|
+
metrics['rule_length'] = len(anchor_rules) if anchor_rules else 0
|
|
1243
|
+
|
|
1244
|
+
# 4. Score de complexité de la règle (bas = simple, haut = complexe)
|
|
1245
|
+
# Normalisé par le nombre de caractéristiques disponibles
|
|
1246
|
+
n_features = len(self._feature_names) if self._feature_names else 1
|
|
1247
|
+
metrics['complexity_score'] = metrics['rule_length'] / n_features if n_features > 0 else 0
|
|
1248
|
+
|
|
1249
|
+
# 5. Stabilité de l'explication (indice de Jaccard moyen pour des petites perturbations)
|
|
1250
|
+
# Pour Anchor, nous utilisons la métrique de stabilité approximative basée sur la précision
|
|
1251
|
+
metrics['stability_score'] = float(metrics['precision']) if metrics['precision'] is not None else None
|
|
1252
|
+
|
|
1253
|
+
# 6. Score global de qualité (moyenne pondérée des scores individuels)
|
|
1254
|
+
# Combiner précision, couverture et inverse de la complexité
|
|
1255
|
+
quality_components = []
|
|
1256
|
+
weights = [0.5, 0.3, 0.2] # Pondération: précision, couverture, simplicité
|
|
1257
|
+
|
|
1258
|
+
if metrics['precision'] is not None:
|
|
1259
|
+
quality_components.append(metrics['precision'] * weights[0])
|
|
1260
|
+
if metrics['coverage'] is not None:
|
|
1261
|
+
quality_components.append(metrics['coverage'] * weights[1])
|
|
1262
|
+
if metrics['complexity_score'] is not None:
|
|
1263
|
+
# Inverser la complexité (plus simple = meilleur score)
|
|
1264
|
+
simplicity = 1.0 - min(1.0, metrics['complexity_score'])
|
|
1265
|
+
quality_components.append(simplicity * weights[2])
|
|
1266
|
+
|
|
1267
|
+
if quality_components:
|
|
1268
|
+
# Normaliser par la somme des poids utilisés
|
|
1269
|
+
used_weights = weights[:len(quality_components)]
|
|
1270
|
+
metrics['quality_score'] = sum(quality_components) / sum(used_weights)
|
|
1271
|
+
else:
|
|
1272
|
+
metrics['quality_score'] = None
|
|
1273
|
+
|
|
1274
|
+
except Exception as e:
|
|
1275
|
+
self._logger.warning(f"Erreur lors du calcul des métriques de qualité: {str(e)}")
|
|
1276
|
+
self._logger.debug(traceback.format_exc())
|
|
1277
|
+
|
|
1278
|
+
return metrics
|
|
1279
|
+
|
|
1280
|
+
def _get_cache_key(self, instance, **kwargs):
|
|
1281
|
+
"""
|
|
1282
|
+
Génère une clé de cache unique pour l'instance et les paramètres.
|
|
1283
|
+
|
|
1284
|
+
Args:
|
|
1285
|
+
instance: Instance à expliquer
|
|
1286
|
+
**kwargs: Paramètres d'explication
|
|
1287
|
+
|
|
1288
|
+
Returns:
|
|
1289
|
+
str: Clé de cache unique (hash MD5)
|
|
1290
|
+
"""
|
|
1291
|
+
try:
|
|
1292
|
+
# Extraire les paramètres qui affectent le résultat de l'explication
|
|
1293
|
+
cache_key_params = {
|
|
1294
|
+
'data_type': kwargs.get('data_type', 'tabular'),
|
|
1295
|
+
'threshold': kwargs.get('threshold', self._config.threshold),
|
|
1296
|
+
'tau': kwargs.get('tau', self._config.tau),
|
|
1297
|
+
'beam_size': kwargs.get('beam_size', self._config.beam_size),
|
|
1298
|
+
'max_anchor_size': kwargs.get('max_anchor_size', self._config.max_anchor_size),
|
|
1299
|
+
'stop_on_first': kwargs.get('stop_on_first', self._config.stop_on_first),
|
|
1300
|
+
'audience_level': str(kwargs.get('audience_level', AudienceLevel.TECHNICAL)),
|
|
1301
|
+
'language': kwargs.get('language', 'en'),
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
# Sérialiser l'instance selon son type
|
|
1305
|
+
instance_str = self._serialize_instance(instance)
|
|
1306
|
+
|
|
1307
|
+
# Créer un dictionnaire complet pour le hachage
|
|
1308
|
+
hash_dict = {
|
|
1309
|
+
'instance': instance_str,
|
|
1310
|
+
'params': cache_key_params,
|
|
1311
|
+
'model_id': id(self._model), # Ajouter l'ID du modèle pour éviter les collisions entre différents modèles
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
# Générer la clé de cache (hash MD5)
|
|
1315
|
+
hash_str = json.dumps(hash_dict, sort_keys=True)
|
|
1316
|
+
return hashlib.md5(hash_str.encode('utf-8')).hexdigest()
|
|
1317
|
+
|
|
1318
|
+
except Exception as e:
|
|
1319
|
+
self._logger.warning(f"Erreur lors de la génération de la clé de cache: {str(e)}")
|
|
1320
|
+
return None
|
|
1321
|
+
|
|
1322
|
+
def _serialize_instance(self, instance):
|
|
1323
|
+
"""
|
|
1324
|
+
Sérialise une instance pour le hachage du cache.
|
|
1325
|
+
|
|
1326
|
+
Args:
|
|
1327
|
+
instance: Instance à sérialiser
|
|
1328
|
+
|
|
1329
|
+
Returns:
|
|
1330
|
+
str: Représentation sérialisée de l'instance
|
|
1331
|
+
"""
|
|
1332
|
+
try:
|
|
1333
|
+
if isinstance(instance, np.ndarray):
|
|
1334
|
+
return instance.tolist()
|
|
1335
|
+
elif isinstance(instance, pd.DataFrame) or isinstance(instance, pd.Series):
|
|
1336
|
+
return instance.to_dict()
|
|
1337
|
+
elif isinstance(instance, (list, dict, str, int, float, bool)):
|
|
1338
|
+
return instance
|
|
1339
|
+
else:
|
|
1340
|
+
return str(instance)
|
|
1341
|
+
except Exception as e:
|
|
1342
|
+
self._logger.warning(f"Erreur lors de la sérialisation de l'instance: {str(e)}")
|
|
1343
|
+
return str(instance)
|
|
1344
|
+
|
|
1345
|
+
def reset_cache(self):
|
|
1346
|
+
"""
|
|
1347
|
+
Réinitialise le cache des explications.
|
|
1348
|
+
Utile lorsque le modèle est mis à jour ou lorsqu'on veut forcer
|
|
1349
|
+
le recalcul des explications.
|
|
1350
|
+
"""
|
|
1351
|
+
try:
|
|
1352
|
+
self._logger.info("Réinitialisation du cache des explications")
|
|
1353
|
+
self._get_cached_explanation.cache_clear()
|
|
1354
|
+
return {"status": "success", "message": "Cache réinitialisé avec succès"}
|
|
1355
|
+
except Exception as e:
|
|
1356
|
+
self._logger.error(f"Erreur lors de la réinitialisation du cache: {str(e)}")
|
|
1357
|
+
return {"status": "error", "message": f"Erreur lors de la réinitialisation du cache: {str(e)}"}
|
|
1358
|
+
|
|
1359
|
+
@lru_cache(maxsize=128)
|
|
1360
|
+
def _get_cached_explanation(self, cache_key, instance, **kwargs):
|
|
1361
|
+
"""
|
|
1362
|
+
Récupère ou calcule une explication avec cache LRU.
|
|
1363
|
+
Cette méthode est décorée avec lru_cache pour mémoriser les explications précédemment calculées.
|
|
1364
|
+
|
|
1365
|
+
Args:
|
|
1366
|
+
cache_key: Clé unique pour l'instance et les paramètres
|
|
1367
|
+
instance: Instance à expliquer
|
|
1368
|
+
**kwargs: Paramètres d'explication
|
|
1369
|
+
|
|
1370
|
+
Returns:
|
|
1371
|
+
dict: Résultat d'explication
|
|
1372
|
+
"""
|
|
1373
|
+
try:
|
|
1374
|
+
# Décorer cette méthode avec lru_cache permet de mettre automatiquement en cache
|
|
1375
|
+
# les résultats selon la clé fournie. Si la même clé est utilisée à nouveau,
|
|
1376
|
+
# le résultat en cache est retourné sans recalcul.
|
|
1377
|
+
|
|
1378
|
+
# Calculer l'explication
|
|
1379
|
+
explanation = self._compute_explanation_cached(instance, **kwargs)
|
|
1380
|
+
|
|
1381
|
+
# Marquer comme provenant du cache
|
|
1382
|
+
if explanation and 'metadata' in explanation:
|
|
1383
|
+
explanation['metadata']['from_cache'] = True
|
|
1384
|
+
|
|
1385
|
+
return explanation
|
|
1386
|
+
|
|
1387
|
+
except Exception as e:
|
|
1388
|
+
self._logger.error(f"Erreur lors de la récupération du cache: {str(e)}")
|
|
1389
|
+
return {"error": f"Erreur de cache: {str(e)}"}
|
|
1390
|
+
|
|
1391
|
+
def _extract_model_type(self):
|
|
1392
|
+
"""
|
|
1393
|
+
Extrait le type de modèle/framework utilisé (scikit-learn, TensorFlow, PyTorch, etc.)
|
|
1394
|
+
|
|
1395
|
+
Returns:
|
|
1396
|
+
str: Type de framework détecté
|
|
1397
|
+
"""
|
|
1398
|
+
module_name = self._model.__class__.__module__.split('.')[0].lower()
|
|
1399
|
+
|
|
1400
|
+
if module_name in ('sklearn', 'lightgbm', 'xgboost'):
|
|
1401
|
+
return module_name
|
|
1402
|
+
elif module_name == 'keras' or 'tensorflow' in module_name:
|
|
1403
|
+
return 'tensorflow'
|
|
1404
|
+
elif 'torch' in module_name:
|
|
1405
|
+
return 'pytorch'
|
|
1406
|
+
else:
|
|
1407
|
+
return 'unknown'
|
|
1408
|
+
|
|
1409
|
+
def _generate_explanation_narrative(self, anchor_rules, feature_importances, prediction=None, audience_level=AudienceLevel.TECHNICAL, language='en'):
|
|
1410
|
+
"""
|
|
1411
|
+
Génère une narrative d'explication adaptée à l'audience et à la langue.
|
|
1412
|
+
|
|
1413
|
+
Args:
|
|
1414
|
+
anchor_rules: Règles d'ancrage générées
|
|
1415
|
+
feature_importances: Importances des caractéristiques
|
|
1416
|
+
prediction: Prédiction du modèle (optionnel)
|
|
1417
|
+
audience_level: Niveau d'audience (technique, business, public)
|
|
1418
|
+
language: Code de langue ('en', 'fr', etc.)
|
|
1419
|
+
|
|
1420
|
+
Returns:
|
|
1421
|
+
dict: Narratives d'explication par niveau d'audience et langue
|
|
1422
|
+
"""
|
|
1423
|
+
narratives = {}
|
|
1424
|
+
|
|
1425
|
+
try:
|
|
1426
|
+
# Définir les templates par niveau d'audience et langue
|
|
1427
|
+
templates = {
|
|
1428
|
+
'en': {
|
|
1429
|
+
'technical': {
|
|
1430
|
+
'intro': "The model's prediction is explained by the following anchor rule conditions:",
|
|
1431
|
+
'rule': "The model predicts '{prediction}' when {conditions}.",
|
|
1432
|
+
'precision': "This rule has {precision:.0%} precision, meaning the prediction is consistent {precision:.0%} of the time when these conditions are met.",
|
|
1433
|
+
'coverage': "The rule covers {coverage:.0%} of instances similar to this one.",
|
|
1434
|
+
'outro': "The model relies primarily on {top_features_str} for this prediction."
|
|
1435
|
+
},
|
|
1436
|
+
'business': {
|
|
1437
|
+
'intro': "The prediction for this case is based on the following key factors:",
|
|
1438
|
+
'rule': "When {conditions_simple}, our model predicts '{prediction}'.",
|
|
1439
|
+
'reliability': "This explanation is reliable in {precision:.0%} of similar cases.",
|
|
1440
|
+
'outro': "The most influential factors were: {top_features_simple}."
|
|
1441
|
+
},
|
|
1442
|
+
'public': {
|
|
1443
|
+
'intro': "Here's why this decision was made:",
|
|
1444
|
+
'rule': "The system predicted '{prediction}' because {conditions_very_simple}.",
|
|
1445
|
+
'outro': "The main factors affecting this decision were: {top_features_very_simple}."
|
|
1446
|
+
}
|
|
1447
|
+
},
|
|
1448
|
+
'fr': {
|
|
1449
|
+
'technical': {
|
|
1450
|
+
'intro': "La prédiction du modèle est expliquée par les conditions d'ancrage suivantes :",
|
|
1451
|
+
'rule': "Le modèle prédit '{prediction}' lorsque {conditions}.",
|
|
1452
|
+
'precision': "Cette règle a une précision de {precision:.0%}, ce qui signifie que la prédiction est cohérente dans {precision:.0%} des cas où ces conditions sont respectées.",
|
|
1453
|
+
'coverage': "La règle couvre {coverage:.0%} des instances similaires à celle-ci.",
|
|
1454
|
+
'outro': "Le modèle s'appuie principalement sur {top_features_str} pour cette prédiction."
|
|
1455
|
+
},
|
|
1456
|
+
'business': {
|
|
1457
|
+
'intro': "La prédiction pour ce cas est basée sur les facteurs clés suivants :",
|
|
1458
|
+
'rule': "Lorsque {conditions_simple}, notre modèle prédit '{prediction}'.",
|
|
1459
|
+
'reliability': "Cette explication est fiable dans {precision:.0%} des cas similaires.",
|
|
1460
|
+
'outro': "Les facteurs les plus influents étaient : {top_features_simple}."
|
|
1461
|
+
},
|
|
1462
|
+
'public': {
|
|
1463
|
+
'intro': "Voici pourquoi cette décision a été prise :",
|
|
1464
|
+
'rule': "Le système a prédit '{prediction}' parce que {conditions_very_simple}.",
|
|
1465
|
+
'outro': "Les principaux facteurs affectant cette décision étaient : {top_features_very_simple}."
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
# S'assurer que la langue est supportée, sinon utiliser l'anglais
|
|
1471
|
+
if language not in templates:
|
|
1472
|
+
language = 'en'
|
|
1473
|
+
|
|
1474
|
+
# Préparer les éléments pour les narratives
|
|
1475
|
+
precision = 0.0
|
|
1476
|
+
coverage = 0.0
|
|
1477
|
+
prediction_str = str(prediction) if prediction is not None else "unknown"
|
|
1478
|
+
|
|
1479
|
+
# Extraire métriques de l'ancrage si disponibles
|
|
1480
|
+
if hasattr(anchor_rules, 'precision'):
|
|
1481
|
+
precision = float(anchor_rules.precision)
|
|
1482
|
+
if hasattr(anchor_rules, 'coverage'):
|
|
1483
|
+
coverage = float(anchor_rules.coverage)
|
|
1484
|
+
|
|
1485
|
+
# Extraire les règles d'ancrage en texte
|
|
1486
|
+
conditions_text = "no specific conditions found"
|
|
1487
|
+
if isinstance(anchor_rules, list) and anchor_rules:
|
|
1488
|
+
conditions_text = " AND ".join(anchor_rules)
|
|
1489
|
+
elif hasattr(anchor_rules, 'names') and anchor_rules.names:
|
|
1490
|
+
conditions_text = " AND ".join(anchor_rules.names)
|
|
1491
|
+
|
|
1492
|
+
# Simplifier les conditions pour les audiences non techniques
|
|
1493
|
+
conditions_simple = conditions_text.replace(' AND ', ' et ') # Version plus simple
|
|
1494
|
+
conditions_very_simple = conditions_text.replace(' AND ', ' et ')
|
|
1495
|
+
if len(conditions_very_simple.split(' et ')) > 2:
|
|
1496
|
+
# Limiter à 2 conditions max pour le public
|
|
1497
|
+
parts = conditions_very_simple.split(' et ')
|
|
1498
|
+
conditions_very_simple = ' et '.join(parts[:2]) + f" et {len(parts)-2} autres facteurs"
|
|
1499
|
+
|
|
1500
|
+
# Extraire les principales caractéristiques
|
|
1501
|
+
top_features = []
|
|
1502
|
+
if feature_importances:
|
|
1503
|
+
# Trier par importance décroissante et prendre les 3 premières
|
|
1504
|
+
sorted_features = sorted(feature_importances, key=lambda x: abs(x.importance), reverse=True)
|
|
1505
|
+
top_features = sorted_features[:3]
|
|
1506
|
+
|
|
1507
|
+
# Formater les noms des caractéristiques principales
|
|
1508
|
+
top_features_str = ", ".join([f"{feature.feature_name} ({feature.importance:.2f})" for feature in top_features]) if top_features else "aucune caractéristique identifiée"
|
|
1509
|
+
top_features_simple = ", ".join([feature.feature_name for feature in top_features]) if top_features else "aucun facteur identifié"
|
|
1510
|
+
top_features_very_simple = top_features_simple
|
|
1511
|
+
|
|
1512
|
+
# Construire la narrative pour chaque niveau d'audience
|
|
1513
|
+
for audience in ['technical', 'business', 'public']:
|
|
1514
|
+
if audience == audience_level.lower() or audience_level == AudienceLevel.ALL:
|
|
1515
|
+
template = templates[language][audience]
|
|
1516
|
+
narrative_parts = []
|
|
1517
|
+
|
|
1518
|
+
# Construction de la narrative selon le niveau d'audience
|
|
1519
|
+
if audience == 'technical':
|
|
1520
|
+
narrative_parts.append(template['intro'])
|
|
1521
|
+
narrative_parts.append(template['rule'].format(
|
|
1522
|
+
prediction=prediction_str,
|
|
1523
|
+
conditions=conditions_text
|
|
1524
|
+
))
|
|
1525
|
+
narrative_parts.append(template['precision'].format(precision=precision))
|
|
1526
|
+
narrative_parts.append(template['coverage'].format(coverage=coverage))
|
|
1527
|
+
narrative_parts.append(template['outro'].format(top_features_str=top_features_str))
|
|
1528
|
+
elif audience == 'business':
|
|
1529
|
+
narrative_parts.append(template['intro'])
|
|
1530
|
+
narrative_parts.append(template['rule'].format(
|
|
1531
|
+
prediction=prediction_str,
|
|
1532
|
+
conditions_simple=conditions_simple
|
|
1533
|
+
))
|
|
1534
|
+
narrative_parts.append(template['reliability'].format(precision=precision))
|
|
1535
|
+
narrative_parts.append(template['outro'].format(top_features_simple=top_features_simple))
|
|
1536
|
+
else: # public
|
|
1537
|
+
narrative_parts.append(template['intro'])
|
|
1538
|
+
narrative_parts.append(template['rule'].format(
|
|
1539
|
+
prediction=prediction_str,
|
|
1540
|
+
conditions_very_simple=conditions_very_simple
|
|
1541
|
+
))
|
|
1542
|
+
narrative_parts.append(template['outro'].format(top_features_very_simple=top_features_very_simple))
|
|
1543
|
+
|
|
1544
|
+
narratives[audience] = " ".join(narrative_parts)
|
|
1545
|
+
|
|
1546
|
+
except Exception as e:
|
|
1547
|
+
self._logger.warning(f"Erreur lors de la génération de narratives d'explication: {str(e)}")
|
|
1548
|
+
narratives['error'] = f"Unable to generate explanation narrative: {str(e)}"
|
|
1549
|
+
|
|
1550
|
+
return narratives
|
|
1551
|
+
|
|
1552
|
+
def _compute_explanation_cached(self, instance, **kwargs):
|
|
1553
|
+
"""
|
|
1554
|
+
Méthode centrale pour calculer l'explication avec toutes les fonctionnalités avancées.
|
|
1555
|
+
Cette méthode intègre la gestion du GPU, le calcul des métriques de qualité,
|
|
1556
|
+
la génération de narratives multilingues et la vérification de conformité.
|
|
1557
|
+
|
|
1558
|
+
Args:
|
|
1559
|
+
instance: Instance à expliquer
|
|
1560
|
+
**kwargs: Paramètres d'explication
|
|
1561
|
+
data_type: Type de données ('tabular', 'text', 'image')
|
|
1562
|
+
threshold: Seuil de précision pour l'ancrage
|
|
1563
|
+
|
|
1564
|
+
# Créer un résultat d'erreur
|
|
1565
|
+
result = ExplanationResult(
|
|
1566
|
+
method=ExplainabilityMethod.ANCHOR,
|
|
1567
|
+
feature_importances=[],
|
|
1568
|
+
metadata={
|
|
1569
|
+
'error': error_msg,
|
|
1570
|
+
'execution_time_ms': int((time.time() - start_time) * 1000)
|
|
1571
|
+
},
|
|
1572
|
+
audience_level=audience_level
|
|
1573
|
+
)
|
|
1574
|
+
else:
|
|
1575
|
+
# Extraire les informations importantes du résultat calculé
|
|
1576
|
+
feature_importances_data = explanation_data.get('feature_importances', [])
|
|
1577
|
+
feature_importances = []
|
|
1578
|
+
use_unk=kwargs.get('use_unk', True),
|
|
1579
|
+
use_uppercase_unk=kwargs.get('use_uppercase_unk', True),
|
|
1580
|
+
sample_proba=kwargs.get('sample_proba', 0.5),
|
|
1581
|
+
top_n=kwargs.get('top_n', 100),
|
|
1582
|
+
temperature=kwargs.get('temperature', 1.0))
|
|
1583
|
+
|
|
1584
|
+
elif data_type == 'image':
|
|
1585
|
+
# Pour les images
|
|
1586
|
+
anchor_explanation = self._explain_image_instance(instance_array,
|
|
1587
|
+
threshold=threshold,
|
|
1588
|
+
segmentation_fn=kwargs.get('segmentation_fn', None),
|
|
1589
|
+
p_sample=kwargs.get('p_sample', 0.5),
|
|
1590
|
+
n_segments=kwargs.get('n_segments', 10))
|
|
1591
|
+
|
|
1592
|
+
# 3. Vérifier que l'explication a été générée avec succès
|
|
1593
|
+
if anchor_explanation is None:
|
|
1594
|
+
raise ValueError("L'explication Anchor n'a pas pu être générée")
|
|
1595
|
+
|
|
1596
|
+
# 4. Obtenir une prédiction du modèle pour l'instance
|
|
1597
|
+
try:
|
|
1598
|
+
if hasattr(self._model, 'predict_proba') and self._get_task_type() == 'classification':
|
|
1599
|
+
prediction = self._model.predict_proba(instance_array)[0]
|
|
1600
|
+
predicted_class = np.argmax(prediction)
|
|
1601
|
+
if self._class_names and predicted_class < len(self._class_names):
|
|
1602
|
+
prediction_label = self._class_names[predicted_class]
|
|
1603
|
+
else:
|
|
1604
|
+
prediction_label = str(predicted_class)
|
|
1605
|
+
else:
|
|
1606
|
+
prediction = self._model.predict(instance_array)[0]
|
|
1607
|
+
prediction_label = str(prediction)
|
|
1608
|
+
except Exception as pred_err:
|
|
1609
|
+
self._logger.warning(f"Erreur lors de la prédiction: {str(pred_err)}")
|
|
1610
|
+
prediction = None
|
|
1611
|
+
prediction_label = "unknown"
|
|
1612
|
+
|
|
1613
|
+
# 5. Extraire les règles d'ancrage
|
|
1614
|
+
feature_names = kwargs.get('feature_names', self._feature_names)
|
|
1615
|
+
anchor_rules = self._extract_anchor_rules(anchor_explanation, feature_names)
|
|
1616
|
+
|
|
1617
|
+
# 6. Convertir les règles en importances de caractéristiques
|
|
1618
|
+
feature_importances = self._convert_rules_to_importances(anchor_rules, feature_names)
|
|
1619
|
+
|
|
1620
|
+
# 7. Calculer les métriques de qualité de l'explication
|
|
1621
|
+
quality_metrics = {}
|
|
1622
|
+
if self._config.compute_quality_metrics:
|
|
1623
|
+
quality_metrics = self._compute_explanation_quality_metrics(
|
|
1624
|
+
anchor_rules=anchor_rules,
|
|
1625
|
+
explanation=anchor_explanation,
|
|
1626
|
+
instance=instance,
|
|
1627
|
+
prediction=prediction
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
# 8. Générer les narratives d'explication
|
|
1631
|
+
narratives = {}
|
|
1632
|
+
narratives[language] = self._generate_explanation_narrative(
|
|
1633
|
+
anchor_rules=anchor_explanation,
|
|
1634
|
+
feature_importances=feature_importances,
|
|
1635
|
+
prediction=prediction_label,
|
|
1636
|
+
audience_level=audience_level,
|
|
1637
|
+
language=language
|
|
1638
|
+
)
|
|
1639
|
+
|
|
1640
|
+
# 9. Construire le résultat d'explication
|
|
1641
|
+
result = {
|
|
1642
|
+
"method": "anchor",
|
|
1643
|
+
"feature_importances": [fi.__dict__ for fi in feature_importances],
|
|
1644
|
+
"anchor_rules": anchor_rules,
|
|
1645
|
+
"anchor_metrics": {
|
|
1646
|
+
"precision": float(anchor_explanation.precision) if hasattr(anchor_explanation, 'precision') else None,
|
|
1647
|
+
"coverage": float(anchor_explanation.coverage) if hasattr(anchor_explanation, 'coverage') else None,
|
|
1648
|
+
},
|
|
1649
|
+
"prediction": prediction_label,
|
|
1650
|
+
"quality_metrics": quality_metrics,
|
|
1651
|
+
"narratives": narratives,
|
|
1652
|
+
"metadata": execution_metadata
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
# 10. Vérifier la conformité réglementaire si demandé
|
|
1656
|
+
if self._config.verify_compliance:
|
|
1657
|
+
compliance_result = self._verify_compliance_requirements(result, instance)
|
|
1658
|
+
result["compliance"] = compliance_result
|
|
1659
|
+
|
|
1660
|
+
# Marquer comme réussi
|
|
1661
|
+
execution_metadata['success'] = True
|
|
1662
|
+
|
|
1663
|
+
except Exception as e:
|
|
1664
|
+
# En cas d'erreur, enregistrer les détails
|
|
1665
|
+
self._logger.error(f"Erreur lors du calcul de l'explication: {str(e)}")
|
|
1666
|
+
self._logger.debug(traceback.format_exc())
|
|
1667
|
+
|
|
1668
|
+
# Retourner un résultat minimal avec l'erreur
|
|
1669
|
+
result = {
|
|
1670
|
+
"method": "anchor",
|
|
1671
|
+
"error": str(e),
|
|
1672
|
+
"traceback": traceback.format_exc(),
|
|
1673
|
+
"metadata": execution_metadata
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
finally:
|
|
1677
|
+
# Calculer le temps d'exécution
|
|
1678
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
1679
|
+
execution_metadata['execution_time_ms'] = execution_time_ms
|
|
1680
|
+
result["metadata"] = execution_metadata
|
|
1681
|
+
|
|
1682
|
+
# Tracer les métriques d'exécution
|
|
1683
|
+
self._logger.info(f"Explication Anchor calculée en {execution_time_ms}ms (succès: {execution_metadata['success']})")
|
|
1684
|
+
|
|
1685
|
+
# Collecter des statistiques d'utilisation de mémoire/GPU si disponible
|
|
1686
|
+
try:
|
|
1687
|
+
import psutil
|
|
1688
|
+
memory_info = psutil.Process().memory_info()
|
|
1689
|
+
execution_metadata['memory_rss_bytes'] = memory_info.rss
|
|
1690
|
+
execution_metadata['memory_vms_bytes'] = memory_info.vms
|
|
1691
|
+
|
|
1692
|
+
# Statistiques GPU si disponible
|
|
1693
|
+
if use_gpu:
|
|
1694
|
+
if self._framework == 'tensorflow':
|
|
1695
|
+
try:
|
|
1696
|
+
import tensorflow as tf
|
|
1697
|
+
if tf.config.list_physical_devices('GPU'):
|
|
1698
|
+
gpu_stats = tf.config.experimental.get_memory_info('GPU:0')
|
|
1699
|
+
execution_metadata['gpu_memory_bytes'] = gpu_stats['current']
|
|
1700
|
+
except:
|
|
1701
|
+
pass
|
|
1702
|
+
elif self._framework == 'pytorch':
|
|
1703
|
+
try:
|
|
1704
|
+
import torch
|
|
1705
|
+
if torch.cuda.is_available():
|
|
1706
|
+
execution_metadata['gpu_memory_bytes'] = torch.cuda.memory_allocated()
|
|
1707
|
+
execution_metadata['gpu_max_memory_bytes'] = torch.cuda.max_memory_allocated()
|
|
1708
|
+
except:
|
|
1709
|
+
pass
|
|
1710
|
+
except ImportError:
|
|
1711
|
+
pass # psutil non disponible
|
|
1712
|
+
except Exception as mem_err:
|
|
1713
|
+
self._logger.debug(f"Erreur lors de la collecte des métriques de mémoire: {str(mem_err)}")
|
|
1714
|
+
|
|
1715
|
+
return result
|
|
1716
|
+
|
|
1717
|
+
def _verify_compliance_requirements(self, explanation_data, instance):
|
|
1718
|
+
"""
|
|
1719
|
+
Vérifie la conformité réglementaire de l'explication générée.
|
|
1720
|
+
|
|
1721
|
+
Args:
|
|
1722
|
+
explanation_data: Données d'explication générées
|
|
1723
|
+
instance: Instance expliquée
|
|
1724
|
+
|
|
1725
|
+
Returns:
|
|
1726
|
+
dict: Résultat de la vérification de conformité
|
|
1727
|
+
"""
|
|
1728
|
+
if not self._compliance_checker:
|
|
1729
|
+
return {"status": "skip", "reason": "Aucun vérificateur de conformité disponible"}
|
|
1730
|
+
|
|
1731
|
+
try:
|
|
1732
|
+
# Extraire les éléments pertinents pour la vérification
|
|
1733
|
+
check_data = {
|
|
1734
|
+
"explainer_type": "AnchorExplainer",
|
|
1735
|
+
"feature_importances": explanation_data.get("feature_importances", []),
|
|
1736
|
+
"anchor_rules": explanation_data.get("anchor_rules", []),
|
|
1737
|
+
"anchor_metrics": explanation_data.get("anchor_metrics", {}),
|
|
1738
|
+
"instance": instance,
|
|
1739
|
+
"metadata": explanation_data.get("metadata", {})
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
# Vérifier la conformité
|
|
1743
|
+
compliance_result = self._compliance_checker.check_explanation(check_data)
|
|
1744
|
+
|
|
1745
|
+
# Enregistrer un audit de conformité
|
|
1746
|
+
try:
|
|
1747
|
+
from ..audit import AuditLogger
|
|
1748
|
+
audit_logger = AuditLogger()
|
|
1749
|
+
audit_logger.log_compliance_check(
|
|
1750
|
+
explainer_type="AnchorExplainer",
|
|
1751
|
+
status=compliance_result.get("status", "unknown"),
|
|
1752
|
+
requirements=compliance_result.get("requirements", {}),
|
|
1753
|
+
details=compliance_result
|
|
1754
|
+
)
|
|
1755
|
+
except ImportError:
|
|
1756
|
+
self._logger.debug("Module d'audit non disponible")
|
|
1757
|
+
except Exception as audit_err:
|
|
1758
|
+
self._logger.debug(f"Erreur lors de l'audit de conformité: {str(audit_err)}")
|
|
1759
|
+
|
|
1760
|
+
return compliance_result
|
|
1761
|
+
|
|
1762
|
+
except Exception as e:
|
|
1763
|
+
error_result = {
|
|
1764
|
+
"status": "error",
|
|
1765
|
+
"reason": f"Erreur lors de la vérification de conformité: {str(e)}",
|
|
1766
|
+
"details": traceback.format_exc()
|
|
1767
|
+
}
|
|
1768
|
+
self._logger.warning(f"Erreur lors de la vérification de conformité: {str(e)}")
|
|
1769
|
+
return error_result
|