724 lines
30 KiB
Plaintext
724 lines
30 KiB
Plaintext
{
|
||
"cells": [
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 埋め込み\n",
|
||
"\n",
|
||
"前回の例では、`vocab_size`の長さを持つ高次元のバッグオブワードベクトルを操作し、低次元の位置表現ベクトルからスパースなワンホット表現に明示的に変換していました。このワンホット表現はメモリ効率が良くない上に、各単語が互いに独立して扱われます。つまり、ワンホットエンコードされたベクトルは単語間の意味的な類似性を表現しません。\n",
|
||
"\n",
|
||
"このユニットでは、**News AG**データセットを引き続き探求します。まず、データをロードし、前のノートブックからいくつかの定義を取得しましょう。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 1,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Loading dataset...\n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\train.csv: 29.5MB [00:01, 18.8MB/s] \n",
|
||
"d:\\WORK\\ai-for-beginners\\5-NLP\\14-Embeddings\\data\\test.csv: 1.86MB [00:00, 11.2MB/s] \n"
|
||
]
|
||
},
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Building vocab...\n",
|
||
"Vocab size = 95812\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"import torch\n",
|
||
"import torchtext\n",
|
||
"import numpy as np\n",
|
||
"from torchnlp import *\n",
|
||
"train_dataset, test_dataset, classes, vocab = load_dataset()\n",
|
||
"vocab_size = len(vocab)\n",
|
||
"print(\"Vocab size = \",vocab_size)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 埋め込みとは?\n",
|
||
"\n",
|
||
"**埋め込み**のアイデアは、単語を低次元の密なベクトルで表現し、その単語の意味を何らかの形で反映させることです。後ほど意味のある単語埋め込みを構築する方法について説明しますが、今は単語ベクトルの次元を下げる方法として埋め込みを考えてみましょう。\n",
|
||
"\n",
|
||
"埋め込み層は単語を入力として受け取り、指定された`embedding_size`の出力ベクトルを生成します。ある意味では`Linear`層に非常に似ていますが、ワンホットエンコードされたベクトルを受け取る代わりに、単語番号を入力として受け取ることができます。\n",
|
||
"\n",
|
||
"ネットワークの最初の層として埋め込み層を使用することで、バッグオブワードモデルから**埋め込みバッグ**モデルに切り替えることができます。このモデルでは、まずテキスト内の各単語を対応する埋め込みに変換し、それらの埋め込み全体に対して`sum`、`average`、`max`などの集約関数を計算します。\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"私たちの分類器ニューラルネットワークは、埋め込み層、集約層、そしてその上に線形分類器を持つ構造になります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 2,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.Embedding(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, x):\n",
|
||
" x = self.embedding(x)\n",
|
||
" x = torch.mean(x,dim=1)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 可変なシーケンスサイズへの対応\n",
|
||
"\n",
|
||
"このアーキテクチャの結果として、ネットワークに渡すミニバッチを特定の方法で作成する必要があります。前のユニットでは、Bag-of-Words(BoW)を使用している際、ミニバッチ内のすべてのBoWテンソルはテキストシーケンスの実際の長さに関係なく、`vocab_size`という同じサイズを持っていました。しかし、単語埋め込み(word embeddings)に移行すると、各テキストサンプル内の単語数が可変となり、それらのサンプルをミニバッチにまとめる際にはパディングを適用する必要があります。\n",
|
||
"\n",
|
||
"これを実現するには、データソースに`collate_fn`関数を提供するという同じ手法を使用します。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 3,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def padify(b):\n",
|
||
" # b is the list of tuples of length batch_size\n",
|
||
" # - first element of a tuple = label, \n",
|
||
" # - second = feature (text sequence)\n",
|
||
" # build vectorized sequence\n",
|
||
" v = [encode(x[1]) for x in b]\n",
|
||
" # first, compute max length of a sequence in this minibatch\n",
|
||
" l = max(map(len,v))\n",
|
||
" return ( # tuple of two tensors - labels and features\n",
|
||
" torch.LongTensor([t[0]-1 for t in b]),\n",
|
||
" torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v])\n",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### 埋め込み分類器のトレーニング\n",
|
||
"\n",
|
||
"適切なデータローダーを定義したので、前のユニットで定義したトレーニング関数を使ってモデルをトレーニングすることができます:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6415625\n",
|
||
"6400: acc=0.6865625\n",
|
||
"9600: acc=0.7103125\n",
|
||
"12800: acc=0.726953125\n",
|
||
"16000: acc=0.739375\n",
|
||
"19200: acc=0.75046875\n",
|
||
"22400: acc=0.7572321428571429\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(0.889799795315499, 0.7623160588611644)"
|
||
]
|
||
},
|
||
"execution_count": 4,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"train_epoch(net,train_loader, lr=1, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"> **注意**: 時間の都合上、ここでは25,000件のレコード(1エポック未満)のみをトレーニングしていますが、トレーニングを続けたり、複数のエポックをトレーニングする関数を書いたり、学習率のパラメータを調整して精度を向上させることができます。約90%の精度に到達することが可能なはずです。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"### EmbeddingBagレイヤーと可変長シーケンスの表現\n",
|
||
"\n",
|
||
"以前のアーキテクチャでは、すべてのシーケンスを同じ長さにパディングしてミニバッチに収める必要がありました。しかし、これは可変長シーケンスを表現する最も効率的な方法ではありません。別のアプローチとして、**オフセット**ベクトルを使用する方法があります。このベクトルは、1つの大きなベクトルに格納されたすべてのシーケンスのオフセットを保持します。\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"> **Note**: 上の図では文字のシーケンスを示していますが、この例では単語のシーケンスを扱っています。ただし、オフセットベクトルでシーケンスを表現するという基本的な原則は同じです。\n",
|
||
"\n",
|
||
"オフセット表現を扱うために、[`EmbeddingBag`](https://pytorch.org/docs/stable/generated/torch.nn.EmbeddingBag.html)レイヤーを使用します。このレイヤーは`Embedding`に似ていますが、コンテンツベクトルとオフセットベクトルを入力として受け取り、さらに平均化レイヤーを含みます。この平均化レイヤーは`mean`、`sum`、または`max`を選択できます。\n",
|
||
"\n",
|
||
"以下は、`EmbeddingBag`を使用するように修正されたネットワークの例です:\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 5,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"class EmbedClassifier(torch.nn.Module):\n",
|
||
" def __init__(self, vocab_size, embed_dim, num_class):\n",
|
||
" super().__init__()\n",
|
||
" self.embedding = torch.nn.EmbeddingBag(vocab_size, embed_dim)\n",
|
||
" self.fc = torch.nn.Linear(embed_dim, num_class)\n",
|
||
"\n",
|
||
" def forward(self, text, off):\n",
|
||
" x = self.embedding(text, off)\n",
|
||
" return self.fc(x)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"トレーニング用のデータセットを準備するために、オフセットベクトルを準備する変換関数を提供する必要があります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 6,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1])) for t in b]\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )\n",
|
||
"\n",
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"前回の例とは異なり、今回のネットワークは2つのパラメータを受け取ります。データベクトルとオフセットベクトルで、それぞれサイズが異なります。同様に、データローダーも2つではなく3つの値を提供します。テキストベクトルとオフセットベクトルの両方が特徴として提供されます。そのため、これに対応するためにトレーニング関数を少し調整する必要があります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6153125\n",
|
||
"6400: acc=0.6615625\n",
|
||
"9600: acc=0.6932291666666667\n",
|
||
"12800: acc=0.715078125\n",
|
||
"16000: acc=0.7270625\n",
|
||
"19200: acc=0.7382291666666667\n",
|
||
"22400: acc=0.7486160714285715\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(22.771553103007037, 0.7551983365323096)"
|
||
]
|
||
},
|
||
"execution_count": 7,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"net = EmbedClassifier(vocab_size,32,len(classes)).to(device)\n",
|
||
"\n",
|
||
"def train_epoch_emb(net,dataloader,lr=0.01,optimizer=None,loss_fn = torch.nn.CrossEntropyLoss(),epoch_size=None, report_freq=200):\n",
|
||
" optimizer = optimizer or torch.optim.Adam(net.parameters(),lr=lr)\n",
|
||
" loss_fn = loss_fn.to(device)\n",
|
||
" net.train()\n",
|
||
" total_loss,acc,count,i = 0,0,0,0\n",
|
||
" for labels,text,off in dataloader:\n",
|
||
" optimizer.zero_grad()\n",
|
||
" labels,text,off = labels.to(device), text.to(device), off.to(device)\n",
|
||
" out = net(text, off)\n",
|
||
" loss = loss_fn(out,labels) #cross_entropy(out,labels)\n",
|
||
" loss.backward()\n",
|
||
" optimizer.step()\n",
|
||
" total_loss+=loss\n",
|
||
" _,predicted = torch.max(out,1)\n",
|
||
" acc+=(predicted==labels).sum()\n",
|
||
" count+=len(labels)\n",
|
||
" i+=1\n",
|
||
" if i%report_freq==0:\n",
|
||
" print(f\"{count}: acc={acc.item()/count}\")\n",
|
||
" if epoch_size and count>epoch_size:\n",
|
||
" break\n",
|
||
" return total_loss.item()/count, acc.item()/count\n",
|
||
"\n",
|
||
"\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## セマンティック埋め込み: Word2Vec\n",
|
||
"\n",
|
||
"前回の例では、モデルの埋め込み層が単語をベクトル表現にマッピングする方法を学びましたが、この表現にはあまり意味的な要素がありませんでした。似たような単語や同義語が、あるベクトル距離(例えばユークリッド距離)に基づいて互いに近いベクトルに対応するようなベクトル表現を学ぶことができれば便利です。\n",
|
||
"\n",
|
||
"そのためには、特定の方法で大量のテキストコレクションを使って埋め込みモデルを事前学習する必要があります。セマンティック埋め込みを学習する最初の方法の一つが [Word2Vec](https://en.wikipedia.org/wiki/Word2vec) と呼ばれるものです。これは、単語の分散表現を生成するために使用される2つの主要なアーキテクチャに基づいています:\n",
|
||
"\n",
|
||
" - **連続型バッグオブワーズ** (CBoW) — このアーキテクチャでは、周囲のコンテキストから単語を予測するようにモデルを訓練します。ngram $(W_{-2},W_{-1},W_0,W_1,W_2)$ が与えられた場合、モデルの目標は $(W_{-2},W_{-1},W_1,W_2)$ から $W_0$ を予測することです。\n",
|
||
" - **連続型スキップグラム** — CBoWとは逆のアプローチです。このモデルは、周囲のコンテキスト単語のウィンドウを使用して現在の単語を予測します。\n",
|
||
"\n",
|
||
"CBoWは高速ですが、スキップグラムは遅いものの、頻度の低い単語をより良く表現することができます。\n",
|
||
"\n",
|
||
"\n",
|
||
"\n",
|
||
"Google Newsデータセットで事前学習されたWord2Vec埋め込みを試すには、**gensim** ライブラリを使用することができます。以下は「neural」に最も類似した単語を見つける例です。\n",
|
||
"\n",
|
||
"> **Note:** 初めて単語ベクトルを作成する際、ダウンロードに時間がかかる場合があります!\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 8,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"import gensim.downloader as api\n",
|
||
"w2v = api.load('word2vec-google-news-300')"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 9,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"neuronal -> 0.7804799675941467\n",
|
||
"neurons -> 0.7326500415802002\n",
|
||
"neural_circuits -> 0.7252851724624634\n",
|
||
"neuron -> 0.7174385190010071\n",
|
||
"cortical -> 0.6941086649894714\n",
|
||
"brain_circuitry -> 0.6923246383666992\n",
|
||
"synaptic -> 0.6699118614196777\n",
|
||
"neural_circuitry -> 0.6638563275337219\n",
|
||
"neurochemical -> 0.6555314064025879\n",
|
||
"neuronal_activity -> 0.6531826257705688\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"for w,p in w2v.most_similar('neural'):\n",
|
||
" print(f\"{w} -> {p}\")"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"単語からベクトル埋め込みを計算し、分類モデルの訓練に使用することもできます(明確にするためにベクトルの最初の20成分のみを表示します):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"array([ 0.01226807, 0.06225586, 0.10693359, 0.05810547, 0.23828125,\n",
|
||
" 0.03686523, 0.05151367, -0.20703125, 0.01989746, 0.10058594,\n",
|
||
" -0.03759766, -0.1015625 , -0.15820312, -0.08105469, -0.0390625 ,\n",
|
||
" -0.05053711, 0.16015625, 0.2578125 , 0.10058594, -0.25976562],\n",
|
||
" dtype=float32)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.word_vec('play')[:20]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"セマンティカル埋め込みの素晴らしい点は、ベクトルエンコーディングを操作して意味を変更できることです。例えば、*king* と *woman* にできるだけ近く、*man* からできるだけ遠いベクトル表現を持つ単語を見つけるように求めることができます。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"('queen', 0.7118192911148071)"
|
||
]
|
||
},
|
||
"execution_count": 10,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"w2v.most_similar(positive=['king','woman'],negative=['man'])[0]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"CBoWとSkip-Gramsはどちらも「予測型」の埋め込みであり、ローカルな文脈のみを考慮します。Word2Vecはグローバルな文脈を活用しません。\n",
|
||
"\n",
|
||
"**FastText**は、Word2Vecを基盤にして、各単語とその中に含まれる文字n-gramのベクトル表現を学習します。これらの表現の値は、各トレーニングステップで1つのベクトルに平均化されます。このプロセスは事前学習に多くの追加計算を必要としますが、単語埋め込みがサブワード情報をエンコードできるようにします。\n",
|
||
"\n",
|
||
"別の手法である**GloVe**は、共起行列のアイデアを活用し、ニューラル手法を用いて共起行列をより表現力があり非線形な単語ベクトルに分解します。\n",
|
||
"\n",
|
||
"gensimは複数の単語埋め込みモデルをサポートしているため、埋め込みをFastTextやGloVeに変更して例を試すことができます。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## PyTorchで事前学習済みの埋め込みを使用する\n",
|
||
"\n",
|
||
"上記の例を修正して、埋め込み層の行列をWord2Vecのような意味的な埋め込みで事前に初期化することができます。事前学習済みの埋め込みの語彙と私たちのテキストコーパスの語彙が一致しない可能性が高いため、欠けている単語の重みはランダムな値で初期化する必要があります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 11,
|
||
"metadata": {
|
||
"tags": []
|
||
},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"Embedding size: 300\n",
|
||
"Populating matrix, this will take some time...Done, found 41080 words, 54732 words missing\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"embed_size = len(w2v.get_vector('hello'))\n",
|
||
"print(f'Embedding size: {embed_size}')\n",
|
||
"\n",
|
||
"net = EmbedClassifier(vocab_size,embed_size,len(classes))\n",
|
||
"\n",
|
||
"print('Populating matrix, this will take some time...',end='')\n",
|
||
"found, not_found = 0,0\n",
|
||
"for i,w in enumerate(vocab.get_itos()):\n",
|
||
" try:\n",
|
||
" net.embedding.weight[i].data = torch.tensor(w2v.get_vector(w))\n",
|
||
" found+=1\n",
|
||
" except:\n",
|
||
" net.embedding.weight[i].data = torch.normal(0.0,1.0,(embed_size,))\n",
|
||
" not_found+=1\n",
|
||
"\n",
|
||
"print(f\"Done, found {found} words, {not_found} words missing\")\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"では、モデルを訓練しましょう。モデルの訓練にかかる時間は、埋め込み層のサイズが大きく、パラメータの数が非常に多いため、前の例よりもかなり長くなります。また、この理由から、過学習を避けたい場合は、より多くの例でモデルを訓練する必要があるかもしれません。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6359375\n",
|
||
"6400: acc=0.68109375\n",
|
||
"9600: acc=0.7067708333333333\n",
|
||
"12800: acc=0.723671875\n",
|
||
"16000: acc=0.73625\n",
|
||
"19200: acc=0.7463541666666667\n",
|
||
"22400: acc=0.7560714285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(214.1013875559821, 0.7626759436980166)"
|
||
]
|
||
},
|
||
"execution_count": 12,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"私たちの場合、精度の大幅な向上は見られませんでした。これは、おそらく異なる語彙が原因です。 \n",
|
||
"異なる語彙の問題を克服するために、以下のいずれかの解決策を使用できます: \n",
|
||
"* 自分の語彙でword2vecモデルを再学習する \n",
|
||
"* 事前学習済みのword2vecモデルの語彙を使用してデータセットを読み込む。データセットを読み込む際に使用する語彙は指定可能です。 \n",
|
||
"\n",
|
||
"後者のアプローチの方が簡単に思えます。特に、PyTorchの`torchtext`フレームワークには埋め込みの組み込みサポートが含まれているためです。例えば、以下の方法でGloVeベースの語彙をインスタンス化できます: \n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 14,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stderr",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"100%|█████████▉| 399999/400000 [00:15<00:00, 25411.14it/s]\n"
|
||
]
|
||
}
|
||
],
|
||
"source": [
|
||
"vocab = torchtext.vocab.GloVe(name='6B', dim=50)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"ロードされた語彙には、以下の基本的な操作があります:\n",
|
||
"* `vocab.stoi` 辞書を使用すると、単語を辞書のインデックスに変換できます\n",
|
||
"* `vocab.itos` はその逆で、数字を単語に変換します\n",
|
||
"* `vocab.vectors` は埋め込みベクトルの配列で、単語 `s` の埋め込みを取得するには、`vocab.vectors[vocab.stoi[s]]` を使用する必要があります\n",
|
||
"\n",
|
||
"以下は、埋め込みを操作して方程式 **kind-man+woman = queen** を示す例です(少し係数を調整して動作させました):\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"'queen'"
|
||
]
|
||
},
|
||
"execution_count": 15,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"# get the vector corresponding to kind-man+woman\n",
|
||
"qvec = vocab.vectors[vocab.stoi['king']]-vocab.vectors[vocab.stoi['man']]+1.3*vocab.vectors[vocab.stoi['woman']]\n",
|
||
"# find the index of the closest embedding vector \n",
|
||
"d = torch.sum((vocab.vectors-qvec)**2,dim=1)\n",
|
||
"min_idx = torch.argmin(d)\n",
|
||
"# find the corresponding word\n",
|
||
"vocab.itos[min_idx]"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"GloVeの語彙を使用してデータセットをエンコードする必要があります。その埋め込みを使用して分類器を訓練するには、まずデータセットをエンコードする必要があります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 16,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"def offsetify(b):\n",
|
||
" # first, compute data tensor from all sequences\n",
|
||
" x = [torch.tensor(encode(t[1],voc=vocab)) for t in b] # pass the instance of vocab to encode function!\n",
|
||
" # now, compute the offsets by accumulating the tensor of sequence lengths\n",
|
||
" o = [0] + [len(t) for t in x]\n",
|
||
" o = torch.tensor(o[:-1]).cumsum(dim=0)\n",
|
||
" return ( \n",
|
||
" torch.LongTensor([t[0]-1 for t in b]), # labels\n",
|
||
" torch.cat(x), # text \n",
|
||
" o\n",
|
||
" )"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"上記のように、すべてのベクトル埋め込みは `vocab.vectors` 行列に保存されています。これにより、単純なコピーを使用して埋め込み層の重みにそれらの重みをロードすることが非常に簡単になります。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 17,
|
||
"metadata": {},
|
||
"outputs": [],
|
||
"source": [
|
||
"net = EmbedClassifier(len(vocab),len(vocab.vectors[0]),len(classes))\n",
|
||
"net.embedding.weight.data = vocab.vectors\n",
|
||
"net = net.to(device)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": []
|
||
},
|
||
{
|
||
"cell_type": "code",
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"outputs": [
|
||
{
|
||
"name": "stdout",
|
||
"output_type": "stream",
|
||
"text": [
|
||
"3200: acc=0.6271875\n",
|
||
"6400: acc=0.68078125\n",
|
||
"9600: acc=0.7030208333333333\n",
|
||
"12800: acc=0.71984375\n",
|
||
"16000: acc=0.7346875\n",
|
||
"19200: acc=0.7455729166666667\n",
|
||
"22400: acc=0.7529464285714286\n"
|
||
]
|
||
},
|
||
{
|
||
"data": {
|
||
"text/plain": [
|
||
"(35.53972978646833, 0.7575175943698017)"
|
||
]
|
||
},
|
||
"execution_count": 18,
|
||
"metadata": {},
|
||
"output_type": "execute_result"
|
||
}
|
||
],
|
||
"source": [
|
||
"train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=offsetify, shuffle=True)\n",
|
||
"train_epoch_emb(net,train_loader, lr=4, epoch_size=25000)"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"私たちが精度の大幅な向上を見られない理由の一つは、データセット内のいくつかの単語が事前学習済みのGloVe語彙に欠けているため、それらが事実上無視されていることにあります。この問題を克服するために、データセット上で独自の埋め込みを学習することができます。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"## 文脈的埋め込み\n",
|
||
"\n",
|
||
"Word2Vecのような従来の事前学習済み埋め込み表現の主な制約の1つは、語義の曖昧性の問題です。事前学習済みの埋め込みは、文脈内での単語の意味をある程度捉えることができますが、単語のすべての可能な意味が同じ埋め込みにエンコードされます。このため、下流のモデルで問題が発生することがあります。例えば、「play」という単語は使用される文脈によって異なる意味を持つことがあります。\n",
|
||
"\n",
|
||
"例えば、「play」という単語は以下の2つの文で全く異なる意味を持っています:\n",
|
||
"- 私は劇場で**劇**を観ました。\n",
|
||
"- ジョンは友達と**遊び**たいと思っています。\n",
|
||
"\n",
|
||
"上記の事前学習済み埋め込みは、「play」という単語のこれら両方の意味を同じ埋め込みで表現しています。この制約を克服するためには、**言語モデル**に基づいた埋め込みを構築する必要があります。この言語モデルは、大規模なテキストコーパスで学習されており、単語が異なる文脈でどのように組み合わされるかを「理解」しています。文脈的埋め込みについて詳しく説明することはこのチュートリアルの範囲外ですが、次のユニットで言語モデルについて話す際に再び取り上げます。\n"
|
||
]
|
||
},
|
||
{
|
||
"cell_type": "markdown",
|
||
"metadata": {},
|
||
"source": [
|
||
"\n---\n\n**免責事項**: \nこの文書はAI翻訳サービス[Co-op Translator](https://github.com/Azure/co-op-translator)を使用して翻訳されています。正確性を追求しておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があります。元の言語で記載された文書を正式な情報源としてお考えください。重要な情報については、専門の人間による翻訳を推奨します。この翻訳の使用に起因する誤解や誤解釈について、当方は一切の責任を負いません。\n"
|
||
]
|
||
}
|
||
],
|
||
"metadata": {
|
||
"interpreter": {
|
||
"hash": "0cb620c6d4b9f7a635928804c26cf22403d89d98d79684e4529119355ee6d5a5"
|
||
},
|
||
"kernelspec": {
|
||
"display_name": "py37_pytorch",
|
||
"language": "python",
|
||
"name": "python3"
|
||
},
|
||
"language_info": {
|
||
"codemirror_mode": {
|
||
"name": "ipython",
|
||
"version": 3
|
||
},
|
||
"file_extension": ".py",
|
||
"mimetype": "text/x-python",
|
||
"name": "python",
|
||
"nbconvert_exporter": "python",
|
||
"pygments_lexer": "ipython3",
|
||
"version": "3.8.12"
|
||
},
|
||
"coopTranslator": {
|
||
"original_hash": "f50b026abce5cf36783a560ea72cb9b1",
|
||
"translation_date": "2025-08-30T10:37:13+00:00",
|
||
"source_file": "lessons/5-NLP/14-Embeddings/EmbeddingsPyTorch.ipynb",
|
||
"language_code": "ja"
|
||
}
|
||
},
|
||
"nbformat": 4,
|
||
"nbformat_minor": 2
|
||
} |