简单的词向量预训练

预训练任务

  • 基本任务就是根据上下文预测下一时刻词: $P\left(w_{t} \mid w_{1} w_{2} \ldots w_{t-1}\right) $
  • 这种监督信号来自于数据自身,因此称为 自监督学习

前馈神经网络预训练词向量

  • 输入层→词向量层→隐含层→输出层
  • 训练后,词向量矩阵$ \boldsymbol{E} \in \mathbb{R}^{d \times|\mathbb{V}|}$即为预训练得到的静态词向量。
class NGramDataset(Dataset):
    def __init__(self, corpus, vocab, context_size=2):
        # context_size:上下文大小为2
        self.data = []
        self.bos = vocab[BOS_TOKEN] # 句首标记id
        self.eos = vocab[EOS_TOKEN] # 句尾标记id
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            # 插入句首、句尾标记符
            sentence = [self.bos] + sentence + [self.eos]
            # 如句子长度小于预定义的上下文大小,则跳过
            if len(sentence) < context_size:
                continue
            for i in range(context_size, len(sentence)):
                # 模型输入:长为context_size的上文
                context = sentence[i-context_size:i]
                # 模型输出:当前词
                target = sentence[i]
                # 每个训练样本由(context,target)构成
                self.data.append((context, target))

    def __len__(self):
        return len(self.data)

    def __getitem__(self, i):
        return self.data[i]

    def collate_fn(self, examples):
        # 从独立样本集合中构建batch的输入输出,并转换为PyTorch张量类型
        inputs = torch.tensor([ex[0] for ex in examples], dtype=torch.long)
        targets = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
        return (inputs, targets)

class FeedForwardNNLM(nn.Module):
    def __init__(self, vocab_size, embedding_dim, context_size, hidden_dim):
        super(FeedForwardNNLM, self).__init__()
        # 词嵌入层
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        # 线性变换:词嵌入层->隐含层
        self.linear1 = nn.Linear(context_size * embedding_dim, hidden_dim)
        # 线性变换:隐含层->输出层
        self.linear2 = nn.Linear(hidden_dim, vocab_size)
        # 使用ReLU激活函数
        self.activate = F.relu
        init_weights(self)

    def forward(self, inputs):
        embeds = self.embeddings(inputs).view((inputs.shape[0], -1))
        hidden = self.activate(self.linear1(embeds))
        output = self.linear2(hidden)
        # 根据输出层(logits)计算概率分布并取对数,以便于计算对数似然
        # 这里采用PyTorch库的log_softmax实现
        log_probs = F.log_softmax(output, dim=1)
        return log_probs

embedding_dim = 64
context_size = 2
hidden_dim = 128
batch_size = 1024
num_epoch = 10

# 读取文本数据,构建FFNNLM训练数据集(n-grams)
corpus, vocab = load_reuters()
# print(corpus[0])
# print(corpus.shape)

dataset = NGramDataset(corpus, vocab, context_size)
data_loader = get_loader(dataset, batch_size)

# 负对数似然损失函数
nll_loss = nn.NLLLoss()
# 构建FFNNLM,并加载至device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = FeedForwardNNLM(len(vocab), embedding_dim, context_size, hidden_dim)
model.to(device)
# 使用Adam优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)

model.train()
total_losses = []
for epoch in range(num_epoch):
    total_loss = 0
    for batch in tqdm(data_loader, desc=f"Training Epoch {epoch}"):
        inputs, targets = [x.to(device) for x in batch]
        optimizer.zero_grad()
        log_probs = model(inputs)
        loss = nll_loss(log_probs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f"Loss: {total_loss:.2f}")
    total_losses.append(total_loss)

# 保存词向量(model.embeddings)
save_pretrained(vocab, model.embeddings.weight.data, "ffnnlm.vec")

模型在训练集上的损失随着迭代轮次的增加而不断减小。需要注意的是,由于训练的目标是获取词向量而不是语言模型本身,所以在以上训练过程中,并不需要以模型达到收敛状态(损失停止下降)作为训练终止条件。

训练过程中模型损失的变化曲线

循环神经网络预训练词向量

创建数据类RnnlmDataset

RnnlmDataset实现训练数据的构建和存取。

对于句子w1w2··· wn,循环神经网络的输入序列为<bos>w1w2··· wn1w2··· wn,输出序列为w1w2··· wn<eos>。与基于定长上下文的前馈神经网络语言模型不同,RNNLM的输入序列长度是动态变化的,因此在 构建批次时,需要对批次内样本进行补齐 ,使其长度一致。

class RnnlmDataset(Dataset):
    def __init__(self, corpus, vocab):
        self.data = []
        self.bos = vocab[BOS_TOKEN]
        self.eos = vocab[EOS_TOKEN]
        self.pad = vocab[PAD_TOKEN]
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            # 模型输入:BOS_TOKEN, w_1, w_2, ..., w_n
            input = [self.bos] + sentence
            # 模型输出:w_1, w_2, ..., w_n, EOS_TOKEN
            target = sentence + [self.eos]
            self.data.append((input, target))

    def __len__(self):
        return len(self.data)

    def __getitem__(self, i):
        return self.data[i]

    def collate_fn(self, examples):
        # 从独立样本集合中构建batch输入输出
        inputs = [torch.tensor(ex[0]) for ex in examples]
        targets = [torch.tensor(ex[1]) for ex in examples]
        # 对batch内的样本进行padding,使其具有相同长度
        inputs = pad_sequence(inputs, batch_first=True, padding_value=self.pad)
        targets = pad_sequence(targets, batch_first=True, padding_value=self.pad)
        return (inputs, targets)

模型:选用LSTM

  • 输入层→词向量层→隐含层→输出层
class RNNLM(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim):
        super(RNNLM, self).__init__()
        # 词嵌入层
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        # 循环神经网络:这里使用LSTM
        self.rnn = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)
        # 输出层
        self.output = nn.Linear(hidden_dim, vocab_size)

    def forward(self, inputs):
        embeds = self.embeddings(inputs)
        # 计算每一时刻的隐含层表示
        hidden, _ = self.rnn(embeds)
        output = self.output(hidden)
        log_probs = F.log_softmax(output, dim=2)
        return log_probs

训练

训练过程与前馈神经网络基本一致,由于输入输出序列可能较长,因此可以视情况调整批次大小(batchsize)。

embedding_dim = 64
context_size = 2
hidden_dim = 128
batch_size = 1024
num_epoch = 10

# 读取文本数据,构建FFNNLM训练数据集(n-grams)
corpus, vocab = load_reuters()
dataset = RnnlmDataset(corpus, vocab)
data_loader = get_loader(dataset, batch_size)

# 负对数似然损失函数,忽略pad_token处的损失
nll_loss = nn.NLLLoss(ignore_index=dataset.pad)
# 构建RNNLM,并加载至device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = RNNLM(len(vocab), embedding_dim, hidden_dim)
model.to(device)
# 使用Adam优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)

model.train()
for epoch in range(num_epoch):
    total_loss = 0
    for batch in tqdm(data_loader, desc=f"Training Epoch {epoch}"):
        inputs, targets = [x.to(device) for x in batch]
        optimizer.zero_grad()
        log_probs = model(inputs)
        loss = nll_loss(log_probs.view(-1, log_probs.shape[-1]), targets.view(-1))
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f"Loss: {total_loss:.2f}")

save_pretrained(vocab, model.embeddings.weight.data, "rnnlm.vec")
  • 然后把词向量层参数和词表(一一对应)保存下来就是静态词向量

Word2Vec 词向量

由于之前的神经网络预训练词向量方法存在一个缺陷,就是当对t时刻词进行预测时,模型只利用了历史词序列作为输入,从而损失了与“未来”上下文之间的共现信息,因此引入Word2wec。

CBOW模型

给定一段文本,CBOW模型的基本思想是根据上下文对目标词进行预测。

对于$w_{t-2} w_{t-1}\quad w_{t} w_{t+1} w_{t+2}$,CBOW模型的任务是根据一定窗口大小内的上下文Ct(若取窗口大小为5,则Ct={wt−2, wt−1, wt+1, wt+2})对t时刻的词wt进行预测。与神经网络语言模型不同。

