<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/scripts/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:h="http://www.w3.org/TR/html4/"><channel><title>XiaoHei&apos;s Blog</title><description>Wir müssen wissen.Wir werden wissen.</description><link>https://www.wht0909.top</link><item><title>基于深度学习的脑电解码经典算法</title><link>https://www.wht0909.top/blog/%E5%9F%BA%E4%BA%8E%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%9A%84%E8%84%91%E7%94%B5%E8%A7%A3%E7%A0%81%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95/%E5%9F%BA%E4%BA%8E%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%9A%84%E8%84%91%E7%94%B5%E8%A7%A3%E7%A0%81%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E5%9F%BA%E4%BA%8E%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%9A%84%E8%84%91%E7%94%B5%E8%A7%A3%E7%A0%81%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95/%E5%9F%BA%E4%BA%8E%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%9A%84%E8%84%91%E7%94%B5%E8%A7%A3%E7%A0%81%E7%BB%8F%E5%85%B8%E7%AE%97%E6%B3%95</guid><description>算法框架汇总</description><pubDate>Mon, 18 May 2026 11:14:26 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside , Spoiler } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;本文旨在汇总一些经典论文中提出的 EEG 解码算法&lt;/p&gt;
&lt;h2&gt;1. EEGNet&lt;/h2&gt;
&lt;h3&gt;论文信息&lt;/h3&gt;
&lt;p&gt;原始论文：&lt;a href=&quot;https://arxiv.org/abs/1611.08024&quot;&gt;EEGNet: A Compact Convolutional Network for EEG-based Brain-Computer Interfaces&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;发表时间：2018&lt;/p&gt;
&lt;p&gt;官方代码（基于 Tensorflow）：https://github.com/vlawhern/arl-eegmodels&lt;/p&gt;
&lt;h3&gt;主要贡献&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;提出了一种跨范式的 BCI 解码算法：可用于 P300 视觉诱发电位、错误相关负电位（ERN）、运动相关皮层电位（MRCP）和感觉运动节律（SMR）等范式&lt;/li&gt;
&lt;li&gt;和同类型基于 CNN 的算法相比，参数量更少&lt;/li&gt;
&lt;li&gt;具备可解释性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心思路：用卷积代替传统的时域、空域滤波器，训练模型自动调整参数；不同卷积分别学习时域、空域特征&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;算法架构&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./EEGNet_img1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;图 1：EEGNet 架构的整体可视化图。线条表示输入与输出（称为特征图）之间的卷积核连接。该网络首先通过时间卷积（第二列）学习频率滤波器，随后使用深度卷积（中间列）——该卷积分别连接到每个特征图——来学习特定频率的空间滤波器。可分离卷积（第四列）由深度卷积和点卷积组合而成：深度卷积为每个特征图单独学习时间摘要，随后点卷积学习如何将特征图进行最优混合。网络架构的完整细节详见表2。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;笔者注：这个架构图不是很清晰，可以结合下面的表格查看每个板块的输入/输出张量形状&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./EEGNet_img2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;张量传递图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./EEGNet_img3.jpg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;tensor 形状变化（在上表和以下叙述中，均省略了$\text{batch size}$）：&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Block 1&lt;/em&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Input：输入时形状为$(C,T)$，$C$代表通道（导联），$T$代表采样点。&lt;/li&gt;
&lt;li&gt;Reshape：改变形状为$(1,C,T)$ -&gt; $1$ 用来“占位”，和“特征图数量”这一变量对齐&lt;/li&gt;
&lt;li&gt;Conv2D：用$F_{1}$个$(1,64)$的卷积核提取&lt;strong&gt;时域特征&lt;/strong&gt;，可以理解为这一步是在用$F_{1}$个不同的滤波器对$C$个通道的、长度为$T$的脑电信号进行&lt;strong&gt;滤波&lt;/strong&gt;，得到$F_{1}$个特征图（即输出形状为$(F_{1},C,T)$）&lt;/li&gt;
&lt;li&gt;BatchNorm：在特征图维度上进行批量归一化，形状不变，仍为$(F_{1},C,T)$&lt;/li&gt;
&lt;li&gt;DepthwiseConv2D：用$D&lt;em&gt;F_{1}$个大小为$(C,1)$的卷积核提取&lt;strong&gt;空域特征&lt;/strong&gt;。大小为$(C,1)$的卷积核的作用是“跨导联”，相当于把$C$个导联的信息加权到一个“平均导联”上，所以得到的单个特征图大小为$(1,T)$。卷积核数量为$D&lt;/em&gt;F_{1}$，相当于对每个大小为$(C,T)$的特征图，用$D$个滤波器进行&lt;strong&gt;空域滤波&lt;/strong&gt;，学习不同的空间信息。因此输出张量的形状是$(D*F_{1},1,T)$&lt;/li&gt;
&lt;li&gt;BatchNorm：在特征图维度上进行批量归一化，形状不变，仍为$(D*F_{1},1,T)$&lt;/li&gt;
&lt;li&gt;Activation：非线性激活，这里使用 ELU 单元&lt;/li&gt;
&lt;li&gt;AveragePool2D：平均池化层，核大小为$(1,4)$，相当于在时域上每隔四个点一采样，输出形状为$(D*F_{1},1,T//4)$&lt;/li&gt;
&lt;li&gt;Dropout：训练时防止过拟合&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Block 2&lt;/em&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SeparableConv2D：可分离卷积，包括两部分：深度卷积核和$1&lt;em&gt;1$卷积。先用$(1,16)$的卷积核再提取一次&lt;strong&gt;时域特征&lt;/strong&gt;，仍然相当于滤波操作，并保持形状不变，仍为$(D&lt;/em&gt;F_{1},1,T//4)$。再用$F_{2}$个$1&lt;em&gt;1$卷积将通道数调整为$F_{2}$。$1&lt;/em&gt;1$卷积相当于在整合各个通道的信息。输出形状为$(F_{2},1,T//4)$&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;注：论文中提到，通常取$F_{2}=F_{1}*D$，即不改变通道数&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;BatchNorm：在特征图维度上进行批量归一化，形状不变，仍为$(F_{2},1,T//4)$&lt;/li&gt;
&lt;li&gt;Activation：非线性激活，这里使用 ELU 单元&lt;/li&gt;
&lt;li&gt;AveragePool2D：平均池化层，核大小为$(1,8)$，相当于在时域上每隔八个点一采样，输出形状为$(F_{2},1,T//32)$&lt;/li&gt;
&lt;li&gt;Dropout：训练时防止过拟合&lt;/li&gt;
&lt;li&gt;Flatten：将张量展平为$D*F_{1}&lt;em&gt;1&lt;/em&gt;T//32$，输入到线性层进行分类&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from scipy.io import loadmat
import torch
from torch import nn
import numpy as np

data_path = &quot;./datatsets/kaggle-P300-datatset/P300S01.mat&quot;

def load_mat_data(data_path=data_path, test_cut=False):
    mat_data = loadmat(data_path, struct_as_record=False, squeeze_me=True)
    data_struct = mat_data[&apos;data&apos;]
    X = data_struct.X  # (358372, 8)  时间点×导联
    y = data_struct.y  # (358372,)    每个时间点的标签
    Fs = 250  # 采样率
    if test_cut:
        X = X[0:8000, :] # 只取前8000个采样点，减少计算量
        y = y[0:8000]
    return X, y, Fs

def preprocessing(X, y, Fs):
    t_pre = int(0.1 * Fs)
    t_post = int(0.6 * Fs)
    window = (-t_pre, t_post)
    epochs = []
    labels = []
    target_idx = np.where(y==1)[0]
    non_target_idx = np.where(y==0)[0]
    for idx in target_idx:
        start = idx + window[0]
        end = idx + window[1]
        if start &gt;= 0 and end &amp;#x3C;= len(X) and (end - start) == t_pre + t_post:
            X_epoch = X[start:end, :].T
            epochs.append(X_epoch)
            labels.append(torch.tensor(1))
    for idx in non_target_idx:
        start = idx + window[0]
        end = idx + window[1]
        if start &gt;= 0 and end &amp;#x3C;= len(X) and (end - start) == t_pre + t_post:
            X_epoch = X[start:end, :].T
            epochs.append(X_epoch)
            labels.append(torch.tensor(0))
    epochs = np.array(epochs)[:, None, :, :]
    epochs = torch.tensor(epochs).float()
    labels = torch.tensor(labels).long()
    print(f&quot;epochs形状：{epochs.shape}&quot;)
    print(f&quot;labels形状：{labels.shape}&quot;)
    return epochs, labels

class EEGNet(nn.Module):
    def __init__(self, batch_size, num_C, num_T, num_F1=4, num_D=2, num_F2=8):
        super().__init__()
        self.batch_size = batch_size
        self.num_C = num_C
        self.num_T = num_T
        self.num_F1 = num_F1
        self.num_D = num_D
        self.num_F2 = num_F2
        self.block1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=self.num_F1, kernel_size=(1,64), padding=&apos;same&apos;), # (1, 4, 8, 175)
            nn.BatchNorm2d(num_features=self.num_F1), # (1, 4, 8, 175)
            nn.Conv2d(in_channels=self.num_F1, out_channels=self.num_D*self.num_F1, kernel_size=(8, 1), padding=0), # (1, 8, 1, 175)
            nn.BatchNorm2d(num_features=self.num_D*self.num_F1),  # (1, 8, 1, 175)
            nn.ELU(),
            nn.AvgPool2d(kernel_size=(1,4)), # (1, 8, 1, 43)
            nn.Dropout(p=0.25)
        )
        self.block2 = nn.Sequential(
            nn.Conv2d(in_channels=self.num_D*self.num_F1, out_channels=self.num_D*self.num_F1, kernel_size=(1,16), padding=&apos;same&apos;, groups=self.num_D*self.num_F1), # (1, 8, 1, 43)
            nn.Conv2d(in_channels=self.num_D*self.num_F1, out_channels=self.num_F2, kernel_size=(1,1)), # (1, 8, 1, 43)
            nn.BatchNorm2d(num_features=self.num_F2),
            nn.ELU(),
            nn.AvgPool2d(kernel_size=(1, 8)),  # (1, 8, 1, 5)
            nn.Dropout(p=0.25),
        )
        with torch.no_grad():
            dummy = torch.randn(1, 1, num_C, num_T)
            out = self.block2(self.block1(dummy))
            self.in_features = out.numel()

        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(self.in_features, 2)
        )
    def forward(self, x):
        # x: (batch_size, num_feature_maps, num_channels, num_times)
        x = self.block1(x)
        x = self.block2(x)
        x = self.classifier(x)
        return x
    
if __name__ == &quot;__main__&quot;:
    X, y, Fs = load_mat_data(test_cut=True)
    epochs, labels = preprocessing(X, y, Fs)
    print(epochs[0].shape) # (1,8,175)
    batch_size = 100
    input_epochs = epochs[0:batch_size] # (batch_size,1,8,175)
    model = EEGNet(batch_size=batch_size, num_C=8, num_T=175)
    output = model(input_epochs.float())
    print(output.shape)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Deep ConvNet &amp;#x26; Shallow ConvNet&lt;/h2&gt;
&lt;h3&gt;论文信息&lt;/h3&gt;
&lt;p&gt;原始论文：&lt;a href=&quot;https://arxiv.org/abs/1703.05051&quot;&gt;Deep learning with convolutional neural networks for brain mapping and decoding of movement-related information from the human EEG&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;发表时间：2017&lt;/p&gt;
&lt;p&gt;官方代码：https://github.com/braindecode/braindecode&lt;/p&gt;
&lt;h3&gt;主要贡献&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;奠定了卷积神经网络用于解码任务的基础，提出的 Deep ConvNet &amp;#x26; Shallow ConvNet 算法超越了传统的滤波器组常见空间模式（FBCSP）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;提出了两种新颖的可视化技术&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;输入特征-单元输出相关性映射（Correlation Mapping）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;计算各层单元输出与输入频带功率之间的相关系数&lt;/li&gt;
&lt;li&gt;对比训练前后模型的相关系数差异，展示网络逐层学习的过程&lt;/li&gt;
&lt;li&gt;可生成地形图展示不同频带特征的贡献空间分布&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;输入扰动-网络预测相关性映射（Perturbation Correlation Mapping）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;通过扰动EEG信号中特定频带的功率，观察网络预测值的变化&lt;/li&gt;
&lt;li&gt;计算扰动值与预测变化之间的相关系数&lt;/li&gt;
&lt;li&gt;可直接解释为特征的因果贡献（而非仅仅是相关性）&lt;/li&gt;
&lt;li&gt;首次在非侵入式EEG中揭示gamma频带运动解码的体感拓扑组织&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;算法架构&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Deep ConvNet&lt;/strong&gt;
&lt;img src=&quot;./DeepConvNet.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;深度卷积神经网络架构。脑电图输入（顶部）逐步向下转换，直至最终分类器输出。黑色长方体：输入/特征图；棕色长方体：卷积/池化核。对应的大小分别用黑色和棕色标注。请注意，在此示意图中，特征图与卷积核的比例仅为近似值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Block 1：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入：$(1,44,534)$，卷积核$(25,1,10)$ -&gt; 输出：$(25,44,510)$&lt;/li&gt;
&lt;li&gt;输入：$(25,44,510)$，卷积核$(25,44,1)$-&gt;输出：$(25,1,510)$ ：跨通道（导联）卷积&lt;/li&gt;
&lt;li&gt;ELU非线性激活&lt;/li&gt;
&lt;li&gt;最大池化：消掉“导联”分量，池化核$(1,3)$。输入：$(25,1,510)$-&gt;输出：$(25,1,171)$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Block 2：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入：$(25,1,171)$，卷积核$(50,1,10)$-&gt;输出：$(50,1,162)$&lt;/li&gt;
&lt;li&gt;ELU非线性激活&lt;/li&gt;
&lt;li&gt;最大池化：输入：$(50,1,162)$，输出：$(50,1,54)$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Block 3：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入：$(50,1,54)$，卷积核$(100,1,10)$-&gt;输出：$(100,1,45)$&lt;/li&gt;
&lt;li&gt;ELU非线性激活&lt;/li&gt;
&lt;li&gt;最大池化：输入：$(100,1,45)$，输出：$(100,1,15)$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Block 4：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入：$(100,1,15)$，卷积核$(200,1,10)$-&gt;输出：$(200,1,6)$&lt;/li&gt;
&lt;li&gt;ELU非线性激活&lt;/li&gt;
&lt;li&gt;最大池化：输入：$(200,1,6)$，输出：$(200,1,2)$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;分类层：
展开$200*2$，分为四类&lt;/p&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
from torch import nn

eeg_input = torch.randn(size=(44,534))
eeg_input = eeg_input.unsqueeze(dim=0) # (1,44,534)
eeg_input = eeg_input.unsqueeze(dim=0) # (1,1,44,534)

class DeepConvNet(nn.Module):
    def __init__(self, num_C=44, num_T=534):
        super().__init__()
        self.conv_ch = [25, 50, 100, 200]
        self.kernel = [1, 10] # 卷积核大小
        self.num_C = num_C # 导联数
        self.num_T = num_T # 采样点数
        self.block1 = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=self.conv_ch[0], kernel_size=(1,10), stride=1),
            nn.BatchNorm2d(num_features=self.conv_ch[0]),
            nn.Conv2d(in_channels=self.conv_ch[0], out_channels=self.conv_ch[0], kernel_size=(self.num_C,1)),
            nn.BatchNorm2d(num_features=self.conv_ch[0]),
            nn.ELU(),
            nn.MaxPool2d((1,3), stride=3)
        )
        self.block2 = nn.Sequential(
            nn.Conv2d(in_channels=self.conv_ch[0], out_channels=self.conv_ch[1], kernel_size=(1,10), stride=1),
            nn.BatchNorm2d(num_features=self.conv_ch[1]),
            nn.ELU(),
            nn.MaxPool2d((1,3), stride=3)
        )
        self.block3 = nn.Sequential(
            nn.Conv2d(in_channels=self.conv_ch[1], out_channels=self.conv_ch[2], kernel_size=(1,10), stride=1),
            nn.BatchNorm2d(num_features=self.conv_ch[2]),
            nn.ELU(),
            nn.MaxPool2d((1,3), stride=3)
        )
        self.block4 = nn.Sequential(
            nn.Conv2d(in_channels=self.conv_ch[2], out_channels=self.conv_ch[3], kernel_size=(1,10), stride=1),
            nn.BatchNorm2d(num_features=self.conv_ch[3]),
            nn.ELU(),
            nn.MaxPool2d((1,3), stride=3)
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(400, 4)
        )
    def forward(self, x):
        x = self.block1(x)
        print(f&quot;经过 block 1 后的形状：{x.shape}&quot;)
        x = self.block2(x)
        print(f&quot;经过 block 2 后的形状：{x.shape}&quot;)
        x = self.block3(x)
        print(f&quot;经过 block 3 后的形状：{x.shape}&quot;)
        x = self.block4(x)
        print(f&quot;经过 block 4 后的形状：{x.shape}&quot;)
        x = self.classifier(x)
        return x

model = DeepConvNet()
output = model(eeg_input)
print(output.shape)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Shallow ConvNet&lt;/strong&gt;
&lt;img src=&quot;./ShallowConvNet.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;输入：$(1,44,534)$，卷积核$(40,1,25)$-&gt;输出：$(40,44,510)$&lt;/li&gt;
&lt;li&gt;输入：$(40,44,510)$，卷积核$(40,44,1)$-&gt;输出：$(40,1,510)$&lt;/li&gt;
&lt;li&gt;平均池化，池化核$(1,75)$，输出：$(40,1,30)$&lt;/li&gt;
&lt;li&gt;展开，分为4类&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
from torch import nn

eeg_input = torch.randn(size=(44,534))
eeg_input = eeg_input.unsqueeze(dim=0) # (1,44,534)
eeg_input = eeg_input.unsqueeze(dim=0) # (1,1,44,534)

class ShallowConvNet(nn.Module):
    def __init__(self, num_C=44, num_T=534):
        super().__init__()
        self.model = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=40, kernel_size=(1,25), stride=1),
            nn.Conv2d(in_channels=40, out_channels=40, kernel_size=(44,1), stride=1),
            nn.AvgPool2d(kernel_size=(1,15)),
            nn.Flatten(),
            nn.Linear(40*34,4)
        )
    def forward(self, x):
        x = self.model(x)
        return x
    
model = ShallowConvNet()
output = model(eeg_input)
print(output.shape)
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>一文读懂域适应神经网络（DANN）</title><link>https://www.wht0909.top/blog/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E5%9F%9F%E9%80%82%E5%BA%94%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9Cdann/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E5%9F%9F%E9%80%82%E5%BA%94%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9Cdann</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E5%9F%9F%E9%80%82%E5%BA%94%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9Cdann/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E5%9F%9F%E9%80%82%E5%BA%94%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9Cdann</guid><description>解决域适应问题的深度学习框架</description><pubDate>Sat, 16 May 2026 15:59:58 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;1. 研究背景：域适应问题&lt;/h2&gt;
&lt;p&gt;域适应神经网络（DANN）由 Yaroslav Ganin 等人在文章&quot;Domain-Adversarial Training of Neural Networks&quot;中首次提出，主要用于解决域适应问题。常规的机器学习算法包含一个潜在的数学假设，即要求训练集和独立测试集是&lt;strong&gt;独立同分布&lt;/strong&gt;的。举例来说，如果任务目标是训练一个能够识别不同犬类品种图片的机器学习算法，那么训练集和测试集必须都是狗的图片。如果训练集里都是狗的图片，而测试集里都是猫的图片，那模型一定会失效。然而，出于数据量稀少或数据本身性质的原因（如脑电信号的类内特异性），一些数据集难以保证训练集和测试集的数据分布情况相同，这就是“跨域”问题。训练集的样本空间被称为“源域”（Source Domain），测试集的样本空间被称为“目标域”（Target Domain）。基于以上背景，“域适应”问题被提出：对于分布不同但有相似性质的训练集和测试集，如何使得在源域上训练的模型能正常在目标域上使用？&lt;/p&gt;
&lt;p&gt;在 DANN 以前，已有一些相关工作提出了解决方法。常见的解决方案包括对齐统计矩、基于几何结构的对齐方法、基于权重的对齐方法等等。DANN 提出了一个基于深度学习的解决方案，其核心结构包括三部分：&lt;strong&gt;特征提取器&lt;/strong&gt;、&lt;strong&gt;标签预测器&lt;/strong&gt;和&lt;strong&gt;域分类器&lt;/strong&gt;。稍后我们将在“算法结构”小节加以解释。&lt;/p&gt;
&lt;h2&gt;2. 数学原理&lt;/h2&gt;
&lt;p&gt;训练数据和测试数据的特征分布不一致的现象称为&lt;strong&gt;协变量偏移&lt;/strong&gt;。设样本特征为$X$，标签为$y$，模型学习到的规律便是$P(y|X)$。值得注意的是，$P(y|X)$在源域和目标域是相同的，也就是说模型学习到的规律本身没有发生改变。结合第一小节的阐述，模型失效的原因是源域和目标域的$X$不服从同一种分布。源域的样本服从$$D_{S}(X,y)$$的联合分布，目标域的样本服从$$D_{T}^{X}$$的边缘分布。依据协变量偏移假设，两域标签条件分布$P(y|x)$一致，只需对齐源域与目标域在特征空间中的输入边缘分布，即可让源域训练的分类器泛化至目标域。&lt;/p&gt;
&lt;p&gt;数学定义（以二维离散情况为例）：&lt;/p&gt;
&lt;p&gt;设二维随机变量$(x,y)$的联合分布为$P(x,y)$，则:
$$P_{X}(x)=\sum_{y}P(x,y)$$&lt;/p&gt;
&lt;p&gt;在域适应问题中，源域（训练集）是带标签的，既知道$X$也知道$y$，因此能够得到联合分布；而目标域（测试集）是不带标签的，只知道$X$不知道$y$，因此只能得到边缘分布。&lt;/p&gt;
&lt;p&gt;原文摘录如下：
&lt;img src=&quot;./image1.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;从源域$D_{S}$中获取带标签的数据$(x_{i},y_{i})$（样本服从$D_{S}(x,y)$的联合分布），从目标域中获取无标签的数据$\textbf{x}&lt;em&gt;{i}$（样本服从$D&lt;/em&gt;{T}^{X}$的边缘分布）。寻找一个从样本空间映射到标签空间的分类器$\eta$（模型），使得$\eta(\textbf{x})$尽可能的等于真实标签$y$。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. 算法思路&lt;/h2&gt;
&lt;p&gt;DANN 尝试让特征提取器学习“域无关”的特征（这一步就是在进行“域对齐”操作），再将学习到的特征输入标签预测器，实现准确的预测。“域无关”在这里指的是那些&lt;strong&gt;不包含领域专属信息&lt;/strong&gt;的特征。比如说，源域是充足光照下的猫咪图片，目标域是夜晚时的猫咪图片，任务目标是识别图中的小猫品种。我们当然希望模型去学习和“猫”有关的特征，而不是和“光照”相关的特征。在这个例子中，光照信息就是域相关的，猫的特征就是域无关的。&lt;/p&gt;
&lt;p&gt;DANN 的域分类器和特征提取器将进行&lt;strong&gt;对抗学习&lt;/strong&gt;。域分类器尽可能地区分来自源域和来自目标域的样本，而特征提取器则尽可能提取能够让域分类器混淆的特征。我们可以举例说明这个训练过程：特征提取器提取了特征$A$，输入到域分类器中，域分类器识别特征$A$来自源域。在训练过程中，域分类器的分类能力会越来越强；而特征提取器也会越来越强，提取出让域分类器难以区分的特征。最终情况是：域分类器已经很强了，但还是无法准确识别特征提取器提取的特征$A$是来自源域还是目标域。当达到这个效果时，我们就说特征提取器成功提取出了“域无关”的特征。在更新参数时，用到的重要结构便是&lt;strong&gt;梯度反转层（GRL）&lt;/strong&gt;，也是 DANN 的主要创新点之一。&lt;/p&gt;
&lt;p&gt;总结一下模型训练的流程：我们通过特征提取器$f$，学习到一个&lt;strong&gt;域不变的特征表示&lt;/strong&gt;，使得源域和目标域在特征空间的分布尽可能接近；然后在这个共享的特征空间上训练分类器$\eta$，让它在源域上分类正确，同时因为特征分布对齐，它也能在目标域上表现良好。&lt;/p&gt;
&lt;p&gt;$$样本空间 \stackrel{特征提取器f}{\longrightarrow} 特征空间 \stackrel{分类器\eta}{\longrightarrow} 标签空间$$&lt;/p&gt;
&lt;h2&gt;4. 算法结构&lt;/h2&gt;
&lt;p&gt;如前文所述，DANN 的核心结构包括三部分：&lt;strong&gt;特征提取器&lt;/strong&gt;、&lt;strong&gt;标签预测器&lt;/strong&gt;和&lt;strong&gt;域分类器&lt;/strong&gt;。其中域分类器用于判别样本所属领域，特征提取器负责剔除域专属信息、学习域无关特征，标签预测器输出最终的预测结果。DANN 的算法结构如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./image2.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;特征提取器$G_{f}$，其参数为$\theta_{f}$（上图绿色部分）；标签预测器$G_{y}$，参数为$\theta_{y}$；域分类器$G_{d}$，参数为$\theta_{d}$&lt;/p&gt;
&lt;p&gt;前向传播过程如下：输入向量$\mathbf{x}$在经过特征提取器$G_{f}$后，提取到特征向量$f$。$f$随后进入两个模块：（1）标签预测器。$f$在经过$G_{y}$预测后，得到预测值$\hat{y}$，可计算和真实标签$y$之间的损失$L_{y}$。（2）域分类器。$f$在经过梯度反转层后，经过$G_{d}$得到域标签$\hat{d}$，可计算和真实域标签$d$之间的损失$L_{d}$。在前向传播时，梯度反转层不起任何作用，相当于一个$f\rightarrow f$的恒等映射。&lt;/p&gt;
&lt;p&gt;DANN 的训练是一个&lt;strong&gt;极大极小问题&lt;/strong&gt;。在同一轮反向传播中自动实现三模块不同优化目标，整体构成极小极大优化博弈。（1）训练标签预测器。这部分的目标是&lt;strong&gt;最小化&lt;/strong&gt;损失$L_{y}$，使标签预测尽可能准确。梯度更新的方向是$-\frac{\partial L_{y}}{\partial \theta_{y}}$，反向传播后得到更新好的参数$\theta_{y}$。（2）训练域分类器。这部分的目标是&lt;strong&gt;最小化&lt;/strong&gt;损失$L_{d}$，使域分类尽可能准确。梯度更新的方向是$-\frac{\partial L_{d}}{\partial \theta_{d}}$。反向传播后得到更新好的参数$\theta_{d}$。（3）训练特征提取器。特征提取器的损失来源于$L_{d}$和$L_{y}$。训练目标是：在最大化分类准确率的同时尽可能地混淆域分类器（让标签预测器尽量准确，域分类器尽量不准确），也就是在让$L_{y}$尽可能小的同时让$L_{d}$尽可能大，因此梯度是：&lt;/p&gt;
&lt;p&gt;$$\nabla_{\theta_{f}} L= \frac{\partial L_{y}}{\partial \theta_{f}}-\lambda \frac{\partial L_{d}}{\partial \theta_{f}}$$&lt;/p&gt;
&lt;p&gt;在反向传播时，梯度反转层的作用便是上式的$-\lambda$（其中$\lambda$是超参数）。经过对抗达到平衡时，域分类器已经是最强状态，无法再进一步区分两个域的特征；特征提取器也已经学到了域无关特征，既能让分类器分准类别，又能让域分类器无法区分来源。&lt;/p&gt;
&lt;p&gt;SGD的更新规则就是（$\alpha$是学习率）：&lt;/p&gt;
&lt;p&gt;$$\theta_{f} \leftarrow \theta_{f}-\alpha \cdot (\frac{\partial L_{y}}{\partial \theta_{f}}-\lambda \frac{\partial L_{d}}{\partial \theta_{f}})$$&lt;/p&gt;
&lt;p&gt;$$Loss = \min_{\theta_{f},\theta_{y}} \max_{\theta_{d}} (L_{y} - \lambda L_{d})$$&lt;/p&gt;
&lt;p&gt;解读：&lt;/p&gt;
&lt;p&gt;参数$\theta_{f}$、$\theta_{y}$的更新目标是让$L_{y} - \lambda L_{d}$尽可能小（式子中的$\min$），也就是让$L_{y}$尽可能小，$L_{d}$尽可能大。参数$\theta_{d}$的更新目标是让$L_{d}$尽可能小（$\theta_{d}$无法调控$L_{y}$，因为它只是域分类器的参数）。&lt;/p&gt;
&lt;p&gt;参考代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import torch
from torch import nn

class ReversalLayer(torch.autograd.Function):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    @staticmethod
    def forward(ctx, x, alpha):
        &quot;&quot;&quot;
        Args:
            ctx : 上下文变量，用于存 alpha
            x : 输入张量
            alpha : 梯度反转强度
        &quot;&quot;&quot;
        ctx.alpha = alpha
        return x
    
    @staticmethod
    def backward(ctx, grad_in):
        alpha = ctx.alpha
        return -grad_in * alpha, None

class DANN(nn.Module):
    def __init__(self, class_num, alpha):
        super().__init__()
        self.class_num = class_num
        self.alpha = alpha
        self.feature_extractor = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=10, kernel_size=(1,64), padding=&apos;same&apos;),
            nn.BatchNorm2d(num_features=10),
            nn.Conv2d(in_channels=10, out_channels=10, kernel_size=(8,1), padding=0),
            nn.BatchNorm2d(num_features=10),
            nn.ELU(),
            nn.MaxPool2d(kernel_size=(1,4)),
            nn.Conv2d(in_channels=10, out_channels=5, kernel_size=(1,1)),
            nn.Flatten() # torch.Size([1, 10000])
        )
        self.classifier = nn.Sequential(
            nn.Linear(10000, 500),
            nn.ReLU(),
            nn.Linear(500, self.class_num)
        )
        self.domain_classifier = nn.Sequential(
            nn.Linear(10000, 500),
            nn.ReLU(),
            nn.Linear(500, 2)
        )
    def forward(self, x):
        features = self.feature_extractor(x)
        class_res = self.classifier(features)
        features2 = ReversalLayer.apply(features, self.alpha)
        domain_res = self.classifier(features2)
        return class_res, domain_res
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码部分参考了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://omegaxyz.com/2021/09/05/dann/&quot;&gt;OmegaXYZ：迁移学习模型 DANN 实现&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.tencent.com/developer/article/2159650&quot;&gt;torch.autograd.Function 用法及注意事项&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>脑电数据处理教程</title><link>https://www.wht0909.top/blog/%E8%84%91%E7%94%B5%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%95%99%E7%A8%8B/%E8%84%91%E7%94%B5%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%95%99%E7%A8%8B</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%84%91%E7%94%B5%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%95%99%E7%A8%8B/%E8%84%91%E7%94%B5%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86%E6%95%99%E7%A8%8B</guid><description>介绍脑电信号的产生原理、特征分析方法，以及基于 EEGLAB 的脑电数据预处理完整流程</description><pubDate>Sun, 10 May 2026 12:21:21 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;脑电图（Electroencephalography, EEG）通过放置在头皮表面的电极记录大脑神经元的突触后电位变化，是认知神经科学和临床诊断中广泛使用的非侵入式脑功能检测技术。由于头皮脑电信号强度微弱且由多种信号混合叠加而成，原始数据需要经过严格的预处理才能用于后续的统计分析。本文从脑电信号的生理基础出发，介绍脑电的预处理方法，并基于 EEGLAB（2026.0.0 版本）详细阐述数据导入、电极定位、滤波、降采样、分段与基线校正、插值坏导、重参考、独立成分分析（ICA）以及伪迹剔除等十个预处理关键步骤的具体操作。&lt;/p&gt;
&lt;h2&gt;1. 脑电信号的产生&lt;/h2&gt;
&lt;p&gt;大脑中的神经元通过动作电位和突触后电位进行信息传递，其电活动可通过不同尺度的记录技术进行测量：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;膜片钳法&lt;/strong&gt;：在离体细胞或脑片上记录单个离子通道的电流活动，信号强度为毫伏（mV）级别，但无法在活体上进行。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;局部场电位&lt;/strong&gt;：将微电极插入深部脑区，记录电极尖端附近神经元群体的突触后电位总和，信号强度为微伏（$\mu \rm{V}$）级别，属于侵入式记录。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;头皮脑电&lt;/strong&gt;：在头皮表面放置电极，记录大脑皮层锥体神经元同步放电所产生的电场变化。由于信号须穿过颅骨、脑脊液和头皮等介质（即容积传导效应），其强度极为微弱，且由多种信号&lt;strong&gt;混合叠加&lt;/strong&gt;而成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本文所讨论的&quot;脑电&quot;指头皮脑电。&lt;/p&gt;
&lt;h2&gt;2. 脑电的特征与分析&lt;/h2&gt;
&lt;p&gt;脑电信号可从以下四个维度进行描述：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时间&lt;/strong&gt;：信号随时间的演变过程&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幅度&lt;/strong&gt;：信号的电压强度（$\mu \rm{V}$）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频率&lt;/strong&gt;：单位时间内的振荡次数（Hz）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通道&lt;/strong&gt;：不同电极位置的信号空间分布&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于上述维度，脑电分析可分为以下几类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时域分析&lt;/strong&gt;：时间 + 幅度，如事件相关电位的波幅和潜伏期测量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;频域分析&lt;/strong&gt;：幅度 + 频率，通过傅里叶变换将时域信号转换至频域，计算各频段的功率谱密度&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;时频分析&lt;/strong&gt;：时间 + 幅度 + 频率，利用短时傅里叶变换（STFT）或小波变换考察信号的频谱随时间的动态变化&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;功能连接&lt;/strong&gt;：时间 + 幅度 + 频率 + 通道，分析不同脑区之间的相干性、相位同步等关系&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据实验范式的不同，脑电图可分为两类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EEG（静息态）&lt;/strong&gt;：受试者在闭眼或睁眼静息状态下记录的自发脑电活动。主要用于频域分析、时域分析、功能连接和微状态分析，常作为被试间变量用于正常人与患者的对比研究。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ERP（任务态）&lt;/strong&gt;：事件相关电位，通过向受试者呈现特定刺激（视觉、听觉等）诱发的锁时脑电活动。主要用于时域分析、时频分析和溯源分析，常作为被试内变量用于不同实验条件的对比。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415161106.png&quot; alt=&quot;EEG与ERP的分类&quot;&gt;&lt;/p&gt;
&lt;p&gt;脑电技术的核心优势在于其&lt;strong&gt;高时间分辨率&lt;/strong&gt;（可达毫秒级），特别适合时频分析；但其空间分辨率较低，难以精确定位深部脑区的神经活动源。&lt;/p&gt;
&lt;h2&gt;3. 脑电的预处理&lt;/h2&gt;
&lt;p&gt;预处理是脑电数据分析的前提和基础。原始脑电数据含有大量非神经活动来源的噪声和伪迹，只有经过系统化的预处理，后续的统计分析才能得出可靠结论。预处理的完整流程如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415161523.png&quot; alt=&quot;预处理流程图&quot;&gt;&lt;/p&gt;
&lt;p&gt;在正式开始预处理之前，需要先完成 EEGLAB 的安装和配置。首先从 &lt;a href=&quot;https://sccn.ucsd.edu/eeglab/&quot;&gt;EEGLAB 官网&lt;/a&gt; 下载最新版本（本文使用 eeglab2026.0.0），将安装包解压至 &lt;code&gt;D:/Matlab_EEG_Toolbox/&lt;/code&gt; 目录下。在 MATLAB 中打开该文件夹，将其添加到 MATLAB 的搜索路径中：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/8dc17ca848ca16beffaf5fe772bce03e.png&quot; alt=&quot;添加EEGLAB到MATLAB路径&quot;&gt;&lt;/p&gt;
&lt;p&gt;EEGLAB 文件夹图标变亮即表示设置成功。此后在 MATLAB 命令窗口中输入 &lt;code&gt;eeglab&lt;/code&gt; 即可启动 EEGLAB：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416154106.png&quot; alt=&quot;EEGLAB启动界面&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.1 导入数据&lt;/h3&gt;
&lt;p&gt;不同厂商的脑电采集设备输出的数据格式各不相同。常见的原始数据格式包括：&lt;/p&gt;
&lt;p&gt;| 格式 | 说明 |
|------|------|
| &lt;code&gt;.eeg&lt;/code&gt; + &lt;code&gt;.vmrk&lt;/code&gt; + &lt;code&gt;.vhdr&lt;/code&gt; | BrainVision 格式，三者配套，不可缺失 |
| &lt;code&gt;.edf&lt;/code&gt; / &lt;code&gt;.bdf&lt;/code&gt; | 欧洲标准格式，&lt;code&gt;.bdf&lt;/code&gt; 精度更高 |
| &lt;code&gt;.cnt&lt;/code&gt; | 老款 NeuroScan 设备导出格式 |
| &lt;code&gt;.mff&lt;/code&gt; | EGI 设备导出格式 |&lt;/p&gt;
&lt;p&gt;EEGLAB 处理的内部格式为 &lt;code&gt;.set&lt;/code&gt; + &lt;code&gt;.fdt&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;BrainVision 格式（&lt;code&gt;.eeg&lt;/code&gt; + &lt;code&gt;.vmrk&lt;/code&gt; + &lt;code&gt;.vhdr&lt;/code&gt;）中三个文件必须完整：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.vhdr&lt;/code&gt;（头文件）：记录电极位置、采样率、通道数等元信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.vmrk&lt;/code&gt;（标记文件）：记录事件和刺激标记（如刺激出现的时间点）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.eeg&lt;/code&gt;（数据文件）：存储真实的脑电波形数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EEGLAB 格式（&lt;code&gt;.set&lt;/code&gt; + &lt;code&gt;.fdt&lt;/code&gt;）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.set&lt;/code&gt;：相当于说明书，记录通道信息、事件、采样率、电极位置和处理历史等信息&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.fdt&lt;/code&gt;：存储真实的脑电信号数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在导入数据前，需要先安装 &lt;strong&gt;bva-io&lt;/strong&gt; 插件（参见&lt;a href=&quot;https://blog.csdn.net/qq_52137732/article/details/145034680&quot;&gt;相关教程&lt;/a&gt;），否则会出现如下错误：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;You are using the latest version of EEGLAB.

错误使用 [plugin_askinstall](matlab:matlab.lang.internal.introspective.errorDocCallback\(&apos;plugin_askinstall&apos;,%20&apos;D:\Matlab_EEG_Toolbox\eeglab2026.0.0\functions\adminfunc\plugin_askinstall.m&apos;,%2051\)) ([第 51 行](matlab:%20opentoline\(&apos;D:\Matlab_EEG_Toolbox\eeglab2026.0.0\functions\adminfunc\plugin_askinstall.m&apos;,51,0\)))  
Cannot find bva-io extension, use EEGLAB Extension Manager to install it  

错误使用 [matlab.ui.internal.controller.WebMenuController/fireMenuSelectedEvent](matlab:matlab.lang.internal.introspective.errorDocCallback\(&apos;matlab.ui.internal.controller.WebMenuController/fireMenuSelectedEvent&apos;,%20&apos;E:\matlab\toolbox\matlab\uitools\uicomponents\components\+matlab\+ui\+internal\+controller\WebMenuController.m&apos;,%2085\)) ([第 85 行](matlab:%20opentoline\(&apos;E:\matlab\toolbox\matlab\uitools\uicomponents\components\+matlab\+ui\+internal\+controller\WebMenuController.m&apos;,85,0\)))  
计算 Menu Callback 时出错。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装插件后，通过菜单 &lt;code&gt;File → Import data → Using EEGLAB functions and plugins → From Brain Vis. Rec. .vhdr file&lt;/code&gt; 导入数据。在弹出的对话框中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Channels/Data&lt;/strong&gt;：默认即可&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interval&lt;/strong&gt;：时间切片，如输入 &lt;code&gt;[1 1000]&lt;/code&gt; 则仅导入第 1 至第 1000 个采样点之间的数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Channels&lt;/strong&gt;：通道选择，如输入 &lt;code&gt;[1:2 4]&lt;/code&gt; 则仅导入第 1、2、4 通道的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415165654.png&quot; alt=&quot;导入数据对话框&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以为数据命名（将显示在数据集列表中，也可留空）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416155943.png&quot; alt=&quot;命名数据集&quot;&gt;&lt;/p&gt;
&lt;p&gt;导入完成后的界面如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415165758.png&quot; alt=&quot;导入完成的EEGLAB界面&quot;&gt;&lt;/p&gt;
&lt;p&gt;导入完成后，通过 &lt;code&gt;File → Save current dataset as&lt;/code&gt; 将数据保存为 &lt;code&gt;.set&lt;/code&gt; 文件。&lt;/p&gt;
&lt;h3&gt;3.2 电极定位&lt;/h3&gt;
&lt;p&gt;此时 EEG 数据中仅有 Fp1、Fp2 等电极的标签名，尚未关联其在大脑表面的三维空间位置。电极定位的目的正是为每个电极标签匹配真实的解剖位置坐标。&lt;/p&gt;
&lt;p&gt;国际通用的电极放置标准为 &lt;strong&gt;10–20 国际标准导联系统&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415200133.png&quot; alt=&quot;10-20国际标准导联系统&quot;&gt;&lt;/p&gt;
&lt;p&gt;EEGLAB 中内置了 10–20 系统的 75 导电极排布：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415200313.png&quot; alt=&quot;EEGLAB 75导电极排布&quot;&gt;&lt;/p&gt;
&lt;p&gt;此外，EEGLAB 的默认电极文件 &lt;code&gt;standard-10-5-cap385.elp&lt;/code&gt; 采用 10–05 系统排布，共包含 385 个电极。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作步骤&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 &lt;code&gt;File → Load Existing Dataset&lt;/code&gt; 加载已保存的 &lt;code&gt;.set&lt;/code&gt; 文件：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415201854.png&quot; alt=&quot;加载数据集&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;通过 &lt;code&gt;Edit → Channel locations&lt;/code&gt; 打开电极定位对话框：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415201956.png&quot; alt=&quot;Channel locations对话框&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;在下拉菜单中选择 &lt;code&gt;Use BESA file for 4-shell dipfit spherical model&lt;/code&gt;，然后点击 OK。该选项使用 EEGLAB 内置的 &lt;code&gt;standard-10-5-cap385.elp&lt;/code&gt; 模板文件（标准电极文件）进行电极位置匹配。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击 &lt;code&gt;Plot 2D&lt;/code&gt; 查看电极二维分布。下面的小字表示 64 个电极位置只显示了 62 个。若命令行提示 &lt;code&gt;Channel lookup: no location for HEO, VEO&lt;/code&gt;，说明 HEO 和 VEO 两个通道未匹配到位置信息，也就是说这两个通道的数据是空的（正常情况下用不到这两个，需要去掉，这里我们先不去掉，把它补上）：&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415202645.png&quot; alt=&quot;2D电极分布图，62/64通道已定位&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;在 MATLAB 中打开 &lt;code&gt;standard-10-5-cap385.elp&lt;/code&gt; 文件（在 &lt;code&gt;D:\Matlab_EEG_Toolbox\eeglab2026.0.0\plugins\dipfit\standard_BESA\standard-10-5-cap385.elp&lt;/code&gt;），可发现模板中的正确字段名为 &lt;strong&gt;HEOG&lt;/strong&gt; 和 &lt;strong&gt;VEOG&lt;/strong&gt;（而非 HEO 和 VEO）：&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415202827.png&quot; alt=&quot;模板文件中的字段名&quot;&gt;&lt;/p&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;将 &lt;code&gt;Channel locations&lt;/code&gt; 对话框中 HEO 和 VEO 对应的标签修改为 HEOG、VEOG 后，重新点击 &lt;code&gt;Look up locs&lt;/code&gt; 即可完成定位。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;完成后将数据另存为 &lt;code&gt;02_channel_location/1.set&lt;/code&gt;。再次加载该文件时，&lt;code&gt;Channel locations&lt;/code&gt; 字段应显示为 &lt;code&gt;Yes&lt;/code&gt;。&lt;/p&gt;
&lt;h3&gt;3.3 剔除无用电极&lt;/h3&gt;
&lt;p&gt;在整个 EEG 信号处理流程中，以下电极通常不参与后续分析，需要剔除：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HEOG&lt;/strong&gt;（垂直眼电）：记录眨眼活动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VEOG&lt;/strong&gt;（水平眼电）：记录眼球水平运动（眼睛左右转）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M1、M2&lt;/strong&gt;（双侧乳突）：常用作参考电极&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;：&lt;code&gt;Edit → Select data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415205030.png&quot; alt=&quot;数据筛选对话框&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 Channel 输入框中填入要剔除的通道，勾选 &lt;code&gt;on-&gt;remove these&lt;/code&gt;，点击 OK：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415205317.png&quot; alt=&quot;剔除无用电极&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.4 滤波&lt;/h3&gt;
&lt;p&gt;脑电数据中混有来自环境和生理活动的各类噪声，包括 50 Hz 的市电（工频）干扰、高频肌电噪声和低频皮肤电漂移等。&lt;strong&gt;滤波的目的并非直接在时域中将超过截止频率的信号&quot;删除&quot;，而是通过傅里叶变换将信号转换至频域，在频域中去除噪声对应的频率成分后再逆变换回时域&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;常用的四种滤波器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;高通滤波器&lt;/strong&gt;：保留高于截止频率的信号成分，抑制低频漂移&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;低通滤波器&lt;/strong&gt;：保留低于截止频率的信号成分，抑制高频噪声&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;带通滤波器&lt;/strong&gt;：仅保留特定频率范围内的信号&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;带阻滤波器&lt;/strong&gt;：抑制特定频率范围内的信号（如 50 Hz 工频干扰）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;滤波参数的选取须结合脑电的自然节律：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415210953.png&quot; alt=&quot;脑电节律分类表&quot;&gt;&lt;/p&gt;
&lt;p&gt;推荐参数范围：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;低通滤波（上界）：30–100 Hz&lt;/li&gt;
&lt;li&gt;高通滤波（下界）：0.1–1 Hz&lt;/li&gt;
&lt;li&gt;带阻滤波（陷波）：49–51 Hz 或 48–52 Hz&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;：&lt;code&gt;Tools → Filter the data → Basic FIR filter (new, default)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415211300.png&quot; alt=&quot;滤波菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;依次设置高通滤波（以 0.1 Hz 为例）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415211342.png&quot; alt=&quot;高通滤波设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;程序运行中：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415211409.png&quot; alt=&quot;滤波运行中&quot;&gt;&lt;/p&gt;
&lt;p&gt;低通滤波（以 40 Hz 为例）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415211726.png&quot; alt=&quot;低通滤波设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;带阻滤波（以 48–52 Hz 为例），注意勾选 &lt;code&gt;Notch filter the data instead of pass band&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260415211801.png&quot; alt=&quot;带阻滤波设置&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.5 降采样&lt;/h3&gt;
&lt;p&gt;采样率指单位时间内记录的采样点数量，单位为 Hz。其计算公式为：&lt;/p&gt;
&lt;p&gt;$$\text{采样率} = \frac{\text{采样点个数}}{\text{采样时间}}$$&lt;/p&gt;
&lt;p&gt;采样从时刻 0 开始：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416164123.png&quot; alt=&quot;采样示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;根据 Nyquist 采样定理，采样率须大于感兴趣的信号最高频率的 2 倍，通常建议达到 3–4 倍。对于脑电信号而言，&lt;strong&gt;250–1000 Hz 的采样率已足够&lt;/strong&gt;。过高的采样率会增大数据量，降低后续计算效率。降采样的意义在于&lt;strong&gt;减少数据量、提高计算速度&lt;/strong&gt;，但此步骤并非必须。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;：&lt;code&gt;Tools → Change sampling rate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416165022.png&quot; alt=&quot;降采样菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;将采样率设为 500 Hz：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416165056.png&quot; alt=&quot;设置新采样率&quot;&gt;&lt;/p&gt;
&lt;p&gt;保存降采样后的数据：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416165159.png&quot; alt=&quot;保存降采样数据&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.6 分段和基线校正&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416165818.png&quot; alt=&quot;基线漂移示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416165933.png&quot; alt=&quot;基线校正原理&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;分段操作&lt;/strong&gt;：&lt;code&gt;Tools → Extract epochs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416170046.png&quot; alt=&quot;Extract epochs菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416170323.png&quot; alt=&quot;分段参数设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;此字段用于指定以何种事件编码为基准切分数据。若留空并按 OK，EEGLAB 将对数据中所有事件类型（&lt;code&gt;10&lt;/code&gt;, &lt;code&gt;100&lt;/code&gt;, &lt;code&gt;11&lt;/code&gt;, &lt;code&gt;200&lt;/code&gt;...）进行切分，生成混杂的试次；若输入特定事件编码（如 &lt;code&gt;10&lt;/code&gt;），EEGLAB 仅在该事件出现的时间点前后进行切分。对于分条件的分析（如仅分析&quot;正确反应&quot;试次），正确选择事件类型至关重要。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Out-of-bounds EEG limits if any [min max]&lt;/strong&gt; 是 EEGLAB 中处理事件靠近数据边界的安全限制参数，用来处理事件离数据边界太近，导致截取的试次超出原始数据范围的情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;留空（默认）：若截取范围超出原始数据边界则直接报错，确保数据完整性&lt;/li&gt;
&lt;li&gt;填写 &lt;code&gt;[min max]&lt;/code&gt; 数值：允许生成不完整试次，超出部分用填充值替代，保证试次长度一致。比如填 &lt;code&gt;[-100 100]&lt;/code&gt;，超出数据范围的部分会用 ±100 的数值填充（或 NaN），保证试次长度一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;点击&lt;code&gt;Time-locking event type(s)&lt;/code&gt;右侧的&lt;code&gt;...&lt;/code&gt;可查看数据中的全部事件类型：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416170352.png&quot; alt=&quot;事件列表&quot;&gt;&lt;/p&gt;
&lt;p&gt;事件编码含义（本示例数据集）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;10&lt;/code&gt;、&lt;code&gt;11&lt;/code&gt;：分别为不同类型刺激的呈现编码&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100&lt;/code&gt;、&lt;code&gt;200&lt;/code&gt;：分别为错误和正确反应的事件标记&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boundary&lt;/code&gt;：边界事件，通常标记数据分段处&lt;/li&gt;
&lt;li&gt;&lt;code&gt;empty&lt;/code&gt;：空事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;选择事件编码 10 和 11 进行分段：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171003.png&quot; alt=&quot;选择事件类型&quot;&gt;&lt;/p&gt;
&lt;p&gt;分段完成后的数据概览：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171130.png&quot; alt=&quot;分段后数据&quot;&gt;&lt;/p&gt;
&lt;p&gt;如需仅保留特定事件编码的试次（如仅保留包含正确反应标记的试次）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417102543.png&quot; alt=&quot;选择保留事件&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 type 下拉菜单中选择要保留的事件编码（此处选择 200），点击 OK：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417102650.png&quot; alt=&quot;选择200事件&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417102723.png&quot; alt=&quot;筛选结果&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;基线校正操作&lt;/strong&gt;：&lt;code&gt;Tools → Remove baseline&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171241.png&quot; alt=&quot;基线校正菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;若分段时设定了 epoch 的时间范围为 −1 至 2 秒，则基线时间默认为 −100 至 0 毫秒（刺激前 100 毫秒）。保持默认值，点击 OK：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171349.png&quot; alt=&quot;基线校正参数&quot;&gt;&lt;/p&gt;
&lt;p&gt;可选择直接覆盖原文件保存：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171512.png&quot; alt=&quot;保存确认&quot;&gt;&lt;/p&gt;
&lt;p&gt;处理后的数据包含 200 个试次（epochs），每个试次时长为 3 秒（−1 至 2 秒）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171625.png&quot; alt=&quot;处理完成的数据&quot;&gt;&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;Plot → Channel data (scroll)&lt;/code&gt; 查看脑电图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416171751.png&quot; alt=&quot;查看脑电图菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416172229.png&quot; alt=&quot;脑电图参数设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;图中左侧为 62 个通道的波形。调整右下角的比例尺可以改变通道波形的纵向间距（此处建议调至 50–100，使各通道波形更为清晰可分）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416172347.png&quot; alt=&quot;调整比例尺后的脑电图&quot;&gt;&lt;/p&gt;
&lt;p&gt;在本示例数据中，事件标记 100 和 200 虽非分段的基准事件，但由于它们恰好落在 −1 至 2 秒的 epoch 时间窗口内，因此同样显示在脑电图中。&lt;/p&gt;
&lt;h3&gt;3.7 插值坏导和剔除坏段&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;容积传导效应&lt;/strong&gt;：大脑内部的电活动并非直接传导至头皮上的电极，而是经由脑脊液、颅骨和头皮等介质传播。正是由于这种信号在空间上的连续性，才能利用周围正常电极的数据对坏导通道进行数学插值。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坏段&lt;/strong&gt;指部分时间段内所有通道信号均出现异常。与坏导不同，坏段直接删除即可——因为最终的 ERP 分析会对所有保留的试次进行叠加平均，个别坏段的删除不会影响结果的统计有效性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416201819.png&quot; alt=&quot;坏导坏段示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;坏导插值操作&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;假设通道 O1 为坏导。通过 &lt;code&gt;Tools → Interpolate electrodes&lt;/code&gt;，在 Channel 输入框中输入 O1，勾选 &lt;code&gt;on remove&lt;/code&gt;，保存即可：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416202222.png&quot; alt=&quot;选择O1通道&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416202338.png&quot; alt=&quot;移除O1通道&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;去坏段操作&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;在脑电波形浏览界面，用鼠标左键选中坏段区域，点击下方的 &lt;code&gt;Reject&lt;/code&gt; 按钮：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416202758.png&quot; alt=&quot;手动选择坏段&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时控制台输出 &lt;code&gt;2/100 trials rejected&lt;/code&gt;，数据集的 epoch 数目由 100 减少为 98：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416202919.png&quot; alt=&quot;去坏段后的数据&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.8 重参考&lt;/h3&gt;
&lt;p&gt;脑电信号本质上是两个电极之间的电位差。在线记录时通常使用一个物理参考电极，而离线分析中常需要重新选择参考点（重参考），以满足不同分析目的。常见参考方案包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;全脑平均参考&lt;/strong&gt;：将所有电极的平均电位作为参考，适用于高密度导联&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;双侧乳突参考&lt;/strong&gt;：以 TP9、TP10（双侧乳突电极）的平均电位为参考，常用于低密度导联&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;REST 参考&lt;/strong&gt;：基于溯源模型的零参考技术&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;重参考必须置于插值步骤之后&lt;/strong&gt;，以确保参与平均参考计算的电极均为有效电极。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416203324.png&quot; alt=&quot;重参考概念图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;：&lt;code&gt;Tools → Re-reference the data&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416203515.png&quot; alt=&quot;重参考菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416203927.png&quot; alt=&quot;重参考设置&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compute average reference&lt;/strong&gt;：全脑平均参考，将所有电极的均值作为新的参考零点&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Huber average ref. with threshold&lt;/strong&gt;：基于 Huber 回归的进阶版平均参考，对异常电极具有更好的鲁棒性&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Re-reference data to channels&lt;/strong&gt;：双侧乳突平均参考，选中的 TP9 TP10 是对应的乳突电极&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Interpolate removed channel(s)&lt;/strong&gt;：插值移除的通道&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;重参考完成后，数据集的 &lt;code&gt;Reference&lt;/code&gt; 字段显示为 &lt;code&gt;TP9 TP10&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416204524.png&quot; alt=&quot;重参考完成&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.9 独立成分分析（ICA）&lt;/h3&gt;
&lt;p&gt;独立成分分析（Independent Component Analysis, ICA）是脑电预处理中的核心步骤，旨在从混合的头皮脑电信号中分离出统计上独立的源成分，从而识别并去除眼动、眨眼、肌电等伪迹。&lt;/p&gt;
&lt;p&gt;ICA 的前提假设：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;头皮记录的脑电信号是多个源信号（脑活动源 + 伪迹源）的线性混合&lt;/li&gt;
&lt;li&gt;源信号之间在统计上相互独立&lt;/li&gt;
&lt;li&gt;源信号的数量不超过记录通道的数量&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416204808.png&quot; alt=&quot;ICA原理示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416205207.png&quot; alt=&quot;ICA分解示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;：&lt;code&gt;Tools → Decompose data by ICA&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416205329.png&quot; alt=&quot;ICA菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416205540.png&quot; alt=&quot;ICA参数设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;上图中 PCA 成分数设为 59，计算方法为：原始 64 通道 − 剔除 HEOG、VEOG（2 个）− 剔除双侧乳突参考（2 个）− 插值的坏导（1 个）= 59 个有效成分。ICA 在运行时将首先使用 PCA 将数据降至该维度。&lt;/p&gt;
&lt;p&gt;ICA 计算较为耗时，运行界面如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416205730.png&quot; alt=&quot;ICA运行中&quot;&gt;&lt;/p&gt;
&lt;p&gt;运行完成后，数据集的 &lt;code&gt;ICA weights&lt;/code&gt; 字段显示为 &lt;code&gt;Yes&lt;/code&gt;：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416210254.png&quot; alt=&quot;ICA完成&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260416210355.png&quot; alt=&quot;ICA weights确认&quot;&gt;&lt;/p&gt;
&lt;h3&gt;3.10 剔除伪迹/坏段&lt;/h3&gt;
&lt;p&gt;完成 ICA 分解后，需要识别并剔除与伪迹对应的独立成分，然后对剩余的坏段数据进行清理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;去除伪迹&lt;/strong&gt;：利用 &lt;strong&gt;ICLabel&lt;/strong&gt; 插件对每个独立成分进行自动分类。ICLabel 基于预训练的深度学习模型，将每个 IC 标注为脑活动、眼动、肌电、心电、线路噪声等七种类型之一的概率。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417101349.png&quot; alt=&quot;ICLabel标记界面1&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417101544.png&quot; alt=&quot;ICLabel标记界面2&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417101646.png&quot; alt=&quot;ICLabel标记界面3&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417101655.png&quot; alt=&quot;ICLabel标记界面4&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417101704.png&quot; alt=&quot;ICLabel标记界面5&quot;&gt;&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;Tools → Classify components using ICLabel → Label components&lt;/code&gt; 完成自动标记：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103005.png&quot; alt=&quot;ICLabel分类菜单&quot;&gt;&lt;/p&gt;
&lt;p&gt;点击 OK 开始分类：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103023.png&quot; alt=&quot;ICLabel分类确认&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103112.png&quot; alt=&quot;ICLabel运行中&quot;&gt;&lt;/p&gt;
&lt;p&gt;分类完成后，EEGLAB 以饼图形式展示各成分的来源概率：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103300.png&quot; alt=&quot;ICLabel分类结果饼图&quot;&gt;&lt;/p&gt;
&lt;p&gt;可点击各成分编号查看其详细属性，包括地形图、功率谱和时域波形：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103442.png&quot; alt=&quot;单个IC详情&quot;&gt;&lt;/p&gt;
&lt;p&gt;设置预阈值标记伪迹成分后，通过 &lt;code&gt;Tools → Classify components using ICLabel → Flag components as artifacts&lt;/code&gt; 去除标记的伪迹成分：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103708.png&quot; alt=&quot;标记伪迹成分&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103725.png&quot; alt=&quot;标记阈值设置&quot;&gt;&lt;/p&gt;
&lt;p&gt;终端输出标记结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103747.png&quot; alt=&quot;终端标记输出&quot;&gt;&lt;/p&gt;
&lt;p&gt;刚才只是标记了伪迹，现在去掉伪迹：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417103903.png&quot; alt=&quot;确认去除成分&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417104047.png&quot; alt=&quot;去除伪迹成分后&quot;&gt;&lt;/p&gt;
&lt;p&gt;需注意，ICA 的随机初始化和优化过程使其结果具有&lt;strong&gt;不可重复性&lt;/strong&gt;，每次运行可能得到略有不同的分解结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;去坏段：绝对值法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;去除电压幅度超过 $100\ \mu\text{V}$ 的试次（在更为严格的标准下可降为 $70\ \mu\text{V}$）：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417110718.png&quot; alt=&quot;绝对值法去坏段&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;预处理的最后一步，必须手动逐段浏览全部数据&lt;/strong&gt;，确保不存在遗漏的异常段。例如在浏览中发现 F6 电极的基线持续向上漂移：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417112455.png&quot; alt=&quot;F6电极异常漂移&quot;&gt;&lt;/p&gt;
&lt;p&gt;此时需要回溯至插值坏导步骤，重新加载数据检查该通道的状态：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./imgs/Pasted_image_20260417112841.png&quot; alt=&quot;重新加载插值前数据&quot;&gt;&lt;/p&gt;
&lt;p&gt;若发现 F6 在插值前即已存在问题，则需从插值步骤重新进行处理。这种迭代式的手动质量检查是确保预处理质量的最后防线。&lt;/p&gt;
&lt;h2&gt;4. 小结&lt;/h2&gt;
&lt;p&gt;脑电数据的预处理是一个系统化、多步骤的工程流程。本文从脑电信号的产生原理出发，依次介绍了数据导入、电极定位、无用电极剔除、滤波、降采样、分段与基线校正、插值坏导与剔除坏段、重参考、ICA 分解及伪迹剔除等十个步骤的完整操作流程与参数设置依据。每一步均需结合具体的实验设计和数据质量进行灵活调整。&lt;/p&gt;
&lt;h2&gt;相关资源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1Bh4113776/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;零基础脑电数据处理教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hfechina.com/article-item-198.html&quot;&gt;MATLAB脑电数据处理精要（三、实操）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>第一次剪视频</title><link>https://www.wht0909.top/blog/%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%89%AA%E8%A7%86%E9%A2%91/%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%89%AA%E8%A7%86%E9%A2%91</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%89%AA%E8%A7%86%E9%A2%91/%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%89%AA%E8%A7%86%E9%A2%91</guid><description>记录第一次剪辑视频的经历</description><pubDate>Tue, 21 Apr 2026 15:09:30 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;这两天为了准备形势与政策课程的大作业，简单学了一下怎么剪视频。我使用的视频剪辑软件是剪映。这个软件的基础操作比较简单，容易上手，而且功能非常齐全。即使有些功能需要开通VIP才能使用，但在淘宝上也能找到比较便宜的代充/账号租借服务。笔者最后在淘宝上租了一个SVIP账号，租借时长为一天，只花了1.88RMB，还是非常合适的。&lt;/p&gt;
&lt;p&gt;我的主题是“从夯到拉锐评川大附近美食”。我没用太多花里胡哨的功能，主要使用了字幕配音和图片切换。简单来说，只要安排好不同时间点上应该出现什么图片、什么字幕，再用软件自带的自动朗读功能就可以了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%89%AA%E8%BE%91%E8%BF%87%E7%A8%8B.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后展示一下剪辑结果吧~&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>试次、通道与采样点</title><link>https://www.wht0909.top/blog/%E8%AF%95%E6%AC%A1%E9%80%9A%E9%81%93%E4%B8%8E%E9%87%87%E6%A0%B7%E7%82%B9/%E8%AF%95%E6%AC%A1%E9%80%9A%E9%81%93%E4%B8%8E%E9%87%87%E6%A0%B7%E7%82%B9</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%AF%95%E6%AC%A1%E9%80%9A%E9%81%93%E4%B8%8E%E9%87%87%E6%A0%B7%E7%82%B9/%E8%AF%95%E6%AC%A1%E9%80%9A%E9%81%93%E4%B8%8E%E9%87%87%E6%A0%B7%E7%82%B9</guid><description>EEG相关概念的辨析</description><pubDate>Sun, 19 Apr 2026 21:03:33 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;{/* 封面摄影作品：Masao Yamamoto(&lt;a href=&quot;https://www.zhiweiye.com/shan-ben-chang-nan&quot;&gt;https://www.zhiweiye.com/shan-ben-chang-nan&lt;/a&gt;)

 */}&lt;/p&gt;
&lt;p&gt;最近在学习EEG信号的预处理，主要是和EEGLAB相关的一些操作。在听&lt;a href=&quot;https://www.bilibili.com/video/BV1Gg4y1G7pP?spm_id_from=333.788.videopod.sections&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;网课&lt;/a&gt;的时候，老师提到了这样一个问题：在EEGLAB加载数据后，如何在matlab中编写代码来提取出第二个通道第300ms的平均幅值？当时在这个部分卡住了很久，代码本身难度并不大，只有三四行；但其中的概念很容易搞混。这篇博客将记录解决这个问题的过程。&lt;/p&gt;
&lt;h2&gt;1. 概念辨析：试次、通道与采样点&lt;/h2&gt;
&lt;p&gt;首先要解决的问题是：这三个名词是什么意思？在BCI研究的过程中，我们通常使用脑电帽从头皮上提取脑电信号（EEG），并对提取出的信号做各种处理。脑电帽通常如下图所示：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E8%84%91%E7%94%B5%E5%B8%BD.png&quot; alt=&quot;脑电帽&quot;&gt;&lt;/p&gt;
&lt;p&gt;脑电帽上分布有很多电极，他们的排列方式通常依据“10-20国际标准导联系统”[1]。头皮上贴的一个电极，就代表一个&lt;strong&gt;通道(channel)&lt;/strong&gt;。脑电采集系统通过电极捕捉大脑活动过程中传递到头皮上的电信号，每个电极位置捕捉到的电压随时间变化，在波形上表现为一条“时间-幅度”曲线。由于时间是一个连续变量，在一段时间内，系统只能做有限次测量。也就是说，采集系统会&lt;strong&gt;每隔一段时间采集一次数据&lt;/strong&gt;。系统每做一次采集，就称为进行了一次&lt;strong&gt;采样（sample）&lt;/strong&gt;。单位时间内（1s）采集的次数，就称为采样率，单位为赫兹（Hz）。例如1000Hz的采样率，就意味着每秒钟采集1000次数据，也就是1ms采集一次数据。最终采集到的脑电信号类似于下图。左侧的（FP1、FPZ...）代表通道，这里展示了10个通道的数据。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E8%84%91%E7%94%B5%E5%9B%BE.png&quot; alt=&quot;脑电图&quot;&gt;&lt;/p&gt;
&lt;p&gt;在EEG研究中，给大脑一个刺激，大脑就会产生一个电信号，这个过程称为一个&lt;strong&gt;试次（trial）&lt;/strong&gt;。举个例子来说明：人眼每看到一张图片（刺激），大脑会产生一个电信号，这是一个试次；实验过程中一共看到了五十张图片，就是五十个试次。由于事件激发的脑电信号（事件相关电位，ERP）比较微弱，通常需要多个试次叠加平均才能看到清晰的ERP波形。&lt;/p&gt;
&lt;h2&gt;2. 如何提取特定条件下的平均幅值&lt;/h2&gt;
&lt;p&gt;回到最初的问题：如何在matlab中编写代码来提取出第二个通道第300ms的平均幅值？要解决这个问题，就要先知道试次、通道、幅值、时间这些变量在EEGLAB中是如何存储的。这里我已经提前加载好数据了，我的数据集如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%95%B0%E6%8D%AE%E9%9B%86.png&quot; alt=&quot;数据集&quot;&gt;&lt;/p&gt;
&lt;p&gt;在matlab的工作区中，找到EEG这个结构体，双击点开，可以看到其中有很多的字段。我们主要使用的是&lt;em&gt;EEG.times&lt;/em&gt;和&lt;em&gt;EEG.data&lt;/em&gt;。&lt;em&gt;EEG.times&lt;/em&gt;是一个$1\times 1500$的行向量，代表了1500个采样点，和上图数据集中显示的相符。&lt;em&gt;EEG.data&lt;/em&gt;的大小是$60\times 1500\times 98$。他们代表的含义非常关键：&lt;strong&gt;60代表60个通道，1500代表有1500个采样点，98代表有98个试次&lt;/strong&gt;。每个索引对应的值就是EEG信号的&lt;strong&gt;幅度&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%B7%A5%E4%BD%9C%E5%8C%BA.png&quot; alt=&quot;工作区&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./EEG.data.png&quot; alt=&quot;EEG.data&quot;&gt;&lt;/p&gt;
&lt;p&gt;分析到这里，我们可以画出一个EEG信号的示意图。要想提取出第二个通道第300ms的平均幅值，我们只需要先把所有试次中的第二个通道全部拿出来，取一个平均，再锁定300ms处的值就可以了。当然，我们也可以直接对所有试次做一个平均（就像上面说的对ERP的处理一样），再锁定第二个通道和第300ms处的数值。这两种方法对应的代码不同，但思路是一样的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%A4%BA%E6%84%8F%E5%9B%BE.png&quot; alt=&quot;示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;matlab代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-matlab&quot;&gt;clc;clear;
eeglab;
% 这里需要手动加载数据集
% 在matlab中编写代码来提取出第二个通道第300ms的平均幅值
% method1: 先处理第二个通道
TOI = find(EEG.times == 300); % 找300ms对应的索引值。TOI（Time Of Interest）意思是感兴趣的时间点/时间窗口
ans = squeeze(mean(EEG.data(2, TOI, :), 3));

% method2: 先得到ERP
avg_erp = squeeze(mean(EEG.data, 3));
TOI = find(EEG.times == 300);
ans = avg_erp(2, TOI);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考文献与脚注&lt;/p&gt;
&lt;p&gt;[1] 10-20系统电极放置法是国际脑电图学会规定的标准电极放置法。额极中点至鼻根的距离和枕点至枕外粗隆的距离各占此连线全长的10％，其余各点均以此连线全长的20％相隔，因此命名为10-20系统。来源：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/101329490&quot;&gt;10-20国际标准导联系统&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>希尔伯特变换</title><link>https://www.wht0909.top/blog/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2/%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2</guid><description>介绍希尔伯特变换</description><pubDate>Tue, 14 Apr 2026 12:06:33 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;在读论文时看到了这个概念，学习一下。信号与系统的知识都快忘光了...&lt;/p&gt;
&lt;h2&gt;1. 解析信号&lt;/h2&gt;
&lt;p&gt;已知，实信号$f(t)$的频谱是共轭对称的。也就是说，如果$f(t)$的幅度谱存在$f_{0}$分量，那么也一定存在$-f_{0}$分量&lt;/p&gt;
&lt;p&gt;根据傅里叶变换，$$\mathcal{F}(f^{&lt;em&gt;}(t))=F^{&lt;/em&gt;}(-j\omega)$$&lt;/p&gt;
&lt;p&gt;由于$f(t)$是实信号，有$$f(t)=f^{*}(t)$$&lt;/p&gt;
&lt;p&gt;则$$F(j\omega)=F^{*}(-j\omega)$$&lt;/p&gt;
&lt;p&gt;根据共轭的性质，$$F(j\omega)$$和$$F(-j\omega)$$的模长相等（幅度谱关于 0Hz 左右对称），相位相反（相位谱关于原点奇对称）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%AE%9E%E4%BF%A1%E5%8F%B7%E7%9A%84%E9%A2%91%E8%B0%B1.png&quot; alt=&quot;实信号的频谱&quot;&gt;&lt;/p&gt;
&lt;p&gt;所以当实信号的正谱确定时，负谱也随之确定。因此只需要单边谱即可表示是信号的频率信息。单边谱对应的时域信号通常是一个&lt;strong&gt;复信号&lt;/strong&gt;，这个信号就称为&lt;strong&gt;解析信号&lt;/strong&gt;，记作$$f_{s}(t)$$&lt;/p&gt;
&lt;p&gt;单边谱可以通过将双边谱的负半轴对称叠加到正半轴上获得，即：&lt;/p&gt;
&lt;h1&gt;$$
F_s(j\omega) = F(j\omega)\left(1 + \text{sgn},\omega\right)&lt;/h1&gt;
&lt;p&gt;\begin{cases}
2F(j\omega), &amp;#x26; \omega &gt; 0 \[6pt]
0, &amp;#x26; \omega &amp;#x3C; 0
\end{cases}
$$&lt;/p&gt;
&lt;p&gt;对$$F_{s}(j\omega)$$求傅里叶反变换即可得到$$f_{s}(t)$$：&lt;/p&gt;
&lt;p&gt;$$f_{s}(t)=f(t)+jf(t)*\frac{1}{\pi t}=f(t)+j\hat{f}(t)$$&lt;/p&gt;
&lt;p&gt;将$$f(t)*\frac{1}{\pi t}$$记作$$\hat{f}(t)$$。$$\hat{f}(t)$$就是$$f(t)$$的&lt;strong&gt;希尔伯特变换&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;2. 希尔伯特变换&lt;/h2&gt;
&lt;p&gt;信号$$f(t)$$的希尔伯特变换$$\hat{f}(t)=f(t)*\frac{1}{\pi t}$$&lt;/p&gt;
&lt;p&gt;频谱函数$$\mathcal{F}(\hat{f}(t))=-jF(j\omega)\text{sgn}(\omega)$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;频域含义&lt;/strong&gt;：希尔伯特变换实际上进行了“移相”的操作。对于频率分量为正的部分，使其相位滞后$$90^{\circ}$$；对于频率分量为负的部分，使其相位超前$$90^{\circ}$$&lt;/p&gt;
&lt;p&gt;$$f(t)=\hat{f}(t)*(-\frac{1}{\pi t})$$&lt;/p&gt;
&lt;p&gt;希尔伯特变换结果：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%B8%8C%E5%B0%94%E4%BC%AF%E7%89%B9%E5%8F%98%E6%8D%A2.png&quot; alt=&quot;希尔伯特变换&quot;&gt;&lt;/p&gt;
&lt;p&gt;参考代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-matlab&quot;&gt;f0 = 10;
fs = 100;
L = 100;
t = (0:L-1)/fs;

x = cos(2*pi*f0*t);
analytic = hilbert(x);
h = imag(analytic);

% FFT + fftshift 得到对称频谱
X = fftshift(fft(x));
H = fftshift(fft(h));
f = (-L/2:L/2-1)*fs/L;

figure(&apos;Color&apos;,&apos;w&apos;);

% 1. 幅度谱
subplot(2,2,1);
plot(f, abs(X), &apos;b&apos;,&apos;LineWidth&apos;,1.5);
title(&apos;cos 信号幅度谱&apos;);
xlim([-50,50]); ylabel(&apos;|X(f)|&apos;);

subplot(2,2,2);
plot(f, abs(H), &apos;r&apos;,&apos;LineWidth&apos;,1.5);
title(&apos;希尔伯特变换 sin 信号幅度谱&apos;);
xlim([-50,50]); ylabel(&apos;|H(f)|&apos;);

% 2. 相位谱
subplot(2,2,3);
plot(f, angle(X), &apos;b&apos;,&apos;LineWidth&apos;,1.5);
title(&apos;cos 信号相位谱&apos;);
xlim([-50,50]); ylabel(&apos;相位 (rad)&apos;);
ylim([-pi, pi]);

subplot(2,2,4);
plot(f, angle(H), &apos;r&apos;,&apos;LineWidth&apos;,1.5);
title(&apos;希尔伯特变换 sin 信号相位谱&apos;);
xlim([-50,50]); ylabel(&apos;相位 (rad)&apos;);
ylim([-pi, pi]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后放一张示意图[2]
&lt;img src=&quot;./%E7%A4%BA%E6%84%8F%E5%9B%BE.png&quot; alt=&quot;示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;参考资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1]&lt;a href=&quot;https://www.bilibili.com/video/BV13qWCznE5d?spm_id_from=333.788.videopod.sections&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;【10分钟速学信号与系统】希尔伯特变换&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[2]&lt;a href=&quot;https://blog.csdn.net/edogawachia/article/details/79366444&quot;&gt;希尔伯特变换（Hilbert Transform）简介及其物理意义&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="undefined"/><enclosure url="undefined"/></item><item><title>谈谈我的个性签名</title><link>https://www.wht0909.top/blog/%E8%B0%88%E8%B0%88%E6%88%91%E7%9A%84%E4%B8%AA%E6%80%A7%E7%AD%BE%E5%90%8D/%E8%B0%88%E8%B0%88%E6%88%91%E7%9A%84%E4%B8%AA%E6%80%A7%E7%AD%BE%E5%90%8D</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%B0%88%E8%B0%88%E6%88%91%E7%9A%84%E4%B8%AA%E6%80%A7%E7%AD%BE%E5%90%8D/%E8%B0%88%E8%B0%88%E6%88%91%E7%9A%84%E4%B8%AA%E6%80%A7%E7%AD%BE%E5%90%8D</guid><description>Wir müssen wissen.Wir werden wissen.</description><pubDate>Sat, 11 Apr 2026 22:23:14 GMT</pubDate><content:encoded>&lt;p&gt;封面画作：Oscar-Claude Monet &lt;em&gt;&lt;strong&gt;Water Lilies, Evening Effect&lt;/strong&gt;&lt;/em&gt;(1897-1899)&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Wir müssen wissen.Wir werden wissen.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;这句话是德国数学家 David Hilbert 的墓志铭。它的意思是：&lt;strong&gt;我们必须知道，我们必将知道&lt;/strong&gt;。David Hilbert 是19世纪末和20世纪前期最具影响力的数学家之一，被誉为“现代数学之父”。他的主要贡献包括：希尔伯特空间、公理化几何等。1900年，David Hilbert 在巴黎的国际数学家大会上提出了 23 个数学问题（后来被称为“希尔伯特的问题”），推动了20世纪数学的发展。&lt;/p&gt;
&lt;p&gt;从高二开始，我就把这句话当做QQ签名了。中间换过几回，不过最后又换了回来。我始终觉得这句话代表了学习的至高境界：仅仅是出于对未知的好奇，想要“知道”。某种程度上，和亚里士多德的“哲学起源于惊奇”异曲同工。当今时代，知识正变得越来越“廉价”：以前需要写几周的程序，Claude Code一个下午就能写完；以前需要翻阅大量书籍文献才能找到的答案，现在问问豆包就可以了。但在摒弃了看似繁琐的学习过程后，我们真的掌握了“知识”吗？我们节约了敲代码、查文档的时间，但失去了历经磨难终于把程序调通那一刻的欣喜；我们提高了检索的效率，但错过了那些在阅读同行的工作时转瞬即逝的灵感。事实上，我们得到的越多，失去的就越多。&lt;/p&gt;
&lt;p&gt;现在的大学真是越来越卷了，不过作为学生，这种局势实在难以抗衡。为了获得有限的资源，不得不利用上课的时间干点别的：比如我大三一年都没怎么听过课，上课铃一响就开始跑代码做科研项目。老师考试之前划重点，只要掌握了这些“重点”就能拿一个不错的成绩。考完了没几天自然什么都不记得了（比如我现在一点也想不起来模电和数电都讲了啥，只记得这两门考试分还挺高的）。有时我不禁思考，这样的学习状态是我想要的吗？答案当然不是。那我应该怎么做呢？似乎理想化的学习状态和有限的竞争资源被放在了天平的两端，永远摇晃着。&lt;/p&gt;
&lt;p&gt;现在想想，还是很怀念大一大二的时光。当时能为了《信号与系统》书上的一个公式研究好几个晚上，即使考试不考；能为了马原的一个课堂展示去图书馆借了冯友兰的《中国哲学简史》读，后来又看了《大问题：简明哲学导论》（这本写的真好），还有一些尼采（虽然都没咋看懂）。到底是这个快的时代容不下一点慢，还是我离正确的道路越来越远了呢？我也想不明白。啰嗦了这么多，谨以此句鞭策自己，希望我永远走在探索的路上。&lt;/p&gt;</content:encoded><h:img src="/_astro/封面作品.CVE4BPz3.png"/><enclosure url="/_astro/封面作品.CVE4BPz3.png"/></item><item><title>BioPapers-生物医学文献检索系统</title><link>https://www.wht0909.top/blog/biopapers-%E7%94%9F%E7%89%A9%E5%8C%BB%E5%AD%A6%E6%96%87%E7%8C%AE%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F/biopapers-%E7%94%9F%E7%89%A9%E5%8C%BB%E5%AD%A6%E6%96%87%E7%8C%AE%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F</link><guid isPermaLink="true">https://www.wht0909.top/blog/biopapers-%E7%94%9F%E7%89%A9%E5%8C%BB%E5%AD%A6%E6%96%87%E7%8C%AE%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F/biopapers-%E7%94%9F%E7%89%A9%E5%8C%BB%E5%AD%A6%E6%96%87%E7%8C%AE%E6%A3%80%E7%B4%A2%E7%B3%BB%E7%BB%9F</guid><description>自己开发的文献检索系统</description><pubDate>Fri, 10 Apr 2026 16:46:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;封面画作：Peter Brown &lt;em&gt;&lt;strong&gt;December Morning, The Brazen Head, Glasgow&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;前段时间正好有空，就想着把上个暑假开发的项目（PaperFinding）更新一下，再重做一版。编写完代码后，我在阿里云租了一台2核2G的轻量级服务器，成功部署了项目。这台服务器即将到期，出于费用原因不再续费（1个月四十五块钱，不是很划算）。于是写一篇博客记录一下BioPapers的一些技术细节、部署方法等，可以当做项目的README文档，也可以作为一个网页开发的技术指南。&lt;/p&gt;
&lt;p&gt;github链接：&lt;a href=&quot;https://github.com/WHT0909/BioPapers&quot;&gt;BioPapers&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. 项目初衷&lt;/h2&gt;
&lt;p&gt;BioPapers的前身是&lt;a href=&quot;https://github.com/WHT0909/PaperFinding&quot;&gt;PaperFinding&lt;/a&gt;，完成于2025年8月。当时刚完成保研夏令营的一堆琐事，度过了一段还算是清闲的时光。PaperFinding的功能很简单：做一个类似于PubMed的主界面，通过调用PubMed提供的api接口获取文献信息，以卡片形式展示。用户点击文献卡片就能跳转到文献详情页面，展示文献作者、摘要等信息。和PubMed不同的是，我在文献详情页面加入了一个大模型文献助手。文献助手能够帮助用户分析文献内容、创新点等，还可以和用户互动回答问题。作为一个Vibe Coding的产物（我的代码功底确实很有限），借助AI便能够完成其中大部分的功能。在一切正常进行的时候，大模型的部署出现了一点问题，卡住了很久。我当时的想法是去阿里云的某个api平台（好像是火山引擎，不太记得了）调用大模型，但搞了好几天一直报错。最后没办法，我在本地借助ollama部署了一个0.2B的deepseek，勉强实现了需求。本地部署暴露的问题很明显：如果我想在服务器上部署这个项目，要连着模型文件一起打包进去，文件大小剧增；如果用户想要本地部署我的项目，还要自己准备一个大模型，流程非常复杂。由于这些问题的存在，PaperFinding的可利用性大大降低，只能当作玩具了。再后来我忙于保研，就没再完善这个项目。&lt;/p&gt;
&lt;p&gt;BioPapers的部分灵感来源于一次和朋友（下文用他的姓名首字母M代替）的聊天。那天我正在和M等三个朋友打麻将，M提到他前几天开发了一个用deepseek自动分析文献的网站，能并行地分析很多个pdf。提及具体的代码实现，M说deepseek的官网就提供了api服务。受此启发，我修改了BioPapers的api源头，从阿里云改到了deepseek。又考虑到我只从PubMed的文献数据库中获取文章，就把项目改名为了BioPapers。&lt;/p&gt;
&lt;h2&gt;2. 构思与实现&lt;/h2&gt;
&lt;p&gt;BioPapers要完成三个页面：&lt;strong&gt;文献检索页（主页面）、大模型文献助手页和项目介绍页（About）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;BioPapers的大部分代码都由AI完成。我发现AI在前端页面的设计上还是很不错的，能完成绝大部分需求。剩下的部分，如卡片间距、布局等只需要微调即可。我通常使用浏览器的开发者模式（F12）微调页面，用鼠标选中要微调的盒子模型，改改间距什么的，非常方便。&lt;/p&gt;
&lt;p&gt;在PaperFinding中，我使用html+css+Js的传统组合搭建前端页面，用Python Flask框架构建后端。这次我使用Python FastAPI作为后端框架。据官方文档说FastAPI是目前最快的框架之一。而且FastAPI和Flask比较像，应该相对容易上手。&lt;/p&gt;
&lt;h3&gt;2.1 文献检索页&lt;/h3&gt;
&lt;p&gt;文献检索页的功能如下：用户在检索框输入关键词，点击查询按钮，等待片刻在主页面显示文章卡片。点击文章卡片时，跳转到文献详情页面，展示作者、发表年份、摘要等信息。点击按钮触发事件的这部分肯定是交给JS了。核心的问题是如何获取并展示文献。好在NCBI提供了Entrez库，里面给了PubMed文献的api接口。Entrez的部分函数介绍可以看这篇文章：&lt;a href=&quot;https://zhuanlan.zhihu.com/p/619251748&quot;&gt;Biopython从入门到精通之Entrez:esearch,efetch和elink&lt;/a&gt;。我这里用了两个比较关键的函数：&lt;code&gt;Entrez.esearch&lt;/code&gt;和&lt;code&gt;Entrez.efetch&lt;/code&gt;。涉及到的部分代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;handle = Entrez.esearch(
    db=&quot;pubmed&quot;,
    term=query,
    retmax=max_results,
    retstart=start,
    sort=&quot;relevance&quot;,
    retmode=&quot;xml&quot;,
    api_key=PUBMED_API_KEY
)

record = Entrez.read(handle)
handle.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码写在&lt;code&gt;search_pubmed&lt;/code&gt;函数里，目标是获取文章的pmid。在此之前，我定义了两个类：一个是&lt;em&gt;Article&lt;/em&gt;，用来存储文章信息，包括pmid、标题、作者等；一个是&lt;em&gt;SearchResult&lt;/em&gt;，它是一个包含了一堆&lt;em&gt;Article&lt;/em&gt;的列表。上面代码里获取的record是一个包含了pmid和一些其他信息的字典。通过解析record，我们就获取到了关键的pmid。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;handle = Entrez.efetch(
    db=&quot;pubmed&quot;,
    id=&quot;,&quot;.join(pmids),
    rettype=&quot;xml&quot;,
    retmode=&quot;xml&quot;,
    api_key=PUBMED_API_KEY
)

