1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[翻訳] MLflow Models From Codeガイド

Posted at

Models From Code Guideの翻訳です。

本書は著者が手動で翻訳したものであり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。

Models From Codeガイド

注意
Models from CodeはMLflow 2.12.2以降で利用できます。この機能をサポートするより以前のバージョンを使っている場合には、Custom Python Modelドキュメントで説明されているレガシーなシリアライゼーションの手法を使う必要があります。

Models from codeはLangChainLlamaIndex、カスタムのpyfunc(PythonModelsのインスタンス)モデルでのみ利用できます。他のライブラリを直接使用している場合には、特定のモデルフレーバーで提供されている保存と記録の機能を使うことをお勧めします。

Models from code機能は、シリアライズされたモデルの重みに依存しないカスタムモデルや(LangChainLlamaIndexのような)特定のフレーバー実装の両方に対する定義、格納、ロードのプロセスを包括的に刷新します。

これらのモデルにおけるレガシーなシリアライゼーションと、Models from Codeアプローチの主な違いは、シリアライゼーションの過程でどのようにモデルが表現されるかです。

レガシーなアプローチでは、cloudpickle(カスタムのpyfuncとLangChain)や、背後のパッケージにおける全ての機能に対する網羅性が不完全なカスタムのシリアライザ(LlamaIndexの場合)のどちらかを用いてモデルオブジェクトに対してシリアライゼーションが行われます。カスタムpyfuncでは、オブジェクトインスタンスのシリアライズにcloudpickleを用いることで、ロードの際にオブジェクトを再構成するために使用されるバイナリーファイルが作成されます。

Models From Codeでは、サポートされるモデルタイプにおいては、カスタムpyfuncやフレーバーのインタフェース(LangChainの場合、スクリプト内に直接LCELチェーンを定義、マークすることができます)の定義とともにシンプルなスクリプトが保存されます。

カスタムpyfuncやサポートされるライブラリ実装でModels From Codeを用いることで得られる最大のメリットは、実装に取り組んでいる際に起こりうる、繰り返しのトライアンドエラーのデバッグの削減です。以下に示すワークフローでは、カスタムモデルのソリューションに取り組んでいる際の2つの手法を比較しています:

レガシーなシリアライゼーションとの違い

カスタムモデルに対するレガシーなモードでは、mlflow.pyfunc.PythonModelのサブスクラスのインスタンスをlog_modelの呼び出しで送信します。オブジェクトのリファレンスを通じて呼び出されると、MLflowはあなたのオブジェクトをシリアライズしようとするために、cloudpickleを活用します。

LangChainのネイティブフレーバーのシリアライゼーションでは、オブジェクトリファレンスを格納するためにcloudpickleが使用されます。しかし、外部の状態参照やAPIでのラムダ関数の使用のため、LangChainで使用されるすべてのオブジェクトタイプのサブセットのみがシリアライゼーションで利用できます。一方、LlamaIndexでは、ライブラリにおけるエッジケースの機能をサポートするための高度に複雑な実装のため、ライブラリのすべてのかばできないフレーバーのネイティブ実装でカスタムのシリアライザを活用します。

Models From Codeでは、あなたのカスタムモデルのインスタンスに対するオブジェクト参照を渡すのではなく、あなたのモデル定義を含むスプリプとへのパスのリファレンスをシンプルに渡すだけです。このモードが使用されると、MLflowは実行環境でこのスクリプトを(メインスクリプトの実行前にすべてのcode_pathの依存関係とともに)シンプルに実行し、mlflow.models.set_model()に対する呼び出しで定義するオブジェクトが何であろうとそれをインスタンス化し、推論ターゲットとしてそのオブジェクトを割り当てます。

このプロセスではpicklecloudpickleのようなシリアライゼーションへの依存がないため、以下のようなこれらのシリアライゼーションパッケージのさまざまな制限を取り除きます:

  • 可搬性と互換性: オブジェクトをシリアライズした際に用いたPythonバージョンと異なるバージョンでpickleやcloudpickleファイルをロードした際に互換性が保証されません。
  • 複雑なオブジェクトのシリアライゼーション: ファイルハンドル、ソケット、外部接続、動的参照、ラムダ関数、システムリソースをpickleすることはできません。
  • 可読性: PickleとCloudPickleの両方は、人間では読めないバイナリフォーマットでシリアライズしたオブジェクトを格納します。
  • パフォーマンス: オブジェクトのシリアライゼーションと依存関係の調査は、特に多くのコード参照の依存関係を持つ複雑な実装においては、非常に遅くなることがあります。

