دسته‌بندی نشده

مدل سازی زبان در پردازش متن (ساخت شعر با هوش‌مصنوعی)

مدل-سازی-زبان-شعر-هوش-مصنوعی

در این مطلب میخواهیم تا با مفهوم مدل‌سازی زبان آشنا شویم. وقتی یک مدل زبانی داشته باشیم، سیستم میتوان احتمال قرارگیری هر کلمه در یک مکان خاص در متن را پیش بینی و متن های جدید(حتی شعر) تولید کند.

همینطور هنگام پردازش صوت، کلمه ی شنیده شده را با توجه به احتمال قرارگیری آن کلمه نسبت به سایر کلمات جمله و با استفاده از مدل زبانی تشخیص دهد.

یکی از بهترین نمونه ها، ویژگی پیش بینی کلمه بعدی صفحه کلید تلفن هوشمند است یا هنگام سرچ یک عبارت در گوگل؛

روش های مختلف مدل سازی زبان

  • مدل های N-بخشی (مبتنی بر شمارش): تخمین تاریخچه کلمات مشاهده شده تنها با استفاده از 1 – N کلمه قبل
  • مدل های N-بخشی(مبتنی بر شبکه های عصبی): تخمین تاریخچه کلمات مشاهده شده تنها با استفاده از 1 – N کلمه قبل در یک فضای پیوسته و یادگیری بهتر وابستگی (ارتباط معنایی) میان کلمات.
  • شبکه های عصبی برگشتی: فشرده سازی تاریخچه کامل کلمات مشاهده شده در یک بردار با اندازه ثابت و یادگیری وابستگی های بلندمدت میان کلمات.

مدل های N-بخشی مبتنی بر شمارش که در گذشته استفاده می شد یک کلمه را با توجه به کلمه قبلی و بصورت گسسته پیش بینی میکرد. مدل سازی بصورت گسسته معمولا نتایج زیاد جالبی در دنباله کلمات یک متن ندارد.

اما مدل های N-بخشی مبتنی بر شبکه های عصبی به صورت پیوسته و بر اساس بردار واژگان میباشند که میتواند ارتباط معنایی بین کلمه ها را تشخیص دهد.

مدل-سازی-زبان-شبکه-عصبی

در تصویر بالا یک مدل سه بخشی را مشاهده میکنید که با استفاده از دو کلمه قبلی احتمال کلمه جدید را تخمین میزند.

چون پیش بینی برحسب نمونه برداری میباشد، دنباله ای جدید از کلمات ایجاد می شود.

نمونه برداری: وقتی که سیستم مدل زبان را یاد گرفت و فهمید که کلمات چگونه پشت سر هم بکار میروند، با استفاده از احتمال اینکه یک کلمه بعد از کلمه دیگر بیاید نمونه برداری میکنیم و یک دنباله جدید (بطور مثال یک بیت شعر) میسازیم.

نکات:

  1. برای تبدیل اعداد بردار هر کلمه به احتمال، بعد از لایه مخفی از تابع غیرخطی Softmax استفاده می‌شود؛
  2. از تابع هزینه Crossentropy برای بیشینه سازی احتمال کلمه درست استفاده باید کرد؛

مدل‌های زبانی مبتنی بر شبکه‌های برگشتی

شبکه های برگشتی یا همان RNNها، به علت اینکه می توانند تاریخچه تمام کلمات را حفظ کنند، خیلی عملکرد بهتری دارند نسبت به شبکه های عصبی معمولی که فقط بر اساس کلمه قبلی پیش بینی انجام میدهند؛

مدل-سازی-زبان-شبکه-برگشتی

در شبکه های برگشتی در هر لحظه نه تنها خروجی به ورودی داده شده بستگی دارد، بلکه به خود state سیستم در لحظه قبل نیز بستگی دارد.

