1. 程式人生 > >sklearn半監督學習

sklearn半監督學習

摘要:半監督學習很重要,為什麼呢?因為人工標註資料成本太高,現在大家參加比賽的資料都是標註好的了,那麼如果老闆給你一份沒有標註的資料,而且有幾百萬條,讓你做個分類什麼的,你怎麼辦?不可能等標註好資料再去訓練模型吧,所以你得會半監督學習演算法。

不過我在這裡先打擊大家一下,用sklearn的包做不了大資料量的半監督學習,我用的資料量大概在15000條以上就要報MemoryError錯誤了,這個是我最討厭的錯誤。暫時我還沒有解決的辦法,如果同志們是小資料量,那就用這個做著玩玩吧

報MemoryError錯誤怎麼辦?sklearn提供這麼全的文件當然會有這部分的考慮啦。看這裡——sklearn 中的模型對於大資料集的處理

。可以用partial_fit增量式計算,可惜只針對部分演算法,對於半監督學習沒有辦法。

好了,該說正題了,最近看了sklearn關於半監督學習的例子,它裡面有三個例子,在這裡我主要想分享一下第三個例子——用半監督學習演算法做數字識別


一. 資料集的解讀

  • 部落格園裡圖片貌似更清晰一些,大家也可以前往去看
    點選這裡

首先我們來看一下這份資料集的特點

 

煉己者

煉己者

二. 程式碼的解讀

sklearn官方例子——用半監督學習做數字識別

我們來看一下操作流程

  • 一共330個點,都是已經標註好的了,我們把其中的320個點賦值為-1,這樣就可以假裝這320個點都是沒有標註的了
  • 訓練一個只有10個標記點的標籤傳播模型
  • 然後從所有資料中選擇要標記的前五個最不確定的點,把它們(帶有正確標籤)放到原來的10個點中
  • 接下來可以訓練15個標記點(原始10個 + 5個新點)
  • 重複這個過程四次,就可以使用30個標記好的點來訓練模型
  • 可以通過改變max_iterations將這個值增加到30以上

以上是sklearn的操作流程,大家可能會有點糊塗
實際任務應該是這樣的。假設我們有一份資料集,共330個數字,其中前十個是已知的,已經標註好了,後320個是未知的,需要我們預測出來的。

  • 首先把這330個數據全部都放到半監督學習演算法裡,訓練模型,預測那320個標籤
  • 然後用某種方法(看下面程式碼的操作)得知這320個數據裡最不確定的前5個數據,對它進行人工標註,然後把它放到之前的10個數據裡,現在就有15個已知資料了
  • 這樣迴圈個幾次,已標註的資料就變多了,那麼分類器的效果肯定也就變好了

1.匯入各種資料包

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from sklearn import datasets
from sklearn.semi_supervised import label_propagation
from sklearn.metrics import classification_report,confusion_matrix

# 再加下面這個,不然會報錯
from scipy.sparse.csgraph import *

2.讀取資料集

digits = datasets.load_digits()
rng = np.random.RandomState(0)
# indices是隨機產生的0-1796個數字,且打亂
indices = np.arange(len(digits.data))
rng.shuffle(indices)

# 取前330個數字來玩
X = digits.data[indices[:330]]
y = digits.target[indices[:330]]
images = digits.images[indices[:330]]

n_total_samples = len(y) # 330
n_labeled_points = 10 # 標註好的資料共10條
max_iterations = 5 # 迭代5次

unlabeled_indices = np.arange(n_total_samples)[n_labeled_points:] # 未標註的資料320條
f = plt.figure() # 畫圖用的

3. 訓練模型且畫圖

建議大家把自己不懂的地方打印出來看看是啥意思,比如下面

for i in range(max_iterations):
    if len(unlabeled_indices) == 0:
        print("no unlabeled items left to label") # 沒有未標記的標籤了,全部標註好了
        break
    y_train = np.copy(y)
    y_train[unlabeled_indices] = -1 #把未標註的資料全部標記為-1,也就是後320條資料
    
    lp_model = label_propagation.LabelSpreading(gamma=0.25,max_iter=5) # 訓練模型
    lp_model.fit(X,y_train)
    
    predicted_labels = lp_model.transduction_[unlabeled_indices] # 預測的標籤
    true_labels = y[unlabeled_indices] # 真實的標籤
    
    cm = confusion_matrix(true_labels,predicted_labels,
                         labels = lp_model.classes_)
    
    print("預測標籤")
    print(predicted_labels)
    print("真實標籤")
    print(true_labels)
    print('----------------------------------------------')

