1.實驗數據獲取:
這裏的實驗數據是本人自己提取的,具體方式是:
(大家可以根據自己喜好進行如下步驟)
1.選取3個不同類別的文本,每類500篇,共1500篇。
2.使用TF-IDF或詞頻等方式,從每個類型的文本中選出100個特徵詞,3個類別,共300個特徵詞。將300個特徵詞存入一個list中。
3.使用300個特徵詞的列表去遍歷每一篇文本,如果第x個特徵詞在該文本中出現次數爲n,則對應該文本的特徵list的第x爲記爲n。1500篇文本,1500個特徵list分別對應每個文本。
4.對每個文本設置標籤,使用one-hot方式表示即可
5.將其文本順序打亂即可,但保證其對應關係不變 文本——文本特徵list——標籤
例如:
這裏我使用TF-IDF從3個不同類別的文本中提取到的300個特徵詞:
我們甚至可以從這些詞中看出這三類文本的類別分別爲:房產,星座,遊戲
當然,有些詞的區分性不是太好,我們可以通過增加文本數量,設置更精確的停用詞實現更好的效果。
提取一篇文本的特徵list:
文本特徵list的每一位與特徵詞list的每一位一一對應,例如文本特徵list[1]=11,對應特徵詞list[1]=“一個”,即表示"一個"這個詞在該文本中出現11次。
再對這篇文本設置標籤:
[‘0’, ‘1’, ‘0’]
表明該文本屬於第二類。
2.代碼實現:
關於理論實現這裏就不做講解(如果想要學習,推薦深度學習入門——基於Python的理論與實現-圖靈教育-[日]齋藤康毅 著),
直接上代碼,具體解釋在註釋中:
代碼參考於:
Tensorflow中文社區
BiliBili視頻 深度學習框架Tensorflow學習與應用
import tensorflow as tf
import dataN #這裏是自己寫的獲取數據的python文件,可以獲取到1200個訓練數據,訓練標籤。300個測試數據,測試標籤
train_dataN,train_labelN,test_dataN,test_labelN=dataN.dataone()
#placeholder爲tensor操作創建佔位符,可以在TensorFlow運行某一計算時根據該佔位符輸入具體的值
x=tf.placeholder("float",shape=[None,300])
#None表示一次傳入數據的個數未知,他會根據實際情況自動設置
y_=tf.placeholder("float",shape=[None,3])
#y_爲正確解標籤,x爲輸入的數據
#定義兩個函數用於初始化W,b:
def weight_variable(shape):
initial=tf.truncated_normal(shape,stddev=0.1) #正態分佈
return tf.Variable(initial)
def bias_variable(shape):
initial=tf.constant(0.1,shape=shape)
return tf.Variable(initial)
#卷積層:
def conv2d(x,W):
return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding="SAME")
# x input tersor of shape [batch,in_height,in_wight,in_channels]
# stride步幅,padd填充,x輸入數據,W權重張量|濾波器,0邊距(padding size)的模板
# strides[0]=strides[3]=1 (默認), strides[1]代表x方向的步長,strides[2]代表y方向的步長
# SAME就是一種padding方法,另一個是VALID,在實際中多采用SAME使卷積層不改變數據大小
# 如果padding設置爲SAME,則輸入圖片大小和輸出圖片大小是一致的,如果是VALID則圖片經過濾波器後可能會變小
# 大家可以去了解下SAME和VALID兩種方式(這很重要!!!)
#池化層:
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#ksize窗口大小|過濾器大小,stride步幅,pdd填充,x輸入數據
#ksize[0]=ksize=[3]=1(默認),ksize[1]代表x方向長,ksize[2]代表y方向長
x_text=tf.reshape(x,[-1,10,30,1]) #改變x的形狀,[1*300,通道數1]變爲[10*30,通道數1],-1表示batch(一個批次)數量未知,1表示通道數爲1
#第一層:::
W_conv1=weight_variable([2,6,1,10])
# 卷積的權重張量形狀是[2,6,1,10],前兩個維度是採樣窗口的大小,接着是輸入的通道數目,最後是輸出的通道數目(表示使用多少卷積核),這裏使用10個卷積核從一個平面抽取特徵,得到10個通道
# 10就是指卷積核的數量,每種卷積只對某些特徵敏感,獲取的特徵有限。
# 因此將多種不同的卷積核分別對圖像進行處理,就能獲得更多的特徵。
# 每個卷積核按照規則掃描完圖像後,就輸出一張特徵圖像(feature map),
# 因此10也指輸出的特徵圖
b_conv1=bias_variable([10])
#對於每一個輸出通道都有一個對應的偏置量,前面因爲每張圖片生成10個特徵,這裏也要對應10個偏置值
h_conv1=tf.nn.relu(conv2d(x_text,W_conv1)+b_conv1)
#這裏的卷積層不改變大小,即數據仍然爲10*30,但因爲使用了10個卷積核進行特徵抽取,產生了10個通道
h_pool1=max_pool_2x2(h_conv1)
#池化得到10個5*15的平面
#第二層:::
W_conv2=weight_variable([2,2,10,20])
#2x2的採樣窗口,20個卷積核從10個平面抽取特徵,輸出20個特徵平面
#輸入的是10個2*15的矩陣,這層要輸出的矩陣個數爲20
b_conv2=bias_variable([20])
#每一個卷積覈對應一個偏置量
h_conv2=tf.nn.relu(conv2d(h_pool1,W_conv2)+b_conv2)
#這裏的卷積層不改變數據大小
h_pool2=max_pool_2x2(h_conv2)
#池化輸出20個3*8的矩陣
#連接層:::初始化第一個全連接層
W_fc1=weight_variable([3*8*20,500])
# 上一層(卷積層)傳入3*8*20個神經元,我們設置全連接層有500個神經元
b_fc1=bias_variable([500])
h_pool2_flat=tf.reshape(h_pool2,[-1,3*8*20])
#把池化層2的輸出扁平化化爲1維,-1表示batch數量未知
h_fc1=tf.nn.relu(tf.matmul(h_pool2_flat,W_fc1)+b_fc1)
# 第一個全連接層的輸出。得到一個長度爲500的向量
keep_prob=tf.placeholder("float")
h_fc1_drop=tf.nn.dropout(h_fc1,keep_prob)
#dropout方法能在運行中自動忽略一些神經元,防止過擬合
#初始化第二個全連接層:
W_fc2=weight_variable([500,3])
#500對應上一層的輸出,3對應這一場的輸出爲3類
b_fc2=bias_variable([3])
prediction=tf.nn.softmax(tf.matmul(h_fc1_drop,W_fc2)+b_fc2)
#計算概率
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_,logits=prediction))
#計算 logits 和 labels 之間的 softmax 交叉熵。
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#學習率爲1e-4,使用梯度下降的方式最小化交叉熵
correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1))
#求正確率
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
with tf.Session() as sess: #啓動圖
sess.run(tf.initialize_all_variables()) #初始化全部變量
for i in range(120): #我們共1200個訓練數據,這裏我們10個爲1批次batch 進行
TrainData_batch = train_dataN[i * 10:(i + 1) * 10]
label_batch = train_labelN[i * 10:(i + 1) * 10]
sess.run(train_step,feed_dict={x:TrainData_batch,y_:label_batch,keep_prob:0.7})
#keep_prob:0.7 只有70%的神經元工作
"""
if i%4==0: #訓練每進行4個批次,我們就傳入一個批次的測試數據,檢測當前的正確率
t=int(i/4)
TestData_batch=test_dataN[t * 10:(t + 1) * 10]
TestLabel_batch=test_labelN[t * 10:(t + 1) * 10]
#pre=sess.run([prediction,y_],feed_dict={x:TrainData_batch,y_:TestLabel_batch,keep_prob:1.0})
#print(pre) pre爲預測結果和真實標籤,大家可以打印出來看看
acc=sess.run(accuracy,feed_dict={x:TrainData_batch,y_:TestLabel_batch,keep_prob:1.0})
print("Iter "+str(t)," accuracy= "+str(acc))
"""
print(sess.run(accuracy,feed_dict={x:test_dataN,y_:test_labelN,keep_prob:1.0}))
#再訓練完成後,一次性將所有測試數據傳入進行測試,得到總的正確率。
#注意!使用該方式,則不能使用使用上方 檢測當前的正確率,不然會造成正確率偏大(因爲測試數據也經過了訓練)
3.運行結果:
通過測試,我得到的測試數據總的正確率爲70%~80%左右。
而每進行4個批次,傳入一個批次的測試數據時正確率總時很低,並且沒有呈現明顯的遞增變化,這可能因爲測試和訓練的數據過少造成:
4.需要注意的點:
以下僅僅是我在學習過程中產生的思考和總結,並不代表正確答案,大家如果有更好解答或其它理解可以在評論區討論。
1.通過CNN的方式實現文本分類是否具有可靠性?
我認爲可靠性並不如CNN對圖片的分類,因爲文本是1維數據,爲了通過卷積神經網絡,需要將其變化爲多維度,而在變化後(比如1x300變爲10x30),這樣的變化有很多種。而無論怎麼改變,原本是1維的數據,在多維度上究竟有何聯繫?我認爲,這樣意義不大的變換並不會帶來什麼更好的結果。
對此,我設定了一個簡單的神經網絡,不使用卷積,對上述文本進行了分類實驗
其代碼如下(講解就不再給出了):
import dataN
import tensorflow as tf
train_dataN,train_labelN,test_dataN,test_labelN=dataN.dataone()
sess = tf.InteractiveSession()
x = tf.placeholder("float", shape=[None, 300])
y_ = tf.placeholder("float", shape=[None, 3])
W = tf.Variable(tf.zeros([300, 3]))
b = tf.Variable(tf.zeros([3]))
sess.run(tf.global_variables_initializer())
y = tf.nn.softmax(tf.matmul(x, W) + b)
cross_entropy = -tf.reduce_sum(y_ * tf.log(y))
train_step = tf.train.GradientDescentOptimizer(0.001).minimize(cross_entropy)
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
for i in range(120):
TrainData_batch = train_dataN[i * 10:(i + 1) * 10]
label_batch = train_labelN[i * 10:(i + 1) * 10]
train_step.run(feed_dict={x: TrainData_batch, y_: label_batch})
for t in range(30):
TestData_batch = test_dataN[t * 10:(t + 1) * 10]
TestLabel_batch = test_labelN[t * 10:(t + 1) * 10]
result = sess.run(correct_prediction, feed_dict={x: TestData_batch, y_: TestLabel_batch})
print(result)
對測試數據的運行結果如下:
我們可以發現,通過簡單的神經網絡訓練,反而得到了更優的效果。但這並不能完全說明上述猜測,或許多層卷積網絡有更好的文本分類方式,在之後的學習中或許能解此疑惑。
2.關於卷積層和池化層的輸出大小:
這裏就不進行講解了,在瞭解卷積和池化的原理,和padding方法SAME和VALID後,我們就能自己推導得出
3.卷積層和全連接層的作用:
卷積取的是局部特徵,全連接就是把以前的局部特徵重新通過權值矩陣組裝成完整的圖。(來自知乎)
4.全連接層神經元個數對結果的影響:
以下結果僅針對上述代碼和數據:
2048個神經元,正確率:75%~85%
1024個神經元,正確率:75%~85%
500個神經元,正確率:70%~80%
100個神經元,正確率:60%~80%
50個神經元,正確率:50%~70%
神經元是否是越多越好呢?
在測試到4096個神經元時,結果出現了不穩定線性,有的很高超過了85%,有的甚至不到50%
在詢問了一位大佬後,得到了這樣的結果:
神經元數量的設置就是玄學,並不是越高或者越低越好,正確率也不一定與神經元數量呈正比,往往通過多次調試進行選擇。
5.關於數據:
在數據傳入之前,一定要對數據進行亂序處理!
在未經過亂序處理或者打亂不完全或有規則的打亂,神經網絡往往會學習到這些規律性變化,對於滿足這些規律的測試文本,往往能獲得到驚人的效果。
在之前,我嘗試過使用【第一類文本,第二類文本,第三類文本,第一類文本…】的有規律性方式打亂訓練文本和測試文本,再經過簡單的神經網絡(無卷積),這時其正確率就已經達到了98%以上,這樣的結果明顯是不和常規的,僅僅對有此規律的文本起作用。