1.5自然语言的分布式表示-word2vec学习的实现

news2024/12/24 21:32:52

文章目录

  • 0写在前面
  • 1优化器
  • 2训练类的构建
    • 2.1训练类的初始化
    • 2.2训练类的拟合模块
      • 2.2.1去重操作remove_duplicate
      • 2.2.2反向传播过程
        • 2.2.2.1 SoftmaxWithLoss层的反向传播
        • 2.2.2.2其它层的反向传播
    • 2.3训练类的损失可视化模块
  • 3训练结果输出

0写在前面

  1. 之前的笔记中,我们已经基于数据构建了语料库、单词与ID之间的映射;并基于语料库(就一个句子)构建了CBOW模型学习所需要的数据,即上下文及其对应的目标词;并将其转换为独热编码的形式;
  2. 接着,我们也构建了简单CBOW模型的网络结构;并根据创建的CBOW模型类创建了模型实例;
  3. 接下来我们来看让模型进行学习的代码实现

1优化器

  1. 本书中提供了多个不同优化器的简单实现;关于优化器的细节这里就不说了;网上也有很多博客;可自行学习;

  2. 优化器的代码如下;根据代码可以知道优化器的作用为:根据给定的学习率、计算的梯度、参数更新公式对参数进行更新;这里的Adam优化器中还在每一个时间步长先对学习率进行了调整。

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = [], []
            for param in params:
                self.m.append(np.zeros_like(param))
                self.v.append(np.zeros_like(param))
    
        self.iter += 1
        # 调整学习率
        lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
    
        for i in range(len(params)):
            self.m[i] += (1 - self.beta1) * (grads[i] - self.m[i])
            self.v[i] += (1 - self.beta2) * (grads[i]**2 - self.v[i])
    				# 用调整后的学习率更新每个参数
            params[i] -= lr_t * self.m[i] / (np.sqrt(self.v[i]) + 1e-7)
    
  3. 有了优化器,就可以构建一个优化器的实例:optimizer = Adam()

  4. 然后我们就可以将之前创建的CBOW模型实例CBOW_model和优化器实例optimizer传入到训练类Trainer中,进行学习;

2训练类的构建

包括模型训练拟合、损失可视化模块。

2.1训练类的初始化

  1. 初始化函数如下所示:

    class Trainer:
        def __init__(self, model, optimizer):
            self.model = model  # CBOW模型实例对象
            self.optimizer = optimizer  # 优化器实例对象
            self.loss_list = []  # 存放训练过程中的损失
            self.eval_interval = None  # 评价间隔;即每隔多少个epoch计算一次训练的平均损失
            self.current_epoch = 0  # 当前是第几个epoch
    