Models From Codeを使う際の主な要件

Models From Code機能を使用する際に、注意すべき重要なコンセプトがあり、すぐには理解できないかもしれないスクリプト経由でモデルを記録する際に実行するオペレーションが存在します。

  • インポート: Models From Codeはレガシーのcloudpickle実装と同じように、非pipのインストール可能なパッケージに対する外部参照をキャプチャしません。外部参照(以下の例をご覧ください)がある場合には、code_paths引数を通じてそれらの依存関係を定義しなくてはなりません。
  • ロギングの際の実行: ロギングするスクリプトファイルが適切であることを検証するために、他のモデルロギングのメソッドと全く同じように、ディスクに書き込まれる前にコードが実行されます。
  • 要件の参照: 定義したモデルスクリプトに基づいてインポートされるパッケージは、モデル実行ロジックで使われているかどうかに関係なく、PyPIからインストールできる場合にはrequirementsとして推定されます。

ティップ
あなたのスクリプトで決して使われないimport文が定義されていた場合、それらは依然としてrequirementsの一覧に含まれます。不適切なパッケージの依存関係を持ち込まないように、実装を定義している際に不要なimport文を特定できることができるリンターを使うことをお勧めします。

警告
Models From Codeをロギングする際、コードにAPIキー、パスワード、その他の認証情報のような機微情報が含まれないことを確認してください。コードはMlflowモデルアーティファクトに平文で保存され、アーティファクトにアクセスできる人はコードを参照できてしまいます。

Jupyter NotebookでModels From Codeを使う

Jupyter(IPython Notebooks)は、一般的に言ってAIアプリケーションやモデリングに取り組む際には非常に便利な手段となります。これのちょっとした制限は、そのセルベースの実行モデルにあります。ノートブックで定義、実行する方法のため、Models From Code機能はモデルとしてノートブックを定義することを直接サポートしていません。むしろ、この機能では、モデルはPythonスクリプト(ファイルの拡張子は .pyで終わる必要があります )として定義する必要があります。

幸運なことに、Jupyterが使用するコアカーネル(IPython)をメンテナンスしている人々は、AI実践者のための開発環境としてノートブックの使いやすさをエンハンスするために、ノートブック内で使用できる数多くのマジックコマンドを開発しました。IPythonをベースとしたすべてのノートブック環境(Jupyter, Databricks Notebooks, etc.)で活用できる最も有用なマジックコマンドの一つが、%%writefileコマンドです。

%%writefileマジックコマンドがノートブックの最初の行に記述されると、マジックコマンドを除くセルのコンテンツ(ノートブック全体ではありません、現在のセルスコープのみです)を指定したファイルに書き込みます。

例えば、ノートブックで以下を実行します:

%%writefile "./hello.py"

print("hello!")

これによって、ノートブックと同じディレクトリに以下を含むファイルが作成されます。

print("hello!")

%%writefileマジックコマンドで使用できるオプションの-aの追記(append)コマンドがあります。このオプションは、セルのコンテンツに保存するファイルにセルのコンテンツを追記します。あなたのモデルの定義ロジックの複数のコピーを含むスクリプトでのデバッグ困難な状況を生み出してしま可能性があるので、このオプションを使うことはお勧めしません。あなたのセルのコンテンツが常に保存されるスクリプトファイルに反映される状態を確保するために、セルを実行する都度、ローカルファイルを上書きする%%writefileのデフォルトの動作を使うことをお勧めします。

Models From Codeの例

これらの例のそれぞれでは、単一のノートブック内からモデルのコードやその他の依存関係の定義をシミュレートするために、スクルプとの定義セルブロックの最初での%%writefileの使い方を説明しています。IDEやテキストエディタで実装を行なっている際には、スクリプトの上部にこのマジックコマンドを書かないでください。

シンプルなModels From Codeモデルの構築

