本文转载自:https://dongzhixiao.github.io/2018/07/21/so-hot/

  1. 今天周六,早晨出门吃饭,全身汗湿透。天气真的是太热了!我决定一天不出门,在屋子里面休息!
  2. 晚上,腾飞给我说了他暑假的计划,决定去长沙、成都去转一圈,并邀请我去,还顺便叫我晚上去吃饭。
  3. 最后我们就一起吃了一顿饭,不过我估计我休息的时间是下下周,因此可能不能和他一起去了。

今天总结一下本周学习到的知识:

周一

在进行神经网络序列输入的时候,发现了一个很好的文件代码用来数据预处理。

注意:后面使用“数据单元”代表数据的一个最小单元,比如训练英文数据就可以代表一个字符——’a’,训练中文数据就可以代表一个汉字——’王’etc

文件名字叫做read_utils.py。该文件中实现了一个类TextConverter和一个工具函数batch_generator

该文件是一个工具类,用于把一个输入文件根据编码输出对应的一批一批的数据用于RNN/LSTM之类的文本处理神经网络训练, 用法是先使用TextConverter类编码所有的内容为数据单元对应数字,然后使用batch_generator函数将编码好的数字分批返回 比如:

  1. text = f.read() #f是open后得到的文件指针
  2. converter = TextConverter(text)
  3. arr = converter.text_to_arr(text)
  4. g = batch_generator(arr,num_seqs,num_steps) #如果输入本来就是编码好的数据,则直接使用这个函数即可

下面让我们来一个一个学习一下。

TextConverter类

TextConverter类是用来将传入的文件中所有数据 首先,我们看看该类的构造函数。

  1. class TextConverter(object):
  2. def __init__(self, text=None, max_vocab=5000, filename=None):
  3. if filename is not None:
  4. with open(filename, 'rb') as f:
  5. self.vocab = pickle.load(f)
  6. else:
  7. vocab = set(text) #存储读取文件中的数据单元所有类型的集合,比如英文文件会是:{'\n','A','b',...,'\r'}
  8. print(len(vocab)) #打印数据单元的种类的数目
  9. # max_vocab_process
  10. vocab_count = {} #存储每一个数据单元在整个读入的文本中出现的次数的字典
  11. for word in vocab:
  12. vocab_count[word] = 0
  13. for word in text:
  14. vocab_count[word] += 1
  15. vocab_count_list = [] #存储元组(数据单元,对应数量)组成的列表,然后按照数量的大小排序,比如[('a',100),('d',20),...,('x',3)]
  16. for word in vocab_count:
  17. vocab_count_list.append((word, vocab_count[word]))
  18. vocab_count_list.sort(key=lambda x: x[1], reverse=True)
  19. if len(vocab_count_list) > max_vocab: #根据传入的最大数量的数据单元数截断前max_vocab大的数据单元,基本上不可能,除非遇到汉字之类的文本
  20. vocab_count_list = vocab_count_list[:max_vocab]
  21. vocab = [x[0] for x in vocab_count_list]
  22. self.vocab = vocab #vocab仅仅存储数据单元按照出现数量从大到小的列表,例如:['a','d',...,'x']
  23. self.word_to_int_table = {c: i for i, c in enumerate(self.vocab)} # 数据单元到数字字典{' ':0,'e':1,...,'c':20,...}
  24. self.int_to_word_table = dict(enumerate(self.vocab)) # 数字到数据单元字典{0:‘ ’,1:'e',...,20:'c',..}

可以看出,该构造函数的输入是(文本内容,最大词限制,文件名)。可以看出最后一个关键字参数filename是用来判断文件是否为空,从而直接读取 不用进入后面的处理环节,这个地方跟后面的保存模块对应的:

  1. def save_to_file(self, filename): #仅仅存储数据单元按照出现数量从大到小的列表到指定文件filename处,例如:['a','d',...,'x']
  2. with open(filename, 'wb') as f:
  3. pickle.dump(self.vocab, f)

保存后,以后就可以直接使用这个词表了。 如果没有传入文件名,则说明需要进行后续的处理,我们仔细看一下后面的代码,发现实际上做的工作就是:

  • 找到所有数据中“数据单元”
  • 遍历文件记录每个“数据单元”出现的次数,根据次数大小对“数据单元”排序
  • 根据传入参数max_vocab截断数据单元,只去前max_vocab个“数据单元”
  • 将留下的数据单元一一映射到自然数0,1,2…上

注意传入的text是一个列表或者列表生成器之类的数据结构,因为后面的代码把它这样子用了(比如去text的集合,用for迭代text等)。

在构造函数中已经实现了“数据单元”到自然数列的映射,因此互相转换的函数就显而易见了,如下所示:

  1. def word_to_int(self, word): #返回数据单元对应的整数
  2. if word in self.word_to_int_table:
  3. return self.word_to_int_table[word]
  4. else:
  5. return len(self.vocab) #如果出现了没有出现的词,则变为<unk>对应的标记
  6. def int_to_word(self, index): #返回整数对应的数据单元
  7. if index == len(self.vocab):
  8. return '<unk>' #没有出现的词被标记为unknown的缩写
  9. elif index < len(self.vocab):
  10. return self.int_to_word_table[index]
  11. else:
  12. raise Exception('Unknown index!')

由上面的函数可知,在映射的时候如果词没有出现在词表中,则标记为<unk>返回,这个是非常重要的一个处理,因为在实际进行数据 输入的时候,由于截断引起的超出数据记录的词,或者在进行测试集的时候很有可能出现这种情况!

既然有了单个“数据单元”和自然数的映射,多个“数据单元”组成的列表当然也能相互转化:

  1. def text_to_arr(self, text): #将输入的text根据word_to_int返回得到对应的编码数,并构成np.ndarray并返回,例如:输入' a\n',则返回类似array([ 0, 0, 4, 10])
  2. arr = []
  3. for word in text:
  4. arr.append(self.word_to_int(word))
  5. return np.array(arr)
  6. def arr_to_text(self, arr): #输入列表类型的数据,返回对应的数据单元的组合
  7. words = []
  8. for index in arr:
  9. words.append(self.int_to_word(index))
  10. return "".join(words)

batch_generator

有了数据编码的类,下面就需要一个样本生成的函数了。 根据输入的数据(这个输入一般就是全部样本组成的文本,并且已经根据所有数据单元编码成为了数字列表), 返回对应的生成器,满足输入的序列个数和序列长度

  1. def batch_generator(arr, n_seqs, n_steps): #根据输入的arr(这个输入一般就是全部样本组成的文本,并且已经根据所有数据单元编码成为了数字列表),返回对应的生成器,满足输入的序列个数和序列长度
  2. arr = copy.copy(arr)
  3. batch_size = n_seqs * n_steps #计算没次输入需要使用的数据单元
  4. n_batches = int(len(arr) / batch_size) #一共可以得到多少组输入数据
  5. arr = arr[:batch_size * n_batches] #直接忽略了后面不能构成一组输入的数据!
  6. arr = arr.reshape((n_seqs, -1))
  7. while True:
  8. np.random.shuffle(arr) #将所有行打乱顺序
  9. for n in range(0, arr.shape[1], n_steps):
  10. x = arr[:, n:n + n_steps] #每次选择对应n_seqs行,n_steps列的数据
  11. y = np.zeros_like(x) #返回跟x同形状的n维数组,数据全部都是0
  12. y[:, :-1], y[:, -1] = x[:, 1:], x[:, 0]
  13. yield x, y

可以看出,该函数根据输入的所有训练数据,和对应的序列一批的个数(n_seqs)和每个输入的序列的长度(n_steps),然后 通过生成器函数不断的迭代取出来数据用于训练。每一个输入和输出的序列刚错开一位,比如:

  1. #如果输入的x是[[48 49 50]
  2. # [ 0 1 2]]
  3. # 则输出的y是[[49 50 48]
  4. # [ 1 2 0]]

周二

文件名字叫做model.py。该文件中实现了一个类CharRNN和一个工具函数pick_top_n

CharRNN

下面介绍模型类,这个模型使用的是TensorFlow模块,然后进行网络的搭建,首先看构造函数:

  1. class CharRNN:
  2. def __init__(self, num_classes, num_seqs=64, num_steps=50,
  3. lstm_size=128, num_layers=2, learning_rate=0.001,
  4. grad_clip=5, sampling=False, train_keep_prob=0.5, use_embedding=False, embedding_size=128):
  5. if sampling is True:
  6. num_seqs, num_steps = 1, 1
  7. else:
  8. num_seqs, num_steps = num_seqs, num_steps
  9. self.num_classes = num_classes
  10. self.num_seqs = num_seqs #序列个数
  11. self.num_steps = num_steps #序列长度
  12. self.lstm_size = lstm_size
  13. self.num_layers = num_layers
  14. self.learning_rate = learning_rate
  15. self.grad_clip = grad_clip
  16. self.train_keep_prob = train_keep_prob
  17. self.use_embedding = use_embedding
  18. self.embedding_size = embedding_size
  19. tf.reset_default_graph()
  20. self.build_inputs() #构建输入层
  21. self.build_lstm() #构建LSTM层
  22. self.build_loss() #构建损失函数
  23. self.build_optimizer() #构建优化器
  24. self.saver = tf.train.Saver() #保存设置
  25. #下面测试,增加总结
  26. tf.summary.scalar('loss',self.loss)
  27. for var in tf.trainable_variables():
  28. tf.summary.histogram(var.op.name, var)
  29. self.merge_summary = tf.summary.merge_all()
  30. self.train_writer = tf.summary.FileWriter('./model')
  31. self.train_writer.add_graph(tf.get_default_graph())

可以看出,该构造函数根据输入的参数,搭建了一个R输入-R输出的神经网络,隐状态用的是LSTM模型。 首先先保存各个输入的设定,然后分别构建各个层和优化保存相关的设置,我们一个一个看:

  1. def build_inputs(self):
  2. with tf.name_scope('inputs'):
  3. self.inputs = tf.placeholder(tf.int32, shape=(
  4. self.num_seqs, self.num_steps), name='inputs')
  5. self.targets = tf.placeholder(tf.int32, shape=(
  6. self.num_seqs, self.num_steps), name='targets')
  7. self.keep_prob = tf.placeholder(tf.float32, name='keep_prob')
  8. # 对于中文,需要使用embedding层
  9. # 英文字母没有必要用embedding层
  10. if self.use_embedding is False:
  11. self.lstm_inputs = tf.one_hot(self.inputs, self.num_classes)
  12. else:
  13. with tf.device("/cpu:0"):
  14. embedding = tf.get_variable('embedding', [self.num_classes, self.embedding_size])
  15. self.lstm_inputs = tf.nn.embedding_lookup(embedding, self.inputs)

上面的函数就是输入层,可以看出,根据输入的参数embedding来确定输入层是否增加一个嵌入层,显然,如果数据的词表 比较大,比如中文,就需要嵌入层降维,如果比较小,就可以不用嵌入层。

然后是LSTM层:

  1. def build_lstm(self):
  2. # 创建单个cell并堆叠多层
  3. def get_a_cell(lstm_size, keep_prob):
  4. lstm = tf.nn.rnn_cell.BasicLSTMCell(lstm_size)
  5. drop = tf.nn.rnn_cell.DropoutWrapper(lstm, output_keep_prob=keep_prob)
  6. return drop
  7. with tf.name_scope('lstm'):
  8. cell = tf.nn.rnn_cell.MultiRNNCell(
  9. [get_a_cell(self.lstm_size, self.keep_prob) for _ in range(self.num_layers)]
  10. )
  11. self.initial_state = cell.zero_state(self.num_seqs, tf.float32)
  12. # 通过dynamic_rnn对cell展开时间维度
  13. self.lstm_outputs, self.final_state = tf.nn.dynamic_rnn(cell, self.lstm_inputs, initial_state=self.initial_state)
  14. # 通过lstm_outputs得到概率
  15. seq_output = tf.concat(self.lstm_outputs, 1)
  16. x = tf.reshape(seq_output, [-1, self.lstm_size])
  17. with tf.variable_scope('softmax'):
  18. softmax_w = tf.Variable(tf.truncated_normal([self.lstm_size, self.num_classes], stddev=0.1))
  19. softmax_b = tf.Variable(tf.zeros(self.num_classes))
  20. self.logits = tf.matmul(x, softmax_w) + softmax_b
  21. self.proba_prediction = tf.nn.softmax(self.logits, name='predictions')

可以看出,LSTM层使用的是多层,层数根据参数self.num_layers确定LSTM的隐层的层数。然后得到输出使用的是softmax激活函数,可以 得到输出的每一个类别的概率。

