在TEB数据集上搭建翻译模型——Seq2Seq_cowteb_llh_1178的博客-程序员秘密

技术标签: LSTM  深度学习  翻译模型  seq2seq  深度学习手记  TensorFlow  

   Seq2Seq模型的基本思想非常简单——使用一个循环神经网络读取输入句子,将整个句子的信息压缩到一个固定维度的编码中;再使用另一个循环神经网络读取这个编码,将其“解压”为目标语言的一个句子。这两个循环神经网络分别为编码器和解码器,这个模型也称为encoder-decoder模型。
  解码器部分结构与语言模型几乎完全相同:输入为单词的词向量,输出为softmax层产生的单词概率,损失函数为log perplexity。事实上,解码器可以理解为以输入编码为基础的语言模型。语言模型中的一些技巧,如共享softmax层、词向量的参数,都可以直接用于Seq2Seq模型的解码器中。

在这里插入图片描述

  编码器部分则为更简单。它与解码器一样拥有词向量层和循环神经网络,但是,由于在编码的过程中并未输出,因此,不需要softmax层。
  在训练过程中,编码器顺序读入每个单词的词向量,然后将最终的隐藏状态复制到解码器作为初始状态。解码器的第一个输入是一个特殊的“sos”符号,每一步预测的单词是训练数据的目标句子,预测序列的最后一个单词是与语言模型相同的“eos”字符。
在这里插入图片描述

1.预处理

import tensorflow as tf

