torchtext的使用
目錄
1.引言
這兩天看了一些torchtext的東西, 其實torchtext的教程並不是很多,當時想着使用torchtext的原因就是, 其中提供了一個BucketIterator的桶排序迭代器,通過這個輸出的批數據中,每批文本長度基本都是一致的,當時就感覺這個似乎可以提升模型的性能,畢竟每次訓練的數據的長度都差不多,不會像以前一樣像狗牙一樣參差不齊,看着揪心了。
但是實際使用起來, 其實發現torchtext並不是那麼好用,而且實際實驗結果表明,隨機抽取文本和按文本長度排序之後再去抽取文本,模型的性能似乎都是一樣的,我在CNN和LSTM上面都做了實驗,發現沒啥提升。至於爲啥沒用預訓練模型做實驗,主要是發現torchtext的限制太多了,而預訓練模型都有自己的tokenizer方式,靈活性比較高,導致這個模塊使用起來特別的彆扭。最主要的還是因爲torchtext封裝的太厲害了,而它的官方文檔說實話,寫的也不是特別清楚,有些地方用的就有點糊里糊塗,還得靠做實驗來看看到底是啥情況,有些操作感覺還是沒有自己動手寫感覺踏實。
github上面關於torchtext+HuggingFace 的使用討論
2.torchtext簡介
因爲也沒研究的特別深,所以這裏介紹的就是平時用的一些方法,而torchtext本身是有很多其他的用途的,例如它裏面提供了很多nlp方面的數據集,可以直接加載使用,也提供了不少訓練好的詞向量之類的,這一點和torchvisio是一樣的(但是限於國內的一些網絡,這些功能一般好像都是處於荒廢的狀態)。
一般我們常用的torchtext主要是3大部分,分別是Field,Dataset和Iteration三大部分。其中Dataset
是對數據進行一些處理操作等,這點和torchvisio還是比較像的,但是這裏的Dataset
其實能做的操作並不是很多,因爲它的很多任務都被Field
所承擔了;至於Iteration
,這個和torchvisio模塊中的DataLoader
很類似,但是Iteration
提供了很多NLP裏面需要的功能,例如對每個batch的數據進行batch內排序,設置排序的關鍵字等。
3.代碼講解
這裏使用一個基於LSTM的情感分析模型進行講解torchtext的簡單使用
3.1 Field
一般來說,第一步是首先設定好Field,Field是對數據格式的一種定義,可以看到官方提供的Field參數如下所示:
~Field.sequential – 輸入的數據是否是序列型的,如果不是,將不使用tokenzie對數據進行處理
~Field.use_vocab – 是否使用Vocab對象,也就是使用輸入的詞向量,這裏以後會講到,如果不使用,那麼輸入Field的對象一定是數字類型的。
~Field.init_token – 給example數據的開頭加一個token,感覺類似<CLS>標籤,example之後會將
~Field.eos_token – 給example數據加一個結束token
~Field.fix_length – 設定序列的長度,不夠的進行填充
~Field.dtype – 表示輸入的example數據的類型
~Field.preprocessing – 將example數據在tokenize之後,但在轉換成數值之前的管道設置,這個我沒有用過,所以不確定具體怎麼用
~Field.postprocessing – 將example數據在轉換成數值之後,但在變成tensor數據之前的管道設置. 管道將每個batch的數據當成一個list進行處理
~Field.lower – 是否將輸入的文本變成小寫
~Field.tokenize – 設置一個tokenize分詞器給Field用,這裏也有內置的一些分詞器可以用
~Field.tokenizer_language – 分詞器tokenize的語言,這裏是針對SpaCy的
~Field.include_lengths – 是否在返回文本序列的時候返回文本的長度,這裏是對LSTM的變長輸入設置非常好用
~Field.batch_first – 輸出的數據的維度中batch的大小放到前面
~Field.pad_token – 用於填充文本的關鍵字,默認是<pad>
~Field.unk_token – 用於填充不在詞彙表中的關鍵字,默認是<unk>
~Field.pad_first – 是否將填充放到文本最前面
~Field.truncate_first – 是否從文本開始的地方將文本截斷
~Field.stop_words – 停止詞的設置
~Field.is_target – 沒看明白乾啥用的
可以看到,Field的功能還是非常多的,畢竟這個是用來對輸入的文本進行一些數據的預處理,首先進行初始化Field,如下所示:
def tokenize(x): return jieba.lcut(x)
sentence_field = Field(sequential=True, tokenize=tokenize,
lower=False, batch_first=True, include_lengths=True)
label_field = Field(sequential=False, use_vocab=False)
然後Field的代碼就這麼多。
3.2 Dataset
這裏是Dataset的代碼介紹,這裏我們需要做的一般是繼承torchtext.data.Dataset
類,然後重寫自己的Dataset,不過torchtext提供了一些內置的Dataset,如果處理的數據不是特別複雜,直接使用官方內置的一些Dataset可以滿足要求,那麼直接使用官方的就行了。不過一般都要自己定製一下吧,畢竟很多時候數據的輸入都要進行一些修改,官方的不一定能滿足要求。
寫Dataset的時候,最主要的其實是一個Example和Field的結合,可以看下面的代碼:
from torchtext.data import Dataset, Example
class SentenceDataset(Dataset):
def __init__(self, data_path, sentence_field, label_field):
fields = [('sentence', sentence_field), ('label', label_field)]
examples = []
with open(data_path, 'r') as f_json:
file_content = json.load(f_json)
self.sentence_list = []
self.label_list = []
for data in file_content:
self.sentence_list.append(data['sentence'])
self.label_list.append(data['label'])
for index in trange(len(self.sentence_list)):
examples.append(Example.fromlist([self.sentence_list[index], self.label_list[index]], fields))
super().__init__(examples, fields)
@staticmethod
def sort_key(input):
return len(input.sentence)
可以看到,這裏將輸入的文本,標籤和Field進行綁定,也就是告訴Field它要具體處理哪些東西,然後最後還要使用super().__init__(examples, fields)
來調用一下父類的初始化方法。這裏還有一個def sort_key(input):
方法,這個方法是幫助後面的Iteration進行數據排序用的關鍵字,其實在Iteration中可以直接設置用於排序的關鍵字,但是因爲在前面的Field裏面使用了include_lengths
關鍵字,好像導致後面的 Iteration直接指定關鍵字無法進行正常的排序,然後在Dataset裏面直接指定關鍵字,Iteration就可以直接進行正常的排序。這裏不排除是我代碼寫的有問題,但是使用上面代碼的那種方法可以正常排序是經過實驗驗證的。
對於上面的example和Field進行綁定的時候,因爲我這裏使用的訓練數據和測試數據都是有標籤的,所以標籤那個位置直接就寫上了,但是一般測試數據都是沒有標籤,如果是沒有標籤的,將上面的代碼改成下面這樣就行了:
examples.append(Example.fromlist([self.sentence_list[index], None], fields)) # 沒有標籤就使用None來代替
當然這裏的Dataset還有其他的一些功能,例如split
方法等,這些大家可以去看官方的API文檔。
3.4 使用Field構建詞向量表
在使用LSTM等一些網絡的時候,我們喜歡使用詞向量對網絡的Embedding層進行初始化,而Field中的build_vocab
提供了這些處理操作。首先我們需要將詞向量讀取進來,在一個txt文本中保存如下格式的詞向量:
公司 0.3919137716293335 0.4011327922344208 ...
公園 0.17394110560417175 0.10003302991390228 ...
公佈 0.24726712703704834 0.06743448227643967 ...
公正 0.1161544919013977 0.07093961536884308 ...
公道 0.44119203090667725 0.21420961618423462 ...
首先需要注意的是,詞向量表中只包含詞語的詞向量,不包含等關鍵字的詞向量,這部分的詞向量可以在build_vocab
進行一定的設置,首先看build_vocab
的參數(實際是torchtext.vocab.Vocab
的參數,但是這裏是經過build_vocab
處理之後將參數傳入到torchtext.vocab.Vocab
):
counter – 這裏用來計算輸入的數據的頻率的,其實沒太看明白英文翻譯,不過這裏對應build_vocab輸入的是Dataset,經過build_vocab處理之後傳遞給torchtext.vocab.Vocab
max_size – 詞向量表的最大大小
min_freq – 參與轉換成詞向量的最小詞頻率,不滿足這個詞頻率的直接就是<unk>了
specials – 需要添加到詞向量表中的一些特殊字符,默認的包含['<unk'>,'<pad>']兩種,也是因爲這個參數,所以我們的txt文件中的詞向量不需要包含這兩個特殊字符。
vectors – 用於加載的預訓練好的詞向量
unk_init (callback) – 用於初始化未知詞彙的詞向量,默認是0
vectors_cache – 存放緩存的目錄,這個不一定在這裏設定,在Vectors類中設置也行
specials_first – 改變特徵字符在詞彙表中的位置,是放在最前面還是放在最後面
不過在build_vocab
詞向量之前,我們需要先將詞向量加載進來,這裏的操作就是使用torchtext.vocab.Vectors
,主要包含以下參數:
name – 這裏實際應該是保存詞向量文件的位置
cache – 用於存放緩存的目錄
url – url for download if vectors not found in cache
unk_init (callback) – 初始化未知詞的詞向量
max_vectors (int) – 用於設置詞向量的大小,API文檔中說,一般保存詞向量的文件中,是按照詞向量的頻率大小從上大小進行排序,然後存儲到文件中,所以放棄一些低頻率的詞向量,對性能可能沒影響,但是還可以節省內存
上面的參數介紹完了,就可以來看代碼了,代碼其實並不怎麼複雜:
cache = 'data/vector_cache'
if not os.path.exists(cache):
os.mkdir(cache)
vectors = Vectors(name=vector_path, cache=cache)
sentence_field.build_vocab(train_dataset, min_freq=min_freq, vectors=vectors)
3.3 Iteration
最後是Iteration方面的介紹,這部分官方提供了三個Iteration,當然也可以自定義,但是目前看官方提供的Iteration就可以滿足大部分情況,所以這裏就沒有進行自定義的Iteration。
train_iterator = BucketIterator(train_dataset, batch_size=batch_size,
device='cuda',
sort_within_batch=True, shuffle=True)
test_iterator = Iterator(test_dataset, batch_size=batch_size,
device='cuda', train=False,
shuffle=False, sort=False, sort_within_batch=True)
這裏使用了BucketIterator
和Iterator
,因爲BucketIterator
可以自動的選擇長度類似的文本組成一個batch,所以用於訓練數據,而測試數據一般而言不想進行排序或者其他的操作,就使用了Iterator
,這裏就不對Iterator
的一些參數進行介紹了,一些重要的常用的基本就是上面列出來的那些了。
4. 總結
torchtext模塊對於傳統的一些模型,例如CNN,LSTM等,使用起來還是比較方便的,特別是一些常用的操作,一些常用的數據集等等,torchtext都是包含的,但是對於目前的預訓練模型,大家可以去網上找一下資料,比如github上面關於torchtext+HuggingFace 的使用討論,其實受限於torchtext本身的一些規則太多,導致很多操作都被隱層了起來,一些想要自定義的功能卻不怎麼方便去自定義,所以感覺對於預訓練模型,或者一些其他的,例如多模態方面的模型,使用起來並不怎麼方便,估計也是因爲這個原因,導致一些相關教程和討論比較少。然後我基於CNN和LSTM,寫了一個torchtext的代碼,所以這裏貼出來github地址本文代碼的github地址