この例では、predict()で呼び出された際に2の冪乗として入力値を使用する非常に基本的なモデルを定義します。個別のノートブックセルとして表現される最初のコードブロックは、ノートブックと同じディレクトリにbasic.pyというファイルを作成します。このファイルのコンテンツはモデルの定義BasicModelとインポート文、推論で使用されるこのモデルのインスタンスを作成するMLflow関数set_modelです。

# If running in a Jupyter or Databricks notebook cell, uncomment the following line:
# %%writefile "./basic.py"

import pandas as pd
from typing import List, Dict
from mlflow.pyfunc import PythonModel
from mlflow.models import set_model


class BasicModel(PythonModel):
    def exponential(self, numbers):
        return {f"{x}": 2**x for x in numbers}

    def predict(self, context, model_input) -> Dict[str, float]:
        if isinstance(model_input, pd.DataFrame):
            model_input = model_input.to_dict()[0].values()
        return self.exponential(model_input)


# Specify which definition in this script represents the model instance
set_model(BasicModel())

次のセクションは、ロギングのロジックを含む別のセルとなります。

import mlflow

mlflow.set_experiment("Basic Model From Code")

model_path = "basic.py"

with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        python_model=model_path,  # Define the model as the path to the script that was just saved
        artifact_path="arithemtic_model",
        input_example=[42.0, 24.0],
    )

MLflow UIで格納されたモデルを参照すると、ランにアーティファクトとして最初のセルのスクリプトを確認することができます。

mlflow.pyfunc.load_model()を通じてこのモデルをロードすると、このスクリプトが実行され、BasicModelのインスタンスが構築され、カスタムモデルのロギングに対するレガシーモデルの代替として、推論のエントリーポイントとしてのpredictメソッドを公開します。

my_model = mlflow.pyfunc.load_model(model_info.model_uri)
my_model.predict([2.2, 3.1, 4.7])

# or, with a Pandas DataFrame input
my_model.predict(pd.DataFrame([5.0, 6.0, 7.0]))

code_paths依存関係によるModels from Codeの使用

この例では、複数のPythonスクリプトの取り扱い方とMLflowのモデル管理におけるcode_pathsの活用方法を説明するより複雑なシナリオを探索します。特に、基本的な計算オペレーションを行う関数を含むシンプルなスクリプトを定義し、別のスクリプトで定義するAddModelのカスタムPythonModelでこの関数を使用します。このモデルはMLflowによって記録されるので、格納されたモデルを用いて予測を行うことができます。

MLflowのcode_paths機能の詳細を学ぶには、こちらの使用法に関するガイドをご覧ください。

このチュートリアルでは以下の方法を説明します:

  • Jupyterノートブックで複数のPythonファイルを作成する。
  • 他のファイルで定義されている外部コードに依存するカスタムモデルをMLflowで記録する。
  • モデルを記録する際に追加のスクリプトを含めるためにcode_paths機能を使い、推論のためにモデルがロードされた際にすべての依存関係を利用できるようにする。

依存コードスクリプトの定義

最初のステップでは、ノートブックで実行する際にはマジックコマンド%%writefileを含むcalculator.pyというファイルでadd関数を定義します:

# If running in a Jupyter or Databricks notebook cell, uncomment the following line:
# %%writefile "./calculator.py"


def add(x, y):
    return x + y

Pythonファイルとしてモデルを定義

次に、AddModelクラスを含む新規ファイルmath_model.pyを作成します。このスクリプトは、外部スクリプトからのadd関数のインポート、モデルの定義、予測の実行、入力データ型の検証に責任を持ちます。predictメソッドは、入力として指定された2つの数値の加算を実行するために、add関数を活用します。

以下のコードブロックは、math_model.pyAddModelクラス定義を書き込みます:

# If running in a Jupyter or Databricks notebook cell, uncomment the following line:
# %%writefile "./math_model.py"

from mlflow.pyfunc import PythonModel
from mlflow.models import set_model

from calculator import add


class AddModel(PythonModel):
    def predict(self, context, model_input, params=None):
        return add(model_input["x"], model_input["y"])


set_model(AddModel())

このモデルは、入力の存在や型をチェックすることでエラーハンドリングを導入することで堅牢性を高めます。これは、どのようにしてカスタムロジックをMLflowモデルでカプセル化しつつも、外部の依存関係を活用できるのかに関する実践的な例として機能します。