2.2训练类的拟合模块

  1. 需要传入的参数有:

    1. x:输入数据;即上下文单词,维度为[6,2,7]
    2. t:真实标签;即目标词向量;维度为[6,7]
    3. max_epoch: 最大迭代轮数
    4. batch_size: 批处理大小
  2. 在开始迭代训练前需要做以下事情:

    def fit(self, x, t, max_epoch=10, batch_size=32, max_grad=None, eval_interval=20):
        '''
    
        :param x:输入数据;即上下文单词,维度为[6,2,7]
        :param t:真实标签;即目标词向量;维度为[6,7]
        :param max_epoch: 最大迭代轮数
        :param batch_size: 批处理大小
        :param max_grad: 最大梯度;用于梯度裁剪
        :param eval_interval: 评价间隔;即每隔多少个epoch计算一次训练的平均损失
        :return:
        '''
        data_size = len(x)
        max_iters = data_size // batch_size # 每个epoch训练完所有数据所需要的step
        self.eval_interval = eval_interval
        model, optimizer = self.model, self.optimizer
        total_loss = 0
        loss_count = 0
    
    1. 主要是计算max_iters;因为每一个epoch所有的训练数据都是要进行训练的;
    2. len(x)x是一个维度为[6,2,7]的ndarray数组,使用len方法则返回第一个维度的大小;即数据总量;
  3. 开始训练时每一个epoch的数据要进行打乱:

    for epoch in range(max_epoch):
        # 每个epoch,打乱数据集的顺序
        idx = numpy.random.permutation(numpy.arange(data_size)) # (6,)一维数组
        # 基于打乱的顺序重新组织输入和真实标签
        x = x[idx] # idx这个一维数组可以起到索引[6,2,7]的x的第一个维度,实现shuffle
        t = t[idx]
    

    在这里插入图片描述

    1. 通过随机化数据顺序,每次训练时模型接收到的数据分布都略有不同,这有助于模型学习到更加鲁棒的特征,从而在未见过的数据上表现更好
    2. 在pytorch里面可以直接通过设置shuffle属性来实现同样的目的;这里因为全部都是手动实现,所以需要自己写一下;
  4. 然后在当前epoch中,遍历所有的数据,每次取出一个mini-batch的数据:

    for iters in range(max_iters):
        # 遍历数据集,每次取出一个batch的数据
        # [0,batch_size),[batch_size,2*batch_size),[2*batch_size,3*batch_size),...
        batch_x = x[iters * batch_size:(iters + 1) * batch_size] # [3,2,7]
        batch_t = t[iters * batch_size:(iters + 1) * batch_size] # [3,7]
    
  5. 然后调用CBOW模型的forward方法进行模型的前向计算:

    1. 即依次计算输入层到中间层、合并上下文计算结果作为中间层结果;然后计算输出层、并使用softmax转换为概率;最后基于交叉熵损失计算损失值并返回;
    2. 这一过程已在1.4自然语言的分布式表示-word2vec实操-CSDN博客中讲述过;
    loss = model.forward(batch_x, batch_t)
    
  6. 然后调用SimpleCBOW类的backward方法,进行梯度的反向传播【稍后讲述过程】

    model.backward()
    
  7. 反向传播之后,模型的梯度会被保存;此时model.params, model.grads的结构如下所示:

    1. 都是列表;里面分别是两个输入层、一个输出层的参数和梯度;

    在这里插入图片描述

  8. 【重点】前面说本书中窗口大小为1时输入有两个向量,因此需要使用两个输入层;而本书采用的方式为构建了两个输入层,这两个输入层共享权重;而共享权重存在一些问题;这一步的去重操作就是为了缓解这个问题;【稍后详解remove_duplicate

    params, grads = remove_duplicate(model.params, model.grads)
    
    1. 去重后参数和梯度就只剩俩:输入层和输出层:

      在这里插入图片描述

  9. 梯度裁剪:

    1. 这里默认不进行梯度裁剪;
    2. 梯度裁剪目的:梯度裁剪是一种在训练神经网络时常用的技术,主要用于防止梯度爆炸问题。梯度爆炸是指在训练过程中,梯度的值变得非常大,以至于更新后的权重会使模型的学习过程变得不稳定;
  10. 然后调用上述Adam优化器的update方法对参数进行更新;

  11. 最后就是记录损失;并根据设置的eval_interval输出模型训练信息:

    1. 主要是输出一下eval_interval个mini-batch的平均损失;并将平均损失存到self.loss_list
    2. 但是目前数据量只有6条;因此这里暂时默认不执行;
    def fit(self, x, t, max_epoch=10, batch_size=32, max_grad=None, eval_interval=20):
      for epoch in range(max_epoch):
        for iters in range(max_iters):
            if (eval_interval is not None) and (iters % eval_interval) == 0:
          		avg_loss = total_loss / loss_count
          		elapsed_time = time.time() - start_time
          		print('| epoch %d |  iter %d / %d | time %d[s] | loss %.2f' % (self.current_epoch + 1, iters + 1, max_iters, elapsed_time, avg_loss))
          		self.loss_list.append(float(avg_loss))
          		total_loss, loss_count = 0, 0
    
  12. 通过不断地迭代,完成模型的学习;

    1. 这里比较简单,就是完成设置的迭代轮数之后就返回了;并没有去检查是否需要提前停止之类的;
  13. 下图为训练过程中打印的损失信息:

    在这里插入图片描述

