前文已經(jīng)介紹了TextCNN的基本原理,如果還不熟悉的建議看看原理:【深度學(xué)習(xí)】textCNN論文與原理[1]及一個簡單的基于pytorch的圖像分類案例:【深度學(xué)習(xí)】卷積神經(jīng)網(wǎng)絡(luò)-圖片分類案例(pytorch實現(xiàn))[2]。 現(xiàn)在介紹一下如何使用textcnn進行文本分類,該部分內(nèi)容參考了:Pytorch-textCNN(不調(diào)用torchtext與調(diào)用torchtext)[3]。當然原文寫的也挺好的,不過感覺不夠工程化。現(xiàn)在我們就來看看如何使用pytorch和cnn來進行文本分類吧。
本文進行的任務(wù)本質(zhì)是一個情感二分類的任務(wù),語料內(nèi)容為英文,其格式如下:
一行文本即實際的一個樣本,樣本數(shù)據(jù)分別在neg.txt和pos.txt文件中。在進行數(shù)據(jù)預(yù)處理之前,先介紹一下本任務(wù)可能用到的一些參數(shù),這些參數(shù)我放在了一個config.py的文件中,內(nèi)容如下:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author: juzipi
@file: config.py
@time:2020/12/06
@description: 配置文件
"""
LARGE_SENTENCE_SIZE = 50 # 句子最大長度
BATCH_SIZE = 128 # 語料批次大小
LEARNING_RATE = 1e-3 # 學(xué)習(xí)率大小
EMBEDDING_SIZE = 200 # 詞向量維度
KERNEL_LIST = [3, 4, 5] # 卷積核長度
FILTER_NUM = 100 # 每種卷積核輸出通道數(shù)
DROPOUT = 0.5 # dropout概率
EPOCH = 20 # 訓(xùn)練輪次
下面就是數(shù)據(jù)預(yù)處理過程啦,先把代碼堆上來:
import numpy as np
from collections import Counter
import random
import torch
from sklearn.model_selection import train_test_split
import config
random.seed(1000)
np.random.seed(1000)
torch.manual_seed(1000)
def read_data(filename):
"""
數(shù)據(jù)讀取
:param filename: 文件路徑
:return: 數(shù)據(jù)讀取內(nèi)容(整個文檔的字符串)
"""
with open(filename, "r", encoding="utf8") as reader:
content = reader.read()
return content
def get_attrs():
"""
獲取語料相關(guān)參數(shù)
:return: vob_size, pos_text, neg_text, total_text, index2word, word2index
"""
pos_text, neg_text = read_data("corpus/pos.txt"), read_data("corpus/neg.txt")
total_text = pos_text + '\n' + neg_text
text = total_text.split()
vocab = [w for w, f in Counter(text).most_common() if f > 1]
vocab = ['<pad>', '<unk>'] + vocab
index2word = {i: word for i, word in enumerate(vocab)}
word2index = {word: i for i, word in enumerate(vocab)}
return len(word2index), pos_text, neg_text, total_text, index2word, word2index
def convert_text2index(sentence, word2index, max_length=config.LARGE_SENTENCE_SIZE):
"""
將語料轉(zhuǎn)成數(shù)字化數(shù)據(jù)
:param sentence: 單條文本
:param word2index: 詞語-索引的字典
:param max_length: text_cnn需要的文本最大長度
:return: 對語句進行截斷和填充的數(shù)字化后的結(jié)果
"""
unk_id = word2index['<unk>']
pad_id = word2index['<pad>']
# 對句子進行數(shù)字化轉(zhuǎn)換,對于未在詞典中出現(xiàn)過的詞用unk的index填充
indexes = [word2index.get(word, unk_id) for word in sentence.split()]
if len(indexes) < max_length:
indexes.extend([pad_id] * (max_length - len(indexes)))
else:
indexes = indexes[:max_length]
return indexes
def number_sentence(pos_text, neg_text, word2index):
"""
語句數(shù)字化處理
:param pos_text: 正例全部文本
:param neg_text: 負例全部文本
:param word2index: 詞到數(shù)字的字典
:return: 經(jīng)過訓(xùn)練集和測試集劃分的結(jié)果X_train, X_test, y_train, y_test
"""
pos_indexes = [convert_text2index(sentence, word2index) for sentence in pos_text.split('\n')]
neg_indexes = [convert_text2index(sentence, word2index) for sentence in neg_text.split('\n')]
# 為了方便處理,轉(zhuǎn)化為numpy格式
pos_indexes = np.array(pos_indexes)
neg_indexes = np.array(neg_indexes)
total_indexes = np.concatenate((pos_indexes, neg_indexes), axis=0)
pos_targets = np.ones((pos_indexes.shape[0])) # 正例設(shè)置為1
neg_targets = np.zeros((neg_indexes.shape[0])) # 負例設(shè)置為0
total_targets = np.concatenate((pos_targets, neg_targets), axis=0).reshape(-1, 1)
return train_test_split(total_indexes, total_targets, test_size=0.2)
def get_batch(x, y, batch_size=config.BATCH_SIZE, shuffle=True):
"""
構(gòu)建迭代器,獲取批次數(shù)據(jù)
:param x: 需要劃分全部特征數(shù)據(jù)的數(shù)據(jù)集
:param y: 需要劃分全部標簽數(shù)據(jù)的數(shù)據(jù)集
:param batch_size: 批次大小
:param shuffle: 是否打亂
:return: 以迭代器的方式返回數(shù)據(jù)
"""
assert x.shape[0] == y.shape[0], "error shape!"
if shuffle:
# 該函數(shù)是對[0, x.shape[0])進行隨機排序
shuffled_index = np.random.permutation(range(x.shape[0]))
# 使用隨機排序后的索引獲取新的數(shù)據(jù)集結(jié)果
x = x[shuffled_index]
y = y[shuffled_index]
n_batches = int(x.shape[0] / batch_size) # 統(tǒng)計共幾個完整的batch
for i in range(n_batches - 1):
x_batch = x[i*batch_size: (i + 1)*batch_size]
y_batch = y[i*batch_size: (i + 1)*batch_size]
yield x_batch, y_batch
其中各個函數(shù)怎么使用以及相關(guān)參數(shù)已經(jīng)在函數(shù)的說明中了,這里再贅述就耽誤觀眾姥爺?shù)臅r間了,哈哈。這些代碼我放在了一個dataloader.py的python文件中了,相信你會合理的使用它,如果有啥不明白的可以留言交流哦。
我依然先把代碼堆出來,不是網(wǎng)傳那么一句話嘛:“talk is cheap, show me code”,客官,代碼來咯:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
"""
@author: juzipi
@file: model.py
@time:2020/12/06
@description:
"""
import numpy as np
import torch
from torch import nn, optim
import matplotlib.pyplot as plt
import config
import dataloader
import utils
class TextCNN(nn.Module):
# output_size為輸出類別(2個類別,0和1),三種kernel,size分別是3,4,5,每種kernel有100個
def __init__(self, vocab_size, embedding_dim, output_size, filter_num=100, kernel_list=(3, 4, 5), dropout=0.5):
super(TextCNN, self).__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim)
# 1表示channel_num,filter_num即輸出數(shù)據(jù)通道數(shù),卷積核大小為(kernel, embedding_dim)
self.convs = nn.ModuleList([
nn.Sequential(nn.Conv2d(1, filter_num, (kernel, embedding_dim)),
nn.LeakyReLU(),
nn.MaxPool2d((config.LARGE_SENTENCE_SIZE - kernel + 1, 1)))
for kernel in kernel_list
])
self.fc = nn.Linear(filter_num * len(kernel_list), output_size)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
x = self.embedding(x) # [128, 50, 200] (batch, seq_len, embedding_dim)
x = x.unsqueeze(1) # [128, 1, 50, 200] 即(batch, channel_num, seq_len, embedding_dim)
out = [conv(x) for conv in self.convs]
out = torch.cat(out, dim=1) # [128, 300, 1, 1],各通道的數(shù)據(jù)拼接在一起
out = out.view(x.size(0), -1) # 展平
out = self.dropout(out) # 構(gòu)建dropout層
logits = self.fc(out) # 結(jié)果輸出[128, 2]
return logits
# 數(shù)據(jù)獲取
VOB_SIZE, pos_text, neg_text, total_text, index2word, word2index = dataloader.get_attrs()
# 數(shù)據(jù)處理
X_train, X_test, y_train, y_test = dataloader.number_sentence(pos_text, neg_text, word2index)
# 模型構(gòu)建
cnn = TextCNN(VOB_SIZE, config.EMBEDDING_SIZE, 2)
# print(cnn)
# 優(yōu)化器選擇
optimizer = optim.Adam(cnn.parameters(), lr=config.LEARNING_RATE)
# 損失函數(shù)選擇
criterion = nn.CrossEntropyLoss()
def train(model, opt, loss_function):
"""
訓(xùn)練函數(shù)
:param model: 模型
:param opt: 優(yōu)化器
:param loss_function: 使用的損失函數(shù)
:return: 該輪訓(xùn)練模型的損失值
"""
avg_acc = []
model.train() # 模型處于訓(xùn)練模式
# 批次訓(xùn)練
for x_batch, y_batch in dataloader.get_batch(X_train, y_train):
x_batch = torch.LongTensor(x_batch) # 需要是Long類型
y_batch = torch.tensor(y_batch).long()
y_batch = y_batch.squeeze() # 數(shù)據(jù)壓縮到1維
pred = model(x_batch) # 模型預(yù)測
# 獲取批次預(yù)測結(jié)果最大值,max返回最大值和最大索引(已經(jīng)默認索引為0的為負類,1為為正類)
acc = utils.binary_acc(torch.max(pred, dim=1)[1], y_batch)
avg_acc.append(acc) # 記錄該批次正確率
# 使用損失函數(shù)計算損失值,預(yù)測值要放在前
loss = loss_function(pred, y_batch)
# 清楚之前的梯度值
opt.zero_grad()
# 反向傳播
loss.backward()
# 參數(shù)更新
opt.step()
# 所有批次數(shù)據(jù)的正確率計算
avg_acc = np.array(avg_acc).mean()
return avg_acc
def evaluate(model):
"""
模型評估
:param model: 使用的模型
:return: 返回當前訓(xùn)練的模型在測試集上的結(jié)果
"""
avg_acc = []
model.eval() # 打開模型評估狀態(tài)
with torch.no_grad():
for x_batch, y_batch in dataloader.get_batch(X_test, y_test):
x_batch = torch.LongTensor(x_batch)
y_batch = torch.tensor(y_batch).long().squeeze()
pred = model(x_batch)
acc = utils.binary_acc(torch.max(pred, dim=1)[1], y_batch)
avg_acc.append(acc)
avg_acc = np.array(avg_acc).mean()
return avg_acc
# 記錄模型訓(xùn)練過程中模型在訓(xùn)練集和測試集上模型預(yù)測正確率表現(xiàn)
cnn_train_acc, cnn_test_acc = [], []
# 模型迭代訓(xùn)練
for epoch in range(config.EPOCH):
# 模型訓(xùn)練
train_acc = train(cnn, optimizer, criterion)
print('epoch={},訓(xùn)練準確率={}'.format(epoch, train_acc))
# 模型測試
test_acc = evaluate(cnn)
print("epoch={},測試準確率={}".format(epoch, test_acc))
cnn_train_acc.append(train_acc)
cnn_test_acc.append(test_acc)
# 模型訓(xùn)練過程結(jié)果展示
plt.plot(cnn_train_acc)
plt.plot(cnn_test_acc)
plt.ylim(ymin=0.5, ymax=1.01)
plt.title("The accuracy of textCNN model")
plt.legend(["train", 'test'])
plt.show()
多說無益程序都在這,相關(guān)原理已經(jīng)介紹了,各位讀者慢慢品嘗,有事call me。 對了,程序最后運行的結(jié)果如下:
模型分類結(jié)果
其中隨著模型的訓(xùn)練,模型倒是在訓(xùn)練集上效果倒好(畢竟模型在訓(xùn)練集上調(diào)整參數(shù)嘛),測試集上的結(jié)果也慢慢上升最后還略有下降,可見開始過擬合咯。本任務(wù)沒有使用一些預(yù)訓(xùn)練的詞向量以及語料介紹,總體也就1萬多條,在測試集達到了這個效果也是差強人意了。主要想說明如何使用pytorch構(gòu)建textcnn模型,實際中的任務(wù)可能更復(fù)雜,對語料的處理也更麻煩(數(shù)據(jù)決定模型的上限嘛)。或許看完這個文章后,你對損失函數(shù)、優(yōu)化器、數(shù)據(jù)批次處理等還有一些未解之謎和改進的期待,我盡力在工作之余書寫相關(guān)文章以饗讀者,敬請關(guān)注哦。打條廣告,想及時看到最新個人原創(chuàng)文章記得關(guān)注“AIAS編程有道”公眾號哦,我在那里等你。至于本文的全部代碼和語料,我都上傳到github上了:https://github.com/Htring/NLP_Applications
TML 文本格式化實例
此例演示如何在一個 HTML 文件中對文本進行格式化
標簽 描述
<b> 定義粗體文本。
<big> 定義大號字。
<em> 定義著重文字。
<i> 定義斜體字。
<small> 定義小號字。
<strong>定義加重語氣。
<sub> 定義下標字。
<sup> 定義上標字。
<ins> 定義插入字。
<del> 定義刪除字。
<html> <body> <b>This text is bold</b> <br /> <strong>This text is strong</strong> <br /> <big>This text is big</big> <br /> <em>This text is emphasized</em> <br /> <i>This text is italic</i> <br /> <small>This text is small</small> <br /> This text contains <sub>subscript</sub> <br /> This text contains <sup>superscript</sup> </body> </html>
HTML 文本格式化實例
覽器的兼容性越來越好,移動端基本是清一色的webkit,經(jīng)常會用到css的不同尺寸/長度單位,這里做個整理。
絕對單位
px : Pixel 像素
pt : Points 磅
pc : Picas 派卡
in : Inches 英寸
mm : Millimeter 毫米
cm : Centimeter 厘米
q : Quarter millimeters 1/4毫米
相對單位
% : 百分比
em : Element meter 根據(jù)文檔字體計算尺寸
rem : Root element meter 根據(jù)根文檔( body/html )字體計算尺寸
ex : 文檔字符“x”的高度
ch : 文檔數(shù)字“0”的的寬度
vh : View height 可視范圍高度
vw : View width 可視范圍寬度
vmin : View min 可視范圍的寬度或高度中較小的那個尺寸
vmax : View max 可視范圍的寬度或高度中較大的那個尺寸
運算
calc : 四則運算
實例:
h1 { width: calc(100% - 10px + 2rem)
單位比例
1in = 2.54cm = 25.4 mm = 101.6q = 72pt = 6pc = 96px
詳細
絕對單位
px - Pixel 像素
像素 px 相對于設(shè)備顯示器屏幕分辨率而言。
div { font-size: 12px } p { text-indent: 24px }
pt Points 磅
1 pt = 1/72 英寸
div { font-size: 10pt } p { height: 100pt }
pc Picas 派卡
十二點活字(印刷中使用的),相當于我國新四號鉛字的尺寸。
div { font-size: 10pc } p { height: 10pc }
in Inches 英寸
div { font-size: 10in } p { height: 10in }
mm Millimeter 毫米
div { font-size: 10mm } p { height: 10mm }
cm Centimeter 厘米
div { font-size: 10cm } p { height: 10cm }
q Quarter millimeters 1/4毫米
div { font-size: 20q } p { height: 100q }
相對單位
% 百分比
相對于父元素寬度
<body>
em Element meter 根據(jù)文檔計算尺寸
相對于當前文檔對象內(nèi)文本的字體尺寸而言,若未指定字體大小則繼承自上級元素,以此類推,直至 body,若 body 未指定則為瀏覽器默認大小。
<body>
rem Root element meter 根據(jù)根文檔( body/html )字體計算尺寸
相對于根文檔對象( body/html )內(nèi)文本的字體尺寸而言,若未指定字體大小則繼承為瀏覽器默認字體大小。
ex 文檔字符“x”的高度
相對于字符“x”的高度,通常為字體高度的一半,若未指定字體尺寸,則相對于瀏覽器的默認字體尺寸。
至于為啥是x,我TM也不知道。
ch 文檔數(shù)字“0”的的寬度
同上,相對于數(shù)字“0”的寬度。
一張圖解釋:
vh View height / vw View Width - 可視范圍
相對于可視范圍的高度和寬度,可視范圍被均分為 100 單位的 vh/vw;可視范圍是指屏幕可見范圍,不是父元素的,百分比是相對于包含它的最近的父元素的高度和寬度。
假設(shè)設(shè)備可視范圍為高度 900px,寬度 750px,則, 1 vh = 900px/100 = 9px,1vw = 750px/100 = 7.5px
。
vmin / vmax 可視范圍的寬度或高度中較小/較大的那個尺寸
假設(shè)瀏覽器的寬度設(shè)置為 1200px,高度設(shè)置為 800px, 則 1vmax = 1200/100px = 12px, 1vmin = 800/100px = 8px 。
如果寬度設(shè)置為 600px,高度設(shè)置為 1080px, 則 1vmin = 6px, 1vmax = 10.8px 。
假設(shè)需要讓一個元素始終在屏幕上可見:
.box {
假設(shè)需要讓這個元素始終鋪滿整個視口的可見區(qū)域:
.box {
em、rem 是實際生產(chǎn)中我們最常用到的單位,可以使用其配合媒體查詢改變 body 字體大小來實現(xiàn)響應(yīng)式的設(shè)計,vh、vw、vmin、vmax也可以很方便地幫助我們控制響應(yīng)尺寸,但實際的可控性可能不如前者,具體按照我們的業(yè)務(wù)需求去實踐吧!
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。