數(shù)幾率回歸實(shí)戰(zhàn):西瓜鳶尾花分類期末大作業(yè)避坑指南)
簡(jiǎn)介這份資源是面向計(jì)算機(jī)相關(guān)專業(yè)在校學(xué)生、教師及企業(yè)員工的期末大作業(yè)與課程設(shè)計(jì)資料包圍繞對(duì)數(shù)幾率回歸模型實(shí)現(xiàn)西瓜與鳶尾花數(shù)據(jù)集的識(shí)別分類任務(wù)展開適合作為機(jī)器學(xué)習(xí)入門進(jìn)階練習(xí)也可直接用于畢設(shè)、課程設(shè)計(jì)或初期項(xiàng)目立項(xiàng)演示。壓縮包共30個(gè)文件約544KB包含6個(gè)Python源碼文件、4個(gè)Jupyter Notebook、2份Word作業(yè)報(bào)告、2份Markdown項(xiàng)目說明以及xml、iml等IDE配置文件和pyc緩存文件源碼附帶詳細(xì)注釋便于理解算法實(shí)現(xiàn)與調(diào)試過程。目前已有296人學(xué)習(xí)下載。讀者可獲得完整的對(duì)數(shù)幾率回歸建模流程、數(shù)據(jù)預(yù)處理與分類評(píng)估代碼、可直接提交的作業(yè)報(bào)告模板以及清晰的目錄結(jié)構(gòu)既能對(duì)照復(fù)現(xiàn)實(shí)驗(yàn)也方便在此基礎(chǔ)上進(jìn)行二次開發(fā)與功能拓展。1. 對(duì)數(shù)幾率回歸做西瓜鳶尾花分類期末大作業(yè)里最容易被低估的一行代碼很多人拿到「基于對(duì)數(shù)幾率回歸模型實(shí)現(xiàn)西瓜鳶尾花識(shí)別分類」這個(gè)題目第一反應(yīng)是邏輯回歸太簡(jiǎn)單了sklearn 三行代碼就能跑完期末大作業(yè)寫這個(gè)是不是顯得沒水平我當(dāng)年也這么想直到幫學(xué)弟調(diào)他的作業(yè)報(bào)告才發(fā)現(xiàn)真正翻車的地方根本不在模型本身——他把西瓜數(shù)據(jù)集和鳶尾花數(shù)據(jù)集混在一起讀特征列對(duì)不上準(zhǔn)確率一直在 0.33 附近晃他還以為是邏輯回歸不適合多分類。對(duì)數(shù)幾率回歸Logistic Regression本質(zhì)是線性分類器套一個(gè) Sigmoid 映射二分類是它的主場(chǎng)多分類要靠 One-vs-Rest 或 Softmax 擴(kuò)展。西瓜數(shù)據(jù)集是二分類好瓜/壞瓜鳶尾花是三分類三種花兩個(gè)任務(wù)放在一份作業(yè)里恰好覆蓋了從二分類到多分類的完整鏈路。這篇筆記面向正在做這個(gè)期末大作業(yè)、或者想拿它當(dāng) Python 入門練手項(xiàng)目的人把數(shù)據(jù)讀取、模型訓(xùn)練、參數(shù)調(diào)節(jié)、報(bào)告撰寫和注釋規(guī)范整條路徑講清楚讓你交上去的東西經(jīng)得起追問。2. 先搞清楚兩個(gè)數(shù)據(jù)集的結(jié)構(gòu)差異西瓜二分類和鳶尾花三分類不是一回事2.1 西瓜數(shù)據(jù)集 3.0 的字段含義與標(biāo)簽編碼西瓜數(shù)據(jù)集 3.0 是周志華《機(jī)器學(xué)習(xí)》里的經(jīng)典數(shù)據(jù)共 17 個(gè)樣本每個(gè)樣本有 8 個(gè)特征色澤、根蒂、敲聲、紋理、臍部、觸感、密度、含糖率。前 6 個(gè)是離散字符串后 2 個(gè)是連續(xù)數(shù)值。標(biāo)簽是「好瓜」和「壞瓜」。很多人直接把這個(gè)數(shù)據(jù)集丟給邏輯回歸會(huì)報(bào)could not convert string to float的錯(cuò)誤因?yàn)?sklearn 不接受字符串特征。常見做法是先用 pandas 讀入然后對(duì)離散列做映射編碼。注意不要用 OneHotEncoder 無腦展開17 個(gè)樣本展開成幾十維反而容易過擬合。我一般用簡(jiǎn)單的字典映射把「青綠/烏黑/淺白」映射成 0/1/2這樣既保留了序關(guān)系又不會(huì)維度爆炸。import pandas as pd import numpy as np # 讀取西瓜數(shù)據(jù)集假設(shè)文件為 watermelon3.0.csv df pd.read_csv(watermelon3.0.csv, encodingutf-8) # 離散特征映射字典按語義順序編碼 color_map {青綠: 0, 烏黑: 1, 淺白: 2} root_map {蜷縮: 0, 稍蜷: 1, 硬挺: 2} sound_map {濁響: 0, 沉悶: 1, 清脆: 2} texture_map {清晰: 0, 稍糊: 1, 模糊: 2} umbilicus_map {凹陷: 0, 稍凹: 1, 平坦: 2} touch_map {硬滑: 0, 軟粘: 1} df[色澤] df[色澤].map(color_map) df[根蒂] df[根蒂].map(root_map) df[敲聲] df[敲聲].map(sound_map) df[紋理] df[紋理].map(texture_map) df[臍部] df[臍部].map(umbilicus_map) df[觸感] df[觸感].map(touch_map) # 標(biāo)簽編碼好瓜1壞瓜0 df[好瓜] df[好瓜].map({是: 1, 否: 0}) # 檢查缺失值 print(df.isnull().sum())這段代碼的關(guān)鍵在于映射字典的順序要和數(shù)據(jù)集里的類別一一對(duì)應(yīng)如果映射錯(cuò)了模型學(xué)到的就是錯(cuò)的序關(guān)系。參數(shù)方面encodingutf-8是為了防止中文列名讀取亂碼如果你在 Windows 上遇到UnicodeDecodeError可以換成gbk試試。isnull().sum()用來確認(rèn)映射過程中有沒有產(chǎn)生 NaN因?yàn)槿绻硞€(gè)類別沒在字典里map 會(huì)返回 NaN后續(xù)訓(xùn)練直接報(bào)錯(cuò)。2.2 鳶尾花數(shù)據(jù)集的加載與特征標(biāo)準(zhǔn)化鳶尾花數(shù)據(jù)集在 sklearn 里自帶150 個(gè)樣本4 個(gè)連續(xù)特征花萼長(zhǎng)度、花萼寬度、花瓣長(zhǎng)度、花瓣寬度3 個(gè)類別各 50 個(gè)樣本。這個(gè)數(shù)據(jù)集干凈得多但有一個(gè)坑三個(gè)類別的特征分布范圍不同花瓣長(zhǎng)度和花萼寬度的量綱差異會(huì)導(dǎo)致邏輯回歸的梯度下降收斂慢。我一般會(huì)做標(biāo)準(zhǔn)化把均值歸到 0、方差歸到 1。from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 加載鳶尾花數(shù)據(jù)集 iris load_iris() X, y iris.data, iris.target # 標(biāo)準(zhǔn)化邏輯回歸對(duì)特征尺度敏感標(biāo)準(zhǔn)化能加速收斂 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 劃分訓(xùn)練集和測(cè)試集stratify 保證類別比例一致 X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.3, random_state42, stratifyy ) print(f訓(xùn)練集樣本數(shù): {X_train.shape[0]}, 測(cè)試集樣本數(shù): {X_test.shape[0]}) print(f類別分布: {np.bincount(y_train)})StandardScaler的fit_transform只能在訓(xùn)練集上做測(cè)試集要用訓(xùn)練集的均值和方差來transform否則就是數(shù)據(jù)泄露。stratifyy保證劃分后每個(gè)類別的比例和原始一致150 個(gè)樣本按 7:3 劃分后訓(xùn)練集每類 35 個(gè)測(cè)試集每類 15 個(gè)。random_state42固定隨機(jī)種子保證你每次跑出來的結(jié)果一樣報(bào)告里寫準(zhǔn)確率才有意義。提示西瓜數(shù)據(jù)集樣本太少標(biāo)準(zhǔn)化后反而可能放大噪聲建議只對(duì)連續(xù)特征「密度」和「含糖率」做標(biāo)準(zhǔn)化離散特征保持原樣。3. 對(duì)數(shù)幾率回歸的兩種實(shí)現(xiàn)路徑手寫梯度下降和 sklearn 調(diào)包3.1 手寫二分類邏輯回歸的梯度下降期末大作業(yè)如果只調(diào)包報(bào)告里沒什么可寫的。我建議至少手寫一遍二分類的邏輯回歸把 Sigmoid、交叉熵?fù)p失、梯度更新這三個(gè)核心公式落到代碼里。這樣報(bào)告里可以畫損失曲線老師一看就知道你真懂了。def sigmoid(z): # 防止 exp 溢出對(duì) z 做截?cái)?z np.clip(z, -500, 500) return 1.0 / (1.0 np.exp(-z)) def train_logistic_regression(X, y, lr0.1, epochs1000): 手寫二分類邏輯回歸訓(xùn)練 X: 特征矩陣 (n_samples, n_features) y: 標(biāo)簽向量 (n_samples,) lr: 學(xué)習(xí)率 epochs: 迭代次數(shù) 返回: 權(quán)重 w, 偏置 b, 損失歷史 n_samples, n_features X.shape w np.zeros(n_features) b 0.0 loss_history [] for i in range(epochs): # 前向傳播 z np.dot(X, w) b y_pred sigmoid(z) # 交叉熵?fù)p失加 1e-9 防止 log(0) loss -np.mean(y * np.log(y_pred 1e-9) (1 - y) * np.log(1 - y_pred 1e-9)) loss_history.append(loss) # 梯度計(jì)算 dw np.dot(X.T, (y_pred - y)) / n_samples db np.mean(y_pred - y) # 參數(shù)更新 w - lr * dw b - lr * db return w, b, loss_history學(xué)習(xí)率lr是最關(guān)鍵的參數(shù)設(shè) 0.1 在標(biāo)準(zhǔn)化后的數(shù)據(jù)上一般能收斂如果損失曲線震蕩就降到 0.01如果下降太慢就升到 0.5 試試。epochs設(shè) 1000 通常夠用你可以畫loss_history看它什么時(shí)候趨于平穩(wěn)。np.clip(z, -500, 500)是防止np.exp溢出這是血淚經(jīng)驗(yàn)不加上去在某些樣本上會(huì)直接報(bào)RuntimeWarning: overflow。3.2 sklearn 的 LogisticRegression 多分類參數(shù)鳶尾花是三分類sklearn 的LogisticRegression默認(rèn)用multi_classauto在數(shù)據(jù)量小的時(shí)候會(huì)選 OvR數(shù)據(jù)量大且類別多的時(shí)候選 Softmax。你可以顯式指定multi_classmultinomial來強(qiáng)制用 Softmax配合solverlbfgs或solversaga。from sklearn.linear_model import LogisticRegression from sklearn.metrics import accuracy_score, classification_report # 多分類邏輯回歸使用 Softmax 回歸 clf LogisticRegression( multi_classmultinomial, solverlbfgs, C1.0, max_iter1000, random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(f準(zhǔn)確率: {accuracy_score(y_test, y_pred):.4f}) print(classification_report(y_test, y_pred, target_namesiris.target_names))C是正則化強(qiáng)度的倒數(shù)默認(rèn) 1.0越小正則化越強(qiáng)。如果報(bào)告里發(fā)現(xiàn)訓(xùn)練集準(zhǔn)確率遠(yuǎn)高于測(cè)試集說明過擬合把C降到 0.1 或 0.01。max_iter默認(rèn) 100在鳶尾花上可能不收斂設(shè) 1000 比較穩(wěn)。classification_report會(huì)輸出每個(gè)類別的精確率、召回率和 F1報(bào)告里直接貼這個(gè)表比只寫一個(gè)準(zhǔn)確率有說服力得多。3.3 西瓜數(shù)據(jù)集上的二分類驗(yàn)證西瓜數(shù)據(jù)集只有 17 個(gè)樣本用留一法交叉驗(yàn)證比 train_test_split 更合理因?yàn)殡S機(jī)劃分可能導(dǎo)致某一類在測(cè)試集里一個(gè)都沒有。from sklearn.model_selection import LeaveOneOut from sklearn.linear_model import LogisticRegression # 準(zhǔn)備西瓜數(shù)據(jù) X_wm df[[色澤, 根蒂, 敲聲, 紋理, 臍部, 觸感, 密度, 含糖率]].values y_wm df[好瓜].values loo LeaveOneOut() acc_list [] for train_idx, test_idx in loo.split(X_wm): X_tr, X_te X_wm[train_idx], X_wm[test_idx] y_tr, y_te y_wm[train_idx], y_wm[test_idx] model LogisticRegression(max_iter500) model.fit(X_tr, y_tr) acc_list.append(model.score(X_te, y_te)) print(f留一法平均準(zhǔn)確率: {np.mean(acc_list):.4f})留一法每次只留一個(gè)樣本做測(cè)試17 個(gè)樣本跑 17 次最終準(zhǔn)確率是 17 次的平均。這個(gè)數(shù)字在報(bào)告里比單次劃分更可信。注意西瓜數(shù)據(jù)集樣本少準(zhǔn)確率波動(dòng)大0.7 到 0.9 之間都算正常不要為了追求高準(zhǔn)確率去調(diào)參數(shù)調(diào)到過擬合。4. 避坑與排查邏輯回歸作業(yè)里最容易翻車的 5 個(gè)地方4.1 準(zhǔn)確率一直上不去檢查標(biāo)簽編碼是否反了現(xiàn)象模型訓(xùn)練完準(zhǔn)確率在 0.3 到 0.5 之間比隨機(jī)猜還差。原因標(biāo)簽映射時(shí)把「好瓜」映射成 0、「壞瓜」映射成 1但預(yù)測(cè)時(shí)又按 1 是好瓜來解讀導(dǎo)致精確率和召回率對(duì)調(diào)。解決統(tǒng)一標(biāo)簽編碼規(guī)范在代碼開頭用注釋寫清楚0壞瓜, 1好瓜所有評(píng)估指標(biāo)都按這個(gè)約定解讀。4.2 報(bào)錯(cuò) could not convert string to float現(xiàn)象fit的時(shí)候直接拋ValueError: could not convert string to float: 青綠。原因西瓜數(shù)據(jù)集的離散列沒有做編碼直接傳給了 sklearn。解決在fit之前用df.select_dtypes(includeobject).columns檢查還有哪些字符串列逐一映射。如果懶得手動(dòng)映射可以用pd.get_dummies做 OneHot 編碼但 17 個(gè)樣本展開后維度太高不推薦。4.3 損失曲線不下降或者震蕩現(xiàn)象手寫梯度下降時(shí)loss_history一直在 0.69 附近不動(dòng)或者上下大幅震蕩。原因?qū)W習(xí)率設(shè)得太大導(dǎo)致震蕩或者特征沒有標(biāo)準(zhǔn)化導(dǎo)致梯度尺度差異大。解決先把學(xué)習(xí)率降到 0.01 試如果下降太慢再逐步升到 0.1、0.3。同時(shí)確認(rèn)連續(xù)特征做了標(biāo)準(zhǔn)化西瓜數(shù)據(jù)集的「密度」在 0.3 到 0.8 之間「含糖率」在 0.05 到 0.45 之間量綱接近但鳶尾花的花瓣長(zhǎng)度在 1 到 7 之間不標(biāo)準(zhǔn)化會(huì)影響收斂。4.4 多分類時(shí) predict_proba 返回的列數(shù)不對(duì)現(xiàn)象鳶尾花三分類predict_proba返回的卻是兩列。原因multi_class參數(shù)設(shè)成了ovr且某個(gè)類別被當(dāng)成正類、其余合并成負(fù)類。解決顯式設(shè)multi_classmultinomial并且確認(rèn)solver支持多分類lbfgs和saga都支持liblinear不支持多分類。4.5 作業(yè)報(bào)告里注釋太少被扣分現(xiàn)象代碼能跑但報(bào)告分?jǐn)?shù)不高老師評(píng)語寫「注釋不足可讀性差」。原因只寫了# 導(dǎo)入庫(kù)這種無意義注釋沒有解釋參數(shù)含義和設(shè)計(jì)決策。解決每個(gè)函數(shù)寫 docstring說明輸入輸出和關(guān)鍵參數(shù)每個(gè)映射字典旁邊寫清楚類別順序每個(gè)超參數(shù)寫一行注釋說明為什么選這個(gè)值。參考 Python 文檔注釋規(guī)范用寫函數(shù)說明用#寫行內(nèi)說明。注意不要用# -*- coding: utf-8 -*-這種老式編碼聲明Python 3 默認(rèn)就是 UTF-8寫了反而顯得不專業(yè)。5. 報(bào)告撰寫與注釋規(guī)范讓大作業(yè)從及格線拉到優(yōu)秀5.1 作業(yè)報(bào)告的結(jié)構(gòu)模板期末大作業(yè)的報(bào)告一般包括問題描述、數(shù)據(jù)集說明、方法原理、實(shí)驗(yàn)設(shè)置、結(jié)果分析、結(jié)論。我建議在「方法原理」部分把對(duì)數(shù)幾率回歸的公式推導(dǎo)寫清楚從線性回歸到 Sigmoid 映射從極大似然估計(jì)到交叉熵?fù)p失從梯度推導(dǎo)到參數(shù)更新。這部分不用太長(zhǎng)一頁紙足夠但公式要自己敲不要截圖?!笇?shí)驗(yàn)設(shè)置」部分要寫清楚訓(xùn)練集測(cè)試集劃分比例、隨機(jī)種子、標(biāo)準(zhǔn)化方法、超參數(shù)取值。這些細(xì)節(jié)決定了你的實(shí)驗(yàn)?zāi)懿荒鼙粡?fù)現(xiàn)?!附Y(jié)果分析」部分除了準(zhǔn)確率還要放混淆矩陣和分類報(bào)告鳶尾花三分類的混淆矩陣能看出哪個(gè)類別容易混。from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # 繪制混淆矩陣 cm confusion_matrix(y_test, y_pred) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsiris.target_names, yticklabelsiris.target_names) plt.xlabel(預(yù)測(cè)類別) plt.ylabel(真實(shí)類別) plt.title(鳶尾花分類混淆矩陣) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150)fmtd表示顯示整數(shù)cmapBlues是配色方案dpi150保證圖片清晰度夠打印。保存圖片而不是只show是因?yàn)閳?bào)告里要插入圖片直接截圖會(huì)有白邊。5.2 代碼注釋的三個(gè)層次第一層是模塊級(jí)注釋在文件開頭寫清楚這個(gè)腳本做什么、依賴哪些庫(kù)、數(shù)據(jù)文件放在哪里。第二層是函數(shù)級(jí)注釋用 docstring 寫清楚輸入?yún)?shù)的類型和含義、返回值的結(jié)構(gòu)。第三層是行內(nèi)注釋只在對(duì)非直觀操作做解釋時(shí)寫比如「對(duì) z 做截?cái)喾乐?exp 溢出」這種。 西瓜鳶尾花分類作業(yè) 依賴: pandas, numpy, sklearn, matplotlib, seaborn 數(shù)據(jù): watermelon3.0.csv 放在同目錄下 運(yùn)行: python classification.py 這種模塊級(jí)注釋看起來簡(jiǎn)單但很多同學(xué)交上來的代碼連文件名和運(yùn)行方式都沒寫老師想跑都跑不起來。加上這三行印象分直接拉滿。5.3 參數(shù)調(diào)優(yōu)的記錄方式報(bào)告里不要只寫「最終準(zhǔn)確率 0.95」要寫「在 C1.0 時(shí)準(zhǔn)確率 0.93C0.1 時(shí) 0.95C0.01 時(shí) 0.91最終選 C0.1」。用一個(gè)表格記錄不同參數(shù)下的結(jié)果比一段文字描述清楚得多。C 值訓(xùn)練集準(zhǔn)確率測(cè)試集準(zhǔn)確率備注0.010.900.91正則化過強(qiáng)欠擬合0.10.960.95最佳1.00.990.93輕微過擬合10.01.000.91過擬合明顯這張表放在報(bào)告里老師一眼就能看出你做了調(diào)參實(shí)驗(yàn)而不是隨便跑了一次就交。注意訓(xùn)練集準(zhǔn)確率和測(cè)試集準(zhǔn)確率的差距差距越大過擬合越嚴(yán)重。5.4 用交叉驗(yàn)證替代單次劃分如果想讓結(jié)果更穩(wěn)可以用cross_val_score做 5 折交叉驗(yàn)證報(bào)告里寫「5 折交叉驗(yàn)證平均準(zhǔn)確率 0.95±0.03」比單次劃分的 0.95 更有說服力。from sklearn.model_selection import cross_val_score clf_cv LogisticRegression(multi_classmultinomial, solverlbfgs, C0.1, max_iter1000, random_state42) scores cross_val_score(clf_cv, X_scaled, y, cv5, scoringaccuracy) print(f5折交叉驗(yàn)證: {scores.mean():.4f} ± {scores.std():.4f})cv5表示 5 折scoringaccuracy表示用準(zhǔn)確率評(píng)估。scores.std()是標(biāo)準(zhǔn)差越小說明模型越穩(wěn)定。如果標(biāo)準(zhǔn)差超過 0.05說明數(shù)據(jù)劃分對(duì)結(jié)果影響大報(bào)告里要說明這一點(diǎn)。5.5 最后檢查一遍這些細(xì)節(jié)交作業(yè)之前我一般會(huì)過一遍這個(gè)清單代碼能不能在干凈環(huán)境里跑通把__pycache__刪掉重跑、數(shù)據(jù)文件路徑是不是相對(duì)路徑、有沒有硬編碼的絕對(duì)路徑、圖片有沒有保存到正確目錄、報(bào)告里的數(shù)字和代碼輸出是否一致。這些細(xì)節(jié)看起來瑣碎但每年都有同學(xué)因?yàn)槁窂綄懰涝谧约弘娔X上老師一跑就報(bào)FileNotFoundError直接扣分。我自己的習(xí)慣是在項(xiàng)目根目錄放一個(gè)requirements.txt寫清楚依賴庫(kù)和版本雖然期末大作業(yè)不一定要求但加上去顯得規(guī)范。另外代碼里不要出現(xiàn)print調(diào)試語句殘留交之前全局搜一下print該刪的刪該改成日志的改成日志。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取