2.2.1去重操作remove_duplicate

  1. 去重思路为:遍历参数列表里的每一项,将该项与其他项进行比较;如果相同则删除一个;直到没有重复的参数为止;代码如下:

    def remove_duplicate(params, grads):
        '''
        将参数列表中重复的权重整合为1个,
        加上与该权重对应的梯度
        '''
        params, grads = params[:], grads[:]  # copy list
    
        while True:
            find_flg = False
            L = len(params) # 总参数个数为3
    
            for i in range(0, L - 1):
                for j in range(i + 1, L):
                    # 在共享权重的情况下就累加这两相同参数的梯度
                    if params[i] is params[j]:
                        grads[i] += grads[j]  # 加上梯度
                        find_flg = True
                        # 删除掉其中一份重复的参数及其梯度
                        params.pop(j)
                        grads.pop(j)
                    # 在作为转置矩阵共享权重的情况下(weight tying)
                    elif params[i].ndim == 2 and params[j].ndim == 2 and params[i].T.shape == params[j].shape and np.all(params[i].T == params[j]):
                        grads[i] += grads[j].T
                        find_flg = True
                        params.pop(j)
                        grads.pop(j)
    
                    if find_flg: break
                if find_flg: break
    
            if not find_flg: break
    
        return params, grads
    
  2. 一些需要解释和注意的地方:

    1. 存在两种共享权重的情况
      1. 像本书的例子中这样,输入层有两个,维度相同,共享权重;
      2. 还存在一种是编码器和解码器共享权重,多出现于自编码器中
        1. 在自编码器中,编码器负责将输入数据编码到一个较低维度的隐藏表示中,而解码器则将这个隐藏表示解码回原始数据空间。通过共享权重,即让编码器和解码器使用相同(或某些情况下为转置关系)的权重矩阵,可以帮助模型学习到更加有效的数据表示,同时减少模型的参数数量
    2. 代码中设置了find_flg参数,每当发现一个重复项时就会设置find_flg = True从而跳出两层循环;因为如果存在相同项,会从参数和梯度列表中弹出其中一个,此时列表的大小已经发生了变化;跳出两层循环可以让循环重新开始,不遗漏任何一个重复项。
    3. 去重之前,是需要将一个重复项的梯度值累加到保留下来的那个参数项的;这和pytorch里面自动累加梯度是一致的;

2.2.2反向传播过程

  1. 关键要会画计算图;
  2. 然后掌握计算图中常用节点类型在反向传播时梯度是怎么传递的即可;
  1. 在训练类中调用反向传播时将进入SimpleCBOW类的backward方法;代码如下;

    def backward(self, dout=1):
        ds = self.loss_layer.backward(dout)
        da = self.out_layer.backward(ds)
        da = da * 0.5
        self.in_layer_0.backward(da)  # 输入层计算完最终梯度之后会将梯度保存在梯度列表里面;因此这里就不需要返回值了
        self.in_layer_1.backward(da)
    