特点

CBOW模型不考虑上下文中单词的位置或者顺序,因此模型的输入实际上是一个“词袋”而非序列,这也是模型取名为“Continuous Bag-of-Words”的原因。

CBOW模型为上图所示的前馈神经网络结构。与一般的前馈神经网络相比,CBOW模型的隐含层只是执行对词向量层取平均的操作,而没有线性变换以及非线性激活的过程。所以,也可以认为CBOW模型是没有隐含层的,这也是CBOW模型具有高训练效率的主要原因。

输入层

**以大小为5的上下文窗口为例,在目标词的左右各取2个词作为模型输入。输入层由4个维度为词表长度|V|的独热表示向量构成。$e_{w_{i}}=[0 ; \ldots ; 1 ; \ldots 0] \in[[0,1]]^{\mathbb{V}_{\perp}}$

词向量层

输入层的每个词的独热表示经由局长$\pmb{E}\in\mathbb{R}^{d\times|\mathbb{V}|}$映射至词向量空间。\boldsymbol{v}_{w_{i}}=\boldsymbol{E} \boldsymbol{e}_{w_{i}}

Wi对应的词向量即为矩阵E中相应位置的列向量,E则为由所有词向量构成的矩阵或查找表。令Ct={Wt−k,···, Wt−1, Wt+1,···, Wt+k}表示Wt的上下文单词集合, 对Ct中所有词向量取平均操作 ,就得到了Wt的上下文表示:

$$ \boldsymbol{v}_{\mathcal{C}_{t}}=\frac{1}{\left|\mathcal{C}_{t}\right|} \sum_{w \in \mathcal{C}_{t}} \boldsymbol{v}_{w} $$

输出层

输出层参数 $\boldsymbol{E}^{\prime} \in \mathbb{R}^{\mathbb{V} \mid \times d}$

$$ P\left(w_{t} \mid \mathcal{C}_{t}\right)=\frac{\exp \left(\boldsymbol{v}_{\mathcal{C}_{t}} \cdot \boldsymbol{v}_{w_{t}}^{\prime}\right)}{\sum_{w^{\prime} \in \mathbb{V}} \exp \left(\boldsymbol{v}_{\mathcal{C}_{t}} \cdot \boldsymbol{v}_{w^{\prime}}^{\prime}\right)} $$

$v_{w_{i}}^{\prime}$是$E^{\prime}$与对应$w_{i}$的行向量。

词向量矩阵

$E和{E}^{\prime}$都可以作为词向量矩阵,他们分别表示了词在作为条件上下文或目标词时的不同性质。常用$E$也可以两者组合起来。

创建数据类CbowDataset

class CbowDataset(Dataset):
    def __init__(self, corpus, vocab, context_size=2):
        self.data = []
        self.bos = vocab[BOS_TOKEN]
        self.eos = vocab[EOS_TOKEN]
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            sentence = [self.bos] + sentence+ [self.eos]
            if len(sentence) < context_size * 2 + 1:
                continue
            for i in range(context_size, len(sentence) - context_size):
                # 模型输入:左右分别取context_size长度的上下文
                context = sentence[i-context_size:i] + sentence[i+1:i+context_size+1]
                # 模型输出:当前词
                target = sentence[i]
                self.data.append((context, target))

    def __len__(self):
        return len(self.data)

    def __getitem__(self, i):
        return self.data[i]

    def collate_fn(self, examples):
        inputs = torch.tensor([ex[0] for ex in examples])
        targets = torch.tensor([ex[1] for ex in examples])
        return (inputs, targets)

模型:CBOW

class CbowModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(CbowModel, self).__init__()
        # 词嵌入层
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        # 线性变换:隐含层->输出层
        self.output = nn.Linear(embedding_dim, vocab_size)
        init_weights(self)

    def forward(self, inputs):
        embeds = self.embeddings(inputs)
        # 计算隐含层:对上下文词向量求平均
        hidden = embeds.mean(dim=1)
        output = self.output(hidden)
        log_probs = F.log_softmax(output, dim=1)
        return log_probs

训练

embedding_dim = 64
context_size = 2
hidden_dim = 128
batch_size = 1024
num_epoch = 10

# 读取文本数据,构建CBOW模型训练数据集
corpus, vocab = load_reuters()
dataset = CbowDataset(corpus, vocab, context_size=context_size)
data_loader = get_loader(dataset, batch_size)

nll_loss = nn.NLLLoss()
# 构建CBOW模型,并加载至device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = CbowModel(len(vocab), embedding_dim)
model.to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)

model.train()
for epoch in range(num_epoch):
    total_loss = 0
    for batch in tqdm(data_loader, desc=f"Training Epoch {epoch}"):
        inputs, targets = [x.to(device) for x in batch]
        optimizer.zero_grad()
        log_probs = model(inputs)
        loss = nll_loss(log_probs, targets)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f"Loss: {total_loss:.2f}")

# 保存词向量(model.embeddings)
save_pretrained(vocab, model.embeddings.weight.data, "cbow.vec")

skip-gram模型

**使用 Ct 中的每个词作为独立的上下文对目标词进行预测”, 即 $P\left(w_{t} \mid w_{t+j}\right)$

**原文献是$P\left(w_{t+j} \mid w_{t}\right)$两者等价

  • 隐藏层向量:$v_{w_{t}}=\boldsymbol{E}_{w_{t}}^{T}$
  • 输出层:参数为 $\boldsymbol{E}^{\prime} \in \mathbb{R}^{\mathbb{V} \times d}, P\left(c \mid w_{t}\right)=\frac{\exp \left(v_{w_{t}} \cdot v_{c}^{\prime}\right)}{\sum_{w^{\prime} \in \mathbb{V}} \exp \left(v_{w_{t}} \cdot v_{w^{\prime}}^{\prime}\right)}$
  • 其中$v_{w_{i}}^{\prime}$是$E^{\prime}$与对应$w_{i}$的行向量。
  • 词向量层:与CBOW相同

skip-gram Dataset

class SkipGramDataset(Dataset):
    def __init__(self, corpus, vocab, context_size=2):
        self.data = []
        self.bos = vocab[BOS_TOKEN]
        self.eos = vocab[EOS_TOKEN]
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            sentence = [self.bos] + sentence + [self.eos]
            for i in range(1, len(sentence)-1):
                # 模型输入:当前词
                w = sentence[i]
                # 模型输出:一定上下文窗口大小内共现的词对
                left_context_index = max(0, i - context_size)
                right_context_index = min(len(sentence), i + context_size)
                context = sentence[left_context_index:i] + sentence[i+1:right_context_index+1]
                self.data.extend([(w, c) for c in context])

    def __len__(self):
        return len(self.data)

    def __getitem__(self, i):
        return self.data[i]

    def collate_fn(self, examples):
        inputs = torch.tensor([ex[0] for ex in examples])
        targets = torch.tensor([ex[1] for ex in examples])
        return (inputs, targets)

模型-skip-gram

class SkipGramModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(SkipGramModel, self).__init__()
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.output = nn.Linear(embedding_dim, vocab_size)
        init_weights(self)

    def forward(self, inputs):
        embeds = self.embeddings(inputs)
        output = self.output(embeds)
        log_probs = F.log_softmax(output, dim=1)
        return log_probs

参数估计与预训练任务

  • 需要估计的参数:$\boldsymbol{\theta}=\left\{\boldsymbol{E}, \boldsymbol{E}^{\prime}\right\}$

CBOW模型的负对数似然损失函数为:

$$ \mathcal{L}(\boldsymbol{\theta})=-\sum_{t=1}^{T} \log P\left(w_{t} \mid \mathcal{C}_{t}\right) $$

Skip-gram模型的负对数似然损失函数为:

$$ \mathcal{L}(\boldsymbol{\theta})=-\sum_{t=1}^{T} \sum_{-k \leq j \leq k, j \neq 0} \log P\left(w_{t+j} \mid w_{t}\right)$ $$

负采样

  • 输出层的归一化计算效率低(当词表很大的时候)
  • 样本 $(w,c)$正样本$c = W{t + j}$,对c进行若干次负采样得到:$\tilde{w}_{i}(i=1, \ldots, K)$
  • 给定当前词 w ww 与上下文词 c cc ,最大化两者共现概率;即简化为对于的二元分类问题
  • 共现:$P(D=1 \mid w, c)=\sigma\left(\boldsymbol{v}_{w} \cdot \boldsymbol{v}_{c}^{\prime}\right)$
  • 不共现
  • 对数似然改为

,其中

  • 负采样分布的选择:假设

表示从训练语料中统计得到的 Unigram 分布,目前通常会使用实际效果较好的负采样分布,如下:

负样本采样实现-skipgram

Skip-gram负采样Dataset

class SGNSDataset(Dataset):
    def __init__(self, corpus, vocab, context_size=2, n_negatives=5, ns_dist=None):
        self.data = []
        self.bos = vocab[BOS_TOKEN]
        self.eos = vocab[EOS_TOKEN]
        self.pad = vocab[PAD_TOKEN]
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            sentence = [self.bos] + sentence + [self.eos]
            for i in range(1, len(sentence)-1):
                # 模型输入:(w, context) ;输出为0/1,表示context是否为负样本
                w = sentence[i]
                left_context_index = max(0, i - context_size)
                right_context_index = min(len(sentence), i + context_size)
                context = sentence[left_context_index:i] + sentence[i+1:right_context_index+1]
                context += [self.pad] * (2 * context_size - len(context))
                self.data.append((w, context))

        # 负样本数量
        self.n_negatives = n_negatives
        # 负采样分布:若参数ns_dist为None,则使用uniform分布
        self.ns_dist = ns_dist if ns_dist is not None else torch.ones(len(vocab))

    def __len__(self):
        return len(self.data)

    def __getitem__(self, i):
        return self.data[i]

    def collate_fn(self, examples):
        words = torch.tensor([ex[0] for ex in examples], dtype=torch.long)
        contexts = torch.tensor([ex[1] for ex in examples], dtype=torch.long)
        batch_size, context_size = contexts.shape
        neg_contexts = []
        # 对batch内的样本分别进行负采样
        for i in range(batch_size):
            # 保证负样本不包含当前样本中的context
            ns_dist = self.ns_dist.index_fill(0, contexts[i], .0)
            # 进行取样,multinomial 是均匀的,反正就是一定根据 ns_dist 取样
            neg_contexts.append(torch.multinomial(ns_dist, self.n_negatives * context_size, replacement=True))
        neg_contexts = torch.stack(neg_contexts, dim=0)
        return words, contexts, neg_contexts

对于每个训练(正)样本,需要根据某个负采样概率分布生成相应的负样本,同时需要保证负样本不包含当前上下文窗口内的词。这次采用的实现方式是,在构建训练数据的过程中就完成负样本的生成,这样在训练时直接读取负样本即可。这样做的优点是训练过程无须再进行负采样,因而效率较高;缺点是每次迭代使用的是同样的负样本,缺乏多样性。

模型

其中需要维护两个词向量:各维护一个 w_embedding 和 c_embedding , 然后 各设置一个 forward_w 和 forward_c, 然后将词向量矩阵和上下文向量矩阵合并作为最终的词向量矩阵,

<span class="ne-text">combined_embeds = model.w_embeddings.weight + model.c_embeddings.weight </span>之所以这么做,是因为每个词向量要包含该词作为目标词和作为上下文的两者的信息。

因为每个词向量要包含该词作为目标词和作为上下文的两者的信息

class SGNSModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(SGNSModel, self).__init__()
        # 词嵌入
        self.w_embeddings = nn.Embedding(vocab_size, embedding_dim)
        # 上下文嵌入
        self.c_embeddings = nn.Embedding(vocab_size, embedding_dim)

    def forward_w(self, words):
        w_embeds = self.w_embeddings(words)
        return w_embeds

    def forward_c(self, contexts):
        c_embeds = self.c_embeddings(contexts)
        return c_embeds

训练

def get_unigram_distribution(corpus, vocab_size):
    # 从给定语料中统计unigram概率分布
    token_counts = torch.tensor([0] * vocab_size)
    total_count = 0
    for sentence in corpus:
        total_count += len(sentence)
        for token in sentence:
            token_counts[token] += 1
    unigram_dist = torch.div(token_counts.float(), total_count)
    return unigram_dist

embedding_dim = 64
context_size = 2
hidden_dim = 128
batch_size = 1024
num_epoch = 10
n_negatives = 10 # 负采样样本的数量

# 读取文本数据
corpus, vocab = load_reuters()
# 计算unigram概率分布
unigram_dist = get_unigram_distribution(corpus, len(vocab))
# 根据unigram分布计算负采样分布: p(w)**0.75
negative_sampling_dist = unigram_dist ** 0.75
negative_sampling_dist /= negative_sampling_dist.sum()
# 构建SGNS训练数据集
dataset = SGNSDataset(
    corpus,
    vocab,
    context_size=context_size,
    n_negatives=n_negatives,
    ns_dist=negative_sampling_dist
)
data_loader = get_loader(dataset, batch_size)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SGNSModel(len(vocab), embedding_dim)
model.to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)

model.train()
for epoch in range(num_epoch):
    total_loss = 0
    for batch in tqdm(data_loader, desc=f"Training Epoch {epoch}"):
        words, contexts, neg_contexts = [x.to(device) for x in batch]
        optimizer.zero_grad()
        batch_size = words.shape[0]
        # 提取batch内词、上下文以及负样本的向量表示
        word_embeds = model.forward_w(words).unsqueeze(dim=2)
        context_embeds = model.forward_c(contexts)
        neg_context_embeds = model.forward_c(neg_contexts)
        # 正样本的分类(对数)似然
        context_loss = F.logsigmoid(torch.bmm(context_embeds, word_embeds).squeeze(dim=2))
        context_loss = context_loss.mean(dim=1)
        # 负样本的分类(对数)似然
        neg_context_loss = F.logsigmoid(torch.bmm(neg_context_embeds, word_embeds).squeeze(dim=2).neg())
        neg_context_loss = neg_context_loss.view(batch_size, -1, n_negatives).sum(dim=2)
        neg_context_loss = neg_context_loss.mean(dim=1)
        # 损失:负对数似然
        loss = -(context_loss + neg_context_loss).mean()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f"Loss: {total_loss:.2f}")

# 合并词嵌入矩阵与上下文嵌入矩阵,作为最终的预训练词向量
combined_embeds = model.w_embeddings.weight + model.c_embeddings.weight
save_pretrained(vocab, combined_embeds.data, "sgns.vec")

Glove词向量

传统的词向量预训练本质上都是利用文本中词与词在局部上下文中的共现信息作为自监督学习信号,另一种是通过矩阵分解的方法,如SVD分解。

GloVe模型的基本思想是利用词向量对“词--上下文”共现矩阵进行预测(或者回归),从而实现隐式的矩阵分解。

  1. 构建贡献矩阵,但是限制在受限窗口大小内得贡献次数(即要使w和c的距离要足够小)
  2. 当w与c共现时,w与c之间的距离,Glove认为距较远的全共效次的贡献数较小
  3. 在获得矩阵M之后,利用词与上下文向量表示对M中的元素(取对数)进行回归计算。具体形式为

分别是w和c的向量表示(就是他们的Glove词向量),分别是相应的偏置项。

参数估计

式中,表示每一个 样本的权重,与共现次数有关。GloVe采用了以下的分段函数进行加权:

不超过某个阈值max时,的值单调递增且小于等于1,他的增长速率由α控制;当超过某个阈值max时,的值恒等于1.

Glove Dataset

class GloveDataset(Dataset):
    def __init__(self, corpus, vocab, context_size=2):
        # 记录词与上下文在给定语料中的共现次数
        self.cooccur_counts = defaultdict(float)
        self.bos = vocab[BOS_TOKEN]
        self.eos = vocab[EOS_TOKEN]
        for sentence in tqdm(corpus, desc="Dataset Construction"):
            sentence = [self.bos] + sentence + [self.eos]
            for i in range(1, len(sentence)-1):
                w = sentence[i]
                left_contexts = sentence[max(0, i - context_size):i]
                right_contexts = sentence[i+1:min(len(sentence), i + context_size)+1]
                # 共现次数随距离衰减: 1/d(w, c)
                for k, c in enumerate(left_contexts[::-1]):
                    self.cooccur_counts[(w, c)] += 1 / (k + 1)
                for k, c in enumerate(right_contexts):
                    self.cooccur_counts[(w, c)] += 1 / (k + 1)
        self.data = [(w, c, count) for (w, c), count in self.cooccur_counts.items()]

模型

GloVe模型与基于负采样的Skip-gram模型类似,唯一的区别在于 增加了两个偏置向量 ,具体代码如下。

class GloveModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(GloveModel, self).__init__()
        # 词嵌入及偏置向量
        self.w_embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.w_biases = nn.Embedding(vocab_size, 1)
        # 上下文嵌入及偏置向量
        self.c_embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.c_biases = nn.Embedding(vocab_size, 1)

    def forward_w(self, words):
        w_embeds = self.w_embeddings(words)
        w_biases = self.w_biases(words)
        return w_embeds, w_biases

    def forward_c(self, contexts):
        c_embeds = self.c_embeddings(contexts)
        c_biases = self.c_biases(contexts)
        return c_embeds, c_biases

训练

# 用以控制样本权重的超参数
m_max = 100
alpha = 0.75
# 从文本数据中构建GloVe训练数据集
corpus, vocab = load_reuters()
dataset = GloveDataset(
    corpus,
    vocab,
    context_size=context_size
)
data_loader = get_loader(dataset, batch_size)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GloveModel(len(vocab), embedding_dim)
model.to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)

model.train()
for epoch in range(num_epoch):
    total_loss = 0
    for batch in tqdm(data_loader, desc=f"Training Epoch {epoch}"):
        words, contexts, counts = [x.to(device) for x in batch]
        # 提取batch内词、上下文的向量表示及偏置
        word_embeds, word_biases = model.forward_w(words)
        context_embeds, context_biases = model.forward_c(contexts)
        # 回归目标值:必要时可以使用log(counts+1)进行平滑
        log_counts = torch.log(counts)
        # 样本权重
        weight_factor = torch.clamp(torch.pow(counts / m_max, alpha), max=1.0)
        optimizer.zero_grad()
        # 计算batch内每个样本的L2损失
        loss = (torch.sum(word_embeds * context_embeds, dim=1) + word_biases + context_biases - log_counts) ** 2
        # 样本加权损失
        wavg_loss = (weight_factor * loss).mean()
        wavg_loss.backward()
        optimizer.step()
        total_loss += wavg_loss.item()
    print(f"Loss: {total_loss:.2f}")

# 合并词嵌入矩阵与上下文嵌入矩阵,作为最终的预训练词向量
combined_embeds = model.w_embeddings.weight + model.c_embeddings.weight
save_pretrained(vocab, combined_embeds.data, "glove.vec")

内部评价方法

词的相关性

def knn(W, x, k):
    #计算查询向量x与矩阵w中每一个行向量之间的余弦相似度,
    #并返回相似度最高的k个向量
    similarities = torch.matmul(x, W.transpose(1, 0)) / (torch.norm(W, dim=1) * torch.norm(x) + 1e-9)
    knn = similarities.topk(k=k)
    return knn.values.tolist(), knn.indices.tolist()

利用该函数,可实现在词向量空间内进行近义词检索。

def find_similar_words(embeds, vocab, query, k=5):
    knn_values, knn_indices = knn(embeds, embeds[vocab[query]], k + 1)
    knn_words = vocab.convert_ids_to_tokens(knn_indices)
    print(f">>> Query word: {query}")
    for i in range(k):
        print(f"cosine similarity={knn_values[i + 1]:.4f}: {knn_words[i + 1]}")

加载斯坦福大学发布的Glove预训练词向量,下载好词向量之后,使用 load_pretrained 函数进行加载,并返回词表与词向量对象。

def load_pretrained(load_path):
    with open(load_path, "r") as fin:
        # Optional: depending on the specific format of pretrained vector file
        n, d = map(int, fin.readline().split())
        tokens = []
        embeds = []
        for line in fin:
            line = line.rstrip().split(' ')
            token, embed = line[0], list(map(float, line[1:]))
            tokens.append(token)
            embeds.append(embed)
        vocab = Vocab(tokens)
        embeds = torch.tensor(embeds, dtype=torch.float)
    return vocab, embeds

在GloVe词向量空间内以“china”“august”为查询词进行近义词检索,可以得到以下结果。

类比性

对于语法或者语义关系相同的两个词,词向量在一定程度上满足

词向量空间内的语义和语法类比推理性质示例

这两个例子分别从词义和词法两个角度展示了词向量的类比性利用这个可以进行词与词之间的关系推理,回答诸如 “a 之于 b 相当于 c 之于 ?”等问题。对于下画线处的词,可以利用下式的词向量空间内搜索得到

def find_analogy(embeds, vocab, word_a, word_b, word_c):
    vecs = embeds[vocab.convert_tokens_to_ids([word_a, word_b, word_c])]
    x = vecs[2] + vecs[1] - vecs[0]
    knn_values, knn_indices = knn(embeds, x, k=1)
    analogies = vocab.convert_ids_to_tokens(knn_indices)
    print(f">>> Query: {word_a}, {word_b}, {word_c}")
    print(f"{analogies}")

上述结果显然效果欠佳,需要语料库的大小越大越好。

外部评价方法

根据下游任务的性能指标判断

NLTK工具集

数据集

  • WordNet : 包含同义词、释义、例句等
  • SentiWordNet : Senti=Sentiment
  • Wikipedia
  • Common Crawl
  • PB 级别,7 年爬虫我的妈,使用 Facebook 的 CC-Net 工具进行处理
  • Hugging Face Datasets

Wikipedia 数据集使用方法

语料处理方法

纯文本语料抽取

pip install wikiextractor
python -m wikiextractor.WikiExtractor
python -m wikiextractor.WikiExtractor -h

中文简繁体切换

我们使用 OpenCC : 甚至可以转换日本新体字等中文字体

pip install opencc
python convert_t2s.py input_file > output_file

数据清洗

包括:删除空的成对符号,删除除了 外残留 html 标签,删除不可见控制字符等

<span class="ne-text">python wikidata_cleaning.py input_file > output_file</span>

Hugging Face Datasets 使用方法

数据集获取

<span class="ne-text">pip install datasets</span>

调用dataset

from datasets import list_datasets, load_dataset
import pprint

# dataset loading
datasets_list = list_datasets()
print(len(datasets_list))  # num_datasets
dataset = load_dataset('sst', split='train')  # load SST (Stanford Sentiment Treebank)
print(len(dataset))  # num_samples
pprint(dataset[0])  # {'label':xxx, 'sentence':xxx, 'tokens':xxx, 'tree':xxx}

调用 metrics

from datasets import list_metrics, load_metric

# metrics
metrics_list = list_metrics()
print(len(metrics_list))  # num_metrics
accuracy_metric = load_metric('accuracy')
results = accuracy_metric.compute(references= [0, 1, 0], predictions= [1, 1, 0])
print(results)  # {'accuracy': 0.6666666}

书名:自然语言处理:基于预训练模型的方法

文本的表示

独热编码表示

独热编码,就是使用一个词表的大小的向量表示一个词的“语义”,然后第i个词用wi表示。词表中第i个词在第i维上被设置为1,其余维均为0。

词的分布式表示

由于用独热编码表示词汇导致数据过于稀疏和庞大。因此John RupertFirth于1957年提出了分布式语义假设:词的含义可由其上下文的分布进行表示。

我们将以词为单位的其它词语作为上下文,因此创建下列共现频次表。

除了词,上下文的选择有很多种方式,而选择不同的上下文得到的词向量表示性质会有所不同。例如,可以使用词在句子中的一个固定窗口内的词作为其上下文,也可以使用所在的文档本身作为上下文。前者得到的词表示将更多地反映词的局部性质:具有相似词法、句法属性的词将会具有相似的向量表示。而后者将更多地反映词代表的主题信息。

直接使用与上下文的共现频次作为词的向量表示,至少存在以下三个问题:

  • 高频词往往没有什么作用,反而会影响计算机国。如"我" "。"的共现频次很高,实际上可能它们并没有关系但由于共现过,从而产生了较高的相似度。
  • 共现频次无法反映词之前的高阶关系。假设词“A”与“B”共现过,“B”与“C”共现过,“C”与“D”共现过,通过共现频次,只能获知“A”与“C”都与“B”共现过,它们之间存在一定的关系,而“A”与“D”这种高阶的关系则无法知晓。
  • 仍然存在稀疏性的问题。即向量中仍有大量的值为0

解决高频词误导计算结果的问题

点互信息

由于共矩阵仍然存在稀疏性的问题。即向量中仍有大量的值为0。最直接的想法就是:如果一个词与很多词共现,则降低其权重;反之,如果一个词只与个别词共现,则提高其权重。信息论中的点互信息(Pointwise Mutual Information,PMI)恰好能够做到这一点。

对于词w和上下文c,其PMI为:

式中,P (w, c)、P (w)、P (c)分别是w与c的共现概率,以及w和c分别出现的概率。可见,通过PMI公式计算,如果w和c的共现概率(与频次正相关)较高,但是w或者c出现的概率也较高(高频词),则最终的PMI值会变小;反之,即便w和c的共现概率不高,但是w或者c出现的概率较低(低频词),则最终的PMI值也可能会比较大。从而较好地解决高频词误导计算结果的问题。

当某个词与上下文之间共现次数较低时,可能会得到负的PMI值。考虑到这种情况下的PMI不太稳定(具有较大的方差),在实际应用中通常采用PPMI (Positive PMI)的形式,即:

奇异值分解

解决共现频次无法反映词之间高阶关系的问题。相关的技术有很多,对共现矩阵M进行奇异值分解。

词的分布式表示取得了不错的效果,但是其仍然存在一些问题。

  • 当共现矩阵规模较大时,奇异值分解的运行速度非常慢;
  • 如果想在原来语料库的基础上增加更多的数据,则需要重新运行奇异值分解算法,代价非常高;
  • 分布式表示只能用于表示比较短的单元,如词或短语等,如果待表示的单元比较长,如段落、句子等,由于与其共现的上下文会非常少,则无法获得有效的分布式表示;
  • 最后,分布式表示一旦训练完成,则无法修改,也就是说,无法根据具体的任务调整其表示方式。为了解决这些问题,可引入一种新的词表示方式——词嵌入表示。

词嵌入表示(Word Embedding)

使用一个连续、低维、稠密的向量来表示词,经常直接简称为词向量

词向量中的向量值,是随着目标任务的优化过程自动调整的,也就是说,可以将词向量中的向量值看作模型的参数

词袋表示

所谓词袋表示,就是假设文本中的词语是没有顺序的集合,将文本中的全部词所对应的向量表示(既可以是独热表示,也可以是分布式表示或词向量)相加,即构成了文本的向量表示。如在使用独热表示时,文本向量表示的每一维恰好是相应的词在文本中出现的次数。

nlp任务

语言模型(统计语言模型)

N元语言模型

基本任务是在给定词序列w1w2··· wt−1的条件下,对下一时刻t可能出现的词wt的条件概率P (wt|w1w2···wt−1)进行估计。一般地,把w1w2··· wt−1称为wt的历史。

随着句子长度增加,w1:i−1出现的次数会越来越少,甚至从未出现过,那么P (wi|w1:i−1)则很可能为0,此时对于概率估计就没有意义了。为了解决该问题,可以假设“下一个词出现的概率只依赖于它前面n−1个词”。

马尔可夫假设

  1. N-gram :N 元语言模型
  • 马尔可夫假设 :
  • 满足该假设称为:N元语法或文法(gram)模型
  • n=1 的 unigram 独立于历史(之前的序列),因此语序无关
  • n=2 的 bigram 也被称为一阶马尔可夫链
  • w0 可以是 可以是
  1. 平滑: 解决未登录词 (OOV, Out-Of-Vocabulary, )的零概率问题
  • 折扣法 :高频补低频(频繁出现的N-gram中匀出一部分概率并分配给低频次(含零频次)的N-gram)
  • 加1平滑 :拉普拉斯平滑

对于unigram:

对于biggram:

也可以使用 +δ 平滑,尤其当训练数据较小时,加一太大了

关于 δ 选择,可以使用验证集对不同值的困惑度比较选择最优参数

  1. 模型评价

1.外部任务评价:计算代价高,实现的难度较大

2.内部评价方法: 基于困惑度 (Perplexity, PPL) ,越小越好

: 测试集到每个词的概率的几何平均值的倒数

测试集到每个词这里针对一个句子而言:我们的目标是使测试集中的所有句子 PPL 最小。

困惑度越低的语言模型并不总是能在外部任务上取得更好的性能指标,但是两者之间通常呈现出一定的正相关性。应用在下游任务之后,关键要看具体任务上的表现。

基础任务

中文分词

  • 正向最多匹配算法(FMM),找当前最长词
  • 可能会造成切分歧义问题:如“哈尔滨市”可以是一个词,也可以认为“哈尔滨”是一个词,“市”是一个词。
  • 未登录词的问题比例更高:未登录词指不在词典中,但是必须要分出来的词

字词切分:词形还原(Lemmatization)或者词干提取(Stemming)

词形还原指的是将变形的词语转换为原形,如将“computing”还原为“compute”;

词干提取则是将前缀、后缀等去掉,保留词干(Stem),如“computing”的词干为“comput”,可见,词干提取的结果可能不是一个完整的单词。

  • 解决数据稀疏问题和大词表问题
  • 传统方法需要大量规则,因此:基于统计的无监督方法(使用尽量长且频次高的子词)
  • 字节对编码 (BPE)生成子词词表,然后使用贪心算法;可以使用缓存算法加快速度
  • WordPiece: 比对BPE, 不过 BPE 选频次最高对,WordPiece 选提升语言模型概率最大对。
  • Unigram Language Model (ULM) : 比对WordPiece, 不同的是,它基于减量法

SentencePiece 开源工具用于子词切分,通过将句子看做 Unicode ,从而能够处理多种语言

词性标注

词性标注(POS Tagging)任务是指给定一个句子,输出句子中每个词相应的词性。

例如,当输入句子为:"他 喜欢 下 象棋"

输出:他/PN 喜欢/VV 下/VV 象棋/NN 。/PU

斜杠后面的PN、VV、NN和PU分别代表代词、动词、名词和标点符号

难点在于歧义性,即一个词在不同的上下文中可能有不同的词性。例如,上例中的“下”,既可以表示动词,也可以表示方位词。因此,需要结合上下文确定词在句子中的具体词性。

句法分析

句法分析(Syntactic Parsing)的主要目标是给定一个句子,分析句子的句法成分信息,例如主谓宾定状补等成分。

最终的目标是将词序列表示的句子转换成树状结构,从而有助于更准确地理解句子的含义,并辅助下游自然语言处理任务

  • 树状结构的主谓宾定状补等
  • 两种句法结构表示:不同点在于依托的文法规则不同

    • 短语结构句法表示:上下文无关文法,层次性的表示法
    • 依存结构句法表示 (DSP):依托依存文法规则

语义分析

词义消歧WSD

从词语的粒度考虑,一个词语可能具有多种语义(词义),例如“打”,含义即可能是“攻击”(如“打人”),还可能是“玩”(如“打篮球”),甚至“编织”(如“打毛衣”)等。根据词语出现的不同上下文,确定其具体含义的自然语言处理任务被称为词义消歧(Word SenseDisambiguation,WSD)。可以使用 WordNet 等语义词典

语义角色标注 SRL : 谓词论元结构

识别谓词后找到论元(语义角色)(施事 Agent 受事 Patient)

附加语义角色: 状语、副词等

语义依存分析SDP:通用图

  • 语义依存图:词作为节点,词词关系作为语义关系边
  • 概念语义图:首先将句子转化为虚拟的概念节点,然后建立语义关系边

专门任务:如自然语言转 SQL

应用任务

信息抽取(Information Extraction,IE)

信息抽取 ,定义:从非结构化的文本中自动提取结构化信息的过程,另外还可以将抽取的记过作为新的知识加入知识库中。

命名实体识别(Information Extraction,IE)

定义:在文本中抽取每个提及的命名实体并标注其类型,一般包括人名、地名和机构名等,也包括专有名称等,如书名、电影名和药物名等。然后往往需要将命名实体链接到知识库或者知识图谱中的具体实体,被称作 实体链接

如“华盛顿”既可以指美国首任总统,也可以指美国首都,需要根据上下文进行判断,这一过程类似于词义消歧任务。

关系抽取(Relation Extraction)

定义:用于识别和分类文本中提及的实体之间的语义关系,如夫妻、子女、工作单位和地理空间上的位置关系等二元关系。

事件抽取(Event Extraction)

从文本中识别人们感兴趣的事件以及事件所涉及的时间、地点和人物等关键元素。事件往往使用文本中提及的具体触发词(Trigger)定义,解析时间、地点、人物等关键因素。

时间表达式(Temporal Expression)

事件发生的时间往往比较关键,通常时间表达式识别被认为是重要的信息抽取子任务。

绝对时间:日期、星期、月份和节假日等

相对时间:明天、两年前等

~SRL : 谓词~Trigger, 论元~事件元素

假设下列句子进行信息抽取

信息抽取结果

情感分析

  1. 情感分类(识别文本中蕴含的情感类型或者情感强度,其中,文本既可以是句子,也可以是篇章)
  2. 情感信息抽取(抽取文本中的情感元素,如评价词语、评价对象和评价搭配等)

如图中用户评论

情感分析结果如下:

问答系统(Question Answering,QA)

系统接收用户以自然语言形式描述问题,并从异构数据中通过检索、匹配和推理等技术获得答案的自然语言处理系统。

根据数据来源的不同,问答系统可以分为4种主要的类型:

  • 检索式:答案来源于归哪个的文本预料库,系统查找相关文档抽取答案并完成回答
  • 知识库:问题→结构化查询语句 →结构化知识存储→推理→答案
  • 常见问题集:对历史积累的常见问题集检索,回答用户提出的类似问题
  • 阅读理解式:抽取给定文档中片段或生成

机器翻译(Machine Translation,MT)

  • 任意时间
  • 任意地点
  • 任意语言

对话系统

用户与计算机通过多轮交互的方式实现特定目标的智能系统。

  1. 任务型:垂直领域的自动业务经理,具有明确的任务目标,如完成机票预订、天气查询等特定的任务。

自然语言理解→对话管理→自然语言生成

NLU : 领域(什么东西)、意图(要干什么)、槽值(?=?)等

DM : 对话状态跟踪 DST 和对话策略优化 DPO,对话状态往往表示为槽值列表

NLG : 有了 DPO 后比较简单,只需要套用问题模板即可

  1. 开放域:聊天系统或者聊天机器人

基本问题

文本分类问题

定义:针对一段文本输入,输出该文本所属的类别

  1. 文本匹配(Text Matching),即判断两段输入文本之间的匹配关系,包括复述关系(Paraphrasing:判断两个表述不同的文本语义是否相同)
  2. 蕴含关系(Entailment:根据一个前提文本,推断与假设文本之间的蕴含或矛盾关系)等。一种转换的方法是将两段文本直接拼接起来,然后按复述或非复述、蕴含或矛盾等关系分类。

结构预测问题

序列标注(Sqquence Labeling)

为输入文本序列中的每个词标注相应的标签,如词性标注是为每个词标注一个词性标签,包括名词、动词和形容词等。其中,输入词和输出标签数目相同且一一对应。

序列标注问题可以简单地看成多个独立的文本分类问题,即针对每个词提取特征,然后进行标签分类,并不考虑输出标签之间的关系。

  1. CRF模型:最广泛应用的序列标注模型,他不仅考虑每个词属于某一标签的概率(发射概率),还考虑标签之间的相互关系(转移概率)。

  1. RNN(循环神经网络)+CRF(条件随机场)

序列分割

  • 分词、NER 等
  • 也可以看成序列标注

NER : B-xxx 表示开始,I-xxx 表示中间,O-xxx 表示非实体

分词同理

图结构生成

  1. 基于图的算法:最小生成树,最小子图等
  2. 基于转移的算法:图→ 状态转移序列,状态→策略→动作等。

使用序列标注方法解决序列分割(分词和命名实体识别)

如用于 DSP 的 标准弧转移算法

转移状态由一个栈和队列组成, 栈存依存结构子树序列,队列存未处理的词

初始转移状态:栈为空

转移动作:

  • 移进 Shift (SH) : 将队列中的第一个元素移入栈顶,形成一个仅包含一个节点的依存子树
  • 左弧归约 Reduce Left (RL) : 将栈顶的两棵依存子树采用一个左弧S1↶S0进行合并,然后S1下栈;
  • 将栈顶的两棵依存子树采用一个右弧S1↷S0进行合并,然后S0下栈。
  • 完成 FIN

弧上的句法关系可以在生成弧的时候(即 RR 或 RL)采用额外的句法关系分类器加以预测

该算法也可以用于短语结构的句法分析方法

面向依存句法分析的标准弧转移算法中的三种动作

序列到序列问题

Encoder-Decoder架构

评价指标

准确率(Accuracy)

最简单、直观的评价指标,经常被应用于文本分类等问题。其计算公式为:

词性标注等序列标注问题也可以采用准确率进行评价,即:

并非全部的序列标注问题都可以采用准确率进行评价,如在将分词、命名实体识别等序列分割问题转化为序列标注问题后,就不应该使用准确率进行评价。

命名实体识别,序列标注的输出标签可以为一个实体的开始(B-XXX)、中间(I-XXX)或者非实体(O)等,其中B代表开始(Begin)、I代表中间(Inside),O代表其他(Other),XXX代表实体的类型,如人名(PER)、地名(LOC)和机构名(ORG)等

F -score

  1. Ner中:

  1. 在句法依存树中:
  • UAS :(unlabeled attachment score): 即准确率,父节点被正确识别的概率
  • LAS :父节点被正确识别且与父节点的关系也正确的概率
  1. 在 Semantic Dependency Graph :多个父节点不能用上述
  • F-score : 图中的弧为单位,计算识别的精确率和召回率
  • 可分为考虑和不考虑语义关系两种情况
  1. 在短语结构句法分析中:也不能用准确率
  • F-score : 句法结构中包含短语的 F 值进行评价
  • 包含短语:包括短语类型以及短语所覆盖的范围

Q1:邓小平的科学发展观在当时面临世界新形势下的中国处于什么地位?

何苑民

科学技术在新形势下有中国特色社会主义事业中处于根本地位,要发展这一事业,就不可能离开科学技术。科学技术是第一生产力,这是一个时代命题。邓小平讲科学技术的发展一日千里,这里的科学技术就是指高新科学技术。邓小平立足使有中国特色的社会主义在发展中赢得和世界资本主义相比较的优势,思考如何发展社会主义社会的生产力,如何增强社会主义国家的综合国力,如何提高人民的生活水平,总起来说就是一句话,“发展才是硬道理”。这个硬道理,就是高新科学技术的发展。

Q2:邓小平的科学发展观对于当时的中国起到了什么作用?

杨智涵

邓小平的科学发展观思想奠定了科学发展观的理论基石。科学发展观第一次把坚持以人为本,促进人的全面发展作为发展观的核心,使中国和中国社会发展目的更加明确,为新时期中国如何发展指明了方向。--杨智涵

Q3:科技生产的同时也要注重科技伦理教育,那么由此引发思考,我们大学的科技伦理教育课程应该注重哪些点呢?

余文成

工学类专业课程,要注重强化学生工程伦理教育,培养学生精益求精的大国工匠精神,激发学生科技报国的家国情怀和使命担当。

农学类专业课程,则要在课程教学中加强生态文明教育,引导学生树立和践行绿水青山就是金山银山的理念。要注重培养学生的“大国三农”情怀,引导学生以强农兴农为己任,“懂农业、爱农村、爱农民”,树立把论文写在祖国大地上的意识和信念,增强学生服务农业农村现代化、服务乡村全面振兴的使命感和责任感,培养知农爱农创新人才。

理学类专业课程,要注重科学思维方法的训练和科学伦理的教育,培养学生探索未知、追求真理、勇攀科学高峰的责任感和使命感。

医学类专业课程,要在课程教学中注重加强医德医风教育,着力培养学生“敬佑生命、救死扶伤、甘于奉献、大爱无疆”的医者精神,注重加强医者仁心教育,在培养精湛医术的同时,教育引导学生始终把人民群众生命安全和身体健康放在首位,尊重患者,善于沟通,提升综合素养和人文修养,提升依法应对重大突发公共卫生事件能力,做党和人民信赖的好医生。

Q4:在知识经济时代,如何理解人才资本是第一资本?

罗欢

人才资本是一种显性社会经济价值,主要影响因素有人才的数量、质量、知识、能力,及贡献大小。

第二种:人才资本是指以人才的数量、质量和知识技能水平、相关工作能力,尤其是具有创造性、创新性的劳动成果以及对人类发展作出较大贡献所显现的一种能力资本,体现在两个方面,一方面是人才本身,另一方面是社会效益。

第三种:人的智慧、知识、技能等创新性因素,集合人力资本的相关内容形成了人才资本,是对人类做出贡献价值的一种智力资源,其形成的条件是具有一定的人力资本。那些具有较高的管理才能、较强的技术能力、敢于创新、思想紧跟时代步伐、有勇有谋,最终取得最大效益的人才所构成的人力资本也就是人才资本。--罗欢

吴仙海

我认为人才资源资源分为人力资源、自然资源、资本资源、资讯资源4大类。21世纪竞争的焦点是科技与知识的竞争。科技与知识的竞争就是人才(人力资源)的竞争。人力资源已经是推动经济增长、社会发展、企业发展的第一资源。

对人力资源应该遵循以下原则进行管理:

1、互补增值原则

互补增值原则是指充分发挥每个个体的优势,采用协调与优化的办法,扬长避短,使人力资源管理功能达到最优。

互补的形式有:知识互补、能力互补、性格互补、技能互补等。

2、公平竞争的原则

竞争各方遵循相同的规则,公平、公正、公开地进行考核、晋升和奖励的竞争方式,目的是培养和激发人的进取心、毅力和创新精神,使人们全面施展自己的才能,达到服务社会,促进经济发展的目的。

3、系统优化原则。

人力资源系统经过组织、协调、执行、控制,使其整体获得最优绩效的准则

4、能级对应原则

在人力资源管理中,要根据人的能力安排工作、岗位和职位,使人尽其才、物尽其用。

5、激励强化的原则

激发员工动机,调动人的主观能动性,强化期望行为,从而显著地提高劳动生产效率。

6、弹性冗余原则

弹性一般都有一个“弹性度”,超过这个“度”,弹性就要丧失。

人力源管理也是一样。职工的劳动强度、工作时间、工作定额都有一定的“度”,任何超过这个“度”的管理,会使员工身心交瘁,疲惫不堪,精神萎靡。

弹性冗余原则强调在充分发挥和调动人力资源的能力、动力、潜力的基础上,主张松紧合理、张弛有度,使人们更有效、更健康地开展工作。

Q5:我国为什么要重视科技和教育的发展?

简夜明

因为在科技与教育建设中,人力资源建设是关键,而在人力资源建设中应包含基础教育、职业教育、高层次人才培养、高层次人才保持和使用、加强终身教育中的科学教育、以及大力开展科普工作等。

首先,教育需要可持续发展,同时教育需要改革,需要为可持续发展的实现而保证有质量的全民教育的实现而进行改革。

在科学能力建设中,最基础和最具战略性的任务是全民科学素质的提高,包括5-18 岁青少年的科学教育。

国民应具备科学素质,首先是个人和国家生存和发展的需要。

科学技术不仅是我们了解世界和我们自身的有力工具,也是强有力的促进人类社会发展的第一生产力。特别是自上世纪五十年代以后,科学技术发展十分迅速,使人类的生活质量、活动范围、通讯方式和生产方式等都发生了革命性的变化。

所以,无论对国家、社会、还是个人而言,掌握科学知识和技术,是能够在知识社会里生存并参与发展的基础,是国家创新能力建设的基础。

肖亿

我觉得当今的世界正处于人类历史上又一个重要的历史转变时期.科学和技术的飞速发展,以及经济全球化的推进,使得人类社会正在向以知识为基础的社会转化。

在以知识为基础的社会里,知识不仅成为影响经济的主要因素,而且将广泛而深刻地影响着社会的各个方面,包括经济、政治、文化、教育、科技和军事等。

例如说,国家和地区应发展什么经济,应采取什么环境保护措施是适当的,等等,不具备一定的科学素质,就无法对这些问题参与讨论和发表意见。

因而,不管我们从事什么职业,就应当要履行公民的义务和权利,参与国家的政治、经济生活决策,就必须具有良好的科学素质。

只有提高了公民的科学素质,才有可能谈论可持续发展中公众的参与,而只有公众参与,才会有社会的可持续发展。

Q6:为什么说科学技术成为推动经济发展的决定因素?

漆仲黎

第一,科学技术对经济发展起首要的变革作用.现代科学技术广泛渗透到经济活动中,渗透到社会生产的各个环节,决定了它成为推动经济发展的决定性因素.科学技术不只是使经济在量上即规模和速度上迅速增长,也使经济发生质的飞跃,在经济结构、劳动结构、产业结构、经营方式等方面发生了变革.

第二,科学技术在生产力诸要素中起着第一位的作用.第二次世界大战之后,科学技术以空前的规模和速度进人生产,使生产力成为一个复杂的体系.在这个体系中,它自身不但直接体现为生产力,而且它作用于其他诸因素,提高劳动者的素质,促进生产工具和生产工艺的进步,扩大了劳动对象的来源和种类,从而成为推动社会生产力的重要力量.

第三,现代科学使管理日趋科学化、现代化.在社会生产力的发展中,使物的要素和人要素有机结合,即管理是使潜在生产力变为现实生产力的关键.科学技术与经济广泛结合,使得管理成为生产力的重要范畴.

科学技术是生产力,这是马克思主义历来的观点.早在100多年前,马克思就说过,机器生产的发展要求自觉地应用自然科学,并且指出:"生产力中也包括科学."现代科学技术的发展,使科学与生产力的关系越来越密切了.科学技术作为生产力,越来越显示出巨大的作用。

Q7:为什么需要学习外来的先进技术

黎佳亿

科学技术是人类共同创造的财富:

首先,科学技术本身没有阶级性。其次,从价值观和大环境的角度来看,不论人们身处何国、信仰如何、是否愿意,实际上已经处在一个命运共同体中。任何科学技术的创新既是对过去所学知识的传承与发扬,又是大环境下演练出的劳动与实践的结果,也终将反馈并服务于大环境。所以,科学技术是人类共同创造的财富。

