Hugging FaceのTensorFlowの哲学
「Hugging FaceのTensorFlowの哲学」とは、Hugging FaceがTensorFlowにおいて持つ考え方・原則のことです
はじめに
PyTorchやJAXからの競争が増えても、TensorFlowは最も使用されるディープラーニングフレームワークのままです。また、それらの他の2つのライブラリとはいくつか非常に重要な点で異なります。特に、高レベルのAPIであるKeras
と、データの読み込みライブラリであるtf.data
との統合が非常に密接です。
PyTorchのエンジニアの中には(ここでオープンプランオフィスを暗く見つめながら私を想像してください)、これを克服すべき問題だと見なす傾向があります。彼らの目標は、TensorFlowが彼らのやり方に従って低レベルのトレーニングとデータの読み込みコードを使用できるようにする方法を見つけることです。これはTensorFlowに取り組む間違った方法です! Kerasは素晴らしい高レベルのAPIです。プロジェクトが数モジュールよりも大きい場合、それを押しのけると、必要になると気付いたときに、その機能のほとんどを自分で再現することになります。
洗練された、尊敬され、非常に魅力的なTensorFlowエンジニアとして、私たちは最先端のモデルの驚異的なパワーと柔軟性を使用したいと思っていますが、私たちが使い慣れたツールとAPIでそれらを扱いたいのです。このブログポストでは、Hugging Faceでそれを実現するために行う選択と、TensorFlowプログラマーとしてフレームワークから期待できることについて説明します。
インタールード:30秒で🤗
経験豊富なユーザーは、このセクションをざっと読んだりスキップしたりして構いませんが、Hugging Faceとtransformers
に初めて出会う方には、ライブラリのコアアイデアについて概要を説明する必要があります。モデルを事前学習済みモデルとして名前でリクエストするだけで、1行のコードで取得できます。最も簡単な方法は、TFAutoModel
クラスを使用するだけです。
from transformers import TFAutoModel
model = TFAutoModel.from_pretrained("bert-base-cased")
この1行でモデルのアーキテクチャがインスタンス化され、重みが読み込まれます。これにより、元の有名なBERTモデルの正確なレプリカが得られます。ただし、このモデル自体ではあまり役に立ちません – 出力ヘッドや損失関数がありません。実際には、これは最後の隠れ層の直後で終了するニューラルネットワークの「ステム」です。では、どのようにして出力ヘッドを追加するのでしょうか?簡単です、異なるAutoModel
クラスを使用するだけです。ここでは、Vision Transformer(ViT)モデルを読み込み、画像分類ヘッドを追加しています。
from transformers import TFAutoModelForImageClassification
model_name = "google/vit-base-patch16-224"
model = TFAutoModelForImageClassification.from_pretrained(model_name)
これで、model
には出力ヘッドがあり、必要に応じて新しいタスクに適した損失関数もあります。新しい出力ヘッドが元のモデルと異なる場合、その重みはランダムに初期化されます。その他の重みは元のモデルから読み込まれます。しかし、なぜこれを行うのでしょうか?必要なモデルを最初から作成する代わりに、既存のモデルのステムを使用する理由は何でしょうか?
実際、大量のデータで事前学習された大型モデルは、重みをランダムに初期化するだけの通常の方法よりも、ほとんどのML問題においてはるかに優れた出発点です。これは転移学習と呼ばれ、よく考えてみれば、それは合理的なことです – テキストのタスクをうまく解決するには、言語の知識が必要であり、視覚的なタスクをうまく解決するには、イメージと空間の知識が必要です。転移学習がない場合、MLはデータに飢えている理由は、この基本的なドメイン知識がすべての問題に対してゼロから再学習される必要があるためです。これには膨大な量のトレーニング例が必要です。しかし、転移学習を使用することで、問題を解決するのに1000のトレーニング例で済むかもしれないものが、それを使用しない場合は100万のトレーニング例が必要であり、最終的な精度も高くなることがよくあります。このトピックの詳細については、Hugging Faceコースの関連セクションをご覧ください!
ただし、転移学習を使用する場合、モデルへの入力をトレーニング時に処理された方法と同じように処理することが非常に重要です。これにより、モデルが新しい問題に対する知識を転移する際に再学習する必要ができるだけ少なくなります。これは、transformers
では、この前処理はしばしばトークナイザで処理されます。トークナイザはモデルと同じようにロードすることができます。その場合は、一致するモデルをロードすることを確認してください!
from transformers import TFAutoModel, AutoTokenizer
# 必ず一致するトークナイザとモデルをロードするようにしてください!
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
model = TFAutoModel.from_pretrained("bert-base-cased")
# データをロードしてトークナイズしましょう
test_strings = ["これは文です!", "これは別の文です!"]
tokenized_inputs = tokenizer(test_strings, return_tensors="np", padding=True)
# これでデータはトークナイズされましたので、モデルに渡すか、fit()で使用することができます!
outputs = model(tokenized_inputs)
これはライブラリの一部の紹介ですが、もっと詳しく知りたい場合は、ノートブックやコードの例をチェックしてください。また、keras.ioにはライブラリのさまざまな使用例もあります!
ここまで基本的な概念とクラスについて理解できました。上記に書いた内容は、フレームワークに依存しないものです(ただし、TFAutoModel
の「TF」を除く)。しかし、モデルのトレーニングやサービスの実際の実行を行いたい場合、フレームワーク間での違いが生じてきます。そして、この記事の主な焦点となるのは、TensorFlowエンジニアとしてのあなたがtransformers
から何を期待できるかです。
フィロソフィー #1: すべてのTensorFlowモデルはKeras Modelオブジェクトであるべきであり、すべてのTensorFlowレイヤーはKeras Layerオブジェクトであるべきです。
TensorFlowライブラリにおいて、これはほとんど当然のことですが、それにもかかわらず強調する価値があります。ユーザーの視点から見ると、この選択の最も重要な効果は、モデルに対してfit()
、compile()
、predict()
などのKerasメソッドを直接呼び出すことができることです。
たとえば、データがすでに準備されトークン化されていると仮定した場合、TensorFlowを使用したシーケンス分類モデルから予測を取得するのは次のように簡単です:
model = TFAutoModelForSequenceClassification.from_pretrained(my_model)
model.predict(my_data)
そして、そのモデルをトレーニングしたい場合は、次のようになります:
model.fit(my_data, my_labels)
ただし、この便利さは、ボックス内のコードで指定されているタスクに制限されることを意味しません。Kerasモデルは他のモデルのレイヤーとして構成することができます。したがって、5つの異なるモデルを組み合わせるという巨大なアイデアがある場合でも、GPUメモリの制限以外には制約はありません。たとえば、事前学習済みの言語モデルと事前学習済みのビジョン変換モデルをマージして、Deepmindの最近のFlamingoのようなハイブリッドモデルを作成したり、Dall-E Mini Craiyonのような次世代のテキストから画像への感覚を作成したりすることができます。以下は、Kerasのサブクラス化を使用したハイブリッドモデルの例です:
class HybridVisionLanguageModel(tf.keras.Model):
def __init__(self):
super().__init__()
self.language = TFAutoModel.from_pretrained("gpt2")
self.vision = TFAutoModel.from_pretrained("google/vit-base-patch16-224")
def call(self, inputs):
# このアイデアは本当に素晴らしいですね
# ただ、このボックスのコードには短すぎます
フィロソフィー #2: 損失関数はデフォルトで提供されますが、簡単に変更することができます。
Kerasでは、モデルを作成し、オプティマイザと損失関数を指定してcompile()
し、最後にfit()
します。transformersを使用してモデルを読み込むことは非常に簡単ですが、損失関数を設定することは難しい場合があります。たとえば、標準の言語モデルトレーニングでも、損失関数は意外にも明確でない場合がありますし、一部のハイブリッドモデルは非常に複雑な損失関数を持っています。
それに対する私たちの解決策は単純です。損失引数を指定せずにcompile()
すると、おそらく必要な損失関数を提供します。具体的には、ベースモデルと出力タイプの両方に一致する損失関数を提供します。たとえば、損失引数を指定せずにBERTベースのマスク言語モデルをcompile()
すると、パディングとマスキングを正しく処理し、破損したトークンのみで損失を計算し、元のBERTトレーニングプロセスと完全に一致するマスク言語モデリングの損失を提供します。何らかの理由でモデルを損失なしでコンパイルしたくない場合は、loss=None
を指定してください。
model = TFAutoModelForQuestionAnswering.from_pretrained("bert-base-cased")
model.compile(optimizer="adam") # 損失引数なし!
model.fit(my_data, my_labels)
さらに重要なことは、より複雑なことをしたい場合には、邪魔にならないようにしたいということです。compile()
に損失引数を指定すると、モデルはデフォルトの損失ではなく、指定した損失を使用します。そしてもちろん、上記のHybridVisionLanguageModel
のような独自のサブクラス化モデルを作成した場合、call()
やtrain_step()
メソッドを使ってモデルの機能のすべての側面を完全に制御できます。
哲学の実装詳細 #3: ラベルは柔軟です
過去に混乱の原因となった1つは、モデルにラベルをどこに渡すべきかでした。Kerasモデルにラベルを渡す標準的な方法は、別の引数として渡すか、または(inputs、labels)のタプルの一部として渡すことです:
model.fit(inputs, labels)
過去には、デフォルトの損失関数を使用する場合にラベルを入力辞書に渡すようユーザーにお願いしていました。これは、その特定のモデルの損失を計算するためのコードがcall()
の順伝播メソッドに含まれていたためです。これは動作しましたが、Kerasモデルとしては非標準的であり、標準的なKerasメトリックとの互換性の問題や、ユーザーの混乱など、いくつかの問題を引き起こしました。幸いにも、これはもはや必要ありません。現在は、ラベルは通常のKerasの方法で渡すことを推奨していますが、後方互換性のために古い方法も引き続き機能します。一般的に、以前は手間がかかっていた多くのことが、TensorFlowモデルでは今では「うまく動作する」ようになりました-ぜひお試しください!
哲学 #4: 特に一般的なタスクには、独自のデータパイプラインを書く必要はありません
transformers
に加えて、事前学習済みモデルの大規模なオープンリポジトリである🤗datasets
が存在します-テキスト、ビジョン、音声などのデータセットです。これらのデータセットは、TensorFlow TensorsとNumpy配列に簡単に変換できるため、トレーニングデータとして使用するのが簡単です。以下は、データセットをトークン化してNumpyに変換するクイックな例です。いつものように、トークナイザーがトレーニングに使用するモデルと一致していることを確認してください。さもないと、非常に奇妙な結果になります!
from datasets import load_dataset
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification
from tensorflow.keras.optimizers import Adam
dataset = load_dataset("glue", "cola") # シンプルなテキスト分類データセット
dataset = dataset["train"] # 今のところトレーニング用のデータセットのみを使用します
# トークナイザーをロードしてデータをトークン化する
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
tokenized_data = tokenizer(dataset["text"], return_tensors="np", padding=True)
labels = np.array(dataset["label"]) # ラベルは既に0と1の配列です
# モデルをロードしてコンパイルする
model = TFAutoModelForSequenceClassification.from_pretrained("bert-base-cased")
# ファインチューニングには低い学習率がよく使われます
model.compile(optimizer=Adam(3e-5))
model.fit(tokenized_data, labels)
このアプローチは機能する場合には素晴らしいですが、大規模なデータセットでは問題になることがあります。なぜなら、トークン化された配列とラベルを完全にメモリに読み込まなければならず、またNumpyは「ジャギー」な配列を処理しないため、すべてのトークン化されたサンプルをデータセット全体で最も長いサンプルの長さまでパディングする必要があるからです。それにより、配列はさらに大きくなり、すべてのパディングトークンがトレーニングを遅くすることになります!
TensorFlowエンジニアとしては、通常、データを全てメモリに読み込むのではなく、ストレージからデータをストリーミングするパイプラインを作成するためにtf.data
に頼ることになります。しかし、それは手間ですので、私たちが提供します。まず、map()
メソッドを使用して、トークナイザーカラムをデータセットに追加します。デフォルトでは、データセットはディスクにバックアップされます-配列に変換するまでメモリに読み込まれません!
def tokenize_dataset(data):
# 返されるディクショナリのキーは、カラムとしてデータセットに追加されます
return tokenizer(data["text"])
dataset = dataset.map(tokenize_dataset)
これで、データセットに必要なカラムが揃いましたが、それをどのようにトレーニングするのでしょうか?簡単です-tf.data.Dataset
でラップするだけで、すべての問題が解決されます-データはオンザフライで読み込まれ、パディングはデータセット全体ではなくバッチごとに適用されるため、パディングトークンがはるかに少なくて済みます:
tf_dataset = model.prepare_tf_dataset(
dataset,
batch_size=16,
shuffle=True
)
model.fit(tf_dataset)
なぜprepare_tf_dataset()がモデルのメソッドなのですか?シンプルです:モデルは、有効な入力名として有効なカラムを知っており、データセット内の無効なカラムを自動的にフィルタリングします!より詳細な制御をしたい場合は、より低レベルのDataset.to_tf_dataset()を使用することもできます。
哲学#5:XLAは素晴らしいです!
XLAは、TensorFlowとJAXで共有されるジャストインタイムコンパイラです。線形代数のコードをより最適化されたバージョンに変換し、より高速に実行し、メモリをより少なく使用します。これは本当にクールで、できるだけサポートするようにしています。TPUでモデルを実行するために非常に重要ですが、GPUやCPUでも速度向上が実現できます!使用するには、jit_compile=True
引数を使用してモデルをcompile()
します(これはHugging Faceのモデルだけでなく、すべてのKerasモデルで機能します):
model.compile(optimizer="adam", jit_compile=True)
最近、この領域でいくつかの重大な改善を行いました。最も重要なのは、XLAを使用してgenerate()
コードを更新したことです。これは、言語モデルからテキスト出力を反復的に生成する関数です。これにより、大幅なパフォーマンスの向上が実現されました。従来のTFコードはPyTorchよりも遅かったですが、新しいコードはそれよりもはるかに高速で、JAXと同等の速度です!詳細については、XLA生成に関するブログ記事をご覧ください。
XLAは生成以外にも便利です!XLAを使用してモデルをトレーニングできるように修正を加えたため、TFモデルは言語モデルトレーニングなどのタスクでJAXのような速度に達しました。
ただし、XLAの主な制限については明確にすることが重要です。XLAは入力の形状が静的であることを期待しています。つまり、タスクに可変のシーケンス長が含まれる場合、モデルに渡す異なる入力形状ごとに新しいXLAコンパイルを実行する必要があります。これはパフォーマンスの利点を相殺する可能性があります!これについてのいくつかの例は、TensorFlowのノートブックや上記のXLA生成ブログ記事で確認できます。
哲学#6:展開はトレーニングと同じくらい重要です
TensorFlowには、他のより研究に重点を置いたフレームワークにはない豊富なエコシステムがあります。モデル展開に関連するツールを使用して、モデル全体を推論に展開できるようにするために積極的に取り組んでいます。特に、TF Serving
とTFX
のサポートに関心があります。興味がある場合は、TF Servingでモデルを展開するには、当社のブログ記事をご覧ください。
ただし、NLPモデルを展開する際の主な障害は、入力をトークン化する必要があることです。つまり、モデルを展開するだけでは十分ではありません。デプロイメントシナリオでは、tokenizers
への依存が厄介な場合があります。そのため、トークン化をモデル自体に埋め込むことが可能になるように取り組んでいます。これにより、入力文字列から出力予測までのパイプライン全体を処理するために、単一のモデルアーティファクトだけを展開できます。現時点では、BERTのような最も一般的なモデルのみをサポートしていますが、これは活発な研究の領域です!ただし、試してみたい場合は、次のようなコードスニペットを使用できます:
# This is a new feature, so make sure to update to the latest version of transformers!
# You will also need to pip install tensorflow_text
import tensorflow as tf
from transformers import TFAutoModel, TFBertTokenizer
class EndToEndModel(tf.keras.Model):
def __init__(self, checkpoint):
super().__init__()
self.tokenizer = TFBertTokenizer.from_pretrained(checkpoint)
self.model = TFAutoModel.from_pretrained(checkpoint)
def call(self, inputs):
tokenized = self.tokenizer(inputs)
return self.model(**tokenized)
model = EndToEndModel(checkpoint="bert-base-cased")
test_inputs = [
"This is a test sentence!",
"This is another one!",
]
model.predict(test_inputs) # Pass strings straight to model!
結論:私たちはオープンソースプロジェクトであり、それはコミュニティがすべてだということです
素敵なモデルを作りましたか?共有しましょう!アカウントを作成し、資格情報を設定したら、次のように簡単です:
model_name = "google/vit-base-patch16-224"
model = TFAutoModelForImageClassification.from_pretrained(model_name)
model.fit(my_data, my_labels)
model.push_to_hub("my-new-model")
また、長時間のトレーニング実行中に定期的にチェックポイントをアップロードするためにPushToHubCallbackを使用することもできます!いずれにしても、モデルページと自動生成されたモデルカードが表示され、何よりも重要なのは、他の誰もが予測を取得したり、さらなるトレーニングの出発点としてモデルを使用するために、既存のモデルをロードするのと同じAPIを使用できることです:
model_name = "your-username/my-new-model"
model = TFAutoModelForImageClassification.from_pretrained(model_name)
私たちはHugging Faceにおいて、有名な大規模な基礎モデルと単一のユーザーによって微調整されたモデルの区別がないという事実は、ユーザーが素晴らしいものを構築する力を具現化していると考えています。機械学習は、数少ない企業が所有する閉じられたモデルからの結果の滴であるべきではありません。それは、常に拡大、テスト、批評、構築されるオープンなツール、アーティファクト、プラクティス、知識のコレクションであり、バザールであり、大聖堂ではありません。新しいアイデアや新しい手法を見つけたり、優れた結果をもたらす新しいモデルを訓練したりした場合は、みんなに知らせてください!
そして同じように、何か足りないことはありませんか?バグですか?不便な点ですか?直感的であるべきなのになっていないことですか?教えてください!もし(比喩的な)シャベルを手に取って修正を始める意思があるなら、それはさらに良いですが、コードベースを改善する時間やスキルセットがない場合でも、遠慮せずに声を上げてください。ユーザーが問題を提起しないため、主要なメンテナーは問題を見落とすことがありますので、私たちが何かを知っているとは思わないでください!あなたにとって気になることがあれば、フォーラムで質問してください。もしくは、それがバグまたは重要な機能の欠落であるとほぼ確信している場合は、問題を報告してください。
これらのことの多くは些細な詳細かもしれませんが、(かなり厄介な)フレーズを作り出すために、素晴らしいソフトウェアは数千の小さなコミットから作られます。オープンソースソフトウェアが改善されるのは、ユーザーとメンテナーの持続的な共同の努力によるものです。機械学習は2020年代において重要な社会的な問題になるでしょうし、オープンソースソフトウェアとコミュニティの強さが、それが批判と再評価の対象となるオープンで民主的な力になるのか、巨大なブラックボックスモデルに支配され、モデルによって意思決定がなされる人々でさえも、その貴重なプロプライエタリな重みを見せてくれないオーナーによって閉ざされるのかを決定します。だからこそ、何かが間違っている場合や、もっと良くできるアイデアがある場合、どこで貢献すればいいかわからない場合でも、私たちに伝えてください!
(そして、クールな新機能がマージされた後にPyTorchチームをトロールするためのミームを作成できるなら、なおさらです。)
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles