2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LLM (Large Language Model) / Attention 機構の基礎から

2
Last updated at Posted at 2026-09-19

LLM (Large Language Model)

本記事では,

  1. 昨今の GPT, claude 等に代表される大規模言語モデルの核となる技術 (Attention, Transformer 機構)
  2. 代表的な言語モデル (GPT, BERT, T5)
  3. Tranformer ベースの大規模言語モデルの問題点とその解決を試みた新しいモデル (RetNet, AFT, RWKV)

を解説します.

1. Transformer

  • $i$: トークンの文中における位置
  • $\boldsymbol{w_i}$: $i$ 番目のトークン
  • $\boldsymbol{e_i}$: $i$ 番目のトークンの埋め込みベクトル

1.1. Transformer モデル図

  • 入力は "単語" またはそれよりも細かい単位である "サブワード" もしくは "文字" を使うことが多い.
    • まとめて "トークン" と呼称する.
  • Transformer の構成は, エンコーダ+デコーダ, エンコーダ only, デコーダ only の 3 種類の構成がある.
エンコーダ only デコーダ only エンコーダ+デコーダ
構成 transformer-encoder.png transformer-decoder.png transformer-encoder-decoder.png
主なタスク 文脈の内部表現ベクトルを得る 次の単語を予測する, チャットなど 翻訳, 要約など
商用モデル BERT, RoBERTa GPT T-5, BART

1.2. 位置符号 (positional encoding)

  • トークンの位置 $i$ を入力データに加えるために, 位置情報 $\boldsymbol{p_i}$ を埋め込みベクトルに加算する.
  • $\boldsymbol{x_i}$: 位置符号入力後の埋め込みベクトル
  • $D$: 埋め込みベクトルの次元数
\begin{align*}
\boldsymbol{p_i} &=
    \begin{bmatrix}
    \sin{(i)} \\
    \cos{(i)} \\
    \vdots \\
    \sin{\big(\frac{i}{10000^{2k/D}}\big)} \\
    \cos{\big(\frac{i}{10000^{2k/D}}\big)} \\
    \vdots \\
    \\
    \sin{\big(\frac{i}{10000^{D-2/D}}\big)} \\
    \cos{\big(\frac{i}{10000^{D-2/D}}\big)} \\
    \end{bmatrix}
\\
\boldsymbol{x_i} &= \sqrt{D}\boldsymbol{e_i} + \boldsymbol{p_i}\\
\end{align*}

1.2.1. RoPE (Rotary Position Embeddings) 12

元祖位置埋め込みは, 位置情報ベクトルを埋め込みベクトルに加算する.
回転位置埋め込みは, 埋め込みベクトルを (2 要素ごとに区切って) 回転させる.

rope-eq15.png

  • 2×2 回転行列が対角に並んでいるだけ