侧边栏倒计时

  <h5 class="widget-title m-t-none text-md">    
   <svg t="1629119495433" class="icon" viewbox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7053" width="30" height="30">
    <path d="M961.468008 336.875396l-8.529899-1.963525 1.969706-8.509296c12.698012-54.853022-33.927984-105.255662-88.723316-97.727098-44.483219 6.113095-77.504644 30.413006-107.305722 63.399404H480.282785V660.016419h139.296966c12.689771 55.143533 62.18173 112.170237 95.267026 142.941746 79.754559 74.18334 161.639533 20.475879 159.276297-47.029827l-0.304933-8.725634 8.750358-0.304933c86.809239-3.024612 132.423598-135.51208-38.883155-207.657723 45.888386-3.997103 91.81798-17.867461 122.105304-36.744499 18.43406-11.486519 33.482946-26.331429 44.852024-43.240821 30.77769-45.779187 5.878213-109.701924-49.174664-122.379332z" fill="#FFB5C0" p-id="7054"></path>
    <path d="M756.398101 129.489642c-24.361722-49.477537-98.285457-59.929755-136.81835-18.71633-38.532893-41.211364-112.456628-30.759147-136.818351 18.71633-22.258093 45.204346-17.490414 114.06371-2.480676 162.585239-29.801078-32.986398-62.824563-57.28631-107.305722-63.399404-54.795332-7.528563-101.421328 42.874076-88.723316 97.727098l1.969706 8.509296-8.529899 1.963525c-55.052877 12.675348-79.952354 76.598085-49.172604 122.377272 11.369078 16.909392 26.417964 31.754302 44.852024 43.240821 30.287324 18.877038 76.214857 32.747396 122.105304 36.744499-171.306753 72.143581-125.692394 204.633111-38.883155 207.657722l8.750359 0.304934-0.304934 8.725634c-2.363235 67.505706 79.521738 121.213167 159.276298 47.029827 33.085296-30.773569 82.577256-87.800274 95.267026-142.941747-3.805489-15.221956-3.490254-69.683509-5.157087-85.123863l81.400789-220.780169 63.053264-62.035445c15.009738-48.521529 19.775356-117.378833-2.480676-162.585239z" fill="#FF8E9E" p-id="7055"></path>
    <path d="M695.825513 354.110326h-82.463936l1.063147 220.780169c2.060362 0.103018-2.085087 0 0 0 68.274221 0 128.778817-55.190922 128.778817-123.467203-0.00206-39.503324-18.535018-74.681948-47.378028-97.312966z" fill="#FFDBE0" p-id="7056"></path>
    <path d="M619.579751 327.803622c-68.274221 0-123.62173 55.347509-123.621731 123.62173 0 66.189135 53.081111 120.226254 118.464644 123.467203 0-82.449513 19.622889-163.454712 81.400789-220.780169a123.077795 123.077795 0 0 0-76.243702-26.308764z" fill="#FFB5C0" p-id="7057"></path>
    <path d="M456.835863 451.425352a247.793577 247.793577 0 0 0-60.992901-7.588314c-107.734278 0-199.368885 68.855243-233.346318 164.952596v0.010302C73.950519 609.974342 0 682.309537 0 773.779316c0 91.111276 73.859863 164.969078 164.971139 164.969078h351.034205V510.594833z" fill="#D4D4FF" p-id="7058"></path>
    <path d="M643.265674 691.286535c-0.006181-115.870648-79.445505-212.728274-186.429811-239.861183-107.132652 27.200901-186.388604 124.276926-186.388603 239.871485 0 136.035412 109.765795 246.419316 245.558084 247.453618h127.629135c68.333972 0 123.728869-55.394897 123.728869-123.728869-0.00206-69.228169-56.579606-124.095614-124.097674-123.735051z" fill="#EFEDFF" p-id="7059"></path>
    <path d="M178.046197 749.17035a61.810865 44.297787 0 1 0 123.621731 0 61.810865 44.297787 0 1 0-123.621731 0Z" fill="#FF8E9E" p-id="7060"></path>
    <path d="M495.341972 749.17035a61.810865 44.297787 0 1 0 123.62173 0 61.810865 44.297787 0 1 0-123.62173 0Z" fill="#FF8E9E" p-id="7061"></path>
    <path d="M269.839453 666.003831a15.452716 15.452716 0 0 0-15.452717 15.452716v24.724346c0 8.53608 6.918696 15.452716 15.452717 15.452717s15.452716-6.916636 15.452716-15.452717v-24.724346a15.452716 15.452716 0 0 0-15.452716-15.452716zM527.172507 666.003831a15.452716 15.452716 0 0 0-15.452716 15.452716v24.724346c0 8.53608 6.918696 15.452716 15.452716 15.452717s15.452716-6.916636 15.452716-15.452717v-24.724346a15.452716 15.452716 0 0 0-15.452716-15.452716zM442.580217 693.796056a15.452716 15.452716 0 0 0-21.802752 1.497884 29.545594 29.545594 0 0 1-22.272515 10.153464 29.549714 29.549714 0 0 1-22.274576-10.153464 15.452716 15.452716 0 1 0-23.298575 20.304869c11.494761 13.188378 28.1054 20.751968 45.57109 20.751968s34.07633-7.56359 45.571091-20.751968a15.450656 15.450656 0 0 0-1.493763-21.802753z" fill="#313D40" p-id="7062"></path>
   </svg>2024年毕业时间:</h5>  
  <center>
   <img src="https://www.ilfishs.com/littlefish/sclool_logo.png" weight="130" height="60" />
  </center> 
  <style>  .gn_box{     border: none;     border-radius: 10px; }  .gn_box {     padding: 8px 10px;     margin: 8px;     margin-bottom: 15px;     text-align: center;     background-color: ; }  #t_d{     color: grey;     font-size: 18px; }  #t_h{     color: grey;     font-size: 18px; }  #t_m{     color: grey;     font-size: 18px; }  #t_s{     color: grey;     font-size: 18px; }  </style> 
  <div class="gn_box"> 
   <h3>距2024年毕业还有</h3>
   <center> 
    <div id="CountMsg" class="HotDate">
     <span id="t_d"> 天</span>
     <span id="t_h"> 时</span>
     <span id="t_m"> 分</span>
     <span id="t_s"> 秒</span>
    </div>
   </center> 
   <script type="text/javascript">  function getRTime() {        var EndTime = new Date('2024/07/01 00:09:00');       var NowTime = new Date();       var t = EndTime.getTime() - NowTime.getTime();              var d = Math.floor(t / 1000 / 60 / 60 / 24);              var h = Math.floor(t / 1000 / 60 / 60 % 24);              var m = Math.floor(t / 1000 / 60 % 60);              var s = Math.floor(t / 1000 % 60);      var day = document.getElementById("t_d");     if (day != null) {         day.innerHTML = d + " 天";        }     var hour = document.getElementById("t_h");     if (hour != null) {         hour.innerHTML = h + " 时";       }     var min = document.getElementById("t_m");     if (min != null) {         min.innerHTML = m + " 分";        }     var sec = document.getElementById("t_s");     if (sec != null) {         sec.innerHTML = s + " 秒";     } }      setInterval(getRTime, 1000);      </script> 
  </div> 

将代码上传至component/sidebar.php中下列代码之后

<div id="sidebar">
         <?php if (@!in_array('column', Utils::checkArray($this->options->sidebarSetting))): ?>

给handsome文章页添加版权信息

在handsome 主题后台提供 CSS 代码的编辑中,在 设置外观 -> 开发者设置 -> 自定义CSS 对话框中加入以下代码即可!

/*版权信息&正文结束分割线 CSS*/
.cutline {
    border-top: 1px dotted #ccc;
    height: 1px;
    margin: 20px 0;
    text-align: center;
    width: 100%;
}
.cutline span {
    background-color: rgb(236, 237, 238);
    border: 1px solid #d6d6d6;
    font: 12px Arial,Microsoft JhengHei;
    padding: 2px 4px;
    position: relative;
    top: -10px;
}
.post-copyright {
    font-size: 13px;
    margin: 8px 0;
    padding: 10px;
    border-left: 4px solid #ff5252;
    background-color: rgba(220, 220, 220, 0.1);
    list-style: none;
    word-break: break-all;
    position: relative;
    overflow: hidden;
}
.post-copyright li {
    display: list-item;
    text-align: -webkit-match-parent;
}
.post-copyright a {
    color: rgba(0, 120, 231, 1);
    text-decoration: none;
    transition: color .1s;
}

首页头像呼吸灯加自动旋转

/*首页头像呼吸灯自动旋转*/
.avatar {
    width: 100px;
    border-radius: 50%;
    animation: light 4s ease-in-out infinite;
    transition: 0.5s;
}

.avatar:hover {
    transform: scale(1.15) rotate(720deg);
}
@keyframes light {
    0% {
        box-shadow: 0 0 4px #f00;
    }

    25% {
        box-shadow: 0 0 16px #0f0;
    }

    50% {
        box-shadow: 0 0 4px #00f;
    }

    75% {
        box-shadow: 0 0 16px #0f0;
    }

    100% {
        box-shadow: 0 0 4px #f00;
    }
}

打赏图标浮动

.btn-pay {
    animation: star 0.5s ease-in-out infinite alternate;
}

@keyframes star {
    from {
        transform: scale(1);
    }

    to {
        transform: scale(1.1);
    }
}