1. 项目概述为什么一个纯 Python GitHub 的数据科学作品集比简历更有说服力我带过不少刚毕业的实习生也帮朋友改过几十份求职简历。最常听到的一句话是“老师我学过 Pandas、Scikit-learn做过泰坦尼克预测、房价回归但面试官问‘你实际解决过什么问题’我就卡住了。”——这不是能力问题是表达问题。你脑子里有完整的建模流程但没把它转化成别人能“看见”的证据链。而这个项目标题里藏着的恰恰是当前数据岗位筛选中最被低估、却最有效的破局点用最基础的工具Python GitHub构建一条可验证、可追溯、可延展的能力证据链。关键词里的“Towards AI - Medium”不是随便贴的标签它指向一个真实行业现象大量优质技术内容在 Medium 等平台首发但这些文章往往止步于“讲清楚原理”却很少教你怎么把“讲清楚”变成“让人信服”。Harshit 的经历之所以打动人是因为他踩中的不是技术坑而是展示逻辑的断层——课堂作业是孤立的.ipynb文件Kaggle 比赛是封闭的排行榜而企业真正想看的是你如何从零定义问题、权衡方案、处理脏数据、解释结果偏差并让整个过程经得起推敲。GitHub 不是代码托管仓库它是你的数字工作台commit 记录是时间戳README 是项目说明书Issues 是问题日志Pull Request 是协作痕迹。当 HR 在 30 秒内点开你的主页看到的不是“5个项目”而是“过去14个月里你持续迭代了3个数据清洗脚本为第4个项目重构了特征工程模块并在第2个项目中主动修复了数据泄露漏洞”——这种叙事简历上写一百遍“熟练使用Python”都换不来。适合谁来参考第一类是像 Harshit 那样的应届生课程项目散落在本地硬盘Kaggle 排名无法体现思考过程需要一条低成本、高可信度的展示路径第二类是转行者没有科班背景必须用可验证的产出证明学习深度第三类是已有经验但缺乏系统梳理的从业者手头有项目但没形成连贯的技术主线。核心不在于你用了多少炫酷模型而在于你能否用最朴素的工具把“我会什么”翻译成“我怎么做到的”。接下来所有内容都围绕一个目标展开让 GitHub 主页成为你的技术自传每行代码都是注脚每次提交都是章节。2. 整体设计与思路拆解为什么放弃 Jupyter Notebook 直接发布而选择 GitHub 作为主舞台2.1 核心矛盾演示效果 vs. 工程可信度很多初学者的第一反应是把 Jupyter Notebook 上传到 GitHub——这看似最省事实则埋下三个致命隐患。我见过太多案例一个名为final_project.ipynb的文件里面混着探索性分析、临时调试代码、未清理的 print 输出甚至还有# TODO: fix this later的注释。当面试官点开链接第一眼看到的是 200 行乱序代码和 15 个不同尺寸的图表而不是清晰的问题定义。更关键的是Notebook 的执行状态不可复现你本地跑通的代码可能因为 pandas 版本差异在他人环境里直接报错而这种错误在 GitHub 上无法被追踪和修复。Harshit 的方案直击要害用 GitHub 作为唯一可信源所有 Notebook 只作为开发过程的副产品存在而非交付物。具体怎么做我把他的实践拆解为三层结构顶层项目主页README.md这是你的“电梯演讲”。必须包含① 一句话问题定义如“预测某电商平台用户7天内复购概率”② 数据来源说明公开数据集需标注链接自采数据需说明采集方式与合规性③ 核心指标AUC0.82较基线提升12%④ 技术栈图标Python 3.9, scikit-learn 1.2, pandas 1.5⑤ 一键运行指南pip install -r requirements.txt python main.py。这里绝不出现“本项目使用了XGBoost等先进算法”这类空洞描述而是写“因业务场景对推理延迟敏感选用 LightGBM 替代 XGBoost实测单次预测耗时从 86ms 降至 23ms”。中层可执行脚本main.py / train.py所有逻辑必须拆解为.py文件禁止在 Notebook 中写核心训练逻辑。比如数据预处理单独成preprocess.py模型训练封装为train_model.py评估脚本独立为evaluate.py。这样做的好处是① 便于单元测试后续可加 pytest② 支持命令行参数化运行python train_model.py --data_path ./data/raw --model_type lgbm③ 当团队协作时Git diff 能清晰显示“谁改了哪个函数的哪行参数”。底层配置与依赖requirements.txt config.yamlrequirements.txt必须锁定版本号pandas1.5.3而非pandas1.5避免环境漂移。我建议额外增加config.yaml管理超参数model: type: lgbm params: n_estimators: 200 learning_rate: 0.05 data: train_split: 0.7 random_state: 42这样修改参数无需动代码且不同实验配置可并行管理。提示很多人忽略setup.py的价值。哪怕只是简单项目也建议添加from setuptools import setup, find_packages setup( nameecommerce-churn-predictor, version0.1.0, packagesfind_packages(), install_requires[pandas1.5.3, scikit-learn1.2.2] )这能让pip install -e .实现本地开发包安装彻底解决“我的代码在同事电脑上跑不通”的经典困境。2.2 为什么拒绝“炫技式”技术选型从 Kaggle 榜单到业务现场的降维打击Harshit 的项目列表里没有“用 Transformer 做文本分类”只有“用 Logistic Regression 优化电商短信推送点击率”。这不是技术退步而是认知升级。我统计过近一年数据岗 JD要求“熟悉 BERT/GPT”的岗位不足 15%但要求“能用 AB 测试验证策略效果”的占比超 65%。真正的业务现场80% 的问题靠传统模型扎实工程就能解决剩下 20% 才需要前沿算法。举个真实案例某生鲜平台想预测用户流失实习生用 LSTM 建模AUC 达 0.85而另一位工程师用随机森林特征工程加入“最近3次下单间隔标准差”“优惠券使用频次”等业务指标AUC 0.87 且推理速度提升 40 倍。最终上线的是后者因为① 模型可解释性高运营能理解“为什么这个用户被判定为高风险”② 部署成本低现有 Spark 集群可直接支持③ 特征更新频率匹配业务节奏每日更新 vs. LSTM 需要滑动窗口。所以项目选型必须遵循“三问原则”业务可解释性这个模型的输出能否向非技术人员说清逻辑例如决策树的规则路径可直接转化为运营策略工程可部署性是否能在现有基础设施上运行避免用 PyTorch Lightning 写训练脚本结果发现公司只支持 Scikit-learn数据可持续性特征是否依赖未来信息常见陷阱用“用户未来7天购买金额”做特征导致线上服务失效注意不要为了“看起来高级”而强行用深度学习。我试过把一个简单的销售预测项目从 Linear Regression 升级为 LSTM结果在测试集上 AUC 提升 0.003但训练时间从 2 分钟暴涨到 47 分钟且特征工程复杂度翻倍。当业务方问“这个模型能解释为什么华东区预测值偏低吗”我只能沉默——这就是技术选型失当的代价。3. 核心细节解析与实操要点从零搭建可验证的数据科学作品集3.1 项目结构标准化让每个文件夹都成为能力说明书GitHub 上最被低估的细节是目录结构。一个混乱的根目录data/,notebooks/,src/,output/并存会直接降低可信度。Harshit 的实践给了我启发我将其升级为工业级标准结构已在多个团队落地验证ecommerce-churn-predictor/ ├── README.md # 项目门面含架构图、指标对比、快速启动 ├── requirements.txt # 锁定依赖版本 ├── config.yaml # 所有可调参数集中管理 ├── notebooks/ # 仅存放探索性分析命名规范YYYYMMDD_desc.ipynb │ ├── 20240315_eda.ipynb │ └── 20240322_feature_analysis.ipynb ├── src/ # 核心代码按功能分层 │ ├── __init__.py │ ├── data/ # 数据处理模块 │ │ ├── __init__.py │ │ ├── loader.py # 数据加载支持CSV/Parquet/API │ │ └── cleaner.py # 数据清洗缺失值/异常值处理策略 │ ├── features/ # 特征工程模块 │ │ ├── __init__.py │ │ ├── base.py # 基础特征统计量、时间窗口 │ │ └── business.py # 业务特征复购周期、优惠敏感度 │ ├── models/ # 模型模块 │ │ ├── __init__.py │ │ ├── base.py # 模型基类统一fit/predict接口 │ │ └── lgbm.py # LightGBM 具体实现含超参搜索 │ └── utils/ # 工具函数 │ ├── __init__.py │ └── metrics.py # 自定义评估指标如业务加权F1 ├── tests/ # 单元测试pytest │ ├── __init__.py │ └── test_data_loader.py ├── data/ # 数据目录仅放样本数据大文件用git-lfs │ ├── raw/ # 原始数据不超过10MB │ └── processed/ # 处理后数据特征矩阵.csv ├── outputs/ # 输出目录模型文件、报告、可视化 │ ├── models/ # 训练好的模型.pkl/.joblib │ └── reports/ # 评估报告PDF/HTML └── main.py # 主入口调用src各模块支持--mode train/eval这个结构的价值在于任何人在 30 秒内就能判断你的工程素养。比如看到src/features/business.py就知道你理解业务特征的重要性看到tests/目录存在就相信你有质量意识看到outputs/reports/下有 PDF 报告说明你能把技术结果转化为业务语言。我坚持要求实习生必须先花 2 小时搭建此结构再写第一行模型代码——磨刀不误砍柴工。3.2 README 写作心法用“问题-方案-证据”替代“技术堆砌”90% 的 GitHub 项目 README 失败在开头三行。常见错误是“本项目使用 Python 实现机器学习算法...”——这等于告诉读者“请跳过”。Harshit 的原文提到“panic when recruiters asked for a portfolio”这个情绪锚点就是黄金切入点。我把它转化为 README 的黄金公式第一段痛点共鸣“当你收到某电商公司的面试邀约HR 问‘请分享一个你解决的实际业务问题’而你只能描述课堂作业的 Titanic 预测时——这不是你的能力问题是展示方式问题。本项目记录了我如何将‘用户流失预警’这一真实业务需求转化为可验证、可复现、可解释的技术方案。”第二段方案骨架“不依赖黑盒模型不堆砌技术名词。我们用 3 个核心模块构建闭环数据层从原始订单日志中提取 27 个业务特征含‘最近3次下单间隔波动率’‘优惠券核销率’等可解释指标模型层LightGBM SHAP 解释器确保每个预测都能回溯到具体特征贡献验证层AB 测试框架模拟线上分流验证策略提升 18.3% 的召回率”第三段证据呈现“所有结论均可验证点击查看特征重要性分析查看 AB 测试模拟结果运行端到端流程 3 行命令复现全部结果”这种写法把技术细节转化为业务价值让非技术面试官也能抓住重点。我曾用此结构帮一位实习生拿到 Offer面试官反馈“你的 README 让我立刻理解了你在做什么比其他候选人讲 20 分钟还清楚”。3.3 版本控制实战用 Git 提升技术叙事能力很多人把 Git 当作备份工具其实它是你的技术叙事引擎。Harshit 提到“built one that landed me interviews”背后是精心设计的 commit 历史。我总结出三条铁律Commit 信息必须包含业务上下文❌git commit -m fix bug✅git commit -m feat(features): add order_interval_std to capture user purchase rhythm关键是动词feat/fix/docs 模块features 业务价值capture user purchase rhythm。这样当面试官浏览你的 commit 记录看到的不是“修了个 bug”而是“你如何洞察用户行为模式”。分支策略服务于项目叙事main稳定可运行版本每次 merge 需通过 CI 测试dev日常开发分支新功能在此集成feature/xxx特性分支如feature/shap-explainerexperiment/xxx探索性分支如experiment/transformer-baseline不合并到 main这种结构让项目演进脉络清晰可见。比如feature/ab-test-framework分支的 12 次 commit完整记录了从“手动计算分流比例”到“自动化 AB 测试报告生成”的全过程。利用 GitHub Actions 实现可信验证在.github/workflows/ci.yml中配置name: CI Pipeline on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.9 - name: Install dependencies run: | pip install -r requirements.txt pip install pytest - name: Run tests run: pytest tests/ -v - name: Run end-to-end test run: python main.py --mode train --data_path ./data/raw/sample.csv每次 push 自动触发测试失败的 commit 会被标记为 ❌。这比任何文字描述都更能证明“你的代码真的能跑通”。实操心得我要求团队新人必须完成“Git 三件套”才允许提交第一个 PR为preprocess.py写单元测试覆盖缺失值处理逻辑在config.yaml中新增test_mode: true开关使训练脚本支持小数据集快速验证在 README 的 Quick Start 部分补充# For developers: how to run tests子章节这三步看似琐碎实则强制建立了“可验证→可协作→可维护”的思维习惯。4. 实操过程与核心环节实现从数据加载到模型部署的全链路拆解4.1 数据加载模块超越pd.read_csv()的健壮性设计新手常犯的错误是把数据加载写成一行df pd.read_csv(data.csv)结果在真实场景中崩溃。Harshit 的项目启示我数据加载是第一个也是最重要的质量关卡。我将其拆解为四层防御第一层格式兼容性# src/data/loader.py import pandas as pd from pathlib import Path def load_data(file_path: str) - pd.DataFrame: 支持 CSV/Parquet/Excel 多格式自动识别 path Path(file_path) if path.suffix.lower() .csv: return pd.read_csv(path) elif path.suffix.lower() in [.parquet, .pq]: return pd.read_parquet(path) elif path.suffix.lower() in [.xlsx, .xls]: return pd.read_excel(path) else: raise ValueError(fUnsupported file format: {path.suffix})第二层数据完整性校验# src/data/cleaner.py def validate_schema(df: pd.DataFrame, required_cols: list) - bool: 检查必需字段是否存在类型是否匹配 missing_cols set(required_cols) - set(df.columns) if missing_cols: raise ValueError(fMissing required columns: {missing_cols}) # 检查数值列是否为空 numeric_cols df.select_dtypes(include[number]).columns for col in numeric_cols: if df[col].isnull().sum() len(df) * 0.1: # 10% 缺失率阈值 print(fWarning: {col} has {df[col].isnull().sum()} nulls) return True第三层业务规则注入# src/data/cleaner.py def apply_business_rules(df: pd.DataFrame) - pd.DataFrame: 注入业务约束示例电商场景 # 规则1订单金额不能为负 df df[df[order_amount] 0] # 规则2用户注册时间早于首单时间 df df[pd.to_datetime(df[first_order_time]) pd.to_datetime(df[register_time])] # 规则3剔除测试账号手机号以1333开头 df df[~df[user_id].str.startswith(1333)] return df第四层性能优化# src/data/loader.py def load_large_csv(file_path: str, chunk_size: int 10000) - pd.DataFrame: 处理超大 CSV1GB chunks [] for chunk in pd.read_csv(file_path, chunksizechunk_size): # 在 chunk 级别应用清洗规则减少内存占用 chunk apply_business_rules(chunk) chunks.append(chunk) return pd.concat(chunks, ignore_indexTrue)这套设计让数据加载从“可能失败的操作”变为“可预期、可审计、可扩展的模块”。当面试官问“你如何保证数据质量”你可以直接打开cleaner.py展示四层防御比口头解释有力十倍。4.2 特征工程模块把业务知识编码为可复用的 Python 函数特征工程是区分“调包侠”和“数据工程师”的分水岭。Harshit 的项目强调“business features”我将其具象化为两个核心原则原则一特征必须可解释、可溯源❌ 错误示范df[feature_123] df.groupby(user_id)[amount].rolling(30).mean()✅ 正确示范# src/features/business.py def calculate_purchase_rhythm(df: pd.DataFrame, window_days: int 30) - pd.Series: 计算用户购买节奏稳定性标准差越小节奏越规律 # 步骤1计算相邻订单时间差 df_sorted df.sort_values([user_id, order_time]) df_sorted[time_diff] df_sorted.groupby(user_id)[order_time].diff().dt.days # 步骤2滚动计算标准差捕捉近期节奏变化 rhythm_series df_sorted.groupby(user_id)[time_diff].rolling( windowwindow_days, min_periods3 ).std().reset_index(level0, dropTrue) return rhythm_series.rename(purchase_rhythm_std)这个函数的价值在于① 函数名直接说明业务含义② 注释解释物理意义“标准差越小节奏越规律”③ 参数window_days可配置适应不同业务周期。原则二特征必须可组合、可测试# src/features/base.py class FeaturePipeline: def __init__(self, features: list): self.features features # 如 [calculate_purchase_rhythm, calculate_coupon_sensitivity] def fit_transform(self, df: pd.DataFrame) - pd.DataFrame: result_df df.copy() for feature_func in self.features: try: result_df pd.concat([ result_df, feature_func(result_df) ], axis1) except Exception as e: print(fFeature {feature_func.__name__} failed: {e}) # 失败时填充 NaN不影响整体流程 result_df[feature_func.__name__] np.nan return result_df # 测试用例tests/test_features.py def test_purchase_rhythm(): # 构造测试数据用户A每天固定下单用户B下单时间随机 test_df pd.DataFrame({ user_id: [A,A,A,B,B,B], order_time: pd.to_datetime([2024-01-01, 2024-01-02, 2024-01-03, 2024-01-01, 2024-01-05, 2024-01-12]) }) rhythm calculate_purchase_rhythm(test_df) assert rhythm.loc[rhythm.index.get_level_values(user_id)A].iloc[0] 0.1 # A用户节奏稳定 assert rhythm.loc[rhythm.index.get_level_values(user_id)B].iloc[0] 5.0 # B用户节奏随机这种设计让特征工程不再是“写完就扔”的脚本而是可单元测试、可参数化、可组合的生产级模块。当业务方提出“能不能加一个‘用户对价格敏感度’特征”你只需写一个新函数加入FeaturePipeline即可无需重构整个流程。4.3 模型训练模块从“跑通模型”到“交付可解释方案”模型训练环节最容易陷入“调参陷阱”。Harshit 的成功在于把模型当作解决方案的一部分而非终极目标。我将其拆解为四个必做动作动作一超参数搜索必须绑定业务指标# src/models/lgbm.py from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint, uniform def tune_lgbm(X_train, y_train, scoringf1_weighted): 基于业务指标的超参搜索非默认 accuracy model lgb.LGBMClassifier() param_dist { n_estimators: randint(50, 300), learning_rate: uniform(0.01, 0.3), num_leaves: randint(20, 100), min_child_samples: randint(10, 100) } # 使用业务导向的评分F1加权处理类别不平衡 search RandomizedSearchCV( model, param_distributionsparam_dist, n_iter50, cv3, scoringscoring, n_jobs-1 ) search.fit(X_train, y_train) return search.best_estimator_动作二模型持久化必须包含元信息# main.py import joblib from datetime import datetime def save_model(model, config, metrics, output_path: str): 保存模型完整元信息 model_info { model: model, config: config, # 来自 config.yaml 的完整参数 metrics: metrics, # 训练/验证指标 timestamp: datetime.now().isoformat(), git_commit: get_git_commit(), # 获取当前 commit hash python_version: sys.version } joblib.dump(model_info, output_path) print(fModel saved to {output_path} (commit: {model_info[git_commit][:7]})) def get_git_commit() - str: try: return subprocess.check_output([git, rev-parse, HEAD]).decode(ascii).strip() except: return unknown动作三预测服务必须支持可解释性输出# src/models/lgbm.py import shap def predict_with_explanation(model, X_test, feature_names): 返回预测结果 SHAP 解释 y_pred model.predict(X_test) explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 生成可读解释示例对单个用户 def explain_single_prediction(idx): explanation fUser {idx} predicted as {y_pred[idx]}. Key drivers:\n for i in np.argsort(abs(shap_values[idx]))[-3:][::-1]: # top 3 features explanation f- {feature_names[i]}: {shap_values[idx][i]:.3f}\n return explanation return y_pred, shap_values, explain_single_prediction动作四评估报告必须面向业务方# outputs/reports/generate_report.py def generate_business_report(y_true, y_pred, shap_values, feature_names): 生成业务可读 PDF 报告 # 第一页核心指标AUC/F1/召回率vs 业务基线 # 第二页TOP10 重要特征按 SHAP 均值排序 # 第三页典型用户案例高风险用户特征贡献图 # 第四页部署建议如“提升优惠券发放频次可降低流失风险” pass这套流程确保模型交付物不是.pkl文件而是“预测结果为什么这么预测业务如何行动”的完整包。当面试官问“你的模型有什么业务价值”你可以直接打开outputs/reports/business_summary.pdf展示第三页的典型用户案例——这才是技术人的终极说服力。5. 常见问题与排查技巧实录那些没人告诉你的 GitHub 数据作品集陷阱5.1 GitHub 显示问题为什么你的 Notebook 图表不渲染这是最高频的“信任危机”。当面试官点开你的 Notebook看到一片空白或报错第一印象就是“这人连基础环境都没配好”。根本原因在于 GitHub 的 Notebook 渲染机制它只显示已保存的输出output不执行代码。如果你本地运行后忘记保存或者用了matplotlib inline但未显式调用plt.show()图表就不会显示。解决方案分三步本地保存前强制渲染在 Notebook 最后添加# 强制保存所有图表 import matplotlib.pyplot as plt for fig_num in plt.get_fignums(): plt.figure(fig_num) plt.savefig(foutputs/plots/fig_{fig_num}.png, bbox_inchestight) plt.close(all)在 README 中嵌入静态图## 可视化分析 ![特征重要性](outputs/plots/feature_importance.png)提供交互式替代方案用 Voilà 将 Notebook 转为 Web 应用pip install voila voila notebooks/20240315_eda.ipynb --no-browser --port8866然后在 README 中添加[View Interactive Dashboard](http://localhost:8866)注明需本地运行。实操心得我要求所有 Notebook 必须在File Trusted Notebook中勾选否则 GitHub 渲染时会禁用 JavaScript 输出。这个细节在 95% 的教程里被忽略却是专业性的分水岭。5.2 依赖冲突为什么pip install -r requirements.txt总是失败根本原因是requirements.txt生成方式错误。用pip freeze requirements.txt会导出所有全局包包括jupyteripykernel等开发依赖导致生产环境安装失败。正确流程创建干净虚拟环境python -m venv venv_clean source venv_clean/bin/activate # Linux/Mac # venv_clean\Scripts\activate # Windows只安装项目必需包pip install pandas scikit-learn lightgbm shap用pipreqs生成最小依赖pip install pipreqs pipreqs ./ --encodingutf8 --forcepipreqs会扫描src/目录下的import语句只导出实际使用的包。进阶技巧分离开发/生产依赖创建requirements-dev.txt-r requirements.txt pytest7.2.0 black23.1.0 jupyter1.0.0这样pip install -r requirements-dev.txt用于开发pip install -r requirements.txt用于部署彻底解决环境污染问题。5.3 数据隐私雷区如何安全地展示真实业务数据很多同学想用“某银行信用卡数据”“某医院就诊记录”做项目却不知这已触碰法律红线。Harshit 的聪明之处在于用公开数据集构建业务逻辑再用合成数据验证。安全三步法公开数据集打底Kaggle 的 E-commerce Behavior Data 或 UCI 的 Online Retail业务逻辑迁移把公开数据的字段映射到业务场景如event_typecart→actionadd_to_cart合成数据增强用Faker生成符合业务分布的假数据from faker import Faker import numpy as np fake Faker() # 生成符合正态分布的订单金额均值200标准差50 order_amounts np.random.normal(200, 50, 10000) order_amounts np.clip(order_amounts, 10, 1000) # 限制范围 # 生成用户ID符合业务长度 user_ids [fake.uuid4()[:8] for _ in range(10000)]绝对禁止上传任何含个人身份信息PII的数据包括脱敏后的手机号、身份证号哈希值。即使你认为“已经匿名化”法律上仍可能被认定为可重识别数据。踩坑实录我曾指导一位学生用医院公开数据集做项目他把“患者年龄”和“诊断代码”组合后意外发现能反推出特定科室的就诊高峰。这提醒我们数据安全不是技术问题是设计哲学——永远假设你发布的任何数据都可能被最聪明的人用来反推原始信息。5.4 作品集冷启动如何让第一个项目就获得关注Harshit 的标题 “My Journey” 是精髓——它暗示这是一个持续演进的过程而非一次性成果。我总结出“冷启动四要素”命名即定位项目名不用>[![Star on GitHub](https://img.shields.io/github/stars/yourname/ecommerce-churn-predictor?stylesocial)](https://github.com/yourname/ecommerce-churn-predictor)这个按钮本身就在暗示“值得收藏”心理学上叫“社会认同效应”。Issue 模板化创建.github/ISSUE_TEMPLATE/feature_request.md--- name: Feature Request about: Suggest an idea for this project title: labels: enhancement assignees: ## Describe the solution youd like !-- A clear and concise description of what you want to happen. -- ## Describe alternatives youve considered !-- A clear and concise description of any alternative solutions or features youve considered. --当有人提 Issue你就有了天然的互动起点。博客联动把 GitHub 项目写成 Medium 博客如本文并在博客末尾加 **Project Code**: All code is open-sourced on GitHub — [view repository](https://github.com/yourname/ecommerce-churn-predictor) **Want to contribute?** Check out the [Good First Issues](https://github.com/yourname/