همانطور که در شکل بالا هم مشاهده میکنید، مقدار لایه مخفی هم به X فعلی بستگی دارد و هم به مقدار خود لایه در تکرار قبلی. به بیان دیگر:

به جای nتا کلمه قبلی، از روی تمام کلمات (یک تاریخچه نامتناهی) کلمه بعدی را پیش بینی میکند؛

مرحله پس انتشار خطا (Back Propagation)

وقتی که یک تابع هزینه داریم، با الگوریتم بک پروپگیشن خطاها را حساب میکنیم و وزن هایی که داشتیم را آپدیت می‌شود.

در بک پروپگیشن شبکه های برگشتی، برخلاف شبکه عصبی معمولی نمیتوان هرکلمه را بصورت جداگانه محاسبه کرد و هر کلمه به کلمات دیگر وابسته است. به این منظور از بک پروپگیشن در طول زمان یا bptt استفاده می شود.

پس-انتشار-پردازش-متن

 

یکی از مشکلاتی که RNN ها دارند، مشکل انفجار یا محو گرادیان میباشد. (بزرگترین مقدار ویژه ماتریس𝜆𝑚𝑎𝑥 بصورت نمایی کاهش یا افزایش می‌یابد)

برای جلوگیری از این مشکل روش های مختلفی وجود دارد:

  • تعیین سقف برای مقدار گرادیان ها پس از محاسبه گرادیان ها یک روش برای جلوگیری از انفجار گرادیان است.
  • استفاده از معماری هایی مانند LSTM و GRU نیز معمولا برای جلوگیری از محو گرادیان استفاده میشود.

ساخت سیستم مدل سازی زبان با پایتون

به عنوان یک پروژه عملی، قصدداریم که مدل سازی زبان را با استفاده از اشعار مولانا انجام دهیم، به طوری که به عنوان نتیجه   هوش مصنوعی شعری کاملا تصادفی برای ما ایجاد کند!

مانند شعر زیر:

شعر گفته شده توسط هوش مصنوعی

شعر گفته شده توسط هوش مصنوعی

نکته: کدها و تصاویر استفاده شده در مطلب، از گیت‌هاب استادرضوی برداشته شده است. برای دریافت سورس کامل به این (لینک) مراجعه کنید؛

مرحله اول: وارد کردن کتابخانه ها

%matplotlib inline
%reload_ext autoreload
%autoreload 2

import os
import sys
import random
import pickle

import numpy as np
import matplotlib.pyplot as plt
from collections import Counter
from tqdm import tqdm_notebook

import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable

from utils import *
from data_utils import Vocabulary
from train_utils import train

from IPython.core.debugger import Pdb  ## DEBUG ##

# setup
plt.style.use('ggplot')
plt.rcParams['figure.figsize'] = (12., 6.)
pdb = Pdb()
use_gpu = torch.cuda.is_available()

توابع کمکی:

def plot_loss(trn_hist, val_hist):
    plt.plot(trn_hist, label='Training Loss')
    plt.plot(val_hist, label='Validation Loss')
    plt.legend()
    plt.xlabel('Epoch')
    plt.ylabel('Loss')
    plt.show()


def tokenize_corpus(corpus_path):
    num_lines = len(open(corpus_path, encoding='utf8').read().split('\n'))
    
    # tokenize corpus
    output = ''
    with open(corpus_path, encoding='utf8') as f:
        for line in tqdm_notebook(f, desc='Tokenizing', total=num_lines):
            tokens = tokenizer(line.strip()) + ['\n']
            output += ' '.join(tokens)
    
    # save tokenized corpus
    tok_corpus_path = corpus_path[:-4] + '_tok.txt'
    with open(tok_corpus_path, 'w', encoding='utf8') as f:
        f.write(output)

به عنوان داده ورودی، اشعار مولانا را از سیستم خود فراخوانی میکنیم:

data_dir = 'data/masnavi'
output_dir = f'{data_dir}/models'

train_data = f'{data_dir}/masnavi.txt'
train_data_tok = train_data[:-4] + '_tok.txt'

توکن بندی کردن متن:

tokenize_corpus(train_data)

text = open(train_data, encoding='utf8').read().split('\n')[:10]
print(text)

print('\n\nAfter Tokenizing:\n\n')
print(tokenizer('\n'.join(text)))
class Corpus(object):
    
    def __init__(self, corpus_path='data/train.txt'):
        self.vocabulary = Vocabulary()
        self.corpus_path = corpus_path
        self.num_sentences = len([line for line in open(corpus_path, encoding='utf8')])
    
    def get_data(self, max_vocab=30000, min_count=3, batch_size=20, split_ratio=0.2):
        
        # First pass: add words to the vocabulary
        trn_tokens, val_tokens = [], []
        with open(self.corpus_path, encoding='utf8') as f:
            for line in tqdm_notebook(f, desc='Building Vocab...', total=self.num_sentences):
                tokens = line.split() + ['<EOS>']
                if len(line) <= 10: continue
                if random.random() < split_ratio:
                    val_tokens += tokens
                else:
                    trn_tokens += tokens
        
        counter = Counter(trn_tokens + val_tokens)
        
        # sort tokens according to their frequencies in the Corpus
        vocabs = [(w, c) for (w, c) in counter.most_common(max_vocab) if c >= min_count]
        
        for i, (word, count) in enumerate(vocabs):
            self.vocabulary.word2index[word] = i
            self.vocabulary.word2count[word] = count
            self.vocabulary.index2word[i] = word
            self.vocabulary.num_words += 1
        self.vocabulary.add_word('<UNK>')
        
        
        # Second pass: Tokenize file content
        UNK_TOKEN = self.vocabulary.word2index['<UNK>']
        
        # train ids
        trn_ids = torch.LongTensor(len(trn_tokens))
        for idx, token in enumerate(trn_tokens):
            if token in self.vocabulary.word2index:
                trn_ids[idx] = self.vocabulary.word2index[token] 
            else:
                trn_ids[idx] = UNK_TOKEN
        
        # validation ids
        val_ids = torch.LongTensor(len(val_tokens))
        for idx, token in enumerate(val_tokens):
            if token in self.vocabulary.word2index:
                val_ids[idx] = self.vocabulary.word2index[token] 
            else:
                val_ids[idx] = UNK_TOKEN
        
        num_batches = trn_ids.size(0) // batch_size
        trn_ids = trn_ids[: num_batches * batch_size]
        
        num_batches = val_ids.size(0) // batch_size
        val_ids = trn_ids[: num_batches * batch_size]

        return trn_ids.view(batch_size, -1), val_ids.view(batch_size, -1)

تعیین هایپرپارامترها

max_vocab = 30000
min_count = 1

# LSTM hyper-parameters
embed_size = 1500
hidden_size = 1500
num_layers = 2

# Training hyper-parameters
num_epochs = 40
batch_size = 50
seq_length = 60
learning_rate = 0.001
corpus = Corpus(train_data_tok)
trn_ids, val_ids = corpus.get_data(max_vocab, min_count, batch_size)
vocab_size = len(corpus.vocabulary)

# save vocabs and ids
pickle.dump(corpus.vocabulary, open(f'{data_dir}/vocab.pkl', 'wb'))
np.save(f'{data_dir}/trn_ids.npy', trn_ids.view(-1).numpy())
np.save(f'{data_dir}/val_ids.npy', val_ids.view(-1).numpy())

استفاده از LSTM برای مدل سازی زبان

