神經網絡系列---卷積


文章目錄

    • 卷積神經網絡
      • 卷積
      • 轉置卷積
    • 卷積核和反卷積的三種實現方式
    • 卷積的次數計算


卷積神經網絡

在神經網絡的卷積層中,向下取整(Floor)是一種常用的策略,特別是在處理輸出尺寸不是整數的情況時。當你計算出卷積層輸出的尺寸(通常是寬度和高度)不是整數時,你可以簡單地去掉小數部分,即對該數進行向下取整。

向下取整通常意味著在卷積操作中你可能會忽略輸入矩陣(也就是圖像或者上一層的輸出)的一小部分。這可能導致一些空間信息的丟失,但在實踐中通常不會產生重大影響。

舉一個簡單的例子,假設你有一個7x7的輸入和一個3x3的卷積核,步長為2。通常,輸出的尺寸會用以下公式來計算:

輸出尺寸 = ? 輸入尺寸 ? 核尺寸 步長 ? + 1 \text{輸出尺寸} = \left\lfloor \frac{{\text{輸入尺寸} - \text{核尺寸}}}{\text{步長}} \right\rfloor + 1 輸出尺寸=?步長輸入尺寸?核尺寸??+1

如果用這個公式計算,輸出尺寸會是:

? 7 ? 3 2 ? + 1 = 3 \left\lfloor \frac{{7 - 3}}{2} \right\rfloor + 1 = 3 ?27?3??+1=3

這里,向下取整實際上沒有影響,因為計算結果剛好是一個整數。但如果輸入尺寸是8x8,那么輸出尺寸會是:

? 8 ? 3 2 ? + 1 = ? 5 2 ? + 1 = 2 + 1 = 3 \left\lfloor \frac{{8 - 3}}{2} \right\rfloor + 1 = \left\lfloor \frac{5}{2} \right\rfloor + 1 = 2 + 1 = 3 ?28?3??+1=?25??+1=2+1=3

在這個例子中,盡管精確的計算結果是3.5,但通過向下取整,輸出尺寸變成了3。

使用向下取整的一個優點是它簡化了實現,因為你不需要特別處理邊界條件。缺點是可能會丟失一些空間信息,尤其是當步長比較大的時候。然而,在許多應用場景中,這種信息丟失通常是可以接受的。

神經網絡中關于卷積池化的計算(不為整數時,卷積向下取整,池化向上取整)

在這里插入圖片描述

在這里插入圖片描述

對于正向傳播,我們使用原始的卷積核進行卷積操作。在反向傳播時,為了計算輸入或權重的梯度,通常需要進行“翻轉”操作。

需要注意的是,正向卷積和反向傳播中的卷積(通常稱為轉置卷積或反卷積)在數學和實現上有一些不同。在正向傳播中,卷積核與輸入數據進行卷積以生成輸出。而在反向傳播中,我們關心的是如何改變輸入或卷積核以最小化某個損失函數。

為了具體說明為什么需要翻轉卷積核,考慮一維情況(二維情況是類似的):

假設正向卷積表示為 y = x ? w y = x * w y=x?w,其中 x x x 是輸入, w w w 是卷積核, y y y 是輸出,‘*’ 是卷積操作。

在反向傳播過程中,我們通常需要計算損失函數 L L L 關于輸入 x x x 的梯度( ? L ? x \frac{\partial L}{\partial x} ?x?L?)。為了找到這個梯度,我們需要用到鏈式法則:

? L ? x = ? L ? y ? rot180 ( w ) \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} * \text{rot180}(w) ?x?L?=?y?L??rot180(w)

其中, rot180 ( w ) \text{rot180}(w) rot180(w) 表示將 w w w 進行180度翻轉。

這樣做的主要原因是數學上的一致性和計算的方便性。這樣,前向和反向傳播可以用相似的卷積操作來實現,大大簡化了算法的實現。

簡而言之,在正向傳播中我們使用原始的卷積核,而在反向傳播時,為了計算梯度,我們通常需要用到翻轉的卷積核。這主要是為了數學和計算的方便。

在反向傳播(backpropagation)過程中,通常會使用原始卷積核(kernel)的翻轉版本。這里的“翻轉”通常意味著沿兩個空間維度(即不是批量維度或通道維度)旋轉180度。

例如,如果你有一個3x3的卷積核:

K = ( a b c d e f g h i ) K = \begin{pmatrix}a & b & c \\d & e & f \\g & h & i\end{pmatrix} K= ?adg?beh?cfi? ?

翻轉這個卷積核會得到:

K rot = ( i h g f e d c b a ) K^{\text{rot}} = \begin{pmatrix}i & h & g\\f & e & d \\c & b & a\end{pmatrix} Krot= ?ifc?heb?gda? ?

在Eigen中,使用reverse()函數并指定需要翻轉的維度可以實現這一點。例如,對于一個Eigen::MatrixXf對象kernel,你可以這樣翻轉它:

Eigen::MatrixXf rotated_kernel = kernel.reverse();

這里簡單假設reverse()默認沿兩個維度翻轉矩陣。實際使用中,請確保你正確地翻轉了維度。

這個翻轉的卷積核(或旋轉180度的卷積核)通常用于反向傳播過程中,以計算相對于輸入的梯度。這與前向傳播中使用的卷積核是同一個卷積核,只是翻轉了。

【卷積神經網絡中的反向傳播動畫演示】
在這里插入圖片描述

通過將輸入和卷積核展開(unroll)為矩陣,可以使用矩陣乘法來實現卷積和轉置卷積操作。下面簡要介紹如何使用這種技術。

卷積

假設我們有一個輸入矩陣 X X X 和一個卷積核 K K K。我們首先將 X X X 展開為一個大矩陣 X unroll X_{\text{unroll}} Xunroll?,其中每一列都包含一個 K K K 能應用于 X X X 的局部區域。然后,我們將 K K K 展開為一個行向量 K unroll K_{\text{unroll}} Kunroll?

接下來,卷積操作可以通過以下矩陣乘法進行:

O = K unroll × X unroll O = K_{\text{unroll}} \times X_{\text{unroll}} O=Kunroll?×Xunroll?

其中 O O O 是輸出矩陣。

轉置卷積

對于轉置卷積,方法基本相同,但展開和乘法的方向會有所不同。

假設我們有一個輸入矩陣 Y Y Y 和相同的卷積核 K K K。為了進行轉置卷積,我們將 Y Y Y 展開為 Y unroll Y_{\text{unroll}} Yunroll?,然后執行以下矩陣乘法:

O = X unroll × K T O = X_{\text{unroll}} \times K^T O=Xunroll?×KT

這里, K T K^T KT K K K 的轉置。

請注意,在這兩種情況下,我們都需要格外注意矩陣的維度和展開的順序。

卷積核和反卷積的三種實現方式

#include <Eigen/Dense>
#include <iostream>//卷積
Eigen::MatrixXf conv2D(const Eigen::MatrixXf& input, const Eigen::MatrixXf& kernel, int stride) {// 計算輸出矩陣的尺寸int rows = (input.rows() - kernel.rows()) / stride + 1;int cols = (input.cols() - kernel.cols()) / stride + 1;// 創建輸出矩陣Eigen::MatrixXf output(rows, cols);for (int i = 0; i < rows; ++i) {for (int j = 0; j < cols; ++j) {// 計算每個輸出元素Eigen::MatrixXf block = input.block(i * stride, j * stride, kernel.rows(), kernel.cols());output(i, j) = (block.array() * kernel.array()).sum();}}return output;
}// deconv2D 是一個函數,用于執行反卷積(也叫轉置卷積)
Eigen::MatrixXf deconv2D( const Eigen::MatrixXf& y_grad,const Eigen::MatrixXf& kernel, int stride) {// 計算輸出尺寸int outputRows = (y_grad.rows() - 1) * stride + kernel.rows();int outputCols = (y_grad.cols() - 1) * stride + kernel.cols();// 初始化輸出矩陣為零Eigen::MatrixXf output = Eigen::MatrixXf::Zero(outputRows, outputCols);// 進行轉置卷積操作for (int i = 0; i < y_grad.rows(); ++i) {for (int j = 0; j < y_grad.cols(); ++j) {// 注意:這里我們假設步長(stride)是1,你可以通過修改下面的索引來調整步長output.block(i * stride, j * stride, kernel.rows(), kernel.cols()) += y_grad(i, j) * kernel;}}return output;
}// 轉置卷積
Eigen::MatrixXf Conv2DTransposed( int rows,int cols ,const Eigen::MatrixXf& kernel, int stride)
{int r = (rows - kernel.rows()) / stride + 1;int c = (cols - kernel.cols()) / stride + 1;// 初始化輸出矩陣為零Eigen::MatrixXf output1 = Eigen::MatrixXf::Zero(r * c, rows * cols);int jj =0;// 進行轉置卷積操作for (int i = 0; i < r; ++i){for (int j = 0; j < c ; ++j){// 初始化輸出矩陣為零Eigen::MatrixXf output = Eigen::MatrixXf::Zero(rows, cols);// 注意:這里我們假設步長(stride)是1,你可以通過修改下面的索引來調整步長output.block(i * stride, j * stride, kernel.rows(), kernel.cols()) = kernel;output1.row(jj++) = output.reshaped<Eigen::RowMajor>();}}return output1;
}
//圖像轉換為列
Eigen::MatrixXf im2col(const Eigen::MatrixXf& input, int kernel_rows, int kernel_cols, int stride) {int output_rows = (input.rows() - kernel_rows) / stride + 1;int output_cols = (input.cols() - kernel_cols) / stride + 1;Eigen::MatrixXf output(kernel_rows * kernel_cols, output_rows * output_cols);int col_idx = 0;for (int row = 0; row <= input.rows() - kernel_rows; row += stride){for (int col = 0; col <= input.cols() - kernel_cols; col += stride){Eigen::VectorXf col_vector = input.block(row, col, kernel_rows, kernel_cols).reshaped<Eigen::RowMajor>();//const Eigen::VectorXf col_vector = Eigen::Map<const Eigen::VectorXf, Eigen::RowMajor>(block.data(), block.size());output.col(col_idx++) = col_vector;}}return output;
}//列轉換為圖像
Eigen::MatrixXf col2im(const Eigen::MatrixXf& input, int original_rows, int original_cols, int kernel_rows, int kernel_cols, int stride) {Eigen::MatrixXf output = Eigen::MatrixXf::Zero(original_rows, original_cols);int col_idx = 0;for (int row = 0; row <= original_rows - kernel_rows; row += stride){for (int col = 0; col <= original_cols - kernel_cols; col += stride){Eigen::MatrixXf block = input.col(col_idx++).reshaped<Eigen::RowMajor>(kernel_rows, kernel_cols);//const Eigen::MatrixXf block = Eigen::Map<const Eigen::MatrixXf, Eigen::RowMajor>(col_vector.data(), kernel_rows, kernel_cols);output.block(row, col, kernel_rows, kernel_cols) += block;}}return output;
}int main() {// 用于測試的輸入和卷積核Eigen::MatrixXf input(5, 5);input << 1, 2, 3, 4, 5,5, 4, 3, 2, 1,1, 2, 3, 4, 5,5, 4, 3, 2, 1,1, 2, 3, 4, 5;Eigen::MatrixXf kernel(3, 3);kernel << 1, 0, -1,1, 5, -1,1, 4, -1;int stride = 2;//第一種實現:正常卷積{//卷積Eigen::MatrixXf output = conv2D(input, kernel, stride);std::cout << "1: Conv2D Output:\n" << output << std::endl;//反卷積Eigen::MatrixXf output1 = deconv2D(output,kernel, stride);std::cout << "1: deconv2D output1:\n" << output1 << std::endl;}//第二種實現:轉置卷積{Eigen::MatrixXf Unfold = Conv2DTransposed(input.rows(),input.cols(),kernel,stride);std::cout << "2: Unfold:\n" << Unfold << std::endl;Eigen::VectorXf Input = input.reshaped<Eigen::RowMajor>();Eigen::MatrixXf output = Unfold * Input;std::cout << "2: Conv2D Output:\n" << output << std::endl;Eigen::MatrixXf output1 =  (Unfold.transpose() * output).reshaped<Eigen::RowMajor>(input.rows(),input.cols());std::cout << "2: deconv2D output1:\n" << output1 << std::endl;}//第三種種實現:圖像轉換為列  矩陣相乘實現  加速運算{Eigen::MatrixXf input_unroll = im2col(input, kernel.rows(),kernel.cols(), stride);Eigen::RowVectorXf kernel_unroll = kernel.reshaped<Eigen::RowMajor>();Eigen::MatrixXf output = kernel_unroll * input_unroll ;std::cout << "3: Conv2D Output:\n" << output << std::endl;Eigen::MatrixXf output_unroll11 = kernel_unroll.transpose() * output;std::cout << "3: output_unroll11:\n" << output_unroll11 << std::endl;Eigen::MatrixXf output1 = col2im(output_unroll11, input.rows(),input.cols(),kernel.rows(),kernel.cols(), stride);std::cout << "3: deconv2D output1:\n" << output1 << std::endl;}}
1: Conv2D Output:
26 24
26 24
1: deconv2D output1:26   0  -2   0 -2426 130  -2 120 -2452 104  -4  96 -4826 130  -2 120 -2426 104  -2  96 -24
2: Unfold:1  0 -1  0  0  1  5 -1  0  0  1  4 -1  0  0  0  0  0  0  0  0  0  0  0  00  0  1  0 -1  0  0  1  5 -1  0  0  1  4 -1  0  0  0  0  0  0  0  0  0  00  0  0  0  0  0  0  0  0  0  1  0 -1  0  0  1  5 -1  0  0  1  4 -1  0  00  0  0  0  0  0  0  0  0  0  0  0  1  0 -1  0  0  1  5 -1  0  0  1  4 -1
2: Conv2D Output:
26
24
26
24
2: deconv2D output1:26   0  -2   0 -2426 130  -2 120 -2452 104  -4  96 -4826 130  -2 120 -2426 104  -2  96 -24
3: Conv2D Output:
26 24 26 24
3: output_unroll11:26  24  26  240   0   0   0
-26 -24 -26 -2426  24  26  24
130 120 130 120
-26 -24 -26 -2426  24  26  24
104  96 104  96
-26 -24 -26 -24
3: deconv2D output1:26   0  -2   0 -2426 130  -2 120 -2452 104  -4  96 -4826 130  -2 120 -2426 104  -2  96 -24

卷積的次數計算

在這里插入圖片描述

當然可以。給定一個輸入特征圖的大小和一個濾波器的大小,以及卷積的步長和填充,以下是如何計算卷積后的輸出特征圖的維度的完整公式:

  1. 高度 H 2 H_2 H2? 的計算:
    H 2 = H 1 ? F H + 2 P S + 1 H_2 = \frac{H_1 - F_{H} + 2P}{S} + 1 H2?=SH1??FH?+2P?+1

  2. 寬度 W 2 W_2 W2? 的計算:
    W 2 = W 1 ? F W + 2 P S + 1 W_2 = \frac{W_1 - F_{W} + 2P}{S} + 1 W2?=SW1??FW?+2P?+1

其中:

  • H 1 , W 1 H_1, W_1 H1?,W1? 是輸入特征圖的高和寬。
  • F H , F W F_H, F_W FH?,FW? 是濾波器的高和寬。
  • P P P 是填充的數量。
  • S S S 是步長。

以下是使用C++和Eigen庫實現的示例:

#include <Eigen/Dense>
#include <iostream>
#include <cmath>std::pair<int, int> computeConvTimes(int input_rows, int input_cols, int kernel_rows, int kernel_cols, int stride) {int rows_times = (input_rows - kernel_rows) / stride + 1;int cols_times = (input_cols - kernel_cols) / stride + 1;return {rows_times, cols_times};
}int main() {int input_rows = 5, input_cols = 5;int kernel_rows = 3, kernel_cols = 3;int stride = 2;auto [rows_times, cols_times] = computeConvTimes(input_rows, input_cols, kernel_rows, kernel_cols, stride);std::cout << "Rows can be convolved: " << rows_times << " times.\n";std::cout << "Columns can be convolved: " << cols_times << " times.\n";return 0;
}

這段代碼首先定義了一個函數computeConvTimes,該函數使用上述公式計算行和列的卷積次數。然后在main函數中展示了對于給定的輸入大小、核大小和步長,可以進行多少次卷積操作。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/712898.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/712898.shtml
英文地址,請注明出處:http://en.pswp.cn/news/712898.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UnityAPI的學習——Mathf類

Mathf類是Unity中的數學類&#xff0c;屬于結構體類型&#xff0c;只有靜態屬性和靜態方法&#xff0c;即不可實例化。 Mathf類靜態屬性 在Mathf類中&#xff0c;涉及的靜態屬性有Deg2Rad、Rad2Deg和Infinity&#xff0c;其中屬性Deg2Rad和Rad2Deg功能相似。 1、Deg2Rad屬性…

UE5 C++ 發射子彈發射(Projectile)

一.相關藍圖的練習&#xff0c;在我之前的文章中射擊子彈案例-CSDN博客 本篇使用C實現 1.創建C類 MyBullet,在MyBullet.h中包含相關頭文件 #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Components/StaticMeshComponent.…

SpringBoot 注解全解析

注解的優勢&#xff1a; 采用純 java 代碼&#xff0c;不在需要配置繁雜的 xml 文件在配置中也可享受面向對象帶來的好處類型安全對重構可以提供良好的支持減少復雜配置文件的同時亦能享受到 springIoC 容器提供的功能 1. 常用的Spring Boot注釋及其用途和示例 1&#xff09;S…

Java 中notify 和 notifyAll 方法介紹

1. notify 方法 notify() 方法是 Java 中 Object 類的一個方法&#xff0c;它用來喚醒在該對象的監視器&#xff08;monitor&#xff09;上等待的單個線程。如果有多個線程都在該對象上等待&#xff0c;則會隨機喚醒其中一個線程。被喚醒的線程將會嘗試重新獲取對象鎖&#xff…

idea集成git詳解教程(實用篇)

0.Git常用命令 Git常用命令-CSDN博客 1.下載git Git - Downloads 一路傻瓜式安裝即可&#xff08;NEXT&#xff09; 2.軟件測試 在Windows桌面空白處&#xff0c;點擊鼠標右鍵&#xff0c;彈出右鍵菜單 Git軟件安裝后&#xff0c;會在右鍵菜單中增加兩個菜單 Git GUI He…

matplotlib繪圖中文亂碼問題

如圖所示&#xff0c;在使用python包matplotlib繪圖時中文文字顯示亂碼&#xff0c;在繪圖前加入以下兩行代碼即可 # 導入包 import matplotlib.pyplot as plt # 解決中文亂碼問題 plt.rcParams[font.sans-serif][SimHei] plt.rcParams[axes.unicode_minus] False重新運行代…

Linux 進程的前臺/后臺切換

目錄 前言 簡單例子 前言 當你用shell啟動一個程序時&#xff0c;往往他是在前臺工作的。程序會一直占用終端命令行&#xff0c;例如你在前臺解壓的時候必須等著&#xff0c;期間干不了別的事&#xff08;除非另開一個終端&#xff09;。 例如經常用連接到遠程服務器執行腳本…

【知識摘要】一文帶你了解什么是RedLock。

1、什么是RedLock 紅鎖&#xff08;RedLock&#xff09;是一種分布式鎖算法&#xff0c;由 Redis 的作者 Salvatore Sanfilippo&#xff08;也稱為 Antirez&#xff09;設計&#xff0c;用于在分布式系統中實現可靠的鎖機制。它的設計解決了單一 Redis 實例作為分布式鎖可能出…

【Django】執行查詢—跨關系查詢中的跨多值關聯問題

跨多值查詢 跨越 ManyToManyField 或反查 ForeignKey &#xff08;例如從 Blog 到 Entry &#xff09;時&#xff0c;對多個屬性進行過濾會產生這樣的問題&#xff1a;是否要求每個屬性都在同一個相關對象中重合。 filter() 先看filter()&#xff0c;通過一個例子看&#xf…

打造無縫滾動體驗:JavaScript中的scrollIntoView()方法實戰指南

在現代Web開發中&#xff0c;提升用戶體驗是至關重要的。通過JavaScript的scrollIntoView()方法&#xff0c;我們可以為用戶創造出流暢而令人愉悅的滾動體驗。本文將深入研究scrollIntoView()的強大功能&#xff0c;并結合實例演示如何在項目中巧妙應用&#xff0c;以打造出無縫…

緩存穿透解決方案之布隆過濾器

布隆過濾器可以快速判斷數據是否存在&#xff0c;避免從數據庫中查詢數據是否存在&#xff0c;減輕數據庫的壓力 布隆過濾器是由一個初值為0的bit數組和N個哈希函數&#xff0c;可以用來快速的判斷某個數據是否存在 當我們想要標記某個數據是否存在時&#xff0c;布隆過濾器會…

Java底層自學大綱_高可用篇

高可用專題_自學大綱所屬類別學習主題建議課時&#xff08;h&#xff09; A 容器化技術001 Docker架構設計原理2.5 A 容器化技術002 Docker部署springboot項目2.5 A 容器化技術003 基于Docker-Compose部署微服務項目2.5 B Nginx實現高可用004 Nginx反向代理&負載均衡&a…

LabVIEW眼結膜微血管采集管理系統

LabVIEW眼結膜微血管采集管理系統 開發一套基于LabVIEW的全自動眼結膜微血管采集管理系統&#xff0c;以提高眼結膜微血管臨床研究的效率。系統集成了自動化圖像采集、圖像質量優化和規范化數據管理等功能&#xff0c;有效縮短了圖像采集時間&#xff0c;提高了圖像質量&#…

idea 多模塊A模塊調用了B模塊的Jar包,而非本地源碼

1&#xff0c;問題描述 對于多模塊的互相調用&#xff0c;比如模塊A&#xff0c;模塊B&#xff0c;模塊C&#xff0c; 這在本地都是可以編輯進行開發的源碼&#xff0c; 按理說是模塊A可以直接點進模塊B的本地源碼&#xff0c; 但是不知道什么原因&#xff0c;導致模塊A點進…

C++小記 - 二叉樹

文章目錄 二叉樹一、二叉樹理論基礎篇二叉樹的種類滿二叉樹完全二叉樹二叉搜索樹平衡二叉搜索樹 二叉樹的存儲方式鏈式存儲&#xff1a;順序存儲&#xff1a;遍歷規則&#xff1a;構造實現&#xff1a; 二叉樹的遍歷方式二叉樹的定義 二、二叉樹的遞歸遍歷遞歸算法的三個要素:遞…

vue+element UI中給指定日期添加標記

1.日期控件中添加:picker-options屬性&#xff0c;即:picker-options“myPickerOptions” <el-date-picker:class"item.scds !null ?xtsjBlue:xtsjRed"v-model"item.date"value-format"yyyy-MM-dd"type"date":picker-options"…

Python中的heapq模塊

Python中的heapq模塊 文章目錄 Python中的heapq模塊1.heapq的方法2.使用heapq創建堆3.使用heapq實現堆排序4.獲取堆中的前n個最大值或最小值Reference heapq模塊實現了堆隊列的算法&#xff0c;即優先隊列算法。heapq其實是實現了一種小頂堆&#xff0c;所以使用pop()方法返回的…

如何進行弱網測試?

&#x1f345; 視頻學習&#xff1a;文末有免費的配套視頻可觀看 &#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 如今這個高度互聯的時代里&#xff0c;網絡環境對于應用程序的影響越來越重要。 而弱網測試就是…

leetcode--接雨水(雙指針法,動態規劃,單調棧)

目錄 方法一&#xff1a;雙指針法 方法二&#xff1a;動態規劃 方法三&#xff1a;單調棧 42. 接雨水 - 力扣&#xff08;LeetCode&#xff09; 黑色的是柱子&#xff0c;藍色的是雨水&#xff0c;我們先來觀察一下雨水的分布情況: 雨水落在凹槽之間&#xff0c;在一個凹槽的…

使用js寫一個登錄驗證碼效果

面試題 登錄頁面獲取驗證碼的功能&#xff0c;用戶點擊獲取驗證碼按鈕(id”btn1”)&#xff0c;按文字變為“(N)后獲取驗證碼”&#xff0c;N為倒計對秒數&#xff0c;從 60 開始&#xff0c;每秒減一&#xff0c;減到 0的時候&#xff0c;按鈕文字變為“獲取驗證碼”&#xff…