records = Entrez.read(handle)
handle.close()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的目的是获取文章的具体信息。我们已经有了每篇文献唯一的标识符pmid，可以用它获取具体内容了。在后续的代码中，我们只需要把需要的信息写在&lt;em&gt;Article&lt;/em&gt;类里面，在前端页面上解析并显示就可以了。&lt;/p&gt;
&lt;p&gt;剩下的的功能比较琐碎，像翻页、每页显示的文献数量、按时间筛选等。但只要有了文献的具体信息，按规则筛选就是件很容易的事了，我选择全部交给AI😂&lt;/p&gt;
&lt;p&gt;展示几张效果图吧~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%A3%80%E7%B4%A2%E9%A1%B5.png&quot; alt=&quot;检索页&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9F%A5%E8%AF%A2%E7%BB%93%E6%9E%9C.png&quot; alt=&quot;查询结果&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%96%87%E7%8C%AE%E8%AF%A6%E6%83%85.png&quot; alt=&quot;文献详情&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2.2 大模型文献助手页&lt;/h3&gt;
&lt;p&gt;这部分基于&lt;a href=&quot;https://langchain-doc.cn/&quot;&gt;LangChain&lt;/a&gt;框架调用deepseek大模型，api从&lt;a href=&quot;https://platform.deepseek.com/usage&quot;&gt;deepseek开放平台&lt;/a&gt;获取。LangChain是比较主流的大模型框架了，提供了非常多的常用接口。文献助手的功能分为三个部分：&lt;strong&gt;根据用户提供的文本分析、根据pdf分析和对话（Chat）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;首先是文本分析。这部分的逻辑是：先检查用户有没有配置deepseek api key，如果没有的话自然就用不了这个功能了（在我的网站上，api key由本人提供）。在分析文本之前需要先设置一些提示词（prompt），帮助用户分析文章内容、创新点等（现在看到创新点这个词都有点PTSD了）。再构建LangChain调用链，把提示词和文本内容拼起来喂给大模型。大模型返回的内容是一个&lt;em&gt;AnalysisResponse&lt;/em&gt;对象，类型是字符串。代码展示如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/api/assistant/analyze-text&quot;, response_model=AnalysisResponse)
async def analyze_text(request: TextAnalysisRequest):
    if not llm:
        raise HTTPException(status_code=500, detail=&quot;DeepSeek API key not configured&quot;)
    
    try:
        prompt = ChatPromptTemplate.from_template(&quot;&quot;&quot;
        你是一个专业的文献分析助手，请分析以下文献内容，总结其主要内容、创新点、研究方法和结论。
        
        文献内容：
        {text}
        
        请按照以下结构输出分析结果：
        1. 主要内容
        2. 创新点
        3. 研究方法
        4. 结论
        5. 潜在的研究方向
        &quot;&quot;&quot;)
        
        chain = prompt | llm
        response = chain.invoke({&quot;text&quot;: request.text})
        
        return AnalysisResponse(response=response.content)
    except Exception as e:
        print(f&quot;Analysis error: {e}&quot;)
        raise HTTPException(status_code=500, detail=&quot;分析失败，请稍后重试&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里有一个有意思的LangChain语法：&lt;code&gt;chain = prompt | llm&lt;/code&gt;。在LangChain中，上面代码里的&lt;code&gt;llm&lt;/code&gt;、&lt;code&gt;chain&lt;/code&gt;都是&lt;em&gt;Runnable&lt;/em&gt;类型。根据&lt;a href=&quot;https://reference.langchain.org.cn/python/langchain_core/runnables/&quot;&gt;LangChain官方文档&lt;/a&gt;的说法，“Runnable是一个可以被调用、批量处理、流式传输、转换和组合的工作单元”。&lt;code&gt;|&lt;/code&gt;运算符起到串联的作用，将前一个&lt;em&gt;Runnable&lt;/em&gt;的输出作为后一个&lt;em&gt;Runnable&lt;/em&gt;的输入。关于LangChain的链式调用规则可以看这篇博客[1]。&lt;/p&gt;
&lt;p&gt;invoke 用于可调用对象（比如LLM）的执行操作。它的输入是一个包含message的消息字典，输出是一个包含执行期间交换的所有消息列表（用户输入、助手回复、工具调用）等信息的字典。&lt;/p&gt;
&lt;p&gt;pdf分析也很类似，只不过多了一个读取pdf内容的环节。这里通过&lt;em&gt;PyPDF2.PdfReader&lt;/em&gt;实现。首先使用fastapi提供的&lt;em&gt;UploadFile&lt;/em&gt;接收用户上传的文件。根据&lt;a href=&quot;https://fastapi.org.cn/reference/uploadfile/#fastapi.UploadFile.headers&quot;&gt;fastapi官方文档&lt;/a&gt;，&lt;em&gt;UploadFile&lt;/em&gt;类的read方法从文件中读取的是字节数据。那么&lt;em&gt;PyPDF2.PdfReader&lt;/em&gt;类在初始化时需要什么样的参数呢？我又去查看了&lt;a href=&quot;https://pypdf2.readthedocs.io/en/3.x/modules/PdfReader.html#PyPDF2.PdfReader&quot;&gt;&lt;em&gt;PyPDF2.PdfReader&lt;/em&gt;的相关文档&lt;/a&gt;，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./pdfreader.png&quot; alt=&quot;PyPDF2.PdfReader&quot;&gt;&lt;/p&gt;
&lt;p&gt;可以看到，参数中包括一个&lt;em&gt;stream&lt;/em&gt;。翻译过来就是：他需要“一个File对象，或一个支持与File对象类似的标准读取和定位方法的对象。也可以是一个表示PDF文件路径的字符串”。也就是说，他需要一个类似于文件流的参数，能让&lt;em&gt;PyPDF2.PdfReader&lt;/em&gt;对象进行读取操作。这就是使用&lt;code&gt;io.BytesIO(content)&lt;/code&gt;的原因了！简单来说，&lt;code&gt;io.BytesIO(content)&lt;/code&gt;可以把这些字节拼起来，伪装成一个内存里的文件，这样就能够读取了。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/api/assistant/analyze-pdf&quot;, response_model=AnalysisResponse)
async def analyze_pdf(file: UploadFile = File(...)):
    if not llm:
        raise HTTPException(status_code=500, detail=&quot;DeepSeek API key not configured&quot;)
    
    try:
        # 读取PDF文件
        content = await file.read()
        pdf_reader = PyPDF2.PdfReader(io.BytesIO(content))
        
        # 提取文本
        text = &quot;&quot;
        for page_num in range(len(pdf_reader.pages)):
            page = pdf_reader.pages[page_num]
            text += page.extract_text()
        
        if not text:
            raise HTTPException(status_code=400, detail=&quot;无法从PDF中提取文本&quot;)
        
        # 分析文本
        prompt = ChatPromptTemplate.from_template(&quot;&quot;&quot;
        你是一个专业的文献分析助手，请分析以下PDF文献内容，总结其主要内容、创新点、研究方法和结论。
        
        文献内容：
        {text}
        
        请按照以下结构输出分析结果：
        1. 主要内容
        2. 创新点
        3. 研究方法
        4. 结论
        5. 潜在的研究方向
        &quot;&quot;&quot;)
        
        chain = prompt | llm
        response = chain.invoke({&quot;text&quot;: text})
        
        return AnalysisResponse(response=response.content)
    except Exception as e:
        print(f&quot;PDF analysis error: {e}&quot;)
        raise HTTPException(status_code=500, detail=&quot;PDF分析失败，请稍后重试&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后是文献对话。文献对话的逻辑是：要让LLM记住对话历史，当用户提问时，把对话历史和用户新提出的问题拼接到一起进行思考，进而给出回答。也就是说，我们需要一个消息列表存储对话，还要区分每段对话是人类（用户）说的还是AI（LLM）说的。这就涉及到两个类：&lt;em&gt;HumanMessage&lt;/em&gt;和&lt;em&gt;AIMessage&lt;/em&gt;。在构建好列表时候，我们只需要使用invoke方法传递给模型即可。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@app.post(&quot;/api/assistant/chat&quot;, response_model=AnalysisResponse)
async def chat(request: ChatRequest):
    if not llm:
        raise HTTPException(status_code=500, detail=&quot;DeepSeek API key not configured&quot;)
    
    try:
        # 构建对话历史
        messages = []
        for msg in request.history:
            if msg[&quot;role&quot;] == &quot;user&quot;:
                messages.append(HumanMessage(content=msg[&quot;content&quot;]))
            elif msg[&quot;role&quot;] == &quot;assistant&quot;:
                messages.append(AIMessage(content=msg[&quot;content&quot;]))
        
        # 添加当前消息
        messages.append(HumanMessage(content=request.message))
        
        # 生成回复
        response = llm.invoke(messages)
        
        return AnalysisResponse(response=response.content)
    except Exception as e:
        print(f&quot;Chat error: {e}&quot;)
        raise HTTPException(status_code=500, detail=&quot;对话失败，请稍后重试&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;展示一下成果吧~&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./LLM%E5%AF%B9%E8%AF%9D.png&quot; alt=&quot;LLM对话&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2.3 项目介绍页&lt;/h3&gt;
&lt;p&gt;这个页面用来介绍一下整个BioPapers项目，包括用法、技术栈、模型版本等。我想在这里完成三个功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在左侧设置一个随页面滑动条滚动的目录&lt;/li&gt;
&lt;li&gt;读到哪个章节，目录对应位置高亮&lt;/li&gt;
&lt;li&gt;点击目录上的章节，右侧页面自动跳转&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些功能主要通过JavaScript实现。和AI对话多轮，终于达成了我想要的效果。一起看看代码吧：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;document.addEventListener(&apos;DOMContentLoaded&apos;, function() {
    const tocLinks = document.querySelectorAll(&apos;.toc-link&apos;);
    const sections = document.querySelectorAll(&apos;.about-section-item&apos;);
    const tocSidebar = document.querySelector(&apos;.toc-sidebar&apos;);
    
    function setActiveLink() {
        let currentSection = &apos;&apos;;
        
        sections.forEach(section =&gt; {
            const sectionTop = section.offsetTop - 100;
            const sectionHeight = section.offsetHeight;
            
            if (window.scrollY &gt;= sectionTop &amp;#x26;&amp;#x26; window.scrollY &amp;#x3C; sectionTop + sectionHeight) {
                currentSection = section.getAttribute(&apos;id&apos;);
            }
        });
        
        tocLinks.forEach(link =&gt; {
            link.classList.remove(&apos;active&apos;);
            if (link.getAttribute(&apos;href&apos;) === &apos;#&apos; + currentSection) {
                link.classList.add(&apos;active&apos;);
            }
        });
    }
    
    tocLinks.forEach(link =&gt; {
        link.addEventListener(&apos;click&apos;, function(e) {
            e.preventDefault();
            const targetId = this.getAttribute(&apos;href&apos;);
            const targetSection = document.querySelector(targetId);
            
            if (targetSection) {
                const offsetTop = targetSection.offsetTop - 80;
                window.scrollTo({
                    top: offsetTop,
                    behavior: &apos;smooth&apos;
                });
            }
        });
    });
    
    window.addEventListener(&apos;scroll&apos;, function() {
        setActiveLink();
    });
    
    setActiveLink();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;总觉得JS这门语言怪怪的，和常规的C、C++、Python这些编程语言的写法不太一样。特别是这个匿名函数满天飞，看得非常不习惯。代码逻辑如下：首先要获取页面上的三个类：&lt;code&gt;.toc-link&lt;/code&gt;、&lt;code&gt;.about-section-item&lt;/code&gt;、&lt;code&gt;.toc-sidebar&lt;/code&gt;。这三个类代表的组件如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D%E9%A1%B5.png&quot; alt=&quot;项目介绍页&quot;&gt;&lt;/p&gt;
&lt;p&gt;遍历每个&lt;code&gt;.about-section-item&lt;/code&gt;组件，检测每个section和页面滚动条的相对位置。如果当前滚动条的位置在章节的顶部和底部之间，就认为正在阅读该章节。获取该章节的id，修改左侧目录对应位置的css样式，改为&lt;em&gt;active&lt;/em&gt;（需要提前在style.css中写好高亮样式&lt;em&gt;active&lt;/em&gt;）。至于点击目录跳转，只需要监听用户的&lt;em&gt;click&lt;/em&gt;事件，找到对应的章节id就可以了。&lt;/p&gt;
&lt;p&gt;这里还实现了一个类似于Mac风格的代码块样式：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* Bash Terminal Styles */
.bash-terminal {
    background-color: #1e1e1e;
    border-radius: 12px;
    overflow: hidden;
    box-shadow: var(--shadow-hover);
    margin: 1.5rem 0;
    border: 1px solid #333;
}

.terminal-header {
    background-color: #2c2c2c;
    padding: 0.75rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-bottom: 1px solid #444;
}

.terminal-buttons {
    display: flex;
    gap: 0.5rem;
}

.terminal-buttons .button {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    display: inline-block;
}

.terminal-buttons .button.close {
    background-color: #ff5f57;
}

.terminal-buttons .button.minimize {
    background-color: #ffbd2e;
}

.terminal-buttons .button.maximize {
    background-color: #28ca42;
}

.terminal-title {
    color: #cccccc;
    font-size: 0.875rem;
    font-weight: 500;
    font-family: &apos;Consolas&apos;, &apos;Monaco&apos;, &apos;Courier New&apos;, monospace;
}

.terminal-body {
    padding: 1.5rem;
    font-family: &apos;Consolas&apos;, &apos;Monaco&apos;, &apos;Courier New&apos;, monospace;
    font-size: 0.95rem;
    line-height: 1.6;
    color: #f8f8f2;
}

.command-line {
    margin-bottom: 0.75rem;
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.prompt {
    color: #50fa7b;
    font-weight: 600;
    min-width: 15px;
}

.command {
    color: #f8f8f2;
    white-space: pre-wrap;
    word-break: break-all;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;p class=&quot;section-content&quot;&gt;1. 克隆项目&amp;#x3C;/p&gt;
&amp;#x3C;div class=&quot;bash-terminal&quot;&gt;
    &amp;#x3C;div class=&quot;terminal-header&quot;&gt;
        &amp;#x3C;div class=&quot;terminal-buttons&quot;&gt;
            &amp;#x3C;span class=&quot;button close&quot;&gt;&amp;#x3C;/span&gt;
            &amp;#x3C;span class=&quot;button minimize&quot;&gt;&amp;#x3C;/span&gt;
            &amp;#x3C;span class=&quot;button maximize&quot;&gt;&amp;#x3C;/span&gt;
        &amp;#x3C;/div&gt;
        &amp;#x3C;div class=&quot;terminal-title&quot;&gt;bash&amp;#x3C;/div&gt;
    &amp;#x3C;/div&gt;
    &amp;#x3C;div class=&quot;terminal-body&quot;&gt;
        &amp;#x3C;div class=&quot;command-line&quot;&gt;
            &amp;#x3C;span class=&quot;prompt&quot;&gt;$&amp;#x3C;/span&gt;
            &amp;#x3C;span class=&quot;command&quot;&gt;git clone https://github.com/WHT0909/BioPapers.git&amp;#x3C;/span&gt;
        &amp;#x3C;/div&gt;
    &amp;#x3C;/div&gt;
&amp;#x3C;/div&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;效果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Mac%E9%A3%8E%E6%A0%BC%E4%BB%A3%E7%A0%81%E5%9D%97.png&quot; alt=&quot;Mac风格代码块&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后简单提一下这个项目怎么启动吧：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uvicorn main:app --host 0.0.0.0 --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 功能展示&lt;/h2&gt;
&lt;p&gt;放个视频展示一下BioPapers的功能~&lt;/p&gt;
&lt;h2&gt;4. 总结&lt;/h2&gt;
&lt;p&gt;BioPapers是一个融合了大模型问答助手的文献检索系统，能够帮助用户快速掌握文献内容。整个系统还存在一些问题，比如NCBI的服务器在境外，导致检索的速度很慢；deepseek的思考时间较长等。如果我有时间的话会再优化一下这个项目。感兴趣的话就在github上star一下吧😊~&lt;/p&gt;
&lt;p&gt;其他参考文献：&lt;/p&gt;
&lt;p&gt;[1] 【LangChain学习笔记】链式调用&lt;/p&gt;</content:encoded><h:img src="/_astro/封面作品.-EX7OYxv.png"/><enclosure url="/_astro/封面作品.-EX7OYxv.png"/></item><item><title>冬天的信</title><link>https://www.wht0909.top/blog/%E5%86%AC%E5%A4%A9%E7%9A%84%E4%BF%A1/%E5%86%AC%E5%A4%A9%E7%9A%84%E4%BF%A1</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E5%86%AC%E5%A4%A9%E7%9A%84%E4%BF%A1/%E5%86%AC%E5%A4%A9%E7%9A%84%E4%BF%A1</guid><description>分享诗人马雁的作品</description><pubDate>Wed, 08 Apr 2026 21:04:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;封面画作：Alfred Sisley &lt;em&gt;&lt;strong&gt;Snow Effect at Veneux&lt;/strong&gt;&lt;/em&gt;(1884)&lt;/p&gt;
&lt;p&gt;冬天的信&lt;/p&gt;
&lt;p&gt;给马骅&lt;/p&gt;
&lt;p&gt;那盏灯入夜就没有熄过。半夜里&lt;/p&gt;
&lt;p&gt;父亲隔墙问我，怎么还不睡？&lt;/p&gt;
&lt;p&gt;我哽咽着：“睡不着。” 有时候，&lt;/p&gt;
&lt;p&gt;我看见他坐在屋子中间，眼泪&lt;/p&gt;
&lt;p&gt;顺着鼻子边滚下来。前天，&lt;/p&gt;
&lt;p&gt;他尚记得理了发。我们的生活&lt;/p&gt;
&lt;p&gt;总会好一点吧，胡萝卜已经上市。&lt;/p&gt;
&lt;p&gt;她瞪着眼睛喘息，也不再生气，&lt;/p&gt;
&lt;p&gt;你给我写信正是她去世的前一天。&lt;/p&gt;
&lt;p&gt;这一阵我上班勤快了些，考评&lt;/p&gt;
&lt;p&gt;好一些了，也许能加点工资，&lt;/p&gt;
&lt;p&gt;等你来的时候，我带你去河边。&lt;/p&gt;
&lt;p&gt;夏天晚上，我常一人在那里&lt;/p&gt;
&lt;p&gt;走路，夜色里也并不能想起你。&lt;/p&gt;
&lt;p&gt;“明月出天山，苍茫云海间”，&lt;/p&gt;
&lt;p&gt;这让人安详，有力气对着虚空&lt;/p&gt;
&lt;p&gt;伸开手臂。你、我之间隔着&lt;/p&gt;
&lt;p&gt;空漠漫长的冬天。我不在时，&lt;/p&gt;
&lt;p&gt;你就劈柴、浇菜地，整理&lt;/p&gt;
&lt;p&gt;一个月前的日记。你不在时，&lt;/p&gt;
&lt;p&gt;我一遍一遍读纪德，指尖冰凉，&lt;/p&gt;
&lt;p&gt;对着蒙了灰尘的书桌发呆。&lt;/p&gt;
&lt;p&gt;那些陡峭的山在寒冷干燥的空气里&lt;/p&gt;
&lt;p&gt;也像我们这样，平静而不痛苦吗？&lt;/p&gt;
&lt;p&gt;2003 年冬&lt;/p&gt;
&lt;p&gt;马雁，回族，生于四川成都，穆斯林，当代女诗人、散文家。1997年，就读于北京大学中文系古典文献专业。2000年，与友人共同创建北大新青年网站。2002年，自印诗歌和小说合集《习作选：1999—2002》。2003年，回成都生活。2007年，自印诗集《迷人之食》。2008年，参加当代艺术广州三年展。2009年9月，获第四届珠江国际诗歌节青年诗人奖。2010年4月至9月，成为北京上苑艺术馆驻馆诗人；11月，获2010年度刘丽安诗歌奖；12月，编定待出版的随笔集《读书与跌宕自喜》；12月28日，赴上海访友；12月30日晚9时许，在上海因病意外辞世。&lt;/p&gt;
&lt;p&gt;马骅，祖籍福建，生于天津，复旦大学国际政治系毕业，诗人、支教志愿者。1991-1996年就读期间参与复旦诗社与燕园剧社，执导并参演多部戏剧，创办“北大新青年”网站，担任“诗生活”网站版主及《诗生活月刊》主编。毕业后辗转上海、厦门、北京多地，曾任北大在线频道经理，参与编纂“藏羚羊”旅行书籍。2003年2月赴云南德钦县明永村义务支教，用稿费及资助款改善学校设施，教授语文、英语并创办村民夜校，带领学生开展环保实践。其间整理藏族民间歌舞曲调，参与“神山调查”项目。2004年6月20日因交通事故坠入澜沧江失踪，遗留诗作《雪山短歌》被视为汉语诗歌的明净之作。&lt;/p&gt;</content:encoded><h:img src="/_astro/封面画作.DGS86tNY.png"/><enclosure url="/_astro/封面画作.DGS86tNY.png"/></item><item><title>记一次使用云服务器部署网站的经历</title><link>https://www.wht0909.top/blog/%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%BD%BF%E7%94%A8%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E7%BD%91%E7%AB%99%E7%9A%84%E7%BB%8F%E5%8E%86/%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%BD%BF%E7%94%A8%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E7%BD%91%E7%AB%99%E7%9A%84%E7%BB%8F%E5%8E%86</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%BD%BF%E7%94%A8%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E7%BD%91%E7%AB%99%E7%9A%84%E7%BB%8F%E5%8E%86/%E8%AE%B0%E4%B8%80%E6%AC%A1%E4%BD%BF%E7%94%A8%E4%BA%91%E6%9C%8D%E5%8A%A1%E5%99%A8%E9%83%A8%E7%BD%B2%E7%BD%91%E7%AB%99%E7%9A%84%E7%BB%8F%E5%8E%86</guid><description>用阿里云的服务器部署了一个文献检索网站</description><pubDate>Sat, 14 Mar 2026 12:00:58 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;封面画作：Alfred Sisley &lt;em&gt;&lt;strong&gt;The Bridge at Villeneuve-la-Garenne&lt;/strong&gt;&lt;/em&gt;(1872)&lt;/p&gt;
&lt;p&gt;这两天刚好没什么事做，想着把去年暑假开发的生物医学文献检索系统&lt;a href=&quot;https://github.com/WHT0909/PaperFinding&quot;&gt;PaperFinding&lt;/a&gt;迭代一下，顺便把它部署在服务器上。笔者之前在写论文的时候曾经开发过一个网站，原本想在老师提供的服务器上部署，但最后由于技术欠佳没有完成任务😭，而且急着向出版社交稿，最后干脆提供了 github 的链接作为替代。这次也想趁着这个机会再尝试一下，争取打破心理阴影😂&lt;/p&gt;
&lt;h2&gt;1. 租云服务器&lt;/h2&gt;
&lt;p&gt;在部署前，笔者已经在本地写好了代码并完成基本的调试工作。想要让别人也能够访问我们的网站，一个简单的流程是：“拥有一台服务器 -&gt; 把程序放在服务器上一直运行 -&gt; 开放对应的端口让别人能访问得到”。当然，后续还有域名绑定等环节，为了方便我们就先省略这些后续的流程了。&lt;/p&gt;
&lt;p&gt;服务器可以简单的理解为一台不关机的电脑，只要把程序放在上面就可以一直运行。我们自己使用的笔记本一般都是会关机的，一旦关机，计算机里运行的程序就会停止，只能等到再开机时我们手动运行，十分麻烦。所以第一步就是要先有一台服务器。提供云服务器租借服务的平台有很多，比如阿里云，腾讯云，华为云等。经过价格比较，笔者最终选择了阿里云的 2 核 2G 的云服务器（首月 9.9 元）。在租借服务器时可以选择阿里云提供的一些镜像，比如 Ubuntu，CentOS 等，笔者选择的是 Ubuntu 镜像，服务器信息如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9C%8D%E5%8A%A1%E5%99%A8%E4%BF%A1%E6%81%AF.png&quot; alt=&quot;服务器信息&quot;&gt;&lt;/p&gt;
&lt;h2&gt;2. 远程连接&lt;/h2&gt;
&lt;p&gt;选购好之后，可以在控制台里看到我们刚刚购买的实例的具体信息，包括 ID，IP 地址等（这个很重要，一会会用到）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9F%A5%E7%9C%8B%E6%8E%A7%E5%88%B6%E5%8F%B0.png&quot; alt=&quot;查看控制台&quot;&gt;&lt;/p&gt;
&lt;p&gt;点击下面的“重置密码”进行密码重置，这个密码是我们的 root 权限密码。在准备工作完成之后，我们就可以启动实例了。按照下图的方式操作，在选择连接方式时选择第二个。笔者在这里做了很多尝试，选择第二个是因为这个方式默认使用 root 用户登录，在接下来上传文件时不会出现权限问题。如果使用第一种方式远程连接，还需要用户提升到 root 权限才能上传本地文件（PS：笔者在这里也没有搞明白为什么第一种方式出现了权限问题，还在研究中）。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%90%AF%E5%8A%A8%E5%AE%9E%E4%BE%8B.png&quot; alt=&quot;启动实例&quot;&gt;&lt;/p&gt;
&lt;p&gt;在输入密码之后，就可以进入终端页面了。左侧的两个控件分别是终端和文件夹，如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%BB%88%E7%AB%AF%E9%A1%B5%E9%9D%A2.png&quot; alt=&quot;终端页面&quot;&gt;&lt;/p&gt;
&lt;h2&gt;3. 上传代码文件&lt;/h2&gt;
&lt;p&gt;接下来可以上传我们本地的代码了。上传方法也很简单，点击右侧带红点的“上传文件”就可以了。由于我们现在在 root 权限下操作，应该不会有权限不足的问题。笔者把代码文件上传到了 /opt 这个目录：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6.png&quot; alt=&quot;上传文件&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. 运行程序&lt;/h2&gt;
&lt;p&gt;一切准备就绪后，就可以运行我们的程序了。我的项目是后端 Python FastAPI，前端 HTML + CSS + JS。在此之前我们还需要配置一下环境：我在网上找到了一份 linux 配置 conda 环境的教程，跟着这篇文章操作就可以了：&lt;a href=&quot;https://blog.csdn.net/Alex_81D/article/details/135692506&quot;&gt;超详细的linux-conda环境安装教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于现在我们的服务器上只有 base 环境，所以需要先创建一个 conda 虚拟环境：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;conda create -n BioPapers python=3.11
conda activate BioPapers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在进入 conda 环境后，安装相应的依赖（这里需要提前在本地用 pipreqs 导出所需的依赖放入 requirements.txt）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./conda%E4%BD%8D%E7%BD%AE.png&quot; alt=&quot;conda位置&quot;&gt;&lt;/p&gt;
&lt;p&gt;解决方法也很简单：切换回那个安装了 conda 的用户就可以了：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;su - admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%88%87%E6%8D%A2%E4%B8%BAadmin%E7%94%A8%E6%88%B7.png&quot; alt=&quot;切换为admin用户&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来切换目录到项目文件夹，运行 FastAPI 程序：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uvicorn main:app --host 0.0.0.0 --port 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码的意思是：&lt;code&gt;uvicorn&lt;/code&gt;相当于网站的启动器；&lt;code&gt;main:app&lt;/code&gt;里的&lt;code&gt;main&lt;/code&gt;指的是项目的入口文件 main.py，&lt;code&gt;app&lt;/code&gt;对应着代码里的&lt;code&gt;app = FastAPI()&lt;/code&gt;，合起来表示运行 main.py 里的 FastAPI 应用；&lt;code&gt;--host 0.0.0.0&lt;/code&gt;表示允许任何 IP 访问你的网站；&lt;code&gt;--port 8000&lt;/code&gt;表示网站运行在 8000 端口。如果想让程序一直在后台运行，可以这么写：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nohup uvicorn main:app --host 0.0.0.0 --port 8000 &gt; server.log 2&gt;&amp;#x26;1 &amp;#x26;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E8%BF%90%E8%A1%8CFastAPI%E7%A8%8B%E5%BA%8F.png&quot; alt=&quot;运行FastAPI程序&quot;&gt;&lt;/p&gt;
&lt;p&gt;在访问之前，我们还需要在控制台打开我们的 8000 端口，这样别人才能访问的到：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%89%93%E5%BC%808000%E7%AB%AF%E5%8F%A3.png&quot; alt=&quot;打开8000端口&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后，找到我们的主机 IP 地址（注意是公有 IP 不是私有 IP），在浏览器输入：&lt;code&gt;http://your_ip:8000&lt;/code&gt;即可访问😊注意是 http 不是 https&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%88%90%E5%8A%9F%E8%AE%BF%E9%97%AE.png&quot; alt=&quot;成功访问&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/picture.C4hoKvnr.jpg"/><enclosure url="/_astro/picture.C4hoKvnr.jpg"/></item><item><title>scikit-learn学习：k近邻</title><link>https://www.wht0909.top/blog/scikit-learn%E5%AD%A6%E4%B9%A0k%E8%BF%91%E9%82%BB/scikit-learn%E5%AD%A6%E4%B9%A0k%E8%BF%91%E9%82%BB</link><guid isPermaLink="true">https://www.wht0909.top/blog/scikit-learn%E5%AD%A6%E4%B9%A0k%E8%BF%91%E9%82%BB/scikit-learn%E5%AD%A6%E4%B9%A0k%E8%BF%91%E9%82%BB</guid><description>scikit-learn 学习笔记</description><pubDate>Fri, 27 Feb 2026 18:50:35 GMT</pubDate><content:encoded>&lt;p&gt;作为 sklearn 系列的第二篇博客，我们将介绍另一种常用的机器学习算法：k 近邻（k-NN）算法&lt;/p&gt;
&lt;h2&gt;1. kNN 算法原理&lt;/h2&gt;
&lt;p&gt;k 近邻（k-NN）是一种基于近邻的机器学习算法。它的原理非常简单：在分类任务中，样本点的标签由距离其最近的 k 个邻居的标签进行多数投票决定。以二分类任务为例，当 k=5 时，kNN 模型将计算待预测样本点与其他样本点的&lt;strong&gt;距离&lt;/strong&gt;，并选择离他最近的 5 个“邻居”。如果这五个邻居中有三个的标签为 1，两个的标签为 0，则认为待预测样本点的标签为 1。回归任务同样类似，未知样本的预测值可由其邻居样本点的预测值经加权平均计算。由于 kNN 高度依赖于邻居样本，一些 kNN 算法的变体通过距离加权等方法输出待预测样本的标签值[1]。容易想到的一种变体算法思想如下：在选出的 k 个样本中，离未知样本靠较近的样本点将获得更高的权重。这种权重可简单设置为距离的倒数，在获得全部 k 个样本点的权重后，通过 softmax 函数映射到$(0, 1)$区间转换为概率分布。&lt;/p&gt;
&lt;p&gt;和随机森林、支持向量机等算法不同，kNN 在模型训练时实际上并没有&lt;strong&gt;拟合&lt;/strong&gt;曲线的过程，因此属于惰性学习（lazy learning）的一种。&lt;/p&gt;
&lt;h2&gt;2. kNN 的局限性&lt;/h2&gt;
&lt;p&gt;kNN 基于近邻的特征也导致了它的局限性：最典型的问题便是维度诅咒（Curse of Dimensionality）。随着特征空间维度的增加，样本点的分布将变得极为稀疏，导致距离度量失效，即每个样本点之间的距离相近的现象。因此在特征维度较高时，传统的 kNN 可能无法有效区分样本。这一现象并非 kNN 的专属问题，事实上所有基于距离的机器学习方法都会面临相似的困境。&lt;/p&gt;
&lt;h2&gt;3. 代码实现&lt;/h2&gt;
&lt;p&gt;kNN 在 sklearn 中的接口类是&lt;code&gt;KNeighborsClassifier&lt;/code&gt;[2]。关键参数包括&lt;code&gt;n_neighbors&lt;/code&gt;、&lt;code&gt;weights&lt;/code&gt;和&lt;code&gt;algorithm&lt;/code&gt;。&lt;code&gt;n_neighbors&lt;/code&gt;表示所选的最近邻数量，通常为奇数。若近邻数量设置的过小，则容易出现过拟合问题；若设置的过大则会导致欠拟合。&lt;code&gt;weights&lt;/code&gt;表示预测中使用的权重函数，其中&lt;code&gt;uniform&lt;/code&gt;为均匀权重，&lt;code&gt;distance&lt;/code&gt;按距离的倒数对点进行加权，此外用户还可以通过自定义函数自拟权重。&lt;code&gt;algorithm&lt;/code&gt;可以选择用于计算最近邻居的算法。&lt;/p&gt;
&lt;p&gt;在训练模型前，需要对数据进行预处理。kNN 基于距离分类，因此特征的量纲极为重要。在训练前需要对数据进行标准化处理（详见下面代码的&lt;code&gt;StandardScaler&lt;/code&gt;）。值得一提的是，StandardScaler 的实例 scaler 有两个重要的方法：&lt;code&gt;fit_transform&lt;/code&gt;和&lt;code&gt;transform&lt;/code&gt;。在第一次对训练数据进行标准化时调用的是&lt;code&gt;fit_transform&lt;/code&gt;，对测试数据则使用&lt;code&gt;transform&lt;/code&gt;[3]。这是因为标准化只能在训练集上拟合（fit）。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler

# 1. 加载数据
iris = load_iris()
X = iris.data
y = iris.target
print(X.shape, y.shape) # (150, 4) (150,)

# 2. 数据预处理
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 3. 模型训练
param_grid = {
    &apos;n_neighbors&apos;: [3, 5, 7, 9, 11],
    &apos;weights&apos;: [&quot;uniform&quot;, &quot;distance&quot;],
    &apos;algorithm&apos;: [&quot;auto&quot;, &quot;ball_tree&quot;, &quot;kd_tree&quot;, &quot;brute&quot;]
}
grid_search = GridSearchCV(estimator=KNeighborsClassifier(),
                           param_grid=param_grid,
                           cv=5,
                           scoring=&apos;accuracy&apos;,
                           n_jobs=1
                           )
grid_search.fit(X_train_scaled, y_train)
print(f&quot;最佳参数：{grid_search.best_params_}&quot;)
print(f&quot;最佳交叉验证分数：{grid_search.best_score_}&quot;)

# 4. 预测与评估
best_kNN_model = grid_search.best_estimator_
y_pred_best = best_kNN_model.predict(X_test)
y_pred_ba_best = best_kNN_model.predict_proba(X_test)
y_pred = best_kNN_model.predict(X_test_scaled)
print(f&quot;预测结果：{y_pred}&quot;)
print(f&quot;真实标签：{y_test}&quot;)

acc = accuracy_score(y_test, y_pred)
print(f&quot;ACC:{acc}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相关资源和参考文献&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1]S. A. Dudani, &quot;The Distance-Weighted k-Nearest-Neighbor Rule,&quot; in IEEE Transactions on Systems, Man, and Cybernetics, vol. SMC-6, no. 4, pp. 325-327, April 1976, doi: 10.1109/TSMC.1976.5408784.&lt;/li&gt;
&lt;li&gt;[2]&lt;a href=&quot;https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html&quot;&gt;KNeighborsClassifier 文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[3]&lt;a href=&quot;https://www.cnblogs.com/smartljy/p/18520268&quot;&gt;博客文章“sklearn当中fit_transform和transform方法的区别；数据标准化”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/kNN.DVBBxWQg.png"/><enclosure url="/_astro/kNN.DVBBxWQg.png"/></item><item><title>超参数优化（1）：网格搜索和随机搜索</title><link>https://www.wht0909.top/blog/%E8%B6%85%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%961%E7%BD%91%E6%A0%BC%E6%90%9C%E7%B4%A2%E5%92%8C%E9%9A%8F%E6%9C%BA%E6%90%9C%E7%B4%A2/%E8%B6%85%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%961%E7%BD%91%E6%A0%BC%E6%90%9C%E7%B4%A2%E5%92%8C%E9%9A%8F%E6%9C%BA%E6%90%9C%E7%B4%A2</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%B6%85%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%961%E7%BD%91%E6%A0%BC%E6%90%9C%E7%B4%A2%E5%92%8C%E9%9A%8F%E6%9C%BA%E6%90%9C%E7%B4%A2/%E8%B6%85%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%961%E7%BD%91%E6%A0%BC%E6%90%9C%E7%B4%A2%E5%92%8C%E9%9A%8F%E6%9C%BA%E6%90%9C%E7%B4%A2</guid><description>介绍超参数搜索方法：网格搜索和随机搜索</description><pubDate>Sun, 22 Feb 2026 20:41:06 GMT</pubDate><content:encoded>&lt;p&gt;在先前的博客“scikit-learn学习：随机森林”中，我们简要提及了网格搜索。在本系列中，我们将介绍几种超参数优化方法，帮助模型取得更高的性能。作为该系列的第一篇文章，本文会结合原理和代码阐述两种重要的超参搜索方法：网格搜索和随机搜索。&lt;/p&gt;
&lt;h2&gt;1. 前言&lt;/h2&gt;
&lt;p&gt;机器学习（Machine Learning）通过在大量数据上训练模型，寻找从输入空间（Input）到输出空间（Output）的最优映射，以完成对未知数据的分类或回归任务。模型训练的过程，即是调整模型内部参数以拟合数据的过程。不同机器学习算法具有不同的参数（Parameters），一部分参数无需提前设置，模型将在训练的过程中自主学习最佳的参数组合，例如卷积神经网络（CNN）中的卷积核权重（Weight）；另一部分参数则需要人为设置，称为超参数（Hyperparameters）。超参数用于控制模型的行为，如深度学习算法中的训练周期（Epoch）、学习率（Learning Rate）等。传统的机器学习算法，如支持向量机（SVM），随机森林（RF），分别具有不同的超参数。超参数优化旨在寻找最佳的超参数组合，最大化发挥模型的性能。在该领域的早期研究中，主要通过手动方法寻找最佳的超参数组合，但该方法过于依赖领域经验，且人工搜索的迭代次数十分有限，很难找到数学意义上的最佳组合[1]。为解决这一问题，后续研究基于不同的思想提出了一系列的超参数搜索方法。本文将总结两种最常用的超参数搜索方法：网格搜索和随机搜索。值得注意的是，本文的分析对象主要是&lt;strong&gt;有监督学习中的单目标优化&lt;/strong&gt;任务，对多目标优化中的粒子群算法（PSO）、遗传算法（GA）等暂不做探讨。&lt;/p&gt;
&lt;h2&gt;2. 网格搜索（Grid Search）&lt;/h2&gt;
&lt;p&gt;网格搜索（Grid Search）是最简单、最常用的超参数搜索方法。只需在程序中提前设置参数网格，通过穷举每种参数组合训练模型即可实现超参数优化。但这种方法也有明显的缺点：网格搜索浪费了大量的计算资源和计算时间[2]。随着参数量的增加，搜索的次数和计算的时间将发生指数级的增长。因此，网格搜索只适用于模型较为简单、参数量较少的情况。但由于其简单性，目前仍是最为主流的调参方法之一。&lt;/p&gt;
&lt;p&gt;下面我们将以随机森林模型为例，通过网格搜索寻找最佳的参数组合。在 sklearn 库提供的 GridSearchCV 接口中，需要指定几个必须的参数：机器学习算法的实例&lt;code&gt;estimator&lt;/code&gt;、搜索网格&lt;code&gt;param_grid&lt;/code&gt;和优化指标&lt;code&gt;scoring&lt;/code&gt;，其中搜索网格需要以字典形式给出。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;param_grid = {
    &apos;n_estimators&apos;: [50, 100, 200],
    &apos;max_depth&apos;: [None, 5, 10, 15],
    &apos;min_samples_split&apos;: [2, 5, 10],
    &apos;max_features&apos;: [&apos;sqrt&apos;, &apos;log2&apos;]
}

grid_search = GridSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,
    scoring=&apos;accuracy&apos;,
    n_jobs=-1
)

grid_search.fit(X_train, y_train)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 随机搜索（Random Search）&lt;/h2&gt;
&lt;p&gt;当模型的参数空间维度过高时，逐一进行网格搜索将导致严重的计算开销：若某个模型的可调超参数有$m$个，每个超参数有$n$种可能的选择，则网格搜索所需的计算次数为$n^{m}$次。随着参数空间维度的增加，计算次数大幅提升。为解决计算量过大的问题，随机搜索（Random Search）应运而生。随机搜索的核心思想是在参数空间中随机采样，通过人为约束迭代次数，在限制计算量的同时完成模型优化。随机搜索的有效性已在各种算法中得到验证：2012 年，Bergstra 等人证明在高维参数空间中随机搜索比网格搜索更有效[3]；2015 年，Mantovani 等人在训练 SVM 时利用随机搜索降低计算次数，得到了和网格搜索及其他更复杂的调优技术相似的模型性能[4]。&lt;/p&gt;
&lt;p&gt;随机搜索在 sklearn 中的接口是 RandomizedSearchCV。和 GridSearchCV 相似，RandomizedSearchCV 的核心参数包括估计器&lt;code&gt;estimator&lt;/code&gt;、参数字典&lt;code&gt;param_distributions&lt;/code&gt;和迭代次数&lt;code&gt;n_iter&lt;/code&gt;。其中&lt;code&gt;param_distributions&lt;/code&gt;的写法与 GridSearchCV 中相似，甚至可以完全照搬上面代码中的&lt;code&gt;param_grid&lt;/code&gt;，但区别在于：&lt;code&gt;param_distributions&lt;/code&gt;中允许设置可调参数范围为连续值，如指数分布（关于这方面的详细介绍见 sklearn 官方文档用户指南中“3.2.2. 随机参数优化”一节[5]）。&lt;code&gt;n_iter&lt;/code&gt;指定了进行交叉验证时的迭代次数，简单来说，若将&lt;code&gt;n_iter&lt;/code&gt;设置为 10，则&lt;code&gt;cv_results_[&quot;params&quot;]&lt;/code&gt;将会包含十个列表，即整个训练过程一共尝试 10 种超参数的组合。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;param_dist = {
    &apos;n_estimators&apos;: range(50, 201, 1),
    &apos;max_depth&apos;: [None, 5, 10, 15],
    &apos;min_samples_split&apos;: [2, 5, 10],
    &apos;max_features&apos;: [&apos;sqrt&apos;, &apos;log2&apos;]
}

grid_search = RandomizedSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_distributions=param_dist,
    n_iter=20,
    cv=5,
    scoring=&apos;accuracy&apos;,
    n_jobs=1
)

grid_search.fit(X_train, y_train)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.1 随机搜索的数学原理&lt;/h3&gt;
&lt;p&gt;读到这里，想必细心的读者会产生这样的疑问：随机搜索只是在整个参数空间随机选点，为什么这种极强的随机性不会对调参产生负面影响，反而能找到较好的参数组合呢？Bergstra 等人的工作对这个问题做出了解答[3]。实际上，模型优化的过程可由下式表示：
$$
\lambda^{(*)} \approx \underset{\lambda \in \Lambda}{\operatorname{argmin}} \Psi(\lambda)
$$
其中$\lambda$表示超参数，$\Psi(\lambda)$是超参响应函数（Hyper-parameter Response Function）。我们的目标是寻找能使得$\Psi(\lambda)$最小的$\lambda$。我们可以想象这样一个画面，一个简化的模型仅有两个超参数，则整个区域是一个三维空间：x 轴和 y 轴分别表示超参数 A 和 B，z 轴表示某种损失（Loss），也就是上式中的$\Psi(\lambda)$。那么三维空间中的某个曲面（我们暂且认为这个曲面是连续光滑的）便包含了所有的参数组合以及对应的模型损失（见下图，这种可视化同样可以通过二维笛卡尔坐标系中的热力图显示）。我们的目标就是找到这个 Loss 曲面的最低点，它对应着一组超参数。更高维的情况同样如此，只是可视化变得更加困难。事实上，$\Psi(\lambda)$对某些维度比其他维度更加敏感，也就是说总有一些参数比另一些参数更加重要。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%8F%82%E6%95%B0%E7%A9%BA%E9%97%B4%E5%8F%AF%E8%A7%86%E5%8C%96.png&quot; alt=&quot;参数空间可视化&quot;&gt;&lt;/p&gt;
&lt;p&gt;铺垫至此，我们可以阐述随机搜索的数学意义了。在可视化图中，我们可以想象这个曲面在某些方向（如向 XOZ 平面的投影）上十分陡峭，而在另一些方向（如向 YOZ 平面的投影）上十分平滑。如果在两种方向上用相同的尺度进行衡量，就必然会出现下面这种情况：在向 XOZ 平面的投影上，模型的超参数 A 只要轻微移动，就会大幅影响模型的性能；而在向 YOZ 平面的投影上，模型的超参数 B 需要较大的移动步长才能让模型的性能有些许变化。这种调整步长的不一致性对网格搜索造成了巨大的影响：在网格搜索过程中，由于需要遍历全部的参数组合，会出现许多“同样的重要参数 A 对应不同的不重要参数 B”的计算，而这种计算显然是无法有效提高性能的 —— 真正的性能决定因素由 A 掌控。而随机搜索借助“随机性”解决了这个问题：正因为选点的随机，在有限的迭代次数内会有更多的重要参数被尝试。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%BD%91%E6%A0%BC%E6%90%9C%E7%B4%A2%E4%B8%8E%E9%9A%8F%E6%9C%BA%E6%90%9C%E7%B4%A2%E6%AF%94%E8%BE%83.png&quot; alt=&quot;网格搜索与随机搜索比较&quot;&gt;&lt;/p&gt;
&lt;p&gt;在论文的原文中，作者给出了一个二维可视化的示意图，也一并放在这里了。其实和上面的图是一个意思，读者可以加深理解：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E4%BA%8C%E8%80%85%E6%AF%94%E8%BE%83%E7%9A%84%E4%BA%8C%E7%BB%B4%E5%8F%AF%E8%A7%86%E5%8C%96.png&quot; alt=&quot;二者比较的二维可视化&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. 小结&lt;/h2&gt;
&lt;p&gt;这篇博客主要介绍了两种重要的调参策略：网格搜索和随机搜索，并结合数学原理以及代码给出了较为全面的解答，如有错误敬请指正。欢迎交流讨论！&lt;/p&gt;
&lt;p&gt;相关资源和参考文献&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1]  A. Priyanshu, R. Naidu, F. Mireshghallah, and M. Malekzadeh, “Efficient Hyperparameter Optimization for Differentially Private Deep Learning,” Aug. 2021, [Online]. Available: http://arxiv.org/abs/2108.03888&lt;/li&gt;
&lt;li&gt;[2]  A. R. M. Rom, S. Ibrahim, A. F. A. Fadzil, N. N. A. Mangshor and N. A. M. Ghani, &quot;A Review of Hyperparameter Tuning ethods in Machine Learning,&quot; 2025 6th International Conference on Artificial Intelligence and Data Sciences (AiDAS), West Java, Indonesia, 2025, pp. 1-6, doi: 10.1109/AiDAS67696.2025.11213530.&lt;/li&gt;
&lt;li&gt;[3]  Bergstra J , Bengio Y .Random Search for Hyper-Parameter Optimization[J].Journal of Machine Learning Research, 2012, 13(1):281-305.DOI:10.1016/j.chemolab.2011.12.002.&lt;/li&gt;
&lt;li&gt;[4]  R. G. Mantovani, A. L. D. Rossi, J. Vanschoren, B. Bischl and A. C. P. L. F. de Carvalho, &quot;Effectiveness of Random Search in SVM hyper-parameter tuning,&quot; 2015 International Joint Conference on Neural Networks (IJCNN), Killarney, Ireland, 2015, pp. 1-8, doi: 10.1109/IJCNN.2015.7280664.&lt;/li&gt;
&lt;li&gt;[5]  sklearn 官方文档中关于随机搜索的说明（3.2.2. 随机参数优化），链接：https://scikit-learn.cn/stable/modules/grid_search.html&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/参数空间可视化.CDvl89g0.png"/><enclosure url="/_astro/参数空间可视化.CDvl89g0.png"/></item><item><title>scikit-learn学习：随机森林</title><link>https://www.wht0909.top/blog/scikit-learn%E5%AD%A6%E4%B9%A0%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97/scikit-learn%E5%AD%A6%E4%B9%A0%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97</link><guid isPermaLink="true">https://www.wht0909.top/blog/scikit-learn%E5%AD%A6%E4%B9%A0%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97/scikit-learn%E5%AD%A6%E4%B9%A0%E9%9A%8F%E6%9C%BA%E6%A3%AE%E6%9E%97</guid><description>scikit-learn 学习笔记</description><pubDate>Tue, 17 Feb 2026 20:40:35 GMT</pubDate><content:encoded>&lt;p&gt;一个多月没写博客了，感觉表达能力退化了不少。新年的第一条博客就开一个新坑吧：本系列将介绍 scikit-learn 内置的一些机器学习模型的构建和训练方法，以及其他的相关知识。主要学习资源来自最近发现的一个 b 站 up 主“java1234官方”的系列视频&lt;a href=&quot;https://www.bilibili.com/video/BV11reUzEEPH?spm_id_from=333.788.videopod.episodes&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;2026版 Scikit-learn Python机器学习 视频教程(无废话版) 玩命更新中~&lt;/a&gt;。这个栏目的主要目的是学习并记录一些常用的调库方法以及相关的模型训练工作流，对机器学习模型的工作原理、数学基础等不作过多介绍（致力于先把代码跑起来）。本系列第一个要介绍的机器学习模型是随机森林（Random Forest）&lt;/p&gt;
&lt;h2&gt;1. scikit-learn 简介&lt;/h2&gt;
&lt;p&gt;什么是 scikit-learn？根据 scikit-learn 中文社区给出的官方介绍：scikit-learn 是一个免费的 Python 软件机器学习库，提供各种分类，回归和聚类算法，如 SVM ，随机森林，梯度提升等。简单来说，scikit-learn 是一个 Python 库，这个库提供了多种模型的实现，用户只需要调用相关的 API 即可完成模型构建、训练等任务，而无需从头构建模型。在本文的叙述过程中，有时会使用 scikit-learn 的简写：sklearn，读者需清楚二者表示的是同一个库。要使用 sklearn，需要先安装 scikit-learn 库，安装指令如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install scikit-learn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sklearn 的官方资源详见以下网址:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://scikit-learn.org/stable/index.html&quot;&gt;scikit-learn 官方网址&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scikit-learn.org.cn/&quot;&gt;scikit-learn 中文社区&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 随机森林算法（Random Forest）&lt;/h2&gt;
&lt;p&gt;在介绍随机森林之前，我们需要引入另一个机器学习算法：决策树（Decision Tree）。决策树的核心思想是&lt;strong&gt;不断挑选特征分裂节点，直到最后一层叶子结点时实现分类&lt;/strong&gt;。下图展示了一个简易的决策树：其中 F1、F2、F3 均为特征，0 / 1 分别代表分类结果是阴性 / 阳性。以示意图为例，根节点特征是 F1，若该样本的 F1 特征满足对应的条件，则进入左孩子节点继续分裂，否则进入右孩子节点，分类结果为 0（阴性）；这个过程将地递归进行，直到到达叶子结点。决策树的节点分裂依据是&lt;strong&gt;信息增益&lt;/strong&gt;[1]，这里我们先不展开介绍，后续的博客中我们将为决策树算法单开一个专题。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%86%B3%E7%AD%96%E6%A0%91%E7%A4%BA%E6%84%8F%E5%9B%BE.png&quot; alt=&quot;决策树示意图&quot;&gt;&lt;/p&gt;
&lt;p&gt;随机森林算法和决策树高度相关，简单来说，随机森林是多个决策树的集成学习算法。集成学习（Ensemble Learning）旨在通过融合多个基分类器的预测结果以提高模型的泛化能力，常用的策略有 Bagging，Boosting，Stacking 等。随机森林属于 Bagging 集成的一种，其核心过程如下[2]：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;数据集采样：按一定比例&lt;strong&gt;有放回地&lt;/strong&gt;选取整个训练集的子数据集进行训练。也就是说，在整个训练过程中，可能有一些样本被选择了多次，也可能有一些样本从未被选择到&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;随机选择特征子集：按一定比例&lt;strong&gt;随机选取&lt;/strong&gt;部分特征进行节点划分&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;训练决策树：用上述数据集和特征子集训练多棵决策树模型&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;集成学习：若目标是分类任务，则使用多数投票的方法输出最终的分类结果；若是回归任务，则取平均输出回归结果&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3. 代码逐步拆解&lt;/h2&gt;
&lt;p&gt;为便于演示，本文中的所有演示代码均在 jupyter lab 上编写。数据集采用 sklearn 提供的鸢尾花数据集，加载数据如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%8A%A0%E8%BD%BD%E9%B8%A2%E5%B0%BE%E8%8A%B1%E6%95%B0%E6%8D%AE%E9%9B%86.png&quot; alt=&quot;加载鸢尾花数据集&quot;&gt;&lt;/p&gt;
&lt;p&gt;通过打印 numpy 数组的形状，我们可以发现：样本共有 150 条，特征有四列。在构建模型前，我们需要划分训练集、验证集和测试集：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%88%92%E5%88%86%E6%95%B0%E6%8D%AE%E9%9B%86.png&quot; alt=&quot;划分数据集&quot;&gt;&lt;/p&gt;
&lt;p&gt;接下来构建随机森林模型。随机森林在 sklearn 库中的接口是&lt;code&gt;RandomForestClassifer&lt;/code&gt;。理论上来说，我们直接实例化这个类，使用默认参数即可完成预测。但在实际的模型构建过程中，有许多未确定的模型参数。为了使算法的性能尽可能达到最高，需要对模型进行&lt;strong&gt;调参&lt;/strong&gt;。寻找最佳参数组合的过程即为模型的调参过程。在本实验中，我们选择网格搜索（Grid Search）方法调参（除了网格搜索外，也有一些其他的调参方法，如贝叶斯优化、随机搜索等[3]）。网格搜索的核心思想是：遍历程序设置的参数网格中所有可能的参数组合，根据定义的指标寻找最优模型。因此，需要我们自己显式地给出参数网格。&lt;/p&gt;
&lt;p&gt;随机森林的可调整参数比较多，官方文档中给出了全部可调参数及对应的含义[4]。本文主要介绍几个常用的可调参数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;n_estimators&lt;/code&gt;：随机森林中的决策树的数量，默认值是 100（旧版本的默认值是 10）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;max_depth&lt;/code&gt;：树的最大深度，默认值是 None。如果为 None，则节点会扩展直到所有叶子都是纯的，或者直到所有叶子包含的样本数少于 min_samples_split。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;min_samples_split&lt;/code&gt;：分割内部节点所需的最小样本数，默认值是 2。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;max_features&lt;/code&gt;：寻找最佳分割时要考虑的特征数量，常设置为 None 或 sqrt 或 log2。以 sqrt 为例，如果 max_features 设置为 sqrt，则每次选取的特征子集数量为全部特征数量的开平方。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;n_jobs&lt;/code&gt;:并行运行的处理器数，默认值为 None，即为 1。如果设置为 -1，则相当于调用 CPU 的所有处理器进行并行计算。&lt;strong&gt;一定要根据服务器的运行情况合理地设置该参数，不要直接设置为 -1&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在设计好参数网格后，需要实例化 GridSearch 对象实现网格搜索。我们选择准确率（Accuracy）作为优化指标，示例代码如下图。经尝试后发现，笔者的计算机只能设置&lt;code&gt;n_jobs=1&lt;/code&gt;，否则会报错。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%BD%91%E6%A0%BC%E5%8F%82%E6%95%B0.png&quot; alt=&quot;网格参数&quot;&gt;&lt;/p&gt;
&lt;p&gt;调用 GridSearchCV 对象的 fit 方法即可实现模型的训练，即&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;grid_search.fit(X)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;模型训练后，GridSearchCV 对象提供了一系列属性供我们调用，以获取交叉验证结果，最佳模型等。&lt;code&gt;cv_results_&lt;/code&gt;属性给出了一个包含所有的交叉验证结果的字典。&lt;code&gt;best_params_&lt;/code&gt;给出了最佳的参数组合，衡量模型性能指标是 GridSearchCV 对象中设置的 &lt;code&gt;scoring=&quot;accuracy&quot;&lt;/code&gt;。&lt;code&gt;best_score_&lt;/code&gt;给出了最佳交叉验证分数。&lt;code&gt;best_estimator_&lt;/code&gt;给出了最佳模型。以上属性获取结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./gridsearch%E5%B1%9E%E6%80%A7.png&quot; alt=&quot;gridsearch属性&quot;&gt;&lt;/p&gt;
&lt;p&gt;获取到最佳的模型后，我们在独立测试集上进行测试，以验证模型在各项指标上的性能。具体的指标计算可直接调用 sklearn 中提供的 API。有些指标的计算不仅要模型输出预测标签，还需要输出预测概率，可分别通过&lt;code&gt;predict&lt;/code&gt;方法和&lt;code&gt;predict_proba&lt;/code&gt;方法获取。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E9%A2%84%E6%B5%8B%E6%A0%87%E7%AD%BE%E5%92%8C%E6%A6%82%E7%8E%87.png&quot; alt=&quot;预测标签和概率&quot;&gt;&lt;/p&gt;
&lt;p&gt;由于 iris 数据集是一个三分类任务，所以&lt;code&gt;predict_proba&lt;/code&gt;有三列，分别对应每个类别的预测概率。通过以下代码计算各个指标：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%8C%87%E6%A0%87%E8%AE%A1%E7%AE%97.png&quot; alt=&quot;指标计算&quot;&gt;&lt;/p&gt;
&lt;p&gt;最后，调用 joblib 库将模型保存下来，方便后续使用：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%A8%A1%E5%9E%8B%E4%BF%9D%E5%AD%98%E4%B8%8E%E5%8A%A0%E8%BD%BD.png&quot; alt=&quot;模型保存与加载&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. 代码实现&lt;/h2&gt;
&lt;p&gt;整体代码实现如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, roc_auc_score, balanced_accuracy_score, \
    matthews_corrcoef
from sklearn.model_selection import train_test_split, GridSearchCV
import joblib

# 1. 加载数据
iris = load_iris()
X = iris.data # numpy 类型，形状(150,4)
y = iris.target # numpy 类型，形状(150, )

# 2. 数据预处理
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, shuffle=True)

# 3. 定义网格参数
param_grid = {
    &apos;n_estimators&apos;: [50, 100, 200],
    &apos;max_depth&apos;: [None, 5, 10, 15],
    &apos;min_samples_split&apos;: [2, 5, 10],
    &apos;max_features&apos;: [&apos;sqrt&apos;, &apos;log2&apos;]
}

# 4. 创建 gridsearch 对象
grid_search = GridSearchCV(
    estimator=RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,
    scoring=&apos;accuracy&apos;,
    n_jobs=1 # 启用所有 CPU 核心并行计算
)

# 5. 在训练集上进行网格搜索
grid_search.fit(X_train, y_train)

# 6. 输出最佳参数
print(f&quot;最佳参数：{grid_search.best_params_}&quot;)
print(f&quot;最佳交叉验证分数：{grid_search.best_score_}&quot;)

# 7. 使用最佳参数的模型进行预测
best_rf_model = grid_search.best_estimator_
y_pred_best = best_rf_model.predict(X_test)
y_pred_ba_best = best_rf_model.predict_proba(X_test)
print(f&quot;调优后指标计算：\n&quot;
      f&quot;准确率：{accuracy_score(y_test, y_pred_best)}\n&quot;
      f&quot;auc：{roc_auc_score(y_test, y_pred_ba_best, multi_class=&apos;ovo&apos;)}\n&quot;
      f&quot;BA：{balanced_accuracy_score(y_test, y_pred_best)}\n&quot;
      f&quot;MCC：{matthews_corrcoef(y_test, y_pred_best)}\n&quot;)

# 8. 保存模型
joblib.dump(best_rf_model, &apos;./models/rf_model.joblib&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相关资源和参考文献&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/133838427&quot;&gt;一文看懂决策树（Decision Tree）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/481560459&quot;&gt;五分钟速通随机森林（附实战案例与数据集）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A. R. M. Rom, S. Ibrahim, A. F. A. Fadzil, N. N. A. Mangshor and N. A. M. Ghani, &quot;A Review of Hyperparameter Tuning Methods in Machine Learning,&quot; 2025 6th International Conference on Artificial Intelligence and Data Sciences (AiDAS), West Java, Indonesia, 2025, pp. 1-6, doi: 10.1109/AiDAS67696.2025.11213530.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://scikit-learn.cn/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html&quot;&gt;RandomForestClassifier 官方文档&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="/_astro/rf.De_u_Nax.png"/><enclosure url="/_astro/rf.De_u_Nax.png"/></item><item><title>Tensorboard学习笔记</title><link>https://www.wht0909.top/blog/tensorboard%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/tensorboard%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</link><guid isPermaLink="true">https://www.wht0909.top/blog/tensorboard%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/tensorboard%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</guid><description>参考 b 站资源的 Tensorboard 学习笔记</description><pubDate>Mon, 19 Jan 2026 19:56:56 GMT</pubDate><content:encoded>&lt;p&gt;封面画作：Henri Matisse &lt;em&gt;&lt;strong&gt;Dance (La Danse)&lt;/strong&gt;&lt;/em&gt;(1910)


&lt;/p&gt;
&lt;p&gt;Tensorboard 是一个用于可视化 pytorch 训练的工具，这篇博客是 b 站资源&lt;a href=&quot;https://www.bilibili.com/video/BV1Qf4y1C7kz/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;&quot;在Pytorch中使用Tensorboard可视化训练过程&quot;&lt;/a&gt;的学习笔记&lt;/p&gt;
&lt;h2&gt;1. Tensorboard 包的导入与实例化&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from torch.utils.tensorboard import SummaryWriter
tb_writer = SummaryWriter(log_dir=&quot;./test&quot;) # 将结果保存在目标文件夹下
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 计算图的创建&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# 实例化模型
model = resnet34(num_classes=args.num_classes).to(device)
# 创建一个全零的图像，作为模型的输入
init_img = torch.zeros((1, 3, 224, 224), device=device)
# 创建计算图
tb_writer.add_graph(model, init_img)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 添加要监控的指标&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;tags = [&quot;train_loss&quot;, &quot;accuracy&quot;, &quot;learning_rate&quot;]
tb_writer.add_scaler(tags[0], mean_loss, epoch)
tb_writer.add_scaler(tags[1], acc, epoch)
tb_writer.add_scaler(tags[2], optimizer.param_groups[0][&quot;lr&quot;], epoch)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第一个参数表示要监控的指标是哪个，将作为图像的标题出现&lt;/p&gt;
&lt;p&gt;第二个参数是具体的指标&lt;/p&gt;
&lt;p&gt;第三个参数代表在什么时候记录，例如上面使用的是 epoch，就是一个 epoch 记录一次&lt;/p&gt;
&lt;h2&gt;4. 绘制直方图&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;tb_writer.add_histogram(tag=&quot;conv1&quot;, values=model.conv1.weight, global_step=epoch)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常用于反映模型权重从训练开始到训练结束时的变化&lt;/p&gt;
&lt;h2&gt;5. 启动 Tensorborad&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tensorboard --logdir &quot;/path/to/dir&quot;
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/封面画作.BYQkRMMI.png"/><enclosure url="/_astro/封面画作.BYQkRMMI.png"/></item><item><title>Linux常用指令（服务器篇）</title><link>https://www.wht0909.top/blog/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AF%87/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AF%87</link><guid isPermaLink="true">https://www.wht0909.top/blog/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AF%87/linux%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AF%87</guid><description>记录一些常用的 Linux 指令</description><pubDate>Thu, 15 Jan 2026 15:53:07 GMT</pubDate><content:encoded>&lt;p&gt;笔者在做毕设时需要使用 Linux 系统的服务器，但由于平时使用的都是 Windows 系统，对 Linux 不甚熟悉，因此在这条博客中记录一些常用到的 Linux 指令&lt;/p&gt;
&lt;h2&gt;1. 进程管理&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;查看正在运行的进程：&lt;code&gt;ps aux&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看正在运行的 python 进程：&lt;code&gt;ps aux | grep python&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;nohup 后台运行&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;nohup python -u kde_entropy_main.py -d AGN -k 10 &gt; kde_entropy_run.log 2&gt;&amp;#x26;1 &amp;#x26;&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&quot;-u&quot;：实时刷新日志&lt;/li&gt;
&lt;li&gt;&quot;kde_entropy_main.py&quot;：程序文件&lt;/li&gt;
&lt;li&gt;&quot;-d AGN -k 10&quot;：命令行传参&lt;/li&gt;
&lt;li&gt;&quot;&gt; kde_entropy_run.log&quot;：重定向到输出文件&lt;/li&gt;
&lt;li&gt;&quot;2&gt;&amp;#x26;1&quot;：将报错信息也记录到文件&lt;/li&gt;
&lt;li&gt;&quot;&amp;#x26;&quot;：将任务挂到后台，释放终端
挂起后，可通过&lt;code&gt; ps aux | grep &quot;kde_entropy_main.py&quot;&lt;/code&gt;查看程序是否在运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 文件与路径&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;查看当前路径：&lt;code&gt;pwd&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;查看某个文件的内容：&lt;code&gt;cat file.txt&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建文件：&lt;code&gt;touch file.txt&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;创建文件夹：&lt;code&gt;mkdir this_dir&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;编辑文件： vim&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;:w&lt;/code&gt;：保存文件&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;:q&lt;/code&gt;：退出 vim 编辑器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;:wq&lt;/code&gt;：保存文件并退出编辑器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;:q!&lt;/code&gt;：强制退出 vim，且不保存修改&lt;/p&gt;
&lt;p&gt;按 Insert 开始编辑，编辑完成后按 Esc 保存或退出&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;
&lt;p&gt;删除文件：&lt;code&gt;rm file.txt&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除非空文件夹：&lt;code&gt;rm -r target_dir&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;删除空文件夹：&lt;code&gt;rmdir target_dir&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;复制文件及目录&lt;/p&gt;
&lt;p&gt;复制文件到目标目录：&lt;code&gt;cp file.txt /path/to/destination/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;复制文件并重命名：&lt;code&gt;cp file.txt /path/to/destination/newfile.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;递归复制目录：&lt;code&gt;cp -r /path/to/source_dir /path/to/destination/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;交互模式复制（如果目标位置已存在同名文件，会提示用户确认是否覆盖）：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cp -i file.txt /path/to/destination/&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content:encoded><h:img src="/_astro/linux.B8V7w_VS.jpg"/><enclosure url="/_astro/linux.B8V7w_VS.jpg"/></item><item><title>The Elements of Statistical Learning 阅读笔记（Chapter 2）</title><link>https://www.wht0909.top/blog/the-elements-of-statistical-learning-%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-chapter-2/the-elements-of-statistical-learning-%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-chapter-2</link><guid isPermaLink="true">https://www.wht0909.top/blog/the-elements-of-statistical-learning-%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-chapter-2/the-elements-of-statistical-learning-%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0-chapter-2</guid><description>The Elements of Statistical Learning 阅读笔记</description><pubDate>Fri, 09 Jan 2026 21:48:46 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;Chapter2: Overview of Supervised Learning&lt;/p&gt;
&lt;h2&gt;1. 最小二乘法与 K 近邻&lt;/h2&gt;
&lt;h3&gt;1.1 线性模型&lt;/h3&gt;
&lt;p&gt;输入：$ X^{T}=(X_{1}, X_{2}, \ldots , X_{p}) $&lt;/p&gt;
&lt;p&gt;输出（估计值）：$ \hat{Y}=\hat{\beta_{0}}+\sum_{j=1}^{p}X_{j}\hat{\beta_{j}} $&lt;/p&gt;
&lt;p&gt;向量形式：$ \hat{Y}=X^{T}\hat{\beta} $&lt;/p&gt;
&lt;p&gt;$f(x, y)=X^{T}\hat{\beta}$  的梯度  $\nabla f=\beta$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;直观理解：如果你站在 p 维的输入空间里，想要最快地爬到 $f(x,y)$（输出值）最高的地方，你应该沿着 $\beta$ 这个向量的方向走&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;1.2 最小二乘法&lt;/h3&gt;
&lt;p&gt;注：这一章节的最小二乘法和 k 近邻方法，实际上都是在寻找一个“决策边界”&lt;/p&gt;
&lt;p&gt;找到使残差平方和（RSS）最小的$\beta$向量训练模型&lt;/p&gt;
&lt;p&gt;$$ RSS(\beta)=\sum_{i=1}^{N} (y_{i}-x_{i}^{T}\beta)=(\mathbf{y}-\mathbf{X}\beta)^{T}(\mathbf{y}-\mathbf{X}\beta) $$&lt;/p&gt;
&lt;p&gt;数学基础：当$\mathbf{X}^{T}\mathbf{X}$不奇异时可求得&lt;/p&gt;
&lt;p&gt;$$\beta=(\mathbf{X}^{T}\mathbf{X})^{-1}(\mathbf{X}^{T}y)$$&lt;/p&gt;
&lt;p&gt;理解：线性二分类的本质就是找一个超平面将空间划分为两类。在二维平面里该超平面表现为决策曲线，通过$f(\mathbf{X})=\mathbf{X}^{T}\beta=t$进行划分（$t$为阈值，比如说$t=0.5$）&lt;/p&gt;
&lt;h3&gt;1.3 K 近邻&lt;/h3&gt;
&lt;p&gt;通过 $x_{i}$ 附近的 $k$ 个样本的平均值估计 $\hat{y}$&lt;/p&gt;
&lt;p&gt;$$
\hat{Y}(x)=\frac{1}{k} \sum_{x_{i} \in N_{k}(x)}y_{i}
$$&lt;/p&gt;
&lt;p&gt;训练集上的误差近似为 $k$ 的递增函数，且当$k=1$时训练集上的误差总为 0。因此，K 近邻方法不能通过最小化误差平方和优化&lt;/p&gt;
&lt;p&gt;与线性模型的比较：k 近邻方法的有效参数数量通常为 $\frac{N}{k}$，通常大于 p（即线性方法中要优化的$\beta$参数数量：$
\beta=
\begin{pmatrix}
\beta_{1} \
\beta_{2} \
\vdots    \
\beta_{p}
\end{pmatrix}
$）&lt;/p&gt;
&lt;p&gt;对比：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小二乘法得出的决策边界平滑、稳定，但高度依赖于线性决策边界的适用性假设&lt;/li&gt;
&lt;li&gt;k 近邻方法不对数据分布做假设，适用范围广泛，但波动性大（决策边界上的任何特定子区域都取决于少数输入点及其具体位置）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 统计决策理论&lt;/h2&gt;
&lt;p&gt;在预测标准采用“最小化均方误差原则”的条件下，对于任意的$X=x$，$Y$的最优预测值就是“$Y$关于$X=x$的条件期望”，即：
$$
f(x)=E(Y|X=x)
$$
这个$f(x)$也被称为回归函数&lt;/p&gt;
&lt;p&gt;这也就解释了为什么 k 近邻原则在数学上能够成立：在 k 近邻中，选取$x_{i}$点的$k$个近邻并计算其平均值的过程，实际上就是计算$E(Y|X=x)$的过程&lt;/p&gt;
&lt;p&gt;对于随机变量 $X$ 和 $Y$，当给定 $X$ 取某个具体值 $x$ 时，$Y$ 的条件期望 $E(Y∣X=x)$ 是 在 $X=x$ 这个条件下，$Y$ 的概率分布对应的均值&lt;/p&gt;
&lt;p&gt;对于 k 近邻方法，当样本总数$N\rightarrow+\infty$，$k\rightarrow+\infty$且$\frac{k}{N}\rightarrow0$时，k 近邻的预测值$\hat{f}(x)$会无限接近最优的回归函数$f(x)$&lt;/p&gt;
&lt;p&gt;局限性：当维度$p$升高时，k 近邻的收敛速度将变慢&lt;/p&gt;
&lt;p&gt;数据分布假设&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;最小二乘法：最小二乘法的核心假设，是回归函数$f(x)$在整个输入空间上可以用线性函数来近似。也就是说，整个输入空间中，$f(x)$都用同一个线性函数来拟合—— 不管$x$落在输入空间的哪个位置，都用这一组$\beta$对应的直线 / 平面 / 超平面来近似$f(x)$&lt;/li&gt;
&lt;li&gt;k 近邻：k 近邻算法假设，在输入空间中某一点 $x$ 的微小局部邻域内，回归函数 $f(x)$ 的值是大致恒定的&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当使用 L1 损失（$\lvert (Y-f(X)) \rvert$）时，最小化&quot;期望预测误差$E(Y-f(X))$&quot;的解变成了条件中位数（$median(Y|X=x)$）&lt;/p&gt;
&lt;p&gt;条件中位数相比于均值更加稳健，但其导数不连续&lt;/p&gt;
&lt;p&gt;当输出是分类变量$G$时，损失函数将改变，变为&quot;损失矩阵$L$&quot;。$L$的大小为$K \times K$，其中$K=card(G)$，即$G$结合中元素的个数（比如说$G=\left{猫, 狗, 老鼠\right}$，则$card(G)=3$）。矩阵的对角线元素$L(k, k)=0$，即分类正确时损失为 0；$L(k, l)$表示将第$k$类错分为第$l$类时的损失&lt;/p&gt;
&lt;p&gt;小结：线性模型和 k-NN 模型的优劣&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;线性模型：稳定但有偏（方差小偏差大）&lt;/li&gt;
&lt;li&gt;k-NN：不太稳定，但明显较少有偏（偏差小方差大）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 高维空间中的局部方法&lt;/h2&gt;
&lt;p&gt;高维空间中数据的分布：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;所有样本点都会靠近样本的边缘&lt;/li&gt;
&lt;li&gt;训练样本稀疏地分布在输入空间&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;偏差-方差分解&lt;/p&gt;
&lt;p&gt;估计在$x=x_{0}$处的均方根误差 $MSE$，训练集为$\tau$：
$$
\begin{align*}
MSE(x_{0}) &amp;#x26;= E_{\tau}[f(x_{0})-\hat{y_{0}}]^{2} \
&amp;#x26;= E_{\tau}[\hat{y_{0}}-E_{\tau}(\hat{y_{0}})]^{2}+[E_{\tau}(\hat{y_{0}})-f(x_{0})]^{2} \
&amp;#x26;= Var_{\tau}(\hat{y_{0}})+Bias^{2}(\hat{y_{0}})
\end{align*}
$$&lt;/p&gt;
&lt;p&gt;下图展示了维度升高时最近邻变远的一维和二维情况。随着维度的增加，最近邻会离目标点越来越远，最终分布在单位超立方体的边缘上&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%BB%B4%E5%BA%A6%E7%81%BE%E9%9A%BE.png&quot; alt=&quot;维度灾难&quot;&gt;&lt;/p&gt;
&lt;p&gt;在不同的数据分布和维度下，有时偏差占主导地位，有时方差占主导地位&lt;/p&gt;
&lt;h2&gt;4. 统计模型、监督学习和函数逼近&lt;/h2&gt;
&lt;p&gt;监督学习的一种理解方式：假定定义域是在$p$维欧式空间$\mathbb{R}^{p}$，监督学习的目标是给定在$\tau$（训练集）上的表达，在$\mathbb{R}^{p}$的某个区域对全体$x$获得对$f(x)$的有用逼近&lt;/p&gt;
&lt;p&gt;函数逼近的一个方法：线性基展开（linear basis expansions）
$$
f(\theta_{x})=\sum_{k=1}^{k}h_{k}(x)\theta_{k}
$$
其中$h_{k}(x)$是关于$x$的多项式或三角函数式等，$\theta_{k}$是待优化的参数&lt;/p&gt;
&lt;p&gt;除了最小二乘法外，最大似然估计是一种更普遍的参数优化方式：
$$
L(\theta)=\sum_{i=1}^{N}\log Pr_{\theta}(y_{i})
$$
概率密度函数$Pr_{\theta}(y_{i})$是指在参数$\theta$下，事件$y_{i}$发生的概率&lt;/p&gt;
&lt;p&gt;最大似然估计的目标是找到$\theta$使得$L(\theta)$最大&lt;/p&gt;
&lt;p&gt;对于定性输出$G$的回归函数$P_{r}(G|X)$的多项式似然（也称为交叉熵）：
$$
L(\theta)=\sum_{i=1}^{N}\log p_{g,\theta_{i}}(x_{i})
$$
其中$P_{r}(G=G_{k}|X=x)=p_{k, \theta}(x)$&lt;/p&gt;
&lt;h2&gt;5. 结构化的回归模型&lt;/h2&gt;
&lt;p&gt;有无限种$f(x)$能够使 RSS 最小，通过添加不同的限制条件便能获得不同的$\hat{f}(x)$：“存在无限多的可能限制条件，每种限制条件均会导致唯一解，因此这种模糊性仅被转移至约束条件的选择上。”&lt;/p&gt;
&lt;p&gt;选取的邻域越大，约束越强：当选取的邻域非常小时，相当于没有约束；但当选取的邻域很大时，例如在很大的邻域范围内线性拟合，则相当于对全局进行了线性拟合，这是一个很强的约束&lt;/p&gt;
&lt;p&gt;k-NN 的数学假设：对于输入$x_{0}$，其邻域内的$f(x)$是平滑变化（不突变）的，因此能用$x_{0}$邻域的$f(x)$估计当前的$f(x_{0})$&lt;/p&gt;
&lt;p&gt;任何试图在小的各向同性邻域内生成局部变化函数的方法，在高维空间中都会遭遇问题 —— 这再次体现了维度诅咒。反之，所有克服维度问题的方法都需采用一种隐式或自适应的度量标准来衡量邻域，这种标准本质上不允许邻域在所有方向上同时保持微小&lt;/p&gt;
&lt;p&gt;如果一个空间中的域（可以理解为“一个范围内”）在所有维度上的方向都均匀，则称其是“各向同性的”，例如三维空间中的球体。而如果域在某些维度上方向不均匀，如三维空间中的椭球体，则称其是“非各向同性的”。在高维空间中，数据非常稀疏，数据点之间的距离很远，因此小邻域内的数据量非常少。如果仍用“各向同性的”邻域度量规则，将面临数据数量不足的“维度诅咒”。而能解决维度诅咒的度量方法的共同点便是不让邻域在所有维度上都保持“小”，而是在数据密集分布的方向上扩大邻域范围，在数据稀疏的方向上缩小邻域范围，以此保证邻域内含有足量的数据，避免维度灾难。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E9%82%BB%E5%9F%9F%E6%95%B0%E6%8D%AE%E5%88%86%E5%B8%83%E5%8F%AF%E8%A7%86%E5%8C%96.png&quot; alt=&quot;邻域数据分布可视化&quot;&gt;&lt;/p&gt;
&lt;h2&gt;6. 受限估计器的种类&lt;/h2&gt;
&lt;p&gt;粗糙度惩罚（Roughness Penalty）
$$
PRSS(f, \lambda)=RSS(f)+\lambda J(f)
$$&lt;/p&gt;
&lt;p&gt;其中$J(f)$为惩罚函数&lt;/p&gt;
&lt;p&gt;核方法（Kernel Methods）：局部邻域由核函数$K_{\lambda}(x, x_{0})$指定，将权重分配到$x_{0}$周围区域的$x$上。其中$x_{0}$是邻域中心，$x$是周边待分配权重的点，$\lambda$是核的宽度。$K_{\lambda}(x, x_{0})$越大，表示$x$对$x_{0}$的贡献越大&lt;/p&gt;
&lt;p&gt;基函数（Basis Function）
$$
f_{\theta}(x)=\sum_{m=1}^{M}\theta_{m}h_{m}(x)
$$&lt;/p&gt;
&lt;p&gt;径向基函数（Radial Basis Functions）：位于特定质心的，对称的 p 维核
$$
f_{\theta}(x)=\sum_{m=1}^{M}K_{\lambda_{m}}(\mu_{m}, x)\theta_{m}
$$
高斯核$K_{\lambda}(\mu, x) = e^{-\frac{||x - \mu||^2}{2\lambda}}$比较常用，其中$||$表示向量的范数，即两向量间的欧氏距离&lt;/p&gt;</content:encoded><h:img src="/_astro/维度灾难.DGxuCP1O.png"/><enclosure url="/_astro/维度灾难.DGxuCP1O.png"/></item><item><title>解决方案汇总</title><link>https://www.wht0909.top/blog/%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%B1%87%E6%80%BB/%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%B1%87%E6%80%BB</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%B1%87%E6%80%BB/%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E6%B1%87%E6%80%BB</guid><description>本文旨在汇总一些问题的解决方案，包括但不限于 bug 的解决方法，技术教程等，亲测有效，保持更新</description><pubDate>Fri, 09 Jan 2026 09:40:49 GMT</pubDate><content:encoded>&lt;h2&gt;1. 博客搭建&lt;/h2&gt;
&lt;p&gt;国内访问 Vercel 部署应用：https://www.bu44er.ink/blog/2025/vercel-gfw&lt;/p&gt;
&lt;p&gt;如何在国内访问 vercel 部署应用：https://juejin.cn/post/7301193497247727652&lt;/p&gt;
&lt;p&gt;Hexo 博客插入数学公式：https://miustannis.github.io/2025/03/27/250327/&lt;/p&gt;
&lt;p&gt;修改 Hexo 博客的文字大小：https://carol-yang09.github.io/2020/11/29/hexo-icarus-themes-codeblock-caption-fontsize-and-picture-position/&lt;/p&gt;
&lt;h2&gt;2. 教材&lt;/h2&gt;
&lt;p&gt;统计学习要素（中文版）：https://esl.hohoweiya.xyz/01-Introduction/1.1-Introduction/index.html&lt;/p&gt;
&lt;h2&gt;3. 深度学习&lt;/h2&gt;
&lt;p&gt;wandb 的使用方法和示例：https://shar-pen.github.io/2025/05/04/uncategorized/wandb/&lt;/p&gt;
&lt;h2&gt;4. AI 工具 / 大模型&lt;/h2&gt;
&lt;p&gt;chatbox 教程：https://zhuanlan.zhihu.com/p/1954839858825629828&lt;/p&gt;
&lt;p&gt;神马中转 API（配合 chatbox 使用）：https://zhuanlan.zhihu.com/p/1955311227166266871&lt;/p&gt;</content:encoded><h:img src="/_astro/thumbnail.DzZDiYKA.jpg"/><enclosure url="/_astro/thumbnail.DzZDiYKA.jpg"/></item><item><title>LaTeX笔记/提纲模板</title><link>https://www.wht0909.top/blog/latex%E7%AC%94%E8%AE%B0-%E6%8F%90%E7%BA%B2%E6%A8%A1%E6%9D%BF/latex%E7%AC%94%E8%AE%B0-%E6%8F%90%E7%BA%B2%E6%A8%A1%E6%9D%BF</link><guid isPermaLink="true">https://www.wht0909.top/blog/latex%E7%AC%94%E8%AE%B0-%E6%8F%90%E7%BA%B2%E6%A8%A1%E6%9D%BF/latex%E7%AC%94%E8%AE%B0-%E6%8F%90%E7%BA%B2%E6%A8%A1%E6%9D%BF</guid><description>自用的 LaTeX 模板</description><pubDate>Wed, 07 Jan 2026 20:57:05 GMT</pubDate><content:encoded>&lt;p&gt;该模板基于四川大学智锐科创协会提供的 LaTeX 模板修改，适用于课堂笔记与提纲整理（如有侵权请联系作者，作者将立刻删除）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;note.tex&lt;/code&gt;文件内容如下，直接复制即可，在编译时使用 &lt;code&gt;XeLaTeX&lt;/code&gt; 作为编译器&lt;/p&gt;
&lt;p&gt;在&lt;code&gt;note.tex&lt;/code&gt;同级的目录下，需要新建 figures 文件夹，并在文件夹中放上&lt;code&gt;logo.png&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-LaTeX&quot;&gt;\documentclass{article} 

% 核心宏包
\usepackage{ctex}       
\usepackage{graphicx}   
\usepackage{float}      
\usepackage{amsmath}    
\usepackage{tabularx}   
\usepackage{booktabs}   
\usepackage{fancyhdr}   
\usepackage{geometry}   
\usepackage{hyperref}   

% 字体设置
% 使用标准Windows字体集
\ctexset{fontset = none}

% 自定义宋体，支持伪斜体和伪粗体
\setCJKfamilyfont{zhsong}[
  AutoFakeSlant = {0.3},    % 伪斜体，倾斜角度0.3
  AutoFakeBold = {2.3}      % 伪粗体，加粗程度2.3
]{SimSun}
\renewcommand*{\songti}{\CJKfamily{zhsong}}

% 定义楷体
\setCJKfamilyfont{zhkai}[AutoFakeBold = {2.3}]{KaiTi}
\newcommand{\mykaiti}{\CJKfamily{zhkai}}


% 设置全局字体
\setCJKmainfont{SimSun}
\setCJKsansfont{SimHei}
\setCJKmonofont{FangSong}

% 页面设置
\geometry{a4paper,left=2cm,right=2cm,top=2cm,bottom=2cm}
\graphicspath{{figures/}} 

% 超链接设置
\hypersetup{
  colorlinks=true,
  linkcolor=black,
  citecolor=blue
}

% 自定义命令
\newcommand{\covertitle}{\zihao{-0}\heiti\bfseries}  
\newcommand{\covertablename}{\heiti \zihao{-3}}      
\newcommand{\covertablecontent}{\mykaiti \zihao{-3}} 
\newcommand\covertable[2]{ 
  \begin{table}[H]
    \centering
    \begin{tabular}{p{12em}}
      {\covertablename #1 }\\
    \end{tabular}
    \begin{tabular}{p{14em}&amp;#x3C;{\centering}}
      {\covertablecontent #2 }\\
      \hline
    \end{tabular}
  \end{table}
}

% 正文字体命令
\newcommand\textFont{\songti \zihao{-4}}

% 修改目录名称
\renewcommand{\contentsname}{\centerline{目录}} 

% 页眉设置
\pagestyle{fancy} 
\fancypagestyle{页眉}{
  \fancyhead{}
  \fancyhead[C]{你的名字}   
  \renewcommand\headrulewidth{.5pt}
}

\begin{document}

% 封面区域
\quad \\ \\ 

% Logo
\begin{figure}[H]
  \centering
  \includegraphics[width=0.5\linewidth]{logo}
\end{figure}

\quad \\ 
\begin{center}
  \covertitle{\zihao{2}文章标题}
\end{center}

\quad \\ \\ \\

% 封面信息表格
\covertable{作\hskip 5em 者}{你的名字}
\covertable{提\quad 交\quad 日\quad 期}{\today}
\covertable{学\hskip 5em 院}{xx学院}
\covertable{专\hskip 5em 业}{xx专业}

% 空白页结束
\thispagestyle{empty}
\newpage

% 目录页
\thispagestyle{empty}  % 目录页不显示页眉页脚

% 生成目录
\tableofcontents
\newpage

% 正文开始
\setcounter{page}{1}
\pagestyle{页眉}

\section{正文基础示例}
\textFont

这是正文基础段落示例，LaTeX会自动完成排版对齐，无需手动调整格式。

\subsection{字体样式示例}
可轻松实现 \textbf{加粗文字}、\emph{斜体文字}，或调整字号 {\zihao{3} 大号文字}。

\textbf{伪粗体测试：这段文字应该显示为伪粗体效果。}

\emph{伪斜体测试：这段文字应该显示为伪斜体效果。}

\subsection{数学公式示例}
行内公式：$E = mc^2$

行间公式：
$$ \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi} $$

\subsection{表格示例}
\begin{table}[h!]
  \centering
  \caption{基础三线表示例}
  \begin{tabularx}{0.9\textwidth}{l X X}
    \toprule
    \textbf{维度} &amp;#x26; \textbf{示例1} &amp;#x26; \textbf{示例2} \\
    \midrule
    内容1 &amp;#x26; 基础文本内容 &amp;#x26; 自动换行的长文本内容，无需手动调整宽度 \\
    内容2 &amp;#x26; 公式嵌入 &amp;#x26; $A = \begin{bmatrix}1 &amp;#x26; 0 \\0 &amp;#x26; 1\end{bmatrix}$ \\
    \bottomrule
  \end{tabularx}
\end{table}

\section{结语}
以上为LaTeX基础使用示例，可根据需求修改内容、样式及结构。

{\songti 宋体文本} {\mykaiti 楷体文本} {\heiti 黑体文本} 

\end{document}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/latex.CuYZK4SE.png"/><enclosure url="/_astro/latex.CuYZK4SE.png"/></item><item><title>uv环境管理简明教程</title><link>https://www.wht0909.top/blog/uv%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B/uv%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B</link><guid isPermaLink="true">https://www.wht0909.top/blog/uv%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B/uv%E7%8E%AF%E5%A2%83%E7%AE%A1%E7%90%86%E7%AE%80%E6%98%8E%E6%95%99%E7%A8%8B</guid><description>总结 uv 的使用方法和常用指令</description><pubDate>Sun, 14 Dec 2025 20:51:45 GMT</pubDate><content:encoded>&lt;p&gt;uv 是一款基于 Rust 编写的 Python 环境管理工具，本文将总结 uv 的使用方法和常用指令[1]。uv 在 Windows 和 Linux / MacOS 系统下的安装详见笔记&quot;Python创建虚拟环境&quot;。本文的所有示例程序均在 Windows Subsystem for Linux 2（WSL 2）的 Ubuntu 20.04 发行版环境中运行。&lt;/p&gt;
&lt;h2&gt;1. 初始化项目&lt;/h2&gt;
&lt;p&gt;在项目文件夹中运行&lt;code&gt;uv init&lt;/code&gt;指令即可完成初始化，运行后的项目结构如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;.
├── .git
├── .gitignore # 版本管理
├── .python-version # python 版本说明
├── README.md # 说明文档
├── main.py # 主程序入口
└── pyproject.toml # 配置文件信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 pyproject.toml 内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[project]
name = &quot;uvtest&quot; # 项目名称
version = &quot;0.1.0&quot; # 项目版本号
description = &quot;Add your description here&quot; # 项目介绍
readme = &quot;README.md&quot; # 说明文档
requires-python = &quot;&gt;=3.9&quot; # python 版本要求
dependencies = [] # 依赖
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 创建虚拟环境&lt;/h2&gt;
&lt;p&gt;通过&lt;code&gt;uv venv&lt;/code&gt;指令即可创建环境。根据提示，可通过&lt;code&gt;source .venv/bin/activate&lt;/code&gt;激活环境。实际上 uv 会首先查找当前项目下有无 .venv 文件夹，无需手动激活也能够正常使用。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(base) root@XiaoHei:/mnt/d/UvTest# uv venv
Using CPython 3.9.12 interpreter at: /root/anaconda3/bin/python3
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常情况下，直接使用&lt;code&gt;uv venv&lt;/code&gt;创建环境会很慢。一个常用的提速方法是配置镜像源[2]：&lt;/p&gt;
&lt;p&gt;Linux / macOS：创建并编辑 /etc/uv/uv.toml 或者 $XDG_CONFIG_DIRS/uv/uv.toml&lt;/p&gt;
&lt;p&gt;Windows：创建并编辑 %SYSTEMDRIVE%\ProgramData\uv\uv.toml&lt;/p&gt;
&lt;p&gt;uv.toml内容为：&lt;code&gt;python-install-mirror = &quot;https://registry.npmmirror.com/-/binary/python-build-standalone/&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在项目中生成的配置文件 pyproject.toml 中添加以下内容：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[tool.uv]
python-install-mirror = &quot;https://registry.npmmirror.com/-/binary/python-build-standalone/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 运行程序&lt;/h2&gt;
&lt;p&gt;使用&lt;code&gt;uv run main.py&lt;/code&gt;运行程序，运行时所有依赖的版本会自动更新&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;uv add pandas numpy&lt;/code&gt;添加依赖&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(UvTest) (base) root@XiaoHei:/mnt/d/UvTest# uv add numpy --default-index https://pypi.tuna.tsinghua.edu.cn/simple
Resolved 4 packages in 782ms                                                                                                          
Audited 1 package in 4ms 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在实际下载的过程中，和 pip 一样，如果不使用国内的镜像源直接下载，uv 将从中央仓库拉取依赖，下载速度非常之慢。配置国内源的方法有两种，一种是直接在指令后加参数（如上面的示例代码）[3]：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add numpy --default-index https://pypi.tuna.tsinghua.edu.cn/simple
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另一种是永久性的配置镜像源：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 清华源
echo &apos;export UV_DEFAULT_INDEX=&quot;https://pypi.tuna.tsinghua.edu.cn/simple&quot;&apos;&gt;&gt; ~/.bashrc
# 令配置立刻生效
source ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依赖下载完成后，可以发现配置文件 pyproject.toml 发生了变化：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;[project]
name = &quot;uvtest&quot;
version = &quot;0.1.0&quot;
description = &quot;Add your description here&quot;
readme = &quot;README.md&quot;
requires-python = &quot;&gt;=3.9&quot;
dependencies = [
    &quot;numpy&gt;=2.0.2&quot;,
    &quot;pandas&gt;=2.3.3&quot;,
]

[[tool.uv.index]]
url = &quot;https://pypi.tuna.tsinghua.edu.cn/simple&quot;
default = true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;还可以安装特定版本的库，如&lt;code&gt;uv add matplotlib==3.9.4&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;移除库时，使用指令&lt;code&gt;uv remove matplotlib&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意：在使用 uv 时，应尽可能避免使用 pip，以免造成环境污染&lt;/p&gt;
&lt;p&gt;通过 uv.lock 文件可严格锁定依赖的版本，避免版本冲突&lt;/p&gt;
&lt;p&gt;通过&lt;code&gt;uv tree&lt;/code&gt;可以清晰地看到项目的依赖关系树：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(base) root@XiaoHei:/mnt/d/UvTest# uv tree
Resolved 24 packages in 9ms
uvtest v0.1.0
├── matplotlib v3.9.4
│   ├── contourpy v1.3.0
│   │   └── numpy v2.0.2
│   ├── cycler v0.12.1
│   ├── fonttools v4.60.2
│   ├── importlib-resources v6.5.2
│   │   └── zipp v3.23.0
│   ├── kiwisolver v1.4.7
│   ├── numpy v2.0.2
│   ├── packaging v25.0
│   ├── pillow v11.3.0
│   ├── pyparsing v3.2.5
│   └── python-dateutil v2.9.0.post0
│       └── six v1.17.0
├── numpy v2.0.2
└── pandas v2.3.3
    ├── numpy v2.0.2
    ├── python-dateutil v2.9.0.post0 (*)
    ├── pytz v2025.2
    └── tzdata v2025.3
(*) Package tree already displayed
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 复现 uv 环境&lt;/h2&gt;
&lt;p&gt;首先移除现有的虚拟环境：&lt;code&gt;rm -rf .venv&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;复现环境时必须有 pyproject.toml 和 uv.lock 两个文件&lt;/p&gt;
&lt;p&gt;只需&lt;code&gt;uv sync&lt;/code&gt;即可实现复现&lt;/p&gt;
&lt;p&gt;此外，还可以通过&lt;code&gt;uv add -r requirements&lt;/code&gt;添加依赖&lt;/p&gt;
&lt;p&gt;本文参考了以下资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1] &lt;a href=&quot;https://www.bilibili.com/video/BV15MVdzaEUw/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;bilibili：全面掌握UV：Python下一代环境管理懒人工具（Python五分钟）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[2] &lt;a href=&quot;https://blog.csdn.net/eididjjd/article/details/150018389&quot;&gt;CSDN：uv下载python加速镜像源 uv python下载加速配置文件 全局配置 项目配置&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[3] &lt;a href=&quot;https://zhuanlan.zhihu.com/p/1930714592423703026&quot;&gt;知乎：别再忍了！uv 下载慢如龟速？一招配置国内镜像，让你的 Python 体验坐上火箭！&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/python.BKvTRH0r.png"/><enclosure url="/_astro/python.BKvTRH0r.png"/></item><item><title>Python创建虚拟环境</title><link>https://www.wht0909.top/blog/python%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/python%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83</link><guid isPermaLink="true">https://www.wht0909.top/blog/python%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83/python%E5%88%9B%E5%BB%BA%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83</guid><description>整理了 Python 创建虚拟环境的几种方法</description><pubDate>Sun, 14 Dec 2025 15:45:35 GMT</pubDate><content:encoded>&lt;p&gt;虚拟环境可以将不同项目所需的库隔离开，使其互不影响。本文将介绍几种创建虚拟环境的方法，所有的示例程序均在 Windows Subsystem for Linux 2（WSL 2）的 Ubuntu 20.04 发行版环境中运行。&lt;/p&gt;
&lt;h2&gt;1. 使用 venv 创建&lt;/h2&gt;
&lt;h3&gt;1.1 venv 的安装和配置&lt;/h3&gt;
&lt;p&gt;基本语法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python -m venv 环境名称
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;环境名称通常命名为.venv。示例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 进入项目目录
mkdir my_project &amp;#x26;&amp;#x26; cd my_project

# 创建虚拟环境（命名为&apos;.venv&apos;是常见约定）
python3 -m venv .venv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;激活虚拟环境：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Windows 系统
.venv\Scripts\activate

# Linux / MacOS 系统
source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;激活后，命令行会显示环境名称：&lt;code&gt;(.venv) $&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;退出虚拟环境时，使用指令：&lt;code&gt;deactivate&lt;/code&gt;即可&lt;/p&gt;
&lt;p&gt;整个过程示例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;(base) root@XiaoHei:/home/wanghaotian/TestCode# python -m venv .venv
(base) root@XiaoHei:/home/wanghaotian/TestCode# source .venv/bin/activate
(.venv) (base) root@XiaoHei:/home/wanghaotian/TestCode# pip install pandas==1.3.3 requests==2.26.0 Django==3.2.12
# 安装过程略
Successfully installed Django-3.2.12 asgiref-3.11.0 certifi-2025.11.12 charset-normalizer-2.0.12 idna-3.11 numpy-2.0.2 pandas-1.3.3 python-dateutil-2.9.0.post0 pytz-2025.2 requests-2.26.0 six-1.17.0 sqlparse-0.5.4 typing_extensions-4.15.0 urllib3-1.26.20
(.venv) (base) root@XiaoHei:/home/wanghaotian/TestCode# pip list
Package            Version
------------------ -----------
asgiref            3.11.0
certifi            2025.11.12
charset-normalizer 2.0.12
Django             3.2.12
idna               3.11
numpy              2.0.2
pandas             1.3.3
pip                22.0.4
python-dateutil    2.9.0.post0
pytz               2025.2
requests           2.26.0
setuptools         58.1.0
six                1.17.0
sqlparse           0.5.4
typing_extensions  4.15.0
urllib3            1.26.20
WARNING: You are using pip version 22.0.4; however, version 25.3 is available.
You should consider upgrading via the &apos;/home/wanghaotian/TestCode/.venv/bin/python -m pip install --upgrade pip&apos; command.
(.venv) (base) root@XiaoHei:/home/wanghaotian/TestCode# deactivate
(base) root@XiaoHei:/home/wanghaotian/TestCode# pip show Django
WARNING: Package(s) not found: Django # 没有找到包，可以看出虚拟环境实现了库的有效隔离
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该部分参考了以下资源：
&lt;a href=&quot;https://www.runoob.com/python3/python-venv.html&quot;&gt;菜鸟教程：Python 虚拟环境的创建（venv）&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;2. 使用 uv 创建&lt;/h2&gt;
&lt;h3&gt;2.1 uv 的安装和配置&lt;/h3&gt;
&lt;p&gt;uv 是一款由 Rust 编写的 Python 包管理器，相比于传统的 venv 更加高效快捷&lt;/p&gt;
&lt;p&gt;uv 的安装：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux / MacOS
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows
 powershell -ExecutionPolicy ByPass -c &quot;irm https://astral.sh/uv/install.ps1 | iex&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装过程比较慢，需要耐心等待&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Windows
PS C:\Windows\system32&gt; powershell -ExecutionPolicy ByPass -c &quot;irm https://astral.sh/uv/install.ps1 | iex&quot;
Downloading uv 0.9.17 (x86_64-pc-windows-msvc)
Installing to C:\Users\15806\.local\bin
  uv.exe
  uvx.exe
  uvw.exe
everything&apos;s installed!

To add C:\Users\15806\.local\bin to your PATH, either restart your shell or run:

    set Path=C:\Users\15806\.local\bin;%Path%   (cmd)
    $env:Path = &quot;C:\Users\15806\.local\bin;$env:Path&quot;   (powershell)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux
(base) root@XiaoHei:/home/wanghaotian/TestCode# curl -LsSf https://astral.sh/uv/install.sh | sh
downloading uv 0.9.17 x86_64-unknown-linux-gnu
no checksums to verify
installing to /root/.local/bin
  uv
  uvx
everything&apos;s installed!

To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，需要将 uv 配置到环境变量中去，并使用&lt;code&gt;uv --version&lt;/code&gt;验证&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 1. 编辑 root 用户的 bash 配置文件
vim ~/.bashrc

# 2. 在文件末尾添加以下内容：
export PATH=&quot;$HOME/.local/bin:$PATH&quot;

# 3. 刷新配置
source ~/.bashrc

# 4. 检查，出现类似于&quot;uv 0.9.17&quot;的提示即成功
uv --version
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.2 uv 管理 python 版本&lt;/h3&gt;
&lt;p&gt;查看可用 python版本：&lt;code&gt;uv python list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;安装特定 python 版本：&lt;code&gt;uv python install 3.12&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;设置全局默认 python 版本：&lt;code&gt;uv python default 3.12&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;2.3 uv 管理虚拟环境&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# uv 创建名为 .venv 虚拟环境
uv venv

# 在 Windows 下激活环境
.venv\Scripts\activate

# 在 Linux 下激活环境
source .venv/bin/activate

# 为当前项目固定 Python 3.11 版本
uv python pin 3.11

# 安装某个特定版本的库
uv pip install requests==2.31.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该部分参考了以下资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://uv.doczh.com/getting-started/installation/&quot;&gt;uv 中文文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.runoob.com/python3/uv-tutorial.html&quot;&gt;菜鸟教程：uv 入门教程 -- Python 包与环境管理工具&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. anaconda 环境管理&lt;/h2&gt;
&lt;p&gt;anaconda 是最常用的环境管理工具之一，网络上有很多下载 anaconda 的教程，笔者在此不再过多赘述，仅给出几个常用的指令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建环境
conda create --name myenv python=3.8 

# 激活环境
conda activate myenv

# 退出环境
conda deactivate

# 删除环境
conda remove --name myenv --all
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该部分参考了以下资源：
&lt;a href=&quot;href=%22https://zhuanlan.zhihu.com/p/24478448255&quot;&gt;知乎：超全常用 conda 命令整理&lt;/a&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/python.BKvTRH0r.png"/><enclosure url="/_astro/python.BKvTRH0r.png"/></item><item><title>Vue学习笔记-2</title><link>https://www.wht0909.top/blog/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-2/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-2</link><guid isPermaLink="true">https://www.wht0909.top/blog/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-2/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-2</guid><description>图灵学院 Vue 学习笔记</description><pubDate>Thu, 11 Dec 2025 21:01:41 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;1. 整体认识 Vue3&lt;/h2&gt;
&lt;h2&gt;1.1 创建工程&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 创建工程
npm create vue@latest 
# 启动项目
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.2 项目结构&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;node_modules&lt;/code&gt;目录：存放依赖&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;public&lt;/code&gt;目录：静态资源，如图片视频等&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;src&lt;/code&gt;目录：放源代码&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;index.html&lt;/code&gt;：Vue 项目访问页面的入口&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt;文件：devDependencies ——依赖，scripts —— 启动脚本等&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;启动项目：&lt;code&gt;npm run dev&lt;/code&gt;
打包项目：&lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;典型的 Vue 项目都只在 index 这一个页面里进行交互，即 SPA&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vue3 的核心是通过 createApp 创建一个应用实例，在实例中构建各种应用&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每一个 .vue 文件都是一个组件，组件可以互相嵌套&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;每个 vue 文件都包括 script, template, style&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2. 理解：数据的双向绑定&lt;/h2&gt;
&lt;p&gt;理解：把 template 里的页面数据和 script 里的数据建立绑定关系&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;v-model 绑定数据&lt;/li&gt;
&lt;li&gt;v-on 绑定方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vue 单文件组件（SFC）的 &lt;code&gt;&amp;#x3C;template&gt;&lt;/code&gt; 要求只能有一个根节点 &lt;code&gt;&amp;#x3C;div&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;示例代码，注意 data 和 methods 的写法：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;template&gt;
  &amp;#x3C;div&gt;
    姓名：&amp;#x3C;input v-model=&quot;userName&quot;&gt;{{ userName }}&amp;#x3C;br&gt;&amp;#x3C;br&gt;
    薪水：&amp;#x3C;input type=&quot;number&quot; v-model=&quot;salary&quot;&gt;{{ salary }}&amp;#x3C;br&gt;&amp;#x3C;br&gt;
    &amp;#x3C;button @click=&quot;addSalary&quot;&gt;加薪&amp;#x3C;/button&gt;
  &amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

{/* lang=&quot;ts&quot; 声明该脚本块使用TypeScript编写 */}
&amp;#x3C;script lang=&quot;ts&quot;&gt;
  export default{
    data(){
      return {
        userName: &quot;roy&quot;,
        salary: 1800
      }
    },
    methods:{
      addSalary(){
        this.salary += 1000
      }
    }
  }
&amp;#x3C;/script&gt;

&amp;#x3C;style scoped&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;style 中的 scope 表示样式只在当前文件中生效&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;常用的双向绑定：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;v-if: 控制元素显示（直接摧毁）&lt;/li&gt;
&lt;li&gt;v-show：控制元素显示（通过 display）&lt;/li&gt;
&lt;li&gt;v-for: 循环显示元素&lt;/li&gt;
&lt;li&gt;@方法: 将事件和方法绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;在写 methods 时不要忘了 this 关键字&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3. OptionsAPI 和 CompositionAPI&lt;/h2&gt;
&lt;p&gt;OptionsAPI：配置式 API，用一个统一的&lt;strong&gt;对象&lt;/strong&gt;封装所有代码逻辑&lt;/p&gt;
&lt;p&gt;OptionsAPI 代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;script&gt;
  export default{
    data(){
      return{
        userInfo: {
          name: &quot;&quot;,
          age: 0,
          gender: 1,
          job: &quot;&quot;,
          skills: [&quot;Java&quot;, &quot;Python&quot;, &quot;Vue&quot;]
        },
        newSkill: &quot;&quot;,
        showUserInfo: false
      }
    },
    methods: {
      learnNewSkill(){
        if(this.newSkill){
          this.userInfo.skills.push(this.newSkill)
        }
      },
      changeShwoInfo(){
        this.showUserInfo = !this.showUserInfo
      }
    }
  }
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CompositionAPI：组合式 API，把数据和方法写在一起&lt;/p&gt;
&lt;p&gt;CompositionAPI 代码示例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;script lang=&quot;ts&quot;&gt;
import { ref } from &apos;vue&apos;;

  export default{
    setup(){ // 在进入页面时就调用这个函数
      let userName = ref(&quot;joy&quot;) // ref 实现双向绑定
      let salary = ref(1500)
      function addSalary(){
        salary.value += 1000
      }
      return {userName, salary, addSalary} // 把需要用的数据和方法返回
    }
  }
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以通过 setup 的语法糖简化代码，在 script 中写 setup，就无需在代码中 setup 并 return 了&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
  import { ref } from &apos;vue&apos;;
  let userName = ref(&quot;joy&quot;)
  let salary = ref(1500)
  function addSalary(){
    salary.value += 1000
  }
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;组合式 API 的另一个好处是可以在另一个文件里写逻辑，在 .vue 中直接调用即可&lt;/p&gt;
&lt;p&gt;例如，在 components 文件夹下新建一个 MySalary.ts，内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-TypeScript&quot;&gt;import { ref } from &quot;vue&quot;;

export default function(){ // 导出一个函数，执行并返回动态的值；而不是导出对象
    let userName = ref(&quot;roy&quot;)
    let salary = ref(1500);
    function addSalary(){
        salary.value += 1000
    }
    return {userName, salary, addSalary}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 App.vue 中直接调用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
  import MySalary from &quot;@/components/MySalary&quot; // @等价于 src 文件夹；自定义导出的匿名函数的名称为 Salary
  let {userName, salary, addSalary} = MySalary() // 解包对象
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Vue3 的数据双向绑定&lt;/h2&gt;
&lt;p&gt;不要操作 ref 对象本身，而是操作它的 value 属性&lt;/p&gt;
&lt;p&gt;让对象具备响应式的能力：reactive&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
import { reactive } from &apos;vue&apos;;
  let userInfo = reactive({userName: &quot;roy&quot;, salary: 15000})
  function addSalary(){
    userInfo.salary += 1000
  }
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：ref 包裹的数据需要通过 value 获取值，而 reactive 不需要&lt;/p&gt;
&lt;p&gt;toRef() 可以把对象转成 ref 对象，使其具备响应式能力&lt;/p&gt;
&lt;p&gt;toRefs(对象)：把这个对象的所有属性都转成 ref 对象&lt;/p&gt;
&lt;h2&gt;5. 自定义组件&lt;/h2&gt;
&lt;h3&gt;5.1 定义子组件&lt;/h3&gt;
&lt;p&gt;在 components 文件夹下新建组件 SalaryInfo.vue，内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;template&gt;
    姓名：&amp;#x3C;input type=&quot;text&quot; v-model=&quot;UserName&quot;/&gt;&amp;#x3C;br /&gt;
    工资：&amp;#x3C;input type=&quot;number&quot; v-model=&quot;UserSalary&quot;/&gt; &amp;#x3C;br /&gt;
    加薪：&amp;#x3C;input type=&quot;button&quot; @click=&quot;AddSalary&quot; value=&quot;点击&quot;&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;!-- 这部分是为了暴露组件，自定义名称为&quot;SalaryInfo&quot; --&gt;
&amp;#x3C;script lang=&quot;ts&quot;&gt; 
    export default{
        name: &quot;SalaryInfo&quot;
    }
&amp;#x3C;/script&gt;

&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
    import { ref } from &apos;vue&apos;;
    let UserName = ref(&quot;roy&quot;)
    let UserSalary = ref(15000)
    function AddSalary(){
        UserSalary.value += 1000
    }   
&amp;#x3C;/script&gt;

&amp;#x3C;style&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;定义好之后，在 App.vue 中引入组件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;template&gt;
&amp;#x3C;div&gt;
  &amp;#x3C;!-- 在这里引入组件 --&gt;
  &amp;#x3C;SalaryInfo&gt;&amp;#x3C;/SalaryInfo&gt;
&amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
import SalaryInfo from &apos;./components/SalaryInfo.vue&apos;;

&amp;#x3C;/script&gt;

&amp;#x3C;style scoped&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 将子组件的对象暴露给父组件&lt;/h3&gt;
&lt;p&gt;在父组件中我们可以获取子组件的一些属性。在此之前，需要让子组件先暴露给父组件：在 SalaryInfo.vue 中作出如下修改：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
    import { ref } from &apos;vue&apos;;
    let userName = ref(&quot;roy&quot;)
    let userSalary = ref(15000)
    function AddSalary(){
        userSalary.value += 1000
    }  
    // 添加以下代码，暴露出父组件需要的部分
    defineExpose({userName, userSalary, AddSalary}) 
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;App.vue 修改如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;template&gt;
&amp;#x3C;div&gt;
  &amp;#x3C;SalaryInfo ref=&quot;salaryInfo&quot;&gt;&amp;#x3C;/SalaryInfo&gt;
  &amp;#x3C;button @click=&quot;showRes&quot;&gt;查看薪水信息&amp;#x3C;/button&gt;
&amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
import { ref } from &apos;vue&apos;;
import SalaryInfo from &apos;./components/SalaryInfo.vue&apos;;
let salaryInfo = ref();
function showRes(){
  console.log(salaryInfo)
}
&amp;#x3C;/script&gt;

&amp;#x3C;style scoped&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&amp;#x3C;SalaryInfo ref=&quot;salaryInfo&quot;&gt;&amp;#x3C;/SalaryInfo&gt;&lt;/code&gt;：ref 在 Vue 中用来获取 DOM 元素或子组件实例的引用。Vue 会自动将该子组件的实例对象挂载到 &lt;code&gt;&amp;#x3C;script setup&gt;&lt;/code&gt; 中同名的 ref 变量上。在这里是&lt;code&gt;let salaryInfo = ref();&lt;/code&gt;。此处已经获取到子组件，通过 console.log 在控制台打印信息&lt;/p&gt;
&lt;h3&gt;5.3 将父组件的对象暴露给子组件&lt;/h3&gt;
&lt;p&gt;修改后的代码如下，下面将作详细说明：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SalaryInfo.vue&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;template&gt;
    姓名：&amp;#x3C;input type=&quot;text&quot; v-model=&quot;salaryInfo.userName&quot;/&gt;&amp;#x3C;br /&gt;
    工资：&amp;#x3C;input type=&quot;number&quot; v-model=&quot;salaryInfo.userSalary&quot;/&gt; &amp;#x3C;br /&gt;
    &amp;#x3C;!-- 加薪：&amp;#x3C;input type=&quot;button&quot; @click=&quot;AddSalary&quot; value=&quot;点击&quot;&gt; --&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;script lang=&quot;ts&quot;&gt;
    export default{
        name: &quot;SalaryInfo&quot;
    }
&amp;#x3C;/script&gt;

&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
    defineProps([
        &quot;salaryInfo&quot;
    ])
&amp;#x3C;/script&gt;

&amp;#x3C;style&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;App.vue&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;template&gt;
&amp;#x3C;div&gt;
  &amp;#x3C;SalaryInfo :salary-info=&quot;salaryInfo&quot;&gt;&amp;#x3C;/SalaryInfo&gt;
  &amp;#x3C;button @click=&quot;showRes&quot;&gt;查看薪水信息&amp;#x3C;/button&gt;
&amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
import { reactive } from &apos;vue&apos;
import SalaryInfo from &apos;./components/SalaryInfo.vue&apos;;
let salaryInfo = reactive({
  userName: &quot;roy&quot;, userSalary: 15000
})

function showRes(){
  salaryInfo.userSalary += 1000
}
&amp;#x3C;/script&gt;

&amp;#x3C;style scoped&gt;

&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 SalaryInfo.vue 中，通过&lt;code&gt;defineProps&lt;/code&gt;接收来自父组件暴露的数据，接受的是一个列表，列表中的元素是暴露的数据的&lt;strong&gt;名字&lt;/strong&gt;，即salaryInfo。&lt;code&gt;&amp;#x3C;input type=&quot;text&quot; v-model=&quot;salaryInfo.userName&quot;/&gt;&lt;/code&gt;中获取的 userName 就来源于它（这里并没有对获取的数据做校验，也就是说不能保证获取到的 salaryInfo 对象一定有 userName 属性，这里可能会报错）。&lt;/p&gt;
&lt;p&gt;在 App.vue 中，&lt;code&gt;&amp;#x3C;SalaryInfo :salary-info=&quot;salaryInfo&quot;&gt;&amp;#x3C;/SalaryInfo&gt;&lt;/code&gt;中的&lt;code&gt;:&lt;/code&gt;是 v-bind 的简写，用于双向绑定动态资源；&lt;code&gt;:salary-info&lt;/code&gt;中的 salary-info 是子组件接收的 Prop 名称，也就是父组件向外暴露的名称。在 Vue 的处理过程中，会把 salary-info 转为 salaryInfo，也就是子组件中要接收的数据的名字。最后的&lt;code&gt;salaryInfo&lt;/code&gt;是数据源的名称，对应着&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;let salaryInfo = reactive({
  userName: &quot;roy&quot;, userSalary: 15000
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 组件的生命周期&lt;/h2&gt;
&lt;p&gt;生命周期分为四个阶段：创建 挂载 更新 销毁（卸载）&lt;/p&gt;
&lt;p&gt;CompositionAPI 的生命周期阶段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建阶段：setup&lt;/li&gt;
&lt;li&gt;挂载阶段：onBeforeMount onMounted&lt;/li&gt;
&lt;li&gt;更新阶段：onBeforeUpdate onUpdated&lt;/li&gt;
&lt;li&gt;卸载阶段：onBeforeUnmount onUnmounted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;用法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;
import { onMounted } from &apos;vue&apos;
onMounted(()=&gt;{
  console.log(&quot;挂载后&quot;)
})
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. Vue-Router 组件路由管理机制&lt;/h2&gt;
&lt;p&gt;路由：实现在页面上点击跳转&lt;/p&gt;
&lt;p&gt;安装 Vue-Router：&lt;code&gt;npm install vue-router@4&lt;/code&gt;（也可以在创建项目时就把 vue-router 选上）&lt;/p&gt;
&lt;p&gt;App.vue 内容如下，目标是通过点击“首页”、“关于”、“新闻”实现跳转：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;&amp;#x3C;/script&gt;

&amp;#x3C;template&gt;
  &amp;#x3C;div id=&quot;app&quot;&gt;
    &amp;#x3C;h1&gt;Hello App!&amp;#x3C;/h1&gt;
    &amp;#x3C;p&gt;
      &amp;#x3C;a href=&quot;&quot;&gt;首页&amp;#x3C;/a&gt;
      &amp;#x3C;a href=&quot;&quot;&gt;关于&amp;#x3C;/a&gt;
      &amp;#x3C;a href=&quot;&quot;&gt;新闻&amp;#x3C;/a&gt;
    &amp;#x3C;/p&gt;
    &amp;#x3C;div class=&quot;container&quot;&gt;&amp;#x3C;/div&gt;

  &amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;style scoped&gt;
  a {
    margin-right: 10px;
    color: green;
  }
  .container {
    background-color: yellowgreen;
    widows: 10%;
    height: 400px;
  }
&amp;#x3C;/style&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先需要在 src/pages 文件夹下创建三个页面对应的组件：HomePage.vue, AboutPage.vue, NewsPage.vue&lt;/p&gt;
&lt;p&gt;在 main.ts 中实现路由逻辑。一共分为三步：配置路由规则、创建路由器、加载路由器&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;import { createApp } from &apos;vue&apos;
import App from &apos;./App.vue&apos;
import HomePage from &apos;./pages/HomePage.vue&apos;
import AboutPage from &apos;./pages/AboutPage.vue&apos;
import NewsPage from &apos;./pages/NewsPage.vue&apos;
import {createRouter, createWebHistory} from &apos;vue-router&apos;

// 1. 配置路由规则
// 创建路由数组 path: 路径 component: 需要跳转到的页面组件 name：路由的别名
const routes = [
    {path: &quot;/home&quot;, component: HomePage, name: &quot;home&quot;},
    {path: &quot;/about&quot;, component: AboutPage, name: &quot;about&quot;},
    {path: &quot;/news&quot;, component: NewsPage, name: &quot;news&quot;}
]

// 2. 创建路由器
const router = createRouter({
    history: createWebHistory(), // 路由工作模式
    routes: routes // 路由规则
})

// 3. 加载路由器
const app = createApp(App)
app.use(router)
app.mount(&apos;#app&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成路由配置后，我们回头修改 App.vue 组件。实现路由跳转需要两个标签：&lt;code&gt;&amp;#x3C;RouterLink&gt;&lt;/code&gt; 和 &lt;code&gt;&amp;#x3C;RouterView&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;其中 &lt;code&gt;&amp;#x3C;RouterLink&gt;&lt;/code&gt; 描述跳转到哪个页面，&lt;code&gt;&amp;#x3C;RouterView&gt;&lt;/code&gt; 确定路由的出口，即跳转的内容渲染到哪里&lt;/p&gt;
&lt;p&gt;App.vue 如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Vue&quot;&gt;&amp;#x3C;script setup lang=&quot;ts&quot;&gt;&amp;#x3C;/script&gt;

&amp;#x3C;template&gt;
  &amp;#x3C;div id=&quot;app&quot;&gt;
    &amp;#x3C;h1&gt;Hello App!&amp;#x3C;/h1&gt;
    &amp;#x3C;p&gt;
      &amp;#x3C;RouterLink to=&quot;/home&quot;&gt;首页&amp;#x3C;/RouterLink&gt; &amp;#x3C;!--字符串跳转--&gt;
      &amp;#x3C;RouterLink :to=&quot;{path: &apos;/about&apos;}&quot;&gt;关于&amp;#x3C;/RouterLink&gt; &amp;#x3C;!--对象跳转--&gt;
      &amp;#x3C;RouterLink :to=&quot;{name: &apos;news&apos;}&quot;&gt;新闻&amp;#x3C;/RouterLink&gt; &amp;#x3C;!--具名跳转--&gt;
    &amp;#x3C;/p&gt;
    &amp;#x3C;div class=&quot;container&quot;&gt;
      &amp;#x3C;RouterView /&gt; &amp;#x3C;!--路由出口--&gt;
    &amp;#x3C;/div&gt;
  &amp;#x3C;/div&gt;
&amp;#x3C;/template&gt;

&amp;#x3C;style scoped&gt;
  a {
    margin-right: 10px;
    color: green;
  }
  .container {
    background-color: yellowgreen;
    widows: 10%;
    height: 400px;
  }
&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面代码中的 RouterLink 分别通过三种跳转方法实现路由跳转，其中对象跳转和具名跳转通过 json 形式包裹对象&lt;/p&gt;
&lt;p&gt;RouterLink 中有一个属性 replace，使用方法如下 &lt;code&gt;&amp;#x3C;RouterLink replace :to=&quot;{name: &apos;news&apos;}&quot;&gt;新闻&amp;#x3C;/RouterLink&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;加上 replace 属性后，浏览器访问时不能回退&lt;/p&gt;</content:encoded><h:img src="/_astro/vue.BZf-sKl9.png"/><enclosure url="/_astro/vue.BZf-sKl9.png"/></item><item><title>Web后端基础</title><link>https://www.wht0909.top/blog/web%E5%90%8E%E7%AB%AF%E5%9F%BA%E7%A1%80/web%E5%90%8E%E7%AB%AF%E5%9F%BA%E7%A1%80</link><guid isPermaLink="true">https://www.wht0909.top/blog/web%E5%90%8E%E7%AB%AF%E5%9F%BA%E7%A1%80/web%E5%90%8E%E7%AB%AF%E5%9F%BA%E7%A1%80</guid><description>黑马程序员网课 Web 基础学习笔记</description><pubDate>Wed, 10 Dec 2025 11:32:31 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;1. 资源和架构&lt;/h2&gt;
&lt;p&gt;静态资源：服务器上存储的，不会改变的资源&lt;/p&gt;
&lt;p&gt;动态资源：随用户请求的变化而变化的资源&lt;/p&gt;
&lt;p&gt;B/S 架构：浏览器/服务器架构，如京东、12306等，应用逻辑和数据存放在服务器&lt;/p&gt;
&lt;p&gt;C/S 架构：客户端/服务器架构，如 QQ 等，应用逻辑和数据存放在本地&lt;/p&gt;
&lt;h2&gt;2. Spring 框架&lt;/h2&gt;
&lt;p&gt;Spring：最流行的 Java 框架，其项目涵盖了各个应用场景&lt;/p&gt;
&lt;p&gt;SpringBoot：快速开发 Web 项目，简化开发，提高效率&lt;/p&gt;
&lt;h2&gt;3. SpringBoot 入门&lt;/h2&gt;
&lt;p&gt;需求：基于SpringBoot的方式开发一个web应用，浏览器发起请求/hello后，给浏览器返回字符串 &quot;Hello xxx ~&quot;&lt;/p&gt;
&lt;p&gt;创建好的 SpringBoot 项目结构如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./SpringBoot%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png&quot; alt=&quot;SpringBoot项目结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;这里已经删掉了不需要的项目结构，只保留了核心：src 文件夹和 pom.xml&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SpringbootWebQuickstartApplication&lt;/code&gt;是项目的启动类 / 引导类，其内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;package com.itheima;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // 启动类的标识
public class SpringbootWebQuickstartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebQuickstartApplication.class, args);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;里面是一个 main 函数，需要启动项目时直接运行这个 main 函数即可&lt;/p&gt;
&lt;p&gt;resources/static 用于存放静态页面（html, css, JS）&lt;/p&gt;
&lt;p&gt;application.properties 是项目的核心配置文件&lt;/p&gt;
&lt;p&gt;接下来编写请求处理类 HelloController，请求处理类一般以 Controller 结尾：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;package com.itheima;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // 表示这是一个请求处理类
public class HelloController {
    @RequestMapping(&quot;/hello&quot;) // 指向请求路径
    public String hello(String name){
        System.out.println(&quot;name&quot; + name);
        return &quot;Hello&quot; + name + &quot;~&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注解：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@RestController&lt;/code&gt;：当使用RestController注解一个类时，Spring会将该类视为控制器（Controller），并处理传入的HTTP请求&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@RequestMapping(value = &quot;/{id}&quot;, method = RequestMethod.GET)&lt;/code&gt;：将 HTTP 请求映射到控制器方法&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.1 SpringBoot 入门程序剖析&lt;/h3&gt;
&lt;p&gt;构建入门程序时自动引入的依赖：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;springboot-starter-web&lt;/code&gt;：包含了 web 应用开发常用的依赖&lt;/li&gt;
&lt;li&gt;&lt;code&gt;springboot-starter-test&lt;/code&gt;：包含了单元测试常用的依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. HTTP 协议&lt;/h2&gt;
&lt;p&gt;HTTP：超文本传输协议，规定了浏览器和服务器之间数据传输的规则&lt;/p&gt;
&lt;p&gt;特点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;基于TCP协议: 面向连接，安全&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;基于请求-响应模型:   一次请求对应一次响应（先请求后响应）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;HTTP协议是无状态协议:  对于数据没有记忆能力。每次请求-响应都是独立的&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;无状态指的是客户端发送HTTP请求给服务端之后，服务端根据请求响应数据，响应完后，不会记录任何信息。&lt;/li&gt;
&lt;li&gt;缺点：多次请求间不能共享数据&lt;/li&gt;
&lt;li&gt;优点:  速度快&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.1 HTTP 请求协议&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./GET%E8%AF%B7%E6%B1%82%E5%8D%8F%E8%AE%AE.png&quot; alt=&quot;GET 请求数据格式&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./POST%E8%AF%B7%E6%B1%82%E5%8D%8F%E8%AE%AE.png&quot; alt=&quot;POST 请求数据格式&quot;&gt;&lt;/p&gt;
&lt;p&gt;POST 请求数据的请求头和请求体之间有一个空行&lt;/p&gt;
&lt;h3&gt;4.2 请求数据获取&lt;/h3&gt;
&lt;p&gt;Web服务器（Tomcat）对HTTP协议的请求数据进行解析，并进行了封装，封装为一个 HttpServletRequest 对象，并在调用Controller方法的时候传递给了该方法。这样就使得程序员不必直接对协议进行操作，让Web开发更加便捷&lt;/p&gt;
&lt;p&gt;获取数据示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;package com.itheima;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RequestController {
    @RequestMapping(&quot;/request&quot;)
    public String request(HttpServletRequest request){
        // 1. 获取请求方式 - GET / POST
        String method = request.getMethod();
        System.out.println(&quot;请求方式：&quot; + method);

        // 2. 获取请求 url 地址
        String url = request.getRequestURL().toString();
        System.out.println(&quot;url: &quot; + url); // http://localhost:8080/request

        String uri = request.getRequestURI();
        System.out.println(&quot;uri: &quot; + uri); // /request

        // 3. 获取请求协议
        String protocal = request.getProtocol();
        System.out.println(&quot;请求协议：&quot; + protocal);

        // 4. 获取请求参数 - name, age
        String name = request.getParameter(&quot;name&quot;);
        System.out.println(&quot;name: &quot; + name);
        String age = request.getParameter(&quot;age&quot;);
        System.out.println(&quot;age: &quot; + age);

        // 5. 获取请求头 - Accept
        String header = request.getHeader(&quot;Accept&quot;);
        System.out.println(&quot;header: &quot; + header);

        return &quot;OK&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在浏览器输入：&lt;code&gt;http://localhost:8080/request?name=wang&amp;#x26;age=21&lt;/code&gt;，控制台输出如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;请求方式：GET
url: http://localhost:8080/request
uri: /request
请求协议：HTTP/1.1
name: wang
age: 21
header: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 HTTP 响应协议&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%93%8D%E5%BA%94%E5%8D%8F%E8%AE%AE.png&quot; alt=&quot;响应协议&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%8A%B6%E6%80%81%E7%A0%81.png&quot; alt=&quot;状态码&quot;&gt;&lt;/p&gt;
&lt;h3&gt;4.4 响应数据设置&lt;/h3&gt;
&lt;p&gt;Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse)，并在调用Controller方法的时候传递给了该方法。这样，就使得程序员不必直接对协议进行操作，让Web开发更加便捷&lt;/p&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;package com.itheima;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;


@RestController
public class ResponseController {
    /**
     * 方式一  HttpServletResponse 设置响应数据
     * @param response
     * @throws IOException
     */
    @RequestMapping(&quot;/response&quot;)
    public void response(HttpServletResponse response) throws IOException {
        // 1. 设置响应状态码
        response.setStatus(401);

        // 2. 设置响应头
        response.setHeader(&quot;name&quot;, &quot;wang&quot;);

        // 3. 设置响应体
        response.getWriter().write(&quot;&amp;#x3C;h1&gt;hello response&amp;#x3C;/h1&gt;&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在浏览器中输入&lt;code&gt;http://localhost:8080/response&lt;/code&gt;，请求结果如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./response%E8%AF%B7%E6%B1%82%E7%BB%93%E6%9E%9C.png&quot; alt=&quot;response请求结果&quot;&gt;&lt;/p&gt;
&lt;p&gt;Spring 中提供了 ResponseEntity，将请求结果封装为一个对象供程序员调用，示例如下:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;/**
 * 方式二 ResponseEntity
 */
@RequestMapping(&quot;/request2&quot;)
public ResponseEntity&amp;#x3C;String&gt; response2(){
    return ResponseEntity
            .status(402) // 响应状态码
            .header(&quot;name&quot;, &quot;javaweb&quot;) // 响应头
            .body(&quot;&amp;#x3C;h1&gt;hello java-web!&amp;#x3C;/h1&gt;&quot;); // 响应体
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;举个例子：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;// 泛型类：用&amp;#x3C;T&gt;作为类型占位符（T是Type的缩写，也可以用E、K、V等）
class Box&amp;#x3C;T&gt; {
    private T content; // 成员变量的类型是T（占位符）
    
    // 方法参数/返回值的类型都是T
    public void put(T content) { this.content = content; }
    public T get() { return content; }
}

// 使用时：给&amp;#x3C;T&gt;传入具体类型（这就是代码里&amp;#x3C;String&gt;的本质）
Box&amp;#x3C;String&gt; strBox = new Box&amp;#x3C;&gt;(); // 此时T=String
strBox.put(&quot;hello&quot;);
String str = strBox.get(); // 无需强制类型转换，安全

Box&amp;#x3C;Integer&gt; intBox = new Box&amp;#x3C;&gt;(); // 此时T=Integer
intBox.put(123);
Integer num = intBox.get(); // 类型安全，不会出错

Box&amp;#x3C;Double&gt; doubleBox = new Box&amp;#x3C;&gt;(); // 还能适配Double，无需改Box类
doubleBox.put(3.14);
Double d = doubleBox.get();
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/spring.DYcFEjz-.png"/><enclosure url="/_astro/spring.DYcFEjz-.png"/></item><item><title>空白的梦中之梦——读《楚王梦雨》之后</title><link>https://www.wht0909.top/blog/%E7%A9%BA%E7%99%BD%E7%9A%84%E6%A2%A6%E4%B8%AD%E4%B9%8B%E6%A2%A6%E8%AF%BB%E6%A5%9A%E7%8E%8B%E6%A2%A6%E9%9B%A8%E4%B9%8B%E5%90%8E/%E7%A9%BA%E7%99%BD%E7%9A%84%E6%A2%A6%E4%B8%AD%E4%B9%8B%E6%A2%A6%E8%AF%BB%E6%A5%9A%E7%8E%8B%E6%A2%A6%E9%9B%A8%E4%B9%8B%E5%90%8E</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E7%A9%BA%E7%99%BD%E7%9A%84%E6%A2%A6%E4%B8%AD%E4%B9%8B%E6%A2%A6%E8%AF%BB%E6%A5%9A%E7%8E%8B%E6%A2%A6%E9%9B%A8%E4%B9%8B%E5%90%8E/%E7%A9%BA%E7%99%BD%E7%9A%84%E6%A2%A6%E4%B8%AD%E4%B9%8B%E6%A2%A6%E8%AF%BB%E6%A5%9A%E7%8E%8B%E6%A2%A6%E9%9B%A8%E4%B9%8B%E5%90%8E</guid><description>分享一首张枣的诗歌</description><pubDate>Mon, 08 Dec 2025 14:43:04 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;封面画作：Andrejs Ko &lt;em&gt;&lt;strong&gt;The Book and the Lilac Branch&lt;/strong&gt;&lt;/em&gt;(2025)&lt;/p&gt;
&lt;p&gt;张枣（1962年12月29日—2010年3月8日），湖南长沙人，中国现代诗人，中国先锋诗歌的代表人之一，1984年秋，不到二十二岁的张枣即以《镜中》闻名大江南北。张枣以“后赫耳墨斯学派（Posthermetische Schule）”闻名于世，被认为一方面给世界文学添加了创新的元素，另一方面又在作品中保留了中国古诗词的特点。（来源于维基百科）&lt;/p&gt;
&lt;p&gt;我要衔接过去一个人的梦，&lt;/p&gt;
&lt;p&gt;纷纷雨滴同享的一朵闲云；&lt;/p&gt;
&lt;p&gt;我的心儿要跳得同样迷乱，&lt;/p&gt;
&lt;p&gt;宫殿春叶般生，酒沫鱼样跃，&lt;/p&gt;
&lt;p&gt;让那个对饮的，也举落我的手。&lt;/p&gt;
&lt;p&gt;我的手扪脉，空亭吐纳云雾，&lt;/p&gt;
&lt;p&gt;我的梦正梦见另一个梦呢。&lt;/p&gt;
&lt;p&gt;枯木上的灵芝，水腰系上绢帛，&lt;/p&gt;
&lt;p&gt;西边的飞蛾探听夕照的虚实。&lt;/p&gt;
&lt;p&gt;它们刚辞别幽所，必定见过&lt;/p&gt;
&lt;p&gt;那个一直轻呼我名字的人，&lt;/p&gt;
&lt;p&gt;那个可能鸣翔，也可能开落，&lt;/p&gt;
&lt;p&gt;给人佩玉，又叫人狐疑的空址。&lt;/p&gt;
&lt;p&gt;她的践约可能是澌澌潮湿的。&lt;/p&gt;
&lt;p&gt;真奇怪，雨滴还未发落的前夕，&lt;/p&gt;
&lt;p&gt;我已感到了周身潮湿呢：&lt;/p&gt;
&lt;p&gt;青翠的竹子可以拧出水，&lt;/p&gt;
&lt;p&gt;山谷来的风吹入它们的内心，&lt;/p&gt;
&lt;p&gt;而我的耳朵似乎飞到了半空，&lt;/p&gt;
&lt;p&gt;或者是凝伫了而燃烧吧，燃烧那个&lt;/p&gt;
&lt;p&gt;一直戏睡在里面，那湫隘的人。&lt;/p&gt;
&lt;p&gt;还燃烧她的耳朵，烧成灰烟，&lt;/p&gt;
&lt;p&gt;决不叫她偷听我心的饥饿。&lt;/p&gt;
&lt;p&gt;你看，这醉我的世界含满了酒，&lt;/p&gt;
&lt;p&gt;竹子也含了晨曦和岁月。&lt;/p&gt;
&lt;p&gt;它们萧萧的声音多痛，多痛，&lt;/p&gt;
&lt;p&gt;愈痛我愈要剥它，剥成七孔，&lt;/p&gt;
&lt;p&gt;那么我的痛也是世界的痛。&lt;/p&gt;
&lt;p&gt;请你不要再聆听我了，莫名的人。&lt;/p&gt;
&lt;p&gt;我知道你在某处，隔风嬉戏。&lt;/p&gt;
&lt;p&gt;空白的梦中之梦，假的荷叶，&lt;/p&gt;
&lt;p&gt;令我彻夜难眠的住址。&lt;/p&gt;
&lt;p&gt;如果雨滴有你，火焰岂不是我？&lt;/p&gt;
&lt;p&gt;人神道殊，而殊途同归，&lt;/p&gt;
&lt;p&gt;我要，我要，爱上你神的热泪。&lt;/p&gt;
&lt;p&gt;1987.6.12 威茨堡大学&lt;/p&gt;
&lt;p&gt;《楚王梦雨》是诗人张枣写于1987年的诗作。整首诗脱胎于楚襄王梦神女的神话，以楚王的视角描述这空幻、诡谲的“梦中之梦”。纳博科夫言：“虽然读书时用的是脑，可真正领略艺术带来的欣悦的部位却在两块肩胛骨之间，可以相当肯定的说，那脊背的微微震颤是人类发展纯艺术、纯科学的过程中，所达到的最高情感宣泄的形式，让我们崇拜自己的脊椎和脊椎的兴奋吧”[1]。张枣的诗作似有异曲同工之妙：大量的留白，跳跃的思绪，场景、人称、时空的不停切换，共同搭建起一座“诗迷宫”，读罢只觉意犹未尽，情绪的起伏竟优先于文本的解读。即便如此，笔者仍试图在草蛇灰线中寻找更一个更清晰的脉络，遂写下对此诗的个人解读。&lt;/p&gt;
&lt;p&gt;“我要衔接过去一个人的梦/纷纷雨滴同享的一朵闲云”。对梦境的解答须追溯到战国时期的辞赋家宋玉的《高唐赋》《神女赋》。《高唐赋》通过宋玉和楚顷襄王的对话描绘了登临高唐观的美景：“惟高唐之大体兮，殊无物类之可仪比”。楚顷襄王和宋玉登上高唐观，极目远眺，只见高唐观上出现了奇异的云气。宋玉于是向楚王讲述了先王游猎高唐，梦遇神女，与其同寝的故事。神女“旦为朝云，暮为行雨”，变化无穷。《神女赋》写于宋玉的晚年，回忆了青年时和楚顷襄王的问答。楚顷襄王就寝时于神女相遇，感叹神女的美貌，却因未能与神女云雨而忧伤，“惆怅垂涕，求之至曙”。在这样的背景下，诗人将现代语言和古典诗意巧妙融合，延续了“化欧化古”的写作风格。&lt;/p&gt;
&lt;p&gt;诗歌的第一小节简要交代了背景，“过去一个人”指代的便是宋玉所说的“先王”。这一节的技巧值得玩味：“纷纷雨滴同享一朵闲云”，自然景观和楚顷襄王与先王的共享梦境，构成了巧妙的对仗。雨是神女的化身（“暮为行雨”），淋湿了整个梦境。神女若隐若现，楚顷襄王的对饮者是谁？亭子为什么是空的？神女的化身已经出现，真人却像隐在梦境之外，虚实相生。楚王的梦和先王的梦互相嵌套，虚幻的梦境便开始了。&lt;/p&gt;
&lt;p&gt;接下来的两节都在为神女的出现造势：“轻呼我名字的人”给出的空址，雨滴还未发落便周身潮湿的楚王。这里第一次出现了火的意象：楚王的耳朵飞到半空，凝伫而燃烧。这个情景非常超现实主义（毕竟是在梦里），让人想起《人类之子》里飞到半空的苹果。水火不容，火焰的出现制造了紧张与对立，一端是楚王，一端是神女。“我心的饥饿”可能代表着楚王两个层面的欲望。第一是对治理天下的欲望：《高唐赋》中写：“王将欲往见，必先斋戒。差时择日，简舆玄服。建云旆，蜺为旌，翠为盖。风起雨止，千里而逝。盖发蒙，往自会。思万方，忧国害。开贤圣，辅不逮。九窍通郁精神察，延年益寿千万岁。”君王与神女相会，可以启发蒙昧，成为圣明之君。第二是对个人的欲望：“褰余帷而请御兮，愿尽心之惓惓。怀贞亮之洁清兮，卒与我兮相难”；“情独私怀，谁者可语？惆怅垂涕，求之至曙”（《神女赋》）。欲望尚未得到满足，他的心便是饥饿的。这种饥饿随即转化为痛苦：我的痛便是世界的痛。这两节诗在情感上层层递进，从最初的期盼，到“心的饥饿”，再到痛苦，在超现实的场景中完成了转换。&lt;/p&gt;
&lt;p&gt;这种痛苦同样是诗人情感的折射。1986年，张枣赴德国读书。他用“孤悬”一词形容自己当时的处境：“我在海外是极端不幸福的，试想想孤悬在这儿有哪点好？”精神上他同样寂寞：“住在德国，生活是枯燥的，尤其到了冬末，静雪覆路，室内映着虚白的光，人会萌生‘红泥小火炉，……能饮一杯无？’的怀想。但就是没有对饮的那个人。……是的，在这个时代，连失眠都是枯燥的，因为没有令人心跳的愿景。……于是，趁着夜深人静，再独自闲饮。这时，内心一定很空惘，身子枯坐在一个角落里，只顾早点浸染上睡意，了却这一天。”[2]在《卡夫卡致菲丽斯》中，诗人发出血一般的呐喊：“夜啊，你总是还够不上夜/孤独，你总是还不够孤独！”这样的孤独让作者难以忍受，那个等待着对饮者现身，空虚痛苦的楚王，便是作者的情感投影。&lt;/p&gt;
&lt;p&gt;隔空嬉戏而不肯现身的神女，虚幻的梦中之梦，空等的楚王，整首诗的情绪在最后一节终于迎来悄无声息的隐退和爆发式的高潮。楚王得到了寻人不遇的答案：人神道殊。人和神的交流本就是偶然，注定走上不同的道路，如同雨滴和火焰本不能共存。对于这种必然的错过和遗憾，作者借楚王发出了自己的声音：“我要，我要，爱上你神的热泪”。整首诗的情绪在这里达到顶峰，并戛然而止，留下了爆炸般的回响。面对无可抵达的彼岸，仰望或许是合理而无奈的答案。楚王无法接近的神女，诗人无法离开的孤独，在文本内外巧妙应和，余音绕梁。&lt;/p&gt;</content:encoded><h:img src="/_astro/封面画作.BYp-Fwrt.png"/><enclosure url="/_astro/封面画作.BYp-Fwrt.png"/></item><item><title>Maven学习笔记</title><link>https://www.wht0909.top/blog/maven%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/maven%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</link><guid isPermaLink="true">https://www.wht0909.top/blog/maven%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/maven%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</guid><description>黑马程序员网课 Maven 学习笔记</description><pubDate>Wed, 03 Dec 2025 11:39:01 GMT</pubDate><content:encoded>&lt;h2&gt;1. Maven 简介&lt;/h2&gt;
&lt;p&gt;Maven 是一个用于创建和管理 Java 项目的工具，其功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;依赖管理&lt;/li&gt;
&lt;li&gt;项目构建&lt;/li&gt;
&lt;li&gt;统一项目结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.1 依赖管理&lt;/h3&gt;
&lt;p&gt;在 Maven 项目中，可通过修改 pom.xml 添加所需依赖，示例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;#x3C;dependencies&gt;
    &amp;#x3C;dependency&gt;
        &amp;#x3C;groupId&gt;commons-io&amp;#x3C;/groupId&gt; &amp;#x3C;!-- 依赖的组织/公司标识 --&gt;
        &amp;#x3C;artifactId&gt;commons-io&amp;#x3C;/artifactId&gt; &amp;#x3C;!-- 依赖的项目/库名称 --&gt;
        &amp;#x3C;version&gt;2.16.1&amp;#x3C;/version&gt; &amp;#x3C;!-- 依赖的版本号 --&gt;
    &amp;#x3C;/dependency&gt;
&amp;#x3C;/dependencies&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;POM 是项目对象模型（Project Object Model）的缩写。Maven 的仓库分为本地仓库、远程仓库（私服）和中央仓库（全球唯一，由 Maven 官方维护）。在 dependency 声明依赖后，系统会依次从本地仓库 —— 私服（如果有的话，一般是公司的服务器）—— 中央仓库搜索并下载。&lt;/p&gt;
&lt;h3&gt;1.2 项目构建&lt;/h3&gt;
&lt;p&gt;Maven 提供了跨平台的自动构建项目方法，在 IDEA 的 Lifecycle 中，可通过指令（如compile package）自动编译、打包&lt;/p&gt;
&lt;h3&gt;1.3 统一项目结构&lt;/h3&gt;
&lt;p&gt;在 IntelliJ IDEA 中可直接创建 Maven 项目，项目结构如下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Maven%E9%A1%B9%E7%9B%AE%E7%BB%93%E6%9E%84.png&quot; alt=&quot;Maven项目结构&quot;&gt;&lt;/p&gt;
&lt;p&gt;其中 src 为主程序文件夹，其下的 java 文件夹存放 java 源代码，resources 文件夹存放依赖；test 文件夹存放测试程序；target 文件夹存放编译、打包出来的文件或临时文件；pom.xml 为核心配置文件&lt;/p&gt;
&lt;h2&gt;2. Maven 坐标&lt;/h2&gt;
&lt;p&gt;Maven 坐标是资源（jar 包）的&lt;strong&gt;唯一标识&lt;/strong&gt;。下面是一个新创建的 Maven 项目的 pom.xml 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;&amp;#x3C;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&amp;#x3C;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot;
         xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
    &amp;#x3C;modelVersion&gt;4.0.0&amp;#x3C;/modelVersion&gt;

    // Maven 坐标
    &amp;#x3C;groupId&gt;com.itheima&amp;#x3C;/groupId&gt;
    &amp;#x3C;artifactId&gt;maven-project01&amp;#x3C;/artifactId&gt;
    &amp;#x3C;version&gt;1.0-SNAPSHOT&amp;#x3C;/version&gt;

    &amp;#x3C;properties&gt;
        &amp;#x3C;maven.compiler.source&gt;25&amp;#x3C;/maven.compiler.source&gt;
        &amp;#x3C;maven.compiler.target&gt;25&amp;#x3C;/maven.compiler.target&gt;
        &amp;#x3C;project.build.sourceEncoding&gt;UTF-8&amp;#x3C;/project.build.sourceEncoding&gt;
    &amp;#x3C;/properties&gt;

&amp;#x3C;/project&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;groupId：项目隶属的组织&lt;/li&gt;
&lt;li&gt;artifactId：模块名/项目名称&lt;/li&gt;
&lt;li&gt;version：版本号&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 verson 中，SNAPSHOT表示快照版本，即仍在开发中的版本；RELEASE表示正式发行的版本&lt;/p&gt;
&lt;h2&gt;3. Maven 依赖管理&lt;/h2&gt;
&lt;p&gt;引入依赖的方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在项目的 pom.xml 文件中引入 &lt;code&gt;&amp;#x3C;dependencies&gt;&lt;/code&gt; 标签&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;&amp;#x3C;dependencies&gt;&lt;/code&gt; 标签下引入 &lt;code&gt;&amp;#x3C;dependency&gt;&lt;/code&gt;，补充 Maven 坐标信息；如果不知道具体的组织名 / 模块名称，可去 Maven 的中央仓库 https://mvnrepository.com/ 查询&lt;/li&gt;
&lt;li&gt;点击刷新引入依赖&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;示例如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;dependencies&gt;
    &amp;#x3C;dependency&gt;
        &amp;#x3C;groupId&gt;org.springframework&amp;#x3C;/groupId&gt;
        &amp;#x3C;artifactId&gt;spring-context&amp;#x3C;/artifactId&gt;
        &amp;#x3C;version&gt;6.1.4&amp;#x3C;/version&gt;
    &amp;#x3C;/dependency&gt;
&amp;#x3C;/dependencies&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;排除依赖：在引入一些依赖时，所引入的依赖可能会带来一些本项目不需要的资源（jar 包），我们可以通过&lt;code&gt;&amp;#x3C;exclusions&gt;&lt;/code&gt;标签主动断开依赖&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;dependencies&gt;
    &amp;#x3C;dependency&gt;
        &amp;#x3C;groupId&gt;org.springframework&amp;#x3C;/groupId&gt;
        &amp;#x3C;artifactId&gt;spring-context&amp;#x3C;/artifactId&gt;
        &amp;#x3C;version&gt;6.1.4&amp;#x3C;/version&gt;

        {/* 排除依赖 */}
        &amp;#x3C;exclusions&gt;
            &amp;#x3C;exclusion&gt;
                &amp;#x3C;groupId&gt;io.micrometer&amp;#x3C;/groupId&gt;
                &amp;#x3C;artifactId&gt;micrometer-observation&amp;#x3C;/artifactId&gt;
            &amp;#x3C;/exclusion&gt;
        &amp;#x3C;/exclusions&gt;
    &amp;#x3C;/dependency&gt;
&amp;#x3C;/dependencies&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：依赖变更时，记得手动刷新&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;4. Maven 的生命周期&lt;/h2&gt;
&lt;p&gt;Maven 有三套相互独立的生命周期：clean, default, site&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clean: 清理上次编译、运行后的参与文件&lt;/li&gt;
&lt;li&gt;default: 核心工作，负责编译、打包、部署等&lt;/li&gt;
&lt;li&gt;site: 生成报告、发布站点等&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;五个重要的生命周期阶段：clean, compile, test, package, install&lt;/p&gt;
&lt;p&gt;其中 clean 属于 clean 周期，其他四个属于 default 周期&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clean: 移除上一次构建生成的文件&lt;/li&gt;
&lt;li&gt;compile: 编译项目源代码&lt;/li&gt;
&lt;li&gt;test: 使用合适的单元测试框架运行测试（JUnit）&lt;/li&gt;
&lt;li&gt;package: 将编译后的文件打包（jar, war）&lt;/li&gt;
&lt;li&gt;install: 将项目安装到本地仓库&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：在&lt;strong&gt;同一套生命周期&lt;/strong&gt;中，后面的操作执行时，一定执行前面的操作（如执行 install 时，compile一定执行）&lt;/p&gt;
&lt;h2&gt;5. Maven 单元测试&lt;/h2&gt;
&lt;p&gt;JUnit: Java 测试框架&lt;/p&gt;
&lt;p&gt;单元测试的三步操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 pom.xml 中，引入 JUnit 依赖&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;dependency&gt;
    &amp;#x3C;groupId&gt;org.junit.jupiter&amp;#x3C;/groupId&gt;
    &amp;#x3C;artifactId&gt;junit-jupiter&amp;#x3C;/artifactId&gt;
    &amp;#x3C;version&gt;5.9.1&amp;#x3C;/version&gt;
    &amp;#x3C;scope&gt;test&amp;#x3C;/scope&gt;
&amp;#x3C;/dependency&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;在 test/java 目录下，创建测试类，并编写对应的测试方法，并在方法上声明 @Test 注解&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;package com.itheima;

import org.junit.jupiter.api.Test;

public class UserServiceTest {

    @Test
    public void testGetAge(){
        UserService userService = new UserService();
        Integer age = userService.getAge(&quot;211231200409090198&quot;);
        System.out.println(age);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;运行单元测试：绿色通过，红色未通过&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;注意：测试类的命名规范为：XxxxTest，测试方法的命名规定为：&lt;code&gt;public void xxx(){...}&lt;/code&gt;，&lt;strong&gt;返回值必须为 void 类型&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;5.1 JUnit 断言&lt;/h3&gt;
&lt;p&gt;断言：判断程序输出的结果是否符合预期&lt;/p&gt;
&lt;p&gt;常用的断言方法如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95_%E6%96%AD%E8%A8%80.png&quot; alt=&quot;断言&quot;&gt;&lt;/p&gt;
&lt;p&gt;其中 exp 是预期的输出结果，act 是实际的输出结果，msg 是报错的提示信息&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@Test
public void getGenderTestWithAssert(){
    UserService userService = new UserService();
    String gender = userService.getGender(&quot;211231200409090215&quot;);
    Assertions.assertEquals(gender, &quot;男&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;assertThrows&lt;/code&gt;用来判断程序抛出的异常和期望的异常是否一致，expType 是程序预期会抛出的异常，exec 是一个函数式接口，用于调用我们自己的方法，示例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@Test
public void getGenderTestWithAssert2(){
    UserService userService = new UserService();
    Assertions.assertThrows(IllegalArgumentException.class, () -&gt; {
        userService.getGender(null);
    });
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5.2 JUnit 常见注解&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./JUnit%E5%B8%B8%E8%A7%81%E6%B3%A8%E8%A7%A3.png&quot; alt=&quot;JUnit常见注解&quot;&gt;&lt;/p&gt;
&lt;p&gt;注意：BeforeAll 和 AfterAll 只能用于静态（static）方法&lt;/p&gt;
&lt;p&gt;ParameterizedTest 和 ValueSource 的使用方法如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Java&quot;&gt;@DisplayName(&quot;测试用户性别&quot;) // 用于给测试类起别名
@ParameterizedTest
@ValueSource(strings = {&quot;211231200409090013&quot;, &quot;211231200409090053&quot;, &quot;211231200409090033&quot;})
public void getGenderTest2(String idCard){
    UserService userService = new UserService();
    String gender = userService.getGender(idCard);
    Assertions.assertEquals(&quot;男&quot;, gender, &quot;性别错误&quot;);
}
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/maven.CMq7xpjk.png"/><enclosure url="/_astro/maven.CMq7xpjk.png"/></item><item><title>Ajax学习笔记</title><link>https://www.wht0909.top/blog/ajax%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ajax%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</link><guid isPermaLink="true">https://www.wht0909.top/blog/ajax%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/ajax%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0</guid><description>黑马程序员网课 Ajax 学习笔记</description><pubDate>Tue, 02 Dec 2025 17:17:09 GMT</pubDate><content:encoded>&lt;h2&gt;1. Ajax 简介&lt;/h2&gt;
&lt;p&gt;Ajax 是一种&lt;strong&gt;异步&lt;/strong&gt;通信方式，能够与服务器端交换数据&lt;/p&gt;
&lt;p&gt;注：异步是指服务器端在处理浏览器发来的请求的过程中，浏览器页面可以继续做其他的操作；同步则是指服务器在处理请求时，浏览器只能等待服务器处理结束并返还数据后，才能继续做其他的操作&lt;/p&gt;
&lt;p&gt;Axios 是一种简单的发送 Ajax 请求的技术&lt;/p&gt;
&lt;p&gt;引入 Axios 的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;srcipt src=&quot;https://unpkg.com/axios/dist/axios.min.js&quot;&gt;&amp;#x3C;/srcipt&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下面是一段通过 Axios 向服务器端发送 GET / POST请求的代码：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;// GET
document.querySelector(&quot;#btn1&quot;).addEventListener(&apos;click&apos;, () =&gt; {
    axios({
        url: &apos;https://mock.apifox.cn/m1/3083103-0-default/emps/update&apos;, // 发送到服务器端的地址
        method: &quot;GET&quot; // 使用 GET 方法
    }).then((result) =&gt; {
        console.log(result) // 成功回调函数
    }).catch((err) =&gt; {
        console.log(err) // 失败回调函数
    })
})

// POST
document.querySelector(&quot;#btn1&quot;).addEventListener(&apos;click&apos;, () =&gt; {
    axios({
        url: &apos;https://mock.apifox.cn/m1/3083103-0-default/emps/update&apos;, // 发送到服务器端的地址
        method: &quot;POST&quot; // 使用 GET 方法
        data: &apos;id=1&apos; //请求体中携带的数据
    }).then((result) =&gt; {
        console.log(result) // 成功回调函数
    }).catch((err) =&gt; {
        console.log(err) // 失败回调函数
    })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里使用了 JS 中处理 click 交互的方法&lt;code&gt;addEventListener&lt;/code&gt;，其中&lt;code&gt;click&lt;/code&gt;表示用户的操作&lt;/p&gt;
&lt;p&gt;成功回调函数：请求成功则执行；失败回调函数：请求失败时执行&lt;/p&gt;
&lt;p&gt;axios 部分还可以简写如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;// GET
axios.get(&apos;https://mock.apifox.cn/m1/3083103-0-default/emps/update&apos;).then((result) =&gt; {
    console.log(result)
}).catch((err) =&gt; {
    console.log(err)
})

// POST
axios.post(&apos;https://mock.apifox.cn/m1/3083103-0-default/emps/update&apos;, &apos;id=1&apos;).then((result) =&gt; {
    console.log(result)
}).catch((err) =&gt; {
    console.log(err)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注：&lt;code&gt;then().catch()&lt;/code&gt; 部分可简写为&lt;code&gt;thenc&lt;/code&gt;，Vscode会自动弹出&lt;/p&gt;
&lt;h2&gt;2. Ajax 请求案例&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;&amp;#x3C;script type=&quot;module&quot;&gt;
    import {createApp} from &apos;https://unpkg.com/vue@3/dist/vue.esm-browser.js&apos;
    createApp({
        data(){
            return{
                searchForm: {
                    name: &apos;&apos;,
                    gender: &apos;&apos;,
                    job: &apos;&apos;
                },
                empList: []
            }
        },
        methods: {
            search(){
                axios.get(`https://web-server.itheima.net/emps/list?name=${this.searchForm.name}&amp;#x26;gender=${this.searchForm.gender}&amp;#x26;job=${this.searchForm.job}`).then((result) =&gt; {
                    this.empList = result.data.data
                }).catch((err) =&gt; {
                    alert(&quot;GET请求失败&quot;)
                });
            },
            clear(){
                this.searchForm = {
                    name: &apos;&apos;,
                    gender: &apos;&apos;,
                    job: &apos;&apos;
                }
                this.search()
            }
        }
    }).mount(&quot;#container&quot;)
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上段代码中，当前 Vue 实例的 search 方法和“查询”按钮相互绑定。当用户点击查询按钮时，search 方法被调用，向服务器发送请求。请求的内容为模板表达式中的内容，包含了 name, gender, job 等查询字段&lt;/p&gt;
&lt;p&gt;值得注意的是&lt;code&gt;this.empList = result.data.data&lt;/code&gt;，result 是服务器返回的对象，包含了 data, status, headers, statusText 等属性，其中result 和 result.data 内容如下：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Vue-Ajax-result.png&quot; alt=&quot;图1. result的内容&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./Vue-Ajax-result.data.png&quot; alt=&quot;图2. result.data的内容&quot;&gt;&lt;/p&gt;
&lt;p&gt;而我们要获取的是其中的 data 字段，因此将当前实例下的 empList 赋值为 result.data.data&lt;/p&gt;
&lt;h2&gt;3. 异步变同步：async &amp;#x26; await&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;aysnc&lt;/code&gt;和&lt;code&gt;await&lt;/code&gt;能够让异步操作变为同步操作，让代码从上到下按顺序执行，增强代码的可维护性&lt;/p&gt;
&lt;p&gt;&lt;code&gt;async&lt;/code&gt;用来声明异步操作，&lt;code&gt;await&lt;/code&gt;用于让异步操作等待服务器端返回数据&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;createApp({
    data(){...},
    methods: {
        async search(){
            let result = await axios.get(&apos;target_url&apos;) // 无需成功回调函数
            this.empList = result.data.data
        }
    }
}).mount(&quot;#container&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Vue 的生命周期/钩子方法&lt;/h2&gt;
&lt;p&gt;Vue 有八个生命周期，如下表所示&lt;/p&gt;
&lt;p&gt;每触发一个生命周期时间，系统会自动执行对应的钩子方法，需要重点关注的是&lt;code&gt;mounted&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mounted&lt;/code&gt;钩子方法在挂载完成后触发，可通过下面的代码在一开始就展示所有的数据：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-JavaScript&quot;&gt;createApp({
    data(){...},
    methods: {
        async search(){
            let res = awit axios.get(&apos;target_url&apos;)
            this.empList = res.data.data
        }
    },
    mounted(){
        this.search()
    }
}).mount(&quot;#container&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;code&gt;mounted&lt;/code&gt;方法要和&lt;code&gt;data&lt;/code&gt;，&lt;code&gt;methods&lt;/code&gt;在同一级别&lt;/p&gt;</content:encoded><h:img src="/_astro/Vue-Ajax-result.zHZt2jr4.png"/><enclosure url="/_astro/Vue-Ajax-result.zHZt2jr4.png"/></item><item><title>Vue学习笔记-1</title><link>https://www.wht0909.top/blog/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-1/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-1</link><guid isPermaLink="true">https://www.wht0909.top/blog/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-1/vue%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-1</guid><description>黑马程序员网课 Vue 学习笔记</description><pubDate>Tue, 02 Dec 2025 11:51:33 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;h2&gt;1. 定义应用实例&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;body&gt;
    &amp;#x3C;div id=&quot;app&quot;&gt;
        &amp;#x3C;h1&gt;{{message}}&amp;#x3C;/h1&gt; // 插值表达式，访问数据对象中的变量
    &amp;#x3C;/div&gt;
    &amp;#x3C;script type=&quot;module&quot;&gt; // module表示模块化 JS 脚本，支持 import / export，这是能引入 Vue 库的前提
        import {createApp} from &apos;https://unpkg.com/vue@3/dist/vue.esm-browser.js&apos; // 导入核心方法，详见 Vue 官方文档 https://cn.vuejs.org/guide/quick-start.html

        // 创建应用实例
        createApp({
            data(){ // 定义数据对象
                return {
                    message: &quot;Hello Vue&quot;
                };
            }
        }).mount(&quot;#app&quot;) // 将 Vue 应用实例挂载到指定的 DOM 元素上
    &amp;#x3C;/script&gt;
&amp;#x3C;/body&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;createApp 创建了一个应用实例，并通过 mount 方法将该实例挂在到 id=&quot;app&quot; 的 DOM 元素上，在上面的代码中即为 &lt;code&gt;&amp;#x3C;div id=&quot;app&quot;&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;挂载后，#app 元素内部的内容会被 Vue 模板替换（如果有原始内容会被覆盖）&lt;/p&gt;
&lt;h2&gt;2. Vue 常用指令&lt;/h2&gt;
&lt;h3&gt;2.1 v-for&lt;/h3&gt;
&lt;p&gt;用途：循环渲染，要渲染哪个标签就把 v-for 写在该标签里&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;table&gt;
    &amp;#x3C;thead&gt;
        ...
    &amp;#x3C;/thead&gt;
    &amp;#x3C;tbody&gt;
        &amp;#x3C;tr v-for=&quot;(e, index) in empList&quot; :key=&quot;e.id&quot;&gt;
            &amp;#x3C;td&gt;{{index + 1}}&amp;#x3C;/td&gt;
            &amp;#x3C;td&gt;{{e.name}}&amp;#x3C;/td&gt;
            &amp;#x3C;td&gt;{{e.gender == 1 ? &apos;男&apos; : &apos;女&apos;}}&amp;#x3C;/td&gt;
            &amp;#x3C;td&gt;{{e.job}}&amp;#x3C;/td&gt;
            &amp;#x3C;td&gt;{{e.entrydate}}&amp;#x3C;/td&gt;
            &amp;#x3C;td&gt;{{e.updatetime}}&amp;#x3C;/td&gt;
            &amp;#x3C;td class=&quot;action-buttons&quot;&gt;
                &amp;#x3C;button type=&quot;button&quot;&gt;编辑&amp;#x3C;/button&gt;
                &amp;#x3C;button type=&quot;button&quot;&gt;删除&amp;#x3C;/button&gt;
            &amp;#x3C;/td&gt;
        &amp;#x3C;/tr&gt;
    &amp;#x3C;/tbody&gt;
&amp;#x3C;/table&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;e 是列表 empList 里的元素，empList 定义如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;createApp({
    data(){
        return{
            empList: [ // 这里仿照了 JSON 文件的写法，模拟浏览器的数据传输
                {
                    &quot;id&quot;: 1,
                    &quot;name&quot;: &quot;谢逊&quot;,
                    &quot;gender&quot;: 1,
                    &quot;job&quot;: 1,
                    &quot;entrydate&quot;: &quot;2023-06-09&quot;,
                    &quot;updatetime&quot;: &quot;2025-12-01T14:59:38&quot;
                },
                {
                    &quot;id&quot;: 2,
                    &quot;name&quot;: &quot;谢逊&quot;,
                    &quot;gender&quot;: 1,
                    &quot;job&quot;: 1,
                    &quot;entrydate&quot;: &quot;2023-06-09&quot;,
                    &quot;updatetime&quot;: &quot;2025-12-01T14:59:38&quot;
                },
                ...
            ]
        }
    }
}).mount(&quot;#container&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果数据对象是数组，定义方式为：&lt;code&gt;对象名: [{..}, {..}]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:key 一般使用 id 作为唯一的键&lt;/p&gt;
&lt;h3&gt;2.2 v-bind&lt;/h3&gt;
&lt;p&gt;用途：用于动态绑定资源&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;td&gt;&amp;#x3C;img v-bind:src=&quot;e.image&quot; :alt=&quot;e.name&quot; style=&quot;width: 150px;&quot;&gt;&amp;#x3C;/td&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;v-bind:src&lt;/code&gt; 也可简写为 &lt;code&gt;:src&lt;/code&gt;，在上述代码中用于动态绑定 image 资源（&lt;code&gt;:alt&lt;/code&gt; 同理）&lt;/p&gt;
&lt;h3&gt;2.3 v-if v-show&lt;/h3&gt;
&lt;p&gt;用途：都用于控制页面上元素显示与否；用&lt;code&gt;&amp;#x3C;span&gt;&lt;/code&gt;标签搭配控制元素&lt;/p&gt;
&lt;p&gt;区别：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;v-if&lt;/code&gt; 只有条件为 true 才会渲染节点，&lt;code&gt;v-show&lt;/code&gt;无论条件真假都会渲染节点，但会通过 css 样式控制是否显示该元素&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;v-show&lt;/code&gt; 适用于频繁切换显示隐藏的场景&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;td&gt;
    &amp;#x3C;!-- v-if 控制--&gt;
    &amp;#x3C;span v-if=&quot;e.job == 1&quot;&gt;班主任&amp;#x3C;/span&gt;
    &amp;#x3C;span v-else-if=&quot;e.job == 2&quot;&gt;讲师&amp;#x3C;/span&gt;
    &amp;#x3C;span v-else-if=&quot;e.job == 3&quot;&gt;学工主管&amp;#x3C;/span&gt;
    &amp;#x3C;span v-else-if=&quot;e.job == 4&quot;&gt;教研主管&amp;#x3C;/span&gt;
    &amp;#x3C;span v-else-if=&quot;e.job == 5&quot;&gt;咨询师&amp;#x3C;/span&gt;
    &amp;#x3C;span v-else&gt;其他&amp;#x3C;/span&gt;

    &amp;#x3C;!-- v-show 控制 --&gt;
    &amp;#x3C;span v-show=&quot;e.job == 1&quot;&gt;班主任&amp;#x3C;/span&gt;
    &amp;#x3C;span v-show=&quot;e.job == 2&quot;&gt;讲师&amp;#x3C;/span&gt;
    &amp;#x3C;span v-show=&quot;e.job == 3&quot;&gt;学工主管&amp;#x3C;/span&gt;
    &amp;#x3C;span v-show=&quot;e.job == 4&quot;&gt;教研主管&amp;#x3C;/span&gt;
    &amp;#x3C;span v-show=&quot;e.job == 5&quot;&gt;咨询师&amp;#x3C;/span&gt;
&amp;#x3C;/td&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 v-model&lt;/h3&gt;
&lt;p&gt;用途：双向绑定表单数据；在绑定前需要先在 data 里创建数据对象&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;form action=&quot;/search&quot; method=&quot;post&quot; class=&quot;search-form&quot;&gt;
    &amp;#x3C;label for=&quot;name&quot;&gt;姓名&amp;#x3C;/label&gt;
    &amp;#x3C;input type=&quot;text&quot; id=&quot;name&quot; v-model=&quot;searchForm.name&quot; placeholder=&quot;请输入姓名&quot;&gt; &amp;#x3C;!-- 和 searchFrom对象中的 name 成员双向绑定 --&gt;
&amp;#x3C;/form&gt;

createApp({
    data(){
        return {
            searchForm: {
                name: &apos;&apos;,
                gender: &apos;&apos;,
                job: &apos;&apos;
            }
        }
    }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.5 v-on&lt;/h3&gt;
&lt;p&gt;用途：绑定事件；语法为&lt;code&gt;v-on:事件=&quot;方法&quot;&lt;/code&gt;，也可以简写为&lt;code&gt;@事件=&quot;方法&quot;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;div class=&quot;button-group&quot;&gt;
    &amp;#x3C;button type=&quot;button&quot; v-on:click=&quot;search&quot;&gt;提交&amp;#x3C;/button&gt;
    &amp;#x3C;button type=&quot;reset&quot; @click=&quot;clear&quot;&gt;清除&amp;#x3C;/button&gt;
&amp;#x3C;/div&gt;

createApp({
    data(){
        ...
    }
    methods: {
        search(){
            console.log(this.searchForm) //这里必须用 this 指向当前实例的 data 对象
        }
        clear(){
            this.searchForm = {
                name: &apos;&apos;,
                gender: &apos;&apos;,
                job: &apos;&apos;
            }
        }
    }
})
&lt;/code&gt;&lt;/pre&gt;</content:encoded><h:img src="/_astro/vue.BZf-sKl9.png"/><enclosure url="/_astro/vue.BZf-sKl9.png"/></item><item><title>诗与歌——以《镜中》为例浅谈诗歌的“语气”</title><link>https://www.wht0909.top/blog/%E8%AF%97%E4%B8%8E%E6%AD%8C%E4%BB%A5%E9%95%9C%E4%B8%AD%E4%B8%BA%E4%BE%8B%E6%B5%85%E8%B0%88%E8%AF%97%E6%AD%8C%E7%9A%84%E8%AF%AD%E6%B0%94/%E8%AF%97%E4%B8%8E%E6%AD%8C%E4%BB%A5%E9%95%9C%E4%B8%AD%E4%B8%BA%E4%BE%8B%E6%B5%85%E8%B0%88%E8%AF%97%E6%AD%8C%E7%9A%84%E8%AF%AD%E6%B0%94</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E8%AF%97%E4%B8%8E%E6%AD%8C%E4%BB%A5%E9%95%9C%E4%B8%AD%E4%B8%BA%E4%BE%8B%E6%B5%85%E8%B0%88%E8%AF%97%E6%AD%8C%E7%9A%84%E8%AF%AD%E6%B0%94/%E8%AF%97%E4%B8%8E%E6%AD%8C%E4%BB%A5%E9%95%9C%E4%B8%AD%E4%B8%BA%E4%BE%8B%E6%B5%85%E8%B0%88%E8%AF%97%E6%AD%8C%E7%9A%84%E8%AF%AD%E6%B0%94</guid><description>分享一首张枣的诗歌</description><pubDate>Mon, 01 Dec 2025 17:54:14 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;封面摄影作品：Masao Yamamoto(&lt;a href=&quot;https://www.zhiweiye.com/shan-ben-chang-nan&quot;&gt;https://www.zhiweiye.com/shan-ben-chang-nan&lt;/a&gt;)

&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;摘要&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本文从新诗的发展开始，阐述了新月派“音乐性”的提出以及对中国新诗的影响，并进一步解释了何为诗歌的“语气”，最后以张枣《镜中》为例进行了简要的分析&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键词&lt;/strong&gt;：新诗、新月派、音乐性、语气

1917年2月，《新青年》刊登出胡适的《白话诗八首》，标志着中国新诗的诞生。在古典诗歌的创作逐渐走向僵化，意象逐渐腐朽，腔调逐渐统一的近代，新诗的到来无疑为中国诗坛注入了巨大的能量。新诗在形式上打破了字数和句式的限制，冲出了韵脚的束缚，在内容上显示其现代性，着重于书写人的内心感受，生活状态以及社会现状等，极大地丰富了可供选择的题材。与此同时，新诗的流派百花齐放，其中湖畔派，新月派，象征派等对后续的诗歌创作产生了导向性的影响。以新月派为例，他们不满于五四以后自由诗人忽视诗艺的作风，提倡新格律诗，主张“理性节制情感”的美学原则与诗歌形式的格律化，为了实现这一理论原则，以闻一多为代表的新月派诗人在诗歌艺术上做了有益的尝试。闻一多在著作《诗的格律》中提出了著名的“三美”主张，即“音乐美”“绘画美”“建筑美”。本文将从诗歌的“音乐美”着手，探讨现代诗歌中的韵律，节奏等问题。&lt;/p&gt;
&lt;p&gt;包括语言的声、韵、调等文字乐感元素，语言的气势、顿歇、色彩、神韵等情绪内涵，词语的选择、语言的组织，语义的关联、声音的修辞等声音的表现技术，新诗的声音是现代汉语综合作用于人们感官的有意味的诗性艺术。传统诗歌的可歌性应该是诗歌的一个基本属性，后来歌与诗开始分离，可歌之诗谓之歌诗(乐歌)，不歌之诗称为诵诗(徒诗)，二者互为影响，诗与歌既分离，又结合，诗始终没有脱离可歌性。传统诗歌的歌性特征在新诗变革中发生了现代性转变[1]。新诗的变革尝试将诗歌从韵律中解放出来:“诗自诗，而歌自歌，歌如歌谣，乐府词曲，或为情感的言语之复写，或不能离乐谱而独立，都是可以唱的;而诗则不必然[2]。郭沫若主张“诗”“歌”分离，将诗从歌的枷锁中解脱出来。自由地书写是白话诗人从审美和精神两个层面上的主张，虽然要在一定程度上放弃形式上的美，但书写及表达的自由又会在精神的维度上重新赋予其美感。诗其所诗，歌其所歌，“从心所欲，不逾矩”地表达是现代诗人普遍追求的境界。&lt;/p&gt;
&lt;p&gt;但并非甩脱了韵脚，现代诗就会变得自由。韵脚，求其根源是诗歌“语气”的一种存在形式。这里所说的诗歌的“语气”可以理解为一种内在的，属于诗歌本身的声音。从这个角度看，与其说是诗人创造了诗歌，不如说是诗歌在引领着诗人，通过诗人的喉咙发声，按着诗人的笔书写，而诗人只是诗歌表达的媒介。&lt;/p&gt;
&lt;p&gt;诗的语气，不仅取决于文字的节奏，声带的震动，还取决于“诗意”。诗意是诗人用语言和情感所形成的意味、意涵、意境。人们通常更愿意理解为是优美的语言和动人的情感[3]。这些情感的表达又受制于写诗的技艺和使用的意象。以张枣的《镜中》为例：&lt;/p&gt;
&lt;p&gt;只要想起一生中后悔的事&lt;/p&gt;
&lt;p&gt;梅花便落了下来&lt;/p&gt;
&lt;p&gt;比如看她游泳到河的另一岸&lt;/p&gt;
&lt;p&gt;比如登上一株松木梯子&lt;/p&gt;
&lt;p&gt;危险的事固然美丽&lt;/p&gt;
&lt;p&gt;不如看她骑马归来&lt;/p&gt;
&lt;p&gt;面颊温暖&lt;/p&gt;
&lt;p&gt;羞惭。低下头，回答着皇帝&lt;/p&gt;
&lt;p&gt;一面镜子永远等候她&lt;/p&gt;
&lt;p&gt;让她坐到镜中常坐的地方&lt;/p&gt;
&lt;p&gt;望着窗外，只要想起一生中后悔的事&lt;/p&gt;
&lt;p&gt;梅花便落满了南山&lt;/p&gt;
&lt;p&gt;这首诗很短，是张枣的代表作品之一，也是张枣“化欧化古”的杰作。梅花、河、松木梯子、骑马的女子、皇帝、镜子、南山等意象流淌着古意，只需单看这些意象，也好像立刻进入了一个桃花源般的世界。如果只看每行最后的音节，就会发现这首诗似乎并没有明确的韵脚，但是如果以诵读的方式读这首诗，又会发现其中似乎隐藏着音乐性，并不会觉得拗口，每小行的分段恰到好处。以“面颊温暖”为例，温暖、羞惭本身就是对少女面颊的形容词，而作者在这首诗中将两个形容词拆开，放入了不同的位置。这种刻意地分离使诗歌又产生了新的意趣：“羞惭”是“低下头”回答着皇帝的原因，并且羞惭使“低下头”“回答”这个动作变得更连贯，也就是说，使诗歌的“语气”延续，贯通了下来；又比如说“一面镜子永远等候她/让她坐到镜中常坐的地方/望着窗外，只要想起一生中后悔的事”。“坐”和“望”是同时进行的两个动作，作者将这两个动作分别放入不同的行中，“望着窗外”后直接衔接想起的内容，如果只看这首诗的最后两句“望着窗外，只要想起一生中后悔的事/梅花便落满了南山”，就会发现这也是一个完整的动作，因此可以认为：镜中只是这件事发生的地点，这在很大程度上保证了“望”和“想”的独立性。诗题名为《镜中》，这给了读者一个潜在的导向，要求读者思考“镜子”这一意象的含义。而上述的分段又很好地平衡了人物与意象，动与静，活跃与沉默的统一，诗意便在叙述中荡漾。值得一提的是，这首诗同样被音乐人（如老狼，钟立风，程璧）谱曲形成了风格各异的音乐作品，在另一个维度上印证了诗歌的音律性。&lt;/p&gt;
&lt;p&gt;在文章的最后感谢中华文化（文学篇）的期中作业让我能静下心来，以文字的方式梳理脑海中琐碎的片段，并简单地赏析一首自己很喜欢的诗歌。让我能有一个恰当的理由沉入诗歌的世界中，短暂地逃避繁重的课业。文学是一阵自由的风，风过无痕，但带来了清凉的一瞬。诗意地栖居对每一个现代人来说都是必要的，当进入到一首诗的世界中时，无论作何解读，只要能有所触动，就已经拥抱了诗意，读诗的人便成为了诗歌的一部分，诗歌也因阅读变得完整。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;参考文献&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[1] 王泽龙.新诗声音的变革与重构[J].华中师范大学学报(人文社会科学版)，2023，62(05)&lt;/li&gt;
&lt;li&gt;[2] 郭沫若:《论诗》，见郭沫若著、黄淳浩校:《文艺论集》(汇校本)，长沙:湖南人民出版社，1984年，第253页&lt;/li&gt;
&lt;li&gt;[3] 沈浩波：什么是诗——从诗意到诗性，从意象到叙述&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/封面作品.ChNn5Ezd.png"/><enclosure url="/_astro/封面作品.ChNn5Ezd.png"/></item><item><title>香橙派学习：镜像烧录 &amp; 启动香橙派</title><link>https://www.wht0909.top/blog/%E9%A6%99%E6%A9%99%E6%B4%BE%E5%AD%A6%E4%B9%A0%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95--%E5%90%AF%E5%8A%A8%E9%A6%99%E6%A9%99%E6%B4%BE/%E9%A6%99%E6%A9%99%E6%B4%BE%E5%AD%A6%E4%B9%A0%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95--%E5%90%AF%E5%8A%A8%E9%A6%99%E6%A9%99%E6%B4%BE</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E9%A6%99%E6%A9%99%E6%B4%BE%E5%AD%A6%E4%B9%A0%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95--%E5%90%AF%E5%8A%A8%E9%A6%99%E6%A9%99%E6%B4%BE/%E9%A6%99%E6%A9%99%E6%B4%BE%E5%AD%A6%E4%B9%A0%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95--%E5%90%AF%E5%8A%A8%E9%A6%99%E6%A9%99%E6%B4%BE</guid><description>学习一下香橙派，还挺有意思的</description><pubDate>Sun, 27 Jul 2025 11:44:41 GMT</pubDate><content:encoded>&lt;p&gt;本文参考了以下文章：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hetong-re4per.com/posts/documenting-an-opizero3-use/&quot;&gt;褐瞳さん 记录一下香橙派 Zero3 的使用&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1yM4m167SK/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;如何低成本搭建一个 docker 轻服务器 随时随地访问小雅影音库 OrangePi Zero3 一键快速部署指南&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1pu4m1u71o?spm_id_from=333.788.videopod.sections&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;香橙派入门教程(一)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1aq421w7x1?spm_id_from=333.788.videopod.sections&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;香橙派入门教程（二）香橙派启动&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/itt21044ZCY/article/details/132742245&quot;&gt;香橙派连接电脑，串口与SSH&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.csdn.net/lsfeitianzhuzhuxia/article/details/134114635&quot;&gt;香橙派OrangePi Zero开发板的WiFi连接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这篇文章记录了作者从购买开发板、镜像烧录、启动开发板的过程，作者之前完全没有接触过嵌入式，希望本文能对和我一样的小白有所帮助&lt;/p&gt;
&lt;h2&gt;1. 准备材料&lt;/h2&gt;
&lt;p&gt;材料清单如下，均在某宝有售：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OrangePi Zero3 主板&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./OrangePiZero3%E4%B8%BB%E6%9D%BF.png&quot; alt=&quot;OrangePi Zero3 主板&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;5V 3A 电源（我的荣耀手机充电线不能给开发板正常供电，所以作者最终使用的是充电宝自带的 type-c 充电线）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sd 卡以及读卡器（我使用的是闪迪的 sd 卡，32G）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./sd%E5%8D%A1.png&quot; alt=&quot;sd 卡&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CH340G USB 转 TTL 模块&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./CH340GUSB%E8%BD%ACTTL%E6%A8%A1%E5%9D%97.png&quot; alt=&quot;CH340G USB 转 TTL 模块&quot;&gt;&lt;/p&gt;
&lt;h2&gt;2. 开始烧录&lt;/h2&gt;
&lt;p&gt;下载 &lt;a href=&quot;https://etcher.balena.io/#download-etcher&quot;&gt;balenaEtcher&lt;/a&gt; 烧录软件，选择适合自己电脑的版本进行安装&lt;/p&gt;
&lt;p&gt;安装好后，将 sd 卡插入读卡器，读卡器插入电脑的 USB 接口，打开软件进行烧录&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%83%A7%E5%BD%95.png&quot; alt=&quot;烧录&quot;&gt;&lt;/p&gt;
&lt;p&gt;等待烧录完成后，将 sd 卡插入开发板，卡槽在板子背面&lt;/p&gt;
&lt;h2&gt;3. 启动香橙派&lt;/h2&gt;
&lt;p&gt;作者采用串口连接的方式：先为香橙派开发板接通电源，待连接正常后，板载红色 LED 会先点亮，几秒后灯光转为绿色并持续闪烁，如下图所示&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E4%B8%BA%E5%BC%80%E5%8F%91%E6%9D%BF%E4%BE%9B%E7%94%B5.png&quot; alt=&quot;为开发板供电&quot;&gt;&lt;/p&gt;
&lt;p&gt;将 CH340 模块连接到开发板上，具体的连接方式如下图（图片来源：&lt;a href=&quot;https://www.hetong-re4per.com/posts/documenting-an-opizero3-use/&quot;&gt;记录一下香橙派 Zero3 的使用&lt;/a&gt;）&lt;/p&gt;
&lt;p&gt;要注意各种颜色线之间的连接关系，详情可查看卖家提供的引脚图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%BC%95%E8%84%9A%E5%9B%BE.png&quot; alt=&quot;引脚图&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./CH340%E5%92%8COrangePi%E7%9A%84%E8%BF%9E%E6%8E%A5%E6%96%B9%E6%B3%95.png&quot; alt=&quot;CH340 和 Orange Pi 的连接方法&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开设备管理器，进入 “端口（COM 和 LPT）” 选项，找到名称含 “CH340” 的串口，从下图可看到对应设备为 COM5&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9F%A5%E7%9C%8BCH340%E7%AB%AF%E5%8F%A3.png&quot; alt=&quot;查看 CH340 端口&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开 MobaXterm ，依次点击 Session ， Serial ， Serial Port 选择刚才查看到的 COM 5，比特率设置为 115200 ，全部选择完毕后点击 OK，即可进入&lt;/p&gt;
&lt;h2&gt;4. 连接 Wifi&lt;/h2&gt;
&lt;p&gt;接下来我们需要给香橙派连接 WiFi ，这部分参考了&lt;a href=&quot;https://blog.csdn.net/lsfeitianzhuzhuxia/article/details/134114635&quot;&gt;香橙派OrangePi Zero开发板的WiFi连接&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;首先，使用&lt;code&gt;nmcli dex wifi&lt;/code&gt;查找周围热点，找到目标热点后，按&lt;code&gt;q&lt;/code&gt;退出&lt;/p&gt;
&lt;p&gt;我的目标热点为&lt;code&gt;Travelsky&lt;/code&gt;，见下图：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9F%A5%E6%89%BE%E5%91%A8%E5%9B%B4%E7%83%AD%E7%82%B9.png&quot; alt=&quot;查找周围热点&quot;&gt;&lt;/p&gt;
&lt;p&gt;使用下面的指令连接 WiFi，其中&lt;code&gt;wifi_name&lt;/code&gt;用目标热点的名称代替，&lt;code&gt;wifi_passwd&lt;/code&gt;用密码代替&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nmcli dev wifi connect wifi_name password wifi_passwd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&lt;code&gt;ifconfig&lt;/code&gt;确认连接状态，在 wlan0 处可以看到 Orang Pi 的 IP 地址为 192.168.0.106&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%A1%AE%E8%AE%A4%E8%BF%9E%E6%8E%A5%E7%8A%B6%E6%80%81.png&quot; alt=&quot;确认连接状态&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开路由器后台，即可查看到该设备&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E8%B7%AF%E7%94%B1%E5%99%A8%E5%90%8E%E5%8F%B0%E7%8A%B6%E6%80%81.png&quot; alt=&quot;路由器后台状态&quot;&gt;&lt;/p&gt;
&lt;h2&gt;5. 使用 ssh 连接香橙派&lt;/h2&gt;
&lt;p&gt;刚才我们已经获取到了香橙派的 IP 地址，下次登录时无需使用串口，可以直接使用 ssh 进行登录&lt;/p&gt;
&lt;p&gt;给开发板连接电源后，使用 win + R + cmd 打开终端，输入&lt;code&gt;ping ip_addr&lt;/code&gt;查看 ip 是否正确。需要将指令中的 ip_addr 替换为上面查看到的 ip 地址，如下图&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9F%A5%E7%9C%8Bip%E6%98%AF%E5%90%A6%E6%AD%A3%E7%A1%AE.png&quot; alt=&quot;查看 ip 是否正确&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开 MobaXterm，按照下图的顺序点击并填写对应内容&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./ssh%E8%BF%9E%E6%8E%A5%E5%BC%80%E5%8F%91%E6%9D%BF.png&quot; alt=&quot;ssh 连接开发板&quot;&gt;&lt;/p&gt;
&lt;p&gt;等待几秒后，输入密码（初始密码为：orangepi），出现以下界面即为连接成功&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./ssh%E8%BF%9E%E6%8E%A5%E6%88%90%E5%8A%9F.png&quot; alt=&quot;ssh 连接成功&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/orange.D2TX7M8i.png"/><enclosure url="/_astro/orange.D2TX7M8i.png"/></item><item><title>Docker学习笔记：镜像的构建与运行</title><link>https://www.wht0909.top/blog/docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E9%95%9C%E5%83%8F%E7%9A%84%E6%9E%84%E5%BB%BA%E4%B8%8E%E8%BF%90%E8%A1%8C/docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E9%95%9C%E5%83%8F%E7%9A%84%E6%9E%84%E5%BB%BA%E4%B8%8E%E8%BF%90%E8%A1%8C</link><guid isPermaLink="true">https://www.wht0909.top/blog/docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E9%95%9C%E5%83%8F%E7%9A%84%E6%9E%84%E5%BB%BA%E4%B8%8E%E8%BF%90%E8%A1%8C/docker%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E9%95%9C%E5%83%8F%E7%9A%84%E6%9E%84%E5%BB%BA%E4%B8%8E%E8%BF%90%E8%A1%8C</guid><description>docker初步学习</description><pubDate>Fri, 01 Aug 2025 14:45:20 GMT</pubDate><content:encoded>&lt;p&gt;封面画作：Claude Monet &lt;em&gt;&lt;strong&gt;The Seine at Bougival in the Evening&lt;/strong&gt;&lt;/em&gt;(1869)&lt;/p&gt;
&lt;p&gt;本文参考了以下文章 / 资源，如有侵权请联系作者，作者将立刻删除：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1THKyzBER6/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;40分钟的Docker实战攻略，一期视频精通Docker&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV18Ztme2E1u/?spm_id_from=333.337.search-card.all.click&amp;#x26;vd_source=0ea0c7956df75b2935422822b2001158&quot;&gt;深度学习入门老司机必备，利用Docker快速构建深度学习镜像，实现开箱即用的深度学习环境&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作者在搭建机器学习实验环境时，常遇到这样的问题：通常情况下本地需要搭建一个环境，调试代码能否正常运行；而真正的计算过程往往在服务器上进行，又需要搭一次环境，并不便捷而且增加了时间成本；而且有时会忘记搭建过程中的某些细节（如版本号对应等）导致各种报错。Docker 就提供了一种这样的部署方式：只需在本地搭建一次环境，在其他机器上运行时，可以直接“获取”这个环境并运行，非常的方便。这就是这篇笔记要解决的问题。&lt;/p&gt;
&lt;h2&gt;1. 什么是 Docker&lt;/h2&gt;
&lt;p&gt;Docker 是一种通过容器化技术，为应用程序封装独立的运行环境，每个运行环境就是一个&lt;strong&gt;容器&lt;/strong&gt;，运行容器的计算机称为&lt;strong&gt;宿主机&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;镜像&lt;/strong&gt;是容器的模板，镜像和容器的关系类似于用模具（镜像）做糕点（容器）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker 仓库&lt;/strong&gt;是用来保存、分享镜像的地方，Docker 的官方仓库是 Docker hub&lt;/p&gt;
&lt;h2&gt;2. 准备项目文件&lt;/h2&gt;
&lt;p&gt;我们以一个 Python 项目为例，将其封装为一个镜像，项目结构如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;weather_cli/
├── app.py
├── requirements.txt
└── Dockerfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个项目的功能是：获取某个城市的天气&lt;/p&gt;
&lt;p&gt;app.py 文件内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests
import sys

def get_weather(city: str) -&gt; None:
    url = f&quot;https://wttr.in/{city}?format=3&quot;
    try:
        r = requests.get(url, timeout=10)
        r.raise_for_status()
        print(r.text.strip())
    except Exception as e:
        print(&quot;获取天气失败:&quot;, e, file=sys.stderr)

if __name__ == &quot;__main__&quot;:
    city = sys.argv[1] if len(sys.argv) &gt; 1 else &quot;Beijing&quot;
    get_weather(city)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;requirements.txt 文件内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;requests==2.31.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;除了项目文件外，还需要一个 Dockerfile 文件，Dockerfile 描述了镜像的具体构建过程，计算机通过执行该文件内容构建镜像&lt;/p&gt;
&lt;p&gt;Dockerfile 内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-Dockerfile&quot;&gt;# 使用官方 Python 3.11 运行镜像作为基础
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 先把 requirements 复制进去，利用缓存
COPY requirements.txt .

# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY app.py .

# 容器启动时执行的命令
ENTRYPOINT [&quot;python&quot;, &quot;app.py&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 构建镜像&lt;/h2&gt;
&lt;p&gt;cd 到项目文件夹中，使用以下指令构建镜像&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build [OPTIONS] PATH | URL | -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通常我们只需使用 -t 指定镜像名称，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker build -t weather-cli:1.0 .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面的&lt;code&gt;.&lt;/code&gt;即为当前目录，意思是：把这个目录里的所有文件当作构建材料，交给 Docker 引擎去处理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%9E%84%E5%BB%BA%E9%95%9C%E5%83%8F.png&quot; alt=&quot;构建镜像&quot;&gt;&lt;/p&gt;
&lt;h2&gt;4. 推送到 Docker Hub&lt;/h2&gt;
&lt;p&gt;依次运行下列指令将构建好的镜像推送至 Docker hub:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker tag weather-cli:1.0 你的用户名/weather-cli:1.0
docker push 你的用户名/weather-cli:1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%8E%A8%E9%80%81%E9%95%9C%E5%83%8F.png&quot; alt=&quot;推送镜像&quot;&gt;&lt;/p&gt;
&lt;p&gt;在我们的镜像仓库中即可看到刚刚推送的镜像&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93%E7%95%8C%E9%9D%A2.png&quot; alt=&quot;镜像仓库界面&quot;&gt;&lt;/p&gt;
&lt;h2&gt;5.运行容器&lt;/h2&gt;
&lt;p&gt;接下来使用刚刚推送的镜像生成容器并运行&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 查询北京天气
docker run --rm weather-cli:1.0 Beijing

# 查询上海天气
docker run --rm weather-cli:1.0 Shanghai
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;终端输出如下，说明程序正确运行：&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.png&quot; alt=&quot;程序运行结果&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/封面画作.CiJUc9Bt.png"/><enclosure url="/_astro/封面画作.CiJUc9Bt.png"/></item><item><title>从零开始本地部署大模型</title><link>https://www.wht0909.top/blog/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E5%A4%A7%E6%A8%A1%E5%9E%8B/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E5%A4%A7%E6%A8%A1%E5%9E%8B</link><guid isPermaLink="true">https://www.wht0909.top/blog/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E5%A4%A7%E6%A8%A1%E5%9E%8B/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2%E5%A4%A7%E6%A8%A1%E5%9E%8B</guid><description>本地部署大模型的教程</description><pubDate>Fri, 01 Aug 2025 11:28:00 GMT</pubDate><content:encoded>&lt;p&gt;import { Aside } from &apos;astro-pure/user&apos;&lt;/p&gt;
&lt;p&gt;本文参考了以下文章，如有侵权请联系作者，作者将立刻删除：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/LaiYun/p/18695293&quot;&gt;记录在本地电脑部署自己的 DeepSeek 大模型AI&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://apifox.com/apiskills/ollama-deploy/&quot;&gt;使用 Ollama 在本地部署 AI 大模型： 安装、部署和 API 调用的分步指南&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/LaiYun/p/18696931&quot;&gt;自定义Ollama安装路径&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在日常使用大模型时，我们往往会选择直接登录模型官网调用 API，但这种方法常受到收费标准、网络环境的限制。相比之下，将模型部署在本地不仅能实现离线使用，还能有效规避数据泄露的风险。本文将详细记录大模型的本地部署方法。&lt;/p&gt;
&lt;h2&gt;1. 下载 Ollama&lt;/h2&gt;
&lt;p&gt;Ollama 是一个“傻瓜式”本地大模型启动器——一条命令就能把 Llama、Qwen、Gemma 等开源大模型下载、安装并运行在你的电脑或服务器上，无需安装复杂依赖，也无需把数据传上云端。&lt;/p&gt;
&lt;p&gt;官网下载 Ollama ：https://ollama.com/&lt;/p&gt;
&lt;p&gt;进入官网后选择适合自己操作系统的版本，点击 Download 即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E4%B8%8B%E8%BD%BDollama.png&quot; alt=&quot;下载ollama&quot;&gt;&lt;/p&gt;
&lt;p&gt;由于 C 盘空间有限，我们选择将软件和模型文件安装在 F 盘（想安装到 C 盘的可以直接跳过以下部分）&lt;/p&gt;
&lt;p&gt;首先打开下载好的 OllamaSetup.exe 所在的文件夹，点击上方的文件路径搜索栏，输入&lt;code&gt;cmd&lt;/code&gt;后回车进入命令行界面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E5%AE%89%E8%A3%85ollama.png&quot; alt=&quot;安装ollama&quot;&gt;&lt;/p&gt;
&lt;p&gt;输入指令&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;OllamaSetup.exe /DIR=Path_To_File
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Path_To_File&lt;/code&gt; 用 OllamaSetup.exe 所在的文件夹地址代替，比如我的指令就是&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;OllamaSetup.exe /DIR=F:\Ollama
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样 Ollama 软件和 models 文件夹就都在 F 盘了&lt;/p&gt;
&lt;h2&gt;2. 下载 ChatBox&lt;/h2&gt;
&lt;p&gt;ChatBox 是一个接入了大模型的客户端界面，理论上我们完成本地部署之后可以直接在命令行界面和 LLM 对话，但 ChatBox 提供了一个 UI 界面，通过它我们可以更清楚地看到对话记录等信息&lt;/p&gt;
&lt;p&gt;官网下载 ChatBox：https://chatboxai.app/zh&lt;/p&gt;
&lt;p&gt;安装过程比较简单，就不演示了 (・ω・)&lt;/p&gt;
&lt;h2&gt;3. 下载模型文件并运行&lt;/h2&gt;
&lt;p&gt;进入 Ollama 官网，点击官网左上角的 “Models”，进入模型选择界面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%A8%A1%E5%9E%8B%E9%80%89%E6%8B%A9%E9%A1%B5%E9%9D%A2.png&quot; alt=&quot;模型选择界面&quot;&gt;&lt;/p&gt;
&lt;p&gt;我们本次要部署的大模型是 deepseek-r1 ，点击 deepseek-r1 后进入如下界面&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./deepseek-r1%E6%A8%A1%E5%9E%8B.png&quot; alt=&quot;deepseek-r1模型&quot;&gt;&lt;/p&gt;
&lt;p&gt;在 Models 中，Name 为模型名称，模型后面的数字表示参数量，如 7b 表示该模型的参数量为 70 亿(7 billion)；Size 为模型占用的磁盘空间大小；Context 为上下文长度，如 128K 表示模型可处理 128,000 个 token 的前文信息；Input 为输入类型，这几个模型的输入信息均为文本类型&lt;/p&gt;
&lt;p&gt;大家可以根据设备的硬件性能选择合适的模型，我选择的模型为 deepseek-r1:1.5b&lt;/p&gt;
&lt;p&gt;打开终端，输入&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ollama run deepseek-r1:1.5b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即可看到模型的下载过程&lt;/p&gt;
&lt;p&gt;下载完成后可直接和模型对话&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%A8%A1%E5%9E%8B%E4%B8%8B%E8%BD%BD%E4%B8%8E%E5%AF%B9%E8%AF%9D.png&quot; alt=&quot;模型下载与对话&quot;&gt;&lt;/p&gt;
&lt;p&gt;常用指令:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 模型文件下载与运行
ollama run model_name

# 查看本地模型列表
ollama list

# 删除某个模型
ollama rm model_name

# 结束和模型的对话
/bye
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. ChatBox 接入本地模型&lt;/h2&gt;
&lt;p&gt;打开 ChatBox，按下图顺序操作，在序号 4 处按图中方式填写，在序号 5 处点击获取后添加对应模型即可&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./ChatBox%E6%8E%A5%E5%85%A5%E6%9C%AC%E5%9C%B0%E6%A8%A1%E5%9E%8B.png&quot; alt=&quot;ChatBox接入本地模型&quot;&gt;&lt;/p&gt;
&lt;p&gt;打开”编辑系统环境变量”，进入”环境变量”，点击”新建”，新建两个环境变量：&lt;/p&gt;
&lt;p&gt;变量名：OLLAMA_HOST 变量值：0.0.0.0&lt;/p&gt;
&lt;p&gt;变量名：OLLAMA_ORIGING 变量值：*&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E6%96%B0%E5%BB%BA%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F.png&quot; alt=&quot;新建环境变量&quot;&gt;&lt;/p&gt;
&lt;p&gt;保存后退出 Ollama，并重启 Ollama&lt;/p&gt;
&lt;p&gt;再打开 ChatBox，即可正常对话&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./%E4%BD%BF%E7%94%A8ChatBox%E5%92%8C%E6%A8%A1%E5%9E%8B%E5%AF%B9%E8%AF%9D.png&quot; alt=&quot;使用ChatBox和模型对话&quot;&gt;&lt;/p&gt;</content:encoded><h:img src="/_astro/deepseek.B_PCq5xU.png"/><enclosure url="/_astro/deepseek.B_PCq5xU.png"/></item><item><title>Hexo常用指令记录</title><link>https://www.wht0909.top/blog/hexo%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E8%AE%B0%E5%BD%95/hexo%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E8%AE%B0%E5%BD%95</link><guid isPermaLink="true">https://www.wht0909.top/blog/hexo%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E8%AE%B0%E5%BD%95/hexo%E5%B8%B8%E7%94%A8%E6%8C%87%E4%BB%A4%E8%AE%B0%E5%BD%95</guid><description>记录一些常用的 Hexo 指令</description><pubDate>Wed, 23 Jul 2025 16:56:08 GMT</pubDate><content:encoded>&lt;p&gt;这篇博客参考了以下文章，如有侵权请联系作者，作者将立刻删除：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cnblogs.com/junlin623/p/17410016.html&quot;&gt;Windows下hexo个人博客详细搭建教程&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.csdn.net/vpqtxzmzezeqjj9977/article/details/122982320&quot;&gt;Hexo的常用指令合集&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;作者的第一版个人网站是使用 &lt;code&gt;html &amp;#x26;&amp;#x26; JavaScript&lt;/code&gt; 编写的，由于编程技术有限，做得比较简陋，且不方便管理和互动，于是想通过 Hexo 框架重新整理一下内容，顺便记录学习历程。&lt;/p&gt;
&lt;h2&gt;1. hexo new &quot;博客名&quot;&lt;/h2&gt;
&lt;p&gt;新建名为“博客名”的文章，文章将存于&lt;code&gt;.source/_posts&lt;/code&gt;文件夹下&lt;/p&gt;
&lt;p&gt;可以通过&lt;code&gt;code 博客名.md&lt;/code&gt;在 vscode 中编辑文章内容&lt;/p&gt;
&lt;p&gt;编辑完成后，使用&lt;code&gt;cd ../..&lt;/code&gt;退回至 blog 文件夹，依次运行以下代码重新生成&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hexo g
hexo s
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. hexo g 或 hexo generate&lt;/h2&gt;
&lt;p&gt;该指令的主要作用是&lt;strong&gt;生成静态网页文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在本地编写完新文章或者修改了文章内容、主题配置等之后，执行 &lt;code&gt;hexo g&lt;/code&gt; 生成最新的静态网页，然后使用 &lt;code&gt;hexo s&lt;/code&gt;（hexo server 的缩写，用于启动本地服务器）启动本地服务器，在浏览器中访问本地地址（如 http://localhost:4000 ），就能看到最新的博客效果。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hexo g&lt;/code&gt; 通常会和其他指令配合使用，比如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;hexo clean&lt;/p&gt;
&lt;p&gt;在执行 &lt;code&gt;hexo g&lt;/code&gt; 之前，有时会先运行 &lt;code&gt;hexo clean&lt;/code&gt; 指令，它会清空 public 文件夹中的内容。这样做是为了确保每次生成的静态文件都是最新的，避免因为之前生成的文件残留而导致的显示异常。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;hexo d&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hexo d&lt;/code&gt; 是 hexo deploy 的缩写，用于将生成好的静态文件（位于 public 文件夹）部署到指定的线上服务（如 GitHub Pages 等 ）。一般在执行完 &lt;code&gt;hexo g&lt;/code&gt; 生成静态文件之后，再执行 &lt;code&gt;hexo d&lt;/code&gt; 就能把博客发布到线上。不过，使用 &lt;code&gt;hexo d&lt;/code&gt; 之前需要先在配置文件中正确配置部署相关的信息，如远程仓库地址、认证信息等&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. hexo clean&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;hexo clean&lt;/code&gt; 指令的作用是&lt;strong&gt;清理缓存文件和之前生成的静态文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在执行 &lt;code&gt;hexo g&lt;/code&gt; 之前，最好先运行 &lt;code&gt;hexo clean&lt;/code&gt; 清理缓存和旧的静态文件，避免因为缓存或旧文件的干扰，导致网页显示的内容与预期不符&lt;/p&gt;
&lt;h2&gt;4. hexo s 或 hexo server&lt;/h2&gt;
&lt;p&gt;用于启动服务，默认地址为4000端口&lt;/p&gt;
&lt;h2&gt;5. hexo d 或 hexo deploy&lt;/h2&gt;
&lt;p&gt;其核心作用是将本地生成的静态文件自动部署到远程服务器或托管平台，实现博客内容的在线发布。&lt;/p&gt;
&lt;p&gt;这部分内容可以看这篇博客：&lt;a href=&quot;https://www.cnblogs.com/junlin623/p/17410016.html&quot;&gt;Windows下hexo个人博客详细搭建教程&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;由于之前作者已经有了一个 “用户名.github.io” 仓库，作者将原来的仓库改名并设为 private ，将新的个人博客放在 “用户名.github.io” 仓库中，但此后出现了网页链接错误的问题。经分析，可能是由于本地 DNS 可能缓存了之前网站的 IP 地址等解析信息。新仓库部署后，尽管 GitHub 已更新对应关系，但本地缓存未刷新，导致访问错误。解决方法如下（&lt;strong&gt;注意：这条指令是在 Windows 系统下操作的&lt;/strong&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ipconfig /flushdns
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后重新连接网页即可&lt;/p&gt;
&lt;p&gt;在对部署到 github.io 域名上的网页进行修改后，在终端依次执行以下代码即可：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hexo clean
hexo g
hexo d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注：如果&lt;code&gt;hexo d&lt;/code&gt;因网络原因报错，请使用&lt;code&gt;ping gihub.com&lt;/code&gt;检查能否正常连接到 github 网站；这一步不要使用管理员权限在终端操作，我在这部分操作时出现了网络原因的报错；直接 win + R 打开 cmd，再&lt;code&gt;hexo d&lt;/code&gt;即可&lt;/p&gt;
&lt;h2&gt;6. 删除 / 修改博客&lt;/h2&gt;
&lt;p&gt;删除博客时，需要将 .source/_posts 下的文章 .md 文件删除，再依次执行以下指令：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hexo clean
hexo g
hexo s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有关 Hexo 博客的搭建可以参考的其他文章&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://webfem.com/post/hexo&quot;&gt;手把手教你搭建 Hexo 博客&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://www.jianshu.com/p/e17711e44e00&quot;&gt;Hexo使用攻略-添加分类及标签&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded><h:img src="/_astro/hexo.DMxQ9mj8.png"/><enclosure url="/_astro/hexo.DMxQ9mj8.png"/></item></channel></rss>