4212 lines
309 KiB
Plaintext
4212 lines
309 KiB
Plaintext
{
|
||
"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/PmzSxbtoxKlSrRsmVLcuTIQdu2benTp08qhiciIiKSPo0aNYrbt28D8PHHH9OuXTveeecdChUqxJw5c+wcnaRVhgE9e8K8eeZpJyf4/nsl/URE5PlQsGBBhgwZwvbt2ylZsiROTk4283v37m2nyERERETSjiQn/i5dukRAQABg7m7K3d2d+vXrp1pgIiIiIulZuXLlrM99fX1ZvXq1HaORZ4FhQL9+MH26edrBAb79FoKC7BuXiIhISvniiy/w9PTkt99+47fffrOZZzKZlPgTERERIRmJP8A6zozlubOzc4oHJCIiIiIiyTdiBEyYYH5uMsHXX0OzZvaNSUREJCWdPHnS3iGIiIiIpHlJTvwZhkHhwoUxmUwA3LlzhxdffNEmGQhw/fr1lI1QREREJJ146aWXWL9+PZkzZ+bFF1+0trvi8/fffz/FyCStmzrVnPizmDUL2ra1XzwiIiKpzTAMgETbSyIiIiLpUZITf3Pnzk3NOERERETSvSZNmuDi4gJA06ZN7RuMPDOWLIFeve5PT5wInTrZLx4REXm2uDu5c2fQHevztO7rr79m3LhxHD16FIDChQvTv39/3nrrLTtHJiIiIpI2JDnxFxwcnJpxiIiIiKR7w4YNAyAmJoaXX36ZUqVKkSlTJvsGJWnahg3w5pvm8f0ABg2CPn3sGpKIiDxjTCYTHs4e9g4jSSZMmMCQIUPo2bMnVatWBWDLli1069aNq1ev0rdvXztHKCIiImJ/yRrjT0RERERSn6OjI/Xq1ePff/9V4k8S9Pff0LQpREaapzt2hI8/tmtIIiIiqWrKlClMnz6ddu3aWcsaN25M8eLFGT58uBJ/IiIiIoDDo6uIiIiIyNNWokQJTpw4Ye8wJI06fhzq14fbt83TjRrBzJmgYY5ERCS5IqIjaL+8Pe2XtyciOsLe4STqwoULVKlSJU55lSpVuHDhgh0iEhEREUl7lPgTERERSYNGjhxJv379+Pnnn7lw4QK3bt2yeUj6df06NGgAly+bp6tWhe++gwzqy0NERB5DdGw0X+39iq/2fkV0bLS9w0lUwYIFWbx4cZzyRYsWUahQITtEJCIiIpL26PSAiIiISBrUoEEDwNx9lemB27gMw8BkMhETE2Ov0MSOIiOhWTM4csQ8XawYrFgB7u72jUtERORpGDFiBK1atWLz5s3WMf7++OMP1q9fH29CUERERCQ9euzEX2RkJCdPnqRAgQJk0OXFIiIiIilq48aN9g5B0hjDgK5dYdMm87SvL6xcCVmy2DUsERGRp6ZZs2bs2LGDiRMnsnz5cgCKFi3Kzp07efHFF+0bnIiIiEgakeyMXVhYGL169eKrr74C4MiRI+TPn59evXqRO3duBg4cmOJBioiIiKQ3NWvWtHcIksZ88gn8fxMcV1fznX7+/nYNSURE5KkrW7Ys33zzjb3DEBEREUmzkj3G36BBg9i7dy+bNm3C1dXVWl6nTh0WLVqUosGJiIiIpHdhYWEcOnSIf/75x+Yh6cuSJfDBB/env/4aKla0XzwiIiJPy4NjGz885rHGQBYRERGJK9l3/C1fvpxFixZRqVIlm/FmihcvzvHjx1M0OBEREZH06sqVK3To0IFffvkl3vka4y/92LkT2rW7Pz1qFLRoYb94REREnqbMmTNz4cIFfH19yZQpk825KAuNgSwiIiJyX7ITf1euXMHX1zdO+d27d+NtfImIiIhI8vXp04ebN2+yY8cOatWqxQ8//MClS5cYOXIk48ePt3d48pRcvAivvQbh4ebp9u1BPeuLiEh6smHDBrL8/4C2GgNZRERE5NGSnfgrV64cK1eupFevXgDWZN/s2bOpXLlyykYnIiIikk5t2LCBH3/8kXLlyuHg4ICfnx9169bFy8uL0aNHExQUZO8QJZVFRkLz5nD+vHm6enWYORN0rZ2IiKQkdyd3Lve7bH2e1jw47rHGQBYRERF5tGQn/kaNGkX9+vU5ePAg0dHRTJ48mYMHD7J161Z+++231IhRREREJN25e/eutZeFzJkzc+XKFQoXLkzJkiX5+++/7RydPA19+sAff5if58ljHufP2dmuIYmIyHPIZDLh4+Fj7zCSZPXq1Xh6elKtWjUApk6dyqxZsyhWrBhTp04lc+bMdo5QRERExP4ckvuCatWqsWfPHqKjoylZsiRr1qzB19eXbdu2UbZs2dSIUURERCTdeeGFFzh8+DAApUuXZubMmZw7d44ZM2aQM2dOO0cnqe3LL2H6dPNzFxf44QfInt2+MYmIiNhb//79uXXrFgD79u0jJCSEBg0acPLkSUJCQuwcnYiIiEjakOw7/gAKFCjArFmzUjoWEREREfl/7777LhcuXABg2LBhvPrqqyxYsABnZ2fmzZtn3+AkVe3YAd2735+eORPKlbNfPCIi8nyLiI4g5Fdz0mxC4ARcMrjYOaKEnTx5kmLFigHw/fff06hRI0aNGsXff/9NgwYN7BydiIiISNqQ7MTfqlWrcHR0JDAw0Kb8119/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
|
||
}
|