2.2.2.1 SoftmaxWithLoss层的反向传播
  1. 先说结论:对于输入x=[a1,a2,a3],经过SoftmaxWithLoss层的计算;反向传播时传到这里的梯度为:

    1. a 1 a_1 a1节点处的梯度为:
      y 1 − t 1 = e x p ( a 1 ) S − t 1 = e x p ( a 1 ) e x p ( a 1 ) + e x p ( a 2 ) + e x p ( a 3 ) − t 1 y_1-t_1=\frac{exp(a_1)}{S}-t_1=\frac{exp(a_1)}{exp(a_1)+exp(a_2)+exp(a_3)}-t_1 y1t1=Sexp(a1)t1=exp(a1)+exp(a2)+exp(a3)exp(a1)t1
      其中[t1,t2,t3]是该条数据对应的真实标签;且是独热编码形式; y 1 y_1 y1就是独热编码向量中当前位置经过softmax概率化之后的结果;

    2. 类似地, a 2 a_2 a2 a 3 a_3 a3节点处的梯度分别为 y 2 − t 2 y_2-t_2 y2t2 y 3 − t 3 y_3-t_3 y3t3​。

  2. 下图是SoftmaxWithLoss层的正向传播和反向传播的计算图:

    在这里插入图片描述

  1. 首先调用loss_layerbackward方法:即SoftmaxWithLoss层的反向传播;该层依次是softmax层、交叉熵损失计算层;关于这里的反向传播过程详见《深度学习入门-基于Python的理论与实现》一书的附录A,其中有详细的使用计算图进行梯度计算的过程(计算过程详见Softmax-with-Loss层的计算图);这里主要补充以下几点:

    1. 使用计算图进行反向传播时,梯度传播法则为:输入侧梯度=输出侧梯度*局部梯度;【即链式求导法则】

    2. 在复杂计算图中,计算当前结点输入侧的梯度时,就是把输入侧看做单个x,而不是x的函数;例如下图: y 1 y_1 y1是再往前的参数计算过来的,即 y 1 y_1 y1是参数的函数,而不是单纯的一个变量;但是在这里反向求梯度时就是把 y 1 y_1 y1看成与其它参数无关的变量;所以这个结点就相当于是 z = x + y + z z=x+y+z z=x+y+z​;对每个变量求偏导结果都是1,因此就用输出侧的梯度-1乘上每个分支的局部梯度即可;

      在这里插入图片描述

    3. 计算图中常见的节点类型:

      1. 加法节点:正向传播时各路分支结果正常相加得到输出;反向传播时输出层的梯度建立多个副本,分别进入输入侧各路分支;当然,这是针对局部梯度为1的情形;实际上是各路分支的梯度=输出侧梯度*当前位置的局部梯度;下图是一个例子( z = x + y z=x+y z=x+y z z z x x x y y y的偏导数都是常数1);这里是两路分支做加法,也可以多路,即求和;

        在这里插入图片描述

      2. 乘法节点(主要指 z = x y z=xy z=xy的情形,即都是变量间的乘法,不是向量矩阵的乘法):乘法节点反向传播时,因为 ∂ z ∂ x = y \frac{\partial{z}}{\partial{x}}=y xz=y ∂ z ∂ y = x \frac{\partial{z}}{\partial{y}}=x yz=x;所以乘的局部梯度正好是两个分支输入交换之后的结果;

        在这里插入图片描述

      3. 分支节点:正向传播时是直接复制到各路分支;反向传播时则是将梯度累加;如下图所示:

        在这里插入图片描述

    4. 这里的SoftmaxWithLoss层的反向传播计算图的一些补充解释:

      1. 反向传播的初始值为常数1;由此我们可以知道,当我们确定了网络结构、输出层以及损失计算函数之后,反向传播时只需要计算一次计算图中各个节点的导数;之后没读取一次mini-batch的数据,就能获得参数的梯度从而进行参数的更新;而模型输出的损失更像是我们之后用来可视化模型训练效果的数据而已;

      2. 交叉熵损失函数在求导时,默认是以e为底数的对数;

      3. softmax层的计算图中的除法节点:该结点可以用 y = 1 x y=\frac{1}{x} y=x1来表示,求导后 ∂ y ∂ x = − 1 x 2 \frac{\partial{y}}{\partial{x}}=-\frac{1}{x^2} xy=x21,结合计算图中的符号,局部梯度为 − 1 S 2 -\frac{1}{S^2} S21;由于在正向传播时这个除法计算完之后形成了三个分支,因此反向传播时要先累加梯度,再乘上局部梯度;如下图所示:

        在这里插入图片描述

        在这里插入图片描述

      4. softmax层的计算图中的exp节点:注意这里是先计算了指数,再形成两个分支的;因此需要先累加梯度;然后乘上exp节点的局部梯度;

        在这里插入图片描述

  2. 从代码层面来看,计算过程如下所示:

    class SoftmaxWithLoss:
        def backward(self, dout=1):
          batch_size = self.t.shape[0] # 6
    
          dx = self.y.copy() # 模型的输出;维度为[6,7]
          dx[np.arange(batch_size), self.t] -= 1 # dx中每条数据真实标签的那个值要减去1(为什么);维度为[6,7];这里其实就是局部梯度
          dx *= dout # SoftmaxWithLoss层输入侧的梯度=输出侧的梯度*局部梯度
          dx = dx / batch_size
    
          return dx # 维度为[3,7]
    
    1. 由于上面反向传播推导的结论是:SoftmaxWithLoss层输入侧的梯度是 y 1 − t 1 y_1-t_1 y1t1​,因此dx首先拷贝自self.y,而self.y= softmax(x)就是概率化的结果;
    2. 然后依然是根据 y 1 − t 1 y_1-t_1 y1t1​(即反向传播的梯度可以简化为模型输出(概率)与真实标签之间的差值)将dx中每条数据的真是样本对应的概率减去1;其他概率不用减,因为其他概率在真实标签向量中对应的那个元素值为0,不需要操作;
    3. dx *= dout其实不需要,因为反向传播时梯度的初始值为1;写在这里也是进一步说明了一下梯度传播法则为:输入侧梯度=输出侧梯度*局部梯度;【即链式求导法则】
    4. 因为损失是所有样本损失的平均,所以梯度也需要平均,以反映对每个样本的平均影响;所以需要执行dx = dx / batch_size