Model from Codeのロギング

AddModelカスタムPythonモデルを定義したら、MLflowでのロギングに進むことができます。このプロセスには、math_model.pyスクリプトのパスの指定と、依存関係としてcalculator.pyを含めるためのcode_pathsパラメーターの使用が含まれます。これによって、別の環境やマシンにモデルがロードされた際に、適切に実行されるようにすべての必要なコードファイルが利用できるようにします。

以下のコードブロックは、MLflowを用いたロギングの方法を示しています。

import mlflow

mlflow.set_experiment("Arithemtic Model From Code")

model_path = "math_model.py"

with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        python_model=model_path,  # The model is defined as the path to the script containing the model definition
        artifact_path="arithemtic_model",
        code_paths=[
            "calculator.py"
        ],  # dependency definition included for the model to successfully import the implementation
    )

このステップは、MLflowでAddModelを記録し、主体のモデルスクリプトとその依存関係の両方がアーティファクトとして格納されるようにします。code_paths引数にcalculator.pyを含めることで、どの環境にデプロイされたとしても信頼性を持ってモデルをロードし、予測に使えるようにしています。

モデルのロードと参照

モデルをロギングしたら、ノートブックやMLflowトラッキングサーバーにアクセスできる他の環境にロードし直すことができます。モデルがロードされると、calculator.pyスクリプトはmath_model.pyと一緒に実行されるので、ArithmeticModelスクリプトのimport文によってadd関数が使えるようになります。

以下のコードブロックは、モデルのロードと予測の方法を示しています。

my_model_from_code = mlflow.pyfunc.load_model(model_info.model_uri)
my_model_from_code.predict({"x": 42, "y": 9001})

この例では、異なる数値入力の取り扱い、加算の実行、計算履歴を保持するためのモデルの能力を説明しています。これらの予測結果には、モデルの計算結果に対する監査や追跡で使用できる数値計算オペレーションの結果と履歴のログが含まれています。

MLflow UIで格納されたモデルを参照すると、ランのアーティファクトとして記録されたmath_model.pycalculator.pyを確認することができます。この包括的なロギングによって、モデルのパラメーターやメトリクスだけでなく、挙動を定義するコードも追跡できるので、UIから直接参照、デバッグすることができます。

MLflowのLangChain Models from Codeネイティブサポート

この若干高度な例では、AIモデルのオペレーションのチェーンを定義、管理するためにMLflow LangChainインテグレーションの使い方を探索します。このチェーンは、特定の地域、領域に基づく入力に基づいた景観デザインの提案を生成する助けとなります。この例では、カスタムプロンプトの定義、レスポンスを生成するための大規模言語モデル(LLM)の活用、MLflowのトラッキング機能を用いてモデルとして環境全体をk録する方法を説明します。

このチュートリアルでは以下を行います:

  • 景観デザインの提案を生成するために入力データを処理するカスタムLangChainモデルを定義するスクリプトの記述。
  • langchainインテグレーションとMLflowによるモデルのロギングによって、オペレーションチェーン全体が捕捉されるようにする。
  • 別のコンテキストで、モデルをロード、使用して予測を行う。

LCELによるモデルの定義

はじめに、景観デザインの提案を生成するオペレーションチェーンを定義するmfc.pyというPythonスクリプトを作成します。このスクリプトは、LangChainライブラリとトレースのキャプチャを有効化するためにMLflowのautolog機能を活用します。

このスクリプトでは以下が含まれます:

  • カスタム関数 (get_regionとget_area): これらの関数は入力データから特定の情報のピース(地域とエリア)を抽出します。
  • プロンプトテンプレート: モデルが操作するタスクとコンテキストを指定することで言語モデルへの入力を構造化すするためにPromptTemplateを定義します。
  • モデル定義: 構造化されたプロンプトを用いてレスポンスを生成するためにChatOpenAIモデルを使います。
  • チェーンの作成: 入力の処理、プロンプトテンプレート、モデルの呼び出し、出力の解析のステップを接続することでチェーンを作成します。

以下のコードブロックはmfc.pyファイルにチェーンの定義を書き込みます:

# If running in a Jupyter or Databricks notebook cell, uncomment the following line:
# %%writefile "./mfc.py"

import os
from operator import itemgetter

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_openai import ChatOpenAI

import mlflow


def get_region(input_data):
    default = "Virginia, USA"
    if isinstance(input_data[0], dict):
        return input_data[0].get("content").get("region", default)
    return default


def get_area(input_data):
    default = "5000 square feet"
    if isinstance(input_data[0], dict):
        return input_data[0].get("content").get("area", default)
    return default


prompt = PromptTemplate(
    template="You are a highly accomplished landscape designer that provides suggestions for landscape design decisions in a particular"
    " geographic region. Your goal is to suggest low-maintenance hardscape and landscape options that involve the use of materials and"
    " plants that are native to the region mentioned. As part of the recommendations, a general estimate for the job of creating the"
    " project should be provided based on the square footage estimate. The region is: {region} and the square footage estimate is:"
    " {area}. Recommendations should be for a moderately sophisticated suburban housing community within the region.",
    input_variables=["region", "area"],
)

model = ChatOpenAI(model="gpt-4o", temperature=0.95, max_tokens=4096)

chain = (
    {
        "region": itemgetter("messages") | RunnableLambda(get_region),
        "area": itemgetter("messages") | RunnableLambda(get_area),
    }
    | prompt
    | model
    | StrOutputParser()
)

mlflow.models.set_model(chain)

このスクリプトは、LangChain Expression Language (LCEL)を用いて、完全なチェーンを構成するのに必要なロジックと入力処理にチェーンが使用するカスタムデフォルトロジックをカプセル化します。そして、定義されたチェーンはset_model関数を用いてモデルのインタフェースオブジェクトとして指定されます。

Models from Codeによるモデルのロギング

mfc.pyにチェーンを定義したら、MLflow用いて記録します。このステップには、チェーンの定義を含むスクリプトのパスの指定と、チェーンのすべての側面が捕捉されるようにするために、MLflowのlangchainインテグレーションの使用が含まれます。

ロギング関数に指定されるinput_exampleは、モデルがどのように呼び出されるべきかを説明するテンプレートとしで動作します。また、このサンプルは記録されるモデルの一部として保存されるので、モデルのユースケースの理解と再利用を簡単にします。

以下のコードブロックでは、MLflowによりLangChainモデルの記録方法を示しています:

import mlflow

mlflow.set_experiment("Landscaping")

chain_path = "./mfc.py"

input_example = {
    "messages": [
        {
            "role": "user",
            "content": {
                "region": "Austin, TX, USA",
                "area": "1750 square feet",
            },
        }
    ]
}

with mlflow.start_run():
    info = mlflow.langchain.log_model(
        lc_model=chain_path,  # Defining the model as the script containing the chain definition and the set_model call
        artifact_path="chain",
        input_example=input_example,
    )

このステップでは、入力の処理からAIモデルの推論に至るオペレーション全体のチェーンが単一でまとまったモデルとして記録されます。定義されたチェーンのコンポーネントオブジェクトのシリアライゼーションに伴う潜在的な複雑性を回避し、Models From Code機能を活用することで、不完全あるいは存在しないシリアライゼーションの機能によるリスクなしに、開発やテストに用いられたそのもののコードとロジックがアプリケーションをデプロイする際に実行されることを確実なものにします。

モデルのロードと参照

モデルをロギングすると、推論のためにあなたの環境にロードし直すことができます。このステップでは、新規の入力データに基づいて景観デザインの推奨を生成するために、どのようにチェーンをロード、使用するのかを説明しています。

以下のコードブロックでは、モデルのロードと予測の実行を説明しています:

# Load the model and run inference
landscape_chain = mlflow.langchain.load_model(model_uri=info.model_uri)

question = {
    "messages": [
        {
            "role": "user",
            "content": {
                "region": "Raleigh, North Carolina USA",
                "area": "3850 square feet",
            },
        },
    ]
}

response = landscape_chain.invoke(question)

このコードブロックは、新規データを用いてロードされたチェーンを呼び出し、特定の地域やエリアに適した景観デザインの提案を提供するレスポンスの生成をデモンストレーションしています。

