Merge branch 'master' into master
This commit is contained in:
65
.vitepress/components/Confetti.vue
Normal file
65
.vitepress/components/Confetti.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup>
|
||||
import confetti from 'canvas-confetti'
|
||||
import { watch } from 'vue'
|
||||
|
||||
const props = defineProps(
|
||||
{
|
||||
passed: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
['passed'],
|
||||
)
|
||||
|
||||
function congrats() {
|
||||
const defaults = {
|
||||
colors: [
|
||||
'#5D8C7B',
|
||||
'#F2D091',
|
||||
'#F2A679',
|
||||
'#D9695F',
|
||||
'#8C4646',
|
||||
],
|
||||
shapes: ['square'],
|
||||
ticks: 500,
|
||||
}
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 80,
|
||||
spread: 100,
|
||||
origin: { y: 0 },
|
||||
})
|
||||
setTimeout(() => {
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 50,
|
||||
angle: 60,
|
||||
spread: 80,
|
||||
origin: { x: 0 },
|
||||
})
|
||||
}, 250)
|
||||
setTimeout(() => {
|
||||
confetti({
|
||||
...defaults,
|
||||
particleCount: 50,
|
||||
angle: 120,
|
||||
spread: 80,
|
||||
origin: { x: 1 },
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.passed,
|
||||
(v) => {
|
||||
if (v)
|
||||
setTimeout(congrats, 300)
|
||||
},
|
||||
{ flush: 'post' },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div />
|
||||
</template>
|
||||
111
.vitepress/components/CustomNotFound.vue
Normal file
111
.vitepress/components/CustomNotFound.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { withBase } from 'vitepress'
|
||||
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
|
||||
import { useLangs } from 'vitepress/dist/client/theme-default/composables/langs'
|
||||
import Sweep from './sweep.vue';
|
||||
|
||||
const { site, theme } = useData()
|
||||
const { localeLinks } = useLangs({ removeCurrent: false })
|
||||
|
||||
const root = ref('/')
|
||||
onMounted(() => {
|
||||
const path = window.location.pathname
|
||||
.replace(site.value.base, '')
|
||||
.replace(/(^.*?\/).*$/, '/$1')
|
||||
if (localeLinks.value.length) {
|
||||
root.value =
|
||||
localeLinks.value.find(({ link }) => link.startsWith(path))?.link ||
|
||||
localeLinks.value[0].link
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="NotFound">
|
||||
<p class="code">{{ theme.notFound?.code ?? '404' }}</p>
|
||||
<h1 class="title">{{ theme.notFound?.title ?? 'PAGE NOT FOUND' }}</h1>
|
||||
<div class="divider" />
|
||||
<div class="action">
|
||||
<a
|
||||
class="link"
|
||||
:href="withBase(root)"
|
||||
:aria-label="theme.notFound?.linkLabel ?? 'go to home'"
|
||||
>
|
||||
{{ theme.notFound?.linkText ?? 'Take me home' }}
|
||||
</a>
|
||||
</div>
|
||||
<blockquote class="quote">
|
||||
{{
|
||||
theme.notFound?.quote ??
|
||||
"虽然你迷路了,但是这是一个扫雷小游戏"
|
||||
}}
|
||||
</blockquote>
|
||||
<Sweep />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.NotFound {
|
||||
padding: 64px 24px 96px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.NotFound {
|
||||
padding: 96px 32px 168px;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
line-height: 128px;
|
||||
font-size: 128px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding-top: 12px;
|
||||
letter-spacing: 2px;
|
||||
line-height: 20px;
|
||||
font-size: 35px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin: 24px auto 1px;
|
||||
width: 64px;
|
||||
height: 1px;
|
||||
background-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.quote {
|
||||
margin: 0 auto;
|
||||
padding-top: 20px;
|
||||
max-width: 256px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.action {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-block;
|
||||
border: 1px solid var(--vp-c-brand);
|
||||
border-radius: 16px;
|
||||
padding: 3px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-brand);
|
||||
transition:
|
||||
border-color 0.25s,
|
||||
color 0.25s;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
border-color: var(--vp-c-brand-dark);
|
||||
color: var(--vp-c-brand-dark);
|
||||
}
|
||||
</style>
|
||||
73
.vitepress/components/MineBlock.vue
Normal file
73
.vitepress/components/MineBlock.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<script setup>
|
||||
defineProps(
|
||||
{
|
||||
block: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
['block']
|
||||
)
|
||||
|
||||
const numberColors = [
|
||||
'text-transparent',
|
||||
'text-blue-500',
|
||||
'text-green-500',
|
||||
'text-yellow-500',
|
||||
'text-orange-500',
|
||||
'text-red-500',
|
||||
'text-purple-500',
|
||||
'text-pink-500',
|
||||
'text-teal-500',
|
||||
]
|
||||
|
||||
function getBlockClass(block) {
|
||||
if (block.flagged)
|
||||
return 'bg-gray-500-10'
|
||||
if (!block.revealed)
|
||||
return 'bg-gray-500-10 hover:bg-gray-500-20'
|
||||
|
||||
return block.mine
|
||||
? 'bg-red-500-50'
|
||||
: numberColors[block.adjacentMines]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.text-transparent { color: transparent; }
|
||||
.text-blue-500 { color: #3b82f6; }
|
||||
.text-green-500 { color: #10b981; }
|
||||
.text-yellow-500 { color: #f59e0b; }
|
||||
.text-orange-500 { color: #f97316; }
|
||||
.text-red-500 { color: #ef4444; }
|
||||
.text-red { color: rgba(248, 113, 113); }
|
||||
.text-purple-500 { color: #8b5cf6; }
|
||||
.text-pink-500 { color: #ec4899; }
|
||||
.text-teal-500 { color: #14b8a6; }
|
||||
.bg-gray-500-10 { background-color: rgba(107, 114, 128, 0.1); }
|
||||
.bg-gray-500-20 { background-color: rgba(107, 114, 128, 0.2); }
|
||||
.bg-red-500-50 { background-color: rgba(239, 68, 68, 0.5); }
|
||||
.font-600 { font-weight: 600; }
|
||||
.button-block { width: 30px; height: 30px; }
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<button
|
||||
style="display: flex; align-items: center; justify-content: center; min-width: 2rem; min-height: 2rem; margin: 1px; border: 0.5px solid rgba(166, 166, 166, 0.1);"
|
||||
:class="getBlockClass(block)"
|
||||
>
|
||||
<template v-if="block.flagged">
|
||||
<div class="text-red button-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M14.4 6L14 4H5v17h2v-7h5.6l.4 2h7V6h-5.6Z"/></svg>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="block.revealed">
|
||||
<div v-if="block.mine" class="button-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M23 13v-2h-3.07a7.988 7.988 0 0 0-1.62-3.9l2.19-2.17l-1.43-1.43l-2.17 2.19A7.988 7.988 0 0 0 13 4.07V1h-2v3.07c-1.42.18-2.77.74-3.9 1.62L4.93 3.5L3.5 4.93L5.69 7.1A7.988 7.988 0 0 0 4.07 11H1v2h3.07c.18 1.42.74 2.77 1.62 3.9L3.5 19.07l1.43 1.43l2.17-2.19c1.13.88 2.48 1.44 3.9 1.62V23h2v-3.07c1.42-.18 2.77-.74 3.9-1.62l2.17 2.19l1.43-1.43l-2.19-2.17a7.988 7.988 0 0 0 1.62-3.9H23M12 8a4 4 0 0 0-4 4H6a6 6 0 0 1 6-6v2Z"/></svg>
|
||||
</div>
|
||||
<div v-else class="font-600">
|
||||
{{ block.adjacentMines }}
|
||||
</div>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
161
.vitepress/components/sweep.vue
Normal file
161
.vitepress/components/sweep.vue
Normal file
@@ -0,0 +1,161 @@
|
||||
<script setup>
|
||||
import { GamePlay } from '../composables/logic.js'
|
||||
import { useNow, useStorage } from '@vueuse/core'
|
||||
import { watchEffect } from 'vue'
|
||||
import MineBlock from './MineBlock.vue'
|
||||
import Confetti from './Confetti.vue'
|
||||
const play = new GamePlay(9, 9, 10)
|
||||
|
||||
const now = $(useNow())
|
||||
const timerMS = $computed(() => Math.round(((play.state.value.endMS || +now) - play.state.value.startMS) / 1000))
|
||||
|
||||
useStorage('vuesweeper-state', play.state)
|
||||
const state = $computed(() => play.board)
|
||||
|
||||
const mineRest = $computed(() => {
|
||||
if (!play.state.value.mineGenerated)
|
||||
return play.mines
|
||||
return play.blocks.reduce((a, b) => a + (b.mine ? 1 : 0) - (b.flagged ? 1 : 0), 0)
|
||||
})
|
||||
|
||||
function newGame(difficulty) {
|
||||
switch (difficulty) {
|
||||
case 'very easy':
|
||||
play.reset(3, 3, 1)
|
||||
break
|
||||
case 'easy':
|
||||
play.reset(9, 9, 10)
|
||||
break
|
||||
case 'medium':
|
||||
play.reset(16, 16, 40)
|
||||
break
|
||||
case 'hard':
|
||||
play.reset(16, 30, 99)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
play.checkGameState()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex gap1 justify-center p4">
|
||||
<button class="btn" @click="play.reset()">
|
||||
New Game
|
||||
</button>
|
||||
<button class="btn" @click="newGame('very easy')">
|
||||
Very Easy
|
||||
</button>
|
||||
<button class="btn" @click="newGame('easy')">
|
||||
Easy
|
||||
</button>
|
||||
<button class="btn" @click="newGame('medium')">
|
||||
Medium
|
||||
</button>
|
||||
<button class="btn" @click="newGame('hard')">
|
||||
Hard
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-10 justify-center">
|
||||
<div class="text-2xl flex gap-1 items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 32 32"><path fill="currentColor" d="M15 11h2v9h-2zm-2-9h6v2h-6z"/><path fill="currentColor" d="m28 9l-1.42-1.41l-2.25 2.25a10.94 10.94 0 1 0 1.18 1.65ZM16 26a9 9 0 1 1 9-9a9 9 0 0 1-9 9Z"/></svg>
|
||||
{{ timerMS }}
|
||||
</div>
|
||||
<div class="text-2xl flex gap-1 items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 24 24"><path fill="currentColor" d="M23 13v-2h-3.07a7.988 7.988 0 0 0-1.62-3.9l2.19-2.17l-1.43-1.43l-2.17 2.19A7.988 7.988 0 0 0 13 4.07V1h-2v3.07c-1.42.18-2.77.74-3.9 1.62L4.93 3.5L3.5 4.93L5.69 7.1A7.988 7.988 0 0 0 4.07 11H1v2h3.07c.18 1.42.74 2.77 1.62 3.9L3.5 19.07l1.43 1.43l2.17-2.19c1.13.88 2.48 1.44 3.9 1.62V23h2v-3.07c1.42-.18 2.77-.74 3.9-1.62l2.17 2.19l1.43-1.43l-2.19-2.17a7.988 7.988 0 0 0 1.62-3.9H23M12 8a4 4 0 0 0-4 4H6a6 6 0 0 1 6-6v2Z"/></svg>
|
||||
{{ mineRest }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p5 w-full overflow-auto">
|
||||
<div
|
||||
v-for="row, y in state"
|
||||
:key="y"
|
||||
class="flex items-center justify-center w-max ma"
|
||||
>
|
||||
<MineBlock
|
||||
v-for="block, x in row" :key="x"
|
||||
:block="block"
|
||||
@click="play.onClick(block)"
|
||||
@dblclick="play.autoExpand(block)"
|
||||
@contextmenu.prevent="play.onRightClick(block)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div flex="~ gap-1" justify-center>
|
||||
<button btn @click="toggleDev()">
|
||||
{{ isDev ? 'DEV' : 'NORMAL' }}
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<Confetti :passed="play.state.value.status === 'won'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.gap1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.gap-1 {
|
||||
gap: 0.25rem;
|
||||
}
|
||||
.gap-10 {
|
||||
gap: 2.5rem;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.btn {
|
||||
background-color: var(--vp-button-brand-bg);
|
||||
border: 1px solid var(--vp-c-brand-light);
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: var(--vp-c-brand-light);
|
||||
border-color: var(--vp-c-brand-light);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.btn:dark {
|
||||
background-color: var(--vp-c-brand-dark);
|
||||
border-color: var(--vp-c-brand-light);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
.p4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
.p5 {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
.w-full {
|
||||
width: 100%;
|
||||
}
|
||||
.w-max {
|
||||
width: max-content;
|
||||
}
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
.ma {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
210
.vitepress/composables/logic.js
Normal file
210
.vitepress/composables/logic.js
Normal file
@@ -0,0 +1,210 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const directions = [
|
||||
[1, 1],
|
||||
[1, 0],
|
||||
[1, -1],
|
||||
[0, -1],
|
||||
[-1, -1],
|
||||
[-1, 0],
|
||||
[-1, 1],
|
||||
[0, 1],
|
||||
];
|
||||
|
||||
export class GamePlay {
|
||||
state = ref();
|
||||
|
||||
constructor(width, height, mines) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mines = mines;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
get board() {
|
||||
return this.state.value.board;
|
||||
}
|
||||
|
||||
get blocks() {
|
||||
return this.state.value.board.flat();
|
||||
}
|
||||
|
||||
reset(width = this.width, height = this.height, mines = this.mines) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.mines = mines;
|
||||
|
||||
this.state.value = {
|
||||
startMS: +Date.now(),
|
||||
endMS: undefined, // 确保结束时间戳被重置
|
||||
mineGenerated: false,
|
||||
status: 'play',
|
||||
board: Array.from({ length: this.height }, (_, y) =>
|
||||
Array.from({ length: this.width }, (_, x) => ({
|
||||
x,
|
||||
y,
|
||||
mine: false, // 初始化 mine 属性
|
||||
flagged: false, // 初始化 flagged 属性
|
||||
adjacentMines: 0,
|
||||
revealed: false,
|
||||
}),
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
randomRange(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
randomInt(min, max) {
|
||||
return Math.round(this.randomRange(min, max));
|
||||
}
|
||||
|
||||
generateMines(state, initial) {
|
||||
const placeRandom = () => {
|
||||
const x = this.randomInt(0, this.width - 1);
|
||||
const y = this.randomInt(0, this.height - 1);
|
||||
const block = state[y][x];
|
||||
if (Math.abs(initial.x - block.x) <= 1 && Math.abs(initial.y - block.y) <= 1)
|
||||
return false;
|
||||
if (block.mine)
|
||||
return false;
|
||||
block.mine = true;
|
||||
return true;
|
||||
};
|
||||
Array.from({ length: this.mines }, () => null)
|
||||
.forEach(() => {
|
||||
let placed = false;
|
||||
let attempts = 0;
|
||||
const maxAttempts = 1000;
|
||||
while (!placed) {
|
||||
if (attempts++ > maxAttempts) {
|
||||
this.reset();
|
||||
break;
|
||||
}
|
||||
placed = placeRandom();
|
||||
}
|
||||
});
|
||||
this.updateNumbers();
|
||||
}
|
||||
|
||||
updateNumbers() {
|
||||
this.board.forEach((raw) => {
|
||||
raw.forEach((block) => {
|
||||
if (block.mine)
|
||||
return;
|
||||
this.getSiblings(block)
|
||||
.forEach((b) => {
|
||||
if (b.mine)
|
||||
block.adjacentMines += 1;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
expendZero(block) {
|
||||
if (block.adjacentMines)
|
||||
return;
|
||||
|
||||
this.getSiblings(block)
|
||||
.forEach((s) => {
|
||||
if (!s.revealed) {
|
||||
s.revealed = true;
|
||||
this.expendZero(s);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onRightClick(block) {
|
||||
if (this.state.value.status !== 'play')
|
||||
return;
|
||||
|
||||
if (block.revealed)
|
||||
return;
|
||||
block.flagged = !block.flagged;
|
||||
}
|
||||
|
||||
onClick(block) {
|
||||
if (this.state.value.status !== 'play')
|
||||
return;
|
||||
|
||||
if (!this.state.value.mineGenerated) {
|
||||
this.generateMines(this.board, block);
|
||||
this.state.value.mineGenerated = true;
|
||||
}
|
||||
|
||||
block.revealed = true;
|
||||
if (block.mine) {
|
||||
this.onGameOver('lost');
|
||||
return;
|
||||
}
|
||||
|
||||
this.expendZero(block);
|
||||
}
|
||||
|
||||
getSiblings(block) {
|
||||
return directions.map(([dx, dy]) => {
|
||||
const x2 = block.x + dx;
|
||||
const y2 = block.y + dy;
|
||||
if (x2 < 0 || x2 >= this.width || y2 < 0 || y2 >= this.height)
|
||||
return undefined;
|
||||
return this.board[y2][x2];
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
showAllMines() {
|
||||
this.board.flat().forEach((i) => {
|
||||
if (i.mine)
|
||||
i.revealed = true;
|
||||
});
|
||||
}
|
||||
|
||||
checkGameState() {
|
||||
if (!this.state.value.mineGenerated)
|
||||
return;
|
||||
const blocks = this.board.flat();
|
||||
|
||||
if (blocks.every(block => block.revealed || block.flagged || block.mine)) {
|
||||
if (blocks.some(block => block.flagged && !block.mine))
|
||||
this.onGameOver('lost');
|
||||
else
|
||||
this.onGameOver('won');
|
||||
}
|
||||
}
|
||||
|
||||
autoExpand(block) {
|
||||
const siblings = this.getSiblings(block);
|
||||
const flags = siblings.reduce((a, b) => a + (b.flagged ? 1 : 0), 0);
|
||||
const notRevealed = siblings.reduce((a, b) => a + (!b.revealed && !b.flagged ? 1 : 0), 0);
|
||||
if (flags === block.adjacentMines) {
|
||||
siblings.forEach((i) => {
|
||||
if (i.revealed || i.flagged)
|
||||
return;
|
||||
i.revealed = true;
|
||||
this.expendZero(i);
|
||||
if (i.mine)
|
||||
this.onGameOver('lost');
|
||||
});
|
||||
}
|
||||
const missingFlags = block.adjacentMines - flags;
|
||||
if (notRevealed === missingFlags) {
|
||||
siblings.forEach((i) => {
|
||||
if (!i.revealed && !i.flagged)
|
||||
i.flagged = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onGameOver(status) {
|
||||
this.state.value.status = status;
|
||||
this.state.value.endMS = +Date.now();
|
||||
if (status === 'lost') {
|
||||
this.showAllMines();
|
||||
setTimeout(() => {
|
||||
alert('lost');
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
// import { defineConfig } from 'vitepress'
|
||||
import { withMermaid } from "vitepress-plugin-mermaid";
|
||||
import { withMermaid } from "vitepress-plugin-mermaid-xyxsw";
|
||||
import mathjax3 from 'markdown-it-mathjax3';
|
||||
import { main_sidebar, chapter2, chapter3, chapter4, chapter5, chapter6, chapter7, chapter8, chapter9 } from './sidebar.js';
|
||||
import { nav } from './nav.js';
|
||||
import PanguPlugin from 'markdown-it-pangu'
|
||||
import { createWriteStream } from 'node:fs'
|
||||
import { resolve } from 'node:path'
|
||||
import { SitemapStream } from 'sitemap'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
const links = []
|
||||
import VueMacros from 'unplugin-vue-macros/vite'
|
||||
|
||||
const customElements = [
|
||||
'mjx-container',
|
||||
@@ -107,6 +103,7 @@ export default withMermaid({
|
||||
title: "HDU-CS-WIKI",
|
||||
description: "HDU 计算机科学讲义",
|
||||
lastUpdated: true,
|
||||
cleanUrls: true,
|
||||
head: [['script', { async: "async", src: 'https://umami.hdu-cs.wiki/script.js', "data-website-id": "3f11687a-faae-463a-b863-6127a8c28301", "data-domains": "wiki.xyxsw.site,hdu-cs.wiki" }]],
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
@@ -146,6 +143,7 @@ export default withMermaid({
|
||||
externalLinkIcon: true,
|
||||
},
|
||||
markdown: {
|
||||
lineNumbers: true,
|
||||
config: (md) => {
|
||||
md.use(mathjax3);
|
||||
md.use(PanguPlugin);
|
||||
@@ -157,32 +155,21 @@ export default withMermaid({
|
||||
isCustomElement: (tag) => customElements.includes(tag),
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
transformHtml: (_, id, { pageData }) => {
|
||||
if (!/[\\/]404\.html$/.test(id))
|
||||
links.push({
|
||||
// you might need to change this if not using clean urls mode
|
||||
url: pageData.relativePath.replace(/((^|\/)index)?\.md$/, '$2'),
|
||||
lastmod: pageData.lastUpdated
|
||||
})
|
||||
},
|
||||
buildEnd: async ({ outDir }) => {
|
||||
const sitemap = new SitemapStream({
|
||||
hostname: 'https://hdu-cs.wiki/'
|
||||
})
|
||||
const writeStream = createWriteStream(resolve(outDir, 'sitemap.xml'))
|
||||
sitemap.pipe(writeStream)
|
||||
links.forEach((link) => sitemap.write(link))
|
||||
sitemap.end()
|
||||
await new Promise((r) => writeStream.on('finish', r))
|
||||
sitemap: {
|
||||
hostname: 'https://hdu-cs.wiki'
|
||||
},
|
||||
vite: {
|
||||
plugins: [
|
||||
VueMacros(),
|
||||
],
|
||||
resolve: {
|
||||
alias: [
|
||||
{
|
||||
find: /^.*\/VPSwitchAppearance\.vue$/,
|
||||
find: /^.*\/NotFound\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./components/CustomSwitchAppearance.vue', import.meta.url)
|
||||
new URL('./components/CustomNotFound.vue', import.meta.url)
|
||||
)
|
||||
}
|
||||
]
|
||||
|
||||
@@ -12,19 +12,20 @@ export function main_sidebar() {
|
||||
text: '1.杭电生存指南(最重要模块)',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '1.1人文社科的重要性(韩健夫老师寄语)', link: '/1.杭电生存指南/1.1人文社科的重要性(韩健夫老师寄语)' },
|
||||
{ text: '1.2竞赛指北', link: '/1.杭电生存指南/1.2竞赛指北' },
|
||||
{ text: '1.3导师选择', link: '/1.杭电生存指南/1.3导师选择' },
|
||||
{ text: '1.4小心项目陷阱', link: '/1.杭电生存指南/1.4小心项目陷阱' },
|
||||
{ text: '1.5小组作业避雷指南', link: '/1.杭电生存指南/1.5小组作业避雷指南' },
|
||||
{ text: '1.6正确解读GPA', link: '/1.杭电生存指南/1.6正确解读GPA' },
|
||||
{ text: '1.7杭电出国自救指南', link: '/1.杭电生存指南/1.7杭电出国自救指南' },
|
||||
{ text: '1.8转专业二三事', link: '/1.杭电生存指南/1.8转专业二三事' },
|
||||
{ text: '1.9问题专题:好想进入实验室', link: '/1.杭电生存指南/1.9问题专题:好想进入实验室' },
|
||||
{ text: '1.10如何读论文', link: '/1.杭电生存指南/1.10如何读论文' },
|
||||
{ text: '1.11陷入虚无主义?进来看看吧', link: '/1.杭电生存指南/1.11陷入虚无主义?进来看看吧' },
|
||||
{ text: '1.12选课原则与抢课技巧', link: '/1.杭电生存指南/1.12选课原则与抢课技巧' },
|
||||
{ text: '1.13数学学习篇', link: '/1.杭电生存指南/1.13数学学习篇'},
|
||||
{ text: '1.1 人文社科的重要性(韩健夫老师寄语)', link: '/1.杭电生存指南/1.1人文社科的重要性(韩健夫老师寄语)' },
|
||||
{ text: '1.2 竞赛指北', link: '/1.杭电生存指南/1.2竞赛指北' },
|
||||
{ text: '1.3 导师选择', link: '/1.杭电生存指南/1.3导师选择' },
|
||||
{ text: '1.4 小心项目陷阱', link: '/1.杭电生存指南/1.4小心项目陷阱' },
|
||||
{ text: '1.5 小组作业避雷指南', link: '/1.杭电生存指南/1.5小组作业避雷指南' },
|
||||
{ text: '1.6 正确解读GPA', link: '/1.杭电生存指南/1.6正确解读GPA' },
|
||||
{ text: '1.7 杭电出国自救指南', link: '/1.杭电生存指南/1.7杭电出国自救指南' },
|
||||
{ text: '1.8 转专业二三事', link: '/1.杭电生存指南/1.8转专业二三事' },
|
||||
{ text: '1.9 问题专题:好想进入实验室', link: '/1.杭电生存指南/1.9问题专题:好想进入实验室' },
|
||||
{ text: '1.10 如何读论文', link: '/1.杭电生存指南/1.10如何读论文' },
|
||||
{ text: '1.11 陷入虚无主义?进来看看吧', link: '/1.杭电生存指南/1.11陷入虚无主义?进来看看吧' },
|
||||
{ text: '1.12 选课原则与抢课技巧', link: '/1.杭电生存指南/1.12选课原则与抢课技巧' },
|
||||
{ text: '1.13 数学学习篇', link: '/1.杭电生存指南/1.13数学学习篇' },
|
||||
{ text: '1.14 杭电“失败”指南', link: '/1.杭电生存指南/1.14 杭电失败指南' },
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -227,11 +228,11 @@ export function chapter3() {
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '3.6.5CS61A食用指南',
|
||||
text: '3.6.5 CS61A 食用指南',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '3.6.5CS61A食用指南', link: '/3.编程思维体系构建/3.6.5CS61A食用指南' },
|
||||
{ text: '3.6.5.1CS61A Sec1', link: '/3.编程思维体系构建/3.6.5.1CS61A Sec1' },
|
||||
{ text: '3.6.5 CS61A 食用指南', link: '/3.编程思维体系构建/3.6.5CS61A食用指南' },
|
||||
{ text: '3.6.5.1 CS61A Sec1', link: '/3.编程思维体系构建/3.6.5.1CS61A Sec1' },
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -674,8 +675,15 @@ export function chapter9() {
|
||||
{ text: '9.1 计网速通', link: '/9.计算机网络/9.1计网速通' },
|
||||
{ text: '9.2.1 物理层' },
|
||||
{ text: '9.2.2 链路层' },
|
||||
{ text: '9.2.3 网络层' , link: '/9.计算机网络/9.2.3网络层'},
|
||||
{ text: '9.2.3.1 IP 协议', link: '/9.计算机网络/9.2.3.1IP协议' },
|
||||
{
|
||||
text: '9.2.3 网络层',
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: '9.2.3 网络层', link: '/9.计算机网络/9.2.3网络层' },
|
||||
{ text: '9.2.3.1 IP 协议', link: '/9.计算机网络/9.2.3.1IP协议' },
|
||||
{ text: '9.2.3.2 子网与无类域间路由', link: '/9.计算机网络/9.2.3.2子网与无类域间路由' }
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
70
.vitepress/theme/Layout.vue
Normal file
70
.vitepress/theme/Layout.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<script setup>
|
||||
import { useData } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import { nextTick, provide } from 'vue'
|
||||
|
||||
const { isDark } = useData()
|
||||
|
||||
const enableTransitions = () =>
|
||||
'startViewTransition' in document &&
|
||||
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
|
||||
|
||||
provide('toggle-appearance', async ({ clientX: x, clientY: y }) => {
|
||||
if (!enableTransitions()) {
|
||||
isDark.value = !isDark.value
|
||||
return
|
||||
}
|
||||
|
||||
const clipPath = [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${Math.hypot(
|
||||
Math.max(x, innerWidth - x),
|
||||
Math.max(y, innerHeight - y)
|
||||
)}px at ${x}px ${y}px)`
|
||||
]
|
||||
|
||||
await document.startViewTransition(async () => {
|
||||
isDark.value = !isDark.value
|
||||
await nextTick()
|
||||
}).ready
|
||||
|
||||
document.documentElement.animate(
|
||||
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
|
||||
{
|
||||
duration: 300,
|
||||
easing: 'ease-in',
|
||||
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
|
||||
}
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultTheme.Layout />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
.dark::view-transition-new(root) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
::view-transition-new(root),
|
||||
.dark::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.VPSwitchAppearance {
|
||||
width: 22px !important;
|
||||
}
|
||||
|
||||
.VPSwitchAppearance .check {
|
||||
transform: none !important;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h, watch } from 'vue'
|
||||
import { inject } from '@vercel/analytics';
|
||||
// import Theme from 'vitepress/theme'
|
||||
import DefaultTheme from 'vitepress/theme-without-fonts'
|
||||
import Layout from './Layout.vue'
|
||||
import Download from '../../components/Download.vue'
|
||||
import Bilibili from '../../components/Bilibili.vue'
|
||||
import Parallax from '../../components/Parallax.vue'
|
||||
@@ -11,14 +11,9 @@ import './rainbow.css'
|
||||
|
||||
let homePageStyle = undefined
|
||||
|
||||
inject()
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
})
|
||||
},
|
||||
Layout: Layout,
|
||||
enhanceApp(ctx) {
|
||||
DefaultTheme.enhanceApp(ctx)
|
||||
ctx.app.component('Download', Download)
|
||||
@@ -29,7 +24,7 @@ export default {
|
||||
|
||||
watch(
|
||||
() => ctx.router.route.data.relativePath,
|
||||
() => updateHomePageStyle(location.pathname === '/' || location.pathname === '/contributors.html'),
|
||||
() => updateHomePageStyle(location.pathname === '/' || location.pathname === '/contributors'),
|
||||
{ immediate: true },
|
||||
)
|
||||
},
|
||||
|
||||
@@ -18,17 +18,31 @@
|
||||
}
|
||||
|
||||
.dark .vp-doc a,#loading, .dark .vp-doc a>code, .dark .VPNavBarMenuLink.VPNavBarMenuLink:hover, .dark .VPNavBarMenuLink.VPNavBarMenuLink.active, .dark .link.link:hover, .dark .link.link.active, .dark .edit-link-button.edit-link-button, .dark .pager-link .title {
|
||||
color: var(--vp-c-brand-lighter);
|
||||
color: var(--vp-c-brand-3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个版本可能有 bug,build 后字体变不了还为 consolas 可能是我自己配的有问题 可能是这个版本拉了 总之就加个临时的
|
||||
* -------------------------------------------------------------------------- */
|
||||
code {
|
||||
font-family: 'Noto Sans Mono', sans-serif, monospace, consolas !important;
|
||||
font-weight: 400 !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--vp-c-brand: #0dadc4;
|
||||
--vp-c-brand-light: #1A9CED;
|
||||
--vp-c-brand-lighter: #1CA8FF;
|
||||
--vp-c-brand-lightest: #5EB2E6;
|
||||
--vp-c-brand-dark: #0B52BF;
|
||||
--vp-c-brand-darker: #015F8F;
|
||||
--vp-c-brand-dimm: rgba(100, 108, 255, 0.08);
|
||||
--vp-c-brand-1: #0dadc4;
|
||||
--vp-c-brand-2: #1A9CED;
|
||||
--vp-c-brand-3: #1CA8FF;
|
||||
--vp-c-brand-soft: #5EB2E6;
|
||||
--vp-code-copy-copied-text-content: '复制成功';
|
||||
--vp-code-color: rgba(60, 60, 67);
|
||||
--vp-custom-block-tip-bg: rgba(100, 108, 255, 0.16) !important;
|
||||
--vp-badge-tip-bg: rgba(100, 108, 255, 0.16) !important;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-code-color: rgba(255, 255, 245, 0.86);
|
||||
}
|
||||
|
||||
:root {
|
||||
@@ -41,15 +55,15 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand);
|
||||
--vp-button-brand-hover-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-light);
|
||||
--vp-button-brand-active-border: var(--vp-c-brand-light);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-button-brand-bg);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,15 +105,10 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand);
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-darker);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-custom-block-tip-border: var(--vp-c-brand);
|
||||
--vp-custom-block-tip-text: var(--vp-c-brand-lightest);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-dimm);
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +116,7 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand) !important;
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
mjx-container {
|
||||
@@ -126,7 +135,6 @@ mjx-container {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
@@ -196,3 +204,11 @@ mjx-container {
|
||||
-webkit-user-drag: none !important;
|
||||
-moz-user-drag: none !important;
|
||||
}
|
||||
|
||||
.custom-block.tip code {
|
||||
background-color: rgba(100, 108, 255, 0.14);
|
||||
}
|
||||
|
||||
mjx-container > svg {
|
||||
display: inline-block;
|
||||
}
|
||||
183
1.杭电生存指南/1.14 杭电失败指南.md
Normal file
183
1.杭电生存指南/1.14 杭电失败指南.md
Normal file
@@ -0,0 +1,183 @@
|
||||
|
||||
# 杭电“失败”指南
|
||||
|
||||
## 前言
|
||||
|
||||
> 淋过雨的人,也想为别人打伞
|
||||
|
||||
毕业很久了,但是回望我的大学生涯,充满了很多遗憾,吃过很多苦头,收获了很多教训,我偶尔会回想,如果当初我如何如何,现在会不会不那么“失败”一些。于是,在某个晚上,有了这篇指南。
|
||||
|
||||
谢谢学弟学妹能够抽出时间看这篇“失败”指南,这篇指南以个人角度讲述了一个 CS 学生在上大学的时候收获到的一些经验和教训,或许没那么适用,但是也许可以让学弟学妹的大学生活不会像我一样“失败”。
|
||||
|
||||
这篇文章尽可能少的讲故事,更多是实用主义的教训(杂谈篇除外)(里面有太多偏激的观点,说教色彩也很浓厚,仅供参考)。
|
||||
|
||||
如果这篇所谓的狗屁指南让你觉得
|
||||
|
||||
> 听过很多道理,依然过不好这一生
|
||||
|
||||
没关系,勇敢地去做自己热爱的事情就好。
|
||||
|
||||
## 杂谈篇
|
||||
|
||||
> 如果你是一个对 CS 充满热爱,怀抱一颗技术宅拯救世界的心,那你可以跳过此篇。(其他章节可以读读 (#^.^#))
|
||||
> 如果你像我一样,是一个明明从小热爱技术,但是有时候有些功利的矛盾人,又或者是大多数对大学充满迷茫,不知道该怎么办的人,或许可以继续看下去。
|
||||
|
||||
### 高考败犬:脱颖而出的心态修炼
|
||||
|
||||
我所在的学院每年都有或多或少一些学生,觉得自己是高考考差才来这里的,刚进来校的我也是这样。这种心态我非常能理解,但是问题是进来了以后,怎么在这所学校脱颖而出?(PS:前提是你想脱颖而出)
|
||||
|
||||
1. 保持这份不甘心。太多同学在刚进大学的时候踌躇满志,最后四年以后可能也是泯然众人。当初的一份志气也被消磨在宿舍、游戏和无意义的社交中。为了让自己能够脱颖而出,首先要做的就是提醒自己,保持不甘心,保持自己的志气,不要甘心自己已经有的一切,永远觉得自己可以更强,更厉害。
|
||||
|
||||
2. 充满野心,渴望成为这个学校最顶尖的那批人。老实说,杭电是一所双非,在未来会对你(大部分人)在很多地方都有些隐性限制,但是,每一所大学最顶尖的那批人,都能够冲破这些束缚。直白些说,即使它是一所双非,但是它在很多领域都有超越一些 211 和 985 的资源和实力。很幸运,CS 领域就是其中之一。不用怀疑,它能够提供你所想要的一切。但是资源是有限的,所以如果你想成为很厉害很厉害的人,那么就一定要尽可能去抓住它能够给你的一切资源。
|
||||
|
||||
### 攫取者:永远向厉害的人去学习
|
||||
|
||||
那么,杭电究竟能够给大家什么资源呢?浓厚的竞赛氛围?浓厚的 CS 氛围?我个人觉得不全是。杭电之所以成为 CS 强校,是因为上述氛围吸引和聚拢了一堆充满理想,热爱技术的人儿。或许你可能只是一名还在迷茫期的新生,但是没关系,去主动靠近和接触他们,永远向比你厉害的人学习,不断提高自己的技能和认知水平。特别是认知。
|
||||
|
||||
之所以强调这一点,是因为技能是可以自己通过时间去磨炼的,但是认知的话,如果你根本不知道有"A",怎么可能去主动学习"A"呢?如果你不知道厉害的人他们平时在干什么,他们在学什么,在打什么比赛,研究什么先进或有趣的东西,你怎么可能去学习这些呢?我觉得进杭电的同学,其实"天赋"和"聪明程度"都差不多,关键在于信息差和执行力。(PS:hdu-cs-wiki 就是一种缩小信息差的工具)
|
||||
|
||||
那么,怎么才能认识到这么多厉害的人,以及向他们学习呢?说白了,就是混进大佬的圈子。作为新生,一个打开局面的方法是,先选择一个方向去钻研学习,结识这个方向的大佬,成为这个方向的大佬,然后认识其他方向的大佬。
|
||||
|
||||
问题就转化成,我该选择哪个方向成为我的起点。
|
||||
|
||||
### 迷茫中的选择:科研/竞赛/开源/学业/技术/工作
|
||||
|
||||
> 犹豫就会败北,世界不是非此即彼,热爱才抵得过岁月漫长
|
||||
|
||||
作为新生,我曾一度非常非常非常迷茫,当时最大的问题是,该选择哪个方向发展,是 CTF?还是 ACM?还是刷绩点争取保研?还是专注我热爱的技术,做有影响力的开源?
|
||||
|
||||
我犯过最大的教训有三个,一个是功利主义地拒绝了自己最热爱的方向,二是总在犹豫和后悔自己的选择,三是总觉得不同方向只能选择一个,无法兼顾。
|
||||
|
||||
TBD...
|
||||
|
||||
## 科研篇
|
||||
|
||||
### 选择障碍:怎么选择研究方向
|
||||
|
||||
TBD...
|
||||
|
||||
### 从 0 入门 AI:实践和理论的权衡
|
||||
|
||||
TBD...
|
||||
|
||||
### 论文相关:从阅读一篇论文说起
|
||||
|
||||
TBD...
|
||||
|
||||
### 论文相关:头疼的文献管理
|
||||
|
||||
TBD...
|
||||
|
||||
### 论文相关:好记性不如烂笔头
|
||||
|
||||
TBD...
|
||||
|
||||
### 经验谈:炼丹师的能力修炼
|
||||
|
||||
TBD...
|
||||
|
||||
### 经验谈:合作交流及信息渠道
|
||||
|
||||
TBD...
|
||||
|
||||
### 实践谈:提出 idea 到投稿的经验教训
|
||||
|
||||
TBD...
|
||||
|
||||
## 竞赛篇
|
||||
|
||||
### 四大竞赛对个人成长的收益分析
|
||||
|
||||
TBD...
|
||||
|
||||
## 开源和个人影响力
|
||||
|
||||
### 为什么要培养个人影响力
|
||||
|
||||
TBD...
|
||||
|
||||
### 如何做人工智能方向的开源项目
|
||||
|
||||
TBD...
|
||||
|
||||
## 技术篇
|
||||
|
||||
### 后悔药:如何选择技术发展方向
|
||||
|
||||
TBD...
|
||||
|
||||
## 学业篇
|
||||
|
||||
### 如何兼顾学业绩点和专业技能培养
|
||||
|
||||
TBD...
|
||||
|
||||
### 过时的个人谬论:如何聪明且高效地刷绩点
|
||||
|
||||
TBD...
|
||||
|
||||
### 2023 年以后读研的必要性如何
|
||||
|
||||
TBD...
|
||||
|
||||
### 保研经验分享
|
||||
|
||||
TBD...
|
||||
|
||||
## 找工篇
|
||||
|
||||
### 后悔药:如何“面向找大厂工作”读大学
|
||||
|
||||
TBD...
|
||||
|
||||
### 经验篇:高效准备基础知识和笔试考核
|
||||
|
||||
TBD...
|
||||
|
||||
### 简历篇:如何做一份或许“优秀”的简历
|
||||
|
||||
TBD...
|
||||
|
||||
### 经验谈:非技术因素在面试中的影响
|
||||
|
||||
之所以选择这个话题,一方面是因为这个东西很容易被大家忽略,另一方面这个也比较普适,无论做开发还是算法都可以参考。
|
||||
|
||||
#### 面试前 - 个人介绍
|
||||
|
||||
在面试前应该准备好自己的个人介绍,除了老生常态的基本信息外,我觉得需要做到既要突出自己的亮点,但是别太过,同时也要根据不同的面试去修改。
|
||||
|
||||
先给大家看一个例子:
|
||||
|
||||
您好,我叫 xxx,是 xx 大学 xx 学院 xx 级学生。自从进入大学以来,我【省略一些内容】。在 xxx 地方实习期间做了 xxx 工作。在平时,xxxxx(主要讲自己的开源社区贡献、技术博客撰写、竞赛等)
|
||||
|
||||
**第一就是要尽量介绍地比较“多面”**,不但要说自己在学校和实习中做了什么,还要说一些个人影响力的东西,比如开源社区贡献这一块。其实就是突出一些自己相对于其他候选人的亮点,相比于其他大部分人来说,你的特别在哪。
|
||||
|
||||
**第二就是要建立“投缘”机会**,在面试前,搜集到这场面试的部门和小组是做什么,甚至面试官是谁。【省略一些例子】。我想强调的关键是在自我介绍中,**突出自己和这个部门,这个组的 connection,寻找尽可能多的共同点和缘分点**。
|
||||
|
||||
#### 面试中 - 项目准备
|
||||
|
||||
在面试前,还应该准备好自己的项目梳理笔记。实际上,在面试过程中,如果没有提前梳理好自己的项目的逻辑的话,很难在面试现场讲清楚以及让面试官及时 get 到你想表达的点。
|
||||
|
||||
一般而言,对于在校期间做的一些项目或者开源项目自己都能比较好地梳理清楚,但是比较难的是在实习过程中,特别是一份复杂的业务实习中梳理自己的产出。我个人认为**面试官比较关注以下几个方面**:一就是业务背景,也就是咱们的业务是什么,大概的架构,发展到哪种阶段,目前的瓶颈等。二就是本人负责的模块是什么,创新点是什么,以及特别重要的是:”我为什么要做这件事!“,三就是这个模块最后的业务收益是多少,怎么去评判收益。可以参考这种逻辑去梳理。
|
||||
|
||||
#### 面试前 - 好记性不如烂笔头
|
||||
|
||||
上文主要讲了个人介绍和项目梳理,我建议大家把个人介绍和项目梳理都放在一个 note 里面,每次面试前半个小时到一个小时回顾下自己的 note,以及根据面试岗位和小组来修改下。这样的好处在于每次都回顾下自己的项目和介绍,面试过程中有参考,不会慌。等面试次数多了,你就会发现,其实**每场面试都是在重复介绍你 note 里面的内容**。
|
||||
|
||||
#### 面试中 - 换位思考很重要
|
||||
|
||||
**面试过程中的一大禁忌就是不考虑面试官的感受,一股脑介绍自己的项目**。一方面,面试官无法在短时间内吸收到你想表达的内容,另一方面,你一直噼里啪啦输出面试官听不懂的东西,面试官也会特别烦躁。
|
||||
|
||||
一个比较好的方式是:每次介绍完一段时,就停下来,反问面试官:您看您对这个项目的 xx 有什么疑问吗?比如说你介绍完项目背景后,就记得停下来,反问下,您看你对这个项目背景有什么疑问吗。
|
||||
|
||||
除了每次介绍完一段就停顿时,还有一个需要注意的点在于,不要梳理完项目以后,就完全按照项目梳理的内容去念,要注意观察面试官的反应,如果感觉他其实并不怎么感兴趣,就想办法赶紧结束。
|
||||
|
||||
#### 面试中 - 反问阶段
|
||||
|
||||
反问阶段是一个及时获取信息的好机会。不同面试阶段有不同的提问。一面基本是你的同事或者你的 mentor,这个时候可以专注问一些工作内容,工作氛围等比较实际的话题。对于三面来说,一般是大老板,这个时候你要抓住机会和他交流下一些比较 high-level 的东西,因为以后很少有机会去和这样 senior 的人交流。话题诸如:职业发展路径、发展前景、绩效考核、我会被分配到什么具体组。
|
||||
|
||||
如果在每场面试的时候,你想知道自己大概的表现,你可以反问阶段,非常非常非常委婉地提问:您看我这次面试还有什么可以提升的地方吗?
|
||||
|
||||
## 结语
|
||||
|
||||
TBD...
|
||||
@@ -4,20 +4,20 @@
|
||||
|
||||
大学的真意是综合而全面的培养一个人。为什么大学有这样的职责?因为这是人最后的受教育阶段(研究生、博士生也是在大学里接受教育)。从接受教育开始到走向社会为止,这期间是人,变成一个人最为宝贵的阶段。
|
||||
|
||||
<strong>大学是唯一可以与来自五湖四海、不同学科背景的人共处一个校园的时期,这里就应该是开放而需要激烈思想碰撞的地方</strong>。大学是专业的,但同时更是广博的,我们在黄金的年龄来到这里,就不要轻易的错过这样疯狂吸纳不同看法的机会。同样,大学是未来所有可能性的孵化器,请保持一颗尝试新事物的心,索取大学所能给予的任何一切,并让它成为提升自我的工具。
|
||||
**大学是唯一可以与来自五湖四海、不同学科背景的人共处一个校园的时期,这里就应该是开放而需要激烈思想碰撞的地方**。大学是专业的,但同时更是广博的,我们在黄金的年龄来到这里,就不要轻易的错过这样疯狂吸纳不同看法的机会。同样,大学是未来所有可能性的孵化器,请保持一颗尝试新事物的心,索取大学所能给予的任何一切,并让它成为提升自我的工具。
|
||||
|
||||
工具,我并不避讳这个词。只不过我并不想将这个词与人画等号。人非工具,而是使用工具的。我们的祖先经过数百万年的努力和尝试,才学会使用并创造工具,目的就是不再沦为工具,我们站在 21 世纪也不应该心甘情愿的将自己锻造成一个工具,这是返祖与退化的表现。
|
||||
|
||||
21 世纪是人类对高科技和超级想象力、创造力最渴求的百年。一个想法和主意胜过千万个重复的机械化劳动,虽然后者也能带来一定程度的好处,但这并不我们的目标。大学是否能迸发出惊人的创造力,在于学生和教师的思想与行为。而不同的学科交叉和碰撞会让创造力和科技的创新更大概率出现。
|
||||
|
||||
听说乔布斯有对禅宗修习的经历,而他的工作可能并不与禅宗搭边。但他的成功却不能不说与他广泛的涉猎有关。工具是没有办法创造工具的,只有人才能创造。<strong>我们大学生应该去思考在大学四年如何想办法把自己锻造成真正的人。</strong>
|
||||
听说乔布斯有对禅宗修习的经历,而他的工作可能并不与禅宗搭边。但他的成功却不能不说与他广泛的涉猎有关。工具是没有办法创造工具的,只有人才能创造。**我们大学生应该去思考在大学四年如何想办法把自己锻造成真正的人。**
|
||||
|
||||
这个人不是做题的机器,不是刷题的工具,不是眼中只有考高分的单一生物,当然更不可能是成天浑浑噩噩、行尸走肉的颓丧者、躺平者。说到这里,可能会讲,难道不躺平去卷吗?并不是,我反对 “卷”,<strong>因为 “卷” 的含义是无意义的恶性竞争和没有实际突破的简单重复劳动,边际效益递减的不划算却无奈的选择。</strong>“卷” 不等于 “勤奋” 、 “创造” 和 “想要变得更强” ,将之画等号我认为是当前观念中一个很大的误区。<strong>我们需要大学生有自己的思考和创造力,需要大学生是一个综合全面发展的人。</strong>只有这样大学才有活力、希望,社会才有动力,国家才有未来。
|
||||
这个人不是做题的机器,不是刷题的工具,不是眼中只有考高分的单一生物,当然更不可能是成天浑浑噩噩、行尸走肉的颓丧者、躺平者。说到这里,可能会讲,难道不躺平去卷吗?并不是,我反对 “卷”,**因为 “卷” 的含义是无意义的恶性竞争和没有实际突破的简单重复劳动,边际效益递减的不划算却无奈的选择。**“卷” 不等于 “勤奋” 、 “创造” 和 “想要变得更强” ,将之画等号我认为是当前观念中一个很大的误区。**我们需要大学生有自己的思考和创造力,需要大学生是一个综合全面发展的人。**只有这样大学才有活力、希望,社会才有动力,国家才有未来。
|
||||
|
||||
我知道大学可能并不如你们想象的那般美好,有不负责的、划水上课的老师以及你看不惯的、瞧不起的种种行为,但这并不是自己选择放弃和抱怨的理由。因为,我相信大学还有认真负责的老师、有思想的学者以及有想法的同学,我们需要和自己钦佩、三观相符的人多接触,让自己成为你佩服的人的样子,而非因为种种不好的现象而选择全盘的否定、甚至自暴自弃。
|
||||
|
||||
<strong> 要有自己的目标,年轻是实现自己目标最好的资本。</strong>为了这个目标,在大学里徜徉、像海绵一样吸纳如水般的知识和见识。博采众长而非简单功利。可能你今天听了不重要的课(相对于专业课),但只要是值得听的老师讲的有涵养的课,你就不算枉费时光。因为这样的经历并不会让你浪费时间,而会在未来的某刻显示出它的价值。
|
||||
** 要有自己的目标,年轻是实现自己目标最好的资本。**为了这个目标,在大学里徜徉、像海绵一样吸纳如水般的知识和见识。博采众长而非简单功利。可能你今天听了不重要的课(相对于专业课),但只要是值得听的老师讲的有涵养的课,你就不算枉费时光。因为这样的经历并不会让你浪费时间,而会在未来的某刻显示出它的价值。
|
||||
|
||||
不要让自己的大脑空空,刷抖音、看游戏直播或者看无聊的视频或许是娱乐的方式,但这些是你可以在余生每天都可做的最没有技术含量的事,而且这些并不会让你大脑填入真正有价值的东西。<strong>去做只有大学时光才能做的事吧!</strong>丰富自己、创造自己、提升自己。多看一页经典的著作,这是人类上百年乃至上千年思想的精华。多认真的听一节有价值的课吧,这是老师积几十年的学识认真准备的盛宴。
|
||||
不要让自己的大脑空空,刷抖音、看游戏直播或者看无聊的视频或许是娱乐的方式,但这些是你可以在余生每天都可做的最没有技术含量的事,而且这些并不会让你大脑填入真正有价值的东西。**去做只有大学时光才能做的事吧!**丰富自己、创造自己、提升自己。多看一页经典的著作,这是人类上百年乃至上千年思想的精华。多认真的听一节有价值的课吧,这是老师积几十年的学识认真准备的盛宴。
|
||||
|
||||
可能你会说,大学没有好的课程,老师都在划水。如果你有这样的看法,我想说,可能是你了解的太少所致,多去了解一下一定有让你满意的老师。另外,为什么大学会出现这样不好的课程呢?因为老师不愿意好好上课。为什么老师不愿意好好教学呢?因为在功利化的指挥棒下,好好上课是性价比最低的行为。如此,你就知道大学只知道功利化、工具化的接受教育,而不培养人文素养的恶果了吧!
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
好处是本身就是落地项目,在论证项目可行性时具有很强的说服力,这类项目也相对来说会容易获奖。对于想要做实际落地项目的技术人员来说是不错的锻炼机会。
|
||||
|
||||
坏处是甲方<strong>可能</strong>提出大量需求,技术人员需付出大量精力跟进,然而因为项目本身参与比赛而无法收取原定的外包费用,技术人员成为白菜大学生被白嫖(当然这个潜在可能性带来的坏处也因人而异,如果不在乎频繁改动需求 / 愿意做没有外包费用的无偿奉献,只想锻炼自己,那就无所谓)。
|
||||
坏处是甲方**可能**提出大量需求,技术人员需付出大量精力跟进,然而因为项目本身参与比赛而无法收取原定的外包费用,技术人员成为白菜大学生被白嫖(当然这个潜在可能性带来的坏处也因人而异,如果不在乎频繁改动需求 / 愿意做没有外包费用的无偿奉献,只想锻炼自己,那就无所谓)。
|
||||
|
||||
- 自立初创项目
|
||||
|
||||
@@ -81,7 +81,7 @@ CTF还有的缺点也是目前热门领域的通病:发展速度过快,后
|
||||
|
||||
更多可以参考计算安全章节
|
||||
|
||||
[传送门](/6.%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8/6.%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8.html)
|
||||
[传送门](/6.%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8/6.%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%AE%89%E5%85%A8)
|
||||
|
||||
## 数据科学竞赛
|
||||
|
||||
|
||||
@@ -10,11 +10,11 @@ GPA,一个酷似成绩的东西,
|
||||
|
||||
毕竟在高中,一份理想的成绩意味着一个光明的未来。
|
||||
|
||||
但是我想说,<strong>在大学,请摆脱高中的绩点惯性</strong>。
|
||||
但是我想说,**在大学,请摆脱高中的绩点惯性**。
|
||||
|
||||
在大学,绩点其实只是一个获得资源的工具,高的绩点并不代表光明的未来,也不能够代表高超的学习能力和扎实的专业知识。同时我们要明白,与高中截然不同的是,大学是一个多目标优化问题,绩点只是优化问题的一个维度,而非所有。
|
||||
|
||||
> <strong>author:晓宇</strong>
|
||||
> **author:晓宇**
|
||||
|
||||
所以本文会讲述 GPA 能够带来什么,什么是一个合理的对待 GPA 的态度,如果需要,如何获得一个较高的 GPA 。
|
||||
|
||||
@@ -90,7 +90,7 @@ B. 大一进来从来不太在意 GPA,大部分经历在研究 Github 项目
|
||||
|
||||
当你已经成为大学生的时候,GPA(成绩)不是和一个人的优秀的呈正相关的,这不过是一个通往一些资源的途径,同样的,也有很多资源可以不用 GPA,可以靠个人硬实力去争取,例如实习,例如科研助理云云。只是说,如果你不知道干啥,先认真学好专业课知识,是一个保有收益的事情,但当有其他选择的时候可以权衡,但也不可走另外一个极端,则完全不在乎 GPA,在职业规划并不清晰的时候不要自废道路。
|
||||
|
||||
最后想说的是,一个获得资源的玩具罢了,不值得让你<strong>常沉苦海,永失真道</strong>。
|
||||
最后想说的是,一个获得资源的玩具罢了,不值得让你**常沉苦海,永失真道**。
|
||||
|
||||
## GPA 补充内容:
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
相信转专业的人里,不少是慕 HDU 的强势理工科热门专业来的。但是在转专业之前,你需要先确认以下几点。
|
||||
|
||||
1. 你是<strong>跟风</strong>听你家七大姑八大姨说这个专业好,才想转来这个专业的吗?
|
||||
2. 你确定你<strong>所向往的</strong>和这个专业所学的<strong>相关</strong>吗?有没有比这个专业更贴切的选择?
|
||||
3. 你确定你能良好适应(<strong>至少不反感</strong>)这个专业所学的专业课吗?
|
||||
1. 你是**跟风**听你家七大姑八大姨说这个专业好,才想转来这个专业的吗?
|
||||
2. 你确定你**所向往的**和这个专业所学的**相关**吗?有没有比这个专业更贴切的选择?
|
||||
3. 你确定你能良好适应(**至少不反感**)这个专业所学的专业课吗?
|
||||
|
||||
以上的问题,如果有觉得不确定的,也没关系,你至少有一学期在学校里探索。可以是问该学院的学长学姐的感受,可以是进研究相关专业知识的社团学习,也可以是自己研究。总之,不能转专业转得迷迷糊糊,因为虽然 HDU 转专业机制比较友好,<strong>但转专业机会只有一次</strong>,如果你转进去之后发现自己完全不适应,那也只能自认倒霉。
|
||||
以上的问题,如果有觉得不确定的,也没关系,你至少有一学期在学校里探索。可以是问该学院的学长学姐的感受,可以是进研究相关专业知识的社团学习,也可以是自己研究。总之,不能转专业转得迷迷糊糊,因为虽然 HDU 转专业机制比较友好,**但转专业机会只有一次**,如果你转进去之后发现自己完全不适应,那也只能自认倒霉。
|
||||
|
||||
像笔者本人最初其实想做游戏开发,本身也热爱绘画之类的艺术。当年只觉得游戏开发和计算机有关,一门心思转计算机,后来转成功之后才发现其实数媒的培养计划更贴合笔者想学的,而现在所在的计科学得更多的是计算机理论方面的知识,也跟游戏开发不搭边。
|
||||
|
||||
@@ -29,16 +29,16 @@
|
||||
|
||||
:::
|
||||
|
||||
转专业填报截止时间一般是在期末考试结束前后,届时<strong>最多填两个</strong>意向专业。
|
||||
转专业填报截止时间一般是在期末考试结束前后,届时**最多填两个**意向专业。
|
||||
|
||||
转专业公示时间一般在开学前后。在转专业结果公示一周内,<strong>可以申请放弃转专业</strong>,此时视作已用过一次转专业机会,<strong>不可再次转专业</strong>。所以如果是非某个专业不去,建议只填报一个意向专业,以防被第二个专业录取,失去转专业资格。
|
||||
转专业公示时间一般在开学前后。在转专业结果公示一周内,**可以申请放弃转专业**,此时视作已用过一次转专业机会,**不可再次转专业**。所以如果是非某个专业不去,建议只填报一个意向专业,以防被第二个专业录取,失去转专业资格。
|
||||
|
||||
## 转专业后的选课
|
||||
|
||||
在转专业公示结束后,转专业才正式生效。在公示期期间,如果想提前选课,需要走特殊退改选;也可以等转专业生效后再正常选课。
|
||||
|
||||
需要注意的是,<strong>你必须退掉原专业所有的课程</strong>。如果在选课结束后,你仍有未退的原专业课程,你必须含泪又交学费又上课,说不好还要考这个课的试。其中要特别注意原专业的短学期实践课,这个课程比较容易被忽视。
|
||||
需要注意的是,**你必须退掉原专业所有的课程**。如果在选课结束后,你仍有未退的原专业课程,你必须含泪又交学费又上课,说不好还要考这个课的试。其中要特别注意原专业的短学期实践课,这个课程比较容易被忽视。
|
||||
|
||||
在排课时,请找到你转入专业的培养计划,根据培养计划的<strong>课程号</strong>进行选课(而不是课程名)。HDU 有些课程同名不同号,如果选择了同名不同号的课程,只能寻求课程替代(而且有概率不成功)。这种情况常见于你发现你原来专业的课程和转入专业的某课程名字一样,你懒得换了,殊不知这两个课程完全不是一个课程号,喜变纯纯冤种。
|
||||
在排课时,请找到你转入专业的培养计划,根据培养计划的**课程号**进行选课(而不是课程名)。HDU 有些课程同名不同号,如果选择了同名不同号的课程,只能寻求课程替代(而且有概率不成功)。这种情况常见于你发现你原来专业的课程和转入专业的某课程名字一样,你懒得换了,殊不知这两个课程完全不是一个课程号,喜变纯纯冤种。
|
||||
|
||||
另外,建议根据开课班级排课。有些课程虽然课程号相同,但可能开给不同学院。比如计算机学院的课程经常和卓越或计算机二专业的一些课程同号,如果你选了其他专业的开课班级的班,期末考试可能会不一样(如计科和卓越的电路与电子学课程号同号,但在 20 级是分开期末考试的)。建议看清开课班级再选课。
|
||||
|
||||
@@ -10,15 +10,15 @@
|
||||
|
||||
概括一下:摒弃伸手,被动思维,能够主动争取各种机会,带着一个能够独当一面的能力进入实验室,与老师能够形成合作关系。
|
||||
|
||||
<strong>能力上:</strong>目前计算机上的科研主要在两大方向,第一大方向是 AI(关于这件事情是不是畸形要后期在讨论)第二大方向是系统数据库,
|
||||
**能力上:**目前计算机上的科研主要在两大方向,第一大方向是 AI(关于这件事情是不是畸形要后期在讨论)第二大方向是系统数据库,
|
||||
|
||||
对于数据库与系统,我目前还没有涉及
|
||||
|
||||
<strong>领域的基础能力:</strong>这里要我力推我们的课题教程,有较好的 AI 路线图,尽管 AI 有非常多子方向,如 NLP,CV,多模态,CV 下面又有检测 语义分割,NLP 又有推荐系统等云云,但是无论如何他们的源头都是 MLP,MLP 的源头都是多维线性回归,都是基于统计学习理论体系下,都是使用 Pytorch,都要学习数学理论体系,所以是可以要有一套类似的培养体系的。视觉被推荐 cs231n NLP 被推荐 CS224n。
|
||||
**领域的基础能力:**这里要我力推我们的课题教程,有较好的 AI 路线图,尽管 AI 有非常多子方向,如 NLP,CV,多模态,CV 下面又有检测 语义分割,NLP 又有推荐系统等云云,但是无论如何他们的源头都是 MLP,MLP 的源头都是多维线性回归,都是基于统计学习理论体系下,都是使用 Pytorch,都要学习数学理论体系,所以是可以要有一套类似的培养体系的。视觉被推荐 cs231n NLP 被推荐 CS224n。
|
||||
|
||||
论文的阅读能力:在了解一个领域的时候,最开始会发现什么词都不懂,一篇文章无论是从语言上还是英文上都无法阅读,所以能否具备快速阅读并读懂一篇文章的能力也是非常重要的。
|
||||
|
||||
<strong>如何寻找老师上:</strong>
|
||||
**如何寻找老师上:**
|
||||
|
||||
学校内和学校外都可以寻求合作,在学校内,
|
||||
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
|
||||
然后你要慢慢询问他的问题,接着你要问各种问题各种检查然后去看,如果有十个人一百个人都这么问,你肯定会受不了的吧。
|
||||
|
||||
事实上, 如果希望能提高得到回答的概率, 提问者应该学会如何更好地提问. 换句话说, 提问者应该去积极思考 "我可以主动做些什么来让对方更方便地帮助我诊断问题".
|
||||
事实上,如果希望能提高得到回答的概率,提问者应该学会如何更好地提问。换句话说,提问者应该去积极思考 "我可以主动做些什么来让对方更方便地帮助我诊断问题".
|
||||
|
||||
如果你的提问方式非常不专业, 很可能没有人愿意关注你的问题, 因为这不仅让人觉得你随便提的问题没那么重要, 而且大家也不愿意花费大量的时间向你来回地咨询.
|
||||
如果你的提问方式非常不专业,很可能没有人愿意关注你的问题,因为这不仅让人觉得你随便提的问题没那么重要,而且大家也不愿意花费大量的时间向你来回地咨询。
|
||||
|
||||
<Bilibili bvid='BV1om4y1H71S'/>
|
||||
|
||||
## 解决
|
||||
|
||||
@@ -30,11 +32,37 @@
|
||||
|
||||

|
||||
|
||||
欢迎大家阅读
|
||||
### 关于截图
|
||||
|
||||
[Stop-Ask-Questions-The-Stupid-Ways/README.md at master · tangx/Stop-Ask-Questions-The-Stupid-Ways](https://github.com/tangx/Stop-Ask-Questions-The-Stupid-Ways/blob/master/README.md) 别像弱智一样提问
|
||||
如果你在问问题的时候掏出一张手机拍电脑🤳🖥️的图片,那么大概率会让想要帮助你解决问题的学长血压升高然后放弃回答你的问题。
|
||||
|
||||
[https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) 提问的智慧
|
||||
除非遇到一些特殊情况(例如你的电脑进 BIOS 了),只能手机拍照,也请保证图片清晰便于识别。
|
||||
|
||||

|
||||
|
||||
在 wiki 的[2.2 优雅的使用工具](2.2优雅的使用工具.md),有推荐一些好用开源的截图工具
|
||||
|
||||
不过一般情况来说,Windows 的组合键`Win+Shift+S`截屏,在任意窗口按下这个组合键便可进入截屏模式,按住鼠标左键拖动框选区域即可截屏,使用`Ctrl+V`操作将截屏内容粘贴到想要保存的地方;如果已经登录了 QQ,那么 QQ 的截图快捷键默认是`Ctrl+Alt+A`,同样使用`Ctrl+V`粘贴截屏内容。
|
||||
|
||||
ps:QQ 有长截图的功能,妈妈再也不用担心我不会滚动捕捉了!
|
||||
|
||||
记住这两个快捷键已经足够满足你对截图的 90% 的需求了
|
||||
|
||||
### 橡皮鸭
|
||||
|
||||
> 来自伯克利大学的学习建议
|
||||
|
||||
当遇到问题时,除了截图外,试着组织语言来解释你遇到困难的地方。
|
||||
|
||||

|
||||
|
||||
**这并不需要一个找到懂得如何解决问题的人 (或者甚至是一个人 —— 这种做法通常被称为橡皮鸭,因为你可以把一只橡皮鸭当作你的练习对象) ,因为主要目标是让你弄清楚你自己的想法,弄清楚你的理解和代码到底在哪里卡住了。这样你可以知道应该专注于哪一部分,以便更好地理解。**
|
||||
|
||||
### 欢迎大家阅读
|
||||
|
||||
[Stop-Ask-Questions-The-Stupid-Ways](https://github.com/tangx/Stop-Ask-Questions-The-Stupid-Ways/blob/master/README.md) 别像弱智一样提问
|
||||
|
||||
[How-To-Ask-Questions-The-Smart-Way](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) 提问的智慧
|
||||
|
||||
## 关于如何搜索代码
|
||||
|
||||
@@ -56,11 +84,11 @@ cv.waitKey(0)
|
||||
接下来,我会去搜索每行代码的作用:(以下是搜索后我做的注释)
|
||||
|
||||
```python
|
||||
import cv2 as cv # 调opencv库
|
||||
import cv2 as cv # 调 opencv 库
|
||||
img = cv.imread('lbxx.jpg',1) # 读取图片(“图片路径”)
|
||||
img_1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 转换成灰度图(图片, 颜色模式)
|
||||
cv.imshow('gray',img_1) # 展示图片(展示img_1为灰度图)
|
||||
cv.imshow('colour',img) # 展示图片(展示img为彩色图)
|
||||
img_1 = cv.cvtColor(img,cv.COLOR_BGR2GRAY) # 转换成灰度图(图片,颜色模式)
|
||||
cv.imshow('gray',img_1) # 展示图片(展示 img_1 为灰度图)
|
||||
cv.imshow('colour',img) # 展示图片(展示 img 为彩色图)
|
||||
cv.waitKey(0) # 保存展示窗口打开
|
||||
```
|
||||
|
||||
|
||||
@@ -12,13 +12,17 @@
|
||||
|
||||
- [Everything](https://www.voidtools.com/zh-cn/downloads/) 电脑上的全局文件搜索 方便你找到不知道丢哪的文件
|
||||
- [SpaceSniffer](http://www.uderzo.it/main_products/space_sniffer/download.html) 快速分析硬盘空间占用情况 解放储存,不解放大脑
|
||||
- [Snipaste](https://zh.snipaste.com/) 全局截图工具,按 F1 键截图,F3 键贴图,简单够用
|
||||
- [eSearch](https://esearch.vercel.app/) 全局截图工具,优化了文字识别功能,可个性化,支持全平台
|
||||
- [ShareX](https://esearch.vercel.app/) 全局截图工具,功能非常强大,高度可个性化,仅支持 Win
|
||||
- [IDM](https://www.internetdownloadmanager.com/) :好用的多线程下载器(付费的),想要免费的话可以搜一下绿色版之类的。(推荐设置线程数为 CPU 核心数的 2 倍,比如 8 核心的 CPU 设置线程数为 16)
|
||||
- [XDM](https://github.com/subhra74/xdm) :IDM 的跨平台版本。
|
||||
- [uTools](https://www.u.tools/) :自由组合插件集(最好用的是 Alt+Space 搜索功能)非常强大,比如安装 fileshare 可以在局域网共享超大文件,而且是跨平台的。
|
||||
- [PowerToys](https://github.com/microsoft/PowerToys) :微软官方出品,包含诸多功能,解决 windows 下一些小痛点。
|
||||
- [Connect to Work or Games from Anywhere | Parsec](https://parsec.app/) :串流小工具,简单来说你就是可以在手机上玩电脑了,远程操作,极致体验~~(也可以玩游戏)~~
|
||||
- [VMware workstation](../3.%E7%BC%96%E7%A8%8B%E6%80%9D%E7%BB%B4%E4%BD%93%E7%B3%BB%E6%9E%84%E5%BB%BA/3.Y.1VMware%E7%9A%84%E5%AE%89%E8%A3%85%E4%B8%8E%E5%AE%89%E8%A3%85Ubuntu22.04%E7%B3%BB%E7%BB%9F.md):虚拟机就用它!但是最好自己找找盗版,正版要钱。
|
||||
- [cloc](https://github.com/AlDanial/cloc): 统计代码行数(空白行,注释行,代码行)的小工具
|
||||
- mv & cp 命令显示进度条: 在复制大文件的时候非常友好,可以通过以下脚本安装(Linux系统)
|
||||
- mv & cp 命令显示进度条:在复制大文件的时候非常友好,可以通过以下脚本安装(Linux 系统)
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
@@ -50,9 +54,9 @@ rm coreutils-8.32 coreutils-8.32.tar.xz
|
||||
|
||||
## 笔记工具
|
||||
|
||||
- [Typora](https://typora.io/) 付费的,~~你可以去并夕夕啊淘宝啊花个不多于 5 块钱的钱买盗版 😋~~,( 正版 $14.99 ),真的好用,感觉没有 Markdown 编辑器能好用过 Typora🤥。
|
||||
- [Typora](https://typora.io/) 付费的,~~你可以去并夕夕啊淘宝啊花个不多于 5 块钱的钱买盗版 😋~~,(正版 $14.99),真的好用,感觉没有 Markdown 编辑器能好用过 Typora🤥。
|
||||
- [MarkText](https://github.com/marktext/marktext) 免费的,平替 Typora。
|
||||
- [MiaoYan](https://github.com/tw93/MiaoYan) 仅支持 apple ,界面挺清爽。
|
||||
- [MiaoYan](https://github.com/tw93/MiaoYan) 仅支持 apple,界面挺清爽。
|
||||
- [思源笔记](https://b3log.org/siyuan/) 一个国产开源的笔记/知识库软件,优势是 本地化、双链、Markdown 语法,与 Obsidian 定位相似,但 Geek 成分和自定义空间相对更高
|
||||
- [Zotero](https://www.zotero.org/):协助文献阅读还有写笔记,支持与平板同传(同时他是开源的,所以可以添加一些插件)
|
||||
|
||||
@@ -67,8 +71,8 @@ rm coreutils-8.32 coreutils-8.32.tar.xz
|
||||
|
||||
## 浏览器插件
|
||||
|
||||
- [沉浸式翻译](https://immersivetranslate.com/docs/installation/):中英文对照翻译,可以给你英文下面写一小行中文翻译(里面免费的 api 只有谷歌,必应,腾讯,不过够了,也可以自行配置其他 api )
|
||||
- (你真的不玩原神吗)来试试这款原神浏览器插件 [派蒙 paimon](https://github.com/daidr/paimon-webext) : 可以实时显示你的树脂,委托,派遣等情况提示。
|
||||
- [沉浸式翻译](https://immersivetranslate.com/docs/installation/):中英文对照翻译,可以给你英文下面写一小行中文翻译(里面免费的 api 只有谷歌,必应,腾讯,不过够了,也可以自行配置其他 api)
|
||||
- (你真的不玩原神吗)来试试这款原神浏览器插件 [派蒙 paimon](https://github.com/daidr/paimon-webext) :可以实时显示你的树脂,委托,派遣等情况提示。
|
||||
- [wappalyzer](https://www.wappalyzer.com/):如果你是个 web 仔,这个插件可以帮你检测网页所用的前后端技术栈。
|
||||
- [FeHelper--Web 前端助手](https://github.com/zxlie/FeHelper):十几个小工具的集合,包括 base64 离线解码等。
|
||||
- [darkreader](https://github.com/darkreader/darkreader):适应网页的暗色模式,夜深人静冲浪更爽
|
||||
|
||||
BIN
2.高效学习/static/01.jpg
Normal file
BIN
2.高效学习/static/01.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 118 KiB |
BIN
2.高效学习/static/02.jpg
Normal file
BIN
2.高效学习/static/02.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
@@ -13,7 +13,7 @@
|
||||
### 这篇讲义讲什么
|
||||
|
||||
- 首先,如上文所述,如何轻松地利用本章乃至整个讲义
|
||||
- 在第一点的基础上,引申出我自己归纳的<strong>编程入门之“道”</strong>
|
||||
- 在第一点的基础上,引申出我自己归纳的**编程入门之“道”**
|
||||
|
||||
### 请随意喷这篇讲义
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
1. 这里的文章的最大的作用是帮你打开信息壁垒,告诉你编程的世界有哪些东西,可以去学什么。
|
||||
2. 把讲义当成字典来用。很多文章并不是完全对新人友好的,你现在不需要看懂,甚至可能不需要看。你只要大概记下有这么个概念和工具,当下次要用到的时候,再查阅、再仔细学习就好。
|
||||
|
||||
简单来说就是,<strong>抱着平和的心态,随便看看</strong>,知道这一章都讲了哪些东西,看完有个印象就好,然后常回家看看。
|
||||
简单来说就是,**抱着平和的心态,随便看看**,知道这一章都讲了哪些东西,看完有个印象就好,然后常回家看看。
|
||||
|
||||
技术细节本身永远都不是最重要的,重要的是思想和方法,如何快速掌握一门技术。
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
这是我的第一个也是最重要的建议。
|
||||
|
||||
无论是学一门语言,还是学一个工具:<strong>尽可能地先用最短的时间搞懂这个东西是做什么的,然后以最快的方式把它“run”起来。</strong>
|
||||
无论是学一门语言,还是学一个工具:**尽可能地先用最短的时间搞懂这个东西是做什么的,然后以最快的方式把它“run”起来。**
|
||||
|
||||
当你已经能跑起一个语言、一个工具的最简单的示例的时候,再去花时间慢慢了解背后的复杂的内容,再去拓展即可。先用起来,跑起来,带着问题去翻资料。
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
那么该怎么学呢?
|
||||
|
||||
<strong>先简单地会一样东西的最核心的部分,再去找一个实际的编程场景、编程任务、项目。你会在完成这个项目中遇到各种各样的问题,无论是遗漏了知识点还是压根没思路,这时候不断地用搜索引擎来学习。( </strong>[2.3 高效的信息检索](../2.%E9%AB%98%E6%95%88%E5%AD%A6%E4%B9%A0/2.3%E9%AB%98%E6%95%88%E7%9A%84%E4%BF%A1%E6%81%AF%E6%A3%80%E7%B4%A2.md)<strong>)</strong>
|
||||
**先简单地会一样东西的最核心的部分,再去找一个实际的编程场景、编程任务、项目。你会在完成这个项目中遇到各种各样的问题,无论是遗漏了知识点还是压根没思路,这时候不断地用搜索引擎来学习。( **[2.3 高效的信息检索](../2.%E9%AB%98%E6%95%88%E5%AD%A6%E4%B9%A0/2.3%E9%AB%98%E6%95%88%E7%9A%84%E4%BF%A1%E6%81%AF%E6%A3%80%E7%B4%A2.md)**)**
|
||||
|
||||
举个例子:你想做一个小程序,来检测某电影院的电影预售。程序大概要做到不断刷新网页,一检测到这个电影预售了,就马上发短信给自己手机(或者直接帮你抢)
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
刚开始你可能什么都不会,什么地方都被阻塞,但当你把坑踩遍了。就发现,哎嘿,不好意思,这玩意我怎么又会!
|
||||
|
||||
<strong>所以让我们基于这个“任务驱动”,再看看本章的内容。这些内容大多看了就忘,因为细节非常多,而且并不一定能解决你手头上的问题。但这些文档,带你领进了新的领域的大门,让你的工具箱里多了一个可以解决问题的工具,以后用到了可以想起他们。并且,这些文章多是通俗的,且作者多是讲述了 ta 所认为的该语言/工具的最核心、最精华的部分,或者说第一次入门最需要学习的部分。</strong>
|
||||
**所以让我们基于这个“任务驱动”,再看看本章的内容。这些内容大多看了就忘,因为细节非常多,而且并不一定能解决你手头上的问题。但这些文档,带你领进了新的领域的大门,让你的工具箱里多了一个可以解决问题的工具,以后用到了可以想起他们。并且,这些文章多是通俗的,且作者多是讲述了 ta 所认为的该语言/工具的最核心、最精华的部分,或者说第一次入门最需要学习的部分。**
|
||||
|
||||
## 圈子
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
当然在这里我们主要讲的是代码编辑器,一个好的编辑器可以节省开发时间,提高工作效率,它们都能提供非常方便易用的开发环境。你可以用它们来编写代码,查看源文件和文档等,简化你的工作。以下是一些常用的代码编辑器,每个不同的编辑器都有不尽相同的目标用户群体。
|
||||
|
||||
- <em>Visual Studio Code</em> : 微软 VS 系列的新作品,适用于多平台的代码编辑器,其很好服从了轻量化 + 拓展的 Unix 思想,在整体快捷方便的同时具有极强的功能拓展空间,是值得首要推荐的编辑器。
|
||||
- <em>Vim </em>: Vim 是从 vi 发展出来的一个文本编辑器,在程序员中被广泛使用,运行在 Linux 环境下。
|
||||
- <em>GNU Emacs</em> : Emacs 是一个轻便、可扩展、免费的编辑器,它比其它的编辑器要更强大,是一个整合环境,或可称它为集成开发环境。它可以处理文字,图像,高亮语法,将代码更直观地展现给开发者。
|
||||
- *Visual Studio Code* : 微软 VS 系列的新作品,适用于多平台的代码编辑器,其很好服从了轻量化 + 拓展的 Unix 思想,在整体快捷方便的同时具有极强的功能拓展空间,是值得首要推荐的编辑器。
|
||||
- *Vim*: Vim 是从 vi 发展出来的一个文本编辑器,在程序员中被广泛使用,运行在 Linux 环境下。
|
||||
- *GNU Emacs* : Emacs 是一个轻便、可扩展、免费的编辑器,它比其它的编辑器要更强大,是一个整合环境,或可称它为集成开发环境。它可以处理文字,图像,高亮语法,将代码更直观地展现给开发者。
|
||||
|
||||
### 什么是编译器
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ author:wenjing
|
||||
|
||||
## ACM 能为我带来什么?
|
||||
|
||||
显然,做为一名计算机专业的学生,编程是一项必须掌握的技能。再次引用 Niklaus Emil Wirth 的一句话:<strong>程序=算法 + 数据结构。</strong>例如在大一开设的程序设计基础中,我们需要重点学习链表这一数据结构,熟悉运用分支与循环结构(勉强也算算法吧)。然而,在 ACM 中,这是基础到不值一提的事物,宛如空气与水一般基础。你们是否想过,花了大量课时学习的这些知识,其实小学生也可以学会(看看远处的小学编程补习班吧,家人们)那做为大学生去学习这些知识,是否应当得到一些不止于考试内容的知识呢?
|
||||
显然,做为一名计算机专业的学生,编程是一项必须掌握的技能。再次引用 Niklaus Emil Wirth 的一句话:**程序=算法 + 数据结构。**例如在大一开设的程序设计基础中,我们需要重点学习链表这一数据结构,熟悉运用分支与循环结构(勉强也算算法吧)。然而,在 ACM 中,这是基础到不值一提的事物,宛如空气与水一般基础。你们是否想过,花了大量课时学习的这些知识,其实小学生也可以学会(看看远处的小学编程补习班吧,家人们)那做为大学生去学习这些知识,是否应当得到一些不止于考试内容的知识呢?
|
||||
|
||||
我认为有两个方向,一是我们去学习一些更底层的逻辑与原理,此外就是学习如何更好的利用链表,实现一些别的数据结构做不到的事情,我认为 ACM 可以极大的提升我们对后者的理解。
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ Div.1、Div.2、Div.3、Div.4 数字越小难度越大。
|
||||
这是一场笔者之前赛后补过的 Div.2,画面右下角分别为赛后公告和题解,右侧便是开启 VP 的按钮。
|
||||

|
||||
|
||||
<em>VP</em><em>模拟赛时的好处就是在虚拟参赛中获得真实比赛才能积累的经验,比如这里笔者发现通过前三题后,我应该先去看看 F 题,因为做出来的人更多,我有更大的可能性做出来,ACM 中题目并不是 100% 按难度排序。</em>
|
||||
*VP模拟赛时的好处就是在虚拟参赛中获得真实比赛才能积累的经验,比如这里笔者发现通过前三题后,我应该先去看看 F 题,因为做出来的人更多,我有更大的可能性做出来,ACM 中题目并不是 100% 按难度排序。*
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
## 学了算法就相当于学好了计算机吗?
|
||||
|
||||
学好了算法当然不等于学好了计算机科学。计算机科学是一个非常庞大的知识体系,包括更为底层的计算机组成原理、编译原理等,更为表层的 AI,开发等,是一门综合性学科。总的来说,算法是计算机科学中较为重要的一部分,但<strong>远远</strong>不是全部。
|
||||
学好了算法当然不等于学好了计算机科学。计算机科学是一个非常庞大的知识体系,包括更为底层的计算机组成原理、编译原理等,更为表层的 AI,开发等,是一门综合性学科。总的来说,算法是计算机科学中较为重要的一部分,但**远远**不是全部。
|
||||
|
||||
## 学算法就要用《算法导论》一类的书吗?
|
||||
|
||||
@@ -32,6 +32,6 @@ ACM 是美国计算机协会(Association for Computing Machinery)的缩写
|
||||
|
||||
在我校,参加 ACM 社团(姑且叫做社团)并不代表能够参加有含金量的团体赛(ICPC、CCPC 等)。你需要先参加由我校教练刘春英老师组织的各种比赛,有资格进入集训队后,才有机会代表学校参加比赛(当然不限名额的个人赛想参加就参加)。
|
||||
|
||||
进入集训队后采取末位淘汰制度(最后留下来的人在 20 人左右),最后留下来的人才有机会参加比赛。<strong>因此个人并不推荐 0 基础的同学对于 ACM 过于执着</strong>,有 0 基础的同学最后进入校队的例子,不过这通常意味着你一天至少得刷一道算法题。如果还是想尝试的同学,可以去洛谷 ([www.luogu.com.cn](http://www.luogu.com.cn))、Codeforces([www.codeforces.com](http://www.codeforces.com))、Atcoder([atcoder.jp](https://atcoder.jp/)) 等平台上注册账号,练习题目,参加这些网站定期组织的一些比赛。
|
||||
进入集训队后采取末位淘汰制度(最后留下来的人在 20 人左右),最后留下来的人才有机会参加比赛。**因此个人并不推荐 0 基础的同学对于 ACM 过于执着**,有 0 基础的同学最后进入校队的例子,不过这通常意味着你一天至少得刷一道算法题。如果还是想尝试的同学,可以去洛谷 ([www.luogu.com.cn](http://www.luogu.com.cn))、Codeforces([www.codeforces.com](http://www.codeforces.com))、Atcoder([atcoder.jp](https://atcoder.jp/)) 等平台上注册账号,练习题目,参加这些网站定期组织的一些比赛。
|
||||
|
||||
如果经过一段时间的练习能够在 Codefoces([www.codeforces.com](http://www.codeforces.com))上达到 1400 以上的 Rating,那么可以再观望观望参与 ACM。
|
||||
|
||||
@@ -22,9 +22,9 @@ C 语言其实是一门优秀的承上启下的语言,既具有高级语言的
|
||||
|
||||
但是其功能毕竟受限,有时候用起来会苦恼其操作受限以及各种奇奇怪怪的 bug 问题。
|
||||
|
||||
<strong>如果为了增强自身的编程能力和计算机素养,培养解决问题的能力,C 语言的你的不二选择。在这里强烈推荐 jyy 老师的各类课程。(</strong><strong>[http://jyywiki.cn/](http://jyywiki.cn/)</strong><strong>)</strong>
|
||||
**如果为了增强自身的编程能力和计算机素养,培养解决问题的能力,C 语言的你的不二选择。在这里强烈推荐 jyy 老师的各类课程。([http://jyywiki.cn/](http://jyywiki.cn/))**
|
||||
|
||||
<strong>我们的任务一部分会使用 C 语言,一方面培养大家编程能力,一方面辅助大家期末考试。</strong>
|
||||
**我们的任务一部分会使用 C 语言,一方面培养大家编程能力,一方面辅助大家期末考试。**
|
||||
|
||||
### C++
|
||||
|
||||
@@ -34,7 +34,7 @@ C 语言其实是一门优秀的承上启下的语言,既具有高级语言的
|
||||
- 更高级的语言特征,可自定义数据类型
|
||||
- 标准库
|
||||
|
||||
<strong>C++ 既有 C 面向过程的特点,又拥有面向对象的特性,是一门系统级的语言。</strong>
|
||||
**C++ 既有 C 面向过程的特点,又拥有面向对象的特性,是一门系统级的语言。**
|
||||
|
||||
编译器、操作系统的开发,高性能服务器的开发,游戏引擎的开发,硬件编程,深度学习框架的开发......只要是和底层系统或者是与性能相关的事情,通常都会有 C++ 的一席之地。
|
||||
|
||||
@@ -46,7 +46,7 @@ Python 在图里是电锯,适合干比较“狂野”的任务,也是深度
|
||||
|
||||
使用缩进控制语句是此语言的特点。
|
||||
|
||||
<strong>作为深度学习的主要使用语言,我们将以</strong><strong>P</strong><strong>ython 为主。</strong>
|
||||
**作为深度学习的主要使用语言,我们将以****P****ython 为主。**
|
||||
|
||||
## JAVA
|
||||
|
||||
@@ -54,7 +54,7 @@ Python 在图里是电锯,适合干比较“狂野”的任务,也是深度
|
||||
|
||||
他太老了,虽然不少框架都依托于 Java,但是不得不说,一些地方略有落后。
|
||||
|
||||
<strong>频繁应用于</strong><strong>W</strong><strong>eb 开发,安卓应用等等。</strong>
|
||||
**频繁应用于****W****eb 开发,安卓应用等等。**
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
更严重的是,他可能会透支学校的信誉。
|
||||
|
||||
<strong>同时,先学好 C 语言对你有以下帮助:</strong>
|
||||
**同时,先学好 C 语言对你有以下帮助:**
|
||||
|
||||
1. 掌握计算机底层知识:C 语言是一种高效的系统级语言,它的语法和数据结构设计直接映射到底层计算机硬件,通过学习 C 语言可以更深入地了解计算机底层运作原理,为理解更高级的编程语言和开发工具奠定基础。
|
||||
2. 提高编程能力:C 语言的语法相对较为简单,但是它要求程序员手动管理内存,这需要编程者深入了解内存结构和指针的使用。通过学习 C 语言,可以锻炼编程能力,提高代码质量和效率。
|
||||
@@ -65,7 +65,7 @@ NJU-ICS-PA 南京大学计算机系统基础
|
||||
|
||||
有且仅有大学有这样好的资源帮助你了
|
||||
|
||||
## <strong>坚持了好久还是搞不定,我想放弃了</strong>
|
||||
## **坚持了好久还是搞不定,我想放弃了**
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Visual Studio(以下简称 VS)是 Windows 下最完美的 C/C++ 等语言的
|
||||
|
||||
什么是 IDE,什么是代码编辑器,什么是编译器等等细碎问题参考文档 [3.1 该使用哪个编辑器???](3.1%E8%AF%A5%E4%BD%BF%E7%94%A8%E5%93%AA%E4%B8%AA%E7%BC%96%E8%BE%91%E5%99%A8%EF%BC%9F%EF%BC%9F%EF%BC%9F.md) 看不懂的话直接无脑装
|
||||
|
||||
### <strong>下载</strong>
|
||||
### **下载**
|
||||
|
||||
[https://visualstudio.microsoft.com/zh-hans/downloads/](https://visualstudio.microsoft.com/zh-hans/downloads/)
|
||||
|
||||
@@ -60,7 +60,7 @@ VS 是项目制,你需要创建一个项目才能开始编写代码并运行
|
||||
|
||||
阅读完以后,就可以将代码全部删去,编写自己的代码了。
|
||||
|
||||
注意控制台项目初始源文件后缀为.cpp 为 C++ 文件,如果编写 C 语言<strong>建议将后缀改为.c</strong>。.cpp 存在隐患:如果不小心使用了 C++ 的语法而非 C 存在的语法,编译器并不会报错,且 C 与 C++ 在某些特性存在区别。
|
||||
注意控制台项目初始源文件后缀为.cpp 为 C++ 文件,如果编写 C 语言**建议将后缀改为.c**。.cpp 存在隐患:如果不小心使用了 C++ 的语法而非 C 存在的语法,编译器并不会报错,且 C 与 C++ 在某些特性存在区别。
|
||||
|
||||
### “运行”你的 C 语言代码
|
||||
|
||||
@@ -88,7 +88,7 @@ IDE 相比于代码编辑器,最强大的一点莫过于成熟的调试系统
|
||||
|
||||

|
||||
|
||||
### <strong>深色主题</strong>
|
||||
### **深色主题**
|
||||
|
||||
需要深色主题请在工具 - 主题里更改为深色
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
## Work an Example Yourself
|
||||
|
||||
尝试设计算法的第一步是<strong>自己(手动)处理至少一个问题实例,为每个参数选择特定值。</strong>往往需要确定<strong>一个正确的示例,以及错误的示例。</strong>
|
||||
尝试设计算法的第一步是**自己(手动)处理至少一个问题实例,为每个参数选择特定值。**往往需要确定**一个正确的示例,以及错误的示例。**
|
||||
|
||||
如果你在这一步陷入困境,这通常意味着两件事中的一件。第一种情况是问题不明确,不清楚你应该做什么。在这种情况下,你必须在继续之前解决问题。如果你正在解决自己创造的问题,你可能需要更仔细地思考正确的答案应该是什么,并完善你对问题的定义。
|
||||
|
||||
@@ -25,15 +25,15 @@
|
||||
|
||||
## Write Down What You Just Did
|
||||
|
||||
这一步中,必须思考解决问题所做的工作,并写下<strong>解决该特定实例的步骤。</strong>思考这一步骤的另一种方式是,写下一组清晰的指示,<strong>其他人可以按照这些指示来重现刚刚解决的特定问题实例的答案</strong>。如果在步骤 1 中执行了多个实例,那么也将重复步骤 2 多次,对步骤 1 中的每个实例重复一次。如果一条指令有点复杂,那没关系,只要指令稍后有明确的含义,我们将把这些复杂的步骤转化为它们自己的编程问题,这些问题将单独解决。
|
||||
这一步中,必须思考解决问题所做的工作,并写下**解决该特定实例的步骤。**思考这一步骤的另一种方式是,写下一组清晰的指示,**其他人可以按照这些指示来重现刚刚解决的特定问题实例的答案**。如果在步骤 1 中执行了多个实例,那么也将重复步骤 2 多次,对步骤 1 中的每个实例重复一次。如果一条指令有点复杂,那没关系,只要指令稍后有明确的含义,我们将把这些复杂的步骤转化为它们自己的编程问题,这些问题将单独解决。
|
||||
|
||||
## Generalize Your Steps
|
||||
|
||||
<strong>将步骤 2 得到的具体步骤,抽象为一般性的结论。</strong>有时可能很难概括步骤。发生这种情况时,返回步骤 1 和 2 可能会有所帮助。做更多的问题实例将提供更多的信息供参考,更能帮助深入算法。这个过程通常被称为编写“伪代码”,以编程方式设计算法,而不使用特定的目标语言。几乎所有的程序员在编写任何实际代码之前都会使用这种方法来确保他们的算法是正确的。
|
||||
**将步骤 2 得到的具体步骤,抽象为一般性的结论。**有时可能很难概括步骤。发生这种情况时,返回步骤 1 和 2 可能会有所帮助。做更多的问题实例将提供更多的信息供参考,更能帮助深入算法。这个过程通常被称为编写“伪代码”,以编程方式设计算法,而不使用特定的目标语言。几乎所有的程序员在编写任何实际代码之前都会使用这种方法来确保他们的算法是正确的。
|
||||
|
||||
## Test Your Algorithm
|
||||
|
||||
在步骤 3 之后,我们有了一个我们认为正确的算法。然而,我们完全有可能在这一路上搞砸了。步骤 4 的主要目的是确保我们的步骤在继续之前是正确的。为了实现这一点,我们使用<strong>不同于设计算法时使用的参数值</strong>来测试我们的算法。我们手动执行算法,并将其获得的答案与正确的答案进行比较。如果它们不同,那么我们知道我们的算法是错误的。我们使用的测试用例(参数值)越多,我们就越能确信我们的算法是正确的。不幸的是,通过测试无法确保我们的算法是正确的。要完全确定你的算法是正确的,唯一的方法就是正式证明它的正确性(使用数学证明),这超出了这个专门化的范围。
|
||||
在步骤 3 之后,我们有了一个我们认为正确的算法。然而,我们完全有可能在这一路上搞砸了。步骤 4 的主要目的是确保我们的步骤在继续之前是正确的。为了实现这一点,我们使用**不同于设计算法时使用的参数值**来测试我们的算法。我们手动执行算法,并将其获得的答案与正确的答案进行比较。如果它们不同,那么我们知道我们的算法是错误的。我们使用的测试用例(参数值)越多,我们就越能确信我们的算法是正确的。不幸的是,通过测试无法确保我们的算法是正确的。要完全确定你的算法是正确的,唯一的方法就是正式证明它的正确性(使用数学证明),这超出了这个专门化的范围。
|
||||
|
||||
确定好的测试用例是一项重要的技能,可以随着实践而提高。对于步骤 4 中的测试,您需要使用至少产生几个不同答案的情况进行测试(例如,如果您的算法有“是”或“否”答案,则应使用同时产生“是”和“否”的参数进行测试)。您还应该测试任何角落情况,其中行为可能与更一般的情况不同。每当您有条件决定(包括计算位置的限制)时,您应该在这些条件的边界附近测试潜在的角点情况。
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
在黑盒测试中,测试人员只考虑功能的预期行为,而不考虑设计测试用例的任何实现细节。缺乏对实现细节的访问是这种测试方法的由来——函数的实现被视为测试人员无法查看的“黑盒子”。
|
||||
|
||||
事实上我们无需执行步骤 1-5 就能够为假设问题设想好的测试用例。实际上,在开始解决问题之前,您可以针对问题提出一组黑盒测试。一些程序员提倡<strong>测试优先</strong>的开发方法。一个优点是,如果您在开始之前编写了一个全面的测试套件,那么在实现代码之后就不太可能在测试上有所疏漏。另一个优点是,通过提前考虑你的情况,你在开发和实现算法时能够降低错误率。
|
||||
事实上我们无需执行步骤 1-5 就能够为假设问题设想好的测试用例。实际上,在开始解决问题之前,您可以针对问题提出一组黑盒测试。一些程序员提倡**测试优先**的开发方法。一个优点是,如果您在开始之前编写了一个全面的测试套件,那么在实现代码之后就不太可能在测试上有所疏漏。另一个优点是,通过提前考虑你的情况,你在开发和实现算法时能够降低错误率。
|
||||
|
||||
- 选择测试用例的一些建议
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# C 语言前置概念学习
|
||||
|
||||
如何学习 C 语言?<strong>第一步:Throw away the textbook。</strong>也许你可以通过以下途径:
|
||||
如何学习 C 语言?**第一步:Throw away the textbook。**也许你可以通过以下途径:
|
||||
|
||||
以下方式难度由易到难,但并不意味着收获由小到大:
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
4.Web:[CNote](https://github.com/coderit666/CNote)(例子密集,学习曲线平滑,覆盖面广且具有深度)
|
||||
|
||||
5.Book:<strong>教材替换用书——《C Primer Plus》!</strong>(基础且深入的恰到好处,有一定拓展,可能后面的章节有点难懂,是一本不可多得的好书,不要忽视课本习题及 Projects)
|
||||
5.Book:**教材替换用书——《C Primer Plus》!**(基础且深入的恰到好处,有一定拓展,可能后面的章节有点难懂,是一本不可多得的好书,不要忽视课本习题及 Projects)
|
||||
|
||||
6.MOOC:[Introductory C Programming 专项课程](https://www.coursera.org/specializations/c-programming)(<strong>全英文</strong>,好处是涉及到计算机思维,包含许多常用 tools 的教学例如 git、make、emacs、gdb,视频讲解结合文档阅读,对于 C 的重要核心知识讲解透彻,难度颇高,建议用作提升)
|
||||
6.MOOC:[Introductory C Programming 专项课程](https://www.coursera.org/specializations/c-programming)(**全英文**,好处是涉及到计算机思维,包含许多常用 tools 的教学例如 git、make、emacs、gdb,视频讲解结合文档阅读,对于 C 的重要核心知识讲解透彻,难度颇高,建议用作提升)
|
||||
|
||||
7.Web:[LinuxC 一站式编程](https://akaedu.github.io/book/)(难度大,枯燥硬核,收获多,基于 linux)
|
||||
|
||||
@@ -38,4 +38,4 @@
|
||||
|
||||

|
||||
|
||||
### <strong>CS education is more than just “learning how to code”!</strong>
|
||||
### **CS education is more than just “learning how to code”!**
|
||||
|
||||
@@ -54,9 +54,9 @@ typedef struct Node* Link;
|
||||
|
||||
例如,若链表无头结点,则对于在链表中第一个数据结点之前插入一个新结点,或者对链表中第一个数据结点做删除操作,都必须要当做特殊情况,进行特殊考虑;而若链表中设有头结点,以上两种特殊情况都可被视为普通情况,不需要特殊考虑,降低了问题实现的难度。
|
||||
|
||||
<strong>链表有头结点,也不一定都是有利的。例如解决约瑟夫环问题,若链表有头结点,在一定程度上会阻碍算法的实现。</strong>
|
||||
**链表有头结点,也不一定都是有利的。例如解决约瑟夫环问题,若链表有头结点,在一定程度上会阻碍算法的实现。**
|
||||
|
||||
<strong>所以,对于一个链表来说,设置头指针是必要且必须的,但有没有头结点,则需要根据实际问题特殊分析。</strong>
|
||||
**所以,对于一个链表来说,设置头指针是必要且必须的,但有没有头结点,则需要根据实际问题特殊分析。**
|
||||
|
||||
首元结点:指的是链表开头第一个存有数据的结点。
|
||||
|
||||
@@ -329,7 +329,7 @@ int ListDelete(Link *L, int i, int* e)
|
||||
- 3 出列后,从 5 开始数 1,2 数 2,所以 2 出列;
|
||||
- 最后只剩下 5 自己,所以 5 胜出。
|
||||
|
||||
那么,究竟要如何用链表实现约瑟夫环呢?如何让一个含 5 个元素的约瑟夫环,能从第 5 个元素出发,访问到第 2 个元素呢?上面所讲的链表操作显然是难以做到的,解决这个问题就需要用到<strong>循环链表</strong>。
|
||||
那么,究竟要如何用链表实现约瑟夫环呢?如何让一个含 5 个元素的约瑟夫环,能从第 5 个元素出发,访问到第 2 个元素呢?上面所讲的链表操作显然是难以做到的,解决这个问题就需要用到**循环链表**。
|
||||
|
||||
## 循环链表
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ It is very dark in here.
|
||||
|
||||
Bye!
|
||||
|
||||
尽管可能微不足道,但该程序确实展示 <em>了</em>任何文本冒险中最重要的方面:描述性文本。一个好的故事是制作一款好的冒险游戏的要素之一。
|
||||
尽管可能微不足道,但该程序确实展示 *了*任何文本冒险中最重要的方面:描述性文本。一个好的故事是制作一款好的冒险游戏的要素之一。
|
||||
|
||||
## 为什么要用英文?
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
举个例子,一条林道可能隐藏着陷阱。虽然通道似乎从位置 A 通向位置 B,但实际上终点是位置 C,即掉进坑了。
|
||||
|
||||
假设我们的洞口被警卫挡住了。玩家就过不去,我们可以简单地将通道的<em>目的地</em>更改为终点位置(或 <em>NULL</em>),但这会导致对<em>诸如 go cave 和 look cave</em> 这样的命令做出不正确的回应:“你在这里看不到任何洞穴。我们需要一个将通道的实际终点和虚假终点分开的单独属性。为此,我们将引入一个属性 prospect 来表示后者。
|
||||
假设我们的洞口被警卫挡住了。玩家就过不去,我们可以简单地将通道的*目的地*更改为终点位置(或 *NULL*),但这会导致对*诸如 go cave 和 look cave* 这样的命令做出不正确的回应:“你在这里看不到任何洞穴。我们需要一个将通道的实际终点和虚假终点分开的单独属性。为此,我们将引入一个属性 prospect 来表示后者。
|
||||
|
||||
1. 在许多冒险中,玩家以及游戏中的 NPC 在携带量方面受到限制。给每件物品一个重量,角色库存中所有物品的总重量不应超过该角色所能承载的最大重量。当然,我们也可以给一个物体一个非常高的重量,使它不可移动(一棵树,一座房子,一座山)。
|
||||
2. RPG 式的冒险游戏需要角色的整个属性范围 ( 玩家与非玩家 ),例如 HP。HP 为零的对象要么死了,要么根本不是角色。
|
||||
@@ -120,7 +120,7 @@ extern OBJECT objs[];
|
||||
::: warning 🤔 思考题:你能否自行实现上述伪代码?
|
||||
:::
|
||||
|
||||
现在,我们已经可以使用新属性 (如果你完成了上面的思考题),<strong>details</strong> 用于新识别的命令<em>外观`<object>`</em>,<strong>textGo</strong> 在我们的命令 <em>go</em> 实现中替换固定文本<em>“OK</em>”。
|
||||
现在,我们已经可以使用新属性 (如果你完成了上面的思考题),**details** 用于新识别的命令*外观`<object>`*,**textGo** 在我们的命令 *go* 实现中替换固定文本*“OK*”。
|
||||
|
||||
## location.c
|
||||
|
||||
@@ -331,11 +331,11 @@ void executeInventory(void)
|
||||
::: warning 🤔 思考题:仔细观察这段代码,看看与你写的有何不同?
|
||||
:::
|
||||
|
||||
权重检查利用了新功能 <em>weightOfContents,</em>它将在<em>misc.c</em>中实现。在同一模块中,我们还对一些现有函数进行了修改,以支持最后几个属性。
|
||||
权重检查利用了新功能 *weightOfContents,*它将在*misc.c*中实现。在同一模块中,我们还对一些现有函数进行了修改,以支持最后几个属性。
|
||||
|
||||
属性内容将替换固定文本<em>“You see”。</em>在列出玩家的库存时,原始文本已经有点奇怪了,但是现在函数<em>listObjectsAtLocation</em>用于显示任何可能对象的内容(请参阅上面的函数<em>expertLook</em>),我们真的需要一些更灵活的东西。
|
||||
属性内容将替换固定文本*“You see”。*在列出玩家的库存时,原始文本已经有点奇怪了,但是现在函数*listObjectsAtLocation*用于显示任何可能对象的内容(请参阅上面的函数*expertLook*),我们真的需要一些更灵活的东西。
|
||||
|
||||
在函数 <em>getPassage</em> 中我们将属性<em>目标</em>替换为 prospect,并改进<em>对所有</em>命令(而不仅仅是 <em>go</em> and <em>look</em>)的响应,这些命令应用于位于“隐藏通道”另一端的位置。
|
||||
在函数 *getPassage* 中我们将属性*目标*替换为 prospect,并改进*对所有*命令(而不仅仅是 *go* and *look*)的响应,这些命令应用于位于“隐藏通道”另一端的位置。
|
||||
|
||||
## misc.h
|
||||
|
||||
@@ -462,8 +462,8 @@ function outputEdge(from, to, style)
|
||||
|
||||
注意:
|
||||
|
||||
- 尽量不要太担心浪费仅在某些类型的对象中使用的属性上的内存空间(例如,<em>textGo</em>仅用于通道),或者许多重复的字符串文本。
|
||||
- 为了演示属性 prospect 的使用,我们使洞穴无法访问。当您查看新<em>地图时,</em>这一点立即变得很明显。进入洞穴的箭头是虚线的,这意味着这是一个虚假的通道,但不是实际的通道。请放心,洞穴将在下一章重新开放。
|
||||
- 尽量不要太担心浪费仅在某些类型的对象中使用的属性上的内存空间(例如,*textGo*仅用于通道),或者许多重复的字符串文本。
|
||||
- 为了演示属性 prospect 的使用,我们使洞穴无法访问。当您查看新*地图时,*这一点立即变得很明显。进入洞穴的箭头是虚线的,这意味着这是一个虚假的通道,但不是实际的通道。请放心,洞穴将在下一章重新开放。
|
||||
- 请注意,更详细的描述往往需要一个更大的字典(更多的对象,更多的标签)。例如,命令 look silver coin 现在返回 "该硬币的正面有一只鹰"。玩家通过输入一个命令 look eagle 来查看银币,但程序并不知道鹰是什么意思 (显然这样子是不行的)。
|
||||
|
||||
输出样例
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
打开一个封闭的通道(在这里是进入洞穴)涉及到改变一些属性值:
|
||||
|
||||
- 目的地从 NULL(空地点) 变为洞穴
|
||||
- <strong>textGo</strong>从 "警卫阻止你...... "改为 "你走进山洞"
|
||||
- **textGo**从 "警卫阻止你...... "改为 "你走进山洞"
|
||||
- 在一些特殊情况下,描述和细节不需要改变。但对于一个门洞或栅栏,其中之一(或两者)通常会包含一些从 "开放 "到 "关闭 "的文字。
|
||||
|
||||
有许多方法来实现这一目标。在这里,我们将讨论一种简单、可维护和通用的方法。
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
接下来,我们引入一个名为条件的新属性,它决定了某个对象是否存在。这两个通道将被赋予互斥的条件,因此在任何时候都只能有一个存在。
|
||||
|
||||
每个条件将被实现为一个布尔函数:<strong>TRUE</strong>意味着该对象存在,<strong>FALSE</strong>意味着它不存在。
|
||||
每个条件将被实现为一个布尔函数:**TRUE**意味着该对象存在,**FALSE**意味着它不存在。
|
||||
|
||||
```c
|
||||
bool intoCaveIsOpen(void)
|
||||
@@ -281,7 +281,7 @@ int listObjectsAtLocation(OBJECT *location)
|
||||
|
||||
注意:
|
||||
|
||||
1. 警卫不可能会死,所以可以说我们的条件函数中的<strong>HP</strong>是很无用的。当然,这很容易通过添加一个 kill 命令来解决,见第 20 章。
|
||||
1. 警卫不可能会死,所以可以说我们的条件函数中的**HP**是很无用的。当然,这很容易通过添加一个 kill 命令来解决,见第 20 章。
|
||||
2. 这两个条件函数是互补的;它们有资格成为重复的代码。为了消除这一点,我们可能决定让一个函数调用另一个函数(用'!'操作符来否定结果)。一个匿名函数没有(稳定的)名字,但我们可以用它的对象来指代它。我们可以用 intoCaveBlocked 的条件函数代替。
|
||||
3. 为了简单起见,条件函数没有参数。实际上,传递一个参数 OBJECT *obj 可能更好;这使得编写更多的通用条件函数成为可能,可以在多个对象中重复使用。
|
||||
4. 在理论上,任何对象都可以成为 "条件"。在下一章,你可以看到一个类似的技术被应用于此。
|
||||
|
||||
@@ -246,7 +246,7 @@ bool matchCommand(const char *src, const char *pattern)
|
||||
|
||||
我们调整各种命令的实现,以利用新的数组参数。
|
||||
|
||||
## <strong>inventory.h</strong>
|
||||
## **inventory.h**
|
||||
|
||||
```c
|
||||
extern bool executeGet(void);
|
||||
@@ -256,7 +256,7 @@ extern bool executeGive(void);
|
||||
extern bool executeInventory(void);
|
||||
```
|
||||
|
||||
## <strong>inventory.c</strong>
|
||||
## **inventory.c**
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 2.探索未知
|
||||
|
||||
::: tip <font size=5><strong> 驾驭项目,而不是被项目驾驭</strong></font>
|
||||
::: tip <font size=5>**驾驭项目,而不是被项目驾驭**</font>
|
||||
|
||||
你和一个项目的关系会经历 4 个阶段:
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
:::
|
||||
下面的代码示例包含三个函数,每个步骤一个函数:
|
||||
|
||||
1. 函数<em>getInput</em>。
|
||||
2. 函数<em>parseAndExecute</em>。
|
||||
3. 函数<em>main</em>,负责重复调用其他两个函数。
|
||||
1. 函数*getInput*。
|
||||
2. 函数*parseAndExecute*。
|
||||
3. 函数*main*,负责重复调用其他两个函数。
|
||||
|
||||
## main.c
|
||||
|
||||
@@ -70,7 +70,7 @@ int main()
|
||||
::: warning 🤔 思考题:static 是什么意思?我为什么要用他?
|
||||
:::
|
||||
|
||||
## <strong>parsexec.h</strong>
|
||||
## **parsexec.h**
|
||||
|
||||
```c
|
||||
extern bool parseAndExecute(char *input);
|
||||
@@ -86,7 +86,7 @@ extern 是干什么的?.h 文件又在干嘛?
|
||||
在这里用指针是为了传参的时候可以传字符串哦
|
||||
:::
|
||||
|
||||
## <strong>parsexec.c</strong>
|
||||
## **parsexec.c**
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
@@ -126,9 +126,9 @@ bool parseAndExecute(char *input)
|
||||
|
||||
你的编译器可能会给出警告 the unused variable‘noun’,这些不用担心,将会在下一章解决。
|
||||
|
||||
返回<em>false</em>将导致主循环结束。
|
||||
返回*false*将导致主循环结束。
|
||||
|
||||
::: warning <font size=5><strong>RTFM&&STFW</strong></font>
|
||||
::: warning <font size=5>**RTFM&&STFW**</font>
|
||||
搞懂 strtok 和 strcmp 的用法
|
||||
:::
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ bx = torch.cat((xs[0], bs[0], xs[1], bs[1], xs[2], bs[2], xs[3], bs[3], xs[4], b
|
||||
这个故事折射出,大公司中程序员的编程习惯也许不比你好多少,他们也会写出 Copy-Paste 这种难以维护的代码。但反过来说,重视编码风格这些企业看中的能力,你从现在就可以开始培养。
|
||||
:::
|
||||
|
||||
<em>传统上,文本冒险是由(许多)不同位置组成的虚拟世界。虽然这不是必需的(一些冒险发生在一个房间里!),但这是解释</em><em>数据结构</em><em>使用的好方法。</em>
|
||||
*传统上,文本冒险是由(许多)不同位置组成的虚拟世界。虽然这不是必需的(一些冒险发生在一个房间里!),但这是解释**数据结构**使用的好方法。*
|
||||
|
||||
我们首先定义一个[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language))来表示一个位置。它包含两个简单的属性开始(稍后可能会有更多的属性)。
|
||||
|
||||
@@ -136,7 +136,7 @@ struct location locs[2] = {
|
||||
};
|
||||
```
|
||||
|
||||
让我们把它付诸实践。在上一章(<em>parsexec.c)</em> 的代码示例中,我们更改了第 4、18 和 22 行)。
|
||||
让我们把它付诸实践。在上一章(*parsexec.c)* 的代码示例中,我们更改了第 4、18 和 22 行)。
|
||||
|
||||
## <strong>parsexec.c</strong>
|
||||
|
||||
@@ -237,9 +237,9 @@ void executeGo(const char *noun)
|
||||
}
|
||||
```
|
||||
|
||||
在 C 语言中,你可以使用单个语句来定义类型(<em>结构位置</em>),声明变量(<em>locs</em>)并用其初始值填充它。
|
||||
在 C 语言中,你可以使用单个语句来定义类型(*结构位置*),声明变量(*locs*)并用其初始值填充它。
|
||||
|
||||
思考题:变量<em>locs</em>是[静态分配的](http://en.wikipedia.org/wiki/Static_memory_allocation),什么是静态分配?
|
||||
思考题:变量*locs*是[静态分配的](http://en.wikipedia.org/wiki/Static_memory_allocation),什么是静态分配?
|
||||
|
||||
静态分配和动态分配有什么不同之处?
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# 4.创建对象
|
||||
|
||||
<em>在我们继续之前,我</em><em>们</em><em>在这里使用的是</em><em>[哲学意义上](https://en.wikipedia.org/wiki/Object_(philosophy))</em><em>的“对象”一词。它与</em><em>[面向对象编程](https://en.wikipedia.org/wiki/Object-oriented_programming)</em><em>无关,也与</em><em>Java</em><em>,</em><em>C#</em><em>和</em><em>Python</em><em>等编程语言中预定义的“对象”类型没有任何共同之处。下面,我将定义一个名为 object 的</em><em>结构体。</em>
|
||||
*在我们继续之前,我们在这里使用的是[哲学意义上](https://en.wikipedia.org/wiki/Object_(philosophy))的“对象”一词。它与[面向对象编程](https://en.wikipedia.org/wiki/Object-oriented_programming)无关,也与Java,C#和Python等编程语言中预定义的“对象”类型没有任何共同之处。下面,我将定义一个名为 object 的结构体。*
|
||||
|
||||
冒险游戏中的大多数谜题都围绕着<strong>物品</strong>。例子:
|
||||
冒险游戏中的大多数谜题都围绕着**物品**。例子:
|
||||
|
||||
- 必须找到一把钥匙,然后用来解锁某扇门。
|
||||
- 必须杀死守卫或者诱骗守卫才能开启房间
|
||||
|
||||
所以,为了表示这个物品,我们可以使用如下[结构](http://en.wikipedia.org/wiki/Struct_(C_programming_language)):
|
||||
|
||||
- <strong>description: </strong><strong>对物品的描述</strong>
|
||||
- <strong>tag: </strong><strong>物品的类型</strong>
|
||||
- <strong>location: </strong><strong>物品所在</strong><strong>的位置。这是</strong><strong>对应</strong><strong>上一章中定义的</strong><strong>物品</strong><strong>位置</strong><strong>的指针。</strong>
|
||||
- **description: ****对物品的描述**
|
||||
- **tag: ****物品的类型**
|
||||
- **location: ****物品所在****的位置。这是****对应****上一章中定义的****物品****位置****的指针。**
|
||||
|
||||
```c
|
||||
struct object {
|
||||
@@ -87,30 +87,30 @@ for (obj = objs; obj < objs + 5; obj++)
|
||||
|
||||
那么,我们有合并这个物品(或地点)列表有什么好处呢?答案是这会让我们的代码变得更加简单,因为许多函数(如上面的函数通过这样的列表)只需要扫描单个列表就可以实现,而不是三个列表。有人可能会说没必要,因为每个命令仅适用于一种类型的对象:
|
||||
|
||||
- 命令 <em>go</em> 适用于位置对象。
|
||||
- 命令 <em>get</em> 应用于获得物品。
|
||||
- 命令 *go* 适用于位置对象。
|
||||
- 命令 *get* 应用于获得物品。
|
||||
- 命令 kill 适应用于杀死人物。
|
||||
|
||||
但这种方法不太对劲,原因有三:
|
||||
|
||||
1. 某些命令适用于多种类型的对象,尤其是<em>检查</em>。
|
||||
1. 某些命令适用于多种类型的对象,尤其是*检查*。
|
||||
2. 有时候会出现很没意思的交互方式,比如说你要吃掉守卫,他说不行。
|
||||
3. 某些对象在游戏中可能具有多个角色。比如说队友系统,NPC 可以是你的物品也可以是对象
|
||||
|
||||
将所有对象放在一个大列表中,很容易添加一个名为“type”的属性来<em>构造对象</em>,以帮助我们区分不同类型的对象。
|
||||
将所有对象放在一个大列表中,很容易添加一个名为“type”的属性来*构造对象*,以帮助我们区分不同类型的对象。
|
||||
|
||||
::: warning 🤔 怎么做怎么遍历呢?先思考吧
|
||||
:::
|
||||
|
||||
但是,对象通常具有同样有效的其他特征:
|
||||
|
||||
- <strong>Locations:通过</strong><strong>道路</strong><strong>连接(将在后面介绍)。如果一个物体无法通过一条通道到达,那么它就不是一个位置。就是这么简单。</strong>
|
||||
- <strong>Items:玩家唯一可以捡起的物品;</strong><strong>可以给他们整一个重量的属性</strong>
|
||||
- <strong>Actors:玩家唯一可以与之交谈,交易,战斗的对象;当然,前提是他们还活着!</strong><strong>可以加一个 HP 属性</strong>
|
||||
- **Locations:通过****道路****连接(将在后面介绍)。如果一个物体无法通过一条通道到达,那么它就不是一个位置。就是这么简单。**
|
||||
- **Items:玩家唯一可以捡起的物品;****可以给他们整一个重量的属性**
|
||||
- **Actors:玩家唯一可以与之交谈,交易,战斗的对象;当然,前提是他们还活着!****可以加一个 HP 属性**
|
||||
|
||||
我们还要向数组中添加一个对象:玩家自己。
|
||||
|
||||
在上一章中,有一个单独的变量 <em>locationOfPlayer</em>。我们将删除它,然后换上用户的位置属性取代他!
|
||||
在上一章中,有一个单独的变量 *locationOfPlayer*。我们将删除它,然后换上用户的位置属性取代他!
|
||||
|
||||
例如,此语句会将玩家移入洞穴:
|
||||
|
||||
@@ -163,7 +163,7 @@ OBJECT objs[] = {
|
||||
};
|
||||
```
|
||||
|
||||
<strong>注意:</strong>要编译此模块,编译器<em>必须</em>支持 Constant folding。这排除了一些更原始的编译器,如 [Z88DK](http://en.wikipedia.org/wiki/Z88DK)。
|
||||
**注意:**要编译此模块,编译器*必须*支持 Constant folding。这排除了一些更原始的编译器,如 [Z88DK](http://en.wikipedia.org/wiki/Z88DK)。
|
||||
|
||||
以下模块将帮助我们找到与指定名词匹配的对象。
|
||||
|
||||
@@ -173,7 +173,7 @@ OBJECT objs[] = {
|
||||
extern OBJECT *getVisible(const char *intention, const char *noun);
|
||||
```
|
||||
|
||||
::: warning <font size=5><strong>🤔 指针?函数?希望你已经掌握这是什么了</strong></font>
|
||||
::: warning <font size=5>**🤔 指针?函数?希望你已经掌握这是什么了**</font>
|
||||
:::
|
||||
|
||||
## noun.c
|
||||
@@ -232,7 +232,7 @@ OBJECT *getVisible(const char *intention, const char *noun)
|
||||
}
|
||||
```
|
||||
|
||||
这是另一个辅助程序的函数。它打印存在于特定位置的对象(物品,NPC)的列表。它将用于函数 <em>executeLook</em>,在下一章中,我们将介绍另一个需要它的命令。
|
||||
这是另一个辅助程序的函数。它打印存在于特定位置的对象(物品,NPC)的列表。它将用于函数 *executeLook*,在下一章中,我们将介绍另一个需要它的命令。
|
||||
|
||||
## misc.h
|
||||
|
||||
@@ -268,7 +268,7 @@ int listObjectsAtLocation(OBJECT *location)
|
||||
}
|
||||
```
|
||||
|
||||
在 <em>location.c</em> 中,命令环<em>顾四周的实现</em>,并根据新的数据结构进行调整。旧的位置数组被删除,变量 <em>locationOfPlayer</em> 也是如此。
|
||||
在 *location.c* 中,命令环*顾四周的实现*,并根据新的数据结构进行调整。旧的位置数组被删除,变量 *locationOfPlayer* 也是如此。
|
||||
|
||||
## location.h
|
||||
|
||||
@@ -302,7 +302,7 @@ void executeLook(const char *noun)
|
||||
|
||||
void executeGo(const char *noun)
|
||||
{
|
||||
//消除了函数<em>executeGo</em>中的循环,代码更优雅了~
|
||||
//消除了函数*executeGo*中的循环,代码更优雅了~
|
||||
OBJECT *obj = getVisible("where you want to go", noun);
|
||||
if (obj == NULL)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
| 玩家从演员那里收到物品 | ask | silver->location = player; |
|
||||
| 列出其他演员的库存 | examine | listObjectsAtLocation(guard); |
|
||||
|
||||
你可以尝试去使用这些命令(上面的前两个示例已经在上一章中实现了)。现在,我们将为玩家和非玩家角色介绍一些典型的<strong>物品栏</strong>操作(命令<em>获取</em>、<em>掉落</em>、<em>给予</em>、<em>询问</em>和<em>物品栏</em>)。
|
||||
你可以尝试去使用这些命令(上面的前两个示例已经在上一章中实现了)。现在,我们将为玩家和非玩家角色介绍一些典型的**物品栏**操作(命令*获取*、*掉落*、*给予*、*询问*和*物品栏*)。
|
||||
|
||||
::: warning 🤔 思考题:
|
||||
你能不能尝试自己实现一下上面的命令?
|
||||
@@ -32,7 +32,7 @@
|
||||
如果你可以在不参考下面内容的情况下就写出基本内容会有很大收获的
|
||||
:::
|
||||
|
||||
## <strong>parsexec.c</strong>
|
||||
## **parsexec.c**
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
@@ -91,7 +91,7 @@ bool parseAndExecute(char *input)
|
||||
|
||||
新命令由以下模块实现。
|
||||
|
||||
## <strong>inventory.h</strong>
|
||||
## **inventory.h**
|
||||
|
||||
```c
|
||||
extern void executeGet(const char *noun);
|
||||
@@ -101,7 +101,7 @@ extern void executeGive(const char *noun);
|
||||
extern void executeInventory(void);
|
||||
```
|
||||
|
||||
## <strong>inventory.c</strong>
|
||||
## **inventory.c**
|
||||
|
||||
```c
|
||||
#include <stdio.h>
|
||||
@@ -160,7 +160,7 @@ void executeInventory(void)
|
||||
}
|
||||
```
|
||||
|
||||
注意:由于动词名词比较好弄,命令 <em>ask</em> 和 <em>give</em> 只有一个参数:item。
|
||||
注意:由于动词名词比较好弄,命令 *ask* 和 *give* 只有一个参数:item。
|
||||
|
||||
::: warning 🤔 思考题:
|
||||
为什么我们要这样设计?
|
||||
@@ -168,7 +168,7 @@ void executeInventory(void)
|
||||
你能否为这些命令多加几个参数?
|
||||
:::
|
||||
|
||||
从本质上讲,<em>get</em>, <em>drop</em>, <em>give</em> and <em>ask 这些命令</em>除了将项目从一个地方移动到另一个地方之外,什么都不做。单个函数 <em>move 对象</em>可以对所有四个命令执行该操作。
|
||||
从本质上讲,*get*, *drop*, *give* and *ask 这些命令*除了将项目从一个地方移动到另一个地方之外,什么都不做。单个函数 *move 对象*可以对所有四个命令执行该操作。
|
||||
|
||||
## move.h
|
||||
|
||||
@@ -229,7 +229,7 @@ void moveObject(OBJECT *obj, OBJECT *to)
|
||||
::: warning 🤔 思考题:识别一些我们拿不了的物品需要考虑什么因素?
|
||||
:::
|
||||
|
||||
命令“get”使用函数<em>getVisible</em>将名词转换为 object,就像命令“go”一样;请参阅上一章。但是对于对玩家(或其他一些参与者)已经持有的对象进行<em>drop</em>, <em>ask</em>, <em>give 等</em>命令时,我们需要稍微不同的东西。我们将在 <em>noun.c</em> 中添加一个函数 <em>getPossession</em>。
|
||||
命令“get”使用函数*getVisible*将名词转换为 object,就像命令“go”一样;请参阅上一章。但是对于对玩家(或其他一些参与者)已经持有的对象进行*drop*, *ask*, *give 等*命令时,我们需要稍微不同的东西。我们将在 *noun.c* 中添加一个函数 *getPossession*。
|
||||
|
||||
## noun.h
|
||||
|
||||
@@ -318,9 +318,9 @@ OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
|
||||
}
|
||||
```
|
||||
|
||||
注意:新函数(45-75 行) <em>getPossession</em> 是 <em>getObject</em> 的装饰器(wrapper)(参见第 52 行),它要么返回匹配的对象,要么返回 NULL(如果没有合适的对象与名词匹配)。
|
||||
注意:新函数(45-75 行) *getPossession* 是 *getObject* 的装饰器(wrapper)(参见第 52 行),它要么返回匹配的对象,要么返回 NULL(如果没有合适的对象与名词匹配)。
|
||||
|
||||
函数 <em>actor 这里</em>用于命令 <em>give</em> 和 <em>ask</em>,但它也可能由其他命令调用。所以我们在<em>misc.c</em>中定义了它。
|
||||
函数 *actor 这里*用于命令 *give* 和 *ask*,但它也可能由其他命令调用。所以我们在*misc.c*中定义了它。
|
||||
|
||||
## misc.h
|
||||
|
||||
@@ -370,7 +370,7 @@ int listObjectsAtLocation(OBJECT *location)
|
||||
::: warning 🤔 思考题:上面第四行中的函数 actorHere 返回的指针指向什么?
|
||||
:::
|
||||
|
||||
在第 9 行中,有一个详尽的,硬编码的非玩家角色列表(到目前为止,只有一个:<em>守卫</em>)。
|
||||
在第 9 行中,有一个详尽的,硬编码的非玩家角色列表(到目前为止,只有一个:*守卫*)。
|
||||
|
||||
在第 10 章中,我们将开始使用属性作为区分角色与项目和其他非参与者对象的更优雅方式。
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# 6.绘制地图
|
||||
|
||||
作为一个 RPG 游戏怎么能没有地图呢,<em>是时候绘制地图了!</em>
|
||||
作为一个 RPG 游戏怎么能没有地图呢,*是时候绘制地图了!*
|
||||
|
||||
绘制地图的最佳工具始终是:一支铅笔和一张纸。基本地图由<strong>位置</strong>(矩形)组成,由道路(箭头)连接。我们已经在第 3 章中创建了位置,现在我们将开始添加道路。
|
||||
绘制地图的最佳工具始终是:一支铅笔和一张纸。基本地图由**位置**(矩形)组成,由道路(箭头)连接。我们已经在第 3 章中创建了位置,现在我们将开始添加道路。
|
||||
|
||||
在虚拟世界中,“道路”可能是连接两个位置的任何东西:一条路,一扇门,沙漠中。基本上,一段经文具有以下属性:
|
||||
|
||||
- 起点(位置)。
|
||||
- 目标(位置)。
|
||||
- 叙述性描述,例如“森林小径”。
|
||||
- 在 <em>go</em> 命令中往哪里走的描述性标记
|
||||
- 在 *go* 命令中往哪里走的描述性标记
|
||||
|
||||
考虑到这些属性,第 4 章中定义的结构对象就非常适合存储道路了。事实上,一个道路与一个项目或 NPC 并没有太大的不同,它作为“可见出口”存在于某个位置(该位置是起点)。它只是与某些命令的行为不同,特别是命令“go”:应用于道路,<em>go</em>将改变玩家的位置。
|
||||
考虑到这些属性,第 4 章中定义的结构对象就非常适合存储道路了。事实上,一个道路与一个项目或 NPC 并没有太大的不同,它作为“可见出口”存在于某个位置(该位置是起点)。它只是与某些命令的行为不同,特别是命令“go”:应用于道路,*go*将改变玩家的位置。
|
||||
|
||||
```c
|
||||
struct object {
|
||||
@@ -24,9 +24,9 @@ struct object {
|
||||
|
||||
注意:
|
||||
|
||||
- 显然,<em>目的地</em>在大多数其他对象(物品,NPC)中都没有使用
|
||||
- 显然,*目的地*在大多数其他对象(物品,NPC)中都没有使用
|
||||
- 通道总是朝一个方向运行;要双向连接两个位置,我们总是必须创建两个单独的通道。乍一看,这似乎很笨拙,但它确实给了我们很大的灵活性来完善命令“go”的行为
|
||||
- 在大地图上,你可能会发现手动创建所有通道很乏味。所以,我强烈建议你使用自定义工具<em>生成</em>地图中重复性更强的部分。这里不会介绍这一点,但您可能会在第 9 章中找到一些灵感,我们将在其中讨论自动胜场。
|
||||
- 在大地图上,你可能会发现手动创建所有通道很乏味。所以,我强烈建议你使用自定义工具*生成*地图中重复性更强的部分。这里不会介绍这一点,但您可能会在第 9 章中找到一些灵感,我们将在其中讨论自动胜场。
|
||||
|
||||
::: warning 🤔 思考题:为什么创建两个通道可以使我们的程序更加灵活?
|
||||
:::
|
||||
@@ -75,7 +75,7 @@ OBJECT objs[] = {
|
||||
};
|
||||
```
|
||||
|
||||
我们将在 <em>misc.c</em> 中添加一个小的帮助函数,以确定两个给定位置之间是否存在通道。
|
||||
我们将在 *misc.c* 中添加一个小的帮助函数,以确定两个给定位置之间是否存在通道。
|
||||
|
||||
## misc.h
|
||||
|
||||
@@ -139,7 +139,7 @@ int listObjectsAtLocation(OBJECT *location)
|
||||
}
|
||||
```
|
||||
|
||||
我们将在命令“go”的实现中使用新功能<em>getPassage</em>来确定是否存在可以将玩家带到所需位置的通道。
|
||||
我们将在命令“go”的实现中使用新功能*getPassage*来确定是否存在可以将玩家带到所需位置的通道。
|
||||
|
||||
## location.h
|
||||
|
||||
@@ -201,7 +201,7 @@ void executeGo(const char *noun)
|
||||
}
|
||||
```
|
||||
|
||||
我们还将使用新功能<em>getPassage</em>来确定从玩家站立的位置是否可以看到某个位置。未通过通道连接到当前位置的位置不被视为可见。
|
||||
我们还将使用新功能*getPassage*来确定从玩家站立的位置是否可以看到某个位置。未通过通道连接到当前位置的位置不被视为可见。
|
||||
|
||||
## noun.h
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# 7.增大距离
|
||||
|
||||
<em>一个典型的冒险包含许多谜题。</em><em>众所周知,[Infocom](https://en.wikipedia.org/wiki/Infocom)</em><em>的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。</em>
|
||||
*一个典型的冒险包含许多谜题。众所周知,[Infocom](https://en.wikipedia.org/wiki/Infocom)的冒险很难完成。解决每个难题可能需要数周甚至数月的反复试验。*
|
||||
|
||||
<em>当玩家操纵角色失败后,如果只是返回“你</em><em>不能这么操作</em><em>”来回应玩家</em><em>,会很 nt,很没意思</em><em>。</em>
|
||||
*当玩家操纵角色失败后,如果只是返回“你不能这么操作”来回应玩家,会很 nt,很没意思。*
|
||||
|
||||
<em>它忽略了</em><em>电脑</em><em>游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。</em>
|
||||
*它忽略了电脑游戏的一个重要方面,而这也是生活本身的一部分:玩家必须从错误中吸取教训。*
|
||||
|
||||
当你的游戏反复输入东西都是,你不能这样做的时候,会显得很无聊的。
|
||||
|
||||
<em>冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。</em>
|
||||
*冒险游戏至少应该做的是解释为什么玩家的命令无法完成:“你不能这样做,因为......”这有助于使虚拟世界更具说服力,故事更可信,游戏更有趣。*
|
||||
|
||||
我们已经付出了相当大的努力让游戏解释<strong>为什么</strong>某些命令是无效的。只需看看<em>名词.c,inventory.c,location.c</em>,<em>move.c</em>中的许多<em>printf</em>调用。但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。
|
||||
我们已经付出了相当大的努力让游戏解释**为什么**某些命令是无效的。只需看看*名词.c,inventory.c,location.c*,*move.c*中的许多*printf*调用。但随着游戏变得越来越复杂,这正成为一个相当大的负担。我们需要一种更结构化的方法来检测和处理错误情况。这就是我们在本章中将要讨论的内容。
|
||||
|
||||
大多数命令对一个或多个对象进行操作,例如:
|
||||
|
||||
- 玩家拿起一件物品,然后把它交给另一个 NPC。
|
||||
- 玩家沿着一条通道到另一个位置。
|
||||
|
||||
首先要检查的(在[解析器](http://en.wikipedia.org/wiki/Parsing)捕获检测是否会有明显[拼写错误](http://en.wikipedia.org/wiki/Typographical_error)之后)是这些对象<strong>是否存在</strong>;
|
||||
首先要检查的(在[解析器](http://en.wikipedia.org/wiki/Parsing)捕获检测是否会有明显[拼写错误](http://en.wikipedia.org/wiki/Typographical_error)之后)是这些对象**是否存在**;
|
||||
|
||||
失败应该导致类似“这里没有...“或”你看不到任何东西...”等文字出现。在本章中,我们将构建一个通用函数,每个命令都可以使用它来找出玩家是否在可及的范围内。
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
| distHereContained | 一个物体(NPC 或“容器”)存在于玩家的位置,正在拿着另一个物体 | object->location != NULL &&<br/>object->location->location == player->location |
|
||||
| distOverthere | 对象是附近的位置 | getPassage(player->location, object) != NULL |
|
||||
|
||||
第一种情况(对象是玩家)可能看起来微不足道,但它仍然很重要。例如,命令“examine yourself”<em>不应该</em>返回“这里没有你自己”。
|
||||
第一种情况(对象是玩家)可能看起来微不足道,但它仍然很重要。例如,命令“examine yourself”*不应该*返回“这里没有你自己”。
|
||||
|
||||
我试图遵循一个逻辑顺序:附近的事物最高优先级,随后优先级会变低。
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
请注意,我们有七种不同的“这里”案例,但只有一种是“不在这里”。这是因为通常,游戏只需要提供有关玩家可以感知的事物的信息。如果它不在这里,那么就没什么可说的了。
|
||||
|
||||
在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 <strong>DISTANCE</strong> 的[枚举](http://en.wikipedia.org/wiki/Enumerated_type)中收集这些名称。
|
||||
在最左边的列中,我们为每个案例提出了一个符号名称。我们将在名为 **DISTANCE** 的[枚举](http://en.wikipedia.org/wiki/Enumerated_type)中收集这些名称。
|
||||
|
||||
```c
|
||||
typedef enum {
|
||||
@@ -91,7 +91,7 @@ DISTANCE getDistance(OBJECT *from, OBJECT *to)
|
||||
注:自行实验即可
|
||||
:::
|
||||
|
||||
就这样!我们可以调用此函数并对其返回值进行比较。例如,我们在 noun<em>.c</em>中有以下代码:
|
||||
就这样!我们可以调用此函数并对其返回值进行比较。例如,我们在 noun*.c*中有以下代码:
|
||||
|
||||
```c
|
||||
else if (!(obj == player ||
|
||||
@@ -125,9 +125,9 @@ else if (getDistance(player, obj) >= distNotHere)
|
||||
::: warning 🤔 尝试理解一下这样做的意义
|
||||
:::
|
||||
|
||||
这只是一个例子,让你对这个概念有所了解;您将在下面找到<em>noun.c</em>的实际实现,看起来略有不同。
|
||||
这只是一个例子,让你对这个概念有所了解;您将在下面找到*noun.c*的实际实现,看起来略有不同。
|
||||
|
||||
是时候把事情落实到位了。枚举 <em>DISTANCE</em> 和函数 <em>getDistance</em> 的定义被添加到 <em>misc.h</em> 和 <em>misc.c</em> 中,因为我们将在多个模块中使用它们。
|
||||
是时候把事情落实到位了。枚举 *DISTANCE* 和函数 *getDistance* 的定义被添加到 *misc.h* 和 *misc.c* 中,因为我们将在多个模块中使用它们。
|
||||
|
||||
## misc.h
|
||||
|
||||
@@ -237,7 +237,7 @@ extern void executeLook(const char *noun);
|
||||
extern void executeGo(const char *noun);
|
||||
```
|
||||
|
||||
在函数 <em>executeGo</em> 中,我们可以用检查距离来替换大多数 <em>if</em> 条件。
|
||||
在函数 *executeGo* 中,我们可以用检查距离来替换大多数 *if* 条件。
|
||||
|
||||
## location.c
|
||||
|
||||
@@ -297,9 +297,9 @@ void executeGo(const char *noun)
|
||||
::: warning 🤔 思考题:你能否为 switch 函数增加更多 case 来完善判断条件?
|
||||
:::
|
||||
|
||||
函数 <em>executeGet</em> 也是如此。
|
||||
函数 *executeGet* 也是如此。
|
||||
|
||||
## <strong>inventory.h</strong>
|
||||
## **inventory.h**
|
||||
|
||||
```c
|
||||
extern void executeGet(const char *noun);
|
||||
@@ -309,7 +309,7 @@ extern void executeGive(const char *noun);
|
||||
extern void executeInventory(void);
|
||||
```
|
||||
|
||||
## <strong>inventory.c</strong>
|
||||
## **inventory.c**
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
@@ -372,7 +372,7 @@ void executeInventory(void)
|
||||
}
|
||||
```
|
||||
|
||||
最后,我们将调整 noun<em>.c</em>中的约束。我们正在向函数<em>getObject</em>添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。
|
||||
最后,我们将调整 noun*.c*中的约束。我们正在向函数*getObject*添加两个参数,以便找到特定名词的匹配项,同时忽略任何被认为不存在的对象。这将在下一章中得到真正的回报,我们将在下一章中介绍具有相同标签的不同对象。
|
||||
|
||||
## noun.h
|
||||
|
||||
@@ -463,7 +463,7 @@ OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
|
||||
::: warning 🤔 思考题:你能理解什么时候加 const,什么时候不用吗?
|
||||
:::
|
||||
|
||||
在本章中,<em>距离</em>的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于<strong>输出</strong>端;它可以同样很好地用于在<strong>输入</strong>端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。
|
||||
在本章中,*距离*的概念主要用于在游戏可以给用户的不同响应之间进行选择。但是,距离的好处并不局限于**输出**端;它可以同样很好地用于在**输入**端进行改进。在下一章中,我们将使用距离来提高对用户输入的名词的识别。
|
||||
|
||||
输出样例
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 8.移动方向
|
||||
|
||||
<em>传统的文本冒险使用</em><em>[指南针方向](https://en.wikipedia.org/wiki/Cardinal_direction)</em><em>进行导航。</em>
|
||||
*传统的文本冒险使用[指南针方向](https://en.wikipedia.org/wiki/Cardinal_direction)进行导航。*
|
||||
|
||||
例如,我们在第 6 章中绘制的地图上,玩家可能想<strong>向东</strong>移动,从田野移动到洞穴。我们可以通过给连接<strong>Cave</strong>的通道标上“east”来实现这一点。但是,我们首先需要解决两个问题。
|
||||
例如,我们在第 6 章中绘制的地图上,玩家可能想**向东**移动,从田野移动到洞穴。我们可以通过给连接**Cave**的通道标上“east”来实现这一点。但是,我们首先需要解决两个问题。
|
||||
|
||||
1. 我们可能仍然想把这段通道称为“entrance”和“east”。但现在,一个对象只能有一个标签。
|
||||
2. 在更大的地图上,具有更多的位置和道路,标签“east”将被定义多次。到目前为止,标签在我们的游戏中是全球独一无二的,没有重复项。
|
||||
@@ -80,9 +80,9 @@ OBJECT objs[] = {
|
||||
};
|
||||
```
|
||||
|
||||
当然,要让这个改动生效,我们还需要调整<em>noun.c</em>中的<em>objectHasTag</em>函数。
|
||||
当然,要让这个改动生效,我们还需要调整*noun.c*中的*objectHasTag*函数。
|
||||
|
||||
<em>同时,我们将让函数 getVisible</em>和<em>getPossession</em> 告知玩家他必须更具体的选择你到底是银币还是金币,而不是随机选择任何一个对象。
|
||||
*同时,我们将让函数 getVisible*和*getPossession* 告知玩家他必须更具体的选择你到底是银币还是金币,而不是随机选择任何一个对象。
|
||||
|
||||
## noun.h
|
||||
|
||||
@@ -91,7 +91,7 @@ extern OBJECT *getVisible(const char *intention, const char *noun);
|
||||
extern OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun);
|
||||
```
|
||||
|
||||
## <strong>noun.c</strong>
|
||||
## **noun.c**
|
||||
|
||||
```c
|
||||
#include <stdbool.h>
|
||||
@@ -188,7 +188,7 @@ OBJECT *getPossession(OBJECT *from, const char *verb, const char *noun)
|
||||
}
|
||||
```
|
||||
|
||||
问题 #3 可以通过从函数<em>parseAndExecute</em>中删除一个 [空格](http://en.wikipedia.org/wiki/Space_(punctuation))字符来解决(下面的第 10 行)。这个解决方案远非完美('silver' 和 'coin' 之间的双空格是打咩的),但直到我们在第 13 章中让自己成为一个更好的解析器之前。
|
||||
问题 #3 可以通过从函数*parseAndExecute*中删除一个 [空格](http://en.wikipedia.org/wiki/Space_(punctuation))字符来解决(下面的第 10 行)。这个解决方案远非完美('silver' 和 'coin' 之间的双空格是打咩的),但直到我们在第 13 章中让自己成为一个更好的解析器之前。
|
||||
|
||||
## parsexec.c
|
||||
|
||||
@@ -246,9 +246,9 @@ bool parseAndExecute(char *input)
|
||||
}
|
||||
```
|
||||
|
||||
模块<em>main.c</em>、<em>inventory.c</em>、<em>location.c</em>、<em>move.c</em> 和<em>misc.c</em>保持不变
|
||||
模块*main.c*、*inventory.c*、*location.c*、*move.c* 和*misc.c*保持不变
|
||||
|
||||
现在对象数组 ( <em>object.c</em> ) 开始在多个维度上增长(特别是在引入多个标签的情况下),我们需要一种使其更易于维护的方法。
|
||||
现在对象数组 ( *object.c* ) 开始在多个维度上增长(特别是在引入多个标签的情况下),我们需要一种使其更易于维护的方法。
|
||||
|
||||
::: warning 🤔 猜猜看该怎么办?
|
||||
:::
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## 前言
|
||||
|
||||
本来打算让各位做下面的任务来进行进一步的学习的,但是想了想,实在是,<strong>太无聊啦</strong>!
|
||||
本来打算让各位做下面的任务来进行进一步的学习的,但是想了想,实在是,**太无聊啦**!
|
||||
|
||||
又想让你们来做一个管理系统,但是又想到你们可能会进行无数个管理系统,<strong>这也太无聊啦</strong>!
|
||||
又想让你们来做一个管理系统,但是又想到你们可能会进行无数个管理系统,**这也太无聊啦**!
|
||||
|
||||
因此呢,我打算带大家玩一个文字冒险游戏,如果你想自己体验全流程的话可以试试玩哦!
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
在这种情况下,系统能跑起来才是王道,跑不起来什么都是浮云,追求面面俱到只会增加代码维护的难度。
|
||||
|
||||
唯一可以把你从 bug 的混沌中拯救出来的就是 KISS 法则,它的宗旨是<strong>从易到难,逐步推进</strong>, 一次只做一件事,少做无关的事。
|
||||
唯一可以把你从 bug 的混沌中拯救出来的就是 KISS 法则,它的宗旨是**从易到难,逐步推进**, 一次只做一件事,少做无关的事。
|
||||
|
||||
如果你不知道这是什么意思,我们以可能发生的 `str` 成员缓冲区溢出问题来作为例子。KISS 法则告诉你,你应该使用 `assert(0)`, 就算不"得体"地处理上述问题,仍然不会影响表达式求值的核心功能的正确性。
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
[GDB 快速入门教程](https://www.bilibili.com/video/BV1EK411g7Li/)
|
||||
|
||||
### <strong>GDB 使用表</strong>
|
||||
### **GDB 使用表**
|
||||
|
||||
`run (r)`运行程序
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
`ptype`查看详细信息
|
||||
|
||||
#### <strong>TUI</strong>
|
||||
#### **TUI**
|
||||
|
||||
`ctrl + x + a`开启
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ C 艹可能没那么容易了····比如说虚函数调用,那你就不太
|
||||
|
||||
你可以写任何一个指令,他完全不会检查 也不会优化 编译器默认你知道你在干什么。
|
||||
|
||||
然后 C 编译器就会将这部分代码 <strong>原封不动地 </strong>拷贝进你的二进制代码当中
|
||||
然后 C 编译器就会将这部分代码 **原封不动地**拷贝进你的二进制代码当中
|
||||
|
||||
当然,你可以通过 RTFM 来将 C 语言的变量塞到汇编中处理。
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
引自 nju-ics-pa
|
||||
|
||||
## <strong>光玉</strong>
|
||||
## 光玉
|
||||
|
||||
想象一下你正在玩 Flappy Bird,你今晚的目标是拿到 100 分,不然就不睡觉。经过千辛万苦,你拿到了 99 分,就要看到成功的曙光的时候,你竟然失手了!你悲痛欲绝,滴血的心在呼喊着,“为什么上天要这样折磨我?为什么不让我存档?”
|
||||
|
||||
[**Play Happy Bird**](https://flappybird.io/)
|
||||
[Play Happy Bird](https://flappybird.io/)
|
||||
|
||||
想象一下你正在写代码,你今晚的目标是实现某一个新功能,不然就不睡觉。经过千辛万苦,你终于把代码写好了,保存并编译运行,你看到调试信息一行一行地在终端上输出。就要看到成功的曙光的时候,竟然发生了错误!你仔细思考,发现你之前的构思有着致命的错误,但之前正确运行的代码已经永远离你而去了。你悲痛欲绝,滴血的心在呼喊着,“为什么上天要这样折磨我?”你绝望地倒在屏幕前 ... ... 这时,你发现身边渐渐出现无数的光玉,把你包围起来,耀眼的光芒令你无法睁开眼睛 ... ... 等到你回过神来,你发现屏幕上正是那份之前正确运行的代码!但在你的记忆中,你确实经历过那悲痛欲绝的时刻 ... ... 这一切真是不可思议啊 ... ...
|
||||
|
||||
## <strong>人生如戏</strong><strong>,</strong><strong>戏如人生</strong>
|
||||
## 人生如戏,戏如人生
|
||||
|
||||
人生就像不能重玩的 Flappy Bird,但软件工程领域却并非如此,而那不可思议的光玉就是“版本控制系统”。版本控制系统给你的开发流程提供了比朋也收集的更强大的光玉,能够让你在过去和未来中随意穿梭,避免上文中的悲剧降临你的身上。
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
在本节,我们使用 `git` 进行版本控制。下面简单介绍如何使用 `git` :
|
||||
|
||||
### <strong>游戏设置</strong>
|
||||
### 游戏设置
|
||||
|
||||
首先你得安装 `git` :
|
||||
|
||||
@@ -52,7 +52,7 @@ git init
|
||||
|
||||
进行初始化。初始化后会创建一个隐藏的文件夹名为 `.git` git 会基于这个文件夹来进行版本控制功能。
|
||||
|
||||
### <strong>查看</strong><strong>commit</strong><strong>信息</strong>
|
||||
### 查看 commit 信息
|
||||
|
||||
使用
|
||||
|
||||
@@ -147,7 +147,7 @@ subject为commit概述
|
||||
|
||||
你可以使用 `git log` 查看存档记录,你应该能看到刚才编辑的注释。
|
||||
|
||||
### <strong>读档</strong><strong>(回溯到某一个 commit)</strong>
|
||||
### 读档(回溯到某一个 commit)
|
||||
|
||||
如果你遇到了上文提到的让你悲痛欲绝的情况,现在你可以使用光玉来救你一命了。首先使用 `git log` 来查看已有的存档,并决定你需要回到哪个过去。每一份存档都有一个 `hash code`,例如 `b87c512d10348fd8f1e32ddea8ec95f87215aaa5` , 你需要通过 `hash code` 来告诉 `git` 你希望读哪一个档。使用以下命令进行读档:
|
||||
|
||||
@@ -194,7 +194,7 @@ git checkout -B 分支名
|
||||
|
||||
不同的分支之间不会相互干扰,这也给项目的分布式开发带来了便利。有了分支功能,你就可以像第三视点那样在一个世界的不同时间 ( 一个分支的多个存档 ),或者是多个平行世界(多个分支)之间来回穿梭了。
|
||||
|
||||
### <strong>更多功能</strong>
|
||||
### 更多功能
|
||||
|
||||
以上介绍的是 `git` 的一些基本功能,`git` 还提供很多强大的功能,例如使用 `git diff` 比较同一个文件在不同版本中的区别,使用 `git bisect` 进行二分搜索来寻找一个 bug 在哪次提交中被引入...
|
||||
|
||||
@@ -246,13 +246,54 @@ git checkout -B 分支名
|
||||
|
||||
最后你访问[GitHub 官网](https://github.com)应该会显示你的 dashboard 管理台界面
|
||||
|
||||
#### 第二步:创建 SSH Key 并获取公钥
|
||||
#### 第二步:选择你拉取仓库的方式
|
||||
|
||||
先在 C:\Users\用户名\.ssh 下找有没有 `id_rsa` 和 `id_rsa.pub` 文件
|
||||
点开 github 某个仓库的绿油油的 `<>Code` 按钮,你会发现有三种 clone 方式。
|
||||
|
||||
分别为
|
||||
|
||||
- https(基本无配置,有图形化界面就能用)
|
||||
- ssh(有公私钥设置,没有图形化界面也能用)
|
||||
- gh-cli(github 出品的 cli 工具)
|
||||
|
||||

|
||||
|
||||
#### 【https 方法配置】账号绑定 github
|
||||
|
||||
在命令行配置好你的 id 和邮箱
|
||||
|
||||
```bash
|
||||
git config --global user.name "Zhang San" # your name
|
||||
git config --global user.email "zhangsan@foo.com" # your email
|
||||
```
|
||||
|
||||
在 GitHub 主页,找到 `New` 或者 `Create repository` 一个绿色的按钮,创建一个新的仓库
|
||||
|
||||

|
||||
|
||||
然后填上这个仓库的大名就可以创建了
|
||||
|
||||

|
||||
|
||||
进入你新创建的仓库
|
||||
|
||||
跟随新创建之后的指引,`…or create a new repository on the command line` 内他描述了如何创建一个文件夹、创建一个 README.md 的文件,然后和 github 仓库绑定。
|
||||
|
||||
push 的时候 github 会弹窗索要你的 github 账号和密码,填上去就能用了。
|
||||
|
||||
#### 【ssh 方法配置】创建 SSH Key 并获取公钥
|
||||
|
||||
先在 `C:\Users\用户名\.ssh` 下找有没有 `id_rsa` 和 `id_rsa.pub` 文件
|
||||
|
||||
:::tip
|
||||
这里 `.ssh` 文件夹是一个隐藏文件夹
|
||||
|
||||
如何显示隐藏文件夹请搜索互联网或看这篇文章 [support microsoft 显示隐藏的文件](https://support.microsoft.com/zh-cn/windows/%E6%98%BE%E7%A4%BA%E9%9A%90%E8%97%8F%E7%9A%84%E6%96%87%E4%BB%B6-0320fe58-0117-fd59-6851-9b7f9840fdb2)
|
||||
:::
|
||||
|
||||
如果有就直接跳过这一步
|
||||
|
||||
如果没有,打开 Shell(Windows 下打开 Git Bash <em>前提是你已经安装好了 git 在桌面右键应该会有 Git bash here 选项 </em>),创建 SSH Key:
|
||||
如果没有,打开 Shell(Windows 下打开 Git Bash *前提是你已经安装好了 git 在桌面右键应该会有 Git bash here 选项*),创建 SSH Key:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t rsa -C "youremail@example.com" # youremail为你注册用的电子邮件地址
|
||||
@@ -260,7 +301,7 @@ ssh-keygen -t rsa -C "youremail@example.com" # youremail为你注册用的电
|
||||
|
||||
打开 `id_rsa.pub`,复制里面的内容
|
||||
|
||||
#### 第三步:绑定 Github
|
||||
#### 【ssh 方法配置】绑定 Github
|
||||
|
||||
登陆 `GitHub`,点击右上角自己的头像,打开 `settings`
|
||||
|
||||
@@ -272,7 +313,7 @@ ssh-keygen -t rsa -C "youremail@example.com" # youremail为你注册用的电
|
||||
|
||||
然后,点 `New SSH Key`,填上任意 Title,在 Key 文本框里粘贴 `id_rsa.pub` 文件的内容即可
|
||||
|
||||
#### 第四步:创建仓库并和本地绑定
|
||||
#### 【ssh 方法配置】创建仓库并和本地绑定
|
||||
|
||||
绑定完 GitHub 然后你可以创建仓库了
|
||||
|
||||
@@ -301,8 +342,6 @@ git remote add origin git@github.com:yourname/gitexample.git
|
||||
git clone git@github.com:yourname/gitexample.git
|
||||
```
|
||||
|
||||
> 以上方法是基于 ssh 方式的,下面方法是基于 HTTPS 方式的
|
||||
|
||||
或者你可以跟随新创建之后的指引,`…or create a new repository on the command line` 内他描述了如何创建一个文件夹、创建一个 README.md 的文件,然后和 github 仓库绑定。
|
||||
|
||||

|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 什么是环境?
|
||||
|
||||
环境是<strong>包的集合</strong>,我们一般用 Anaconda 来配置虚拟环境。
|
||||
环境是**包的集合**,我们一般用 Anaconda 来配置虚拟环境。
|
||||
|
||||
[戳这里安装](https://www.anaconda.com/)
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
|
||||
打开 [Python 官方网站](https://www.python.org/),找到“Download”里的“Latest: Python 3.x.y”。
|
||||
|
||||
<strong>下载完成后,请大家按照下图的示意,务必勾选“Add Python 3.x to PATH”,然后再点击“Install Now”,等待安装完成后关闭安装程序。</strong>
|
||||
**下载完成后,请大家按照下图的示意,务必勾选“Add Python 3.x to PATH”,然后再点击“Install Now”,等待安装完成后关闭安装程序。**
|
||||
|
||||
<strong>注意:windows11 安装好后 命令行输入 python 可能会跳到 Microsoft 应用商店 可在 customize installation(自定义安装)next 勾选 install for all users</strong>
|
||||
**注意:windows11 安装好后 命令行输入 python 可能会跳到 Microsoft 应用商店 可在 customize installation(自定义安装)next 勾选 install for all users**
|
||||
|
||||
<font size=5><strong>GNU/Linux 系统</strong></font>
|
||||
<font size=5>**GNU/Linux 系统**</font>
|
||||
|
||||
在终端输入 `sudo apt install python3` 即可完成 Python3 的全部安装流程
|
||||
|
||||
|
||||
@@ -135,9 +135,9 @@ s
|
||||
|
||||
可变对象的示例包括列表和字典。不可变对象的示例包括元组和函数。
|
||||
|
||||
我们假定已经知道了如何使用 `==` 运算符来检查两个表达式的计算结果是否<strong>相同</strong>。
|
||||
我们假定已经知道了如何使用 `==` 运算符来检查两个表达式的计算结果是否**相同**。
|
||||
|
||||
我们现在引入一个新的比较运算符 `is`,它检查两个表达式的计算结果是否<strong>相同</strong>。
|
||||
我们现在引入一个新的比较运算符 `is`,它检查两个表达式的计算结果是否**相同**。
|
||||
|
||||
```python
|
||||
>>> 2 + 2 == 3 + 1
|
||||
|
||||
@@ -37,7 +37,7 @@ iterator = iter(iterable)
|
||||
# do something
|
||||
```
|
||||
|
||||
- 首先,在可迭代对象上调用内置 `iter` 函数以创建对应的<em>迭代器</em>。
|
||||
- 首先,在可迭代对象上调用内置 `iter` 函数以创建对应的*迭代器*。
|
||||
- 要获取序列中的下一个元素,在此迭代器上调用内置 `next` 函数。
|
||||
|
||||
如果没有下一个元素了,怎么办?
|
||||
@@ -76,7 +76,7 @@ StopIteration
|
||||
|
||||
## 英语练习,对迭代器的类比
|
||||
|
||||
<strong>Analogy</strong>: An iterable is like a book (one can flip through the pages) and an iterator for a book would be a bookmark (saves the position and can locate the next page). Calling `iter` on a book gives you a new bookmark independent of other bookmarks, but calling `iter` on a bookmark gives you the bookmark itself, without changing its position at all. Calling `next` on the bookmark moves it to the next page, but does not change the pages in the book. Calling `next` on the book wouldn't make sense semantically. We can also have multiple bookmarks, all independent of each other.
|
||||
**Analogy**: An iterable is like a book (one can flip through the pages) and an iterator for a book would be a bookmark (saves the position and can locate the next page). Calling `iter` on a book gives you a new bookmark independent of other bookmarks, but calling `iter` on a bookmark gives you the bookmark itself, without changing its position at all. Calling `next` on the bookmark moves it to the next page, but does not change the pages in the book. Calling `next` on the book wouldn't make sense semantically. We can also have multiple bookmarks, all independent of each other.
|
||||
|
||||
## 生成器:懒人迭代器!
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CS61A Sec1
|
||||
|
||||
<strong>观前须知:</strong>
|
||||
**观前须知:**
|
||||
|
||||
本章节内容基于 Berkeley 大学的教材 [Composing Programs](http://www.composingprograms.com/) by [John DeNero](http://www.denero.org/),并在此基础上做了部分修改,是在[知识共享协议](https://creativecommons.org/licenses/by-nc-sa/3.0/deed.zh)下所许可的。
|
||||
|
||||
@@ -28,7 +28,7 @@ from urllib.request import urlopen
|
||||
|
||||
这个 Python 代码是一个导入语句,加载了在互联网上访问数据的功能。实际上,它提供了一个叫做 urlopen 的函数,它可以在一个[统一资源定位符(URL)](https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL)上访问内容,可以通过它来访问互联网上的数据。
|
||||
|
||||
<strong>语句和表达式</strong>
|
||||
**语句和表达式**
|
||||
|
||||
Python 代码由语句和表达式组成。大体上,计算机程序由以下指令组成
|
||||
|
||||
@@ -37,7 +37,7 @@ Python 代码由语句和表达式组成。大体上,计算机程序由以下
|
||||
|
||||
语句通常描述行动;当 Python 解释器执行一个语句时,它执行相应的动作。另一方面,表达式通常描述的是计算;当 Python 评估一个表达式时,它计算该表达式的值。在这篇文章下,介绍了几种类型的声明和表达方式。
|
||||
|
||||
<strong>赋值语句</strong>
|
||||
**赋值语句**
|
||||
|
||||
```python
|
||||
shakespeare = urlopen('http://www.composingprograms.com/shakespeare.txt')
|
||||
@@ -47,7 +47,7 @@ shakespeare = urlopen('http://www.composingprograms.com/shakespeare.txt')
|
||||
|
||||
将变量名 `shakespeare` 用 `=` 和后面的表达式的值联系起来。该表达式将 `urlopen` 函数应用于一个 URL,该 URL 包含威廉 - 莎士比亚 37 部戏剧的完整文本,全部保存在一个文本文件中。
|
||||
|
||||
<strong>函数</strong>
|
||||
**函数**
|
||||
|
||||
函数封装了操作数据的逻辑。
|
||||
|
||||
@@ -64,7 +64,7 @@ shakespeare = urlopen('http://www.composingprograms.com/shakespeare.txt')
|
||||
|
||||
你可能不了解 `urlopen` 这个函数背后的逻辑,但这不影响你去调用这个函数,这就是函数封装的好处之一。
|
||||
|
||||
<strong>因此,函数是本章节关注的重点。</strong>
|
||||
**因此,函数是本章节关注的重点。**
|
||||
|
||||
我们来看另一个赋值语句:
|
||||
|
||||
@@ -74,7 +74,7 @@ words = set(shakespeare.read().decode().split())
|
||||
|
||||
这个语句将名字词与莎士比亚戏剧中出现的所有出现过的词(重复出现的词只统计一次)的集合联系起来,其中有 33,721(?) 个词。上述语句包含一个读取、解码和分割的命令链,每个命令都在一个中间计算实体上操作:我们从打开的 URL 中读取数据,然后将数据解码成文本,最后将文本分割成单词。所有这些词都被放在一个集合(Set,Python 中的一种数据类型)中。
|
||||
|
||||
<strong>对象</strong>
|
||||
**对象**
|
||||
|
||||
前文中提到的 Set,不仅仅是数据类型,也是一个对象。对象用一种能同时处理两者复杂性的方式,把数据和操作该数据的逻辑无缝衔接在一起。
|
||||
|
||||
@@ -91,7 +91,7 @@ words = set(shakespeare.read().decode().split())
|
||||
|
||||
这是一个复合表达式,其值是所有长度为 6 的、本身和反向拼写都在原集合中的词组成的集合。其中的 `w[::-1]` 是一种隐式表达,它枚举了 `w` 中的所有字母,但因为 `step = -1` 规定了步长是反方向的。
|
||||
|
||||
<strong>解释器</strong>
|
||||
**解释器**
|
||||
|
||||
计算复合表达式需要一个精确的程序,以可预测的方式解释代码。一个能实现程序和计算符合表达式的程序被称为解释器;没错,其实解释器是程序(可能再写一篇文章来讲讲解释器和编译器的区别?)
|
||||
|
||||
@@ -109,9 +109,9 @@ words = set(shakespeare.read().decode().split())
|
||||
|
||||
每种强大的语言都有三种这样的机制:
|
||||
|
||||
- <strong>原始的表达式和语句</strong>,代表了该语言提供的最简单的构建模块。
|
||||
- <strong>组合的方式</strong>,由较简单的元素建立成复合元素。
|
||||
- <strong>抽象的手段</strong>,通过它,复合元素可以作为单位被命名和操作。
|
||||
- **原始的表达式和语句**,代表了该语言提供的最简单的构建模块。
|
||||
- **组合的方式**,由较简单的元素建立成复合元素。
|
||||
- **抽象的手段**,通过它,复合元素可以作为单位被命名和操作。
|
||||
|
||||
在编程中,我们处理两种元素:函数和数据。(很快就会发现,它们其实并不那么明显)。不那么正式地说,数据是我们想要操作的东西,而函数描述了操作数据的规则。因此,任何强大的编程语言都应该能够描述原始数据和原始函数,以及有一些方法来组合和抽象函数和数据。
|
||||
|
||||
@@ -133,20 +133,20 @@ words = set(shakespeare.read().decode().split())
|
||||
0.9921875
|
||||
```
|
||||
|
||||
这些数学表达式使用<em>中缀</em>符号,其中<em>运算符</em>(例如,+,-,*,或/)出现在<em>操作数</em>(数字)之间。Python 包括许多形成复合表达式的方法。我们不会试图立即列举它们,而是会随着我们的学习引入新的表达形式,以及它们所支持的语言特性。
|
||||
这些数学表达式使用*中缀*符号,其中*运算符*(例如,+,-,*,或/)出现在*操作数*(数字)之间。Python 包括许多形成复合表达式的方法。我们不会试图立即列举它们,而是会随着我们的学习引入新的表达形式,以及它们所支持的语言特性。
|
||||
|
||||
最重要的一种复合表达式是<em>调用表达式</em>,它将一个函数应用于一些参数。回顾一下代数,函数的数学概念是一个从一些自变量到因变量的映射。例如,一个求最大值的函数将其的多个输入映射到当中最大值的一个单一的输出。Python 表达函数应用的方式与传统数学中相同。
|
||||
最重要的一种复合表达式是*调用表达式*,它将一个函数应用于一些参数。回顾一下代数,函数的数学概念是一个从一些自变量到因变量的映射。例如,一个求最大值的函数将其的多个输入映射到当中最大值的一个单一的输出。Python 表达函数应用的方式与传统数学中相同。
|
||||
|
||||
```python
|
||||
>>> max(7.5, 9.5)
|
||||
9.5
|
||||
```
|
||||
|
||||
这个调用表达式有子表达式:<em>操作符</em>是括号前的表达式,它包含了一个用逗号分隔的<em>操作数</em>列表。
|
||||
这个调用表达式有子表达式:*操作符*是括号前的表达式,它包含了一个用逗号分隔的*操作数*列表。
|
||||
|
||||

|
||||
|
||||
运算符指定了一个<em>函数</em>。当这个调用表达式被评估时,我们说对<em>参数</em>`7.5` 和 `9.5`<em>调用</em>函数 `max`,并<em>返回</em>一个 9.5 的<em>返回值</em>。
|
||||
运算符指定了一个*函数*。当这个调用表达式被评估时,我们说对*参数*`7.5` 和 `9.5`*调用*函数 `max`,并*返回*一个 9.5 的*返回值*。
|
||||
|
||||
调用表达式中参数的顺序很重要。例如,函数 `pow` 计算第一个参数的第二个参数次方。
|
||||
|
||||
@@ -166,7 +166,7 @@ words = set(shakespeare.read().decode().split())
|
||||
|
||||
不会产生歧义,因为函数名总是优先于其参数。
|
||||
|
||||
此外,函数符号以一种直接的方式延伸到<em>嵌套表达式</em>,其中的元素本身就是复合表达式。在嵌套的调用表达式中,与复合的中缀表达式不同,嵌套的结构在括号中是完全明确的。
|
||||
此外,函数符号以一种直接的方式延伸到*嵌套表达式*,其中的元素本身就是复合表达式。在嵌套的调用表达式中,与复合的中缀表达式不同,嵌套的结构在括号中是完全明确的。
|
||||
|
||||
```python
|
||||
>>> max(min(1, -2), min(pow(3, 5), -4))
|
||||
@@ -205,7 +205,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
|
||||
### 变量名和环境
|
||||
|
||||
编程语言的一个关键方面是它提供了使用变量名来指代计算对象的手段。如果一个值被赋予了一个变量名,我们就说这个变量名与这个值<em>绑定</em>了。
|
||||
编程语言的一个关键方面是它提供了使用变量名来指代计算对象的手段。如果一个值被赋予了一个变量名,我们就说这个变量名与这个值*绑定*了。
|
||||
|
||||
在 Python 中,我们可以使用赋值语句建立新的绑定,其中包含左边的变量名 `=` 右边的值。
|
||||
|
||||
@@ -225,9 +225,9 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
1.0002380197528042
|
||||
```
|
||||
|
||||
`=` 符号在 Python(以及许多其他语言)中被称为<em>赋值</em>操作符。赋值是我们最简单的抽象手段,因为它允许我们使用简单的名称来指代复合操作的结果。用这种方式,复杂的程序就是通过一步一步地建立复杂度越来越高的计算对象来构建的。
|
||||
`=` 符号在 Python(以及许多其他语言)中被称为*赋值*操作符。赋值是我们最简单的抽象手段,因为它允许我们使用简单的名称来指代复合操作的结果。用这种方式,复杂的程序就是通过一步一步地建立复杂度越来越高的计算对象来构建的。
|
||||
|
||||
将变量名与值绑定,然后通过变量名检索这些值意味着解释器必须保持某种内存,以跟踪变量名、值和绑定。这样的内存空间被称为<em>环境</em>。
|
||||
将变量名与值绑定,然后通过变量名检索这些值意味着解释器必须保持某种内存,以跟踪变量名、值和绑定。这样的内存空间被称为*环境*。
|
||||
|
||||
变量名也可以被绑定到函数上。例如,变量名 `max` 与我们使用的求最大值的函数绑定。与数字不同的是,函数在呈现为文本时很棘手,所以当被要求描述一个函数时,Python 会打印一个识别描述。
|
||||
|
||||
@@ -256,7 +256,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
2
|
||||
```
|
||||
|
||||
在 Python 中,名称通常被称为<em>变量名</em>或<em>变量</em>,因为它们在执行程序的过程中可能被绑定到不同的值。当一个名称通过赋值被绑定到一个新的值时,它就不再被绑定到任何以前的值。人们甚至可以将内置名称与新值绑定。
|
||||
在 Python 中,名称通常被称为*变量名*或*变量*,因为它们在执行程序的过程中可能被绑定到不同的值。当一个名称通过赋值被绑定到一个新的值时,它就不再被绑定到任何以前的值。人们甚至可以将内置名称与新值绑定。
|
||||
|
||||
```python
|
||||
>>> max = 5
|
||||
@@ -315,7 +315,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
1. 计算运算符和操作数的子表达式,然后
|
||||
2. 将作为运算符子表达式的值的函数应用于作为运算符子表达式的值的参数。
|
||||
|
||||
即使这是个简单的程序也说明了关于一般过程的一些重要观点。第一步决定了为了完成一个调用表达式的计算过程,我们必须首先计算其他表达式。因此,计算过程在本质上是<em>递归的</em>;也就是说,作为其步骤之一,它也包括调用规则本身。
|
||||
即使这是个简单的程序也说明了关于一般过程的一些重要观点。第一步决定了为了完成一个调用表达式的计算过程,我们必须首先计算其他表达式。因此,计算过程在本质上是*递归的*;也就是说,作为其步骤之一,它也包括调用规则本身。
|
||||
|
||||
例如,计算
|
||||
|
||||
@@ -328,7 +328,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
|
||||

|
||||
|
||||
这张插图被称为<em>表达式树</em>。在计算机科学中,树(Tree,一种数据结构,我们将在后续的章节中进行讨论)通常是自上而下生长的。树中每一点的对象被称为节点;在这张插图的情况下,节点是与值配对的表达式。
|
||||
这张插图被称为*表达式树*。在计算机科学中,树(Tree,一种数据结构,我们将在后续的章节中进行讨论)通常是自上而下生长的。树中每一点的对象被称为节点;在这张插图的情况下,节点是与值配对的表达式。
|
||||
|
||||
计算它的根,即顶部的完整表达式,需要首先计算作为其子表达式的分支。叶表达式(即没有分支的节点)代表函数或数字。内部节点有两个部分:我们的计算规则所适用的调用表达式,以及该表达式的结果。从这棵树的计算来看,我们可以想象操作数的值是向上渗滤的,从末端节点开始,然后在越来越高的层级上进行组合。
|
||||
|
||||
@@ -357,7 +357,7 @@ Python 定义了大量的函数,包括上一节中提到的运算符函数,
|
||||
|
||||
这个语句不返回一个值,也不在某些参数上调用一个函数,因为赋值的目的是将一个变量名绑定到一个值上。
|
||||
|
||||
一般来说,赋值语句不是被计算而是<em>被执行</em>;它们不产生一个值,而是做一些改变。每种类型的表达式或语句都有自己的计算或执行过程。
|
||||
一般来说,赋值语句不是被计算而是*被执行*;它们不产生一个值,而是做一些改变。每种类型的表达式或语句都有自己的计算或执行过程。
|
||||
|
||||
### 纯函数和非纯函数
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ cs61a 绝对是一个挑战,但是我们都希望你学习并且成功,所
|
||||
- 再次强调,这门课对于大多数学生来说都不是一门简单的课程,你可能会感到不知所措。给你的小伙伴们发送一个信息,并与班上的其他同学取得联系; 一起完成作业或一起学习准备考试,只要你不违反课程大纲中规定的诚实学习的[课程政策](https://inst.eecs.berkeley.edu/~cs61a/fa22/articles/about/#academic-misconduct)。
|
||||
- 当遇到问题时,试着组织语言来解释你遇到困难的地方。
|
||||
|
||||
- 这并不需要一个找到懂得如何解决问题的人 (或者甚至是一个人——这种做法通常被称为**橡皮鸭**,因为你可以把一只橡皮鸭当作你的练习对象) ,因为主要目标是让你弄清楚你自己的想法,弄清楚你的理解和代码到底在哪里卡住了。从那里你可以专注于那一部分,以便更好地理解。
|
||||
- 这并不需要一个找到懂得如何解决问题的人 (或者甚至是一个人——这种做法通常被称为**橡皮鸭**,因为你可以把一只橡皮鸭当作你的练习对象) ,因为主要目标是让你弄清楚你自己的想法,弄清楚你的理解和代码到底在哪里卡住了。这样你可以知道应该专注于哪一部分,以便更好地理解。
|
||||
- 做所有的 hw(或者至少认真地尝试)。我们没有给出很多 hw 问题,但那些我们给你的可能会发现是具有挑战性、耗时且有回报的。
|
||||
- 做所有的 lab。其中大部分的设计更多的是作为一个课程材料的介绍,并可能需要半个小时左右的时间。这是一个熟悉新的知识点的好时机。
|
||||
|
||||
@@ -99,15 +99,21 @@ cs61a 绝对是一个挑战,但是我们都希望你学习并且成功,所
|
||||
|
||||
要使用 Ok 来运行指定函数的 doctests,请运行以下命令
|
||||
|
||||
```python3 ok -q <specified function> #会和你要补充的代码一起给出```
|
||||
```bash
|
||||
python3 ok -q <specified function> #会和你要补充的代码一起给出
|
||||
```
|
||||
|
||||
默认情况下,只有没有通过的测试才会出现。你可以使用-v 选项来显示所有的测试,包括你已经通过的测试
|
||||
|
||||
```python3 ok -v```
|
||||
```bash
|
||||
python3 ok -v
|
||||
```
|
||||
|
||||
有时我们会看到类似这样的 ok 指令
|
||||
|
||||
```python3 ok -q control -u```
|
||||
```bash
|
||||
python3 ok -q control -u
|
||||
```
|
||||
|
||||
在终端中输入后,需要按照要求回答问题,有些时候会做为某些函数测试的前置条件
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 3.X 聊聊设计模式和程序设计
|
||||
|
||||
<em>Author: yyl
|
||||
Last revised 2022/08/07</em>
|
||||
*Author: yyl
|
||||
Last revised 2022/08/07*
|
||||
|
||||
## 前言
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# VMware 的安装与安装 Ubuntu22.04 系统
|
||||
|
||||
::: warning 💡 与 wsl 安装二选一 安装了 wsl 不用 VMware
|
||||
::: warning 💡
|
||||
一般与 wsl 安装二选一,因为都是虚拟系统,安装了 wsl 不用 VMware
|
||||
|
||||
文章撰写于 2022 年,可能其中的一些内容已过时。
|
||||
:::
|
||||
|
||||
首先下载 VMware
|
||||
|
||||
如果是 pro16 版本(key <strong>ZF3R0-FHED2-M80TY-8QYGC-NPKYF</strong>)
|
||||
如果是 pro16 版本(key **ZF3R0-FHED2-M80TY-8QYGC-NPKYF**)
|
||||
|
||||
如果是 pro17 版本(key <strong>JU090-6039P-08409-8J0QH-2YR7F</strong><strong> </strong>)
|
||||
如果是 pro17 版本(key **JU090-6039P-08409-8J0QH-2YR7F**)
|
||||
|
||||
本文写的时候用的版本是 pro16,但目前已经更新到 pro17 所以来更新个 key(如下安装与 16 版本无异)
|
||||
|
||||
@@ -25,9 +28,14 @@
|
||||
|
||||
安装过后点许可证 输上面的 key 激活
|
||||
|
||||
[https://mirror.nju.edu.cn/ubuntu-releases/22.04/ubuntu-22.04.2-desktop-amd64.iso](https://mirror.nju.edu.cn/ubuntu-releases/22.04/ubuntu-22.04.2-desktop-amd64.iso)
|
||||
[https://mirror.nju.edu.cn/ubuntu-releases/22.04](https://mirror.nju.edu.cn/ubuntu-releases/22.04)
|
||||
|
||||
去这里下载 Ubuntu22.04 镜像包 iso 选择 `ubuntu-<version>-desktop-amd64.iso`
|
||||
|
||||
:::tip
|
||||
这里推荐使用多线程下载器下载,比如 [IDM](../2.高效学习/2.2优雅的使用工具.md),如果直接用浏览器下载(线程少)可能会出现下载慢、下载失败的情况。
|
||||
:::
|
||||
|
||||
去这里下载 Ubuntu22.04 镜像包 iso
|
||||
|
||||
下好回到 VMware
|
||||
|
||||
@@ -101,7 +109,7 @@
|
||||
|
||||

|
||||
|
||||
<strong>至此 恭喜安装完成!</strong>
|
||||
**至此 恭喜安装完成!**
|
||||
|
||||
之后就可以在桌面上右键
|
||||
|
||||
@@ -109,4 +117,4 @@
|
||||
|
||||
打开命令行
|
||||
|
||||
<strong>开始你的 Linux 学习吧</strong>
|
||||
**开始你的 Linux 学习吧**
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
::: warning 💡与 VMware 安装二选一 安装了 VMware 不用 wsl
|
||||
:::
|
||||
|
||||
先说<strong>坏处</strong>:
|
||||
先说**坏处**:
|
||||
|
||||
1. 开启 hyperv 的后果是 如果你电脑装模拟器玩手游的话 装了 hyperv 你的模拟器是打不开的(目前只有 `蓝叠国际版HyperV版`(性能很差)支持共存 hyperv 和模拟器)
|
||||
2. WSL 很难装辣 安装过程中会出很多 bug 需要你自行 STFW
|
||||
|
||||
## <strong>官方文档</strong>
|
||||
## **官方文档**
|
||||
|
||||
## [史上最全的 WSL 安装教程](https://blog.csdn.net/wojiuguowei/article/details/122100090)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||

|
||||
|
||||
如果你的 windows 版本为<strong>家庭版</strong> 那么 hyperv 选项是没有的
|
||||
如果你的 windows 版本为**家庭版** 那么 hyperv 选项是没有的
|
||||
|
||||
你需要右键以管理员权限打开以下脚本来强行开启 hyperv
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ Linux 命令行中的命令使用格式都是相同的:
|
||||
man 3 freopen
|
||||
```
|
||||
|
||||
### <strong>统计代码行数</strong>
|
||||
### **统计代码行数**
|
||||
|
||||
第一个例子是统计一个目录中 (包含子目录) 中的代码行数。如果想知道当前目录下究竟有多少行的代码,就可以在命令行中键入如下命令:
|
||||
|
||||
@@ -65,7 +65,7 @@ find . | grep '\.c$\|\.h$' | xargs wc -l
|
||||
|
||||
我们最后的任务是统计这些文件所占用的总行数,此时可以用 `man` 查看 `wc` 命令。`wc` 命令的 `-l` 选项能够计算代码的行数。`xargs` 命令十分特殊,它能够将标准输入转换为参数,传送给第一个参数所指定的程序。所以,代码中的 `xargs wc -l` 就等价于执行 `wc -l aaa.c bbb.c include/ccc.h ...`, 最终完成代码行数统计。
|
||||
|
||||
### <strong>统计磁盘使用情况</strong>
|
||||
### **统计磁盘使用情况**
|
||||
|
||||
以下命令统计 `/usr/share` 目录下各个目录所占用的磁盘空间:
|
||||
|
||||
@@ -83,7 +83,7 @@ du -sc /usr/share/* | sort -nr | more
|
||||
|
||||
此时将会看到输出的前几行结果。`more` 工具使用空格翻页,并可以用 `q` 键在中途退出。`less` 工具则更为强大,不仅可以向下翻页,还可以向上翻页,同样使用 `q` 键退出。这里还有一个[关于 less 的小故事](http://en.wikipedia.org/wiki/Less_(Unix)).
|
||||
|
||||
### <strong>在 Linux 下编写 Hello World 程序</strong>
|
||||
### **在 Linux 下编写 Hello World 程序**
|
||||
|
||||
Linux 中用户的主目录是 `/home/用户名称`, 如果你的用户名是 `user`, 你的主目录就是 `/home/user`. 用户的 `home` 目录可以用波浪符号 `~` 替代,例如临时文件目录 `/home/user/Templates` 可以简写为 `~/Templates`. 现在我们就可以进入主目录并编辑文件了。如果 `Templates` 目录不存在,可以通过 `mkdir` 命令创建它:
|
||||
|
||||
@@ -100,7 +100,7 @@ cd Templates
|
||||
|
||||
可以完成目录的切换。注意在输入目录名时,`tab` 键可以提供联想。
|
||||
|
||||
#### <strong> 你感到键入困难吗?</strong>
|
||||
#### ** 你感到键入困难吗?**
|
||||
|
||||
::: warning 💡 你可能会经常要在终端里输入类似于
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ apt-get install vim
|
||||
|
||||
如果您能够遵循上述步骤,并且坚持使用新的编辑器完成您所有的文本编辑任务,那么学习一个复杂的代码编辑器的过程一般是这样的:头两个小时,您会学习到编辑器的基本操作,例如打开和编辑文件、保存与退出、浏览缓冲区。当学习时间累计达到 20 个小时之后,您使用新编辑器的效率应该已经和使用老编辑器一样快。在此之后,其益处开始显现:有了足够的知识和肌肉记忆后,使用新编辑器将大大节省你的时间。而现代文本编辑器都是些复杂且强大的工具,永远有新东西可学:学的越多,效率越高。
|
||||
|
||||
## <strong>Vim 的哲学</strong>
|
||||
## **Vim 的哲学**
|
||||
|
||||
在编程的时候,你会把大量时间花在阅读/编辑而不是在写代码上。所以,Vim 是一个_多模态_编辑 器:它对于插入文字和操纵文字有不同的模式。Vim 是可编程的(可以使用 Vimscript 或者像 Python 一样的其他程序语言),Vim 的接口本身也是一个程序语言:键入操作(以及其助记名)是命令,这些命令也是可组合的。Vim 避免了使用鼠标,因为那样太慢了;Vim 甚至避免用 上下左右键因为那样需要太多的手指移动。
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<font color=green>大白话 -> 提取后的逻辑链条</font> -> <font color=red>科研写作 -> 英文翻译</font>
|
||||
|
||||
<strong>干了什么:</strong>
|
||||
**干了什么:**
|
||||
|
||||
1. 如果没有想清楚要做的是什么,要写什么,可以先用大白话,在草稿上写,有利于理清思路,抽丝剥茧
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
|
||||
之所以要用大白话是因为基础的不足,如果有一定功底的人,可能先天写出来文字自带规范性,所以仅供大家参考)
|
||||
|
||||
<strong>表达规范性:</strong>
|
||||
**表达规范性:**
|
||||
|
||||
此处的方法论为一句话,则是从模仿到超越的浑然天成。
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
author:廖总
|
||||
|
||||
<em>Last revised 2023/04/18</em>
|
||||
*Last revised 2023/04/18*
|
||||
|
||||
先声夺人:AI 时代最大的陷阱,就是盲目考察 AI 能为我们做什么,而不去考虑我们能为 AI 提供什么
|
||||
|
||||
## <em>免责声明</em>
|
||||
## *免责声明*
|
||||
|
||||
本文纯文本量达 16k(我也不知道字数统计的 28k 是怎么来的),在这 游离散乱的主线 和 支离破碎的文字 中挣扎,可能浪费您生命中宝贵的十数分钟。
|
||||
|
||||
@@ -616,7 +616,7 @@ AutoGPT 主要特性如下:
|
||||
|
||||
作为正题的回归,我们需要重新考虑什么是一个 AI,一个能帮助我们的 AI 应当处于什么样的现实形态?
|
||||
|
||||
<em>我们需要的 </em><em>AI</em><em> 仅仅是大语言模型吗?如果是,它能帮我们做什么呢?如果不是,那 AI 的实质是什么呢?</em>
|
||||
*我们需要的 **AI** 仅仅是大语言模型吗?如果是,它能帮我们做什么呢?如果不是,那 AI 的实质是什么呢?*
|
||||
|
||||
我首先武断地认为,我们需要的 AI,并不是一个语言模型实体,而是一个复杂智能系统
|
||||
|
||||
@@ -803,7 +803,7 @@ Generative Agents 的知觉设计:关联性难题
|
||||
|
||||
仅就这方面而言,作为一个方向性的倡议,对知觉系统的开发可能分为以下步骤
|
||||
|
||||
#### <em>数据处理/管理</em>
|
||||
#### *数据处理/管理*
|
||||
|
||||
- 对 办公文件/数据 构建通用读取接口
|
||||
- 以同类信息为单位,设计通用的字段(由人设计和管理,AI 能力尚不至此)
|
||||
@@ -827,7 +827,7 @@ Generative Agents 的知觉设计:关联性难题
|
||||
- 如储存进 mongoDB
|
||||
- (设计孪生数据的自动更新机制)
|
||||
|
||||
#### <em>知觉系统驱动</em>
|
||||
#### *知觉系统驱动*
|
||||
|
||||
- 基于上述索引数据库,以视图为单位进行访问,并设计 视图 2 Prompt 的转化格式
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
author:Marlene
|
||||
|
||||
<em>Last revised 2023/07/26</em>
|
||||
*Last revised 2023/07/26*
|
||||
|
||||
## 引言
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
### CV(计算机视觉)
|
||||
|
||||
计算机视觉旨在<strong>用计算机模拟人类处理图片信息的能力</strong>,就比如这里有一张图片——手写数字 9
|
||||
计算机视觉旨在**用计算机模拟人类处理图片信息的能力**,就比如这里有一张图片——手写数字 9
|
||||
|
||||

|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
相信你通过上面简单的介绍应该能够了解到计算机视觉是在干嘛了,接下来我会举几个相对复杂的例子来让大家了解一下目前的 cv 是在做怎样的研究:
|
||||
|
||||
::: warning 🐱 <strong>图像分割</strong>是在图片中对物体分类,并且把它们所对应的位置标示出来。下图就是把人的五官,面部皮肤和头发分割出来,效 (小) 果 (丑) 图如下:
|
||||
::: warning 🐱 **图像分割**是在图片中对物体分类,并且把它们所对应的位置标示出来。下图就是把人的五官,面部皮肤和头发分割出来,效 (小) 果 (丑) 图如下:
|
||||
:::
|
||||
|
||||
<table>
|
||||
@@ -29,15 +29,15 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
::: warning 🐱 <strong>图像生成</strong>相信大家一定不陌生,NovalAI 在 2022 年火的一塌糊涂,我觉得不需要我过多赘述,对它 (Diffusion model) 的改进工作也是层出不穷,这里就放一张由可控姿势网络 (ControlNet) 生成的图片吧:
|
||||
::: warning 🐱 **图像生成**相信大家一定不陌生,NovalAI 在 2022 年火的一塌糊涂,我觉得不需要我过多赘述,对它 (Diffusion model) 的改进工作也是层出不穷,这里就放一张由可控姿势网络 (ControlNet) 生成的图片吧:
|
||||
:::
|
||||
|
||||

|
||||
|
||||
::: warning 🐱 <strong>三维重建</strong>也是很多研究者关注的方向,指的是传入对同一物体不同视角的照片,来生成 3D 建模的任务。这方面比图像处理更加前沿并且难度更大。具体见[4.6.5.4 神经辐射场 (NeRF)](4.6.5.4%E7%A5%9E%E7%BB%8F%E8%BE%90%E5%B0%84%E5%9C%BA(NeRF).md) 章节。
|
||||
::: warning 🐱 **三维重建**也是很多研究者关注的方向,指的是传入对同一物体不同视角的照片,来生成 3D 建模的任务。这方面比图像处理更加前沿并且难度更大。具体见[4.6.5.4 神经辐射场 (NeRF)](4.6.5.4%E7%A5%9E%E7%BB%8F%E8%BE%90%E5%B0%84%E5%9C%BA(NeRF).md) 章节。
|
||||
:::
|
||||
|
||||
如果对计算机视觉有兴趣,可以通过以下路线进行学习:深度学习快速入门—> 经典网络。本块内容的主要撰写者之一<strong>SRT 社团</strong>多数成员主要从事 CV 方向研究,欢迎与我们交流。
|
||||
如果对计算机视觉有兴趣,可以通过以下路线进行学习:深度学习快速入门—> 经典网络。本块内容的主要撰写者之一**SRT 社团**多数成员主要从事 CV 方向研究,欢迎与我们交流。
|
||||
|
||||
### NLP(自然语言处理)
|
||||
|
||||
@@ -56,9 +56,9 @@
|
||||
|
||||
而多模态就是让计算机能够将不同模态的信息相对应,一种常用的方法就是让计算机把图片的内容和文本的内容理解为相同的语义(在这个领域一般用一个较长的向量来表示语义)。
|
||||
|
||||
也就是说我<strong>传入一张狗子的照片经过模型得到的向量</strong>与<strong>DOG 这个单词经过模型得到的向量</strong>相近。
|
||||
也就是说我**传入一张狗子的照片经过模型得到的向量**与**DOG 这个单词经过模型得到的向量**相近。
|
||||
|
||||
具体的任务比如说<strong>图片问答</strong>,传入一张图片,问 AI 这张图片里面有几只猫猫,它们是什么颜色,它告诉我有一只猫猫,是橙色的:
|
||||
具体的任务比如说**图片问答**,传入一张图片,问 AI 这张图片里面有几只猫猫,它们是什么颜色,它告诉我有一只猫猫,是橙色的:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
|
||||
|
||||

|
||||
|
||||
<em>θ 表示当前的权重值。J(θ) 表示「当前权重的代价」。</em>
|
||||
*θ 表示当前的权重值。J(θ) 表示「当前权重的代价」。*
|
||||
|
||||
这个等式表示,在当前权重值下,我们估价程序的偏离程度。
|
||||
|
||||
@@ -203,7 +203,7 @@ def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
|
||||
|
||||
不过幸运的是,有很多办法来处理这种情况。有许多机器学习算法可以处理非线性数据。除此之外,灵活使用线性回归也能拟合更复杂的线条。在所有的情况下,寻找最优权重这一基本思路依然适用。
|
||||
|
||||
<strong>如果你还是无法理解,你可以将 cost 类比为你出错误的程度,而数学科学家找到各种方法来降低这种程度,当程度降到最低时,我们就可以知道我们要求的数值了</strong>
|
||||
**如果你还是无法理解,你可以将 cost 类比为你出错误的程度,而数学科学家找到各种方法来降低这种程度,当程度降到最低时,我们就可以知道我们要求的数值了**
|
||||
|
||||
另外,我忽略了过拟合(overfitting)的概念。得到一组能完美预测原始数据集中房价的权重组很简单,但用这组权重组来预测原始数据集之外的任何新房屋其实都不怎么准确。这也是有许多解决办法的(如[正则化](https://link.zhihu.com/?target=http%3A//en.wikipedia.org/wiki/Regularization_%2528mathematics%2529%23Regularization_in_statistics_and_machine_learning)以及使用[交叉验证](https://link.zhihu.com/?target=http%3A//en.wikipedia.org/wiki/Cross-validation_%2528statistics%2529)的数据集)。学习如何应对这一问题,是学习如何成功应用机器学习技术的重点之一。
|
||||
|
||||
@@ -226,7 +226,7 @@ def estimate_house_sales_price(num_of_bedrooms, sqft, neighborhood):
|
||||
|
||||

|
||||
|
||||
<em>箭头头表示了函数中的权重。</em>
|
||||
*箭头头表示了函数中的权重。*
|
||||
|
||||
然而,这个算法仅仅能用于处理一些简单的问题,就是那些输入和输出有着线性关系的问题。但如果真实价格和决定因素的关系并不是如此简单,那我们该怎么办?比如说,地段对于大户型和小户型的房屋有很大影响,然而对中等户型的房屋并没有太大影响。那我们该怎么在我们的模型中收集这种复杂的信息呢?
|
||||
|
||||
@@ -392,13 +392,13 @@ model.add(Activation('relu'))# 激活函数,你可以理解为加上这个东
|
||||
|
||||
### 卷积是如何工作的
|
||||
|
||||
之前我们提到过,我们可以把一整张图片当做一串数字输入到神经网络里面。不同的是,这次我们会利用<strong>平移不变性</strong>的概念来把这件事做得更智能。
|
||||
之前我们提到过,我们可以把一整张图片当做一串数字输入到神经网络里面。不同的是,这次我们会利用**平移不变性**的概念来把这件事做得更智能。
|
||||
|
||||
当然也有最新研究说卷积不具备平移不变性,但是我这里使用这个概念是为了大伙更好的理解,举个例子:你将 8 无论放在左上角还是左下角都改变不了他是 8 的事实
|
||||
|
||||

|
||||
|
||||
我们将一张图像分成这么多个小块,然后输入神经网络中的是一个小块。<em>每次判断一张小图块。</em>
|
||||
我们将一张图像分成这么多个小块,然后输入神经网络中的是一个小块。*每次判断一张小图块。*
|
||||
|
||||
然而,有一个非常重要的不同:对于每个小图块,我们会使用同样的神经网络权重。换一句话来说,我们平等对待每一个小图块。如果哪个小图块有任何异常出现,我们就认为这个图块是「异常」
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
- 最优解 (Optimal Solution)
|
||||
|
||||
- 在所有解决方案中路径成本最低的解决方案。
|
||||
- 在搜索过程中,数据通常存储在<strong>节点 (Node)</strong> 中,节点是一种包含以下数据的数据结构:
|
||||
- 在搜索过程中,数据通常存储在**节点 (Node)** 中,节点是一种包含以下数据的数据结构:
|
||||
|
||||
- 状态——state
|
||||
- 其父节点,通过该父节点生成当前节点——parent node
|
||||
@@ -314,6 +314,6 @@ def remove(self):
|
||||
|
||||
- 深度限制的极大极小算法 (Depth-Limited Minimax)
|
||||
|
||||
- 总共有$255168$个可能的井字棋游戏,以及有$10^{29000}$个可能的国际象棋中游戏。到目前为止,最小最大算法需要生成从某个点到<strong>终端条件</strong>的所有假设游戏状态。虽然计算所有的井字棋游戏状态对现代计算机来说并不是一个挑战,但目前用来计算国际象棋是不可能的。
|
||||
- 总共有$255168$个可能的井字棋游戏,以及有$10^{29000}$个可能的国际象棋中游戏。到目前为止,最小最大算法需要生成从某个点到**终端条件**的所有假设游戏状态。虽然计算所有的井字棋游戏状态对现代计算机来说并不是一个挑战,但目前用来计算国际象棋是不可能的。
|
||||
|
||||
- 深度限制的 Minimax 算法在停止之前只考虑预先定义的移动次数,而从未达到终端状态。然而,这不允许获得每个动作的精确值,因为假设的游戏还没有结束。为了解决这个问题,深度限制 Minimax 依赖于一个评估函数,该函数从给定状态估计游戏的预期效用,或者换句话说,为状态赋值。例如,在国际象棋游戏中,效用函数会将棋盘的当前配置作为输入,尝试评估其预期效用(基于每个玩家拥有的棋子及其在棋盘上的位置),然后返回一个正值或负值,表示棋盘对一个玩家对另一个玩家的有利程度。这些值可以用来决定正确的操作,并且评估函数越好,依赖它的 Minimax 算法就越好。
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
人类根据现有的知识进行推理并得出结论。表示知识并从中得出结论的概念也被用于人工智能中,在本章中我们将探讨如何实现这种行为。
|
||||
|
||||
::: warning <font size=5><strong>说好的 AI 呢?怎么感觉越来越偏了?</strong></font>
|
||||
::: warning <font size=5>**说好的 AI 呢?怎么感觉越来越偏了?**</font>
|
||||
|
||||
如果有这样的疑问的同学,可能存在一定的误区,认为人工智能就是局限在深度学习的算法或者说机器学习的部分算法上,其实这是对这个领域一个巨大的误解。
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
- 命题符号通常是用于表示命题的字母$P、Q、R$
|
||||
- 逻辑连接词 (Logical Connectives)
|
||||
- 逻辑连接词是连接命题符号的逻辑符号,以便以更复杂的方式对世界进行推理。
|
||||
- <strong>Not</strong><strong> </strong><strong>(</strong>$\lnot$<strong>)</strong> 逻辑非:命题真值的反转。例如,如果 $P$:“正在下雨”,那么 $¬P$:“没有下雨”。
|
||||
- **Not**** ****(**$\lnot$**)** 逻辑非:命题真值的反转。例如,如果 $P$:“正在下雨”,那么 $¬P$:“没有下雨”。
|
||||
- 真值表用于将所有可能的真值赋值与命题进行比较。该工具将帮助我们更好地理解与不同逻辑连接词相关联的命题的真值。例如,下面是我们的第一个真值表:
|
||||
|
||||
| $P$ | $\lnot P$ |
|
||||
@@ -50,7 +50,7 @@
|
||||
| false(0) | true(1) |
|
||||
| true(1) | false(0) |
|
||||
|
||||
- <strong>And(</strong>$\land$<strong>)</strong> 逻辑乘 (合取): 连接两个不同的命题。当这两个命题$P$和$Q$用$∧$连接时,得到的命题$P∧Q$只有在$P$和$Q$都为真的情况下才为真。
|
||||
- **And(**$\land$**)** 逻辑乘 (合取): 连接两个不同的命题。当这两个命题$P$和$Q$用$∧$连接时,得到的命题$P∧Q$只有在$P$和$Q$都为真的情况下才为真。
|
||||
|
||||
| $P$ | $Q$ | $P\land Q$ |
|
||||
| --- | --- | ---------- |
|
||||
@@ -59,7 +59,7 @@
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 1 |
|
||||
|
||||
- <strong>Or(</strong>$\lor$<strong>)</strong> 逻辑和 (析取): 只要它的任何一个参数为真,它就为真。这意味着要使 $P ∨ Q$为真,$P$ 或 $Q$ 中至少有一个必须为真。
|
||||
- **Or(**$\lor$**)** 逻辑和 (析取): 只要它的任何一个参数为真,它就为真。这意味着要使 $P ∨ Q$为真,$P$ 或 $Q$ 中至少有一个必须为真。
|
||||
|
||||
| $P$ | $Q$ | $P\lor Q$ |
|
||||
| --- | --- | --------- |
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
- 值得一提的是,Or 有两种类型:同或 Or 和异或 Or。在异或中,如果$P\lor Q$为真,则$P∧Q$为假。也就是说,一个异或要求它只有一个论点为真,而不要求两者都为真。如果$P、Q$或$P∧Q$中的任何一个为真,则包含或为真。在 Or($\lor$) 的情况下,意图是一个包含的 Or。
|
||||
|
||||
- <strong>Implication (→)</strong> 逻辑蕴含:表示“如果$P$,则$Q$的结构。例如,如果$P$:“正在下雨”,$Q$:“我在室内”,则$P→ Q$的意思是“如果下雨,那么我在室内。”在$P$的情况下,意味着$Q$,$P$被称为前件,$Q$ 被称为后件。
|
||||
- **Implication (→)** 逻辑蕴含:表示“如果$P$,则$Q$的结构。例如,如果$P$:“正在下雨”,$Q$:“我在室内”,则$P→ Q$的意思是“如果下雨,那么我在室内。”在$P$的情况下,意味着$Q$,$P$被称为前件,$Q$ 被称为后件。
|
||||
|
||||
- 当前件为真时,在后件为真的情况下,整个蕴含逻辑为真(这是有道理的:如果下雨,我在室内,那么“如果下雨,那么我在室内”这句话是真的)。当前件为真时,如果后件为假,则蕴含逻辑为假(如果下雨时我在外面,那么“如果下雨,那么我在室内”这句话是假的)。然而,当前件为假时,无论后件如何,蕴含逻辑总是真的。这有时可能是一个令人困惑的概念。从逻辑上讲,我们不能从蕴含中学到任何东西$(P→ Q)$如果前件 ($P$) 为假。看一下我们的例子,如果没有下雨,这个蕴含逻辑并没有说我是否在室内的问题。我可能是一个室内型的人,即使不下雨也不在外面走,或者我可能是一个室外型的人,不下雨的时候一直在外面。当前件是假的,我们说蕴含逻辑是真的。
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 1 |
|
||||
|
||||
|
||||
- <strong>Biconditional (</strong>$\leftrightarrow$<strong>)</strong> :是一个双向的蕴含。你可以把它读成“如果且仅当”$P↔ Q$等同$P→ Q$和$Q→ P$合在一起。例如,如果$P$:“正在下雨”,$Q$:“我在室内”,那么$P↔ Q$的意思是“如果下雨,那么我在室内”,“如果我在室内,那么就在下雨。”这意味着我们可以推断出比简单蕴含更多的东西。如果$P$为假,那么$Q$ 也为假;如果不下雨,我们知道我也不在室内。
|
||||
|
||||
| $P$ | $Q$ | $P\leftrightarrow Q$ |
|
||||
|
||||
@@ -340,11 +340,11 @@ print(Counter(data))
|
||||
|
||||
- 到目前为止,我们已经研究了概率问题,给出了我们观察到的一些信息。在这种范式中,时间的维度没有以任何方式表示。然而,许多任务确实依赖于时间维度,例如预测。为了表示时间变量,我们将创建一个新的变量$X$,并根据感兴趣的事件对其进行更改,使$X_t$ 是当前事件,$X_{t+1}$ 是下一个事件,依此类推。为了能够预测未来的事件,我们将使用马尔可夫模型。
|
||||
|
||||
### 马尔科夫假设 (<strong>The Markov Assumption</strong>)
|
||||
### 马尔科夫假设 (**The Markov Assumption**)
|
||||
|
||||
- 马尔科夫假设是一个假设,即当前状态只取决于有限的固定数量的先前状态。想想预测天气的任务。在理论上,我们可以使用过去一年的所有数据来预测明天的天气。然而,这是不可行的,一方面是因为这需要计算能力,另一方面是因为可能没有关于基于 365 天前天气的明天天气的条件概率的信息。使用马尔科夫假设,我们限制了我们以前的状态(例如,在预测明天的天气时,我们要考虑多少个以前的日子),从而使这个任务变得可控。这意味着我们可能会得到感兴趣的概率的一个更粗略的近似值,但这往往足以满足我们的需要。此外,我们可以根据最后一个事件的信息来使用马尔可夫模型(例如,根据今天的天气来预测明天的天气)。
|
||||
|
||||
### 马尔科夫链 (<strong>Markov Chain</strong>)
|
||||
### 马尔科夫链 (**Markov Chain**)
|
||||
|
||||
- 马尔科夫链是一个随机变量的序列,每个变量的分布都遵循马尔科夫假设。也就是说,链中的每个事件的发生都是基于之前事件的概率。
|
||||
- 为了构建马尔可夫链,我们需要一个过渡模型,该模型将根据当前事件的可能值来指定下一个事件的概率分布。
|
||||
@@ -399,9 +399,9 @@ print(model.sample(50))
|
||||
|
||||
- 基于隐马尔科夫模型,可以实现多种任务:
|
||||
|
||||
- 筛选 Filtering: 给定从开始到现在的观察结果,计算出<strong>当前</strong>状态的概率分布。例如,给从从特定时间开始到今天人们带伞的信息,我们产生一个今天是否下雨的概率分布。
|
||||
- 预测 Prediction: 给定从开始到现在的观察,计算<strong>未来</strong>状态的概率分布。
|
||||
- 平滑化 Smoothing: 给定从开始到现在的观察,计算<strong>过去</strong>状态的概率分布。例如,鉴于今天人们带了雨伞,计算昨天下雨的概率。
|
||||
- 筛选 Filtering: 给定从开始到现在的观察结果,计算出**当前**状态的概率分布。例如,给从从特定时间开始到今天人们带伞的信息,我们产生一个今天是否下雨的概率分布。
|
||||
- 预测 Prediction: 给定从开始到现在的观察,计算**未来**状态的概率分布。
|
||||
- 平滑化 Smoothing: 给定从开始到现在的观察,计算**过去**状态的概率分布。例如,鉴于今天人们带了雨伞,计算昨天下雨的概率。
|
||||
- 最可能的解释 Most likely explanation: 鉴于从开始到现在的观察,计算最可能的事件顺序。
|
||||
- 最可能的解释任务可用于语音识别等过程,根据多个波形,人工智能推断出给这些波形带来的最有可能的单词或音节的序列。接下来是一个隐马尔科夫模型的 Python 实现,我们将用于最可能的解释任务:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 你可能会需要的术语介绍
|
||||
|
||||
众所周知,一个领域的黑话对新人来说是比较不友好的,为此我从知乎上找了一篇黑话大赏(bushi)做了点改良放在这里。如果遇到看不懂的词了可以来这找找。<strong>在系统学习之前可以先无视这篇文章,遇到问题再来找找</strong><u>。</u>
|
||||
众所周知,一个领域的黑话对新人来说是比较不友好的,为此我从知乎上找了一篇黑话大赏(bushi)做了点改良放在这里。如果遇到看不懂的词了可以来这找找。**在系统学习之前可以先无视这篇文章,遇到问题再来找找**<u>。</u>
|
||||
|
||||
> 作者:Young<br/>链接:[https://www.zhihu.com/question/469612040/answer/2008770105](https://www.zhihu.com/question/469612040/answer/2008770105)<br/>来源:知乎
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
- 体素:我把世界变成 MC 了,世界是一堆方块,他们在不同视角下有各自的颜色和透明度
|
||||
- 点云:我每采样一次得到一个点,由这些点去表示我要的物体,不太直观,来张图
|
||||
|
||||
这是我用照片重建的独角兽<strong>稀疏</strong>点云,红色的不用管,是照相机视角(图不够多,巨糊)
|
||||
这是我用照片重建的独角兽**稀疏**点云,红色的不用管,是照相机视角(图不够多,巨糊)
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 深度学习快速入门
|
||||
|
||||
## <strong>刘二大人(Pytorch)</strong>
|
||||
## **刘二大人(Pytorch)**
|
||||
|
||||
## 速成课:人工智能
|
||||
[【速成课:人工智能】Ai - [21 集全/中英双语] - Artificial Intelligence_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1P7411r7Dw)
|
||||
@@ -62,7 +62,7 @@ Crash course 的课程,可以基本了解pytorch的内容,但是当然有很
|
||||
|
||||
## torch 我还不会呢!
|
||||
|
||||
学会一个<strong>庞大并且高度封装</strong>的包并不是一蹴而就的,我们建议从实践开始,比如说自己搭建一个神经网络来实现 MNIST 的分类。在使用这些函数和类的过程中你能更快地掌握它们的方法。
|
||||
学会一个**庞大并且高度封装**的包并不是一蹴而就的,我们建议从实践开始,比如说自己搭建一个神经网络来实现 MNIST 的分类。在使用这些函数和类的过程中你能更快地掌握它们的方法。
|
||||
|
||||
# 关于梯度下降算法:
|
||||
|
||||
@@ -103,12 +103,12 @@ Crash course 的课程,可以基本了解pytorch的内容,但是当然有很
|
||||
|
||||
# 接下来干什么?
|
||||
|
||||
- <strong>我想学 CV !!!!!!</strong>
|
||||
- **我想学 CV !!!!!!**
|
||||
|
||||
你可以在 CV 模块中找到[4.6.5.3CV中的经典网络](4.6.5.3CV%E4%B8%AD%E7%9A%84%E7%BB%8F%E5%85%B8%E7%BD%91%E7%BB%9C.md) ,这里是一些最最经典的论文,我们推荐你阅读它们的原文并且复现它们的代码,这可以同时锻炼你的<strong>coding 能力和论文阅读能力</strong>,在阅读前,请参见[如何读论文](../1.%E6%9D%AD%E7%94%B5%E7%94%9F%E5%AD%98%E6%8C%87%E5%8D%97/1.10%E5%A6%82%E4%BD%95%E8%AF%BB%E8%AE%BA%E6%96%87.md) 。本模块的撰写者<strong>SRT 社团</strong>主要从事 CV 方向的研究,遇到问题欢迎与我们交流。(你都完成这些了不至于找不到我们的联系方式吧~)<strong>如果你读完了经典网络模块,你可以在它的最后找到接下来的学习路线~</strong>
|
||||
你可以在 CV 模块中找到[4.6.5.3CV中的经典网络](4.6.5.3CV%E4%B8%AD%E7%9A%84%E7%BB%8F%E5%85%B8%E7%BD%91%E7%BB%9C.md) ,这里是一些最最经典的论文,我们推荐你阅读它们的原文并且复现它们的代码,这可以同时锻炼你的**coding 能力和论文阅读能力**,在阅读前,请参见[如何读论文](../1.%E6%9D%AD%E7%94%B5%E7%94%9F%E5%AD%98%E6%8C%87%E5%8D%97/1.10%E5%A6%82%E4%BD%95%E8%AF%BB%E8%AE%BA%E6%96%87.md) 。本模块的撰写者**SRT 社团**主要从事 CV 方向的研究,遇到问题欢迎与我们交流。(你都完成这些了不至于找不到我们的联系方式吧~)**如果你读完了经典网络模块,你可以在它的最后找到接下来的学习路线~**
|
||||
|
||||
- <strong>我想做</strong><strong>NLP</strong><strong> !!!!!!</strong>
|
||||
- **我想做****NLP**** !!!!!!**
|
||||
|
||||
NLP 研究方向庞大且复杂,若直接从 GPT 系列开始不免有些过于困难。我们建议你从了解 NLP 的任务开始,在有足够的基础后开始学习 RNN,LSTM 基准方法后向 [4.6.7Transformer](4.6.7Transformer.md) 进发 ,这个方法广泛运用在几乎所有深度学习领域,尤其是 NLP 的前沿研究已经无法离开 Transformer 了 hhhh。这个模块中我们也加入了一些 Transformer 的改进工作,包括 NLP,CV,和多模态
|
||||
|
||||
- <strong>如果你想做多模态,对比学习等</strong>,请同时了解 CV 和 NLP 模块。这将是你后续知识的基础。多模态我们没有完善的讲义推出,对比学习可以参见[4.6.8对比学习](4.6.8%E5%AF%B9%E6%AF%94%E5%AD%A6%E4%B9%A0.md) 。这是撰写者之一的论文阅读笔记,不保证准确性与理解是否准确,可以作为论文阅读路线图来参考~
|
||||
- **如果你想做多模态,对比学习等**,请同时了解 CV 和 NLP 模块。这将是你后续知识的基础。多模态我们没有完善的讲义推出,对比学习可以参见[4.6.8对比学习](4.6.8%E5%AF%B9%E6%AF%94%E5%AD%A6%E4%B9%A0.md) 。这是撰写者之一的论文阅读笔记,不保证准确性与理解是否准确,可以作为论文阅读路线图来参考~
|
||||
|
||||
@@ -17,7 +17,7 @@ Pip 在通过 python 官网下载 python 并安装时可以选择同时安装 pi
|
||||
3. 对于 Windows 用户,在 C:\Users\xx\pip 目录下(没有 pip 目录就新建)创建一个 pip.ini 文件,并将下面代码块中内容复制进去:
|
||||
4. 对于 Linux 用户,同样在~/.pip/pip.conf 进行配置。如果没有.pip 目录就新建,然后将下面代码块中内容复制进去:
|
||||
|
||||
```
|
||||
```bash
|
||||
[global]
|
||||
index-url = http://pypi.douban.com/simple
|
||||
extra-index-url = https://pypi.mirrors.ustc.edu.cn/simple/
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||

|
||||
|
||||
#### (a)Image classification <strong>图像分类</strong>
|
||||
#### (a)Image classification **图像分类**
|
||||
|
||||
- 识别这个图片整体所属的类别,解决的是"what"问题,给这个图片打上相应的标签,在 a 图内标签是 `bottle,cup,cube`,其他类型的图片也都有它们自己的标签,然后把这些打上标签的图片带进网络结构里作为训练集训练。
|
||||
|
||||
#### (b)Object localization <strong>目标检测</strong>(对象定位)
|
||||
#### (b)Object localization **目标检测**(对象定位)
|
||||
|
||||
- 识别图片中各个物体所在的位置,解决的是"where"问题,此处还细分两个问题:
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
|
||||

|
||||
|
||||
#### (c)Semantic segmentation <strong>语义分割</strong>
|
||||
#### (c)Semantic segmentation **语义分割**
|
||||
|
||||
- 语义分割需要进一步判断图像中哪些像素属于哪个目标(进阶目标检测)。
|
||||
- 看图右下角两个 `cube` 是连在一块的 并没有分出哪一部分是哪一个的 `cube`
|
||||
|
||||
#### (d)Instance segmentation <strong>实例分割</strong>
|
||||
#### (d)Instance segmentation **实例分割**
|
||||
|
||||
- 实例分割需要区分出哪些像素属于第一个物体、哪些像素属于第二个物体,即目标检测 + 语义分割。
|
||||
- 看图右下角两个 `cube` 是分开的
|
||||
|
||||
@@ -20,7 +20,7 @@ PyTorch 中的 Dataset 类是一个抽象类,它可以用来表示数据集。
|
||||
|
||||
下面我们来编写一个简单的例子,看下如何使用 Dataset 类定义一个 Tensor 类型的数据集。
|
||||
|
||||
```
|
||||
```python
|
||||
import torch
|
||||
from torch.utils.data import Dataset
|
||||
|
||||
@@ -43,7 +43,7 @@ class MyDataset(Dataset):
|
||||
|
||||
然后我们来看一下如何调用刚才定义的数据集。首先随机生成一个 10*3 维的数据 Tensor,然后生成 10 维的标签 Tensor,与数据 Tensor 相对应。利用这两个 Tensor,生成一个 MyDataset 的对象。查看数据集的大小可以直接用 len() 函数,索引调用数据可以直接使用下标。
|
||||
|
||||
```
|
||||
```python
|
||||
# 生成数据
|
||||
data_tensor = torch.randn(10, 3)
|
||||
target_tensor = torch.randint(2, (10,)) # 标签是0或1
|
||||
@@ -67,7 +67,7 @@ DataLoader 是一个迭代器,最基本的使用方法就是传入一个 Datas
|
||||
|
||||
DataLoader 类的调用方式如下:
|
||||
|
||||
```
|
||||
```python
|
||||
from torch.utils.data import DataLoader
|
||||
tensor_dataloader = DataLoader(dataset=my_dataset, # 传入的数据集, 必须参数
|
||||
batch_size=2, # 输出的batch大小
|
||||
@@ -107,7 +107,7 @@ One batch tensor data: [tensor([[ 0.9451, -0.4923, -1.8178],
|
||||
- shuffle:bool 类型,在每个 epoch 开始的时候,是否对数据进行重新打乱;
|
||||
- num_workers:int 类型,加载数据的进程数,0 意味着所有的数据都会被加载进主进程,默认为 0。
|
||||
|
||||
<strong>思考题</strong>
|
||||
**思考题**
|
||||
|
||||
按照上述代码,One batch tensor data 的输出是否正确,若不正确,为什么?
|
||||
|
||||
@@ -143,7 +143,7 @@ MNIST 数据集是 ubyte 格式存储,我们先将“训练集图片”解析
|
||||
|
||||
以 MNIST 为例,我们可以用如下方式调用:
|
||||
|
||||
```
|
||||
```python
|
||||
# 以MNIST为例
|
||||
import torchvision
|
||||
mnist_dataset = torchvision.datasets.MNIST(root='./data',
|
||||
@@ -173,7 +173,7 @@ torchvision.datasets.MNIST 是一个类,对它进行实例化,即可返回
|
||||
|
||||
如果想要查看 mnist_dataset 中的具体内容,我们需要把它转化为列表。(如果 IOPub data rate 超限,可以只加载测试集数据,令 train=False)
|
||||
|
||||
```
|
||||
```python
|
||||
mnist_dataset_list = list(mnist_dataset)
|
||||
print(mnist_dataset_list)
|
||||
```
|
||||
@@ -182,7 +182,7 @@ print(mnist_dataset_list)
|
||||
|
||||
这里图像数据是 PIL.Image.Image 类型的,这种类型可以直接在 Jupyter 中显示出来。显示一条数据的代码如下。
|
||||
|
||||
```
|
||||
```python
|
||||
display(mnist_dataset_list[0][0])
|
||||
print("Image label is:", mnist_dataset_list[0][1])
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ Torchvision 库中的 torchvision.transforms 包中提供了常用的图像操
|
||||
|
||||
我们来看一个具体的例子加深理解。将图片进行一下数据类型的相互转换。具体代码如下:
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
|
||||
@@ -58,7 +58,7 @@ print(type(img2))
|
||||
'''
|
||||
```
|
||||
|
||||
首先用读取图片,查看一下图片的类型为 PIL.JpegImagePlugin.JpegImageFile,这里需要注意,<strong>PIL.JpegImagePlugin.JpegImageFile 类是 PIL.Image.Image 类的子类</strong>。然后,用 transforms.ToTensor() 将 PIL.Image 转换为 Tensor。最后,再将 Tensor 转换回 PIL.Image。
|
||||
首先用读取图片,查看一下图片的类型为 PIL.JpegImagePlugin.JpegImageFile,这里需要注意,**PIL.JpegImagePlugin.JpegImageFile 类是 PIL.Image.Image 类的子类**。然后,用 transforms.ToTensor() 将 PIL.Image 转换为 Tensor。最后,再将 Tensor 转换回 PIL.Image。
|
||||
|
||||
## 对 PIL.Image 和 Tensor 进行变换
|
||||
|
||||
@@ -68,7 +68,7 @@ torchvision.transforms 提供了丰富的图像变换方法,例如:改变尺
|
||||
|
||||
将输入的 PIL Image 或 Tensor 尺寸调整为给定的尺寸,具体定义为:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.Resize(size, interpolation=2)
|
||||
```
|
||||
|
||||
@@ -81,7 +81,7 @@ torchvision.transforms.Resize(size, interpolation=2)
|
||||
|
||||
在 resize 之后呢,一般会接一个 crop 操作,crop 到指定的大小。对于高与宽接近的图片来说,这么做问题不大,但是高与宽的差距较大时,就会 crop 掉很多有用的信息。关于这一点,我们在后续的图像分类部分还会遇到,到时我在详细展开。
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
|
||||
@@ -105,7 +105,7 @@ torchvision.transforms 提供了多种剪裁方法,例如中心剪裁、随机
|
||||
|
||||
先说中心剪裁,在中心裁剪指定的 PIL Image 或 Tensor,其定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.CenterCrop(size)
|
||||
```
|
||||
|
||||
@@ -113,7 +113,7 @@ torchvision.transforms.CenterCrop(size)
|
||||
|
||||
然后是随机剪裁,在一个随机位置剪裁指定的 PIL Image 或 Tensor,定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.RandomCrop(size, padding=None)
|
||||
```
|
||||
|
||||
@@ -121,13 +121,13 @@ torchvision.transforms.RandomCrop(size, padding=None)
|
||||
|
||||
最后要说的是 FiveCrop,我们将给定的 PIL Image 或 Tensor ,分别从四角和中心进行剪裁,共剪裁成五块,定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.FiveCrop(size)
|
||||
```
|
||||
|
||||
size 可以是 int 或 tuple,用法同上。掌握了各种剪裁的定义和参数用法以后,我们来看一下这些剪裁操作具体如何调用,代码如下:
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
|
||||
@@ -158,13 +158,13 @@ for img in imgs:
|
||||
|
||||
以概率 p 随机水平翻转图像,定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.RandomHorizontalFlip(p=0.5)
|
||||
```
|
||||
|
||||
以概率 p 随机垂直翻转图像,定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.RandomVerticalFlip(p=0.5)
|
||||
```
|
||||
|
||||
@@ -172,7 +172,7 @@ torchvision.transforms.RandomVerticalFlip(p=0.5)
|
||||
|
||||
这里的随机翻转,是为数据增强提供方便。如果想要必须执行翻转操作的话,将 p 设置为 1 即可。图片翻转代码如下:
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
|
||||
@@ -202,7 +202,7 @@ display(img2)
|
||||
|
||||
标准化是指每一个数据点减去所在通道的平均值,再除以所在通道的标准差,数学的计算公式:output=(input−mean)/std
|
||||
|
||||
而对图像进行标准化,就是对图像的每个通道利用均值和标准差进行正则化。这样做的目的,是<strong>为了保证数据集中所有的图像分布都相似,这样在训练的时候更容易收敛,既加快了训练速度,也提高了训练效果</strong>。
|
||||
而对图像进行标准化,就是对图像的每个通道利用均值和标准差进行正则化。这样做的目的,是**为了保证数据集中所有的图像分布都相似,这样在训练的时候更容易收敛,既加快了训练速度,也提高了训练效果**。
|
||||
|
||||
让我来解释一下:首先,标准化是一个常规做法,可以理解为无脑进行标准化后再训练的效果,大概率要好于不进行标准化。
|
||||
|
||||
@@ -212,7 +212,7 @@ display(img2)
|
||||
|
||||
torchvision.transforms 提供了对 Tensor 进行标准化的函数,定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.Normalize(mean, std, inplace=False)
|
||||
```
|
||||
|
||||
@@ -224,7 +224,7 @@ torchvision.transforms.Normalize(mean, std, inplace=False)
|
||||
|
||||
我们来看看以 (R, G, B) 均值和标准差均为 (0.5, 0.5, 0.5) 来标准化图片后,是什么效果:
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from torchvision import transforms
|
||||
|
||||
@@ -254,7 +254,7 @@ display(img_norm)
|
||||
|
||||
Compose 类是将多个变换组合到一起,它的定义如下:
|
||||
|
||||
```
|
||||
```python
|
||||
torchvision.transforms.Compose(transforms)
|
||||
```
|
||||
|
||||
@@ -262,7 +262,7 @@ torchvision.transforms.Compose(transforms)
|
||||
|
||||
我们还是结合例子动手试试,如果我们想要将图片变为 200*200 像素大小,并且随机裁切成 80 像素的正方形。那么我们可以组合 Resize 和 RandomCrop 变换,具体代码如下所示:
|
||||
|
||||
```
|
||||
```python
|
||||
from PIL import Image
|
||||
from IPython.display import display
|
||||
from torchvision import transforms
|
||||
@@ -292,7 +292,7 @@ Compose 类是未来我们在实际项目中经常要使用到的类,结合 to
|
||||
|
||||
我们还是以读取 MNIST 数据集为例,看下如何在读取数据的同时,完成数据预处理等操作。具体代码如下:
|
||||
|
||||
```
|
||||
```python
|
||||
from torchvision import transforms
|
||||
from torchvision import datasets
|
||||
|
||||
@@ -320,7 +320,7 @@ print(type(item[0]))
|
||||
|
||||
我们下面先来看看,在图像分类实战中使用的 transform,可以感受一下实际使用的 transforms 是什么样子:
|
||||
|
||||
```
|
||||
```python
|
||||
transform = transforms.Compose([
|
||||
transforms.RandomResizedCrop(dest_image_size),
|
||||
transforms.RandomHorizontalFlip(),
|
||||
|
||||
@@ -10,17 +10,17 @@ AlexNet 有 6 千万个参数和 650,000 个神经元。
|
||||
|
||||
[论文](http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf)
|
||||
|
||||
### <strong>网络框架图</strong>
|
||||
### **网络框架图**
|
||||
|
||||

|
||||
|
||||
### <strong>使用 ReLU 激活函数代替 tanh</strong>
|
||||
### **使用 ReLU 激活函数代替 tanh**
|
||||
|
||||
在当时,标准的神经元激活函数是 tanh()函数,这种饱和的非线性函数在梯度下降的时候要比非饱和的非线性函数慢得多,因此,在 AlexNet 中使用 ReLU 函数作为激活函数。
|
||||
|
||||

|
||||
|
||||
### <strong>采用 Dropout 防止过拟合</strong>
|
||||
### **采用 Dropout 防止过拟合**
|
||||
|
||||
dropout 方法会遍历网络的每一层,并设置消除神经网络中节点的概率。假设网络中的每一层,每个节点都以抛硬币的方式设置概率,每个节点得以保留和消除的概率都是 0.5,设置完节点概率,我们会消除一些节点,然后删除掉从该节点进出的连线,最后得到一个节点更少,规模更小的网络(如下图所示),然后再用反向传播方法进行训练。
|
||||
|
||||
@@ -32,7 +32,7 @@ dropout 方法会遍历网络的每一层,并设置消除神经网络中节点
|
||||
|
||||
###
|
||||
|
||||
### <strong>视频讲解</strong>
|
||||
### **视频讲解**
|
||||
|
||||
# 思考
|
||||
|
||||
@@ -40,7 +40,7 @@ dropout 方法会遍历网络的每一层,并设置消除神经网络中节点
|
||||
|
||||
AlexNet 中有着卷积和 MLP 两种不同的网络结构,那两者之间有着何种区别和联系呢?(可以从两者的权值矩阵去思考)
|
||||
|
||||
### <strong>思考 2</strong>
|
||||
### **思考 2**
|
||||
|
||||
卷积中有一个叫感受野的概念,是什么意思呢?不同的感受野对网络有什么影响?
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
图像分割领域的开山之作。
|
||||
|
||||
首次将<strong>End-to-End</strong>的思想应用在了 CV 领域。
|
||||
首次将**End-to-End**的思想应用在了 CV 领域。
|
||||
|
||||
[知乎](https://zhuanlan.zhihu.com/p/30195134)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
FCN 对图像进行像素级的分类,从而解决了语义级别的图像分割问题。与经典的 CNN 在卷积层之后使用全连接层得到固定长度的特征向量进行分类不同,FCN 可以接受任意尺寸的输入图像,采用反卷积层对最后一个卷积层的 feature map 进行上采样, 使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生了一个预测, 同时保留了原始输入图像中的空间信息, 最后在上采样的特征图上进行逐像素分类。
|
||||
|
||||
<strong>简单的来说,FCN 与 CNN 的区域在把于 CNN 最后的全连接层换成卷积层,输出的是一张已经 Label 好的图片。</strong>
|
||||
**简单的来说,FCN 与 CNN 的区域在把于 CNN 最后的全连接层换成卷积层,输出的是一张已经 Label 好的图片。**
|
||||
|
||||
### 反卷积
|
||||
|
||||
|
||||
@@ -8,13 +8,13 @@ NeRF 想做这样一件事,不需要中间三维重建的过程,仅根据位
|
||||
|
||||

|
||||
|
||||
你可以看到,这 100 张图片是对一个乐高推土机的多角度拍摄结果。我们需要的是一个可<strong>以获取这个推土机在任意角度下拍摄的图片</strong>的模型。如图所示:
|
||||
你可以看到,这 100 张图片是对一个乐高推土机的多角度拍摄结果。我们需要的是一个可**以获取这个推土机在任意角度下拍摄的图片**的模型。如图所示:
|
||||
|
||||

|
||||
|
||||
现在来看 NeRF 网络:
|
||||
|
||||
在 NeRF 中,我们把空间<strong>认为是一个个的小方块叠成的空间</strong>(可以理解为 MC)每一个方块有以下属性:
|
||||
在 NeRF 中,我们把空间**认为是一个个的小方块叠成的空间**(可以理解为 MC)每一个方块有以下属性:
|
||||
|
||||
- 3 个位置坐标(x,y,z)
|
||||
- 透明度$\sigma$
|
||||
@@ -24,27 +24,27 @@ NeRF 想做这样一件事,不需要中间三维重建的过程,仅根据位
|
||||
|
||||
## 得到模型
|
||||
|
||||
我们需要的是每个视角下的图片,可以理解为从一个视角发射<strong>光线</strong>,<u>一根光线对应一个像素点</u>。这些光线穿透路径上的所有方块,把这些方块上的属性信息以某种方式累计,就能得到这个像素的颜色。这是 一个已有的公式,只要我们获得每个小方块的颜色信息和不透明度,我们就能知道这个角度下的视图。(这个我们后面介绍)
|
||||
我们需要的是每个视角下的图片,可以理解为从一个视角发射**光线**,<u>一根光线对应一个像素点</u>。这些光线穿透路径上的所有方块,把这些方块上的属性信息以某种方式累计,就能得到这个像素的颜色。这是 一个已有的公式,只要我们获得每个小方块的颜色信息和不透明度,我们就能知道这个角度下的视图。(这个我们后面介绍)
|
||||
|
||||
现在的难点在于:我们不知道<strong>每个小方块的颜色信息</strong>(因为颜色会随着观察角度变化)。众所周知,算法解决不了的问题就扔给神经网络试试啦~
|
||||
现在的难点在于:我们不知道**每个小方块的颜色信息**(因为颜色会随着观察角度变化)。众所周知,算法解决不了的问题就扔给神经网络试试啦~
|
||||
|
||||
<strong>为了获取根据角度变化而变化的颜色信息,我们选择了神经网络。</strong>
|
||||
**为了获取根据角度变化而变化的颜色信息,我们选择了神经网络。**
|
||||
|
||||
<strong>这个网络的输入是:</strong>
|
||||
**这个网络的输入是:**
|
||||
|
||||
- 小方块的位置坐标(x,y,z)
|
||||
- 观察角度(以二维坐标表示两个偏转角)
|
||||
|
||||
<strong>这个网络的输出是:</strong>
|
||||
**这个网络的输出是:**
|
||||
|
||||
- 对应的小方块的 RGB 信息
|
||||
- 不透明度
|
||||
|
||||

|
||||
|
||||
在这里,作者选择了最简单的 MLP,因此,<strong>这是一个输入为 5 维,输出为 4 维向量</strong>($R,G,B,\sigma$)的简单网络,值得注意的是,不透明度与观察角度无关,这里在网络中进行了特殊处理,让这个值与后两维无关。
|
||||
在这里,作者选择了最简单的 MLP,因此,**这是一个输入为 5 维,输出为 4 维向量**($R,G,B,\sigma$)的简单网络,值得注意的是,不透明度与观察角度无关,这里在网络中进行了特殊处理,让这个值与后两维无关。
|
||||
|
||||
<strong>现在我们能够输入坐标和视角信息得到小方块的颜色和不透明度,我们就可以对光线穿过的小方块进行计算了。</strong>
|
||||
**现在我们能够输入坐标和视角信息得到小方块的颜色和不透明度,我们就可以对光线穿过的小方块进行计算了。**
|
||||
|
||||
## 进行渲染
|
||||
|
||||
@@ -54,9 +54,9 @@ NeRF 想做这样一件事,不需要中间三维重建的过程,仅根据位
|
||||
|
||||
这个公式对光线上的所有小方块的颜色进行加权求和,权重是关于不透明度$\sigma$的一个函数$T(\sigma)$,不透明度在[0,1]之间,越不透明这个值越大。也就是越不透明,占的颜色比重越高,比如空气的$\sigma$就接近于 0,乐高本身就接近 1。而求和的结果就是这个光线对应像素的颜色。
|
||||
|
||||
这里展开说一下$T(\sigma)$,我们把不透明度理解为光线在这个小方块被阻止的概率,越不透明,越容易阻挡光线,而光线一旦被阻挡,就不用计算后面的小方块颜色了。因此,我们的$T(\sigma)$就表示<strong>光线能够行进到这个小方块的概率</strong>,也就是这点之前所有小方块的$(1-\sigma)$的乘积。
|
||||
这里展开说一下$T(\sigma)$,我们把不透明度理解为光线在这个小方块被阻止的概率,越不透明,越容易阻挡光线,而光线一旦被阻挡,就不用计算后面的小方块颜色了。因此,我们的$T(\sigma)$就表示**光线能够行进到这个小方块的概率**,也就是这点之前所有小方块的$(1-\sigma)$的乘积。
|
||||
|
||||
这段要仔细看和推导,第一遍不容易直接懂。顺带一提,我们的<strong>小方块</strong>学名叫<strong>体素</strong>,<del>为了显得我们更专业一点以后就叫它体素罢</del>
|
||||
这段要仔细看和推导,第一遍不容易直接懂。顺带一提,我们的**小方块**学名叫**体素**,<del>为了显得我们更专业一点以后就叫它体素罢</del>
|
||||
|
||||

|
||||
|
||||
@@ -84,7 +84,7 @@ NeRF 想做这样一件事,不需要中间三维重建的过程,仅根据位
|
||||
|
||||
我们使用了两个网络:粗网络和精细网络。
|
||||
|
||||
粗网络就是上述采样方法用的普通网络,而<strong>粗网络输出的不透明度值会被作为一个概率分布函数</strong>,精细网络根据这个概率分布在光线上进行采样,不透明度越大的点,它的邻域被采样的概率越大,也就实现了我们要求的在实体上多采样,空气中少采样。最后精细网络输出作为结果,因此粗网络可以只求不透明度,无视颜色信息。
|
||||
粗网络就是上述采样方法用的普通网络,而**粗网络输出的不透明度值会被作为一个概率分布函数**,精细网络根据这个概率分布在光线上进行采样,不透明度越大的点,它的邻域被采样的概率越大,也就实现了我们要求的在实体上多采样,空气中少采样。最后精细网络输出作为结果,因此粗网络可以只求不透明度,无视颜色信息。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
|
||||
### 1.Pixel-nerf
|
||||
|
||||
<strong>Pixel-nerf</strong><strong> </strong>对输入图像使用卷积进行特征提取再执行 nerf,若有多个输入,对每个视角都执行 CNN,在计算光线时,取每一个已有视角下该坐标的特征,经过 mlp 后算平均。可以在少量视角下重建视图,需要进行预训练才能使用,有一定自动补全能力(有限)
|
||||
**Pixel-nerf**** **对输入图像使用卷积进行特征提取再执行 nerf,若有多个输入,对每个视角都执行 CNN,在计算光线时,取每一个已有视角下该坐标的特征,经过 mlp 后算平均。可以在少量视角下重建视图,需要进行预训练才能使用,有一定自动补全能力(有限)
|
||||
|
||||

|
||||
|
||||
### 2.IBRnet
|
||||
|
||||
<strong>IBRnet</strong><strong> </strong>是 pixel-nerf 的改进版,取消了 CNN,并且在 mlp 后接入了 transformer 结构处理体密度(不透明度),对这条光线上所有的采样点进行一个 transformer。同时,在获取某个体素的颜色和密度时,作者用了本视角相邻的两个视角,获取对应体素在这两张图片中的像素,以图片像素颜色,视角,图片特征作为 mlp 的输入。
|
||||
**IBRnet**** **是 pixel-nerf 的改进版,取消了 CNN,并且在 mlp 后接入了 transformer 结构处理体密度(不透明度),对这条光线上所有的采样点进行一个 transformer。同时,在获取某个体素的颜色和密度时,作者用了本视角相邻的两个视角,获取对应体素在这两张图片中的像素,以图片像素颜色,视角,图片特征作为 mlp 的输入。
|
||||
|
||||

|
||||
|
||||
### 3.MVSnerf
|
||||
|
||||
<strong>MVSnerf</strong><strong> </strong>它用 MVS 的方法构建代价体然后在后面接了一个 nerf,MVS 是使用<strong>多视角立体匹配</strong>构建一个代价体,用 3D 卷积网络进行优化,这里对代价体进行 nerf 采样,可以得到可泛化网络。它需要 15min 的微调才能在新数据上使用。<strong>多视角立体匹配是一种传统算法,通过光线,几何等信息计算图像中小块的相似度,得出两个相机视角之间的位置关系。这个算法也被广泛使用在得到我们自己采样的数据的相机变换矩阵上(我就是这么干的)</strong>
|
||||
**MVSnerf**** **它用 MVS 的方法构建代价体然后在后面接了一个 nerf,MVS 是使用**多视角立体匹配**构建一个代价体,用 3D 卷积网络进行优化,这里对代价体进行 nerf 采样,可以得到可泛化网络。它需要 15min 的微调才能在新数据上使用。**多视角立体匹配是一种传统算法,通过光线,几何等信息计算图像中小块的相似度,得出两个相机视角之间的位置关系。这个算法也被广泛使用在得到我们自己采样的数据的相机变换矩阵上(我就是这么干的)**
|
||||
|
||||

|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
|
||||
## 2)可以 zero-shot 或者 fine-tune 类
|
||||
|
||||
<strong>MVSnerf</strong><strong>,上面已经说了。</strong>
|
||||
**MVSnerf****,上面已经说了。**
|
||||
|
||||
# 2.速度提升
|
||||
|
||||
### 1.instan-ngp
|
||||
|
||||
使用了<strong>哈希表</strong>结构的<strong>instant-ngp</strong>,渲染完美只需要几分钟(采样正常的情况下)这块的速度已经到极致了。
|
||||
使用了**哈希表**结构的**instant-ngp**,渲染完美只需要几分钟(采样正常的情况下)这块的速度已经到极致了。
|
||||
|
||||
展开说说:其实这也是神经网络发展的一个方向,以前的深层网络倾向于把所有东西用网络参数表示,这样推理速度就会慢,这里使用哈希表的快速查找能力存储一些数据信息,instant-ngp 就是把要表达的模型数据特征按照不同的精细度存在哈希表中,使用时通过哈希表调用或插值调用。
|
||||
|
||||
@@ -61,15 +61,15 @@
|
||||
|
||||
### 1.Human-nerf
|
||||
|
||||
<strong>Human-nerf</strong><strong> </strong>生成可编辑的人体运动视频建模,输入是一段人随便动动的视频。输出的动作可以编辑修改,并且对衣物折叠等有一定优化。使用的模型并非全隐式的,并且对头发和衣物单独使用变换模型。使用了逆线性蒙皮模型提取人物骨骼(可学习的模型),上面那个蓝色的就是姿态矫正模块,这个模块赋予骨骼之间运动关系的权重(因为使用的是插值处理同一运动时不同骨骼的平移旋转矩阵,一块骨骼动会牵动其他骨骼)图中的 Ω 就是权重的集合,它通过 mlp 学习得到。然后得到显式表达的人物骨骼以及传入视频中得到的对应骨骼的 mesh,skeletal motion 就是做游戏人物动作用的编辑器这种,后面残差链接了一个 non-rigid-motion(非刚性动作),这个是专门处理衣物和毛发的,主要通过学习得到,然后粗暴的加起来就能得到模型,再经过传统的 nerf 渲染出图像。
|
||||
**Human-nerf**** **生成可编辑的人体运动视频建模,输入是一段人随便动动的视频。输出的动作可以编辑修改,并且对衣物折叠等有一定优化。使用的模型并非全隐式的,并且对头发和衣物单独使用变换模型。使用了逆线性蒙皮模型提取人物骨骼(可学习的模型),上面那个蓝色的就是姿态矫正模块,这个模块赋予骨骼之间运动关系的权重(因为使用的是插值处理同一运动时不同骨骼的平移旋转矩阵,一块骨骼动会牵动其他骨骼)图中的 Ω 就是权重的集合,它通过 mlp 学习得到。然后得到显式表达的人物骨骼以及传入视频中得到的对应骨骼的 mesh,skeletal motion 就是做游戏人物动作用的编辑器这种,后面残差链接了一个 non-rigid-motion(非刚性动作),这个是专门处理衣物和毛发的,主要通过学习得到,然后粗暴的加起来就能得到模型,再经过传统的 nerf 渲染出图像。
|
||||
|
||||

|
||||
|
||||
### 2.Neural Body
|
||||
|
||||
<strong>Neural Body</strong> 通过下面这种<strong>单视角视频</strong>或稀<strong>疏视角照片</strong>来生成人体建模。
|
||||
**Neural Body** 通过下面这种**单视角视频**或稀**疏视角照片**来生成人体建模。
|
||||
|
||||
因为生成的是人体建模,作者使用了他们以前的工作 EasyMocap 得到<strong>SMPL 模型(就是人体的结构)然后在 SMPL 表面生成一些 latent code(包含颜色,不透明度和位置),也就是下左中的那些点。</strong>
|
||||
因为生成的是人体建模,作者使用了他们以前的工作 EasyMocap 得到**SMPL 模型(就是人体的结构)然后在 SMPL 表面生成一些 latent code(包含颜色,不透明度和位置),也就是下左中的那些点。**
|
||||
|
||||
[EasyMocap 的代码](https://link.zhihu.com/?target=https%3A//github.com/zju3dv/EasyMocap)
|
||||
|
||||
@@ -85,25 +85,25 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
个人感觉这个模型不能很好处理光影效果,还有待改进。
|
||||
|
||||
是个预训练模型,<strong>训练的模块就是这个 3D 卷积神经网络</strong>。
|
||||
是个预训练模型,**训练的模块就是这个 3D 卷积神经网络**。
|
||||
|
||||

|
||||
|
||||
### 3.wild-nerf
|
||||
|
||||
<strong>wild-nerf</strong> 思路很简单,就是加入了新的输入参数来调整白天黑夜等等一些简单的变化,并且把行人车辆之类的在采样过程中<strong>不固定的物品</strong>作为<strong>随机项</strong>,在渲染时按照概率加入。
|
||||
**wild-nerf** 思路很简单,就是加入了新的输入参数来调整白天黑夜等等一些简单的变化,并且把行人车辆之类的在采样过程中**不固定的物品**作为**随机项**,在渲染时按照概率加入。
|
||||
|
||||
### 4.D-nerf
|
||||
|
||||
<strong>D-nerf</strong> 是一种动态编辑的 nerf,输入为:x,y,z,相机位置,相机角度,<strong>时间 t。</strong>
|
||||
**D-nerf** 是一种动态编辑的 nerf,输入为:x,y,z,相机位置,相机角度,**时间 t。**
|
||||
|
||||
把整个网络分为两块,一块是正常的 nerf 渲染,另一块是下面这个,输入时间与现在的位置坐标,输出<strong>这个位置坐标中的物体现在的位置</strong>与 t=0 时的<strong>位置的差</strong>。再用 t=0 时物体的点信息进行渲染。
|
||||
把整个网络分为两块,一块是正常的 nerf 渲染,另一块是下面这个,输入时间与现在的位置坐标,输出**这个位置坐标中的物体现在的位置**与 t=0 时的**位置的差**。再用 t=0 时物体的点信息进行渲染。
|
||||
|
||||
在此网络的单个输出上貌似是不监督的,因为没办法进行人为标注。这点我不是很确定,以后如果发现了会来修改的。
|
||||
|
||||

|
||||
|
||||
渲染经过形变的物体时,光线其实是在 t=0 时刻进行渲染的,因为推土机的铲子放下去了,所以<strong>光线是弯曲的</strong>。
|
||||
渲染经过形变的物体时,光线其实是在 t=0 时刻进行渲染的,因为推土机的铲子放下去了,所以**光线是弯曲的**。
|
||||
|
||||

|
||||
|
||||
@@ -115,13 +115,13 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
### 1.clip-nerf
|
||||
|
||||
<strong>clip-nerf</strong><strong> 太贵了玩不起,没仔细研究,应该是文本跟 3D 建模关联,跟 clip 一样。</strong>
|
||||
**clip-nerf**** 太贵了玩不起,没仔细研究,应该是文本跟 3D 建模关联,跟 clip 一样。**
|
||||
|
||||
# 6.生成类(指加入新物体或者额外生成新场景)
|
||||
|
||||
### 1.GRAF
|
||||
|
||||
<strong>GRAF</strong><strong> </strong>把 GAN 与 nerf 结合,增加了两个输入,分别是<strong>外观/形状编码 z</strong>和<strong>2D 采样编码 v</strong>,z 用来改变渲染出来东西的特征,比如把生成的车变色或者变牌子,suv 变老爷车之类的。v(s,u)用来改变下图 2 中训练时选择光线的标准。这里训练时不是拿 G 生成的整张图扔进 D 网络,而是根据 v 的参数选择一些光线组成的 batch 扔进 D 进行辨别
|
||||
**GRAF**** **把 GAN 与 nerf 结合,增加了两个输入,分别是**外观/形状编码 z**和**2D 采样编码 v**,z 用来改变渲染出来东西的特征,比如把生成的车变色或者变牌子,suv 变老爷车之类的。v(s,u)用来改变下图 2 中训练时选择光线的标准。这里训练时不是拿 G 生成的整张图扔进 D 网络,而是根据 v 的参数选择一些光线组成的 batch 扔进 D 进行辨别
|
||||
|
||||

|
||||
|
||||
@@ -129,7 +129,7 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
### 2.GIRAFFE
|
||||
|
||||
<strong>GIRAFFE</strong> 是 GRAF 的改进工作,可以把图片中的物品,背景一个个解耦出来单独进行改变或者移动和旋转,也可以增加新的物品或者减少物品,下图中蓝色是不可训练的模块,橙色可训练。以我的理解好像要设置你要解耦多少个(N)物品再训练,网络根据类似 k 近邻法的方法在特征空间上对物品进行分割解耦,然后分为 N 个渲染 mlp 进行训练,训练前加入外观/形状编码 z。最后还是要扔进 D 训练。
|
||||
**GIRAFFE** 是 GRAF 的改进工作,可以把图片中的物品,背景一个个解耦出来单独进行改变或者移动和旋转,也可以增加新的物品或者减少物品,下图中蓝色是不可训练的模块,橙色可训练。以我的理解好像要设置你要解耦多少个(N)物品再训练,网络根据类似 k 近邻法的方法在特征空间上对物品进行分割解耦,然后分为 N 个渲染 mlp 进行训练,训练前加入外观/形状编码 z。最后还是要扔进 D 训练。
|
||||
|
||||

|
||||
|
||||
@@ -137,7 +137,7 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
### 3.OSF
|
||||
|
||||
<strong>OSF</strong>Object-Centric Neural Scene Rendering,可以给移动的物体生成合理的阴影和光照效果。加入了新的坐标信息:光源位置,与相机坐标等一起输入。对每个小物件构建一个单独的小 nerf,计算这个小 nerf 的体素时要先经过光源照射处理(训练出来的)然后在每个小物件之间也要计算反射这样的光线影响,最后进行正常的渲染。<del>这篇文章没人写 review,有点冷门,这些都是我自己读完感觉的,不一定对。</del>
|
||||
**OSF**Object-Centric Neural Scene Rendering,可以给移动的物体生成合理的阴影和光照效果。加入了新的坐标信息:光源位置,与相机坐标等一起输入。对每个小物件构建一个单独的小 nerf,计算这个小 nerf 的体素时要先经过光源照射处理(训练出来的)然后在每个小物件之间也要计算反射这样的光线影响,最后进行正常的渲染。<del>这篇文章没人写 review,有点冷门,这些都是我自己读完感觉的,不一定对。</del>
|
||||
|
||||

|
||||
|
||||
@@ -147,9 +147,9 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
作者用了几个我比较陌生的技术,比如超网络 hypernet,还有超网络与 gan 结合的 INR-Gan。
|
||||
|
||||
<strong>hypernet</strong>:把随机初始化和直接梯度回传更新的网络参数用另一个神经网络来更新,就是我们要同时训练两个网络,一个是本体,一个是调整参数用的超网络。
|
||||
**hypernet**:把随机初始化和直接梯度回传更新的网络参数用另一个神经网络来更新,就是我们要同时训练两个网络,一个是本体,一个是调整参数用的超网络。
|
||||
|
||||
<strong>INR-Gan</strong>:把超网络技术与 Gan 结合,并且用了 INR 技术,这个技术类似 nerf,不过是处理图片用到的,是构建一个坐标(x,y)->RGB 的网络,可以让图片达到更高分辨率,也就是把离散的像素变成连续的。
|
||||
**INR-Gan**:把超网络技术与 Gan 结合,并且用了 INR 技术,这个技术类似 nerf,不过是处理图片用到的,是构建一个坐标(x,y)->RGB 的网络,可以让图片达到更高分辨率,也就是把离散的像素变成连续的。
|
||||
|
||||
左边是常规卷积网络生成图像,右边是用 INR 生成图像。
|
||||
|
||||
@@ -161,12 +161,12 @@ EasyMocap 是通过多视角视频生成骨架以及 SMPL 模型的一个工作
|
||||
|
||||
2.因为使用神经网路去表示图片,占用内存更大。
|
||||
|
||||
因此,作者设计了<strong>FMM</strong>去应对这两个问题,这也是 Hyper-nerf-gan 借鉴的主要部分。
|
||||
因此,作者设计了**FMM**去应对这两个问题,这也是 Hyper-nerf-gan 借鉴的主要部分。
|
||||
|
||||
FMM 主要是把要学习的矩阵转化为两个低秩矩阵,去先生成他们俩再相乘,减少网络计算量。
|
||||
|
||||

|
||||
|
||||
现在开始讲 Hyper-nerf-gan 本身,它看上去其实就是 nerf 接在 gan 上。不过有一些变化,比如输入不再包含视角信息,我<strong>很怀疑它不能很好表达反光效果</strong>。而且抛弃了粗网络细网络的设计,只使用粗网络减少计算量。这里的 generator 完全就是 INR-Gan 的形状,生成权重,然后再经过 nerf 的 mlp 层生成,没啥别的了,就这样吧。
|
||||
现在开始讲 Hyper-nerf-gan 本身,它看上去其实就是 nerf 接在 gan 上。不过有一些变化,比如输入不再包含视角信息,我**很怀疑它不能很好表达反光效果**。而且抛弃了粗网络细网络的设计,只使用粗网络减少计算量。这里的 generator 完全就是 INR-Gan 的形状,生成权重,然后再经过 nerf 的 mlp 层生成,没啥别的了,就这样吧。
|
||||
|
||||

|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||

|
||||
|
||||
这里主要是记录一下它的原理:
|
||||
首先是一个经典关键点匹配技术:<strong>SIFT</strong>
|
||||
首先是一个经典关键点匹配技术:**SIFT**
|
||||
|
||||
# SIFT 特征点匹配
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
下面是原理方法:
|
||||
|
||||
首先是<strong>高斯金字塔</strong>,它是把原图先放大两倍,然后使用高斯滤波(高斯卷积)对图像进行模糊化数次,取出倒数第三层缩小一半继续进行这个过程,也就是说它是由一组一组的小金字塔组成的。
|
||||
首先是**高斯金字塔**,它是把原图先放大两倍,然后使用高斯滤波(高斯卷积)对图像进行模糊化数次,取出倒数第三层缩小一半继续进行这个过程,也就是说它是由一组一组的小金字塔组成的。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
|
||||
下面给出了 NLP 的四大常见的应用。由于预训练的模型是在连续的文本序列上训练的,所以需要进行一些修改才能将其应用于不同的这些 NLP 任务。
|
||||
|
||||
<strong>分类 (text classification):</strong> 给一句话或者一段文本,判断一个标签。
|
||||
**分类 (text classification):** 给一句话或者一段文本,判断一个标签。
|
||||
|
||||

|
||||
|
||||
图 2:分类 (text classification)
|
||||
|
||||
<strong>蕴含 (textual entailment):</strong> 给一段话,和一个假设,看看前面这段话有没有蕴含后面的假设。
|
||||
**蕴含 (textual entailment):** 给一段话,和一个假设,看看前面这段话有没有蕴含后面的假设。
|
||||
|
||||

|
||||
|
||||
图 3:蕴含 (textual entailment)
|
||||
|
||||
<strong>相似 (Similarity):</strong> 判断两段文字是否相似。
|
||||
**相似 (Similarity):** 判断两段文字是否相似。
|
||||
|
||||

|
||||
|
||||
图 4:相似 (Similarity)
|
||||
|
||||
<strong>多选题 (Multiple Choice):</strong> 给个问题,从 N 个答案中选出正确答案。
|
||||
**多选题 (Multiple Choice):** 给个问题,从 N 个答案中选出正确答案。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# 推荐系统概念解释 and 一个好的推荐系统
|
||||
|
||||
- <strong>用户满意度</strong>
|
||||
- **用户满意度**
|
||||
|
||||
- 用户满意度是推荐系统测评的重要指标,但是实际上,用户满意度数据获得的方式十分有限,因为这是一种用户的主观情感。
|
||||
- 设计合适的方式对于用户的满意度进行回收分析,是改进推荐系统的一个很好的方式。这样的的方式包括但不限于,设计合适的调查问卷,在物品的购买结束后附上一份满意度调查。
|
||||
- 满意度在一些程度上可以细分为更加具体的信息。例如点击率,用户停留时间,转化率,完播率,或者是哔站视频点赞,三连的比例。
|
||||
- <strong>预测准确度</strong>
|
||||
- **预测准确度**
|
||||
|
||||
- <strong>召回率(Recall)</strong>
|
||||
- **召回率(Recall)**
|
||||
|
||||
$$
|
||||
Recall =\frac{\sum_{u\in U}{\vert R(u)\cap T(u) \vert}}{\sum_{u\in U \vert T(u)\vert}}
|
||||
@@ -19,7 +19,7 @@
|
||||
- 召回率的意义?可以参考机器学习中留下的定义进行理解
|
||||
|
||||
|
||||
- <strong>精确率</strong>
|
||||
- **精确率**
|
||||
|
||||
$$
|
||||
Precision =\frac{\sum_{u\in U}{\vert R(u)\cap T(u)\vert}}{\sum_{u\in U}{\vert R(u) \vert}}
|
||||
@@ -31,7 +31,7 @@
|
||||
- 精确率的意义?
|
||||
|
||||
|
||||
- <strong>覆盖率</strong>
|
||||
- **覆盖率**
|
||||
|
||||
- 描述了一个系统对于物品长尾的发掘能力。
|
||||
- 覆盖率的一个定义可以是:
|
||||
@@ -42,7 +42,7 @@
|
||||
- 覆盖率的意义:覆盖率越高,以为这系统中被推荐给用户的物品,占所有物品的比例越大,对于一个好的推荐系统,不仅需要有较高的用户满意度,还需要有较高的覆盖率。
|
||||
- 当然对于覆盖率的定义,不止以上的这一种,甚至说,在实际使用上,上述简单的覆盖率不足以支撑大规模复杂系统的覆盖率计算,所以如何对于覆盖率进行修正和更新?信息熵与基尼系数!
|
||||
- 推荐了解,马太效应,一个强者更强,弱者更弱的效应,在推荐系统中也同样存在。
|
||||
- <strong>多样性</strong>
|
||||
- **多样性**
|
||||
|
||||
- 假设,$s(i,j)$ 定义了物品 i 和 j 之间的相似度,给用户 $u$ 的推荐列表 $R(u)$的多样性定义:
|
||||
$$
|
||||
@@ -52,12 +52,12 @@
|
||||
$$
|
||||
Diversity = \frac{1}{\vert U\vert}\sum_{u\in U}{Diversity(R(u))}
|
||||
$$
|
||||
- <strong>信任度</strong>
|
||||
- **信任度**
|
||||
|
||||
- 用户对于该系统的信任程度
|
||||
- <strong>实时性</strong>
|
||||
- **实时性**
|
||||
|
||||
- 系统对于数据更新的时效性
|
||||
- <strong>健壮性</strong>
|
||||
- **健壮性**
|
||||
|
||||
- 系统对于外来攻击的防护性
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
|
||||
- 时间信息对于用户的的影响可以主要分为以下几项:
|
||||
|
||||
- <strong>用户的兴趣是变化的</strong>
|
||||
- **用户的兴趣是变化的**
|
||||
对于一个用户,其幼年时期和青年时期喜欢的动画片是不一样的;晴天和雨天想要的物品是不一样的;一个人开始工作前和开始工作后的需求也是不同的。
|
||||
所以应该关注用户的近期行为,确定他的兴趣,最后给予用户推荐。
|
||||
- <strong>物品具有生命周期</strong>
|
||||
- **物品具有生命周期**
|
||||
流行物品会随着热度持续火爆一段时间,但最终会无人问津;生活必需品无论在什么时候都有稳定的需求量。
|
||||
- <strong>季节效应</strong>
|
||||
- **季节效应**
|
||||
正如概述中列出的冬衣与夏衣的区别,应该在合适的季节给用户推荐合适的物品。
|
||||
|
||||
### 系统时间特性分析
|
||||
@@ -25,10 +25,10 @@
|
||||
- 当系统由之前的静态系统变成随时间变化的时变系统后,需要关注特性也会发生变化,则需要重新观测一些数据,以推断系统的关于时间变化的特性。
|
||||
下面是一些可以用来观测的数据:
|
||||
|
||||
- <strong>确定系统的用户增长数</strong>,以判断系统的增长情况或是衰退情况。
|
||||
- <strong>物品的平均在线天数</strong>,即将满足用户物品互动次数的物品标记为在线,测算物品的平均在线天数以标量物品的生命周期。
|
||||
- 系统的时效性,判断<strong>相隔一段时间的物品流行度向量的相似度</strong>,若是相隔一段时间的相似度仍然较大,说明经过一段时间后,该物品还是被大众喜欢,则说明这件物品具有持久流行性。而对于系统来说,若是系统中大量物品的相似度变化都不大,则说明这个系统是一个推荐热度较持久物品的系统,说明系统的时效性较弱。
|
||||
- 系统对于用户的黏着性,统计<strong>用户的平均活跃天数</strong>,或者计算<strong>相隔一段时间的用户活跃度</strong>,以此判断系统对于用户的留存力或者说黏着性。
|
||||
- **确定系统的用户增长数**,以判断系统的增长情况或是衰退情况。
|
||||
- **物品的平均在线天数**,即将满足用户物品互动次数的物品标记为在线,测算物品的平均在线天数以标量物品的生命周期。
|
||||
- 系统的时效性,判断**相隔一段时间的物品流行度向量的相似度**,若是相隔一段时间的相似度仍然较大,说明经过一段时间后,该物品还是被大众喜欢,则说明这件物品具有持久流行性。而对于系统来说,若是系统中大量物品的相似度变化都不大,则说明这个系统是一个推荐热度较持久物品的系统,说明系统的时效性较弱。
|
||||
- 系统对于用户的黏着性,统计**用户的平均活跃天数**,或者计算**相隔一段时间的用户活跃度**,以此判断系统对于用户的留存力或者说黏着性。
|
||||
|
||||
### 推荐系统的实时性
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
|
||||
综上,时间多样性会提高用户的满意度,所以如何在确保精度的条件下提高系统的时间多样性呢?
|
||||
|
||||
- <strong>需要用户在有新行为时,更新推荐列表</strong>
|
||||
- **需要用户在有新行为时,更新推荐列表**
|
||||
传统的离线更新的推荐系统无法满足需求,所以需要使用实时推荐系统。
|
||||
- <strong>需要用户在没有新行为的时候,经常变化推荐列表</strong>
|
||||
- **需要用户在没有新行为的时候,经常变化推荐列表**
|
||||
通常采取以下三种方法:
|
||||
|
||||
- 生成推荐列表时加入一定的随机性。
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
### 时间上下文推荐算法
|
||||
|
||||
- <strong>最近最热门</strong>
|
||||
- **最近最热门**
|
||||
一种最朴素的思想, 在系统引入了时间信息之后,最简单的非个性化推荐算法就是给用户推荐最近最热门的物品。
|
||||
|
||||
给定时间 T,物品 i 在最近的流行度可定义为:
|
||||
@@ -67,10 +67,10 @@
|
||||
n_i(T)= \sum_{(u,i,t) \in Train ,t<T} \frac{1}{1+\alpha(T-t)}
|
||||
$$
|
||||
|
||||
- <strong>时间上下文相关的 itemCF 算法</strong>
|
||||
- **时间上下文相关的 itemCF 算法**
|
||||
itemCF 算法所依赖的核心部分,在引入时间信息后可以进行进一步更新
|
||||
|
||||
- <strong>物品相似度</strong> 利用用户行为,计算物品间的相似度,用户在相隔很短的时间内喜欢的物品通常具有更高的相似度,所以可以在相似度计算公式中引入时间信息,使得相似度计算更加准确。
|
||||
- **物品相似度** 利用用户行为,计算物品间的相似度,用户在相隔很短的时间内喜欢的物品通常具有更高的相似度,所以可以在相似度计算公式中引入时间信息,使得相似度计算更加准确。
|
||||
原本的相似度公式为:
|
||||
|
||||
$$
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
其中$\alpha$ 是时间衰减参数,它的取值与系统的对于自身定义有关系。收到用户兴趣变化的额外影响。
|
||||
|
||||
- <strong>在线推荐</strong> 用户近期行为相比用户很久之前的行为,更能体现用户目前的兴趣,所以在进行预测时,应当加重用户近期行为的权重,但不应该偏离用户长期行为的行为基调。
|
||||
- **在线推荐** 用户近期行为相比用户很久之前的行为,更能体现用户目前的兴趣,所以在进行预测时,应当加重用户近期行为的权重,但不应该偏离用户长期行为的行为基调。
|
||||
原本的用户u对于物品i的兴趣$p(u,i)$ 可通过如下公式计算:
|
||||
|
||||
$$p(u,i)=\sum_{j\in N(u)}{sim(i,j)}$$
|
||||
@@ -106,11 +106,11 @@
|
||||
|
||||
在上面的更新后公式中,$t_0$ 表示当前时间,该公式表明,当 $t_{uj}$ 与 $t_0$ 越靠近,和物品j相似的物品就会在用户u的推荐列表中获得更高的排名。其中的$\beta$和上文的 $\alpha$ 是一样的,需要根据系统的情况选择合适的值。
|
||||
|
||||
- <strong>时间上下文相关的userCF算法</strong>
|
||||
- **时间上下文相关的userCF算法**
|
||||
|
||||
与itemCF算法类似,userCF在引入时间信息后也可以进行更新
|
||||
|
||||
- <strong>用户兴趣相似度</strong> 用户相似度在引入时间信息后,会将用户相同的逆时序选择相似度降低。简单来说,就是A一月BF1长时间在线,二月BF5长时间在线,而B一月BF5长时间在线,二月BF1长时间在线;C行为信息与A相同。如果不引入时间信息,那么AB的相似度与AC的相似度是一样的,而实际上,AC的相似度会大于AB的相似度。
|
||||
- **用户兴趣相似度** 用户相似度在引入时间信息后,会将用户相同的逆时序选择相似度降低。简单来说,就是A一月BF1长时间在线,二月BF5长时间在线,而B一月BF5长时间在线,二月BF1长时间在线;C行为信息与A相同。如果不引入时间信息,那么AB的相似度与AC的相似度是一样的,而实际上,AC的相似度会大于AB的相似度。
|
||||
|
||||
userCF的用户uv间相似度的基本公式为:
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
|
||||
同样增加了一个时间衰减因子,用户uv对于i的作用时间差距越大,那么两人的相似度会相应降低。
|
||||
|
||||
- <strong>相似兴趣用户的最近行为</strong> 对于用户u来说,存在最近行为与用户u相似的用户v,那么用户v的最近行为,将会比用户u很久之前的行为更具有参考价值。
|
||||
- **相似兴趣用户的最近行为** 对于用户u来说,存在最近行为与用户u相似的用户v,那么用户v的最近行为,将会比用户u很久之前的行为更具有参考价值。
|
||||
|
||||
userCF中用户u对于物品i兴趣的基础公式为:
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
p(u,i)=\sum_{v\in S(u,k)}{w_{ui}r_{vi}} \frac{1}{1+\alpha(\vert t_0-t_{vi}\vert)}
|
||||
$$
|
||||
|
||||
- <strong>时间段图模型</strong>
|
||||
- **时间段图模型**
|
||||
同样是一个基于图的推荐系统模型,引入时间信息,建立一个二分图时间段图模型:
|
||||
|
||||
$$
|
||||
@@ -169,11 +169,11 @@
|
||||
在构建了引入时间信息的图结构后,最简单的思想就是利用PersonalRank算法给用进行个性化推荐。但由于其复杂度较高,所以引入路径融合算法。
|
||||
一般来说,图上两个点的相关度强有以下的特征:
|
||||
|
||||
- <strong>两个顶点间有很多路径</strong>
|
||||
- **两个顶点间有很多路径**
|
||||
|
||||
- <strong>两个顶点间路径比较短</strong>
|
||||
- **两个顶点间路径比较短**
|
||||
|
||||
- <strong>两点间不经过出度大的点</strong> ,即不经过与很多其他点相连的节点,在推荐系统思维中等效于不与过热门物品关系紧密。
|
||||
- **两点间不经过出度大的点** ,即不经过与很多其他点相连的节点,在推荐系统思维中等效于不与过热门物品关系紧密。
|
||||
|
||||
#### 路径融合算法
|
||||
|
||||
@@ -199,15 +199,15 @@
|
||||
|
||||
### 地点信息效应
|
||||
|
||||
- <strong>基于用户当前位置的推荐</strong>:对于用户当前位置,为其推荐距离更近的餐馆,娱乐场所或消费场所。
|
||||
- **基于用户当前位置的推荐**:对于用户当前位置,为其推荐距离更近的餐馆,娱乐场所或消费场所。
|
||||
|
||||
- <strong>基于用户活跃位置的推荐</strong>:对于用户长期活跃的区域,降低该区域内物品的权重,提高范围外物品的权重,以提高系统的新鲜度。
|
||||
- **基于用户活跃位置的推荐**:对于用户长期活跃的区域,降低该区域内物品的权重,提高范围外物品的权重,以提高系统的新鲜度。
|
||||
|
||||
### 基于位置的推荐算法
|
||||
|
||||
- 明尼苏达大学的LARS推荐系统(Location Aware Recommender System,位置感知推荐系统)。
|
||||
|
||||
- <strong>对于数据的预处理</strong>
|
||||
- **对于数据的预处理**
|
||||
|
||||
|
||||
将物品分为两类:(1)有空间属性的物品,餐馆,商店,旅游景点。(2)没有空间属性的物品,图书电影等。
|
||||
@@ -223,9 +223,9 @@
|
||||
(用户,用户位置,物品,物品位置,评分):记录了某个位置的用户,对于某个地点的物品的评分。
|
||||
|
||||
|
||||
- <strong>研究前两组数据</strong>:发现两种特征:(1)兴趣本地化,不同位置的用户存在较大的兴趣差异,不同国家和不同地区的差异。(2)活动本地化,一个用户往往在附近的地区活动。
|
||||
- **研究前两组数据**:发现两种特征:(1)兴趣本地化,不同位置的用户存在较大的兴趣差异,不同国家和不同地区的差异。(2)活动本地化,一个用户往往在附近的地区活动。
|
||||
|
||||
- <strong>对于不同数据的处理</strong>
|
||||
- **对于不同数据的处理**
|
||||
|
||||
- 第一种数据:LARS的基本思想是,采用树状结构来进行数据集划分。
|
||||
|
||||
@@ -236,7 +236,7 @@
|
||||
(3)LARS通过该节点的行为数据,利用基本推荐算法进行为用户进行推荐。
|
||||
|
||||
但是,对于上述过程,若是树的深度较大,则划分到每个节点的用户数据将较少,难以训练出一个令人满意的模型。所以有改进方法如下:
|
||||
从根节点出发,利用每个中间节点的数据训练出一个模型,而最终的推荐结果,是这一些列推荐模型所产出的推荐结果的加权结果。这个模型也被称为“<strong>金字塔模型</strong>”,其中<strong>深度</strong>是影响这个模型性能的重要参数,选取合适的深度对于该算法十分重要。
|
||||
从根节点出发,利用每个中间节点的数据训练出一个模型,而最终的推荐结果,是这一些列推荐模型所产出的推荐结果的加权结果。这个模型也被称为“**金字塔模型**”,其中**深度**是影响这个模型性能的重要参数,选取合适的深度对于该算法十分重要。
|
||||
|
||||
- 第二种数据:对于物品i在用户u推荐列表中的权重公式进行修正
|
||||
(1)首先忽略物品的位置信息,利用itemCF算法计算用户u对物品i的兴趣。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
VIT前Transformer模型被大量应用在NLP自然语言处理当中,而在CV领域,Transformer的注意力机制attention也被广泛应用,比如Se模块,CBAM模块等等注意力模块,这些注意力模块能够帮助提升网络性能。
|
||||
|
||||
而<strong>VIT的工作展示了不需要依赖CNN的结构,也可以在图像分类任务上达到很好的效果</strong>。
|
||||
而**VIT的工作展示了不需要依赖CNN的结构,也可以在图像分类任务上达到很好的效果**。
|
||||
|
||||
同时VIT也影响了近2年的CV领域,改变了自2012年AlexNet提出以来卷积神经网络在CV领域的绝对统治地位。
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
结构上,VIT 采取的是原始 Transformer 模型,方便开箱即用,即在 encoder-decoder 结构上与 NLP 的 Transform 模型并无差别。
|
||||
|
||||
主要做出的贡献在于<strong>数据处理和分类头</strong>
|
||||
主要做出的贡献在于**数据处理和分类头**
|
||||
|
||||
### Patch embedding
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
> 今天天气不错,我要去看电影
|
||||
|
||||
其中<strong>我</strong>则编码为[0.5,0.6,0.6]
|
||||
其中**我**则编码为[0.5,0.6,0.6]
|
||||
|
||||
而具体来说 Word embedding 分为以下两步
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
# 前言
|
||||
|
||||
BERT 是一种基于 transformer 架构的自然语言处理模型,它把在 cv 领域广为应用的<strong>预训练(pre-trainning)</strong>和<strong>微调(fine-tune)</strong>的结构成功引入了 NLP 领域。
|
||||
BERT 是一种基于 transformer 架构的自然语言处理模型,它把在 cv 领域广为应用的**预训练(pre-trainning)**和**微调(fine-tune)**的结构成功引入了 NLP 领域。
|
||||
|
||||
简单来说,BERT 就是一种<strong>认识几乎所有词的</strong>,<strong>训练好</strong>的网络,当你要做一些下游任务时,可以在 BERT 预训练模型的基础上进行一些微调,以进行你的任务。也就是 backbone 模型,输出的是文本特征。
|
||||
简单来说,BERT 就是一种**认识几乎所有词的**,**训练好**的网络,当你要做一些下游任务时,可以在 BERT 预训练模型的基础上进行一些微调,以进行你的任务。也就是 backbone 模型,输出的是文本特征。
|
||||
|
||||
举个例子,我要做一个文本情感分析任务,也就是把文本对情感进行分类,那我只需要在 BERT 的基础上加一个 mlp 作为分类头,在我的小规模数据上进行继续训练即可(也就是微调)。
|
||||
|
||||
@@ -24,9 +24,9 @@ mlp 的重点和创新并非它的模型结构,而是它的训练方式,前
|
||||
|
||||
在文本被输入模型之前,我们要对它进行一些处理:
|
||||
|
||||
1. <strong>词向量</strong>(wordpiece embedding):单词本身的向量表示。每个词(或者进行时过去时后缀之类的)会被记录为一个向量。它们被储存在一个字典里,这一步其实就是在字典中查找这个词对应的向量。
|
||||
2. <strong>位置向量</strong>(position embedding):将单词的位置信息编码成特征向量。构建 position embedding 有两种方法:BERT 是初始化一个 position embedding,<strong>然后通过训练将其学出来</strong>;而 Transformer 是通过<strong>制定规则</strong>来构建一个 position embedding。
|
||||
3. <strong>句子向量</strong>(segment embedding):用于区分两个句子的向量表示。这个在问答等非对称句子中是用于区别的。(这个主要是因为可能会用到对句子的分析中)
|
||||
1. **词向量**(wordpiece embedding):单词本身的向量表示。每个词(或者进行时过去时后缀之类的)会被记录为一个向量。它们被储存在一个字典里,这一步其实就是在字典中查找这个词对应的向量。
|
||||
2. **位置向量**(position embedding):将单词的位置信息编码成特征向量。构建 position embedding 有两种方法:BERT 是初始化一个 position embedding,**然后通过训练将其学出来**;而 Transformer 是通过**制定规则**来构建一个 position embedding。
|
||||
3. **句子向量**(segment embedding):用于区分两个句子的向量表示。这个在问答等非对称句子中是用于区别的。(这个主要是因为可能会用到对句子的分析中)
|
||||
|
||||
BERT 模型的输入就是上面三者的和,如图所示:
|
||||
|
||||
@@ -34,7 +34,7 @@ BERT 模型的输入就是上面三者的和,如图所示:
|
||||
|
||||
## 模型结构
|
||||
|
||||
简单来说,BERT 是 transformer<strong>编码器</strong>的叠加,<strong>也就是下图左边部分</strong>。这算一个 block。
|
||||
简单来说,BERT 是 transformer**编码器**的叠加,**也就是下图左边部分**。这算一个 block。
|
||||
|
||||

|
||||
|
||||
@@ -50,7 +50,7 @@ BERT 训练方式跟 cv 里的很多 backbone 模型一样,是先用几个具
|
||||
|
||||
跟以往的 nlp 模型不同,BERT 的掩码并非 transformer 那样,给前面不给后面,而是在句子中随机把单词替换为 mask,让模型去猜,也就是完形填空。下面给个例子:
|
||||
|
||||
<strong>划掉的单词是被 mask 的</strong>
|
||||
**划掉的单词是被 mask 的**
|
||||
|
||||
正常的掩码:I am a <del>little cat</del>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ cv 领域,其实预训练模型早已推广,一般是在 imagenet 上进行
|
||||
|
||||
那么问题来了,既然我们要学习 BERT 的随机掩码,那么我们应该对什么做 mask 呢?
|
||||
|
||||
因为图片不像文本,有单词这一基础单位。图片的基础单位像素在被单独拿出来的时候包含的语义信息是完全不如单词的。因为像素的语义信息与<strong>上下左右的连续关系</strong>很密切。于是作者采用了像 VIT 那样把图片分成好几个 patch,对 patch 做随机掩码。
|
||||
因为图片不像文本,有单词这一基础单位。图片的基础单位像素在被单独拿出来的时候包含的语义信息是完全不如单词的。因为像素的语义信息与**上下左右的连续关系**很密切。于是作者采用了像 VIT 那样把图片分成好几个 patch,对 patch 做随机掩码。
|
||||
|
||||
# 模型结构与训练方式
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
对比学习,故名思意,是对比着来学习。而我们拿来对比的东西就是在模型眼里的语义,也就是我们叫做特征的向量。
|
||||
|
||||
在具体讲对比之前,我们先看看传统的<strong>监督学习</strong>是怎么学特征的:
|
||||
在具体讲对比之前,我们先看看传统的**监督学习**是怎么学特征的:
|
||||
|
||||
数据 + 模型=> 特征,特征对人工标注进行学习,也就是说我们要把模型抽取的特征尽可能的靠近人工标注
|
||||
|
||||
@@ -26,15 +26,15 @@
|
||||
|
||||

|
||||
|
||||
我们通过<strong>正样本</strong>(跟拿到的特征<strong>应当相近</strong>的另一个特征)与<strong>负样本</strong>(反之)的对比,使得
|
||||
我们通过**正样本**(跟拿到的特征**应当相近**的另一个特征)与**负样本**(反之)的对比,使得
|
||||
|
||||
越相近的物体,它们的特征就在超球面上越靠近,越不像的物体离的越远,去学习图片更本质的特征
|
||||
|
||||
那么具体的对比学习方法我在后面结合一些论文一起讲吧~
|
||||
|
||||
这部分内容更像一个综述,讲述对比学习这几年的发展路程,所以我会尽可能的描述作者在论文里讲的<strong>故事</strong>,来方便大家弄清为什么要这么做。
|
||||
这部分内容更像一个综述,讲述对比学习这几年的发展路程,所以我会尽可能的描述作者在论文里讲的**故事**,来方便大家弄清为什么要这么做。
|
||||
|
||||
<strong>可能会有很多我的主观理解在此</strong>,并且<strong>不会</strong>深入细节。可以算是一个总结和分享,我会在这里带着读者过一遍近期对比学习的工作来给大家一个对比学习方向的直观感性理解。
|
||||
**可能会有很多我的主观理解在此**,并且**不会**深入细节。可以算是一个总结和分享,我会在这里带着读者过一遍近期对比学习的工作来给大家一个对比学习方向的直观感性理解。
|
||||
|
||||
同时因为笔者水平,视野,精力有限,不可能包含所有的算法,也不可能保证完全正确。因此仅作为笔记分享使用。若有错误,请多多指正。
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
既然有了上面这个发现,那么作者想我能不能把分类任务推到极致呢?
|
||||
|
||||
于是他们<strong>把每一个图片当作一个类别</strong>,去跟其他的图片做对比,具体模型如下
|
||||
于是他们**把每一个图片当作一个类别**,去跟其他的图片做对比,具体模型如下
|
||||
|
||||

|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
2.后面接了一个 Non-param Softmax(非参数 softmax),其实就是一个不被训练的,把所有特征投射到超球面上的一个分类头(把所有特征模长变为 1)。
|
||||
|
||||
3.后面的<strong>Memory Bank</strong>是这篇文章的<strong>重点</strong>,它是一个<strong>动态字典</strong>。我们把每一个图片抽取出来的特征存入 memory bank,每次计算时抽取其中部分作为一个 batch 进行对比学习,把更新后的模型得到的特征替换 memory bank 里原先的特征。
|
||||
3.后面的**Memory Bank**是这篇文章的**重点**,它是一个**动态字典**。我们把每一个图片抽取出来的特征存入 memory bank,每次计算时抽取其中部分作为一个 batch 进行对比学习,把更新后的模型得到的特征替换 memory bank 里原先的特征。
|
||||
|
||||
4.具体损失函数用的是一个叫 NCEloss 的损失,它把多分类问题分为<strong>若干个二分类问题</strong>,<strong>是</strong>与<strong>不是</strong>,每个 batch 中只有一个的 ground truth 是’yes‘,其余都是’no‘
|
||||
4.具体损失函数用的是一个叫 NCEloss 的损失,它把多分类问题分为**若干个二分类问题**,**是**与**不是**,每个 batch 中只有一个的 ground truth 是’yes‘,其余都是’no‘
|
||||
|
||||
在训练的时候,相当于是有一组以前的编码器抽取的特征 A,B,C,D...,一组当前编码器抽取的特征 a,b,c,d...,对它们进行对比学习。对 a 来说,A 是正样本,其他都是负样本,同理类推。
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
|
||||
用动量更新的方法去更新 memory bank 中的特征
|
||||
|
||||
也就是让特征的变化<strong>不那么剧烈</strong>
|
||||
也就是让特征的变化**不那么剧烈**
|
||||
|
||||
原因:如果一直保持更新,<strong>特征总体的变化就会比较大</strong>,而我们在大数据集上训练的时候,等第二次调用一个特征时,它跟现在的特征分布已经大相径庭,那就不好训练了,也就是<strong>特征缺乏一致性</strong>。因此我们引入动量更新来确保特征进行平稳的改变,而非突变。
|
||||
原因:如果一直保持更新,**特征总体的变化就会比较大**,而我们在大数据集上训练的时候,等第二次调用一个特征时,它跟现在的特征分布已经大相径庭,那就不好训练了,也就是**特征缺乏一致性**。因此我们引入动量更新来确保特征进行平稳的改变,而非突变。
|
||||
|
||||
#### 关于动量的小拓展
|
||||
|
||||
@@ -60,4 +60,4 @@ m 表示动量,k 是新的特征,q 是上一个特征,只要设置小的
|
||||
|
||||
总体来说,Inst Disc 把对比学习成功引入了 CV 领域,核心思想是构建动态字典进行对比学习
|
||||
|
||||
<strong>PS:若无特殊说明,最后保留下来去做下游任务的模型只有编码器,其他都删除了。</strong>
|
||||
**PS:若无特殊说明,最后保留下来去做下游任务的模型只有编码器,其他都删除了。**
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||

|
||||
|
||||
这是处理音频的一个例子,<strong>给模型 t 时刻以前的信息,让它抽取特征并对后文进行预测,真正的后文作为正样本,负样本当然是随便选取就好啦。</strong>
|
||||
这是处理音频的一个例子,**给模型 t 时刻以前的信息,让它抽取特征并对后文进行预测,真正的后文作为正样本,负样本当然是随便选取就好啦。**
|
||||
|
||||
不同于之前说的个体判别,这个是<strong>生成式模型</strong>,这个模型不止可以处理音频,还可以处理图片(每一个块换成一个词)或者处理图片(以 patch 为单位)。
|
||||
不同于之前说的个体判别,这个是**生成式模型**,这个模型不止可以处理音频,还可以处理图片(每一个块换成一个词)或者处理图片(以 patch 为单位)。
|
||||
|
||||
是不是有点眼熟?这跟我前面写的 BERT 和 MAE 其实异曲同工,不过这两位是随机 mask,而非时序性的 mask。
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@ MoCo 是 Inst Disc 的改进工作,那我们自然要先看一下 Inst Disc
|
||||
|
||||
## 3.NCEloss 负样本分类不合理的问题
|
||||
|
||||
NCE 把<strong>所有负样本都视作一样的</strong>,但实际上负样本<strong>并不能被完全归为一类</strong>
|
||||
NCE 把**所有负样本都视作一样的**,但实际上负样本**并不能被完全归为一类**
|
||||
|
||||
举个例子:我现在的正样本是<strong>猫猫</strong>,然后有两个负样本是<strong>狗勾</strong>和<strong>汽车</strong>,那<strong>猫猫</strong>肯定跟<strong>狗勾</strong>更相近,跟<strong>汽车</strong>更不相似,也就是说<strong>狗</strong>的得分虽然低于<strong>猫</strong>,但是一定要高于<strong>汽车</strong>,<strong>而不是像 NCE 那样把狗和车打成一类</strong>,这样不利于模型学习。
|
||||
举个例子:我现在的正样本是**猫猫**,然后有两个负样本是**狗勾**和**汽车**,那**猫猫**肯定跟**狗勾**更相近,跟**汽车**更不相似,也就是说**狗**的得分虽然低于**猫**,但是一定要高于**汽车**,**而不是像 NCE 那样把狗和车打成一类**,这样不利于模型学习。
|
||||
|
||||
并且它也不是很灵活,下文细讲
|
||||
|
||||
@@ -34,7 +34,7 @@ NCE 把<strong>所有负样本都视作一样的</strong>,但实际上负样
|
||||
|
||||
## 2.针对动量更新不能完全解决特征一致性差的问题
|
||||
|
||||
作者提出了一个新的<strong>动量编码器</strong>来替代动量更新。
|
||||
作者提出了一个新的**动量编码器**来替代动量更新。
|
||||
|
||||
动量编码器是独立于原编码器的一个编码器,它的参数是根据原编码器动量更新的,k 和 q 就是指代全部参数了
|
||||
|
||||
@@ -52,9 +52,9 @@ NCE 把<strong>所有负样本都视作一样的</strong>,但实际上负样
|
||||
|
||||
q·k 其实就是各个特征(因为那时候用的都是 transformer 了,这里就是 trnasformer 里的 k 和 q)
|
||||
|
||||
这里分母级数上的<strong>k 是代表负样本的个数,也就是 k=batchsize-1(总样本-正样本)</strong>。其实就是<strong>对一个 batch 做 k+1 分类</strong>,并且引入了一个<strong>超参数 T</strong>。它的名字叫做<strong>温度参数</strong>,控制的是 softmax 后得分分布的平滑程度(直观理解,不是很严谨)
|
||||
这里分母级数上的**k 是代表负样本的个数,也就是 k=batchsize-1(总样本-正样本)**。其实就是**对一个 batch 做 k+1 分类**,并且引入了一个**超参数 T**。它的名字叫做**温度参数**,控制的是 softmax 后得分分布的平滑程度(直观理解,不是很严谨)
|
||||
|
||||
T 越大,损失函数就越对所有负样本<strong>一视同仁</strong>,退化为二分类的 NCEloss;T 越小,损失函数就<strong>越关注一些难分类的特征</strong>,但有时候会出现两张其实都是猫猫的图片,你硬要让模型说猫猫跟猫猫不一样,这也不太好,这个参数要根据数据集情况适中调整。
|
||||
T 越大,损失函数就越对所有负样本**一视同仁**,退化为二分类的 NCEloss;T 越小,损失函数就**越关注一些难分类的特征**,但有时候会出现两张其实都是猫猫的图片,你硬要让模型说猫猫跟猫猫不一样,这也不太好,这个参数要根据数据集情况适中调整。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ x 是输入的图片,它经过两种不同的数据增强得到 xi 和 xj 两
|
||||
|
||||

|
||||
|
||||
左右的<strong>f 都是编码器</strong>,并且是<strong>完全一致共享权重</strong>的,可以说是同一个。
|
||||
左右的**f 都是编码器**,并且是**完全一致共享权重**的,可以说是同一个。
|
||||
|
||||
而 g 是一层 mlp 结构,只在训练中使用,<strong>应用到下游任务时用的仅仅是 f</strong>(与前面几篇一样都是 RES50),很神奇的是,就仅仅多了这么一层 mlp,它在 imagenet 上的正确率直接加了十个点。
|
||||
而 g 是一层 mlp 结构,只在训练中使用,**应用到下游任务时用的仅仅是 f**(与前面几篇一样都是 RES50),很神奇的是,就仅仅多了这么一层 mlp,它在 imagenet 上的正确率直接加了十个点。
|
||||
|
||||
关于这点也很奇怪,作者做了很多实验但是也没有很合理的解释。
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# 前言
|
||||
|
||||
与前面的一些工作不同,SwAV<strong>不再进行个体判别任务</strong>,而是提出了新的任务————<strong>聚类</strong>
|
||||
与前面的一些工作不同,SwAV**不再进行个体判别任务**,而是提出了新的任务————**聚类**
|
||||
|
||||
并在训练的模型结构上也做了相应改动,而非只调整训练方法。
|
||||
|
||||
@@ -26,17 +26,17 @@
|
||||
|
||||
## 聚类中心?
|
||||
|
||||
首先我们有个新的东西<strong>prototypes</strong>,它是<strong>聚类中心的集合</strong>,也就是许多作为聚类中心的向量构成的矩阵。
|
||||
首先我们有个新的东西**prototypes**,它是**聚类中心的集合**,也就是许多作为聚类中心的向量构成的矩阵。
|
||||
|
||||
这些聚类中心是我设定在超球面上的,离散的一些点,我希望让不同的特征向它们靠拢以进行区分(也就是所谓聚类)。
|
||||
|
||||
更直白地讲,我在地上撒了一把面包屑,地上本来散乱的蚂蚁会向面包屑聚集,形成一个个<strong>小团体</strong>。蚂蚁就是<strong>不同图像的特征</strong>,面包屑就是<strong>我设定的聚类中心</strong>
|
||||
更直白地讲,我在地上撒了一把面包屑,地上本来散乱的蚂蚁会向面包屑聚集,形成一个个**小团体**。蚂蚁就是**不同图像的特征**,面包屑就是**我设定的聚类中心**
|
||||
|
||||
## 聚类中心我知道了,然后呢?
|
||||
|
||||
先说我拿他干了什么,再一步步讲为什么要这么做吧。
|
||||
|
||||
首先我们手里有抽取出来的特征<strong>z1</strong>,<strong>z2</strong>,以及一个我随机初始化的<strong>聚类中心矩阵 c</strong>。我分别求这个<strong>矩阵</strong>和<strong>z1</strong>,<strong>z2</strong>的内积,并<strong>进行一些变换</strong>得到 Q1,Q2。当 z1,z2 都是正样本时,我希望<strong>Q1 与 z2 相近</strong>,<strong>Q2 与 z1 相近</strong>。如果有一个是负样本则尽可能远离。也就是拿 Q 当 ground-truth 做训练。最后这步前面已经讲过 NCEloss 等损失函数了,用它们就可以达成这个任务。
|
||||
首先我们手里有抽取出来的特征**z1**,**z2**,以及一个我随机初始化的**聚类中心矩阵 c**。我分别求这个**矩阵**和**z1**,**z2**的内积,并**进行一些变换**得到 Q1,Q2。当 z1,z2 都是正样本时,我希望**Q1 与 z2 相近**,**Q2 与 z1 相近**。如果有一个是负样本则尽可能远离。也就是拿 Q 当 ground-truth 做训练。最后这步前面已经讲过 NCEloss 等损失函数了,用它们就可以达成这个任务。
|
||||
|
||||
而我们的优化要采用 [K-means](https://zhuanlan.zhihu.com/p/78798251)(不懂可以看这里)的类似做法,先对聚类中心进行优化,再对特征进行优化。
|
||||
|
||||
@@ -46,13 +46,13 @@ so,why?相信你现在肯定是一脸懵,不过别急,希望我能为你
|
||||
|
||||
## 首先是第一步,为什么要求内积?
|
||||
|
||||
如果你有好好了解线性代数的几何性质,应当了解<strong>两个向量的内积就是一个向量在另一个向量上的投影</strong>,而一个向量与一个矩阵的内积,<strong>就是把这个向量投影到这个矩阵代表的基空间中</strong>。
|
||||
如果你有好好了解线性代数的几何性质,应当了解**两个向量的内积就是一个向量在另一个向量上的投影**,而一个向量与一个矩阵的内积,**就是把这个向量投影到这个矩阵代表的基空间中**。
|
||||
|
||||
我做的第一步就是把<strong>抽出来的特征 z 用聚类中心的向量表示,这样更加方便对比聚类成功与否</strong>。
|
||||
我做的第一步就是把**抽出来的特征 z 用聚类中心的向量表示,这样更加方便对比聚类成功与否**。
|
||||
|
||||
## 然后是第二步,我说的变换是什么呢?
|
||||
|
||||
我们现在求内积是为了把特征投影到聚类中心空间,为了避免模型训练坍塌(就是网络把特征全部聚到同一个点,<del>开摆~</del>)我要保证每个聚类中心被<strong>"使用"</strong>的次数,所以我们请出了<strong>Sinkhorn-Knopp 算法。</strong>这个算法比较硬核,我在这里不展开了,大家知道它是干啥的就行,具体的推导可以看我后面贴的视频,那里面有讲。
|
||||
我们现在求内积是为了把特征投影到聚类中心空间,为了避免模型训练坍塌(就是网络把特征全部聚到同一个点,<del>开摆~</del>)我要保证每个聚类中心被**"使用"**的次数,所以我们请出了**Sinkhorn-Knopp 算法。**这个算法比较硬核,我在这里不展开了,大家知道它是干啥的就行,具体的推导可以看我后面贴的视频,那里面有讲。
|
||||
|
||||
## 第三步应该不用怎么讲了吧?
|
||||
|
||||
@@ -64,7 +64,7 @@ so,why?相信你现在肯定是一脸懵,不过别急,希望我能为你
|
||||
|
||||
# 总结
|
||||
|
||||
主要贡献是上面我说的三步聚类算法以及后面的小 trick,<strong>Sinkhorn-Knopp 算法难度较高,大家有兴趣的话自行观看后面这个视频理解哈~</strong>
|
||||
主要贡献是上面我说的三步聚类算法以及后面的小 trick,**Sinkhorn-Knopp 算法难度较高,大家有兴趣的话自行观看后面这个视频理解哈~**
|
||||
|
||||
# 相关资料
|
||||
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
|
||||
# 前言
|
||||
|
||||
这篇论文的主要特点是<strong>它的训练不需要负样本</strong>,并且能保证<strong>模型不坍塌</strong>。
|
||||
这篇论文的主要特点是**它的训练不需要负样本**,并且能保证**模型不坍塌**。
|
||||
|
||||
当一个普通的对比学习模型没有负样本时,它的损失函数就<strong>只有正样本之间的差距</strong>,这样模型只会学到一个<strong>捷径解</strong>————你给我什么输入我都输出同一个值,这样 loss 就永远=0 了(<del>开摆</del>)
|
||||
当一个普通的对比学习模型没有负样本时,它的损失函数就**只有正样本之间的差距**,这样模型只会学到一个**捷径解**————你给我什么输入我都输出同一个值,这样 loss 就永远=0 了(~~开摆~~)
|
||||
|
||||
而<strong>BYOL</strong>就解决了这一问题,使得训练不再需要负样本。
|
||||
而**BYOL**就解决了这一问题,使得训练不再需要负样本。
|
||||
|
||||
# 模型结构
|
||||
|
||||
前半部分很普通,跟 SimCLR 是基本一致的,一个图片经过两种不同的数据增强,进入两个编码器,得到两个不同的特征。
|
||||
|
||||
值得一提的是,这里下面的这个粉色的编码器用的是<strong>动量编码器</strong>的更新方式。也就是说它是紫色那个编码器的动量编码器。
|
||||
值得一提的是,这里下面的这个粉色的编码器用的是**动量编码器**的更新方式。也就是说它是紫色那个编码器的动量编码器。
|
||||
|
||||
而提取特征之后,经过两个 `SimCLR` 中提出的额外的 mlp 层 z,在此之后,它们给紫色的那支加了一个新的模块,<strong>predictor</strong>。
|
||||
而提取特征之后,经过两个 `SimCLR` 中提出的额外的 mlp 层 z,在此之后,它们给紫色的那支加了一个新的模块,**predictor**。
|
||||
|
||||
<strong>predictor</strong>的模型结构就是跟 z 一样的<strong>mlp 层</strong>。它的任务是<strong>通过紫色的特征去预测粉色的特征</strong>。也就是说它的代理任务换成了<strong>生成式</strong>。
|
||||
**predictor**的模型结构就是跟 z 一样的**mlp 层**。它的任务是**通过紫色的特征去预测粉色的特征**。也就是说它的代理任务换成了**生成式**。
|
||||
|
||||

|
||||
|
||||
而具体的损失只有预测特征和真实特征的损失,用的是<strong>MSEloss</strong>。
|
||||
而具体的损失只有预测特征和真实特征的损失,用的是**MSEloss**。
|
||||
|
||||
下面的粉色分支最后一步是不进行梯度回传的。它的更新完全依赖紫色的那个编码器。
|
||||
|
||||
@@ -42,29 +42,29 @@ BN 根据批次的均值和方差进行归一化
|
||||
|
||||
推理时,均值、方差是基于所有批次的期望计算所得。
|
||||
|
||||
因此,博客作者认为,虽然我们只用了正样本进行训练,但是这个正样本包含了<strong>本批次所有样本的信息</strong>(均值,方差),所以<strong>实际上并不是真正的无负样本。</strong>
|
||||
因此,博客作者认为,虽然我们只用了正样本进行训练,但是这个正样本包含了**本批次所有样本的信息**(均值,方差),所以**实际上并不是真正的无负样本。**
|
||||
|
||||
而这个 batch 的均值,即平均图片,可以看作 `SawAV` 里的聚类中心,是所有历史样本的聚类中心。(<del>很玄学</del>)
|
||||
|
||||
### 作者看到这个博客就急了
|
||||
|
||||
如果真是这样的话,<strong>BYOL 就还是没有逃脱出对比学习的范畴</strong>,它还是找了一个东西去做对比,其创新性就大大降低了。所以作者赶紧做实验,看看能不能找到 BYOL 模型不坍塌的另外一种解释。最终又写了一篇论文进行回应。
|
||||
如果真是这样的话,**BYOL 就还是没有逃脱出对比学习的范畴**,它还是找了一个东西去做对比,其创新性就大大降低了。所以作者赶紧做实验,看看能不能找到 BYOL 模型不坍塌的另外一种解释。最终又写了一篇论文进行回应。
|
||||
|
||||
这篇论文叫 BYOL works even without batch statistics,即在没有 BN 的时候 BYOL 照样能工作,详细的消融实验结果如下表所示 :
|
||||
|
||||

|
||||
|
||||
<strong>BN 非常关键</strong>:只要是 `projector`(SimCLR 提出的 mlp)中没有 BN 的地方,SimCLR 性稍微下降;但是 BYOL 全都模型坍塌了。
|
||||
**BN 非常关键**:只要是 `projector`(SimCLR 提出的 mlp)中没有 BN 的地方,SimCLR 性稍微下降;但是 BYOL 全都模型坍塌了。
|
||||
|
||||
<strong>有 BN 也会坍塌</strong>:作者找到了特例(红色框),即使当 `projector` 有 BN 的时候,BYOL 还是训练失败了 。如果 BN 真的很关键,它真的提供了隐式负样本的对比学习的话,训练就不应该失败
|
||||
**有 BN 也会坍塌**:作者找到了特例(红色框),即使当 `projector` 有 BN 的时候,BYOL 还是训练失败了 。如果 BN 真的很关键,它真的提供了隐式负样本的对比学习的话,训练就不应该失败
|
||||
|
||||
<strong>完全没有 BN,SimCLR 也坍塌</strong>(最后三列的结果。要注意 SimCLR 只有一层 projector)。这表明完全不用归一化,SimCLR 这种使用负样本进行对比学习的方式也无法训练。
|
||||
**完全没有 BN,SimCLR 也坍塌**(最后三列的结果。要注意 SimCLR 只有一层 projector)。这表明完全不用归一化,SimCLR 这种使用负样本进行对比学习的方式也无法训练。
|
||||
|
||||
最终结论:BN 跟它原来的设计初衷一样,主要作用就是提高模型训练时的稳定性,从而不会导致模型坍塌 。作者进一步延伸,如果一开始就能让模型初始化的比较好,后面的训练即使离开了 BN 也没有问题。
|
||||
|
||||
作者为此又设计了一个实验,使用 `group norm`+`weight standardization` (前者也是一种归一化方式,后者是一种卷积权重标准化方式,但都没有对 batch 中的数据进行融合),BYOL 的 top-准确率可以达到 74.1%,和原来精度可以认为是一样了(74.3%)。
|
||||
|
||||
<strong>至今其实这个问题也没有一个很合理能服众的解释。</strong>
|
||||
**至今其实这个问题也没有一个很合理能服众的解释。**
|
||||
|
||||
# 总结
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@ BYOL 之后,大家都发现对比学习是靠许许多多的小 trick 和技
|
||||
|
||||
原论文中提出的解释并不是最完美的。而且这个问题的解释涉及了动力学等知识,我也没有足够的知识储备去讲解这个问题,这里只能讲一些与解答相关的信息,如果有兴趣可以看下面链接中的解释:
|
||||
|
||||
这里要涉及到一个机器学习的经典算法,<strong>EM 算法</strong>,它也是<strong>k-means</strong>的核心思想之一。
|
||||
这里要涉及到一个机器学习的经典算法,**EM 算法**,它也是**k-means**的核心思想之一。
|
||||
|
||||
因为本文的主旨原因,我不会在这里细讲这个算法,但是大家了解这是个什么东西即可。
|
||||
|
||||
<strong>EM 算法</strong>用于优化带有未知参数的模型,`k-means` 的聚类中心就可以看作一个未知参数,我们要同时优化模型本体和聚类中心。所以我们先对其中一个目标 A 做<strong>随机初始化</strong>,然后<strong>先优化</strong>另一个目标 B,再反过来用另一个目标 B 优化后<strong>的结果优化被随机初始化的目标 A</strong>,这就是一次迭代,只要不断循环这个迭代,EM 算法往往能找到最优解。
|
||||
**EM 算法**用于优化带有未知参数的模型,`k-means` 的聚类中心就可以看作一个未知参数,我们要同时优化模型本体和聚类中心。所以我们先对其中一个目标 A 做**随机初始化**,然后**先优化**另一个目标 B,再反过来用另一个目标 B 优化后**的结果优化被随机初始化的目标 A**,这就是一次迭代,只要不断循环这个迭代,EM 算法往往能找到最优解。
|
||||
|
||||
这里可以把<strong>经过 predictor 预测头的特征</strong>作为 `k-means` 里的特征,而另一个作为<strong>目标的特征</strong>作为<strong>聚类中心</strong>,经过预测头的特征直接反向传播进行优化,作为目标的特征则是通过上面说的对称的操作经过预测头进行优化。
|
||||
这里可以把**经过 predictor 预测头的特征**作为 `k-means` 里的特征,而另一个作为**目标的特征**作为**聚类中心**,经过预测头的特征直接反向传播进行优化,作为目标的特征则是通过上面说的对称的操作经过预测头进行优化。
|
||||
|
||||
最最直白地解读结论的话,可以说是,这种先后优化的 EM 算法,使得模型“来不及“去把权重全部更新为 0。(模型坍塌)具体的推导需要动力学的知识,这里不做展开。
|
||||
|
||||
@@ -48,9 +48,8 @@ BYOL 之后,大家都发现对比学习是靠许许多多的小 trick 和技
|
||||
|
||||
下面是这些网络训练结果的对比,也列出了它们分别有哪些 trick(用的是分类任务)
|
||||
|
||||
```
|
||||
负样本 动量编码器 训练轮数
|
||||
```
|
||||
负样本 动量编码器 训练轮数
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ MoCo v3,它缝合了 MoCo 和 SimSiam,以及新的骨干网络 VIT。
|
||||
|
||||

|
||||
|
||||
在使用 VIT 训练的时候,batchsize 不算太大时训练很平滑,但是一旦 batchsize 变大,训练的图像就会出现如上图这样的<strong>波动</strong>。于是作者去查看了每一层的梯度,发现问题出在<strong>VIT 的第一层线性变换</strong>上。也就是下图中的粉色那个层,<strong>把图片打成 patch 后展平做的线性变换</strong>。
|
||||
在使用 VIT 训练的时候,batchsize 不算太大时训练很平滑,但是一旦 batchsize 变大,训练的图像就会出现如上图这样的**波动**。于是作者去查看了每一层的梯度,发现问题出在**VIT 的第一层线性变换**上。也就是下图中的粉色那个层,**把图片打成 patch 后展平做的线性变换**。
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
在周志华的机器学习一书半监督学习章节中,有对基本的图学习策略进行基本的描述,详见我为了应付课程考试整理的[图半监督学习](http://blog.cyasylum.top/index.php/2020/07/05/%E5%9B%BE%E5%8D%8A%E7%9B%91%E7%9D%A3%E5%AD%A6%E4%B9%A0/)。其基本思路是这样的,通过”样本距离度量“刻画获取样本之间的联系,将样本嵌入到“图”,即样本即其关系的集合中。后通过图将半监督学习中有标记样本的标签对未标记样本进行传递,从而获取未标记样本的属性,进行学习。
|
||||
|
||||
如此,便定下来图网络的基本思路,即通过<em>信息在图上的传递</em>,迭代学习知识。有了这样的基础,我们便可以开始对图网络进行讨论了。
|
||||
如此,便定下来图网络的基本思路,即通过*信息在图上的传递*,迭代学习知识。有了这样的基础,我们便可以开始对图网络进行讨论了。
|
||||
|
||||
接下来,我们从最基础的部分来讲讲,信息是如何在图上进行传播的。
|
||||
|
||||
@@ -55,7 +55,7 @@ $$
|
||||
|
||||
现在,我们可以尝试用$\mathbf{L}$对图进行表示了。
|
||||
|
||||
另外还有个<em>随机游走归一化拉普拉斯矩阵</em>
|
||||
另外还有个*随机游走归一化拉普拉斯矩阵*
|
||||
$$
|
||||
\mathbf{L}^{sym}=\mathbf{D}^{-1}\mathbf{L}=\mathbf{I}-\mathbf{D}^{-1}\mathbf{A}
|
||||
$$
|
||||
|
||||
@@ -54,7 +54,7 @@ author:zzm
|
||||
|
||||
day 1 End!🤣
|
||||
|
||||
## Day 2
|
||||
### Day 2
|
||||
|
||||
昨天真是美滋滋的一天,玩了一晚上的你有点头昏脑涨,今天就开始干活好了,反正一周时间呢,比期末复习周可长太多了,就做这么个玩意我还能做不出来吗?
|
||||
|
||||
@@ -79,7 +79,7 @@ day 1 End!🤣
|
||||
什么?!刚开始学 python?!woc! 完蛋,你逐渐来到了绝望之谷,唉!明天继续做吧!看来休息不了了。
|
||||
day 2 End 😔!
|
||||
|
||||
## Day 3
|
||||
### Day 3
|
||||
|
||||
God!No! 昨天已经够累的了,今天老师还要讲课,还要早起!你期待着老师可以降低要求,可是当老师托起长音,讲起了他知道了学生的累,所以今天决定开始讲课了!(现在讲有毛用啊,你明天就要验收我们的进度了!)
|
||||
|
||||
@@ -117,7 +117,7 @@ God!No! 昨天已经够累的了,今天老师还要讲课,还要早起!你
|
||||
|
||||
day 3 end!👿 👹 👺 🤡
|
||||
|
||||
## Day 4
|
||||
### Day 4
|
||||
|
||||
老师在验收的时候认为你什么工作也没做,他认为一份数据实在是太单薄了,特别是被你疯狂结构优化后的数据已经没几个特征了,让你去做点看得到的东西,不然就要让你不及格了,你的心里很难过,你想到也许你需要一些更好看的东西。数据可视化你在昨天的 pandas 看到过,可是你并没有详细了解,你觉得 pandas 已经在昨天把你狠狠的暴捶一顿了,并且老师想要更好看的图。
|
||||
|
||||
@@ -141,7 +141,7 @@ day 3 end!👿 👹 👺 🤡
|
||||
|
||||
day 4 end!~🤤
|
||||
|
||||
## Day 5
|
||||
### Day 5
|
||||
|
||||
你睡得很死,因为你已经你做完了所有的东西,第二天只要美美的验收结束,买了机票就可以回家了,可是老师仍然制止了你,跟你说如果你今晚走了就给你挂科,因为你没有用机器学习来分析他!
|
||||
|
||||
@@ -169,7 +169,7 @@ day 4 end!~🤤
|
||||
|
||||
day 5 end!😍 🥰 😘
|
||||
|
||||
## Day 6
|
||||
### Day 6
|
||||
|
||||
验收日,老师端坐在底下,宛如一尊大佛,提出了一系列无关紧要的问题,比如问“我们能不能拿这个程序给老年人查资料???”
|
||||
|
||||
@@ -212,7 +212,7 @@ The End~~~~~~~~~~
|
||||
|
||||
## 补充内容:下个定义
|
||||
|
||||
数据分析是独立于开发和算法岗的另一个方向,它主要是通过<strong>应用</strong>机器学习和深度学习的<strong>已有算法</strong>来分析现实问题的一个方向
|
||||
数据分析是独立于开发和算法岗的另一个方向,它主要是通过**应用**机器学习和深度学习的**已有算法**来分析现实问题的一个方向
|
||||
|
||||
我们常说:数据是客观的,但是解读数据的人是主观的。
|
||||
|
||||
|
||||
@@ -11,17 +11,17 @@ Email: yqykrhf@163.com
|
||||
|
||||
## 术语的介绍
|
||||
|
||||
<strong>Benchmark:</strong>评测的基准。通常会是一些公开的数据集。
|
||||
**Benchmark:**评测的基准。通常会是一些公开的数据集。
|
||||
|
||||
<strong>Baseline:</strong> 基准,一般指的是一个现有的工作。
|
||||
**Baseline:** 基准,一般指的是一个现有的工作。
|
||||
|
||||
<strong>SOTA </strong>(state-of-art): 截止目前,指标最好。
|
||||
**SOTA **(state-of-art): 截止目前,指标最好。
|
||||
|
||||
举个例子:
|
||||
|
||||
我们选取 XXX-Net 作为我们的 Baseline,在加入我们设计的注意力机制的模块,在 KITTI 这个 Benchmark 上性能达到了 SOTA。
|
||||
|
||||
<strong>Backbone:</strong>
|
||||
**Backbone:**
|
||||
|
||||
这个单词原意指的是人的脊梁骨,后来引申为支柱,核心的意思。
|
||||
|
||||
@@ -29,13 +29,13 @@ Email: yqykrhf@163.com
|
||||
|
||||
所以将这一部分网络结构称为 backbone 十分形象,仿佛是一个人站起来的支柱。
|
||||
|
||||
<strong>Solid</strong>
|
||||
**Solid**
|
||||
|
||||
一般是描述这个工作非常扎实。
|
||||
|
||||
这个工作很 solid。每一步都 make sense(合理)。没有特意为了刷 benchmark 上的指标,用一些 fancy trick(奇技淫巧)。
|
||||
|
||||
<strong>Robust</strong>
|
||||
**Robust**
|
||||
|
||||
鲁棒性,是描述一个系统受到外界的干扰情况下,仍然能保持较好的性能。
|
||||
|
||||
@@ -76,7 +76,7 @@ Step 3. 验证解决方案的有效性。
|
||||
|
||||
从上一小节的几个例子当中,其实不同的人做研究所需要完成的工作是完全不一样的。很多时候只需要做 step 3 即可,从功利的角度来讲这是性价比最高的。
|
||||
|
||||
如果我们是一个合格的博士或者我们致力于如此,那么首先的第一步要找到一个好的问题,这是一个非常重要的开始,<strong>一个好的问题往往意味着研究已经成功了一半。 </strong>什么是一个好的问题?它可能会有以下几个特点:
|
||||
如果我们是一个合格的博士或者我们致力于如此,那么首先的第一步要找到一个好的问题,这是一个非常重要的开始,**一个好的问题往往意味着研究已经成功了一半。 **什么是一个好的问题?它可能会有以下几个特点:
|
||||
|
||||
1. 理论上能实现某种意义上的统一,从而使得问题的描述变得非常优雅。比如 [DepthAwareCNN](https://arxiv.org/abs/1803.06791)
|
||||
2. 对于之后的工作非常具有启发的作用,甚至达到某种意义的纠偏作用。比如 [OccuSeg](https://arxiv.org/abs/2003.06537)
|
||||
@@ -91,7 +91,7 @@ Step 3. 验证解决方案的有效性。
|
||||
2. 做加法。举个例子:图片可以做语义分割。那么图片 + 深度图如何更好的做语义分割。图片 + 文字描述的做语义分割。现在的语义分割的标注都是 0,1,2,3 这些数字,然后每一个数字对应一个实际的类别,这个对应表是认为规定的。如果我把 0,1 的对应关系换掉。重新训练以后就,网络的性能是否会影响?
|
||||
3. 做减法。对于点云的语义分割的标注,通过是非常费时费力的。那么对于点云来说,少量的标注是否是可行的?比如只标注百分之 10 的点。
|
||||
|
||||
以上是一些技巧,把输入调整一下,约束去掉一些,就会有很多新的问题。这个过程通常被叫做<strong>“调研”</strong>
|
||||
以上是一些技巧,把输入调整一下,约束去掉一些,就会有很多新的问题。这个过程通常被叫做**“调研”**
|
||||
|
||||
这个过程在是一个相对比较痛苦的过程,因为调研的过程中你会发现很多问题,想到很多所谓创新的解决方法,但是实际上你会发现你的解决方法已经有很多人做过了。这一阶段调整心态很重要,切忌急于求成。
|
||||
|
||||
@@ -114,9 +114,9 @@ Step 3. 验证解决方案的有效性。
|
||||
|
||||
## 快速出成果的捷径与方法
|
||||
|
||||
如何快速的出成果,不管别人如何帮你,前提是你自己要足够的强。不能存在 <strong>“靠别人” </strong>的想法。
|
||||
如何快速的出成果,不管别人如何帮你,前提是你自己要足够的强。不能存在 **“靠别人” **的想法。
|
||||
|
||||
对于一个博士生来讲,出成果保毕业,那么可能要对学术的进展要敏感,比如 Nerf 八月份刚出来的时候,如果你非常敏锐的意识到这个工作的基础性和重要性。那么你稍微思考一两个月,总是能有一些创新的 ieda 产生的。所以这个<strong>timing 和 senstive</strong>就非常重要,当然导师是不是审稿人可能更重要。
|
||||
对于一个博士生来讲,出成果保毕业,那么可能要对学术的进展要敏感,比如 Nerf 八月份刚出来的时候,如果你非常敏锐的意识到这个工作的基础性和重要性。那么你稍微思考一两个月,总是能有一些创新的 ieda 产生的。所以这个**timing 和 senstive**就非常重要,当然导师是不是审稿人可能更重要。
|
||||
|
||||
对于一个本科生来讲,当然是跟着指导老师的脚步去做。但是如果指导老师只是把你当成一个工具人,一直打杂货的话。你想发论文,一种所谓的捷径是 A+B。就是把一个方法直接拿过来用在另一个地方,大概率这样会有一些问题,那么你就可以针对性的改进,如何针对性的改进?不好的方式是 A+B 套娃,好一些的方式是分析这个不好的原因在哪里,现有的方法多大程度可以帮助解决这个问题,或者现有的方法解决不了这个问题,但是其中的一个模块是否是可以参考的。
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ AI 是一个复杂且多样化的研究领域,他能取得如此长远的发
|
||||
|
||||
而这些都不会使他停滞
|
||||
|
||||
<strong>这是本讲义想做的第三件事,拥有学习新技术,跟上时代的能力**</strong>
|
||||
**这是本讲义想做的第三件事,拥有学习新技术,跟上时代的能力****
|
||||
|
||||
而愿不愿意在这激荡翻腾的年份,贡献出你的力量,让世界变得更好/更坏,就取决于你的选择了!
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ $$
|
||||
print(similarity_matrix)
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
A B C D E
|
||||
A 1.000000 -0.476731 -0.123091 0.532181 0.969458
|
||||
B -0.476731 1.000000 0.645497 -0.310087 -0.478091
|
||||
@@ -124,7 +124,7 @@ $$
|
||||
print(f'与物品{target_item}最相似的{num}个物品为:{sim_items}')
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
与物品E最相似的2个物品为:['A', 'D']
|
||||
```
|
||||
|
||||
@@ -147,7 +147,7 @@ $$
|
||||
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred}')
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
用户 Alice 对物品E的预测评分为:4.6
|
||||
```
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ UserCF算法的两个步骤:
|
||||
print(similarity_matrix)
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
1 2 3 4 5
|
||||
1 1.000000 0.852803 0.707107 0.000000 -0.792118
|
||||
2 0.852803 1.000000 0.467707 0.489956 -0.900149
|
||||
@@ -253,7 +253,7 @@ UserCF算法的两个步骤:
|
||||
print(f'与用户{target_user}最相似的{num}个用户为:{sim_users}')
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
与用户 Alice 最相似的2个用户为:['user1', 'user2']
|
||||
```
|
||||
|
||||
@@ -277,7 +277,7 @@ UserCF算法的两个步骤:
|
||||
print(f'用户{target_user}对物品{target_item}的预测评分为:{target_item_pred}')
|
||||
```
|
||||
|
||||
```
|
||||
```python
|
||||
用户 Alice 对物品E的预测评分为:4.871979899370592
|
||||
```
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user