{"id":358,"date":"2026-03-29T22:54:10","date_gmt":"2026-03-29T14:54:10","guid":{"rendered":"https:\/\/www.ntblog.cn\/?p=358"},"modified":"2026-03-29T23:16:44","modified_gmt":"2026-03-29T15:16:44","slug":"py-sklearn-airqualityapp","status":"publish","type":"post","link":"https:\/\/www.ntblog.cn\/index.php\/2026\/03\/29\/py-sklearn-airqualityapp\/","title":{"rendered":"\u57fa\u4e8ePython+Sklearn\u7684\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u7cfb\u7edf"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">Main<\/h1>\n\n\n\n<p>\u5176\u5b9eAI\u91cf\u7279\u522b\u9ad8\uff0c\u4f46\u662f\u4ee4\u4eba\u611f\u52a8\u7684\u662f\u8fd9\u4e2a\u4ee3\u7801\u7b2c\u4e00\u6b21\u8fd0\u884c\u6ca1\u6709\u62a5\u9519\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u56fe\u7247\u9884\u89c8<\/h2>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper lazyload-container-unload' data-fancybox='post-images' href='https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-1-1024x810.png'><img class=\"lazyload lazyload-style-11\" src=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-1-1024x810.png\"  loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"810\" data-original=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-1-1024x810.png\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB\/AAffA0nNPuCLAAAAAElFTkSuQmCC\" alt=\"\" class=\"wp-image-363\"  sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper lazyload-container-unload' data-fancybox='post-images' href='https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-2-1024x810.png'><img class=\"lazyload lazyload-style-11\" src=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-2-1024x810.png\"  loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"810\" data-original=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-2-1024x810.png\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB\/AAffA0nNPuCLAAAAAElFTkSuQmCC\" alt=\"\" class=\"wp-image-364\"  sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><div class='fancybox-wrapper lazyload-container-unload' data-fancybox='post-images' href='https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-3-1024x810.png'><img class=\"lazyload lazyload-style-11\" src=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-3-1024x810.png\"  loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"810\" data-original=\"https:\/\/cn-sy1.rains3.com\/ntblog\/2026\/03\/image-3-1024x810.png\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB\/AAffA0nNPuCLAAAAAElFTkSuQmCC\" alt=\"\" class=\"wp-image-365\"  sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\u6587\u4ef6\u5206\u4eab<\/h2>\n\n\n\n<p>\u4e3a\u4e86\u9632\u6b62\u672c\u7ad9\u88ab\u5237\u7206\u6d41\u91cf\uff0c\u6545\u653e\u51fa\u7b2c\u4e09\u65b9\u4e91\u76d8\u4e0b\u8f7d\u6e20\u9053\uff1a<\/p>\n\n\n\n<p><a href=\"https:\/\/www.123865.com\/s\/qPh5Vv-LrdO?pwd=1145#\">https:\/\/www.123865.com\/s\/qPh5Vv-LrdO?pwd=1145#<\/a><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u4e3b\u8981\u4ee3\u7801\u5c55\u793a\uff08air_quality_Wenzhou\uff09\uff1a<\/h2>\n\n\n\n<h3 class=\"wp-block-heading has-text-align-center\">train_model.py<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\"\"\"\n\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u6a21\u578b\u8bad\u7ec3\u811a\u672c\n\u8bad\u7ec3\u5b8c\u6210\u540e\u4f1a\u751f\u6210 models\/air_quality_model.pkl\n\"\"\"\n\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.preprocessing import LabelEncoder, StandardScaler\nfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix\nimport joblib\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport os\nimport json\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# \u8bbe\u7f6e\u4e2d\u6587\u5b57\u4f53\nplt.rcParams&#91;'font.sans-serif'] = &#91;'SimHei', 'Microsoft YaHei', 'Noto Sans CJK JP']\nplt.rcParams&#91;'axes.unicode_minus'] = False\n\nclass AirQualityModelTrainer:\n    def __init__(self, data_path='data\/cata_6716.csv'):\n        \"\"\"\u521d\u59cb\u5316\u6a21\u578b\u8bad\u7ec3\u5668\"\"\"\n        self.data_path = data_path\n        self.df = None\n        self.X = None\n        self.y = None\n        self.label_encoder = None\n        self.scaler = None\n        self.model = None\n\n    def load_and_preprocess(self):\n        \"\"\"\u6b65\u9aa41\uff1a\u52a0\u8f7d\u548c\u9884\u5904\u7406\u6570\u636e\"\"\"\n        print(\"=== \u6b65\u9aa41: \u52a0\u8f7d\u548c\u9884\u5904\u7406\u6570\u636e ===\")\n\n        # 1.1 \u8bfb\u53d6\u6570\u636e\uff08\u4f7f\u7528GBK\u7f16\u7801\u5904\u7406\u4e2d\u6587\uff09\n        try:\n            self.df = pd.read_csv(self.data_path, encoding='gbk')\n        except:\n            # \u5c1d\u8bd5\u5176\u4ed6\u7f16\u7801\n            self.df = pd.read_csv(self.data_path, encoding='utf-8')\n\n        print(f\"\u2713 \u6570\u636e\u52a0\u8f7d\u6210\u529f: {self.df.shape&#91;0]} \u884c, {self.df.shape&#91;1]} \u5217\")\n        print(f\"\u2713 \u6570\u636e\u65f6\u95f4\u8303\u56f4: {self.df&#91;'\u5e74\u4efd'].min()}\u5e74 \u81f3 {self.df&#91;'\u5e74\u4efd'].max()}\u5e74\")\n        print(f\"\u2713 \u57ce\u5e02: {self.df&#91;'\u57ce\u5e02\u540d'].iloc&#91;0] if '\u57ce\u5e02\u540d' in self.df.columns else '\u672a\u6307\u5b9a'}\")\n\n        # 1.2 \u68c0\u67e5\u5e76\u9009\u62e9\u7279\u5f81\u5217\n        feature_columns = &#91;\n            '\u7ec6\u9897\u7c92\u7269',        # PM2.5\n            '\u53ef\u5438\u5165\u9897\u7c92\u7269',    # PM10\n            '\u4e8c\u6c27\u5316\u786b',        # SO2\n            '\u4e8c\u6c27\u5316\u6c2e',        # NO2\n            '\u4e00\u6c27\u5316\u78b3',        # CO\n            '\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747'     # O3\n        ]\n\n        # \u68c0\u67e5\u7279\u5f81\u662f\u5426\u5b58\u5728\n        available_features = &#91;col for col in feature_columns if col in self.df.columns]\n        if len(available_features) &lt; len(feature_columns):\n            print(f\"\u26a0 \u8b66\u544a: \u90e8\u5206\u7279\u5f81\u4e0d\u5b58\u5728\uff0c\u4f7f\u7528\u53ef\u7528\u7279\u5f81: {available_features}\")\n\n        # 1.3 \u521b\u5efa\u7279\u5f81\u77e9\u9635\n        self.X = self.df&#91;available_features].copy()\n        print(f\"\u2713 \u4f7f\u7528\u7279\u5f81: {available_features}\")\n\n        # 1.4 \u521b\u5efa\u76ee\u6807\u53d8\u91cf\uff08\u4eba\u7fa4\u7c7b\u522b\uff09\n        print(\"\\n=== \u521b\u5efa\u76ee\u6807\u53d8\u91cf ===\")\n        self.df&#91;'\u4eba\u7fa4\u7c7b\u522b'] = self.df.apply(self._create_crowd_label, axis=1)\n\n        # 1.5 \u7f16\u7801\u76ee\u6807\u53d8\u91cf\n        self.label_encoder = LabelEncoder()\n        self.y = self.label_encoder.fit_transform(self.df&#91;'\u4eba\u7fa4\u7c7b\u522b'])\n\n        # \u663e\u793a\u7c7b\u522b\u5206\u5e03\n        label_counts = pd.Series(self.y).value_counts().sort_index()\n        label_dist = pd.DataFrame({\n            '\u7f16\u7801': range(len(self.label_encoder.classes_)),\n            '\u4eba\u7fa4\u7c7b\u522b': self.label_encoder.classes_,\n            '\u6837\u672c\u6570\u91cf': label_counts.values\n        })\n        print(\"\u76ee\u6807\u53d8\u91cf\u5206\u5e03:\")\n        print(label_dist.to_string(index=False))\n\n        # 1.6 \u7279\u5f81\u6807\u51c6\u5316\n        self.scaler = StandardScaler()\n        self.X_scaled = self.scaler.fit_transform(self.X)\n\n        return self.X_scaled, self.y, available_features\n\n    def _create_crowd_label(self, row):\n        \"\"\"\u6839\u636eAQI\u548c\u6c61\u67d3\u7269\u6d53\u5ea6\u521b\u5efa\u4eba\u7fa4\u7c7b\u522b\u6807\u7b7e\"\"\"\n        aqi = row&#91;'\u7a7a\u6c14\u8d28\u91cf\u6307\u6570'] if '\u7a7a\u6c14\u8d28\u91cf\u6307\u6570' in row else 50\n\n        # \u57fa\u4e8e\u4e2d\u56fd\u7a7a\u6c14\u8d28\u91cf\u6807\u51c6\u548c\u5065\u5eb7\u5efa\u8bae\n        if aqi &lt;= 50:\n            return '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa'\n        elif aqi &lt;= 100:\n            # \u5728\u826f\u7684\u8303\u56f4\u5185\uff0c\u6839\u636e\u4e3b\u8981\u6c61\u67d3\u7269\u8fdb\u4e00\u6b65\u7ec6\u5206\n            pm25 = row&#91;'\u7ec6\u9897\u7c92\u7269'] if '\u7ec6\u9897\u7c92\u7269' in row else 35\n            o3 = row&#91;'\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747'] if '\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747' in row else 100\n\n            if pm25 &gt; 35 or o3 &gt; 100:\n                return '\u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa'\n            else:\n                return '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa'\n        elif aqi &lt;= 150:\n            return '\u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa'\n        elif aqi &lt;= 200:\n            return '\u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa'\n        elif aqi &lt;= 300:\n            return '\u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa'\n        else:\n            return '\u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73'\n\n    def train_model(self, test_size=0.2, random_state=42):\n        \"\"\"\u6b65\u9aa42\uff1a\u8bad\u7ec3\u6a21\u578b\"\"\"\n        print(\"\\n=== \u6b65\u9aa42: \u8bad\u7ec3\u6a21\u578b ===\")\n\n        # 2.1 \u5212\u5206\u8bad\u7ec3\u96c6\u548c\u6d4b\u8bd5\u96c6\n        X_train, X_test, y_train, y_test = train_test_split(\n            self.X_scaled, self.y,\n            test_size=test_size,\n            random_state=random_state,\n            stratify=self.y\n        )\n\n        print(f\"\u2713 \u8bad\u7ec3\u96c6: {X_train.shape&#91;0]} \u6837\u672c\")\n        print(f\"\u2713 \u6d4b\u8bd5\u96c6: {X_test.shape&#91;0]} \u6837\u672c\")\n\n        # 2.2 \u8bad\u7ec3\u968f\u673a\u68ee\u6797\u6a21\u578b\n        print(\"\u8bad\u7ec3\u968f\u673a\u68ee\u6797\u6a21\u578b\u4e2d...\")\n        self.model = RandomForestClassifier(\n            n_estimators=200,\n            max_depth=15,\n            min_samples_split=5,\n            min_samples_leaf=2,\n            random_state=random_state,\n            class_weight='balanced',\n            n_jobs=-1,\n            verbose=0\n        )\n\n        self.model.fit(X_train, y_train)\n        print(\"\u2713 \u6a21\u578b\u8bad\u7ec3\u5b8c\u6210\")\n\n        # 2.3 \u6a21\u578b\u8bc4\u4f30\n        y_pred = self.model.predict(X_test)\n        accuracy = accuracy_score(y_test, y_pred)\n\n        print(f\"\\n=== \u6a21\u578b\u8bc4\u4f30\u7ed3\u679c ===\")\n        print(f\"\u51c6\u786e\u7387: {accuracy:.4f}\")\n        print(\"\\n\u8be6\u7ec6\u5206\u7c7b\u62a5\u544a:\")\n        print(classification_report(y_test, y_pred,\n                                   target_names=self.label_encoder.classes_))\n\n        # 2.4 \u7279\u5f81\u91cd\u8981\u6027\u5206\u6790\n        feature_importance = pd.DataFrame({\n            '\u7279\u5f81': self.X.columns,\n            '\u91cd\u8981\u6027': self.model.feature_importances_\n        }).sort_values('\u91cd\u8981\u6027', ascending=False)\n\n        print(\"\\n\u7279\u5f81\u91cd\u8981\u6027\u6392\u5e8f:\")\n        print(feature_importance.to_string(index=False))\n\n        # 2.5 \u53ef\u89c6\u5316\u7279\u5f81\u91cd\u8981\u6027\n        self._plot_feature_importance(feature_importance)\n\n        # 2.6 \u751f\u6210\u6df7\u6dc6\u77e9\u9635\n        self._plot_confusion_matrix(y_test, y_pred)\n\n        return accuracy, X_test, y_test, y_pred\n\n    def _plot_feature_importance(self, feature_importance):\n        \"\"\"\u53ef\u89c6\u5316\u7279\u5f81\u91cd\u8981\u6027\"\"\"\n        plt.figure(figsize=(10, 6))\n        bars = plt.barh(feature_importance&#91;'\u7279\u5f81'], feature_importance&#91;'\u91cd\u8981\u6027'],\n                       color=plt.cm.viridis(np.linspace(0.3, 0.9, len(feature_importance))))\n        plt.xlabel('\u7279\u5f81\u91cd\u8981\u6027', fontsize=12)\n        plt.title('\u6c61\u67d3\u7269\u7279\u5f81\u91cd\u8981\u6027\u6392\u5e8f', fontsize=14, fontweight='bold')\n        plt.gca().invert_yaxis()  # \u6700\u91cd\u8981\u7684\u663e\u793a\u5728\u6700\u4e0a\u9762\n\n        # \u6dfb\u52a0\u6570\u503c\u6807\u7b7e\n        for bar in bars:\n            width = bar.get_width()\n            plt.text(width + 0.001, bar.get_y() + bar.get_height()\/2,\n                    f'{width:.3f}', ha='left', va='center', fontsize=10)\n\n        plt.tight_layout()\n        plt.savefig('feature_importance.png', dpi=300, bbox_inches='tight')\n        print(\"\u2713 \u7279\u5f81\u91cd\u8981\u6027\u56fe\u5df2\u4fdd\u5b58: feature_importance.png\")\n        plt.close()\n\n    def _plot_confusion_matrix(self, y_test, y_pred):\n        \"\"\"\u7ed8\u5236\u6df7\u6dc6\u77e9\u9635\"\"\"\n        cm = confusion_matrix(y_test, y_pred)\n\n        plt.figure(figsize=(10, 8))\n        sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                   xticklabels=self.label_encoder.classes_,\n                   yticklabels=self.label_encoder.classes_)\n        plt.title('\u6df7\u6dc6\u77e9\u9635', fontsize=14, fontweight='bold')\n        plt.xlabel('\u9884\u6d4b\u6807\u7b7e', fontsize=12)\n        plt.ylabel('\u771f\u5b9e\u6807\u7b7e', fontsize=12)\n        plt.xticks(rotation=45, ha='right')\n        plt.tight_layout()\n        plt.savefig('confusion_matrix.png', dpi=300, bbox_inches='tight')\n        print(\"\u2713 \u6df7\u6dc6\u77e9\u9635\u5df2\u4fdd\u5b58: confusion_matrix.png\")\n        plt.close()\n\n    def save_model(self, model_path='models\/air_quality_model.pkl'):\n        \"\"\"\u6b65\u9aa43\uff1a\u4fdd\u5b58\u6a21\u578b\u548c\u76f8\u5173\u6570\u636e\"\"\"\n        print(\"\\n=== \u6b65\u9aa43: \u4fdd\u5b58\u6a21\u578b ===\")\n\n        # 3.1 \u521b\u5efa\u4fdd\u5b58\u76ee\u5f55\n        os.makedirs('models', exist_ok=True)\n\n        # 3.2 \u51c6\u5907\u4fdd\u5b58\u7684\u6570\u636e\n        model_data = {\n            'model': self.model,\n            'label_encoder': self.label_encoder,\n            'scaler': self.scaler,\n            'feature_columns': list(self.X.columns),\n            'feature_importance': pd.DataFrame({\n                '\u7279\u5f81': self.X.columns,\n                '\u91cd\u8981\u6027': self.model.feature_importances_\n            }).sort_values('\u91cd\u8981\u6027', ascending=False).to_dict('records'),\n            'class_descriptions': {\n                0: '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa - \u7a7a\u6c14\u8d28\u91cf\u4f18\uff0c\u9002\u5408\u6240\u6709\u6237\u5916\u6d3b\u52a8\uff0c\u5305\u62ec\u8dd1\u6b65\u3001\u9a91\u884c\u7b49\u5267\u70c8\u8fd0\u52a8',\n                1: '\u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa - \u7a7a\u6c14\u8d28\u91cf\u826f\uff0c\u654f\u611f\u4eba\u7fa4\u5e94\u51cf\u5c11\u957f\u65f6\u95f4\u6237\u5916\u6d3b\u52a8',\n                2: '\u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa - \u8f7b\u5ea6\u6c61\u67d3\uff0c\u513f\u7ae5\u3001\u8001\u4eba\u3001\u547c\u5438\u9053\u75be\u75c5\u60a3\u8005\u907f\u514d\u6237\u5916\u6d3b\u52a8',\n                3: '\u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa - \u4e2d\u5ea6\u6c61\u67d3\uff0c\u5efa\u8bae\u51cf\u5c11\u6237\u5916\u6d3b\u52a8\uff0c\u5916\u51fa\u65f6\u4f69\u6234\u53e3\u7f69',\n                4: '\u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa - \u91cd\u5ea6\u6c61\u67d3\uff0c\u654f\u611f\u4eba\u7fa4\u5e94\u7559\u5728\u5ba4\u5185\uff0c\u5173\u95ed\u95e8\u7a97',\n                5: '\u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73 - \u4e25\u91cd\u6c61\u67d3\uff0c\u6240\u6709\u4eba\u7fa4\u907f\u514d\u6237\u5916\u6d3b\u52a8\uff0c\u4f7f\u7528\u7a7a\u6c14\u51c0\u5316\u5668'\n            },\n            'training_info': {\n                '\u6837\u672c\u6570\u91cf': len(self.df),\n                '\u7279\u5f81\u6570\u91cf': len(self.X.columns),\n                '\u8bad\u7ec3\u65f6\u95f4': pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S'),\n                '\u6a21\u578b\u7c7b\u578b': 'RandomForestClassifier',\n                '\u6570\u636e\u6765\u6e90': self.data_path\n            }\n        }\n\n        # 3.3 \u4fdd\u5b58\u6a21\u578b\n        joblib.dump(model_data, model_path)\n        print(f\"\u2713 \u6a21\u578b\u5df2\u4fdd\u5b58\u5230: {model_path}\")\n\n        # 3.4 \u4fdd\u5b58\u7279\u5f81\u8303\u56f4\uff08\u7528\u4e8eGUI\u6ed1\u5757\u8bbe\u7f6e\uff09\n        feature_ranges = {}\n        for col in self.X.columns:\n            feature_ranges&#91;col] = {\n                'min': float(self.df&#91;col].min()),\n                'max': float(self.df&#91;col].max()),\n                'mean': float(self.df&#91;col].mean()),\n                'std': float(self.df&#91;col].std()),\n                'q1': float(self.df&#91;col].quantile(0.25)),\n                'q3': float(self.df&#91;col].quantile(0.75))\n            }\n\n        ranges_path = 'models\/feature_ranges.json'\n        with open(ranges_path, 'w', encoding='utf-8') as f:\n            json.dump(feature_ranges, f, ensure_ascii=False, indent=2)\n        print(f\"\u2713 \u7279\u5f81\u8303\u56f4\u5df2\u4fdd\u5b58\u5230: {ranges_path}\")\n\n        # 3.5 \u4fdd\u5b58\u6a21\u578b\u5143\u6570\u636e\n        metadata = {\n            'model_info': {\n                'name': 'AirQualityCrowdAdviceModel',\n                'version': '1.0.0',\n                'description': '\u57fa\u4e8e\u7a7a\u6c14\u8d28\u91cf\u6570\u636e\u7684\u4eba\u7fa4\u5916\u51fa\u5efa\u8bae\u6a21\u578b',\n                'author': 'Air Quality System',\n                'created_date': pd.Timestamp.now().strftime('%Y-%m-%d')\n            },\n            'features': list(self.X.columns),\n            'classes': list(self.label_encoder.classes_),\n            'feature_ranges': feature_ranges\n        }\n\n        metadata_path = 'models\/model_metadata.json'\n        with open(metadata_path, 'w', encoding='utf-8') as f:\n            json.dump(metadata, f, ensure_ascii=False, indent=2)\n        print(f\"\u2713 \u6a21\u578b\u5143\u6570\u636e\u5df2\u4fdd\u5b58\u5230: {metadata_path}\")\n\n    def run_training_pipeline(self):\n        \"\"\"\u8fd0\u884c\u5b8c\u6574\u7684\u8bad\u7ec3\u6d41\u7a0b\"\"\"\n        print(\"=\"*60)\n        print(\"\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u6a21\u578b\u8bad\u7ec3\u7cfb\u7edf\")\n        print(\"=\"*60)\n\n        try:\n            # 1. \u52a0\u8f7d\u548c\u9884\u5904\u7406\u6570\u636e\n            X, y, features = self.load_and_preprocess()\n\n            # 2. \u8bad\u7ec3\u6a21\u578b\n            accuracy, X_test, y_test, y_pred = self.train_model()\n\n            # 3. \u4fdd\u5b58\u6a21\u578b\n            self.save_model()\n\n            print(\"\\n\" + \"=\"*60)\n            print(\"\ud83c\udf89 \u6a21\u578b\u8bad\u7ec3\u5b8c\u6210\uff01\")\n            print(f\"\ud83d\udcca \u6700\u7ec8\u51c6\u786e\u7387: {accuracy:.4f}\")\n            print(f\"\ud83d\udd22 \u7279\u5f81\u6570\u91cf: {len(features)}\")\n            print(f\"\ud83c\udff7\ufe0f  \u7c7b\u522b\u6570\u91cf: {len(self.label_encoder.classes_)}\")\n            print(\"=\"*60)\n            print(\"\\n\ud83d\udcc1 \u751f\u6210\u7684\u6587\u4ef6:\")\n            print(\"  - models\/air_quality_model.pkl (\u4e3b\u6a21\u578b\u6587\u4ef6)\")\n            print(\"  - models\/feature_ranges.json (\u7279\u5f81\u8303\u56f4)\")\n            print(\"  - models\/model_metadata.json (\u6a21\u578b\u5143\u6570\u636e)\")\n            print(\"  - feature_importance.png (\u7279\u5f81\u91cd\u8981\u6027\u56fe)\")\n            print(\"  - confusion_matrix.png (\u6df7\u6dc6\u77e9\u9635)\")\n\n            return True\n\n        except Exception as e:\n            print(f\"\\n\u274c \u8bad\u7ec3\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u9519\u8bef: {str(e)}\")\n            import traceback\n            traceback.print_exc()\n            return False\n\nif __name__ == \"__main__\":\n    # \u8bad\u7ec3\u6a21\u578b\n    trainer = AirQualityModelTrainer(data_path='data\/cata_6716.csv')\n    success = trainer.run_training_pipeline()\n\n    if success:\n        print(\"\\n\u2705 \u4e0b\u4e00\u6b65: \u8fd0\u884c 'python predict_app.py' \u542f\u52a8GUI\u5e94\u7528\")\n    else:\n        print(\"\\n\u274c \u8bad\u7ec3\u5931\u8d25\uff0c\u8bf7\u68c0\u67e5\u6570\u636e\u548c\u4ee3\u7801\")\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading has-text-align-center\">predict_app.py<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\"\"\"\n\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8baeGUI\u5e94\u7528\n\u4f7f\u7528\u8bad\u7ec3\u597d\u7684\u6a21\u578b\u63d0\u4f9b\u5b9e\u65f6\u5efa\u8bae\n\"\"\"\n\nimport tkinter as tk\nfrom tkinter import ttk, messagebox, font\nimport joblib\nimport numpy as np\nimport json\nimport os\nfrom PIL import Image, ImageTk\nimport pandas as pd\n\nclass AirQualityApp:\n    def __init__(self, root):\n        \"\"\"\u521d\u59cb\u5316GUI\u5e94\u7528\"\"\"\n        self.root = root\n        self.root.title(\"\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u7cfb\u7edf\")\n        self.root.geometry(\"1000x750\")\n        self.root.configure(bg='#f0f8ff')\n\n        # \u8bbe\u7f6e\u56fe\u6807\n        try:\n            self.root.iconbitmap('icon.ico')\n        except:\n            pass\n\n        # \u6a21\u578b\u6570\u636e\n        self.model_data = None\n        self.model = None\n        self.label_encoder = None\n        self.scaler = None\n        self.feature_columns = None\n        self.feature_ranges = None\n\n        # \u52a0\u8f7d\u6a21\u578b\n        if not self.load_model():\n            messagebox.showerror(\"\u9519\u8bef\", \"\u65e0\u6cd5\u52a0\u8f7d\u6a21\u578b\uff0c\u8bf7\u5148\u8fd0\u884ctrain_model.py\u8bad\u7ec3\u6a21\u578b\")\n            return\n\n        # \u8bbe\u7f6e\u6837\u5f0f\n        self.setup_styles()\n\n        # \u521b\u5efa\u754c\u9762\n        self.create_widgets()\n\n    def setup_styles(self):\n        \"\"\"\u8bbe\u7f6e\u754c\u9762\u6837\u5f0f\"\"\"\n        style = ttk.Style()\n        style.theme_use('clam')\n\n        # \u81ea\u5b9a\u4e49\u6837\u5f0f\n        style.configure('Title.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 22, 'bold'),\n                       background='#f0f8ff',\n                       foreground='#1e3d59')\n\n        style.configure('Subtitle.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 12),\n                       background='#f0f8ff',\n                       foreground='#2c3e50')\n\n        style.configure('Result.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 16, 'bold'),\n                       background='#f0f8ff',\n                       foreground='#2c3e50')\n\n        style.configure('Advice.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 13),\n                       background='#f0f8ff',\n                       foreground='#34495e',\n                       wraplength=400)\n\n        style.configure('Slider.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 10, 'bold'),\n                       background='#f0f8ff',\n                       foreground='#2c3e50')\n\n        style.configure('Value.TLabel',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 10),\n                       background='#f0f8ff',\n                       foreground='#1e3d59')\n\n        style.configure('Accent.TButton',\n                       font=('\u5fae\u8f6f\u96c5\u9ed1', 12, 'bold'),\n                       background='#4a90e2',\n                       foreground='white')\n\n    def load_model(self):\n        \"\"\"\u52a0\u8f7d\u8bad\u7ec3\u597d\u7684\u6a21\u578b\"\"\"\n        model_path = 'models\/air_quality_model.pkl'\n\n        if not os.path.exists(model_path):\n            return False\n\n        try:\n            self.model_data = joblib.load(model_path)\n            self.model = self.model_data&#91;'model']\n            self.label_encoder = self.model_data&#91;'label_encoder']\n            self.scaler = self.model_data&#91;'scaler']\n            self.feature_columns = self.model_data&#91;'feature_columns']\n\n            # \u52a0\u8f7d\u7279\u5f81\u8303\u56f4\n            ranges_path = 'models\/feature_ranges.json'\n            if os.path.exists(ranges_path):\n                with open(ranges_path, 'r', encoding='utf-8') as f:\n                    self.feature_ranges = json.load(f)\n\n            print(f\"\u2713 \u6a21\u578b\u52a0\u8f7d\u6210\u529f!\")\n            print(f\"\u2713 \u7279\u5f81: {self.feature_columns}\")\n            print(f\"\u2713 \u7c7b\u522b: {list(self.label_encoder.classes_)}\")\n\n            return True\n        except Exception as e:\n            print(f\"\u2717 \u52a0\u8f7d\u6a21\u578b\u5931\u8d25: {str(e)}\")\n            return False\n\n    def create_widgets(self):\n        \"\"\"\u521b\u5efa\u754c\u9762\u63a7\u4ef6\"\"\"\n        # \u4e3b\u5bb9\u5668\n        main_container = ttk.Frame(self.root)\n        main_container.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)\n\n        # \u6807\u9898\u533a\u57df\n        title_frame = ttk.Frame(main_container, style='Title.TFrame')\n        title_frame.pack(pady=(0, 20))\n\n        title_label = ttk.Label(title_frame,\n                               text=\"\ud83c\udf24\ufe0f \u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u7cfb\u7edf\",\n                               style='Title.TLabel')\n        title_label.pack()\n\n        subtitle_label = ttk.Label(title_frame,\n                                 text=\"\u8c03\u6574\u6c61\u67d3\u7269\u53c2\u6570\uff0c\u83b7\u53d6\u9488\u5bf9\u4e0d\u540c\u4eba\u7fa4\u7684\u5065\u5eb7\u5916\u51fa\u5efa\u8bae\",\n                                 style='Subtitle.TLabel')\n        subtitle_label.pack(pady=5)\n\n        # \u4e3b\u5185\u5bb9\u533a\u57df\n        content_frame = ttk.Frame(main_container)\n        content_frame.pack(fill=tk.BOTH, expand=True)\n\n        # \u5de6\u4fa7\u53c2\u6570\u63a7\u5236\u533a\u57df\n        control_frame = ttk.LabelFrame(content_frame, text=\"\u7a7a\u6c14\u8d28\u91cf\u53c2\u6570\u8bbe\u7f6e\", padding=20)\n        control_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))\n\n        # \u5b9a\u4e49\u53c2\u6570\u914d\u7f6e\n        self.param_configs = {\n            '\u7ec6\u9897\u7c92\u7269': {\n                'unit': '\u03bcg\/m\u00b3',\n                'desc': 'PM2.5 - \u76f4\u5f84\u22642.5\u03bcm\u7684\u9897\u7c92\u7269',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u226435\u03bcg\/m\u00b3',\n                'color': '#ff6b6b'\n            },\n            '\u53ef\u5438\u5165\u9897\u7c92\u7269': {\n                'unit': '\u03bcg\/m\u00b3',\n                'desc': 'PM10 - \u76f4\u5f84\u226410\u03bcm\u7684\u9897\u7c92\u7269',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u226450\u03bcg\/m\u00b3',\n                'color': '#4ecdc4'\n            },\n            '\u4e8c\u6c27\u5316\u786b': {\n                'unit': '\u03bcg\/m\u00b3',\n                'desc': 'SO\u2082 - \u4e3b\u8981\u6765\u81ea\u71c3\u7164',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u226460\u03bcg\/m\u00b3',\n                'color': '#45b7d1'\n            },\n            '\u4e8c\u6c27\u5316\u6c2e': {\n                'unit': '\u03bcg\/m\u00b3',\n                'desc': 'NO\u2082 - \u4e3b\u8981\u6765\u81ea\u673a\u52a8\u8f66\u5c3e\u6c14',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u226440\u03bcg\/m\u00b3',\n                'color': '#96ceb4'\n            },\n            '\u4e00\u6c27\u5316\u78b3': {\n                'unit': 'mg\/m\u00b3',\n                'desc': 'CO - \u65e0\u8272\u65e0\u5473\u6709\u6bd2\u6c14\u4f53',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u22644mg\/m\u00b3',\n                'color': '#feca57'\n            },\n            '\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747': {\n                'unit': '\u03bcg\/m\u00b3',\n                'desc': 'O\u2083 - \u5149\u5316\u5b66\u70df\u96fe\u4e3b\u8981\u6210\u5206',\n                'standard': '\u56fd\u5bb6\u6807\u51c6: \u2264160\u03bcg\/m\u00b3',\n                'color': '#ff9ff3'\n            }\n        }\n\n        # \u5b58\u50a8\u6ed1\u5757\u548c\u53d8\u91cf\n        self.sliders = {}\n        self.slider_vars = {}\n\n        # \u521b\u5efa\u6ed1\u5757\n        for i, param in enumerate(self.feature_columns):\n            if param not in self.param_configs:\n                continue\n\n            config = self.param_configs&#91;param]\n\n            # \u83b7\u53d6\u8303\u56f4\n            if self.feature_ranges and param in self.feature_ranges:\n                min_val = int(self.feature_ranges&#91;param]&#91;'min'])\n                max_val = int(self.feature_ranges&#91;param]&#91;'max'] * 1.2)  # \u7559\u6709\u4f59\u91cf\n                default_val = int(self.feature_ranges&#91;param]&#91;'mean'])\n            else:\n                # \u9ed8\u8ba4\u8303\u56f4\n                ranges = {\n                    '\u7ec6\u9897\u7c92\u7269': (0, 500, 50),\n                    '\u53ef\u5438\u5165\u9897\u7c92\u7269': (0, 600, 70),\n                    '\u4e8c\u6c27\u5316\u786b': (0, 200, 20),\n                    '\u4e8c\u6c27\u5316\u6c2e': (0, 300, 30),\n                    '\u4e00\u6c27\u5316\u78b3': (0, 10, 1),\n                    '\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747': (0, 500, 50)\n                }\n                min_val, max_val, default_val = ranges.get(param, (0, 100, 50))\n\n            # \u53c2\u6570\u6846\u67b6\n            param_frame = ttk.Frame(control_frame)\n            param_frame.pack(fill=tk.X, pady=(0, 15))\n\n            # \u53c2\u6570\u6807\u7b7e\n            label_frame = ttk.Frame(param_frame)\n            label_frame.pack(fill=tk.X, pady=(0, 5))\n\n            ttk.Label(label_frame,\n                     text=f\"{param} ({config&#91;'unit']})\",\n                     style='Slider.TLabel').pack(side=tk.LEFT)\n\n            ttk.Label(label_frame,\n                     text=config&#91;'desc'],\n                     font=('\u5fae\u8f6f\u96c5\u9ed1', 8),\n                     foreground='#7f8c8d').pack(side=tk.LEFT, padx=(10, 0))\n\n            # \u6ed1\u5757\u548c\u6570\u503c\u663e\u793a\u6846\u67b6\n            slider_frame = ttk.Frame(param_frame)\n            slider_frame.pack(fill=tk.X)\n\n            # \u6570\u503c\u663e\u793a\n            var = tk.DoubleVar(value=default_val)\n            value_label = ttk.Label(slider_frame,\n                                   textvariable=var,\n                                   style='Value.TLabel',\n                                   width=8)\n            value_label.pack(side=tk.RIGHT, padx=(10, 0))\n\n            # \u5355\u4f4d\u6807\u7b7e\n            ttk.Label(slider_frame,\n                     text=config&#91;'unit'],\n                     font=('\u5fae\u8f6f\u96c5\u9ed1', 9),\n                     foreground='gray').pack(side=tk.RIGHT)\n\n            # \u6ed1\u5757\n            slider = ttk.Scale(slider_frame,\n                              from_=min_val,\n                              to=max_val,\n                              orient=tk.HORIZONTAL,\n                              length=350,\n                              variable=var,\n                              command=lambda v, p=param: self.on_slider_change(p, v))\n            slider.pack(side=tk.LEFT, fill=tk.X, expand=True)\n\n            # \u56fd\u5bb6\u6807\u51c6\u663e\u793a\n            std_label = ttk.Label(param_frame,\n                                 text=f\"\ud83d\udcca {config&#91;'standard']}\",\n                                 font=('\u5fae\u8f6f\u96c5\u9ed1', 8),\n                                 foreground='#27ae60')\n            std_label.pack(anchor='w', pady=(2, 0))\n\n            # \u5b58\u50a8\u5f15\u7528\n            self.sliders&#91;param] = slider\n            self.slider_vars&#91;param] = var\n\n        # \u63a7\u5236\u6309\u94ae\u533a\u57df\n        button_frame = ttk.Frame(control_frame)\n        button_frame.pack(fill=tk.X, pady=(20, 0))\n\n        # \u9884\u6d4b\u6309\u94ae\n        predict_btn = ttk.Button(button_frame,\n                                text=\"\ud83d\ude80 \u83b7\u53d6\u5916\u51fa\u5efa\u8bae\",\n                                style='Accent.TButton',\n                                command=self.predict)\n        predict_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 10))\n\n        # \u91cd\u7f6e\u6309\u94ae\n        reset_btn = ttk.Button(button_frame,\n                              text=\"\ud83d\udd04 \u91cd\u7f6e\u53c2\u6570\",\n                              command=self.reset_sliders)\n        reset_btn.pack(side=tk.LEFT, expand=True, fill=tk.X)\n\n        # \u53f3\u4fa7\u7ed3\u679c\u663e\u793a\u533a\u57df\n        result_frame = ttk.LabelFrame(content_frame, text=\"\u5916\u51fa\u5efa\u8bae\", padding=20)\n        result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(10, 0))\n\n        # \u7ed3\u679c\u5c55\u793a\u533a\u57df\n        self.result_text = tk.Text(result_frame,\n                                  height=12,\n                                  width=40,\n                                  font=('\u5fae\u8f6f\u96c5\u9ed1', 12),\n                                  wrap=tk.WORD,\n                                  bg='#f8f9fa',\n                                  fg='#2c3e50',\n                                  relief=tk.FLAT,\n                                  borderwidth=2)\n        self.result_text.pack(fill=tk.BOTH, expand=True, pady=(0, 15))\n\n        # \u8bbe\u7f6e\u521d\u59cb\u63d0\u793a\u6587\u672c\n        initial_text = \"\"\"\ud83d\udd0d \u8bf7\u8c03\u6574\u5de6\u4fa7\u7684\u6c61\u67d3\u7269\u53c2\u6570\u6ed1\u5757\uff0c\u7136\u540e\u70b9\u51fb\"\u83b7\u53d6\u5916\u51fa\u5efa\u8bae\"\u6309\u94ae\u3002\n\n\u7cfb\u7edf\u5c06\u6839\u636e\u5f53\u524d\u7684\u7a7a\u6c14\u8d28\u91cf\u53c2\u6570\uff0c\u4e3a\u60a8\u63d0\u4f9b\u9488\u5bf9\u4e0d\u540c\u4eba\u7fa4\u7684\u5065\u5eb7\u5916\u51fa\u5efa\u8bae\u3002\n\n\ud83d\udcca \u53c2\u6570\u8bf4\u660e\uff1a\n\u2022 \u7ec6\u9897\u7c92\u7269(PM2.5): \u53ef\u5165\u80ba\u9897\u7c92\u7269\uff0c\u5065\u5eb7\u5371\u5bb3\u5927\n\u2022 \u53ef\u5438\u5165\u9897\u7c92\u7269(PM10): \u53ef\u8fdb\u5165\u547c\u5438\u9053\u7684\u9897\u7c92\u7269\n\u2022 \u4e8c\u6c27\u5316\u786b(SO\u2082): \u523a\u6fc0\u6027\u6c14\u4f53\uff0c\u4e3b\u8981\u6765\u81ea\u71c3\u7164\n\u2022 \u4e8c\u6c27\u5316\u6c2e(NO\u2082): \u523a\u6fc0\u6027\u6c14\u4f53\uff0c\u4e3b\u8981\u6765\u81ea\u673a\u52a8\u8f66\n\u2022 \u4e00\u6c27\u5316\u78b3(CO): \u65e0\u8272\u65e0\u5473\u6709\u6bd2\u6c14\u4f53\n\u2022 \u81ed\u6c27(O\u2083): \u5149\u5316\u5b66\u70df\u96fe\u4e3b\u8981\u6210\u5206\n\n\ud83d\udca1 \u63d0\u793a\uff1a\u7eff\u8272\u6807\u6ce8\u4e3a\u56fd\u5bb6\u6807\u51c6\u9650\u503c\u3002\"\"\"\n\n        self.result_text.insert(1.0, initial_text)\n        self.result_text.config(state=tk.DISABLED)\n\n        # \u8be6\u7ec6\u5efa\u8bae\u6846\u67b6\n        detail_frame = ttk.LabelFrame(result_frame, text=\"\u8be6\u7ec6\u5065\u5eb7\u5efa\u8bae\", padding=15)\n        detail_frame.pack(fill=tk.BOTH, expand=True)\n\n        self.detail_text = tk.Text(detail_frame,\n                                   height=8,\n                                   font=('\u5fae\u8f6f\u96c5\u9ed1', 11),\n                                   wrap=tk.WORD,\n                                   bg='#f8f9fa',\n                                   fg='#34495e',\n                                   relief=tk.FLAT)\n        self.detail_text.pack(fill=tk.BOTH, expand=True)\n\n        # \u8bbe\u7f6e\u521d\u59cb\u8be6\u7ec6\u5efa\u8bae\n        detail_initial = \"\"\"\ud83d\udc65 \u9002\u7528\u4eba\u7fa4\u5206\u7c7b\uff1a\n\u2022 \u5168\u90e8\u4eba\u7fa4\uff1a\u5065\u5eb7\u6210\u5e74\u4eba\n\u2022 \u654f\u611f\u4eba\u7fa4\uff1a\u513f\u7ae5\u3001\u8001\u4eba\u3001\u5b55\u5987\u3001\u547c\u5438\u9053\u75be\u75c5\u60a3\u8005\n\u2022 \u7279\u6b8a\u4eba\u7fa4\uff1a\u5fc3\u810f\u75c5\u3001\u54ee\u5598\u7b49\u6162\u6027\u75c5\u60a3\u8005\n\n\ud83d\udee1\ufe0f \u9632\u62a4\u5efa\u8bae\u7b49\u7ea7\uff1a\n\u2022 \u2705 \u9002\u5b9c\u5916\u51fa\uff1a\u65e0\u9700\u7279\u6b8a\u9632\u62a4\n\u2022 \u26a0\ufe0f \u51cf\u5c11\u5916\u51fa\uff1a\u51cf\u5c11\u6237\u5916\u65f6\u95f4\n\u2022 \ud83d\udeab \u907f\u514d\u5916\u51fa\uff1a\u5c3d\u91cf\u7559\u5728\u5ba4\u5185\n\u2022 \ud83c\udfe0 \u5ba4\u5185\u6d3b\u52a8\uff1a\u5173\u95ed\u95e8\u7a97\uff0c\u4f7f\u7528\u7a7a\u6c14\u51c0\u5316\u5668\"\"\"\n\n        self.detail_text.insert(1.0, detail_initial)\n        self.detail_text.config(state=tk.DISABLED)\n\n        # \u72b6\u6001\u680f\n        status_frame = ttk.Frame(main_container, relief=tk.SUNKEN, borderwidth=1)\n        status_frame.pack(fill=tk.X, pady=(10, 0))\n\n        self.status_label = ttk.Label(status_frame,\n                                     text=\"\u5c31\u7eea | \u6a21\u578b\u5df2\u52a0\u8f7d | \u7b49\u5f85\u8f93\u5165\u53c2\u6570\",\n                                     font=('\u5fae\u8f6f\u96c5\u9ed1', 9),\n                                     foreground='#7f8c8d')\n        self.status_label.pack(side=tk.LEFT, padx=5)\n\n    def on_slider_change(self, param, value):\n        \"\"\"\u6ed1\u5757\u53d8\u5316\u56de\u8c03\"\"\"\n        # \u66f4\u65b0\u72b6\u6001\u680f\n        self.status_label.config(text=f\"\u8c03\u6574: {param} = {float(value):.1f}\")\n\n    def reset_sliders(self):\n        \"\"\"\u91cd\u7f6e\u6240\u6709\u6ed1\u5757\u5230\u9ed8\u8ba4\u503c\"\"\"\n        for param, slider in self.sliders.items():\n            if self.feature_ranges and param in self.feature_ranges:\n                default_val = self.feature_ranges&#91;param]&#91;'mean']\n            else:\n                default_val = 50\n            self.slider_vars&#91;param].set(default_val)\n\n        # \u6e05\u7a7a\u7ed3\u679c\u663e\u793a\n        self.result_text.config(state=tk.NORMAL)\n        self.result_text.delete(1.0, tk.END)\n        self.result_text.insert(1.0, \"\u53c2\u6570\u5df2\u91cd\u7f6e\uff0c\u8bf7\u91cd\u65b0\u83b7\u53d6\u5efa\u8bae\")\n        self.result_text.config(state=tk.DISABLED)\n\n        self.detail_text.config(state=tk.NORMAL)\n        self.detail_text.delete(1.0, tk.END)\n        self.detail_text.insert(1.0, \"\u7b49\u5f85\u65b0\u7684\u9884\u6d4b\u7ed3\u679c...\")\n        self.detail_text.config(state=tk.DISABLED)\n\n        self.status_label.config(text=\"\u53c2\u6570\u5df2\u91cd\u7f6e | \u7b49\u5f85\u8f93\u5165\")\n\n    def predict(self):\n        \"\"\"\u8fdb\u884c\u9884\u6d4b\u5e76\u663e\u793a\u7ed3\u679c\"\"\"\n        if self.model is None:\n            messagebox.showerror(\"\u9519\u8bef\", \"\u6a21\u578b\u672a\u52a0\u8f7d\uff0c\u65e0\u6cd5\u8fdb\u884c\u9884\u6d4b\")\n            return\n\n        try:\n            # 1. \u83b7\u53d6\u5f53\u524d\u53c2\u6570\u503c\n            features = &#91;]\n            for param in self.feature_columns:\n                value = self.slider_vars&#91;param].get()\n                features.append(value)\n\n            # 2. \u8f6c\u6362\u4e3anumpy\u6570\u7ec4\u5e76\u6807\u51c6\u5316\n            X = np.array(&#91;features], dtype=np.float32)\n            X_scaled = self.scaler.transform(X)\n\n            # 3. \u8fdb\u884c\u9884\u6d4b\n            prediction = self.model.predict(X_scaled)&#91;0]\n            probability = self.model.predict_proba(X_scaled)&#91;0]\n\n            # 4. \u89e3\u7801\u9884\u6d4b\u7ed3\u679c\n            label = self.label_encoder.inverse_transform(&#91;prediction])&#91;0]\n\n            # 5. \u83b7\u53d6\u9884\u6d4b\u6982\u7387\n            prob_dict = {}\n            for i, cls in enumerate(self.label_encoder.classes_):\n                prob_dict&#91;cls] = probability&#91;i] * 100\n\n            # 6. \u663e\u793a\u7ed3\u679c\n            self.display_result(label, prob_dict, features)\n\n            # 7. \u66f4\u65b0\u72b6\u6001\n            self.status_label.config(text=f\"\u9884\u6d4b\u5b8c\u6210: {label} | \u7f6e\u4fe1\u5ea6: {probability&#91;prediction]*100:.1f}%\")\n\n        except Exception as e:\n            messagebox.showerror(\"\u9884\u6d4b\u9519\u8bef\", f\"\u9884\u6d4b\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u9519\u8bef:\\n{str(e)}\")\n            self.status_label.config(text=\"\u9884\u6d4b\u5931\u8d25\")\n\n    def display_result(self, label, probabilities, features):\n        \"\"\"\u663e\u793a\u9884\u6d4b\u7ed3\u679c\"\"\"\n        # \u7ed3\u679c\u989c\u8272\u6620\u5c04\n        color_map = {\n            '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa': '#27ae60',  # \u7eff\u8272\n            '\u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': '#f39c12',  # \u6a59\u8272\n            '\u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa': '#e74c3c',  # \u7ea2\u8272\n            '\u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': '#c0392b',  # \u6df1\u7ea2\n            '\u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa': '#8e44ad',  # \u7d2b\u8272\n            '\u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73': '#2c3e50'        # \u6df1\u7070\n        }\n\n        # \u56fe\u6807\u6620\u5c04\n        icon_map = {\n            '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa': '\u2705',\n            '\u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': '\u26a0\ufe0f',\n            '\u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa': '\ud83d\udeab',\n            '\u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': '\ud83d\udd34',\n            '\u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa': '\ud83d\udfe3',\n            '\u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73': '\ud83c\udfe0'\n        }\n\n        # \u8be6\u7ec6\u5efa\u8bae\u6620\u5c04\n        advice_map = {\n            '\u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa': \"\"\"\u2705 \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u6240\u6709\u4eba\u7fa4\u53ef\u6b63\u5e38\u8fdb\u884c\u6237\u5916\u6d3b\u52a8\n\u2022 \u9002\u5408\u8dd1\u6b65\u3001\u9a91\u884c\u3001\u7403\u7c7b\u7b49\u5404\u7c7b\u8fd0\u52a8\n\u2022 \u65e0\u9700\u7279\u6b8a\u9632\u62a4\u63aa\u65bd\n\u2022 \u5efa\u8bae\u5f00\u7a97\u901a\u98ce\uff0c\u4fdd\u6301\u5ba4\u5185\u7a7a\u6c14\u6d41\u901a\n\n\ud83c\udfaf \u9002\u7528\u4eba\u7fa4\uff1a\n\u2022 \u5065\u5eb7\u6210\u5e74\u4eba\n\u2022 \u513f\u7ae5\u548c\u8001\u4eba\n\u2022 \u5b55\u5987\n\u2022 \u547c\u5438\u9053\u75be\u75c5\u60a3\u8005\"\"\",\n\n            '\u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': \"\"\"\u26a0\ufe0f \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u654f\u611f\u4eba\u7fa4\u5e94\u51cf\u5c11\u957f\u65f6\u95f4\u3001\u9ad8\u5f3a\u5ea6\u7684\u6237\u5916\u6d3b\u52a8\n\u2022 \u5065\u5eb7\u4eba\u7fa4\u53ef\u6b63\u5e38\u6d3b\u52a8\uff0c\u4f46\u907f\u514d\u5267\u70c8\u8fd0\u52a8\n\u2022 \u5916\u51fa\u65f6\u53ef\u8003\u8651\u4f69\u6234\u53e3\u7f69\n\u2022 \u5efa\u8bae\u51cf\u5c11\u5f00\u7a97\u65f6\u95f4\n\n\ud83c\udfaf \u654f\u611f\u4eba\u7fa4\u5305\u62ec\uff1a\n\u2022 \u513f\u7ae5\u3001\u8001\u4eba\u3001\u5b55\u5987\n\u2022 \u54ee\u5598\u3001\u652f\u6c14\u7ba1\u708e\u60a3\u8005\n\u2022 \u5fc3\u810f\u75c5\u3001\u9ad8\u8840\u538b\u60a3\u8005\n\u2022 \u8fc7\u654f\u4f53\u8d28\u8005\"\"\",\n\n            '\u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa': \"\"\"\ud83d\udeab \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u654f\u611f\u4eba\u7fa4\u5e94\u5c3d\u91cf\u907f\u514d\u6237\u5916\u6d3b\u52a8\n\u2022 \u5065\u5eb7\u4eba\u7fa4\u51cf\u5c11\u6237\u5916\u6d3b\u52a8\u65f6\u95f4\n\u2022 \u5fc5\u987b\u5916\u51fa\u65f6\u8bf7\u4f69\u6234\u9632\u62a4\u53e3\u7f69\n\u2022 \u5173\u95ed\u95e8\u7a97\uff0c\u4f7f\u7528\u7a7a\u6c14\u51c0\u5316\u5668\n\u2022 \u907f\u514d\u6668\u7ec3\u7b49\u6237\u5916\u8fd0\u52a8\n\n\ud83c\udfe5 \u91cd\u70b9\u5173\u6ce8\uff1a\n\u2022 \u513f\u7ae5\u548c\u8001\u4eba\u5c3d\u91cf\u7559\u5728\u5ba4\u5185\n\u2022 \u547c\u5438\u9053\u75be\u75c5\u60a3\u8005\u907f\u514d\u5916\u51fa\n\u2022 \u5b66\u6821\u51cf\u5c11\u6237\u5916\u4f53\u80b2\u8bfe\"\"\",\n\n            '\u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa': \"\"\"\ud83d\udd34 \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u6240\u6709\u4eba\u7fa4\u5747\u5e94\u51cf\u5c11\u6237\u5916\u6d3b\u52a8\n\u2022 \u907f\u514d\u957f\u65f6\u95f4\u505c\u7559\u5728\u5ba4\u5916\n\u2022 \u5916\u51fa\u5fc5\u987b\u4f69\u6234\u6709\u6548\u9632\u62a4\u53e3\u7f69\n\u2022 \u5173\u95ed\u95e8\u7a97\uff0c\u5f00\u542f\u7a7a\u6c14\u51c0\u5316\n\u2022 \u53d6\u6d88\u6237\u5916\u6d3b\u52a8\u548c\u96c6\u4f1a\n\n\ud83d\udee1\ufe0f \u9632\u62a4\u63aa\u65bd\uff1a\n\u2022 N95\/KN95\u7ea7\u522b\u53e3\u7f69\n\u2022 \u5c3d\u91cf\u51cf\u5c11\u6237\u5916\u505c\u7559\u65f6\u95f4\n\u2022 \u56de\u5bb6\u540e\u53ca\u65f6\u6e05\u6d17\u9762\u90e8\u548c\u9f3b\u8154\"\"\",\n\n            '\u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa': \"\"\"\ud83d\udfe3 \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u513f\u7ae5\u3001\u8001\u4eba\u3001\u5b55\u5987\u52a1\u5fc5\u7559\u5728\u5ba4\u5185\n\u2022 \u5065\u5eb7\u6210\u4eba\u5c3d\u91cf\u51cf\u5c11\u4e0d\u5fc5\u8981\u5916\u51fa\n\u2022 \u5fc5\u987b\u5916\u51fa\u65f6\u505a\u597d\u5168\u9762\u9632\u62a4\n\u2022 \u5b66\u6821\u5e94\u505c\u6b62\u6240\u6709\u6237\u5916\u6d3b\u52a8\n\u2022 \u517b\u8001\u9662\u3001\u5e7c\u513f\u56ed\u52a0\u5f3a\u9632\u62a4\n\n\ud83d\udc76 \u7279\u6b8a\u4fdd\u62a4\uff1a\n\u2022 \u513f\u7ae5\u514d\u75ab\u529b\u8f83\u4f4e\uff0c\u6613\u53d7\u5f71\u54cd\n\u2022 \u8001\u4eba\u547c\u5438\u7cfb\u7edf\u8106\u5f31\uff0c\u98ce\u9669\u9ad8\n\u2022 \u6162\u6027\u75c5\u60a3\u8005\u9700\u683c\u5916\u6ce8\u610f\"\"\",\n\n            '\u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73': \"\"\"\ud83c\udfe0 \u5065\u5eb7\u5efa\u8bae\uff1a\n\u2022 \u6240\u6709\u4eba\u7fa4\u5e94\u907f\u514d\u6237\u5916\u6d3b\u52a8\n\u2022 \u5c3d\u91cf\u7559\u5728\u5ba4\u5185\uff0c\u5173\u95ed\u95e8\u7a97\n\u2022 \u5fc5\u987b\u5916\u51fa\u65f6\u505a\u597d\u6700\u9ad8\u7ea7\u522b\u9632\u62a4\n\u2022 \u4f7f\u7528\u9ad8\u6548\u7a7a\u6c14\u51c0\u5316\u5668\n\u2022 \u907f\u514d\u6240\u6709\u6237\u5916\u8fd0\u52a8\u548c\u6d3b\u52a8\n\n\ud83d\udea8 \u7d27\u6025\u63aa\u65bd\uff1a\n\u2022 \u5c3d\u91cf\u51cf\u5c11\u5916\u51fa\uff0c\u8fdc\u7a0b\u529e\u516c\/\u5b66\u4e60\n\u2022 \u5916\u51fa\u4f69\u6234\u4e13\u4e1a\u9632\u62a4\u8bbe\u5907\n\u2022 \u5bc6\u5207\u5173\u6ce8\u5065\u5eb7\u72b6\u51b5\n\u2022 \u5982\u6709\u4e0d\u9002\u53ca\u65f6\u5c31\u533b\"\"\"\n        }\n\n        # \u83b7\u53d6\u989c\u8272\u548c\u56fe\u6807\n        color = color_map.get(label, '#2c3e50')\n        icon = icon_map.get(label, '\ud83d\udcca')\n\n        # \u6784\u5efa\u7ed3\u679c\u663e\u793a\u6587\u672c\n        result_content = f\"\"\"{icon} \u3010\u5916\u51fa\u5efa\u8bae\u3011{icon}\n{label}\n\n\ud83d\udcc8 \u9884\u6d4b\u7f6e\u4fe1\u5ea6: {probabilities&#91;label]:.1f}%\n\n\ud83d\udcca \u6240\u6709\u53ef\u80fd\u6027:\n\"\"\"\n\n        # \u6309\u6982\u7387\u6392\u5e8f\n        sorted_probs = sorted(probabilities.items(), key=lambda x: x&#91;1], reverse=True)\n        for cls, prob in sorted_probs:\n            if prob &gt; 1:  # \u53ea\u663e\u793a\u6982\u7387\u5927\u4e8e1%\u7684\u7c7b\u522b\n                result_content += f\"  \u2022 {cls}: {prob:.1f}%\\n\"\n\n        result_content += f\"\\n\ud83d\udd22 \u5f53\u524d\u53c2\u6570\u503c:\\n\"\n        for param, value in zip(self.feature_columns, features):\n            result_content += f\"  \u2022 {param}: {value:.1f}\\n\"\n\n        # \u66f4\u65b0\u7ed3\u679c\u6587\u672c\u6846\n        self.result_text.config(state=tk.NORMAL)\n        self.result_text.delete(1.0, tk.END)\n        self.result_text.insert(1.0, result_content)\n\n        # \u8bbe\u7f6e\u6807\u9898\u989c\u8272\n        self.result_text.tag_add(\"title\", \"1.0\", \"3.0\")\n        self.result_text.tag_config(\"title\", foreground=color, font=('\u5fae\u8f6f\u96c5\u9ed1', 14, 'bold'))\n\n        self.result_text.config(state=tk.DISABLED)\n\n        # \u66f4\u65b0\u8be6\u7ec6\u5efa\u8bae\n        self.detail_text.config(state=tk.NORMAL)\n        self.detail_text.delete(1.0, tk.END)\n        self.detail_text.insert(1.0, advice_map.get(label, \"\u6682\u65e0\u8be6\u7ec6\u5efa\u8bae\"))\n        self.detail_text.config(state=tk.DISABLED)\n\ndef main():\n    \"\"\"\u4e3b\u51fd\u6570\"\"\"\n    root = tk.Tk()\n    app = AirQualityApp(root)\n\n    # \u8bbe\u7f6e\u7a97\u53e3\u5c45\u4e2d\n    root.update_idletasks()\n    width = root.winfo_width()\n    height = root.winfo_height()\n    x = (root.winfo_screenwidth() \/\/ 2) - (width \/\/ 2)\n    y = (root.winfo_screenheight() \/\/ 2) - (height \/\/ 2)\n    root.geometry(f'{width}x{height}+{x}+{y}')\n\n    # \u8bbe\u7f6e\u6700\u5c0f\u7a97\u53e3\u5927\u5c0f\n    root.minsize(900, 600)\n\n    root.mainloop()\n\nif __name__ == \"__main__\":\n    main()\n<\/code><\/pre>\n\n\n\n<p>\u6b64\u5904\u4e3aReadme\u5c55\u793a\uff08\u7531AI\u751f\u6210\uff09\uff1a<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">\u7a7a\u6c14\u8d28\u91cf\u5916\u51fa\u5efa\u8bae\u7cfb\u7edf\uff08readme.md\u59cb\uff09<\/h1>\n\n\n\n<p>\u57fa\u4e8e\u673a\u5668\u5b66\u4e60\u7684\u7a7a\u6c14\u8d28\u91cf\u5206\u6790\u4e0e\u4eba\u7fa4\u5065\u5eb7\u5efa\u8bae\u7cfb\u7edf\uff0c\u53ef\u6839\u636e\u6c61\u67d3\u7269\u6d53\u5ea6\u9884\u6d4b\u9002\u5b9c\u5916\u51fa\u4eba\u7fa4\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u529f\u80fd\u7279\u70b9<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6570\u636e\u5206\u6790<\/strong>\uff1a\u5bf9\u5386\u53f2\u7a7a\u6c14\u8d28\u91cf\u6570\u636e\u8fdb\u884c\u6df1\u5165\u5206\u6790<\/li>\n\n\n\n<li><strong>\u6a21\u578b\u8bad\u7ec3<\/strong>\uff1a\u4f7f\u7528\u968f\u673a\u68ee\u6797\u7b97\u6cd5\u8bad\u7ec3\u9884\u6d4b\u6a21\u578b<\/li>\n\n\n\n<li><strong>\u5b9e\u65f6\u9884\u6d4b<\/strong>\uff1a\u901a\u8fc7GUI\u754c\u9762\u5b9e\u65f6\u8c03\u6574\u53c2\u6570\u83b7\u53d6\u5efa\u8bae<\/li>\n\n\n\n<li><strong>\u5065\u5eb7\u5efa\u8bae<\/strong>\uff1a\u9488\u5bf9\u4e0d\u540c\u4eba\u7fa4\u63d0\u4f9b\u8be6\u7ec6\u5916\u51fa\u5efa\u8bae<\/li>\n\n\n\n<li><strong>\u53ef\u89c6\u5316<\/strong>\uff1a\u751f\u6210\u7279\u5f81\u91cd\u8981\u6027\u3001\u6df7\u6dc6\u77e9\u9635\u7b49\u5206\u6790\u56fe\u8868<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u5feb\u901f\u5f00\u59cb<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. \u73af\u5883\u914d\u7f6e<\/h3>\n\n\n\n<p>bash<br>\u514b\u9686\u6216\u4e0b\u8f7d\u9879\u76ee<br>git clone<br>cd air_quality_system<br>\u521b\u5efa\u865a\u62df\u73af\u5883\uff08\u63a8\u8350\uff09<br>python -m venv venv<br>\u6fc0\u6d3b\u865a\u62df\u73af\u5883<br>Windows:<br>venv\\Scripts\\activate<br>Linux\/Mac:<br>source venv\/bin\/activate<br>\u5b89\u88c5\u4f9d\u8d56<br>pip install -r requirements.txt<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. \u51c6\u5907\u6570\u636e<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u5c06\u60a8\u7684\u7a7a\u6c14\u8d28\u91cf\u6570\u636eCSV\u6587\u4ef6\uff08\u5982<code>cata_6716.csv<\/code>\uff09\u653e\u5165<code>data\/<\/code>\u76ee\u5f55<\/li>\n\n\n\n<li>\u786e\u4fdd\u6570\u636e\u5305\u542b\u4ee5\u4e0b\u5b57\u6bb5\uff08\u6216\u7c7b\u4f3c\u5b57\u6bb5\uff09\uff1a<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7ec6\u9897\u7c92\u7269 (PM2.5)<\/li>\n\n\n\n<li>\u53ef\u5438\u5165\u9897\u7c92\u7269 (PM10)<\/li>\n\n\n\n<li>\u4e8c\u6c27\u5316\u786b (SO2)<\/li>\n\n\n\n<li>\u4e8c\u6c27\u5316\u6c2e (NO2)<\/li>\n\n\n\n<li>\u4e00\u6c27\u5316\u78b3 (CO)<\/li>\n\n\n\n<li>\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747 (O3)<\/li>\n\n\n\n<li>\u7a7a\u6c14\u8d28\u91cf\u6307\u6570 (AQI)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. \u8bad\u7ec3\u6a21\u578b<\/h3>\n\n\n\n<p>bash<br>python train_model.py<\/p>\n\n\n\n<p>\u8bad\u7ec3\u8fc7\u7a0b\u5c06\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u52a0\u8f7d\u5e76\u9884\u5904\u7406\u6570\u636e<\/li>\n\n\n\n<li>\u8bad\u7ec3\u968f\u673a\u68ee\u6797\u6a21\u578b<\/li>\n\n\n\n<li>\u8bc4\u4f30\u6a21\u578b\u6027\u80fd<\/li>\n\n\n\n<li>\u4fdd\u5b58\u6a21\u578b\u5230<code>models\/<\/code>\u76ee\u5f55<\/li>\n\n\n\n<li>\u751f\u6210\u53ef\u89c6\u5316\u56fe\u8868<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">4. \u4f7f\u7528GUI\u5e94\u7528<\/h3>\n\n\n\n<p>bash<br>python predict_app.py<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6587\u4ef6\u7ed3\u6784<\/h2>\n\n\n\n<p>air_quality_system\/<br>\u251c\u2500\u2500 train_model.py # \u6a21\u578b\u8bad\u7ec3\u811a\u672c<br>\u251c\u2500\u2500 predict_app.py # GUI\u5e94\u7528\u754c\u9762<br>\u251c\u2500\u2500 data\/ # \u6570\u636e\u76ee\u5f55<br>\u2502 \u2514\u2500\u2500 cata_6716.csv # \u7a7a\u6c14\u8d28\u91cf\u6570\u636e<br>\u251c\u2500\u2500 models\/ # \u6a21\u578b\u4fdd\u5b58\u76ee\u5f55<br>\u2502 \u251c\u2500\u2500 air_quality_model.pkl # \u8bad\u7ec3\u597d\u7684\u6a21\u578b<br>\u2502 \u251c\u2500\u2500 feature_ranges.json # \u7279\u5f81\u8303\u56f4<br>\u2502 \u2514\u2500\u2500 model_metadata.json # \u6a21\u578b\u5143\u6570\u636e<br>\u251c\u2500\u2500 requirements.txt # Python\u4f9d\u8d56<br>\u251c\u2500\u2500 README.md # \u9879\u76ee\u8bf4\u660e<br>\u251c\u2500\u2500 feature_importance.png # \u7279\u5f81\u91cd\u8981\u6027\u56fe<br>\u2514\u2500\u2500 confusion_matrix.png # \u6df7\u6dc6\u77e9\u9635<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6a21\u578b\u8bf4\u660e<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u7b97\u6cd5<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u4f7f\u7528\u968f\u673a\u68ee\u6797\u5206\u7c7b\u5668<\/li>\n\n\n\n<li>\u7279\u5f81\u6807\u51c6\u5316\u5904\u7406<\/li>\n\n\n\n<li>\u7c7b\u522b\u5e73\u8861\u5904\u7406<\/li>\n\n\n\n<li>\u4ea4\u53c9\u9a8c\u8bc1\u8bc4\u4f30<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u9884\u6d4b\u7c7b\u522b<\/h3>\n\n\n\n<p>\u7cfb\u7edf\u53ef\u9884\u6d4b6\u79cd\u5916\u51fa\u5efa\u8bae\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u2705 \u5168\u90e8\u4eba\u7fa4\u9002\u5b9c\u5916\u51fa<\/li>\n\n\n\n<li>\u26a0\ufe0f \u654f\u611f\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa<\/li>\n\n\n\n<li>\ud83d\udeab \u654f\u611f\u4eba\u7fa4\u907f\u514d\u5916\u51fa<\/li>\n\n\n\n<li>\ud83d\udd34 \u6240\u6709\u4eba\u7fa4\u51cf\u5c11\u5916\u51fa<\/li>\n\n\n\n<li>\ud83d\udfe3 \u513f\u7ae5\u8001\u4eba\u907f\u514d\u5916\u51fa<\/li>\n\n\n\n<li>\ud83c\udfe0 \u5ba4\u5185\u6d3b\u52a8\u4e3a\u4f73<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">GUI\u754c\u9762\u529f\u80fd<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u53c2\u6570\u8c03\u8282<\/strong>\uff1a6\u4e2a\u6c61\u67d3\u7269\u6d53\u5ea6\u6ed1\u5757<\/li>\n\n\n\n<li><strong>\u5b9e\u65f6\u9884\u6d4b<\/strong>\uff1a\u70b9\u51fb\u83b7\u53d6\u5b9e\u65f6\u5efa\u8bae<\/li>\n\n\n\n<li><strong>\u7ed3\u679c\u5c55\u793a<\/strong>\uff1a\u9884\u6d4b\u7ed3\u679c\u548c\u7f6e\u4fe1\u5ea6<\/li>\n\n\n\n<li><strong>\u8be6\u7ec6\u5efa\u8bae<\/strong>\uff1a\u9488\u5bf9\u4e0d\u540c\u4eba\u7fa4\u7684\u5065\u5eb7\u5efa\u8bae<\/li>\n\n\n\n<li><strong>\u53c2\u6570\u91cd\u7f6e<\/strong>\uff1a\u4e00\u952e\u91cd\u7f6e\u6240\u6709\u53c2\u6570<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u6280\u672f\u7ec6\u8282<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u7279\u5f81\u5de5\u7a0b<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7279\u5f81\u6807\u51c6\u5316\uff1a\u4f7f\u7528StandardScaler<\/li>\n\n\n\n<li>\u7279\u5f81\u9009\u62e9\uff1a\u57fa\u4e8e\u91cd\u8981\u6027\u6392\u5e8f<\/li>\n\n\n\n<li>\u6807\u7b7e\u7f16\u7801\uff1a\u4f7f\u7528LabelEncoder<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u6a21\u578b\u8bc4\u4f30<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u51c6\u786e\u7387\u3001\u7cbe\u786e\u7387\u3001\u53ec\u56de\u7387\u3001F1\u5206\u6570<\/li>\n\n\n\n<li>\u6df7\u6dc6\u77e9\u9635\u5206\u6790<\/li>\n\n\n\n<li>\u7279\u5f81\u91cd\u8981\u6027\u5206\u6790<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u6570\u636e\u9884\u5904\u7406<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u7f3a\u5931\u503c\u5904\u7406<\/li>\n\n\n\n<li>\u5f02\u5e38\u503c\u68c0\u6d4b<\/li>\n\n\n\n<li>\u7279\u5f81\u8303\u56f4\u89c4\u8303\u5316<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u81ea\u5b9a\u4e49\u914d\u7f6e<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u4fee\u6539\u7279\u5f81<\/h3>\n\n\n\n<p>\u5728<code>train_model.py<\/code>\u4e2d\u4fee\u6539<code>feature_columns<\/code>\u5217\u8868\uff1a<\/p>\n\n\n\n<p>python<br>feature_columns = [<br>&#8216;\u7ec6\u9897\u7c92\u7269&#8217;, # PM2.5<br>&#8216;\u53ef\u5438\u5165\u9897\u7c92\u7269&#8217;, # PM10<br>&#8216;\u4e8c\u6c27\u5316\u786b&#8217;, # SO2<br>&#8216;\u4e8c\u6c27\u5316\u6c2e&#8217;, # NO2<br>&#8216;\u4e00\u6c27\u5316\u78b3&#8217;, # CO<br>&#8216;\u81ed\u6c271\u5c0f\u65f6\u5e73\u5747&#8217; # O3<br>]<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u8c03\u6574\u6a21\u578b\u53c2\u6570<\/h3>\n\n\n\n<p>\u5728<code>train_model.py<\/code>\u4e2d\u4fee\u6539RandomForestClassifier\u53c2\u6570\uff1a<\/p>\n\n\n\n<p>python<br>self.model = RandomForestClassifier(<br>n_estimators=200, # \u6811\u7684\u6570\u91cf<br>max_depth=15, # \u6811\u7684\u6700\u5927\u6df1\u5ea6<br>min_samples_split=5, # \u5206\u88c2\u6240\u9700\u6700\u5c0f\u6837\u672c\u6570<br>min_samples_leaf=2, # \u53f6\u8282\u70b9\u6700\u5c0f\u6837\u672c\u6570<br>random_state=42, # \u968f\u673a\u79cd\u5b50<br>class_weight=&#8217;balanced&#8217; # \u7c7b\u522b\u6743\u91cd<br>)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6ce8\u610f\u4e8b\u9879<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u6570\u636e\u8d28\u91cf<\/strong>\uff1a\u786e\u4fdd\u8f93\u5165\u6570\u636e\u8d28\u91cf\uff0c\u7f3a\u5931\u503c\u548c\u5f02\u5e38\u503c\u4f1a\u5f71\u54cd\u6a21\u578b\u6027\u80fd<\/li>\n\n\n\n<li><strong>\u6a21\u578b\u66f4\u65b0<\/strong>\uff1a\u5efa\u8bae\u5b9a\u671f\u7528\u65b0\u6570\u636e\u91cd\u65b0\u8bad\u7ec3\u6a21\u578b<\/li>\n\n\n\n<li><strong>\u5730\u57df\u5dee\u5f02<\/strong>\uff1a\u4e0d\u540c\u5730\u533a\u7684\u7a7a\u6c14\u8d28\u91cf\u7279\u5f81\u53ef\u80fd\u4e0d\u540c<\/li>\n\n\n\n<li><strong>\u5b9e\u65f6\u6027<\/strong>\uff1a\u6a21\u578b\u57fa\u4e8e\u5386\u53f2\u6570\u636e\uff0c\u5b9e\u65f6\u9884\u6d4b\u9700\u7ed3\u5408\u5b9e\u65f6\u76d1\u6d4b<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u6545\u969c\u6392\u9664<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u5e38\u89c1\u95ee\u9898<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>\u7f16\u7801\u9519\u8bef<\/strong>\uff1a\u5982\u679cCSV\u6587\u4ef6\u8bfb\u53d6\u5931\u8d25\uff0c\u5c1d\u8bd5\u4fee\u6539\u7f16\u7801<\/li>\n<\/ol>\n\n\n\n<p>python<br>\u5728train_model.py\u4e2d\u4fee\u6539<br>self.df = pd.read_csv(self.data_path, encoding=&#8217;gbk&#8217;) # \u6216&#8217;utf-8&#8242;<\/p>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><strong>\u7f3a\u5c11\u7279\u5f81<\/strong>\uff1a\u68c0\u67e5\u6570\u636e\u6587\u4ef6\u662f\u5426\u5305\u542b\u6240\u6709\u5fc5\u8981\u7279\u5f81<\/li>\n\n\n\n<li><strong>\u5185\u5b58\u4e0d\u8db3<\/strong>\uff1a\u51cf\u5c11\u6811\u7684\u6570\u91cf\u6216\u8c03\u6574\u5176\u4ed6\u53c2\u6570<\/li>\n\n\n\n<li><strong>GUI\u542f\u52a8\u5931\u8d25<\/strong>\uff1a\u786e\u4fdd\u5df2\u5b89\u88c5\u6240\u6709\u4f9d\u8d56\uff0c\u7279\u522b\u662ftkinter<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u9519\u8bef\u65e5\u5fd7<\/h3>\n\n\n\n<p>\u67e5\u770b\u63a7\u5236\u53f0\u8f93\u51fa\u83b7\u53d6\u8be6\u7ec6\u9519\u8bef\u4fe1\u606f\uff0c\u65e5\u5fd7\u5305\u542b\uff1a<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u6570\u636e\u5904\u7406\u72b6\u6001<\/li>\n\n\n\n<li>\u6a21\u578b\u8bad\u7ec3\u8fdb\u5ea6<\/li>\n\n\n\n<li>\u8bc4\u4f30\u7ed3\u679c<\/li>\n\n\n\n<li>\u6587\u4ef6\u4fdd\u5b58\u4f4d\u7f6e<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u6269\u5c55\u529f\u80fd<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">\u8ba1\u5212\u4e2d\u7684\u529f\u80fd<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u591a\u57ce\u5e02\u6570\u636e\u652f\u6301<\/li>\n\n\n\n<li>\u65f6\u95f4\u5e8f\u5217\u9884\u6d4b<\/li>\n\n\n\n<li>\u79fb\u52a8\u7aef\u5e94\u7528<\/li>\n\n\n\n<li>\u6570\u636e\u81ea\u52a8\u66f4\u65b0<\/li>\n\n\n\n<li>\u591a\u8bed\u8a00\u652f\u6301<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">\u81ea\u5b9a\u4e49\u6269\u5c55<\/h3>\n\n\n\n<p>\u60a8\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u65b9\u5f0f\u6269\u5c55\u7cfb\u7edf\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u6dfb\u52a0\u65b0\u7684\u6c61\u67d3\u7269\u7279\u5f81<\/li>\n\n\n\n<li>\u8c03\u6574\u5065\u5eb7\u5efa\u8bae\u6807\u51c6<\/li>\n\n\n\n<li>\u96c6\u6210\u5b9e\u65f6\u6570\u636eAPI<\/li>\n\n\n\n<li>\u6dfb\u52a0\u66f4\u591a\u53ef\u89c6\u5316\u56fe\u8868<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">\u8bb8\u53ef\u8bc1<\/h2>\n\n\n\n<p>\u672c\u9879\u76ee\u4ec5\u4f9b\u5b66\u4e60\u548c\u7814\u7a76\u4f7f\u7528\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u6280\u672f\u652f\u6301<\/h2>\n\n\n\n<p>\u5982\u6709\u95ee\u9898\u6216\u5efa\u8bae\uff0c\u8bf7\uff1a<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>\u68c0\u67e5\u9519\u8bef\u65e5\u5fd7<\/li>\n\n\n\n<li>\u53c2\u8003\u4ee3\u7801\u6ce8\u91ca<\/li>\n\n\n\n<li>\u786e\u4fdd\u73af\u5883\u914d\u7f6e\u6b63\u786e<\/li>\n\n\n\n<li>\u9a8c\u8bc1\u6570\u636e\u683c\u5f0f<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><strong>\u5f00\u59cb\u4f7f\u7528<\/strong>\uff1a\u6309\u7167&#8221;\u5feb\u901f\u5f00\u59cb&#8221;\u6b65\u9aa4\u64cd\u4f5c\uff0c\u51e0\u5206\u949f\u5185\u5373\u53ef\u8fd0\u884c\u5b8c\u6574\u7cfb\u7edf\uff01<\/p>\n\n\n\n<p>\u4e09\u3001\u4f7f\u7528\u6b65\u9aa4<br>\u6b65\u9aa41\uff1a\u521b\u5efa\u9879\u76ee\u7ed3\u6784<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">\u521b\u5efa\u9879\u76ee\u76ee\u5f55<\/h1>\n\n\n\n<p>mkdir air_quality_system<br>cd air_quality_system<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">\u521b\u5efa\u5b50\u76ee\u5f55<\/h1>\n\n\n\n<p>mkdir data<br>mkdir models<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">\u521b\u5efa\u4ee3\u7801\u6587\u4ef6<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">\u5c06\u4e0a\u9762\u7684\u4ee3\u7801\u5206\u522b\u4fdd\u5b58\u4e3a\u5bf9\u5e94\u7684\u6587\u4ef6\uff1a<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">train_model.py<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">predict_app.py<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">requirements.txt<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">README.md<\/h1>\n\n\n\n<p>\u6b65\u9aa42\uff1a\u653e\u7f6e\u6570\u636e\u6587\u4ef6<br>\u5c06\u60a8\u7684 cata_6716.csv\u6587\u4ef6\u653e\u5165 data\/\u76ee\u5f55<br>\u6b65\u9aa43\uff1a\u5b89\u88c5\u4f9d\u8d56<\/p>\n\n\n\n<p>pip install -r requirements.txt<\/p>\n\n\n\n<p>\u6b65\u9aa44\uff1a\u8bad\u7ec3\u6a21\u578b<\/p>\n\n\n\n<p>python train_model.py<\/p>\n\n\n\n<p>\u6b65\u9aa45\uff1a\u8fd0\u884cGUI\u5e94\u7528<\/p>\n\n\n\n<p>python predict_app.py<\/p>\n\n\n\n<p>\u56db\u3001\u6ce8\u610f\u4e8b\u9879<\/p>\n\n\n\n<p>\u7f16\u7801\u95ee\u9898\uff1a\u60a8\u7684CSV\u6587\u4ef6\u662fGBK\u7f16\u7801\uff0c\u4ee3\u7801\u4e2d\u5df2\u5904\u7406<br>\u7279\u5f81\u5bf9\u5e94\uff1a\u786e\u4fdd\u60a8\u7684\u6570\u636e\u5217\u540d\u4e0e\u4ee3\u7801\u4e2d\u7684\u7279\u5f81\u540d\u5339\u914d<br>\u8def\u5f84\u6b63\u786e\uff1a\u786e\u4fdd\u6570\u636e\u6587\u4ef6\u5728\u6b63\u786e\u8def\u5f84 data\/cata_6716.csv<br>\u4f9d\u8d56\u5b89\u88c5\uff1a\u5982\u679ctkinter\u6709\u95ee\u9898\uff0c\u53ef\u80fd\u9700\u8981\u5b89\u88c5\u7cfb\u7edf\u7ea7\u7684tkinter\u5305<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u6a21\u578b\u4fdd\u5b58\uff1a\u8bad\u7ec3\u5b8c\u6210\u540e\u4f1a\u5728models\u76ee\u5f55\u751f\u6210\u591a\u4e2a\u6587\u4ef6<\/code><\/pre>\n\n\n\n<p>\u8fd9\u4e2a\u5b8c\u6574\u7684\u4ee3\u7801\u5305\u53ef\u4ee5\u76f4\u63a5\u4f7f\u7528\uff0c\u5305\u542b\u4e86\u6570\u636e\u5904\u7406\u3001\u6a21\u578b\u8bad\u7ec3\u3001GUI\u754c\u9762\u7b49\u6240\u6709\u529f\u80fd\u3002\u8bad\u7ec3\u5b8c\u6210\u540e\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7GUI\u754c\u9762\u5b9e\u65f6\u8c03\u6574\u6c61\u67d3\u7269\u53c2\u6570\uff0c\u83b7\u53d6\u9488\u5bf9\u4e0d\u540c\u4eba\u7fa4\u7684\u5065\u5eb7\u5916\u51fa\u5efa\u8bae\u3002<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">readme.md\u7ec8<\/h1>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Main \u5176\u5b9eAI\u91cf\u7279\u522b\u9ad8\uff0c\u4f46\u662f\u4ee4\u4eba\u611f\u52a8\u7684\u662f\u8fd9\u4e2a\u4ee3\u7801\u7b2c\u4e00\u6b21\u8fd0\u884c\u6ca1\u6709\u62a5\u9519\u3002 \u56fe\u7247\u9884\u89c8 \u6587\u4ef6\u5206\u4eab \u4e3a\u4e86\u9632\u6b62\u672c\u7ad9\u88ab\u5237 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":363,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,22],"tags":[19,17,21,6],"class_list":["post-358","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","category-22","tag-python-2","tag-17","tag-21","tag-6"],"_links":{"self":[{"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/358","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/comments?post=358"}],"version-history":[{"count":5,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/358\/revisions"}],"predecessor-version":[{"id":371,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/posts\/358\/revisions\/371"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/media\/363"}],"wp:attachment":[{"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/media?parent=358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/categories?post=358"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.ntblog.cn\/index.php\/wp-json\/wp\/v2\/tags?post=358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}