者走入AI開發(fā)的第一課)
如果你寫過幾年 Web 項(xiàng)目最近開始關(guān)注 AI 開發(fā)大概率會(huì)有一種共同的感覺網(wǎng)上關(guān)于 AI 的內(nèi)容要么一頭扎進(jìn)數(shù)學(xué)公式看到矩陣和偏導(dǎo)數(shù)就想退出要么直接教調(diào)用大模型 API幾行代碼跑通但關(guān)掉編輯器之后你依然不知道“模型”到底是怎么工作的。我給你的建議是別急著啃深度學(xué)習(xí)先把線性回歸學(xué)透。這個(gè)算法簡單到可以用 Excel 實(shí)現(xiàn)但它的價(jià)值恰恰在于——它是機(jī)器學(xué)習(xí)項(xiàng)目的最小完整閉環(huán)。數(shù)據(jù)準(zhǔn)備、模型訓(xùn)練、效果評(píng)估、預(yù)測推斷所有環(huán)節(jié)它都有而且你完全能親手把每個(gè)環(huán)節(jié)拆開看。這比“一行代碼出結(jié)果”的黑盒方案能給你更扎實(shí)的 AI 開發(fā)基礎(chǔ)。這篇文章會(huì)用 Web 開發(fā)者熟悉的視角把線性回歸的原理和實(shí)現(xiàn)完整講一遍。讀完你能做到三件事看懂一個(gè)機(jī)器學(xué)習(xí)項(xiàng)目的基本流程用 Python 從零實(shí)現(xiàn)線性回歸用 scikit-learn 完成工程化的訓(xùn)練、評(píng)估與預(yù)測。1. 為什么線性回歸是 Web 開發(fā)者進(jìn)入 AI 開發(fā)的第一課1.1 Web 開發(fā)與 AI 開發(fā)差在哪里做過 Web 開發(fā)的人都知道一個(gè)典型后端接口的寫法大致是接收請(qǐng)求參數(shù)查庫套業(yè)務(wù)規(guī)則返回結(jié)果。你寫的每一行判斷邏輯都是你大腦里規(guī)則的編碼。而 AI 開發(fā)做的事情換了一個(gè)思路你把一堆“輸入 正確答案”扔給算法讓它自己找規(guī)律生成一個(gè)可以預(yù)測的模型。打個(gè)比方。Web 開發(fā)像你手寫一份菜譜每個(gè)步驟都是人工確定的機(jī)器學(xué)習(xí)則是你喂給系統(tǒng)大量數(shù)據(jù)和結(jié)果讓它自己總結(jié)出“什么因素會(huì)導(dǎo)致什么結(jié)果”。所以從 Web 開發(fā)轉(zhuǎn) AI 開發(fā)第一個(gè)要轉(zhuǎn)變的思維方式就是從“寫規(guī)則”到“學(xué)規(guī)律”。1.2 線性回歸是“最小可運(yùn)行模型”線性回歸的數(shù)學(xué)模型就是一條直線y w0 w1 * x。w0 是截距w1 是權(quán)重模型要做的事就是從數(shù)據(jù)里學(xué)出合適的 w0 和 w1。不要因?yàn)楣胶唵尉偷凸浪K瑱C(jī)器學(xué)習(xí)項(xiàng)目的全部核心問題數(shù)據(jù)怎么準(zhǔn)備特征怎么表示怎么定義“預(yù)測得準(zhǔn)”——這需要損失函數(shù)怎么自動(dòng)調(diào)整參數(shù)——這需要優(yōu)化算法怎么知道模型有沒有泛化能力——這需要評(píng)估方法。這些問題在神經(jīng)網(wǎng)絡(luò)、大模型預(yù)訓(xùn)練里同樣存在。你把線性回歸吃透后面再學(xué)邏輯回歸、決策樹、神經(jīng)網(wǎng)絡(luò)會(huì)發(fā)現(xiàn)很多概念都是老朋友。對(duì) Web 開發(fā)者來說線性回歸還有一個(gè)額外優(yōu)勢(shì)它的每一步都可以直觀可視化。訓(xùn)練過程就是一根直線慢慢靠近數(shù)據(jù)點(diǎn)的過程你親眼看到它就理解了機(jī)器學(xué)習(xí)。要補(bǔ)充的是現(xiàn)在的 AI 應(yīng)用開發(fā)大量工作是把大模型能力和具體業(yè)務(wù)場景結(jié)合本質(zhì)上是在做“業(yè)務(wù)編排 模型調(diào)用”。如果你完全不懂底層模型就不知道模型在什么情況下會(huì)失敗出了問題也只能盲目調(diào)提示詞。線性回歸訓(xùn)練出來的這套“定義指標(biāo)、拆分?jǐn)?shù)據(jù)、訓(xùn)練、評(píng)估”的方法論在調(diào)用大模型時(shí)同樣成立。2. 線性回歸的核心概念與數(shù)學(xué)原理2.1 特征、標(biāo)簽和樣本用 Web 開發(fā)者最熟悉的方式理解這幾個(gè)概念樣本sample一條數(shù)據(jù)記錄類似數(shù)據(jù)庫里的一行記錄或者一次 API 請(qǐng)求。特征feature輸入變量類似接口入?yún)?。在一元線性回歸里只有一個(gè)特征。標(biāo)簽label要預(yù)測的目標(biāo)值類似接口返回的 result 字段。舉個(gè)例子要預(yù)測“學(xué)習(xí)時(shí)長 → 考試成績”學(xué)習(xí)時(shí)長是特征考試成績是標(biāo)簽一條條學(xué)生的記錄就是樣本。2.2 假設(shè)函數(shù)一元線性回歸的假設(shè)函數(shù)是y_pred w0 w1 * xw0 是截距intercept表示 x 為 0 時(shí)的預(yù)測值w1 是斜率coefficient表示 x 每增加 1 個(gè)單位預(yù)測值變化多少。如果特征不止一個(gè)就變成多元線性回歸y_pred w0 w1 * x1 w2 * x2 ...模型訓(xùn)練的目標(biāo)就是找到一組 w0、w1讓預(yù)測值 y_pred 盡量接近真實(shí)標(biāo)簽 y。2.3 損失函數(shù)用 MSE 量化“錯(cuò)多少”怎么衡量“接近”最常用的是均方誤差MSEMean Squared ErrorMSE (1 / n) * Σ(y_i - y_pred_i)2其中 n 是樣本數(shù)y_i 是真實(shí)標(biāo)簽y_pred_i 是預(yù)測值。為什么用平方而不直接用差值有兩個(gè)原因避免正負(fù)誤差相互抵消。如果直接用 y_pred - y正誤差和負(fù)誤差加起來可能為 0掩蓋真實(shí)誤差放大較大誤差讓模型更關(guān)注偏離嚴(yán)重的樣本。差 10 分和差 1 分平方后是 100 和 1代價(jià)差距很明顯。MSE 越小說明模型預(yù)測越準(zhǔn)。2.4 求解參數(shù)的兩條路線有了損失函數(shù)下一步是找一組讓損失最小的參數(shù)。常見兩條路線正規(guī)方程最小二乘法直接算解析解。公式為w (X?X)?1X?y。適合特征少、數(shù)據(jù)量小的場景。梯度下降從任意初始參數(shù)出發(fā)計(jì)算損失函數(shù)對(duì)每個(gè)參數(shù)的梯度沿著梯度反方向更新參數(shù)。適合數(shù)據(jù)量大、特征多的場景也是神經(jīng)網(wǎng)絡(luò)訓(xùn)練的基礎(chǔ)。梯度下降的參數(shù)更新公式是w w - learning_rate * gradientlearning_rate 是學(xué)習(xí)率控制每次走多大步。它類似 Web 開發(fā)里的“提交粒度”步子太大容易跳過最優(yōu)解步子太小訓(xùn)練太慢。為了幫你快速建立概念映射下面這個(gè)表可以收藏概念Web 開發(fā)類比機(jī)器學(xué)習(xí)定義樣本數(shù)據(jù)庫中的一行記錄一條特征與標(biāo)簽的組合特征接口入?yún)uery 參數(shù)輸入變量 x標(biāo)簽接口返回值 result目標(biāo)值 y損失函數(shù)線上錯(cuò)誤率、報(bào)錯(cuò)率預(yù)測值與真實(shí)值的偏差程度模型訓(xùn)練編寫并調(diào)試業(yè)務(wù)邏輯學(xué)習(xí)參數(shù) w0、w1模型評(píng)估測試環(huán)境驗(yàn)收、灰度驗(yàn)證在測試集上驗(yàn)證模型效果3. 環(huán)境準(zhǔn)備與開發(fā)工具鏈做機(jī)器學(xué)習(xí)實(shí)驗(yàn)最常用的開發(fā)環(huán)境是 Python Jupyter Notebook。你也可以直接在 VS Code 里用 Python 文件運(yùn)行本文的代碼兩種方式都支持。建議使用 Python 3.9 以上版本具體版本以你本機(jī)環(huán)境為準(zhǔn)。建議先創(chuàng)建虛擬環(huán)境避免污染全局環(huán)境python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate然后安裝四個(gè)基礎(chǔ)庫pip install numpy scikit-learn matplotlib jupyter安裝完成后驗(yàn)證一下環(huán)境python -c import numpy, sklearn, matplotlib; print(環(huán)境OK)各庫的作用庫用途numpy數(shù)值計(jì)算處理矩陣和向量scikit-learn機(jī)器學(xué)習(xí)算法庫線性回歸模型在這里matplotlib繪圖可視化數(shù)據(jù)和模型jupyter交互式 Notebook方便邊寫邊看結(jié)果如果后面畫圖時(shí)中文亂碼需要再設(shè)置 matplotlib 中文字體第 6 章會(huì)給出配置代碼。4. 從零實(shí)現(xiàn)線性回歸這一章先不引入現(xiàn)成的機(jī)器學(xué)習(xí)庫只用 numpy 手寫核心邏輯。目的是拆開模型訓(xùn)練的黑盒讓你看清每一步發(fā)生了什么。4.1 準(zhǔn)備一份簡單演示數(shù)據(jù)先造一份“學(xué)習(xí)時(shí)長 → 考試成績”的演示數(shù)據(jù)特征 x 從 1 到 8標(biāo)簽 y 整體呈線性增長帶一點(diǎn)波動(dòng)。import numpy as np # 特征學(xué)習(xí)時(shí)長小時(shí) X np.array([1, 2, 3, 4, 5, 6, 7, 8], dtypefloat).reshape(-1, 1) # 標(biāo)簽考試成績0-100 y np.array([50, 55, 58, 65, 70, 75, 78, 85], dtypefloat) print(X.shape, y.shape)輸出結(jié)果是(8, 1)和(8,)。X 是二維列向量y 是一維數(shù)組。這是 scikit-learn 標(biāo)準(zhǔn)輸入要求特征是二維矩陣標(biāo)簽是一維數(shù)組。這個(gè)習(xí)慣要盡早養(yǎng)成。4.2 用正規(guī)方程直接求解為了能同時(shí)算出截距 w0需要在 X 左邊拼接一列 1# 在 X 左側(cè)拼接一列 1對(duì)應(yīng)截距項(xiàng) w0 X_b np.c_[np.ones((X.shape[0], 1)), X] # 正規(guī)方程w (X^T X)^(-1) X^T y w np.linalg.inv(X_b.T X_b) X_b.T y print(截距 w0:, w[0]) print(斜率 w1:, w[1])這段代碼的要點(diǎn)第一列全 1 的含義是當(dāng) x 0 時(shí)預(yù)測值 y_pred w0正好對(duì)應(yīng)截距。np.c_是按列拼接矩陣。是矩陣乘法運(yùn)算符。np.linalg.inv是求矩陣的逆。運(yùn)行這段代碼會(huì)得到類似 w0 ≈ 44.7、w1 ≈ 4.95 的結(jié)果。也就是說最佳擬合線大約是y 44.7 4.95 * x用業(yè)務(wù)語言翻譯每多學(xué)一小時(shí)成績大約提高 5 分。4.3 用梯度下降模擬“學(xué)習(xí)”過程正規(guī)方程一步出結(jié)果但數(shù)據(jù)量大了之后矩陣求逆會(huì)變得非常慢。梯度下降是更通用、也更接近神經(jīng)網(wǎng)絡(luò)訓(xùn)練方式的優(yōu)化方法。import numpy as np X np.array([1, 2, 3, 4, 5, 6, 7, 8], dtypefloat).reshape(-1, 1) y np.array([50, 55, 58, 65, 70, 75, 78, 85], dtypefloat).reshape(-1, 1) X_b np.c_[np.ones((X.shape[0], 1)), X] w np.zeros((2, 1)) learning_rate 0.001 n_iterations 5000 m len(X_b) for _ in range(n_iterations): y_pred X_b w error y_pred - y gradient (2 / m) * (X_b.T error) w - learning_rate * gradient print(截距 w0:, w[0][0]) print(斜率 w1:, w[1][0])每一步在做什么y_pred X_b w用當(dāng)前參數(shù)做一次預(yù)測error y_pred - y計(jì)算預(yù)測值和真實(shí)值的差距gradient (2 / m) * (X_b.T error)計(jì)算損失函數(shù)對(duì) w0 和 w1 的梯度w - learning_rate * gradient參數(shù)往損失減小的方向走一小步。運(yùn)行輸出和正規(guī)方程的結(jié)果應(yīng)該非常接近。這意味著兩種方法都在做同一件事找到損失函數(shù)的最小值點(diǎn)。這里有一個(gè)新手很容易踩的坑y 必須 reshape 成列向量(8,1)才能和X_b w的結(jié)果直接相減。如果發(fā)現(xiàn)矩陣乘法報(bào)維度錯(cuò)誤第一反應(yīng)就是打印各數(shù)組的 shape。4.4 兩種解法怎么選對(duì)比項(xiàng)正規(guī)方程梯度下降計(jì)算方式矩陣求逆一次性求解析解多次迭代逼近最優(yōu)解數(shù)據(jù)量大計(jì)算慢內(nèi)存占用高按批次取數(shù)據(jù)適用性好特征多不適合求逆代價(jià)高相對(duì)推薦理解難度數(shù)學(xué)上直接代碼簡單需要理解導(dǎo)數(shù)和學(xué)習(xí)率神經(jīng)網(wǎng)絡(luò)不適用是基礎(chǔ)訓(xùn)練方法結(jié)論很簡單小數(shù)據(jù)、少特征用正規(guī)方程方便大規(guī)模數(shù)據(jù)和復(fù)雜模型梯度下降才是通用方案。5. 使用 scikit-learn 實(shí)現(xiàn)線性回歸從零實(shí)現(xiàn)是為了讓你看懂原理。工程里直接手寫就不合理了。scikit-learn 提供封裝好的線性回歸模型幾行代碼就能完成訓(xùn)練和預(yù)測。5.1 構(gòu)建數(shù)據(jù)并劃分訓(xùn)練集、測試集為了更好演示訓(xùn)練和測試劃分這次用 10 個(gè)樣本import numpy as np from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error, r2_score # 準(zhǔn)備數(shù)據(jù) X np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtypefloat).reshape(-1,