はじめに
魔瞳の大鷲寮に入りました! nikkieです。
Hugging Face社が開発する、機械学習評価指標のライブラリについて、なぜ非推奨のインストール方法を案内するエラーメッセージが出されるのか調べました。
目次
- はじめに
- 目次
- 🤗 evaluate 「you need to install dependencies['scikit-learn'] using 'pip install sklearn'」
- pip install sklearnという案内による混乱と、対応の過程
- 実装を覗く
- 終わりに
🤗 evaluate 「you need to install dependencies['scikit-learn'] using 'pip install sklearn'」
- Python 3.11.8
- evaluate 0.4.3
Accuracyを算出してみましょう。
>>> import evaluate >>> metric = evaluate.load("accuracy") Downloading builder script: 100%|██████████| 4.20k/4.20k [00:00<00:00, 7.01MB/s] Traceback (most recent call last): File "/.../.venv/lib/python3.11/site-packages/evaluate/loading.py", line 265, in _download_additional_modules raise ImportError( ImportError: To be able to use evaluate-metric/accuracy, you need to install the following dependencies['scikit-learn'] using 'pip install sklearn' for instance'
エラーメッセージによると
- evaluateでaccuracyを算出するには、scikit-learnが必要1
- 「
pip install sklearn」と案内
しかしながら、scikit-learnのインストールは pip install scikit-learn です。
昔はpip install sklearnでもインストールできましたが、今は非推奨です。
ではなぜ、evaluateは「pip install sklearn」と案内するのでしょうか?
pip install sklearnという案内による混乱と、対応の過程
執筆時点で、evaluateはscikit-learnが必要なときにpip install sklearnと案内するために、混乱があるように見受けられます。
sklearnと表示される箇所は、かつては2箇所あったようです
- 昔は
dependencies['sklearn']だったが、今はdependencies['scikit-learn']に修正された - 昔も今も
pip install sklearnと案内される2(pip install scikit-learnにすべき)
dependencies['sklearn'] -> dependencies['scikit-learn']の修正は、こちらのプルリクのマージによると思われます。
放置気味だったのでしょう3、同様のプルリク(現在はコンフリクト中なものも含む)も見つけました
- Fix incorrect error instructing to "pip install sklearn" by dongreenberg · Pull Request #436 · huggingface/evaluate · GitHub
- fix import error message for sklearn by bingwork · Pull Request #608 · huggingface/evaluate · GitHub
- Fix: message for lack of sklearn by ArkiZh · Pull Request #614 · huggingface/evaluate · GitHub
実装を覗く
エラーメッセージ送出箇所
scikit-learnのインストールを求めるエラーメッセージ送出の実装はこちら(_download_additional_modules()関数)。
https://github.com/huggingface/evaluate/blob/v0.4.3/src/evaluate/loading.py#L256-L269
# Check library imports needs_to_be_installed = set() for library_import_name, library_import_path in library_imports: try: lib = importlib.import_module(library_import_name) # noqa F841 except ImportError: library_import_name = "scikit-learn" if library_import_name == "sklearn" else library_import_name needs_to_be_installed.add((library_import_name, library_import_path)) if needs_to_be_installed: raise ImportError( f"To be able to use {name}, you need to install the following dependencies" f"{[lib_name for lib_name, lib_path in needs_to_be_installed]} using 'pip install " f"{' '.join([lib_path for lib_name, lib_path in needs_to_be_installed])}' for instance'" )
needs_to_be_installed(set)に要素があるとき、例外(ImportError)が送出されます。
エラーメッセージのf-stringのうち
nameはこの関数(_download_additional_modules())の引数- 実際のエラーメッセージ(
evaluate-metric/accuracy)から、metricのnameと思われます
- 実際のエラーメッセージ(
- dependenciesに続く部分はリスト
pip installの部分はリストの要素をjoinした文字列
です。
このリストですが、内包表記で作られていて、大元はneeds_to_be_installedですね。
needs_to_be_installedには要素2のタプルが入っています
needs_to_be_installed: set[tuple[str, str]] = set()
例えばsklearnがimportできない場合4、集合needs_to_be_installedには("scikit-learn", "sklearn")が追加されます。
このためエラーメッセージは
you need to install the following dependencies['scikit-learn'] using 'pip install sklearn' for instance'
となります(余談ですが、メッセージ末尾に不要なシングルクォートがあるのですね)
ですが、案内すべきはpip install scikit-learnですから、
you need to install the following dependencies['scikit-learn'] using 'pip install scikit-learn' for instance'
としたく、try ... except ... のexceptの条件式5を使っているところは
library_import_name = "scikit-learn" if library_import_name == "sklearn" else library_import_name
+library_import_path = "scikit-learn" if library_import_name == "sklearn" else library_import_path
のような修正が必要そうです6。
sklearnという文字列はどこから来るのか
上記のコードのlibrary_importsの要素が("sklearn", "sklearn")となっている理由も見ていきます。
_download_additional_modules()関数 https://github.com/huggingface/evaluate/blob/v0.4.3/src/evaluate/loading.py#L212-L214
def _download_additional_modules( name: str, base_path: str, imports: Tuple[str, str, str, str], download_config: Optional[DownloadConfig] ) -> List[Tuple[str, str]]: # library_importsに関係する箇所のみ抜き出し library_imports = [] for import_type, import_name, import_path, sub_directory in imports: if import_type == "library": library_imports.append((import_name, import_path)) # Import from a library continue # 省略 # Check library imports (*すでに見た箇所*)
関数_download_additional_modules()の引数importsのうち、import_typeが"library"の要素をlibrary_importsとしています。
では、importsにはどのような値が渡るのでしょうか。
エラーメッセージを参考にすると呼び出しはこちら。
https://github.com/huggingface/evaluate/blob/v0.4.3/src/evaluate/loading.py#L489
class HubEvaluationModuleFactory(_EvaluationModuleFactory): def get_module(self) -> ImportableModule: # 省略 # get script and other files try: local_path = self.download_loading_script(revision) except FileNotFoundError as err: # 省略 imports = get_imports(local_path) local_imports = _download_additional_modules( name=self.name, base_path=hf_hub_url(path=self.name, name="", revision=revision), imports=imports, download_config=self.download_config, ) # 省略
メソッド名とデバッガで見た変数の値から判断すると、evaluate.load("accuracy")したときに、metricを実装したPythonファイルをダウンロードするようです(「Downloading builder script」出力)。
https://github.com/huggingface/evaluate/blob/v0.4.3/metrics/accuracy/accuracy.py と思しきファイルがローカルPCにキャッシュされていました。
関数get_imports()は、metricの実装から正規表現でimport ...やfrom ... import ...の行を取り出しています。
https://github.com/huggingface/evaluate/blob/v0.4.3/src/evaluate/loading.py#L139
相対importの考慮や
The import starts with a '.', we will download the relevant file
evaluate独自仕様と思われる「From:」で始めるコメントの処理がありました。
The import has a comment with 'From:', we'll retrieve it from the given url
scikit-learnの場合はこの分岐を通ると思われます
https://github.com/huggingface/evaluate/blob/v0.4.3/src/evaluate/loading.py#L207
imports.append(("library", match.group(2), match.group(2), None)) # get_imports() は imports を返します
accuracyの実装にはscikit-learnを使った行がありますから、
from sklearn.metrics import accuracy_score
match.group(2)として from ... import ...の(先頭の.を除いて)最初の.まで(すなわち"sklearn")。
よって、get_imports()の返り値に ("library", "sklearn", "sklearn", None) と含まれます。
scikit-learnというPyPIの登録名ではなく、コード中での名前(sklearn)となるわけですね。
すでに見た_download_additional_modules()関数では、importしてみてできなければImportErrorを送出するという実装ですから、エラーメッセージでだけscikit-learnという名前に変える必要があるわけですね
終わりに
🤗 evaluateで見かけた pip install sklearn という非推奨の案内がどこから来るかを理解しました。
evaluate.load("accuracy")と呼び出したとき、accuracyの実装のPythonファイルをダウンロードしている- accuracyの実装のPythonファイルから正規表現で依存ライブラリを取り出す
sklearn表記
- importしてみてできない場合は
ImportErrorを送出- エラーメッセージにあたり
sklearnをscikit-learn表記に変えようとしているが、実装が不十分
- エラーメッセージにあたり
エラーメッセージの表示をどう直すのがよさそうか、私の中では整理できました。
いっちょプルリク出してきます💪
と思ったのですが、このプルリクをマージできるように動くだけでよさそうということに気づきました。ありがたや〜
- scikit-learnに依存する実装です。 https://github.com/huggingface/evaluate/blob/v0.4.3/metrics/accuracy/accuracy.py#L17↩
- 重複気味ですが、別のissueも立っています ↩
- 放置気味だったためにプルリクが重複した私の経験から ↩
- importlib.import_module でモジュールをimportしています↩
- 条件式の参考 ↩
- もし Fix: message for lack of sklearn by ArkiZh · Pull Request #614 · huggingface/evaluate · GitHub が代わりにマージされていたら、「you need to install the following dependencies['sklearn'] using 'pip install scikit-learn' for instance'」と表示されていて、現状よりも混乱が少なかったと思われます↩