モデルが記録された、MLflow UIで詳細を探索することができます。インタフェースでは、記録されたモデルのアーティファクトとしてのmfc.pyとチェーンの定義、関連メタデータが表示されます。これによって、モデルのコンポーネント、入力サンプル、その他の主要な情報を容易に確認することができます。

mlflow.langchain.load_model()を用いてこのモデルをロードすると、mfc.pyで定義されたチェーン全体が実行され、期待した通りにモデルが動作し、景観デザインに関するAIドリブンの推奨を生成します。

Models from CodeのFAQ

モデルをロギングするためにModels From Code機能を使用する際に注意すべき点がいくつかあります。レガシーなモデルシリアライゼーションを使用した際と挙動は似ていますが、あなたの開発ワークフローやコードのアーキテクチャで注意すべきいくつかの違いが存在します。

依存関係管理とrequirements

新規環境であなたのモデルがロード、デプロイされるようにするために、依存関係とrequirementsの適切な管理が重要となります。

保存したスクリプトからモデルをロードした際にNameErrorになるのはなぜ?

スクリプト(あるいはノートブックのセル)を定義する際に、スクリプト内で必要なすべてのimport文が定義されているようにしてください。importの依存関係に抜けがあると、名前解決エラーになるだけではなく、要件の依存関係がモデルのrequirements.txtファイルに含まれなくなります。

モデルをロードするとImportErrorエラーが発生します

PyPIで利用できない外部依存関係がモデル定義に含まれる場合、モデルをロギング、保存する際にcode_paths引数を用いてそれらの参照を含めなくてはなりません。モデルをロードする際に、必要なすべての依存関係が含まれるように、モデルをロギングする際にこれらの外部スクリプトからのimport依存関係を手動でextra_pip_requirements引数に追加する必要がある場合があります。

なぜrequirements.txtファイルにモデルで使っていないパッケージが含まれているのか?

MLflowはモジュールレベルのimport文に基づいてモデルからrequirementsのリストを構築します。あなたのモデルロジックがimportで宣言されているすべてを必要としているかどうかを検証するための調査プロセスはありません。あなたのモデルが動作するために必要なimport文が最小限になるように、スクリプト内のimportを削ぎ落とすことをお勧めします。大規模なパッケージを多数importすると、モデルのロードやデプロイでのインストールの遅延を引き起こし、あなたがデプロイした推論環境のメモリーを圧迫します。

Models From Codeによるロギング

定義したPythonファイルのモデルをロギングする際、オブジェクトのリファレンスを提供するレガシーなモデルシリアライゼーションプロセスとのちょっとした違いに遭遇するでしょう。

私のスクリプトに間違ってAPIキーを含めてしまいました。どうしたらいいですか?

Models From Code機能は平文でスクリプト定義を保存するという事実から、MLflow UIのアーティファクトの参照者にはアクセスキーや他の認証に関するシークレットを含む機密データはすべて見えてしまい、セキュリティリスクとなります。モデルをロギングする際に、間違ってスクリプトに直接機密のキーを残してしまったら、以下を行うことをお勧めします:

  1. 漏洩したキーを含むMLflowランを削除する。UIやdelete_run APIでこれを行うことができます。
  2. ランに関連づけられているアーティファクトを削除します。mlflow gc CLIコマンドでこれを行うことができます。
  3. ソースシステムの管理インタフェースで新規のキーを作成し、漏洩したシークレットを削除することでセンシティブなキーをローテーションします。
  4. 新規ランを再度ロギングし、モデル定義スクリプトにセンシティブなキーが設定されていないようにします。

ロギングする際にモデルが実行されるのはなぜですか?

モデルを定義するPythonファイルでコードが実行可能であることを検証するために、MLflowはset_model APIでモデルとして定義されているオブジェクトのインスタンスを作成します。モデルの初期化の際に外部呼び出しが行われる場合には、ロギングの前にコードが実行可能であることを確実にするために呼び出しが行われます。このような呼び出しにはサービスに対して認証されたアクセスが必要となるので、コードが実行されるようにモデルがロギングされる環境に適切な認証が設定されていることを確認してください。

その他のリソース

MLflowの「Models From Code」機能の理解を深めることができる、その他の関連トピックについては、MLflowドキュメントの以下のセクションを探索してください:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?