トランスフォーマーのA-Z:知っておくべきすべてのこと
トランスフォーマーA-Z:必知の全て
トランスフォーマーについて知っておく必要があること、およびそれらの実装方法
なぜ新しいトランスフォーマーのチュートリアルが必要なのか?
おそらくすでにトランスフォーマーについて聞いたことがあり、誰もがそれについて話していますが、なぜ新しい記事を書く必要があるのでしょうか?
それは、私が研究者であり、それには使用するツールに非常に深い理解が必要だからです(なぜなら、それらを理解していなければ、どこが間違っているのかを特定し、それを改善する方法を見つけることはできませんよね?)。
私がトランスフォーマーの世界に深く入り込むにつれて、リソースの山に埋もれていく自分自身を見つけました。しかし、そのすべての読書にもかかわらず、私にはアーキテクチャの一般的な感覚といくつかの疑問が残りました。
このガイドでは、それらの知識のギャップを埋めることを目指しています。トランスフォーマーについての強力な直感、アーキテクチャへの深いダイブ、そしてゼロからの実装を提供するガイドです。
強くお勧めしますが、Githubのコードに従ってください:
awesome-ai-tutorials/NLP/007 – Transformers From Scratch at main ·…
The best collection of AI tutorials to make you a boss of Data Science! – awesome-ai-tutorials/NLP/007 – Transformers…
github.com
お楽しみください! 🤗
少しの歴史:
多くの人は、注目メカニズムの概念をGoogle Brainチームの著名な論文「Attention is All You Need」に帰する。ただし、これは物語の一部に過ぎない。
注目メカニズムの起源は、Dzmitry Bahdanau、KyungHyun Cho、およびYoshua Bengioによって著された「Neural Machine Translation by Jointly Learning to Align and Translate」という別の論文にまでさかのぼることができます。
Bahdanauの主な課題は、再帰ニューラルネットワーク(RNN)を使用して長文をベクトルにエンコードする際の制限に対処することでした。具体的には、RNNを使用する際には、重要な情報がしばしば失われることがありました。
翻訳演習からの類推を描きながら、BahdanauはRNN内の隠れ層に重みを割り当てることを目指しました。このアプローチは印象的な結果をもたらし、以下の図に示されています。
しかし、この問題に取り組んでいたのはBahdanauだけではありませんでした。Google Brainチームは彼の画期的な研究を参考にし、大胆なアイデアを提案しました:
「すべてを取り払い、注意機構に完全に焦点を当てるのはどうだろうか?」
彼らは、成功の主な要因はRNNではなく注意機構であると考えていました。
この確信が彼らの論文「Attention is All You Need」として結実しました。
おもしろいですね?
トランスフォーマーアーキテクチャ
最初に、埋め込み
この図はTransformerアーキテクチャを表しています。最初は何も理解できなくても心配しないでください、私たちは絶対にすべてを説明します。
テキストからベクトルへの変換:入力が単語のシーケンス、例えば「The cat drinks milk」であると想像してください。このシーケンスはseq_len
と呼ばれる長さを持っています。私たちの直接的な課題は、これらの単語をモデルが理解できる形式、具体的にはベクトルに変換することです。それがEmbedderが登場する理由です。
各単語はベクトルになるために変換されます。この変換プロセスは「埋め込み(embedding)」と呼ばれます。これらのベクトルまたは「埋め込み」のそれぞれのサイズはd_model = 512
です。
では、このEmbedderとは具体的に何でしょうか?その中核は、E
と示される線形マッピング(行列)です。これはサイズが(d_model, vocab_size)
である行列として視覚化することができます。ここでvocab_size
は私たちの語彙のサイズです。
埋め込みプロセスの後、私たちはサイズがd_model
のベクトルのコレクションを得ます。このフォーマットを理解することは重要です。なぜなら、エンコーダーの入力、エンコーダーの出力など、さまざまな段階でこれを見ることがあるからです。
では、この部分をコーディングしましょう:
class Embeddings(nn.Module): def __init__(self, d_model, vocab): super(Embeddings, self).__init__() self.lut = nn.Embedding(vocab, d_model) self.d_model = d_model def forward(self, x): return self.lut(x) * math.sqrt(self.d_model)
注:正規化の目的でd_modelを掛けています(後で説明します)
注2:個人的に、事前学習済みの埋め込み器を使用するか、少なくとも事前学習済みの埋め込み器から始めて微調整すべきかと思いました。しかし、いいえ、埋め込みは完全にゼロから学習され、ランダムに初期化されます。
位置エンコーディング
なぜ位置エンコーディングが必要なのでしょうか?
現在のセットアップでは、単語を表すベクトルのリストを持っています。Transformerモデルにそのまま与えると、単語の順序というキーの要素が欠けます。自然言語では、単語の意味はしばしばその位置から派生しています。”John loves Mary”という文は”Mary loves John”とは異なる感情を持ちます。この順序をモデルが捉えるために、位置エンコーディングを導入します。
では、「最初の単語に+1、2番目に+2、などの単純な増加」をただ追加すれば良いのではないかと思うかもしれません。このアプローチにはいくつかの課題があります:
- 多次元性:各トークンは512次元で表現されます。単なる増加ではこの複雑な空間を捉えるのに十分ではありません。
- 正規化の考慮事項:値が-1から1の間にあることを理想的には望んでいます。そのため、大きな数値(例えば長いテキストに対して+2000)を直接追加することは問題があります。
- シーケンスの長さ依存性:直接的な増加を使用すると、スケーリングに対応していません。テキストが長い場合、位置が+5000になることがありますが、この数値はそのトークンの関連する文での相対的な位置を真に反映していません。そして、単語の意味は、テキスト内の絶対的な位置よりも文内の相対的な位置により依存しています。
数学を学んだことがあるなら、円座標のアイデア-具体的には正弦と余弦関数-が直感に共鳴するはずです。これらの関数は、我々のニーズに応える独特な位置エンコーディングの方法を提供します。
サイズが(seq_len, d_model)
の行列と、同じサイズの位置エンコーディングという別の行列を加えることを目指しています。
ここが重要な概念です:
- 各トークンについて、著者はペアワイズ次元(2k)に正弦座標、および(2k+1)に余弦座標を与えることを提案しています。
- トークンの位置を固定し、次元を変更すると正弦/余弦が周波数的に減少することが分かります。
- テキスト内のさらに後ろの位置にあるトークンを見ると、この現象がより迅速に起こります(周波数が増加します)。
これは以下のグラフでも要約されていますが(ただし、頭を悩ませすぎないでください)、位置符号化はトランスフォーマーが文章内のトークンの順序を把握するための数学関数です。これは非常に活発な研究領域です。
class PositionalEncoding(nn.Module): "PE関数を実装する。" def __init__(self, d_model, dropout, max_len=5000): super(PositionalEncoding, self).__init__() self.dropout = nn.Dropout(p=dropout) # 位置符号化を対数空間で一度計算する。 pe = torch.zeros(max_len, d_model) position = torch.arange(0, max_len).unsqueeze(1) div_term = torch.exp( torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model) ) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term) pe = pe.unsqueeze(0) self.register_buffer("pe", pe) def forward(self, x): x = x + self.pe[:, : x.size(1)].requires_grad_(False) return self.dropout(x)
注意機構(シングルヘッド)
Googleの論文の核心概念である注意機構について深く掘り下げてみましょう。
高いレベルの直感:
注意機構は、ベクトル/トークン間のコミュニケーションメカニズムです。モデルが出力を生成する際に、入力の特定の部分に焦点を当てることができます。入力データの一部にスポットライトを当てることをイメージしてください。この「スポットライト」は、関連性の高い部分により明るくなり(それらに注目を向け)、関連性の低い部分には暗くなります。
文では、注意は単語間の関係を決定するのに役立ちます。文内のいくつかの単語は、意味や機能上で密接に関連しており、他の単語とはそうではありません。注意機構はこれらの関係を定量化します。
例:
文「彼女は彼に彼女の本を渡しました。」を考えてみましょう。
もし「彼女」という単語に焦点を当てると、注意機構は以下のことを判断するかもしれません:
- 「彼女」と「本」とは強い関連性を持っている。なぜなら、「彼女」は「本」を所有していることを示しているからです。
- 「彼女」と「彼」はVoAGIの関連性を持っている。なぜなら、「彼女」と「彼」はおそらく同じものを参照しているからです。
- 「与えた」や「彼」などの他の単語とは関連性が弱い。
注意機構の技術的な詳細
各トークンについて、3つのベクトルを生成します:
- クエリ(Q):
直感: クエリをトークンが提示する「質問」と考えてください。現在の単語を表し、シーケンスのどの部分がそれに関連しているかを特定しようとします。
2. キー(K):
直感: キーは、シーケンス内の各単語の「識別子」と考えることができます。クエリが「質問」をする際に、キーはシーケンス内の各単語がクエリに対してどれだけ関連しているかを「回答する」のに役立ちます。
3. 値(V):
直感:キーを通じた各単語のクエリに対する関連性が確定すると、現在のトークンの補助にはそれらの単語からの実際の情報やコンテンツが必要です。この時、値が重要です。 それは各単語の内容を表しています。
Q、K、Vはどのように生成されるのですか?
クエリとキーの類似度は、2つのベクトル間の類似度を測定するドット積によって計算され、このランダム変数の標準偏差で除算され、すべてを正規化します。
例を挙げて説明しましょう:
1つのクエリがあり、KとVのアテンションの結果がわかるようにします:
さて、q1とキーの間の類似度を計算しましょう:
数値の3/2と1/8は比較的近いように見えるかもしれませんが、ソフトマックス関数の指数的な特性により、その違いが増幅されます。
この差は、q1がk1との関連性がk2よりも顕著であることを示しています。
さて、アテンションの結果を見てみましょう。それは値(アテンションの重み)の組み合わせです
素晴らしい!この操作をすべてのトークン(q1からqn)に対して繰り返すと、n個のベクトルの集合が得られます。
実際には、この操作は効率的に行うために行列の積によってベクトル化されます。
さあ、コードにしてみましょう:
def attention(query, key, value, mask=None, dropout=None): "スケーリング済みドット積アテンションを計算する" d_k = query.size(-1) scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k) if mask is not None: scores = scores.masked_fill(mask == 0, -1e9) p_attn = scores.softmax(dim=-1) if dropout is not None: p_attn = dropout(p_attn) return torch.matmul(p_attn, value), p_attn
マルチヘッドアテンション
シングルヘッドアテンションの問題は何ですか?
シングルヘッドアテンションのアプローチでは、各トークンはたった1つのクエリを提出します。これにより、通常、softmaxが1つの値を重要視し、他の値をほぼゼロに近づけてしまう傾向があるため、1つの他のトークンとの強い関係が生じます。しかし、言語と文章の構造を考えると、単語単位で複数の他の単語との関連があることが一般的です。
この制限に取り組むために、マルチヘッドアテンションを紹介します。コアのアイデアは何でしょうか?各トークンに対して「h」回のアテンションプロセスを並列に実行することで、複数の質問(クエリ)を同時に行えるようにします。元のトランスフォーマーでは8つのヘッドが使用されています。
8つのヘッドの結果を取得したら、それらを行列に連結します。
これもコード化するのは簡単ですが、次元に注意が必要です:
class MultiHeadedAttention(nn.Module): def __init__(self, h, d_model, dropout=0.1): "モデルのサイズとヘッド数を受け取ります。" super(MultiHeadedAttention, self).__init__() assert d_model % h == 0 # d_vは常にd_kと等しいと仮定します self.d_k = d_model // h self.h = h self.linears = clones(nn.Linear(d_model, d_model), 4) self.attn = None self.dropout = nn.Dropout(p=dropout) def forward(self, query, key, value, mask=None): "Figure 2を実装します" if mask is not None: # 各ヘッドに同じマスクを適用します mask = mask.unsqueeze(1) nbatches = query.size(0) # 1) バッチでのすべての線形射影を行います(d_model => h x d_k) query, key, value = [ lin(x).view(nbatches, -1, self.h, self.d_k).transpose(1, 2) for lin, x in zip(self.linears, (query, key, value)) ] # 2) バッチ内のすべての射影ベクトルにアテンションを適用します x, self.attn = attention(query, key, value, mask=mask, dropout=self.dropout) # 3) ビューを使用して「連結」し、最終的な線形を適用します x = x.transpose(1, 2).contiguous().view(nbatches, -1, self.h * self.d_k) del query del key del value return self.linears[-1](x)
トランスフォーマーが非常に強力である理由が分かり始めたかと思います。それらは並列処理を最大限に利用しています。
トランスフォーマーの要素の組み立て
高レベルでは、トランスフォーマーは3つの要素の組み合わせです:エンコーダ、デコーダ、ジェネレータ
1. エンコーダ
- 目的:入力シーケンスを新しいシーケンス(通常は小さい次元)に変換し、元のデータの本質を捉えます。
- 注意:BERTモデルについて聞いたことがある場合、それはトランスフォーマーのこのエンコーディング部分を使用しています。
2. デコーダ
- 目的:エンコーダからのエンコードされたシーケンスを使用して、出力シーケンスを生成します。
- 注意:トランスフォーマーのデコーダは、通常のオートエンコーダのデコーダとは異なります。トランスフォーマーでは、デコーダはエンコーディングされた出力だけでなく、これまで生成したトークンも考慮します。
3. ジェネレータ
- 目的:ベクトルをトークンに変換します。これは、ベクトルをボキャブラリのサイズに投影し、softmax関数で最も確率の高いトークンを選ぶことによって行います。
それをコード化しましょう:
class EncoderDecoder(nn.Module): """ 標準的なエンコーダデコーダアーキテクチャ。このモデル自体や他の多くのモデルの基礎となります。 """ def __init__(self, encoder, decoder, src_embed, tgt_embed, generator): super(EncoderDecoder, self).__init__() self.encoder = encoder self.decoder = decoder self.src_embed = src_embed self.tgt_embed = tgt_embed self.generator = generator def forward(self, src, tgt, src_mask, tgt_mask): "マスクされたソースおよびターゲットのシーケンスを受け取り、処理します。" return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask) def encode(self, src, src_mask): return self.encoder(self.src_embed(src), src_mask) def decode(self, memory, src_mask, tgt, tgt_mask): return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)class Generator(nn.Module): "標準的な線形+ソフトマックスの生成ステップを定義します。" def __init__(self, d_model, vocab): super(Generator, self).__init__() self.proj = nn.Linear(d_model, vocab) def forward(self, x): return log_softmax(self.proj(x), dim=-1)
ここでの注意点として、「src」は入力シーケンスを指し、「target」は生成されるシーケンスを指します。出力は自己回帰的にトークンごとに生成されるため、ターゲットシーケンスも追跡する必要があることを忘れないでください。
エンコーダの積み重ね
Transformerのエンコーダは単一のレイヤーではありません。実際にはN層のスタックです。具体的には:
- 元のTransformerモデルのエンコーダは、N=6個の同一のレイヤーから構成されています。
エンコーダレイヤ内部では、非常に類似した2つのサブレイヤーブロックが見られます((1)と(2)):「残差接続」に続く「レイヤーノルム」です。
- ブロック(1):セルフアテンションメカニズム: エンコーダが入力時に異なる単語に焦点を当てるのに役立ちます。
- ブロック(2):フィードフォワードニューラルネットワーク: 各位置に独立に適用される小さなニューラルネットワークです。
では、それをコード化しましょう:
まず、SublayerConnection:
全般的なアーキテクチャに従い、「sublayer」を「self-attention」または「FFN」に変更できます。
class SublayerConnection(nn.Module): """ 残差接続に続くレイヤーノルム。 コードのシンプルさのために、最初ではなく最後に正規化が行われます。 """ def __init__(self, size, dropout): super(SublayerConnection, self).__init__() self.norm = nn.LayerNorm(size) # PyTorchのLayerNormを使用 self.dropout = nn.Dropout(dropout) def forward(self, x, sublayer): "同じサイズの任意のサブレイヤーに対して残差接続を適用します。" return x + self.dropout(sublayer(self.norm(x)))
次に完全なエンコーダレイヤを定義できます:
class EncoderLayer(nn.Module): "エンコーダはセルフアテンションとフィードフォワードで構成されます(以下で定義)" def __init__(self, size, self_attn, feed_forward, dropout): super(EncoderLayer, self).__init__() self.self_attn = self_attn self.feed_forward = feed_forward self.sublayer = clones(SublayerConnection(size, dropout), 2) self.size = size def forward(self, x, mask): # self-attention、ブロック1 x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, mask)) # フィードフォワード、ブロック2 x = self.sublayer[1](x, self.feed_forward) return x
エンコーダレイヤができました。これをチェーンして完全なエンコーダを構築しましょう:
def clones(module, N): "N個の同一のレイヤーを作成します。" return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])class Encoder(nn.Module): "コアエンコーダはN層のスタックです" def __init__(self, layer, N): super(Encoder, self).__init__() self.layers = clones(layer, N) self.norm = nn.LayerNorm(layer.size) def forward(self, x, mask): "入力(およびマスク)を各層に渡します。" for layer in self.layers: x = layer(x, mask) return self.norm(x)
デコーダ
デコーダは、エンコーダと同様に、複数の同じレイヤーが上に積み重ねられた構造を持っています。これらのレイヤーの数は、通常、元のトランスフォーマーモデルでは6です。
デコーダはエンコーダとどう違うのでしょうか?
デコーダには、エンコーダとの相互作用に追加のサブレイヤーがあります。これがクロスアテンションです。
- サブレイヤー(1)はエンコーダと同じです。これはセルフアテンションメカニズムで、デコーダに入力されたトークンからすべて(Q、K、V)を生成します。
- サブレイヤー(2)は新しい通信メカニズムで、クロスアテンションと呼ばれます。これは、(1)の出力を使用してクエリを生成し、エンコーダの出力を使用してキーと値(K、V)を生成します。言い換えれば、文を生成するためには、デコーダによってこれまでに生成されたもの(セルフアテンション)と最初にエンコーダに尋ねた内容(クロスアテンション)の両方を見る必要があります。
- サブレイヤー(3)はエンコーダと同じです。
さて、デコーダレイヤーをコード化しましょう。エンコーダレイヤーのメカニズムを理解しているなら、これは非常に簡単でしょう。
class DecoderLayer(nn.Module): "デコーダはセルフアテンション、ソースアテンション、およびフィードフォワードで構成されています" def __init__(self, size, self_attn, src_attn, feed_forward, dropout): super(DecoderLayer, self).__init__() self.size = size self.self_attn = self_attn self.src_attn = src_attn self.feed_forward = feed_forward self.sublayer = clones(SublayerConnection(size, dropout), 3) def forward(self, x, memory, src_mask, tgt_mask): "右に示されている図1の接続に従います。" m = memory x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, tgt_mask)) # 新しいサブレイヤー(クロスアテンション) x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, src_mask)) return self.sublayer[2](x, self.feed_forward)
そして、N=6のデコーダレイヤーをチェーンしてデコーダを形成できます:
class Decoder(nn.Module): "マスキングされた一般的なNレイヤーデコーダ" def __init__(self, layer, N): super(Decoder, self).__init__() self.layers = clones(layer, N) self.norm = nn.LayerNorm(layer.size) def forward(self, x, memory, src_mask, tgt_mask): for layer in self.layers: x = layer(x, memory, src_mask, tgt_mask) return self.norm(x)
この時点で、Transformerの90%以上を理解しています。まだいくつかの詳細があります:
トランスフォーマーモデルの詳細
パディング:
- 通常のトランスフォーマーでは、シーケンスの最大長があります(たとえば、「max_len=5000」)。これは、モデルが処理できる最長のシーケンスを定義します。
- ただし、現実の文は長さが異なる場合があります。短い文を処理するために、パディングを使用します。
- パディングは、すべてのバッチ内のシーケンスを同じ長さにするために特別な「パディングトークン」を追加することです。
マスキング
マスキングは、注意計算中に特定のトークンを無視することを保証します。
マスキングの2つのシナリオ:
- src_masking: シーケンスにパディングトークンを追加したため、モデルがこれらの意味を持たないトークンに注意を払う必要はありません。したがって、これらをマスクアウトします。
- tgt_maskingまたは先読み/因果的マスキング:デコーダでは、トークンを逐次的に生成する際に、各トークンは前のトークンにのみ影響を受けるべきであり、将来のトークンには影響を受けないべきです。たとえば、文の5番目の単語を生成する際、6番目の単語については知りません。これにより、トークンの逐次的な生成が保証されます。
その後、対応するトークンを無視するためにこのマスクをマイナス無限に追加します。この例が事情を明確にするはずです:
FFN: フィードフォワードネットワーク
- Transformerの図にある “フィードフォワード” レイヤーは少し誤解を招くかもしれません。これは単なる1つの操作ではなく、それらのシーケンスです。
- FFNには2つの線形層があります。興味深いことに、入力データは最初に
d_model=512
の次元からより高い次元のd_ff=2048
に変換され、その後元の次元 (d_model=512
) にマッピングされます。 - これは、データが操作の途中で「展開」され、元のサイズに「圧縮」されるイメージとして可視化できます。
これは簡単にコード化できます:
class PositionwiseFeedForward(nn.Module): "FFN式を実装します。" def __init__(self, d_model, d_ff, dropout=0.1): super(PositionwiseFeedForward, self).__init__() self.w_1 = nn.Linear(d_model, d_ff) self.w_2 = nn.Linear(d_ff, d_model) self.dropout = nn.Dropout(dropout) def forward(self, x): return self.w_2(self.dropout(self.w_1(x).relu()))
結論
Transformerモデルの無類の成功と人気は、いくつかの主要な要素に帰せられます:
- 柔軟性。 Transformersは任意のベクトルのシーケンスで動作することができます。これらのベクトルは単語の埋め込みかもしれません。これは、画像を異なるパッチに変換し、パッチをベクトルに展開することによってコンピュータビジョンにも適用できます。また、オーディオでは、音声を異なるパーツに分割し、ベクトル化することができます。
- 汎用性:最小限の帰納バイアスで、Transformerはデータの複雑なパターンを捉え、より良く学習して一般化することができます。
- 速度と効率:GPUの膨大な計算能力を活用し、Transformerは並列処理を設計されています。
読んでいただきありがとうございます!退場する前に:
私のTransformer Githubリポジトリで実験を実行できます。
もっと素晴らしいチュートリアルは、私のAIチュートリアルのコンピレーションをGithubでチェックしてください。
GitHub — FrancoisPorcher/awesome-ai-tutorials: AIチュートリアルの最高のコレクションであなたを…
データサイエンスのボスになるためのAIチュートリアルの最高のコレクション! — GitHub …
github.com
私の記事をメールで受け取ることができます。 こちらで購読 してください。
VoAGIのプレミアム記事にアクセスしたい場合は、月額$5のメンバーシップが必要です。私のリンクで 登録すると、追加料金なしで料金の一部をサポートしていただけます。
この記事が有益で参考になった場合は、私をフォローしてもっと詳しいコンテンツを提供するために拍手をしていただけると嬉しいです!あなたのサポートは、私たちの共通理解を助けるコンテンツの制作を続けるのに役立ちます。
参考文献
- Attention is all you need
- The Annotated Transformer(コードの大部分は、彼らのブログ投稿からインスピレーションを得ています)
- Andrej Karpathy Stanford 講義
更に進むには
包括的なガイドがあっても、Transformersに関連する他の多くの領域があります。以下は、探索したいいくつかのアイデアです:
- 位置エンコーディング:「相対位置エンコーディング」と「Rotary Positional Embedding (RoPE)」のチェックをお勧めします
- レイヤー正規化、およびバッチ正規化、グループ正規化との違い
- 残差接続と、グラデーションのスムージングへの影響
- BERT(Roberta、ELECTRA、Camembert)への改良
- 大型モデルの蒸留(小型モデルへの圧縮)
- Transformersの他の領域(主にビジョンとオーディオ)での応用
- Transformersとグラフニューラルネットワークの関連
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
- Note This translation conveys the same meaning as the original English phrase, which refers to going from a state of poverty to wealth.
- 「GitHubツールでデータサイエンスプロジェクトをスーパーチャージングする」
- 「生成AIで企業検索を変革する」
- データの宇宙をマスターする:繁栄するデータサイエンスのキャリアへの鍵となる手順
- 「LlamaIndex:カスタムデータで簡単にLLMアプリケーションを強化する」
- 「不確実な未来を航行するための仮説指向シミュレーション」
- 「ダイナミックプライシングを活用して収益を最適化する方法は?」