ใน ep ที่แล้วเราใช้ Naive Bayes และ Logistic Regression ที่เป็นอัลกอริทึมที่เป็นที่นิยมก่อนยุค Deep Learning
แต่ใน ep นี้เราจะเปลี่ยนมาใช้ Deep Neural Network แทนว่าจะมีประสิทธิภาพต่างกันอย่างไร
Universal Language Model Fine-tuning (ULMFit)

ในการเทรน เราจะไม่เทรนแต่ต้น โดย Initialize Weight ด้วยค่า Random หรือ Kaiming แต่เราจะใช้ Transfer Learning จาก Language Model ที่เทรนกับ Corpus ขนาดใหญ่กว่ามาก่อน เช่น WikiText-103
แล้ว Fine-Tuned Language Model นั้นเข้ากับ กับภาษา สำนวน คำศัพท์ ใน IMDB
เมื่อได้ Language Model ที่ Fine-Tuned กับภาษาของ IMDB เรียบร้อยแล้ว เราจึงนำส่วนของ Encoder มาสร้าง Sentiment Classifier ต่อ
กระบวนการนี้ เรียกว่า ULMFit หรือ Universal Language Model Fine-tuning ที่จะลดเวลาการเทรนโดยรวม และเพิ่มประสิทธิภาพการทำงานของโมเดล
AWD_LSTM Model Architecture

AWD_LSTM เป็น LSTM / Recurrent Neural Network (RNN) แบบหนึ่ง ที่มีการใช้ DropOut / DropConnect หลากหลายแบบภายในส่วนต่าง ๆ ของโมเดล อย่างชาญฉลาด จะอธิบายต่อไป

