您好,登錄后才能下訂單哦!
使用pytorch和torchtext怎么對文本進(jìn)行分類?很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
1. 文本數(shù)據(jù)預(yù)處理
首先數(shù)據(jù)存儲在三個csv文件中,分別是train.csv,valid.csv,test.csv,第一列存儲的是文本數(shù)據(jù),例如情感分類問題經(jīng)常是用戶的評論review,例如imdb或者amazon數(shù)據(jù)集。第二列是情感極性polarity,N分類問題的話就有N個值,假設(shè)值得范圍是0~N-1。
下面是很常見的文本預(yù)處理流程,英文文本的話不需要分詞,直接按空格split就行了,這里只會主要說說第4點。
1、去除非文本部分
2、分詞
3、去除停用詞
4、對英文單詞進(jìn)行詞干提取(stemming)和詞型還原(lemmatization)
5、轉(zhuǎn)為小寫
6、特征處理
Bag of Words
Tf-idf
N-gram
Word2vec
詞干提取和詞型還原
from nltk.stem import SnowballStemmer stemmer = SnowballStemmer("english") # 選擇語言 from nltk.stem import WordNetLemmatizer wnl = WordNetLemmatizer()
SnowballStemmer較為激進(jìn),轉(zhuǎn)換有可能出現(xiàn)錯誤,這里較為推薦使用WordNetLemmatizer,它一般只在非??隙ǖ那闆r下才進(jìn)行轉(zhuǎn)換,否則會返回原來的單詞。
stemmer.stem('knives') # knive wnl.lemmatize('knives') # knife
因為我沒有系統(tǒng)學(xué)習(xí)和研究過NLTK的代碼,所以就不多說了,有興趣的可以自己去閱讀NLTK的源碼。
2. 使用torchtext加載文本數(shù)據(jù)
本節(jié)主要是用的模塊是torchtext里的data模塊,處理的數(shù)據(jù)同上一節(jié)所描述。
首先定義一個tokenizer用來處理文本,比如分詞,小寫化,如果你已經(jīng)根據(jù)上一節(jié)的詞干提取和詞型還原的方法處理過文本里的每一個單詞后可以直接分詞就夠了。
tokenize = lambda x: x.split()
或者也可以更保險點,使用spacy庫,不過就肯定更耗費時間了。
import spacy spacy_en = spacy.load('en') def tokenizer(text): return [toke.text for toke in spacy_en.tokenizer(text)]
然后要定義Field,至于Field是啥,你可以簡單地把它理解為一個能夠加載、預(yù)處理和存儲文本數(shù)據(jù)和標(biāo)簽的對象。我們可以用它根據(jù)訓(xùn)練數(shù)據(jù)來建立詞表,加載預(yù)訓(xùn)練的Glove詞向量等等。
def DataLoader(): tokenize = lambda x: x.split() # 用戶評論,include_lengths設(shè)為True是為了方便之后使用torch的pack_padded_sequence REVIEW = data.Field(sequential=True,tokenize=tokenize, include_lengths=True) # 情感極性 POLARITY = data.LabelField(sequential=False, use_vocab=False, dtype = torch.long) # 假如train.csv文件并不是只有兩列,比如1、3列是review和polarity,2列是我們不需要的數(shù)據(jù), # 那么就要添加一個全是None的元組, fields列表存儲的Field的順序必須和csv文件中每一列的順序?qū)?yīng), # 否則review可能就加載到polarity Field里去了 fields = [('review', REVIEW), (None, None), ('polarity', POLARITY)] # 加載train,valid,test數(shù)據(jù) train_data, valid_data, test_data = data.TabularDataset.splits( path = 'amazon', train = 'train.csv', validation = 'valid.csv', test = 'test.csv', format = 'csv', fields = fields, skip_header = False # 是否跳過文件的第一行 ) return REVIEW, POLARITY, train_data
加載完數(shù)據(jù)可以開始建詞表。如果本地沒有預(yù)訓(xùn)練的詞向量文件,在運行下面的代碼時會自動下載到當(dāng)前文件夾下的'.vector_cache'文件夾內(nèi),如果本地已經(jīng)下好了,可以用Vectors指定文件名name,路徑cache,還可以使用Glove。
from torchtext.vocab import Vectors, Glove import torch REVIEW, POLARITY, train_data = DataLoader() # vectors = Vectors(name='glove.6B.300d.txt', cache='.vector_cache') REVIEW.build_vocab(train_data, # 建詞表是用訓(xùn)練集建,不要用驗證集和測試集 max_size=400000, # 單詞表容量 vectors='glove.6B.300d', # 還有'glove.840B.300d'已經(jīng)很多可以選 unk_init=torch.Tensor.normal_ # 初始化train_data中不存在預(yù)訓(xùn)練詞向量詞表中的單詞 ) # print(REVIEW.vocab.freqs.most_common(20)) 數(shù)據(jù)集里最常出現(xiàn)的20個單詞 # print(REVIEW.vocab.itos[:10]) 列表 index to word # print(REVIEW.vocab.stoi) 字典 word to index
接著就是把預(yù)訓(xùn)練詞向量加載到model的embedding weight里去了。
pretrained_embeddings = REVIEW.vocab.vectors model.embedding.weight.data.copy_(pretrained_embeddings) UNK_IDX = REVIEW.vocab.stoi[REVIEW.unk_token] PAD_IDX = REVIEW.vocab.stoi[REVIEW.pad_token] # 因為預(yù)訓(xùn)練的權(quán)重的unk和pad的詞向量不是在我們的數(shù)據(jù)集語料上訓(xùn)練得到的,所以最好置零 model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM) model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM)
然后用torchtext的迭代器來批量加載數(shù)據(jù),torchtext.data里的BucketIterator非常好用,它可以把長度相近的文本數(shù)據(jù)盡量都放到一個batch里,這樣最大程度地減少padding,數(shù)據(jù)就少了很多無意義的0,也減少了矩陣計算量,也許還能對最終準(zhǔn)確度有幫助(誤)?我憑直覺猜的,沒有做實驗對比過,但是至少能加速訓(xùn)練迭代應(yīng)該是沒有疑問的,如果哪天我有錢了買了臺好點的服務(wù)器做完實驗再來補(bǔ)充。
sort_within_batch設(shè)為True的話,一個batch內(nèi)的數(shù)據(jù)就會按sort_key的排列規(guī)則降序排列,sort_key是排列的規(guī)則,這里使用的是review的長度,即每條用戶評論所包含的單詞數(shù)量。
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits( (train_data, valid_data, test_data), batch_size=32, sort_within_batch=True, sort_key = lambda x:len(x.review), device=torch.device('cpu'))
最后就是加載數(shù)據(jù)喂給模型了。
for batch in train_iterator: # 因為REVIEW Field的inclue_lengths為True,所以還會包含一個句子長度的Tensor review, review_len = batch.review # review.size = (seq_length, batch_size) , review_len.size = (batch_size, ) polarity = batch.polarity # polarity.size = (batch_size, ) predictions = model(review, review_lengths) loss = criterion(predictions, polarity) # criterion = nn.CrossEntropyLoss()
3. 使用pytorch寫一個LSTM情感分類器
下面是我簡略寫的一個模型,僅供參考
import torch.nn as nn import torch.nn.functional as F from torch.nn.utils.rnn import pack_padded_sequence import torch class LSTM(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, n_layers, bidirectional, dropout, pad_idx): super(LSTM, self).__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx) self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=n_layers, bidirectional=bidirectional, dropout=dropout) self.Ws = nn.Parameter(torch.Tensor(hidden_dim, output_dim)) self.bs = nn.Parameter(torch.zeros((output_dim, ))) nn.init.uniform_(self.Ws, -0.1, 0.1) nn.init.uniform_(self.bs, -0.1, 0.1) self.dropout = nn.Dropout(p=0.5) def forward(self, x, x_len): x = self.embedding(x) x = pack_padded_sequence(x, x_len) H, (h_n, c_n) = self.lstm(x) h_n = self.dropout(h_n) h_n = torch.squeeze(h_n) res = torch.matmul(h_n, self.Ws) + self.bs y = F.softmax(res, dim=1) # y.size(batch_size, output_dim) return y
訓(xùn)練函數(shù)
def train(model, iterator, optimizer, criterion): epoch_loss = 0 num_sample = 0 correct = 0 model.train() for batch in iterator: optimizer.zero_grad() review, review_lengths = batch.review polarity = batch.polarity predictions = model(review, review_lengths) correct += torch.sum(torch.argmax(preds, dim=1) == polarity) loss = criterion(predictions, polarity) loss.backward() epoch_loss += loss.item() num_sample += len(batch) optimizer.step() return epoch_loss / num_sample, correct.float() / num_sample if __name__ == '__main__': for epoch in range(N_EPOCHS): train_loss, acc = train(model, train_iter, optimizer, criterion) print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {acc* 100:.2f}%')
注意事項和遇到的一些坑
文本情感分類需不需要去除停用詞?
應(yīng)該是不用的,否則acc有可能下降。
data.TabularDataset.splits雖然好用,但是如果你只想加載訓(xùn)練集,這時候如果直接不給validation和test參數(shù)賦值,那么其他代碼和原來一樣,比如這樣
train_data = data.TabularDataset.splits( path = '', train = 'train.csv', format = 'csv', fields = fields, skip_header = False # 是否跳過文件的第一行 )
那么底下你一定會報錯,因為data.TabularDataset.splits返回的是一個元組,也就是如果是訓(xùn)練驗證測試三個文件都給了函數(shù),就返回(train_data, valid_data, test_data),這時候你用三個變量去接受函數(shù)返回值當(dāng)然沒問題,元組會自動拆包。
當(dāng)只給函數(shù)一個文件train.csv時,函數(shù)返回的是(train_data)而非train_data,因此正確的寫法應(yīng)該如下
train_data = data.TabularDataset.splits( path = '', train = 'train.csv', format = 'csv', fields = fields, skip_header = False # 是否跳過文件的第一行 )[0] # 注意這里的切片,選擇元組的第一個也是唯一一個元素賦給train_data
同理data.BucketIterator.splits也有相同的問題,它不但返回的是元組,它的參數(shù)datasets要求也是以元組形式,即(train_data, valid_data, test_data)進(jìn)行賦值,否則在下面的運行中也會出現(xiàn)各種各樣奇怪的問題。
如果你要生成兩個及以上的迭代器,那么沒問題,直接照上面寫就完事了。
如果你只要生成train_iterator,那么正確的寫法應(yīng)該是下面這樣
train_iter = data.BucketIterator( train_data, batch_size=32, sort_key=lambda x:len(x.review), sort_within_batch=True, shuffle=True # 訓(xùn)練集需要shuffle,但因為驗證測試集不需要 # 可以生成驗證和測試集的迭代器直接用data.iterator.Iterator類就足夠了 )
出現(xiàn)的問題 x = pack_padded_sequence(x, x_len) 當(dāng)數(shù)據(jù)集有長度為0的句子時, 就會后面報錯
Adagrad效果比Adam好的多
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。