# 语言预处理流程(数据里面的单词已经全部转化为单词编号)
# 使用Dataset从一个文件中读取一个语言的数据。
# 数据的格式为每行一句话,单词已经转化为单词编号。
class PrepDataset(object):
    def __init__(self, src_path, trg_path, batch_size, max_len, sos_id):
        self.batch_size = batch_size 
        self.src_path = src_path  
        self.trg_path = trg_path  
        self.max_len = max_len 
        self.sos_id = sos_id 
        
    def MakeDataset(self, file_path):
        dataset = tf.data.TextLineDataset(file_path)
        # 根据空格将单词编号切分开并放入一个一维向量。
        dataset = dataset.map(lambda string: tf.string_split([string]).values)
        # 将字符串形式的单词编号转化为整数。
        dataset = dataset.map(lambda string: tf.string_to_number(string, tf.int32))
        # 统计每个句子的单词数量,并与句子内容一起放入Dataset中。        
        dataset = dataset.map(lambda x: (x, tf.size(x)))
        return dataset
    
    # 删除内容为空(只包含<EOS>)的句子和长度过长的句子。
    def FilterLength(self, src_tuple, trg_tuple):
        ((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
        src_len_ok = tf.logical_and(
            tf.greater(src_len, 1), tf.less_equal(src_len, self.max_len)
        )
        trg_len_ok = tf.logical_and(
            tf.greater(trg_len, 1), tf.less_equal(trg_len, self.max_len)
        )
        return tf.logical_and(src_len_ok, trg_len_ok)
       
    # 解码器需要两种格式的目标句子:
        #   1.解码器的输入(trg_input),形式如同"<sos> X Y Z"
        #   2.解码器的目标输出(trg_label),形式如同"X Y Z <eos>"
        #  上面从文件中读到的目标句子是"X Y Z <eos>"的形式,我们需要从中生成"<sos> X Y Z" 形式并加入到Dataset中。
    def MakeTrgInput(self, src_tuple, trg_tuple):
        '''
        
        tf.concat()函数的用法:
        t1 = [[1, 2, 3], [4, 5, 6]]
        t2 = [[7, 8, 9], [10, 11, 12]]
        tf.concat([t1, t2], 0)  # [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
        tf.concat([t1, t2], 1)  # [[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]]
       
        '''
        ((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
        trg_input = tf.concat(
            [[self.sos_id], trg_label[:-1]], axis=0
        )   
        return ((src_input, src_len), (trg_input, trg_label, trg_len))
    
    # 从源语言文件src_path和目标语言文件trg_path中分别读取数据,并进行填充和batching操作。
    def MakeSrcTrgDataset(self):
            # 首先分别读取源语言数据和目标语言数据。
        src_data = self.MakeDataset(src_path)
        trg_data = self.MakeDataset(trg_path)
        # 通过zip操作将两个Dataset合并为一个Dataset。现在每个Dataset中每一项数据ds由4个张量组成:
            #   ds[0][0]是源句子
            #   ds[0][1]是源句子长度
            #   ds[1][0]是目标句子
            #   ds[1][1]是目标句子长度
        dataset = tf.data.Dataset.zip((src_data, trg_data))
        dataset = dataset.filter(self.FilterLength)
        dataset = dataset.map(self.MakeTrgInput)
        # 随机打乱训练数据。
        dataset = dataset.shuffle(10000)

        # 规定填充后输出的数据维度。
        padded_shapes = (
            (tf.TensorShape([None]),      # 源句子是长度未知的向量
             tf.TensorShape([])),         # 源句子长度是单个数字
            (tf.TensorShape([None]),      # 目标句子(解码器输入)是长度未知的向量
             tf.TensorShape([None]),      # 目标句子(解码器目标输出)是长度未知的向量
             tf.TensorShape([])))         # 目标句子长度是单个数字
        # 调用padded_batch方法进行batching操作。
        batched_dataset = dataset.padded_batch(batch_size, padded_shapes)
        return batched_dataset
# 测试一下padding batch有没有问题
if __name__ == "__main__":
    src_path = 'train.en'
    trg_path = 'train.zh'
    batch_size = 100 
    max_len = 50
    sos_id = 1
    makedataset = PrepDataset(src_path, trg_path, batch_size, max_len, sos_id)
    batched_dataset = makedataset.MakeSrcTrgDataset()
    iterator = batched_dataset.make_initializable_iterator()
    (src_input, src_len), (trg_input, trg_label, trg_len) = iterator.get_next()

2.定义翻译模型

  要使用的参数:

# 假设输入数据已经用9.2.1小节中的方法转换成了单词编号的格式。
SRC_TRAIN_DATA = "./train.en"        # 源语言输入文件。
TRG_TRAIN_DATA = "./train.zh"        # 目标语言输入文件。
CHECKPOINT_PATH = "./seq2seq_ckpt"   # checkpoint保存路径。  

HIDDEN_SIZE = 1024                   # LSTM的隐藏层规模。
NUM_LAYERS = 2                       # 深层循环神经网络中LSTM结构的层数。
SRC_VOCAB_SIZE = 10000               # 源语言词汇表大小。
TRG_VOCAB_SIZE = 4000                # 目标语言词汇表大小。
BATCH_SIZE = 100                     # 训练数据batch的大小。
NUM_EPOCH = 5                        # 使用训练数据的轮数。
KEEP_PROB = 0.8                      # 节点不被dropout的概率。
MAX_GRAD_NORM = 5                    # 用于控制梯度膨胀的梯度大小上限。
SHARE_EMB_AND_SOFTMAX = True         # 在Softmax层和词向量层之间共享参数。

MAX_LEN = 50   # 限定句子的最大单词数量。
SOS_ID  = 1    # 目标语言词汇表中<sos>的ID。

  定义NMTModel类来描述模型:

class NMTModel(object):
    # 在模型的初始化函数中定义要使用到的变量。
    def __init__(self):
        # 定义编码器和解码器所使用的LSTM结构。
        self.enc_cell = tf.nn.rnn_cell.MultiRNNCell(
          [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
           for _ in range(NUM_LAYERS)])
        self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
          [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE) 
           for _ in range(NUM_LAYERS)])

        # 为源语言和目标语言分别定义词向量。   
        self.src_embedding = tf.get_variable(
            "src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
        self.trg_embedding = tf.get_variable(
            "trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])

        # 定义softmax层的变量
        if SHARE_EMB_AND_SOFTMAX:
           self.softmax_weight = tf.transpose(self.trg_embedding)
        else:
           self.softmax_weight = tf.get_variable(
               "weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
        self.softmax_bias = tf.get_variable(
            "softmax_bias", [TRG_VOCAB_SIZE])

    # 在forward函数中定义模型的前向计算图。
    # src_input, src_size, trg_input, trg_label, trg_size分别是上面
    # MakeSrcTrgDataset函数产生的五种张量。
    def forward(self, src_input, src_size, trg_input, trg_label, trg_size):
        batch_size = tf.shape(src_input)[0]
    
        # 将输入和输出单词编号转为词向量。
        src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)
        trg_emb = tf.nn.embedding_lookup(self.trg_embedding, trg_input)
        
        # 在词向量上进行dropout。
        src_emb = tf.nn.dropout(src_emb, KEEP_PROB)
        trg_emb = tf.nn.dropout(trg_emb, KEEP_PROB)

        # 使用dynamic_rnn构造编码器。
        # 编码器读取源句子每个位置的词向量,输出最后一步的隐藏状态enc_state。
        # 因为编码器是一个双层LSTM,因此enc_state是一个包含两个LSTMStateTuple类
        # 张量的tuple,每个LSTMStateTuple对应编码器中的一层。
        # enc_outputs是顶层LSTM在每一步的输出,它的维度是[batch_size, 
        # max_time, HIDDEN_SIZE]。Seq2Seq模型中不需要用到enc_outputs,而
        # 后面介绍的attention模型会用到它。
        with tf.variable_scope("encoder"):
            enc_outputs, enc_state = tf.nn.dynamic_rnn(
                self.enc_cell, src_emb, src_size, dtype=tf.float32)

        # 使用dyanmic_rnn构造解码器。
        # 解码器读取目标句子每个位置的词向量,输出的dec_outputs为每一步
        # 顶层LSTM的输出。dec_outputs的维度是 [batch_size, max_time,
        # HIDDEN_SIZE]。
        # initial_state=enc_state表示用编码器的输出来初始化第一步的隐藏状态。
        with tf.variable_scope("decoder"):
            dec_outputs, _ = tf.nn.dynamic_rnn(
                self.dec_cell, trg_emb, trg_size, initial_state=enc_state)

        # 计算解码器每一步的log perplexity。这一步与语言模型代码相同。
        output = tf.reshape(dec_outputs, [-1, HIDDEN_SIZE])
        logits = tf.matmul(output, self.softmax_weight) + self.softmax_bias
        loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
            labels=tf.reshape(trg_label, [-1]), logits=logits)

        # 在计算平均损失时,需要将填充位置的权重设置为0,以避免无效位置的预测干扰
        # 模型的训练。
        label_weights = tf.sequence_mask(
            trg_size, maxlen=tf.shape(trg_label)[1], dtype=tf.float32)
        label_weights = tf.reshape(label_weights, [-1])
        cost = tf.reduce_sum(loss * label_weights)
        cost_per_token = cost / tf.reduce_sum(label_weights)
        
        # 定义反向传播操作。反向操作的实现与语言模型代码相同。
        trainable_variables = tf.trainable_variables()

        # 控制梯度大小,定义优化方法和训练步骤。
        grads = tf.gradients(cost / tf.to_float(batch_size),
                             trainable_variables)
        grads, _ = tf.clip_by_global_norm(grads, MAX_GRAD_NORM)
        optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
        train_op = optimizer.apply_gradients(
            zip(grads, trainable_variables))
        return cost_per_token, train_op

3.训练过程主函数

# 使用给定的模型model上训练一个epoch,并返回全局步数。
# 每训练200步便保存一个checkpoint。
def run_epoch(session, cost_op, train_op, saver, step):
    # 训练一个epoch。
    # 重复训练步骤直至遍历完Dataset中所有数据。
    while True:
        try:
            # 运行train_op并计算损失值。训练数据在main()函数中以Dataset方式提供。
            cost, _ = session.run([cost_op, train_op])
            if step % 10 == 0:
                print("After %d steps, per token cost is %.3f" % (step, cost))
            # 每200步保存一个checkpoint。
            if step % 200 == 0:
                saver.save(session, CHECKPOINT_PATH, global_step=step)
            step += 1
        except tf.errors.OutOfRangeError:
            break
    return step

def main():
    # 定义初始化函数。
    initializer = tf.random_uniform_initializer(-0.05, 0.05)

    # 定义训练用的循环神经网络模型。
    with tf.variable_scope("nmt_model", reuse=None, 
                           initializer=initializer):
        train_model = NMTModel()
  
    # 定义输入数据。
    data = MakeSrcTrgDataset(SRC_TRAIN_DATA, TRG_TRAIN_DATA, BATCH_SIZE)
    iterator = data.make_initializable_iterator()
    (src, src_size), (trg_input, trg_label, trg_size) = iterator.get_next()
 
    # 定义前向计算图。输入数据以张量形式提供给forward函数。
    cost_op, train_op = train_model.forward(src, src_size, trg_input,
                                            trg_label, trg_size)

    # 训练模型。
    saver = tf.train.Saver()
    step = 0
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        for i in range(NUM_EPOCH):
            print("In iteration: %d" % (i + 1))
            sess.run(iterator.initializer)
            step = run_epoch(sess, cost_op, train_op, saver, step)
if __name__ == "__main__":
    main()

In iteration: 1
After 0 steps, per token cost is 8.292
After 10 steps, per token cost is 7.941
After 20 steps, per token cost is 7.233
After 30 steps, per token cost is 7.367
After 40 steps, per token cost is 6.881
After 50 steps, per token cost is 6.818

After 380 steps, per token cost is 5.281
After 390 steps, per token cost is 5.087
After 400 steps, per token cost is 5.040
After 1780 steps, per token cost is 3.742
After 1790 steps, per token cost is 3.710
After 1800 steps, per token cost is 3.669

In iteration: 5
After 7220 steps, per token cost is 2.678
After 7230 steps, per token cost is 2.892
After 7240 steps, per token cost is 2.736
After 7250 steps, per token cost is 2.702
After 7260 steps, per token cost is 2.755
After 7270 steps, per token cost is 2.665

After 8960 steps, per token cost is 2.321
After 8970 steps, per token cost is 2.347
After 8980 steps, per token cost is 2.389
After 8990 steps, per token cost is 2.505
After 9000 steps, per token cost is 2.486
After 9010 steps, per token cost is 2.441

  模型建立好了之后,拿训练保存的参数对数据进行测试。在进行翻译的过程中,因为解码器无法读取目标训练句子(y),只能看到输入的句子(X),因此无法直接使用dynamic_rnn简单地展开成前馈网络,而是需要使用一个循环结构来实现,这个循环结构是由tf.while_loop()来实现的。下面进入测试阶段:

1.参数设置

import tensorflow as tf
import codecs
import sys
# 读取checkpoint的路径。9000表示是训练程序在第9000步保存的checkpoint。
CHECKPOINT_PATH = "./seq2seq_ckpt-9000"

# 模型参数。必须与训练时的模型参数保持一致。
HIDDEN_SIZE = 1024                         	# LSTM的隐藏层规模。
NUM_LAYERS = 2                             	# 深层循环神经网络中LSTM结构的层数。
SRC_VOCAB_SIZE = 10000                   	# 源语言词汇表大小。
TRG_VOCAB_SIZE = 4000                    	# 目标语言词汇表大小。
SHARE_EMB_AND_SOFTMAX = True            	# 在Softmax层和词向量层之间共享参数。

# 词汇表文件
SRC_VOCAB = "./en.vocab"
TRG_VOCAB = "./zh.vocab"

# 词汇表中<sos>和<eos>的ID。在解码过程中需要用<sos>作为第一步的输入,并将检查
# 是否是<eos>,因此需要知道这两个符号的ID。
SOS_ID = 1
EOS_ID = 2

2.定义NMT模型和解码步骤

# 定义NMTModel类来描述模型。
class NMTModel(object):
    # 在模型的初始化函数中定义模型要用到的变量。
    def __init__(self):
        # 定义编码器和解码器所使用的LSTM结构。
        self.enc_cell = tf.nn.rnn_cell.MultiRNNCell(
          [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE)
           for _ in range(NUM_LAYERS)])
        self.dec_cell = tf.nn.rnn_cell.MultiRNNCell(
          [tf.nn.rnn_cell.BasicLSTMCell(HIDDEN_SIZE) 
           for _ in range(NUM_LAYERS)])

        # 为源语言和目标语言分别定义词向量。   
        self.src_embedding = tf.get_variable(
            "src_emb", [SRC_VOCAB_SIZE, HIDDEN_SIZE])
        self.trg_embedding = tf.get_variable(
            "trg_emb", [TRG_VOCAB_SIZE, HIDDEN_SIZE])

        # 定义softmax层的变量
        if SHARE_EMB_AND_SOFTMAX:
            self.softmax_weight = tf.transpose(self.trg_embedding)
        else:
            self.softmax_weight = tf.get_variable(
               "weight", [HIDDEN_SIZE, TRG_VOCAB_SIZE])
        self.softmax_bias = tf.get_variable(
            "softmax_bias", [TRG_VOCAB_SIZE])

    def inference(self, src_input):
        # 虽然输入只有一个句子,但因为dynamic_rnn要求输入是batch的形式,因此这里
        # 将输入句子整理为大小为1的batch。
        src_size = tf.convert_to_tensor([len(src_input)], dtype=tf.int32)
        src_input = tf.convert_to_tensor([src_input], dtype=tf.int32)
        src_emb = tf.nn.embedding_lookup(self.src_embedding, src_input)

        # 使用dynamic_rnn构造编码器。这一步与训练时相同。
        with tf.variable_scope("encoder"):
            enc_outputs, enc_state = tf.nn.dynamic_rnn(
                self.enc_cell, src_emb, src_size, dtype=tf.float32)
   
        # 设置解码的最大步数。这是为了避免在极端情况出现无限循环的问题。
        MAX_DEC_LEN=100

        with tf.variable_scope("decoder/rnn/multi_rnn_cell"):
            # 使用一个变长的TensorArray来存储生成的句子。
            init_array = tf.TensorArray(dtype=tf.int32, size=0,
                dynamic_size=True, clear_after_read=False)
            # 填入第一个单词<sos>作为解码器的输入。
            init_array = init_array.write(0, SOS_ID)
            # 构建初始的循环状态。循环状态包含循环神经网络的隐藏状态,保存生成句子的
            # TensorArray,以及记录解码步数的一个整数step。
            init_loop_var = (enc_state, init_array, 0)

            # tf.while_loop的循环条件:
            # 循环直到解码器输出<eos>,或者达到最大步数为止。
            def continue_loop_condition(state, trg_ids, step):
                return tf.reduce_all(tf.logical_and(
                    tf.not_equal(trg_ids.read(step), EOS_ID),
                    tf.less(step, MAX_DEC_LEN-1)))

            def loop_body(state, trg_ids, step):
                # 读取最后一步输出的单词,并读取其词向量。
                trg_input = [trg_ids.read(step)]
                trg_emb = tf.nn.embedding_lookup(self.trg_embedding,
                                                 trg_input)
                # 这里不使用dynamic_rnn,而是直接调用dec_cell向前计算一步。
                dec_outputs, next_state = self.dec_cell.call(
                    state=state, inputs=trg_emb)
                # 计算每个可能的输出单词对应的logit,并选取logit值最大的单词作为
                # 这一步的而输出。
                output = tf.reshape(dec_outputs, [-1, HIDDEN_SIZE])
                logits = (tf.matmul(output, self.softmax_weight)
                          + self.softmax_bias)
                next_id = tf.argmax(logits, axis=1, output_type=tf.int32)
                # 将这一步输出的单词写入循环状态的trg_ids中。
                trg_ids = trg_ids.write(step+1, next_id[0])
                return next_state, trg_ids, step+1

            # 执行tf.while_loop,返回最终状态。
            state, trg_ids, step = tf.while_loop(
                continue_loop_condition, loop_body, init_loop_var)
            return trg_ids.stack()

3.翻译一个测试句子

def main():
    # 定义训练用的循环神经网络模型。
    with tf.variable_scope("nmt_model", reuse=None):
        model = NMTModel()

    # 定义个测试句子。
    test_en_text = "This is a test . <eos>"
    print(test_en_text)
    
    # 根据英文词汇表,将测试句子转为单词ID。
    with codecs.open(SRC_VOCAB, "r", "utf-8") as f_vocab:
        src_vocab = [w.strip() for w in f_vocab.readlines()]
        src_id_dict = dict((src_vocab[x], x) for x in range(len(src_vocab)))
    test_en_ids = [(src_id_dict[token] if token in src_id_dict else src_id_dict['<unk>'])
                   for token in test_en_text.split()]
    print(test_en_ids)

    # 建立解码所需的计算图。
    output_op = model.inference(test_en_ids)
    sess = tf.Session()
    saver = tf.train.Saver()
    saver.restore(sess, CHECKPOINT_PATH)

    # 读取翻译结果。
    output_ids = sess.run(output_op)
    print(output_ids)
    
    # 根据中文词汇表,将翻译结果转换为中文文字。
    with codecs.open(TRG_VOCAB, "r", "utf-8") as f_vocab:
        trg_vocab = [w.strip() for w in f_vocab.readlines()]
    output_text = ''.join([trg_vocab[x] for x in output_ids])
    
    # 输出翻译结果。
    print(output_text.encode('utf8').decode(sys.stdout.encoding))
    sess.close()

if __name__ == "__main__":
    main()

This is a test . 《eos》
[90, 13, 9, 689, 4, 2]
INFO:tensorflow:Restoring parameters from ./seq2seq_ckpt-9000
[ 1 10 7 9 12 411 271 6 2]
《sos》 这 是 一 个 测 试 。 《eos》

参考:《TensorFlow实战Google深度学习框架》

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/llh_1178/article/details/89322373

智能推荐

mpvue开发微信小程序navigateBack失效于tabBar_就像风1样的博客-程序员秘密

返回上一层的navigateBack路由要是与配置的tabBar栏目路由相同的话,返回会失效,需换用switchTab//app.json里面设置的tabBar"tabBar": { "list": [ { "text": "返利", "pagePath": "pages/index/main", "iconPath": ...

模块化(Commonjs与ES6 Module)_es6module是动态还是静态_迟r的博客-程序员秘密

模块化(Commonjs与ES6 Module)描述:1.CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。2.ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。注意:es6 module自动开启严格模式。ES6 Module使用export导出,import导入。export

u-boot向linux内核传递启动参数(详细)_灿哥!的博客-程序员秘密

U-BOOT 在启动内核时,会向内核传递一些参数.BootLoader 可以通过两种方法传递参数给内核,一种是旧的参数结构方式(parameter_struct),主要是 2.6 之前的内核使用的方式。另外一种就是现在的 2.6内核在用的参数链表 (tagged list) 方式。这些参数主要包括,系统的根设备标志,页面大小,内存的起始地址和大小,RAMDISK的起始地址和大小,压缩的RAMDISK根文件系统的起始地址和大小,当前内核命令参数等而这些参数是通过 struct tag来传递的。U-boot

IntelliJ IDEA 2018软件激活大全_xiangzhihong8的博客-程序员秘密

众所周知,IntelliJ IDEA 系列软件作为时下最热门的开发工具,是每个开发者必备的开发神器,不过近年来IntelliJ IDEA 开始陆续收费也是困扰了不少的程序员童鞋。不过,在我天朝,多半是没有人会为这种事情买单的,那么下面收集了几种破解手段。方法一 License server在打开的License Activation窗口中选择“License server”,在输入框...

vc++之windows api_vc++如何申明windowsapi_wula0010的博客-程序员秘密

WindowsAPI函数清单  WindowsAPI简介:   API的英文全称(Application Programming Interface),WIN32 API也就是MicrosoftWindows 32位平台的应用程序编程接口。对这个定义的理解,需要追溯到操作系统的发展历史上,当WINDOWS操作系统开始占据主导地位的时候,开发WINDOWS平台下的应用程序成为人们的需要。而在WI

Nanopi-NEO点亮SPI-TFT_nanopi neo2 spi屏幕_Three_Sheep的博客-程序员秘密

busybox的根文件太简约,可以用于项目,但是缺胳膊少腿的不好玩,咱们还是搞个Debian吧,丰富稳定。1.先安装工具,debootstrap是debian官方的生成rootfs的工具,可制作debian或者ubuntu的根目录,非常方便apt-get install debootstrap multistrap binfmt-support2.生成debian根目录,包括丰富的工具和命令...

随便推点

Spark2.x学习笔记:7、Spark应用程序设计_程裕强的博客-程序员秘密

第7章 Spark应用程序设计7.1 基本流程1.创建SparkContext对象每个Spark应用程序有且仅有一个SparkContext对象,封装了Spark执行环境信息2.创建RDD可以冲Scala集合或者Hadoop数据集上创建3.在RDD之上进行转换和ActionMapReduce只提供了map和reduce两种操作,而Spark提供了多种转换和action函数4.返回结果保存到H

windows 安装oracle补丁教程_oracle补丁安装_azuremind的博客-程序员秘密

完成oracle补丁安装有以下几个步骤:1、自行下载好oracle补丁。2、windows server 服务器配置环境变量;新建ORACLE_HOME,值填写安装oracle路劲到dbhome_1即可,例如:E:\app\Administrator\product\11.2.0\dbhome_13、把有关oracle的服务全部关掉4、把新下载的文件OPatch替换旧的OPatch文件,旧的OPatch文件路径:E:\app\Administrator\product\11.2.0\dbhome_

Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias Cause: java.lang.Class_海涛高软的博客-程序员秘密

Caused by: org.apache.ibatis.type.TypeException: Could not resolve type alias 'com.bootdo.vedio.domain.VedioFieldAlbumDo'. Cause: java.lang.ClassNotFoundException: Cannot find class: com.bootdo.vedi...

网站终于稳定下来了_penghaijun911的博客-程序员秘密

感谢我的好朋友呀,他给我提供稳定的网站空间!欢迎大家访问:http://www.shforce.com首亨软件(重庆)有限公司,是一家主营通信领域内软件系统研发的技术型企业。位于重庆高新区石桥铺,交通便利,办公环境舒适,提供零食、各种趣味玩具、棋具、品茶工具、健身器具等。产品主要应用于通信运营、金融、保险、零售业、文化出版、音响出版等行业。首亨软件在全国各地均有十分扎实的市场基础与商业合作伙伴,覆盖全国的市场营销网络及技术支持、技术服务体系。首亨经过不断的技术创新和经验积累,已成

TrustZone入门知识_prz999的博客-程序员秘密

最近实习在研究这个东西,就分享一些个人的知识给要用的人吧。TrustZone是ARM推出的系统级安全方案。可以

2021/9/27 表单校验不生效问题_Zhang_aichi的博客-程序员秘密

在vue中使用rules对表单字段进行验证_我的博客-程序员秘密_rules

推荐文章

热门文章

相关标签