經對比發現預測的標籤只有7個類,而非10個類

  • 原因就是我們一開始訓練的那10個數據只有7個類,所以預測其他320條資料的時候只能預測出這7個類
預測標籤
[2 8 6 6 6 6 1 9 5 8 8 2 8 7 7 6 7 9 2 9 7 7 6 8 9 1 8 1 9 1 1 6 7 7 9 9 7
 6 2 1 9 6 7 9 9 9 9 1 6 9 9 2 8 7 2 9 2 6 9 1 8 9 5 1 2 1 2 2 9 7 2 8 6 9
 9 8 7 5 1 2 9 9 8 1 7 7 1 1 6 1 5 9 2 6 8 9 2 1 7 7 9 7 8 9 7 5 8 2 1 9 2
 9 8 1 1 7 9 6 1 5 8 9 9 6 9 9 5 7 9 6 2 8 6 9 6 1 5 1 5 9 9 1 8 9 6 1 8 9
 1 7 6 7 6 5 6 9 8 8 9 8 6 1 9 7 2 6 8 8 6 7 1 9 6 9 9 8 9 8 9 7 7 9 7 8 9
 7 8 9 6 7 5 9 1 7 6 1 9 8 9 9 9 9 2 1 1 2 1 1 1 9 2 1 9 8 7 6 1 8 8 1 6 9
 9 6 9 2 2 9 7 6 1 1 9 7 2 7 8 6 6 7 5 2 8 7 2 7 9 5 7 9 9 2 6 5 9 7 1 8 8
 9 8 6 7 6 9 2 6 1 8 8 1 6 7 5 2 1 5 8 2 1 6 9 1 5 7 9 1 6 2 9 9 1 2 2 9 9
 6 9 7 2 9 7 5 8 6 7 8 2 8 7 9 7 2 6 5 1 5 1 9 8]
真實標籤
[2 8 6 6 6 6 1 0 5 8 8 7 8 4 7 5 4 9 2 9 4 7 6 8 9 4 3 1 0 1 8 6 7 7 1 0 7
 6 2 1 9 6 7 9 0 0 5 1 6 3 0 2 3 4 1 9 2 6 9 1 8 3 5 1 2 8 2 2 9 7 2 3 6 0
 5 3 7 5 1 2 9 9 3 1 7 7 4 8 5 8 5 5 2 5 9 0 7 1 4 7 3 4 8 9 7 9 8 2 6 5 2
 5 8 4 8 7 0 6 1 5 9 9 9 5 9 9 5 7 5 6 2 8 6 9 6 1 5 1 5 9 9 1 5 3 6 1 8 9
 8 7 6 7 6 5 6 0 8 8 9 8 6 1 0 4 1 6 3 8 6 7 4 5 6 3 0 3 3 3 0 7 7 5 7 8 0
 7 8 9 6 4 5 0 1 4 6 4 3 3 0 9 5 9 2 1 4 2 1 6 8 9 2 4 9 3 7 6 2 3 3 1 6 9
 3 6 3 2 2 0 7 6 1 1 9 7 2 7 8 5 5 7 5 2 3 7 2 7 5 5 7 0 9 1 6 5 9 7 4 3 8
 0 3 6 4 6 3 2 6 8 8 8 4 6 7 5 2 4 5 3 2 4 6 9 4 5 4 3 4 6 2 9 0 1 7 2 0 9
 6 0 4 2 0 7 9 8 5 4 8 2 8 4 3 7 2 6 9 1 5 1 0 8]
----------------------------------------------

3.1 完整程式碼

