はじめに: 「私の名前はジェイコブです。Google AI Residency プログラムの奨学生です。2017 年の夏にプログラムに参加したとき、私は豊富なプログラミング経験と機械学習に対する深い理解を持っていましたが、Tensorflow を使用したことはありませんでした。当時は、自分の能力ですぐに Tensorflow を習得できると思っていましたが、学習プロセスがこれほど浮き沈みがあるとは思っていませんでした。プログラムに参加してから数か月経っても、時々混乱し、Tensorflow コードを使用して新しいアイデアを実装する方法がわかりませんでした。
このブログ投稿は、過去の自分へのボトルメッセージのようなものです。振り返ってみると、始めた頃にこのような紹介があればよかったと思います。また、この記事が同僚の役に立ち、参考になることを願っています。 「AI Frontline は、現役の Google Brain エンジニアが Tensorflow の学習で遭遇したさまざまな困難について書いた記事を翻訳し、皆様のお役に立てれば幸いです。」 過去のチュートリアルには何が欠けていたのでしょうか? Tensorflow はリリースされてから 3 年が経ち、ディープラーニング エコシステムの基盤となっています。しかし、特に PyTorch や DyNet などの実行時に定義されるニューラル ネットワーク ライブラリと比較すると、初心者にとって理解するのはそれほど簡単ではありません。 Tensorflow には、線形回帰、MNIST 分類、さらには機械翻訳までをカバーする入門チュートリアルが多数あります。これらの具体的で実用的なガイドは、Tensorflow プロジェクトをすぐに開始して実行するのに役立ち、同様のプロジェクトの出発点として役立ちます。しかし、開発者の中には、自分のアプリケーションに適したチュートリアル リファレンスを持っていない人もいれば、新しいルートを模索しているプロジェクトもあります (研究では非常に一般的です)。これらの開発者にとって、Tensorflow を使い始めるのは非常に混乱を招きます。 このギャップを埋めるためにこの記事を書きました。この記事では、特定のタスクを研究するのではなく、より一般的なアプローチを提案し、Tensorflow の基本的な抽象概念について説明します。これらの概念を習得すると、Tensorflow を使用したディープラーニングがより直感的で理解しやすくなります。 対象者 このチュートリアルは、プログラミングと機械学習の経験があり、Tensorflow を使い始めたい実践者を対象としています。対象者は、ディープラーニング コースの最後のプロジェクトで Tensorflow を使用したいと考えている CS 専攻の学生、ディープラーニングを含むプロジェクトに異動になったばかりのソフトウェア エンジニア、または混乱している Google AI 初心者 (Jacob に感謝) などです。基本から始める必要がある場合は、次のリソースを参照してください。これらすべてを理解したら、始めましょう! Tensorflowを理解する Tensorflow は通常の Python ライブラリではありません。 ほとんどの Python ライブラリは、Python の自然な拡張として記述されています。ライブラリをインポートすると、コーディングの「ツールボックス」を補完および拡張する一連の変数、関数、およびクラスが得られます。これらのライブラリを使用すると、どのような結果が生成されるかがわかります。 Tensorflow について語るときには、こうした認識は捨て去るべきだと思います。こうした認識は、Tensorflow の哲学と根本的に矛盾しており、TF が他のコードと対話する方法を反映できません。 Python と Tensorflow の関係は、Javascript と HTML の関係に似ています。 Javascript は、あらゆる種類の驚くべき効果を実現できるフル機能のプログラミング言語です。 HTML は、特定の種類の有用なコンピューティング抽象化 (この場合は、Web ブラウザーでレンダリングできるコンテンツ) を表すフレームワークです。インタラクティブな Web ページにおける Javascript の役割は、ブラウザーが認識する HTML オブジェクトを組み立て、必要に応じて新しい HTML に更新してそれらのオブジェクトと対話することです。 HTML と同様に、Tensorflow は「計算グラフ」と呼ばれる特定の種類の計算抽象化を表現するためのフレームワークです。 Python で Tensorflow を使用する場合、Python コードで最初に行うことは計算グラフを組み立てることです。 2 番目のタスクは、(Tensorflow の「セッション」を使用して) それと対話することです。ただし、計算グラフは変数内ではなく、グローバル名前空間内に存在することを覚えておくことが重要です。シェイクスピアはかつてこう言いました。「すべての RAM はステージであり、すべての変数はポインタにすぎない。」 最初の重要な抽象化: 計算グラフ Tensorflow のドキュメントを閲覧していると、「グラフ」や「ノード」への参照が見つかることがあります。注意深く読み、さらに深く掘り下げていくと、私が詳細に説明した内容をより正確かつ技術的なスタイルでカバーしているこのページが見つかるかもしれません。このセクションでは、技術的な詳細の一部を省略しながら、主要な直感的な概念を把握しながら、概要から始めます。 では、計算グラフとは何でしょうか? 計算グラフは本質的にグローバルなデータ構造です。計算グラフは、計算を実行する方法に関する指示をキャプチャする有向グラフです。 例を構築する方法を見てみましょう。下の図では、上部は実行したコードとその出力、下部は結果の計算グラフです。 当然ですが、Tensorflow をインポートするだけでは、興味深い計算グラフは得られず、ただの空のグローバル変数しか得られません。しかし、Tensorflow 操作を呼び出すと何が起こるでしょうか? 見てください! 定数 2 を含むノードがあります。驚かれると思いますが、その驚きは tf.constant という関数です。この変数を印刷すると、先ほど作成したノードへのポインターである tf.Tensor オブジェクトが返されることがわかります。この点を強調するために、別の例を挙げます。 tf.constant を呼び出すたびに、グラフに新しいノードが作成されます。ノードが既存のノードと機能的に同一であっても、ノードを同じ変数に再割り当てしても、あるいはノードを変数にまったく割り当てなくても、結果は同じになります。 代わりに、新しい変数を作成し、それを既存のノードと同じ値に設定すると、そのノードへのポインターをコピーするだけで、グラフには何も追加されません。 さて、さらに一歩進めてみましょう。 さて、見てみましょう。これが私たちが探していた実際の計算グラフです。Tensorflow では + 演算がオーバーロードされているため、2 つのテンソルを追加すると、Tensorflow 演算のようには見えなくても、グラフにノードが追加されることに注意してください。 わかりました。two_node は 2 を含むノードを指し、three_node は 3 を含むノードを指し、sum_node は... + を含むノードを指します。何ですか? 5 を含むべきではないですか? 結局、そうではありませんでした。計算グラフには計算ステップのみが含まれ、結果は含まれません。少なくとも…まだです! 2番目の重要な抽象化: セッション TensorFlow の抽象化が March Madness 競技 (アメリカの大学バスケットボールの忙しいチャンピオンシップ シーズン) であると誤って理解された場合、「セッション」は毎年ランキングのトップ シードになります。私たちがこの厄介な栄誉を得た理由は、セッションが直感に反する命名にもかかわらず、非常に広く使用されているためです。ほぼすべての Tensorflow プログラムは、tf.Session() を少なくとも 1 回は呼び出します。 セッションの役割は、メモリの割り当てと最適化を処理し、グラフによって指定された計算を実際に実行できるようにすることです。計算グラフは、実行する計算の「テンプレート」と考えることができます。計算グラフには、すべてのステップがリストされます。このチャートを使用するには、実際にタスクを完了できるようにするセッションを開始する必要もあります。たとえば、テンプレートのすべてのノードを走査して、計算出力を格納するためのメモリのセットを割り当てます。 Tensorflow を使用してさまざまな計算を実行するには、グラフとセッションの両方が必要です。 セッションには、すべてのノードへのポインターで常に更新されるグローバル グラフへのポインターが含まれています。つまり、ノードが作成される前か後かにセッションを作成するかどうかは関係ありません。 セッション オブジェクトを作成したら、 sess.run(node) を使用してノードの値を返すことができ、Tensorflow はその値を決定するために必要なすべての計算を実行します。 素晴らしい! リスト sess.run([node1, node2, ...]) を渡して、複数の出力を返すこともできます。 一般的に、 sess.run() 呼び出しは TensorFlow の最大のボトルネックの 1 つになる傾向があるため、呼び出す回数が少ないほど効果的です。可能な場合は、複数回の呼び出しを行うのではなく、1 回の sess.run() 呼び出しで複数の項目を返します。 プレースホルダーと feed_dict これまで行ってきた計算は退屈なものでした。入力を得る機会がないので、常に同じものが出力されます。実際のアプリケーションでは、入力を受け取り、それを何らかの(一貫した)方法で処理し、出力を返す計算グラフを構築することが考えられます。 最も簡単な方法はプレースホルダーを使用することです。プレースホルダーは外部入力を受け入れるノードです。 ...例外が発生するため、これは悪い例です。プレースホルダーには値が与えられることが期待されていますが、値が指定されなかったため、Tensorflow がクラッシュしました。 値を提供するには、sess.run() の feed_dict 属性を使用します。 ずっと良くなりました。 feed_dictに渡される値の形式に注意してください。キーは、グラフ内のプレースホルダー ノードに対応する変数である必要があります (前述のように、実際にはグラフ内のプレースホルダー ノードへのポインターを意味します)。対応する値は、各プレースホルダーに割り当てられるデータ要素です。通常はスカラーまたは Numpy 配列です。 3 番目の重要な抽象化: パスの計算 プレースホルダーを使用した別の例を次に示します。 2 回目の sess.run() 呼び出しが失敗するのはなぜでしょうか。input_placeholder をチェックしていないのに、なぜ input_placeholder に関連するエラーがスローされるのでしょうか。その答えは、Tensorflow の最後の重要な抽象化である計算パスにあります。幸いなことに、この抽象化は非常に直感的です。 グラフ内の他のノードに依存するノードで sess.run() を呼び出す場合は、それらのノードの値も計算する必要があります。これらのノードに依存関係がある場合は、計算グラフの「最上部」に到達するまで、それらの値(など)を計算する必要があります。この「最上部」では、すべてのノードに先行ノードがありません。 sum_node の計算パスを考えてみましょう。 sum_node の値を計算するには、3 つのノードすべてを評価する必要があります。何よりも素晴らしいのは、これには未入力のプレースホルダーが含まれており、例外が説明されていることです。 対照的に、three_node の計算パスを考えてみましょう。 グラフの構造上、必要なノードを評価するためにすべてのノードを評価する必要はありません。three_node を評価するために placeholder_node を評価する必要がないため、sess.run(three_node) を実行しても例外は発生しません。 Tensorflow では、計算を必要なノードのみに自動的にルーティングすることが大きな利点です。計算グラフが非常に大きく、不要なノードが多数ある場合は、実行時間を大幅に節約できます。これにより、単一の共有コアノードコレクションを使用して、実行される計算パスに応じてさまざまなタスクを実行する、大規模な「多目的」グラフを構築できます。ほとんどすべてのアプリケーションでは、 sess.run() が呼び出される方法を、それが取る計算パスの観点から考慮することが重要です。 変数と副作用 これまで、tf.constant (実行ごとに同じ) と tf.placeholder (実行ごとに異なる) という 2 種類の「祖先なし」ノードを見てきました。 3 番目の種類のノードがあります。通常は同じ値を持ちますが、新しい値に更新することもできるノードです。ここで変数が作用します。 モデルのパラメータは変数であるため、変数を理解することは Tensorflow を使用したディープラーニングにとって非常に重要です。トレーニング中は、勾配降下法によって各ステップでパラメータを更新しますが、計算中はパラメータを固定したまま、大規模で多様なテスト入力セットをモデルに渡します。モデルのトレーニング可能なパラメータはすべて変数である可能性があります。 変数を作成するには、tf.get_variable() を使用します。 tf.get_variable() の最初の 2 つの引数は必須ですが、残りはオプションです。それらは、tf.get_variable(name,shape) です。 name は、この変数オブジェクトを一意に識別する文字列です。グローバル グラフ内で一意である必要があるため、名前が重複していないことを確認してください。 shape は、テンソルの形状に対応する整数の配列です。構文は単純で、次元ごとに 1 つの整数が順番に配置されます。たとえば、3×8行列の形状は[3,8]になります。スカラーを作成するには、空のリストを形状として使用します: []。 別の異常を発見しました。変数ノードが最初に作成されるとき、その値は基本的に「null」であり、その値に対して計算を実行しようとすると、この例外がスローされます。変数に値を割り当てた後でのみ、変数を使った計算を行うことができます。変数に値を割り当てる主な方法は、初期化子と tf.assign() の 2 つです。まずtf.assign()を見てみましょう: tf.assign(target,value) には、これまで見てきたノードと比較して、いくつかの独特なプロパティがあります。
「副作用」ノードはほとんどの Tensorflow ディープラーニング ワークフローに存在するため、必ず理解しておいてください。 sess.run (assign_node) を呼び出すと、計算パスはassign_nodeとzero_nodeを通過します。 計算がグラフ内のいずれかのノードを通過すると、そのノードによって制御される副作用 (緑色で表示) も有効になります。 tf.assign の特殊な副作用により、count_variable (以前は「null」) に関連付けられたメモリが *** 0 に設定されるようになりました。つまり、次に sess.run(count_variable) を呼び出したときに、例外はスローされません。代わりに、0 が返されます。 次に、初期化子を見てみましょう。 ここで何が起こっているのでしょうか? 初期化子が動作しないのはなぜですか? 問題は、セッションとグラフの分離です。 get_variable の初期化属性を const_init_node に設定しましたが、これはグラフ内のノード間に新しい接続を追加するだけです。例外の原因となるようなことは何もしていません。変数ノードに関連付けられたメモリ (グラフではなくセッションに保存されます) はまだ「null」です。 const_init_node がセッション経由で変数を更新する必要があります。 これを行うには、別の特別なノード init = tf.global_variables_initializer() を追加します。 tf.assign() と同様に、これは副作用のあるノードです。 tf.assign() とは異なり、実際に入力を指定する必要はありません。tf.global_variables_initializer() は、グローバル グラフの作成時にそれを調べ、グラフ内のすべての tf.initializer に依存関係を自動的に追加します。 sess.run(init) を呼び出すと、各初期化子にジョブを終了するように指示し、変数を初期化して、sess.run(count_variable) を呼び出したときに問題が起こらないようにします。 変数の共有 共有され、スコープが定義され、reuse=True が設定されている変数を含む Tensorflow コードに遭遇することがあります。コード内で変数の共有を使用しないことを強くお勧めします。 1 つの変数を複数の場所で使用したい場合は、その変数ノードへのポインターを使用して、必要な場所で使用します。つまり、メモリに保持する予定の各パラメータに対して tf.get_variable() を 1 回だけ呼び出す必要があります。 オプティマイザ ***: 本格的なディープラーニングを始めましょう! まだ読んでいるなら、残りの概念はあなたにとってかなり簡単なはずです。 ディープラーニングでは、典型的な「内部ループ」トレーニングは次のようになります。
すべてをスクリプトにまとめて、単純な線形回帰問題を解いてみましょう。 ご覧のとおり、損失は本質的に変化しておらず、実際のパラメータの非常に正確な推定値が得られています。このコードのうち、新しいのは 1 行か 2 行だけです。 Tensorflow の基本的な概念を十分に理解できたので、このコードは非常にわかりやすいはずです。行 ***、optimizer = tf.train.GradientDescentOptimizer(1e-3) は、グラフにノードを追加しません。いくつかの便利な関数を含む Python オブジェクトを作成するだけです。 2 行目の train_op = optimizer.minimize(loss) は、グラフにノードを追加し、train_op へのポインターを割り当てます。 train_op ノードには出力はありませんが、非常に複雑な副作用があります。 train_op は入力の計算パスをたどり、変数ノードを探します。見つかった変数ノードごとに、損失に対する変数の勾配を計算します。次に、その変数の新しい値(現在の値から勾配を引いて学習率を掛けたもの)を計算します。 *** は、変数の値を更新するために代入操作を実行します。 基本的に、 sess.run(train_op) を呼び出すと、すべての変数に対して勾配降下法操作が実行されます。もちろん、入力プレースホルダーと出力プレースホルダーを埋めるために feed_dict を使用する必要があり、また、デバッグを容易にするために損失を印刷することも必要です。 tf.Print によるデバッグ Tensorflow でより複雑なことを実行し始めると、デバッグが必要になります。一般に、計算グラフで何が起こっているかを検査するのは困難です。印刷される値にはアクセスできないため、通常の Python print ステートメントは使用できません。印刷される値は sess.run() 呼び出し内でロックされています。たとえば、sess.run() を呼び出す前に、存在しない計算の中間値を検査するとします。ただし、sess.run() 呼び出しが戻ると、中間値は消えてしまいます。 簡単な例を見てみましょう。 結果は 5 であることがわかります。しかし、中間値 two_node と three_node を調べたい場合はどうすればよいでしょうか。中間値を調べる 1 つの方法は、調べたい中間ノードそれぞれを指す戻りパラメータを sess.run() に追加し、戻った後にそれを印刷することです。 これは通常は問題ありませんが、コードが複雑になると扱いにくくなる可能性があります。より便利な方法は、tf.Print ステートメントを使用することです。紛らわしいことに、tf.Print は実際には出力と副作用を持つ Tensorflow ノードです。これには、コピーするノードと印刷する項目のリストという 2 つの必須引数があります。 「コピーするノード」はグラフ内の任意のノードにすることができ、tf.Print は「コピーするノード」に関連付けられた識別操作です。つまり、入力のコピーを出力します。ただし、「印刷リスト」内のすべての値が印刷されるという副作用があります。 tf.Print に関する重要だがやや微妙な点: 印刷は実際には単なる副作用です。他のすべての副作用と同様に、計算が tf.Print ノードを通過する場合にのみ印刷が行われます。 tf.Print ノードが計算パスにない場合は何も印刷されません。 tf.Print ノードがコピーする元のノードが計算パス上にある場合でも、tf.Print ノード自体は計算パス上にない可能性があります。この問題に注意してください。この問題が発生すると、非常にイライラすることになり、問題が何であるかを把握するプロセスに苦労することになります。一般的には、コピーしたいノードを作成した直後に tf.Print ノードを作成します。 より実用的なデバッグのアドバイスを提供する優れたリソースがここにあります。 結論は この投稿が Tensorflow の仕組みや使い方をより深く理解する助けになれば幸いです。結局のところ、ここで紹介した概念はすべての Tensorflow プログラムにとって重要ですが、まだ表面をなぞっただけです。 Tensorflow を試してみると、条件文、反復処理、分散 Tensorflow、変数スコープ、モデルの保存と読み込み、マルチグラフ、マルチセッション、マルチコア データ ローダー キューなど、使用したいさまざまな興味深い機能に出会うかもしれません。 |
>>: パスワード危機: ディープラーニングがパスワードクラッキングを加速!
[[176432]] 【導入】ほとんどの科学研究では、大量の実験データの統計分析は、通常、コンピュー...
今年、AI分野では大規模言語モデル(LLM)が注目を浴びています。 LLM はさまざまな自然言語処理...
最新の3Dアニメーションをご覧になった方は、その壮大な世界に衝撃を受けるかもしれません。もしこれらの...
[[442468]]この記事では、PyTorch を使用して深層モデルをトレーニングするための最も労...
[[339720]]この記事はWeChatの公開アカウント「Uncle Who Knows a L...
3月10日、Appleは2022年春のカンファレンスで、M1 Maxチップのアップグレード版であるM...
世界の歴史は発明の歴史でもあります。火薬の発明は世界地図を変え、電灯の発明は夜を変え、車の発明は空間...
SF作家の劉慈欣はかつて、自身の小説の中でこのような天気予報を描写した。小説の主人公は気象大学を卒...
[[386219]]基本的な紹介キューは、配列またはリンク リストを使用して実装できる順序付きリス...
今日、世界は、パーソナライズされたエクスペリエンスを提供しながら、人間が重要な決定を下したり、重要な...
医療製造にロボット工学と自動化を導入したダヴィンチ ロボット手術システムが発売されてから 20 年が...
世界的なCOVID-19危機は依然として猛威を振るっていますが、一部の組織はすでに将来のパンデミック...
最新世代のスマートフォンに搭載されつつある 3D センサーは、機械学習によって解き放たれた写真撮影技...
著者 | 崔昊レビュー | Chonglouまとめこの記事では、大規模な言語モデルと AI ビデオ生...