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,2238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SHAP Explainer pour XPLIA
|
|
3
|
+
========================
|
|
4
|
+
|
|
5
|
+
Module d'explicabilité avancé basé sur SHAP (SHapley Additive exPlanations) pour XPLIA.
|
|
6
|
+
|
|
7
|
+
Ce module implémente une intégration sophistiquée des explications SHAP dans le framework
|
|
8
|
+
XPLIA, offrant une solution complète et optimisée pour l'explicabilité des modèles d'IA
|
|
9
|
+
via la méthode des valeurs de Shapley.
|
|
10
|
+
|
|
11
|
+
Caractéristiques principales:
|
|
12
|
+
-----------------------------
|
|
13
|
+
* Support multi-framework automatique (scikit-learn, XGBoost, LightGBM, TensorFlow, PyTorch)
|
|
14
|
+
* Optimisations de performance avancées pour grands jeux de données
|
|
15
|
+
* Échantillonnage intelligent et parallélisation
|
|
16
|
+
* Métriques de qualité d'explication intégrées (fidélité, cohérence, stabilité)
|
|
17
|
+
* Adaptation sophistiquée par niveau d'audience (technique, métier, réglementaire)
|
|
18
|
+
* Génération automatique de narratives en langage naturel
|
|
19
|
+
* Gestion des valeurs d'interaction et des analyses contrefactuelles
|
|
20
|
+
* Robustesse face aux valeurs manquantes et aberrantes
|
|
21
|
+
* Visualisations avancées et interactives
|
|
22
|
+
* Intégration transparente avec les modules de conformité réglementaire
|
|
23
|
+
* Benchmarking de performance et qualité intégré
|
|
24
|
+
* Cache intelligent pour réexplications rapides
|
|
25
|
+
|
|
26
|
+
Références théoriques:
|
|
27
|
+
---------------------
|
|
28
|
+
* Lundberg, S. M., & Lee, S. I. (2017). A unified approach to interpreting model predictions.
|
|
29
|
+
* Štrumbelj, E., & Kononenko, I. (2014). Explaining prediction models and individual predictions.
|
|
30
|
+
* Sundararajan, M., & Najmi, A. (2020). The many Shapley values for model explanation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
import time
|
|
35
|
+
import warnings
|
|
36
|
+
import inspect
|
|
37
|
+
import os
|
|
38
|
+
import json
|
|
39
|
+
import hashlib
|
|
40
|
+
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
|
|
41
|
+
from functools import lru_cache, partial
|
|
42
|
+
from dataclasses import dataclass, field
|
|
43
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, Callable, Set, Type
|
|
44
|
+
|
|
45
|
+
import numpy as np
|
|
46
|
+
import pandas as pd
|
|
47
|
+
import matplotlib.pyplot as plt
|
|
48
|
+
from scipy import stats
|
|
49
|
+
|
|
50
|
+
# Import des optimisations de performance XPLIA
|
|
51
|
+
from ..core.optimizations import (
|
|
52
|
+
optimize, parallel_map, chunked_processing, cached_call,
|
|
53
|
+
optimize_memory, XPLIAOptimizer
|
|
54
|
+
)
|
|
55
|
+
from ..core.performance import (
|
|
56
|
+
ParallelExecutor, cached_result, memory_efficient,
|
|
57
|
+
process_in_chunks, optimize_explanations as opt_explanations
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
from ..core.base import (
|
|
61
|
+
AudienceLevel, ExplainerBase, ExplainabilityMethod,
|
|
62
|
+
ExplanationResult, FeatureImportance, ModelMetadata,
|
|
63
|
+
ExplanationQuality, ExplanationFormat
|
|
64
|
+
)
|
|
65
|
+
from ..core.registry import register_explainer
|
|
66
|
+
from ..utils.performance import Timer, MemoryTracker
|
|
67
|
+
from ..compliance import ComplianceChecker
|
|
68
|
+
|
|
69
|
+
# Définition de types personnalisés pour une meilleure lisibilité
|
|
70
|
+
DataType = Union[np.ndarray, pd.DataFrame, List[Union[List, Dict]]]
|
|
71
|
+
ModelType = Any
|
|
72
|
+
ShapValuesType = Union[np.ndarray, List[np.ndarray]]
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class ShapConfig:
|
|
76
|
+
"""Configuration avancée pour l'explainer SHAP."""
|
|
77
|
+
# Paramètres d'initialisation
|
|
78
|
+
background_data: Optional[DataType] = None
|
|
79
|
+
n_samples: int = 100
|
|
80
|
+
link: str = 'identity'
|
|
81
|
+
shap_type: str = 'auto' # 'kernel', 'tree', 'deep', 'gradient', 'auto'
|
|
82
|
+
|
|
83
|
+
# Paramètres de performance
|
|
84
|
+
use_gpu: bool = False
|
|
85
|
+
n_jobs: int = -1 # -1 = tous les CPU disponibles
|
|
86
|
+
batch_size: int = 100
|
|
87
|
+
cache_size: int = 128 # Taille du cache LRU
|
|
88
|
+
timeout: Optional[float] = None # Timeout en secondes
|
|
89
|
+
|
|
90
|
+
# Paramètres d'explicabilité
|
|
91
|
+
include_interactions: bool = False
|
|
92
|
+
feature_perturbation: str = 'interventional' # ou 'tree_path_dependent'
|
|
93
|
+
masker_type: Optional[str] = None # 'independent', 'tabular', 'image', 'text'
|
|
94
|
+
approximate: bool = False # Approximation pour gagner en performance
|
|
95
|
+
clustering: bool = False # Clustering pour réduire la dimensionnalité
|
|
96
|
+
|
|
97
|
+
# Métriques de qualité
|
|
98
|
+
compute_metrics: bool = True
|
|
99
|
+
|
|
100
|
+
# Contraintes réglementaires
|
|
101
|
+
compliance_mode: bool = False
|
|
102
|
+
compliance_regs: List[str] = field(default_factory=list)
|
|
103
|
+
|
|
104
|
+
def __post_init__(self):
|
|
105
|
+
if self.n_jobs == -1:
|
|
106
|
+
import multiprocessing
|
|
107
|
+
self.n_jobs = multiprocessing.cpu_count()
|
|
108
|
+
|
|
109
|
+
# Vérification de la cohérence des paramètres
|
|
110
|
+
if self.clustering and not self.approximate:
|
|
111
|
+
warnings.warn("Le clustering est plus efficace avec approximate=True")
|
|
112
|
+
|
|
113
|
+
if self.use_gpu:
|
|
114
|
+
try:
|
|
115
|
+
import tensorflow as tf
|
|
116
|
+
if not tf.test.is_gpu_available():
|
|
117
|
+
warnings.warn("GPU demandé mais non disponible. Utilisation du CPU.")
|
|
118
|
+
self.use_gpu = False
|
|
119
|
+
except ImportError:
|
|
120
|
+
warnings.warn("TensorFlow requis pour l'utilisation du GPU. Utilisation du CPU.")
|
|
121
|
+
self.use_gpu = False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@register_explainer
|
|
125
|
+
class ShapExplainer(ExplainerBase):
|
|
126
|
+
"""
|
|
127
|
+
Explainer avancé basé sur SHAP (SHapley Additive exPlanations).
|
|
128
|
+
|
|
129
|
+
Cette classe implémente une solution sophistiquée d'explicabilité basée sur la théorie
|
|
130
|
+
des jeux coopératifs, attribuant à chaque caractéristique une valeur d'importance
|
|
131
|
+
optimale pour des décisions plus transparentes et justifiables.
|
|
132
|
+
|
|
133
|
+
Caractéristiques principales:
|
|
134
|
+
--------------------------
|
|
135
|
+
* Détection automatique et optimisée du type de modèle et du framework
|
|
136
|
+
* Sélection intelligente de l'implémentation SHAP optimale (kernel, tree, deep, etc.)
|
|
137
|
+
* Optimisations avancées de performance:
|
|
138
|
+
- Calcul parallélisé multi-CPU/GPU
|
|
139
|
+
- Mise en cache intelligente des résultats
|
|
140
|
+
- Échantillonnage adaptatif pour grands jeux de données
|
|
141
|
+
- Gestion avancée de la mémoire et timeouts configurables
|
|
142
|
+
* Métriques de qualité d'explication intégrées:
|
|
143
|
+
- Fidélité (conformité aux prédictions du modèle)
|
|
144
|
+
- Cohérence (stabilité face à des variations mineures)
|
|
145
|
+
- Sparsité (concision des explications)
|
|
146
|
+
- Exactitude (précision des attributions)
|
|
147
|
+
* Support de cas d'usage spécialisés:
|
|
148
|
+
- Analysis d'interactions entre variables
|
|
149
|
+
- Évaluation contrefactuelle
|
|
150
|
+
- Robustesse à l'adversarial noise
|
|
151
|
+
- Agrégation multi-modèles
|
|
152
|
+
* Interface flexible et multi-format:
|
|
153
|
+
- Visualisations adaptatives selon le niveau d'audience
|
|
154
|
+
- Génération de narratives en langage naturel
|
|
155
|
+
- Exportations multi-formats (JSON, HTML, PDF)
|
|
156
|
+
* Intégration complète avec les modules de conformité réglementaire
|
|
157
|
+
* Robustesse face aux données manquantes et aberrantes
|
|
158
|
+
* Documentation contextuelle et exemples intégrés
|
|
159
|
+
|
|
160
|
+
Cette implémentation inclut les dernières avancées de recherche en matière
|
|
161
|
+
d'explicabilité et d'interprétabilité des modèles d'IA, tout en offrant
|
|
162
|
+
une interface simple et des performances optimales pour l'utilisation en
|
|
163
|
+
production.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
# Constantes de la classe
|
|
167
|
+
_SHAP_TYPES = ["kernel", "tree", "deep", "gradient", "partition", "permutation", "auto"]
|
|
168
|
+
_LINK_FUNCTIONS = ["identity", "logit"]
|
|
169
|
+
_MODEL_TYPES = {
|
|
170
|
+
"tree": [
|
|
171
|
+
"RandomForestClassifier", "RandomForestRegressor",
|
|
172
|
+
"GradientBoostingClassifier", "GradientBoostingRegressor",
|
|
173
|
+
"XGBClassifier", "XGBRegressor", "Booster",
|
|
174
|
+
"LGBMClassifier", "LGBMRegressor",
|
|
175
|
+
"CatBoostClassifier", "CatBoostRegressor",
|
|
176
|
+
"DecisionTreeClassifier", "DecisionTreeRegressor",
|
|
177
|
+
"GradientBoosting", "RandomForest", "ExtraTreesClassifier",
|
|
178
|
+
"ExtraTreesRegressor", "IsolationForest"
|
|
179
|
+
],
|
|
180
|
+
"neural": [
|
|
181
|
+
"Sequential", "Model", "Module", "Functional",
|
|
182
|
+
"MLPClassifier", "MLPRegressor", "KerasClassifier", "KerasRegressor"
|
|
183
|
+
],
|
|
184
|
+
"differentiable": [
|
|
185
|
+
"LogisticRegression", "LinearRegression",
|
|
186
|
+
"SGDClassifier", "SGDRegressor", "LinearSVC", "LinearSVR",
|
|
187
|
+
"Ridge", "Lasso", "ElasticNet"
|
|
188
|
+
],
|
|
189
|
+
"generic": [
|
|
190
|
+
"SVC", "SVR", "KNeighborsClassifier", "KNeighborsRegressor",
|
|
191
|
+
"GaussianProcessClassifier", "GaussianProcessRegressor",
|
|
192
|
+
"Pipeline", "BaggingClassifier", "BaggingRegressor",
|
|
193
|
+
"VotingClassifier", "VotingRegressor", "StackingClassifier", "StackingRegressor"
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
_FRAMEWORK_IDENTIFIERS = {
|
|
197
|
+
"sklearn": ["sklearn"],
|
|
198
|
+
"xgboost": ["xgboost"],
|
|
199
|
+
"lightgbm": ["lightgbm"],
|
|
200
|
+
"catboost": ["catboost"],
|
|
201
|
+
"tensorflow": ["tensorflow", "keras", "tf"],
|
|
202
|
+
"pytorch": ["torch"],
|
|
203
|
+
"mxnet": ["mxnet"]
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
def __init__(self, model: ModelType, **kwargs):
|
|
207
|
+
"""
|
|
208
|
+
Initialise l'explainer SHAP avancé avec détection intelligente et optimisations.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
model: Le modèle à expliquer (support multi-framework)
|
|
212
|
+
**kwargs: Configuration avancée (voir ShapConfig pour tous les paramètres)
|
|
213
|
+
background_data: Données d'arrière-plan pour les explications
|
|
214
|
+
n_samples: Nombre d'échantillons pour l'approximation
|
|
215
|
+
link: Fonction de lien ('identity', 'logit')
|
|
216
|
+
shap_type: Type SHAP ('kernel', 'tree', 'deep', 'gradient', 'auto')
|
|
217
|
+
use_gpu: Utiliser l'accélération GPU si disponible
|
|
218
|
+
n_jobs: Nombre de processus parallèles (-1 = tous les CPU)
|
|
219
|
+
batch_size: Taille des batches pour les grands datasets
|
|
220
|
+
include_interactions: Calculer les interactions entre variables
|
|
221
|
+
compute_metrics: Calculer les métriques de qualité d'explication
|
|
222
|
+
approximate: Utiliser des approximations pour grandes dimensions
|
|
223
|
+
compliance_mode: Activer le mode conformité réglementaire
|
|
224
|
+
compliance_regs: Liste des réglementations à vérifier
|
|
225
|
+
|
|
226
|
+
Exemples:
|
|
227
|
+
>>> explainer = ShapExplainer(model)
|
|
228
|
+
>>> explainer = ShapExplainer(model, background_data=X_train[:100])
|
|
229
|
+
>>> explainer = ShapExplainer(
|
|
230
|
+
... model,
|
|
231
|
+
... shap_type='tree',
|
|
232
|
+
... use_gpu=True,
|
|
233
|
+
... compute_metrics=True,
|
|
234
|
+
... include_interactions=True
|
|
235
|
+
... )
|
|
236
|
+
"""
|
|
237
|
+
super().__init__(model, **kwargs)
|
|
238
|
+
self._method = ExplainabilityMethod.SHAP
|
|
239
|
+
|
|
240
|
+
# Configuration avancée
|
|
241
|
+
if isinstance(kwargs.get('config'), ShapConfig):
|
|
242
|
+
self._config = kwargs.get('config')
|
|
243
|
+
else:
|
|
244
|
+
self._config = ShapConfig(**kwargs)
|
|
245
|
+
|
|
246
|
+
# Journalisation avancée
|
|
247
|
+
self._logger = logging.getLogger(__name__)
|
|
248
|
+
self._logger.info(f"Initialisation de ShapExplainer avec {self._config.shap_type} SHAP")
|
|
249
|
+
|
|
250
|
+
# Statistiques internes et métriques
|
|
251
|
+
self._stats = {
|
|
252
|
+
"explanations_count": 0,
|
|
253
|
+
"total_time_seconds": 0,
|
|
254
|
+
"avg_time_seconds": 0,
|
|
255
|
+
"max_time_seconds": 0,
|
|
256
|
+
"min_time_seconds": float('inf'),
|
|
257
|
+
"cache_hits": 0,
|
|
258
|
+
"feature_importance_consistency": [],
|
|
259
|
+
"explanation_quality": {}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Extraction de métadonnées et configuration du mode de fonctionnement
|
|
263
|
+
self._model_type = None
|
|
264
|
+
self._model_framework = None
|
|
265
|
+
self._is_classifier = False
|
|
266
|
+
self._output_names = None
|
|
267
|
+
self._feature_names = None
|
|
268
|
+
self._extract_model_metadata()
|
|
269
|
+
|
|
270
|
+
# Initialisation des hooks de pré/post-processing
|
|
271
|
+
self._pre_explain_hooks = []
|
|
272
|
+
self._post_explain_hooks = []
|
|
273
|
+
|
|
274
|
+
# Compatibilité réglementaire si activée
|
|
275
|
+
self._compliance_checker = None
|
|
276
|
+
if self._config.compliance_mode:
|
|
277
|
+
self._setup_compliance()
|
|
278
|
+
|
|
279
|
+
# Configuration du cache selon les paramètres
|
|
280
|
+
self._setup_caching()
|
|
281
|
+
|
|
282
|
+
# Initialisation de l'explainer SHAP adapté au modèle
|
|
283
|
+
self._shap_explainer = None
|
|
284
|
+
with Timer() as timer:
|
|
285
|
+
self._initialize_explainer()
|
|
286
|
+
self._logger.info(f"Initialisation de l'explainer SHAP terminée en {timer.duration:.3f}s")
|
|
287
|
+
|
|
288
|
+
def _extract_model_metadata(self):
|
|
289
|
+
"""Extrait des métadonnées complètes du modèle pour optimiser l'explication."""
|
|
290
|
+
# Détection du type de modèle et du framework
|
|
291
|
+
self._model_type = self._detect_model_type()
|
|
292
|
+
self._model_framework = self._detect_model_framework()
|
|
293
|
+
self._is_classifier = self._detect_is_classifier()
|
|
294
|
+
|
|
295
|
+
# Recherche des noms de variables
|
|
296
|
+
self._feature_names = self._extract_feature_names()
|
|
297
|
+
self._output_names = self._extract_output_names()
|
|
298
|
+
|
|
299
|
+
self._logger.info(f"Modèle détecté: {self._model_framework} - {self._model_type} - "
|
|
300
|
+
f"{'classificateur' if self._is_classifier else 'régresseur'}")
|
|
301
|
+
|
|
302
|
+
def _setup_caching(self):
|
|
303
|
+
"""Configure le cache intelligent pour les explications."""
|
|
304
|
+
cache_size = self._config.cache_size
|
|
305
|
+
if cache_size > 0:
|
|
306
|
+
# Application du décorateur LRU cache à certaines méthodes internes
|
|
307
|
+
self._compute_shap_values = lru_cache(maxsize=cache_size)(self._compute_shap_values)
|
|
308
|
+
self._feature_importance_to_explanation = lru_cache(maxsize=cache_size)(self._feature_importance_to_explanation)
|
|
309
|
+
self._logger.info(f"Cache LRU activé avec {cache_size} entrées")
|
|
310
|
+
|
|
311
|
+
def _setup_compliance(self):
|
|
312
|
+
"""Configure le vérificateur de conformité réglementaire pour les explications."""
|
|
313
|
+
try:
|
|
314
|
+
self._compliance_checker = ComplianceChecker()
|
|
315
|
+
self._logger.info(f"Module de conformité initialisé avec les réglementations: "
|
|
316
|
+
f"{self._config.compliance_regs or ['par défaut']}")
|
|
317
|
+
except Exception as e:
|
|
318
|
+
self._logger.warning(f"Impossible d'initialiser le module de conformité: {str(e)}")
|
|
319
|
+
self._compliance_checker = None
|
|
320
|
+
|
|
321
|
+
def _maybe_use_gpu_context(self):
|
|
322
|
+
"""
|
|
323
|
+
Contexte de gestion des ressources GPU pour l'explication.
|
|
324
|
+
|
|
325
|
+
Permet d'optimiser automatiquement l'utilisation des ressources GPU
|
|
326
|
+
lors du calcul des valeurs SHAP, particulièrement pour les grands jeux de données
|
|
327
|
+
et les modèles complexes.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Un gestionnaire de contexte qui configure l'environnement GPU optimalement
|
|
331
|
+
"""
|
|
332
|
+
if not self._config.use_gpu:
|
|
333
|
+
# Si GPU non activé, retourner un contexte vide (no-op)
|
|
334
|
+
from contextlib import nullcontext
|
|
335
|
+
return nullcontext()
|
|
336
|
+
|
|
337
|
+
# Contexte GPU adapté au framework détecté
|
|
338
|
+
model_type = self._detect_model_type()
|
|
339
|
+
|
|
340
|
+
class _GPUContext:
|
|
341
|
+
def __init__(self, explainer):
|
|
342
|
+
self._explainer = explainer
|
|
343
|
+
self._prev_state = None
|
|
344
|
+
self._tf_context = None
|
|
345
|
+
self._torch_context = None
|
|
346
|
+
|
|
347
|
+
def __enter__(self):
|
|
348
|
+
try:
|
|
349
|
+
if 'tensorflow' in model_type:
|
|
350
|
+
import tensorflow as tf
|
|
351
|
+
# Activer GPU pour TensorFlow
|
|
352
|
+
self._explainer._set_gpu_memory_growth()
|
|
353
|
+
# Enregistrer les devices disponibles pour les restaurer ensuite
|
|
354
|
+
self._prev_state = tf.config.get_visible_devices()
|
|
355
|
+
# N'autoriser que GPU si disponible
|
|
356
|
+
gpus = tf.config.list_physical_devices('GPU')
|
|
357
|
+
if gpus:
|
|
358
|
+
tf.config.set_visible_devices(gpus, 'GPU')
|
|
359
|
+
self._explainer._logger.info(f"GPU activé pour TensorFlow: {len(gpus)} périphériques")
|
|
360
|
+
else:
|
|
361
|
+
self._explainer._logger.warning("GPU demandé mais non disponible pour TensorFlow")
|
|
362
|
+
|
|
363
|
+
elif 'pytorch' in model_type or 'torch' in model_type:
|
|
364
|
+
import torch
|
|
365
|
+
# Activer CUDA pour PyTorch si disponible
|
|
366
|
+
if torch.cuda.is_available():
|
|
367
|
+
# Préparer le device et définir le contexte par défaut
|
|
368
|
+
self._prev_state = torch.cuda.current_device()
|
|
369
|
+
torch.cuda.set_device(0) # Premier GPU par défaut
|
|
370
|
+
self._explainer._logger.info(f"GPU activé pour PyTorch: {torch.cuda.get_device_name(0)}")
|
|
371
|
+
else:
|
|
372
|
+
self._explainer._logger.warning("GPU demandé mais CUDA non disponible pour PyTorch")
|
|
373
|
+
|
|
374
|
+
# Configuration spécifique pour SHAP
|
|
375
|
+
# L'API SHAP n'a pas d'API publique pour configurer le GPU, mais certaines
|
|
376
|
+
# implémentations de SHAP détectent automatiquement et utilisent le GPU si disponible
|
|
377
|
+
except Exception as e:
|
|
378
|
+
self._explainer._logger.error(f"Erreur lors de l'activation GPU: {str(e)}")
|
|
379
|
+
|
|
380
|
+
return self
|
|
381
|
+
|
|
382
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
383
|
+
try:
|
|
384
|
+
# Restaurer l'état précédent
|
|
385
|
+
if 'tensorflow' in model_type and self._prev_state is not None:
|
|
386
|
+
import tensorflow as tf
|
|
387
|
+
tf.config.set_visible_devices(self._prev_state)
|
|
388
|
+
elif ('pytorch' in model_type or 'torch' in model_type) and self._prev_state is not None:
|
|
389
|
+
import torch
|
|
390
|
+
if torch.cuda.is_available():
|
|
391
|
+
torch.cuda.set_device(self._prev_state)
|
|
392
|
+
# Libérer la mémoire GPU
|
|
393
|
+
torch.cuda.empty_cache()
|
|
394
|
+
except Exception as e:
|
|
395
|
+
self._explainer._logger.error(f"Erreur lors de la restauration du contexte GPU: {str(e)}")
|
|
396
|
+
|
|
397
|
+
# Retourner une instance du gestionnaire de contexte
|
|
398
|
+
return _GPUContext(self)
|
|
399
|
+
|
|
400
|
+
def _set_gpu_memory_growth(self):
|
|
401
|
+
"""
|
|
402
|
+
Configure la croissance mémoire GPU dynamique pour TensorFlow.
|
|
403
|
+
|
|
404
|
+
Cette méthode contextuelle permet une utilisation optimale de la mémoire GPU
|
|
405
|
+
en configurant une allocation dynamique, évitant ainsi les erreurs OOM et
|
|
406
|
+
permettant une meilleure répartition des ressources entre plusieurs processus.
|
|
407
|
+
"""
|
|
408
|
+
try:
|
|
409
|
+
import tensorflow as tf
|
|
410
|
+
gpus = tf.config.list_physical_devices('GPU')
|
|
411
|
+
if gpus:
|
|
412
|
+
# Configurer la croissance mémoire dynamique sur tous les GPU
|
|
413
|
+
for gpu in gpus:
|
|
414
|
+
tf.config.experimental.set_memory_growth(gpu, True)
|
|
415
|
+
self._logger.info(f"Allocation mémoire GPU dynamique activée pour {len(gpus)} GPU(s)")
|
|
416
|
+
except ImportError:
|
|
417
|
+
self._logger.info("TensorFlow non installé, croissance mémoire GPU non configurée")
|
|
418
|
+
except Exception as e:
|
|
419
|
+
self._logger.warning(f"Erreur lors de la configuration de la mémoire GPU: {str(e)}")
|
|
420
|
+
|
|
421
|
+
def _initialize_explainer(self):
|
|
422
|
+
"""
|
|
423
|
+
Initialise l'explainer SHAP optimal en fonction du type de modèle détecté.
|
|
424
|
+
|
|
425
|
+
Cette méthode implémente une sélection intelligente et optimisée de l'explainer
|
|
426
|
+
SHAP approprié selon une analyse approfondie du modèle et du contexte.
|
|
427
|
+
Inclut des optimisations avancées pour maximiser performance et précision.
|
|
428
|
+
"""
|
|
429
|
+
import shap
|
|
430
|
+
|
|
431
|
+
# Mesure des performances
|
|
432
|
+
with Timer() as timer, MemoryTracker() as mem_tracker:
|
|
433
|
+
# Détection sophistiquée du type de modèle
|
|
434
|
+
model_type = self._detect_model_type()
|
|
435
|
+
framework = model_type.split('-')[0] if '-' in model_type else 'unknown'
|
|
436
|
+
|
|
437
|
+
self._logger.info(f"Initialisation de l'explainer SHAP pour: {model_type}")
|
|
438
|
+
|
|
439
|
+
# Préparation des paramètres communs
|
|
440
|
+
kwargs = {}
|
|
441
|
+
|
|
442
|
+
# Appliquer les optimisations avancées selon la configuration
|
|
443
|
+
if hasattr(shap, 'utils') and hasattr(shap.utils, 'set_parallelism'):
|
|
444
|
+
try:
|
|
445
|
+
shap.utils.set_parallelism(self._config.n_jobs)
|
|
446
|
+
self._logger.debug(f"Parallélisme SHAP configuré avec {self._config.n_jobs} threads")
|
|
447
|
+
except Exception as e:
|
|
448
|
+
self._logger.debug(f"Impossible de configurer le parallélisme SHAP: {str(e)}")
|
|
449
|
+
|
|
450
|
+
# Configuration spécifique aux modèles d'arbres
|
|
451
|
+
if self._config.shap_type == 'tree' or (self._config.shap_type == 'auto' and
|
|
452
|
+
(self._is_tree_model() or any(x in model_type.lower() for x in ['tree', 'forest', 'boost', 'xgb', 'lgbm', 'catboost']))):
|
|
453
|
+
|
|
454
|
+
# Optimisations pour les modèles d'arbres
|
|
455
|
+
kwargs = {
|
|
456
|
+
'model_output': 'raw', # ou 'probability', 'margin', 'logit'
|
|
457
|
+
'feature_perturbation': self._config.feature_perturbation
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Gérer les interactions si nécessaire
|
|
461
|
+
if self._config.include_interactions:
|
|
462
|
+
kwargs['interactions'] = True
|
|
463
|
+
self._logger.debug("Calcul des interactions activé pour TreeExplainer")
|
|
464
|
+
|
|
465
|
+
# Détecter le framework XGBoost/LightGBM/Catboost pour appliquer les optimisations spécifiques
|
|
466
|
+
if 'xgboost' in framework:
|
|
467
|
+
self._logger.debug("Optimisations spécifiques pour XGBoost activées")
|
|
468
|
+
kwargs['data'] = self._get_background_data()
|
|
469
|
+
if self._config.approximate:
|
|
470
|
+
kwargs['approximate'] = True
|
|
471
|
+
if self._config.use_gpu:
|
|
472
|
+
# Vérifier si XGBoost est compilé avec GPU
|
|
473
|
+
try:
|
|
474
|
+
import xgboost
|
|
475
|
+
if hasattr(xgboost, 'gpu_predictor'):
|
|
476
|
+
kwargs['gpu_predictor'] = True
|
|
477
|
+
self._logger.debug("Support GPU XGBoost activé")
|
|
478
|
+
except (ImportError, AttributeError):
|
|
479
|
+
pass
|
|
480
|
+
|
|
481
|
+
# Création de l'explainer avec tous les paramètres optimisés
|
|
482
|
+
try:
|
|
483
|
+
self._logger.info("Initialisation de TreeExplainer avec paramètres optimisés")
|
|
484
|
+
self._shap_explainer = shap.TreeExplainer(self._model, **kwargs)
|
|
485
|
+
return
|
|
486
|
+
except Exception as e:
|
|
487
|
+
self._logger.warning(f"Erreur lors de l'initialisation de TreeExplainer: {str(e)}. Repli...")
|
|
488
|
+
|
|
489
|
+
# Configuration pour les réseaux de neurones profonds
|
|
490
|
+
elif self._config.shap_type == 'deep' or (self._config.shap_type == 'auto' and
|
|
491
|
+
(self._is_deep_model() or any(x in model_type.lower() for x in ['tensorflow', 'keras', 'torch', 'neural']))):
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
background_data = self._get_background_data()
|
|
495
|
+
|
|
496
|
+
# Optimisations pour réseaux de neurones
|
|
497
|
+
if framework == 'tensorflow':
|
|
498
|
+
# Vérifier si on peut utiliser GradientExplainer qui est plus efficace pour TF
|
|
499
|
+
if hasattr(shap, 'GradientExplainer'):
|
|
500
|
+
self._logger.info("Initialisation de GradientExplainer pour TensorFlow")
|
|
501
|
+
self._shap_explainer = shap.GradientExplainer(self._model, background_data)
|
|
502
|
+
return
|
|
503
|
+
|
|
504
|
+
# Déterminer le bon type d'explainer pour réseaux profonds
|
|
505
|
+
self._logger.info("Initialisation de DeepExplainer")
|
|
506
|
+
if self._config.use_gpu:
|
|
507
|
+
with self._set_gpu_memory_growth():
|
|
508
|
+
self._shap_explainer = shap.DeepExplainer(self._model, background_data)
|
|
509
|
+
else:
|
|
510
|
+
self._shap_explainer = shap.DeepExplainer(self._model, background_data)
|
|
511
|
+
return
|
|
512
|
+
except Exception as e:
|
|
513
|
+
self._logger.warning(f"Erreur lors de l'initialisation de DeepExplainer: {str(e)}. Repli...")
|
|
514
|
+
|
|
515
|
+
# Configuration pour les modèles différentiables
|
|
516
|
+
elif self._config.shap_type == 'gradient' or (self._config.shap_type == 'auto' and
|
|
517
|
+
self._is_differentiable_model()):
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
background_data = self._get_background_data()
|
|
521
|
+
self._logger.info("Initialisation de GradientExplainer")
|
|
522
|
+
self._shap_explainer = shap.GradientExplainer(self._model, background_data)
|
|
523
|
+
return
|
|
524
|
+
except Exception as e:
|
|
525
|
+
self._logger.warning(f"Erreur lors de l'initialisation de GradientExplainer: {str(e)}. Repli...")
|
|
526
|
+
|
|
527
|
+
# Configuration par défaut: KernelSHAP (universel mais plus lent)
|
|
528
|
+
try:
|
|
529
|
+
background_data = self._get_background_data()
|
|
530
|
+
|
|
531
|
+
# Optimisations pour KernelExplainer
|
|
532
|
+
kwargs = {
|
|
533
|
+
'link': self._config.link,
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if hasattr(self._model, 'predict_proba'):
|
|
537
|
+
kwargs['output_names'] = 'probability'
|
|
538
|
+
|
|
539
|
+
if self._config.approximate:
|
|
540
|
+
# Mode approximation pour grands jeux de données
|
|
541
|
+
kwargs['nsamples'] = self._config.n_samples
|
|
542
|
+
# Plus l'approximation est fine, plus précis mais plus lent
|
|
543
|
+
if self._config.n_samples <= 50:
|
|
544
|
+
self._logger.warning("Petit nombre d'échantillons, précision possiblement limitée")
|
|
545
|
+
|
|
546
|
+
if self._config.clustering and hasattr(shap, 'kmeans'):
|
|
547
|
+
# Clustering pour réduire les calculs
|
|
548
|
+
with Timer() as cluster_timer:
|
|
549
|
+
n_clusters = min(50, len(background_data) // 10) if len(background_data) > 500 else None
|
|
550
|
+
if n_clusters:
|
|
551
|
+
try:
|
|
552
|
+
background_data = shap.kmeans(background_data, n_clusters)
|
|
553
|
+
self._logger.debug(f"Clustering appliqué: {n_clusters} clusters en {cluster_timer.duration:.2f}s")
|
|
554
|
+
except Exception as e:
|
|
555
|
+
self._logger.warning(f"Erreur lors du clustering: {str(e)}")
|
|
556
|
+
|
|
557
|
+
# Implémentation de maskers si disponible dans la version de SHAP
|
|
558
|
+
masker = None
|
|
559
|
+
if self._config.masker_type and hasattr(shap, 'maskers'):
|
|
560
|
+
if self._config.masker_type == 'tabular' and hasattr(shap.maskers, 'Tabular'):
|
|
561
|
+
masker = shap.maskers.Tabular(background_data)
|
|
562
|
+
elif self._config.masker_type == 'text' and hasattr(shap.maskers, 'Text'):
|
|
563
|
+
masker = shap.maskers.Text()
|
|
564
|
+
elif self._config.masker_type == 'image' and hasattr(shap.maskers, 'Image'):
|
|
565
|
+
masker = shap.maskers.Image()
|
|
566
|
+
|
|
567
|
+
self._logger.info("Initialisation de KernelExplainer (explainer générique)")
|
|
568
|
+
|
|
569
|
+
if masker and hasattr(shap, 'Explainer'):
|
|
570
|
+
# Utiliser le nouvel API SHAP si disponible
|
|
571
|
+
self._shap_explainer = shap.Explainer(self._model_predict_wrapper, masker, **kwargs)
|
|
572
|
+
else:
|
|
573
|
+
# API standard KernelExplainer
|
|
574
|
+
self._shap_explainer = shap.KernelExplainer(
|
|
575
|
+
self._model_predict_wrapper,
|
|
576
|
+
background_data,
|
|
577
|
+
**kwargs
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
except Exception as e:
|
|
581
|
+
self._logger.error(f"Erreur critique lors de l'initialisation de l'explainer SHAP: {str(e)}")
|
|
582
|
+
raise RuntimeError(f"Initialisation de l'explainer SHAP impossible: {str(e)}")
|
|
583
|
+
|
|
584
|
+
# Log des performances de l'initialisation
|
|
585
|
+
memory_usage = mem_tracker.peak_usage_mb
|
|
586
|
+
self._logger.info(
|
|
587
|
+
f"Explainer SHAP initialisé en {timer.duration:.2f}s avec {memory_usage:.1f}MB "
|
|
588
|
+
f"(Type: {self._config.shap_type if self._config.shap_type != 'auto' else 'auto-detect'})"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
def _maybe_use_gpu_context(self):
|
|
592
|
+
"""
|
|
593
|
+
Contexte de gestion des ressources GPU pour l'explication.
|
|
594
|
+
|
|
595
|
+
Permet d'optimiser automatiquement l'utilisation des ressources GPU
|
|
596
|
+
lors du calcul des valeurs SHAP, particulièrement pour les grands jeux de données
|
|
597
|
+
et les modèles complexes.
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
Un gestionnaire de contexte qui configure l'environnement GPU optimalement
|
|
601
|
+
"""
|
|
602
|
+
import contextlib
|
|
603
|
+
|
|
604
|
+
@contextlib.contextmanager
|
|
605
|
+
def _gpu_context():
|
|
606
|
+
if not self._config.use_gpu:
|
|
607
|
+
yield
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
try:
|
|
611
|
+
# TensorFlow GPU configuration
|
|
612
|
+
try:
|
|
613
|
+
import tensorflow as tf
|
|
614
|
+
if hasattr(tf, 'config') and hasattr(tf.config, 'experimental'):
|
|
615
|
+
with self._set_gpu_memory_growth():
|
|
616
|
+
yield
|
|
617
|
+
return
|
|
618
|
+
except (ImportError, AttributeError):
|
|
619
|
+
pass
|
|
620
|
+
|
|
621
|
+
# PyTorch GPU configuration
|
|
622
|
+
try:
|
|
623
|
+
import torch
|
|
624
|
+
if torch.cuda.is_available():
|
|
625
|
+
self._logger.debug(f"Utilisation de PyTorch avec GPU: {torch.cuda.get_device_name(0)}")
|
|
626
|
+
# Optionally set additional PyTorch GPU configurations here
|
|
627
|
+
pass
|
|
628
|
+
except (ImportError, AttributeError):
|
|
629
|
+
pass
|
|
630
|
+
|
|
631
|
+
# No specific GPU config needed/available
|
|
632
|
+
yield
|
|
633
|
+
except Exception as e:
|
|
634
|
+
self._logger.warning(f"Erreur lors de la configuration GPU: {str(e)}")
|
|
635
|
+
yield
|
|
636
|
+
|
|
637
|
+
return _gpu_context()
|
|
638
|
+
|
|
639
|
+
def _set_gpu_memory_growth(self):
|
|
640
|
+
"""Configure la croissance mémoire GPU dynamique pour TensorFlow.
|
|
641
|
+
|
|
642
|
+
Cette méthode contextuelle permet une utilisation optimale de la mémoire GPU
|
|
643
|
+
en configurant une allocation dynamique, évitant ainsi les erreurs OOM et
|
|
644
|
+
permettant une meilleure répartition des ressources entre plusieurs processus.
|
|
645
|
+
"""
|
|
646
|
+
import contextlib
|
|
647
|
+
|
|
648
|
+
@contextlib.contextmanager
|
|
649
|
+
def _set_tf_memory_growth():
|
|
650
|
+
try:
|
|
651
|
+
import tensorflow as tf
|
|
652
|
+
if hasattr(tf, 'config') and hasattr(tf.config, 'experimental'):
|
|
653
|
+
gpus = tf.config.experimental.list_physical_devices('GPU')
|
|
654
|
+
if gpus:
|
|
655
|
+
for gpu in gpus:
|
|
656
|
+
tf.config.experimental.set_memory_growth(gpu, True)
|
|
657
|
+
self._logger.debug(f"{len(gpus)} GPU(s) configuré(s) avec memory growth")
|
|
658
|
+
except Exception as e:
|
|
659
|
+
self._logger.debug(f"Impossible de configurer la mémoire GPU: {str(e)}")
|
|
660
|
+
yield
|
|
661
|
+
|
|
662
|
+
return _set_tf_memory_growth()
|
|
663
|
+
|
|
664
|
+
def _detect_model_type(self) -> str:
|
|
665
|
+
"""
|
|
666
|
+
Détecte le type de modèle pour choisir l'explainer SHAP optimal.
|
|
667
|
+
|
|
668
|
+
Cette méthode avancée identifie avec précision la nature du modèle à travers
|
|
669
|
+
une analyse multiniveau (framework, architecture, caractéristiques), permettant
|
|
670
|
+
de sélectionner l'implémentation SHAP la plus performante et précise pour ce modèle.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
str: Type de modèle détecté avec son framework ('sklearn-RandomForestClassifier', etc.)
|
|
674
|
+
"""
|
|
675
|
+
# Analyser le framework du modèle
|
|
676
|
+
model_module = self._get_model_module()
|
|
677
|
+
framework = 'unknown'
|
|
678
|
+
for fw_name, identifiers in self._FRAMEWORK_IDENTIFIERS.items():
|
|
679
|
+
if any(ident in model_module.lower() for ident in identifiers):
|
|
680
|
+
framework = fw_name
|
|
681
|
+
break
|
|
682
|
+
|
|
683
|
+
# Récupérer le nom spécifique du modèle avec gestion avancée des cas complexes
|
|
684
|
+
model_name = type(self._model).__name__
|
|
685
|
+
|
|
686
|
+
# Cas 1: Pipeline scikit-learn - récupérer le modèle final
|
|
687
|
+
if self._is_pipeline():
|
|
688
|
+
try:
|
|
689
|
+
# Extraire le dernier estimateur du pipeline
|
|
690
|
+
if hasattr(self._model, 'steps') and self._model.steps:
|
|
691
|
+
last_step = self._model.steps[-1][1]
|
|
692
|
+
model_name = type(last_step).__name__
|
|
693
|
+
# Vérifier le module du dernier estimateur pour plus de précision
|
|
694
|
+
last_module = last_step.__module__
|
|
695
|
+
for fw_name, identifiers in self._FRAMEWORK_IDENTIFIERS.items():
|
|
696
|
+
if any(ident in last_module.lower() for ident in identifiers):
|
|
697
|
+
framework = fw_name
|
|
698
|
+
break
|
|
699
|
+
except (IndexError, AttributeError) as e:
|
|
700
|
+
self._logger.debug(f"Impossible d'analyser le pipeline: {str(e)}")
|
|
701
|
+
|
|
702
|
+
# Cas 2: Méta-estimateurs et ensembles - analyser les estimateurs de base
|
|
703
|
+
elif self._is_ensemble():
|
|
704
|
+
try:
|
|
705
|
+
base_estimator = None
|
|
706
|
+
# Essayer différentes structures d'ensembles connues
|
|
707
|
+
if hasattr(self._model, 'estimators_') and self._model.estimators_:
|
|
708
|
+
base_estimator = self._model.estimators_[0]
|
|
709
|
+
elif hasattr(self._model, 'estimators') and self._model.estimators:
|
|
710
|
+
base_estimator = self._model.estimators[0]
|
|
711
|
+
elif hasattr(self._model, 'base_estimator'):
|
|
712
|
+
base_estimator = self._model.base_estimator
|
|
713
|
+
|
|
714
|
+
if base_estimator:
|
|
715
|
+
model_name = f"Ensemble({type(base_estimator).__name__})"
|
|
716
|
+
except (IndexError, AttributeError) as e:
|
|
717
|
+
self._logger.debug(f"Impossible d'analyser l'ensemble: {str(e)}")
|
|
718
|
+
|
|
719
|
+
# Cas 3: Modèles à noyaux - identifier le type de noyau
|
|
720
|
+
elif any(kernel_type in model_name.lower() for kernel_type in ["svc", "svr", "gaussianprocess"]):
|
|
721
|
+
kernel_name = "rbf" # noyau par défaut
|
|
722
|
+
if hasattr(self._model, "kernel") and isinstance(self._model.kernel, str):
|
|
723
|
+
kernel_name = self._model.kernel
|
|
724
|
+
elif hasattr(self._model, "get_params"):
|
|
725
|
+
params = self._model.get_params()
|
|
726
|
+
if "kernel" in params and isinstance(params["kernel"], str):
|
|
727
|
+
kernel_name = params["kernel"]
|
|
728
|
+
model_name = f"{model_name}({kernel_name})"
|
|
729
|
+
|
|
730
|
+
# Cas 4: Modèles de deep learning - analyser l'architecture
|
|
731
|
+
elif framework in ["tensorflow", "pytorch", "mxnet"]:
|
|
732
|
+
# Ajouter des informations sur la profondeur ou le type d'architecture
|
|
733
|
+
if framework == "tensorflow" and hasattr(self._model, "layers"):
|
|
734
|
+
try:
|
|
735
|
+
n_layers = len(self._model.layers)
|
|
736
|
+
layer_types = [l.__class__.__name__ for l in self._model.layers][:3] # Premiers types de couches
|
|
737
|
+
model_name = f"{model_name}(layers={n_layers}, types={','.join(layer_types)}...)"
|
|
738
|
+
except:
|
|
739
|
+
pass
|
|
740
|
+
elif framework == "pytorch" and hasattr(self._model, "_modules"):
|
|
741
|
+
try:
|
|
742
|
+
n_modules = len(list(self._model._modules.items()))
|
|
743
|
+
model_name = f"{model_name}(modules={n_modules})"
|
|
744
|
+
except:
|
|
745
|
+
pass
|
|
746
|
+
|
|
747
|
+
# Cas 5: Détection avancée pour modèles spécifiques nécessitant un traitement particulier
|
|
748
|
+
if framework == "xgboost" and "booster" in model_name.lower():
|
|
749
|
+
# XGBoost nécessite une détection spécifique pour les types d'objectif
|
|
750
|
+
if hasattr(self._model, "objective"):
|
|
751
|
+
model_name = f"{model_name}({self._model.objective})"
|
|
752
|
+
|
|
753
|
+
# Enregistrer et retourner le résultat final avec format standard
|
|
754
|
+
detected_type = f"{framework}-{model_name}"
|
|
755
|
+
self._logger.info(f"Type de modèle détecté: {detected_type}")
|
|
756
|
+
return detected_type
|
|
757
|
+
|
|
758
|
+
def _get_model_module(self) -> str:
|
|
759
|
+
"""Obtient le module du modèle pour aider à la détection du framework."""
|
|
760
|
+
try:
|
|
761
|
+
return str(self._model.__module__)
|
|
762
|
+
except (AttributeError, TypeError):
|
|
763
|
+
# Essayer d'obtenir le module via le type
|
|
764
|
+
return str(type(self._model).__module__)
|
|
765
|
+
|
|
766
|
+
def _is_pipeline(self) -> bool:
|
|
767
|
+
"""Vérifie si le modèle est un pipeline ou une chaîne de traitement."""
|
|
768
|
+
# Vérifier scikit-learn Pipeline et ColumnTransformer
|
|
769
|
+
if hasattr(self._model, "steps") and callable(getattr(self._model, "fit", None)):
|
|
770
|
+
return True
|
|
771
|
+
# Vérifier les pipelines PyTorch (nn.Sequential)
|
|
772
|
+
if hasattr(self._model, "forward") and hasattr(self._model, "_modules"):
|
|
773
|
+
return True
|
|
774
|
+
# Vérifier les pipelines Keras (Sequential)
|
|
775
|
+
if hasattr(self._model, "layers") and hasattr(self._model, "add"):
|
|
776
|
+
return True
|
|
777
|
+
return False
|
|
778
|
+
|
|
779
|
+
def _is_ensemble(self) -> bool:
|
|
780
|
+
"""Vérifie si le modèle est un ensemble ou un méta-estimateur."""
|
|
781
|
+
# Ensembles scikit-learn standards
|
|
782
|
+
if hasattr(self._model, "estimators_") or hasattr(self._model, "estimators"):
|
|
783
|
+
return True
|
|
784
|
+
# Autres méta-estimateurs
|
|
785
|
+
if hasattr(self._model, "base_estimator") or hasattr(self._model, "base_estimator_"):
|
|
786
|
+
return True
|
|
787
|
+
# Méta-estimateurs de vote
|
|
788
|
+
if hasattr(self._model, "estimators") and isinstance(getattr(self._model, "estimators", None), list):
|
|
789
|
+
return True
|
|
790
|
+
return False
|
|
791
|
+
|
|
792
|
+
def _is_tree_model(self) -> bool:
|
|
793
|
+
"""Vérifie si le modèle est un modèle basé sur des arbres."""
|
|
794
|
+
tree_modules = ['sklearn.ensemble', 'xgboost', 'lightgbm', 'catboost']
|
|
795
|
+
model_module = self._model.__class__.__module__
|
|
796
|
+
return any(module in model_module for module in tree_modules)
|
|
797
|
+
|
|
798
|
+
def _is_deep_model(self) -> bool:
|
|
799
|
+
"""Vérifie si le modèle est un réseau de neurones profond."""
|
|
800
|
+
deep_modules = ['keras', 'tensorflow', 'torch']
|
|
801
|
+
model_module = self._model.__class__.__module__
|
|
802
|
+
return any(module in model_module for module in deep_modules)
|
|
803
|
+
|
|
804
|
+
def _is_differentiable_model(self) -> bool:
|
|
805
|
+
"""Vérifie si le modèle est différentiable (pour GradientExplainer)."""
|
|
806
|
+
model_type = self._detect_model_type()
|
|
807
|
+
return any(framework in model_type for framework in ['tensorflow', 'pytorch', 'torch'])
|
|
808
|
+
|
|
809
|
+
def _model_predict_wrapper(self, X):
|
|
810
|
+
"""
|
|
811
|
+
Wrapper unifié pour obtenir des prédictions standardisées de différents types de modèles.
|
|
812
|
+
|
|
813
|
+
Cette méthode gère intelligemment les spécificités des différents frameworks de ML
|
|
814
|
+
et normalise les formats de sortie pour faciliter les calculs de métriques de qualité.
|
|
815
|
+
|
|
816
|
+
Args:
|
|
817
|
+
X: Données d'entrée (DataFrame, ndarray, etc.)
|
|
818
|
+
|
|
819
|
+
Returns:
|
|
820
|
+
np.ndarray: Prédictions normalisées
|
|
821
|
+
"""
|
|
822
|
+
model_type = self._detect_model_type()
|
|
823
|
+
|
|
824
|
+
# Optimisation: utiliser le GPU si configuré
|
|
825
|
+
with self._maybe_use_gpu_context():
|
|
826
|
+
try:
|
|
827
|
+
# Convertir en format approprié si nécessaire
|
|
828
|
+
X_processed = X
|
|
829
|
+
if hasattr(self._model, 'predict') and not ('tensorflow' in model_type or 'pytorch' in model_type):
|
|
830
|
+
# Modèles scikit-learn, XGBoost, etc.
|
|
831
|
+
if hasattr(self._model, 'predict_proba'):
|
|
832
|
+
# Classifier avec probabilités
|
|
833
|
+
predictions = self._model.predict_proba(X_processed)
|
|
834
|
+
else:
|
|
835
|
+
# Régression ou autre
|
|
836
|
+
predictions = self._model.predict(X_processed)
|
|
837
|
+
# Conversion 1D -> 2D si nécessaire
|
|
838
|
+
if predictions.ndim == 1:
|
|
839
|
+
predictions = predictions.reshape(-1, 1)
|
|
840
|
+
elif 'tensorflow' in model_type:
|
|
841
|
+
# Modèles TensorFlow
|
|
842
|
+
import tensorflow as tf
|
|
843
|
+
# Conversion en tenseur TF si nécessaire
|
|
844
|
+
if not isinstance(X_processed, tf.Tensor) and not isinstance(X_processed, tf.Variable):
|
|
845
|
+
X_processed = tf.convert_to_tensor(X_processed, dtype=tf.float32)
|
|
846
|
+
predictions = self._model(X_processed).numpy()
|
|
847
|
+
elif 'pytorch' in model_type or 'torch' in model_type:
|
|
848
|
+
# Modèles PyTorch
|
|
849
|
+
import torch
|
|
850
|
+
# Conversion en tenseur PyTorch si nécessaire
|
|
851
|
+
if not isinstance(X_processed, torch.Tensor):
|
|
852
|
+
X_processed = torch.tensor(X_processed.values if hasattr(X_processed, 'values') else X_processed,
|
|
853
|
+
dtype=torch.float32)
|
|
854
|
+
# Désactiver le calcul de gradient pour l'inférence
|
|
855
|
+
with torch.no_grad():
|
|
856
|
+
predictions = self._model(X_processed).cpu().numpy()
|
|
857
|
+
else:
|
|
858
|
+
# Méthode générique pour les autres cas
|
|
859
|
+
if hasattr(self._model, '__call__'):
|
|
860
|
+
predictions = self._model(X_processed)
|
|
861
|
+
# Convertir en numpy si nécessaire
|
|
862
|
+
if not isinstance(predictions, np.ndarray):
|
|
863
|
+
predictions = np.array(predictions)
|
|
864
|
+
else:
|
|
865
|
+
raise ValueError(f"Type de modèle non supporté pour les prédictions: {model_type}")
|
|
866
|
+
|
|
867
|
+
return predictions
|
|
868
|
+
|
|
869
|
+
except Exception as e:
|
|
870
|
+
self._logger.error(f"Erreur lors de la prédiction avec le modèle: {str(e)}")
|
|
871
|
+
raise RuntimeError(f"Erreur lors de la prédiction: {str(e)}")
|
|
872
|
+
|
|
873
|
+
def _get_background_data(self):
|
|
874
|
+
"""
|
|
875
|
+
Récupère les données d'arrière-plan pour les explainers qui en ont besoin.
|
|
876
|
+
|
|
877
|
+
Returns:
|
|
878
|
+
numpy.ndarray: Données d'arrière-plan
|
|
879
|
+
"""
|
|
880
|
+
if self._background_data is not None:
|
|
881
|
+
return self._background_data
|
|
882
|
+
|
|
883
|
+
# Données synthétiques si aucune donnée n'est fournie
|
|
884
|
+
# C'est une solution de repli, l'idéal est de fournir de vraies données
|
|
885
|
+
self._logger.warning("Aucune donnée d'arrière-plan fournie. "
|
|
886
|
+
"Génération de données synthétiques, ce qui peut affecter la qualité des explications.")
|
|
887
|
+
|
|
888
|
+
# Tenter de déduire la forme d'entrée du modèle
|
|
889
|
+
input_shape = self._infer_model_input_shape()
|
|
890
|
+
if input_shape:
|
|
891
|
+
# Générer des données aléatoires normalisées
|
|
892
|
+
return np.random.normal(0, 0.1, size=(self._n_samples, *input_shape))
|
|
893
|
+
|
|
894
|
+
# Si impossible de déduire la forme, erreur
|
|
895
|
+
raise ValueError("Impossible de générer des données d'arrière-plan. "
|
|
896
|
+
"Veuillez fournir des données via le paramètre 'background_data'.")
|
|
897
|
+
|
|
898
|
+
def _infer_model_input_shape(self):
|
|
899
|
+
"""
|
|
900
|
+
Tente de déduire la forme des entrées du modèle.
|
|
901
|
+
|
|
902
|
+
Returns:
|
|
903
|
+
tuple ou None: Forme déduite ou None si impossible
|
|
904
|
+
"""
|
|
905
|
+
model_type = self._detect_model_type()
|
|
906
|
+
|
|
907
|
+
if model_type == 'tensorflow':
|
|
908
|
+
try:
|
|
909
|
+
# Pour les modèles Keras
|
|
910
|
+
return self._model.input_shape[1:]
|
|
911
|
+
except (AttributeError, IndexError):
|
|
912
|
+
pass
|
|
913
|
+
|
|
914
|
+
# Pour les modèles sklearn, xgboost, etc.
|
|
915
|
+
try:
|
|
916
|
+
if hasattr(self._model, 'n_features_in_'):
|
|
917
|
+
return (self._model.n_features_in_,)
|
|
918
|
+
except AttributeError:
|
|
919
|
+
pass
|
|
920
|
+
|
|
921
|
+
# Impossible de déduire
|
|
922
|
+
return None
|
|
923
|
+
|
|
924
|
+
def _model_predict_wrapper(self, x):
|
|
925
|
+
"""
|
|
926
|
+
Wrapper pour la fonction de prédiction du modèle, adapté pour SHAP.
|
|
927
|
+
|
|
928
|
+
Args:
|
|
929
|
+
x: Données d'entrée
|
|
930
|
+
|
|
931
|
+
Returns:
|
|
932
|
+
numpy.ndarray: Prédictions du modèle
|
|
933
|
+
"""
|
|
934
|
+
try:
|
|
935
|
+
model_type = self._detect_model_type()
|
|
936
|
+
|
|
937
|
+
if model_type in ['sklearn-ensemble', 'sklearn-linear', 'xgboost', 'lightgbm', 'catboost']:
|
|
938
|
+
if hasattr(self._model, 'predict_proba'):
|
|
939
|
+
return self._model.predict_proba(x)
|
|
940
|
+
else:
|
|
941
|
+
return self._model.predict(x)
|
|
942
|
+
elif model_type in ['tensorflow', 'pytorch']:
|
|
943
|
+
# Conversion en format attendu par le modèle
|
|
944
|
+
if isinstance(x, pd.DataFrame):
|
|
945
|
+
x = x.values
|
|
946
|
+
|
|
947
|
+
# Appel au modèle
|
|
948
|
+
result = self._model(x)
|
|
949
|
+
|
|
950
|
+
# Conversion du résultat si nécessaire (pour PyTorch)
|
|
951
|
+
if hasattr(result, 'detach') and hasattr(result.detach(), 'numpy'):
|
|
952
|
+
return result.detach().numpy()
|
|
953
|
+
return result
|
|
954
|
+
else:
|
|
955
|
+
# Cas générique
|
|
956
|
+
if hasattr(self._model, 'predict'):
|
|
957
|
+
return self._model.predict(x)
|
|
958
|
+
else:
|
|
959
|
+
return self._model(x)
|
|
960
|
+
except Exception as e:
|
|
961
|
+
self._logger.error(f"Erreur dans le wrapper de prédiction: {str(e)}")
|
|
962
|
+
raise RuntimeError(f"Échec de la prédiction du modèle: {str(e)}")
|
|
963
|
+
|
|
964
|
+
def explain(self, X, y=None, **kwargs):
|
|
965
|
+
"""
|
|
966
|
+
Génère des explications SHAP optimisées pour un ensemble de données avec métriques de qualité.
|
|
967
|
+
|
|
968
|
+
Cette implémentation avancée prend en charge l'explicabilité à grande échelle avec
|
|
969
|
+
des optimisations de performance, des métriques de qualité d'explication, des contrôles
|
|
970
|
+
de conformité réglementaire et une adaptation dynamique au niveau d'audience.
|
|
971
|
+
|
|
972
|
+
Args:
|
|
973
|
+
X: Données d'entrée à expliquer (DataFrame, numpy array, ou liste)
|
|
974
|
+
y: Valeurs cibles réelles (optionnel), utilisées pour évaluer la fidélité
|
|
975
|
+
**kwargs: Paramètres avancés
|
|
976
|
+
output_index: Indice de sortie pour les modèles multi-sorties
|
|
977
|
+
audience_level: Niveau d'audience (AudienceLevel.TECHNICAL, BUSINESS, PUBLIC)
|
|
978
|
+
summarize: Résumer les résultats pour l'ensemble du dataset
|
|
979
|
+
sample_size: Pour grands datasets, taille d'échantillon représentatif à utiliser
|
|
980
|
+
compute_quality_metrics: Calculer les métriques de qualité d'explication (True par défaut)
|
|
981
|
+
verify_compliance: Vérifier la conformité réglementaire de l'explication
|
|
982
|
+
regulations: Liste des réglementations à vérifier (ex: ['RGPD', 'AI_ACT', 'HIPAA'])
|
|
983
|
+
return_raw_shap: Inclure les valeurs SHAP brutes dans le résultat (False par défaut)
|
|
984
|
+
batch_size: Nombre d'instances à traiter simultanément (pour grandes dimensions)
|
|
985
|
+
include_predictions: Inclure les prédictions du modèle avec l'explication
|
|
986
|
+
generate_narratives: Générer des descriptions textuelles des explications
|
|
987
|
+
language: Langue pour les narratives générées ('fr' ou 'en')
|
|
988
|
+
|
|
989
|
+
Returns:
|
|
990
|
+
ExplanationResult: Résultat enrichi avec métriques de qualité et informations de conformité
|
|
991
|
+
|
|
992
|
+
Raises:
|
|
993
|
+
ValueError: Si le format des données est incompatible
|
|
994
|
+
RuntimeError: Si l'explication échoue pour des raisons techniques
|
|
995
|
+
TimeoutError: Si le calcul dépasse le timeout configuré
|
|
996
|
+
"""
|
|
997
|
+
# Validation et prétraitement des données
|
|
998
|
+
if self._shap_explainer is None:
|
|
999
|
+
raise ValueError("L'explainer SHAP n'a pas été correctement initialisé.")
|
|
1000
|
+
|
|
1001
|
+
# Extraction des paramètres avancés avec valeurs par défaut
|
|
1002
|
+
output_index = kwargs.get('output_index', None)
|
|
1003
|
+
audience_level = kwargs.get('audience_level', AudienceLevel.TECHNICAL)
|
|
1004
|
+
summarize = kwargs.get('summarize', False)
|
|
1005
|
+
sample_size = kwargs.get('sample_size', None)
|
|
1006
|
+
compute_quality = kwargs.get('compute_quality_metrics', True)
|
|
1007
|
+
verify_compliance = kwargs.get('verify_compliance', self._config.verify_compliance)
|
|
1008
|
+
regulations = kwargs.get('regulations', self._config.compliance_regulations)
|
|
1009
|
+
return_raw_shap = kwargs.get('return_raw_shap', False)
|
|
1010
|
+
batch_size = kwargs.get('batch_size', self._config.batch_size)
|
|
1011
|
+
include_predictions = kwargs.get('include_predictions', True)
|
|
1012
|
+
generate_narratives = kwargs.get('generate_narratives', False)
|
|
1013
|
+
language = kwargs.get('language', 'fr')
|
|
1014
|
+
|
|
1015
|
+
# Gestion des grands jeux de données avec échantillonnage intelligent
|
|
1016
|
+
if sample_size and len(X) > sample_size:
|
|
1017
|
+
self._logger.info(f"Échantillonnage de {sample_size} instances parmi {len(X)} pour performance")
|
|
1018
|
+
if hasattr(X, 'sample'):
|
|
1019
|
+
# Échantillonnage stratifié si y est disponible
|
|
1020
|
+
if y is not None and hasattr(pd, 'DataFrame'):
|
|
1021
|
+
try:
|
|
1022
|
+
# Tentative d'échantillonnage stratifié
|
|
1023
|
+
temp_df = pd.DataFrame(X)
|
|
1024
|
+
temp_df['_target'] = y
|
|
1025
|
+
stratified = temp_df.groupby('_target', group_keys=False).apply(
|
|
1026
|
+
lambda x: x.sample(min(len(x), int(sample_size * len(x) / len(temp_df))))
|
|
1027
|
+
)
|
|
1028
|
+
indices = stratified.index
|
|
1029
|
+
X_sample = X.iloc[indices] if hasattr(X, 'iloc') else X[indices]
|
|
1030
|
+
y_sample = y[indices] if y is not None else None
|
|
1031
|
+
self._logger.debug("Échantillonnage stratifié appliqué")
|
|
1032
|
+
except Exception as e:
|
|
1033
|
+
self._logger.debug(f"Échec de l'échantillonnage stratifié: {str(e)}. Retour à aléatoire.")
|
|
1034
|
+
X_sample = X.sample(sample_size) if hasattr(X, 'sample') else X[np.random.choice(len(X), sample_size, replace=False)]
|
|
1035
|
+
y_sample = y[X_sample.index] if y is not None and hasattr(y, '__getitem__') else None
|
|
1036
|
+
else:
|
|
1037
|
+
# Échantillonnage aléatoire
|
|
1038
|
+
X_sample = X.sample(sample_size)
|
|
1039
|
+
y_sample = y[X_sample.index] if y is not None and hasattr(y, '__getitem__') else None
|
|
1040
|
+
else:
|
|
1041
|
+
# Échantillonnage numpy
|
|
1042
|
+
indices = np.random.choice(len(X), sample_size, replace=False)
|
|
1043
|
+
X_sample = X[indices]
|
|
1044
|
+
y_sample = y[indices] if y is not None and hasattr(y, '__getitem__') else None
|
|
1045
|
+
|
|
1046
|
+
# Utilisation des échantillons à la place des données complètes
|
|
1047
|
+
X, y = X_sample, y_sample
|
|
1048
|
+
|
|
1049
|
+
# Préparation du calcul des valeurs SHAP
|
|
1050
|
+
with Timer() as compute_timer, MemoryTracker() as compute_memory:
|
|
1051
|
+
# Utilisation du cache si activé
|
|
1052
|
+
cache_key = None
|
|
1053
|
+
if self._config.use_cache:
|
|
1054
|
+
try:
|
|
1055
|
+
import hashlib
|
|
1056
|
+
import pickle
|
|
1057
|
+
|
|
1058
|
+
# Génération d'une clé de cache unique pour ces données
|
|
1059
|
+
data_hash = hashlib.md5(pickle.dumps((X, output_index))).hexdigest()
|
|
1060
|
+
model_hash = self._model_hash if hasattr(self, '_model_hash') else hashlib.md5(
|
|
1061
|
+
pickle.dumps(self._extract_model_signature())).hexdigest()
|
|
1062
|
+
cache_key = f"shap_{model_hash}_{data_hash}_{self._config.n_samples}"
|
|
1063
|
+
|
|
1064
|
+
# Tentative de récupération depuis le cache
|
|
1065
|
+
if hasattr(self, '_explanation_cache') and cache_key in self._explanation_cache:
|
|
1066
|
+
self._logger.info("Explication récupérée depuis le cache")
|
|
1067
|
+
shap_values, expected_value, execution_time = self._explanation_cache[cache_key]
|
|
1068
|
+
cached = True
|
|
1069
|
+
except Exception as e:
|
|
1070
|
+
self._logger.debug(f"Erreur lors de l'accès au cache: {str(e)}")
|
|
1071
|
+
cache_key = None
|
|
1072
|
+
|
|
1073
|
+
# Calcul des valeurs SHAP si non trouvées dans le cache
|
|
1074
|
+
if not cache_key or not hasattr(self, '_explanation_cache') or cache_key not in self._explanation_cache:
|
|
1075
|
+
self._logger.info("Calcul des valeurs SHAP en cours...")
|
|
1076
|
+
|
|
1077
|
+
# Traitement par lots pour grands jeux de données
|
|
1078
|
+
if batch_size and len(X) > batch_size:
|
|
1079
|
+
self._logger.info(f"Traitement par lots de {batch_size} instances")
|
|
1080
|
+
|
|
1081
|
+
# Division en lots
|
|
1082
|
+
num_batches = (len(X) + batch_size - 1) // batch_size
|
|
1083
|
+
results = []
|
|
1084
|
+
expected_values = []
|
|
1085
|
+
|
|
1086
|
+
# Paramètres de parallélisation
|
|
1087
|
+
parallel = self._config.n_jobs > 1
|
|
1088
|
+
|
|
1089
|
+
if parallel:
|
|
1090
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
1091
|
+
|
|
1092
|
+
# Fonction pour calcul parallèle des valeurs SHAP par lot
|
|
1093
|
+
def process_batch(batch_idx):
|
|
1094
|
+
start_idx = batch_idx * batch_size
|
|
1095
|
+
end_idx = min(start_idx + batch_size, len(X))
|
|
1096
|
+
batch_X = X[start_idx:end_idx]
|
|
1097
|
+
with self._maybe_use_gpu_context():
|
|
1098
|
+
batch_result = self._shap_explainer.shap_values(
|
|
1099
|
+
batch_X, l1_reg=self._config.l1_reg, output_index=output_index
|
|
1100
|
+
)
|
|
1101
|
+
return batch_result
|
|
1102
|
+
|
|
1103
|
+
try:
|
|
1104
|
+
self._logger.debug(f"Exécution parallèle avec {self._config.n_jobs} workers")
|
|
1105
|
+
with ProcessPoolExecutor(max_workers=self._config.n_jobs) as executor:
|
|
1106
|
+
batch_results = list(executor.map(process_batch, range(num_batches)))
|
|
1107
|
+
|
|
1108
|
+
# Fusion des résultats
|
|
1109
|
+
if isinstance(batch_results[0], list):
|
|
1110
|
+
# Multi-output: list of arrays per class
|
|
1111
|
+
shap_values = [np.vstack([batch[i] for batch in batch_results])
|
|
1112
|
+
for i in range(len(batch_results[0]))]
|
|
1113
|
+
else:
|
|
1114
|
+
# Single output: array per instance
|
|
1115
|
+
shap_values = np.vstack(batch_results)
|
|
1116
|
+
except Exception as e:
|
|
1117
|
+
self._logger.warning(f"Erreur lors du calcul parallèle: {str(e)}. Passage en mode séquentiel.")
|
|
1118
|
+
parallel = False
|
|
1119
|
+
|
|
1120
|
+
# Traitement séquentiel si parallélisation impossible
|
|
1121
|
+
if not parallel:
|
|
1122
|
+
batch_results = []
|
|
1123
|
+
for batch_idx in range(num_batches):
|
|
1124
|
+
self._logger.debug(f"Traitement du lot {batch_idx+1}/{num_batches}")
|
|
1125
|
+
start_idx = batch_idx * batch_size
|
|
1126
|
+
end_idx = min(start_idx + batch_size, len(X))
|
|
1127
|
+
batch_X = X[start_idx:end_idx]
|
|
1128
|
+
|
|
1129
|
+
with self._maybe_use_gpu_context():
|
|
1130
|
+
batch_result = self._shap_explainer.shap_values(
|
|
1131
|
+
batch_X, l1_reg=self._config.l1_reg, output_index=output_index
|
|
1132
|
+
)
|
|
1133
|
+
batch_results.append(batch_result)
|
|
1134
|
+
|
|
1135
|
+
# Fusion des résultats
|
|
1136
|
+
if isinstance(batch_results[0], list):
|
|
1137
|
+
# Multi-output: list of arrays per class
|
|
1138
|
+
shap_values = [np.vstack([batch[i] for batch in batch_results])
|
|
1139
|
+
for i in range(len(batch_results[0]))]
|
|
1140
|
+
else:
|
|
1141
|
+
# Single output: array per instance
|
|
1142
|
+
shap_values = np.vstack(batch_results)
|
|
1143
|
+
|
|
1144
|
+
# Récupération de la valeur attendue
|
|
1145
|
+
if hasattr(self._shap_explainer, 'expected_value'):
|
|
1146
|
+
expected_value = self._shap_explainer.expected_value
|
|
1147
|
+
else:
|
|
1148
|
+
# Calcul manuel si non disponible
|
|
1149
|
+
try:
|
|
1150
|
+
if isinstance(shap_values, list):
|
|
1151
|
+
expected_value = [np.mean(self._model_predict_wrapper(background_data))
|
|
1152
|
+
for _ in range(len(shap_values))]
|
|
1153
|
+
else:
|
|
1154
|
+
expected_value = np.mean(self._model_predict_wrapper(background_data))
|
|
1155
|
+
except Exception as e:
|
|
1156
|
+
self._logger.warning(f"Erreur lors du calcul de expected_value: {str(e)}")
|
|
1157
|
+
expected_value = 0
|
|
1158
|
+
else:
|
|
1159
|
+
# Traitement standard sans lots
|
|
1160
|
+
with self._maybe_use_gpu_context():
|
|
1161
|
+
shap_values = self._shap_explainer.shap_values(
|
|
1162
|
+
X, l1_reg=self._config.l1_reg, output_index=output_index
|
|
1163
|
+
)
|
|
1164
|
+
expected_value = self._shap_explainer.expected_value if hasattr(self._shap_explainer, 'expected_value') else 0
|
|
1165
|
+
|
|
1166
|
+
# Mise en cache des résultats
|
|
1167
|
+
if cache_key:
|
|
1168
|
+
if not hasattr(self, '_explanation_cache'):
|
|
1169
|
+
from functools import lru_cache
|
|
1170
|
+
self._explanation_cache = lru_cache(maxsize=self._config.cache_size)(lambda x: x)({})
|
|
1171
|
+
self._explanation_cache[cache_key] = (shap_values, expected_value, compute_timer.duration)
|
|
1172
|
+
|
|
1173
|
+
# Métriques de qualité d'explication
|
|
1174
|
+
quality_metrics = {}
|
|
1175
|
+
if compute_quality:
|
|
1176
|
+
try:
|
|
1177
|
+
# Calcul des métriques de qualité
|
|
1178
|
+
quality_metrics = self._compute_explanation_quality(X, y, shap_values, expected_value)
|
|
1179
|
+
except Exception as e:
|
|
1180
|
+
self._logger.warning(f"Erreur lors du calcul des métriques de qualité: {str(e)}")
|
|
1181
|
+
|
|
1182
|
+
# Vérification de la conformité réglementaire
|
|
1183
|
+
compliance_results = None
|
|
1184
|
+
if verify_compliance and hasattr(self, '_compliance_checker'):
|
|
1185
|
+
try:
|
|
1186
|
+
compliance_results = self._compliance_checker.check_explanation(
|
|
1187
|
+
model=self._model,
|
|
1188
|
+
data=X,
|
|
1189
|
+
shap_values=shap_values,
|
|
1190
|
+
explainer=self,
|
|
1191
|
+
regulations=regulations
|
|
1192
|
+
)
|
|
1193
|
+
except Exception as e:
|
|
1194
|
+
self._logger.warning(f"Erreur lors de la vérification de conformité: {str(e)}")
|
|
1195
|
+
|
|
1196
|
+
# Génération des narratives explicatives
|
|
1197
|
+
narrative = None
|
|
1198
|
+
if generate_narratives:
|
|
1199
|
+
try:
|
|
1200
|
+
narrative = self._generate_explanation_narrative(
|
|
1201
|
+
shap_values, X, audience_level=audience_level, language=language
|
|
1202
|
+
)
|
|
1203
|
+
except Exception as e:
|
|
1204
|
+
self._logger.warning(f"Erreur lors de la génération de narratives: {str(e)}")
|
|
1205
|
+
|
|
1206
|
+
# Extraction des métadonnées complètes
|
|
1207
|
+
metadata = self._extract_metadata()
|
|
1208
|
+
metadata.update({
|
|
1209
|
+
'computation_time_seconds': compute_timer.duration,
|
|
1210
|
+
'memory_usage_mb': compute_memory.peak_usage_mb,
|
|
1211
|
+
'sample_size': len(X),
|
|
1212
|
+
'quality_metrics': quality_metrics,
|
|
1213
|
+
'audience_level': audience_level,
|
|
1214
|
+
'computation_mode': 'parallel' if self._config.n_jobs > 1 else 'sequential',
|
|
1215
|
+
'batch_processing': bool(batch_size and len(X) > batch_size),
|
|
1216
|
+
'cache_hit': bool(cache_key and hasattr(self, '_explanation_cache') and cache_key in self._explanation_cache)
|
|
1217
|
+
})
|
|
1218
|
+
|
|
1219
|
+
# Création du résultat d'explication
|
|
1220
|
+
result = self._create_explanation_result(
|
|
1221
|
+
shap_values=shap_values,
|
|
1222
|
+
expected_value=expected_value,
|
|
1223
|
+
X=X,
|
|
1224
|
+
y=y if include_predictions else None,
|
|
1225
|
+
metadata=metadata,
|
|
1226
|
+
compliance=compliance_results,
|
|
1227
|
+
narrative=narrative,
|
|
1228
|
+
raw_shap=shap_values if return_raw_shap else None
|
|
1229
|
+
)
|
|
1230
|
+
|
|
1231
|
+
# Conversion des données en format approprié
|
|
1232
|
+
if isinstance(X, pd.DataFrame):
|
|
1233
|
+
feature_names = X.columns.tolist()
|
|
1234
|
+
X_values = X.values
|
|
1235
|
+
else:
|
|
1236
|
+
X_values = X
|
|
1237
|
+
feature_names = kwargs.get('feature_names',
|
|
1238
|
+
[f"feature_{i}" for i in range(X_values.shape[1])])
|
|
1239
|
+
|
|
1240
|
+
# Limiter le nombre d'échantillons pour performance si nécessaire
|
|
1241
|
+
if X_values.shape[0] > max_samples:
|
|
1242
|
+
self._logger.warning(f"Échantillonnage de {max_samples} instances sur {X_values.shape[0]} pour performance.")
|
|
1243
|
+
indices = np.random.choice(X_values.shape[0], max_samples, replace=False)
|
|
1244
|
+
X_sample = X_values[indices]
|
|
1245
|
+
else:
|
|
1246
|
+
X_sample = X_values
|
|
1247
|
+
|
|
1248
|
+
# Tracer l'action
|
|
1249
|
+
self.add_audit_record("explain", {
|
|
1250
|
+
"n_samples": X_sample.shape[0],
|
|
1251
|
+
"n_features": X_sample.shape[1],
|
|
1252
|
+
"audience_level": audience_level.value if isinstance(audience_level, AudienceLevel) else audience_level,
|
|
1253
|
+
"include_interaction_values": include_interaction_values,
|
|
1254
|
+
"summarize": summarize
|
|
1255
|
+
})
|
|
1256
|
+
|
|
1257
|
+
try:
|
|
1258
|
+
# Calculer les valeurs SHAP
|
|
1259
|
+
shap_values = self._shap_explainer.shap_values(X_sample)
|
|
1260
|
+
|
|
1261
|
+
# Gérer les différents formats de sortie selon le type d'explainer SHAP
|
|
1262
|
+
if isinstance(shap_values, list):
|
|
1263
|
+
# Cas multi-classe: une liste de tableaux
|
|
1264
|
+
if output_indices is not None:
|
|
1265
|
+
if isinstance(output_indices, int):
|
|
1266
|
+
output_indices = [output_indices]
|
|
1267
|
+
shap_values = [shap_values[i] for i in output_indices]
|
|
1268
|
+
|
|
1269
|
+
# Calculer les valeurs d'interaction si demandé
|
|
1270
|
+
interaction_values = None
|
|
1271
|
+
if include_interaction_values and hasattr(self._shap_explainer, 'shap_interaction_values'):
|
|
1272
|
+
try:
|
|
1273
|
+
interaction_values = self._shap_explainer.shap_interaction_values(X_sample)
|
|
1274
|
+
except Exception as e:
|
|
1275
|
+
self._logger.warning(f"Impossible de calculer les valeurs d'interaction: {str(e)}")
|
|
1276
|
+
|
|
1277
|
+
# Préparer les importances de caractéristiques
|
|
1278
|
+
feature_importances = []
|
|
1279
|
+
|
|
1280
|
+
# Cas où on résume les résultats pour l'ensemble du dataset
|
|
1281
|
+
if summarize:
|
|
1282
|
+
if isinstance(shap_values, list):
|
|
1283
|
+
# Moyenne des valeurs absolues pour chaque classe
|
|
1284
|
+
global_importances = np.zeros(X_sample.shape[1])
|
|
1285
|
+
for sv in shap_values:
|
|
1286
|
+
global_importances += np.mean(np.abs(sv), axis=0)
|
|
1287
|
+
global_importances /= len(shap_values)
|
|
1288
|
+
else:
|
|
1289
|
+
# Moyenne des valeurs absolues
|
|
1290
|
+
global_importances = np.mean(np.abs(shap_values), axis=0)
|
|
1291
|
+
|
|
1292
|
+
# Créer les objets FeatureImportance
|
|
1293
|
+
for i, (name, importance) in enumerate(zip(feature_names, global_importances)):
|
|
1294
|
+
feature_importances.append(FeatureImportance(
|
|
1295
|
+
feature_name=name,
|
|
1296
|
+
importance=float(importance),
|
|
1297
|
+
# Ajouter des statistiques si disponibles
|
|
1298
|
+
std_dev=float(np.std(np.abs(shap_values[0][:, i])) if isinstance(shap_values, list)
|
|
1299
|
+
else np.std(np.abs(shap_values[:, i])))
|
|
1300
|
+
))
|
|
1301
|
+
|
|
1302
|
+
# Extraire les métadonnées du modèle
|
|
1303
|
+
if not self._metadata:
|
|
1304
|
+
self._extract_metadata()
|
|
1305
|
+
|
|
1306
|
+
# Créer le résultat d'explication
|
|
1307
|
+
result = ExplanationResult(
|
|
1308
|
+
method=ExplainabilityMethod.SHAP,
|
|
1309
|
+
model_metadata=self._metadata,
|
|
1310
|
+
feature_importances=feature_importances,
|
|
1311
|
+
raw_explanation={
|
|
1312
|
+
"shap_values": shap_values,
|
|
1313
|
+
"interaction_values": interaction_values,
|
|
1314
|
+
"feature_names": feature_names,
|
|
1315
|
+
"data": X_sample
|
|
1316
|
+
},
|
|
1317
|
+
audience_level=audience_level
|
|
1318
|
+
)
|
|
1319
|
+
|
|
1320
|
+
return result
|
|
1321
|
+
|
|
1322
|
+
except Exception as e:
|
|
1323
|
+
self._logger.error(f"Erreur lors du calcul des valeurs SHAP: {str(e)}")
|
|
1324
|
+
raise RuntimeError(f"Échec de l'explication SHAP: {str(e)}")
|
|
1325
|
+
|
|
1326
|
+
def explain_instance(self, instance, **kwargs) -> ExplanationResult:
|
|
1327
|
+
"""
|
|
1328
|
+
Explique une instance spécifique avec explications SHAP optimisées et enrichies.
|
|
1329
|
+
|
|
1330
|
+
Cette méthode implémente une explication avancée d'instance individuelle avec:
|
|
1331
|
+
- Optimisations de performance (GPU, cache)
|
|
1332
|
+
- Métriques de qualité d'explication
|
|
1333
|
+
- Conformité réglementaire
|
|
1334
|
+
- Générations de narratives explicatives adaptées à l'audience
|
|
1335
|
+
- Métadonnées complètes et traces d'audit
|
|
1336
|
+
|
|
1337
|
+
Args:
|
|
1338
|
+
instance: Instance à expliquer (array, liste, dict ou pandas.Series)
|
|
1339
|
+
**kwargs: Paramètres additionnels
|
|
1340
|
+
- output_index: Indice de la sortie à expliquer pour les modèles multi-sorties
|
|
1341
|
+
- audience_level: Niveau d'audience (TECHNICAL, BUSINESS, PUBLIC)
|
|
1342
|
+
- language: Langue des narratives ('fr', 'en')
|
|
1343
|
+
- include_interaction_values: Calculer les valeurs d'interaction entre caractéristiques
|
|
1344
|
+
- check_compliance: Vérifier la conformité réglementaire
|
|
1345
|
+
- compliance_regs: Liste des réglementations à vérifier
|
|
1346
|
+
- compute_metrics: Calculer des métriques de qualité d'explication
|
|
1347
|
+
- return_raw_shap: Renvoyer les valeurs SHAP brutes (potentiellement volumineuses)
|
|
1348
|
+
- generate_narratives: Générer des descriptions textuelles des explications
|
|
1349
|
+
- include_predictions: Inclure les prédictions du modèle dans le résultat
|
|
1350
|
+
- feature_names: Noms des caractéristiques (si non disponibles dans les données)
|
|
1351
|
+
|
|
1352
|
+
Returns:
|
|
1353
|
+
ExplanationResult: Résultat standardisé et enrichi de l'explication
|
|
1354
|
+
|
|
1355
|
+
Raises:
|
|
1356
|
+
ValueError: Si le format d'instance n'est pas supporté
|
|
1357
|
+
RuntimeError: Si l'explication échoue pour des raisons techniques
|
|
1358
|
+
"""
|
|
1359
|
+
# Convertir l'instance en format approprié
|
|
1360
|
+
if isinstance(instance, dict):
|
|
1361
|
+
# Convertir dict en DataFrame
|
|
1362
|
+
instance_df = pd.DataFrame([instance])
|
|
1363
|
+
feature_names = list(instance.keys())
|
|
1364
|
+
elif isinstance(instance, pd.Series):
|
|
1365
|
+
instance_df = pd.DataFrame([instance])
|
|
1366
|
+
feature_names = instance.index.tolist()
|
|
1367
|
+
elif isinstance(instance, (list, np.ndarray)):
|
|
1368
|
+
instance_array = np.array(instance).reshape(1, -1)
|
|
1369
|
+
instance_df = pd.DataFrame(instance_array)
|
|
1370
|
+
feature_names = kwargs.get('feature_names',
|
|
1371
|
+
[f"feature_{i}" for i in range(instance_array.shape[1])])
|
|
1372
|
+
else:
|
|
1373
|
+
raise ValueError("Format d'instance non supporté. Utilisez un dict, pandas.Series, liste ou numpy.ndarray.")
|
|
1374
|
+
|
|
1375
|
+
# Extraction des paramètres avancés
|
|
1376
|
+
output_index = kwargs.get('output_index', None)
|
|
1377
|
+
audience_level = kwargs.get('audience_level', AudienceLevel.TECHNICAL)
|
|
1378
|
+
language = kwargs.get('language', 'fr')
|
|
1379
|
+
include_interaction_values = kwargs.get('include_interaction_values', False)
|
|
1380
|
+
check_compliance = kwargs.get('check_compliance', self._config.compliance_mode)
|
|
1381
|
+
compliance_regs = kwargs.get('compliance_regs', self._config.compliance_regs)
|
|
1382
|
+
compute_metrics = kwargs.get('compute_metrics', self._config.compute_metrics)
|
|
1383
|
+
return_raw_shap = kwargs.get('return_raw_shap', False)
|
|
1384
|
+
generate_narratives = kwargs.get('generate_narratives', True)
|
|
1385
|
+
include_predictions = kwargs.get('include_predictions', True)
|
|
1386
|
+
|
|
1387
|
+
# Préparation du cache et des métriques de performance
|
|
1388
|
+
compute_timer = Timer()
|
|
1389
|
+
compute_memory = MemoryTracker()
|
|
1390
|
+
cache_key = None
|
|
1391
|
+
|
|
1392
|
+
# Création d'une clé de cache unique pour cette instance
|
|
1393
|
+
if hasattr(self, '_explanation_cache'):
|
|
1394
|
+
try:
|
|
1395
|
+
# Génère une clé de hachage unique pour cette instance
|
|
1396
|
+
instance_hash = hashlib.md5(pickle.dumps(instance_df)).hexdigest()
|
|
1397
|
+
params_hash = hashlib.md5(str({
|
|
1398
|
+
'output_index': output_index,
|
|
1399
|
+
'audience_level': audience_level,
|
|
1400
|
+
'include_interaction_values': include_interaction_values
|
|
1401
|
+
}).encode()).hexdigest()
|
|
1402
|
+
cache_key = f"instance_{instance_hash}_{params_hash}"
|
|
1403
|
+
|
|
1404
|
+
# Vérifie si l'explication est déjà en cache
|
|
1405
|
+
if cache_key in self._explanation_cache:
|
|
1406
|
+
self._logger.info("Explication récupérée du cache")
|
|
1407
|
+
cached_result = self._explanation_cache[cache_key]
|
|
1408
|
+
# Mise à jour des métadonnées pour refléter l'utilisation du cache
|
|
1409
|
+
if isinstance(cached_result, ExplanationResult) and hasattr(cached_result, 'metadata'):
|
|
1410
|
+
cached_result.metadata['cache_hit'] = True
|
|
1411
|
+
cached_result.metadata['computation_time_seconds'] = 0.0
|
|
1412
|
+
return cached_result
|
|
1413
|
+
except Exception as e:
|
|
1414
|
+
self._logger.warning(f"Erreur lors de l'accès au cache: {str(e)}")
|
|
1415
|
+
cache_key = None
|
|
1416
|
+
|
|
1417
|
+
# Tracer l'action avec paramètres enrichis
|
|
1418
|
+
self.add_audit_record("explain_instance", {
|
|
1419
|
+
"n_features": len(feature_names),
|
|
1420
|
+
"audience_level": audience_level.value if isinstance(audience_level, AudienceLevel) else audience_level,
|
|
1421
|
+
"include_interaction_values": include_interaction_values,
|
|
1422
|
+
"check_compliance": check_compliance,
|
|
1423
|
+
"compute_metrics": compute_metrics,
|
|
1424
|
+
"language": language,
|
|
1425
|
+
"use_gpu": self._config.use_gpu
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
compute_timer.start()
|
|
1429
|
+
compute_memory.start_tracking()
|
|
1430
|
+
|
|
1431
|
+
try:
|
|
1432
|
+
# Récupération de la valeur attendue (baseline) de l'explainer
|
|
1433
|
+
expected_value = self._shap_explainer.expected_value
|
|
1434
|
+
if isinstance(expected_value, np.ndarray):
|
|
1435
|
+
expected_value = expected_value.tolist()
|
|
1436
|
+
elif isinstance(expected_value, list) and all(isinstance(x, np.ndarray) for x in expected_value):
|
|
1437
|
+
expected_value = [x.tolist() for x in expected_value]
|
|
1438
|
+
|
|
1439
|
+
# Utilisation du contexte GPU si configuré
|
|
1440
|
+
with self._maybe_use_gpu_context():
|
|
1441
|
+
# Calculer les valeurs SHAP avec optimisations
|
|
1442
|
+
shap_values = self._shap_explainer.shap_values(instance_df)
|
|
1443
|
+
|
|
1444
|
+
# Gérer les différents formats de sortie selon le type d'explainer SHAP
|
|
1445
|
+
if isinstance(shap_values, list):
|
|
1446
|
+
# Cas multi-classe: une liste de tableaux
|
|
1447
|
+
if output_index is not None:
|
|
1448
|
+
shap_values = shap_values[output_index]
|
|
1449
|
+
else:
|
|
1450
|
+
# Prendre la classe avec la probabilité maximale
|
|
1451
|
+
predictions = self._model_predict_wrapper(instance_df)
|
|
1452
|
+
if predictions.ndim > 1 and predictions.shape[1] > 1:
|
|
1453
|
+
output_index = np.argmax(predictions[0])
|
|
1454
|
+
shap_values = shap_values[output_index]
|
|
1455
|
+
else:
|
|
1456
|
+
# Cas binaire, prendre la classe positive
|
|
1457
|
+
shap_values = shap_values[1] if len(shap_values) > 1 else shap_values[0]
|
|
1458
|
+
|
|
1459
|
+
# Calcul des valeurs d'interaction si demandé
|
|
1460
|
+
interaction_values = None
|
|
1461
|
+
if include_interaction_values and hasattr(self._shap_explainer, 'shap_interaction_values'):
|
|
1462
|
+
try:
|
|
1463
|
+
with self._maybe_use_gpu_context():
|
|
1464
|
+
interaction_values = self._shap_explainer.shap_interaction_values(instance_df)
|
|
1465
|
+
if isinstance(interaction_values, list) and output_index is not None:
|
|
1466
|
+
interaction_values = interaction_values[output_index]
|
|
1467
|
+
except Exception as e:
|
|
1468
|
+
self._logger.warning(f"Impossible de calculer les valeurs d'interaction: {str(e)}")
|
|
1469
|
+
|
|
1470
|
+
# Génération des prédictions du modèle si demandé
|
|
1471
|
+
y_pred = None
|
|
1472
|
+
if include_predictions:
|
|
1473
|
+
try:
|
|
1474
|
+
y_pred = self._model_predict_wrapper(instance_df)
|
|
1475
|
+
except Exception as e:
|
|
1476
|
+
self._logger.warning(f"Impossible de générer les prédictions: {str(e)}")
|
|
1477
|
+
|
|
1478
|
+
# Métriques de qualité d'explication
|
|
1479
|
+
quality_metrics = {}
|
|
1480
|
+
if compute_metrics:
|
|
1481
|
+
try:
|
|
1482
|
+
# Pour une instance unique, nous calculons des métriques spécifiques à l'instance
|
|
1483
|
+
quality_metrics = {
|
|
1484
|
+
'local_fidelity': self._compute_local_fidelity(instance_df, shap_values, expected_value),
|
|
1485
|
+
'feature_sparsity': self._gini_index(np.abs(shap_values)),
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
# Si des prédictions ont été faites, ajout de métriques supplémentaires
|
|
1489
|
+
if y_pred is not None:
|
|
1490
|
+
quality_metrics['prediction_impact'] = self._compute_prediction_impact(shap_values, expected_value, y_pred)
|
|
1491
|
+
except Exception as e:
|
|
1492
|
+
self._logger.warning(f"Erreur lors du calcul des métriques de qualité: {str(e)}")
|
|
1493
|
+
|
|
1494
|
+
# Vérification de conformité
|
|
1495
|
+
compliance_results = None
|
|
1496
|
+
if check_compliance and hasattr(self, '_compliance_checker'):
|
|
1497
|
+
try:
|
|
1498
|
+
compliance_results = self._compliance_checker.check_explanation(
|
|
1499
|
+
shap_values=shap_values,
|
|
1500
|
+
data=instance_df,
|
|
1501
|
+
model=self._model,
|
|
1502
|
+
regulations=compliance_regs
|
|
1503
|
+
)
|
|
1504
|
+
except Exception as e:
|
|
1505
|
+
self._logger.warning(f"Erreur lors de la vérification de conformité: {str(e)}")
|
|
1506
|
+
|
|
1507
|
+
# Génération des narratives explicatives
|
|
1508
|
+
narrative = None
|
|
1509
|
+
if generate_narratives:
|
|
1510
|
+
try:
|
|
1511
|
+
narrative = self._generate_explanation_narrative(
|
|
1512
|
+
shap_values, instance_df, audience_level=audience_level, language=language
|
|
1513
|
+
)
|
|
1514
|
+
except Exception as e:
|
|
1515
|
+
self._logger.warning(f"Erreur lors de la génération de narratives: {str(e)}")
|
|
1516
|
+
|
|
1517
|
+
# Arrêt du minuteur et du suivi mémoire
|
|
1518
|
+
compute_timer.stop()
|
|
1519
|
+
compute_memory.stop_tracking()
|
|
1520
|
+
|
|
1521
|
+
# Extraction des métadonnées complètes
|
|
1522
|
+
metadata = self._extract_metadata()
|
|
1523
|
+
metadata.update({
|
|
1524
|
+
'computation_time_seconds': compute_timer.duration,
|
|
1525
|
+
'memory_usage_mb': compute_memory.peak_usage_mb,
|
|
1526
|
+
'quality_metrics': quality_metrics,
|
|
1527
|
+
'audience_level': audience_level,
|
|
1528
|
+
'computation_mode': 'gpu' if self._config.use_gpu else 'cpu',
|
|
1529
|
+
'cache_hit': False,
|
|
1530
|
+
'is_single_instance': True
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
# Créer le résultat d'explication enrichi en utilisant notre méthode avancée
|
|
1534
|
+
result = self._create_explanation_result(
|
|
1535
|
+
shap_values=shap_values,
|
|
1536
|
+
expected_value=expected_value,
|
|
1537
|
+
X=instance_df,
|
|
1538
|
+
y=y_pred if include_predictions else None,
|
|
1539
|
+
metadata=metadata,
|
|
1540
|
+
compliance=compliance_results,
|
|
1541
|
+
narrative=narrative,
|
|
1542
|
+
raw_shap=shap_values if return_raw_shap else None,
|
|
1543
|
+
interaction_values=interaction_values if include_interaction_values else None,
|
|
1544
|
+
feature_names=feature_names,
|
|
1545
|
+
output_index=output_index
|
|
1546
|
+
)
|
|
1547
|
+
|
|
1548
|
+
# Mise en cache du résultat si possible
|
|
1549
|
+
if cache_key and hasattr(self, '_explanation_cache'):
|
|
1550
|
+
try:
|
|
1551
|
+
self._explanation_cache[cache_key] = result
|
|
1552
|
+
except Exception as e:
|
|
1553
|
+
self._logger.warning(f"Erreur lors de la mise en cache du résultat: {str(e)}")
|
|
1554
|
+
|
|
1555
|
+
return result
|
|
1556
|
+
|
|
1557
|
+
except Exception as e:
|
|
1558
|
+
self._logger.error(f"Erreur lors du calcul des valeurs SHAP pour l'instance: {str(e)}")
|
|
1559
|
+
raise RuntimeError(f"Échec de l'explication SHAP pour l'instance: {str(e)}")
|
|
1560
|
+
|
|
1561
|
+
def _compute_local_fidelity(self, X, shap_values, expected_value):
|
|
1562
|
+
"""
|
|
1563
|
+
Calcule la fidélité locale de l'explication SHAP pour une instance.
|
|
1564
|
+
|
|
1565
|
+
La fidélité locale mesure comment les attributions SHAP expliquent
|
|
1566
|
+
effectivement la différence entre la prédiction du modèle et la valeur de référence.
|
|
1567
|
+
|
|
1568
|
+
Args:
|
|
1569
|
+
X: Instance à expliquer (DataFrame ou ndarray)
|
|
1570
|
+
shap_values: Valeurs SHAP calculées
|
|
1571
|
+
expected_value: Valeur attendue (baseline)
|
|
1572
|
+
|
|
1573
|
+
Returns:
|
|
1574
|
+
float: Score de fidélité locale (0-1)
|
|
1575
|
+
"""
|
|
1576
|
+
try:
|
|
1577
|
+
# Prédiction du modèle pour l'instance
|
|
1578
|
+
prediction = self._model_predict_wrapper(X)
|
|
1579
|
+
|
|
1580
|
+
# Format des données selon le type de sortie
|
|
1581
|
+
if hasattr(prediction, 'shape') and len(prediction.shape) > 1 and prediction.shape[1] > 1:
|
|
1582
|
+
# Classification multi-classes
|
|
1583
|
+
if isinstance(shap_values, list):
|
|
1584
|
+
# Vérifier la somme des valeurs SHAP pour chaque classe
|
|
1585
|
+
class_fidelities = []
|
|
1586
|
+
for i, sv in enumerate(shap_values):
|
|
1587
|
+
if sv.ndim > 1:
|
|
1588
|
+
sv_sum = sv[0].sum()
|
|
1589
|
+
else:
|
|
1590
|
+
sv_sum = sv.sum()
|
|
1591
|
+
|
|
1592
|
+
ev = expected_value[i] if isinstance(expected_value, list) else expected_value
|
|
1593
|
+
pred = prediction[0, i] if prediction.ndim > 1 else prediction[i]
|
|
1594
|
+
|
|
1595
|
+
# Différence entre la prédiction réelle et la prédiction explicable
|
|
1596
|
+
fidelity = 1.0 - min(1.0, abs(pred - (ev + sv_sum)) / max(0.01, abs(pred)))
|
|
1597
|
+
class_fidelities.append(fidelity)
|
|
1598
|
+
|
|
1599
|
+
# Moyenne des fidélités par classe
|
|
1600
|
+
return np.mean(class_fidelities)
|
|
1601
|
+
else:
|
|
1602
|
+
# Cas binaire avec shap_values pour classe positive
|
|
1603
|
+
if shap_values.ndim > 1:
|
|
1604
|
+
sv_sum = shap_values[0].sum()
|
|
1605
|
+
else:
|
|
1606
|
+
sv_sum = shap_values.sum()
|
|
1607
|
+
|
|
1608
|
+
ev = expected_value[1] if isinstance(expected_value, list) else expected_value
|
|
1609
|
+
pred = prediction[0, 1] if prediction.ndim > 1 else prediction[1]
|
|
1610
|
+
|
|
1611
|
+
return 1.0 - min(1.0, abs(pred - (ev + sv_sum)) / max(0.01, abs(pred)))
|
|
1612
|
+
else:
|
|
1613
|
+
# Régression ou classification binaire (sortie simple)
|
|
1614
|
+
if shap_values.ndim > 1:
|
|
1615
|
+
sv_sum = shap_values[0].sum()
|
|
1616
|
+
else:
|
|
1617
|
+
sv_sum = shap_values.sum()
|
|
1618
|
+
|
|
1619
|
+
ev = expected_value[0] if isinstance(expected_value, list) else expected_value
|
|
1620
|
+
pred = prediction[0] if hasattr(prediction, '__len__') else prediction
|
|
1621
|
+
|
|
1622
|
+
return 1.0 - min(1.0, abs(pred - (ev + sv_sum)) / max(0.01, abs(pred)))
|
|
1623
|
+
except Exception as e:
|
|
1624
|
+
self._logger.warning(f"Erreur lors du calcul de la fidélité locale: {str(e)}")
|
|
1625
|
+
return None
|
|
1626
|
+
|
|
1627
|
+
def _compute_prediction_impact(self, shap_values, expected_value, prediction):
|
|
1628
|
+
"""
|
|
1629
|
+
Évalue l'impact des attributions sur la prédiction du modèle.
|
|
1630
|
+
|
|
1631
|
+
Cette méthode détermine dans quelle mesure les attributions SHAP
|
|
1632
|
+
influencent substantiellement la prédiction finale par rapport à la valeur de référence.
|
|
1633
|
+
|
|
1634
|
+
Args:
|
|
1635
|
+
shap_values: Valeurs SHAP calculées
|
|
1636
|
+
expected_value: Valeur attendue (baseline)
|
|
1637
|
+
prediction: Prédiction du modèle
|
|
1638
|
+
|
|
1639
|
+
Returns:
|
|
1640
|
+
float: Ratio d'impact (0-1) indiquant l'importance relative des attributions
|
|
1641
|
+
"""
|
|
1642
|
+
try:
|
|
1643
|
+
# Gérer les formats de données diverses
|
|
1644
|
+
if isinstance(shap_values, list):
|
|
1645
|
+
# Classification multi-classes
|
|
1646
|
+
impacts = []
|
|
1647
|
+
for i, sv in enumerate(shap_values):
|
|
1648
|
+
sv_sum = np.abs(sv).sum() if sv.ndim == 1 else np.abs(sv[0]).sum()
|
|
1649
|
+
pred_val = prediction[0, i] if prediction.ndim > 1 else prediction[i]
|
|
1650
|
+
baseline = expected_value[i] if isinstance(expected_value, list) else expected_value
|
|
1651
|
+
|
|
1652
|
+
# Impact normalisé: contrib des attributions / écart total à la baseline
|
|
1653
|
+
impacts.append(sv_sum / max(0.001, abs(pred_val - baseline) + sv_sum))
|
|
1654
|
+
return np.mean(impacts)
|
|
1655
|
+
else:
|
|
1656
|
+
# Régression ou classification binaire
|
|
1657
|
+
sv_sum = np.abs(shap_values).sum() if shap_values.ndim == 1 else np.abs(shap_values[0]).sum()
|
|
1658
|
+
pred_val = prediction[0] if hasattr(prediction, '__len__') else prediction
|
|
1659
|
+
baseline = expected_value[0] if isinstance(expected_value, list) else expected_value
|
|
1660
|
+
|
|
1661
|
+
return sv_sum / max(0.001, abs(pred_val - baseline) + sv_sum)
|
|
1662
|
+
except Exception as e:
|
|
1663
|
+
self._logger.warning(f"Erreur lors du calcul de l'impact prédictif: {str(e)}")
|
|
1664
|
+
return None
|
|
1665
|
+
|
|
1666
|
+
def _compute_explanation_quality(self, X, y, shap_values, expected_value):
|
|
1667
|
+
"""
|
|
1668
|
+
Calcule des métriques avancées de qualité pour évaluer la fiabilité des explications.
|
|
1669
|
+
|
|
1670
|
+
Cette méthode implémente un ensemble de métriques pour quantifier divers aspects
|
|
1671
|
+
de la qualité d'explication tels que:
|
|
1672
|
+
- Fidélité: Comment les valeurs SHAP prédisent correctement les sorties du modèle
|
|
1673
|
+
- Stabilité: Cohérence des explications sur des variations minimales des données
|
|
1674
|
+
- Sparsité: Concentration des attributions sur un sous-ensemble de caractéristiques
|
|
1675
|
+
|
|
1676
|
+
Args:
|
|
1677
|
+
X: Données d'entrée
|
|
1678
|
+
y: Valeurs cibles réelles ou prédites
|
|
1679
|
+
shap_values: Valeurs SHAP calculées
|
|
1680
|
+
expected_value: Valeur attendue du modèle
|
|
1681
|
+
|
|
1682
|
+
Returns:
|
|
1683
|
+
dict: Dictionnaire de métriques de qualité avec leurs valeurs
|
|
1684
|
+
"""
|
|
1685
|
+
metrics = {}
|
|
1686
|
+
|
|
1687
|
+
try:
|
|
1688
|
+
# Conversion en numpy pour calculs homogènes
|
|
1689
|
+
X_np = X.values if hasattr(X, 'values') else np.array(X)
|
|
1690
|
+
|
|
1691
|
+
# 1. Métrique de fidélité (corrélation prédiction-explication)
|
|
1692
|
+
if isinstance(shap_values, list):
|
|
1693
|
+
# Multi-class case
|
|
1694
|
+
predictions = self._model_predict_wrapper(X)
|
|
1695
|
+
if predictions.ndim > 1 and predictions.shape[1] > 1:
|
|
1696
|
+
# Prendre la classe maximale pour chaque prédiction
|
|
1697
|
+
pred_class = np.argmax(predictions, axis=1)
|
|
1698
|
+
# Extraire les valeurs SHAP correspondantes à la classe prédite
|
|
1699
|
+
class_shap = np.array([shap_values[pred_class[i]][i].sum() for i in range(len(X_np))])
|
|
1700
|
+
|
|
1701
|
+
# Calculer la corrélation
|
|
1702
|
+
if hasattr(y, 'values'):
|
|
1703
|
+
y_np = y.values
|
|
1704
|
+
else:
|
|
1705
|
+
y_np = np.array(y) if y is not None else pred_class
|
|
1706
|
+
|
|
1707
|
+
fidelity_score = np.corrcoef(class_shap, predictions.max(axis=1))[0, 1]
|
|
1708
|
+
metrics['fidelity_correlation'] = float(fidelity_score)
|
|
1709
|
+
else:
|
|
1710
|
+
# Regression/binary case
|
|
1711
|
+
predictions = self._model_predict_wrapper(X)
|
|
1712
|
+
if predictions.ndim > 1 and predictions.shape[1] > 1:
|
|
1713
|
+
# Binary with probability output
|
|
1714
|
+
predictions = predictions[:, 1] # Take positive class probability
|
|
1715
|
+
|
|
1716
|
+
# Sum des valeurs SHAP + expected value devrait être proche de la prédiction
|
|
1717
|
+
if isinstance(shap_values, np.ndarray):
|
|
1718
|
+
shap_sum = shap_values.sum(axis=1)
|
|
1719
|
+
if isinstance(expected_value, (list, np.ndarray)):
|
|
1720
|
+
expected_scalar = expected_value[0] if len(expected_value) > 0 else 0
|
|
1721
|
+
else:
|
|
1722
|
+
expected_scalar = expected_value
|
|
1723
|
+
|
|
1724
|
+
shap_predictions = shap_sum + expected_scalar
|
|
1725
|
+
fidelity_score = np.corrcoef(shap_predictions.flatten(), predictions.flatten())[0, 1]
|
|
1726
|
+
metrics['fidelity_correlation'] = float(fidelity_score)
|
|
1727
|
+
|
|
1728
|
+
# RMSE entre prédictions et reconstruction SHAP
|
|
1729
|
+
fidelity_rmse = np.sqrt(np.mean((shap_predictions.flatten() - predictions.flatten()) ** 2))
|
|
1730
|
+
metrics['fidelity_rmse'] = float(fidelity_rmse)
|
|
1731
|
+
|
|
1732
|
+
# 2. Métrique de stabilité (variance des explications)
|
|
1733
|
+
if isinstance(shap_values, list):
|
|
1734
|
+
avg_variance = np.mean([np.var(sv, axis=0).mean() for sv in shap_values])
|
|
1735
|
+
else:
|
|
1736
|
+
avg_variance = np.var(shap_values, axis=0).mean()
|
|
1737
|
+
metrics['stability_variance'] = float(avg_variance)
|
|
1738
|
+
|
|
1739
|
+
# 3. Métrique de sparsité (concentration des attributions)
|
|
1740
|
+
if isinstance(shap_values, list):
|
|
1741
|
+
avg_gini = np.mean([self._gini_index(np.abs(sv).mean(axis=0)) for sv in shap_values])
|
|
1742
|
+
else:
|
|
1743
|
+
feature_importance = np.abs(shap_values).mean(axis=0)
|
|
1744
|
+
avg_gini = self._gini_index(feature_importance)
|
|
1745
|
+
metrics['sparsity_gini'] = float(avg_gini)
|
|
1746
|
+
|
|
1747
|
+
# 4. Proportion d'attributions nulles ou négligeables
|
|
1748
|
+
threshold = 0.01 * (shap_values[0].max() if isinstance(shap_values, list) else shap_values.max())
|
|
1749
|
+
if isinstance(shap_values, list):
|
|
1750
|
+
avg_sparsity = np.mean([np.mean(np.abs(sv) < threshold) for sv in shap_values])
|
|
1751
|
+
else:
|
|
1752
|
+
avg_sparsity = np.mean(np.abs(shap_values) < threshold)
|
|
1753
|
+
metrics['feature_sparsity'] = float(avg_sparsity)
|
|
1754
|
+
|
|
1755
|
+
except Exception as e:
|
|
1756
|
+
self._logger.warning(f"Erreur lors du calcul des métriques de qualité: {str(e)}")
|
|
1757
|
+
|
|
1758
|
+
return metrics
|
|
1759
|
+
|
|
1760
|
+
def _generate_explanation_narrative(self, shap_values, X, audience_level=AudienceLevel.TECHNICAL, language='fr'):
|
|
1761
|
+
"""
|
|
1762
|
+
Génère des descriptions textuelles des explications SHAP adaptées au niveau d'audience.
|
|
1763
|
+
|
|
1764
|
+
Cette méthode traduit les valeurs SHAP complexes en narratives explicatives claires
|
|
1765
|
+
qui s'adaptent automatiquement au niveau de technicité requis par l'audience.
|
|
1766
|
+
|
|
1767
|
+
Args:
|
|
1768
|
+
shap_values: Les valeurs SHAP calculées
|
|
1769
|
+
X: Données expliquées
|
|
1770
|
+
audience_level: Niveau d'audience ('TECHNICAL', 'BUSINESS', 'PUBLIC')
|
|
1771
|
+
language: Langue des narratives ('fr' ou 'en')
|
|
1772
|
+
|
|
1773
|
+
Returns:
|
|
1774
|
+
dict: Narratives explicatives structurées par niveau et fonction
|
|
1775
|
+
"""
|
|
1776
|
+
narratives = {}
|
|
1777
|
+
feature_names = X.columns.tolist() if hasattr(X, 'columns') else [f'feature_{i}' for i in range(X.shape[1])]
|
|
1778
|
+
|
|
1779
|
+
try:
|
|
1780
|
+
# Calcul des importances globales moyennes
|
|
1781
|
+
if isinstance(shap_values, list):
|
|
1782
|
+
# Multi-class
|
|
1783
|
+
global_importance = np.mean([np.abs(sv).mean(axis=0) for sv in shap_values], axis=0)
|
|
1784
|
+
classes = len(shap_values)
|
|
1785
|
+
else:
|
|
1786
|
+
# Binary/Regression
|
|
1787
|
+
global_importance = np.abs(shap_values).mean(axis=0)
|
|
1788
|
+
classes = 1
|
|
1789
|
+
|
|
1790
|
+
# Tri des caractéristiques par importance
|
|
1791
|
+
sorted_idx = np.argsort(-global_importance)
|
|
1792
|
+
top_features = [feature_names[idx] for idx in sorted_idx[:5]] # Top 5 features
|
|
1793
|
+
top_importance = global_importance[sorted_idx[:5]]
|
|
1794
|
+
top_importance_norm = top_importance / top_importance.sum() * 100 # Normalisation en %
|
|
1795
|
+
|
|
1796
|
+
# Génération des narratives selon le niveau d'audience
|
|
1797
|
+
if language == 'fr':
|
|
1798
|
+
if audience_level == AudienceLevel.TECHNICAL:
|
|
1799
|
+
# Version technique détaillée
|
|
1800
|
+
narratives['summary'] = f"L'analyse SHAP a identifié {len(feature_names)} variables explicatives pour ce modèle. "
|
|
1801
|
+
narratives['main_drivers'] = f"Les principaux facteurs contributifs sont {', '.join(top_features[:3])}, "
|
|
1802
|
+
narratives['main_drivers'] += f"avec des importances relatives de {', '.join([f'{v:.1f}%' for v in top_importance_norm[:3]])}."
|
|
1803
|
+
|
|
1804
|
+
# Détails techniques
|
|
1805
|
+
narratives['technical_details'] = f"L'indice de Gini des attributions SHAP est de {self._gini_index(global_importance):.3f}, "
|
|
1806
|
+
narratives['technical_details'] += "indiquant "
|
|
1807
|
+
if self._gini_index(global_importance) > 0.7:
|
|
1808
|
+
narratives['technical_details'] += "une forte concentration des effets sur un petit nombre de variables."
|
|
1809
|
+
elif self._gini_index(global_importance) > 0.4:
|
|
1810
|
+
narratives['technical_details'] += "une distribution moyennement concentrée des effets sur les variables."
|
|
1811
|
+
else:
|
|
1812
|
+
narratives['technical_details'] += "une distribution relativement équilibrée des effets sur l'ensemble des variables."
|
|
1813
|
+
|
|
1814
|
+
# Interactions (si disponible)
|
|
1815
|
+
if hasattr(self, '_last_interaction_values') and self._last_interaction_values is not None:
|
|
1816
|
+
narratives['interactions'] = "Des effets d'interaction significatifs ont été détectés entre certaines variables."
|
|
1817
|
+
|
|
1818
|
+
elif audience_level == AudienceLevel.BUSINESS:
|
|
1819
|
+
# Version intermédiaire orientée métier
|
|
1820
|
+
narratives['summary'] = f"Ce modèle utilise {len(feature_names)} variables pour établir ses prédictions."
|
|
1821
|
+
narratives['main_drivers'] = f"Les 3 facteurs les plus influents sont {', '.join(top_features[:3])}."
|
|
1822
|
+
narratives['business_impact'] = "Ces facteurs représentent "
|
|
1823
|
+
total_pct = sum(top_importance_norm[:3])
|
|
1824
|
+
narratives['business_impact'] += f"ensemble {total_pct:.1f}% de l'impact total sur les décisions du modèle."
|
|
1825
|
+
|
|
1826
|
+
# Recommandation métier
|
|
1827
|
+
if total_pct > 70:
|
|
1828
|
+
narratives['recommendation'] = "Recommandation: Concentrez votre attention sur ces facteurs clés qui dominent la décision."
|
|
1829
|
+
else:
|
|
1830
|
+
narratives['recommendation'] = "Recommandation: Tenez compte de l'ensemble des facteurs qui contribuent de manière significative aux décisions."
|
|
1831
|
+
|
|
1832
|
+
else: # PUBLIC
|
|
1833
|
+
# Version simplifiée grand public
|
|
1834
|
+
narratives['summary'] = "Voici une explication simplifiée des résultats de ce modèle."
|
|
1835
|
+
narratives['main_factors'] = f"Les principaux éléments qui ont influencé ce résultat sont {', '.join(top_features[:3])}."
|
|
1836
|
+
if len(top_features) > 3:
|
|
1837
|
+
narratives['additional_info'] = f"D'autres facteurs comme {', '.join(top_features[3:5])} ont également joué un rôle, mais moins important."
|
|
1838
|
+
else: # English
|
|
1839
|
+
if audience_level == AudienceLevel.TECHNICAL:
|
|
1840
|
+
# Technical detailed version
|
|
1841
|
+
narratives['summary'] = f"SHAP analysis identified {len(feature_names)} explanatory variables for this model. "
|
|
1842
|
+
narratives['main_drivers'] = f"The main contributing factors are {', '.join(top_features[:3])}, "
|
|
1843
|
+
narratives['main_drivers'] += f"with relative importances of {', '.join([f'{v:.1f}%' for v in top_importance_norm[:3]])}."
|
|
1844
|
+
|
|
1845
|
+
# Technical details
|
|
1846
|
+
narratives['technical_details'] = f"The Gini index of SHAP attributions is {self._gini_index(global_importance):.3f}, "
|
|
1847
|
+
narratives['technical_details'] += "indicating "
|
|
1848
|
+
if self._gini_index(global_importance) > 0.7:
|
|
1849
|
+
narratives['technical_details'] += "a high concentration of effects on a small number of variables."
|
|
1850
|
+
elif self._gini_index(global_importance) > 0.4:
|
|
1851
|
+
narratives['technical_details'] += "a moderately concentrated distribution of effects across variables."
|
|
1852
|
+
else:
|
|
1853
|
+
narratives['technical_details'] += "a relatively balanced distribution of effects across all variables."
|
|
1854
|
+
|
|
1855
|
+
# Interactions (if available)
|
|
1856
|
+
if hasattr(self, '_last_interaction_values') and self._last_interaction_values is not None:
|
|
1857
|
+
narratives['interactions'] = "Significant interaction effects were detected between certain variables."
|
|
1858
|
+
|
|
1859
|
+
elif audience_level == AudienceLevel.BUSINESS:
|
|
1860
|
+
# Intermediate business-oriented version
|
|
1861
|
+
narratives['summary'] = f"This model uses {len(feature_names)} variables to make its predictions."
|
|
1862
|
+
narratives['main_drivers'] = f"The 3 most influential factors are {', '.join(top_features[:3])}."
|
|
1863
|
+
narratives['business_impact'] = "These factors together represent "
|
|
1864
|
+
total_pct = sum(top_importance_norm[:3])
|
|
1865
|
+
narratives['business_impact'] += f"{total_pct:.1f}% of the total impact on model decisions."
|
|
1866
|
+
|
|
1867
|
+
# Business recommendation
|
|
1868
|
+
if total_pct > 70:
|
|
1869
|
+
narratives['recommendation'] = "Recommendation: Focus your attention on these key factors that dominate the decision."
|
|
1870
|
+
else:
|
|
1871
|
+
narratives['recommendation'] = "Recommendation: Consider all factors that contribute significantly to decisions."
|
|
1872
|
+
|
|
1873
|
+
else: # PUBLIC
|
|
1874
|
+
# Simplified public version
|
|
1875
|
+
narratives['summary'] = "Here is a simplified explanation of this model's results."
|
|
1876
|
+
narratives['main_factors'] = f"The main elements that influenced this result are {', '.join(top_features[:3])}."
|
|
1877
|
+
if len(top_features) > 3:
|
|
1878
|
+
narratives['additional_info'] = f"Other factors like {', '.join(top_features[3:5])} also played a role, but less important."
|
|
1879
|
+
|
|
1880
|
+
# Ajout d'informations spécifiques à l'instance
|
|
1881
|
+
# Valeurs des features importantes pour cette prédiction spécifique
|
|
1882
|
+
instance_values = {}
|
|
1883
|
+
for i, feature in enumerate(top_features[:3]):
|
|
1884
|
+
idx = feature_names.index(feature)
|
|
1885
|
+
value = X.iloc[0, idx] if hasattr(X, 'iloc') else X[0, idx]
|
|
1886
|
+
instance_values[feature] = value
|
|
1887
|
+
|
|
1888
|
+
if language == 'fr':
|
|
1889
|
+
narratives['instance_specific'] = "Pour cette prédiction spécifique, les valeurs clés sont: "
|
|
1890
|
+
else: # English
|
|
1891
|
+
narratives['instance_specific'] = "For this specific prediction, the key values are: "
|
|
1892
|
+
|
|
1893
|
+
instance_details = [f"{feature}: {value}" for feature, value in instance_values.items()]
|
|
1894
|
+
narratives['instance_specific'] += ", ".join(instance_details)
|
|
1895
|
+
|
|
1896
|
+
except Exception as e:
|
|
1897
|
+
self._logger.warning(f"Erreur lors de la génération des narratives: {str(e)}")
|
|
1898
|
+
if language == 'fr':
|
|
1899
|
+
narratives['error'] = "Impossible de générer une explication narrative complète."
|
|
1900
|
+
else: # English
|
|
1901
|
+
narratives['error'] = "Unable to generate a complete narrative explanation."
|
|
1902
|
+
|
|
1903
|
+
return narratives
|
|
1904
|
+
|
|
1905
|
+
def _gini_index(self, array):
|
|
1906
|
+
"""
|
|
1907
|
+
Calcule l'indice de Gini pour mesurer l'inégalité des attributions.
|
|
1908
|
+
Un Gini proche de 1 indique une forte concentration (attributions inégales),
|
|
1909
|
+
proche de 0 indique des attributions uniformes.
|
|
1910
|
+
"""
|
|
1911
|
+
# Assurer que l'array est 1D et non négatif
|
|
1912
|
+
if array.ndim > 1:
|
|
1913
|
+
array = np.abs(array).mean(axis=0)
|
|
1914
|
+
else:
|
|
1915
|
+
array = np.abs(array)
|
|
1916
|
+
|
|
1917
|
+
# Trier les valeurs
|
|
1918
|
+
array = np.sort(array)
|
|
1919
|
+
n = array.size
|
|
1920
|
+
index = np.arange(1, n + 1)
|
|
1921
|
+
|
|
1922
|
+
# Calculer l'indice de Gini
|
|
1923
|
+
return (np.sum((2 * index - n - 1) * array)) / (n * np.sum(array))
|
|
1924
|
+
|
|
1925
|
+
def _generate_explanation_narrative(self, shap_values, X, audience_level='TECHNICAL', language='fr'):
|
|
1926
|
+
"""
|
|
1927
|
+
Génère des descriptions textuelles des explications SHAP adaptées au niveau d'audience.
|
|
1928
|
+
|
|
1929
|
+
Cette méthode traduit les valeurs SHAP complexes en narratives explicatives claires
|
|
1930
|
+
qui s'adaptent automatiquement au niveau de technicité requis par l'audience.
|
|
1931
|
+
|
|
1932
|
+
Args:
|
|
1933
|
+
shap_values: Les valeurs SHAP calculées
|
|
1934
|
+
X: Données expliquées
|
|
1935
|
+
audience_level: Niveau d'audience ('TECHNICAL', 'BUSINESS', 'PUBLIC')
|
|
1936
|
+
language: Langue des narratives ('fr' ou 'en')
|
|
1937
|
+
|
|
1938
|
+
Returns:
|
|
1939
|
+
dict: Narratives explicatives structurées par niveau et fonction
|
|
1940
|
+
"""
|
|
1941
|
+
narratives = {}
|
|
1942
|
+
feature_names = X.columns.tolist() if hasattr(X, 'columns') else [f'feature_{i}' for i in range(X.shape[1])]
|
|
1943
|
+
|
|
1944
|
+
try:
|
|
1945
|
+
# Calcul des importances globales moyennes
|
|
1946
|
+
if isinstance(shap_values, list):
|
|
1947
|
+
# Multi-class
|
|
1948
|
+
global_importance = np.mean([np.abs(sv).mean(axis=0) for sv in shap_values], axis=0)
|
|
1949
|
+
classes = len(shap_values)
|
|
1950
|
+
else:
|
|
1951
|
+
# Binary/Regression
|
|
1952
|
+
global_importance = np.abs(shap_values).mean(axis=0)
|
|
1953
|
+
classes = 1
|
|
1954
|
+
|
|
1955
|
+
# Tri des caractéristiques par importance
|
|
1956
|
+
sorted_idx = np.argsort(-global_importance)
|
|
1957
|
+
top_features = [feature_names[idx] for idx in sorted_idx[:5]] # Top 5 features
|
|
1958
|
+
top_importance = global_importance[sorted_idx[:5]]
|
|
1959
|
+
top_importance_norm = top_importance / top_importance.sum() * 100 # Normalisation en %
|
|
1960
|
+
|
|
1961
|
+
# Génération des narratives selon le niveau d'audience
|
|
1962
|
+
if language == 'fr':
|
|
1963
|
+
# Français
|
|
1964
|
+
if audience_level == 'TECHNICAL':
|
|
1965
|
+
# Narrative technique détaillée
|
|
1966
|
+
gini_value = self._gini_index(global_importance)
|
|
1967
|
+
concentration_type = "forte concentration" if gini_value > 0.6 else "répartition équilibrée"
|
|
1968
|
+
model_type_str = 'multi-classes' if classes > 1 else 'binaire/régression'
|
|
1969
|
+
summary = (
|
|
1970
|
+
f"L'analyse SHAP a identifié {len(feature_names)} caractéristiques dont les "
|
|
1971
|
+
f"principales sont {', '.join(top_features[:3])} représentant "
|
|
1972
|
+
f"{top_importance_norm[:3].sum():.1f}% "
|
|
1973
|
+
f"Le modèle {model_type_str} "
|
|
1974
|
+
f"présente une distribution d'attributions avec un indice de Gini "
|
|
1975
|
+
f"de {gini_value:.3f}, indiquant une "
|
|
1976
|
+
f"{concentration_type} "
|
|
1977
|
+
f"de l'influence prédictive."
|
|
1978
|
+
)
|
|
1979
|
+
elif audience_level == 'BUSINESS':
|
|
1980
|
+
# Narrative simplifiée pour business
|
|
1981
|
+
summary = (
|
|
1982
|
+
f"Les facteurs principaux influençant les prédictions sont "
|
|
1983
|
+
f"{', '.join(top_features[:3])} avec un impact respectif de "
|
|
1984
|
+
f"{', '.join([f'{v:.1f}%' for v in top_importance_norm[:3]])}. "
|
|
1985
|
+
f"Le modèle s'appuie {'largement sur un petit nombre de facteurs clés' if self._gini_index(global_importance) > 0.6 else 'sur un ensemble diversifié de facteurs'} "
|
|
1986
|
+
f"pour établir ses prédictions."
|
|
1987
|
+
)
|
|
1988
|
+
else: # 'PUBLIC'
|
|
1989
|
+
# Narrative très simplifiée pour le grand public
|
|
1990
|
+
summary = (
|
|
1991
|
+
f"Les éléments les plus importants dans cette décision sont "
|
|
1992
|
+
f"{', '.join(top_features[:3])}, "
|
|
1993
|
+
f"qui représentent ensemble {top_importance_norm[:3].sum():.0f}% "
|
|
1994
|
+
f"de l'influence sur le résultat."
|
|
1995
|
+
)
|
|
1996
|
+
else:
|
|
1997
|
+
# Anglais
|
|
1998
|
+
if audience_level == 'TECHNICAL':
|
|
1999
|
+
gini_value = self._gini_index(global_importance)
|
|
2000
|
+
model_type_str = "multi-class" if classes > 1 else "binary/regression"
|
|
2001
|
+
concentration_str = "high concentration" if gini_value > 0.6 else "balanced distribution"
|
|
2002
|
+
summary = (
|
|
2003
|
+
f"SHAP analysis identified {len(feature_names)} features with "
|
|
2004
|
+
f"the top drivers being {', '.join(top_features[:3])} representing "
|
|
2005
|
+
f"{top_importance_norm[:3].sum():.1f}% of explanatory impact. "
|
|
2006
|
+
f"The {model_type_str} model "
|
|
2007
|
+
f"displays an attribution distribution with Gini index "
|
|
2008
|
+
f"of {gini_value:.3f}, indicating "
|
|
2009
|
+
f"{concentration_str} "
|
|
2010
|
+
f"of predictive influence."
|
|
2011
|
+
)
|
|
2012
|
+
elif audience_level == 'BUSINESS':
|
|
2013
|
+
gini_value = self._gini_index(global_importance)
|
|
2014
|
+
model_strategy = "relies heavily on a small number of key factors" if gini_value > 0.6 else "leverages a diverse set of factors"
|
|
2015
|
+
summary = (
|
|
2016
|
+
f"The main factors influencing predictions are "
|
|
2017
|
+
f"{', '.join(top_features[:3])} with respective impacts of "
|
|
2018
|
+
f"{', '.join([f'{v:.1f}%' for v in top_importance_norm[:3]])}. "
|
|
2019
|
+
f"The model {model_strategy} "
|
|
2020
|
+
f"to establish its predictions."
|
|
2021
|
+
)
|
|
2022
|
+
else: # 'PUBLIC'
|
|
2023
|
+
summary = (
|
|
2024
|
+
f"The most important elements in this decision are "
|
|
2025
|
+
f"{', '.join(top_features[:3])}, "
|
|
2026
|
+
f"which together represent {top_importance_norm[:3].sum():.0f}% "
|
|
2027
|
+
f"of the influence on the outcome."
|
|
2028
|
+
)
|
|
2029
|
+
|
|
2030
|
+
narratives['summary'] = summary
|
|
2031
|
+
|
|
2032
|
+
# Ajouter d'autres narratives spécialisées selon le besoin
|
|
2033
|
+
# Par exemple: analyses contrefactuelles, alertes sur des biais potentiels, etc.
|
|
2034
|
+
|
|
2035
|
+
except Exception as e:
|
|
2036
|
+
self._logger.warning(f"Erreur lors de la génération des narratives: {str(e)}")
|
|
2037
|
+
# Narrative de secours en cas d'échec
|
|
2038
|
+
narratives['summary'] = f"Analyse SHAP effectuée sur {len(feature_names)} caractéristiques."
|
|
2039
|
+
|
|
2040
|
+
return narratives
|
|
2041
|
+
|
|
2042
|
+
def _create_explanation_result(self, shap_values, expected_value, X, metadata, y=None,
|
|
2043
|
+
compliance=None, narrative=None, raw_shap=None, **kwargs):
|
|
2044
|
+
"""
|
|
2045
|
+
Crée un objet ExplanationResult enrichi avec métriques de qualité et conformité.
|
|
2046
|
+
|
|
2047
|
+
Cette implémentation avancée génère un résultat d'explication complet avec données
|
|
2048
|
+
structurées pour tous les aspects de l'explicabilité: attributions, métriques de qualité,
|
|
2049
|
+
conformité réglementaire, narratives adaptées à différentes audiences, et métadonnées
|
|
2050
|
+
exhaustives pour la traçabilité et l'audit.
|
|
2051
|
+
|
|
2052
|
+
Args:
|
|
2053
|
+
shap_values: Valeurs SHAP calculées (par instance et par caractéristique)
|
|
2054
|
+
expected_value: Valeur attendue (baseline) du modèle
|
|
2055
|
+
X: Données d'entrée expliquées
|
|
2056
|
+
metadata: Métadonnées de l'explication et du processus
|
|
2057
|
+
y: Valeurs réelles ou prédites (optionnel)
|
|
2058
|
+
compliance: Résultats de conformité réglementaire (optionnel)
|
|
2059
|
+
narrative: Descriptions textuelles des explications (optionnel)
|
|
2060
|
+
raw_shap: Valeurs SHAP brutes pour analyses avancées (optionnel)
|
|
2061
|
+
**kwargs: Paramètres additionnels
|
|
2062
|
+
feature_names: Noms des caractéristiques
|
|
2063
|
+
class_names: Noms des classes pour classification
|
|
2064
|
+
interaction_values: Valeurs d'interaction entre caractéristiques
|
|
2065
|
+
output_indices: Indices des sorties expliquées
|
|
2066
|
+
|
|
2067
|
+
Returns:
|
|
2068
|
+
ExplanationResult: Résultat complet et enrichi de l'explication
|
|
2069
|
+
"""
|
|
2070
|
+
# Détermine les noms des caractéristiques
|
|
2071
|
+
feature_names = kwargs.get('feature_names')
|
|
2072
|
+
if feature_names is None:
|
|
2073
|
+
if hasattr(X, 'columns'):
|
|
2074
|
+
feature_names = X.columns.tolist()
|
|
2075
|
+
else:
|
|
2076
|
+
feature_names = [f"feature_{i}" for i in range(X.shape[1])]
|
|
2077
|
+
|
|
2078
|
+
# Détermine les noms des classes
|
|
2079
|
+
class_names = kwargs.get('class_names')
|
|
2080
|
+
if class_names is None:
|
|
2081
|
+
if isinstance(shap_values, list):
|
|
2082
|
+
class_names = [f"class_{i}" for i in range(len(shap_values))]
|
|
2083
|
+
|
|
2084
|
+
# Calcul de l'importance globale des caractéristiques
|
|
2085
|
+
if isinstance(shap_values, list):
|
|
2086
|
+
# Multi-class
|
|
2087
|
+
importance_values = np.mean([np.abs(sv).mean(axis=0) for sv in shap_values], axis=0)
|
|
2088
|
+
else:
|
|
2089
|
+
# Binary/Regression
|
|
2090
|
+
importance_values = np.abs(shap_values).mean(axis=0)
|
|
2091
|
+
|
|
2092
|
+
# Création des objets FeatureImportance
|
|
2093
|
+
feature_importances = []
|
|
2094
|
+
for i, (name, value) in enumerate(zip(feature_names, importance_values)):
|
|
2095
|
+
feature_importances.append(FeatureImportance(
|
|
2096
|
+
feature_name=name,
|
|
2097
|
+
importance_value=float(value),
|
|
2098
|
+
importance_rank=i + 1,
|
|
2099
|
+
direction="positive" if value >= 0 else "negative"
|
|
2100
|
+
))
|
|
2101
|
+
|
|
2102
|
+
# Tri par importance décroissante
|
|
2103
|
+
feature_importances.sort(key=lambda x: x.importance_value, reverse=True)
|
|
2104
|
+
|
|
2105
|
+
# Prépare les métriques de qualité
|
|
2106
|
+
quality_metrics = metadata.get('quality_metrics', {})
|
|
2107
|
+
explanation_quality = None
|
|
2108
|
+
if quality_metrics:
|
|
2109
|
+
explanation_quality = ExplanationQuality(
|
|
2110
|
+
fidelity=quality_metrics.get('fidelity_correlation', None),
|
|
2111
|
+
stability=1.0 - min(1.0, quality_metrics.get('stability_variance', 0)),
|
|
2112
|
+
sparsity=quality_metrics.get('feature_sparsity', None),
|
|
2113
|
+
consistency=None # À implémenter si disponible
|
|
2114
|
+
)
|
|
2115
|
+
|
|
2116
|
+
# Création de l'objet ExplanationResult
|
|
2117
|
+
result = ExplanationResult(
|
|
2118
|
+
explanation_method=ExplainabilityMethod.SHAP,
|
|
2119
|
+
feature_importances=feature_importances,
|
|
2120
|
+
instance_explanations=self._create_instance_explanations(shap_values, X, expected_value),
|
|
2121
|
+
global_explanation={
|
|
2122
|
+
'expected_value': expected_value,
|
|
2123
|
+
'feature_importance_mean': dict(zip(feature_names, importance_values.tolist())),
|
|
2124
|
+
'feature_importance_std': dict(zip(feature_names, np.std([np.abs(sv) for sv in shap_values], axis=0).tolist()))
|
|
2125
|
+
if isinstance(shap_values, list) else dict(zip(feature_names, np.std(np.abs(shap_values), axis=0).tolist()))
|
|
2126
|
+
},
|
|
2127
|
+
metadata=metadata,
|
|
2128
|
+
quality=explanation_quality,
|
|
2129
|
+
target_values=y.tolist() if y is not None and hasattr(y, 'tolist') else y,
|
|
2130
|
+
raw_data={
|
|
2131
|
+
'raw_shap_values': raw_shap,
|
|
2132
|
+
'compliance_results': compliance,
|
|
2133
|
+
'narrative': narrative
|
|
2134
|
+
} if raw_shap is not None or compliance is not None or narrative is not None else None,
|
|
2135
|
+
visualizations=None # Les visualisations seront générées séparément
|
|
2136
|
+
)
|
|
2137
|
+
|
|
2138
|
+
return result
|
|
2139
|
+
|
|
2140
|
+
def _create_instance_explanations(self, shap_values, X, expected_value):
|
|
2141
|
+
"""
|
|
2142
|
+
Crée des explications détaillées pour chaque instance expliquée.
|
|
2143
|
+
|
|
2144
|
+
Args:
|
|
2145
|
+
shap_values: Valeurs SHAP calculées
|
|
2146
|
+
X: Données d'entrée
|
|
2147
|
+
expected_value: Valeur de base (expected value)
|
|
2148
|
+
|
|
2149
|
+
Returns:
|
|
2150
|
+
list: Liste des explications par instance
|
|
2151
|
+
"""
|
|
2152
|
+
instance_explanations = []
|
|
2153
|
+
feature_names = X.columns.tolist() if hasattr(X, 'columns') else [f"feature_{i}" for i in range(X.shape[1])]
|
|
2154
|
+
|
|
2155
|
+
# Traitement selon le type de sortie (classification ou régression)
|
|
2156
|
+
if isinstance(shap_values, list):
|
|
2157
|
+
# Classification multi-classes
|
|
2158
|
+
for instance_idx in range(shap_values[0].shape[0]):
|
|
2159
|
+
instance_exp = {}
|
|
2160
|
+
for class_idx, sv in enumerate(shap_values):
|
|
2161
|
+
class_exp = {
|
|
2162
|
+
'base_value': float(expected_value[class_idx]) if isinstance(expected_value, (list, np.ndarray)) else float(expected_value),
|
|
2163
|
+
'output_value': float(expected_value[class_idx] + sv[instance_idx].sum())
|
|
2164
|
+
if isinstance(expected_value, (list, np.ndarray)) else float(expected_value + sv[instance_idx].sum()),
|
|
2165
|
+
'features': {}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
# Détails des contributions par caractéristique
|
|
2169
|
+
for j, fname in enumerate(feature_names):
|
|
2170
|
+
class_exp['features'][fname] = {
|
|
2171
|
+
'contribution': float(sv[instance_idx, j]),
|
|
2172
|
+
'value': float(X.iloc[instance_idx, j]) if hasattr(X, 'iloc') else float(X[instance_idx, j])
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
instance_exp[f"class_{class_idx}"] = class_exp
|
|
2176
|
+
instance_explanations.append(instance_exp)
|
|
2177
|
+
else:
|
|
2178
|
+
# Régression ou classification binaire
|
|
2179
|
+
for instance_idx in range(shap_values.shape[0]):
|
|
2180
|
+
instance_exp = {
|
|
2181
|
+
'base_value': float(expected_value) if isinstance(expected_value, (int, float)) else float(expected_value[0]),
|
|
2182
|
+
'output_value': float(expected_value + shap_values[instance_idx].sum())
|
|
2183
|
+
if isinstance(expected_value, (int, float)) else float(expected_value[0] + shap_values[instance_idx].sum()),
|
|
2184
|
+
'features': {}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
# Détails des contributions par caractéristique
|
|
2188
|
+
for j, fname in enumerate(feature_names):
|
|
2189
|
+
instance_exp['features'][fname] = {
|
|
2190
|
+
'contribution': float(shap_values[instance_idx, j]),
|
|
2191
|
+
'value': float(X.iloc[instance_idx, j]) if hasattr(X, 'iloc') else float(X[instance_idx, j])
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
instance_explanations.append(instance_exp)
|
|
2195
|
+
|
|
2196
|
+
return instance_explanations
|
|
2197
|
+
|
|
2198
|
+
def _extract_metadata(self):
|
|
2199
|
+
"""
|
|
2200
|
+
Extrait les métadonnées de l'explainer et du modèle.
|
|
2201
|
+
|
|
2202
|
+
Returns:
|
|
2203
|
+
dict: Métadonnées structurées
|
|
2204
|
+
"""
|
|
2205
|
+
model_type = self._detect_model_type()
|
|
2206
|
+
framework = model_type.split('-')[0] if '-' in model_type else model_type
|
|
2207
|
+
|
|
2208
|
+
# Déterminer le type de modèle (classification ou régression)
|
|
2209
|
+
is_classifier = False
|
|
2210
|
+
if hasattr(self._model, 'predict_proba'):
|
|
2211
|
+
is_classifier = True
|
|
2212
|
+
elif hasattr(self._model, '_estimator_type') and self._model._estimator_type == 'classifier':
|
|
2213
|
+
is_classifier = True
|
|
2214
|
+
elif model_type in ['tensorflow', 'pytorch']:
|
|
2215
|
+
# Pour les modèles deep learning, vérifier la forme de sortie
|
|
2216
|
+
try:
|
|
2217
|
+
# Utiliser les données d'arrière-plan pour une inférence
|
|
2218
|
+
bg_data = self._get_background_data()
|
|
2219
|
+
sample = bg_data[0:1]
|
|
2220
|
+
preds = self._model_predict_wrapper(sample)
|
|
2221
|
+
|
|
2222
|
+
# Si la sortie a plusieurs dimensions et la dernière dimension > 1, c'est probablement une classification
|
|
2223
|
+
if preds.ndim > 1 and preds.shape[-1] > 1:
|
|
2224
|
+
is_classifier = True
|
|
2225
|
+
except:
|
|
2226
|
+
pass
|
|
2227
|
+
|
|
2228
|
+
# Créer les métadonnées
|
|
2229
|
+
self._metadata = ModelMetadata(
|
|
2230
|
+
model_type="classification" if is_classifier else "regression",
|
|
2231
|
+
framework=framework,
|
|
2232
|
+
input_shape=self._infer_model_input_shape(),
|
|
2233
|
+
output_shape=None, # À compléter si nécessaire
|
|
2234
|
+
feature_names=getattr(self, '_feature_names', None),
|
|
2235
|
+
target_names=None, # À compléter si disponible
|
|
2236
|
+
model_params={},
|
|
2237
|
+
model_version="1.0.0"
|
|
2238
|
+
)
|