之前所學(xué)的全連接神經(jīng)網(wǎng)絡(luò)(DNN)和卷積神經(jīng)網(wǎng)絡(luò)(CNN),他們的前一個輸入和后一個輸入是沒有關(guān)系的(從輸入層到隱含層再到輸出層,層與層之間是全連接的,每層之間的節(jié)點是無連接的)。如下面這樣,輸出層X,經(jīng)過隱藏層,到輸出層Y,通過調(diào)節(jié)權(quán)重Win和Wout就可以實現(xiàn)學(xué)習(xí)的效果。

但是當(dāng)我們處理序列信息的時候,某些前面的輸入和后面的輸入是有關(guān)系的,比如:當(dāng)我們在理解一句話意思時,孤立的理解這句話的每個詞是不夠的,我們需要處理這些詞連接起來的整個序列;這個時候我們就需要使用到循環(huán)神經(jīng)網(wǎng)絡(luò)(Recurrent Neural Network)。比如:手機壞了,我要買一個256g蘋果,結(jié)合前面的手機壞了,這個蘋果含義是一臺手機,而不是不是吃的蘋果。
1 基本循環(huán)神經(jīng)網(wǎng)絡(luò)RNN

X是輸入向量,O是輸出,S是隱藏層,U是輸入到隱藏層的權(quán)重矩陣,V是隱藏層到輸出層的權(quán)重矩陣,循環(huán)神經(jīng)網(wǎng)絡(luò)的隱藏層的值s不僅僅取決于當(dāng)前這次的輸入x,還取決于上一次隱藏層的值s。權(quán)重矩陣w就是隱藏層上一次的值作為這一次的輸入的權(quán)重。上面的圖可以在時間維度進行展開成一個鏈式的結(jié)構(gòu)。

這個網(wǎng)絡(luò)在t時刻接收到輸入Xt之后,隱藏層的值是St,輸出值是Ot。關(guān)鍵一點是,St的值不僅僅取決于Xt,還取決于St-1。
隱藏層計算公式為:St=f(UXt + W * St-1),其中f為激活函數(shù)。
輸出層計算公式為:Ot = g(VSt),其中g(shù)為激活函數(shù),V是權(quán)重矩陣。
結(jié)合前面的例子,“手機壞了,我要買一個256g蘋果”,被分詞之后,成一組向量[X1,X2,...,X6]

循環(huán)神經(jīng)網(wǎng)絡(luò)從左到右閱讀這個句子,不斷調(diào)用相同的RNN CELL來處理。但是上面方法有一個明顯的缺陷,當(dāng)閱讀的句子很長的時候,網(wǎng)絡(luò)會變得復(fù)雜甚至無效。當(dāng)前面的信息在傳遞到后面的同時,信息的權(quán)重會下降(梯度爆炸和梯度消失),導(dǎo)致預(yù)測不準。比如下面兩句話,was和were要根據(jù)前面的student的單復(fù)數(shù)來確定。句子過長的情況下,就難以判定了,因此RNN這種網(wǎng)絡(luò)被稱為短時記憶網(wǎng)絡(luò)(Short Term Memory)。
he student,who got A+ in the exam,was excellent.
The students,who got A+ in the exam,were excellent.
2 LSTM循環(huán)神經(jīng)網(wǎng)絡(luò)
為了解決上面記憶信息不足的問題,引入一種長短時記憶網(wǎng)絡(luò)(Long Short Term Memory,LSTM)。原始RNN的隱藏層只有一個狀態(tài),即h,它對于短期的輸入非常敏感。那么如果我們再增加一個門(gate)機制用于控制特征的流通和損失,即c,讓它來保存長期的狀態(tài)。

左邊是不同時刻的X,中間黃色球是隱藏層H,右邊綠色是輸出Y,和基本RNN相比,除了黃色的鏈條(Short Term Memory),LSTM增加了一個新的紅色鏈條,用C來表示,叫LongTerm Memory,且兩個鏈條相互作用,相關(guān)更新,將這兩條線“拍平”后如下:

我們在學(xué)習(xí)的時候,會經(jīng)??吹较旅孢@幅圖,比較難以理解,主要是二維的圖難以想象成三維的結(jié)構(gòu)。理解上面三維結(jié)構(gòu)的兩條線,就知道下面的二維圖兩條線是怎么進行數(shù)據(jù)更新的。

現(xiàn)在正式介紹LSTM中三個重要的門結(jié)構(gòu)。
2.1 遺忘門
函數(shù)f1是sigmoid函數(shù),可以把矩陣的值壓縮到0-1之間,矩陣元素相乘的時候,因為任何數(shù)乘以 0 都得 0,這部分信息就會剔除掉。同樣的,任何數(shù)乘以 1 都得到它本身,這部分信息就會完美地保存下來。這樣網(wǎng)絡(luò)就能了解哪些數(shù)據(jù)是需要遺忘,哪些數(shù)據(jù)是需要保存。

數(shù)據(jù)更新公式:

與基本RNN的內(nèi)部結(jié)構(gòu)計算非常相似,首先將當(dāng)前時間步輸入x(t)與上一個時間步隱含狀態(tài)h(t—1)拼接,得到[x(t),h(t—1)],然后通過一個全連接層做變換,最后通過sigmoid函數(shù)進行激活得到f1(t),我們可以將f1(t)看作是門值,好比一扇門開合的大小程度,門值都將作用在通過該扇門的張量,遺忘門門值將作用的上一層的細胞狀態(tài)上,代表遺忘過去的多少信息,又因為遺忘門門值是由x(t),h(t—1)計算得來的,因此整個公式意味著根據(jù)當(dāng)前時間步輸入和上一個時間步隱含狀態(tài)h(t—1)來決定遺忘多少上一層的細胞狀態(tài)所攜帶的過往信息。
2.2 輸入門


我們看到輸入門的計算公式有兩個,第一個就是產(chǎn)生輸入門門值的公式,它和遺忘門公式幾乎相同,區(qū)別只是在于它們之后要作用的目標上,這個公式意味著輸入信息有多少需要進行過濾。輸入門的第二個公式是與傳統(tǒng)RNN的內(nèi)部結(jié)構(gòu)計算相同。對于LSTM來講,它得到的是當(dāng)前的細胞狀態(tài),而不是像經(jīng)典RNN一樣得到的是隱含狀態(tài)。最后,第一個公式f1與上一次的Ct-1做全連接然后,加上f2之后的結(jié)果,更新給當(dāng)前的Ct,這個過程被稱為LSTM的細胞狀態(tài)更新。
2.3 輸出門


輸出門部分的公式也是兩個,第一個即是計算輸出門的門值,它和遺忘門,輸入門計算方式相同。第二個即是使用這個門值產(chǎn)生隱含狀態(tài)h(t),他將作用在更新后的細胞狀態(tài)C(t)上,并做tanh激活,最終得到h(t)作為下一時間步輸入的一部分.整個輸出門的過程,就是為了產(chǎn)生隱含狀態(tài)h(t)。
上面的過程用代碼表示就是

# 定義一個LSTM模型
class LSTM(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, output_size):
super(LSTM, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隱藏狀態(tài)h0, c0為全0向量
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)
# 將輸入x和隱藏狀態(tài)(h0, c0)傳入LSTM網(wǎng)絡(luò)
out, _ = self.lstm(x, (h0, c0))
# 取最后一個時間步的輸出作為LSTM網(wǎng)絡(luò)的輸出
out = self.fc(out[:, -1, :])
return out
3 BiLSTM神經(jīng)網(wǎng)絡(luò)
雙向循環(huán)神經(jīng)網(wǎng)絡(luò)(Bi-Directional Long Short-Term Memory,BiLSTM)是一種特殊的循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)架構(gòu),它包含一個正向LSTM 層和一個反向LSTM層。這兩個LSTM層分別對序列中的元素進行正向和反向傳遞,并在最后的隱藏層中進行合并。這樣,BiLSTM可以同時考慮序列中的歷史信息和未來信息,使得它在處理序列數(shù)據(jù)任務(wù)中(如文本分類和序列標注)有著良好的表現(xiàn)。