class LSTM_LM(nn.Module):
    def __init__(self, vocab_size, embed_size, hidden_size, num_layers=1, drop=0.35, tie=True):
        super(LSTM_LM, self).__init__()
        
        if tie:
            embed_size = hidden_size
        
        self.embed_size = embed_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
            
        self.dropout = nn.Dropout(drop)
        self.embedding = nn.Embedding(vocab_size, embed_size)
        self.lstm = nn.LSTM(embed_size, hidden_size, num_layers, batch_first=True, dropout=0.35)
        self.fc = nn.Linear(hidden_size, vocab_size)
        
        if tie:
            # Use the same weights both for embedding and classification
            self.fc.weight.data = self.embedding.weight.data
            
        self.init_weights()
        
    def init_weights(self):
        self.embedding.weight.data.uniform_(-0.1, 0.1)
        self.fc.weight.data.uniform_(-0.1, 0.1)
        self.fc.bias.data.fill_(0)
        
    def init_hidden(self, batch_size):
        return (to_var(torch.zeros(self.num_layers, batch_size, self.hidden_size)),
                to_var(torch.zeros(self.num_layers, batch_size, self.hidden_size)))
        
    def forward(self, x, hidden):
        # embed word ids to vectors
        x = self.embedding(x)
        x = self.dropout(x)  # DROPOUT
        
        # forward RNN step
        x, hidden = self.lstm(x, hidden)
        x = self.dropout(x)  # DROPOUT
        
        # reshape output to (bs * seq_length, hidden_size)
        x = x.contiguous().view(x.size(0) * x.size(1), x.size(2))
        
        # decode hidden states of all time steps
        x = self.fc(x)
        
        return x, hidden
    
    def save(self, epoch, loss, save_to=output_dir):
        if not os.path.exists(output_dir):
            os.mkdir(output_dir)
        filename = output_dir + '/lm-masnavi-epoch-{}-em-{}-hi-{}-nl-{}-{:.2f}-{:.2f}.pth'.format(
            epoch, self.embed_size, self.hidden_size, self.num_layers, loss, np.exp(loss))
        torch.save(self.state_dict(), filename)

تعریف مدل

# model
model = LSTM_LM(vocab_size, embed_size, hidden_size, num_layers, drop=0.65)
if use_gpu:
    model = model.cuda()

# loss function
criterion = nn.CrossEntropyLoss()
if use_gpu:
    criterion = criterion.cuda()
    
# optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.9)

آموزش مدل

hist = train(model, trn_ids, val_ids, 
             criterion, optimizer, scheduler, 
             num_epochs, batch_size, seq_length)

نمونه گیری

N = 1000
p = torch.FloatTensor([0.5, 0.25, 0.15, 0.10])
# p = torch.FloatTensor([50, 25, 15, 10])

counter = Counter()

# Draw N samples
for _ in range(N):
    sample = torch.multinomial(p, num_samples=1, replacement=True)[0]
    counter[sample] += 1
    
# print samples and their counts
for sample, count in counter.most_common():
    print("{:d}: {:2d}".format(sample, count))
def get_sample(model, sample_len):
    model.eval()
    sample = ''
    state = model.init_hidden(1)

    # select a random word id to start sampling
    probs = torch.ones(vocab_size)
    inp = to_var(torch.multinomial(probs, num_samples=1).unsqueeze(1), volatile=True)

    for i in tqdm_notebook(range(sample_len)):
        output, state = model(inp, state)

        # Sample an id
        probs = output.squeeze().data.exp().cpu()
        word_id = torch.multinomial(probs, 1)[0]

        # Feed sampled word id to next time step
        inp.data.fill_(word_id)

        # write to file
        word = corpus.vocabulary.index2word[word_id]
        if word == '<EOS>':
            sample += '\n'
        else:
            sample += ' ' + word
    
    return sample
sample = get_sample(model, 300)
print(sample)

 

author-avatar

درباره محمد اسماعیلی

علاقه مند به مفاهیم هوش مصنوعی، دیتاساینس و سئو؛ مطالبی که برام جالب باشه رو اینجا می نویسم، و این دلیل بر متخصص بودن من در اون حوزه ها نمیشه😊

نوشته های مرتبط

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *