建釣魚(yú)郵件識(shí)別系統(tǒng):從數(shù)據(jù)處理到模型部署的完整實(shí)踐)
每年這個(gè)時(shí)候都有不少同學(xué)拿著“網(wǎng)絡(luò)釣魚(yú)郵件識(shí)別系統(tǒng)”這個(gè)題目來(lái)問(wèn)我說(shuō)老師讓做畢設(shè)自己除了會(huì)用框架寫(xiě)點(diǎn)頁(yè)面一碰機(jī)器學(xué)習(xí)就發(fā)怵。其實(shí)這個(gè)選題是典型的“看起來(lái)難、拆開(kāi)不難”的題目核心就一件事把一封郵件判斷成“正?!边€是“釣魚(yú)”本質(zhì)是一個(gè)二分類(lèi)問(wèn)題。難的不是算法本身而是你能不能把數(shù)據(jù)清洗、模型訓(xùn)練、系統(tǒng)整合這一整條鏈路跑通并且在答辯的時(shí)候講清楚每一步為什么這么設(shè)計(jì)。這篇文章我就圍繞“Django 機(jī)器學(xué)習(xí) CNN”這套組合把整個(gè)項(xiàng)目從選題拆分、數(shù)據(jù)準(zhǔn)備、模型訓(xùn)練到系統(tǒng)實(shí)現(xiàn)、答辯演練的完整閉環(huán)拆開(kāi)講一遍。全程都是實(shí)際做畢設(shè)時(shí)用得上、踩過(guò)的坑和能直接抄的配置適合正在做這個(gè)題目或者類(lèi)似文本分類(lèi)題目的同學(xué)也適合想了解CNN怎么做文本識(shí)別的開(kāi)發(fā)者參考。1. 項(xiàng)目定位與整體設(shè)計(jì)思路1.1 核心需求拆解釣魚(yú)郵件檢測(cè)到底在解一個(gè)什么問(wèn)題先把這個(gè)題目拆到不能再拆。釣魚(yú)郵件識(shí)別輸入是一封郵件的文本內(nèi)容輸出是一個(gè)標(biāo)簽正常ham或者釣魚(yú)phishing。技術(shù)上講這是一個(gè)監(jiān)督學(xué)習(xí)下的文本二分類(lèi)任務(wù)。所謂“基于機(jī)器學(xué)習(xí)”其實(shí)是說(shuō)別用純規(guī)則比如黑名單匹配而是讓模型從數(shù)據(jù)里學(xué)習(xí)哪些詞、哪些句式、哪些結(jié)構(gòu)特征更容易出現(xiàn)在釣魚(yú)郵件里。但畢設(shè)不能只做一個(gè)模型你還得有一個(gè)能演示的系統(tǒng)。這就引出了題目的后半段用Django做Web端用戶(hù)粘貼一封郵件內(nèi)容或上傳一個(gè)郵件文件系統(tǒng)調(diào)用訓(xùn)練好的模型返回“正常/釣魚(yú)”的預(yù)測(cè)結(jié)果和置信度并保存檢測(cè)記錄方便回溯。所以整個(gè)項(xiàng)目其實(shí)由兩條線(xiàn)組成算法線(xiàn)數(shù)據(jù)集構(gòu)建 → 文本預(yù)處理 → 特征表示 → CNN模型訓(xùn)練 → 評(píng)估調(diào)優(yōu)工程線(xiàn)Django項(xiàng)目搭建 → 模型加載與推理服務(wù) → Web界面 → 檢測(cè)記錄管理 → 答辯演示把這兩條線(xiàn)在腦子里分開(kāi)進(jìn)度就好排了。很多同學(xué)一上來(lái)就寫(xiě)代碼結(jié)果算法沒(méi)搞定就開(kāi)始搭界面最后兩邊都半吊子。我個(gè)人的建議是先花一周把模型跑通再花一周做系統(tǒng)整合最后留三天做答辯材料和預(yù)演。1.2 技術(shù)選型為什么是Django為什么是CNN選Django做Web框架理由其實(shí)很實(shí)在。第一它自帶Admin后臺(tái)和ORM用戶(hù)管理、數(shù)據(jù)表操作這些代碼量能省掉一大半第二它有一套成熟的模板系統(tǒng)做一個(gè)簡(jiǎn)單的檢測(cè)頁(yè)面不需要前后端分離一條鏈路走下來(lái)代碼最少第三答辯的時(shí)候老師問(wèn)“你的系統(tǒng)架構(gòu)是什么”,你可以直接說(shuō)“MTV模式Model定義數(shù)據(jù)表Template渲染頁(yè)面View處理業(yè)務(wù)邏輯”這是教科書(shū)級(jí)別的對(duì)應(yīng)關(guān)系好講也好答。模型方面選CNN卷積神經(jīng)網(wǎng)絡(luò)是很多人的疑問(wèn)點(diǎn)CNN不是做圖像的嗎怎么用來(lái)做文本這里要解釋清楚文本經(jīng)過(guò)詞嵌入Word Embedding之后會(huì)變成一個(gè)“詞向量矩陣”它的形狀本質(zhì)上和圖像一樣是二維的只不過(guò)圖像的維度是“高度×寬度×通道”文本的維度是“詞數(shù)×詞向量維度”。既然數(shù)據(jù)形式一樣卷積就能處理。CNN在文本分類(lèi)中的經(jīng)典做法是使用一維卷積Conv1D用多個(gè)不同大小的卷積核去捕捉局部詞序特征。比起傳統(tǒng)的機(jī)器學(xué)習(xí)方法比如樸素貝葉斯、邏輯回歸、SVMCNN的優(yōu)勢(shì)在于它能自動(dòng)學(xué)習(xí)特征組合不需要人工做大量的特征工程。比起LSTM這類(lèi)循環(huán)神經(jīng)網(wǎng)絡(luò)CNN訓(xùn)練速度快、參數(shù)更少在小規(guī)模數(shù)據(jù)集上不容易過(guò)擬合而且反向傳播路徑短梯度更穩(wěn)定。對(duì)畢設(shè)而言CNN還有一個(gè)隱藏的好處網(wǎng)絡(luò)結(jié)構(gòu)就那么幾層每層的作用都能在答辯PPT里畫(huà)清楚解釋起來(lái)不費(fèi)勁。1.3 系統(tǒng)功能模塊劃分先畫(huà)圖再寫(xiě)代碼很多同學(xué)拿到題目就急著往Django里寫(xiě)視圖函數(shù)這是最容易翻車(chē)的做法。動(dòng)手之前先把系統(tǒng)有哪些功能模塊畫(huà)出來(lái)哪怕用一張手繪圖都行。這套系統(tǒng)的功能模塊大致是這樣的模塊功能說(shuō)明對(duì)應(yīng)的Django組件用戶(hù)認(rèn)證模塊登錄、注冊(cè)、會(huì)話(huà)管理Django自帶User模型 登錄視圖郵件檢測(cè)模塊支持粘貼郵件文本、上傳.eml文件調(diào)用模型推理View層 模型預(yù)測(cè)接口檢測(cè)記錄模塊保存每次檢測(cè)的文本、結(jié)果、置信度、時(shí)間Model層如DetectionRecord樣本管理模塊瀏覽和標(biāo)注歷史郵件樣本用于后續(xù)增量訓(xùn)練Admin后臺(tái) 自定義列表頁(yè)模型管理模塊查看模型信息、版本、訓(xùn)練集統(tǒng)計(jì)指標(biāo)配置表 展示頁(yè)數(shù)據(jù)統(tǒng)計(jì)模塊統(tǒng)計(jì)檢測(cè)總量、釣魚(yú)命中率、置信度分布圖表頁(yè)面可用ECharts這里容易栽的坑是功能模塊一口氣規(guī)劃了七八個(gè)結(jié)果每個(gè)都做得淺最后演示時(shí)每個(gè)頁(yè)面點(diǎn)兩下就沒(méi)內(nèi)容了。我建議選三個(gè)主功能往深了做——郵件檢測(cè)、記錄管理、統(tǒng)計(jì)展示。其他的能用Django Admin解決的絕不動(dòng)手寫(xiě)頁(yè)面這樣省下的時(shí)間可以拿去調(diào)模型和做實(shí)驗(yàn)對(duì)比。2. 數(shù)據(jù)準(zhǔn)備與預(yù)處理環(huán)節(jié)2.1 數(shù)據(jù)集從哪里來(lái)公開(kāi)數(shù)據(jù)與自建標(biāo)注相結(jié)合訓(xùn)練模型先要有數(shù)據(jù)。公開(kāi)渠道可以找到一些已被標(biāo)注的郵件數(shù)據(jù)集里面分成正常郵件和釣魚(yú)郵件兩類(lèi)。這類(lèi)數(shù)據(jù)集的優(yōu)點(diǎn)是標(biāo)注質(zhì)量相對(duì)有保障缺點(diǎn)是時(shí)間比較早新的釣魚(yú)手法沒(méi)有覆蓋。在實(shí)際畢設(shè)項(xiàng)目里我見(jiàn)過(guò)最穩(wěn)的做法是主體用公開(kāi)數(shù)據(jù)集做訓(xùn)練再自己采集和標(biāo)注一部分近期樣本做補(bǔ)充。自己標(biāo)注的時(shí)候要嚴(yán)格遵守一套標(biāo)準(zhǔn)比如包含索要賬號(hào)/密碼、誘導(dǎo)點(diǎn)擊鏈接、緊急施壓語(yǔ)氣的判為釣魚(yú)僅包含營(yíng)銷(xiāo)推廣、訂閱通知但沒(méi)有惡意意圖的判為正常爭(zhēng)議樣本單獨(dú)放一個(gè)文件夾不能憑感覺(jué)硬標(biāo)標(biāo)注完成之后要做一致性檢查。方法很簡(jiǎn)單同一封郵件找兩個(gè)人分別標(biāo)對(duì)比不一致的地方再討論確定。這一步雖然枯燥但直接決定了模型的上限因?yàn)闃?biāo)注噪聲一大再好的網(wǎng)絡(luò)結(jié)構(gòu)也學(xué)不出干凈的模式。數(shù)據(jù)量的把控也很關(guān)鍵。CNN在小數(shù)據(jù)集上容易過(guò)擬合一般建議正常郵件不少于3000封釣魚(yú)郵件不少于2000封。如果數(shù)據(jù)量不夠可以做簡(jiǎn)單的數(shù)據(jù)增強(qiáng)比如對(duì)正常郵件進(jìn)行同義詞替換、打亂句子順序但注意不要破壞關(guān)鍵語(yǔ)義。不過(guò)數(shù)據(jù)增強(qiáng)在郵件分類(lèi)上要克制因?yàn)獒烎~(yú)郵件本身的文本長(zhǎng)度就不長(zhǎng)過(guò)度改寫(xiě)容易失真。2.2 文本清洗與預(yù)處理郵件格式比想象中臟得多郵件文本和普通新聞?wù)Z料不一樣它里面混雜了大量結(jié)構(gòu)信息發(fā)件人地址、收件人地址、主題、正文、簽名檔、HTML標(biāo)簽、URL鏈接、Base64編碼的圖片塊等。如果不對(duì)這些內(nèi)容做處理模型學(xué)到的“特征”很可能只是一堆噪音?;镜那逑戳鞒淌菑?eml文件或原始文本中提取出主題和正文去除HTML標(biāo)簽保留可見(jiàn)文本正則匹配并抽取URL統(tǒng)一替換成特殊標(biāo)記例如URL將發(fā)件人郵箱地址規(guī)范化只保留域名部分過(guò)濾用戶(hù)名數(shù)字刪除冗余的簽名檔和免責(zé)聲明這些內(nèi)容長(zhǎng)度長(zhǎng)且與釣魚(yú)意圖無(wú)關(guān)對(duì)正文進(jìn)行小寫(xiě)化、去數(shù)字或替換為NUM、去停用詞這里有一個(gè)很關(guān)鍵的細(xì)節(jié)URL域名如何處理。釣魚(yú)郵件中最具區(qū)分度的特征往往就是URL本身但如果你直接把完整URL放進(jìn)文本測(cè)試集里幾乎不可能出現(xiàn)和訓(xùn)練集完全相同的URL模型學(xué)到的“記憶”不具備泛化能力。我推薦的做法是保留URL結(jié)構(gòu)但把域名中的數(shù)字和子域名部分泛化掉只留可泛化的模式。這一步想不清楚的話(huà)很容易做出一個(gè)準(zhǔn)確率虛高、實(shí)際上不可用的模型。2.3 文本向量化詞嵌入與序列填充清洗完文本之后要把文字變成CNN能吃的數(shù)字矩陣。流程是按照訓(xùn)練集統(tǒng)計(jì)出詞表通常保留出現(xiàn)頻率最高的前2萬(wàn)到5萬(wàn)個(gè)詞每個(gè)詞分配一個(gè)整數(shù)索引。然后對(duì)每條郵件文本做序列填充padding把所有樣本統(tǒng)一成同一個(gè)長(zhǎng)度比如100個(gè)詞。超過(guò)部分截?cái)嗖蛔悴糠盅a(bǔ)0。之后有兩種常見(jiàn)的選擇一是自己訓(xùn)練Embedding層也就是把每個(gè)詞的索引映射為一個(gè)可學(xué)習(xí)的向量二是直接用預(yù)訓(xùn)練的詞向量比如Word2Vec來(lái)初始化Embedding矩陣。畢設(shè)場(chǎng)景我更推薦前者原因很簡(jiǎn)單自己的訓(xùn)練數(shù)據(jù)領(lǐng)域性強(qiáng)郵件文本預(yù)訓(xùn)練向量是通用領(lǐng)域的加載反而要處理詞表對(duì)齊徒增工作量。Embedding維度選128或200都?jí)蛴锰罅擞?xùn)練慢且容易過(guò)擬合。整個(gè)預(yù)處理的邏輯要封裝成兩個(gè)函數(shù)一個(gè)fit_transform用于訓(xùn)練集一個(gè)transform用于測(cè)試集和后續(xù)新樣本。關(guān)鍵點(diǎn)是詞表只能從訓(xùn)練集生成嚴(yán)禁在預(yù)處理時(shí)把測(cè)試集一起拿去統(tǒng)計(jì)詞頻否則會(huì)造成嚴(yán)重的數(shù)據(jù)泄漏評(píng)估指標(biāo)完全失真。我在實(shí)際項(xiàng)目中排查過(guò)一個(gè)準(zhǔn)確率高達(dá)99.2%的模型最后發(fā)現(xiàn)就是詞表構(gòu)建時(shí)把所有數(shù)據(jù)混在一起做了歸一化兇手就是它。3. CSV模型設(shè)計(jì)與訓(xùn)練調(diào)優(yōu)3.1 模型結(jié)構(gòu)從Embedding到卷積再到分類(lèi)文本CNN的標(biāo)準(zhǔn)結(jié)構(gòu)不難難的是理解每一層在干什么。我用一個(gè)非常直觀的方式說(shuō)明假設(shè)一條郵件文本長(zhǎng)度是100個(gè)詞每個(gè)詞用一個(gè)128維的向量表示輸入的矩陣就是100×128。第一層卷積用多個(gè)不同“寬度”的卷積核去掃描這個(gè)矩陣。寬度是什么意思就是一次看幾個(gè)詞。比如寬度為3的卷積核一次讀3個(gè)連續(xù)詞的向量相當(dāng)于在看一個(gè)局部詞組的三元語(yǔ)法。用寬度分別為3、4、5的三組卷積核并行掃描就能同時(shí)捕捉到不同長(zhǎng)度的局部模式比如“點(diǎn)擊鏈接”、“立即更新密碼”、“驗(yàn)證您的賬戶(hù)”這類(lèi)短語(yǔ)。卷積輸出的每個(gè)位置再經(jīng)過(guò)GlobalMaxPooling也就是從每個(gè)特征圖里挑出最大值。這一步的含義是在整個(gè)句子中找到最能夠激活這個(gè)特征的位置。最后把所有池化后的特征拼接起來(lái)過(guò)一層全連接層用Softmax輸出兩個(gè)類(lèi)別的概率。具體結(jié)構(gòu)可以這樣設(shè)計(jì)from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Embedding, Conv1D, GlobalMaxPooling1D, Concatenate, Dense, Dropout def build_text_cnn(vocab_size50000, max_len100, embedding_dim128, num_filters128): inputs Input(shape(max_len,), dtypeint32) embedding Embedding(vocab_size, embedding_dim, mask_zeroTrue)(inputs) conv_blocks [] for kernel_size in [3, 4, 5]: conv Conv1D(filtersnum_filters, kernel_sizekernel_size, activationrelu)(embedding) pool GlobalMaxPooling1D()(conv) conv_blocks.append(pool) concat Concatenate()(conv_blocks) dropout Dropout(0.5)(concat) outputs Dense(2, activationsoftmax)(dropout) model Model(inputs, outputs) return model這里幾個(gè)超參數(shù)值得解釋詞表大小5萬(wàn)理論上覆蓋了絕大多數(shù)郵件文本詞匯卷積核寬度取3、4、5是為了覆蓋不同長(zhǎng)度的短語(yǔ)模式這是文本CNN的經(jīng)典配置Dropout設(shè)置為0.5是為了在卷積層抽取出的高維特征和最終分類(lèi)之間加一道“隨機(jī)失活”的屏障降低過(guò)擬合風(fēng)險(xiǎn)。3.2 損失函數(shù)、評(píng)估指標(biāo)與類(lèi)別不平衡處理模型輸出的是兩個(gè)類(lèi)別的概率分布那么損失函數(shù)就選用分類(lèi)問(wèn)題最標(biāo)準(zhǔn)的多分類(lèi)交叉熵CategoricalCrossentropy。訓(xùn)練時(shí)配合Adam優(yōu)化器學(xué)習(xí)率從0.001起步如果在驗(yàn)證集上損失不降再逐次調(diào)低到0.0005、0.0002。評(píng)估指標(biāo)這兒要尤其小心。很多同學(xué)只盯著準(zhǔn)確率Accuracy看在郵件分類(lèi)這個(gè)場(chǎng)景里這是不夠的。因?yàn)檎`]件和釣魚(yú)郵件的比例可能懸殊如果所有樣本都預(yù)測(cè)為正常準(zhǔn)確率也能有80%以上但這個(gè)模型一點(diǎn)用都沒(méi)有。所以必須同時(shí)看精確率Precision、召回率Recall和F1值。簡(jiǎn)單說(shuō)精確率關(guān)心“我判成釣魚(yú)的里面有多少真是釣魚(yú)”召回率關(guān)心“真的是釣魚(yú)的郵件里我抓到了多少”。二者天然有矛盾F1是它們的調(diào)和平均用來(lái)做綜合權(quán)衡。訓(xùn)練之前需要把訓(xùn)練集和測(cè)試集的類(lèi)別分布統(tǒng)計(jì)清楚。如果釣魚(yú)郵件偏少考慮做兩類(lèi)處理過(guò)采樣復(fù)制少數(shù)類(lèi)樣本讓訓(xùn)練集中兩類(lèi)數(shù)量接近設(shè)置類(lèi)別權(quán)重給少數(shù)類(lèi)的損失乘以一個(gè)權(quán)重系數(shù)懲罰漏判釣魚(yú)郵件實(shí)操中我兩邊都用過(guò)。類(lèi)別權(quán)重更省事直接在model.fit里傳入class_weight字典即可。過(guò)采樣雖然能增加樣本量但過(guò)度復(fù)制會(huì)讓模型對(duì)少數(shù)樣本過(guò)度學(xué)習(xí)也沒(méi)有額外信息量。3.3 訓(xùn)練過(guò)程與調(diào)參記錄一份可復(fù)現(xiàn)的參數(shù)清單訓(xùn)練要有一個(gè)清晰的過(guò)程記錄。我自己常用的日志格式很簡(jiǎn)單每個(gè)epoch記錄訓(xùn)練損失、訓(xùn)練準(zhǔn)確率、驗(yàn)證損失、驗(yàn)證F1。訓(xùn)練完成后把結(jié)果保存成CSV方便后續(xù)畫(huà)曲線(xiàn)。模型結(jié)構(gòu)層面的關(guān)鍵參數(shù)記錄如下參數(shù)項(xiàng)設(shè)置值設(shè)置理由輸入序列長(zhǎng)度100覆蓋大多數(shù)郵件正文長(zhǎng)度超過(guò)部分截?cái)嘣~表大小50000減少低頻詞噪聲兼顧訓(xùn)練速度Embedding維度128平衡表示能力和訓(xùn)練開(kāi)銷(xiāo)卷積核數(shù)量128特征提取充分度與參數(shù)量的折中卷積核寬度3、4、5捕捉二元到五元的局部詞序模式Dropout0.5緩解全連接層過(guò)擬合Batch Size32小批量訓(xùn)練穩(wěn)定顯存占用低Epochs20含早停訓(xùn)練集損失下降變緩后停止優(yōu)化器Adam lr0.001自適應(yīng)學(xué)習(xí)率收斂平穩(wěn)訓(xùn)練時(shí)開(kāi)啟EarlyStopping監(jiān)控驗(yàn)證集F1連續(xù)3個(gè)epoch不提升就停止并恢復(fù)最優(yōu)權(quán)重。開(kāi)啟ModelCheckpoint保存最優(yōu)權(quán)重文件。我實(shí)際的訓(xùn)練記錄中大約在第7到第10個(gè)epoch時(shí)驗(yàn)證集指標(biāo)達(dá)到峰值后續(xù)如果繼續(xù)訓(xùn)練驗(yàn)證損失開(kāi)始反彈這就是過(guò)擬合信號(hào)。3.4 樸素貝葉斯/SVM/LSTM對(duì)比實(shí)驗(yàn)的必要性答辯的時(shí)候老師大概率會(huì)問(wèn)一個(gè)問(wèn)題你用了CNN那它比樸素貝葉斯好在哪這個(gè)問(wèn)題如果沒(méi)有數(shù)據(jù)支撐只能空談算法原理說(shuō)服力不足。我的建議是至少做兩組對(duì)比實(shí)驗(yàn)基準(zhǔn)模型樸素貝葉斯或邏輯回歸用TF-IDF特征序列模型單層LSTM同樣用Embedding 全連接三組模型在同一份訓(xùn)練/測(cè)試劃分上跑記錄準(zhǔn)確率、F1、訓(xùn)練時(shí)間三個(gè)指標(biāo)。對(duì)比結(jié)果一般會(huì)是樸素貝葉斯訓(xùn)練極快但F1偏低LSTM的F1可能略高于CNN但訓(xùn)練時(shí)間長(zhǎng)很多CNN在訓(xùn)練時(shí)間和性能之間比較均衡。有了這張對(duì)比表答辯時(shí)你就能說(shuō)清楚選CNN是在性能和計(jì)算成本之間做的工程權(quán)衡而不是只會(huì)照著論文抄。4. Django系統(tǒng)集成與功能實(shí)現(xiàn)4.1 系統(tǒng)架構(gòu)與請(qǐng)求處理流程模型訓(xùn)練完成后要把它變成一個(gè)能用的Web系統(tǒng)。整個(gè)系統(tǒng)的請(qǐng)求流程是用戶(hù)在頁(yè)面填寫(xiě)郵件文本或上傳.eml文件Django視圖接收數(shù)據(jù)調(diào)用預(yù)處理模塊對(duì)文本做清洗、分詞、序列填充將預(yù)處理后的整數(shù)序列輸入模型模型輸出分類(lèi)概率視圖組織返回結(jié)果檢測(cè)記錄寫(xiě)入數(shù)據(jù)庫(kù)頁(yè)面展示結(jié)果與置信度這個(gè)流程看著簡(jiǎn)單有幾個(gè)實(shí)現(xiàn)細(xì)節(jié)要注意。模型加載必須在Django啟動(dòng)時(shí)完成不能在視圖函數(shù)里反復(fù)加載否則每次請(qǐng)求都要重新讀權(quán)重推理速度極慢。通常做法是放到一個(gè)單獨(dú)模塊里用模塊級(jí)全局變量保存模型實(shí)例因?yàn)槭侵蛔x推理Django多線(xiàn)程下不會(huì)有寫(xiě)沖突但為了保險(xiǎn)也可以在預(yù)測(cè)接口加一個(gè)線(xiàn)程鎖。實(shí)際項(xiàng)目里如果單worker并發(fā)量不大不加鎖問(wèn)題也不大。4.2 數(shù)據(jù)表設(shè)計(jì)與關(guān)鍵代碼實(shí)現(xiàn)Django的數(shù)據(jù)表設(shè)計(jì)不用復(fù)雜。用戶(hù)用自帶User表自己需要建一張檢測(cè)記錄表大概字段如下from django.db import models from django.contrib.auth.models import User class DetectionRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name用戶(hù)) content models.TextField(verbose_name郵件文本) result models.CharField(max_length16, choices[(normal, 正常), (phishing, 釣魚(yú))], verbose_name檢測(cè)結(jié)果) confidence models.FloatField(verbose_name模型置信度) created_at models.DateTimeField(auto_now_addTrue, verbose_name檢測(cè)時(shí)間) class Meta: verbose_name 檢測(cè)記錄 ordering [-created_at]視圖代碼要分兩個(gè)視圖一個(gè)渲染頁(yè)面GET請(qǐng)求一個(gè)處理預(yù)測(cè)POST請(qǐng)求。預(yù)測(cè)視圖中把文本預(yù)處理和模型調(diào)用拆成獨(dú)立函數(shù)這樣做的好處是方便寫(xiě)單元測(cè)試。如果處理邏輯報(bào)錯(cuò)視圖內(nèi)要捕獲異常并返回友好提示不要讓用戶(hù)看到堆棧信息。4.3 模型部署與預(yù)測(cè)模塊的封裝模型部署上有一個(gè)比較經(jīng)典的坑Python版本和依賴(lài)庫(kù)版本不一致。訓(xùn)練時(shí)用的TensorFlow/Keras版本如果和Django運(yùn)行環(huán)境的版本對(duì)不上模型文件經(jīng)常加載報(bào)錯(cuò)。最穩(wěn)妥的方案是把依賴(lài)版本固定記錄到requirements.txt中比如tensorflow2.x.x、django4.x.x并在部署環(huán)境中嚴(yán)格安裝這些版本。模型保存用Keras的SavedModel格式不要只存權(quán)重文件因?yàn)槟P徒Y(jié)構(gòu)也要一并恢復(fù)。預(yù)測(cè)模塊的封裝大致是這樣import re from tensorflow.keras.models import load_model import numpy as np from .text_preprocess import clean_text, text_to_sequence _model None def get_model(): global _model if _model is None: _model load_model(model/phishing_cnn_model.keras) return _model def predict_email(text): cleaned clean_text(text) seq text_to_sequence(cleaned, max_len100) seq np.array([seq]) probs get_model().predict(seq, verbose0)[0] label_idx int(np.argmax(probs)) label phishing if label_idx 1 else normal confidence float(probs[label_idx]) return label, confidence這個(gè)封裝看起來(lái)簡(jiǎn)單但把模型加載、文本預(yù)處理、預(yù)測(cè)推理、結(jié)果格式化幾個(gè)職責(zé)都分清了。后續(xù)如果要從CNN換成LSTM只需要改模型路徑和預(yù)處理函數(shù)視圖層完全不用動(dòng)。4.4 可視化統(tǒng)計(jì)與Admin后臺(tái)增強(qiáng)只做一個(gè)檢測(cè)頁(yè)面答辯時(shí)內(nèi)容略顯單薄。建議在后臺(tái)加一個(gè)簡(jiǎn)單的統(tǒng)計(jì)頁(yè)面用圖表展示每日檢測(cè)數(shù)量趨勢(shì)正常/釣魚(yú)檢測(cè)結(jié)果占比模型置信度分布低于0.6的要特別關(guān)注最近一周釣魚(yú)郵件的高頻詞匯這些統(tǒng)計(jì)信息的計(jì)算邏輯可以直接寫(xiě)在視圖函數(shù)里通過(guò)ORM聚合查詢(xún)完成不需要額外引入重型框架。前端圖表庫(kù)可以用輕量級(jí)的ECharts通過(guò)簡(jiǎn)單JSON接口把統(tǒng)計(jì)數(shù)據(jù)傳給前端渲染。這一步雖然工作量不大但答辯演示時(shí)效果非常好老師會(huì)覺(jué)得你考慮了系統(tǒng)的完整性和可用性。5. 答辯環(huán)節(jié)的準(zhǔn)備與高頻問(wèn)題應(yīng)答5.1 演示流程設(shè)計(jì)先系統(tǒng)后PPT答辯的演示順序很關(guān)鍵。我建議先花兩到三分鐘現(xiàn)場(chǎng)演示系統(tǒng)再講PPT。原因很簡(jiǎn)單先讓老師直觀看到系統(tǒng)的輸入輸出、檢測(cè)效果再講技術(shù)細(xì)節(jié)老師的注意力更容易被抓住。如果先講PPT講到最后老師可能已經(jīng)疲勞了再演示時(shí)提問(wèn)會(huì)更苛刻。演示準(zhǔn)備一條主線(xiàn)用一封明顯帶有“賬號(hào)異常請(qǐng)點(diǎn)擊鏈接更新密碼”特征的釣魚(yú)郵件文本現(xiàn)場(chǎng)粘貼到檢測(cè)頁(yè)面顯示“釣魚(yú)”結(jié)果和高置信度再粘貼一封普通通知郵件顯示“正?!苯Y(jié)果。兩組結(jié)果對(duì)比清晰節(jié)奏快講解時(shí)重點(diǎn)放在“模型關(guān)注了哪些詞和結(jié)構(gòu)”上。演示前一定要錄好備用視頻防止現(xiàn)場(chǎng)環(huán)境出錯(cuò)。5.2 高頻答辯問(wèn)題與應(yīng)答思路答辯高頻問(wèn)題集中在以下幾個(gè)方面提前想好答案很關(guān)鍵。為什么用CNN而不用LSTM/Transformer可以這樣回答郵件文本長(zhǎng)度相對(duì)較短CNN通過(guò)多尺度卷積核能高效提取局部關(guān)鍵短語(yǔ)LSTM雖能建模長(zhǎng)距離依賴(lài)但訓(xùn)練速度慢在中小規(guī)模數(shù)據(jù)集上優(yōu)勢(shì)不明顯Transformer需要更大數(shù)據(jù)量預(yù)訓(xùn)練對(duì)畢設(shè)項(xiàng)目而言性?xún)r(jià)比不高。你的數(shù)據(jù)集是怎么來(lái)的標(biāo)注怎么保證質(zhì)量要如實(shí)說(shuō)明公開(kāi)數(shù)據(jù)加自標(biāo)注并強(qiáng)調(diào)標(biāo)注規(guī)范、雙人校驗(yàn)、爭(zhēng)議樣本剔除。只要數(shù)據(jù)標(biāo)注過(guò)程是嚴(yán)謹(jǐn)?shù)睦蠋熞话悴粫?huì)為難你。模型的誤報(bào)和漏報(bào)怎么處理可以回答系統(tǒng)保留了置信度閾值設(shè)置對(duì)置信度在0.5到0.7之間的模糊樣本轉(zhuǎn)入人工復(fù)核流程同時(shí)記錄了檢測(cè)日志后續(xù)可用增量數(shù)據(jù)重新訓(xùn)練模型。這個(gè)回答展示了你對(duì)系統(tǒng)工程化問(wèn)題的思考。特征工程時(shí)怎么處理URL和域名這是釣魚(yú)郵件特有的問(wèn)題。可以說(shuō)明提取URL結(jié)構(gòu)并做泛化處理避免模型對(duì)特定域名過(guò)擬合。再補(bǔ)充一句惡意域名的信用信息可以作為輔助特征但模型主體特征仍然是文本內(nèi)容。5.3 常見(jiàn)開(kāi)發(fā)坑與排查速查表整理一份我見(jiàn)過(guò)最多的坑這些你在自己開(kāi)發(fā)時(shí)也大概率會(huì)遇到現(xiàn)象可能原因排查方法預(yù)測(cè)接口第一次請(qǐng)求特別慢模型在視圖內(nèi)被重復(fù)加載檢查是否在啟動(dòng)時(shí)加載模型到全局變量中文郵件亂碼編碼未統(tǒng)一成UTF-8文件讀寫(xiě)和數(shù)據(jù)庫(kù)連接都顯式指定utf-8mb4模型結(jié)果總是同一類(lèi)類(lèi)別權(quán)重未設(shè)置數(shù)據(jù)嚴(yán)重不平衡打印測(cè)試集類(lèi)別分布檢查class_weight準(zhǔn)確率高但演示時(shí)效果差測(cè)試集與真實(shí)輸入分布不一致檢查預(yù)處理函數(shù)是否和訓(xùn)練時(shí)完全一致詞表是否覆蓋Django部署時(shí)模型加載失敗依賴(lài)版本不一致用requirements.txt鎖定版本復(fù)現(xiàn)訓(xùn)練環(huán)境.eml文件解析后正文為空HTML郵件只解析了純文本使用郵件庫(kù)同時(shí)解析HTML和純文本部分這套速查表你在答辯準(zhǔn)備時(shí)過(guò)一遍大概率能救你一命。因?yàn)楹芏鄦?wèn)題是到了演示前一天才暴露的提前排查可以避免現(xiàn)場(chǎng)翻車(chē)。6. 一點(diǎn)個(gè)人經(jīng)驗(yàn)帶過(guò)的畢設(shè)項(xiàng)目里凡是按“先數(shù)據(jù)、再模型、后系統(tǒng)”順序推進(jìn)的最后效果基本都不差凡是上來(lái)就寫(xiě)代碼的多半在答辯前一周還在趕工。這個(gè)題目真正的難點(diǎn)不在模型結(jié)構(gòu)而在數(shù)據(jù)治理和系統(tǒng)接口的解耦能力。你有本事把預(yù)處理和模型推理封裝成不留后患的獨(dú)立模塊后面所有環(huán)節(jié)都會(huì)順起來(lái)。做這一類(lèi)系統(tǒng)的時(shí)候我個(gè)人習(xí)慣在訓(xùn)練之前先把評(píng)估閉環(huán)想清楚——哪些指標(biāo)能說(shuō)明模型有效哪些情況下模型會(huì)失效這件事想透了答辯時(shí)你就有底氣說(shuō)“我的模型在什么條件下有效、在什么條件下需要人工兜底”這比背十頁(yè)論文都管用。最后再送一個(gè)小建議把訓(xùn)練過(guò)程中的實(shí)驗(yàn)記錄整理成一份簡(jiǎn)短的實(shí)驗(yàn)報(bào)告貼到系統(tǒng)的模型管理頁(yè)面里。答辯時(shí)你直接點(diǎn)開(kāi)頁(yè)面說(shuō)出每組實(shí)驗(yàn)的指標(biāo)對(duì)比比任何口頭解釋都更有說(shuō)服力。