前向的LSTML依次輸入“我”,“愛”,“你”得到三個向量{h0,h1,h2}。后向的LSTMR依次輸入“你”,“愛”,“我”得到三個向量{h5,h4,h3}。最后將前向和后向的隱向量進行拼接得到{[h0,h5],[h1,h4],[h2,h3]},即{A,B,C},對于情感分類任務(wù)來說,我們采用的句子表示往往是[h2,h5],因為這其中包含了前向和后向的所有信息。
4 電影評價的極性分析實踐
4.1 劃分訓(xùn)練集、測試集
import torch
import torch.nn.functional as F
from torchtext import data
from torchtext import datasets
from torchtext.legacy import data, datasets
import time
import random
torch.backends.cudnn.deterministic = True
# 定義超參數(shù)
RANDOM_SEED = 123
torch.manual_seed(RANDOM_SEED)
VOCABULARY_SIZE = 20000
LEARNING_RATE = 1e-3
BATCH_SIZE = 128
NUM_EPOCHS = 15
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
if __name__ == '__main__':
# 注意:由于 RNN 只能處理序列中的非 padded 元素(即非0數(shù)據(jù))
# 對于任何 padded 元素輸出都是 0 。所以在準備數(shù)據(jù)的時候?qū)nclude_length設(shè)置為True
# 以獲得句子的實際長度。
TEXT = data.Field(tokenize='spacy', include_lengths=True, tokenizer_language='en_core_web_sm')
LABEL = data.LabelField(dtype=torch.float)
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
datasets.IMDB.splits(TEXT,LABEL)
# 從訓(xùn)練集中選取部分做驗證集
train_data, valid_data = train_data.split(random_state=random.seed(RANDOM_SEED), split_ratio=0.8)
print(f'Num Train: {len(train_data)}')
print(f'Num Valid: {len(valid_data)}')
print(f'Num Test: {len(test_data)}')
print("train_data[0:200]", test_data.examples[0].text[0:100])

