天天看點

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

文本分類實驗項目日志(最後更新-2019.07.19)

文章目錄

  • 1 One vs rest & stacking 二分類多分類器文本分類實驗
    • 1.1 描述
    • 1.2 實驗設計
      • 1.2.1 舊方法
      • 1.2.2 新方法
    • 1.3 實驗結果和分析
      • 1.3.1 實驗-舊方法
      • 1.3.2 實驗-新方法
      • 1.3.3 實驗-新方法-新資料
    • 1.4 結論
    • 1.5 內建學習的資源
    • 1.6 項目位址

1 One vs rest & stacking 二分類多分類器文本分類實驗

1.1 描述

根據5-6月份 one vs rest(all)和stacking的融合實驗的若幹問題,6月中旬進行了改進。

5-6月份最終形成的方案為:融入KB;加入第一層分類器;改變KB傳回類目數量,形成多個對比實驗。具體方法和結果詳見1.3.4

5-6月份實驗存在的問題說明如下:

(1) 類間的相似性可能是影響最終結果的主要因素;

(2) 采取one-hot文本表示出現的次元過大以及OOA問題(如對于5:4:1比例,測試集占1/10,若測試集中某個詞未出現在訓練二分類器中的詞空間裡,則不能表示該特征,那麼測試時會失去該特征,進而影響測試結果);

(3) 最終準确率難以提升。

6月初提出的改進方案如下:

(1) 去掉知識庫;

(2) 加入第二層層級分類,使用fasttext訓練二級分類器。

(3) 使用詞向量表示文本特征。

1.2、 1.3給出了具體的實驗設計和結果。

1.2 實驗設計

1.2.1 舊方法

詳細的實驗方法如圖1所示。(舊版本)

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

圖1 文本分類架構

整個實驗可分為6個環節:資料預處理,訓練一級fasttext分類器,建構三級分類知識庫,訓練二分類分類器,訓練融合分類器和測試分類器。每個環節的詳細方法如下:

  1. 資料預處理。原始資料集儲存在兩個txt檔案,大小共約680MB,具體資料形狀如圖2所示。
    大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

    圖2 資料集形狀

    根據實驗設計思路,需要抽取并生成新的資料集。表1列出了4次實驗的資料集詳細資訊,其中1-3次是5月份實驗用資料集,第4次是6月份資料集,第4次與前3次的差別是:(1)類别數量增加一倍左右;(2)每個三級類的文檔數量未固定,即類别資料分布不均衡。(3)細化資料清洗環節(如抽取時考慮類号名字特點、去除無效文檔、去除重複詞語等)。

    表1 資料集介紹

實驗次數 大小 類别數量 文檔數量
1 7MB 10 1000*10=10000
2 332MB 467 1000*467=467000
3 278MB 358 1000*358=358000
4 312MB 773 727367

資料集劃分:每個類下,訓練二分類資料集:訓練融合分類資料集:最終測試集 = 5:4:1。

下面的流程主要以實驗4的資料集為例。

2. 訓練一級分類器。将所有資料集按照每行資料為‘__label__A,word1\tword2’的格式存儲。A代表一級分類号,word1代表文檔分詞後的第一個詞。然後使用fasttext模型訓練分類器,最後抽取總資料集的20%作為一級分類器測試。

3. 訓練二分分類器。對于每個三級類,長度為 l e 3 l e n g t h le3_{length} le3length​,那麼該類的二分訓練集長度為 l e 3 b i n a r y L e n g t h = l e 3 l e n g t h ∗ 0.5 le3_{binaryLength} = le3_{length}*0.5 le3binaryLength​=le3length​∗0.5,從這些資料中随機抽取10個子集,然後加入其它三級類的若幹資料(長度大緻與屬于本類資料相同,記為 s u b n sub_n subn​),最終的目标是使得每個子集包含sub_n個屬于該類的文檔, s u b n sub_n subn​個不屬于該類的文檔。這 2 ∗ s u b n 2*sub_n 2∗subn​個文檔使用機器學習分類算法訓練一個二分類器。共得到773*10=7730個分類器。

4. 訓練融合分類器。将 l e 3 l e n g t h le3_{length} le3length​,那麼該類的融合訓練集長度為 l e 3 m e r g e L e n g t h = l e 3 l e n g t h ∗ 0.4 le3_{mergeLength} = le3_{length}*0.4 le3mergeLength​=le3length​∗0.4,抽取 l e 3 m e r g e L e n g t h le3_{mergeLength} le3mergeLength​條該類文檔和不屬于該類的 l e 3 m e r g e L e n g t h le3_{mergeLength} le3mergeLength​條文檔組合成 2 ∗ l e 3 m e r g e L e n g t h 2*le3_{mergeLength} 2∗le3mergeLength​條文檔,然後用10個該類的二分分類器進行分類,結果以機率形式給出(分到類A81的機率),并儲存為長度為10的向量,标簽為1或0(如“A81”類資料,則标簽記為1,非“A81”類記0)。然後将這些向量進行訓練,得出融合的分類器。這樣共得到773個融合分類器。

5. 測試分類器。測試過程包括5個方面:測試一級分類器;知識庫(KB)分類;測試二分分類器;測試融合分類器;測試系統分類器。其中,測試系統分類器有多種方案,将在1.3.4節的結果和分析中提到。

1.2.2 新方法

新方法與舊方法的差別如下:

  • 去掉KB。原因是經過實驗發現,加入KB會過濾掉正确的類号;去掉KB的副作用是增加測試的時間開銷。
  • 加入二級分類。在一級分類的基礎上加入二級分類,原因是為了降低測試的時間開銷。
  • 文本表示階段采用詞向量方法。原因是原來方法(tf-idf)會生成詞彙映射表檔案(将近900MB),并且會造成次元過大和OOA問題;使用詞向量可以避免前兩個問題,OOA問題與詞向量模型以及資料集有關。
  • 使用詞向量中的詞作為自定義詞典分詞。這樣做的好處是在文本表示階段盡可能使每一個特征(詞語)都能夠用詞向量表示。(目前還在實驗階段)

    新方法的具體步驟如下:

  1. 資料預處理。同1.2.1的1,僅使用第4次抽取的資料集。
  2. 訓練一級分類器。同1.2.1的2。
  3. 訓練二級分類器。将所有資料集按照每行資料為‘__label__A8,word1\tword2’的格式存儲。A8代表二級分類号,word1代表文檔分詞後的第一個詞。然後使用fasttext模型訓練分類器,最後抽取總資料集的20%作為測試。
  4. 訓練二分分類器和融合分類器。步驟與1.2.1的3和4大緻同,僅文本表示階段不同。
  5. 測試分類器。測試過程包括5個方面:測試一級分類器;測試二級分類器;測試二分分類器;測試融合分類器;測試系統分類器。其中,測試系統分類器較複雜,将在1.3中提到。

1.3 實驗結果和分析

1.3.1 實驗-舊方法

本次實驗加入了層級分類(FC)和知識庫(KB),第一層分類器使用fasttext算法,第二層分類器使用svm算法。其中,fasttext的測試結果為0.95.

最終測試時,經過和老師及學長讨論形成了兩種方案,後來經過實驗,又總結了幾種優化政策。下面是這幾種方案的介紹。

(1) 文檔首先經過KB,得到20個三級類号,然後僅在這20個類的20*10=200個二分類器和20個融合分類器中進行分類測試。記為way_1。

(2) 文檔首先經過7730個二分類器和773個融合分類器,然後将所有分類結果按機率從大到小排序,與KB的結果取交集。記為way_2。

(3) 加入一級分類。文檔首先經過一級分類器,得到一級分類号(如A),并經過KB,得到20個三級分類号(如A81,A84,B02,…,C81),然後僅在一級分類号下并且存在于20個三級分類号的分類器中進行分類測試(如在A81,A84中測試)。記為way_3。

以上三種方案,把最後得到排名前三的三級類号作為最終的分類結果。

此外,加入了baseline,即不加入KB和層級分類的結果,如下表所示。

表2 實驗4結果

實驗代号 準确率 運作時間/s
Baseline 0.184 58113(約16h)
Way_1 0.215 14886(約4.1h)
Way_2 0.216 28106(約7.8h)
Way_3 0.231 7035(約2h)

從表2可以看出,增加KB,準确率提高了近0.03,并且時間性能大大改善。way_1和way_2主要差别在于時間,way_1運作時間更短,這與方案(1)和方案(2)的步驟有關。(1)和(2)是計算所有三級類的分類結果并排序,而(1)僅需要計算由知識庫得到的三級類下的分類器并排序,是以(1)的時間性能更好。

實驗Way_3驗證了加入第一層分類器是有效的,時間性能提高了一倍,準确率提高了0.015。是以下一步将考慮以way_3為基礎進行優化。

實驗發現,對于方案(3),增加從知識庫中傳回的結果數目,如經過KB得到30個結果,最終的準确率得到提高。是以,進行了幾組優化實驗,如表3所示。

表3 way_3的優化實驗結果

實驗代号 KB類号數 準确率 運作時間/s
Way_3 20 0.231 7035(約2h)
Way_4 30 0.269 10951(約3h)
Way_5 50 0.305 20411(約5.6h)
Way_6 60 0.312 20150(約5.6h)

由表3,增加KB的類号後,準确率顯著提高,但相應的時間開銷也增加。

為了分析具體分類時每個類的效果,實驗統計了每個文檔在分類時的第一層分類結果、KB分類結果、最終分類結果清單(top10)。以way_6為例,選取三個有代表性的類别,其中其整體的準确率如表4所示,選取其中若幹個文檔具體分析結果如表5所示。

表4 三個類的準确率

三級類号 文檔數 準确率
A849 1526 0.84
D08 820 0.475
Q593 732 0.000

表5 三個文檔的具體分類結果

文檔 KB結果 Le1結果 最終結果(取top10,不足10個全部選擇)
A849_C1 A:12;B:1;C:1;D:20… A [‘A849’, ‘A81’]
D08_C1 B:1;C:1;D:5;Q:41… D [‘D52’,‘D43’,‘D90’]
Q593_C1 S:7;Q:40… Q [‘Q942’,‘Q945’,‘Q95-3’,‘Q25’, ‘Q53’,‘Q935’,‘Q954’,‘Q936’, ‘Q948’, ‘Q24’]

由表5知,在D08類中一個文檔的KB結果中出現了較多的Q類,并且最終分類結果中未命中D08;在Q593類中一個文檔中雖然大部分都是Q類,但最終結果(top3)并沒有命中。

表4和表5隻是列出了個别文檔的分類情況,這三個類全部分類情況見附件1.

此外,為了分析其它類的分類效果,統計了那些效果較差的類别,如圖5所示。其中,橫坐标表示準确率,縱坐标表示低于準确率的三級類類别數量。類别總數為773.

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

圖5 三級類準确率分布情況

從圖5可以看出,大部分三級類的準确率低于0.5,有将近一半低于0.3,有20個三級類準确率為0.0.

基于目前的實驗,可考慮的優化方法包括這幾個方面:改善KB;改善第一層分類器性能;剔除效果非常差的類别。

1.3.2 實驗-新方法

本次實驗加入了第二層級分類(FC),并且文本表示使用詞向量方法(百科訓練的詞向量模型,大小1.7GB),第二層分類器使用fasttext算法,第三層分類器使用svm算法。其中,第一層分類器的測試結果為0.95,第二層分類器的測試結果為0.93.

新方法的幾次過程結果如表6所示。

實驗代号 描述 準确率 運作時間/s
Baseline(Way_6) 舊方法最好結果 0.312 20150(約5.6h)
New_way_1 加入二級分類器 0.334 14169(約3.9h)
New_way_2 在new_way_1基礎上去掉KB 0.484 64741(約18h)
New_way_3 在new_way_2基礎上使用詞向量 0.455 33425(約9.3h)

由表6,New_way_1加入二級分類器的效果提升不大,但是時間開銷降低。New_way_2去掉KB後準确率大幅提升,達到0.484,但是時間開銷增大3倍多。New_way_3使用詞向量,準确率降低了0.03,但時間開銷降低了一倍。

由此分析,新方法是有效的。

對于新方法New_way_3,使用詞向量仍會出現OOA問題,故使用詞向量中的詞作為自定義詞典分詞。這樣做的好處是在文本表示階段盡可能使每一個特征(詞語)都能夠用詞向量表示。這個方法目前在實驗階段。

1.3.3 實驗-新方法-新資料

本次資料選取的是規模為7.32GB資料,經過預處理後,大小為5.16GB。預處理包括分詞、去除無效資料等。分詞使用百度詞向量生成的詞典進行分詞。5.16GB資料的情況如表7所示。

表7 新資料集介紹

實驗次數 大小 類别數量 文檔數量
5 5.16GB 1679

由于類别資料少會影響分類性能,故去掉資料小于500條的三級類别,共得到851個三級類。

根據這851個類進行實驗,方法與1.3.2相同。

使用fasttext訓練一級分類器的準确率為0.92,訓練二級分類器的準确率為0.77。

表8給出了實驗的結果。

實驗代号 描述 準确率 運作時間/s
New_way_data_1 和New_way_3不同的是未加入2級分類器 0.20 147581(約41h)
New_way_data_2 方法和new_way_3同 0.41 35556(約9.8h)

1.4 結論

根據新-舊實驗和若幹次讨論,得到結論如下:

(1) 加入第二層分類器是有效的。對比表6的幾個方案結果,發現加入第二層分類對分類效果和時間性能有一定提升。

(2) 去掉知識庫是有效的。根據表6的結果,去掉KB能大幅提升準确率。

(3) 使用詞向量可以縮短訓練和測試時間。根據表6結果,詞向量在準确率方面和TF-IDF方法相差不大,但時間開銷降低一半,此外,使用詞向量不需要生成額外的映射檔案。

根據以上結論,新方法是目前最好的文本分類方案,将應用到實際的标引項目中。

1.5 內建學習的資源

內建學習-1:https://blog.csdn.net/qq_20386411/article/details/82985219

內建學習-2: https://blog.csdn.net/qq_32690999/article/details/78759463

Stacking原理:https://blog.csdn.net/wstcjf/article/details/77989963

Stacking代碼實作:https://www.jianshu.com/p/5905f19c4df6

模型融合(stacking&blending) :https://blog.csdn.net/u014356002/article/details/54376138

1.6 項目位址

Hierarchical-text-classify

開發OpenWrt路由器上LuCI的子產品

學校裡最近改造了校園網,要求必須用iNode驗證,萬幸的是路由器能刷OpenWrt,并且OpenWrt上有好多iNode認證的開源項目,比如njit8021xclient就非常好用,但其沒有好用的Web管理子產品。好在用Lua為LuCI寫配置子產品很簡單,索性就自己做了一個,現在把開發的流程寫一下,友善初學的同學去做LuCI子產品的開發。

【題外話】

學校裡最近改造了校園網,要求必須用iNode驗證,萬幸的是路由器能刷OpenWrt,并且OpenWrt上有好多iNode認證的開源項目,比如njit8021xclient(以下簡稱njit-client)就非常好用。雖然程式寫的好用,但是配置起來還是稍微麻煩一些的,大家通常的方法是在/etc/init.d下寫啟動腳本,把使用者名、密碼什麼的都直接填進去,但畢竟配置起來不友善,同時日後修改起來也不便。好在用Lua為LuCI寫配置子產品很簡單,索性就自己做了一個,現在把開發的流程寫一下,友善初學的同學去做。為njit-client做好的Web配置界面也已經開源,位址:https://github.com/mayswind/luci-app-njitclient,或者直接下載下傳編譯好(不限平台)的檔案:http://pan.baidu.com/s/1CbPal

【文章索引】

  1. LuCI配置界面開發的架構
  2. 用Lua和UCI接口開發LuCI配置子產品
  3. 在Bash檔案中調用UCI接口
  4. 編譯開發的程式

【一、LuCI配置界面開發的架構】

LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三層架構,同時其使用Lua腳本開發,是以開發LuCI的配置界面不需要編輯任何的Html代碼,除非想自己單獨去建立網頁(View層),否則我們基本上隻需要修改Model層就可以了。官方也有一個如何去建立子產品的說明文檔,雖然寫的比較晦澀:http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo

要為LuCI增加一個新子產品,首先需要建立兩個檔案,一個位于Controller(/usr/lib/lua/luci/controller/)下,定義子產品的入口;另一個位于Model(/usr/lib/lua/luci/model/cbi/)下,為配置子產品實際的代碼。

首先我們定義子產品的入口,在/usr/lib/lua/luci/controller/下建立一個lua檔案,類似如下:

module("luci.controller.控制器名", package.seeall)

function index()
        entry(路徑, 調用目标, _("顯示名稱"), 顯示順序)
        end      

第一行說明了程式和子產品的名稱,比如在controller/目錄建立一個mymodule.lua,那麼就可以寫成“luci.controller.mymodule”,如果你的程式比較多,可能分為好幾個子產品,那麼可以在controller下再常見一個子目錄,比如controller/myapp/,那麼就可以寫成“luci.controller.myapp.mymodule”。

接下來的entry表示添加一個新的子產品入口,官方給出了entry的定義,其中後兩項都是可以為空的:

entry(path, target, title=nil, order=nil)      

第一項是通路的路徑,不過路徑是按字元串數組給定的,比如路徑按如下方式寫“{"click", "here", "now"}”,那麼就可以在浏覽器裡通路“http://192.168.1.1/cgi-bin/luci/click/here/now”來通路這個腳本。而通常我們希望為管理者菜單添加腳本,那麼我們需要按如下方式編寫“{"admin", "一級菜單名", "菜單項名"}”,系統會自動在對應的菜單中生成菜單項。比如想在“網絡”菜單下建立一個菜單項,那麼一級菜單名可以寫為“network”。

第二項為調用目标,調用目标分為三種,分别是執行指定方法(Action)、通路指定頁面(Views)以及調用CBI Module。

  • 第一種可以直接調用指定的函數,比如點選菜單項就直接重新開機路由器等等,比如寫為“call("function_name")”,然後在lua檔案下編寫名為function_name的函數就可以調用了。
  • 第二種可以通路指定的頁面,比如寫為“template("myapp/mymodule")”就可以調用/usr/lib/lua/luci/view/myapp/mymodule.htm檔案了。
  • 而如果要編寫配置頁面,那麼使用第三種方法無非是最友善的,比如寫為“cbi("myapp/mymodule")”就可以調用/usr/lib/lua/luci/model/cbi/myapp/mymodule.lua檔案了。

而title和order無非是針對管理者菜單來的,可以參考其他的lua檔案來決定編寫的内容。

這裡我們建立/usr/lib/lua/luci/controller/njitclient.lua檔案,定義我們的入口,代碼如下:

module("luci.controller.njitclient", package.seeall)

function index()
        entry({"admin", "network", "njitclient"}, cbi("njitclient"), _("NJIT Client"), 100)
        end      

【二、用Lua和UCI接口開發LuCI配置子產品】

我們要做的實際上就是希望能将使用者名、密碼等資訊存儲在路由器檔案中,同時路由器開機時能根據設定的配置自動運作njit-client,同時我們還希望能動态的禁用和啟用njit-client等等。是以最友善的方式就是使用CBI Module,上一節我們也添加了這個調用,那麼接下來我們就要根據上邊寫的路徑來建立/usr/lib/lua/luci/model/cbi/njitclient.lua檔案。

開發LuCI的配置子產品有很多種方式,比較基本的可以用SimpleForm,就跟開發普通的Web應用類似,當然最友善的還是使用UCI(Unified Configuration Interface,統一配置接口)的方式,因為使用UCI接口可以使得在LuCI中可以無需考慮配置檔案如何存儲和讀取(這種方式也會自動建立“儲存&應用”、“儲存”以及“複位”三個按鈕),同時在Bash檔案中也可以非常友善的存儲和讀取。

對于使用UCI的方式,我們首先需要建立對應的配置檔案(如果配置檔案不存在的話,通路配置頁面将會報錯),格式即為linux配置檔案的格式,檔案需要存儲在/etc/config,比如檔案路徑為“/etc/config/njitclient”,内容如下:

config login
    option username ''
    option password ''
    option ifname 'eth0'
    option domain ''      

然後我們要在CBI Module的lua檔案中首先需要映射與存儲檔案的關系,比如:

m = Map("配置檔案檔案名", "配置頁面标題", "配置頁面說明")      

第一個參數即為配置檔案存儲的檔案名,不包含路徑,比如按上述建立的話,應該寫為“njitclient”,而第二與第三個參數則是用在來頁面上顯示的,比如如下所示的圖:

開發OpenWrt路由器上LuCI的子產品

接下來需要建立與配置檔案中對應的Section,Section分為兩種,NamedSection和TypedSection,前者根據配置檔案中的Section名,而後者根據配置檔案中的Section類型,這裡我們使用後者,代碼如下。同時我們設定不允許增加或删除Section(“.addremove = false”),以及不顯示Section的名稱(“.anonymous = true”)。

s = m:section(TypedSection, "login", "")
s.addremove = false
s.anonymous = true      

接下來我們需要建立Section中不同内容的互動(建立Option),常見的比如有Value(文本框)、ListValue(下拉框)、Flag(選擇框)等等,詳細的可以參考官方的文檔:http://luci.subsignal.org/trac/wiki/Documentation/CBI

建立Option的過程非常簡單,而且建立後系統會無需考慮讀取以及寫入配置檔案的問題,系統都會自動處理。但是根據上述的要求,我們在應用配置以後可能希望啟用、禁用或重新啟動njit-client,是以我們還需要在頁面最後判斷使用者是否點選了“應用”按鈕,這裡與編寫asp網頁等都是相同的,我們可以通過如下的代碼判斷是否點選了“應用”按鈕:

local apply = luci.http.formvalue("cbi.apply")
if apply then
    --[[
        需要處理的代碼
    ]]--
end      

由于剩餘的代碼都非常簡單,是以是以這部分的全部代碼見下:

1 require("luci.sys")
 2 
 3 m = Map("njitclient", translate("NJIT Client"), translate("Configure NJIT 802.11x client."))
 4 
 5 s = m:section(TypedSection, "login", "")
 6 s.addremove = false
 7 s.anonymous = true
 8 
 9 enable = s:option(Flag, "enable", translate("Enable"))
10 name = s:option(Value, "username", translate("Username"))
11 pass = s:option(Value, "password", translate("Password"))
12 pass.password = true
13 domain = s:option(Value, "domain", translate("Domain"))
14 
15 ifname = s:option(ListValue, "ifname", translate("Interfaces"))
16 for k, v in ipairs(luci.sys.net.devices()) do
17     if v ~= "lo" then
18         ifname:value(v)
19     end
20 end
21 
22 local apply = luci.http.formvalue("cbi.apply")
23 if apply then
24     io.popen("/etc/init.d/njitclient restart")
25 end
26 
27 return m      

其中Luci全部類庫的函數定義和使用說明可以參考如下位址:http://luci.subsignal.org/api/luci/index.html

【三、在Bash檔案中調用UCI接口】

上邊我們已經完成了LuCI配置界面的開發,在配置界面中我們已經能讀取并儲存配置檔案了。接下來我們要編寫/etc/init.d/njitclient腳本,使程式最終能運作起來。關于UCI接口在腳本檔案中的官方說明可以參考:http://wiki.openwrt.org/doc/devel/config-scripting

要使用UCI調用腳本,首先第一步需要讀取配置檔案,指令為“config_load 配置檔案名”,比如我們可以這樣讀入剛才的配置檔案:

config_load njitclient      

接下來要周遊配置檔案中的Section,可以使用“config_foreach 周遊函數名 Section類型”,例如我們可以這樣:

config_foreach run_njit login      

然後我們去編寫名為“run_njit”的函數,在這個函數中,我們可以使用“config_get 變量名 Section名 Section參數名”擷取變量的值,或者使用“config_get_bool 變量名 Section名 Section參數名”擷取布爾型的值。是以全部的代碼見下:

1 #!/bin/sh /etc/rc.common
 2 START=50
 3 
 4 run_njit()
 5 {
 6     local enable
 7     config_get_bool enable $1 enable
 8     
 9     if [ $enable ]; then
10         local username
11         local password
12         local domain
13         local ifname
14         
15         config_get username $1 username
16         config_get password $1 password
17         config_get domain $1 domain
18         config_get ifname $1 ifname
19         
20         if [ "$domain" != "" ]; then
21             njit-client $username@$domain $password $ifname &
22         else
23             njit-client $username $password $ifname &
24         fi
25         
26         echo "NJIT Client has started."
27     fi
28 }
29 
30 start()
31 {
32     config_load njitclient
33     config_foreach run_njit login
34 }
35 
36 stop()
37 {
38     killall njit-client
39     killall udhcpc
40     
41     echo "NJIT Client has stoped."
42 }      

【四、編譯開發的程式】

如果按上述内容建立好上述4個檔案,那麼配置頁面和程式就能在OpenWrt上運作起來了。但是如果要想把自己寫的程式打包,還需要建立OpenWrt的Makefile來使用OpenWrt的SDK進行編譯。

關于LuCI上配置Makefile的官方說明可以見這個位址:http://luci.subsignal.org/trac/wiki/Documentation/Modules

無非就是定義包的名稱(PKG_NAME)、版本和生成次數(PKG_VERSION、PKG_RELEASE)、在menuconfig中的分類說明等(define Package/luci-app-njitclient)以及安裝時進行的操作(define Package/luci-app-njitclient/install)等等。其中安裝的檔案分為三種,分别是配置檔案、可執行檔案以及其他資料檔案,其中配置可執行檔案時,會自動加入執行權限的,是以不需要額外進行處理。Makefile全部的代碼見下:

1 include $(TOPDIR)/rules.mk
 2 
 3 PKG_NAME:=luci-app-njitclient
 4 PKG_VERSION=1.0
 5 PKG_RELEASE:=1
 6 
 7 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
 8 
 9 include $(INCLUDE_DIR)/package.mk
10 
11 define Package/luci-app-njitclient
12     SECTION:=luci
13     CATEGORY:=LuCI
14     SUBMENU:=3. Applications
15     TITLE:=NJIT 802.1X Client for LuCI
16     PKGARCH:=all
17 endef
18 
19 define Package/luci-app-njitclient/description
20     This package contains LuCI configuration pages for njit8021xclient.
21 endef
22 
23 define Build/Prepare
24 endef
25 
26 define Build/Configure
27 endef
28 
29 define Build/Compile
30 endef
31 
32 define Package/luci-app-njitclient/install
33     $(INSTALL_DIR) $(1)/etc/config
34     $(INSTALL_DIR) $(1)/etc/init.d
35     $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi
36     $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
37     
38     $(INSTALL_CONF) ./files/root/etc/config/njitclient $(1)/etc/config/njitclient
39     $(INSTALL_BIN) ./files/root/etc/init.d/njitclient $(1)/etc/init.d/njitclient
40     $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/model/cbi/njitclient.lua $(1)/usr/lib/lua/luci/model/cbi/njitclient.lua
41     $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/controller/njitclient.lua $(1)/usr/lib/lua/luci/controller/njitclient.lua
42 endef
43 
44 $(eval $(call BuildPackage,luci-app-njitclient))      

接下來在編譯目錄下的package目錄下建立一個檔案夾,如njitclient,然後将所有的檔案按目錄複制到該目錄下即可。之後配置好OpenWrt的交叉編譯環境後就可以使用OpenWrt SDK進行編譯了,由于這類文章較多,故不再贅述,可以參考相關連結3及之後的文章。

【相關連結】

  1. LuCI:http://luci.subsignal.org/trac/wiki
  2. LuCI界面修改實作802.1x配置界面:http://chaochaoblog.com/archives/359
  3. 【詳細教程】編譯openwrt + njit-client 1.3 通過iNode認證:http://www.7forz.com/1973/
  4. openwrt SDK, 利用SDK生成自己的ipk安裝包:http://blog.chinaunix.net/uid-27194309-id-3432651.html

如果您覺得本文對您有所幫助,不妨點選下方的“推薦”按鈕來支援我!

本文及文章中代碼均基于“署名-非商業性使用-相同方式共享 3.0”,文章歡迎轉載,但請您務必注明文章的作者和出處連結,如有疑問請私信我聯系!

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

文本分類實驗項目日志(最後更新-2019.07.19)

文章目錄

  • 1 One vs rest & stacking 二分類多分類器文本分類實驗
    • 1.1 描述
    • 1.2 實驗設計
      • 1.2.1 舊方法
      • 1.2.2 新方法
    • 1.3 實驗結果和分析
      • 1.3.1 實驗-舊方法
      • 1.3.2 實驗-新方法
      • 1.3.3 實驗-新方法-新資料
    • 1.4 結論
    • 1.5 內建學習的資源
    • 1.6 項目位址

1 One vs rest & stacking 二分類多分類器文本分類實驗

1.1 描述

根據5-6月份 one vs rest(all)和stacking的融合實驗的若幹問題,6月中旬進行了改進。

5-6月份最終形成的方案為:融入KB;加入第一層分類器;改變KB傳回類目數量,形成多個對比實驗。具體方法和結果詳見1.3.4

5-6月份實驗存在的問題說明如下:

(1) 類間的相似性可能是影響最終結果的主要因素;

(2) 采取one-hot文本表示出現的次元過大以及OOA問題(如對于5:4:1比例,測試集占1/10,若測試集中某個詞未出現在訓練二分類器中的詞空間裡,則不能表示該特征,那麼測試時會失去該特征,進而影響測試結果);

(3) 最終準确率難以提升。

6月初提出的改進方案如下:

(1) 去掉知識庫;

(2) 加入第二層層級分類,使用fasttext訓練二級分類器。

(3) 使用詞向量表示文本特征。

1.2、 1.3給出了具體的實驗設計和結果。

1.2 實驗設計

1.2.1 舊方法

詳細的實驗方法如圖1所示。(舊版本)

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

圖1 文本分類架構

整個實驗可分為6個環節:資料預處理,訓練一級fasttext分類器,建構三級分類知識庫,訓練二分類分類器,訓練融合分類器和測試分類器。每個環節的詳細方法如下:

  1. 資料預處理。原始資料集儲存在兩個txt檔案,大小共約680MB,具體資料形狀如圖2所示。
    大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

    圖2 資料集形狀

    根據實驗設計思路,需要抽取并生成新的資料集。表1列出了4次實驗的資料集詳細資訊,其中1-3次是5月份實驗用資料集,第4次是6月份資料集,第4次與前3次的差別是:(1)類别數量增加一倍左右;(2)每個三級類的文檔數量未固定,即類别資料分布不均衡。(3)細化資料清洗環節(如抽取時考慮類号名字特點、去除無效文檔、去除重複詞語等)。

    表1 資料集介紹

實驗次數 大小 類别數量 文檔數量
1 7MB 10 1000*10=10000
2 332MB 467 1000*467=467000
3 278MB 358 1000*358=358000
4 312MB 773 727367

資料集劃分:每個類下,訓練二分類資料集:訓練融合分類資料集:最終測試集 = 5:4:1。

下面的流程主要以實驗4的資料集為例。

2. 訓練一級分類器。将所有資料集按照每行資料為‘__label__A,word1\tword2’的格式存儲。A代表一級分類号,word1代表文檔分詞後的第一個詞。然後使用fasttext模型訓練分類器,最後抽取總資料集的20%作為一級分類器測試。

3. 訓練二分分類器。對于每個三級類,長度為 l e 3 l e n g t h le3_{length} le3length​,那麼該類的二分訓練集長度為 l e 3 b i n a r y L e n g t h = l e 3 l e n g t h ∗ 0.5 le3_{binaryLength} = le3_{length}*0.5 le3binaryLength​=le3length​∗0.5,從這些資料中随機抽取10個子集,然後加入其它三級類的若幹資料(長度大緻與屬于本類資料相同,記為 s u b n sub_n subn​),最終的目标是使得每個子集包含sub_n個屬于該類的文檔, s u b n sub_n subn​個不屬于該類的文檔。這 2 ∗ s u b n 2*sub_n 2∗subn​個文檔使用機器學習分類算法訓練一個二分類器。共得到773*10=7730個分類器。

4. 訓練融合分類器。将 l e 3 l e n g t h le3_{length} le3length​,那麼該類的融合訓練集長度為 l e 3 m e r g e L e n g t h = l e 3 l e n g t h ∗ 0.4 le3_{mergeLength} = le3_{length}*0.4 le3mergeLength​=le3length​∗0.4,抽取 l e 3 m e r g e L e n g t h le3_{mergeLength} le3mergeLength​條該類文檔和不屬于該類的 l e 3 m e r g e L e n g t h le3_{mergeLength} le3mergeLength​條文檔組合成 2 ∗ l e 3 m e r g e L e n g t h 2*le3_{mergeLength} 2∗le3mergeLength​條文檔,然後用10個該類的二分分類器進行分類,結果以機率形式給出(分到類A81的機率),并儲存為長度為10的向量,标簽為1或0(如“A81”類資料,則标簽記為1,非“A81”類記0)。然後将這些向量進行訓練,得出融合的分類器。這樣共得到773個融合分類器。

5. 測試分類器。測試過程包括5個方面:測試一級分類器;知識庫(KB)分類;測試二分分類器;測試融合分類器;測試系統分類器。其中,測試系統分類器有多種方案,将在1.3.4節的結果和分析中提到。

1.2.2 新方法

新方法與舊方法的差別如下:

  • 去掉KB。原因是經過實驗發現,加入KB會過濾掉正确的類号;去掉KB的副作用是增加測試的時間開銷。
  • 加入二級分類。在一級分類的基礎上加入二級分類,原因是為了降低測試的時間開銷。
  • 文本表示階段采用詞向量方法。原因是原來方法(tf-idf)會生成詞彙映射表檔案(将近900MB),并且會造成次元過大和OOA問題;使用詞向量可以避免前兩個問題,OOA問題與詞向量模型以及資料集有關。
  • 使用詞向量中的詞作為自定義詞典分詞。這樣做的好處是在文本表示階段盡可能使每一個特征(詞語)都能夠用詞向量表示。(目前還在實驗階段)

    新方法的具體步驟如下:

  1. 資料預處理。同1.2.1的1,僅使用第4次抽取的資料集。
  2. 訓練一級分類器。同1.2.1的2。
  3. 訓練二級分類器。将所有資料集按照每行資料為‘__label__A8,word1\tword2’的格式存儲。A8代表二級分類号,word1代表文檔分詞後的第一個詞。然後使用fasttext模型訓練分類器,最後抽取總資料集的20%作為測試。
  4. 訓練二分分類器和融合分類器。步驟與1.2.1的3和4大緻同,僅文本表示階段不同。
  5. 測試分類器。測試過程包括5個方面:測試一級分類器;測試二級分類器;測試二分分類器;測試融合分類器;測試系統分類器。其中,測試系統分類器較複雜,将在1.3中提到。

1.3 實驗結果和分析

1.3.1 實驗-舊方法

本次實驗加入了層級分類(FC)和知識庫(KB),第一層分類器使用fasttext算法,第二層分類器使用svm算法。其中,fasttext的測試結果為0.95.

最終測試時,經過和老師及學長讨論形成了兩種方案,後來經過實驗,又總結了幾種優化政策。下面是這幾種方案的介紹。

(1) 文檔首先經過KB,得到20個三級類号,然後僅在這20個類的20*10=200個二分類器和20個融合分類器中進行分類測試。記為way_1。

(2) 文檔首先經過7730個二分類器和773個融合分類器,然後将所有分類結果按機率從大到小排序,與KB的結果取交集。記為way_2。

(3) 加入一級分類。文檔首先經過一級分類器,得到一級分類号(如A),并經過KB,得到20個三級分類号(如A81,A84,B02,…,C81),然後僅在一級分類号下并且存在于20個三級分類号的分類器中進行分類測試(如在A81,A84中測試)。記為way_3。

以上三種方案,把最後得到排名前三的三級類号作為最終的分類結果。

此外,加入了baseline,即不加入KB和層級分類的結果,如下表所示。

表2 實驗4結果

實驗代号 準确率 運作時間/s
Baseline 0.184 58113(約16h)
Way_1 0.215 14886(約4.1h)
Way_2 0.216 28106(約7.8h)
Way_3 0.231 7035(約2h)

從表2可以看出,增加KB,準确率提高了近0.03,并且時間性能大大改善。way_1和way_2主要差别在于時間,way_1運作時間更短,這與方案(1)和方案(2)的步驟有關。(1)和(2)是計算所有三級類的分類結果并排序,而(1)僅需要計算由知識庫得到的三級類下的分類器并排序,是以(1)的時間性能更好。

實驗Way_3驗證了加入第一層分類器是有效的,時間性能提高了一倍,準确率提高了0.015。是以下一步将考慮以way_3為基礎進行優化。

實驗發現,對于方案(3),增加從知識庫中傳回的結果數目,如經過KB得到30個結果,最終的準确率得到提高。是以,進行了幾組優化實驗,如表3所示。

表3 way_3的優化實驗結果

實驗代号 KB類号數 準确率 運作時間/s
Way_3 20 0.231 7035(約2h)
Way_4 30 0.269 10951(約3h)
Way_5 50 0.305 20411(約5.6h)
Way_6 60 0.312 20150(約5.6h)

由表3,增加KB的類号後,準确率顯著提高,但相應的時間開銷也增加。

為了分析具體分類時每個類的效果,實驗統計了每個文檔在分類時的第一層分類結果、KB分類結果、最終分類結果清單(top10)。以way_6為例,選取三個有代表性的類别,其中其整體的準确率如表4所示,選取其中若幹個文檔具體分析結果如表5所示。

表4 三個類的準确率

三級類号 文檔數 準确率
A849 1526 0.84
D08 820 0.475
Q593 732 0.000

表5 三個文檔的具體分類結果

文檔 KB結果 Le1結果 最終結果(取top10,不足10個全部選擇)
A849_C1 A:12;B:1;C:1;D:20… A [‘A849’, ‘A81’]
D08_C1 B:1;C:1;D:5;Q:41… D [‘D52’,‘D43’,‘D90’]
Q593_C1 S:7;Q:40… Q [‘Q942’,‘Q945’,‘Q95-3’,‘Q25’, ‘Q53’,‘Q935’,‘Q954’,‘Q936’, ‘Q948’, ‘Q24’]

由表5知,在D08類中一個文檔的KB結果中出現了較多的Q類,并且最終分類結果中未命中D08;在Q593類中一個文檔中雖然大部分都是Q類,但最終結果(top3)并沒有命中。

表4和表5隻是列出了個别文檔的分類情況,這三個類全部分類情況見附件1.

此外,為了分析其它類的分類效果,統計了那些效果較差的類别,如圖5所示。其中,橫坐标表示準确率,縱坐标表示低于準确率的三級類類别數量。類别總數為773.

大規模文本分類實驗-項目日志1 One vs rest & stacking 二分類多分類器文本分類實驗

圖5 三級類準确率分布情況

從圖5可以看出,大部分三級類的準确率低于0.5,有将近一半低于0.3,有20個三級類準确率為0.0.

基于目前的實驗,可考慮的優化方法包括這幾個方面:改善KB;改善第一層分類器性能;剔除效果非常差的類别。

1.3.2 實驗-新方法

本次實驗加入了第二層級分類(FC),并且文本表示使用詞向量方法(百科訓練的詞向量模型,大小1.7GB),第二層分類器使用fasttext算法,第三層分類器使用svm算法。其中,第一層分類器的測試結果為0.95,第二層分類器的測試結果為0.93.

新方法的幾次過程結果如表6所示。

實驗代号 描述 準确率 運作時間/s
Baseline(Way_6) 舊方法最好結果 0.312 20150(約5.6h)
New_way_1 加入二級分類器 0.334 14169(約3.9h)
New_way_2 在new_way_1基礎上去掉KB 0.484 64741(約18h)
New_way_3 在new_way_2基礎上使用詞向量 0.455 33425(約9.3h)

由表6,New_way_1加入二級分類器的效果提升不大,但是時間開銷降低。New_way_2去掉KB後準确率大幅提升,達到0.484,但是時間開銷增大3倍多。New_way_3使用詞向量,準确率降低了0.03,但時間開銷降低了一倍。

由此分析,新方法是有效的。

對于新方法New_way_3,使用詞向量仍會出現OOA問題,故使用詞向量中的詞作為自定義詞典分詞。這樣做的好處是在文本表示階段盡可能使每一個特征(詞語)都能夠用詞向量表示。這個方法目前在實驗階段。

1.3.3 實驗-新方法-新資料

本次資料選取的是規模為7.32GB資料,經過預處理後,大小為5.16GB。預處理包括分詞、去除無效資料等。分詞使用百度詞向量生成的詞典進行分詞。5.16GB資料的情況如表7所示。

表7 新資料集介紹

實驗次數 大小 類别數量 文檔數量
5 5.16GB 1679

由于類别資料少會影響分類性能,故去掉資料小于500條的三級類别,共得到851個三級類。

根據這851個類進行實驗,方法與1.3.2相同。

使用fasttext訓練一級分類器的準确率為0.92,訓練二級分類器的準确率為0.77。

表8給出了實驗的結果。

實驗代号 描述 準确率 運作時間/s
New_way_data_1 和New_way_3不同的是未加入2級分類器 0.20 147581(約41h)
New_way_data_2 方法和new_way_3同 0.41 35556(約9.8h)

1.4 結論

根據新-舊實驗和若幹次讨論,得到結論如下:

(1) 加入第二層分類器是有效的。對比表6的幾個方案結果,發現加入第二層分類對分類效果和時間性能有一定提升。

(2) 去掉知識庫是有效的。根據表6的結果,去掉KB能大幅提升準确率。

(3) 使用詞向量可以縮短訓練和測試時間。根據表6結果,詞向量在準确率方面和TF-IDF方法相差不大,但時間開銷降低一半,此外,使用詞向量不需要生成額外的映射檔案。

根據以上結論,新方法是目前最好的文本分類方案,将應用到實際的标引項目中。

1.5 內建學習的資源

內建學習-1:https://blog.csdn.net/qq_20386411/article/details/82985219

內建學習-2: https://blog.csdn.net/qq_32690999/article/details/78759463

Stacking原理:https://blog.csdn.net/wstcjf/article/details/77989963

Stacking代碼實作:https://www.jianshu.com/p/5905f19c4df6

模型融合(stacking&blending) :https://blog.csdn.net/u014356002/article/details/54376138

1.6 項目位址

Hierarchical-text-classify

開發OpenWrt路由器上LuCI的子產品

學校裡最近改造了校園網,要求必須用iNode驗證,萬幸的是路由器能刷OpenWrt,并且OpenWrt上有好多iNode認證的開源項目,比如njit8021xclient就非常好用,但其沒有好用的Web管理子產品。好在用Lua為LuCI寫配置子產品很簡單,索性就自己做了一個,現在把開發的流程寫一下,友善初學的同學去做LuCI子產品的開發。

【題外話】

學校裡最近改造了校園網,要求必須用iNode驗證,萬幸的是路由器能刷OpenWrt,并且OpenWrt上有好多iNode認證的開源項目,比如njit8021xclient(以下簡稱njit-client)就非常好用。雖然程式寫的好用,但是配置起來還是稍微麻煩一些的,大家通常的方法是在/etc/init.d下寫啟動腳本,把使用者名、密碼什麼的都直接填進去,但畢竟配置起來不友善,同時日後修改起來也不便。好在用Lua為LuCI寫配置子產品很簡單,索性就自己做了一個,現在把開發的流程寫一下,友善初學的同學去做。為njit-client做好的Web配置界面也已經開源,位址:https://github.com/mayswind/luci-app-njitclient,或者直接下載下傳編譯好(不限平台)的檔案:http://pan.baidu.com/s/1CbPal

【文章索引】

  1. LuCI配置界面開發的架構
  2. 用Lua和UCI接口開發LuCI配置子產品
  3. 在Bash檔案中調用UCI接口
  4. 編譯開發的程式

【一、LuCI配置界面開發的架構】

LuCI是OpenWrt上的Web管理界面,LuCI采用了MVC三層架構,同時其使用Lua腳本開發,是以開發LuCI的配置界面不需要編輯任何的Html代碼,除非想自己單獨去建立網頁(View層),否則我們基本上隻需要修改Model層就可以了。官方也有一個如何去建立子產品的說明文檔,雖然寫的比較晦澀:http://luci.subsignal.org/trac/wiki/Documentation/ModulesHowTo

要為LuCI增加一個新子產品,首先需要建立兩個檔案,一個位于Controller(/usr/lib/lua/luci/controller/)下,定義子產品的入口;另一個位于Model(/usr/lib/lua/luci/model/cbi/)下,為配置子產品實際的代碼。

首先我們定義子產品的入口,在/usr/lib/lua/luci/controller/下建立一個lua檔案,類似如下:

module("luci.controller.控制器名", package.seeall)

function index()
        entry(路徑, 調用目标, _("顯示名稱"), 顯示順序)
        end      

第一行說明了程式和子產品的名稱,比如在controller/目錄建立一個mymodule.lua,那麼就可以寫成“luci.controller.mymodule”,如果你的程式比較多,可能分為好幾個子產品,那麼可以在controller下再常見一個子目錄,比如controller/myapp/,那麼就可以寫成“luci.controller.myapp.mymodule”。

接下來的entry表示添加一個新的子產品入口,官方給出了entry的定義,其中後兩項都是可以為空的:

entry(path, target, title=nil, order=nil)      

第一項是通路的路徑,不過路徑是按字元串數組給定的,比如路徑按如下方式寫“{"click", "here", "now"}”,那麼就可以在浏覽器裡通路“http://192.168.1.1/cgi-bin/luci/click/here/now”來通路這個腳本。而通常我們希望為管理者菜單添加腳本,那麼我們需要按如下方式編寫“{"admin", "一級菜單名", "菜單項名"}”,系統會自動在對應的菜單中生成菜單項。比如想在“網絡”菜單下建立一個菜單項,那麼一級菜單名可以寫為“network”。

第二項為調用目标,調用目标分為三種,分别是執行指定方法(Action)、通路指定頁面(Views)以及調用CBI Module。

  • 第一種可以直接調用指定的函數,比如點選菜單項就直接重新開機路由器等等,比如寫為“call("function_name")”,然後在lua檔案下編寫名為function_name的函數就可以調用了。
  • 第二種可以通路指定的頁面,比如寫為“template("myapp/mymodule")”就可以調用/usr/lib/lua/luci/view/myapp/mymodule.htm檔案了。
  • 而如果要編寫配置頁面,那麼使用第三種方法無非是最友善的,比如寫為“cbi("myapp/mymodule")”就可以調用/usr/lib/lua/luci/model/cbi/myapp/mymodule.lua檔案了。

而title和order無非是針對管理者菜單來的,可以參考其他的lua檔案來決定編寫的内容。

這裡我們建立/usr/lib/lua/luci/controller/njitclient.lua檔案,定義我們的入口,代碼如下:

module("luci.controller.njitclient", package.seeall)

function index()
        entry({"admin", "network", "njitclient"}, cbi("njitclient"), _("NJIT Client"), 100)
        end      

【二、用Lua和UCI接口開發LuCI配置子產品】

我們要做的實際上就是希望能将使用者名、密碼等資訊存儲在路由器檔案中,同時路由器開機時能根據設定的配置自動運作njit-client,同時我們還希望能動态的禁用和啟用njit-client等等。是以最友善的方式就是使用CBI Module,上一節我們也添加了這個調用,那麼接下來我們就要根據上邊寫的路徑來建立/usr/lib/lua/luci/model/cbi/njitclient.lua檔案。

開發LuCI的配置子產品有很多種方式,比較基本的可以用SimpleForm,就跟開發普通的Web應用類似,當然最友善的還是使用UCI(Unified Configuration Interface,統一配置接口)的方式,因為使用UCI接口可以使得在LuCI中可以無需考慮配置檔案如何存儲和讀取(這種方式也會自動建立“儲存&應用”、“儲存”以及“複位”三個按鈕),同時在Bash檔案中也可以非常友善的存儲和讀取。

對于使用UCI的方式,我們首先需要建立對應的配置檔案(如果配置檔案不存在的話,通路配置頁面将會報錯),格式即為linux配置檔案的格式,檔案需要存儲在/etc/config,比如檔案路徑為“/etc/config/njitclient”,内容如下:

config login
    option username ''
    option password ''
    option ifname 'eth0'
    option domain ''      

然後我們要在CBI Module的lua檔案中首先需要映射與存儲檔案的關系,比如:

m = Map("配置檔案檔案名", "配置頁面标題", "配置頁面說明")      

第一個參數即為配置檔案存儲的檔案名,不包含路徑,比如按上述建立的話,應該寫為“njitclient”,而第二與第三個參數則是用在來頁面上顯示的,比如如下所示的圖:

開發OpenWrt路由器上LuCI的子產品

接下來需要建立與配置檔案中對應的Section,Section分為兩種,NamedSection和TypedSection,前者根據配置檔案中的Section名,而後者根據配置檔案中的Section類型,這裡我們使用後者,代碼如下。同時我們設定不允許增加或删除Section(“.addremove = false”),以及不顯示Section的名稱(“.anonymous = true”)。

s = m:section(TypedSection, "login", "")
s.addremove = false
s.anonymous = true      

接下來我們需要建立Section中不同内容的互動(建立Option),常見的比如有Value(文本框)、ListValue(下拉框)、Flag(選擇框)等等,詳細的可以參考官方的文檔:http://luci.subsignal.org/trac/wiki/Documentation/CBI

建立Option的過程非常簡單,而且建立後系統會無需考慮讀取以及寫入配置檔案的問題,系統都會自動處理。但是根據上述的要求,我們在應用配置以後可能希望啟用、禁用或重新啟動njit-client,是以我們還需要在頁面最後判斷使用者是否點選了“應用”按鈕,這裡與編寫asp網頁等都是相同的,我們可以通過如下的代碼判斷是否點選了“應用”按鈕:

local apply = luci.http.formvalue("cbi.apply")
if apply then
    --[[
        需要處理的代碼
    ]]--
end      

由于剩餘的代碼都非常簡單,是以是以這部分的全部代碼見下:

1 require("luci.sys")
 2 
 3 m = Map("njitclient", translate("NJIT Client"), translate("Configure NJIT 802.11x client."))
 4 
 5 s = m:section(TypedSection, "login", "")
 6 s.addremove = false
 7 s.anonymous = true
 8 
 9 enable = s:option(Flag, "enable", translate("Enable"))
10 name = s:option(Value, "username", translate("Username"))
11 pass = s:option(Value, "password", translate("Password"))
12 pass.password = true
13 domain = s:option(Value, "domain", translate("Domain"))
14 
15 ifname = s:option(ListValue, "ifname", translate("Interfaces"))
16 for k, v in ipairs(luci.sys.net.devices()) do
17     if v ~= "lo" then
18         ifname:value(v)
19     end
20 end
21 
22 local apply = luci.http.formvalue("cbi.apply")
23 if apply then
24     io.popen("/etc/init.d/njitclient restart")
25 end
26 
27 return m      

其中Luci全部類庫的函數定義和使用說明可以參考如下位址:http://luci.subsignal.org/api/luci/index.html

【三、在Bash檔案中調用UCI接口】

上邊我們已經完成了LuCI配置界面的開發,在配置界面中我們已經能讀取并儲存配置檔案了。接下來我們要編寫/etc/init.d/njitclient腳本,使程式最終能運作起來。關于UCI接口在腳本檔案中的官方說明可以參考:http://wiki.openwrt.org/doc/devel/config-scripting

要使用UCI調用腳本,首先第一步需要讀取配置檔案,指令為“config_load 配置檔案名”,比如我們可以這樣讀入剛才的配置檔案:

config_load njitclient      

接下來要周遊配置檔案中的Section,可以使用“config_foreach 周遊函數名 Section類型”,例如我們可以這樣:

config_foreach run_njit login      

然後我們去編寫名為“run_njit”的函數,在這個函數中,我們可以使用“config_get 變量名 Section名 Section參數名”擷取變量的值,或者使用“config_get_bool 變量名 Section名 Section參數名”擷取布爾型的值。是以全部的代碼見下:

1 #!/bin/sh /etc/rc.common
 2 START=50
 3 
 4 run_njit()
 5 {
 6     local enable
 7     config_get_bool enable $1 enable
 8     
 9     if [ $enable ]; then
10         local username
11         local password
12         local domain
13         local ifname
14         
15         config_get username $1 username
16         config_get password $1 password
17         config_get domain $1 domain
18         config_get ifname $1 ifname
19         
20         if [ "$domain" != "" ]; then
21             njit-client $username@$domain $password $ifname &
22         else
23             njit-client $username $password $ifname &
24         fi
25         
26         echo "NJIT Client has started."
27     fi
28 }
29 
30 start()
31 {
32     config_load njitclient
33     config_foreach run_njit login
34 }
35 
36 stop()
37 {
38     killall njit-client
39     killall udhcpc
40     
41     echo "NJIT Client has stoped."
42 }      

【四、編譯開發的程式】

如果按上述内容建立好上述4個檔案,那麼配置頁面和程式就能在OpenWrt上運作起來了。但是如果要想把自己寫的程式打包,還需要建立OpenWrt的Makefile來使用OpenWrt的SDK進行編譯。

關于LuCI上配置Makefile的官方說明可以見這個位址:http://luci.subsignal.org/trac/wiki/Documentation/Modules

無非就是定義包的名稱(PKG_NAME)、版本和生成次數(PKG_VERSION、PKG_RELEASE)、在menuconfig中的分類說明等(define Package/luci-app-njitclient)以及安裝時進行的操作(define Package/luci-app-njitclient/install)等等。其中安裝的檔案分為三種,分别是配置檔案、可執行檔案以及其他資料檔案,其中配置可執行檔案時,會自動加入執行權限的,是以不需要額外進行處理。Makefile全部的代碼見下:

1 include $(TOPDIR)/rules.mk
 2 
 3 PKG_NAME:=luci-app-njitclient
 4 PKG_VERSION=1.0
 5 PKG_RELEASE:=1
 6 
 7 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
 8 
 9 include $(INCLUDE_DIR)/package.mk
10 
11 define Package/luci-app-njitclient
12     SECTION:=luci
13     CATEGORY:=LuCI
14     SUBMENU:=3. Applications
15     TITLE:=NJIT 802.1X Client for LuCI
16     PKGARCH:=all
17 endef
18 
19 define Package/luci-app-njitclient/description
20     This package contains LuCI configuration pages for njit8021xclient.
21 endef
22 
23 define Build/Prepare
24 endef
25 
26 define Build/Configure
27 endef
28 
29 define Build/Compile
30 endef
31 
32 define Package/luci-app-njitclient/install
33     $(INSTALL_DIR) $(1)/etc/config
34     $(INSTALL_DIR) $(1)/etc/init.d
35     $(INSTALL_DIR) $(1)/usr/lib/lua/luci/model/cbi
36     $(INSTALL_DIR) $(1)/usr/lib/lua/luci/controller
37     
38     $(INSTALL_CONF) ./files/root/etc/config/njitclient $(1)/etc/config/njitclient
39     $(INSTALL_BIN) ./files/root/etc/init.d/njitclient $(1)/etc/init.d/njitclient
40     $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/model/cbi/njitclient.lua $(1)/usr/lib/lua/luci/model/cbi/njitclient.lua
41     $(INSTALL_DATA) ./files/root/usr/lib/lua/luci/controller/njitclient.lua $(1)/usr/lib/lua/luci/controller/njitclient.lua
42 endef
43 
44 $(eval $(call BuildPackage,luci-app-njitclient))      

接下來在編譯目錄下的package目錄下建立一個檔案夾,如njitclient,然後将所有的檔案按目錄複制到該目錄下即可。之後配置好OpenWrt的交叉編譯環境後就可以使用OpenWrt SDK進行編譯了,由于這類文章較多,故不再贅述,可以參考相關連結3及之後的文章。

【相關連結】

  1. LuCI:http://luci.subsignal.org/trac/wiki
  2. LuCI界面修改實作802.1x配置界面:http://chaochaoblog.com/archives/359
  3. 【詳細教程】編譯openwrt + njit-client 1.3 通過iNode認證:http://www.7forz.com/1973/
  4. openwrt SDK, 利用SDK生成自己的ipk安裝包:http://blog.chinaunix.net/uid-27194309-id-3432651.html

如果您覺得本文對您有所幫助,不妨點選下方的“推薦”按鈕來支援我!

本文及文章中代碼均基于“署名-非商業性使用-相同方式共享 3.0”,文章歡迎轉載,但請您務必注明文章的作者和出處連結,如有疑問請私信我聯系!