之后是损失和优化:

  1. def build_loss(self):
  2. with tf.name_scope('loss'):
  3. y_one_hot = tf.one_hot(self.targets, self.num_classes)
  4. y_reshaped = tf.reshape(y_one_hot, self.logits.get_shape())
  5. loss = tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=y_reshaped)
  6. self.loss = tf.reduce_mean(loss)
  7. def build_optimizer(self):
  8. # 使用clipping gradients
  9. tvars = tf.trainable_variables()
  10. grads, _ = tf.clip_by_global_norm(tf.gradients(self.loss, tvars), self.grad_clip)
  11. train_op = tf.train.AdamOptimizer(self.learning_rate)
  12. self.optimizer = train_op.apply_gradients(zip(grads, tvars))

损失使用的就是一般常用的交叉熵损失,优化则使用的是比较著名的自适应优化器adam

之后就可以开始训练了:

  1. def train(self, batch_generator, max_steps, save_path, save_every_n, log_every_n):
  2. self.session = tf.Session()
  3. with self.session as sess:
  4. sess.run(tf.global_variables_initializer())
  5. # Train network
  6. step = 0
  7. new_state = sess.run(self.initial_state)
  8. for x, y in batch_generator:
  9. step += 1
  10. start = time.time()
  11. feed = {self.inputs: x,
  12. self.targets: y,
  13. self.keep_prob: self.train_keep_prob,
  14. self.initial_state: new_state}
  15. batch_loss, new_state, _ , train_summary = sess.run([self.loss,
  16. self.final_state,
  17. self.optimizer,
  18. self.merge_summary],
  19. feed_dict=feed)
  20. end = time.time()
  21. # control the print lines
  22. if step % log_every_n == 0:
  23. print('step: {}/{}... '.format(step, max_steps),
  24. 'loss: {:.4f}... '.format(batch_loss),
  25. '{:.4f} sec/batch'.format((end - start)))
  26. self.train_writer.add_summary(train_summary, step)
  27. if (step % save_every_n == 0):
  28. self.saver.save(sess, os.path.join(save_path, 'model'), global_step=step)
  29. if step >= max_steps:
  30. break
  31. self.saver.save(sess, os.path.join(save_path, 'model'), global_step=step)

可以看出,训练就是根据前面搭建的网络和生成的样本,往里面不断的喂数据。然后将结果不断保存。

训练好模型后,我们就可以读取保存好的模型:

  1. def load(self, checkpoint):
  2. self.session = tf.Session()
  3. self.saver.restore(self.session, checkpoint)
  4. print('Restored from: {}'.format(checkpoint))

读取了保存好模型中的各种参数后,就看一通过这个网络生成样本:

  1. def sample(self, n_samples, prime, vocab_size): #n_samples:一共输出多少个基本单元;prime:开始的几个基本单元;vocab_size:一共有多少个类型的基本单元+1(未知数据编码)
  2. samples = [c for c in prime]
  3. sess = self.session
  4. new_state = sess.run(self.initial_state)
  5. preds = np.ones((vocab_size, )) # for prime=[]
  6. for c in prime: #根据输入的“基本单元”的多少,不断更新状态,直到最后的输入为止!真好的实现!
  7. x = np.zeros((1, 1))
  8. # 输入单个字符
  9. x[0, 0] = c
  10. feed = {self.inputs: x,
  11. self.keep_prob: 1.,
  12. self.initial_state: new_state} #每次输入时更新状态即可达到连续的效果,对应LSTM状态是元组(c,h)
  13. preds, new_state = sess.run([self.proba_prediction, self.final_state],
  14. feed_dict=feed)
  15. c = pick_top_n(preds, vocab_size)
  16. # 添加字符到samples中
  17. samples.append(c)
  18. # 不断生成字符,直到达到指定数目
  19. for i in range(n_samples):
  20. x = np.zeros((1, 1))
  21. x[0, 0] = c
  22. feed = {self.inputs: x,
  23. self.keep_prob: 1.,
  24. self.initial_state: new_state}
  25. preds, new_state = sess.run([self.proba_prediction, self.final_state],
  26. feed_dict=feed)
  27. c = pick_top_n(preds, vocab_size)
  28. samples.append(c)
  29. return np.array(samples)