2.2.2.2其它层的反向传播
  1. 输出层的反向传播:

    1. 输出层是矩阵乘法,梯度的求导可看这篇博客:【深度学习】7-矩阵乘法运算的反向传播求梯度_矩阵梯度公式-CSDN博客;

    2. 执行的结果为:

      da = self.out_layer.backward(ds) # [3,3],即[mini-batch大小,中间层神经元个数]
      
  2. 接着是两个输入层相加取平均的操作,可以用上述计算图来表示;

    1. 取平均的操作结果就是

      da = da * 0.5
      
    2. 相加的操作结果就是:梯度复制两份,分别传入两个输入层;即执行:

      self.in_layer_0.backward(da)  # 输入层计算完最终梯度之后会将梯度保存在梯度列表里面;因此这里就不需要返回值了
      self.in_layer_1.backward(da)
      
      
  3. 两个输入层的反向传播同输出层;

  4. 不过,需要特别指出的是,MatMul层最后保存梯度时执行的是:

    self.grads[0][...] = dW # [...]表示所有元素
    # 而不是语句:
    self.grads[0] = dW
    
    1. 两个语句达到的目的是一样的,都是赋值;

    2. 但是省略号的方法可以在不改变self.grads[0]所在的内存地址的前提下完成梯度的更新;这样,在SimpleCBOW类中,只需要在__init__函数中执行一次参数汇总的操作(如下代码所示)即可;因为梯度数值在更新,但是内存地址没变,之后依然可以通过这里汇总的参数变量来访问被更新的梯度数值;

      class SimpleCBOW():
      		def __init__(self, vocab_size, hidden_size):
          		...
            	for layer in layers:
              		self.params += layer.params
              		self.grads += layer.grads
      

2.3训练类的损失可视化模块

  1. 代码如下:

    def plot(self, ylim=None):
        x = numpy.arange(len(self.loss_list)) # 作为横轴[1000]
        if ylim is not None:
            # 默认不执行
            plt.ylim(*ylim)
        plt.plot(x, self.loss_list, label='train') # loss_list维度为[1000]
        plt.xlabel('iterations (x' + str(self.eval_interval) + ')')
        plt.ylabel('loss')
        plt.show()
    
  2. 得到下图所示曲线:

    1. 与书上不一样是因为我设置eval_interval=2,因为本示例中max_iters=2

    在这里插入图片描述

3训练结果输出

  1. 输入层的权重可以用来表示单词的分布式表示;

  2. 因此通过以下代码打印密集向量表示:

    # 获取训练后的单词密集向量表示
    word_vecs=CBOW_model.wordvec
    max_word_length = max(len(word) for word in id_to_word.values())
    for word_id, word in id_to_word.items():
        print(f"{word:<{max_word_length}} {word_vecs[word_id]}")
    
  3. 输出结果如下图所示:

    在这里插入图片描述

  4. 总结:

    1. 目前语料库就一个句子;显然不够;
    2. 当前这个CBOW模型的实现在处理效率方面存在几个问题;后续学习过程中我们再一起进行修改;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1860070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USB2.0学习1--基本概念

目录 1.USB概念 2.USB协议发展 3.USB接口类型 3.1 TYPE类型 3.2 Mini类型 3.3 Micro类型 4. USB体系结构和关键概念 4.1 USB工作原理 4.2 USB物理拓扑结构 4.3 USB逻辑拓扑结构 4.4 USB软件架构 4.5 USB数据流模型 4.5.1 USB设备端点 4.5.2 USB管道 4.6 USB即插…

linux中的调试工具gdb

目录 1.背景知识补充 2.使用 知识补充 1.背景知识补充 1.gcc下编译默认是release方式发布的&#xff0c;无法直接进行调试 如果要以debug方式发布&#xff0c;需要携带-g 可以使用grep查询 因为携带debug信息&#xff0c;其文件体积要大一些 2.使用 1.gdb 可执行程序 …

【喜报】恭喜云贝两位学员通过Oracle 19c OCP考试

Oracle 19c OCP成绩展示 课程特色 课程训练周期为两个半月&#xff0c;一年内循环听课&#xff0c;直至掌握技能&#xff1b; 专业讲师亲授OCP技能知识点&#xff0c;工作经验分享 课下布置作业(涵盖考点)辅助学生从实践能力上提升水平 理论培训实验操作&#xff0c;以原理和…

Kotlin设计模式:代理模式详解

Kotlin设计模式&#xff1a;代理模式详解 在软件开发中&#xff0c;设计模式是解决常见问题的一种优雅方法。本文将介绍Kotlin中的代理模式&#xff08;Proxy Pattern&#xff09;&#xff0c;其应用场景&#xff0c;以及如何通过实例代码实现这一模式。 代理模式的目的 代理…

模拟面试之外卖点单系统(高频面试题目mark)

今天跟大家分享一个大家简历中常见的项目-《外卖点单系统》&#xff0c;这是一个很经典的项目&#xff0c;有很多可以考察的知识点和技能点&#xff0c;但大多数同学都是学期项目&#xff0c;没有实际落地&#xff0c;对面试问题准备不充分&#xff0c;回答时抓不到重点&#x…

学会整理电脑,基于小白用户(无关硬件升级)

如果你不想进行硬件升级&#xff0c;就要学会进行整理维护电脑 基于小白用户&#xff0c;每一个操作点我都会在后续整理出流程&#xff0c;软件推荐会选择占用小且实用的软件 主要从三个角度去讨论【如果有新的内容我会随时修改&#xff0c;也希望有补充告诉我&#xff0c;我…

疲劳驾驶智能识别摄像机

疲劳驾驶智能识别摄像机在道路安全管理中扮演着越来越重要的角色。这些先进的设备不仅仅是简单的监控工具&#xff0c;它们通过先进的技术和算法&#xff0c;有效地识别和预防司机疲劳驾驶&#xff0c;从而大大减少了交通事故的风险。 首先&#xff0c;这些智能识别摄像机采用高…

MySQL数据库初学者指南:从安装到常用命令的飞跃!

本文介绍 MySQL 的安装详细图文教程以及常用命令介绍。 1 MySQL 下载 1、官网下载 下载地址&#xff1a;https://dev.mysql.com/downloads/&#xff0c;选择社区版 目前 MySQL 最新版本为 8.0.23&#xff0c;如果想下载之前的版本可以点击 “Looking for previous GA versions?…

利用LabVIEW和数字孪生技术实现PCB电路板测试

利用LabVIEW和数字孪生技术对PCB电路板进行测试&#xff0c;可以通过动画展示实现测试过程的生动、形象和直观。本文详细说明了如何结合LabVIEW与数字孪生技术进行PCB电路板的测试&#xff0c;包括系统架构、实现方法以及具体展示效果&#xff0c;适合对外展示。 在现代电子制造…

C# 唯一性进程的方法封装(Winform/WPF通用)

C#唯一进程封装 C# 唯一性进程的方法封装 public class UniqueProcess{/// <summary>/// 焦点切换指定的窗口&#xff0c;并将其带到前台/// </summary>/// <param name"hWnd"></param>/// <param name"fAltTab"></para…

