普通视图
CNN 分层详解:卷积、池化到全连接的作用与原理
一、第一层卷积 + 池化
// 第1层卷积:学边缘
model.add(tf.layers.conv2d({
inputShape: [28, 28, 1], // 输入是28×28的灰度图
filters: 32, // 提取32种特征
kernelSize: 3, // 卷积核大小3×3
activation: 'relu' // 激活函数:非线性
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
作用:
- 卷积核(3×3)会扫描图片,学习到“局部边缘特征”(比如横线、竖线、斜线)。
- 32 个 filters 就相当于 32 双不同的“眼睛”,每个眼睛关注不同方向的边缘。
-
relu
保证输出非线性,否则网络只能学直线关系。 -
MaxPooling(2×2)
把特征图缩小一半,减少计算,同时保留“最显著”的特征(增强平移不变性)。
二、第二层卷积 + 池化
// 第2层卷积:学纹理
model.add(tf.layers.conv2d({
filters: 64,
kernelSize: 3,
activation: 'relu'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
作用:
- 在第一层学到“边缘”的基础上,这一层能把边缘组合成“纹理、角点、曲线”。
-
64 filters
= 64 种更复杂的特征检测器。 - 再次
MaxPooling
→ 缩小尺寸,避免特征图过大。
三、第三层卷积
// 第3层卷积:学更复杂的形状
model.add(tf.layers.conv2d({
filters: 128,
kernelSize: 3,
activation: 'relu'
}));
model.add(tf.layers.flatten());
作用:
- 继续在“纹理”基础上,学习更高级的形状,比如:圈、数字结构、局部物体。
-
128 filters
= 学习更多样的复杂特征。 -
Flatten
:把二维的特征图摊平成一维向量,方便后面的全连接层做分类。
四、全连接层
// 全连接层:组合特征
model.add(tf.layers.dense({units: 128, activation: 'relu'}));
作用:
- Dense 层相当于“大脑整合”:把所有局部特征(边缘、纹理、形状)组合在一起。
-
units: 128
表示有 128 个神经元,每个神经元会学习一种组合方式。 -
relu
继续保持非线性,增强表达能力。
五、输出层
// 输出层:分类 0-9
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
作用:
-
最终分类层,把上一步的综合特征转化为“属于每个类别的概率”。
-
units: 10
= 10 个类别(数字 0–9)。 -
softmax
会把输出变成概率分布,比如:[0.01, 0.02, 0.05, 0.80, 0.02, 0.05, 0.01, 0.02, 0.01, 0.01]
→ 表示模型认为这张图是“3”的概率最高(80%)。
CNN 多层设计详解:从边缘到高级特征的逐层学习
一、概念
在 卷积神经网络(CNN) 中,模型通常不是一两层,而是由 多层卷积层、池化层、全连接层 堆叠起来。
原因在于:每一层都在学习不同层次的特征,从 简单 → 复杂,逐步组合。
二、原理
1. 层的分工
-
卷积层(Conv2D)
- 提取局部特征。
- 第一层学边缘、线条;第二层学纹理、角点;更深层学物体部件。
-
激活函数层(ReLU, LeakyReLU 等)
- 引入非线性,否则网络只能学“线性变换”。
-
池化层(MaxPooling/AveragePooling)
- 下采样,减少参数,保留关键信息,增强平移不变性。
-
全连接层(Dense)
- 把卷积提取的特征组合起来,完成分类/回归。
-
Softmax 层
- 把数值转成概率,用于分类输出。
2. 分层特征表示
可以类比人类视觉:
- 低层神经元只对“边缘/亮度”敏感;
- 中层神经元能识别“形状/图案”;
- 高层神经元能识别“物体整体”(比如数字 5、猫耳朵)。
三、对比
网络层 | 学习内容 | 作用 | 类比人类视觉 |
---|---|---|---|
卷积层1 | 边缘、直线、颜色斑块 | 初步特征提取 | 看到“黑白对比” |
卷积层2 | 纹理、角点 | 组合低层特征 | 看到“弯曲的线” |
卷积层3+ | 复杂形状、局部物体 | 更高语义特征 | 看到“眼睛/鼻子/数字形状” |
池化层 | 缩小空间维度 | 提升鲁棒性 | 只在意“有无”,不在意具体位置 |
全连接层 | 全局组合 | 输出分类/预测结果 | 大脑做“最终判断” |
四、实践(代码示例:多层 CNN)
const model = tf.sequential();
// 第1层卷积:学边缘
model.add(tf.layers.conv2d({
inputShape: [28, 28, 1],
filters: 32,
kernelSize: 3,
activation: 'relu'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
// 第2层卷积:学纹理
model.add(tf.layers.conv2d({
filters: 64,
kernelSize: 3,
activation: 'relu'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
// 第3层卷积:学更复杂的形状
model.add(tf.layers.conv2d({
filters: 128,
kernelSize: 3,
activation: 'relu'
}));
model.add(tf.layers.flatten());
// 全连接层:组合特征
model.add(tf.layers.dense({units: 128, activation: 'relu'}));
// 输出层:分类 0-9
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
👉 注释:
- Conv2D×3:逐层提取特征。
- MaxPooling2D:缩小特征图,减少计算。
- Flatten + Dense:把空间特征变成全局判断。
🐣 最简单的卷积与激活函数指南(带示例)
1. 卷积层是干啥的?
你可以把卷积层想象成:
-
相机镜头 📷:不同的卷积核像不同滤镜,能抓住不同的特征。
-
多层卷积 = 多重观察:
- 第一层:看边缘和线条
- 第二层:看形状和纹理
- 第三层:看出“这是一只猫”
2. 常用卷积函数(记住就好)
-
Conv1D
→ 处理 一维数据(声音波形、股票曲线) -
Conv2D
→ 处理 图片(最常用) -
Conv3D
→ 处理 视频 / 医学 CT 扫描 -
SeparableConv2D
→ 轻量化,适合 手机端模型 -
Conv2DTranspose
→ 把小图变大图(生成图片、分割任务)
👉 大多数情况下,图像直接用 Conv2D
。
3. 激活函数是干啥的?
没有激活函数,网络就像一个“只能拉直线的画家”,学不了复杂图形。
激活函数让网络会“弯”,能画出复杂关系。
4. 激活函数怎么选?
隐藏层(卷积层后面)
- ReLU 👉 默认首选,简单好用
- LeakyReLU 👉 ReLU 的改进版,避免神经元“死掉”
- Swish / GELU 👉 更高级,现代模型里常用,但计算慢
输出层(最后一层,和任务相关)
-
二分类(猫 vs 狗) 👉
sigmoid
-
多分类(猫 / 狗 / 兔子) 👉
softmax
- 回归(预测房价) 👉 不加激活(线性输出)
👉 口诀:中间层 ReLU,最后一层看任务。
5. 示例(大量对比)
示例 A:猫狗二分类
layers.Conv2D(32, (3,3), activation='relu') # 隐藏层用 ReLU
...
layers.Dense(1, activation='sigmoid') # 最后一层 sigmoid
示例 B:三分类(猫/狗/兔子)
layers.Conv2D(64, (3,3), activation='relu')
...
layers.Dense(3, activation='softmax') # 最后一层 softmax
示例 C:预测房价(回归)
layers.Conv2D(32, (3,3), activation='relu')
...
layers.Dense(1) # 最后一层不用激活
示例 D:声音数据(语音情感识别)
layers.Conv1D(64, 3, activation='relu', input_shape=(1000, 20))
layers.GlobalMaxPooling1D()
layers.Dense(3, activation='softmax') # 三种情感
示例 E:视频分类(动作识别:跑 / 跳 / 走)
layers.Conv3D(32, (3,3,3), activation='relu', input_shape=(16, 112, 112, 3))
layers.MaxPooling3D((2,2,2))
layers.Dense(3, activation='softmax') # 三类动作
示例 F:轻量化模型(移动端)
layers.SeparableConv2D(32, (3,3), activation='relu')
layers.Dense(1, activation='sigmoid')
6. 总结一句话
-
卷积层选法:
- 图片 → Conv2D
- 视频 → Conv3D
- 声音/文本 → Conv1D
-
激活函数选法:
- 中间层 → ReLU
- 输出层 → 按任务选(sigmoid / softmax / 无)
👉 口诀:
“中间 ReLU,最后看任务;图像 2D,视频 3D,声音 1D。” 🎯
安装 tensflow 连接 windows
📖 小说网站预导航技术全解:秒开下一章 + 浏览器前进/后退支持
一、前言
在小说网站中,点击「下一章」通常需要等待网络请求,体验不佳。为提升用户阅读体验,很多网站采用 预导航(Prefetch / Pre-navigation) 技术:提前获取下一章内容,点击时立即展示。
本文将从三个角度详细介绍实现方式:
- HTML
<link>
预加载(最简单,零 JS 方案) - JS
fetch
主动预加载(可控性更高) -
fetch + History API
(支持浏览器前进/后退,最佳体验) - 如何通过
history.back()
控制浏览器箭头显示
二、方式一:<link rel="prefetch">
HTML5 提供了原生预取机制,写在 <head>
中:
<link rel="prefetch" href="/chapter2.html" as="document">
- prefetch:浏览器空闲时预加载资源(下一章 HTML / 图片等)
- prerender:在后台完整渲染页面,点击时几乎零延迟,但兼容性较差
优点:
- 实现简单,一行代码搞定
- 浏览器自动缓存资源
缺点:
- 无法控制加载时机
- 不会自动与浏览器前进/后退联动
适用场景: 简单优化,如博客或小说目录页
三、方式二:JS fetch
主动预加载
如果需要更精确的控制,可用 JS fetch
:
let nextUrl = "/chapter2.html";
let cache = null;
// 页面加载时预取下一章
fetch(nextUrl)
.then(res => res.text())
.then(html => cache = html);
// 点击下一章
document.getElementById("next").addEventListener("click", (e) => {
e.preventDefault();
if (cache) {
document.getElementById("content").innerHTML = cache;
} else {
location.href = nextUrl;
}
});
优点:
- 精确控制加载时机
- 可缓存多章内容
缺点:
- 浏览器前进/后退按钮失效(未写入历史栈)
四、方式三:fetch + History API
(推荐🔥)
为了让前进/后退按钮也能秒开,需要结合 pushState
和 popstate
:
const contentEl = document.getElementById("content");
let preloadCache = {};
// 初始化历史栈
history.replaceState({ html: contentEl.innerHTML }, "", location.href);
// 预取下一章
function preload(url) {
if (preloadCache[url]) return;
fetch(url)
.then(res => res.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, "text/html");
preloadCache[url] = doc.querySelector("#content").innerHTML;
});
}
// 点击切换章节
document.querySelectorAll("a.nav").forEach(link => {
link.addEventListener("click", async (e) => {
e.preventDefault();
const url = link.href;
let newContent = preloadCache[url];
if (!newContent) {
const res = await fetch(url);
const html = await res.text();
const doc = new DOMParser().parseFromString(html, "text/html");
newContent = doc.querySelector("#content").innerHTML;
}
contentEl.innerHTML = newContent;
history.pushState({ html: newContent }, "", url);
// 顺便预取下一章
const next = link.nextElementSibling?.href;
if (next) preload(next);
});
});
// 浏览器前进/后退
window.addEventListener("popstate", (e) => {
if (e.state && e.state.html) {
contentEl.innerHTML = e.state.html;
} else {
location.reload();
}
});
优点:
- 点击下一章秒开
- 前进/后退按钮秒开
- 可扩展多章预取策略
缺点:
- 需要额外 JS 逻辑
- 刷新页面仍需服务端响应
五、控制浏览器箭头可用
- 浏览器前进按钮只有在 有可前进历史 时才激活
- pushState 不会自动激活前进按钮
- 可行方法:先创建多条历史,然后调用
history.back()
// 初始化
history.replaceState({id:1},"","#1");
history.pushState({id:2},"","#2");
// 此时前进按钮仍灰色
history.back(); // 后退到第1章,前进按钮可用
JS 无法直接让浏览器箭头激活,只能通过创建历史并后退实现
六、方案对比
方案 | 代码复杂度 | 可控性 | 前进/后退支持 | 适用场景 |
---|---|---|---|---|
<link rel="prefetch"> |
极低 | 低 | ❌ | 简单优化 |
JS fetch
|
中 | 高 | ❌ | 定制预取策略 |
fetch + History API |
高 | 高 | ✅ | 小说网站 / 阅读器 |
七、进一步优化思路
- 条件预取:根据
navigator.connection.saveData
判断是否省流量 - 多章节预取:提前缓存后两章,实现点击前进秒开
- Service Worker:离线缓存,断网也能阅读
- 无限滚动:直接拼接章节,去掉翻页操作
八、总结
-
最简单方式:
<link rel="prefetch">
,无需 JS -
更灵活方式:JS
fetch
,可缓存下一章 -
最佳体验方式:
fetch + History API
,前进/后退全支持
👉 对于小说网站,推荐第三种方案,配合预加载和缓存,可实现媲美本地阅读器的流畅体验
CSS 里的斜杠 /:你可能忽略的小细节
在日常写 CSS 时,很多人以为 /
只是数学除号,或者是可以随便用来分隔的符号。但实际上,它在 CSS 中有两种很重要的用法:
- 作为语法分隔符,区分不同类型的值。
- 作为数学运算符,表示除法或比例。
我们一起来看看常见的场景。
1. 斜杠作为分隔符
有些 CSS 简写属性的语法,必须用 /
来区分不同部分,否则浏览器就分不清。
字体简写:字号 / 行高
p {
font: 16px/1.5 "Arial", sans-serif;
}
这里的 16px/1.5
表示 字号是 16px,行高是 1.5。如果你把 /
去掉写成 16px 1.5
,浏览器会直接报错。
圆角:椭圆半径
.card {
border-radius: 50% / 30%;
}
这里 /
的意思是 横向半径 50%,纵向半径 30% ,结果就是一个椭圆角。
如果没有 /
,写成 50% 30%
,那就成了「左上=50%,右上=30%,右下=50%,左下=30%」,完全不同的效果。
Grid 布局:区域范围
.item {
grid-area: 1 / 2 / 3 / 5;
}
这里的四个数字依次是:行起点 / 列起点 / 行终点 / 列终点。
每个 /
把行和列的定义分隔开,否则就混淆了。
背景图:位置 / 尺寸
.hero {
background: url(bg.jpg) center / cover no-repeat;
}
这里 center / cover
的意思是:位置居中,尺寸为 cover。
如果没有 /
,浏览器会把 cover
当成颜色或其他属性,结果完全不对。
边框图像:三段式写法
.box {
border-image: url(border.png) 30 / 12 / 6 stretch;
}
这里的 /
分别把 slice / width / outset 分开。少一个斜杠就会解析出错。
2. 斜杠作为比例或除法
除了分隔符,/
还可以真正做「数学」。
宽高比
.video {
aspect-ratio: 16 / 9;
}
这是最常见的用法,16 / 9
就等于「16:9 的宽高比」。
calc() 计算
.col {
width: calc(100% / 3);
}
这里 100% / 3
表示把父容器宽度平均分成三份。
要注意:只能用数字去除单位值,像 1rem / 2rem
是不合法的。
3. 小结
可以把 CSS 里的 /
记成两类:
-
分隔符:用在简写属性里,把不同语义的值分开。
- 代表场景:
font
、border-radius
、grid-area
、background
、border-image
、mask
- 代表场景:
-
比例/除法:用在需要算比例或分配空间的地方。
- 代表场景:
aspect-ratio
、calc()
- 代表场景:
一句口诀:
👉 在简写里,/
是语法规定的“隔板”;在计算里,/
是数学里的“除法”。
Nuxt 3 微前端:模块导入导出与路由跳转实战
在 Nuxt 3 中使用微前端(Micro Frontend, MFE)可以实现模块化、按需加载和跨应用路由跳转。相比 Nuxt 2,Nuxt 3 提供了原生 Composition API、Vite 支持以及更灵活的模块系统,使得微前端集成更方便。
1. 概念
- 主应用(Host) :负责加载和渲染子应用,管理全局路由与状态。
- 子应用(Remote) :独立部署的模块化 Nuxt 3 应用,可通过 Module Federation 暴露组件或页面。
- Module Federation:Webpack 5 的微前端实现方案,允许子应用暴露模块,主应用按需加载。
- 路由跳转:微前端下,需处理主应用和子应用之间的路由通信。
2. 原理
2.1 子应用导出模块
Nuxt 3 配合 Webpack Module Federation,通过 exposes
导出组件:
// remoteApp/nuxt.config.ts
import { defineNuxtConfig } from 'nuxt/config'
import { ModuleFederationPlugin } from 'webpack'
export default defineNuxtConfig({
build: {
extend(config) {
config.plugins?.push(
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Widget': './components/Widget.vue'
},
shared: ['vue', 'vue-router']
})
)
}
}
})
-
name
:子应用名称。 -
filename
:远程入口文件。 -
exposes
:导出的组件列表。 -
shared
:共享依赖,避免重复打包。
2.2 主应用导入模块
主应用通过 remotes
引入子应用组件:
// hostApp/nuxt.config.ts
import { defineNuxtConfig } from 'nuxt/config'
import { ModuleFederationPlugin } from 'webpack'
export default defineNuxtConfig({
build: {
extend(config) {
config.plugins?.push(
new ModuleFederationPlugin({
remotes: {
remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js'
},
shared: ['vue', 'vue-router']
})
)
}
}
})
主应用可以直接使用子应用暴露的组件:
<script setup lang="ts">
import Widget from 'remoteApp/Widget'
</script>
<template>
<div>
<h1>Host App</h1>
<Widget />
</div>
</template>
2.3 路由跳转
Nuxt 3 使用 Composition API 的 useRouter
和 useRoute
进行路由操作。微前端中:
- 主应用内部跳转:
const router = useRouter()
router.push('/dashboard')
- 子应用跳转到主应用或其他子应用:
// 子应用中
function navigateToHost(path: string) {
window.dispatchEvent(new CustomEvent('navigate', { detail: path }))
}
// 主应用中
window.addEventListener('navigate', (e: any) => {
const router = useRouter()
router.push(e.detail)
})
3. 对比
功能 | Nuxt 3 单体 | Nuxt 3 微前端 |
---|---|---|
模块拆分 | 不支持 | 支持按子应用拆分 |
部署 | 单包 | 子应用独立部署 |
路由 | 全局 | 需协调主子应用路由 |
组件复用 | 受限 | 可跨应用导入 |
4. 实践示例
4.1 创建子应用(Remote)
npx nuxi init remoteApp
cd remoteApp
npm install
npm install -D webpack webpack-cli
- 配置
nuxt.config.ts
添加 Module Federation。 - 暴露组件
Widget.vue
。 - 启动服务:
npm run dev
,生成remoteEntry.js
。
4.2 创建主应用(Host)
npx nuxi init hostApp
cd hostApp
npm install
npm install -D webpack webpack-cli
- 配置
nuxt.config.ts
添加remotes
。 - 在页面中引入
<Widget />
。 - 添加全局事件监听处理子应用路由跳转。
5. 拓展功能
-
状态共享:通过 Pinia 或 Vue 3 的
provide/inject
实现主子应用状态共享。 - 权限控制:主应用统一管理路由权限,子应用仅渲染组件。
- 懒加载:使用动态 import 按需加载子应用,减少主应用首屏压力。
- 多子应用组合:支持多个微前端模块组合成复杂系统。
6. 潜在问题
- CSS 冲突:子应用样式可能污染主应用,建议使用 Scoped 或 CSS Module。
- 路由冲突:子应用路由与主应用冲突时,需要命名空间或前缀处理。
- 依赖版本冲突:Vue/Nuxt 版本需保持兼容。
- 性能开销:过多子应用增加网络请求和运行时开销。
7. 思路图示
+-------------------+ +-------------------+
| Host App | | Remote App |
| | | |
| +---------------+ | | +---------------+ |
| | Nuxt Router |<-----> | | Widget.vue | |
| +---------------+ | | +---------------+ |
| | | |
| <Widget /> | | remoteEntry.js |
+-------------------+ +-------------------+