#P4278. 第3题-卷积结构实现
-
1000ms
Tried: 116
Accepted: 25
Difficulty: 6
所属公司 :
华为
时间 :2025年10月23日-AI方向(留学生)
-
算法标签>二维卷积
第3题-卷积结构实现
解题思路
本题需要手写二维卷积 Conv2D(input, weight, bias, stride, padding, dilation),支持步幅、零填充与空洞(扩张)卷积。
设输入形状为 (C, H, W),卷积核形状为 (Out, In, K, K)。
-
输出尺寸计算 令有效核尺寸
K_eff = dilation * (K - 1) + 1H_out = floor((H + 2*padding - K_eff) / stride) + 1 W_out = floor((W + 2*padding - K_eff) / stride) + 1 -
卷积计算 对每个输出通道
oc、输出位置(oh, ow):y[oc, oh, ow] = (bias[oc] if 有偏置 else 0) + Σ_{ic=0..In-1} Σ_{kh=0..K-1} Σ_{kw=0..K-1} x[ic, ih, iw] * w[oc, ic, kh, kw] 其中: ih = oh*stride + kh*dilation - padding iw = ow*stride + kw*dilation - padding 超出边界的 (ih, iw) 视为 0(零填充)。 -
读写顺序
-
输入与权重均按“先行后列”展开;多维时采用 通道优先 再行、再列,即:
input展开顺序:ic -> ih -> iwweight展开顺序:oc -> ic -> kh -> kw
-
输出打印顺序:
oc -> oh -> ow,全部保留 4 位小数,以空格分隔。
-
复杂度分析
- 时间复杂度:
O(Out * In * H_out * W_out * K * K) - 空间复杂度:
O(C*H*W + Out*In*K*K + Out*H_out*W_out)(主要为存储输入、权重与结果),额外辅助空间为O(1)。
代码实现
Python
# -*- coding: utf-8 -*-
# 题面要求:主函数内做输入输出,功能在外部函数;ACM 风格;中文注释
import sys
def conv2d(input_arr, C, H, W, weight_arr, Out, InC, K, bias_arr, has_bias, stride, padding, dilation):
# 计算输出尺寸
K_eff = dilation * (K - 1) + 1
H_out = (H + 2 * padding - K_eff) // stride + 1
W_out = (W + 2 * padding - K_eff) // stride + 1
# 索引函数(行优先)
def idx_input(ic, ih, iw):
return ic * (H * W) + ih * W + iw
def idx_weight(oc, ic, kh, kw):
return (((oc * InC + ic) * K + kh) * K + kw)
# 结果数组(扁平存储:oc -> oh -> ow)
out_size = Out * H_out * W_out
out_arr = [0.0] * out_size
# 卷积主循环
for oc in range(Out):
b = bias_arr[oc] if has_bias else 0.0
for oh in range(H_out):
for ow in range(W_out):
s = b
base_h = oh * stride - padding
base_w = ow * stride - padding
for ic in range(InC):
for kh in range(K):
ih = base_h + kh * dilation
if ih < 0 or ih >= H:
continue
for kw in range(K):
iw = base_w + kw * dilation
if iw < 0 or iw >= W:
continue
s += input_arr[idx_input(ic, ih, iw)] * weight_arr[idx_weight(oc, ic, kh, kw)]
out_index = (oc * H_out + oh) * W_out + ow
out_arr[out_index] = s
return out_arr, H_out, W_out
def main():
data = sys.stdin.read().strip().split()
it = iter(data)
# 读取输入形状 C H W
C = int(next(it)); H = int(next(it)); W = int(next(it))
# 读取输入数据(行优先,通道优先)
input_cnt = C * H * W
input_arr = [float(next(it)) for _ in range(input_cnt)]
# 读取卷积核形状 Out In K K
Out = int(next(it)); InC = int(next(it)); K1 = int(next(it)); K2 = int(next(it))
K = K1 # 题目保证为方核
# 读取权重
weight_cnt = Out * InC * K * K
weight_arr = [float(next(it)) for _ in range(weight_cnt)]
# 读取 bias 标志、stride、padding、dilation
has_bias_flag = int(next(it))
stride = int(next(it)); padding = int(next(it)); dilation = int(next(it))
# 读取 bias
if has_bias_flag == 1:
bias_arr = [float(next(it)) for _ in range(Out)]
has_bias = True
else:
bias_arr = [0.0] * Out
has_bias = False
# 计算卷积
out_arr, H_out, W_out = conv2d(input_arr, C, H, W, weight_arr, Out, InC, K, bias_arr, has_bias, stride, padding, dilation)
# 输出一行,四位小数
res = ["{:.4f}".format(v) for v in out_arr]
print(" ".join(res))
if __name__ == "__main__":
main()
Java
// ACM 风格;类名 Main;中文注释
import java.io.*;
import java.util.*;
public class Main {
// 计算一维索引(行优先)
static int idxInput(int ic, int ih, int iw, int C, int H, int W) {
return ic * (H * W) + ih * W + iw;
}
static int idxWeight(int oc, int ic, int kh, int kw, int Out, int InC, int K) {
return (((oc * InC + ic) * K + kh) * K + kw);
}
static double[] conv2d(double[] input, int C, int H, int W,
double[] weight, int Out, int InC, int K,
double[] bias, boolean hasBias,
int stride, int padding, int dilation) {
int K_eff = dilation * (K - 1) + 1;
int H_out = (H + 2 * padding - K_eff) / stride + 1;
int W_out = (W + 2 * padding - K_eff) / stride + 1;
double[] out = new double[Out * H_out * W_out];
for (int oc = 0; oc < Out; oc++) {
double b = hasBias ? bias[oc] : 0.0;
for (int oh = 0; oh < H_out; oh++) {
for (int ow = 0; ow < W_out; ow++) {
double s = b;
int base_h = oh * stride - padding;
int base_w = ow * stride - padding;
for (int ic = 0; ic < InC; ic++) {
for (int kh = 0; kh < K; kh++) {
int ih = base_h + kh * dilation;
if (ih < 0 || ih >= H) continue;
for (int kw = 0; kw < K; kw++) {
int iw = base_w + kw * dilation;
if (iw < 0 || iw >= W) continue;
s += input[idxInput(ic, ih, iw, C, H, W)] *
weight[idxWeight(oc, ic, kh, kw, Out, InC, K)];
}
}
}
int outIdx = (oc * H_out + oh) * W_out + ow;
out[outIdx] = s;
}
}
}
return out;
}
// 简单快速读入(兼顾性能与易懂)
static class FastScanner {
private final InputStream in;
private final byte[] buffer = new byte[1 << 16];
private int ptr = 0, len = 0;
FastScanner(InputStream is) { in = is; }
private int read() throws IOException {
if (ptr >= len) {
len = in.read(buffer);
ptr = 0;
if (len <= 0) return -1;
}
return buffer[ptr++];
}
String next() throws IOException {
StringBuilder sb = new StringBuilder();
int c;
while ((c = read()) != -1 && c <= ' ') {}
if (c == -1) return null;
do {
sb.append((char)c);
c = read();
} while (c != -1 && c > ' ');
return sb.toString();
}
int nextInt() throws IOException { return Integer.parseInt(next()); }
double nextDouble() throws IOException { return Double.parseDouble(next()); }
}
public static void main(String[] args) throws Exception {
FastScanner fs = new FastScanner(System.in);
// 读取输入形状 C H W
int C = Integer.parseInt(fs.next());
int H = Integer.parseInt(fs.next());
int W = Integer.parseInt(fs.next());
// 输入数据
int inCnt = C * H * W;
double[] input = new double[inCnt];
for (int i = 0; i < inCnt; i++) input[i] = Double.parseDouble(fs.next());
// 卷积核形状 Out In K K
int Out = Integer.parseInt(fs.next());
int InC = Integer.parseInt(fs.next());
int K1 = Integer.parseInt(fs.next());
int K2 = Integer.parseInt(fs.next());
int K = K1; // 方核
// 权重
int wCnt = Out * InC * K * K;
double[] weight = new double[wCnt];
for (int i = 0; i < wCnt; i++) weight[i] = Double.parseDouble(fs.next());
// bias 标志、stride、padding、dilation
int hasBiasFlag = Integer.parseInt(fs.next());
int stride = Integer.parseInt(fs.next());
int padding = Integer.parseInt(fs.next());
int dilation = Integer.parseInt(fs.next());
// bias
double[] bias = new double[Out];
boolean hasBias = hasBiasFlag == 1;
if (hasBias) {
for (int i = 0; i < Out; i++) bias[i] = Double.parseDouble(fs.next());
}
// 计算
double[] out = conv2d(input, C, H, W, weight, Out, InC, K, bias, hasBias, stride, padding, dilation);
// 输出一行,四位小数
StringBuilder sb = new StringBuilder();
Locale.setDefault(Locale.US);
for (int i = 0; i < out.length; i++) {
if (i > 0) sb.append(' ');
sb.append(String.format(Locale.US, "%.4f", out[i]));
}
System.out.println(sb.toString());
}
}
C++
// ACM 风格;中文注释;保持语法简洁
#include <bits/stdc++.h>
using namespace std;
inline int idxInput(int ic, int ih, int iw, int C, int H, int W) {
return ic * (H * W) + ih * W + iw;
}
inline int idxWeight(int oc, int ic, int kh, int kw, int Out, int InC, int K) {
return (((oc * InC + ic) * K + kh) * K + kw);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int C, H, W;
if (!(cin >> C >> H >> W)) return 0;
// 输入数据
int inCnt = C * H * W;
vector<double> input(inCnt);
for (int i = 0; i < inCnt; ++i) cin >> input[i];
// 卷积核形状 Out In K K
int Out, InC, K1, K2;
cin >> Out >> InC >> K1 >> K2;
int K = K1; // 方核
// 权重
int wCnt = Out * InC * K * K;
vector<double> weight(wCnt);
for (int i = 0; i < wCnt; ++i) cin >> weight[i];
// bias 标志、stride、padding、dilation
int hasBiasFlag, stride, padding, dilation;
cin >> hasBiasFlag >> stride >> padding >> dilation;
// bias
vector<double> bias(Out, 0.0);
bool hasBias = (hasBiasFlag == 1);
if (hasBias) {
for (int i = 0; i < Out; ++i) cin >> bias[i];
}
// 输出尺寸
int K_eff = dilation * (K - 1) + 1;
int H_out = (H + 2 * padding - K_eff) / stride + 1;
int W_out = (W + 2 * padding - K_eff) / stride + 1;
vector<double> out(Out * H_out * W_out, 0.0);
// 卷积
for (int oc = 0; oc < Out; ++oc) {
double b = hasBias ? bias[oc] : 0.0;
for (int oh = 0; oh < H_out; ++oh) {
for (int ow = 0; ow < W_out; ++ow) {
double s = b;
int base_h = oh * stride - padding;
int base_w = ow * stride - padding;
for (int ic = 0; ic < InC; ++ic) {
for (int kh = 0; kh < K; ++kh) {
int ih = base_h + kh * dilation;
if (ih < 0 || ih >= H) continue;
for (int kw = 0; kw < K; ++kw) {
int iw = base_w + kw * dilation;
if (iw < 0 || iw >= W) continue;
s += input[idxInput(ic, ih, iw, C, H, W)] *
weight[idxWeight(oc, ic, kh, kw, Out, InC, K)];
}
}
}
out[(oc * H_out + oh) * W_out + ow] = s;
}
}
}
// 按要求输出为一行,四位小数
cout.setf(std::ios::fixed);
cout << setprecision(4);
for (size_t i = 0; i < out.size(); ++i) {
if (i) cout << ' ';
cout << out[i];
}
cout << '\n';
return 0;
}
题目内容
卷积神经网络 (CNN) 是计算机视觉领域的核心模型,ResNed 通过残差连接 (Residual Connection) 进一步解决了深层神经网络梯度消失的问题,本题要求实现 CNN 基础的卷积函数 $Conv2D(input, weight, bias, stride,padding, dilation)$,相关参数描述如下:
input:输入数据;
weight:卷积核的权重;
bias:卷积核的偏置
stride:卷积核的移动步长;
padding:输入数据边缘填充的像素数(填充 0 );
dilation:卷积核元素之间的间隔;
输入描述
第 1 行:输入数据的形状 c,x,y,以空格隔开
第 2 行:输入数据,为 c∗x∗y 个实数,按照先行后列排序。
第 3 行:卷积核的形状 out,in,k,k
第 4 行:卷积的权重,数量为 out∗in∗k∗k ,按照先行后列排序
第 5 行: bias,stride,padding,dilation
第 6 行:若 bias 为 1 ,则该行为 bias 的具体值,长度为 out ,否则该行为空
其中 0<x,y<1000,0<k<100
输出描述
卷积的计算结果,输出为一行,保留 4 位小数,不足四位小数补 0
样例1
输入
1 4 4
1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0
1 1 3 3
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
0 1 0 1
输出
54.0000 63.0000 90.0000 99.0000
说明
输入的形状为 (1.4,4) ,故第二行的数据 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 的数据排列方式为:
[[[1.0,2.0,3.0,4.0],
[5.0.6.0,7.0,8.0],
[9.0,10.0,11.0,12.0]
[13.0,14.0,15.0,16.0]]]
卷积计算结果为:
[[[54.0000,63.0000],
[90.0000,99.0000]
输出为:54.0000 63.0000 90.0000 99.0000
样例2
输入
1 4 4
1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0
1 1 3 3
1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
1 1 0 1
1.0
输出
55.0000 64.0000 91.0000 100.0000