NLP实战-中文新闻文本分类
目錄
1、思路
2、基于paddle的ERINE模型進行遷移學習訓練
3、分步實現
3.1 獲取數據
(1)數據解壓
(2)將文本轉成變量,這里為了好計算,我只選了新聞標題做文本分類
3.2 中文分詞
基于jieba的分詞
基于paddlehub的lac分詞
3.3 創建語料字典,并將語料序列化
基于語料本身的序列化
基于word2vec或已有字典的序列化
3.4 詞嵌入
基于embedding的詞嵌入
基于word2vec的詞嵌入
3.5 構建分類模型
基于LSTM的分類模型:
實現環境:AI studio
1、思路
文本分類任務步驟通常是
- 文本預處理
文本預處理的方法很多,類似于詞性分析,句法分析,命名實體識別等,在進行文本分類之前,需要將文本進行結構化,常見的方法有one-hot,n_gram,word2vec等,與英文不同(可以簡單用空格和符號進行分詞),中文是比較緊密連接的,結構化之前需要對文本進行分詞,如jieba分詞,此外還需要將分詞之后的語料轉化為ID序列,然后進行詞嵌入(word Embedding)。
- DL特征提取和分類模型
適合文本的dl?model有RNN,LSTM,GRU等。
- 預測
訓練完,然后對數據進行預測。
2、基于paddle的ERINE模型進行遷移學習訓練
教程:https://aistudio.baidu.com/aistudio/projectdetail/1735533?forkThirdPart=1
3、分步實現
3.1 獲取數據
(1)數據解壓
!unzip /home/aistudio/data/data8164/THUCNews.zip解壓之后結果有14個類別,文件夾里邊是新聞文本:
(2)將文本轉成變量,這里為了好計算,我只選了新聞標題做文本分類
(后續覺得數據量太小,就選了全量數據來做,遇到了正則表達式處理慢和分詞慢的情況,推薦FlashText,中英文符號處理)
def read_data(file_dir): z_list = []for parent, dirnames, filenames in os.walk(file_dir):if parent==file_dir:print('父目錄')else:for curDir, dirs, files in os.walk(parent):print("當前文件夾:",curDir)label = curDir.split('/')[-1]title_list = []for i in range(len(files)):fo = open(parent+'/'+files[i],'r',encoding='utf-8')title = fo.readline().strip()title_list.append((title,label))z_list.extend(title_list)return z_list file_dir = '/home/aistudio/THUCNews' data = read_data(file_dir)3.2 中文分詞
-
基于jieba的分詞
-
基于paddlehub的lac分詞
3.3 創建語料字典,并將語料序列化
-
基于語料本身的序列化
(1)構建語料字典
# 構造詞典,統計每個詞的頻率,并根據頻率將每個詞轉換為一個整數id def build_dict(corpus):word_freq_dict = dict()for sentence,_ in corpus:for word in sentence:if word not in word_freq_dict:word_freq_dict[word] = 0word_freq_dict[word] += 1word_freq_dict = sorted(word_freq_dict.items(), key = lambda x:x[1], reverse = True)word2id_dict = dict()word2id_freq = dict()# 一般來說,我們把oov和pad放在詞典前面,給他們一個比較小的id,這樣比較方便記憶,并且易于后續擴展詞表# word2id_dict['[oov]'] = 0# word2id_freq[0] = 1e10# word2id_dict['[pad]'] = 1# word2id_freq[1] = 1e10for word, freq in word_freq_dict:word2id_dict[word] = len(word2id_dict)word2id_freq[word2id_dict[word]] = freqreturn word2id_freq, word2id_dictword2id_freq, word2id_dict = build_dict(jieba_c_list) vocab_size = len(word2id_freq) print("there are totoally %d different words in the corpus" % vocab_size) for _, (word, word_id) in zip(range(10), word2id_dict.items()):print("word %s, its id %d, its word freq %d" % (word, word_id, word2id_freq[word_id]))(2)ID序列化
label_dict ={'時政':1,'星座':2,'股票':3,'彩票':4,'科技':5,'娛樂':6,'房產':7,'社會':8,'財經':9,'游戲':10,'體育':11,'時尚':12,'家居':13,'教育':14} def convert_corpus_to_id(corpus, word2id_dict):data_set = []for sentence, sentence_label in corpus:# 將句子中的詞逐個替換成id,如果句子中的詞不在詞表內,則替換成oov# 這里需要注意,一般來說我們可能需要查看一下test-set中,句子oov的比例,# 如果存在過多oov的情況,那就說明我們的訓練數據不足或者切分存在巨大偏差,需要調整sentence = [word2id_dict[word] if word in word2id_dict \else word2id_dict['[oov]'] for word in sentence] data_set.append((sentence, label_dict[sentence_label]))return data_settrain_corpus = convert_corpus_to_id(jieba_c_list, word2id_dict) print("%d tokens in the corpus" % len(train_corpus)) print(train_corpus[:5])-
基于word2vec或已有字典的序列化
3.4 詞嵌入
-
基于embedding的詞嵌入
embedding在構建神經網絡模型時使用。函數為paddle.nn.Embedding
Embedding(num_embeddings=vocab_size, embedding_dim=hidden_size, sparse=False, weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Uniform(low=-init_scale, high=init_scale)))-
基于word2vec的詞嵌入
Word2Vec有GBOW和skip-gram兩種實現方式,實現的庫有gensim、paddlehub,這里用gensim實現。
import gensim #將取字符串,忽略類別(已轉成ID序列) from gensim.models.word2vec import Word2Vecnum_features = 100 # Word vector dimensionality num_workers = 8 # Number of threads to run in paralleltrain_texts = list(map(lambda x: list(x.split()), train_texts)) model = Word2Vec(train_texts, workers=num_workers, size=num_features) model.init_sims(replace=True)# save model model.save("./word2vec.bin") #也可轉換為txt結果的第一行是(詞數量,詞向量的維度就是num_features)
第二行就是詞向量ID和對應的詞向量
3.5 構建分類模型
基于LSTM的分類模型(此處的class_num改為和新聞類別一致的數量):
from paddle.nn import LSTM, Embedding, Dropout, Linear import paddle.nn.functional as F# 定義一個用于情感分類的網絡實例,SentimentClassifier class SentimentClassifier(paddle.nn.Layer):def __init__(self, hidden_size, vocab_size, class_num=14, num_steps=128, num_layers=1, init_scale=0.1, dropout=None):# 參數含義如下:# 1.hidden_size,表示embedding-size,hidden和cell向量的維度# 2.vocab_size,模型可以考慮的詞表大小# 3.class_num,情感類型個數,可以是2分類,也可以是多分類# 4.num_steps,表示這個情感分析模型最大可以考慮的句子長度# 5.num_layers,表示網絡的層數# 6.init_scale,表示網絡內部的參數的初始化范圍# 長短時記憶網絡內部用了很多Tanh,Sigmoid等激活函數,這些函數對數值精度非常敏感,# 因此我們一般只使用比較小的初始化范圍,以保證效果super(SentimentClassifier, self).__init__()self.hidden_size = hidden_sizeself.vocab_size = vocab_sizeself.class_num = class_numself.init_scale = init_scaleself.num_layers = num_layersself.num_steps = num_stepsself.dropout = dropout# 聲明一個LSTM模型,用來把每個句子抽象成向量self.simple_lstm_rnn = LSTM(input_size=hidden_size, hidden_size=hidden_size, num_layers=num_layers)# 聲明一個embedding層,用來把句子中的每個詞轉換為向量self.embedding = Embedding(num_embeddings=vocab_size, embedding_dim=hidden_size, sparse=False, weight_attr=paddle.ParamAttr(initializer=paddle.nn.initializer.Uniform(low=-init_scale, high=init_scale)))# 在得到一個句子的向量表示后,需要根據這個向量表示對這個句子進行分類# 一般來說,可以把這個句子的向量表示乘以一個大小為[self.hidden_size, self.class_num]的W參數,# 并加上一個大小為[self.class_num]的b參數,從而達到把句子向量映射到分類結果的目的# 我們需要聲明最終在使用句子向量映射到具體情感類別過程中所需要使用的參數# 這個參數的大小一般是[self.hidden_size, self.class_num]self.cls_fc = Linear(in_features=self.hidden_size, out_features=self.class_num, weight_attr=None, bias_attr=None)self.dropout_layer = Dropout(p=self.dropout, mode='upscale_in_train')def forward(self, input, label):# 首先我們需要定義LSTM的初始hidden和cell,這里我們使用0來初始化這個序列的記憶init_hidden_data = np.zeros((self.num_layers, batch_size, embedding_size), dtype='float32')init_cell_data = np.zeros((self.num_layers, batch_size, embedding_size), dtype='float32')# 將這些初始記憶轉換為飛槳可計算的向量# 設置stop_gradient=True,避免這些向量被更新,從而影響訓練效果init_hidden = paddle.to_tensor(init_hidden_data)init_hidden.stop_gradient = Trueinit_cell = paddle.to_tensor(init_cell_data)init_cell.stop_gradient = Trueinit_h = paddle.reshape(init_hidden, shape=[self.num_layers, -1, self.hidden_size])init_c = paddle.reshape(init_cell, shape=[self.num_layers, -1, self.hidden_size])# 將輸入的句子的mini-batch轉換為詞向量表示x_emb = self.embedding(input)x_emb = paddle.reshape(x_emb, shape=[-1, self.num_steps, self.hidden_size])if self.dropout is not None and self.dropout > 0.0:x_emb = self.dropout_layer(x_emb)# 使用LSTM網絡,把每個句子轉換為向量表示rnn_out, (last_hidden, last_cell) = self.simple_lstm_rnn(x_emb, (init_h, init_c))last_hidden = paddle.reshape(last_hidden[-1], shape=[-1, self.hidden_size])# 將每個句子的向量表示映射到具體的情感類別上projection = self.cls_fc(last_hidden)pred = F.softmax(projection, axis=-1)# 根據給定的標簽信息,計算整個網絡的損失函數,這里我們可以直接使用分類任務中常使用的交叉熵來訓練網絡loss = F.softmax_with_cross_entropy(logits=projection, label=label, soft_label=False)loss = paddle.mean(loss)# 最終返回預測結果pred,和網絡的lossreturn pred, loss?
(5)訓練和預測
# 編寫一個迭代器,每次調用這個迭代器都會返回一個新的batch,用于訓練或者預測 def build_batch(word2id_dict, corpus, batch_size, epoch_num, max_seq_len, shuffle = True, drop_last = True):# 模型將會接受的兩個輸入:# 1. 一個形狀為[batch_size, max_seq_len]的張量,sentence_batch,代表了一個mini-batch的句子。# 2. 一個形狀為[batch_size, 1]的張量,sentence_label_batch,每個元素都是非0即1,代表了每個句子的情感類別(正向或者負向)sentence_batch = []sentence_label_batch = []for _ in range(epoch_num): #每個epoch前都shuffle一下數據,有助于提高模型訓練的效果#但是對于預測任務,不要做數據shuffleif shuffle:random.shuffle(corpus)for sentence, sentence_label in corpus:sentence_sample = sentence[:min(max_seq_len, len(sentence))]if len(sentence_sample) < max_seq_len:for _ in range(max_seq_len - len(sentence_sample)):sentence_sample.append(word2id_dict['[pad]'])sentence_sample = [[word_id] for word_id in sentence_sample]sentence_batch.append(sentence_sample)sentence_label_batch.append([sentence_label])if len(sentence_batch) == batch_size:yield np.array(sentence_batch).astype("int64"), np.array(sentence_label_batch).astype("int64")sentence_batch = []sentence_label_batch = []if not drop_last and len(sentence_batch) > 0:yield np.array(sentence_batch).astype("int64"), np.array(sentence_label_batch).astype("int64")#訓練預測 def train():step = 0sentiment_classifier = SentimentClassifier(embedding_size, vocab_size, num_steps=max_seq_len, num_layers=1)# 創建優化器Optimizer,用于更新這個網絡的參數 optimizer = paddle.optimizer.Adam(learning_rate=0.01, beta1=0.9, beta2=0.999, parameters= sentiment_classifier.parameters()) sentiment_classifier.train()for sentences, labels in build_batch(word2id_dict, train_corpus, batch_size, epoch_num, max_seq_len):sentences_var = paddle.to_tensor(sentences)labels_var = paddle.to_tensor(labels)pred, loss = sentiment_classifier(sentences_var, labels_var)# 后向傳播loss.backward()# 最小化lossoptimizer.step()# 清除梯度optimizer.clear_grad()step += 1if step % 100 == 0:print("step %d, loss %.3f" % (step, loss.numpy()[0]))# 我們希望在網絡訓練結束以后評估一下訓練好的網絡的效果# 通過eval()函數,將網絡設置為eval模式,在eval模式中,網絡不會進行梯度更新eval(sentiment_classifier)def eval(sentiment_classifier):sentiment_classifier.eval()# 這里我們需要記錄模型預測結果的準確率# 對于二分類任務來說,準確率的計算公式為:# (true_positive + true_negative) / # (true_positive + true_negative + false_positive + false_negative)tp = 0.tn = 0.fp = 0.fn = 0.for sentences, labels in build_batch(word2id_dict, test_corpus, batch_size, 1, max_seq_len):sentences_var = paddle.to_tensor(sentences)labels_var = paddle.to_tensor(labels)# 獲取模型對當前batch的輸出結果pred, loss = sentiment_classifier(sentences_var, labels_var)# 把輸出結果轉換為numpy array的數據結構# 遍歷這個數據結構,比較預測結果和對應label之間的關系,并更新tp,tn,fp和fnpred = pred.numpy()for i in range(len(pred)):if labels[i][0] == 1:if pred[i][1] > pred[i][0]:tp += 1else:fn += 1else:if pred[i][1] > pred[i][0]:fp += 1else:tn += 1# 輸出最終評估的模型效果print("the acc in the test set is %.3f" % ((tp + tn) / (tp + tn + fp + fn)))(全量數據,帶新聞內容的)預測準確率在0.682,考慮優化特征向量或者更改其他模型!
參考資料:
https://blog.csdn.net/qq_42067550/article/details/106101183
https://paddleinference.paddlepaddle.org.cn/product_introduction/inference_intro.html
https://blog.csdn.net/xiaoxiaojie521/article/details/97240436
總結
以上是生活随笔為你收集整理的NLP实战-中文新闻文本分类的全部內容,希望文章能夠幫你解決所遇到的問題。