注意这个函数是根据输入的前几个自然数序列(已经通过“基本单元”映射为自然数了),预测下一个输出的对应自然数。 其中第一个for循环出色的使用了权重共享的思想,使用sample这个函数的时候使得在构造函数时sample这个参数为True。 然后一个一个的将“基本单元”映射后的自然数输入,这样每次仅更新隐状态输出的状态参数。 之后第二个for循环依次生成后续的一个一个自然数。

pick_top_n

在上一小节的最后一个sample函数中,用到了pick_top_n函数,这个函数的内容如下:

  1. def pick_top_n(preds, vocab_size, top_n=5):
  2. p = np.squeeze(preds) #squeeze函数从数组的形状中删除单维度条目,即把shape中为1的维度去掉
  3. # 将除了top_n个预测值的位置都置为0
  4. p[np.argsort(p,kind = 'mergesort')[:-top_n]] = 0 #argsort函数可以按照给定方法排序
  5. # 归一化概率
  6. p = p / np.sum(p)
  7. # 随机选取一个字符
  8. c = np.random.choice(vocab_size, 1, p=p)[0]
  9. return c

可以看出,该函数通过输入的各个序列的概率,然后根据n取得概率前几个最大的概率,之后通过这些概率进行归一化,然后得到留下来 的数字序列对应的概率分布律,最后通过np.random.choice按照各个字符的分布律来随机选择一个字符并返回。

周三

预测和精度

今天,通过前两天的代码的学习,我今天将我需要用到的数据序列通过read_utils.py预处理,之后放到model.py里面进行训练。 之后设置了20000步的训练,结果发现可以成功运行并根据输入生成一系列新的输出,但是我希望能够直接得到下一个字符的概率,因此 可以按照如下的方式进行实现:

也可以通过这个网络预测下一个出现的“数据单元”的概率:

  1. def prediction_next_n(self,prime,vocab_size,next_n =3 , **k): #prime:开始的几个基本单元;vocab_size:一共有多少个类型的基本单元+1(未知数据编码)
  2. # samples = [c for c in prime]
  3. sess = self.session
  4. new_state = sess.run(self.initial_state)
  5. preds = np.ones((vocab_size,)) # for prime=[]
  6. for c in prime: # 根据输入的“基本单元”的多少,不断更新状态,直到最后的输入为止!真好的实现!
  7. x = np.zeros((1, 1))
  8. # 输入单个字符
  9. x[0, 0] = c
  10. feed = {self.inputs: x,
  11. self.keep_prob: 1.,
  12. self.initial_state: new_state} # 每次输入时更新状态即可达到连续的效果,对应LSTM状态是元组(c,h)
  13. preds, new_state = sess.run([self.proba_prediction, self.final_state],
  14. feed_dict=feed)
  15. p = np.squeeze(preds) #squeeze函数从数组的形状中删除单维度条目,即把shape中为1的维度去掉
  16. # 将next_n个最大的概率的位置得到
  17. next_n_num = np.argsort(p,kind = 'mergesort')[-next_n:] #argsort函数可以按照给定方法排序
  18. #返回的应该是标号和对应的概率值
  19. s_p_d = []
  20. for i in next_n_num:
  21. s_p_d.append((i,p[i]))
  22. return s_p_d

返回的这个各个自然数的概率,就可以进行预测生成新的数据对应的结果了。

为了后续的测试,我需要得到精度,因此实现一个计算精度的函数:

  1. def get_accuracy(self,dualList,vocab_size,next_n =3): #输入的序列满足有开始的标记,没有结尾的标记
  2. success_num = 0
  3. for one_session in dualList:
  4. if one_session[-1] in self.prediction_next_n(one_session[:-1],vocab_size,next_n):
  5. success_num = success_num + 1
  6. print(success_num,len(dualList))
  7. print('精度是:%.4f' % (success_num/len(dualList)) )

TensorBoard的使用

为了将所有数据都显示出来,我使用了TensorBoard进行显示。

