實戰(zhàn):從特征工程到模型部署)
簡介這套客戶留存分析與流失預測項目包面向電信、保險等行業(yè)的客戶分析場景也適合人工智能、計算機科學等專業(yè)用于畢業(yè)設計或課程作業(yè)。方案基于生存分析模型刻畫客戶流失隨時間變化的趨勢并計算生命周期價值同時使用隨機森林模型預測流失概率通過Flask Web應用完成交互式部署與結果展示。壓縮包共62個文件容量19.89MB主體包括3個Jupyter Notebook數據探索、生存分析、流失預測建模、1個Python應用入口、1個前端HTML模板以及49張結果可視化圖片如生存曲線、特征重要性、SHAP解釋、部分依賴圖等另有訓練好的模型pkl文件、依賴清單和說明文檔。已有163人學習下載。下載后可按README指引快速復現(xiàn)流程直觀獲取完整代碼、圖表與模型文件便于對照學習客戶生命周期價值計算、特征解釋及模型部署等關鍵環(huán)節(jié)。1. 客戶留存分析與流失預測系統(tǒng)先想清楚拿到zip之后要干什么拿到客戶留存分析與流失預測系統(tǒng).zip的那個下午我以為是解壓就能跑的現(xiàn)成工具結果從理解業(yè)務到真正用它做出判斷花了兩周。這套系統(tǒng)要解決的問題有兩層一層是留存分析——用戶來了之后有多少留到今天有多少在第幾天離開另一層是流失預測——在用戶徹底離開之前用一個概率分圈出高流失風險名單交給運營去觸達。它適合的不是算法團隊而是手里有用戶行為數據、想用低成本方式建一套留存監(jiān)控與預警流程的運營或開發(fā)。下面講的是我在落地這套系統(tǒng)時的完整路徑包括數據清洗、建模、打包交付和最常見的坑。2. 數據清洗與特征工程留存分析不是跑個模型的事2.1 用戶行為數據長什么樣從訂單表到用戶日活表很多團隊的數據現(xiàn)狀是訂單表、注冊表、登錄日志各存各的甚至取數的口徑都要問三個部門。做留存分析之前第一步不是寫模型而是把數據在用戶維度上拉平。常見的做法是以 user_id 為主鍵把訂單表和日志表聚合成一張寬表每一行是一個用戶字段是“最近一次下單距今多少天”、“累計訂單金額”、“最近7天活躍天數”等聚合值。寬表的產出質量決定了后面模型的上限。先別急著造高階特征先用 SQL 或 pandas 跑一遍留存率驗證數據能和業(yè)務直覺對上。比如你定義“第7日留存率”為某天新增用戶中注冊后第7天仍有活躍行為的占比。這里的活躍行為是“登錄”還是“下單”要和業(yè)務方確認口徑不同數字能差出一倍。我一般會先按天算一張留存矩陣確認沒有明顯的周末效應或數據斷檔再繼續(xù)造特征。數據樣例可以這樣理解訂單表有三個字段 user_id、order_id、order_time、amount行為日志表有 user_id、log_time、action。你的目標是把這些變成每用戶一行。如果日志表特別大千萬級以上別在 pandas 里硬做 groupby先在數據庫或數倉里用 SQL 做預聚合只把用戶維度的統(tǒng)計結果取回來。這不是優(yōu)化建議是血淚經驗。2.2 留存分析的三個核心特征RFM、使用頻次與沉默期RFM 是客戶分析里的老框架Recency最近一次消費距今多久、Frequency消費頻次、Monetary消費金額。在流失預測里R 的權重通常最高因為沉默的定義本身就依賴“最近一次活躍距今多久”。F 和 M 則能區(qū)分兩類用戶高頻低客單的用戶和低頻高客單的用戶它們的流失信號完全不一樣前者可能只是最近沒下單后者可能是徹底換供應商了。沉默期怎么定不要拍腦袋。常見做法是統(tǒng)計全體用戶相鄰兩次活躍的間隔天數取90分位數。比如你算出來90%的間隔都小于15天那“15天沒有活躍”就可以作為沉默閾值。這個閾值直接用來打標簽在歷史數據里如果一個用戶最后一次活躍距離預測時點超過了15天就標記為流失。這個標簽定義是后續(xù)所有模型的地基得先把它寫進配置中心而不是散落在腳本里。使用頻次特征比金額特征要穩(wěn)定。我個人的做法是先按自然周統(tǒng)計每周活躍天數再做一個28天滑動窗口的加權和時間越近權重越高。權重可以用指數衰減weight 0.95 ** (days_diff)。寫成代碼并不復雜但收益明顯因為用戶的近期行為模式比累計值更能反映當下的狀態(tài)。注意所有特征都必須基于預測時點之前的數據這個“時點”是最后一道紅線后面避坑章會專門講。2.3 用Python把原始表轉成訓練集完整代碼與參數說明下面這個腳本是聚合訓練集的最小實現(xiàn)。假設你有 users.csv 和 actions.csv目標是生成一個寬表包含 user_id、last_active_days、recent_days、recent_count、total_amount。為了后續(xù)做時間切分代碼里顯式引入 prediction_date。import pandas as pd import numpy as np # 讀數據注意解析事件時間 users pd.read_csv(users.csv, parse_dates[reg_date]) actions pd.read_csv(actions.csv, parse_dates[log_time]) prediction_date 2024-06-01 # 預測時點用字符串方便改 # 特征1最近一次活躍距今天數 last_active ( actions.groupby(user_id)[log_time] .max() .reset_index() .rename(columns{log_time: last_active_time}) ) last_active[last_active_days] ( pd.Timestamp(prediction_date) - last_active[last_active_time] ).dt.days # 特征2/3近30天活躍天數和行為次數只統(tǒng)計預測時點之前的30天 window_start pd.Timestamp(prediction_date) - pd.DateOffset(days30) mask (actions[log_time] window_start) (actions[log_time] pd.Timestamp(prediction_date)) recent actions[mask].groupby(user_id).agg( recent_days(log_time, lambda x: (x.max() - x.min()).days 1), recent_count(log_time, size), ).reset_index() # 特征4累計金額 amount_agg actions.groupby(user_id)[amount].sum().rename(total_amount).reset_index() # 合并成寬表 feat users[[user_id]].merge(last_active[[user_id, last_active_days]], onuser_id, howleft) feat feat.merge(recent, onuser_id, howleft) feat feat.merge(amount_agg, onuser_id, howleft) # 沒有行為記錄的用戶用999標記沉默時間其他缺失填0 feat[last_active_days] feat[last_active_days].fillna(999) feat[recent_days] feat[recent_days].fillna(0) feat[recent_count] feat[recent_count].fillna(0) feat[total_amount] feat[total_amount].fillna(0) print(feat.head())代碼邏輯分成三步先按 groupby 把最近一次活躍時間取出來再拿時間差得到 last_active_days第二步用條件過濾得到近30天行為數據用 agg 聚合第三步把各聚合結果在 user_id 上合并。注意 recent_days 這里用的是近似“首尾活躍時間跨度加一”如果用戶在這個窗口內活躍不連續(xù)它會比真實活躍天數偏大。更精確的做法是 x.dt.date.nunique()但那樣在千萬級數據上會慢很多看你的數據量取舍。參數說明prediction_date 是整套系統(tǒng)的錨點訓練時要把錨點滾動到每個歷史月份用來生成不同月份的樣本。last_active_days 用999填充是因為這些用戶沒有任何活躍記錄他們不是“沉默”而是“從未激活”應當被模型識別為特殊群體。recent_days 和 recent_count 的缺失填0代表觀察窗口內沒有活躍行為這個0是真實事件不是缺失。真實業(yè)務中你可能還要加更多特征注冊天數、平均消費間隔、商品類目偏好、客服反饋次數。我給的原則是先把 RFM 和沉默期做成可運行版本再逐步加特征每加一個就用時間切分驗證 AUC 是否真的提升而不是一味堆特征。這個系統(tǒng)里的特征工程不是一次性的它要能跟著業(yè)務周期持續(xù)迭代。提示千萬不要在造特征時混入預測時點之后的數據比如用“這個用戶后面有沒有下單”來填充近期行為。這是最常見的特征泄漏后面避坑章有一條專門講它。3. 流失預測模型選擇算法與調參的實戰(zhàn)思路3.1 為什么先跑邏輯回歸和XGBoost做基線模型選擇上我見過最常翻車的不是模型不夠強而是上來就調 LightGBM 的黑匣子最后解釋不了業(yè)務方的問題“為什么他分高”。所以建議先跑邏輯回歸它的系數能直接解釋特征方向。比如 last_active_days 的系數是正說明最近一次活躍距今越久流失概率越高符合直覺。邏輯回歸作為基線也能給出一個 AUC如果 XGBoost 比它高不了多少那說明特征工程才是瓶頸。XGBoost 適合表格數據對特征縮放不敏感還能自動處理缺失值。我們用它做主力模型但注意樹的深度、葉子數和正則參數會直接影響過擬合。數據量只有幾千到幾萬條的留存場景太深的樹幾乎必定過擬合。常見初始參數是 max_depth3、learning_rate0.05、n_estimators300然后用早停法選迭代輪數。這里有一個容易被忽略的點流失場景的正樣本往往很少。一個月流失10%用戶聽起來不少但在特征空間里樹模型很容易通過切分把這些樣本撿出來然后在驗證集上表現(xiàn)得很好等上線遇到分布變化就失效。因此基線模型不只是看 AUC還要看它預測出的概率分布是否合理。我常用的辦法是把預測概率分桶看每個桶里的真實流失率是否單調遞增如果不是單調說明模型學到了噪聲或者特征有泄漏。3.2 流失閾值怎么定基于業(yè)務沉默天數而非模型輸出很多系統(tǒng)的預測結果是一堆0到1的概率但業(yè)務方問“那到底誰流失了”你如果回答“大于0.5的流失”這個閾值會讓運營抓狂。正確做法是先定沉默期再根據沉默期生成歷史標簽模型只是學“哪些特征能預測這種沉默行為”。所以模型輸出的 score 其實是“在沉默期定義下該用戶未來7天內進入沉默的概率”而不是絕對的流失概率。閾值的選擇取決于你的運營資源資源多閾值調低多撈人資源少閾值調高只保重點。具體怎么定閾值用驗證集畫出精確率-召回率曲線運營能接受的精確率比如30%就選對應的閾值。這里的精確率意思是“預測流失的人里真的流失了多少”召回率是“真流失的人里被預測到了多少”??蛻袅舸嫦到y(tǒng)的目標通常不是精確率最大化而是在保證一定精確率的前提下提高召回率因為漏掉一個高價值用戶的損失遠大于打擾一個普通用戶。如果每個用戶的貢獻金額已知可以把閾值做成代價敏感形式。比如一個高價值用戶流失一個月?lián)p失1000元一次無效觸達成本5元。那么只有當預計流失概率 × 1000 5 時才值得觸達算出的閾值為0.5%。這個計算比拍腦袋強但前提是你有用戶貢獻金額數據并且愿意維護這組系數。我把這個邏輯寫進配置每次預測后直接輸出“建議觸達”和“可以再等等”兩個名單。3.3 訓練與評估代碼P/R曲線、AUC與代價矩陣下面的代碼展示了用邏輯回歸和 XGBoost 訓練并比較的流程。假設你已經有了 feat 和 labellabel 是1表示該用戶在未來一個周期內沉默。import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from xgboost import XGBClassifier from sklearn.metrics import roc_auc_score, precision_recall_curve, precision_score, recall_score # 準備數據假設 feat 有 user_id 和特征列l(wèi)abel 單獨一列 X feat.drop([user_id], axis1) y feat[label] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 邏輯回歸基線 lr LogisticRegression(max_iter1000, C1.0, class_weightbalanced) lr.fit(X_train, y_train) y_prob_lr lr.predict_proba(X_test)[:, 1] print(LR AUC:, roc_auc_score(y_test, y_prob_lr)) # XGBoost 主力模型 xgb XGBClassifier( max_depth3, learning_rate0.05, n_estimators500, subsample0.8, colsample_bytree0.8, scale_pos_weightsum(y_train 0) / sum(y_train 1), eval_metriclogloss, early_stopping_rounds50, verbosity0, ) xgb.fit(X_train, y_train, eval_set[(X_test, y_test)], verboseFalse) y_prob_xgb xgb.predict_proba(X_test)[:, 1] print(XGB AUC:, roc_auc_score(y_test, y_prob_xgb)) # 根據運營目標選閾值 precision, recall, thresholds precision_recall_curve(y_test, y_prob_xgb) # 找精確率超過30%且召回率最高的閾值 valid [(t, p, r) for t, p, r in zip(thresholds, precision[:-1], recall[:-1]) if p 0.3] if valid: best max(valid, keylambda x: x[2]) print(建議閾值:, best[0], 精確率:, best[1], 召回率:, best[2])參數說明max_depth3 是為了控制模型復雜度當前量級的數據不需要太深。learning_rate0.05 配合 n_estimators500讓模型一步一步走不容易在訓練集上飆升。subsample 和 colsample_bytree 都取0.8相當于每棵樹只用80%的樣本和特征增加隨機性減小方差。scale_pos_weight 寫成正負樣本比例的倒數讓少數類被漏掉時損失更大。early_stopping_rounds50 在驗證集 logloss 不再下降時提前停止避免過擬合。邏輯回歸的 C1.0 是正則強度的倒數越小正則越強。如果特征之間存在共線性比如 recent_count 和 total_amount 高度相關可以調小 C 或先做標準化否則系數的絕對值大但方向不穩(wěn)定。class_weightbalanced 會自動調整正負樣本權重但代價是預測概率會出現(xiàn)整體偏移閾值也需要重新校準所以我不建議在最終交付時用這個參數只把它作為基線檢查用。評估部分AUC 看整體排序能力但真正決定業(yè)務動作的是精確率和召回率的取舍。我給的這個閾值搜索邏輯是“運營只能跟進精確率30%以上的客戶在這個前提下挑召回率最高的點”這是一個可落地的折中。實際你還要看每一檔閾值下會撈到多少人、多少獎勵金額這需要結合客戶分群下一章會講。4. 把系統(tǒng)打包成zip從訓練腳本到可交付的預測工具4.1 目錄結構設計模型文件、配置、增量更新腳本交付不是把幾個 .py 丟給對方就行。一個能跑起來的客戶留存分析與流失預測系統(tǒng)至少要有訓練腳本、預測腳本、配置文件、數據樣例、模型輸出目錄和 README。我通常按下面這樣的目錄組織retention_system/ ├── config.yaml ├── train.py ├── predict.py ├── data/ │ ├── users.csv │ └── actions.csv ├── model/ │ └── xgb_model.json └── README.md配置文件里寫數據路徑、預測時點、沉默閾值、模型參數、特征列表。這樣換一批數據跑的時候不用改代碼。訓練腳本讀配置產出的模型放在 model/ 下。預測腳本讀配置和模型輸入當天的特征表輸出帶 churn_score 的用戶名單。config.yaml 的樣式我一般這樣寫data: users_path: data/users.csv actions_path: data/actions.csv prediction: date: 2024-06-01 silent_days: 15 model: type: xgboost params: max_depth: 3 learning_rate: 0.05 n_estimators: 500 subsample: 0.8 colsample_bytree: 0.8 output: score_path: output/churn_scores.csv為什么用 YAML 而不是命令行參數因為運營同事要改的是數據路徑和閾值不該改代碼。把參數從代碼里剝離出來這個系統(tǒng)才談得上可維護。README 里除了安裝命令還要寫清楚 Python 版本和依賴我建議用 requirements.txt并告訴對方用虛擬環(huán)境。別假設對方會用 conda直接把命令寫出來。4.2 用zipfile和命令行實現(xiàn)一鍵打包把整個文件夾打成 zip 是交付前最后一步也是需求量極高的動作。在 Linux 上最省事的命令是zip -r retention_system.zip retention_system/如果需要排除體積大的緩存目錄比如 data/raw/用zip -r retention_system.zip retention_system/ -x retention_system/data/raw/* -x *.pyc-x 參數接的是通配符排除規(guī)則*.pyc 能去掉 Python 緩存文件這個一定要加否則對方解壓后會看到一堆pycache顯得很不專業(yè)。如果輸出包要帶日期可以這樣命名retention_system_20250611.zip方便后續(xù)回溯。如果你希望打包過程可重復我建議寫成腳本尤其是當模型文件越來越多的時候import zipfile import os def zip_dir(dir_path, zip_path, excludes): with zipfile.ZipFile(zip_path, w, zipfile.ZIP_DEFLATED) as zf: for root, dirs, files in os.walk(dir_path): for f in files: full os.path.join(root, f) rel os.path.relpath(full, dir_path) if any(ex in rel for ex in excludes): continue zf.write(full, rel) if __name__ __main__: zip_dir( retention_system, retention_system_20250611.zip, excludes[__pycache__, .pyc, /data/raw/] )這段腳本用 os.walk 遍歷目錄把文件寫入 zipZIP_DEFLATED 表示壓縮。excludes 里的子串如果出現(xiàn)在相對路徑中就跳過。這樣每次交付跑一遍python package.py就能得到新包不會漏文件。打包之前先在干凈的臨時目錄里跑一遍 train.py確認數據路徑不是硬編碼的絕對路徑我見過太多次本地能跑、zip 包解壓到別處就報 FileNotFoundError 的情況原因就是代碼里寫了C:/Users/xxx/data/actions.csv。路徑要寫成相對于配置文件的相對路徑或者運行時自動獲取腳本所在目錄。4.3 交付后如何運行解壓即用的最小命令與驗證交付后對方第一步是解壓。如果對方拿到的包是加密的先要移除 zip 密碼通常做法是問打包人要密碼別去用什么暴力破解工具那是給自己找坑。解壓后按 README 跑python -m venv .venv source .venv/bin/activate pip install -r requirements.txt python train.py --config config.yaml python predict.py --config config.yaml --score output/churn_scores.csv預測腳本里要加一個 --score 參數讓結果輸出到指定路徑。第一次運行完驗證三步訓練日志里有沒有 AUC 指標預測輸出里 churn_score 是不是0到1之間的分布隨機抽一個明顯很久沒活躍的用戶看他的 score 是否偏高。如果這幾步都正常系統(tǒng)才算真正跑通。我還會給一個最小驗證清單像這樣檢查項命令/操作期望結果訓練能跑通python train.py --config config.yaml輸出 AUC 和模型文件預測能跑通python predict.py --score output/churn_scores.csv輸出 CSV 且不少于100行概率分布合理查看 score 列大部分在0.1~0.5之間少量接近1沉默用戶命中抽查 15 天未活躍的用戶score 排名靠前跨平臺注意點Windows 上壓縮的 zip 在 Linux 解壓后中文文件名容易亂碼所以項目文件和 README 最好都用英文命名內容可以中文。另外 zip 包里的換行符也會在跨平臺時出問題代碼文件盡量用 LF 換行這個可以在打包前用 dos2unix 統(tǒng)一一下。如果對方要在 Windows 上跑記得把 .venv 相關命令換成venv\Scripts\activateREADME 寫兩個版本。5. 客戶流失預測落地避坑這5個問題最容易翻車5.1 現(xiàn)象訓練時AUC很高上線后預測全偏向不流失這是一個典型的“離線很爽、線上翻車”的例子。我見過有人把 AUC 做到 0.98結果第二輪預測出來的高分用戶全是些已經消失很久的老用戶對運營毫無指導意義。原因是特征泄漏造特征時用了未來數據離線驗證時又用隨機切分而不是時間切分模型在訓練集里見過“答案”了。解決方法是嚴格按時間切分用預測時點之前的數據訓練之后的數據驗證特征工程里禁用未來函數。檢查方法也很簡單把預測時點換個時間窗口看 AUC 是否基本穩(wěn)定如果大幅下跌先查特征里有沒有用到標簽信息。5.2 現(xiàn)象新用戶沒有歷史數據特征全部為空新注冊用戶沒有近30天活躍記錄也沒有累計金額模型會給它們一個不高不低的分數。但運營真正關心的是“剛進來還沒留存的用戶”你如果只用一個模型覆蓋所有用戶新用戶永遠被忽略。原因是沒有區(qū)分用戶生命周期階段。解決方法是先給每個用戶打一個 has_history 標志注冊不足30天的用戶單獨建?;蛘哂美鋯犹卣鞅热缱郧馈⒆詴r間、首日是否完成關鍵行為。在填充缺失時不要一律用0因為0可能表示“沒有行為”也可能表示“不適用”建議同時保留一個“數據不足”標記列。5.3 現(xiàn)象zip包換臺機器跑路徑全是反斜杠錯誤這類問題幾乎每個交付的人都踩過。代碼里寫了 Windows 的絕對路徑C:\Users\xxx\data\actions.csv打包到 Linux 服務器或 Mac 上跑一啟動就報文件不存在。原因很簡單路徑分隔符硬編碼。解決方法是統(tǒng)一用 pathlib.Path 拼接路徑配置里的路徑全部寫成相對路徑然后在代碼入口通過Path(__file__).parent定位項目根目錄。打包前我還會用grep -r C:掃一遍代碼看到絕對路徑就先改掉再壓縮省得對方解壓后拿著日志截圖來找你。5.4 現(xiàn)象流失標簽定義錯了模型學到的其實是“定義本身”這是最隱蔽的坑。我見過有人把“近30天無活躍”當作流失標簽但特征里又包含“近30天活躍次數”。標簽和特征直接重疊模型根本不是在預測而是在把特征里的0映射成標簽里的1AUC自然很高上線后卻一無是處。解決方法是標簽和特征之間必須有嚴格的時間間隔。用 t-1 月的特征預測 t 月是否沉默特征窗口和標簽窗口不交叉。我在代碼里把 prediction_date 作為硬邊界生成樣本時確保特征截止日比標簽開始日早至少一個沉默期。5.5 現(xiàn)象模型每周要重訓數據量太大內存直接崩客戶留存系統(tǒng)的數據是不斷累積的幾周后全量行為日志可能過億行。直接用 pandas 讀進來做 groupby內存很容易撐爆。原因不是代碼寫錯而是聚合方式不適合增量場景。解決方法是把特征聚合下沉到數據庫或數倉用 SQL 先算好每周的最新統(tǒng)計量Python 只負責讀寬表。如果一定要在 Python 里做可以按用戶分片用 Dask 或 Polars 代替 pandas另外把用戶ID、渠道ID這些低基數字段轉成 category 類型能省不少內存。更聰明的做法是設計成增量特征表每周只更新近30天的窗口統(tǒng)計歷史累計值用上次結果加本周增量而不是全量重算。6. 進階用生存分析和流失概率做分群運營6.1 為什么不只預測“會不會流失”還要預測“還有多久”流失預測給的是一個概率但運營接到名單后會問下一句“他還能留多久”如果預測發(fā)現(xiàn)一個用戶本月會流失觸達動作可以更緊一點如果預測他三個月后才流失那可能是正常的使用周期不需要打擾。生存分析解決的正是“還要多久”的問題。它不把流失當成一個二分類標簽而是把每個用戶看成一個帶時間的事件樣本輸出的是“存活到下一個周期”的概率曲線。6.2 用lifelines做Cox回歸的落地代碼我常用 lifelines 庫跑 Cox 比例風險模型。它接受兩條信息用戶觀察時長 T以及是否在觀察期內流失 E。下面是最小用法from lifelines import CoxPHFitter df_surv feat.copy() df_surv[T] df_surv[observe_days] # 從第一次活躍到觀察結束的天數 df_surv[E] df_surv[label] # 1流失0未流失 cph CoxPHFitter() cph.fit(df_surv[[T, E, last_active_days, recent_count, total_amount]], duration_colT, event_colE) cph.print_summary()Cox 模型的參數解讀比樹模型直觀如果 last_active_days 的風險比大于1說明最近一次活躍距今越久流失風險越高。有了存活曲線你還可以對每個用戶預測未來30天的存活概率把概率和 XGBoost 的流失分結合做一個二維分群高流失分且低存活概率的優(yōu)先觸達高流失分但存活概率還高的可能是需要長期培育的用戶不用急著打擾。6.3 從預測到行動生命周期積分卡與觸達時機最后這套系統(tǒng)真正產生價值的不是模型文件而是那張輸出名單。我會把預測結果做成一張積分卡流失風險分、預計剩余活躍天數、用戶貢獻等級、最近觸達時間。運營只需要關注“預計剩余活躍天數小于30天”的用戶再結合風險分決定用短信還是電話。我第一次上線時只顧著把 AUC 調高忽略了閾值和行動策略結果運營拿著一張全是中低分用戶的名單不知道干嘛。后來我把閾值和觸達成本寫進配置每個月復盤一次預測命中率這個系統(tǒng)才算真正轉起來了。希望幫到你。本文還有配套的精品資源點擊獲取