เรามาเริ่มกันเลยดีกว่า
ใน ep ที่แล้วเราใช้ Naive Bayes และ Logistic Regression ที่เป็นอัลกอริทึมที่เป็นที่นิยมก่อนยุค Deep Learning แต่ใน ep นี้เราจะเปลี่ยนมาใช้ Deep Neural Network และ Transfer Learning เรียกว่า ULMFit หรือ Universal Language Model Fine-tuning ที่จะลดเวลาการเทรน และเพิ่มประสิทธิภาพการทำงานของโมเดล AWD_LSTM ซึ่งเป็น LSTM / Recurrent Neural Network (RNN) แบบนึง ที่มีการใช้ Dropout หลากหลายแบบภายในส่วนต่าง ๆ ของโมเดล
0. Install¶
%reload_ext autoreload
%autoreload 2
%matplotlib inline
Install Library ที่จำเป็น
## Colab
! curl -s https://course.fast.ai/setup/colab | bash
เช็ค GPU
! nvidia-smi
1. Import¶
Import Library ที่จะใช้ ในที่นี้คือ fastai และ fastai.text
from fastai import *
from fastai.text import *
from fastai.callbacks import *
from fastai.callbacks.mem import *
# import fastai.utils.collect_env
# fastai.utils.collect_env.show_install()
สำหรับ Google Colab เราจะกำหนด path ที่เก็บ DataSet และ Mount Google Drive
dataset = 'imdb_sample'
dataset2 = 'imdb'
# Google Colab
config_path = Path('/content/drive')
data_path_base = Path('/content/datasets/')
data_path = data_path_base/dataset
data_path2 = data_path_base/dataset2
from google.colab import drive
drive.mount(str(config_path))
2. Dataset¶
ในเคสนี้เราจะใช้ IMDB Movie Review เป็นรีวิวหนังจากเว็บ IMDB ที่มีข้อความ และ คะแนนว่าเป็นแง่บวก หรือแง่ลบ เหมือนใน ep ก่อน ๆ
ในการพัฒนา เราจะใช้ Dataset ชุดเล็กก่อน จะได้เร็ว เมื่อเทสทุกอย่างเรียบร้อย แล้วจึงขยับไปใช้ Dataset ชุดเต็ม
path = untar_data(URLs.IMDB_SAMPLE, dest=data_path)
path.ls()
3. Preprocessing¶
3.1 Data Block API¶
เราจะใช้ Data Block API กำหนด Data Pipeline ทีละขั้น Tokenization, Numericalization, Split, Labeling ก็จะทำให้ยืดหยุ่นมากขึ้น
data_lm = (TextList.from_csv(path, 'texts.csv', cols='text')
.split_from_df(col=2)
.label_from_df(cols=0)
.databunch())
ดูตัวอย่างคำศัพท์ใน vocab Dictionary
data_lm.vocab.itos[:20]
ดูตัวอย่างข้อมูล text
data_lm.train_ds[0][0]
ดูข้อมูลภายใน ในรูปแบบตัวเลข
data_lm.train_ds[0][0].data[:20]
2/2. Full Dataset¶
Download ชุดข้อมูลตัวเต็ม IMDB DataSet ไปไว้ใน path ที่กำหนด
path = untar_data(URLs.IMDB, dest=data_path2)
path.ls()
(path/'train').ls()
3/2. Preprocessing¶
Data Pipeline ด้วย Data Block API
# bs=48
# bs=24
bs=64
data_lm = (TextList.from_folder(path)
.filter_by_folder(include=['train', 'test', 'unsup'])
.split_by_rand_pct(0.1)
.label_for_lm()
.databunch(bs=bs, num_workers=1))
ในข้อมูล Training Set 90,000 Record มีคำศัพท์ 60,000 คำ (Default)
len(data_lm.vocab.itos), len(data_lm.train_ds)
data_lm.show_batch()
เซฟไว้ก่อน คราวหน้าจะได้ไม่ต้องเสียเวลา Preprocess ใหม่
data_lm.save('lm_databunch')
โหลด Language Model DataBunch ที่เซฟไว้ แล้วเช็คข้อมูล
data_lm = load_data(path, 'lm_databunch', bs=bs)
len(data_lm.vocab.itos), len(data_lm.train_ds)
4. Language Model จาก WikiText-103¶
4.1 AWD_LSTM Model Architecture¶
สร้าง Language Model Learner ขึ้นมา โดยใช้ data_lm จาก IMDB ด้านบน และ สถาปัตยกรรม AWD_LSTM Model Architecture โดยที่ยังไม่ได้เริ่มต้นเทรน
AWD_LSTM ได้ถูก Pre-Train กับ WikiText-103 Dataset เรียบร้อยแล้ว
learn_lm = language_model_learner(data_lm, AWD_LSTM,
drop_mult=0.3, model_dir=config_path/'My Drive/models',
callback_fns=[ShowGraph, PeakMemMetric])
# learn_lm
learn_lm.model[0]
learn_lm.model[0].encoder
4.2 vocab Dictionary ของ WikiText-103¶
โหลด Dictionary ของ WikiText-103 ขึ้นมา
Language Model ต้องมาพร้อมกับ vocab Dictionary เสมอ ไม่เช่นนั้นเราจะไม่รู้ว่า คำศัพท์ไหนเป็นคำไหน เพราะในโมเดลจะมีแต่ตัวเลข Index / Embedding
Config().model_path().ls()
(Config().model_path()/'wt103-fwd').ls()
wiki_itos = pickle.load(open(Config().model_path()/'wt103-fwd/itos_wt103.pkl', 'rb'))
ดูจำนวนคำศัพท์ ตัวอย่างข้อมูล หัว / ท้าย
len(wiki_itos), wiki_itos[:20], wiki_itos[-20:]
4.3 vocab Dictionary ของ IMDB¶
ดูตัวอย่างคำศัพท์ใน IMDB
vocab = data_lm.vocab
imdb_itos = vocab.itos
vocab.stoi['love']
imdb_itos[vocab.stoi['love']]
imdb_itos[vocab.stoi['Sukhumvit']]
awd = learn_lm.model[0]
# from scipy.spatial.distance import cosine as dist
enc = learn_lm.model[0].encoder
Embedding 400 มิติของ คำศัพท์ 60,000 คำ
enc.weight.size()
4.4 Difference in vocab between IMDB and Wikipedia¶
เปรียบเทียบ 2 vocab Dictionary พบว่า ขนาด vocab เท่ากัน
len(wiki_itos), len(imdb_itos)
แต่ข้างใน ไม่เหมือนกัน เปรียบเทียบ 10 คำศัพท์สุดท้าย
wiki_itos[-10:], imdb_itos[-10:]
wiki_words = set(wiki_itos)
imdb_words = set(imdb_itos)
คำศัพท์ที่อยู่ใน vocab ของ Wiki แต่ไม่อยู่ใน vocab ของ IMDB
wiki_not_imdb = wiki_words.difference(imdb_words)
len(wiki_not_imdb), [val for i, val in enumerate(itertools.islice(wiki_not_imdb, 20))]
คำศัพท์ที่อยู่ใน vocab ของ IMDB แต่ไม่อยู่ใน vocab ของ Wiki
imdb_not_wiki = imdb_words.difference(wiki_words)
len(imdb_not_wiki), [val for i, val in enumerate(itertools.islice(imdb_not_wiki, 20))]
4.5 Out of vocab Initialization¶
คำว่า verification อยู่ใน vocab ของ WikiText-103 แต่ไม่อยู่ใน IMDB
w = "verification"
vocab.stoi[w], w in wiki_words, w in imdb_words
คำเหล่านี้ อยู่ใน vocab ของ IMDB แต่ไม่อยู่ใน WikiText-103 เราจะ Initialize Embedding ของคำเหล่านี้ แบบ Random
w = "senselessness"
vocab.stoi[w], w in wiki_words, w in imdb_words
w = "unthinking"
vocab.stoi[w], w in wiki_words, w in imdb_words
w = "forklift"
vocab.stoi[w], w in wiki_words, w in imdb_words
Initialize ด้วยค่า Random เล็ก ๆ ใกล้เคียงกัน
np.allclose(enc.weight[vocab.stoi['senselessness'], :],
enc.weight[vocab.stoi['unthinking'], :])
แต่ถ้าเทียบกับ Embedding ที่เทรนเรียบร้อยแล้ว ก็จะต่างกันมาก
np.allclose(enc.weight[vocab.stoi['senselessness'], :],
enc.weight[vocab.stoi['capital'], :])
3 คำแรกมีใน IMDB แต่คำสุดท้ายไม่มี
vocab.stoi['senselessness'], vocab.stoi['unthinking'], vocab.stoi['capital'], vocab.stoi['cpu']
ดู Embedding ของคำที่มีใน IMDB แต่ไม่มีใน Wiki จะเห็นว่าเท่ากันมีค่าเท่ากัน
len(enc.weight[vocab.stoi['senselessness'], :]), enc.weight[vocab.stoi['senselessness'], :][:50]
len(enc.weight[vocab.stoi['unthinking'], :]), enc.weight[vocab.stoi['unthinking'], :][:50]
ต่างกับค่าที่มีใน Wiki
len(enc.weight[vocab.stoi['capital'], :]), enc.weight[vocab.stoi['capital'], :][:50]
4.6 Fake Movie Reviews from WikiText Language Model¶
เราจะลองใช้ Language Model ที่ Pre-Train จาก Wikipedia มาลองสร้าง รีวิวหนัง Movie Reviews ดูว่าจะเป็นอย่างไร
ชอบหนังเรื่องนี้เพราะว่า
TEXT = "I loved this film because"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
ไม่ชอบหนังเรื่องนี้เพราะว่า
TEXT = "I hated this movie because"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
หนังเรื่องนี้ ... จริง ๆ
TEXT = "This movie is really"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
จะเห็นว่า รีวิวหนังที่ได้ ค่อนข้างจะมีความเป็น Wikpedia อยู่มาก สำนวน คำศัพท์ต่าง ๆ ไม่ค่อยเหมือนรีวิวหนังทั่วไป
ลองลด temperature ลง ทำให้ข้อความ Random น้อยลง รีวิวหนังที่ได้จะคล้าย ๆ กันมากขึ้น
TEXT = "This movie is really"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.1) for _ in range(N_SENTENCES)))
5. Train Model¶
5.1 Train Last Layer¶
เทรน Layer สุดท้าย
learn_lm.lr_find()
learn_lm.recorder.plot(suggestion=True)
lr = 1e-2
learn_lm.to_fp16();
learn_lm.fit_one_cycle(1, lr, moms=(0.8, 0.7),
callbacks=[SaveModelCallback(learn_lm, every='epoch', monitor='accuracy')])
learn_lm.save("26g-01")
learn_lm.load("26g-01");
5.2 Unfreeze and Train whole model¶
learn_lm.unfreeze()
learn_lm.lr_find()
learn_lm.recorder.plot(suggestion=True)
lr = slice(3e-6, 3e-3)
learn_lm.fit_one_cycle(10, max_lr=lr, moms=(0.8, 0.7),
callbacks=[SaveModelCallback(learn_lm, every='epoch', monitor='accuracy')])
learn_lm.save("26g-02")
learn_lm.save_encoder("26g-02-enc")
learn_lm.load("26g-02");
5.3 Check Embedding Again¶
ลองเช็คอีกครั้ง หลังจากเทรนเสร็จแล้ว Embedding ของทั้ง 2 คำ มีค่าต่างกันแล้ว
np.allclose(enc.weight[vocab.stoi['senselessness'], :],
enc.weight[vocab.stoi['unthinking'], :])
3 คำแรกมีใน IMDB แต่คำสุดท้ายไม่มี
vocab.stoi['senselessness'], vocab.stoi['unthinking'], vocab.stoi['capital'], vocab.stoi['cpu']
ดู Embedding ของคำที่มีใน IMDB แต่ไม่มีใน Wiki จะเห็นว่าแต่ละคำค่า Embedding ได้เปลี่ยนไปเรียบร้อยแล้ว ไม่มีคำไหนค่าซ้ำกันเลย
len(enc.weight[vocab.stoi['senselessness'], :]), enc.weight[vocab.stoi['senselessness'], :][:50]
len(enc.weight[vocab.stoi['unthinking'], :]), enc.weight[vocab.stoi['unthinking'], :][:50]
len(enc.weight[vocab.stoi['capital'], :]), enc.weight[vocab.stoi['capital'], :][:50]
5.4 Fake Movie Reviews from IMDB Language Model¶
เราจะลอง Generate รีวิวหนัง Movie Reviews ใหม่จาก Language Model ที่เราเพิ่งเทรนกับ IMDB DataSet เสร็จ
ชอบหนังเรื่องนี้เพราะว่า
TEXT = "I loved this film because"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
ไม่ชอบหนังเรื่องนี้เพราะว่า
TEXT = "I hated this movie because"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
หนังเรื่องนี้ ... จริง ๆ
TEXT = "This movie is really"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.75) for _ in range(N_SENTENCES)))
จะได้รีวิวหนัง Movie Reviews ที่เหมือนรีวิวหนังมากขึ้น
ลด temperature ลง ทำให้ข้อความ Random น้อยลง
TEXT = "This movie is really"
N_WORDS = 60
N_SENTENCES = 3
print("\n".join(learn_lm.predict(TEXT, N_WORDS, temperature=0.1) for _ in range(N_SENTENCES)))
6. Classifier¶
กลับมาที่งานหลักของเรา ในเคสนี้ คือ การ Sentiment Analysis หรือ Sentiment Classification ว่ารีวิวหนัง Movie Review จะอยู่ใน Class ไหน ใน 2 Class ว่าเป็นรีวิวแง่บวก หรือแง่ลบ Positive หรือ Negative
6.1 สร้าง Databunch ใหม่สำหรับ Classifier¶
ใช้ Data Block API สร้าง Data Pipeline ใหม่ สำหรรับงาน Classifier ที่จะมี label เป็น positive, negative
bs = 48
data_clas = (TextList.from_folder(path, vocab=data_lm.vocab)
.split_by_folder(valid='test')
.label_from_folder(classes=['neg', 'pos'])
.databunch(bs=bs, num_workers=8))
data_clas.save('26g_imdb_textlist_clas')
data_clas = load_data(path, '26g_imdb_textlist_clas', bs=bs, num_workers=8)
data_clas.show_batch()
6.2 สร้าง Learner สำหรับงาน Classifier¶
สร้าง learner สำหรับงาน Classifier แล้ว Load Encoder จาก Language Model ที่เทรนจาก Wiki-103 และ Fine-Tune ด้วย IMDB Dataset ด้านบน เป็นการ Transfer Learning เรียกว่า ULMFit
learn_clas = text_classifier_learner(data_clas, AWD_LSTM,
drop_mult=0.3, model_dir=config_path/'My Drive/models',
callback_fns=[ShowGraph, PeakMemMetric]).to_fp16()
learn_clas.load_encoder('26g-02-enc')
learn_clas.freeze()
6.3 เทรน Layer สุดท้าย¶
เทรน Classifier เริ่มจาก Layer Group สุดท้าย
learn_clas.lr_find()
learn_clas.recorder.plot(suggestion=True)
lr = 3e-2
learn_clas.fit_one_cycle(1, max_lr=lr, moms=(0.8, 0.7))
learn_clas.save('26g-21')
learn_clas.load('26g-21');
6.4 ค่อย ๆ Unfreeze แล้วเทรน Layer Group ต่อ ๆ มา¶
เทรน Classifier โดยเทรน Layer Group สุดท้าย + Layer Group ก่อนหน้า
learn_clas.freeze_to(-2)
learn_clas.lr_find()
learn_clas.recorder.plot(suggestion=True)
lr = 3e-4
learn_clas.fit_one_cycle(1, max_lr=lr, moms=(0.8, 0.7))
learn_clas.save('26g-22')
learn_clas.load('26g-22');
6.5 ค่อย ๆ Unfreeze แล้วเทรน Layer Group ต่อ ๆ มา¶
เทรน Classifier โดยเทรน Layer Group สุดท้าย + Layer Group ก่อนหน้า + Layer Group ก่อนหน้าอีก
learn_clas.freeze_to(-3)
learn_clas.lr_find()
learn_clas.recorder.plot(suggestion=True)
lr = 3e-5
learn_clas.fit_one_cycle(1, max_lr=lr, moms=(0.8, 0.7))
learn_clas.save('26g-23')
learn_clas.load('26g-23');
6.6 Unfreeze แล้วเทรนทั้งโมเดล¶
Unfreeze ทั้งโมเดล แล้วเทรนต่อ
learn_clas.unfreeze()
learn_clas.lr_find()
learn_clas.recorder.plot(suggestion=True)
lr = 1e-4
learn_clas.fit_one_cycle(4, max_lr=lr, moms=(0.8, 0.7))
โมเดลของเรา สามารถ Predict IMDB DataSet Sentiment Classification ได้ Accuracy ถึง 94% เทียบกับ 88% ใน ep ที่แล้ว
learn_clas.save('26g-24')
learn_clas.load('26g-24');
6.7 Predict¶
ลองให้โมเดล ทำนายข้อความ รีวิวหนัง Movie Review ที่เราเขียนเอง
รีวิวแง่บวก
learn_clas.predict('It is possibly one of the most completely wonderful story I have ever watched.')
learn_clas.predict('An intelligent, funny, charming, sweet film with surprising depth and heart.')
learn_clas.predict("I for one thought that this is an effective comedy movie that was really entertaining. I don't know what people's problem is that they have to dump on this movie so bad.")
รีวิวแง่ลบ
learn_clas.predict('Big disappointment even for non-discriminating zombie apocalypse movie fans.')
learn_clas.predict("Truly appalling This is an action thriller movie by numbers... but the action isn't very exciting and the plot holes just boring and annoy. ")
learn_clas.predict('A terrible "action" film remake that is more accidentally humorous than seriously suspenseful.')
สรุป¶
- เราได้เรียนรู้วิธีการทำงาน และประโยชน์ของ Language Model และ vocab Dictionary
- เราได้ใช้ Language Model ในการ Generate รีวิวหนัง Movie Review ที่อ่านแล้ว คล้ายกลับรีวิวหนังจริง ๆ
- เราใช้เทคนิค Transfer Learning ชื่อ ULMFit มาช่วยในการลดเวลา และเพิ่มประสิทธืภาพในการเทรน Deep Neural Network ใน NLP เช่น Sentiment Analysis
- เราได้เรียนรู้การสร้าง Deep Neural Network ด้วยสถาปัตยกรรม AWD_LSTM ใช้ในงาน Sentiment Classification ที่มีประสิทธิภาพชนะ เทคนิคเก่า ๆ อย่างขาดลอย
Credit¶
- https://www.youtube.com/watch?v=dt7sArnLo1g&list=PLtmWHNX-gukKocXQOkQjuVxglSDYWsSh9&index=6&t=0s
- https://www.bualabs.com/archives/2693/data-block-api-data-pipeline-machine-learning-supervised-learning-preprocessing-ep-5/
- https://www.bualabs.com/archives/926/sentiment-analysis-imdb-movie-review-ulmfit-sentiment-analysis-ep-1/
- https://www.bualabs.com/archives/3000/sentiment-classification-imdb-movie-reviews-with-naive-bayes-logistic-regression-nlp-ep-5/
- https://docs.fast.ai/data_block.html
- https://docs.fast.ai/text.data.html
- https://forums.fast.ai/t/language-model-zoo-gorilla/14623
- https://www.imdb.com/interfaces/
- https://blog.einstein.ai/the-wikitext-long-term-dependency-language-modeling-dataset/
- https://arxiv.org/abs/1801.06146
- https://arxiv.org/abs/1708.02182