tensorflow的写诗代码分析【转】的更多相关文章

  1. tensorflow自动写诗

    1.目录结构 2.入口类 # coding = utf-8 """ 注意:RNN使用的数据为序列化的数据 RNN网络:主要由多个LSTM计算单元组成,依靠BPTT算法进行 ...

  2. tensorflow笔记:多层LSTM代码分析

    tensorflow笔记:多层LSTM代码分析 标签(空格分隔): tensorflow笔记 tensorflow笔记系列: (一) tensorflow笔记:流程,概念和简单代码注释 (二) ten ...

  3. Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback

    Linux Kernel文件系统写I/O流程代码分析(二)bdi_writeback 上一篇# Linux Kernel文件系统写I/O流程代码分析(一),我们看到Buffered IO,写操作写入到 ...

  4. Linux Kernel文件系统写I/O流程代码分析(一)

    Linux Kernel文件系统写I/O流程代码分析(一) 在Linux VFS机制简析(二)这篇博客上介绍了struct address_space_operations里底层文件系统需要实现的操作 ...

  5. tensorflow笔记:多层CNN代码分析

    tensorflow笔记系列: (一) tensorflow笔记:流程,概念和简单代码注释 (二) tensorflow笔记:多层CNN代码分析 (三) tensorflow笔记:多层LSTM代码分析 ...

  6. Qt侠:像写诗一样写代码,玩游戏一样的开心心情,还能领工资!

    [软]上海-Qt侠 2017/7/12 16:11:20我完全是兴趣主导,老板不给我钱,我也要写好代码!白天干,晚上干,周一周五干,周末继续干!编程已经深入我的基因,深入我的骨髓,深入我的灵魂!当我解 ...

  7. 开源项目kcws代码分析--基于深度学习的分词技术

    http://blog.csdn.net/pirage/article/details/53424544 分词原理 本小节内容参考待字闺中的两篇博文: 97.5%准确率的深度学习中文分词(字嵌入+Bi ...

  8. 为你写诗:3 步搭建 Serverless AI 应用

    作者 | 杜万(倚贤) 阿里巴巴技术专家 本文整理自 1 月 2 日社群分享,每月 2 场高质量分享,点击加入社群. 关注"阿里巴巴云原生"公众号,回复关键词 0102 即可下载本 ...

  9. 贪吃蛇的java代码分析(三)

    代码剖析 在上一篇文章中,我们完成了贪吃蛇部分代码的构造.回头审视我们写的代码与思路,会发现我们遗漏了一个重要的地方,那就是:贪吃蛇的自身移动.想必大家都知道,贪吃蛇自身是会自己移动的,并且会跟随你的 ...

随机推荐

  1. PowerSploit: The Easiest Shell You'll Ever Get - Pentest Geek - Penetration Testing - Infosec Professionals

                    PowerSploit: The Easiest Shell You'll Ever Get - Pentest... Sometimes you just want ...

  2. elasticsearch的重启

    没有重启的操作,只有关闭了再启动的操作. ps -ef | grep elastic e表示全部的进程,f表示展示进程间的相关关系,如父子进程. 然后找到你启动es的那个账号,不是root,一般是新建 ...

  3. oracle数据迁移到mysql

    今天遇到需求要把oracle的部分数据搬到mysql,用java代码抓数据,然后拼接成sql语句,然后用navicat执行sql脚本的方法,导入数据库. import oracle.jdbc.driv ...

  4. 棋盘问题---poj1321(dfs)

    http://poj.org/problem?id=1321 由于搜索是原来写的,而集训的时候没来所以只能现在补补咯-_- 简单的深搜 #include<stdio.h> #include ...

  5. SpringMVC 问题 org.springframework.beans.factory.BeanDefinitionStoreException

    HTTP Status 500 – Internal Server Error Type Exception Report Message Servlet.init() for servlet [sp ...

  6. pycharm中python模板代码自动生成

    # -*- coding: utf-8 -*- """ ------------------------------------------------- File Na ...

  7. python 基础 列表生成式 生成器

    列表生成式 列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式 举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, ...

  8. TC命令流量控制测试(针对具体IP地址和IP协议)

    这里测试系统为Linux操作系统,通过简单的TC命令来实现对带宽的控制. 1对具体IP地址的流量控制 这里采用iperf来进行带宽的测试,首先在服务器和客户端都安装上iperf软件,通过该软件下的命令 ...

  9. testng入门教程1在testng运行一个简单的testcase

    在eclips运行java,创建一个Java类文件名TestNGSimpleTest  C:\ > TestNG_WORKSPACE import org.testng.annotations. ...

  10. 【CUDA并行程序设计系列(1)】GPU技术简介

    http://www.cnblogs.com/5long/p/cuda-parallel-programming-1.html 本系列目录: [CUDA并行程序设计系列(1)]GPU技术简介 [CUD ...