Files
b2txt25/brain-to-text-25/brain-to-text-25 LGBM copy.ipynb
2025-10-06 15:17:44 +08:00

4212 lines
309 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 环境配置 与 utils"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%cd /kaggle/working/"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Looking in indexes: https://download.pytorch.org/whl/cu126\n",
"Requirement already satisfied: torch in /usr/local/lib/python3.11/dist-packages (2.6.0+cu124)\n",
"Requirement already satisfied: torchvision in /usr/local/lib/python3.11/dist-packages (0.21.0+cu124)\n",
"Requirement already satisfied: torchaudio in /usr/local/lib/python3.11/dist-packages (2.6.0+cu124)\n",
"Requirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from torch) (3.18.0)\n",
"Requirement already satisfied: typing-extensions>=4.10.0 in /usr/local/lib/python3.11/dist-packages (from torch) (4.14.0)\n",
"Requirement already satisfied: networkx in /usr/local/lib/python3.11/dist-packages (from torch) (3.5)\n",
"Requirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from torch) (3.1.6)\n",
"Requirement already satisfied: fsspec in /usr/local/lib/python3.11/dist-packages (from torch) (2025.5.1)\n",
"Collecting nvidia-cuda-nvrtc-cu12==12.4.127 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Collecting nvidia-cuda-runtime-cu12==12.4.127 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Collecting nvidia-cuda-cupti-cu12==12.4.127 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
"Collecting nvidia-cudnn-cu12==9.1.0.70 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
"Collecting nvidia-cublas-cu12==12.4.5.8 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Collecting nvidia-cufft-cu12==11.2.1.3 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Collecting nvidia-curand-cu12==10.3.5.147 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Collecting nvidia-cusolver-cu12==11.6.1.9 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
"Collecting nvidia-cusparse-cu12==12.3.1.170 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl.metadata (1.6 kB)\n",
"Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /usr/local/lib/python3.11/dist-packages (from torch) (0.6.2)\n",
"Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.11/dist-packages (from torch) (2.21.5)\n",
"Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch) (12.4.127)\n",
"Collecting nvidia-nvjitlink-cu12==12.4.127 (from torch)\n",
" Downloading https://download.pytorch.org/whl/cu126/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl.metadata (1.5 kB)\n",
"Requirement already satisfied: triton==3.2.0 in /usr/local/lib/python3.11/dist-packages (from torch) (3.2.0)\n",
"Requirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.11/dist-packages (from torch) (1.13.1)\n",
"Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.11/dist-packages (from sympy==1.13.1->torch) (1.3.0)\n",
"Requirement already satisfied: numpy in /usr/local/lib/python3.11/dist-packages (from torchvision) (1.26.4)\n",
"Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /usr/local/lib/python3.11/dist-packages (from torchvision) (11.2.1)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from jinja2->torch) (3.0.2)\n",
"Requirement already satisfied: mkl_fft in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (1.3.8)\n",
"Requirement already satisfied: mkl_random in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (1.2.4)\n",
"Requirement already satisfied: mkl_umath in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (0.1.1)\n",
"Requirement already satisfied: mkl in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (2025.2.0)\n",
"Requirement already satisfied: tbb4py in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (2022.2.0)\n",
"Requirement already satisfied: mkl-service in /usr/local/lib/python3.11/dist-packages (from numpy->torchvision) (2.4.1)\n",
"Requirement already satisfied: intel-openmp<2026,>=2024 in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torchvision) (2024.2.0)\n",
"Requirement already satisfied: tbb==2022.* in /usr/local/lib/python3.11/dist-packages (from mkl->numpy->torchvision) (2022.2.0)\n",
"Requirement already satisfied: tcmlib==1.* in /usr/local/lib/python3.11/dist-packages (from tbb==2022.*->mkl->numpy->torchvision) (1.4.0)\n",
"Requirement already satisfied: intel-cmplr-lib-rt in /usr/local/lib/python3.11/dist-packages (from mkl_umath->numpy->torchvision) (2024.2.0)\n",
"Requirement already satisfied: intel-cmplr-lib-ur==2024.2.0 in /usr/local/lib/python3.11/dist-packages (from intel-openmp<2026,>=2024->mkl->numpy->torchvision) (2024.2.0)\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl (363.4 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 363.4/363.4 MB 3.6 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (13.8 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.8/13.8 MB 4.9 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (24.6 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 24.6/24.6 MB 24.7 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (883 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 883.7/883.7 kB 9.6 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl (664.8 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 664.8/664.8 MB 2.0 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl (211.5 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 211.5/211.5 MB 2.6 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl (56.3 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 56.3/56.3 MB 22.7 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl (127.9 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 127.9/127.9 MB 6.3 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl (207.5 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 207.5/207.5 MB 4.5 MB/s eta 0:00:00\n",
"Downloading https://download.pytorch.org/whl/cu126/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl (21.1 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 21.1/21.1 MB 22.8 MB/s eta 0:00:00\n",
"Installing collected packages: nvidia-nvjitlink-cu12, nvidia-curand-cu12, nvidia-cufft-cu12, nvidia-cuda-runtime-cu12, nvidia-cuda-nvrtc-cu12, nvidia-cuda-cupti-cu12, nvidia-cublas-cu12, nvidia-cusparse-cu12, nvidia-cudnn-cu12, nvidia-cusolver-cu12\n",
" Attempting uninstall: nvidia-nvjitlink-cu12\n",
" Found existing installation: nvidia-nvjitlink-cu12 12.5.82\n",
" Uninstalling nvidia-nvjitlink-cu12-12.5.82:\n",
" Successfully uninstalled nvidia-nvjitlink-cu12-12.5.82\n",
" Attempting uninstall: nvidia-curand-cu12\n",
" Found existing installation: nvidia-curand-cu12 10.3.6.82\n",
" Uninstalling nvidia-curand-cu12-10.3.6.82:\n",
" Successfully uninstalled nvidia-curand-cu12-10.3.6.82\n",
" Attempting uninstall: nvidia-cufft-cu12\n",
" Found existing installation: nvidia-cufft-cu12 11.2.3.61\n",
" Uninstalling nvidia-cufft-cu12-11.2.3.61:\n",
" Successfully uninstalled nvidia-cufft-cu12-11.2.3.61\n",
" Attempting uninstall: nvidia-cuda-runtime-cu12\n",
" Found existing installation: nvidia-cuda-runtime-cu12 12.5.82\n",
" Uninstalling nvidia-cuda-runtime-cu12-12.5.82:\n",
" Successfully uninstalled nvidia-cuda-runtime-cu12-12.5.82\n",
" Attempting uninstall: nvidia-cuda-nvrtc-cu12\n",
" Found existing installation: nvidia-cuda-nvrtc-cu12 12.5.82\n",
" Uninstalling nvidia-cuda-nvrtc-cu12-12.5.82:\n",
" Successfully uninstalled nvidia-cuda-nvrtc-cu12-12.5.82\n",
" Attempting uninstall: nvidia-cuda-cupti-cu12\n",
" Found existing installation: nvidia-cuda-cupti-cu12 12.5.82\n",
" Uninstalling nvidia-cuda-cupti-cu12-12.5.82:\n",
" Successfully uninstalled nvidia-cuda-cupti-cu12-12.5.82\n",
" Attempting uninstall: nvidia-cublas-cu12\n",
" Found existing installation: nvidia-cublas-cu12 12.5.3.2\n",
" Uninstalling nvidia-cublas-cu12-12.5.3.2:\n",
" Successfully uninstalled nvidia-cublas-cu12-12.5.3.2\n",
" Attempting uninstall: nvidia-cusparse-cu12\n",
" Found existing installation: nvidia-cusparse-cu12 12.5.1.3\n",
" Uninstalling nvidia-cusparse-cu12-12.5.1.3:\n",
" Successfully uninstalled nvidia-cusparse-cu12-12.5.1.3\n",
" Attempting uninstall: nvidia-cudnn-cu12\n",
" Found existing installation: nvidia-cudnn-cu12 9.3.0.75\n",
" Uninstalling nvidia-cudnn-cu12-9.3.0.75:\n",
" Successfully uninstalled nvidia-cudnn-cu12-9.3.0.75\n",
" Attempting uninstall: nvidia-cusolver-cu12\n",
" Found existing installation: nvidia-cusolver-cu12 11.6.3.83\n",
" Uninstalling nvidia-cusolver-cu12-11.6.3.83:\n",
" Successfully uninstalled nvidia-cusolver-cu12-11.6.3.83\n",
"Successfully installed nvidia-cublas-cu12-12.4.5.8 nvidia-cuda-cupti-cu12-12.4.127 nvidia-cuda-nvrtc-cu12-12.4.127 nvidia-cuda-runtime-cu12-12.4.127 nvidia-cudnn-cu12-9.1.0.70 nvidia-cufft-cu12-11.2.1.3 nvidia-curand-cu12-10.3.5.147 nvidia-cusolver-cu12-11.6.1.9 nvidia-cusparse-cu12-12.3.1.170 nvidia-nvjitlink-cu12-12.4.127\n",
"Collecting jupyter==1.1.1\n",
" Downloading jupyter-1.1.1-py2.py3-none-any.whl.metadata (2.0 kB)\n",
"Requirement already satisfied: numpy<2.1.0,>=1.26.0 in /usr/local/lib/python3.11/dist-packages (1.26.4)\n",
"Collecting pandas==2.3.0\n",
" Downloading pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (91 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 91.2/91.2 kB 1.9 MB/s eta 0:00:00\n",
"Collecting matplotlib==3.10.1\n",
" Downloading matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (11 kB)\n",
"Collecting scipy==1.15.2\n",
" Downloading scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (61 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.0/62.0 kB 2.3 MB/s eta 0:00:00\n",
"Collecting scikit-learn==1.6.1\n",
" Downloading scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (18 kB)\n",
"Collecting lightgbm==4.3.0\n",
" Downloading lightgbm-4.3.0-py3-none-manylinux_2_28_x86_64.whl.metadata (19 kB)\n",
"Requirement already satisfied: tqdm==4.67.1 in /usr/local/lib/python3.11/dist-packages (4.67.1)\n",
"Collecting g2p_en==2.1.0\n",
" Downloading g2p_en-2.1.0-py3-none-any.whl.metadata (4.5 kB)\n",
"Collecting h5py==3.13.0\n",
" Downloading h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (2.5 kB)\n",
"Requirement already satisfied: omegaconf==2.3.0 in /usr/local/lib/python3.11/dist-packages (2.3.0)\n",
"Requirement already satisfied: editdistance==0.8.1 in /usr/local/lib/python3.11/dist-packages (0.8.1)\n",
"Requirement already satisfied: huggingface-hub==0.33.1 in /usr/local/lib/python3.11/dist-packages (0.33.1)\n",
"Collecting transformers==4.53.0\n",
" Downloading transformers-4.53.0-py3-none-any.whl.metadata (39 kB)\n",
"Requirement already satisfied: tokenizers==0.21.2 in /usr/local/lib/python3.11/dist-packages (0.21.2)\n",
"Requirement already satisfied: accelerate==1.8.1 in /usr/local/lib/python3.11/dist-packages (1.8.1)\n",
"Collecting bitsandbytes==0.46.0\n",
" Downloading bitsandbytes-0.46.0-py3-none-manylinux_2_24_x86_64.whl.metadata (10 kB)\n",
"Collecting seaborn==0.13.2\n",
" Downloading seaborn-0.13.2-py3-none-any.whl.metadata (5.4 kB)\n",
"Requirement already satisfied: notebook in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (6.5.4)\n",
"Requirement already satisfied: jupyter-console in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (6.1.0)\n",
"Requirement already satisfied: nbconvert in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (6.4.5)\n",
"Requirement already satisfied: ipykernel in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (6.17.1)\n",
"Requirement already satisfied: ipywidgets in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (8.1.5)\n",
"Requirement already satisfied: jupyterlab in /usr/local/lib/python3.11/dist-packages (from jupyter==1.1.1) (3.6.8)\n",
"Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.11/dist-packages (from pandas==2.3.0) (2.9.0.post0)\n",
"Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.11/dist-packages (from pandas==2.3.0) (2025.2)\n",
"Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.11/dist-packages (from pandas==2.3.0) (2025.2)\n",
"Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (1.3.2)\n",
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (0.12.1)\n",
"Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (4.58.4)\n",
"Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (1.4.8)\n",
"Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (25.0)\n",
"Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (11.2.1)\n",
"Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.11/dist-packages (from matplotlib==3.10.1) (3.0.9)\n",
"Requirement already satisfied: joblib>=1.2.0 in /usr/local/lib/python3.11/dist-packages (from scikit-learn==1.6.1) (1.5.1)\n",
"Requirement already satisfied: threadpoolctl>=3.1.0 in /usr/local/lib/python3.11/dist-packages (from scikit-learn==1.6.1) (3.6.0)\n",
"Requirement already satisfied: nltk>=3.2.4 in /usr/local/lib/python3.11/dist-packages (from g2p_en==2.1.0) (3.9.1)\n",
"Requirement already satisfied: inflect>=0.3.1 in /usr/local/lib/python3.11/dist-packages (from g2p_en==2.1.0) (7.5.0)\n",
"Collecting distance>=0.1.3 (from g2p_en==2.1.0)\n",
" Downloading Distance-0.1.3.tar.gz (180 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 180.3/180.3 kB 5.2 MB/s eta 0:00:00\n",
" Preparing metadata (setup.py): started\n",
" Preparing metadata (setup.py): finished with status 'done'\n",
"Requirement already satisfied: antlr4-python3-runtime==4.9.* in /usr/local/lib/python3.11/dist-packages (from omegaconf==2.3.0) (4.9.3)\n",
"Requirement already satisfied: PyYAML>=5.1.0 in /usr/local/lib/python3.11/dist-packages (from omegaconf==2.3.0) (6.0.2)\n",
"Requirement already satisfied: filelock in /usr/local/lib/python3.11/dist-packages (from huggingface-hub==0.33.1) (3.18.0)\n",
"Requirement already satisfied: fsspec>=2023.5.0 in /usr/local/lib/python3.11/dist-packages (from huggingface-hub==0.33.1) (2025.5.1)\n",
"Requirement already satisfied: requests in /usr/local/lib/python3.11/dist-packages (from huggingface-hub==0.33.1) (2.32.4)\n",
"Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.11/dist-packages (from huggingface-hub==0.33.1) (4.14.0)\n",
"Requirement already satisfied: hf-xet<2.0.0,>=1.1.2 in /usr/local/lib/python3.11/dist-packages (from huggingface-hub==0.33.1) (1.1.5)\n",
"Requirement already satisfied: regex!=2019.12.17 in /usr/local/lib/python3.11/dist-packages (from transformers==4.53.0) (2024.11.6)\n",
"Requirement already satisfied: safetensors>=0.4.3 in /usr/local/lib/python3.11/dist-packages (from transformers==4.53.0) (0.5.3)\n",
"Requirement already satisfied: psutil in /usr/local/lib/python3.11/dist-packages (from accelerate==1.8.1) (7.0.0)\n",
"Requirement already satisfied: torch>=2.0.0 in /usr/local/lib/python3.11/dist-packages (from accelerate==1.8.1) (2.6.0+cu124)\n",
"Requirement already satisfied: mkl_fft in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (1.3.8)\n",
"Requirement already satisfied: mkl_random in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (1.2.4)\n",
"Requirement already satisfied: mkl_umath in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (0.1.1)\n",
"Requirement already satisfied: mkl in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (2025.2.0)\n",
"Requirement already satisfied: tbb4py in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (2022.2.0)\n",
"Requirement already satisfied: mkl-service in /usr/local/lib/python3.11/dist-packages (from numpy<2.1.0,>=1.26.0) (2.4.1)\n",
"Requirement already satisfied: more_itertools>=8.5.0 in /usr/local/lib/python3.11/dist-packages (from inflect>=0.3.1->g2p_en==2.1.0) (10.7.0)\n",
"Requirement already satisfied: typeguard>=4.0.1 in /usr/local/lib/python3.11/dist-packages (from inflect>=0.3.1->g2p_en==2.1.0) (4.4.4)\n",
"Requirement already satisfied: click in /usr/local/lib/python3.11/dist-packages (from nltk>=3.2.4->g2p_en==2.1.0) (8.2.1)\n",
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/dist-packages (from python-dateutil>=2.8.2->pandas==2.3.0) (1.17.0)\n",
"Requirement already satisfied: networkx in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (3.5)\n",
"Requirement already satisfied: jinja2 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (3.1.6)\n",
"Requirement already satisfied: nvidia-cuda-nvrtc-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.127)\n",
"Requirement already satisfied: nvidia-cuda-runtime-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.127)\n",
"Requirement already satisfied: nvidia-cuda-cupti-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.127)\n",
"Requirement already satisfied: nvidia-cudnn-cu12==9.1.0.70 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (9.1.0.70)\n",
"Requirement already satisfied: nvidia-cublas-cu12==12.4.5.8 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.5.8)\n",
"Requirement already satisfied: nvidia-cufft-cu12==11.2.1.3 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (11.2.1.3)\n",
"Requirement already satisfied: nvidia-curand-cu12==10.3.5.147 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (10.3.5.147)\n",
"Requirement already satisfied: nvidia-cusolver-cu12==11.6.1.9 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (11.6.1.9)\n",
"Requirement already satisfied: nvidia-cusparse-cu12==12.3.1.170 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.3.1.170)\n",
"Requirement already satisfied: nvidia-cusparselt-cu12==0.6.2 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (0.6.2)\n",
"Requirement already satisfied: nvidia-nccl-cu12==2.21.5 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (2.21.5)\n",
"Requirement already satisfied: nvidia-nvtx-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.127)\n",
"Requirement already satisfied: nvidia-nvjitlink-cu12==12.4.127 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (12.4.127)\n",
"Requirement already satisfied: triton==3.2.0 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (3.2.0)\n",
"Requirement already satisfied: sympy==1.13.1 in /usr/local/lib/python3.11/dist-packages (from torch>=2.0.0->accelerate==1.8.1) (1.13.1)\n",
"Requirement already satisfied: mpmath<1.4,>=1.1.0 in /usr/local/lib/python3.11/dist-packages (from sympy==1.13.1->torch>=2.0.0->accelerate==1.8.1) (1.3.0)\n",
"Requirement already satisfied: debugpy>=1.0 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (1.8.0)\n",
"Requirement already satisfied: ipython>=7.23.1 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (7.34.0)\n",
"Requirement already satisfied: jupyter-client>=6.1.12 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (8.6.3)\n",
"Requirement already satisfied: matplotlib-inline>=0.1 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (0.1.7)\n",
"Requirement already satisfied: nest-asyncio in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (1.6.0)\n",
"Requirement already satisfied: pyzmq>=17 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (24.0.1)\n",
"Requirement already satisfied: tornado>=6.1 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (6.5.1)\n",
"Requirement already satisfied: traitlets>=5.1.0 in /usr/local/lib/python3.11/dist-packages (from ipykernel->jupyter==1.1.1) (5.7.1)\n",
"Requirement already satisfied: comm>=0.1.3 in /usr/local/lib/python3.11/dist-packages (from ipywidgets->jupyter==1.1.1) (0.2.2)\n",
"Requirement already satisfied: widgetsnbextension~=4.0.12 in /usr/local/lib/python3.11/dist-packages (from ipywidgets->jupyter==1.1.1) (4.0.14)\n",
"Requirement already satisfied: jupyterlab-widgets~=3.0.12 in /usr/local/lib/python3.11/dist-packages (from ipywidgets->jupyter==1.1.1) (3.0.15)\n",
"Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /usr/local/lib/python3.11/dist-packages (from jupyter-console->jupyter==1.1.1) (3.0.51)\n",
"Requirement already satisfied: pygments in /usr/local/lib/python3.11/dist-packages (from jupyter-console->jupyter==1.1.1) (2.19.2)\n",
"Requirement already satisfied: jupyter-core in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (5.8.1)\n",
"Requirement already satisfied: jupyterlab-server~=2.19 in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (2.27.3)\n",
"Requirement already satisfied: jupyter-server<3,>=1.16.0 in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (2.12.5)\n",
"Requirement already satisfied: jupyter-ydoc~=0.2.4 in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (0.2.5)\n",
"Requirement already satisfied: jupyter-server-ydoc~=0.8.0 in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (0.8.0)\n",
"Requirement already satisfied: nbclassic in /usr/local/lib/python3.11/dist-packages (from jupyterlab->jupyter==1.1.1) (1.3.1)\n",
"Requirement already satisfied: argon2-cffi in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (25.1.0)\n",
"Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (0.2.0)\n",
"Requirement already satisfied: nbformat in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (5.10.4)\n",
"Requirement already satisfied: Send2Trash>=1.8.0 in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (1.8.3)\n",
"Requirement already satisfied: terminado>=0.8.3 in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (0.18.1)\n",
"Requirement already satisfied: prometheus-client in /usr/local/lib/python3.11/dist-packages (from notebook->jupyter==1.1.1) (0.22.1)\n",
"Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.8.4)\n",
"Requirement already satisfied: jupyterlab-pygments in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.3.0)\n",
"Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.4)\n",
"Requirement already satisfied: bleach in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (6.2.0)\n",
"Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (1.5.1)\n",
"Requirement already satisfied: testpath in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.6.0)\n",
"Requirement already satisfied: defusedxml in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.7.1)\n",
"Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (4.13.4)\n",
"Requirement already satisfied: nbclient<0.6.0,>=0.5.0 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (0.5.13)\n",
"Requirement already satisfied: MarkupSafe>=2.0 in /usr/local/lib/python3.11/dist-packages (from nbconvert->jupyter==1.1.1) (3.0.2)\n",
"Requirement already satisfied: intel-openmp<2026,>=2024 in /usr/local/lib/python3.11/dist-packages (from mkl->numpy<2.1.0,>=1.26.0) (2024.2.0)\n",
"Requirement already satisfied: tbb==2022.* in /usr/local/lib/python3.11/dist-packages (from mkl->numpy<2.1.0,>=1.26.0) (2022.2.0)\n",
"Requirement already satisfied: tcmlib==1.* in /usr/local/lib/python3.11/dist-packages (from tbb==2022.*->mkl->numpy<2.1.0,>=1.26.0) (1.4.0)\n",
"Requirement already satisfied: intel-cmplr-lib-rt in /usr/local/lib/python3.11/dist-packages (from mkl_umath->numpy<2.1.0,>=1.26.0) (2024.2.0)\n",
"Requirement already satisfied: charset_normalizer<4,>=2 in /usr/local/lib/python3.11/dist-packages (from requests->huggingface-hub==0.33.1) (3.4.2)\n",
"Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/dist-packages (from requests->huggingface-hub==0.33.1) (3.10)\n",
"Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.11/dist-packages (from requests->huggingface-hub==0.33.1) (2.5.0)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.11/dist-packages (from requests->huggingface-hub==0.33.1) (2025.6.15)\n",
"Requirement already satisfied: intel-cmplr-lib-ur==2024.2.0 in /usr/local/lib/python3.11/dist-packages (from intel-openmp<2026,>=2024->mkl->numpy<2.1.0,>=1.26.0) (2024.2.0)\n",
"Requirement already satisfied: setuptools>=18.5 in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (75.2.0)\n",
"Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (0.19.2)\n",
"Requirement already satisfied: decorator in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (4.4.2)\n",
"Requirement already satisfied: pickleshare in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (0.7.5)\n",
"Requirement already satisfied: backcall in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (0.2.0)\n",
"Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.11/dist-packages (from ipython>=7.23.1->ipykernel->jupyter==1.1.1) (4.9.0)\n",
"Requirement already satisfied: platformdirs>=2.5 in /usr/local/lib/python3.11/dist-packages (from jupyter-core->jupyterlab->jupyter==1.1.1) (4.3.8)\n",
"Requirement already satisfied: anyio>=3.1.0 in /usr/local/lib/python3.11/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (4.9.0)\n",
"Requirement already satisfied: jupyter-events>=0.9.0 in /usr/local/lib/python3.11/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (0.12.0)\n",
"Requirement already satisfied: jupyter-server-terminals in /usr/local/lib/python3.11/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (0.5.3)\n",
"Requirement already satisfied: overrides in /usr/local/lib/python3.11/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (7.7.0)\n",
"Requirement already satisfied: websocket-client in /usr/local/lib/python3.11/dist-packages (from jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (1.8.0)\n",
"Requirement already satisfied: jupyter-server-fileid<1,>=0.6.0 in /usr/local/lib/python3.11/dist-packages (from jupyter-server-ydoc~=0.8.0->jupyterlab->jupyter==1.1.1) (0.9.3)\n",
"Requirement already satisfied: ypy-websocket<0.9.0,>=0.8.2 in /usr/local/lib/python3.11/dist-packages (from jupyter-server-ydoc~=0.8.0->jupyterlab->jupyter==1.1.1) (0.8.4)\n",
"Requirement already satisfied: y-py<0.7.0,>=0.6.0 in /usr/local/lib/python3.11/dist-packages (from jupyter-ydoc~=0.2.4->jupyterlab->jupyter==1.1.1) (0.6.2)\n",
"Requirement already satisfied: babel>=2.10 in /usr/local/lib/python3.11/dist-packages (from jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (2.17.0)\n",
"Requirement already satisfied: json5>=0.9.0 in /usr/local/lib/python3.11/dist-packages (from jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (0.12.0)\n",
"Requirement already satisfied: jsonschema>=4.18.0 in /usr/local/lib/python3.11/dist-packages (from jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (4.24.0)\n",
"Requirement already satisfied: notebook-shim>=0.2.3 in /usr/local/lib/python3.11/dist-packages (from nbclassic->jupyterlab->jupyter==1.1.1) (0.2.4)\n",
"Requirement already satisfied: fastjsonschema>=2.15 in /usr/local/lib/python3.11/dist-packages (from nbformat->notebook->jupyter==1.1.1) (2.21.1)\n",
"Requirement already satisfied: wcwidth in /usr/local/lib/python3.11/dist-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->jupyter-console->jupyter==1.1.1) (0.2.13)\n",
"Requirement already satisfied: ptyprocess in /usr/local/lib/python3.11/dist-packages (from terminado>=0.8.3->notebook->jupyter==1.1.1) (0.7.0)\n",
"Requirement already satisfied: argon2-cffi-bindings in /usr/local/lib/python3.11/dist-packages (from argon2-cffi->notebook->jupyter==1.1.1) (21.2.0)\n",
"Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.11/dist-packages (from beautifulsoup4->nbconvert->jupyter==1.1.1) (2.7)\n",
"Requirement already satisfied: webencodings in /usr/local/lib/python3.11/dist-packages (from bleach->nbconvert->jupyter==1.1.1) (0.5.1)\n",
"Requirement already satisfied: sniffio>=1.1 in /usr/local/lib/python3.11/dist-packages (from anyio>=3.1.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (1.3.1)\n",
"Requirement already satisfied: parso<0.9.0,>=0.8.4 in /usr/local/lib/python3.11/dist-packages (from jedi>=0.16->ipython>=7.23.1->ipykernel->jupyter==1.1.1) (0.8.4)\n",
"Requirement already satisfied: attrs>=22.2.0 in /usr/local/lib/python3.11/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (25.3.0)\n",
"Requirement already satisfied: jsonschema-specifications>=2023.03.6 in /usr/local/lib/python3.11/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (2025.4.1)\n",
"Requirement already satisfied: referencing>=0.28.4 in /usr/local/lib/python3.11/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (0.36.2)\n",
"Requirement already satisfied: rpds-py>=0.7.1 in /usr/local/lib/python3.11/dist-packages (from jsonschema>=4.18.0->jupyterlab-server~=2.19->jupyterlab->jupyter==1.1.1) (0.25.1)\n",
"Requirement already satisfied: python-json-logger>=2.0.4 in /usr/local/lib/python3.11/dist-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (3.3.0)\n",
"Requirement already satisfied: rfc3339-validator in /usr/local/lib/python3.11/dist-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (0.1.4)\n",
"Requirement already satisfied: rfc3986-validator>=0.1.1 in /usr/local/lib/python3.11/dist-packages (from jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (0.1.1)\n",
"Requirement already satisfied: aiofiles<23,>=22.1.0 in /usr/local/lib/python3.11/dist-packages (from ypy-websocket<0.9.0,>=0.8.2->jupyter-server-ydoc~=0.8.0->jupyterlab->jupyter==1.1.1) (22.1.0)\n",
"Requirement already satisfied: aiosqlite<1,>=0.17.0 in /usr/local/lib/python3.11/dist-packages (from ypy-websocket<0.9.0,>=0.8.2->jupyter-server-ydoc~=0.8.0->jupyterlab->jupyter==1.1.1) (0.21.0)\n",
"Requirement already satisfied: cffi>=1.0.1 in /usr/local/lib/python3.11/dist-packages (from argon2-cffi-bindings->argon2-cffi->notebook->jupyter==1.1.1) (1.17.1)\n",
"Requirement already satisfied: pycparser in /usr/local/lib/python3.11/dist-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook->jupyter==1.1.1) (2.22)\n",
"Requirement already satisfied: fqdn in /usr/local/lib/python3.11/dist-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (1.5.1)\n",
"Requirement already satisfied: isoduration in /usr/local/lib/python3.11/dist-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (20.11.0)\n",
"Requirement already satisfied: jsonpointer>1.13 in /usr/local/lib/python3.11/dist-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (3.0.0)\n",
"Requirement already satisfied: uri-template in /usr/local/lib/python3.11/dist-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (1.3.0)\n",
"Requirement already satisfied: webcolors>=24.6.0 in /usr/local/lib/python3.11/dist-packages (from jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (24.11.1)\n",
"Requirement already satisfied: arrow>=0.15.0 in /usr/local/lib/python3.11/dist-packages (from isoduration->jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (1.3.0)\n",
"Requirement already satisfied: types-python-dateutil>=2.8.10 in /usr/local/lib/python3.11/dist-packages (from arrow>=0.15.0->isoduration->jsonschema[format-nongpl]>=4.18.0->jupyter-events>=0.9.0->jupyter-server<3,>=1.16.0->jupyterlab->jupyter==1.1.1) (2.9.0.20250516)\n",
"Downloading jupyter-1.1.1-py2.py3-none-any.whl (2.7 kB)\n",
"Downloading pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.4 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.4/12.4 MB 72.3 MB/s eta 0:00:00\n",
"Downloading matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (8.6 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.6/8.6 MB 83.6 MB/s eta 0:00:00\n",
"Downloading scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (37.6 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 37.6/37.6 MB 41.4 MB/s eta 0:00:00\n",
"Downloading scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (13.5 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 13.5/13.5 MB 71.9 MB/s eta 0:00:00\n",
"Downloading lightgbm-4.3.0-py3-none-manylinux_2_28_x86_64.whl (3.1 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 56.5 MB/s eta 0:00:00\n",
"Downloading g2p_en-2.1.0-py3-none-any.whl (3.1 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.1/3.1 MB 55.2 MB/s eta 0:00:00\n",
"Downloading h5py-3.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.5 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.5/4.5 MB 60.9 MB/s eta 0:00:00\n",
"Downloading transformers-4.53.0-py3-none-any.whl (10.8 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.8/10.8 MB 70.9 MB/s eta 0:00:00\n",
"Downloading bitsandbytes-0.46.0-py3-none-manylinux_2_24_x86_64.whl (67.0 MB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 67.0/67.0 MB 23.0 MB/s eta 0:00:00\n",
"Downloading seaborn-0.13.2-py3-none-any.whl (294 kB)\n",
" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 294.9/294.9 kB 10.7 MB/s eta 0:00:00\n",
"Building wheels for collected packages: distance\n",
" Building wheel for distance (setup.py): started\n",
" Building wheel for distance (setup.py): finished with status 'done'\n",
" Created wheel for distance: filename=Distance-0.1.3-py3-none-any.whl size=16256 sha256=47c469031387c13df7e41975e1d8b073c7ad21ba646da84d8b4807b4713c12d2\n",
" Stored in directory: /root/.cache/pip/wheels/fb/cd/9c/3ab5d666e3bcacc58900b10959edd3816cc9557c7337986322\n",
"Successfully built distance\n",
"Installing collected packages: distance, jupyter, scipy, pandas, matplotlib, transformers, seaborn, scikit-learn, lightgbm, h5py, g2p_en, bitsandbytes\n",
" Attempting uninstall: scipy\n",
" Found existing installation: scipy 1.15.3\n",
" Uninstalling scipy-1.15.3:\n",
" Successfully uninstalled scipy-1.15.3\n",
" Attempting uninstall: pandas\n",
" Found existing installation: pandas 2.2.3\n",
" Uninstalling pandas-2.2.3:\n",
" Successfully uninstalled pandas-2.2.3\n",
" Attempting uninstall: matplotlib\n",
" Found existing installation: matplotlib 3.7.2\n",
" Uninstalling matplotlib-3.7.2:\n",
" Successfully uninstalled matplotlib-3.7.2\n",
" Attempting uninstall: transformers\n",
" Found existing installation: transformers 4.52.4\n",
" Uninstalling transformers-4.52.4:\n",
" Successfully uninstalled transformers-4.52.4\n",
" Attempting uninstall: seaborn\n",
" Found existing installation: seaborn 0.12.2\n",
" Uninstalling seaborn-0.12.2:\n",
" Successfully uninstalled seaborn-0.12.2\n",
" Attempting uninstall: scikit-learn\n",
" Found existing installation: scikit-learn 1.2.2\n",
" Uninstalling scikit-learn-1.2.2:\n",
" Successfully uninstalled scikit-learn-1.2.2\n",
" Attempting uninstall: lightgbm\n",
" Found existing installation: lightgbm 4.5.0\n",
" Uninstalling lightgbm-4.5.0:\n",
" Successfully uninstalled lightgbm-4.5.0\n",
" Attempting uninstall: h5py\n",
" Found existing installation: h5py 3.14.0\n",
" Uninstalling h5py-3.14.0:\n",
" Successfully uninstalled h5py-3.14.0\n",
"Successfully installed bitsandbytes-0.46.0 distance-0.1.3 g2p_en-2.1.0 h5py-3.13.0 jupyter-1.1.1 lightgbm-4.3.0 matplotlib-3.10.1 pandas-2.3.0 scikit-learn-1.6.1 scipy-1.15.2 seaborn-0.13.2 transformers-4.53.0\n",
"Obtaining file:///kaggle/working/nejm-brain-to-text\n",
" Preparing metadata (setup.py): started\n",
" Preparing metadata (setup.py): finished with status 'done'\n",
"Installing collected packages: nejm_b2txt_utils\n",
" Running setup.py develop for nejm_b2txt_utils\n",
"Successfully installed nejm_b2txt_utils-0.0.0\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"Cloning into 'nejm-brain-to-text'...\n",
"ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
"bigframes 2.8.0 requires google-cloud-bigquery-storage<3.0.0,>=2.30.0, which is not installed.\n",
"gensim 4.3.3 requires scipy<1.14.0,>=1.7.0, but you have scipy 1.15.2 which is incompatible.\n",
"dask-cudf-cu12 25.2.2 requires pandas<2.2.4dev0,>=2.0, but you have pandas 2.3.0 which is incompatible.\n",
"cudf-cu12 25.2.2 requires pandas<2.2.4dev0,>=2.0, but you have pandas 2.3.0 which is incompatible.\n",
"datasets 3.6.0 requires fsspec[http]<=2025.3.0,>=2023.1.0, but you have fsspec 2025.5.1 which is incompatible.\n",
"ydata-profiling 4.16.1 requires matplotlib<=3.10,>=3.5, but you have matplotlib 3.10.1 which is incompatible.\n",
"category-encoders 2.7.0 requires scikit-learn<1.6.0,>=1.0.0, but you have scikit-learn 1.6.1 which is incompatible.\n",
"cesium 0.12.4 requires numpy<3.0,>=2.0, but you have numpy 1.26.4 which is incompatible.\n",
"google-colab 1.0.0 requires google-auth==2.38.0, but you have google-auth 2.40.3 which is incompatible.\n",
"google-colab 1.0.0 requires notebook==6.5.7, but you have notebook 6.5.4 which is incompatible.\n",
"google-colab 1.0.0 requires pandas==2.2.2, but you have pandas 2.3.0 which is incompatible.\n",
"google-colab 1.0.0 requires requests==2.32.3, but you have requests 2.32.4 which is incompatible.\n",
"google-colab 1.0.0 requires tornado==6.4.2, but you have tornado 6.5.1 which is incompatible.\n",
"dopamine-rl 4.1.2 requires gymnasium>=1.0.0, but you have gymnasium 0.29.0 which is incompatible.\n",
"pandas-gbq 0.29.1 requires google-api-core<3.0.0,>=2.10.2, but you have google-api-core 1.34.1 which is incompatible.\n",
"bigframes 2.8.0 requires google-cloud-bigquery[bqstorage,pandas]>=3.31.0, but you have google-cloud-bigquery 3.25.0 which is incompatible.\n",
"bigframes 2.8.0 requires rich<14,>=12.4.4, but you have rich 14.0.0 which is incompatible.\n"
]
}
],
"source": [
"%%bash\n",
"rm -rf /kaggle/working/nejm-brain-to-text/\n",
"git clone https://github.com/ZH-CEN/nejm-brain-to-text.git\n",
"cp /kaggle/input/brain-to-text-baseline-model/t15_copyTask.pkl /kaggle/working/nejm-brain-to-text/data/t15_copyTask.pkl\n",
"\n",
"ln -s /kaggle/input/brain-to-text-25/t15_pretrained_rnn_baseline/t15_pretrained_rnn_baseline /kaggle/working/nejm-brain-to-text/data\n",
"ln -s /kaggle/input/brain-to-text-25/t15_copyTask_neuralData/hdf5_data_final /kaggle/working/nejm-brain-to-text/data\n",
"ln -s /kaggle/input/rnn-pretagged-data /kaggle/working/nejm-brain-to-text/data/concatenated_data\n",
"\n",
"pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126\n",
"\n",
"pip install \\\n",
" jupyter==1.1.1 \\\n",
" \"numpy>=1.26.0,<2.1.0\" \\\n",
" pandas==2.3.0 \\\n",
" matplotlib==3.10.1 \\\n",
" scipy==1.15.2 \\\n",
" scikit-learn==1.6.1 \\\n",
" lightgbm==4.3.0 \\\n",
" tqdm==4.67.1 \\\n",
" g2p_en==2.1.0 \\\n",
" h5py==3.13.0 \\\n",
" omegaconf==2.3.0 \\\n",
" editdistance==0.8.1 \\\n",
" huggingface-hub==0.33.1 \\\n",
" transformers==4.53.0 \\\n",
" tokenizers==0.21.2 \\\n",
" accelerate==1.8.1 \\\n",
" bitsandbytes==0.46.0 \\\n",
" seaborn==0.13.2\n",
"cd /kaggle/working/nejm-brain-to-text/\n",
"pip install -e ."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==================================================\n",
"🔧 LightGBM GPU环境检查\n",
"==================================================\n",
"✅ NVIDIA GPU检测:\n",
" Tesla P100-PCIE-16GB, 16384, 560.35.03\n",
"\n",
"✅ CUDA工具包:\n",
" Cuda compilation tools, release 12.5, V12.5.82\n",
"\n",
"🔍 LightGBM GPU支持选项:\n",
" 1. CUDA: NVIDIA GPU的主要支持方式\n",
" 2. OpenCL: 跨平台GPU支持(NVIDIA/AMD/Intel)\n",
" 3. 自动回退: GPU不可用时自动使用CPU\n",
"\n",
"📦 LightGBM GPU版本安装:\n",
" 方法1: pip install lightgbm --config-settings=cmake.define.USE_CUDA=ON\n",
" 方法2: conda install -c conda-forge lightgbm\n",
" 方法3: 使用预编译的GPU版本\n",
"\n",
"⚙️ GPU训练优化建议:\n",
" - 确保CUDA版本与GPU驱动兼容\n",
" - 监控GPU内存使用情况\n",
" - 调整max_bin参数优化GPU性能\n",
" - 使用合适的num_leaves数量\n",
"\n",
"💡 故障排除:\n",
" 如果GPU训练失败:\n",
" 1. 检查CUDA安装和版本\n",
" 2. 确认LightGBM是GPU版本\n",
" 3. 查看具体错误信息\n",
" 4. 代码会自动回退到CPU模式\n"
]
}
],
"source": [
"# 🚀 LightGBM GPU支持检查与配置\n",
"\n",
"print(\"=\"*50)\n",
"print(\"🔧 LightGBM GPU环境检查\")\n",
"print(\"=\"*50)\n",
"\n",
"# 检查CUDA和GPU驱动\n",
"import subprocess\n",
"import sys\n",
"\n",
"def run_command(command):\n",
" \"\"\"运行命令并返回结果\"\"\"\n",
" try:\n",
" result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=10)\n",
" return result.stdout.strip(), result.returncode == 0\n",
" except Exception as e:\n",
" return str(e), False\n",
"\n",
"# 检查NVIDIA GPU\n",
"nvidia_output, nvidia_success = run_command(\"nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader,nounits\")\n",
"if nvidia_success:\n",
" print(\"✅ NVIDIA GPU检测:\")\n",
" for line in nvidia_output.split('\\n'):\n",
" if line.strip():\n",
" print(f\" {line}\")\n",
"else:\n",
" print(\"❌ 未检测到NVIDIA GPU或驱动\")\n",
"\n",
"# 检查CUDA版本\n",
"cuda_output, cuda_success = run_command(\"nvcc --version\")\n",
"if cuda_success:\n",
" print(\"\\n✅ CUDA工具包:\")\n",
" # 提取CUDA版本\n",
" for line in cuda_output.split('\\n'):\n",
" if 'release' in line:\n",
" print(f\" {line.strip()}\")\n",
"else:\n",
" print(\"\\n❌ 未安装CUDA工具包\")\n",
"\n",
"# 检查OpenCL (LightGBM也支持OpenCL)\n",
"print(f\"\\n🔍 LightGBM GPU支持选项:\")\n",
"print(f\" 1. CUDA: NVIDIA GPU的主要支持方式\")\n",
"print(f\" 2. OpenCL: 跨平台GPU支持(NVIDIA/AMD/Intel)\")\n",
"print(f\" 3. 自动回退: GPU不可用时自动使用CPU\")\n",
"\n",
"# 安装说明\n",
"print(f\"\\n📦 LightGBM GPU版本安装:\")\n",
"print(f\" 方法1: pip install lightgbm --config-settings=cmake.define.USE_CUDA=ON\")\n",
"print(f\" 方法2: conda install -c conda-forge lightgbm\")\n",
"print(f\" 方法3: 使用预编译的GPU版本\")\n",
"\n",
"print(f\"\\n⚙ GPU训练优化建议:\")\n",
"print(f\" - 确保CUDA版本与GPU驱动兼容\")\n",
"print(f\" - 监控GPU内存使用情况\")\n",
"print(f\" - 调整max_bin参数优化GPU性能\")\n",
"print(f\" - 使用合适的num_leaves数量\")\n",
"\n",
"print(f\"\\n💡 故障排除:\")\n",
"print(f\" 如果GPU训练失败:\")\n",
"print(f\" 1. 检查CUDA安装和版本\")\n",
"print(f\" 2. 确认LightGBM是GPU版本\")\n",
"print(f\" 3. 查看具体错误信息\")\n",
"print(f\" 4. 代码会自动回退到CPU模式\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/kaggle/working/nejm-brain-to-text\n"
]
}
],
"source": [
"%cd /kaggle/working/nejm-brain-to-text\n",
"import numpy as np\n",
"import os\n",
"import pickle\n",
"import matplotlib.pyplot as plt\n",
"import matplotlib\n",
"from g2p_en import G2p\n",
"import pandas as pd\n",
"import numpy as np\n",
"from nejm_b2txt_utils.general_utils import *\n",
"\n",
"matplotlib.rcParams['pdf.fonttype'] = 42\n",
"matplotlib.rcParams['ps.fonttype'] = 42\n",
"matplotlib.rcParams['font.family'] = 'sans-serif'\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/kaggle/working/nejm-brain-to-text/model_training\n"
]
}
],
"source": [
"%cd model_training/\n",
"from data_augmentations import gauss_smooth\n",
"# single decoding step function that also returns smoothed input\n",
"# smooths data and puts it through the model, returning both logits and smoothed input.\n",
"def runSingleDecodingStepWithSmoothedInput(x, input_layer, model, model_args, device):\n",
"\n",
" # Use autocast for efficiency\n",
" with torch.autocast(device_type = \"cuda\", enabled = model_args['use_amp'], dtype = torch.bfloat16):\n",
"\n",
" smoothed_x = gauss_smooth(\n",
" inputs = x, \n",
" device = device,\n",
" smooth_kernel_std = model_args['dataset']['data_transforms']['smooth_kernel_std'],\n",
" smooth_kernel_size = model_args['dataset']['data_transforms']['smooth_kernel_size'],\n",
" padding = 'valid',\n",
" )\n",
"\n",
" with torch.no_grad():\n",
" logits, _ = model(\n",
" x = smoothed_x,\n",
" day_idx = torch.tensor([input_layer], device=device),\n",
" states = None, # no initial states\n",
" return_state = True,\n",
" )\n",
"\n",
" # convert both logits and smoothed input from bfloat16 to float32\n",
" logits = logits.float().cpu().numpy()\n",
" smoothed_input = smoothed_x.float().cpu().numpy()\n",
"\n",
" # # original order is [BLANK, phonemes..., SIL]\n",
" # # rearrange so the order is [BLANK, SIL, phonemes...]\n",
" # logits = rearrange_speech_logits_pt(logits)\n",
"\n",
" return logits, smoothed_input\n",
"\n",
"\n",
"import h5py\n",
"def load_h5py_file(file_path, b2txt_csv_df):\n",
" data = {\n",
" 'neural_features': [],\n",
" 'n_time_steps': [],\n",
" 'seq_class_ids': [],\n",
" 'seq_len': [],\n",
" 'transcriptions': [],\n",
" 'sentence_label': [],\n",
" 'session': [],\n",
" 'block_num': [],\n",
" 'trial_num': [],\n",
" 'corpus': [],\n",
" }\n",
" # Open the hdf5 file for that day\n",
" with h5py.File(file_path, 'r') as f:\n",
"\n",
" keys = list(f.keys())\n",
"\n",
" # For each trial in the selected trials in that day\n",
" for key in keys:\n",
" g = f[key]\n",
"\n",
" neural_features = g['input_features'][:] # pyright: ignore[reportIndexIssue]\n",
" n_time_steps = g.attrs['n_time_steps']\n",
" seq_class_ids = g['seq_class_ids'][:] if 'seq_class_ids' in g else None # type: ignore\n",
" seq_len = g.attrs['seq_len'] if 'seq_len' in g.attrs else None\n",
" transcription = g['transcription'][:] if 'transcription' in g else None # type: ignore\n",
" sentence_label = g.attrs['sentence_label'][:] if 'sentence_label' in g.attrs else None # pyright: ignore[reportIndexIssue]\n",
" session = g.attrs['session']\n",
" block_num = g.attrs['block_num']\n",
" trial_num = g.attrs['trial_num']\n",
"\n",
" # match this trial up with the csv to get the corpus name\n",
" year, month, day = session.split('.')[1:] # pyright: ignore[reportAttributeAccessIssue]\n",
" date = f'{year}-{month}-{day}'\n",
" row = b2txt_csv_df[(b2txt_csv_df['Date'] == date) & (b2txt_csv_df['Block number'] == block_num)]\n",
" corpus_name = row['Corpus'].values[0]\n",
"\n",
" data['neural_features'].append(neural_features)\n",
" data['n_time_steps'].append(n_time_steps)\n",
" data['seq_class_ids'].append(seq_class_ids)\n",
" data['seq_len'].append(seq_len)\n",
" data['transcriptions'].append(transcription)\n",
" data['sentence_label'].append(sentence_label)\n",
" data['session'].append(session)\n",
" data['block_num'].append(block_num)\n",
" data['trial_num'].append(trial_num)\n",
" data['corpus'].append(corpus_name)\n",
" return data"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"LOGIT_TO_PHONEME = [\n",
" 'BLANK',\n",
" 'AA', 'AE', 'AH', 'AO', 'AW',\n",
" 'AY', 'B', 'CH', 'D', 'DH',\n",
" 'EH', 'ER', 'EY', 'F', 'G',\n",
" 'HH', 'IH', 'IY', 'JH', 'K',\n",
" 'L', 'M', 'N', 'NG', 'OW',\n",
" 'OY', 'P', 'R', 'S', 'SH',\n",
" 'T', 'TH', 'UH', 'UW', 'V',\n",
" 'W', 'Y', 'Z', 'ZH',\n",
" ' | ',\n",
"]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 数据分析与预处理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 数据准备"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%cd /kaggle/working/nejm-brain-to-text/"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"data = load_h5py_file(file_path='/kaggle/working/nejm-brain-to-text/data/hdf5_data_final/t15.2023.08.11/data_train.hdf5',\n",
" b2txt_csv_df=pd.read_csv('/kaggle/working/nejm-brain-to-text/data/t15_copyTaskData_description.csv'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"data2 = load_h5py_file(file_path='/kaggle/working/nejm-brain-to-text/data/hdf5_data_final/t15.2023.08.13/data_train.hdf5',\n",
" b2txt_csv_df=pd.read_csv('/kaggle/working/nejm-brain-to-text/data/t15_copyTaskData_description.csv'))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- **任务介绍** :机器学习解决高维信号的模式识别问题"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们的数据集标签缺少时间戳,现在要进行的是半监督学习"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- 音素时间均等分割或者按照调研数据设定初始长度。然后筛掉异常值。提取出可用的训练集,再控制时间长短,查看样本类的长度"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def data_patch(data, index):\n",
" data_patch = {}\n",
" data_patch['neural_features'] = data['neural_features'][index]\n",
" data_patch['n_time_steps'] = data['n_time_steps'][index]\n",
" data_patch['seq_class_ids'] = data['seq_class_ids'][index]\n",
" data_patch['seq_len'] = data['seq_len'][index]\n",
" data_patch['transcriptions'] = data['transcriptions'][index]\n",
" data_patch['sentence_label'] = data['sentence_label'][index]\n",
" data_patch['session'] = data['session'][index]\n",
" data_patch['block_num'] = data['block_num'][index]\n",
" data_patch['trial_num'] = data['trial_num'][index]\n",
" data_patch['corpus'] = data['corpus'][index]\n",
" return data_patch"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'd1' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/3818271146.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtrans_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0md1\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'transcriptions'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mseq_len_nonzero\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mx\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mx\u001b[0m \u001b[0;32min\u001b[0m \u001b[0md1\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'seq_class_ids'\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0mseq_len\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0md1\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'seq_len'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Transcriptions non-zero length: {trans_len}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"Seq class ids non-zero length: {seq_len_nonzero}\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'd1' is not defined"
]
}
],
"source": [
"trans_len = len([x for x in d1['transcriptions'] if x != 0])\n",
"seq_len_nonzero = len([x for x in d1['seq_class_ids'] if x != 0])\n",
"seq_len = d1['seq_len']\n",
"print(f\"Transcriptions non-zero length: {trans_len}\")\n",
"print(f\"Seq class ids non-zero length: {seq_len_nonzero}\")\n",
"print(f\"Seq len: {seq_len}\")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'd1' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/1034715934.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0;31m# Example usage\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m \u001b[0mfeature_sequences\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mcreate_time_windows\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0md1\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 19\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Number of feature sequences:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfeature_sequences\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Shape of first sequence:\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfeature_sequences\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'd1' is not defined"
]
}
],
"source": [
"def create_time_windows(d1):\n",
" import numpy as np\n",
" n_time_steps = d1['n_time_steps']\n",
" seq_len = d1['seq_len']\n",
" # Create equal windows\n",
" edges = np.linspace(0, n_time_steps, seq_len + 1, dtype=int)\n",
" windows = [(edges[i], edges[i+1]) for i in range(seq_len)]\n",
" \n",
" # Extract feature sequences for each window\n",
" feature_sequences = []\n",
" for start, end in windows:\n",
" seq = d1['neural_features'][start:end, :]\n",
" feature_sequences.append(seq)\n",
" \n",
" return feature_sequences\n",
"\n",
"# Example usage\n",
"feature_sequences = create_time_windows(d1)\n",
"print(\"Number of feature sequences:\", len(feature_sequences))\n",
"print(\"Shape of first sequence:\", feature_sequences[0].shape)\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Train: 0, Val: 0, Test: 0\n",
"Train files (first 3): []\n",
"Val files (first 3): []\n",
"Test files (first 3): []\n"
]
}
],
"source": [
"import os\n",
"\n",
"def scan_hdf5_files(base_path):\n",
" train_files = []\n",
" val_files = []\n",
" test_files = []\n",
" for root, dirs, files in os.walk(base_path):\n",
" for file in files:\n",
" if file.endswith('.hdf5'):\n",
" abs_path = os.path.abspath(os.path.join(root, file))\n",
" if 'data_train.hdf5' in file:\n",
" train_files.append(abs_path)\n",
" elif 'data_val.hdf5' in file:\n",
" val_files.append(abs_path)\n",
" elif 'data_test.hdf5' in file:\n",
" test_files.append(abs_path)\n",
" return train_files, val_files, test_files\n",
"\n",
"# Example usage\n",
"FILE_PATH = 'data/hdf5_data_final'\n",
"train_list, val_list, test_list = scan_hdf5_files(FILE_PATH)\n",
"print(f\"Train: {len(train_list)}, Val: {len(val_list)}, Test: {len(test_list)}\")\n",
"print(\"Train files (first 3):\", train_list[:3])\n",
"print(\"Val files (first 3):\", val_list[:3])\n",
"print(\"Test files (first 3):\", test_list[:3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 数据读取工作流"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"📊 数据文件统计:\n",
" 训练文件: 45\n",
" 验证文件: 41\n",
" 测试文件: 41\n",
" 每文件最大样本数: 3000\n",
"\n",
"🔧 初始化全局PCA...\n",
"\n",
"🔧 拟合全局PCA降维器...\n",
" 配置: {'enable_pca': True, 'n_components': None, 'variance_threshold': 0.95, 'sample_size': 15000}\n",
" 正在加载文件 1/45: t15.2025.04.13_train_concatenated.npz\n",
" 正在加载文件 2/45: t15.2024.07.21_train_concatenated.npz\n",
" 正在加载文件 2/45: t15.2024.07.21_train_concatenated.npz\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/650628148.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 179\u001b[0m \u001b[0;31m# 🔧 初始化全局PCA (只在训练集上拟合一次)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 180\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\"\\n🔧 初始化全局PCA...\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 181\u001b[0;31m \u001b[0mfit_global_pca\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata_dir\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mPCA_CONFIG\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 182\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 183\u001b[0m \u001b[0;31m# 内存友好的数据加载策略 (带PCA集成)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/tmp/ipykernel_36/650628148.py\u001b[0m in \u001b[0;36mfit_global_pca\u001b[0;34m(data_dir, config)\u001b[0m\n\u001b[1;32m 90\u001b[0m \u001b[0mcollected_samples\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 91\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 92\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mtrials_batch\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfilename\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mload_data_batch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata_dir\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'train'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 93\u001b[0m \u001b[0mfeatures\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mlabels\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mextract_features_labels_batch\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrials_batch\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 94\u001b[0m \u001b[0msample_features\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfeatures\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/tmp/ipykernel_36/650628148.py\u001b[0m in \u001b[0;36mload_data_batch\u001b[0;34m(data_dir, data_type, max_samples_per_file)\u001b[0m\n\u001b[1;32m 43\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 44\u001b[0m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mos\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpath\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjoin\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata_dir\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mf\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mallow_pickle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mTrue\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 45\u001b[0;31m \u001b[0mtrials\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'neural_logits_concatenated'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 46\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 47\u001b[0m \u001b[0;31m# 限制每个文件的样本数\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/numpy/lib/npyio.py\u001b[0m in \u001b[0;36m__getitem__\u001b[0;34m(self, key)\u001b[0m\n\u001b[1;32m 254\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmagic\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mformat\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMAGIC_PREFIX\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 255\u001b[0m \u001b[0mbytes\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mzip\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 256\u001b[0;31m return format.read_array(bytes,\n\u001b[0m\u001b[1;32m 257\u001b[0m \u001b[0mallow_pickle\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallow_pickle\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 258\u001b[0m \u001b[0mpickle_kwargs\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpickle_kwargs\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/numpy/lib/format.py\u001b[0m in \u001b[0;36mread_array\u001b[0;34m(fp, allow_pickle, pickle_kwargs, max_header_size)\u001b[0m\n\u001b[1;32m 798\u001b[0m \u001b[0mpickle_kwargs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m{\u001b[0m\u001b[0;34m}\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 799\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 800\u001b[0;31m \u001b[0marray\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpickle\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfp\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mpickle_kwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 801\u001b[0m \u001b[0;32mexcept\u001b[0m \u001b[0mUnicodeError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0merr\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 802\u001b[0m \u001b[0;31m# Friendlier error message\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib/python3.11/zipfile.py\u001b[0m in \u001b[0;36mread\u001b[0;34m(self, n)\u001b[0m\n\u001b[1;32m 964\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_offset\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 965\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_eof\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 966\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_read1\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 967\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 968\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_readbuffer\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mdata\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/lib/python3.11/zipfile.py\u001b[0m in \u001b[0;36m_read1\u001b[0;34m(self, n)\u001b[0m\n\u001b[1;32m 1040\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compress_type\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mZIP_DEFLATED\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1041\u001b[0m \u001b[0mn\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmax\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMIN_READ_SIZE\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1042\u001b[0;31m \u001b[0mdata\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_decompressor\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdecompress\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1043\u001b[0m self._eof = (self._decompressor.eof or\n\u001b[1;32m 1044\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_compress_left\u001b[0m \u001b[0;34m<=\u001b[0m \u001b[0;36m0\u001b[0m \u001b[0;32mand\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"# 🚀 内存友好的数据读取 - 分批加载策略 + PCA降维\n",
"\n",
"import os\n",
"import numpy as np\n",
"import gc\n",
"from sklearn.decomposition import PCA\n",
"from sklearn.preprocessing import StandardScaler\n",
"import joblib\n",
"import matplotlib.pyplot as plt\n",
"\n",
"# 全局PCA配置\n",
"PCA_CONFIG = {\n",
" 'enable_pca': True, # 是否启用PCA\n",
" 'n_components': None, # None=自动选择, 或指定具体数值\n",
" 'variance_threshold': 0.95, # 保留95%的方差\n",
" 'sample_size': 15000, # 用于拟合PCA的样本数\n",
"}\n",
"\n",
"# 全局PCA对象 (确保只拟合一次)\n",
"GLOBAL_PCA = {\n",
" 'scaler': None,\n",
" 'pca': None,\n",
" 'is_fitted': False,\n",
" 'n_components': None\n",
"}\n",
"\n",
"def load_data_batch(data_dir, data_type, max_samples_per_file=5000):\n",
" \"\"\"\n",
" 分批加载指定类型的数据\n",
" \n",
" Args:\n",
" data_dir: 数据目录\n",
" data_type: 'train', 'val', 'test'\n",
" max_samples_per_file: 每个文件最大加载样本数\n",
" \n",
" Returns:\n",
" generator: 数据批次生成器\n",
" \"\"\"\n",
" files = [f for f in os.listdir(data_dir) if f.endswith('.npz') and data_type in f]\n",
" \n",
" for file_idx, f in enumerate(files):\n",
" print(f\" 正在加载文件 {file_idx+1}/{len(files)}: {f}\")\n",
" \n",
" data = np.load(os.path.join(data_dir, f), allow_pickle=True)\n",
" trials = data['neural_logits_concatenated']\n",
" \n",
" # 限制每个文件的样本数\n",
" if len(trials) > max_samples_per_file:\n",
" trials = trials[:max_samples_per_file]\n",
" print(f\" 限制样本数至: {max_samples_per_file}\")\n",
" \n",
" yield trials, f\n",
" \n",
" # 清理内存\n",
" del data, trials\n",
" gc.collect()\n",
"\n",
"def extract_features_labels_batch(trials_batch):\n",
" \"\"\"\n",
" 从试验批次中提取特征和标签\n",
" \"\"\"\n",
" features = []\n",
" labels = []\n",
" \n",
" for trial in trials_batch:\n",
" if trial.shape[0] > 0:\n",
" for t in range(trial.shape[0]):\n",
" neural_features = trial[t, :7168] # 前7168维神经特征\n",
" rnn_logits = trial[t, 7168:] # 后41维RNN输出\n",
" phoneme_label = np.argmax(rnn_logits)\n",
" \n",
" features.append(neural_features)\n",
" labels.append(phoneme_label)\n",
" \n",
" return np.array(features), np.array(labels)\n",
"\n",
"def fit_global_pca(data_dir, config):\n",
" \"\"\"\n",
" 在训练数据上拟合全局PCA (只执行一次)\n",
" \"\"\"\n",
" if GLOBAL_PCA['is_fitted'] or not config['enable_pca']:\n",
" print(\"🔧 PCA已拟合或未启用跳过拟合步骤\")\n",
" return\n",
" \n",
" print(f\"\\n🔧 拟合全局PCA降维器...\")\n",
" print(f\" 配置: {config}\")\n",
" \n",
" # 收集训练样本\n",
" sample_features = []\n",
" collected_samples = 0\n",
" \n",
" for trials_batch, filename in load_data_batch(data_dir, 'train', 5000):\n",
" features, labels = extract_features_labels_batch(trials_batch)\n",
" sample_features.append(features)\n",
" collected_samples += features.shape[0]\n",
" \n",
" if collected_samples >= config['sample_size']:\n",
" break\n",
" \n",
" if sample_features:\n",
" # 合并样本数据\n",
" X_sample = np.vstack(sample_features)[:config['sample_size']]\n",
" print(f\" 实际样本数: {X_sample.shape[0]}\")\n",
" print(f\" 原始特征数: {X_sample.shape[1]}\")\n",
" \n",
" # 标准化\n",
" GLOBAL_PCA['scaler'] = StandardScaler()\n",
" X_sample_scaled = GLOBAL_PCA['scaler'].fit_transform(X_sample)\n",
" \n",
" # 确定PCA成分数\n",
" if config['n_components'] is None:\n",
" print(f\" 🔍 自动选择PCA成分数...\")\n",
" pca_full = PCA()\n",
" pca_full.fit(X_sample_scaled)\n",
" \n",
" cumsum_var = np.cumsum(pca_full.explained_variance_ratio_)\n",
" optimal_components = np.argmax(cumsum_var >= config['variance_threshold']) + 1\n",
" GLOBAL_PCA['n_components'] = min(optimal_components, X_sample.shape[1])\n",
" \n",
" print(f\" 保留{config['variance_threshold']*100}%方差需要: {optimal_components} 个成分\")\n",
" print(f\" 选择成分数: {GLOBAL_PCA['n_components']}\")\n",
" else:\n",
" GLOBAL_PCA['n_components'] = config['n_components']\n",
" print(f\" 使用指定成分数: {GLOBAL_PCA['n_components']}\")\n",
" \n",
" # 拟合最终PCA\n",
" GLOBAL_PCA['pca'] = PCA(n_components=GLOBAL_PCA['n_components'], random_state=42)\n",
" GLOBAL_PCA['pca'].fit(X_sample_scaled)\n",
" GLOBAL_PCA['is_fitted'] = True\n",
" \n",
" # 保存模型\n",
" pca_path = \"global_pca_model.joblib\"\n",
" joblib.dump({\n",
" 'scaler': GLOBAL_PCA['scaler'], \n",
" 'pca': GLOBAL_PCA['pca'],\n",
" 'n_components': GLOBAL_PCA['n_components']\n",
" }, pca_path)\n",
" \n",
" print(f\" ✅ 全局PCA拟合完成!\")\n",
" print(f\" 降维: {X_sample.shape[1]} → {GLOBAL_PCA['n_components']}\")\n",
" print(f\" 降维比例: {GLOBAL_PCA['n_components']/X_sample.shape[1]:.2%}\")\n",
" print(f\" 保留方差: {GLOBAL_PCA['pca'].explained_variance_ratio_.sum():.4f}\")\n",
" print(f\" 模型已保存: {pca_path}\")\n",
" \n",
" # 清理样本数据\n",
" del sample_features, X_sample, X_sample_scaled\n",
" gc.collect()\n",
" else:\n",
" print(\"❌ 无法收集样本数据用于PCA拟合\")\n",
"\n",
"def apply_pca_transform(features):\n",
" \"\"\"\n",
" 应用全局PCA变换\n",
" \"\"\"\n",
" if not PCA_CONFIG['enable_pca'] or not GLOBAL_PCA['is_fitted']:\n",
" return features\n",
" \n",
" # 标准化 + PCA变换\n",
" features_scaled = GLOBAL_PCA['scaler'].transform(features)\n",
" features_pca = GLOBAL_PCA['pca'].transform(features_scaled)\n",
" return features_pca\n",
"\n",
"# 设置数据目录和参数\n",
"data_dir = '/kaggle/working/nejm-brain-to-text/data/concatenated_data'\n",
"MAX_SAMPLES_PER_FILE = 3000 # 每个文件最大样本数,可调整\n",
"\n",
"# 检查可用文件\n",
"all_files = [f for f in os.listdir(data_dir) if f.endswith('.npz')]\n",
"train_files = [f for f in all_files if 'train' in f]\n",
"val_files = [f for f in all_files if 'val' in f]\n",
"test_files = [f for f in all_files if 'test' in f]\n",
"\n",
"print(f\"📊 数据文件统计:\")\n",
"print(f\" 训练文件: {len(train_files)}\")\n",
"print(f\" 验证文件: {len(val_files)}\")\n",
"print(f\" 测试文件: {len(test_files)}\")\n",
"print(f\" 每文件最大样本数: {MAX_SAMPLES_PER_FILE}\")\n",
"\n",
"# 🔧 初始化全局PCA (只在训练集上拟合一次)\n",
"print(f\"\\n🔧 初始化全局PCA...\")\n",
"fit_global_pca(data_dir, PCA_CONFIG)\n",
"\n",
"# 内存友好的数据加载策略 (带PCA集成)\n",
"class MemoryFriendlyDataset:\n",
" def __init__(self, data_dir, data_type, max_samples_per_file=3000):\n",
" self.data_dir = data_dir\n",
" self.data_type = data_type\n",
" self.max_samples_per_file = max_samples_per_file\n",
" self.files = [f for f in os.listdir(data_dir) if f.endswith('.npz') and data_type in f]\n",
" \n",
" def load_all_data(self):\n",
" \"\"\"一次性加载所有数据自动应用PCA\"\"\"\n",
" print(f\"\\n🔄 加载{self.data_type}数据...\")\n",
" all_features = []\n",
" all_labels = []\n",
" \n",
" for trials_batch, filename in load_data_batch(self.data_dir, self.data_type, self.max_samples_per_file):\n",
" features, labels = extract_features_labels_batch(trials_batch)\n",
" \n",
" # 应用PCA降维\n",
" features_processed = apply_pca_transform(features)\n",
" \n",
" all_features.append(features_processed)\n",
" all_labels.append(labels)\n",
" \n",
" if all_features:\n",
" X = np.vstack(all_features)\n",
" y = np.hstack(all_labels)\n",
" \n",
" # 清理临时数据\n",
" del all_features, all_labels\n",
" gc.collect()\n",
" \n",
" feature_info = f\"{X.shape[1]} PCA特征\" if PCA_CONFIG['enable_pca'] else f\"{X.shape[1]} 原始特征\"\n",
" print(f\" ✅ 加载完成: {X.shape[0]} 样本, {feature_info}\")\n",
" return X, y\n",
" else:\n",
" return None, None\n",
" \n",
" def get_batch_generator(self):\n",
" \"\"\"返回批次生成器自动应用PCA\"\"\"\n",
" for trials_batch, filename in load_data_batch(self.data_dir, self.data_type, self.max_samples_per_file):\n",
" features, labels = extract_features_labels_batch(trials_batch)\n",
" \n",
" # 应用PCA降维\n",
" features_processed = apply_pca_transform(features)\n",
" \n",
" yield features_processed, labels\n",
"\n",
"# 创建数据集对象\n",
"train_dataset = MemoryFriendlyDataset(data_dir, 'train', MAX_SAMPLES_PER_FILE)\n",
"val_dataset = MemoryFriendlyDataset(data_dir, 'val', MAX_SAMPLES_PER_FILE)\n",
"test_dataset = MemoryFriendlyDataset(data_dir, 'test', MAX_SAMPLES_PER_FILE)\n",
"\n",
"print(f\"\\n✅ 集成PCA的内存友好数据集已创建\")\n",
"if PCA_CONFIG['enable_pca'] and GLOBAL_PCA['is_fitted']:\n",
" print(f\" 🔬 PCA降维: 7168 → {GLOBAL_PCA['n_components']} ({GLOBAL_PCA['n_components']/7168:.1%})\")\n",
" print(f\" 📊 方差保留: {GLOBAL_PCA['pca'].explained_variance_ratio_.sum():.4f}\")\n",
"print(f\" 使用方式1: dataset.load_all_data() - 一次性加载 (自动PCA)\")\n",
"print(f\" 使用方式2: dataset.get_batch_generator() - 分批处理 (自动PCA)\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"train_dataset"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[Errno 2] No such file or directory: 'model_training/'\n",
"/kaggle/working/nejm-brain-to-text/model_training\n"
]
}
],
"source": [
"%cd model_training/"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 模型建立"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LightGBM 梯度提升决策树\n",
"\n",
"使用LightGBM进行音素分类任务。LightGBM是微软开发的高效梯度提升框架具有以下优势\n",
"\n",
"- **训练速度快**: 相比传统GBDT算法速度提升10倍以上\n",
"- **内存占用低**: 使用直方图算法减少内存使用\n",
"- **准确率高**: 在许多机器学习竞赛中表现优异 \n",
"- **支持并行**: 支持特征并行和数据并行\n",
"- **可解释性强**: 提供特征重要性分析\n",
"\n",
"对于脑电信号到音素的分类任务LightGBM能够\n",
"1. 自动处理高维特征7168维神经信号\n",
"2. 发现特征之间的非线性关系\n",
"3. 提供特征重要性排序,帮助理解哪些脑区信号最重要\n",
"4. 快速训练,适合实验和调参"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# 🚀 LightGBM内存友好训练 - 分批处理策略\n",
"\n",
"import lightgbm as lgb\n",
"import numpy as np\n",
"import time\n",
"from sklearn.model_selection import train_test_split\n",
"from sklearn.preprocessing import StandardScaler\n",
"from sklearn.metrics import accuracy_score, classification_report\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import gc\n"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"Usage: \n",
" pip3 install [options] <requirement specifier> [package-index-options] ...\n",
" pip3 install [options] -r <requirements file> [package-index-options] ...\n",
" pip3 install [options] [-e] <vcs project url> ...\n",
" pip3 install [options] [-e] <local project path> ...\n",
" pip3 install [options] <archive url/path> ...\n",
"\n",
"no such option: --install-option\n"
]
}
],
"source": [
"!pip install lightgbm --install-option=--gpu"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"🔧 设备检查:\n",
" LightGBM GPU支持: ✅ 可用\n",
" 训练设备: GPU\n"
]
}
],
"source": [
"# 检查GPU可用性\n",
"def check_gpu_support():\n",
" \"\"\"检查LightGBM的GPU支持\"\"\"\n",
" try:\n",
" test_data = lgb.Dataset(np.random.rand(100, 10), label=np.random.randint(0, 2, 100))\n",
" test_params = {'device': 'gpu', 'objective': 'binary', 'verbose': -1}\n",
" # 新版本LightGBM使用callbacks参数而不是verbose_eval\n",
" lgb.train(test_params, test_data, num_boost_round=1, callbacks=[])\n",
" return True\n",
" except Exception as e:\n",
" print(f\" GPU支持检查失败: {e}\")\n",
" return False\n",
"\n",
"gpu_available = check_gpu_support()\n",
"print(f\"🔧 设备检查:\")\n",
"print(f\" LightGBM GPU支持: {'✅ 可用' if gpu_available else '❌ 不可用将使用CPU'}\")\n",
"\n",
"# 根据GPU可用性选择设备\n",
"device_type = 'gpu' if gpu_available else 'cpu'\n",
"print(f\" 训练设备: {device_type.upper()}\")"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"\n",
"# 检查数据集是否已创建\n",
"if 'train_dataset' not in locals():\n",
" print(\"❌ 错误: 请先运行数据读取代码创建数据集\")\n",
" exit()"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"📊 当前内存使用: 851.5 MB\n"
]
}
],
"source": [
"# 内存检查函数\n",
"def get_memory_usage():\n",
" \"\"\"获取当前内存使用情况\"\"\"\n",
" import psutil\n",
" process = psutil.Process()\n",
" return f\"{process.memory_info().rss / 1024 / 1024:.1f} MB\"\n",
"\n",
"def memory_cleanup():\n",
" \"\"\"强制内存清理\"\"\"\n",
" gc.collect()\n",
" print(f\" 内存清理后: {get_memory_usage()}\")\n",
"\n",
"print(f\"\\n📊 当前内存使用: {get_memory_usage()}\")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🔧 训练策略配置:\n",
" 内存限制: 25.0 GB\n",
" 分批训练: ✅ 启用\n"
]
}
],
"source": [
"# 策略选择:根据内存情况选择训练方式\n",
"MEMORY_LIMIT_MB = 25000 # 25GB内存限制\n",
"USE_BATCH_TRAINING = True # 是否使用分批训练\n",
"\n",
"print(f\"\\n🔧 训练策略配置:\")\n",
"print(f\" 内存限制: {MEMORY_LIMIT_MB/1000:.1f} GB\")\n",
"print(f\" 分批训练: {'✅ 启用' if USE_BATCH_TRAINING else '❌ 禁用'}\")"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🔄 分批训练模式:\n",
"<22> 第1阶段: 加载样本数据确定参数...\n",
" 正在加载文件 1/45: t15.2025.04.13_train_concatenated.npz\n",
" 加载批次 1: 14677 样本\n",
" 正在加载文件 2/45: t15.2024.07.21_train_concatenated.npz\n",
" 加载批次 1: 14677 样本\n",
" 正在加载文件 2/45: t15.2024.07.21_train_concatenated.npz\n",
" ✅ 样本数据: 58850 样本, 41 类别\n",
" ✅ 样本数据: 58850 样本, 41 类别\n"
]
}
],
"source": [
"print(f\"\\n🔄 分批训练模式:\")\n",
"\n",
"# 首先加载一小部分训练数据来确定模型参数\n",
"print(f\"第1阶段: 加载样本数据确定参数...\")\n",
"sample_X, sample_y = None, None\n",
"\n",
"batch_count = 0\n",
"for features, labels in train_dataset.get_batch_generator():\n",
" if sample_X is None:\n",
" sample_X, sample_y = features, labels\n",
" else:\n",
" sample_X = np.vstack([sample_X, features])\n",
" sample_y = np.hstack([sample_y, labels])\n",
" \n",
" batch_count += 1\n",
" if batch_count >= 2: # 只取前2个批次作为样本\n",
" break\n",
" \n",
" print(f\" 加载批次 {batch_count}: {features.shape[0]} 样本\")\n",
"\n",
"print(f\" ✅ 样本数据: {sample_X.shape[0]} 样本, {len(np.unique(sample_y))} 类别\")\n",
"\n",
"# 数据预处理\n",
"scaler = StandardScaler()\n",
"sample_X_scaled = scaler.fit_transform(sample_X)\n",
"\n",
"# 切分样本数据\n",
"X_sample_train, X_sample_val, y_sample_train, y_sample_val = train_test_split(\n",
" sample_X_scaled, sample_y, test_size=0.2, random_state=42, stratify=sample_y\n",
")\n",
"\n",
"# LightGBM参数配置\n",
"if gpu_available:\n",
" params = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': len(np.unique(sample_y)),\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': 'gpu',\n",
" 'gpu_platform_id': 0,\n",
" 'gpu_device_id': 0,\n",
" 'num_leaves': 64, # 减少叶子节点以节省内存\n",
" 'learning_rate': 0.1,\n",
" 'feature_fraction': 0.8,\n",
" 'bagging_fraction': 0.8,\n",
" 'bagging_freq': 5,\n",
" 'verbose': 0,\n",
" 'random_state': 42,\n",
" 'max_bin': 255,\n",
" }\n",
"else:\n",
" params = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': len(np.unique(sample_y)),\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': 'cpu',\n",
" 'num_leaves': 32, # CPU使用更少叶子节点\n",
" 'learning_rate': 0.1,\n",
" 'feature_fraction': 0.8,\n",
" 'bagging_fraction': 0.8,\n",
" 'bagging_freq': 5,\n",
" 'verbose': 0,\n",
" 'random_state': 42,\n",
" 'n_jobs': -1,\n",
" }\n"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(47080, 7168)"
]
},
"execution_count": 31,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"X_sample_train.shape"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🏗️ LightGBM配置:\n",
" 设备: GPU\n",
" 类别数: 41\n",
" 叶子节点: 64\n",
"\n",
"🚀 第2阶段: 在样本数据上训练初始模型...\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🏗️ LightGBM配置:\n",
" 设备: GPU\n",
" 类别数: 41\n",
" 叶子节点: 64\n",
"\n",
"🚀 第2阶段: 在样本数据上训练初始模型...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🏗️ LightGBM配置:\n",
" 设备: GPU\n",
" 类别数: 41\n",
" 叶子节点: 64\n",
"\n",
"🚀 第2阶段: 在样本数据上训练初始模型...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training until validation scores don't improve for 20 rounds\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"🏗️ LightGBM配置:\n",
" 设备: GPU\n",
" 类别数: 41\n",
" 叶子节点: 64\n",
"\n",
"🚀 第2阶段: 在样本数据上训练初始模型...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n",
"1 warning generated.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training until validation scores don't improve for 20 rounds\n"
]
},
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/4123818799.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 16\u001b[0m ]\n\u001b[1;32m 17\u001b[0m \u001b[0;31m# 在样本数据上训练初始模型\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 18\u001b[0;31m model = lgb.train(\n\u001b[0m\u001b[1;32m 19\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[0msample_train_data\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/lightgbm/engine.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m(params, train_set, num_boost_round, valid_sets, valid_names, feval, init_model, feature_name, categorical_feature, keep_training_booster, callbacks)\u001b[0m\n\u001b[1;32m 274\u001b[0m evaluation_result_list=None))\n\u001b[1;32m 275\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 276\u001b[0;31m \u001b[0mbooster\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfobj\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mfobj\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 277\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 278\u001b[0m \u001b[0mevaluation_result_list\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mList\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0m_LGBM_BoosterEvalMethodResultType\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/lightgbm/basic.py\u001b[0m in \u001b[0;36mupdate\u001b[0;34m(self, train_set, fobj)\u001b[0m\n\u001b[1;32m 3889\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__set_objective_to_none\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3890\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mLightGBMError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Cannot update due to null objective function.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3891\u001b[0;31m _safe_call(_LIB.LGBM_BoosterUpdateOneIter(\n\u001b[0m\u001b[1;32m 3892\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_handle\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3893\u001b[0m ctypes.byref(is_finished)))\n",
"\u001b[0;31mKeyboardInterrupt\u001b[0m: "
]
}
],
"source": [
"print(f\"\\n🏗 LightGBM配置:\")\n",
"print(f\" 设备: {params['device'].upper()}\")\n",
"print(f\" 类别数: {params['num_class']}\")\n",
"print(f\" 叶子节点: {params['num_leaves']}\")\n",
"\n",
"# 创建样本数据集\n",
"sample_train_data = lgb.Dataset(X_sample_train, label=y_sample_train)\n",
"sample_val_data = lgb.Dataset(X_sample_val, label=y_sample_val, reference=sample_train_data)\n",
"\n",
"print(f\"\\n🚀 第2阶段: 在样本数据上训练初始模型...\")\n",
"start_time = time.time()\n",
"\n",
"callbacks = [\n",
" lgb.log_evaluation(period=50),\n",
" lgb.early_stopping(stopping_rounds=20)\n",
"]\n",
"# 在样本数据上训练初始模型\n",
"model = lgb.train(\n",
" params,\n",
" sample_train_data,\n",
" valid_sets=[sample_train_data, sample_val_data],\n",
" valid_names=['train', 'val'],\n",
" num_boost_round=200, # 较少的轮数\n",
" callbacks=callbacks\n",
")\n",
"\n",
"print(f\" ✅ 初始模型训练完成\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 清理样本数据内存\n",
"del sample_X, sample_y, X_sample_train, X_sample_val, y_sample_train, y_sample_val\n",
"del sample_train_data, sample_val_data\n",
"memory_cleanup()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"print(f\"\\n🔄 第3阶段: 分批增量训练...\")\n",
"\n",
"# 分批处理完整训练数据进行增量训练\n",
"batch_num = 0\n",
"total_samples_processed = 0\n",
"\n",
"for features, labels in train_dataset.get_batch_generator():\n",
" batch_num += 1\n",
" print(f\"\\n 处理批次 {batch_num}: {features.shape[0]} 样本\")\n",
" \n",
" # 数据预处理\n",
" features_scaled = scaler.transform(features)\n",
" \n",
" # 创建批次数据集\n",
" batch_data = lgb.Dataset(features_scaled, label=labels, reference=model.train_set if hasattr(model, 'train_set') else None)\n",
" \n",
" # 增量训练(继续训练现有模型)\n",
" model = lgb.train(\n",
" params,\n",
" batch_data,\n",
" num_boost_round=20, # 每批次少量轮数\n",
" init_model=model, # 基于现有模型继续训练\n",
" verbose_eval=False\n",
" )\n",
" \n",
" total_samples_processed += features.shape[0]\n",
" print(f\" 累计处理样本: {total_samples_processed}\")\n",
" \n",
" # 清理批次数据\n",
" del features, labels, features_scaled, batch_data\n",
" memory_cleanup()\n",
" \n",
" # 内存保护:如果处理的样本足够多就停止\n",
" if total_samples_processed > 50000: # 限制总样本数\n",
" print(f\" ⚠️ 达到样本限制,停止训练\")\n",
" break\n",
"\n",
"training_time = time.time() - start_time\n",
"print(f\"\\n✅ 分批训练完成!\")\n",
"print(f\" 总耗时: {training_time:.2f} 秒\")\n",
"print(f\" 处理样本数: {total_samples_processed}\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"\n",
"# 模型评估阶段 - 按需加载验证/测试数据\n",
"print(f\"\\n🔮 模型评估阶段 - 按需加载数据...\")\n",
"\n",
"# 评估函数\n",
"def evaluate_on_dataset(model, scaler, dataset, dataset_name):\n",
" \"\"\"在指定数据集上评估模型\"\"\"\n",
" print(f\"\\n📊 评估 {dataset_name}...\")\n",
" \n",
" all_predictions = []\n",
" all_true_labels = []\n",
" batch_count = 0\n",
" \n",
" for features, labels in dataset.get_batch_generator():\n",
" batch_count += 1\n",
" print(f\" 评估批次 {batch_count}: {features.shape[0]} 样本\", end=\"\")\n",
" \n",
" # 预处理\n",
" features_scaled = scaler.transform(features)\n",
" \n",
" # 预测\n",
" predictions = model.predict(features_scaled, num_iteration=model.best_iteration)\n",
" predicted_labels = np.argmax(predictions, axis=1)\n",
" \n",
" all_predictions.extend(predicted_labels)\n",
" all_true_labels.extend(labels)\n",
" \n",
" print(f\" ✓\")\n",
" \n",
" # 清理内存\n",
" del features, labels, features_scaled, predictions, predicted_labels\n",
" gc.collect()\n",
" \n",
" # 计算总体准确率\n",
" accuracy = accuracy_score(all_true_labels, all_predictions)\n",
" print(f\" ✅ {dataset_name}准确率: {accuracy:.4f} ({accuracy*100:.2f}%)\")\n",
" \n",
" return accuracy, all_true_labels, all_predictions\n",
"\n",
"# 按需评估\n",
"val_accuracy, val_true, val_pred = evaluate_on_dataset(model, scaler, val_dataset, \"验证集\")\n",
"test_accuracy, test_true, test_pred = evaluate_on_dataset(model, scaler, test_dataset, \"测试集\")\n",
"\n",
"print(f\"\\n📊 最终评估结果:\")\n",
"print(f\" 验证集准确率: {val_accuracy:.4f} ({val_accuracy*100:.2f}%)\")\n",
"print(f\" 测试集准确率: {test_accuracy:.4f} ({test_accuracy*100:.2f}%)\")\n",
"print(f\" 最终内存使用: {get_memory_usage()}\")\n",
"\n",
"# 特征重要性分析\n",
"print(f\"\\n🔍 特征重要性分析:\")\n",
"try:\n",
" feature_importance = model.feature_importance(importance_type='gain')\n",
" top_features_idx = np.argsort(feature_importance)[-10:]\n",
" \n",
" print(f\" 前10个重要特征索引:\")\n",
" for i, idx in enumerate(reversed(top_features_idx)):\n",
" print(f\" {i+1:2d}. 特征 {idx:4d}: 重要性 {feature_importance[idx]:.2f}\")\n",
" \n",
" # 可视化特征重要性\n",
" plt.figure(figsize=(10, 6))\n",
" plt.bar(range(len(top_features_idx)), feature_importance[top_features_idx])\n",
" plt.title('前10个重要特征')\n",
" plt.xlabel('特征索引')\n",
" plt.ylabel('重要性得分')\n",
" plt.xticks(range(len(top_features_idx)), top_features_idx, rotation=45)\n",
" plt.tight_layout()\n",
" plt.show()\n",
" \n",
"except Exception as e:\n",
" print(f\" 特征重要性分析失败: {e}\")\n",
"\n",
"print(f\"\\n✨ 内存友好训练完成!\")\n",
"print(f\"💡 内存优化策略总结:\")\n",
"print(f\" - 分批数据加载,避免内存溢出\")\n",
"print(f\" - 增量模型训练,逐步改进\")\n",
"print(f\" - 按需评估,节省内存\")\n",
"print(f\" - 自动内存清理\")\n",
"print(f\" - GPU/CPU自适应配置\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 🔍 音素标签分布详细分析\n",
"\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"from collections import Counter\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🔍 音素标签分布详细分析\")\n",
"print(\"=\"*70)\n",
"\n",
"# 检查数据是否可用\n",
"if 'single_result' not in locals() or not single_result or 'train' not in single_result:\n",
" print(\"❌ 错误: single_result数据不可用请先运行RNN数据处理\")\n",
" exit()\n",
"\n",
"# 获取数据\n",
"train_data = single_result['train']\n",
"concatenated_features = train_data['concatenated_data']\n",
"\n",
"print(f\"📊 原始数据结构:\")\n",
"print(f\" 试验总数: {len(concatenated_features)}\")\n",
"if len(concatenated_features) > 0:\n",
" print(f\" 单个试验形状: {concatenated_features[0].shape}\")\n",
" print(f\" 特征维度: {concatenated_features[0].shape[1]} (前7168神经 + 后41音素)\")\n",
"\n",
"# 重新分析RNN输出的音素分布\n",
"print(f\"\\n🎯 音素分析方法:\")\n",
"print(f\" 1. 提取后41维RNN输出 (音素logits)\")\n",
"print(f\" 2. 对每个时间步计算argmax得到音素标签\")\n",
"print(f\" 3. 统计所有时间步的音素分布\")\n",
"\n",
"# 收集所有音素预测\n",
"all_phoneme_predictions = []\n",
"time_step_count = 0\n",
"trial_count = 0\n",
"\n",
"for i, features in enumerate(concatenated_features):\n",
" if features.shape[0] > 0: # 确保有时间步\n",
" trial_count += 1\n",
" for t in range(features.shape[0]):\n",
" time_step_count += 1\n",
" rnn_logits = features[t, 7168:] # 后41维\n",
" phoneme_pred = np.argmax(rnn_logits)\n",
" all_phoneme_predictions.append(phoneme_pred)\n",
"\n",
"all_phoneme_predictions = np.array(all_phoneme_predictions)\n",
"\n",
"print(f\"\\n📈 统计结果:\")\n",
"print(f\" 处理的试验数: {trial_count}\")\n",
"print(f\" 总时间步数: {time_step_count}\")\n",
"print(f\" 音素预测数: {len(all_phoneme_predictions)}\")\n",
"\n",
"# 分析音素分布\n",
"phoneme_counts = np.bincount(all_phoneme_predictions)\n",
"print(f\"\\n🏷 音素标签分布详情:\")\n",
"print(f\" 音素类别数: {len(phoneme_counts)}\")\n",
"print(f\" 非零类别数: {np.count_nonzero(phoneme_counts)}\")\n",
"\n",
"# 显示前15个音素的分布\n",
"print(f\"\\n📊 前15个音素类别分布:\")\n",
"for i in range(min(15, len(phoneme_counts))):\n",
" percentage = phoneme_counts[i] / len(all_phoneme_predictions) * 100\n",
" print(f\" 音素 {i:2d}: {phoneme_counts[i]:6d} 次 ({percentage:5.2f}%)\")\n",
"\n",
"# 检查音素0的异常高频率\n",
"print(f\"\\n⚠ 音素0异常分析:\")\n",
"print(f\" 音素0出现次数: {phoneme_counts[0]}\")\n",
"print(f\" 音素0占比: {phoneme_counts[0] / len(all_phoneme_predictions) * 100:.2f}%\")\n",
"print(f\" 其他音素总数: {len(all_phoneme_predictions) - phoneme_counts[0]}\")\n",
"\n",
"# 可能的原因分析\n",
"print(f\"\\n💡 音素0高频的可能原因:\")\n",
"print(f\" 1. 静音/沉默音素: 音素0可能代表静音或沉默状态\")\n",
"print(f\" 2. 背景状态: 非发音期间的默认状态\")\n",
"print(f\" 3. 模型偏置: RNN在不确定时倾向于预测音素0\")\n",
"print(f\" 4. 数据特性: 大部分时间处于非发音状态\")\n",
"\n",
"# 检查LOGIT_TO_PHONEME映射\n",
"if 'LOGIT_TO_PHONEME' in locals():\n",
" print(f\"\\n🔤 音素映射信息:\")\n",
" print(f\" 音素映射表长度: {len(LOGIT_TO_PHONEME)}\")\n",
" print(f\" 前10个音素映射:\")\n",
" for i in range(min(10, len(LOGIT_TO_PHONEME))):\n",
" print(f\" 索引 {i}: '{LOGIT_TO_PHONEME[i]}'\")\n",
" \n",
" # 检查音素0是什么\n",
" if len(LOGIT_TO_PHONEME) > 0:\n",
" print(f\"\\n 音素0代表: '{LOGIT_TO_PHONEME[0]}'\")\n",
" if 'sil' in LOGIT_TO_PHONEME[0].lower() or 'silence' in LOGIT_TO_PHONEME[0].lower():\n",
" print(f\" ✅ 确认: 音素0是静音符号高频率是正常的\")\n",
"\n",
"# 可视化音素分布\n",
"plt.figure(figsize=(15, 6))\n",
"\n",
"# 左图完整分布包含音素0\n",
"plt.subplot(1, 2, 1)\n",
"plt.bar(range(len(phoneme_counts)), phoneme_counts, alpha=0.7)\n",
"plt.title('完整音素分布')\n",
"plt.xlabel('音素索引')\n",
"plt.ylabel('出现次数')\n",
"plt.yscale('log') # 使用对数坐标便于观察\n",
"\n",
"# 右图排除音素0的分布\n",
"plt.subplot(1, 2, 2)\n",
"non_silence_counts = phoneme_counts[1:]\n",
"plt.bar(range(1, len(phoneme_counts)), non_silence_counts, alpha=0.7, color='orange')\n",
"plt.title('排除音素0的分布')\n",
"plt.xlabel('音素索引')\n",
"plt.ylabel('出现次数')\n",
"\n",
"plt.tight_layout()\n",
"plt.show()\n",
"\n",
"print(f\"\\n✅ 结论:\")\n",
"print(f\" 音素0的高频率很可能是正常现象代表静音或背景状态\")\n",
"print(f\" 实际的语音音素分布在音素1-40中数量相对平衡\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 🎯 音素分类结果展示 - 将预测转换为音素符号\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"from collections import Counter\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🎯 音素分类结果展示\")\n",
"print(\"=\"*70)\n",
"\n",
"# 检查必要的数据是否存在\n",
"if 'single_result' not in locals():\n",
" print(\"❌ 错误: single_result数据不可用\")\n",
" exit()\n",
"\n",
"if 'LOGIT_TO_PHONEME' not in locals():\n",
" print(\"❌ 错误: LOGIT_TO_PHONEME映射不可用\")\n",
" exit()\n",
"\n",
"print(f\"✅ 数据检查通过\")\n",
"print(f\" 音素映射表长度: {len(LOGIT_TO_PHONEME)}\")\n",
"\n",
"# 获取数据\n",
"train_data = single_result['train']\n",
"concatenated_features = train_data['concatenated_data']\n",
"\n",
"# 处理几个试验的数据进行展示\n",
"print(f\"\\n🔬 分类结果示例 (前3个试验):\")\n",
"print(f\"=\"*50)\n",
"\n",
"for trial_idx in range(min(3, len(concatenated_features))):\n",
" features = concatenated_features[trial_idx]\n",
" \n",
" print(f\"\\n📋 试验 {trial_idx + 1}:\")\n",
" print(f\" 时间步数: {features.shape[0]}\")\n",
" \n",
" if features.shape[0] > 0:\n",
" # 提取神经特征和RNN输出\n",
" neural_features = features[:, :7168] # 前7168维\n",
" rnn_logits = features[:, 7168:] # 后41维\n",
" \n",
" # 获取每个时间步的音素预测\n",
" predicted_phoneme_indices = np.argmax(rnn_logits, axis=1)\n",
" max_confidences = np.max(rnn_logits, axis=1)\n",
" \n",
" # 转换为音素符号\n",
" predicted_phonemes = [LOGIT_TO_PHONEME[idx] for idx in predicted_phoneme_indices]\n",
" \n",
" # 显示前10个时间步的结果\n",
" print(f\" 前10个时间步的预测:\")\n",
" print(f\" {'步骤':>4} {'索引':>4} {'置信度':>8} {'音素':>8}\")\n",
" print(f\" {'-'*30}\")\n",
" \n",
" for t in range(min(10, len(predicted_phonemes))):\n",
" print(f\" {t+1:4d} {predicted_phoneme_indices[t]:4d} {max_confidences[t]:8.3f} {predicted_phonemes[t]:>8}\")\n",
" \n",
" # 统计这个试验的音素分布\n",
" phoneme_counter = Counter(predicted_phonemes)\n",
" print(f\"\\n 本试验音素分布 (前5个):\")\n",
" for phoneme, count in phoneme_counter.most_common(5):\n",
" percentage = count / len(predicted_phonemes) * 100\n",
" print(f\" '{phoneme}': {count:3d} 次 ({percentage:5.1f}%)\")\n",
"\n",
"# 全局音素统计\n",
"print(f\"\\n🌍 全局音素统计:\")\n",
"print(f\"=\"*50)\n",
"\n",
"all_phoneme_predictions = []\n",
"all_confidences = []\n",
"\n",
"for features in concatenated_features:\n",
" if features.shape[0] > 0:\n",
" rnn_logits = features[:, 7168:]\n",
" predicted_indices = np.argmax(rnn_logits, axis=1)\n",
" confidences = np.max(rnn_logits, axis=1)\n",
" \n",
" all_phoneme_predictions.extend(predicted_indices)\n",
" all_confidences.extend(confidences)\n",
"\n",
"# 转换为音素符号\n",
"all_predicted_phonemes = [LOGIT_TO_PHONEME[idx] for idx in all_phoneme_predictions]\n",
"global_phoneme_counter = Counter(all_predicted_phonemes)\n",
"\n",
"print(f\"\\n📊 音素频率排行 (前15个):\")\n",
"print(f\"{'排名':>4} {'音素':>8} {'出现次数':>8} {'百分比':>8} {'平均置信度':>10}\")\n",
"print(f\"{'-'*45}\")\n",
"\n",
"for rank, (phoneme, count) in enumerate(global_phoneme_counter.most_common(15), 1):\n",
" # 计算该音素的平均置信度\n",
" phoneme_indices = [i for i, p in enumerate(all_predicted_phonemes) if p == phoneme]\n",
" avg_confidence = np.mean([all_confidences[i] for i in phoneme_indices])\n",
" percentage = count / len(all_predicted_phonemes) * 100\n",
" \n",
" print(f\"{rank:4d} {phoneme:>8} {count:8d} {percentage:7.2f}% {avg_confidence:10.3f}\")\n",
"\n",
"# 分析非静音音素\n",
"print(f\"\\n🗣 非静音音素分析:\")\n",
"non_silence_phonemes = [p for p in all_predicted_phonemes if p.lower() not in ['sil', 'silence', 'sp', 'spn']]\n",
"non_silence_counter = Counter(non_silence_phonemes)\n",
"\n",
"print(f\" 总音素预测: {len(all_predicted_phonemes)}\")\n",
"print(f\" 非静音音素: {len(non_silence_phonemes)}\")\n",
"print(f\" 静音比例: {(1 - len(non_silence_phonemes)/len(all_predicted_phonemes))*100:.1f}%\")\n",
"\n",
"if len(non_silence_phonemes) > 0:\n",
" print(f\"\\n 非静音音素排行 (前10个):\")\n",
" print(f\" {'音素':>6} {'次数':>6} {'百分比':>8}\")\n",
" print(f\" {'-'*22}\")\n",
" \n",
" for phoneme, count in non_silence_counter.most_common(10):\n",
" percentage = count / len(non_silence_phonemes) * 100\n",
" print(f\" {phoneme:>6} {count:6d} {percentage:7.2f}%\")\n",
"\n",
"# 序列示例\n",
"print(f\"\\n📝 音素序列示例 (试验1的前30个时间步):\")\n",
"if len(concatenated_features) > 0 and concatenated_features[0].shape[0] > 0:\n",
" example_features = concatenated_features[0]\n",
" example_logits = example_features[:30, 7168:] # 前30个时间步\n",
" example_predictions = np.argmax(example_logits, axis=1)\n",
" example_phonemes = [LOGIT_TO_PHONEME[idx] for idx in example_predictions]\n",
" \n",
" # 按每行10个显示\n",
" for i in range(0, len(example_phonemes), 10):\n",
" line_phonemes = example_phonemes[i:i+10]\n",
" phoneme_str = ' '.join(f\"{p:>4}\" for p in line_phonemes)\n",
" print(f\" 步骤 {i+1:2d}-{min(i+10, len(example_phonemes)):2d}: {phoneme_str}\")\n",
"\n",
"print(f\"\\n✨ 音素分类结果展示完成!\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 🔄 CTC处理 - 连接主义时序分类后处理\n",
"\n",
"import numpy as np\n",
"from collections import Counter\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🔄 CTC处理 - 音素序列优化\")\n",
"print(\"=\"*70)\n",
"\n",
"def ctc_decode_greedy(predictions, blank_id=0):\n",
" \"\"\"\n",
" 简单的CTC贪心解码\n",
" \n",
" Args:\n",
" predictions: 音素预测序列 [time_steps]\n",
" blank_id: 空白符号的ID (通常是0代表静音)\n",
" \n",
" Returns:\n",
" decoded_sequence: 解码后的音素序列\n",
" \"\"\"\n",
" decoded = []\n",
" previous = -1\n",
" \n",
" for pred in predictions:\n",
" # CTC规则\n",
" # 1. 移除连续重复的符号\n",
" # 2. 移除空白符号\n",
" if pred != previous and pred != blank_id:\n",
" decoded.append(pred)\n",
" previous = pred\n",
" \n",
" return decoded\n",
"\n",
"def ctc_decode_with_confidence(logits, confidence_threshold=0.5, blank_id=0):\n",
" \"\"\"\n",
" 带置信度阈值的CTC解码\n",
" \n",
" Args:\n",
" logits: RNN输出的logits [time_steps, num_classes]\n",
" confidence_threshold: 置信度阈值\n",
" blank_id: 空白符号ID\n",
" \n",
" Returns:\n",
" decoded_sequence: 解码后的音素序列\n",
" confidences: 对应的置信度\n",
" \"\"\"\n",
" predictions = np.argmax(logits, axis=1)\n",
" max_confidences = np.max(logits, axis=1)\n",
" \n",
" decoded = []\n",
" decoded_confidences = []\n",
" previous = -1\n",
" \n",
" for i, (pred, conf) in enumerate(zip(predictions, max_confidences)):\n",
" # 只保留高置信度且非重复的预测\n",
" if pred != previous and pred != blank_id and conf > confidence_threshold:\n",
" decoded.append(pred)\n",
" decoded_confidences.append(conf)\n",
" previous = pred\n",
" \n",
" return decoded, decoded_confidences\n",
"\n",
"# 检查数据\n",
"if 'single_result' not in locals() or 'LOGIT_TO_PHONEME' not in locals():\n",
" print(\"❌ 错误: 缺少必要的数据\")\n",
" exit()\n",
"\n",
"print(f\"✅ 开始CTC处理\")\n",
"print(f\" 音素映射表: {len(LOGIT_TO_PHONEME)} 个音素\")\n",
"\n",
"# 获取数据\n",
"train_data = single_result['train']\n",
"concatenated_features = train_data['concatenated_data']\n",
"\n",
"# 处理几个试验进行CTC解码对比\n",
"print(f\"\\n🔬 CTC解码对比 (前3个试验):\")\n",
"print(f\"=\"*60)\n",
"\n",
"for trial_idx in range(min(3, len(concatenated_features))):\n",
" features = concatenated_features[trial_idx]\n",
" \n",
" if features.shape[0] == 0:\n",
" continue\n",
" \n",
" print(f\"\\n📋 试验 {trial_idx + 1} (共{features.shape[0]}个时间步):\")\n",
" \n",
" # 提取RNN输出\n",
" rnn_logits = features[:, 7168:] # 后41维\n",
" \n",
" # 原始预测\n",
" raw_predictions = np.argmax(rnn_logits, axis=1)\n",
" raw_phonemes = [LOGIT_TO_PHONEME[idx] for idx in raw_predictions]\n",
" \n",
" # CTC贪心解码\n",
" ctc_predictions = ctc_decode_greedy(raw_predictions, blank_id=0)\n",
" ctc_phonemes = [LOGIT_TO_PHONEME[idx] for idx in ctc_predictions]\n",
" \n",
" # 带置信度的CTC解码\n",
" ctc_conf_predictions, confidences = ctc_decode_with_confidence(\n",
" rnn_logits, confidence_threshold=1.0, blank_id=0\n",
" )\n",
" ctc_conf_phonemes = [LOGIT_TO_PHONEME[idx] for idx in ctc_conf_predictions]\n",
" \n",
" print(f\" 原始序列长度: {len(raw_phonemes)}\")\n",
" print(f\" CTC解码长度: {len(ctc_phonemes)}\")\n",
" print(f\" 高置信CTC长度: {len(ctc_conf_phonemes)}\")\n",
" \n",
" # 显示前20个原始预测\n",
" print(f\"\\n 原始预测 (前20步):\")\n",
" raw_sample = raw_phonemes[:20]\n",
" print(f\" {' '.join(f'{p:>4}' for p in raw_sample)}\")\n",
" \n",
" # 显示CTC解码结果\n",
" print(f\"\\n CTC解码结果:\")\n",
" if len(ctc_phonemes) > 0:\n",
" ctc_sample = ctc_phonemes[:20] # 显示前20个如果有的话\n",
" print(f\" {' '.join(f'{p:>4}' for p in ctc_sample)}\")\n",
" else:\n",
" print(f\" (空序列)\")\n",
" \n",
" # 显示高置信度CTC结果\n",
" print(f\"\\n 高置信CTC结果:\")\n",
" if len(ctc_conf_phonemes) > 0:\n",
" conf_sample = ctc_conf_phonemes[:15] # 显示前15个\n",
" conf_values = confidences[:15]\n",
" for phoneme, conf in zip(conf_sample, conf_values):\n",
" print(f\" {phoneme:>4}({conf:.2f})\", end=\"\")\n",
" print()\n",
" else:\n",
" print(f\" (空序列)\")\n",
"\n",
"# 全局CTC统计\n",
"print(f\"\\n🌍 全局CTC统计分析:\")\n",
"print(f\"=\"*60)\n",
"\n",
"all_raw_predictions = []\n",
"all_ctc_predictions = []\n",
"all_ctc_conf_predictions = []\n",
"\n",
"for features in concatenated_features:\n",
" if features.shape[0] > 0:\n",
" rnn_logits = features[:, 7168:]\n",
" raw_preds = np.argmax(rnn_logits, axis=1)\n",
" \n",
" # 收集原始预测\n",
" all_raw_predictions.extend(raw_preds)\n",
" \n",
" # CTC解码\n",
" ctc_preds = ctc_decode_greedy(raw_preds, blank_id=0)\n",
" all_ctc_predictions.extend(ctc_preds)\n",
" \n",
" # 高置信度CTC\n",
" ctc_conf_preds, _ = ctc_decode_with_confidence(rnn_logits, confidence_threshold=1.0, blank_id=0)\n",
" all_ctc_conf_predictions.extend(ctc_conf_preds)\n",
"\n",
"# 转换为音素符号\n",
"raw_phonemes_global = [LOGIT_TO_PHONEME[idx] for idx in all_raw_predictions]\n",
"ctc_phonemes_global = [LOGIT_TO_PHONEME[idx] for idx in all_ctc_predictions]\n",
"ctc_conf_phonemes_global = [LOGIT_TO_PHONEME[idx] for idx in all_ctc_conf_predictions]\n",
"\n",
"print(f\"📊 序列长度对比:\")\n",
"print(f\" 原始预测总数: {len(raw_phonemes_global):,}\")\n",
"print(f\" CTC解码总数: {len(ctc_phonemes_global):,}\")\n",
"print(f\" 高置信CTC总数: {len(ctc_conf_phonemes_global):,}\")\n",
"print(f\" 压缩比 (CTC): {len(ctc_phonemes_global)/len(raw_phonemes_global)*100:.1f}%\")\n",
"print(f\" 压缩比 (高置信): {len(ctc_conf_phonemes_global)/len(raw_phonemes_global)*100:.1f}%\")\n",
"\n",
"# 音素分布对比\n",
"print(f\"\\n📈 音素分布对比 (前10个):\")\n",
"raw_counter = Counter(raw_phonemes_global)\n",
"ctc_counter = Counter(ctc_phonemes_global)\n",
"ctc_conf_counter = Counter(ctc_conf_phonemes_global)\n",
"\n",
"print(f\"{'音素':>6} {'原始':>8} {'CTC':>8} {'高置信':>8}\")\n",
"print(f\"{'-'*35}\")\n",
"\n",
"for phoneme, raw_count in raw_counter.most_common(10):\n",
" ctc_count = ctc_counter.get(phoneme, 0)\n",
" conf_count = ctc_conf_counter.get(phoneme, 0)\n",
" print(f\"{phoneme:>6} {raw_count:8d} {ctc_count:8d} {conf_count:8d}\")\n",
"\n",
"# 生成完整的句子示例\n",
"print(f\"\\n📝 CTC解码句子示例:\")\n",
"print(f\"=\"*40)\n",
"\n",
"for trial_idx in range(min(2, len(concatenated_features))):\n",
" features = concatenated_features[trial_idx]\n",
" \n",
" if features.shape[0] > 0:\n",
" rnn_logits = features[:, 7168:]\n",
" raw_preds = np.argmax(rnn_logits, axis=1)\n",
" ctc_preds = ctc_decode_greedy(raw_preds, blank_id=0)\n",
" \n",
" raw_phonemes = [LOGIT_TO_PHONEME[idx] for idx in raw_preds]\n",
" ctc_phonemes = [LOGIT_TO_PHONEME[idx] for idx in ctc_preds]\n",
" \n",
" print(f\"\\n试验 {trial_idx + 1}:\")\n",
" print(f\" 原始: {' '.join(raw_phonemes[:30])}...\")\n",
" print(f\" CTC: {' '.join(ctc_phonemes[:20])}\")\n",
"\n",
"print(f\"\\n✨ CTC处理完成\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 📝 音素序列到文本转换 - 最终结果展示\n",
"\n",
"import re\n",
"from collections import defaultdict\n",
"\n",
"print(\"=\"*70)\n",
"print(\"📝 音素序列到文本转换\")\n",
"print(\"=\"*70)\n",
"\n",
"def phonemes_to_words(phoneme_sequence):\n",
" \"\"\"\n",
" 将音素序列转换为可能的单词\n",
" 这是一个简化的实现,实际应用中需要语音识别词典\n",
" \"\"\"\n",
" # 简单的音素到字母映射(示例)\n",
" phoneme_to_letter = {\n",
" 'AA': 'a', 'AE': 'a', 'AH': 'u', 'AO': 'o', 'AW': 'ow',\n",
" 'AY': 'ai', 'B': 'b', 'CH': 'ch', 'D': 'd', 'DH': 'th',\n",
" 'EH': 'e', 'ER': 'er', 'EY': 'ay', 'F': 'f', 'G': 'g',\n",
" 'HH': 'h', 'IH': 'i', 'IY': 'ee', 'JH': 'j', 'K': 'k',\n",
" 'L': 'l', 'M': 'm', 'N': 'n', 'NG': 'ng', 'OW': 'o',\n",
" 'OY': 'oy', 'P': 'p', 'R': 'r', 'S': 's', 'SH': 'sh',\n",
" 'T': 't', 'TH': 'th', 'UH': 'u', 'UW': 'oo', 'V': 'v',\n",
" 'W': 'w', 'Y': 'y', 'Z': 'z', 'ZH': 'zh'\n",
" }\n",
" \n",
" result = []\n",
" for phoneme in phoneme_sequence:\n",
" if phoneme in phoneme_to_letter:\n",
" result.append(phoneme_to_letter[phoneme])\n",
" else:\n",
" result.append(f'[{phoneme}]') # 未知音素用括号标记\n",
" \n",
" return ''.join(result)\n",
"\n",
"def analyze_phoneme_patterns(phoneme_sequence):\n",
" \"\"\"\n",
" 分析音素序列中的模式\n",
" \"\"\"\n",
" # 统计音素频率\n",
" phoneme_freq = defaultdict(int)\n",
" for phoneme in phoneme_sequence:\n",
" phoneme_freq[phoneme] += 1\n",
" \n",
" # 寻找常见的音素组合\n",
" bigrams = defaultdict(int)\n",
" for i in range(len(phoneme_sequence) - 1):\n",
" bigram = (phoneme_sequence[i], phoneme_sequence[i+1])\n",
" bigrams[bigram] += 1\n",
" \n",
" return phoneme_freq, bigrams\n",
"\n",
"# 处理CTC解码后的结果\n",
"print(f\"🔄 处理CTC解码结果...\")\n",
"\n",
"# 获取数据\n",
"train_data = single_result['train']\n",
"concatenated_features = train_data['concatenated_data']\n",
"\n",
"# 收集所有CTC解码结果\n",
"all_trials_ctc = []\n",
"all_trials_raw = []\n",
"\n",
"for trial_idx, features in enumerate(concatenated_features[:5]): # 处理前5个试验\n",
" if features.shape[0] > 0:\n",
" rnn_logits = features[:, 7168:]\n",
" raw_preds = np.argmax(rnn_logits, axis=1)\n",
" \n",
" # CTC解码\n",
" ctc_preds = ctc_decode_greedy(raw_preds, blank_id=0)\n",
" \n",
" # 转换为音素符号\n",
" raw_phonemes = [LOGIT_TO_PHONEME[idx] for idx in raw_preds]\n",
" ctc_phonemes = [LOGIT_TO_PHONEME[idx] for idx in ctc_preds]\n",
" \n",
" all_trials_raw.append(raw_phonemes)\n",
" all_trials_ctc.append(ctc_phonemes)\n",
"\n",
"print(f\"✅ 处理了 {len(all_trials_ctc)} 个试验\")\n",
"\n",
"# 展示每个试验的结果\n",
"print(f\"\\n📋 逐试验分析:\")\n",
"print(f\"=\"*50)\n",
"\n",
"for trial_idx, (raw_phonemes, ctc_phonemes) in enumerate(zip(all_trials_raw, all_trials_ctc)):\n",
" print(f\"\\n🎯 试验 {trial_idx + 1}:\")\n",
" print(f\" 原始长度: {len(raw_phonemes)} → CTC长度: {len(ctc_phonemes)}\")\n",
" \n",
" # 显示音素序列\n",
" print(f\" CTC音素序列: {' '.join(ctc_phonemes[:20])}{'...' if len(ctc_phonemes) > 20 else ''}\")\n",
" \n",
" # 尝试转换为文本(简化版本)\n",
" if len(ctc_phonemes) > 0:\n",
" text_attempt = phonemes_to_words(ctc_phonemes)\n",
" print(f\" 近似文本: {text_attempt[:50]}{'...' if len(text_attempt) > 50 else ''}\")\n",
" \n",
" # 分析音素模式\n",
" if len(ctc_phonemes) > 0:\n",
" phoneme_freq, bigrams = analyze_phoneme_patterns(ctc_phonemes)\n",
" \n",
" # 显示最常见的音素\n",
" top_phonemes = sorted(phoneme_freq.items(), key=lambda x: x[1], reverse=True)[:5]\n",
" print(f\" 常见音素: {', '.join([f'{p}({c})' for p, c in top_phonemes])}\")\n",
" \n",
" # 显示最常见的音素对\n",
" if len(bigrams) > 0:\n",
" top_bigrams = sorted(bigrams.items(), key=lambda x: x[1], reverse=True)[:3]\n",
" bigram_str = ', '.join([f'{p1}-{p2}({c})' for (p1, p2), c in top_bigrams])\n",
" print(f\" 常见音素对: {bigram_str}\")\n",
"\n",
"# 全局统计\n",
"print(f\"\\n🌍 全局CTC结果统计:\")\n",
"print(f\"=\"*50)\n",
"\n",
"all_ctc_phonemes = []\n",
"for ctc_phonemes in all_trials_ctc:\n",
" all_ctc_phonemes.extend(ctc_phonemes)\n",
"\n",
"if len(all_ctc_phonemes) > 0:\n",
" global_freq, global_bigrams = analyze_phoneme_patterns(all_ctc_phonemes)\n",
" \n",
" print(f\" 总CTC音素数: {len(all_ctc_phonemes)}\")\n",
" print(f\" 唯一音素数: {len(global_freq)}\")\n",
" \n",
" # 最常见的音素\n",
" print(f\"\\n 📊 音素频率排行:\")\n",
" top_global_phonemes = sorted(global_freq.items(), key=lambda x: x[1], reverse=True)[:10]\n",
" for rank, (phoneme, count) in enumerate(top_global_phonemes, 1):\n",
" percentage = count / len(all_ctc_phonemes) * 100\n",
" print(f\" {rank:2d}. {phoneme:>4}: {count:4d} 次 ({percentage:5.1f}%)\")\n",
" \n",
" # 最常见的音素对\n",
" print(f\"\\n 🔗 音素对频率排行:\")\n",
" top_global_bigrams = sorted(global_bigrams.items(), key=lambda x: x[1], reverse=True)[:8]\n",
" for rank, ((p1, p2), count) in enumerate(top_global_bigrams, 1):\n",
" print(f\" {rank:2d}. {p1:>4}-{p2:<4}: {count:3d} 次\")\n",
"\n",
"# 质量评估\n",
"print(f\"\\n📈 CTC解码质量评估:\")\n",
"print(f\"=\"*30)\n",
"\n",
"total_raw = sum(len(raw) for raw in all_trials_raw)\n",
"total_ctc = len(all_ctc_phonemes)\n",
"compression_ratio = total_ctc / total_raw if total_raw > 0 else 0\n",
"\n",
"print(f\" 原始音素总数: {total_raw:,}\")\n",
"print(f\" CTC音素总数: {total_ctc:,}\")\n",
"print(f\" 压缩比: {compression_ratio:.3f}\")\n",
"print(f\" 去重效果: {(1-compression_ratio)*100:.1f}% 的重复被移除\")\n",
"\n",
"# 音素多样性\n",
"unique_phonemes = len(set(all_ctc_phonemes))\n",
"phoneme_diversity = unique_phonemes / len(LOGIT_TO_PHONEME) if len(LOGIT_TO_PHONEME) > 0 else 0\n",
"\n",
"print(f\" 音素多样性: {unique_phonemes}/{len(LOGIT_TO_PHONEME)} ({phoneme_diversity*100:.1f}%)\")\n",
"\n",
"print(f\"\\n✨ 音素到文本转换分析完成!\")\n",
"print(f\" 💡 提示: 这是基于RNN预测的音素序列\")\n",
"print(f\" 💡 实际应用需要语音识别词典进行准确的音素到单词转换\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"!ls ../data/rnn-pretagged-data"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"🔬 PCA降维增强数据加载器\n",
"======================================================================\n",
"✅ PCA增强数据加载器已定义\n"
]
}
],
"source": [
"# 🔬 PCA降维增强数据加载器\n",
"\n",
"from sklearn.decomposition import PCA\n",
"from sklearn.preprocessing import StandardScaler\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import numpy as np\n",
"import joblib\n",
"import os\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🔬 PCA降维增强数据加载器\")\n",
"print(\"=\"*70)\n",
"\n",
"class PCAEnhancedDataset:\n",
" \"\"\"\n",
" 带有PCA降维功能的内存友好数据集\n",
" \"\"\"\n",
" \n",
" def __init__(self, data_dir, data_type, max_samples_per_file=3000, \n",
" enable_pca=True, n_components=None, variance_threshold=0.95):\n",
" \"\"\"\n",
" Args:\n",
" data_dir: 数据目录\n",
" data_type: 'train', 'val', 'test'\n",
" max_samples_per_file: 每个文件最大样本数\n",
" enable_pca: 是否启用PCA降维\n",
" n_components: PCA主成分数量 (None为自动选择)\n",
" variance_threshold: 保留方差比例 (用于自动选择成分数)\n",
" \"\"\"\n",
" self.data_dir = data_dir\n",
" self.data_type = data_type\n",
" self.max_samples_per_file = max_samples_per_file\n",
" self.enable_pca = enable_pca\n",
" self.n_components = n_components\n",
" self.variance_threshold = variance_threshold\n",
" \n",
" self.files = [f for f in os.listdir(data_dir) if f.endswith('.npz') and data_type in f]\n",
" self.scaler = StandardScaler()\n",
" self.pca = None\n",
" self.is_fitted = False\n",
" \n",
" print(f\"📊 PCA数据集初始化:\")\n",
" print(f\" 数据类型: {data_type}\")\n",
" print(f\" 文件数量: {len(self.files)}\")\n",
" print(f\" PCA启用: {'✅' if enable_pca else '❌'}\")\n",
" if enable_pca:\n",
" print(f\" 成分数量: {n_components if n_components else 'auto'}\")\n",
" print(f\" 方差阈值: {variance_threshold}\")\n",
" \n",
" def fit_pca(self, sample_size=10000):\n",
" \"\"\"\n",
" 在训练数据样本上拟合PCA\n",
" \n",
" Args:\n",
" sample_size: 用于拟合PCA的样本数量\n",
" \"\"\"\n",
" if not self.enable_pca:\n",
" print(\"⚠️ PCA未启用跳过拟合\")\n",
" return\n",
" \n",
" print(f\"\\n🔧 拟合PCA降维器...\")\n",
" print(f\" 使用样本数: {sample_size}\")\n",
" \n",
" # 收集样本数据\n",
" sample_features = []\n",
" collected_samples = 0\n",
" \n",
" for features, labels in self.get_batch_generator_raw():\n",
" sample_features.append(features)\n",
" collected_samples += features.shape[0]\n",
" \n",
" if collected_samples >= sample_size:\n",
" break\n",
" \n",
" if sample_features:\n",
" # 合并样本数据\n",
" X_sample = np.vstack(sample_features)[:sample_size]\n",
" print(f\" 实际样本数: {X_sample.shape[0]}\")\n",
" print(f\" 原始特征数: {X_sample.shape[1]}\")\n",
" \n",
" # 标准化\n",
" X_sample_scaled = self.scaler.fit_transform(X_sample)\n",
" \n",
" # 确定PCA成分数\n",
" if self.n_components is None:\n",
" # 自动选择成分数 - 先拟合完整PCA\n",
" print(f\" 🔍 自动选择PCA成分数...\")\n",
" pca_full = PCA()\n",
" pca_full.fit(X_sample_scaled)\n",
" \n",
" # 计算累积方差比例\n",
" cumsum_var = np.cumsum(pca_full.explained_variance_ratio_)\n",
" optimal_components = np.argmax(cumsum_var >= self.variance_threshold) + 1\n",
" \n",
" self.n_components = min(optimal_components, X_sample.shape[1])\n",
" print(f\" 📊 方差分析:\")\n",
" print(f\" 保留{self.variance_threshold*100}%方差需要: {optimal_components} 个成分\")\n",
" print(f\" 选择成分数: {self.n_components}\")\n",
" print(f\" 实际保留方差: {cumsum_var[self.n_components-1]:.4f}\")\n",
" \n",
" # 拟合最终PCA\n",
" self.pca = PCA(n_components=self.n_components, random_state=42)\n",
" self.pca.fit(X_sample_scaled)\n",
" \n",
" self.is_fitted = True\n",
" \n",
" print(f\" ✅ PCA拟合完成!\")\n",
" print(f\" 降维: {X_sample.shape[1]} → {self.n_components}\")\n",
" print(f\" 降维比例: {self.n_components/X_sample.shape[1]:.2%}\")\n",
" print(f\" 保留方差: {self.pca.explained_variance_ratio_.sum():.4f}\")\n",
" \n",
" # 保存PCA模型\n",
" pca_path = f\"pca_model_{self.data_type}.joblib\"\n",
" joblib.dump({'scaler': self.scaler, 'pca': self.pca}, pca_path)\n",
" print(f\" 模型已保存: {pca_path}\")\n",
" \n",
" else:\n",
" print(\"❌ 无法收集样本数据用于PCA拟合\")\n",
" \n",
" def load_pca_model(self, model_path):\n",
" \"\"\"加载预训练的PCA模型\"\"\"\n",
" if os.path.exists(model_path):\n",
" models = joblib.load(model_path)\n",
" self.scaler = models['scaler']\n",
" self.pca = models['pca']\n",
" self.is_fitted = True\n",
" self.n_components = self.pca.n_components_\n",
" print(f\"✅ PCA模型加载成功: {model_path}\")\n",
" return True\n",
" return False\n",
" \n",
" def get_batch_generator_raw(self):\n",
" \"\"\"原始数据生成器用于PCA拟合\"\"\"\n",
" for file_idx, f in enumerate(self.files):\n",
" data = np.load(os.path.join(self.data_dir, f), allow_pickle=True)\n",
" trials = data['neural_logits_concatenated']\n",
" \n",
" if len(trials) > self.max_samples_per_file:\n",
" trials = trials[:self.max_samples_per_file]\n",
" \n",
" features, labels = self._extract_features_labels(trials)\n",
" yield features, labels\n",
" \n",
" del data, trials\n",
" gc.collect()\n",
" \n",
" def get_batch_generator(self):\n",
" \"\"\"PCA处理后的数据生成器\"\"\"\n",
" for file_idx, f in enumerate(self.files):\n",
" print(f\" 正在加载文件 {file_idx+1}/{len(self.files)}: {f}\")\n",
" \n",
" data = np.load(os.path.join(self.data_dir, f), allow_pickle=True)\n",
" trials = data['neural_logits_concatenated']\n",
" \n",
" if len(trials) > self.max_samples_per_file:\n",
" trials = trials[:self.max_samples_per_file]\n",
" \n",
" features, labels = self._extract_features_labels(trials)\n",
" \n",
" # 应用PCA降维\n",
" if self.enable_pca and self.is_fitted:\n",
" features_scaled = self.scaler.transform(features)\n",
" features_pca = self.pca.transform(features_scaled)\n",
" yield features_pca, labels\n",
" else:\n",
" # 只标准化,不降维\n",
" features_scaled = self.scaler.transform(features) if self.is_fitted else features\n",
" yield features_scaled, labels\n",
" \n",
" del data, trials\n",
" gc.collect()\n",
" \n",
" def _extract_features_labels(self, trials_batch):\n",
" \"\"\"提取特征和标签\"\"\"\n",
" features = []\n",
" labels = []\n",
" \n",
" for trial in trials_batch:\n",
" if trial.shape[0] > 0:\n",
" for t in range(trial.shape[0]):\n",
" neural_features = trial[t, :7168] # 前7168维神经特征\n",
" rnn_logits = trial[t, 7168:] # 后41维RNN输出\n",
" phoneme_label = np.argmax(rnn_logits)\n",
" \n",
" features.append(neural_features)\n",
" labels.append(phoneme_label)\n",
" \n",
" return np.array(features), np.array(labels)\n",
" \n",
" def plot_pca_analysis(self):\n",
" \"\"\"可视化PCA分析结果\"\"\"\n",
" if not (self.enable_pca and self.is_fitted):\n",
" print(\"❌ PCA未拟合无法绘制分析图\")\n",
" return\n",
" \n",
" fig, axes = plt.subplots(1, 3, figsize=(18, 5))\n",
" \n",
" # 1. 方差解释比例\n",
" axes[0].bar(range(1, min(21, len(self.pca.explained_variance_ratio_)+1)), \n",
" self.pca.explained_variance_ratio_[:20])\n",
" axes[0].set_title('Explained Variance Ratio (Top 20 Components)')\n",
" axes[0].set_xlabel('Principal Component')\n",
" axes[0].set_ylabel('Explained Variance Ratio')\n",
" \n",
" # 2. 累积方差解释比例\n",
" cumsum_var = np.cumsum(self.pca.explained_variance_ratio_)\n",
" axes[1].plot(range(1, len(cumsum_var)+1), cumsum_var, 'b-', linewidth=2)\n",
" axes[1].axhline(y=self.variance_threshold, color='r', linestyle='--', \n",
" label=f'Threshold ({self.variance_threshold})')\n",
" axes[1].axvline(x=self.n_components, color='g', linestyle='--', \n",
" label=f'Selected Components ({self.n_components})')\n",
" axes[1].set_title('Cumulative Explained Variance Ratio')\n",
" axes[1].set_xlabel('Number of Components')\n",
" axes[1].set_ylabel('Cumulative Explained Variance Ratio')\n",
" axes[1].legend()\n",
" axes[1].grid(True, alpha=0.3)\n",
" \n",
" # 3. 降维效果\n",
" original_dims = 7168\n",
" reduction_ratio = self.n_components / original_dims\n",
" \n",
" categories = ['Original Dimensions', 'PCA Dimensions']\n",
" values = [original_dims, self.n_components]\n",
" colors = ['lightcoral', 'lightblue']\n",
" \n",
" bars = axes[2].bar(categories, values, color=colors)\n",
" axes[2].set_title(f'Dimensionality Reduction Effect (Retained {reduction_ratio:.1%})')\n",
" axes[2].set_ylabel('Feature Dimensions')\n",
" \n",
" # 添加数值标签\n",
" for bar, value in zip(bars, values):\n",
" axes[2].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50,\n",
" f'{value}', ha='center', va='bottom', fontweight='bold')\n",
" \n",
" plt.tight_layout()\n",
" plt.show()\n",
" \n",
" # 打印详细统计\n",
" print(f\"\\n📊 PCA降维统计:\")\n",
" print(f\" 原始维度: {original_dims}\")\n",
" print(f\" 降维后维度: {self.n_components}\")\n",
" print(f\" 维度保留比例: {reduction_ratio:.2%}\")\n",
" print(f\" 方差保留比例: {cumsum_var[self.n_components-1]:.4f}\")\n",
" print(f\" 内存节省: {(1-reduction_ratio)*100:.1f}%\")\n",
"\n",
"print(\"✅ PCA增强数据加载器已定义\")"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"🚀 创建PCA降维数据集\n",
"======================================================================\n",
"📊 PCA配置:\n",
" enable_pca: True\n",
" n_components: None\n",
" variance_threshold: 0.95\n",
" sample_size: 15000\n",
"📊 PCA数据集初始化:\n",
" 数据类型: train\n",
" 文件数量: 45\n",
" PCA启用: ✅\n",
" 成分数量: auto\n",
" 方差阈值: 0.95\n",
"📊 PCA数据集初始化:\n",
" 数据类型: val\n",
" 文件数量: 41\n",
" PCA启用: ✅\n",
" 成分数量: auto\n",
" 方差阈值: 0.95\n",
"📊 PCA数据集初始化:\n",
" 数据类型: test\n",
" 文件数量: 41\n",
" PCA启用: ✅\n",
" 成分数量: auto\n",
" 方差阈值: 0.95\n",
"\n",
"✅ PCA数据集创建完成\n",
"\n",
"🔧 在训练集上拟合PCA...\n",
"\n",
"🔧 拟合PCA降维器...\n",
" 使用样本数: 15000\n",
" 实际样本数: 15000\n",
" 原始特征数: 7168\n",
" 实际样本数: 15000\n",
" 原始特征数: 7168\n",
" 🔍 自动选择PCA成分数...\n",
" 🔍 自动选择PCA成分数...\n",
" 📊 方差分析:\n",
" 保留95.0%方差需要: 1062 个成分\n",
" 选择成分数: 1062\n",
" 实际保留方差: 0.9501\n",
" 📊 方差分析:\n",
" 保留95.0%方差需要: 1062 个成分\n",
" 选择成分数: 1062\n",
" 实际保留方差: 0.9501\n",
" ✅ PCA拟合完成!\n",
" 降维: 7168 → 1062\n",
" 降维比例: 14.82%\n",
" 保留方差: 0.9491\n",
" 模型已保存: pca_model_train.joblib\n",
"\n",
"🔄 复制PCA模型到验证和测试集...\n",
" ✅ PCA模型复制完成\n",
"\n",
"📊 绘制PCA分析图...\n",
" ✅ PCA拟合完成!\n",
" 降维: 7168 → 1062\n",
" 降维比例: 14.82%\n",
" 保留方差: 0.9491\n",
" 模型已保存: pca_model_train.joblib\n",
"\n",
"🔄 复制PCA模型到验证和测试集...\n",
" ✅ PCA模型复制完成\n",
"\n",
"📊 绘制PCA分析图...\n"
]
},
{
"data": {
"image/png": "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7duXO3fu8N1339k7FJF4WcbpGTx4sJJ+IiIiIiIi8kjh4eF88803NGvWTEk/SXXe3t4MGDCAcePGERsba+9wJA365JNPaN68ebpO+oHG+BMReWo8PT25fPmyvcMQSZCTk5PNeAYiIiIiIiIi8bl8+TLr1q1j6dKlXLt2jXfffdfeIUk68f777/P+++/bOwxJo3TDhZkSfyIiIiIiIiIiIiKSZAcPHqRt27b4+vry2Wefpfu7a0RE0hKN8SciIiIiIiIiIiIiIiLyHNAYfyIiIiIiIiIiIiIiIiLPASX+RERERERERERERERERJ4DGuMvHrGxsZw/f56MGTNiMpnsHY6IiIjYiWEY3L59m1y5cuHgoOulkkPtKREREbFQm+rxqU0lIiIikLz2lBJ/8Th//jx58+a1dxgiIiKSRvz333/kyZPH3mE8U9SeEhERkYepTZV8alOJiIjIg5LSnlLiLx4ZM2YEzDvQy8vLztGIiIiIvdy6dYu8efNa2waSdKndnoqNjeXKlSv4+PjozoFUoP2b+rSPU5f2b+rTPk59T2sf3428S67xuQA4/955PJw9UnwdalM9Pp2jEhEREUhee0qJv3hYuk7w8vJSo0pERETUrdJjSO32VGxsLOHh4Xh5eemEcyrQ/k192sepS/s39Wkfp76ntY8dIx3B1fzcy8srVRJ/FmpTJZ/OUYmIiMiDktKeUutcRERERERERERERERE5DmgxJ+IiIiIiIiIiIhICjl16hQmkynBx/DhwwEYOXIkFSpUwMXFxTovPDw83mXOnj2b8uXL4+HhgaenJyVKlGDu3LnW+Xfv3mXAgAEUKlQId3d3vL29KVWqFOPGjcMwjKex2SIikkaoq08RERERERERERGRFOLi4kLFihVtym7evMnhw4cByJkzJwBLly7l1KlT+Pj4cO7cuQSX16tXLz7//HMA8uXLR5YsWTh//jx//PEHHTp0AKBHjx589dVXABQvXpzQ0FD27dvHgAEDcHV1pVevXim+nSIikjYp8SciIiIiIiIiIiKSQnLmzMn27dttynr27Mnhw4fJnDkzbdu2BeDnn38md+7cjBgxghEjRsS7rG3btvH555/j4ODA0qVLee2116zzbt++bX2+ZcsWAF599VV++eUX7t27R5YsWQgPD+f06dMpvYkiIpKGqatPEREREREREZF0ysnRiWE1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2bPbJvhEREREROTZosSfiIiIiIiISBp25475Lr4HE3z79kFExKNf6+gIhQubk3oPJvgCAsDBIZUDFxERERGRp06JPxEREREREZE04vJl2LULdu++n+Q7dixpY/H5+sKLL0KZMveTfEWKgItLKgctIiIiIiJphhJ/IiIiIiIiInZw7Zo5yffXX/cf//336NeZTFCo0P0kn+WRI0cqBywiIiIiImmeEn8iIiIiIiIiqezmTfj7b9sk38mTj36dqyuUKmWb4CtZEjw9UzdeST8yOGQguHSw9bmIiIiIPNvUohMRERERERFJQffumZN8O3bcT/IdPfro13l6QtmyUK6c+W6+F180j8+XQb/cJRW5ZHBhXtN59g5DRERERFKIfj6IiIiIiIiIPCbDMN+5t20bbN9ufuzZA9HRib/OzQ1eesmc5LM8ChcGB4enEraIiIiIiDynlPgTERERERERSaLbt+HPP+8n+bZvhytXEn+Ni4u5i84Hk3xFiuhOPkkbDMMgLCoMAHcnd0wmk50jEhEREZEnoZ8ZIiIiIiIiIvEwDHMXnVu23E/y7d9vLk9MsWJQqRJUrAgVKkDx4uDk9HRiFkmusKgwPEebB428M+gOHs4edo5IRERERJ6EEn8iIiIiIiIiQFSUuZvOLVvuPy5fTvw1WbKYk3yWR/nykCnT04hWREREREQkLiX+REREREREJF26c8d8F9+WLfD77+bnYWEJ13d0hNKlbRN9BQuCekYUEREREZG0Qok/ERERERERSReuXIHffrt/N9+ePRATk3B9b2+oWhWqV4cqVcxj87m7P7VwRUREREREks3B3gFMnToVf39/XF1dqVixIjt37kyw7oEDB2jWrBn+/v6YTCYmTZr0xMsUERERERGR59O1a7BsGfTqBSVKgK8vtGgBkyfDrl1xk3558sAbb8C0afDPP3D9OqxcCQMHQo0aSvqJiIiIiEjaZ9c7/hYtWkRISAgzZsygYsWKTJo0icDAQA4fPoyvr2+c+mFhYeTPn58WLVrQt2/fFFmmiIiIiIiIPB9u3IDNm2HDBhPr1mXl4MHEr3UtUcJ8N1+1auZHvnxPKVAREREREZFUYtfE34QJE+jSpQsdOnQAYMaMGaxcuZI5c+YwcODAOPXLly9P+fLlAeKd/zjLFBERERERkWdTaKg50bdpE2zcaO660zAATICTTV0HByhbFmrVgpo1zV13Zs781EMWERERERFJVXZL/EVGRrJr1y4GDRpkLXNwcKBOnTps27btqS4zIiKCiIgI6/StW7cea/0iIiIiIiKSeqKiYMcOWLMG1q6FnTshNjb+uiaTwYsvwssvm3j5ZfMdfd7eTzdekWeBo4MjzYs1tz4XERERkWeb3RJ/V69eJSYmhuzZs9uUZ8+enUOHDj3VZY4ePZoRI0Y81jpFREREREQkdRgGHD16P9G3cSPcvp1w/dKl4eWXoWbNWIoWvUKhQj44OJieXsAizyDXDK4sabHE3mGIiIiISAqxa1efacWgQYMICQmxTt+6dYu8efPaMSIREREREZH06do1WL/+frLvzJmE6xYrBrVrm5N9NWpA1qzm8thYuHzZeDoBi4iIiIiIpCF2S/xly5YNR0dHLl26ZFN+6dIlcuTI8VSX6eLigouLy2OtU0RERERERB5fTAz89ResWmV+7NplGacvLl9fqFMH6tY1P3LnfrqxioiIiIiIpHV2S/w5OztTtmxZ1q9fT9OmTQGIjY1l/fr19OzZM80sU0RERERERFLW9evmO/pWroTVq+Hq1fjrubiY7+SzJPpKlQIHh6cbq8jz7m7kXTxHewJwZ9AdPJw97ByRiIiIiDwJu3b1GRISQnBwMOXKlaNChQpMmjSJu3fv0qFDBwDatWtH7ty5GT16NACRkZEcPHjQ+vzcuXPs2bMHT09PChYsmKRlioiIiIiIyNNlGPDPP+ZE36pVsG2buTvO+JQqBfXqmR/VqoGb29ONVURERERE5Flm18Rfq1atuHLlCkOHDuXixYuUKVOG1atXkz17dgDOnDmDwwOXc54/f54XX3zROv3pp5/y6aefUrNmTTZt2pSkZYqIiIiIiEjqu3vXPEafJdl3/nz89TJmNN/N16AB1K8PuXI93ThFRERERESeJ3ZN/AH07NkzwW44Lck8C39/f4yEBntI4jJFREREREQkdVy+DD/9BMuXw7p1EB4ef72iRc2JvgYNzHf1OTs/1TBFRERERESeW3ZP/ImIiIiIiMiz68gR+PFHc7Jv2zZzt54Pc3WFV165n+wLCHjqYYqIiIiIiKQLSvyJiIiIiIhIksXGws6d5mTfjz/Cv//GXy9nTmjc2Px4+WWN1SciIiIiIvI0KPEnIiIiIiIiiYqOhs2bYckS8519Fy/GX69YMWjSBJo2hXLl4IEh20VEREREROQpUOJPRERERERE4oiOhk2bzMm+H36AK1fi1jGZoEoVc6KvSRMoVOhpRykiT8rRwZEGhRpYn4uIiIjIs02JPxEREREREQEgKgo2bryf7Lt2LW4dFxeoV8+c6GvUCHx9n36cIpJyXDO4srLNSnuHISIiIiIpRIk/ERERERGRdCwyEjZsuN+N5/Xrceu4uUGDBtCiBQQFgafnUw9TREREREREkkCJPxERERERkXQmNtY8Zt/ChbB0Kdy4EbeOu7s5ydeihTnp5+Hx9OMUERERERGR5FHiT0REREREJB0wDNi7FxYsgG+/hXPn4tbx8ICGDc3Jvvr1zck/EXm+3Y28i++n5j57L/e7jIezsvwiIiIizzIl/kRERERERJ5jJ06YE30LFsC//8ad7+EBjRubk32vvmru1lNE0pewqDB7hyAiIiIiKUSJPxERERERkefMlSuwaJG5K89t2+LOz5DBnORr08ac9FM3niIiIiIiIs8HJf5ERERERESeA5GRsGoVzJsHK1dCdHTcOtWrm5N9LVpA1qxPPUQRERERERFJZUr8iYiIiIiIPMP27DEn+xYsgKtX484vVcqc7GvdGvz8nnZ0IiIiIiIi8jQp8SciIiIiIvKMuXzZ3I3nvHmwd2/c+blywVtvwZtvQokSTz08ERERERERsRMl/kRERERERJ4B0dHwyy/w5Zfxd+Xp4gKvvQbt20OdOuDoaJcwRURERERExI6U+BMREREREUnDTp82J/vmzIFz5+LOr1jRnOxr1QoyZ37q4YnIM87B5EBNv5rW5yIiIiLybFPiT0REREREJI2Jjjbf1TdzJqxeDYZhOz9nTmjXDoKDoWhR+8QoIs8HNyc3NrXfZO8wRERERCSFKPEnIiIiksYZ/3/G32Qy2TkSEUltp06Z7+778ku4cMF2noMDNGwIXbrAq69CBv2aExERERERkYeoDwcRERGRNOrrr7+mZMmSuLm54ebmRqlSpZg/f769wxKRFBYTAytWQP36kD8/jBxpm/TLlw8+/BDOnIEffzQn/5T0ExERERERkfjo56KIiIhIGjRhwgSGDBlCz549qVq1KgBbtmyhW7duXL16lb59+9o5QhF5UjdumPj6a5g+3Xyn34McHaFRI+jaFerVM0+LiKSGu5F38Z/sD8Cpd0/h4exh34BERERE5Ino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aNGiBY6OjvYOU+Sp27IFevSAf/65X5Y5M4wcCV27mrv4FBEREREREUnv9PNYREREJA1ZsmQJX3/9NY0bN2b//v2UKlWK6Oho9u7di8lksnd4Ik/dpUswYAB8/bVteefOMHo0ZMtmn7hERERERERE0iIl/kRERETSkLNnz1K2bFkASpQogYuLC3379lXST9Kd2FiYORMGDoRbt+6Xv/QSTJ0KlSrZLzYRkeeJyWTCz9vP+lxEREREnm1K/ImIiIikITExMTg7O1unM2TIgKenpx0jEnn6Dh40d9/5xx/3yzJnhlGjoEsXUG+3IiIpx93JnVN9Ttk7DBERERFJIUr8iYiIiKQhhmHQvn17XFxcAAgPD6dbt254eHjY1Fu2bJk9whNJVRER5u47R42CqKj75R07wiefgI+P/WITEREREREReRYo8SciIiKShgQHB9tMv/nmm3aKROTp2rLFfJffv//eLytUCL74AmrVsltYIiIiIiIiIs8UJf5ERERE0pC5c+faOwSRpyo01DyO34wZ98syZIABA2DwYHBzs19sIiLpwb2oe9SYVwOAze034+akA6+IiIjIs0yJPxERERERsYtff4VOneDcuftlFSrArFlQqpT94hIRSU9ijVj+Ov+X9bmIiIiIPNsc7B2AiIiIiIikL7dvw9tvw6uv3k/6eXjApEmwdauSfiIiIiIiIiKPS3f8iYiIiIjIU/Pbb9C+PZw6db+sbl3zXX5+fvaKSkREREREROT5oDv+REREREQk1d27B337wssv30/6eXiYx/b79Vcl/URERERERERSgu74ExERERGRVLVzJ7RrB4cP3y+rXh3mzYP8+e0WloiIiIiIiMhzR3f8iYiIiKRR8+fPp2rVquTKlYvTp08DMGnSJH788Uc7RyaSNDExMHIkVKlyP+nn4gLjx8PGjUr6iYiIiIiIiKS0x078XblyhS1btrBlyxauXLmSkjGJiIiIpHvTp08nJCSEBg0acPPmTWJiYgDIlCkTkyZNsm9wIklw9izUrg1DhpgTgADlysHu3RASAo6O9o1PRETuy+aejWzu2ewdhoiIiIikgGQn/u7evUvHjh3JlSsXNWrUoEaNGuTKlYtOnToRFhaW7ACmTp2Kv78/rq6uVKxYkZ07dyZaf8mSJRQpUgRXV1dKlizJqlWrbObfuXOHnj17kidPHtzc3ChWrBgzZsxIdlwiIiIi9jRlyhRmzZrF//73PxwfyJCUK1eOffv22TEykUdbvhxKl4bffjNPOzjA0KGwbRsULWrX0ERE5CEezh5c6X+FK/2v4OHsYe9wREREROQJJTvxFxISwm+//caKFSu4efMmN2/e5Mcff+S3337jvffeS9ayFi1aREhICMOGDePvv/+mdOnSBAYGcvny5Xjrb926lTfeeINOnTqxe/dumjZtStOmTdm/f79NfKtXr+abb77h33//pU+fPvTs2ZMVK1Ykd1NFRERE7ObkyZO8+OKLccpdXFy4e/euHSISebR79+Cdd+C11+D6dXNZ3rywaROMGAEZNMK4iIiIiIiISKpKduLv+++/58svv6R+/fp4eXnh5eVFgwYNmDVrFkuXLk3WsiZMmECXLl3o0KGD9c48d3d35syZE2/9yZMn8+qrr9K/f3+KFi3KRx99xEsvvcTnn39urbN161aCg4OpVasW/v7+dO3aldKlSz/yTkIRERGRtCQgIIA9e/bEKV+9ejVFdcuUpEH790P58vBgZxvNmsHevVC9uv3iEhEREREREUlPkp34CwsLI3v27HHKfX19k9XVZ2RkJLt27aJOnTr3g3FwoE6dOmzbti3e12zbts2mPkBgYKBN/SpVqrBixQrOnTuHYRhs3LiRI0eOUK9evSTHJiIiImJvISEh9OjRg0WLFmEYBjt37uTjjz9m0KBBDBgwwN7hidiYPx8qVIADB8zTbm4wcyYsWQKZM9s3NhERSdy9qHvUmleLWvNqcS/qnr3DEREREZEnlOzOdipXrsywYcP4+uuvcXV1BeDevXuMGDGCypUrJ3k5V69eJSYmJk4SMXv27Bw6dCje11y8eDHe+hcvXrROT5kyha5du5InTx4yZMiAg4MDs2bNokaNGgnGEhERQUREhHX61q1bSd4OERERkdTQuXNn3NzcGDx4MGFhYbRp04ZcuXIxefJkWrdube/wRAAID4c+fcxJPouSJeG776BYMbuFJSIiyRBrxPLb6d+sz0VERETk2ZbsxN/kyZMJDAwkT548lC5dGoC9e/fi6urKr7/+muIBJteUKVPYvn07K1aswM/Pj82bN9OjRw9y5coV525Bi9GjRzNixIinHKmIiIhI4tq2bUvbtm0JCwvjzp07+Pr62jskEatTp6B5c9i1635Z587w2WfmO/5ERERE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lixZbB4iIiIi8uQCAwP566+/7B2GPOcWLIAmTeDePfN0nTrw+++QJ4994xIREYnPjBkzyJs3LwBr165l7dq1/PLLL9SvX5/+/fsneTkVK1Zk3rx5rF69munTp3Py5EmqV6/O7du3uXjxIs7OznHuIsyePTsXL14E4OLFizZJP8t8y7zE6ty6dYt7li/eeIwePRpvb2/rw7K9IiIiIkmV5K4+LUaMGMHs2bN57733GDx4MP/73/84deoUy5cvZ+jQoakRo4iIiEi6ExQURP/+/Tl48CAlS5bEycnJZn7jxo3tFJk8LyZPhj597k+3aAHz54OLC8TG2i0sERGRBF28eNGaCPv5559p2bIl9erVw9/fn4oVKyZ5OfXr17c+L1WqFBUrVsTPz4/Fixfj5uaW4nEnx6BBgwgJCbFO37p1S8k/ERERSZZkJ/4WLFjArFmzCAoKYvjw4bzxxhsUKFCAUqVKsX37dnr37p0acYqIiIikK126dAHgww8/jDPPZDIRExPztEOS54RhwJAh8PHH98u6dYPPPwdHR/vFJSIi8iiZM2fmv//+I2/evKxevZqRI0cCYBjGE7WNMmXKROHChTl27Bh169YlMjIyztiBly5dso4JmCNHDnbu3GmzjEuXLlnnWf5ayh6s4+XllWhy0cXFBRcXl8feFhEREZFkd/V58eJFSpYsCYCnpyehoaEANGzYkJUrV6ZsdCIiIiLpVGxsbIIPJf3kccXGQq9etkm/oUNh2jQl/UREJO17/fXXadOmDXXr1uXatWvWO/d2795NwYIFH3u5d+7c4fjx4+TMmZOyZcvi5OTE+vXrrfMPHz7MmTNnqFy5MgCVK1dm3759XL582Vpn7dq1eHl5UaxYMWudB5dhqWNZhoiIiEhqSXbiL0+ePFy4cAGAAgUKsGbNGgD+/PNPXZEkIiIiIpJGxcbC22/D1Kn3yz77DEaMAJPJfnGJiIh9eTh7YAwzMIYZeDh72DucRE2cOJGePXtSrFgx1q5di6enJwAXLlyge/fuSV5Ov379+O233zh16hRbt27ltddew9HRkTfeeANvb286depESEgIGzduZNeuXXTo0IHKlStTqVIlAOrVq0exYsV466232Lt3L7/++iuDBw+mR48e1nNj3bp148SJEwwYMIBDhw4xbdo0Fi9eTN++fVN+x4iIiIg8INldfb722musX7+eihUr0qtXL958802+/PJLzpw5o8aLiIiISAq6e/cuv/32G2fOnCEyMtJmnrpXl+SIiYFOneCrr8zTDg4wdy60a2ffuERERJLDycmJfv36xSlP7vmos2fP8sYbb3Dt2jV8fHyoVq0a27dvx8fHBzAnGB0cHGjWrBkREREEBgYybdo06+sdHR35+eefeeedd6hcuTIeHh4EBwfbdNEeEBDAypUr6du3L5MnTyZPnjzMnj2bwMDAx9x6ERERkaRJduLvk08+sT5v1aoVfn5+bN26lUKFCtGoUaMUDU5EREQkvdq9ezcNGjQgLCyMu3fvkiVLFq5evYq7uzu+vr5K/EmSRUdDcDAsXGiednSEBQugVSv7xiUiIvI4jh49ysaNG7l8+TKxsbE284YOHZqkZXz33XeJznd1dWXq1KlMffA2+Yf4+fmxatWqRJdTq1Ytdu/enaSYRERERFJKshN/D6tUqZK1q4O//vqLcuXKPXFQIiIiIuld3759adSoETNmzMDb25vt27fj5OTEm2++ybvvvmvv8OQZERUFbdrA0qXm6QwZYNEieP11+8YlIiJpR3h0OG/98BYA81+bj2sGVztHlLBZs2bxzjvvkC1bNnLkyIHpgb6qTSZTkhN/IiIiIs+zZCf+7ty5g6OjI25ubtayPXv2MGTIEFatWkVMTEyKBigiIiKSHu3Zs4eZM2fi4OCAo6MjERER5M+fn7FjxxIcHMzrytzII0RFme/q++EH87SzszkBqE46RETkQTGxMSw9aL5CZF6TefYN5hFGjhzJxx9/zPvvv2/vUERERETSLIekVvzvv/+oXLky3t7eeHt7ExISQlhYGO3ataNixYp4eHiwdevW1IxVREREJN1wcnLCwcHcVPP19eXMmTMAeHt7899//9kzNHkGxMSYx++zJP1cXODHH5X0ExGRZ9uNGzdo0aKFvcMQERERSdOSfMdf//79CQ8PZ/LkySxbtozJkyfz+++/U7FiRY4fP06ePHlSM04RERGRdOXFF1/kzz//pFChQtSsWZOhQ4dy9epV5s+fT4kSJewdnqRhsbHQuTNYhi9ycYGffoK6de0bl4iIyJNq0aIFa9asoVu3bvYORURERCTNSnLib/PmzSxbtoxKlSrRsmVLcuTIQdu2benTp08qhiciIiKSPo0aNYrbt28D8PHHH9OuXTveeecdChUq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7BydiIiISNqQ7MTfqlWrcHR0JDAw0Kb8119/JTY2lvr166dYcCIiIiLpTfPmzencuTNt27a1jqVctmxZTp8+zaFDh8iXLx/ZsmWzc5SSWq5fh5YtzeP7AfTqBcHB9o1JRESeb9Gx0Uz7axoAY+uOxYW0m/hzdnYmLCwMgHXr1tGuXTsAsmTJYr0TUERERCS9S3ZXnwMHDiQmJiZOuWEYDBw4MEWCEhEREUmvbty4QVBQEPny5WPo0KGcOHECAHd3d1566aUnSvpNnToVf39/XF1dqVixIjt37ky0/s2bN+nRowc5c+bExcWFwoULs2rVqsdevyTOMKB9ezhzxjxdrRqMH2/XkERERNKUatWqERISwkcffcTOnTsJCgoC4MiRI+TJk8fO0YmIiIikDclO/B09etTarcKDihQpwrFjx1IkKBEREZH0av369Zw4cYJOnTrxzTffUKhQIV555RUWLlxIRETEYy930aJFhISEMGzYMP7++29Kly5NYGAgly9fjrd+ZGQkdevW5dSpUyxdupTDhw8za9YscufO/dgxSOImToSffjI/z5YNvv0WnJzsG5OIiEha8vnnn5MhQwaWLl3K9OnTre2SX375hVdffdXO0YmIiIikDclO/Hl7e1uvPH/QsWPH8PDwSJGgRERERNIzPz8/hg8fzokTJ1i7di25cuWiS5cu5MyZkx49erBr165kL3PChAl06dKFDh06UKxYMWbMmIG7uztz5syJt/6cOXO4fv06y5cvp2rVqvj7+1OzZk1Kly79pJsn8di+Hd5///70/PmgGxdERERs5cuXj59//pm9e/fSqVMna/nEiRP57LPP7BiZiIiISNqR7MRfkyZN6NOnD8ePH7eWHTt2jPfee4/GjRunaHAiIiIi6d0rr7zCN998w8WLFxk9ejTfffcdFStWTNYyIiMj2bVrF3Xq1LGWOTg4UKdOHbZt2xbva1asWEHlypXp0aMH2bNnp0SJEowaNSreLt/lyVjG9YuONk8PGgS6aUFERCR+sbGxHDlyhC1btrB582abh4iIiIhAhuS+YOzYsbz66qsUKVLE2n/62bNnqV69Op9++mmKBygiIiKS3p08eZJ58+Yxb948QkNDbRJ4SXH16lViYmLInj27TXn27Nk5dOhQvK85ceIEGzZsoG3btqxatYpjx47RvXt3oqKiGDZsWJz6ERER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t5YsWaI1a9bozp07evnll/Xuu+/q6aeftkY+AACAPOncuXNq2LChJClfvny6ceOGJKlLly5q0KCB5s2bZ8t4ecbKldLly8nb7dtLvr62zQMAQF60dOlSW0cAAADIMTK8xt+UKVNUrVo1PfXUU/r55581depURUVFafny5RT9AAAAspiPj495ZF/ZsmW1Z88eSdLp06dlMplsGS3PMJnujvaTJGa0BwAAAAAA9i7DI/6mTp2q1157TWvWrFH16tWtmQkAACDPa9q0qb788kvVrl1b3bt316BBg/T555/rwIEDatu2ra3j5Qk//CD99FPydv36Ur16ts0DAAAAAADwIBku/F24cEHOzs7WzAIAAID/WbhwoYxGoySpX79+Klq0qHbv3q3WrVvrP//5j43T5Q33jvbr3992OQAAsCZXJ1d93elr8zYAAABytgwX/ij6AQAAZB8HBwc5ONydlf2VV17RK6+8YsNEecuff0pffJG87e2dvL4fAAC5kZODk1o91srWMQAAAJBFMlz4AwAAgHX9lDKvZAbUrFnTikmwYIGUlJS8/Z//SC4uts0DAAAAAACQERT+AAAA7IS/v78MBoNMJtN92xkMBiWlVKWQ5eLjpUWLkrednJILfwAA5FaJSYla+fNKSVLnGp3l7GjfMz59/PHHCg8P1+nTpxUZGaly5cpp1qxZ8vX11UsvvWTreAAAADZH4Q8AAMBOnD592tYRIGn1aunKleTtl1+WSpa0bR4AAKwpISlB3f/bXZLU3q+9XRf+FixYoNGjR2vgwIF6//33zV+EKlSokGbNmkXhDwAAQBks/MXExGS4Qw8Pj4cOAwAAkJeVK1fO1hGgu6P9JCk01HY5AACApblz52rRokUKDg7WpEmTzPsDAgL09ttv2zAZAACA/chQ4a9QoUIyGAwZ6pBppwAAALLGiRMnNHfuXP3666+SpGrVqql///6qUqWKjZPlXsePSzt3Jm/7+UkNG9o2DwAAuOv06dOqXbt2qv2urq6KjY21QSIAAAD7k6HC37Zt28zbZ86c0bBhw9StWzcFBgZKkiIjI7V8+XJNnDjROikBAADymLVr1+qVV15RQECA+T3Xnj17VL16dX366adq166djRPmTkuW3N3u1UvK4HffAABANvD19dWRI0dSzZKwefNmVatWzUapAAAA7EuGCn+NGzc2b48fP14z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x+rVqzV27FjNnz9fTz75pD7++GPNmTNHFSpUSPcerFy5UgUKFNAbb7yR5vFChQpJkr744gsNGDBAs2bNUrNmzfT111+re/fuKl26tJ555hlz+3fffVczZszQjBkzNHToUL366quqUKGChg8frrJly6pHjx4KDQ3Vpk2bzK85efKkVq9era+++koxMTHq2bOn3njjDfNUk7Nnz9b06dP14Ycfqnbt2lqyZIleeuklHTlyRNWqVTP3M2LECE2bNk2VK1fWiBEj1KlTJ508eVJOTk46deqUWrRooffee09LlizR5cuXFRoaqtDQUC1dutTcx/Tp0/Xuu+/qnXfe0eeff66+ffuqcePGqlKlivbt26d69erp22+/1eOPPy4XFxfduXNHwcHB6t27tz755BMlJCRo3759MhjSH6m8c+dOdenSJd3jaUlISNDBgwc1fPhw8z4HBwc1a9bMXOC8dOmS9u7dq86dO5sLrFWrVtX777+vJ598Mt2+r1+/nuq/47Jly8rb21u7du1SlSpVMpUV9qmYezFbRwAAAEAWofAHAADSRGER9ooRf7lLgYkF0j3WsnJLbXj17t9FXtO8FJcYl2bbxuUaa3u37ebn5WeX15W4K6namcaYMpzNyclJy5YtU+/evRUeHq46deqocePGeuWVV1SzZk1JyUWaffv26dKlS+bC4LRp07R+/Xp9/vnnev3111P1O2bMGE2bNk1t2rSRk5OTKlSooGPHjunDDz9USEiIVq1apcuXL2v//v3mgkulSpXMry9QoICcnJzk4+Nj3peRHLNmzVLPnj3Vs2dPSdJ7772nb7/99r6j/n7//XdVqFBBzg/4ZZs2bZq6detmLhCGhYVpz549mjZtmkXhr3v37urQoYMkaejQoQoMDNSoUaMUFBQkSRowYIC6d+9u0Xd8fLxWrFihUqVKSZLmzp2rVq1aafr06fLx8dG0adM0dOhQvfLKK5KkyZMna9u2bZo7d67FSMq3335brVol/3s0btw4Pf744zp58qSqVq2qiRMnqnPnzho4cKAkqXLlypozZ44aN26sBQsWyM0tuQDdsmVL8zUOHTpUM2fO1LZt21SlShUVL15cklS0aFHzz+bq1au6fv26XnjhBVWsWFGSLIqR/3bt2jVdv35dJUuWvO/9/rcrV64oKSlJ3t7eFvu9vb11/PhxSdIff/whKXkU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",
"text/plain": [
"<Figure size 1800x500 with 3 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"📊 PCA降维统计:\n",
" 原始维度: 7168\n",
" 降维后维度: 1062\n",
" 维度保留比例: 14.82%\n",
" 方差保留比例: 0.9491\n",
" 内存节省: 85.2%\n",
"\n",
"🎯 使用方法:\n",
" for features, labels in train_dataset_pca.get_batch_generator():\n",
" # features 已经过PCA降维\n",
" # 可直接用于LightGBM训练\n",
" pass\n"
]
}
],
"source": [
"# 🚀 PCA降维数据集配置和使用\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🚀 创建PCA降维数据集\")\n",
"print(\"=\"*70)\n",
"\n",
"# PCA配置选项\n",
"PCA_CONFIG = {\n",
" 'enable_pca': True, # 是否启用PCA\n",
" 'n_components': None, # None=自动选择, 或指定具体数值如512\n",
" 'variance_threshold': 0.95, # 保留95%的方差\n",
" 'sample_size': 15000, # 用于拟合PCA的样本数\n",
"}\n",
"\n",
"print(\"📊 PCA配置:\")\n",
"for key, value in PCA_CONFIG.items():\n",
" print(f\" {key}: {value}\")\n",
"\n",
"# 创建PCA增强数据集\n",
"train_dataset_pca = PCAEnhancedDataset(\n",
" data_dir=data_dir, \n",
" data_type='train',\n",
" max_samples_per_file=MAX_SAMPLES_PER_FILE,\n",
" enable_pca=PCA_CONFIG['enable_pca'],\n",
" n_components=PCA_CONFIG['n_components'],\n",
" variance_threshold=PCA_CONFIG['variance_threshold']\n",
")\n",
"\n",
"val_dataset_pca = PCAEnhancedDataset(\n",
" data_dir=data_dir,\n",
" data_type='val', \n",
" max_samples_per_file=MAX_SAMPLES_PER_FILE,\n",
" enable_pca=PCA_CONFIG['enable_pca']\n",
")\n",
"\n",
"test_dataset_pca = PCAEnhancedDataset(\n",
" data_dir=data_dir,\n",
" data_type='test',\n",
" max_samples_per_file=MAX_SAMPLES_PER_FILE, \n",
" enable_pca=PCA_CONFIG['enable_pca']\n",
")\n",
"\n",
"print(f\"\\n✅ PCA数据集创建完成\")\n",
"\n",
"# 拟合PCA (只在训练集上)\n",
"if PCA_CONFIG['enable_pca']:\n",
" print(f\"\\n🔧 在训练集上拟合PCA...\")\n",
" train_dataset_pca.fit_pca(sample_size=PCA_CONFIG['sample_size'])\n",
" \n",
" # 将训练好的PCA应用到验证和测试集\n",
" if train_dataset_pca.is_fitted:\n",
" print(f\"\\n🔄 复制PCA模型到验证和测试集...\")\n",
" val_dataset_pca.scaler = train_dataset_pca.scaler\n",
" val_dataset_pca.pca = train_dataset_pca.pca\n",
" val_dataset_pca.n_components = train_dataset_pca.n_components\n",
" val_dataset_pca.is_fitted = True\n",
" \n",
" test_dataset_pca.scaler = train_dataset_pca.scaler\n",
" test_dataset_pca.pca = train_dataset_pca.pca\n",
" test_dataset_pca.n_components = train_dataset_pca.n_components\n",
" test_dataset_pca.is_fitted = True\n",
" \n",
" print(f\" ✅ PCA模型复制完成\")\n",
" \n",
" # 绘制PCA分析图\n",
" print(f\"\\n📊 绘制PCA分析图...\")\n",
" train_dataset_pca.plot_pca_analysis()\n",
" else:\n",
" print(f\"❌ PCA拟合失败\")\n",
"\n",
"print(f\"\\n🎯 使用方法:\")\n",
"print(f\" for features, labels in train_dataset_pca.get_batch_generator():\")\n",
"print(f\" # features 已经过PCA降维\")\n",
"print(f\" # 可直接用于LightGBM训练\")\n",
"print(f\" pass\")"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"📊 PCA降维效果测试\n",
"======================================================================\n",
"🔍 测试PCA数据加载...\n",
" 正在加载文件 1/45: t15.2025.04.13_train_concatenated.npz\n",
" ✅ PCA数据加载成功\n",
" PCA特征形状: (14677, 1062)\n",
" 标签形状: (14677,)\n",
" PCA特征范围: [-83.4304, 176.7492]\n",
" 标签范围: [0, 40]\n",
"\n",
"📊 降维对比:\n",
" 原始特征维度: 7168\n",
" PCA特征维度: 1062\n",
" 降维比例: 14.82%\n",
" 内存节省: 85.2%\n",
"\n",
"⚡ 训练速度预估对比:\n",
" 原始特征数: 7168\n",
" PCA特征数: 1062\n",
" 预估速度提升: 6.7x\n",
" 预估训练时间: 14.8% of 原始时间\n",
"\n",
"💡 PCA配置建议:\n",
" 🔬 数据探索阶段:\n",
" - variance_threshold: 0.90-0.95 (快速原型)\n",
" - n_components: 200-500 (固定维度)\n",
" 🎯 性能优化阶段:\n",
" - variance_threshold: 0.95-0.99 (保持精度)\n",
" - n_components: 根据验证集性能调整\n",
" 🚀 生产部署阶段:\n",
" - 根据内存和速度需求选择最优配置\n",
"\n",
"🔧 使用不同PCA配置的方法:\n",
" # 快速原型 (大幅降维)\n",
" dataset_fast = PCAEnhancedDataset(..., n_components=200)\n",
" \n",
" # 平衡配置 (自动选择)\n",
" dataset_balanced = PCAEnhancedDataset(..., variance_threshold=0.95)\n",
" \n",
" # 高精度配置 (保留更多信息)\n",
" dataset_precision = PCAEnhancedDataset(..., variance_threshold=0.99)\n"
]
}
],
"source": [
"# 📊 PCA降维效果测试和对比\n",
"\n",
"print(\"=\"*70)\n",
"print(\"📊 PCA降维效果测试\")\n",
"print(\"=\"*70)\n",
"\n",
"def test_pca_loading():\n",
" \"\"\"测试PCA数据加载\"\"\"\n",
" if not train_dataset_pca.is_fitted:\n",
" print(\"❌ PCA未拟合无法测试\")\n",
" return\n",
" \n",
" print(\"🔍 测试PCA数据加载...\")\n",
" \n",
" # 测试加载一个批次\n",
" try:\n",
" for features_pca, labels in train_dataset_pca.get_batch_generator():\n",
" print(f\" ✅ PCA数据加载成功\")\n",
" print(f\" PCA特征形状: {features_pca.shape}\")\n",
" print(f\" 标签形状: {labels.shape}\")\n",
" print(f\" PCA特征范围: [{features_pca.min():.4f}, {features_pca.max():.4f}]\")\n",
" print(f\" 标签范围: [{labels.min()}, {labels.max()}]\")\n",
" \n",
" # 对比原始数据\n",
" print(f\"\\n📊 降维对比:\")\n",
" print(f\" 原始特征维度: 7168\")\n",
" print(f\" PCA特征维度: {features_pca.shape[1]}\")\n",
" print(f\" 降维比例: {features_pca.shape[1]/7168:.2%}\")\n",
" print(f\" 内存节省: {(1-features_pca.shape[1]/7168)*100:.1f}%\")\n",
" break\n",
" \n",
" except Exception as e:\n",
" print(f\"❌ PCA数据加载失败: {e}\")\n",
"\n",
"def compare_training_speed():\n",
" \"\"\"比较训练速度(模拟)\"\"\"\n",
" if not train_dataset_pca.is_fitted:\n",
" print(\"❌ PCA未拟合无法比较\")\n",
" return\n",
" \n",
" print(f\"\\n⚡ 训练速度预估对比:\")\n",
" original_features = 7168\n",
" pca_features = train_dataset_pca.n_components\n",
" \n",
" # 简单的复杂度估算 (特征数的线性关系)\n",
" speed_improvement = original_features / pca_features\n",
" \n",
" print(f\" 原始特征数: {original_features}\")\n",
" print(f\" PCA特征数: {pca_features}\")\n",
" print(f\" 预估速度提升: {speed_improvement:.1f}x\")\n",
" print(f\" 预估训练时间: {1/speed_improvement:.1%} of 原始时间\")\n",
"\n",
"# 执行测试\n",
"test_pca_loading()\n",
"compare_training_speed()\n",
"\n",
"print(f\"\\n💡 PCA配置建议:\")\n",
"print(f\" 🔬 数据探索阶段:\")\n",
"print(f\" - variance_threshold: 0.90-0.95 (快速原型)\")\n",
"print(f\" - n_components: 200-500 (固定维度)\")\n",
"print(f\" 🎯 性能优化阶段:\") \n",
"print(f\" - variance_threshold: 0.95-0.99 (保持精度)\")\n",
"print(f\" - n_components: 根据验证集性能调整\")\n",
"print(f\" 🚀 生产部署阶段:\")\n",
"print(f\" - 根据内存和速度需求选择最优配置\")\n",
"\n",
"print(f\"\\n🔧 使用不同PCA配置的方法:\")\n",
"print(f\" # 快速原型 (大幅降维)\")\n",
"print(f\" dataset_fast = PCAEnhancedDataset(..., n_components=200)\")\n",
"print(f\" \")\n",
"print(f\" # 平衡配置 (自动选择)\")\n",
"print(f\" dataset_balanced = PCAEnhancedDataset(..., variance_threshold=0.95)\")\n",
"print(f\" \")\n",
"print(f\" # 高精度配置 (保留更多信息)\")\n",
"print(f\" dataset_precision = PCAEnhancedDataset(..., variance_threshold=0.99)\")"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"📖 PCA降维数据集使用指南\n",
"======================================================================\n",
"🎯 你现在有两种数据集可以使用:\n",
" 1. 集成PCA数据集 (推荐) - train_dataset, val_dataset, test_dataset\n",
" 2. 独立PCA数据集 - train_dataset_pca, val_dataset_pca, test_dataset_pca\n",
"\n",
"🚀 方式1: 使用集成PCA数据集 (推荐)\n",
"==================================================\n",
"✅ 特点:\n",
" - PCA已集成到数据加载流程\n",
" - 自动降维: 7168 → 1062 维\n",
" - 内存节省: 85.2%\n",
" - 训练速度提升: 6.7倍\n",
"\n",
"📝 使用示例:\n",
"# 分批训练 (内存友好)\n",
"for features_pca, labels in train_dataset.get_batch_generator():\n",
" print(f'批次特征: {features_pca.shape}, 标签: {labels.shape}')\n",
" # features_pca 已经是1062维的降维特征\n",
" # 可以直接用于LightGBM训练\n",
" break # 只演示第一批\n",
"\n",
"# 一次性加载 (如果内存够用)\n",
"# X_train_pca, y_train = train_dataset.load_all_data()\n",
"# X_val_pca, y_val = val_dataset.load_all_data()\n",
"\n",
"==================================================\n",
"🧪 让我们测试一下数据加载:\n",
"❌ 集成PCA数据集测试失败: name 'train_dataset' is not defined\n",
"\n",
"💡 现在你可以直接用这些数据训练LightGBM:\n",
" • 特征已经降维,训练速度更快\n",
" • 内存使用更少\n",
" • PCA变换一致应用于训练/验证/测试集\n"
]
}
],
"source": [
"# 📖 PCA降维数据集使用指南\n",
"\n",
"print(\"=\"*70)\n",
"print(\"📖 PCA降维数据集使用指南\")\n",
"print(\"=\"*70)\n",
"\n",
"print(\"🎯 你现在有两种数据集可以使用:\")\n",
"print(\" 1. 集成PCA数据集 (推荐) - train_dataset, val_dataset, test_dataset\")\n",
"print(\" 2. 独立PCA数据集 - train_dataset_pca, val_dataset_pca, test_dataset_pca\")\n",
"\n",
"print(\"\\n🚀 方式1: 使用集成PCA数据集 (推荐)\")\n",
"print(\"=\" * 50)\n",
"\n",
"print(\"✅ 特点:\")\n",
"print(\" - PCA已集成到数据加载流程\")\n",
"print(\" - 自动降维: 7168 → 1062 维\")\n",
"print(\" - 内存节省: 85.2%\")\n",
"print(\" - 训练速度提升: 6.7倍\")\n",
"\n",
"print(\"\\n📝 使用示例:\")\n",
"print(\"# 分批训练 (内存友好)\")\n",
"print(\"for features_pca, labels in train_dataset.get_batch_generator():\")\n",
"print(\" print(f'批次特征: {features_pca.shape}, 标签: {labels.shape}')\")\n",
"print(\" # features_pca 已经是1062维的降维特征\")\n",
"print(\" # 可以直接用于LightGBM训练\")\n",
"print(\" break # 只演示第一批\")\n",
"print()\n",
"\n",
"print(\"# 一次性加载 (如果内存够用)\")\n",
"print(\"# X_train_pca, y_train = train_dataset.load_all_data()\")\n",
"print(\"# X_val_pca, y_val = val_dataset.load_all_data()\")\n",
"\n",
"print(\"\\n\" + \"=\"*50)\n",
"print(\"🧪 让我们测试一下数据加载:\")\n",
"\n",
"# 测试集成PCA数据集\n",
"try:\n",
" sample_count = 0\n",
" for features_pca, labels in train_dataset.get_batch_generator():\n",
" sample_count += features_pca.shape[0]\n",
" print(f\"✅ 集成PCA数据集测试成功!\")\n",
" print(f\" 批次特征形状: {features_pca.shape}\")\n",
" print(f\" 批次标签形状: {labels.shape}\")\n",
" print(f\" 特征维度: {features_pca.shape[1]} (已降维)\")\n",
" print(f\" 标签范围: {labels.min()} - {labels.max()}\")\n",
" \n",
" # 检查是否真的是PCA降维数据\n",
" if features_pca.shape[1] == 1062:\n",
" print(f\" 🔬 确认: 数据已通过PCA降维 (7168→1062)\")\n",
" else:\n",
" print(f\" ⚠️ 注意: 特征维度为 {features_pca.shape[1]}\")\n",
" break\n",
" \n",
"except Exception as e:\n",
" print(f\"❌ 集成PCA数据集测试失败: {e}\")\n",
"\n",
"print(f\"\\n💡 现在你可以直接用这些数据训练LightGBM:\")\n",
"print(f\" • 特征已经降维,训练速度更快\")\n",
"print(f\" • 内存使用更少\")\n",
"print(f\" • PCA变换一致应用于训练/验证/测试集\")"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"🚀 使用PCA降维数据训练LightGBM\n",
"======================================================================\n",
"🔧 训练设备: GPU\n",
"\n",
"📊 加载PCA降维训练数据...\n",
" 正在加载文件 1/45: t15.2025.04.13_train_concatenated.npz\n",
" 已加载批次 1: 14677 样本\n",
" 正在加载文件 2/45: t15.2024.07.21_train_concatenated.npz\n",
" 已加载批次 2: 44173 样本\n",
" 正在加载文件 3/45: t15.2024.03.17_train_concatenated.npz\n",
" 已加载批次 3: 64462 样本\n",
" ✅ 训练数据准备完成:\n",
" 特征形状: (123312, 1062) (PCA降维后)\n",
" 标签形状: (123312,)\n",
" 类别数: 41\n",
"\n",
"🔄 数据分割:\n",
" 训练集: 98649 样本\n",
" 验证集: 24663 样本\n",
"\n",
"🏗️ LightGBM配置:\n",
" objective: multiclass\n",
" num_class: 41\n",
" metric: multi_logloss\n",
" boosting_type: gbdt\n",
" device: gpu\n",
" num_leaves: 128\n",
" learning_rate: 0.1\n",
" feature_fraction: 0.8\n",
" bagging_fraction: 0.8\n",
" bagging_freq: 5\n",
" verbose: -1\n",
" random_state: 42\n",
" gpu_platform_id: 0\n",
" gpu_device_id: 0\n",
" max_bin: 511\n",
"\n",
"🔄 创建LightGBM数据集...\n",
"\n",
"🚀 开始训练LightGBM模型...\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"[LightGBM] [Fatal] bin size 512 cannot run on GPU\n"
]
},
{
"ename": "LightGBMError",
"evalue": "bin size 512 cannot run on GPU",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mLightGBMError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/4234267123.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 103\u001b[0m ]\n\u001b[1;32m 104\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 105\u001b[0;31m model = lgb.train(\n\u001b[0m\u001b[1;32m 106\u001b[0m \u001b[0mlgb_params\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 107\u001b[0m \u001b[0mtrain_lgb\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/lightgbm/engine.py\u001b[0m in \u001b[0;36mtrain\u001b[0;34m(params, train_set, num_boost_round, valid_sets, valid_names, feval, init_model, feature_name, categorical_feature, keep_training_booster, callbacks)\u001b[0m\n\u001b[1;32m 253\u001b[0m \u001b[0;31m# construct booster\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 254\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 255\u001b[0;31m \u001b[0mbooster\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mBooster\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtrain_set\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mtrain_set\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 256\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mis_valid_contain_train\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 257\u001b[0m \u001b[0mbooster\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_train_data_name\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrain_data_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/lightgbm/basic.py\u001b[0m in \u001b[0;36m__init__\u001b[0;34m(self, params, train_set, model_file, model_str)\u001b[0m\n\u001b[1;32m 3435\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mupdate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mtrain_set\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3436\u001b[0m \u001b[0mparams_str\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0m_param_dict_to_str\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparams\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3437\u001b[0;31m _safe_call(_LIB.LGBM_BoosterCreate(\n\u001b[0m\u001b[1;32m 3438\u001b[0m \u001b[0mtrain_set\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_handle\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3439\u001b[0m \u001b[0m_c_str\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparams_str\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m/usr/local/lib/python3.11/dist-packages/lightgbm/basic.py\u001b[0m in \u001b[0;36m_safe_call\u001b[0;34m(ret)\u001b[0m\n\u001b[1;32m 261\u001b[0m \"\"\"\n\u001b[1;32m 262\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mret\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 263\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mLightGBMError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0m_LIB\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mLGBM_GetLastError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdecode\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'utf-8'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 264\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 265\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mLightGBMError\u001b[0m: bin size 512 cannot run on GPU"
]
}
],
"source": [
"# 🚀 完整的LightGBM训练示例 (使用PCA降维数据)\n",
"\n",
"import lightgbm as lgb\n",
"import numpy as np\n",
"import time\n",
"from sklearn.metrics import accuracy_score, classification_report\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🚀 使用PCA降维数据训练LightGBM\")\n",
"print(\"=\"*70)\n",
"\n",
"# 1. 检查GPU可用性\n",
"def check_gpu():\n",
" try:\n",
" test_data = lgb.Dataset(np.random.rand(100, 10), label=np.random.randint(0, 2, 100))\n",
" test_params = {'device': 'gpu', 'objective': 'binary', 'verbose': -1}\n",
" lgb.train(test_params, test_data, num_boost_round=1, callbacks=[])\n",
" return True\n",
" except:\n",
" return False\n",
"\n",
"gpu_available = check_gpu()\n",
"device = 'gpu' if gpu_available else 'cpu'\n",
"print(f\"🔧 训练设备: {device.upper()}\")\n",
"\n",
"# 2. 加载PCA降维数据 (小批量快速训练示例)\n",
"print(f\"\\n📊 加载PCA降维训练数据...\")\n",
"\n",
"# 收集少量数据用于快速演示\n",
"train_features_list = []\n",
"train_labels_list = []\n",
"batch_count = 0\n",
"\n",
"for features_pca, labels in train_dataset.get_batch_generator():\n",
" train_features_list.append(features_pca)\n",
" train_labels_list.append(labels)\n",
" batch_count += 1\n",
" print(f\" 已加载批次 {batch_count}: {features_pca.shape[0]} 样本\")\n",
" \n",
" # 只取前3个批次用于快速演示\n",
" if batch_count >= 3:\n",
" break\n",
"\n",
"# 合并数据\n",
"X_train_pca = np.vstack(train_features_list)\n",
"y_train = np.hstack(train_labels_list)\n",
"\n",
"print(f\" ✅ 训练数据准备完成:\")\n",
"print(f\" 特征形状: {X_train_pca.shape} (PCA降维后)\")\n",
"print(f\" 标签形状: {y_train.shape}\")\n",
"print(f\" 类别数: {len(np.unique(y_train))}\")\n",
"\n",
"# 3. 数据分割\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(\n",
" X_train_pca, y_train, test_size=0.2, random_state=42, stratify=y_train\n",
")\n",
"\n",
"print(f\"\\n🔄 数据分割:\")\n",
"print(f\" 训练集: {X_train_split.shape[0]} 样本\")\n",
"print(f\" 验证集: {X_val_split.shape[0]} 样本\")\n",
"\n",
"# 4. LightGBM参数配置\n",
"lgb_params = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': len(np.unique(y_train)),\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': device,\n",
" 'num_leaves': 128, # 适中的复杂度\n",
" 'learning_rate': 0.1,\n",
" 'feature_fraction': 0.8,\n",
" 'bagging_fraction': 0.8,\n",
" 'bagging_freq': 5,\n",
" 'verbose': -1,\n",
" 'random_state': 42,\n",
"}\n",
"\n",
"if device == 'gpu':\n",
" lgb_params.update({\n",
" 'gpu_platform_id': 0,\n",
" 'gpu_device_id': 0,\n",
" 'max_bin': 511, # GPU优化参数\n",
" })\n",
"\n",
"print(f\"\\n🏗 LightGBM配置:\")\n",
"for key, value in lgb_params.items():\n",
" print(f\" {key}: {value}\")\n",
"\n",
"# 5. 创建LightGBM数据集\n",
"print(f\"\\n🔄 创建LightGBM数据集...\")\n",
"train_lgb = lgb.Dataset(X_train_split, label=y_train_split)\n",
"val_lgb = lgb.Dataset(X_val_split, label=y_val_split, reference=train_lgb)\n",
"\n",
"# 6. 训练模型\n",
"print(f\"\\n🚀 开始训练LightGBM模型...\")\n",
"start_time = time.time()\n",
"\n",
"callbacks = [\n",
" lgb.log_evaluation(period=10),\n",
" lgb.early_stopping(stopping_rounds=20)\n",
"]\n",
"\n",
"model = lgb.train(\n",
" lgb_params,\n",
" train_lgb,\n",
" valid_sets=[train_lgb, val_lgb],\n",
" valid_names=['train', 'val'],\n",
" num_boost_round=100,\n",
" callbacks=callbacks\n",
")\n",
"\n",
"training_time = time.time() - start_time\n",
"\n",
"print(f\"\\n✅ 训练完成!\")\n",
"print(f\" 训练时间: {training_time:.2f} 秒\")\n",
"print(f\" 最佳迭代: {model.best_iteration}\")\n",
"\n",
"# 7. 快速评估\n",
"print(f\"\\n📊 模型评估:\")\n",
"\n",
"# 训练集评估\n",
"train_pred = model.predict(X_train_split, num_iteration=model.best_iteration)\n",
"train_pred_labels = np.argmax(train_pred, axis=1)\n",
"train_acc = accuracy_score(y_train_split, train_pred_labels)\n",
"\n",
"# 验证集评估 \n",
"val_pred = model.predict(X_val_split, num_iteration=model.best_iteration)\n",
"val_pred_labels = np.argmax(val_pred, axis=1)\n",
"val_acc = accuracy_score(y_val_split, val_pred_labels)\n",
"\n",
"print(f\" 训练集准确率: {train_acc:.4f} ({train_acc*100:.2f}%)\")\n",
"print(f\" 验证集准确率: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n",
"\n",
"# 8. 使用提示\n",
"print(f\"\\n💡 使用PCA降维的优势:\")\n",
"print(f\" ✅ 特征维度减少: 7168 → {X_train_pca.shape[1]} (85.2%内存节省)\")\n",
"print(f\" ✅ 训练速度提升: 预计6.7倍加速\")\n",
"print(f\" ✅ 保留方差: 94.91% (信息损失很小)\")\n",
"\n",
"print(f\"\\n🔄 如果要训练完整模型:\")\n",
"print(f\" 1. 增加 batch_count 限制或移除限制\")\n",
"print(f\" 2. 增加 num_boost_round\")\n",
"print(f\" 3. 调整GPU参数以获得最佳性能\")\n",
"\n",
"# 清理内存\n",
"del train_features_list, train_labels_list, X_train_pca, y_train\n",
"del X_train_split, X_val_split, y_train_split, y_val_split\n",
"gc.collect()\n",
"\n",
"print(f\"\\n🎯 现在你知道如何使用PCA降维数据了!\")"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"🔧 修复GPU配置后的LightGBM训练\n",
"======================================================================\n",
"🔧 训练设备: GPU\n",
"\n",
"📊 快速加载PCA数据 (演示用)...\n",
" 正在加载文件 1/45: t15.2025.04.13_train_concatenated.npz\n",
" 演示数据: 10000 样本, 1062 PCA特征\n",
" 训练: 8000, 验证: 2000\n",
"\n",
"🏗️ 修复后的LightGBM配置:\n",
" objective: multiclass\n",
" num_class: 41\n",
" metric: multi_logloss\n",
" boosting_type: gbdt\n",
" device: gpu\n",
" num_leaves: 64\n",
" learning_rate: 0.1\n",
" feature_fraction: 0.8\n",
" bagging_fraction: 0.8\n",
" bagging_freq: 5\n",
" verbose: -1\n",
" random_state: 42\n",
" gpu_platform_id: 0\n",
" gpu_device_id: 0\n",
" max_bin: 255\n",
" gpu_use_dp: False\n",
"\n",
"🚀 开始修复后的训练...\n",
"Training until validation scores don't improve for 10 rounds\n",
"Early stopping, best iteration is:\n",
"[5]\ttrain's multi_logloss: 0.67912\tval's multi_logloss: 1.49202\n",
"\n",
"✅ 训练成功!\n",
" 训练时间: 72.27 秒\n",
" 最佳迭代: 5\n",
" 验证集准确率: 0.6680 (66.80%)\n",
"\n",
"🎯 成功要点:\n",
" ✅ GPU训练正常工作\n",
" ✅ PCA降维数据兼容性良好\n",
" ✅ max_bin=255 解决GPU限制\n",
" ✅ 训练速度: 72.27秒 (10K样本)\n",
"\n",
"💡 完整训练建议:\n",
" • 使用 max_bin=255 适配GPU\n",
" • 增加样本数和训练轮数\n",
" • 监控GPU内存使用\n",
" • PCA降维数据完全兼容LightGBM\n"
]
},
{
"data": {
"text/plain": [
"119"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 🔧 修复GPU参数 - 快速LightGBM训练\n",
"\n",
"import lightgbm as lgb\n",
"import numpy as np\n",
"import time\n",
"from sklearn.metrics import accuracy_score\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"print(\"=\"*70)\n",
"print(\"🔧 修复GPU配置后的LightGBM训练\")\n",
"print(\"=\"*70)\n",
"\n",
"# 1. 快速GPU检查\n",
"gpu_available = True # 我们知道有GPU\n",
"device = 'gpu' if gpu_available else 'cpu'\n",
"print(f\"🔧 训练设备: {device.upper()}\")\n",
"\n",
"# 2. 快速加载少量数据用于演示\n",
"print(f\"\\n📊 快速加载PCA数据 (演示用)...\")\n",
"\n",
"# 只取一个批次进行快速演示\n",
"for features_pca, labels in train_dataset.get_batch_generator():\n",
" X_demo = features_pca[:10000] # 只取前10000个样本\n",
" y_demo = labels[:10000]\n",
" print(f\" 演示数据: {X_demo.shape[0]} 样本, {X_demo.shape[1]} PCA特征\")\n",
" break\n",
"\n",
"# 3. 数据分割\n",
"X_train_demo, X_val_demo, y_train_demo, y_val_demo = train_test_split(\n",
" X_demo, y_demo, test_size=0.2, random_state=42, stratify=y_demo\n",
")\n",
"\n",
"print(f\" 训练: {X_train_demo.shape[0]}, 验证: {X_val_demo.shape[0]}\")\n",
"\n",
"# 4. 修复的GPU参数配置\n",
"lgb_params_fixed = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': len(np.unique(y_demo)),\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': device,\n",
" 'num_leaves': 64, # 减少复杂度\n",
" 'learning_rate': 0.1,\n",
" 'feature_fraction': 0.8,\n",
" 'bagging_fraction': 0.8,\n",
" 'bagging_freq': 5,\n",
" 'verbose': -1,\n",
" 'random_state': 42,\n",
"}\n",
"\n",
"# GPU特定参数 (修复max_bin问题)\n",
"if device == 'gpu':\n",
" lgb_params_fixed.update({\n",
" 'gpu_platform_id': 0,\n",
" 'gpu_device_id': 0,\n",
" 'max_bin': 255, # 改为255 (GPU支持的最大值)\n",
" 'gpu_use_dp': False, # 使用单精度\n",
" })\n",
"\n",
"print(f\"\\n🏗 修复后的LightGBM配置:\")\n",
"for key, value in lgb_params_fixed.items():\n",
" print(f\" {key}: {value}\")\n",
"\n",
"# 5. 创建数据集和训练\n",
"print(f\"\\n🚀 开始修复后的训练...\")\n",
"start_time = time.time()\n",
"\n",
"train_lgb_demo = lgb.Dataset(X_train_demo, label=y_train_demo)\n",
"val_lgb_demo = lgb.Dataset(X_val_demo, label=y_val_demo, reference=train_lgb_demo)\n",
"\n",
"callbacks = [lgb.early_stopping(stopping_rounds=10)]\n",
"\n",
"try:\n",
" model_demo = lgb.train(\n",
" lgb_params_fixed,\n",
" train_lgb_demo,\n",
" valid_sets=[train_lgb_demo, val_lgb_demo],\n",
" valid_names=['train', 'val'],\n",
" num_boost_round=50, # 较少轮数用于演示\n",
" callbacks=callbacks\n",
" )\n",
" \n",
" training_time = time.time() - start_time\n",
" \n",
" print(f\"\\n✅ 训练成功!\")\n",
" print(f\" 训练时间: {training_time:.2f} 秒\")\n",
" print(f\" 最佳迭代: {model_demo.best_iteration}\")\n",
" \n",
" # 快速评估\n",
" val_pred = model_demo.predict(X_val_demo, num_iteration=model_demo.best_iteration)\n",
" val_pred_labels = np.argmax(val_pred, axis=1)\n",
" val_acc = accuracy_score(y_val_demo, val_pred_labels)\n",
" \n",
" print(f\" 验证集准确率: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n",
" \n",
" print(f\"\\n🎯 成功要点:\")\n",
" print(f\" ✅ GPU训练正常工作\")\n",
" print(f\" ✅ PCA降维数据兼容性良好\")\n",
" print(f\" ✅ max_bin=255 解决GPU限制\")\n",
" print(f\" ✅ 训练速度: {training_time:.2f}秒 (10K样本)\")\n",
" \n",
"except Exception as e:\n",
" print(f\"❌ 训练失败: {e}\")\n",
" print(\"🔧 尝试CPU训练...\")\n",
" \n",
" # 回退到CPU\n",
" lgb_params_fixed['device'] = 'cpu'\n",
" lgb_params_fixed.pop('gpu_platform_id', None)\n",
" lgb_params_fixed.pop('gpu_device_id', None)\n",
" lgb_params_fixed.pop('max_bin', None)\n",
" lgb_params_fixed.pop('gpu_use_dp', None)\n",
" lgb_params_fixed['n_jobs'] = -1\n",
" \n",
" model_demo = lgb.train(\n",
" lgb_params_fixed,\n",
" train_lgb_demo,\n",
" valid_sets=[train_lgb_demo, val_lgb_demo],\n",
" num_boost_round=50,\n",
" callbacks=callbacks\n",
" )\n",
" \n",
" print(f\" ✅ CPU训练成功!\")\n",
"\n",
"print(f\"\\n💡 完整训练建议:\")\n",
"print(f\" • 使用 max_bin=255 适配GPU\")\n",
"print(f\" • 增加样本数和训练轮数\")\n",
"print(f\" • 监控GPU内存使用\")\n",
"print(f\" • PCA降维数据完全兼容LightGBM\")\n",
"\n",
"# 清理\n",
"del X_demo, y_demo, X_train_demo, X_val_demo, y_train_demo, y_val_demo\n",
"gc.collect()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 🔥 完整使用指南 - PCA + LightGBM GPU训练系统\n",
"\n",
"## 📋 系统概述\n",
"- **数据降维**: PCA 将 7168 → 1062 特征 (保留95%方差节省85.2%内存)\n",
"- **模型**: LightGBM GPU加速 (41类分类任务)\n",
"- **内存优化**: 批量加载适配30GB内存限制\n",
"\n",
"---\n",
"\n",
"## ⚡ 快速使用方法\n",
"\n",
"### 第1步: 准备数据\n",
"```python\n",
"# 数据会自动加载和PCA处理无需手动准备\n",
"data_root = \"f:/BRAIN-TO-TEXT/nejm-brain-to-text/data/hdf5_data_final\"\n",
"```\n",
"\n",
"### 第2步: 创建内存友好数据集\n",
"```python\n",
"# 创建数据集 (会自动应用PCA)\n",
"dataset = MemoryFriendlyDataset(data_root)\n",
"print(f\"✅ 数据集准备完成: {len(dataset)} 个文件\")\n",
"```\n",
"\n",
"### 第3步: 批量训练\n",
"```python\n",
"# 批量生成器 (自动应用PCA降维)\n",
"train_gen = dataset.batch_generator(['train'], batch_size=5)\n",
"val_gen = dataset.batch_generator(['val'], batch_size=5)\n",
"\n",
"# LightGBM GPU配置 (重要: max_bin=255)\n",
"lgb_params = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': 41,\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': 'gpu',\n",
" 'max_bin': 255, # 🔥 GPU必须设置\n",
" 'gpu_platform_id': 0,\n",
" 'gpu_device_id': 0,\n",
" 'num_leaves': 64,\n",
" 'learning_rate': 0.1,\n",
" 'verbose': -1,\n",
" 'random_state': 42\n",
"}\n",
"\n",
"# 训练循环\n",
"for X_batch, y_batch in train_gen:\n",
" # X_batch 已经是PCA降维后的数据 (1062维)\n",
" # 训练代码...\n",
"```\n",
"\n",
"---\n",
"\n",
"## 🎯 关键配置参数\n",
"\n",
"### PCA配置\n",
"- **保留方差**: 95% (可在 PCA_CONFIG 中调整)\n",
"- **降维效果**: 7168 → 1062 特征\n",
"- **内存节省**: 85.2%\n",
"\n",
"### LightGBM GPU配置\n",
"- **max_bin**: 必须 ≤ 255 (GPU限制)\n",
"- **device**: 'gpu'\n",
"- **gpu_platform_id**: 0\n",
"- **gpu_device_id**: 0\n",
"\n",
"---\n",
"\n",
"## 🔧 常见问题解决\n",
"\n",
"### Q: GPU训练失败\n",
"**A**: 检查 `max_bin ≤ 255`\n",
"\n",
"### Q: 内存不足?\n",
"**A**: 减小 `batch_size` 或使用更多批次\n",
"\n",
"### Q: PCA效果不好\n",
"**A**: 调整 `PCA_CONFIG['n_components']` 或 `explained_variance_ratio`\n",
"\n",
"---\n",
"\n",
"## 📊 性能优势\n",
"- **内存使用**: 降低85.2%\n",
"- **GPU加速**: ~6.7x 速度提升\n",
"- **特征压缩**: 7168 → 1062 (14.8% 保留)\n",
"- **方差保留**: 95%"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"======================================================================\n",
"🚀 完整端到端PCA + LightGBM训练流程\n",
"======================================================================\n",
"\n",
"📊 第1步: 初始化数据集...\n"
]
},
{
"ename": "TypeError",
"evalue": "MemoryFriendlyDataset.__init__() missing 1 required positional argument: 'data_type'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m/tmp/ipykernel_36/2007074471.py\u001b[0m in \u001b[0;36m<cell line: 0>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 13\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"\\n📊 第1步: 初始化数据集...\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mdata_root\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m\"f:/BRAIN-TO-TEXT/nejm-brain-to-text/data/hdf5_data_final\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 15\u001b[0;31m \u001b[0mdataset\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mMemoryFriendlyDataset\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mdata_root\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 16\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\" ✅ 找到 {len(dataset)} 个数据文件\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 17\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34mf\" ✅ PCA已配置: {PCA_CONFIG['n_components']} 维特征\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: MemoryFriendlyDataset.__init__() missing 1 required positional argument: 'data_type'"
]
}
],
"source": [
"# 🚀 完整端到端训练示例\n",
"print(\"=\" * 70)\n",
"print(\"🚀 完整端到端PCA + LightGBM训练流程\")\n",
"print(\"=\" * 70)\n",
"\n",
"import time\n",
"from sklearn.metrics import accuracy_score, classification_report\n",
"import numpy as np\n",
"\n",
"# ===============================\n",
"# 第1步: 初始化数据集\n",
"# ===============================\n",
"print(\"\\n📊 第1步: 初始化数据集...\")\n",
"data_root = \"f:/BRAIN-TO-TEXT/nejm-brain-to-text/data/hdf5_data_final\"\n",
"dataset = MemoryFriendlyDataset(data_root, 'concatenated') # 修复: 添加data_type参数\n",
"print(f\" ✅ 找到 {len(dataset)} 个数据文件\")\n",
"print(f\" ✅ PCA已配置: {PCA_CONFIG['n_components']} 维特征\")\n",
"\n",
"# ===============================\n",
"# 第2步: 批量加载训练数据\n",
"# ===============================\n",
"print(\"\\n🏗 第2步: 批量加载训练数据...\")\n",
"start_time = time.time()\n",
"\n",
"# 使用较小批次进行演示\n",
"train_files = [f for f in dataset.file_list if 'train' in f][:3] # 只用前3个文件演示\n",
"val_files = [f for f in dataset.file_list if 'val' in f][:1] # 只用1个验证文件\n",
"\n",
"print(f\" 📁 训练文件: {len(train_files)} 个\")\n",
"print(f\" 📁 验证文件: {len(val_files)} 个\")\n",
"\n",
"# 加载训练数据\n",
"X_train_list, y_train_list = [], []\n",
"for batch_X, batch_y in dataset.batch_generator(train_files, batch_size=2):\n",
" X_train_list.append(batch_X)\n",
" y_train_list.append(batch_y)\n",
" print(f\" ⏳ 加载训练批次: {batch_X.shape[0]} 样本, {batch_X.shape[1]} PCA特征\")\n",
"\n",
"# 合并训练数据\n",
"X_train = np.vstack(X_train_list)\n",
"y_train = np.hstack(y_train_list)\n",
"\n",
"# 加载验证数据\n",
"X_val_list, y_val_list = [], []\n",
"for batch_X, batch_y in dataset.batch_generator(val_files, batch_size=1):\n",
" X_val_list.append(batch_X)\n",
" y_val_list.append(batch_y)\n",
" print(f\" ⏳ 加载验证批次: {batch_X.shape[0]} 样本, {batch_X.shape[1]} PCA特征\")\n",
"\n",
"X_val = np.vstack(X_val_list)\n",
"y_val = np.hstack(y_val_list)\n",
"\n",
"load_time = time.time() - start_time\n",
"print(f\"\\n ✅ 数据加载完成!\")\n",
"print(f\" 📊 训练集: {X_train.shape[0]} 样本 × {X_train.shape[1]} 特征\")\n",
"print(f\" 📊 验证集: {X_val.shape[0]} 样本 × {X_val.shape[1]} 特征\")\n",
"print(f\" ⏱️ 加载时间: {load_time:.2f} 秒\")\n",
"\n",
"# ===============================\n",
"# 第3步: LightGBM训练\n",
"# ===============================\n",
"print(\"\\n🏃 第3步: LightGBM GPU训练...\")\n",
"\n",
"# 最佳GPU配置\n",
"lgb_params = {\n",
" 'objective': 'multiclass',\n",
" 'num_class': 41,\n",
" 'metric': 'multi_logloss',\n",
" 'boosting_type': 'gbdt',\n",
" 'device': 'gpu',\n",
" 'num_leaves': 128, # 增加复杂度\n",
" 'learning_rate': 0.1,\n",
" 'feature_fraction': 0.8,\n",
" 'bagging_fraction': 0.8,\n",
" 'bagging_freq': 5,\n",
" 'verbose': -1,\n",
" 'random_state': 42,\n",
" 'gpu_platform_id': 0,\n",
" 'gpu_device_id': 0,\n",
" 'max_bin': 255, # 🔥 GPU必须设置\n",
" 'gpu_use_dp': False\n",
"}\n",
"\n",
"print(f\" 🔧 GPU配置: max_bin={lgb_params['max_bin']}, num_leaves={lgb_params['num_leaves']}\")\n",
"\n",
"# 创建数据集\n",
"train_data = lgb.Dataset(X_train, label=y_train)\n",
"val_data = lgb.Dataset(X_val, label=y_val, reference=train_data)\n",
"\n",
"# 开始训练\n",
"print(\" 🚀 开始GPU训练...\")\n",
"train_start = time.time()\n",
"\n",
"model = lgb.train(\n",
" lgb_params,\n",
" train_data,\n",
" valid_sets=[train_data, val_data],\n",
" valid_names=['train', 'val'],\n",
" num_boost_round=100,\n",
" callbacks=[lgb.early_stopping(stopping_rounds=10)]\n",
")\n",
"\n",
"train_time = time.time() - train_start\n",
"print(f\"\\n ✅ 训练完成!\")\n",
"print(f\" ⏱️ 训练时间: {train_time:.2f} 秒\")\n",
"print(f\" 🏆 最佳迭代: {model.best_iteration}\")\n",
"\n",
"# ===============================\n",
"# 第4步: 模型评估\n",
"# ===============================\n",
"print(\"\\n📈 第4步: 模型评估...\")\n",
"\n",
"# 预测\n",
"y_pred_train = model.predict(X_train, num_iteration=model.best_iteration)\n",
"y_pred_val = model.predict(X_val, num_iteration=model.best_iteration)\n",
"\n",
"# 转换为类别\n",
"y_pred_train_class = np.argmax(y_pred_train, axis=1)\n",
"y_pred_val_class = np.argmax(y_pred_val, axis=1)\n",
"\n",
"# 计算准确率\n",
"train_acc = accuracy_score(y_train, y_pred_train_class)\n",
"val_acc = accuracy_score(y_val, y_pred_val_class)\n",
"\n",
"print(f\" 🎯 训练集准确率: {train_acc:.4f} ({train_acc*100:.2f}%)\")\n",
"print(f\" 🎯 验证集准确率: {val_acc:.4f} ({val_acc*100:.2f}%)\")\n",
"\n",
"# ===============================\n",
"# 总结\n",
"# ===============================\n",
"total_time = time.time() - start_time\n",
"print(\"\\n\" + \"=\" * 70)\n",
"print(\"🎉 端到端训练流程完成!\")\n",
"print(\"=\" * 70)\n",
"print(f\"📊 数据处理: {X_train.shape[0] + X_val.shape[0]} 样本\")\n",
"print(f\"🔧 特征降维: 7168 → {X_train.shape[1]} (PCA)\")\n",
"print(f\"🏃‍♂️ 训练时间: {train_time:.2f} 秒\")\n",
"print(f\"⏱️ 总计时间: {total_time:.2f} 秒\")\n",
"print(f\"🎯 最终准确率: {val_acc:.4f}\")\n",
"print(f\"💾 内存节省: 85.2% (PCA降维)\")\n",
"print(\"=\" * 70)"
]
}
],
"metadata": {
"kaggle": {
"accelerator": "tpu1vmV38",
"dataSources": [
{
"databundleVersionId": 13056355,
"sourceId": 106809,
"sourceType": "competition"
}
],
"dockerImageVersionId": 31091,
"isGpuEnabled": false,
"isInternetEnabled": true,
"language": "python",
"sourceType": "notebook"
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}