深層学習を用いたトランスクリプトームの分類(実践編)

# BioinfoにおけるPandas,Matplotlibの基礎
この記事は、Pythonで実践　生命科学データの機械学習 (https://www.yodosha.co.jp/yodobook/book/9784758122634/) の内容を含んでいます。

https://www.yodosha.co.jp/yodobook/book/9784758122634/

私は現在バイオインフォマティクス研究室に所属する学生です。
勉強した事をアウトプットする場として用いていますため、何卒ご理解のほどよろしくお願いいたします。
(＞人＜;)

## PyTorchでニューラルネットワークを構築する

https://qiita.com/tai-sei/items/16edcec749a78c171b86

⇧前回の記事では、深層学習の基本知識に深掘りを行ないました。
この知識を踏まえて、今回は実際にPuTorchを扱ってみました。

ベースとなる進め方は、
- モデル構築
- 訓練データセットで学習
- 検証データセットで過学習の確認
- データセットのスコアの確認

この手順になっていきます。
以下は学習段階で疑問に思ったこと、より調べたくなったことを記載していきます！
最初に行ったこと、
1️⃣ データ読み込み 
2️⃣ クラスラベル追加 
3️⃣ データ分割（学習・検証・テスト） 
4️⃣ 特徴量・ラベルの分割 
5️⃣ データ標準化

データセットの仕上げとして、データの標準化を行いました。
```python
# 標準化する
from sklearn.preprocessing import StandardScaler
```
# 標準化（Standardization）の目的と具体的な処理

##  標準化の目的
標準化（Standardization）は、機械学習モデルの **学習を安定させる** ために重要な前処理です。特に、異なるスケールの特徴量を持つデータを扱う際に役立ちます。

### 1. 学習の安定化
- 特徴量のスケールが異なると、モデルが適切に学習できず、**特定の特徴量に偏った重みが学習される** 可能性がある。
- **例**:  
  - **身長（150〜180cm）**
  - **体重（50〜80kg）**
  - スケールが異なるため、大きな値の特徴（身長）が学習を支配してしまう。

### 2. 勾配降下法の最適化
- ニューラルネットワークや線形回帰などで使用される **勾配降下法（Gradient Descent）** の収束を早める。
- スケールがバラバラだと、学習が非効率になり、最適解への収束が遅くなる。

### 3. モデルの精度向上
- 多くの機械学習アルゴリズム（ロジスティック回帰・SVM・k-NN・ニューラルネットワークなど）は、データのスケールに敏感。
- 標準化を行うことで、モデルの性能が向上する可能性が高い。

---

## 具体的に何をしているのか？
### ① 平均を 0、標準偏差を 1 にする
標準化では、各特徴量を以下の式に従って変換します：

$$
X' = \frac{X - \mu}{\sigma}
$$

- **$(X\)$：元の特徴量**
- **$(\mu\) $：その特徴量の平均**
- **$(\sigma\) $：その特徴量の標準偏差**
- **$(X'\) $：標準化後の特徴量**

---

### ② Python（scikit-learn）での標準化の実装
Pythonでは、`StandardScaler` を使って簡単に標準化できます。

```python
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()  # StandardScalerのインスタンスを作成

# 学習データの平均と標準偏差を計算
sc.fit(X_train)

# トレーニングデータの標準化
X_train_std = sc.transform(X_train)

# 検証データ・テストデータにも同じ変換を適用
X_val_std = sc.transform(X_val)
X_test_std = sc.transform(X_test)
```
### 具体例
例：標準化前と標準化後の比較
標準化前
```
身長 (cm) | 体重 (kg)
---------------------
170      |  65
160      |  55
180      |  75
```
### 問題点
身長と体重のスケールが異なる
大きい値（身長）が学習に大きな影響を与える可能性がある
標準化後
```
身長 (標準化) | 体重 (標準化)
---------------------
 0.0         |  0.0
-1.0         | -1.0
 1.0         |  1.0
 ```
すべての特徴量のスケールが揃い、学習が安定！
### 標準化が必要なアルゴリズム
- 標準化が重要なモデル
- ロジスティック回帰
- SVM（サポートベクターマシン）
- k近傍法（k-NN）
- ニューラルネットワーク
- 主成分分析（PCA）
- 線形回帰（勾配降下法を用いる場合）

### 標準化が不要なモデル
- 決定木（Decision Tree）
- ランダムフォレスト（Random Forest）
- XGBoost, LightGBM（勾配ブースティング系）
- k-means（ただし、特徴量のスケールが大きく異なる場合は必要）

---
## コードの再現性と乱数
```python
# コードに再現性を持たせるために乱数を固定
torch.manual_seed(42)
torch.cuda.manual_seed(42)
torch.backends.cudnn.deterministic = True
torch.use_deterministic_algorithms = True
```
### PyTorch で乱数を固定する方法とその目的
#### なぜ乱数を固定するのか？
機械学習・ディープラーニングのモデルをトレーニングする際、以下の理由から 乱数の固定（シード設定） は重要です：

**1. 再現性の確保**
同じコードを実行しても、常に同じ結果が得られるようにする。
モデルの学習結果の一貫性を保ち、デバッグや比較を容易にする。

**2. 実験の公平性**
複数のモデルを比較する際、ランダムな初期値による影響を排除し、公平な比較を可能にする。
**3. バグの発見**
結果が毎回変わると、予期しない挙動が起きても原因を特定しづらい。
乱数を固定することで、問題の切り分けがしやすくなる。

次に、PyTorchを学ぶ上での基本文法を示す。
# **PyTorchのDatasetを使ってデータを管理する**

## **1. `Dataset` クラスとは？**
PyTorchの`Dataset`クラスを継承して、独自のデータセットを作成します。
これにより、データを簡単に管理でき、`DataLoader` を使ってバッチ単位でデータを取得できます。

---

## **2. クラスの詳細説明**

### **① 訓練 & 検証データ (`TrainValData`)**
```python
class TrainValData(Dataset):
    def __init__(self, X_data, y_data):
        self.X_data = X_data
        self.y_data = y_data
    
    def __getitem__(self, index):
        return self.X_data[index], self.y_data[index]
    
    def __len__(self):
        return len(self.X_data)
```
 `X_data` (特徴量) と `y_data` (ラベル) を受け取る
 `__getitem__` で `index` 番目のデータを取得
 `__len__` でデータの総数を返す

### **② テストデータ (`TestData`)**
```python
class TestData(Dataset):
    def __init__(self, X_data):
        self.X_data = X_data
    
    def __getitem__(self, index):
        return self.X_data[index]
    
    def __len__(self):
        return len(self.X_data)
```
 `y_data` (ラベル) を持たない
 `X_data` のみを管理
**テストデータを誤って訓練に使用しないための対策**

---

## **3. データセットの作成**

```python
train_data = TrainValData(torch.FloatTensor(X_train_std), torch.FloatTensor(y_train))
val_data = TrainValData(torch.FloatTensor(X_val_std), torch.FloatTensor(y_val))
test_data = TestData(torch.FloatTensor(X_test_std))
```
 `torch.FloatTensor(...)` に変換し、PyTorchで扱いやすい形に
 `train_data` → 訓練用
 `val_data` → 検証用
 `test_data` → テスト用（ラベルなし）

---
# PyTorch の学習ステップ

PyTorchを用いた学習プロセスは **5 ステップの繰り返し** です！



## PyTorch における学習の流れ

機械学習の学習ステップは、次の **5つのステップ** に分かれます。

1. **順伝播（forward propagation）**  
2. **損失計算（loss calculation）**  
3. **勾配の初期化（zero gradients）**  
4. **誤差逆伝播（backpropagation）**  
5. **パラメータ更新（update parameters）**  

それぞれ **わかりやすく** 解説していきます！

---

## ステップ1：順伝播（Forward Propagation）

### 何をするの？
モデルに入力データを与え、ニューラルネットワークを通して **予測値** を計算します。

### イメージ
人間の脳に例えると、「問題（入力）」を見て「答え（出力）」を考えるステップです。

### PyTorchコード
```python
outputs = model(inputs)  # モデルにデータを入力し、出力を得る
```

### ポイント
- `model(inputs)` で、ニューラルネットワークがデータを処理し、予測値を出す。
- 「前回の学習結果をもとに、どのように予測するか」をニューラルネットワークが考える。

---

## ステップ2：損失計算（Loss Calculation）

### 何をするの？
モデルの予測 (`outputs`) と正解ラベル (`targets`) を比べて、**どれだけ間違っているか（損失）** を計算します。

### イメージ
テストの答案を採点して、「何点間違えたか？」を測るステップ。

### PyTorchコード
```python
loss = loss_function(outputs, targets)  # 損失関数で誤差を計算
```

### ポイント
- `loss_function` は、例えば **MSE（平均二乗誤差）** や **クロスエントロピー損失** を使う。
- 目標は **損失（誤差）を小さくすること**。

**例**  
もし猫の画像を入力したとき、モデルが **「犬」** と予測したら **損失は大きく** なります。  
逆に **「猫」** と正しく予測したら、損失は **小さく** なります。

---

## ステップ3：勾配の初期化（Zero Gradients）

### 何をするの？
前回の学習の影響を消して、新しく勾配を計算できるようにします。

### イメージ
ホワイトボードに前回の計算式が残っていたら、新しい計算ができませんよね？  
だから、**一旦ホワイトボードを消す** ステップです。

### PyTorchコード
```python
optimizer.zero_grad()  # 勾配を初期化
```

### ポイント
- PyTorch は **勾配を累積** する性質があるので、毎回リセットが必要！
- `zero_grad()` を忘れると、前の計算の影響で **間違った学習** になる。

---

## ステップ4：誤差逆伝播（Backpropagation）

### 💡 何をするの？
損失を小さくするために、**どの重み（パラメータ）をどれくらい調整すればよいか？** を計算します。

### イメージ
試験の点数が悪かったとき、「どの問題を間違えたか？」を分析し、**次に何を勉強すれば点数が上がるか？** を考えるステップ。

### PyTorchコード
```python
loss.backward()  # 誤差逆伝播で勾配を計算
```

### ポイント
- `.backward()` で、各パラメータが **どれくらい損失に影響しているか** を計算。
- これにより、「どの重みをどれくらい変更すればよいか」が分かる。

---

## ステップ5：パラメータ更新（Update Parameters）

### 何をするの？
計算した勾配を使って、モデルのパラメータ（重み）を更新し、より良い予測ができるようにします。

### イメージ
間違えた問題を復習して、次は **より良い回答ができるように改善** するステップ。

### PyTorchコード
```python
optimizer.step()  # パラメータ更新
```

### ポイント
- `.step()` を実行すると、学習率 (`lr`) に基づいて、パラメータが調整される。
- これにより、次回の予測が **より正しくなる可能性** が高まる。

---

## まとめ：PyTorch の学習プロセス

```python
for epoch in range(num_epochs):  # 学習を複数回繰り返す
    outputs = model(inputs)  # (1) 順伝播
    loss = loss_function(outputs, targets)  # (2) 損失計算

    optimizer.zero_grad()  # (3) 勾配初期化
    loss.backward()  # (4) 誤差逆伝播
    optimizer.step()  # (5) パラメータ更新
```
## PyTorchでニューラルネットワークを作る方法

## 1. どんなニューラルネットワークを作るの？

今回は **2値分類（Binary Classification）** を行う **ニューラルネットワーク** を定義します！

具体的には、以下の構造を持つ **4層のモデル** になります。

| 層 | 入力特徴量 | 出力特徴量 | 処理内容 |
|----|----------|----------|-----------|
| **入力層** | 13 | 32 | 線形変換 + Batch Normalization + ReLU |
| **隠れ層1** | 32 | 128 | 線形変換 + Batch Normalization + ReLU |
| **隠れ層2** | 128 | 64 | 線形変換 + Batch Normalization + ReLU |
| **出力層** | 64 | 1 | 線形変換（シグモイドは不要） |

**特徴**
- `nn.Sequential` を使って、レイヤーを **簡潔に定義**！
- `BatchNorm1d` を追加して、**学習の安定化**！
- `ReLU` を使って **活性化関数** を適用！
- `Binary Classification` のため、**出力は1つの値（0 or 1 の確率）**！

---

## 2. ニューラルネットワークの定義

では、**PyTorchでニューラルネットワークを定義** してみましょう！

`torch.nn.Module` を継承した `BinaryClassificationNet` クラスを作成します。

```python
import torch.nn as nn

# 各層のノード数を定義
num_layers = 3
num_features = [32, 128, 64]

# ニューラルネットワークの定義
class BinaryClassificationNet(nn.Module):
    def __init__(self):
        super(BinaryClassificationNet, self).__init__()

        # 入力層（13 -> 32）
        self.layers1 = nn.Sequential(
            nn.Linear(in_features=13, out_features=num_features[0]),
            nn.BatchNorm1d(num_features[0]),  # バッチ正規化
            nn.ReLU(),  # 活性化関数
        )

        # 隠れ層1（32 -> 128）
        self.layers2 = nn.Sequential(
            nn.Linear(in_features=num_features[0], out_features=num_features[1]),
            nn.BatchNorm1d(num_features[1]),
            nn.ReLU(),
        )

        # 隠れ層2（128 -> 64）
        self.layers3 = nn.Sequential(
            nn.Linear(in_features=num_features[1], out_features=num_features[2]),
            nn.BatchNorm1d(num_features[2]),
            nn.ReLU(),
        )

        # 出力層（64 -> 1）
        self.layers_out = nn.Linear(in_features=num_features[2], out_features=1)

    # 順伝播の処理を定義
    def forward(self, x):
        x = self.layers1(x)
        x = self.layers2(x)
        x = self.layers3(x)
        x = self.layers_out(x)
        return x
```

**コードのポイント**
- `__init__()` で **各層を定義**
- `forward()` で **データを順番に流す処理を記述**
- `nn.Sequential` を使って、**簡潔に記述**

---

## 3. まとめ
- `nn.Module` を継承してニューラルネットワークを定義する方法！
- `nn.Sequential` を使って **コードを簡潔にするテクニック**！
- **2値分類用のモデル構築** に必要なレイヤー（`Linear`, `BatchNorm1d`, `ReLU`）の役割！
- **順伝播（forward）でデータを流す処理を記述する方法**！

## 学習率0.001のAdamオプティマイザとは？

### **Adamオプティマイザとは？**
Adam（Adaptive Moment Estimation）は、機械学習・深層学習で広く使われている最適化アルゴリズムです。  
勾配降下法の一種で、**学習の進み方を適応的に調整** する特徴があります。

**Adamの主なメリット**  
- **学習率の調整が不要**（自動的に適応）  
- **SGD（確率的勾配降下法）より収束が速い**  
- **慣性（Momentum）と適応学習率（Adaptive Learning Rate）を組み合わせている**

---

### **学習率（Learning Rate）とは？**
学習率（lr）は、ニューラルネットワークのパラメータ（重み）をどれくらい更新するかを決めるハイパーパラメータです。

| 学習率 | 特徴 |
|--------|------|
| **大きすぎる（例: 0.1）** | 収束が早いが、最適解を飛び越えてしまい、精度が安定しない可能性がある。 |
| **小さすぎる（例: 0.00001）** | 収束が遅くなり、学習に時間がかかる。最適解にたどり着かないことも。 |
| **適切（例: 0.001）** | 速く安定して学習できる。一般的に推奨される値。 |

---

### **`optim.Adam(model.parameters(), lr=0.001)` の意味**
```python
LEARNING_RATE = 0.001
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)
```
このコードは、以下のような意味を持ちます。

1. `model.parameters()`：モデルのパラメータ（重み）を最適化の対象とする  
2. `optim.Adam(...)`：Adamオプティマイザを使用する  
3. `lr=0.001`：学習率を **0.001** に設定（一般的な初期値）  

**なぜ0.001なのか？**  
- Adamオプティマイザのデフォルトの学習率が **0.001** であり、一般的に良い結果が得られるため。  
- 多くの研究・実験で **最適な初期値** として使われている。  
- 必要に応じて `0.0001` や `0.01` などに変更してチューニング可能。  

***

https://qiita.com/tai-sei/items/bbe71a7705960ebdc5b2