1.2. Self-attention (自己注意機構)

  • Key, Query, Value 注意機構

    • $V$: Value
    • $K$: Key
    • $Q$: Query
  • 連想配列 (dict, map) に類似.

  • 連想配列:
    Query と Key を比較して, 一致する Key に紐づいた Value を取得する.

  • Key, Query, Value 注意機構:
    Query 埋め込みとすべての Key 埋め込みの関連性のスコアが計算され, そのスコアをもとにすべての Value 埋め込みの重み付け和が出力される.

  • 連想配列と Key, Query, Value 注意機構のモデル図

    連想配列 Key, Query, Value 注意機構
    associative_array.png key_query_value-architecture.png
  • 計算の手順:

    1. 入力: $\boldsymbol{h}_i$, 内部パラメータ: $\boldsymbol{W}_q$, $\boldsymbol{W}_k$, $\boldsymbol{W}_v$
    2. クエリ埋め込み $\boldsymbol{q}_i$, キー埋め込み $\boldsymbol{k}_i$, バリュー埋め込み $\boldsymbol{v}_i$ の計算
         \begin{cases}
             \boldsymbol{q}_i &= \boldsymbol{W}_q \boldsymbol{h}_i \\
             \boldsymbol{k}_i &= \boldsymbol{W}_k \boldsymbol{h}_i \\
             \boldsymbol{v}_i &= \boldsymbol{W}_v \boldsymbol{h}_i \\
         \end{cases}
      
    3. 関連性スコアの計算
         s_{ij} = \frac{\boldsymbol{q}_i^T \boldsymbol{k}_j}{\sqrt{D}}
      
    4. 関連性スコアの正規化
         \alpha_{ij} = \text{softmax}{(s_{ij}, s_{ij'})} = \frac{\exp(s_{ij})}{\sum_{j'}{\exp(s_{ij'})}}
      
    5. 出力埋め込み $\boldsymbol{o_i}$ の計算
         \boldsymbol{o}_i = \sum_j{\alpha_{ij} \boldsymbol{v}_j}
      

1.3. マルチヘッド注意機構

ヘッドを複数構成にしたもの.

  • シングルヘッド注意機構:
    \boldsymbol{W}_q, \boldsymbol{W}_k, \boldsymbol{W}_v, s_{ij}, \alpha_{ij}, \boldsymbol{o}_i
    
  • マルチヘッド注意機構:
    \boldsymbol{W}^{(m)}_q, \boldsymbol{W}^{(m)}_k, \boldsymbol{W}^{(m)}_v, s^{(m)}_{ij}, \alpha^{(m)}_{ij}, \boldsymbol{o}^{(m)}_i
    
    • ただし, 最終出力 $\boldsymbol{o}_i$ は追加の内部パラメータ $\boldsymbol{W}_o$ と $\boldsymbol{o}^{(m)}_i$ を連結して計算される.
      \boldsymbol{o}_i = \boldsymbol{W}_o
          \begin{bmatrix}
              \boldsymbol{o}^{(1)}_i \\
              \vdots \\
              \boldsymbol{o}^{(M)}_i \\
          \end{bmatrix}
      

1.4. Layer normalization (層正規化)

入力ベクトル $x$ の平均 $\mu_x$ を $0$, 標準偏差 $\sigma_x$ を $0$ に正規化する.

1.5. ドロップアウト

ある小さい確率で入力ベクトルの一部の要素を $0$ にする.

1.6. Cross-attention (交差注意機構)

  • 計算の手順:
    1. 入力: $\boldsymbol{h}_i$, $\boldsymbol{z}_j$, 内部パラメータ: $\boldsymbol{W}_q$, $\boldsymbol{W}_k$, $\boldsymbol{W}_v$
    2. クエリ埋め込み $\boldsymbol{q}_i$, キー埋め込み $\boldsymbol{k}_i$, バリュー埋め込み $\boldsymbol{v}_i$ の計算
         \begin{cases}
             \boldsymbol{q}_i &= \boldsymbol{W}_q \boldsymbol{h}_i \\
             \boldsymbol{k}_j &= \boldsymbol{W}_k \boldsymbol{z}_j \\
             \boldsymbol{v}_j &= \boldsymbol{W}_v \boldsymbol{z}_j \\
         \end{cases}
      
    3. 関連性スコアの計算
         s_{ij} = \frac{\boldsymbol{q}_i^T \boldsymbol{k}_j}{\sqrt{D}}
      
    4. 関連性スコアの正規化
         \alpha_{ij} = \text{softmax}{(s_{ij}, s_{ij'})} = \frac{\exp(s_{ij})}{\sum_{j'}{\exp(s_{ij'})}}
      
    5. 出力埋め込み $\boldsymbol{o_i}$ の計算
         \boldsymbol{o}_i = \sum_j{\alpha_{ij} \boldsymbol{v}_j}
      

1.7. トークン出力分布の計算

   P(w_{i+1}|u_1, \dots, u_M, w_1, \dots, w_i) = \text{softmax}_{w_{i+1}}(\boldsymbol{E} \boldsymbol{h_i})
  • $\boldsymbol{E}$: (デコーダーの) 入力トークン埋め込み行列
  • $\boldsymbol{h}_i$: (デコーダーの) 出力位置 $i$ に対応する出力埋め込み

1.8. 学習の並列化

encoder-decoder-inference.png

  • まとめて推論 (予測) する.
  • $w_i$ を推論 (予測) するとき, デコーダーが "カンニング" しないように ($w_i, \dots, w_{N-1}$ にアクセスできないようにするために) マスキング処理を行う.
    • 具体的には, $w_i, \dots, w_{N-1}$ の関連性スコアを $0$ にする.
        s_{ij} =
        \begin{cases}
           \frac{\boldsymbol{q}_i^T \boldsymbol{k}_j}{\sqrt{D}} & (j \le i) \\
           -\infty & (j > i) \\
        \end{cases}
      

2. 主要な大規模言語モデル

2.1. GPT (2018)

  • デコーダ only 構成
  • 事前学習: $w_1, \dots, w_{i-1}$ から次のトークン $w_i$ を予測する.
  • 連続した文を出力するときは, 出力したトークンを次の入力トークンに追加する.

2.2. BERT, RoBERTa (2018)

  • エンコーダ only 構成
    • 特殊トークン $[\text{CLS}]$: 入力の先頭に挿入する.
    • 特殊トークン $[\text{SEP}]$: 文の区切りを表す.
    • 特殊トークン $[\text{MASK}]$: マスクされたトークンであることを表す.
  • 事前学習
    1. マスク言語モデリング: 入力トークン列 ${ w_1, \dots, w_K }$ のいくつかのトークンを特殊トークン $[\text{MASK}]$ に置き換えて入力し, $[\text{MASK}]$ の置換前のトークンを予測する.
    2. 次文予測: 二つの文を $[\text{SEP}]$ で区切って入力し, $[\text{CLS}]$ の出力埋め込みを利用して 2 値分類を行う.
      • 同一の文書の連続する文を入力した場合: true を返すようにする.
      • 異なる文書の文を入力した場合: false を返すようにする.

2.3. SpanBERT

  • トークン単位の穴埋めではなく, スパン (複数の連続したトークン) 単位で穴埋めする.

2.3. T5 (2019)

t5-architecture.drawio.png

  • エンコーダ・デコーダ構成
  • スパン単位の穴埋め問題
  • エンコーダの入力は SpanBERT と (ほぼ) 同じ.
    • 先頭を表す特殊トークン $[\text{CLS}]$ はなし.
  • デコーダでは, $[\text{MASK}_1]$, マスクされたトークン列 1, $[\text{MASK}_2]$, マスクされたトークン列 2, ... を予測する.

2.4. BART (2019)

  • Encoder-Decoder 構成

bart1.drawio.png

2.5. どの構成がベストか?

  • タスクの特性に合った構成のモデルを選択することが重要.
  • 文書単位・トークン単位の分類のような, 言語生成が不要なタスクはエンコーダ only 構成で充分.
  • チャットのような会話が即座に切り替わる可能性のあるタスクはデコーダ only 構成が望ましい.

2.5.1. Attention 機構の計算量とメモリ使用量について

構成 計算量 (1 トークンあたり) メモリ使用量
Decoder-only (KV Cache あり) $O(nd)$ (スコア計算) $\text{Memory} \propto O(nd)$

:warning: つまり, 入力する系列長に比例して, 計算量とメモリ使用量が比例して増加.
→ Transformer(Attention) ベースの LLM の欠点

3. トークナイゼーション

トークンをどのように定義すべきか?

  • 単語単位 → △
  • 文字単位 → ×
  • サブワード (単語と文字の中間) 単位 → 〇

3.1. サブワードの作り方

バイト対符号化 (byte-pair encoding; BPE)

4. 文脈内学習 (In-context learning; ICL)

例示を入力文に与えることで, 推論時に (追加の情報を) 学習させることができる.

  • few-shot learning: いくつかの例示を入力する.
  • one-shot learning: 一つの例示を入力する.
  • zero-shot learning: 例示を入力しない.

5. Attention/Transformer の代替モデル

Attention/Transformer は一推論のたびに計算量 $O(n)$ で処理を行うため, 推論コストが大きい.
一方, RNN などの回帰モデルは推論コスト $O(1)$ で処理が行え, 推論コストが小さい.
→ エッジデバイスでの処理に向く.

代表的なモデル:

  • AFT (Attention Free Transformer) (2021)
  • RWKV (Receptance Weighted Key Value) (2023)
  • RetNet (Retentive Network) (2023)
  • xLSTM (Extended Long Short-Term Memory) (2024)
  • Mamba (Selective State Space Model ベース) (2023)

5.1. RetNet (arXiv)

  • 学習時は Transformer のように複数文を並列学習 (≠ BPTT(Back propagation through time))
  • 推論時は RNN のように回帰

retnet-fig3.png

5.1.1. Retention の定義 (Fig.3-a 参照)

\begin{align*}
   W_Q, W_K &\in \mathbb{R}^{d \times d} & \text{(パラメータ)}\\
   X &= [x_1, ..., x_{|x|}] \in \mathbb{R}^{|x| \times d} & \text{(入力埋め込み)} \\
   &\begin{cases}
      Q = (X W_Q) \odot \Theta \\
      K = (X W_K) \odot \overline{\Theta} \\
      V = X W_V \\
   \end{cases} \\
   \text{ただし} \\
   \Theta_n &= e^{in\theta} = \big[{e^{in\theta_0}, e^{in\theta_1}, ..., e^{in\theta_{d/2-1}}}\big] \\
   D_{nm} &=
   \begin{cases}
      \gamma^{n-m}, &n \geq m\\
      0, & n < m
   \end{cases} \\
   \text{として} \\
   \text{Retention}(X) &:= (Q K^T \odot D) V
\end{align*}
  • 複素数 $e^{in\theta}$ は $n \theta$ 分回転させる 2x2 回転行列と同値である.
  • $\overline{\Theta}$ は 複素共役, つまり逆方向への回転を意味する.

回転行列として書き表すと,

\begin{align*}
      Q &= (X W_Q) \odot \Theta \\
      (X W_Q)_n \odot \Theta_n &= R_n (X W_Q)_n \\
      ただし R_n &=
         \begin{bmatrix}
            R(n \theta_0)& 0 & \cdots & 0 \\
            0 & R(n \theta_1) & 0 & \cdots \\
            &  & \ddots & \\
            0 & \cdots & 0 & R(n \theta_{d/2-1}) \\
         \end{bmatrix}
         \in \mathbb{R}^{d \times d} \\
         (X W_Q) \odot \Theta &=
         \begin{bmatrix}
            R_1(X W_Q)_1 \\
            R_2(X W_Q)_2 \\
            \vdots \\
            R_{|x|}(X W_Q)_{|x|} \\
         \end{bmatrix}
         \in \mathbb{R}^{|x| \times d} \\
\end{align*}
  • $R_n$ は 2x2 回転行列が対角要素にずらっと並んだ ブロック対角行列.
  • $\odot$ は要素ごとの積

5.1.2. なぜ $Q$ に $\Theta$, $K$ に $\bar{\Theta}$ を掛けるのか?

複素共役を使うことにより, 内積計算時に 相対位置 がきれいに出る.

例えば, 位置 $n$ の Query と 位置 $m$ の Key の相互作用を考えると

  • $Q_n$ には $e^{in\theta}$
  • $K_m$ には $\overline{e^{im\theta}} = e^{-im\theta}$

が掛かるため, 掛け合わせると

   e^{in\theta} \cdot e^{-im\theta} = e^{i(n-m)\theta}

となり, 相対的な回転が得られる. (相対位置エンコーディング)

5.1.3. $D_{nm}$ の役割: 減衰率+マスク

  • $\gamma^{n - m}$ で時間方向に減衰させる. (時間構造)
  • 未来情報をカンニングしない. (GPT の章を参照)

5.1.4. 回帰的な表現 (RNN) (Fig.3-b 参照)

\begin{align*}
   S_n &= \gamma S_{n-1} + K_n^T V_n \\
   \text{Retention}(X_n) &= Q_n S_n \\
\end{align*}

位置 $n$ の出力 $y_n$ だけに注目すると:

   y_n = \sum_{m \leq n} {\underbrace{(\boxed{Q_n} K_m^T)}_\text{類似度} \underbrace{\gamma^{n-m}}_\text{減衰} V_m}

↑ $\boxed{Q_n}$ は変数 $m$ に依存しない 点がポイント.

過去を全部まとめた状態 $S_n$ を定義すると,
状態 $S_n$ と $\boxed{Q_n}$ を使って出力 $y_n$ を次のように書き表せる.

\begin{align*}
   S_n &= \sum_{m \leq n}{\gamma^{n-m} K_m^T V_m} \\
   y_n &= \boxed{Q_n} S_n
\end{align*}

ステップ $n-1$ の状態 $S_{n-1}$ について,

\begin{align*}
   S_{n-1} &= \sum_{m \leq {n-1}}{\gamma^{n-1-m} K_m^T V_m} \\
\end{align*}

両辺に $\gamma$ を掛けると

\begin{align*}
   \gamma S_{n-1} &= \color{red}{\sum_{m \leq {n-1}}{\gamma^{n-m} K_m^T V_m}} \\
   \therefore S_n &= \color{red}{\sum_{m \leq {n-1}}{\gamma^{n-1-m} K_m^T V_m}} + \color{blue}{\gamma^{0} K_n^T V_n} \\
                  &= \color{red}{\gamma S_{n-1}} + \color{blue}{K_n^T V_n} \\
\end{align*}

5.2. Attention Free Transformer (AFT) 3

image-6-1024x424.png

5.2.1. AFT-full

\begin{align*}
   Q &= X W_Q \\
   K &= X W_K \\
   V &= X W_V \\
   Y(Q, K, V)_t
      &= \sigma(Q_t) \odot \frac{\sum_{t'=1}^{T}{\big[{\exp{(K_{t'} + w_{t, t'})} \odot V_{t'}}\big]}}{\sum_{t'=1}^{T}{\big[{\exp{(K_{t'} + w_{t, t'})}}\big]}} \\
      &= \sigma(Q_t) \odot \sum_{t'=1}^{T}{\big[{\text{softmax}{(K_{t'} + w_{t, t'})} \odot V_{t'}}\big]} \\
      &\in \mathbb{R}^d
\end{align*}
  • $\sum_{t'=1}^{T}{(\cdot)}$ なので, 現在時刻 $t$ よりも未来情報 ($t' > t$) を考慮している. なぜ?
    • 双方向 (BERT) 的設定で学習する場合: 全時刻を使い文全体を一度に処理.
    • 自己回帰 (GPT) 的設定で学習する場合: 未来情報を参照しないようにマスクを入れる.
      • 位置バイアス $w_{t, t'}$ は学習可能パラメータだが, 自己回帰モードでは $t'>t$ の場合は $-\infty$ を強制セットし, マスクの役割にする.
        → $\text{softmax}{(\cdot)}$ の分母に $\exp(-\infty) = \infty$ が出現するため, 結果として $\text{softmax}{(\cdot)} = 0$ になり, $Y_t$ の計算結果に寄与しなくなる.
    • ∴「BERT的学習とGPT的学習のどちらでも学習が可能です」を 1つの式で表した結果.
  • $\sigma(\cdot)$ は通常 $\text{sigmoid}(\cdot)$ を使用し, 結果として入力ゲートのような役割を果たす.

5.2.2. AFT のバリエーションと計算量/メモリ消費量の比較

aft-table1.png

AFT-simple は $w_{t, t'}$ をアーキテクチャから除外した形.

\begin{align*}
   Y(Q, K, V)_t
      &= \sigma(Q_t) \odot \boxed{\sum_{t'=1}^{T}{\big[{\text{softmax}{(K_{t'})} \odot V_{t'}}\big]}} \\
\end{align*}

→ 累積和部分 $\boxed{\cdot}$ の計算量が 全時刻分の走査が必要なため $O(Td)$ になる.

AFT-full でなぜ計算量が $O(T^2 d)$ になるのか?
→ $w_{t, t'}$ の部分により, $T \times T$ 回数分の計算が必要なため.

5.3. RWKV: Receptance Weighted Key Value 4

この記事5がわかりやすい.

rwkv-fig2.png

5.3.1. Time Mixing ブロック

過去~現在までの情報を時間方向に混ぜる. (Attention/RNN に相当)

\begin{align*}
   r_t &=W_r \cdot (\mu_r \odot x_t + (1 - \mu_r) \odot x_{t-1}) \\
   k_t &=W_k \cdot (\mu_k \odot x_t + (1 - \mu_k) \odot x_{t-1}) \\
   v_t &=W_v \cdot (\mu_v \odot x_t + (1 - \mu_v) \odot x_{t-1}) \\
\end{align*}

5.3.2. Channel Mixing ブロック

同一時刻 (同一トークン) のベクトル成分どうしを混ぜて非線形変換する. (FFN に相当)

\begin{align*}
   r'_t &=W'_r \cdot (\mu'_r \odot x_t + (1 - \mu'_r) \odot x_{t-1}) \\
   k'_t &=W'_k \cdot (\mu'_k \odot x_t + (1 - \mu'_k) \odot x_{t-1}) \\
\end{align*}
  • :warning: "$x_t$" が二回出てくるが, Time Mixing ブロックへの入力 / Channel Mixing ブロックへの入力 で中身は異なる.
    (厳密に添え字を使わずに省略してあるため, 同じものに見えるが違うもの)
  • Token shift: 前時刻の入力 $x_{t-1}$ を差分注入する.
    (残差結合のように入力の変化を捉えることができるようになる)
  • State: 過去の要約
    (RNN でいうところの内部状態と同じ).

rwkv-fig3.png

5.3.3. WKV Operator

\begin{align*}
   \text{WKV}_t
      &= \frac{{\color{red}{\sum_{i=1}^{t-1}{\big[{e^{-(t-1-i) w + k_i} \odot v_i}\big]}}} + e^{u + k_t} \odot v_t}{\color{blue}{\sum_{i=1}^{t-1}\big[{e^{-(t-1-i) w + k_i}}\big]} + e^{u + k_t}} \\
      &= \frac{{\color{red}{\boxed{a_{t-1}}} + e^{u + k_t} \odot v_t}}{\color{blue}{\boxed{b_{t-1}}} + e^{u + k_t}} \\
\end{align*}

5.3.3.1. 状態更新式 (再帰式), RNN-like

RWKV での内部状態は $a_t, b_t$
メモリサイズは $O(d)$

\begin{align*}
   a_t &= e^{-w} \odot a_{t-1} + e^{k_t} \odot v_t \\
   b_t &= e^{-w} \odot b_{t-1} + e^{k_t} \\
\end{align*}

5.3.3.2. 再帰式の導出

\begin{align*}
   a_{t-1} &:= \sum_{i=1}^{t-1}{\big[{e^{-(t-1-i) w + k_i} \odot v_i}\big]} \\
   a_{t}
      &= \sum_{i=1}^{t}{\big[{e^{-(t-i) w + k_i} \odot v_i}\big]} \\
      &= \sum_{i=1}^{t-1}{\big[{e^{-(t-i) w + k_i} \odot v_i}\big]} + e^{k_t} \odot v_t \\
      &= \sum_{i=1}^{t-1}{\big[{e^{-w + (-(t-1-i) w + k_i)} \odot v_i}\big]} + e^{k_t} \odot v_t \\
      &= e^{-w} \sum_{i=1}^{t-1}{\big[{e^{-(t-1-i) w + k_i} \odot v_i}\big]} + e^{k_t} \odot v_t \\
      &= e^{-w} a_{t-1} + e^{k_t} \odot v_t
\end{align*}

$b_t$ の再帰式もほぼ同じ過程で導出可.

5.3.4. 学習時の並列計算 (Transformer-like)

\begin{align*}
   a_t
      &= \sum_{i=1}^{t}{\big[{e^{-(t-i) w + k_i} \odot v_i}\big]} \\
      &= \sum_{i=1}^{t}{\big[{\{e^{k_i + i w} e^{- t w}\} \odot v_i}\big]} \\
   b_t
      &= \sum_{i=1}^{t}{\big[{e^{-(t-i) w + k_i}}\big]} \\
      &= \sum_{i=1}^{t}{\big[e^{k_i + i w} e^{- t w}\big]} \\
\end{align*}

→ すべての $k_i$, $v_i$ を一斉に計算し, (GPT の学習と同様に) 全時刻並列 forward ができる.


参考文献

大規模言語モデル入門 | 技術評論社

  1. Jianlin Su, Yu Lu, Shengfeng Pan, Bo Wen, and Yunfeng Liu. Roformer: Enhanced transformer with rotary position embedding. arXiv preprint arXiv:2104.09864, 2021. ↩

  2. https://qiita.com/ymgc3/items/9e6c8e01e1d7e63ae412 ↩

  3. S. Zhai et al., “An Attention Free Transformer,” Sep. 21, 2021, arXiv: arXiv:2105.14103. doi: 10.48550/arXiv.2105.14103. ↩

  4. B. Peng et al., “RWKV: Reinventing RNNs for the Transformer Era,” Dec. 11, 2023, arXiv: arXiv:2305.13048. doi: 10.48550/arXiv.2305.13048. ↩

  5. https://qiita.com/lighlighlighlighlight/items/1e0f5c9ab5012188b7e4 ↩

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?