Num Train: 20000
Num Valid: 5000
Num Test: 25000
train_data[0:200] ['Based', 'on', 'an', 'actual', 'story', ',', 'John', 'Boorman', 'shows', 'the', 'struggle', 'of', 'an', 'American', 'doctor', ',', 'whose', 'husband', 'and', 'son', 'were', 'murdered', 'and', 'she', 'was', 'continually', 'plagued', 'with', 'her', 'loss', '.', 'A', 'holiday', 'to', 'Burma', 'with', 'her', 'sister', 'seemed', 'like', 'a', 'good', 'idea', 'to', 'get', 'away', 'from', 'it', 'all', ',', 'but', 'when', 'her', 'passport', 'was', 'stolen', 'in', 'Rangoon', ',', 'she', 'could', 'not', 'leave', 'the', 'country', 'with', 'her', 'sister', ',', 'and', 'was', 'forced', 'to', 'stay', 'back', 'until', 'she', 'could', 'get', 'I.D.', 'papers', 'from', 'the', 'American', 'embassy', '.', 'To', 'fill', 'in', 'a', 'day', 'before', 'she', 'could', 'fly', 'out', ',', 'she', 'took', 'a']
4.2 創(chuàng)建詞向量
TEXT.build_vocab(train_data, max_size=VOCABULARY_SIZE)
LABEL.build_vocab(train_data)
print(f'Vocabulary size: {len(TEXT.vocab)}')
print(f'Number of classes: {len(LABEL.vocab)}')
輸出:
Vocabulary size: 20002
Number of classes: 2
TEXT.build_vocab表示從預(yù)訓(xùn)練的詞向量中,將當(dāng)前訓(xùn)練數(shù)據(jù)中的詞匯的詞向量抽取出來,構(gòu)成當(dāng)前訓(xùn)練集的 Vocab(詞匯表)。對于當(dāng)前詞向量語料庫中沒有出現(xiàn)的單詞(記為UNK)。
4.3 創(chuàng)建數(shù)據(jù)迭代器
BATCH_SIZE = 64
# 根據(jù)當(dāng)前環(huán)境選擇是否調(diào)用GPU進行訓(xùn)練
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 創(chuàng)建數(shù)據(jù)迭代器
train_loader, valid_loader, test_loader = data.BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size=BATCH_SIZE,
sort_within_batch=True, # 為了 packed_padded_sequence
device=device)
4.4 定義RNN模型
class RNN(nn.Module):
def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):
super().__init__()
self.embedding = nn.Embedding(input_dim, embedding_dim)
self.rnn = nn.RNN(embedding_dim, hidden_dim)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, text, text_length):
embedded = self.embedding(text)
# pack_padded_sequence 技術(shù)的應(yīng)用
packed = torch.nn.utils.rnn.pack_padded_sequence(embedded, text_length)
output, hidden = self.rnn(packed)
#squeeze(0)的作用是將張量中維度大小為1的維度進行壓縮,減少張量的維度數(shù)量和大小。
#如果張量中沒有維度大小為1的維度,那么squeeze(0)函數(shù)不會對張量進行任何修改,它會返回與原始張量相同的張量
# view(-1)將張量重塑為一維形狀
return self.fc(hidden.squeeze(0)).view(-1)
關(guān)于pack_padded_sequence(處理Pad問題)的解釋:
"Pad問題"是指填充操作中的一個常見問題,即如何處理填充元素(通常用特殊的占位符,如<pad>)對模型訓(xùn)練和推理的影響。我們需要對電影評論進行情感分類,這些評論往往具有不同長度的單詞數(shù)量。當(dāng)我們將這些評論句子作為輸入傳遞給循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)進行處理時,由于RNN的輸入需要是固定長度的張量,我們需要對序列進行填充(padding)操作,使得每個評論都具有相同的長度。
假設(shè)我們有三個電影評論,分別是"這是一部很好看的電影","這個電影一般般"和"我不喜歡這部電影"。我們可以將這些評論編碼為以下張量:
評論1: [這, 是, 一部, 很, 好看, 的, 電影]
評論2: [這個, 電影, 一般般]
評論3: [我, 不喜歡, 這部, 電影]
在這個例子中,我們有3個電影評論。它們的長度分別是7、3和4。我們需要將它們填充到相同的長度,以便能夠?qū)⑺鼈冏鳛橐粋€批次輸入到模型中。填充后的序列是:
評論1: [這, 是, 一部, 很, 好看, 的, 電影]
評論2: [這個, 電影, 一般般, <pad>, <pad>, <pad>, <pad>]
評論3: [我, 不喜歡, 這部, 電影, <pad>, <pad>, <pad>]
4.5 RNN模型訓(xùn)練
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 128
HIDDEN_DIM = 256
OUTPUT_DIM = 1
torch.manual_seed(RANDOM_SEED)
model = RNN(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM)
model = model.to(DEVICE)
#選擇Adam優(yōu)化器
optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE)
start_time = time.time()
for epoch in range(NUM_EPOCHS):
model.train()
for batch_idx, batch_data in enumerate(train_loader):
text, text_lengths = batch_data.text
logits = model(text, text_lengths)
cost = F.binary_cross_entropy_with_logits(logits, batch_data.label)
optimizer.zero_grad()
cost.backward()
optimizer.step()
if not batch_idx % 50:
print(f'Epoch: {epoch + 1:03d}/{NUM_EPOCHS:03d} | '
f'Batch {batch_idx:03d}/{len(train_loader):03d} | '
f'Cost: {cost:.4f}')
4.6 RNN模型評估
def compute_binary_accuracy(model, data_loader, device):
model.eval()
correct_pred, num_examples = 0, 0
with torch.no_grad():
for batch_idx, batch_data in enumerate(data_loader):
text, text_lengths = batch_data.text
logits = model(text, text_lengths)
predicted_labels = (torch.sigmoid(logits) > 0.5).long()
num_examples += batch_data.label.size(0)
correct_pred += (predicted_labels == batch_data.label.long()).sum()
return correct_pred.float() / num_examples * 100
def predict_sentiment(model, sentence):
model.eval()
tokenized = [tok.text for tok in nlp.tokenizer(sentence)]
indexed = [TEXT.vocab.stoi[t] for t in tokenized]
length = [len(indexed)]
tensor = torch.LongTensor(indexed).to(DEVICE)
tensor = tensor.unsqueeze(1)
length_tensor = torch.LongTensor(length)
prediction = torch.sigmoid(model(tensor, length_tensor))
return prediction.item()
with torch.set_grad_enabled(False):
print(f'training accuracy: '
f'{compute_binary_accuracy(model, train_loader, DEVICE):.2f}%'
f'\nvalid accuracy: '
f'{compute_binary_accuracy(model, valid_loader, DEVICE):.2f}%')
print(f'Time elapsed: {(time.time() - start_time) / 60:.2f} min')
print(f'Total Training Time: {(time.time() - start_time) / 60:.2f} min')
print(f'Test accuracy: {compute_binary_accuracy(model, test_loader, DEVICE):.2f}%')
nlp = spacy.load('en_core_web_sm')
ret = predict_sentiment(model, "I really love this movie. This movie is so great!")
print("ret=", ret)
輸出:
Num Train: 20000
Num Valid: 5000
Num Test: 25000
train_data[0:200] ['Based', 'on', 'an', 'actual', 'story', ',', 'John', 'Boorman', 'shows', 'the', 'struggle', 'of', 'an', 'American', 'doctor', ',', 'whose', 'husband', 'and', 'son', 'were', 'murdered', 'and', 'she', 'was', 'continually', 'plagued', 'with', 'her', 'loss', '.', 'A', 'holiday', 'to', 'Burma', 'with', 'her', 'sister', 'seemed', 'like', 'a', 'good', 'idea', 'to', 'get', 'away', 'from', 'it', 'all', ',', 'but', 'when', 'her', 'passport', 'was', 'stolen', 'in', 'Rangoon', ',', 'she', 'could', 'not', 'leave', 'the', 'country', 'with', 'her', 'sister', ',', 'and', 'was', 'forced', 'to', 'stay', 'back', 'until', 'she', 'could', 'get', 'I.D.', 'papers', 'from', 'the', 'American', 'embassy', '.', 'To', 'fill', 'in', 'a', 'day', 'before', 'she', 'could', 'fly', 'out', ',', 'she', 'took', 'a']
Vocabulary size: 20002
Number of classes: 2
Epoch: 001/004 | Batch 000/313 | Cost: 0.7078
Epoch: 001/004 | Batch 050/313 | Cost: 0.6911
Epoch: 001/004 | Batch 100/313 | Cost: 0.6901
Epoch: 001/004 | Batch 150/313 | Cost: 0.6965
Epoch: 001/004 | Batch 200/313 | Cost: 0.6274
Epoch: 001/004 | Batch 250/313 | Cost: 0.6855
Epoch: 001/004 | Batch 300/313 | Cost: 0.6413
training accuracy: 66.27%
valid accuracy: 65.26%
Time elapsed: 6.34 min
Epoch: 002/004 | Batch 000/313 | Cost: 0.6546
Epoch: 002/004 | Batch 050/313 | Cost: 0.6024
Epoch: 002/004 | Batch 100/313 | Cost: 0.6676
Epoch: 002/004 | Batch 150/313 | Cost: 0.6437
Epoch: 002/004 | Batch 200/313 | Cost: 0.6236
Epoch: 002/004 | Batch 250/313 | Cost: 0.6862
Epoch: 002/004 | Batch 300/313 | Cost: 0.5634
training accuracy: 54.29%
valid accuracy: 52.32%
Time elapsed: 12.72 min
Epoch: 003/004 | Batch 000/313 | Cost: 0.6892
Epoch: 003/004 | Batch 050/313 | Cost: 0.6420
Epoch: 003/004 | Batch 100/313 | Cost: 0.6250
Epoch: 003/004 | Batch 150/313 | Cost: 0.6815
Epoch: 003/004 | Batch 200/313 | Cost: 0.5970
Epoch: 003/004 | Batch 250/313 | Cost: 0.6502
Epoch: 003/004 | Batch 300/313 | Cost: 0.5945
training accuracy: 68.32%
valid accuracy: 61.98%
Time elapsed: 19.41 min
Epoch: 004/004 | Batch 000/313 | Cost: 0.5901
Epoch: 004/004 | Batch 050/313 | Cost: 0.3887
Epoch: 004/004 | Batch 100/313 | Cost: 0.6483
Epoch: 004/004 | Batch 150/313 | Cost: 0.5912
Epoch: 004/004 | Batch 200/313 | Cost: 0.5973
Epoch: 004/004 | Batch 250/313 | Cost: 0.4288
Epoch: 004/004 | Batch 300/313 | Cost: 0.4574
training accuracy: 75.49%
valid accuracy: 65.98%
Time elapsed: 25.52 min
Total Training Time: 25.52 min
Test accuracy: 65.84%
ret= 0.9203115105628967
0.9大于0.5 代表是積極的觀點,但是測試集上準確率只有65.84%。
4.7 定義LSTM模型
class LSTM(nn.Module):
def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim):
super().__init__()
self.embedding = nn.Embedding(input_dim, embedding_dim)
self.lstm = nn.LSTM(embedding_dim, hidden_dim)
self.fc = nn.Linear(hidden_dim, output_dim)
def forward(self, text, text_length):
embedded = self.embedding(text)
packed = torch.nn.utils.rnn.pack_padded_sequence(embedded, text_length)
packed_output, (hidden, cell) = self.lstm(packed)
return self.fc(hidden.unsqueeze(0)).view(-1)
4.8 LSTM 模型訓(xùn)練和評估
model2 = LSTM(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM).to(DEVICE)
optimizer2 = torch.optim.Adam(model2.parameters(), lr=LEARNING_RATE)
start_time = time.time()
for epoch in range(NUM_EPOCHS):
model2.train()
for batch_idx, batch_data in enumerate(train_loader):
text, text_lengths = batch_data.text
logits = model2(text, text_lengths)
cost2 = F.binary_cross_entropy_with_logits(logits, batch_data.label)
optimizer2.zero_grad()
cost2.backward()
optimizer2.step()
if not batch_idx % 50:
print (f'Epoch: {epoch+1:03d}/{NUM_EPOCHS:03d} | '
f'Batch {batch_idx:03d}/{len(train_loader):03d} | '
f'Cost: {cost2:.4f}')
with torch.set_grad_enabled(False):
print(f'training accuracy: '
f'{compute_binary_accuracy(model2, train_loader, DEVICE):.2f}%'
f'\nvalid accuracy: '
f'{compute_binary_accuracy(model2, valid_loader, DEVICE):.2f}%')
print(f'Time elapsed: {(time.time() - start_time)/60:.2f} min')
print(f'Total Training Time: {(time.time() - start_time)/60:.2f} min')
print(f'Test accuracy: {compute_binary_accuracy(model2, test_loader, DEVICE):.2f}%')
輸出:
Num Train: 20000
Num Valid: 5000
Num Test: 25000
train_data[0:200] ['Based', 'on', 'an', 'actual', 'story', ',', 'John', 'Boorman', 'shows', 'the', 'struggle', 'of', 'an', 'American', 'doctor', ',', 'whose', 'husband', 'and', 'son', 'were', 'murdered', 'and', 'she', 'was', 'continually', 'plagued', 'with', 'her', 'loss', '.', 'A', 'holiday', 'to', 'Burma', 'with', 'her', 'sister', 'seemed', 'like', 'a', 'good', 'idea', 'to', 'get', 'away', 'from', 'it', 'all', ',', 'but', 'when', 'her', 'passport', 'was', 'stolen', 'in', 'Rangoon', ',', 'she', 'could', 'not', 'leave', 'the', 'country', 'with', 'her', 'sister', ',', 'and', 'was', 'forced', 'to', 'stay', 'back', 'until', 'she', 'could', 'get', 'I.D.', 'papers', 'from', 'the', 'American', 'embassy', '.', 'To', 'fill', 'in', 'a', 'day', 'before', 'she', 'could', 'fly', 'out', ',', 'she', 'took', 'a']
Vocabulary size: 20002
Number of classes: 2
Epoch: 001/010 | Batch 000/313 | Cost: 0.6930
Epoch: 001/010 | Batch 050/313 | Cost: 0.6436
Epoch: 001/010 | Batch 100/313 | Cost: 0.6402
Epoch: 001/010 | Batch 150/313 | Cost: 0.5405
Epoch: 001/010 | Batch 200/313 | Cost: 0.6803
Epoch: 001/010 | Batch 250/313 | Cost: 0.6905
Epoch: 001/010 | Batch 300/313 | Cost: 0.6695
training accuracy: 56.28%
valid accuracy: 56.62%
Time elapsed: 73.09 min
Epoch: 002/010 | Batch 000/313 | Cost: 0.6772
Epoch: 002/010 | Batch 050/313 | Cost: 0.6866
Epoch: 002/010 | Batch 100/313 | Cost: 0.6674
Epoch: 002/010 | Batch 150/313 | Cost: 0.6037
Epoch: 002/010 | Batch 200/313 | Cost: 0.6808
Epoch: 002/010 | Batch 250/313 | Cost: 0.6685
Epoch: 002/010 | Batch 300/313 | Cost: 0.6927
training accuracy: 50.33%
valid accuracy: 50.40%
Time elapsed: 148.10 min
Epoch: 003/010 | Batch 000/313 | Cost: 0.7443
Epoch: 003/010 | Batch 050/313 | Cost: 0.6509
Epoch: 003/010 | Batch 100/313 | Cost: 0.6160
Epoch: 003/010 | Batch 150/313 | Cost: 0.6501
Epoch: 003/010 | Batch 200/313 | Cost: 0.5341
Epoch: 003/010 | Batch 250/313 | Cost: 0.4378
Epoch: 003/010 | Batch 300/313 | Cost: 0.4366
training accuracy: 84.29%
valid accuracy: 81.76%
Time elapsed: 218.03 min
Epoch: 004/010 | Batch 000/313 | Cost: 0.3864
Epoch: 004/010 | Batch 050/313 | Cost: 0.2678
Epoch: 004/010 | Batch 100/313 | Cost: 0.2225
Epoch: 004/010 | Batch 150/313 | Cost: 0.3614
Epoch: 004/010 | Batch 200/313 | Cost: 0.2415
Epoch: 004/010 | Batch 250/313 | Cost: 0.1816
Epoch: 004/010 | Batch 300/313 | Cost: 0.2577
training accuracy: 91.74%
valid accuracy: 86.48%
Time elapsed: 285.13 min
Test accuracy: 90.72%
ret= 0.9601113557815552