AI: ニューラルネットワーク
最終更新:2026-08-26
「深層学習」は謎めいて複雑に聞こえるかもしれませんが、その基本単位である「ニューロン」とは、単なる重み付き和に非線形変換を加えたものです。パーセプトロンから始めて、この章では多層ネットワーク、活性化関数、順伝播、誤差逆伝播を段階的に案内し、純粋な Python で MLP をゼロから実装して XOR 問題を解く方法を示します。
1. 学べること
- パーセプトロン:最も単純なニューラルネットワーク単位
- 多層パーセプトロン(MLP)の構造と直感
- 活性化関数(Sigmoid/ReLU/Tanh)の役割と選択
- 順伝播と誤差逆伝播の核心的な直感
- 深層学習 = 多層ニューラルネットワークの積み重ね
2. ストーリー:10 行のコードの裏にある「脳」
(1) 痛みの種:深層学習ってそもそも何?
Bob は「深層学習」が顔認識も言語翻訳も文章生成もできると聞きましたが、ニューラルネットワークが何なのかまるで分かりませんでした。Wikipedia で調べてみましたが、偏微分と連鎖律だらけの画面にげんなりしてすぐにページを閉じました。「『ニューロン』が実際に何をしているのか、一番簡単な言葉で説明してくれないか?」
(2) Alice の 10 行のコード
Alice は微笑みながら Python を開き、10 行でパーセプトロンを書きました。2 つの数値を入力として受け取り、0 か 1 を出力します。
# 単一パーセプトロン:AND ゲート
def perceptron(x1, x2, w1=1, w2=1, b=-1.5):
return 1 if w1 * x1 + w2 * x2 + b > 0 else 0
for x1, x2 in [(0,0),(0,1),(1,0),(1,1)]:
print(f"AND({x1},{x2}) = {perceptron(x1, x2)}")
AND(0,0) = 0
AND(0,1) = 0
AND(1,0) = 0
AND(1,1) = 1
Bob は気づきました。「これって単なる重み付き投票じゃないか?両方の入力が 1 で、重み付き和が閾値を超えれば出力が 1 になる。つまりニューラルネットワークって、こうした単純な決定ユニットをつなげたものなんだ!」
(3) メリット:単純なユニットから強力なネットワークへ
Alice はうなずきました。「その通り。単一のパーセプトロンは線形分類しかできませんが、数十~数百のパーセプトロンを多層ネットワークに積み上げれば、極めて複雑な関数にもフィットできます。そこから深層学習が始まるのです。」
3. パーセプトロン――最も単純なニューラルネットワーク
(1) パーセプトロンの数学的定義
1958 年に心理学者 Rosenblatt が提案したパーセプトロンは、最初期の人工ニューロンモデルの一つです。複数の入力 $x_1, x_2, \ldots, x_n$ を受け取り、それぞれに重み $w_i$ とバイアス $b$ を加え、最後にステップ関数を通して 0 か 1 を出力します。
$$y = \text{step}\left(\sum_{i=1}^{n} w_i x_i + b\right)$$
特に、$\text{step}(z) = 1$ if $z > 0$、それ以外は $0$ です。
直感: ランニングに行くかどうか決めていると想像してください。気温、空気の質、時間があるか、気分――各要素に「重み」があり、合計が「サボり閾値」を超えれば出かけます。
(2) パーセプトロンの幾何学的意味
パーセプトロンは二次元空間に直線 $w_1 x_1 + w_2 x_2 + b = 0$ を引き、直線の一方の側で 1、もう一方で 0 を出力します。AND や OR のような線形分離可能な問題は完璧に解けますが、XOR 問題は解けません――XOR では 2 つのクラスの点を一本の直線で分離できないからです。
▶ サンプル: パーセプトロンの AND ゲートを純粋な Python で実装する(難易度:⭐)
# 学習付きパーセプトロン AND ゲート
def step(z):
return 1 if z > 0 else 0
def train_perceptron(X, y, lr=0.1, epochs=10):
n_features = len(X[0])
weights = [0.0] * n_features
bias = 0.0
for epoch in range(epochs):
total_error = 0
for xi, yi in zip(X, y):
z = sum(w * x for w, x in zip(weights, xi)) + bias
pred = step(z)
error = yi - pred
total_error += abs(error)
for j in range(n_features):
weights[j] += lr * error * xi[j]
bias += lr * error
if total_error == 0:
break
return weights, bias
X = [[0,0],[0,1],[1,0],[1,1]]
y_and = [0, 0, 0, 1]
w, b = train_perceptron(X, y_and)
print(f"重み: {w}, バイアス: {b}")
for xi in X:
print(f"AND{xi} = {step(sum(ww*xx for ww,xx in zip(w,xi)) + b)}")
重み: [0.2, 0.1], バイアス: -0.2
AND[0, 0] = 0
AND[0, 1] = 0
AND[1, 0] = 0
AND[1, 1] = 1
4. 多層パーセプトロン(MLP)のアーキテクチャ
(1) なぜ複数層が必要か?
単層パーセプトロンは直線しか引けず、XOR 問題を解けません。しかし 2 つのパーセプトロンの出力を第 3 のパーセプトロンに入力すれば、2 本の直線を組み合わせて非線形の境界を作れるようになります。これが多層パーセプトロン(MLP)の核心アイデアです。
(2) MLP の三層構造
| 層 | 機能 | 例 |
|---|---|---|
| 入力層 | 生の特徴を受け取る | 2 ノード(x₁, x₂) |
| 隠れ層 | 中間特徴を抽出 | 4 ノード(学習された特徴) |
| 出力層 | 最終予測を出力 | 1 ノード(0 か 1) |
各層のすべてのノードは前の層のすべてのノードと接続されており(全結合)、その結合の強さを 重み が表します。
(3) ニューラルネットワークの順伝播プロセス
graph LR
X1["x₁"] --> H1["h₁ (sigmoid)"]
X1 --> H2["h₂ (sigmoid)"]
X2["x₂"] --> H1
X2 --> H2
H1 --> Y["y (出力)"]
H2 --> Y
style X1 fill:#e1f5fe
style X2 fill:#e1f5fe
style H1 fill:#fff9c4
style H2 fill:#fff9c4
style Y fill:#c8e6c9
直感: 入力層は目、隠れ層は脳の処理領域、出力層は答えを口にするようなものです。各層は「重み付き和 + 非線形変換」を行います。
(4) 比較:パーセプトロン vs MLP vs 深層ニューラルネットワーク
| 比較軸 | パーセプトロン | MLP | 深層ニューラルネットワーク |
|---|---|---|---|
| 層数 | 1 層(隠れ層なし) | 2–3 層 | ≥ 4 層(複数の隠れ層を含む) |
| 能力 | 線形分類 | 非線形分類/回帰 | 複雑な特徴の自動抽出 |
| 学習 | パーセプトロン規則 | 誤差逆伝播 | 誤差逆伝播 + 最適化技法 |
| 典型応用 | 単純な論理ゲート | 小規模分類/回帰 | 画像認識、NLP、生成 |
| パラメータ数 | 数十 | 数百~数千 | 数万~数十億 |
| 代表モデル | Rosenblatt パーセプトロン | 2 層 MLP | CNN、Transformer |
5. 活性化関数――ネットワークに非線形性を加える
(1) なぜ活性化関数が必要か?
活性化関数がないと、層をいくら重ねても本質的には線形変換です。複数の線形変換の合成は線形のままだからです。活性化関数は 非線形性 を導入し、ネットワークが複雑な曲線や境界にフィットできるようにします。
比喩: 線形変換は「伸縮と回転」のようなもの。何度伸縮や回転を繰り返しても、結果は直線や平面のまま。活性化関数は「折りたたみ」のようなもので、紙を一度折れば空間が変わります。
(2) 一般的な活性化関数の比較
| 関数 | 式 | 出力範囲 | 長所 | 短所 |
|---|---|---|---|---|
| Sigmoid | $\sigma(z)=\frac{1}{1+e^{-z}}$ | (0, 1) | 出力を確率として扱える | 勾配消失、ゼロ中心でない |
| Tanh | $\tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}$ | (-1, 1) | ゼロ中心 | 勾配が消失する |
| ReLU | $\max(0, z)$ | [0, +∞) | 計算が速く、勾配消失を緩和 | ニューロン死(負の領域で勾配が 0) |
| Leaky ReLU | $\max(0.01z, z)$ | (-∞, +∞) | ニューロン死に対処 | ハイパーパラメータ調整が必要 |
▶ サンプル: 異なる活性化関数の出力値を比較する(難易度:⭐)
# 活性化関数を比較
import numpy as np
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def tanh(z):
return np.tanh(z)
def relu(z):
return np.maximum(0, z)
def leaky_relu(z, alpha=0.01):
return np.where(z > 0, z, alpha * z)
z_values = np.array([-5, -2, -1, 0, 1, 2, 5])
print(f"{'z':>5} | {'Sigmoid':>8} | {'Tanh':>8} | {'ReLU':>8} | {'LeakyReLU':>10}")
print("-" * 55)
for z in z_values:
s = sigmoid(z)
t = tanh(z)
r = float(relu(np.array([z]))[0])
lr = float(leaky_relu(np.array([z]))[0])
print(f"{z:5.1f} | {s:8.4f} | {t:8.4f} | {r:8.4f} | {lr:10.4f}")
z | Sigmoid | Tanh | ReLU | LeakyReLU
-------------------------------------------------------
-5.0 | 0.0067 | -0.9999 | 0.0000 | -0.0500
-2.0 | 0.1192 | -0.9640 | 0.0000 | -0.0200
-1.0 | 0.2689 | -0.7616 | 0.0000 | -0.0100
0.0 | 0.5000 | 0.0000 | 0.0000 | 0.0000
1.0 | 0.7311 | 0.7616 | 1.0000 | 1.0000
2.0 | 0.8808 | 0.9640 | 2.0000 | 2.0000
5.0 | 0.9933 | 0.9999 | 5.0000 | 5.0000
6. 順伝播と誤差逆伝播
(1) 順伝播:入力から出力へ
順伝播は、データが入力層から各層を通って出力層に至るプロセスです。
- 隠れ層入力を計算:$z^{(1)} = W^{(1)} \cdot x + b^{(1)}$
- 活性化関数を適用:$h = \sigma(z^{(1)})$
- 出力層入力を計算:$z^{(2)} = W^{(2)} \cdot h + b^{(2)}$
- 予測を出力:$\hat{y} = \sigma(z^{(2)})$
(2) 損失関数:予測と真値のずれを測る
平均二乗誤差(MSE)は損失関数として一般的に使われます。
損失とは、実測値と予測値の差の二乗です。
損失が小さいほど予測は正確です。学習の目標は、損失を最小化する重みを見つけることです。
(3) 誤差逆伝播:誤差から重み更新へ
誤差逆伝播の核心アイデア:「出力層の誤差から出発し、各重みが誤差にどう『寄与』したかをネットワークを層ごとに後ろ向きに伝播させて計算し、それに応じて重みを調整する」ことです。
直感的な比喩:工場で製品に不具合があったとします。
- まず、最終工程(出力層)で何件の誤差が起きたか確認する
- 次に、前工程(隠れ層)がどれだけの誤差を引き起こしたか問う
- 原材料(入力層)に達するまで層を追っていく
- 各工程は自分が寄与した誤差に基づいて作業手順を調整する
数学的にはこれが 連鎖律 です:$\frac{\partial L}{\partial w} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z} \cdot \frac{\partial z}{\partial w}$
(4) 誤差逆伝播の重要概念一覧
| 概念 | 意味 | 直感 |
|---|---|---|
| 勾配 | 損失の重みに関する偏微分 | 重みをどちらの方向にどれだけ調整すべきか |
| 学習率 | 1 反復あたりのステップ幅 | 大きすぎ → 最適点を飛び越す;小さすぎ → 学習が遅い |
| 連鎖律 | 勾配を層ごとに伝播 | 出力から入力へ「誰の責任か」を層ごとに追跡 |
| 重み更新 | $w \leftarrow w - \eta \cdot \frac{\partial L}{\partial w}$ | 勾配の逆方向へ一歩進む |
| 勾配消失 | 勾配が層ごとに減少し 0 に近づく | 最初の数層はほとんど学習しない |
| 勾配爆発 | 勾配が層ごとに増幅 | 重み更新が激しく、モデルが不安定に |
(5) 損失曲面の直感
山の上に立って、谷底(最小損失)に到達するのが目標だと想像してください。一歩ごとに最も急な斜面を下ります。それが 勾配降下法 です。学習率は歩幅を決めます。
- 歩幅が長すぎ:谷底を突き抜けて反対側の斜面に着地してしまう
- 歩幅が短すぎ:いくら歩いてもまだ山の中腹
- 最適な歩幅:安定かつ素早く谷底へ到達
7. sklearn で MLP を手軽に試す
▶ サンプル: sklearn の MLPClassifier で分類する(難易度:⭐)
# XOR 問題での MLPClassifier
from sklearn.neural_network import MLPClassifier
import numpy as np
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
mlp = MLPClassifier(hidden_layer_sizes=(4,), activation='relu',
max_iter=2000, random_state=42)
mlp.fit(X, y)
print("XOR 予測:")
for xi in X:
print(f" {xi} -> {mlp.predict([xi])[0]}")
print(f"精度: {mlp.score(X, y):.2f}")
XOR 予測:
[0 0] -> 0
[0 1] -> 1
[1 0] -> 1
[1 1] -> 0
精度: 1.00
MLPClassifier は順伝播、誤差逆伝播、重みの初期化を含めすべての詳細を自動処理します。原理を理解してしまえば、ライブラリを使うのが最も効率的なアプローチです。
▶ サンプル: 決定境界の変化を可視化する(難易度:⭐⭐)
# XOR での MLP 決定境界を可視化
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neural_network import MLPClassifier
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
hidden_configs = [(2,), (4,), (8,)]
for ax, config in zip(axes, hidden_configs):
mlp = MLPClassifier(hidden_layer_sizes=config, activation='relu',
max_iter=3000, random_state=42)
mlp.fit(X, y)
xx, yy = np.meshgrid(np.linspace(-0.5, 1.5, 100),
np.linspace(-0.5, 1.5, 100))
Z = mlp.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
ax.contourf(xx, yy, Z, alpha=0.3, cmap='coolwarm')
ax.scatter(X[:,0], X[:,1], c=y, cmap='coolwarm', edgecolors='k', s=100)
ax.set_title(f"隠れ層: {config}, 精度: {mlp.score(X,y):.2f}")
ax.set_xlabel("x1")
ax.set_ylabel("x2")
plt.tight_layout()
plt.savefig("mlp_decision_boundary.png", dpi=100)
plt.show()
出力: (例を実行して実際の出力を確認するか、上のコードコメントの期待出力の注釈を参照してください。)
▶ サンプル: 隠れ層のノード数が性能に与える影響(難易度:⭐⭐)
# 隠れ層サイズが学習損失に与える影響
import numpy as np
from sklearn.neural_network import MLPClassifier
import matplotlib.pyplot as plt
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([0, 1, 1, 0])
fig, ax = plt.subplots(figsize=(8, 5))
for n_hidden in [2, 4, 8, 16]:
mlp = MLPClassifier(hidden_layer_sizes=(n_hidden,), activation='relu',
max_iter=3000, random_state=42, solver='lbfgs')
mlp.fit(X, y)
ax.plot(range(len(mlp.loss_curve_)), mlp.loss_curve_,
label=f"Hidden={n_hidden}")
ax.set_xlabel("反復回数")
ax.set_ylabel("損失")
ax.set_title("学習損失 vs 隠れ層サイズ")
ax.legend()
ax.set_yscale('log')
plt.savefig("hidden_size_loss.png", dpi=100)
plt.show()
出力: (例を実行して実際の出力を確認するか、上のコードコメントの期待出力の注釈を参照してください。)
8. 深層学習 = 多層ニューラルネットワーク
(1) 伝統的 ML と深層学習の比較
| 比較軸 | 伝統的機械学習 | 深層学習 |
|---|---|---|
| 特徴エンジニアリング | 特徴の手動設計と選択が必要 | データから特徴を自動学習 |
| データ量要件 | 少量のデータで十分 | 一般に大量のデータが必要 |
| 計算資源 | CPU のみ | 一般に GPU が必要 |
| モデル解釈性 | 高い(決定木、線形モデル) | 低い(ブラックボックス) |
| 性能上限 | 特徴の質に依存 | データとモデルサイズに応じてスケール |
| 代表アルゴリズム | SVM、ランダムフォレスト、XGBoost | CNN、RNN、Transformer |
| 適用シナリオ | 構造化データ、小標本 | 画像・テキスト・音声などの非構造化データ |
(2) 「深い」とはどういう意味か?
「深さ」とは 隠れ層の数 を指します。一般的に以下のように合意されています。
- 浅いネットワーク: 隠れ層 1 層(MLP)
- 深層ニューラルネットワーク: 隠れ層 2 層以上
- 超深層ネットワーク: 数十~数百層(ResNet-152 は 152 層)
層が多いほど、ネットワークが抽出できる特徴はより抽象的になります。第 1 層はエッジを、第 2 層はテクスチャを、第 3 層は部品を学習するかもしれません。ネットワークを進むほど特徴はますます「意味的」になります。
(3) 深ければ良いとは限らない
より深いネットワークが必ずしも高性能とは限りません。層が多すぎると以下を招きます。
- 勾配消失/爆発: 信号が連続する層を伝わるにつれ減衰または増幅する
- 過学習: パラメータが多すぎ、モデルが学習ではなく訓練データを丸暗記する
- 学習コスト: パラメータが多い = 学習時間の増加 + 計算量の増加
モデルの深さはタスクの複雑さとデータ量に基づいて選ぶべきです。高層ビルを建てるようなものです。高ければ良いとは限らず、基礎と予算を考慮しなければなりません。
9. 総合実践:MLP 分類器をゼロから実装する
▶ サンプル: ゼロから MLP を実装して XOR 問題を解く(難易度:⭐⭐⭐)
以下では、純粋な Python(フレームワークなし)で完全な MLP を実装します。順伝播、誤差逆伝播、学習ループを含み、XOR 問題を解くのが目標です。
# ゼロから MLP:純粋な Python + NumPy で XOR を解く
import numpy as np
# --- 活性化関数とその導関数 ---
def sigmoid(z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def sigmoid_deriv(a):
return a * (1 - a)
# --- MLP クラス ---
class MLP:
def __init__(self, layer_sizes):
# layer_sizes 例: [2, 4, 1] => 入力2、隠れ4、出力1
self.weights = []
self.biases = []
for i in range(len(layer_sizes) - 1):
w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * 0.5
b = np.zeros((1, layer_sizes[i+1]))
self.weights.append(w)
self.biases.append(b)
def forward(self, X):
self.activations = [X]
for i in range(len(self.weights)):
z = self.activations[-1] @ self.weights[i] + self.biases[i]
a = sigmoid(z)
self.activations.append(a)
return self.activations[-1]
def backward(self, y, lr=0.5):
m = y.shape[0]
deltas = [None] * len(self.weights)
# 出力層のデルタ
output = self.activations[-1]
deltas[-1] = (output - y) * sigmoid_deriv(output)
# 隠れ層のデルタ(逆伝播)
for i in range(len(self.weights) - 2, -1, -1):
deltas[i] = (deltas[i+1] @ self.weights[i+1].T) * sigmoid_deriv(self.activations[i+1])
# 重みとバイアスを更新
for i in range(len(self.weights)):
self.weights[i] -= lr * (self.activations[i].T @ deltas[i]) / m
self.biases[i] -= lr * np.mean(deltas[i], axis=0, keepdims=True)
def train(self, X, y, epochs=5000, lr=0.5):
self.losses = []
for epoch in range(epochs):
pred = self.forward(X)
loss = np.mean((y - pred) ** 2) / 2
self.losses.append(loss)
self.backward(y, lr)
if epoch % 1000 == 0:
print(f"Epoch {epoch:4d} | 損失: {loss:.6f}")
def predict(self, X):
return (self.forward(X) > 0.5).astype(int)
# --- XOR で学習 ---
np.random.seed(42)
X = np.array([[0,0],[0,1],[1,0],[1,1]], dtype=float)
y = np.array([[0],[1],[1],[0]], dtype=float)
mlp = MLP([2, 4, 1])
mlp.train(X, y, epochs=5000, lr=1.0)
print("\n最終予測:")
preds = mlp.predict(X)
for i in range(4):
print(f" XOR{X[i].astype(int).tolist()} = {preds[i,0]} (目標: {y[i,0].astype(int)})")
Epoch 0 | 損失: 0.132415
Epoch 1000 | 損失: 0.004726
Epoch 2000 | 損失: 0.002517
Epoch 3000 | 損失: 0.001751
Epoch 4000 | 損失: 0.001330
最終予測:
XOR[0, 0] = 0 (目標: 0)
XOR[0, 1] = 1 (目標: 1)
XOR[1, 0] = 1 (目標: 1)
XOR[1, 1] = 0 (目標: 0)
❓ よくある質問
📖 まとめ
- パーセプトロン: 重み付き和 + ステップ関数、最も単純な線形分類器
- MLP: 多層パーセプトロン。隠れ層が非線形能力をもたらす
- 活性化関数: 非線形性を導入。ReLU が現代のデフォルト選択
- 順伝播: データが入力層から出力層へ層ごとに処理される
- 誤差逆伝播: 出力層から誤差を層ごとに後ろ向きに伝播し重みを更新
- 深層学習: 多層ニューラルネットワーク。多段階の特徴を自動学習
- 勾配降下法: 損失曲面の最急斜面を下って最適重みを見つける
重要概念: ニューロン = 重み付き和 + 非線形変換。ニューラルネットワーク = 多くのニューロンを積み上げたもの。深層学習 = 層の多いニューラルネットワーク。10 行のパーセプトロンから GPT まで、本質的にはすべて同じパラダイムに従っています。
📝 練習問題
- 基本(⭐): OR ゲートのパーセプトロンを実装しなさい。OR ゲートの真理値表は:(0,0) → 0、(0,1) → 1、(1,0) → 1、(1,1) → 1 です。この章のパーセプトロン学習コードのラベルを変更してモデルを学習し、結果を検証しなさい。
# スターターコード:OR ゲートのパーセプトロン
X = [[0,0],[0,1],[1,0],[1,1]]
y_or = [0, 1, 1, 1]
# TODO: 学習してテストする
- 応用(⭐⭐): sklearn の MLPClassifier で Iris データセットを分類し、パラメータを調整して異なる構成の精度を記録しなさい。
# スターターコード:Iris での MLP
from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42)
# TODO: 異なる hidden_layer_sizes、activation、learning_rate を試す
# 各構成の精度を記録する
少なくとも 3 つの異なる hidden_layer_sizes 構成((10,)、(50,50)、(100,) など)と 2 つの活性化関数を試し、どの構成が最良かメモしなさい。
- 挑戦(⭐⭐⭐): この章の純粋 Python MLP 実装に基づき、手書き MLP の損失曲線を描きなさい。
# セクション 9 で MLP を学習後、損失曲線を描く
import matplotlib.pyplot as plt
# mlp.losses は学習中にすでに記録されている
plt.figure(figsize=(8, 5))
plt.plot(range(len(mlp.losses)), mlp.losses, linewidth=1)
plt.xlabel("Epoch")
plt.ylabel("損失 (MSE)")
plt.title("MLP 学習損失曲線 (XOR)")
plt.yscale('log')
plt.grid(True, alpha=0.3)
plt.savefig("mlp_loss_curve.png", dpi=100)
plt.show()
発展課題:① 異なる学習率(0.1、0.5、1.0、2.0)の損失曲線を同じグラフで比較する;② 異なる隠れ層サイズ(2、4、8)で収束速度への影響を観察する;③ 学習率が高すぎると損失が振動したり発散したりするか観察する。