for i in range(max_iterations):
    if len(unlabeled_indices) == 0:
        print("no unlabeled items left to label") # 沒有未標記的標籤了,全部標註好了
        break
    y_train = np.copy(y)
    y_train[unlabeled_indices] = -1 #把未標註的資料全部標記為-1,也就是後320條資料
    
    lp_model = label_propagation.LabelSpreading(gamma=0.25,max_iter=5) # 訓練模型
    lp_model.fit(X,y_train)
    
    predicted_labels = lp_model.transduction_[unlabeled_indices] # 預測的標籤
    true_labels = y[unlabeled_indices] # 真實的標籤
    
    cm = confusion_matrix(true_labels,predicted_labels,
                         labels = lp_model.classes_)
    
    print("iteration %i %s" % (i,70 * "_")) # 列印迭代次數
    print("Label Spreading model: %d labeled & %d unlabeled (%d total)"
         % (n_labeled_points,n_total_samples-n_labeled_points,n_total_samples))
    
    print(classification_report(true_labels,predicted_labels))
    
    print("Confusion matrix")
    print(cm)
    
    # 計算轉換標籤分佈的熵
    # lp_model.label_distributions_作用是Categorical distribution for each item
    pred_entropies = stats.distributions.entropy(
    lp_model.label_distributions_.T)
    
    # 選擇分類器最不確定的前5位數字的索引
    # 首先計算出所有的熵,也就是不確定性,然後從320箇中選擇出前5個熵最大的
    # numpy.argsort(A)提取排序後各元素在原來陣列中的索引。具體情況可看下面
    #  np.in1d 用於測試一個數組中的值在另一個數組中的成員資格,返回一個布林型陣列。具體情況可看下面
    uncertainty_index = np.argsort(pred_entropies)[::1]
    uncertainty_index = uncertainty_index[
        np.in1d(uncertainty_index,unlabeled_indices)][:5] # 這邊可以確定每次選前幾個作為不確定的數,最終都會加回到訓練集
    
    # 跟蹤我們獲得標籤的索引
    delete_indices = np.array([])
    
    # 視覺化前5次的結果
    if i < 5:
        f.text(.05,(1 - (i + 1) * .183),
              'model %d\n\nfit with\n%d labels' %
              ((i + 1),i*5+10),size=10)
    for index,image_index in enumerate(uncertainty_index):
        # image_index是前5個不確定標籤
        # index就是0-4
        image = images[image_index]

        # 視覺化前5次的結果
        if i < 5:
            sub = f.add_subplot(5,5,index + 1 + (5*i))
            sub.imshow(image,cmap=plt.cm.gray_r)
            sub.set_title("predict:%i\ntrue: %i" % (
                lp_model.transduction_[image_index],y[image_index]),size=10)
            sub.axis('off')
        
        # 從320條裡刪除要那5個不確定的點
        # np.where裡面的引數是條件,返回的是滿足條件的索引
        delete_index, = np.where(unlabeled_indices == image_index)
        delete_indices = np.concatenate((delete_indices,delete_index))
        
    unlabeled_indices = np.delete(unlabeled_indices,delete_indices)
    # n_labeled_points是前面不確定的點有多少個被標註了
    n_labeled_points += len(uncertainty_index)
    
f.suptitle("Active learning with label propagation.\nRows show 5 most"
          "uncertain labels to learn with the next model")
plt.subplots_adjust(0.12,0.03,0.9,0.8,0.2,0.45)
plt.show()

3.2 numpy.argsort()函式

  • 提取排序後各元素在原來陣列中的索引
import numpy as np
B=np.array([[4,2,3,55],[5,6,37,8],[-7,68,9,0]])
print('B:')
print(B)

print('')
print('預設輸出')
print(np.argsort(B))#預設的輸出每行元素的索引值。這些索引值對應的元素是從小到大排序的。

看列印的結果

B:
[[ 4  2  3 55]
 [ 5  6 37  8]
 [-7 68  9  0]]

預設輸出
[[1 2 0 3]
 [0 1 3 2]
 [0 3 2 1]]

3.3 np.in1d() 函式

  • 用於測試一個數組中的值在另一個數組中的成員資格,返回一個布林型陣列
values = np.array([6, 0, 0, 3, 2, 5, 6])
np.in1d(values, [2, 3, 6])

看列印的結果

array([ True, False, False,  True,  True, False,  True])

三. 總結

這次主要是想用半監督學習演算法做NLP文字分類,看到sklearn庫里正好有這個演算法包,想拿來試一下,結果跑不了那麼大的資料量,算是失敗了。但是我覺得還是從中瞭解了很多,後面會寫一篇關於它的部落格,裡面關於文字的處理讓我學到了很多,走了很多的彎路。接下來我還會繼續探索怎麼用少標註的資料來做文字分類。



作者:煉己者
連結:https://www.jianshu.com/p/a21817a81890
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。