linux和Win——显卡驱动、Anaconda及pytorch安装(无需单独安装cuda、cudnn)

今天给新电脑的双系统&#xff08;windows11和ubuntu22.04&#xff09;安装了深度学习环境&#xff0c;在此记录一下。 一、Linux系统 &#xff08;一&#xff09;安装显卡驱动 &#xff08;1&#xff09;在安装Nvidia显卡驱动前&#xff0c;一定要点一下下面的“软件更新器…

达梦(DM8)数据库备份与还原(逻辑备份)二

一、达梦数据库的逻辑备份分四种级别的导出&#xff08;dexp&#xff09;与导入&#xff08;dimp&#xff09;的备份 第一种是&#xff1a;数据库级&#xff1a;导出或导入数据库中所有的对象。主要参数是&#xff1a;FULL 第二种是&#xff1a;用户级别&#xff1a;导出或导…

Kotlin 中的数据类型有隐式转换吗?

在 Kotlin 中&#xff0c;数据类型不可隐式转换。在 Java 中&#xff0c;如果数据是从小到大&#xff0c;是可以隐式转换的&#xff0c;数据类型将自动提升。 下面以 int 类型的数据为例&#xff0c;在 Java 中这样写是可以的&#xff1a; int a 2312; long b a;但是在 Kot…

智能电能表如何助力智慧农业

智能电能表作为智能电网数据采集的基本设备之一&#xff0c;不仅具备传统电能表基本用电量的计量功能&#xff0c;还具备双向多种费率计量功能、用户端控制功能、多种数据传输模式的双向数据通信功能以及防窃电功能等智能化的功能。这些功能使得智能电能表在农业领域的应用具有…

不要升级mmkv1.3.5

腾讯有点不负责任的感觉。1.3.5开始直接ban掉了v1.3.5 / 2024-04-24 Drop armv7 & x86 support.&#xff0c;x86和v7a的支持&#xff08;大概率是这个原因&#xff09;。 从打包后的包解压可以看到&#xff0c;只有arm64-v8a和x64目录里面有库。而1.3.4打包解压后&#x…

【话题】分数限制下,选好专业还是选好学校?

目录 引言&#xff1a;一、专业优先的考量二、学校优先的考量三、个人经历与决策四、综合考虑因素五、建议与策略结论文章推荐 引言&#xff1a; 随着24年高考的落幕&#xff0c;考生们迎来了人生中的重要抉择时刻&#xff1a;选择专业还是选择学校&#xff1f;两者皆重要&…

冷静!42.9分还不是SCI?别被影响因子迷了眼!最新JCR变化有哪些?

2024年发布的JCR变化有哪些&#xff1f; 2024年6月20日&#xff0c;科睿唯安正式发布2024年度《期刊引证报告》&#xff08;JCR™&#xff09;。 首先明确几个基本概念&#xff1a; &#xff08;1&#xff09;2024年发布2023JCR &#xff08;2&#xff09;JCR中有254个学科 …

基于FreeRTOS+STM32CubeMX+LCD1602+MCP4162(SPI接口)的数字电位器Proteus仿真

一、仿真原理图: 二、仿真效果: 三、STM32CubeMX配置: 1)、SPI配置: 2)、时钟配置: 四、软件部分: 1)、主函数: /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : mai…

3)JDK1.8核心Jar包详细详解

JDK1.8核心Jar包详细介绍 JDK1.8包含jar包核心Jar包rt.jar名称含义内容概览功能与作用注意事项 小结 JDK1.8包含jar包 JDK 1.8&#xff08;Java Development Kit 8&#xff09;包含了多个JAR文件&#xff0c;这些JAR文件是Java开发的核心组成部分&#xff0c;提供必要的类库和…

操纵系统的特征

操纵系统的特征 并发&#xff1a; 一个厨师&#xff08;单核CPU&#xff09;快速切换轮流处理三样菜&#xff08;进程&#xff09;&#xff0c;看起来像同时处理 并行&#xff1a; 有多个厨师&#xff08;多核CPU&#xff09;&#xff0c;每个厨师独立处理一样菜&#xff08;进…