转载地址:MindSpore21天实战营(2):基于BERT实现中文新闻分类实战_MindSpore_昇腾论坛_华为云论坛

作者:胡琦

“ModelArts + MindSpore”实战BERT中文新闻分类

Copy攻城狮人狠话不多,学AI就到huaweicloud.ai,和“MM”一起玩转AI。

前言

大家好,我是Copy攻城狮胡琦,有幸参与华为业界首个全场景AI实战营。今天是MIndSpore 21天实战营的第二次课,光接触的名词就已经碉堡了--一站式AI开发平台ModelArts、全栈全场景AI计算框架MindSpore、昇腾Ascend 910 8卡服务器、最强NLP模型BERT,优秀的她们组合在一起会有怎样的化学反应?关于BERT,本大狮有幸尝试过基于TensorFlow的实现--【手模手学ModelArts】分分钟部署一个Bert命名实体识别在线服务,并尝试以该服务为原型“落地AI”--【Copy攻城狮日志】ModelArts与AppCube双“魔”合璧庆双节,从0到1实现了一个简单的命名实体识别应用。这次,我们将基于MIndSpore实现中文新闻分类实战。

准备

本次实战基于一站式AI开发平台ModelArts,使用昇腾Ascend 910环境,搭载MIndSpore-0.5-python3.7-aarch64。
依旧感谢小助手的暖心提示,另外有看到小伙伴问优惠券哪里领,建议关注MIndSpore官方微信及ModelArts官网。

1️⃣ 环境准备(本次实验不涉及使用到本地环境):
准备项目:华为云账号和昇腾集群公测资格

步骤:
❶ 华为云账号注册(地址: https://www.huaweicloud.com/)。
❷ 按照操作指引完成账号注册,账号注册完成后,进入到ModelArts界面,进行昇腾集群公测资格的申请。(地址: https://www.huaweicloud.com/product/modelarts.html)。
❸ 按照指引完成公测资格申请。
❹ 要完成公测资格申请,需要完成两步,实名认证和访问授权,其中实名认证需要各位学员使用自己的身份证号码和姓名完成认证,认证的审核需要一定的时间,约为1天左右。
❺ 访问授权的则直接点击右下方的“自动创建”按钮,其他的默认即可。
❻ 在弹出的界面当中点击“立即申请”按钮。
❼ 之后可以查看自己的审批状态。“审批通过”则代表已经取得了公测资格。

2️⃣ 知识准备:
Python相关知识:
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017063413904832
bert论文:https://arxiv.org/abs/1810.04805

3️⃣ 数据集和代码:
中文维基数据集:https://dumps.wikimedia.org/zhwiki/
数据集提取:https://github.com/attardi/wikiextractor
BERT预训练模型: http://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip
tnews数据集: https://storage.googleapis.com/cluebenchmark/tasks/tnews_public.zip
代码:https://21days-bert.obs.cn-north-4.myhuaweicloud.com/bert.zip
BERT官方仓库: https://github.com/google-research/bert
MindSpore提供的bert_base.ckpt:https://21days-bert.obs.cn-north-4.myhuaweicloud.com/bert_base.ckpt?AccessKeyId=M7KX8KLMT0ZL1P8QWXZ5&Expires=1634522231&Signature=1CSNQY%2BK%2BmQxRBkqBwBHMbf0%2BE4%3D

数据处理

本次数据处理基于ModelArts的我的笔记本实现,不得不夸赞一下我的笔记本--即开即用、用于机器学习的在线集成开发环境,可以轻松的构建、训练、调试、部署机器学习算法与模型。无论是CPU环境还是GPU环境,一键切换,且仍然保留工作空间的文件,用来处理数据也是非常便捷。本次实践采用MindSpore 21天实战营提供的处理之后的tnews数据集。

中文wiki数据集提取

我们下载的中文维基数据集是.bz2后缀的文件,需要进行转换,这里用到的工具是wikiextractor,WikiExtractor.py是一个Python脚本,可从Wikipedia数据库转储中提取和清除文本。这里使用pip安装,在我的笔记本中新建.ipynb文件执行:

!pip install wikiextractor
!python -m wikiextractor.WikiExtractor zhwiki-latest-pages-articles.xml.bz2 -o tnews-pre --log_file wiki.log

可运行!python -m wikiextractor.WikiExtractor查看具体参数,我这里将数据提取到tnew-pre文件夹,由于数据比较庞大,提取比较耗时,我开了8U+64GiB的环境,CPU基本跑到80%,大概20分钟左右。

提取完毕会生成形如AA/wiki_00的文件,里面的内容包裹在doc中。

转换数据集格式

此次实践在MindSpore中使用的是tfrecord的格式,同时为数据集添加词表处理,这里就需要用到BERT预训练模型中的中文语义表文件==vocab.txt,整个过程通过BERT提供的脚本create_pretraining_data.py实现。这里我思考了一下,从数据集中抽取了20条,以8:2的比例拆分生成train.tf_recorddev.tf_record,不知道逻辑和流程对不对。

生成tfrecord格式:

python bert/create_pretraining_data.py \--input_file=text/AN/wiki_72,text/AC/wiki_68,text/AK/wiki_02,text/AD/wiki_78,text/AE/wiki_77,text/AG/wiki_71,text/AN/wiki_90,text/AO/wiki_21,text/AI/wiki_19,text/AK/wiki_77,text/AM/wiki_00,text/AA/wiki_76,text/AH/wiki_63,text/AF/wiki_21,text/AJ/wiki_55,text/AG/wiki_19 \--output_file=temp/train.tf_record \--vocab_file=vocab.txt \--do_lower_case=True \--max_seq_length=128 \--max_predictions_per_seq=20 \--masked_lm_prob=0.15 \--random_seed=12345 \--dupe_factor=5
python bert/create_pretraining_data.py \--input_file=text/AA/wiki_77,text/AL/wiki_48,text/AB/wiki_44,text/AJ/wiki_62 \--output_file=temp/dev.tf_record \--vocab_file=vocab.txt \--do_lower_case=True \--max_seq_length=128 \--max_predictions_per_seq=20 \--masked_lm_prob=0.15 \--random_seed=12345 \--dupe_factor=5

这里值得注意的是,当我下载完bert源码时,通过pip去安装依赖,默认装的TensorFlow是2.x的版本,需要重新安装1.x的版本,不然在进行tfrecord格式生成时会报错TensorFlow 2.x下没有某些特定的API,这应该是bert/create_pretraining_data.py这个脚本本身使用的是低版本的TensorFlow。数据格式转换结果如下:

转换完毕,我们还能通过Moxing将文件直接上传到OBS:

import moxing as mox
mox.file.copy_parallel('train.tf_record','obs://huqi88/mindspore-camp/BERT-new/dataset/tnews/train.tf_record')
mox.file.copy_parallel('dev.tf_record','obs://huqi88/mindspore-camp/BERT-new/dataset/tnews/dev.tf_record')

BERT fine-tune

首先我们将代码、数据集、预训练模型按照如下位置上传到OBS:

├─bert                                       // MindSpore提供的BERT代码
│  ├─scripts
│  └─src
│  └─vocab.txt
│  └─bert_base.ckpt
|  └─src
└─dataset                              // 处理完毕的数据集
│  └─tnews
│  └─├─dev.tf_record
│  └─├─train.tf_record

鉴于近期ModelArts训练作业中用到的昇腾910资源比较火爆,本次实践转用ModelArts的notebook中的Ascend资源,因此我们需要在开发环境中新建一个Ascend 910的notebook。

在使用Ascend环境的时候,我们的文件是挂载在OBS中的,因此在新建环境的时候,我们要注意OBS的路径,我这边直接选择的是包含代码、预训练模型文件以及数据集的最外层文件夹目录,同时还需要在notebook的文件界面点击Sync OBS将环境中的文件和OBS中的文件进行同步。

我们在bert代码的目录下新建finetune.ipynb,编写finetune的代码。当然,这里值得注意的是,如果基于实战营的源代码来运行的话,需要简单修改一些配置。比如bert/src/finetune_config.py中我们需要修改一些文件路径的参数。

我的finetune.ipynb如下,其实是复制的finetube.py,简单修改下配置:

import os,sys
source_file_path = os.environ['HOME'] + '/work/' + 'bert' + '/'
os.chdir(source_file_path)
print(source_file_path)# Copyright 2020 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================'''
Bert finetune script.
'''import os
import sys
import argparse
from src.utils import BertFinetuneCell, BertCLS, BertNER, BertSquad, BertSquadCell
from src.finetune_config import cfg, bert_net_cfg, tag_to_index
import mindspore.common.dtype as mstype
from mindspore import context
from mindspore import log as logger
import mindspore.dataset as de
import mindspore.dataset.transforms.c_transforms as C
from mindspore.nn.wrap.loss_scale import DynamicLossScaleUpdateCell
from mindspore.nn.optim import AdamWeightDecayDynamicLR, Lamb, Momentum
from mindspore.train.model import Model
from mindspore.train.callback import Callback
from mindspore.train.callback import CheckpointConfig, ModelCheckpoint
from mindspore.train.serialization import load_checkpoint, load_param_into_netimport moxing as moxos.environ['MINDSPORE_HCCL_CONFIG_PATH'] = os.getenv('RANK_TABLE_FILE')
job_id = os.getenv('JOB_ID')
job_id = job_id if job_id != "" else "default"
device_id = int(os.getenv('DEVICE_ID'))
device_num = int(os.getenv('RANK_SIZE'))
# global_rank_id = int(os.getenv('RANK_ID').split('-')[-1])
global_rank_id = 2
rank_id = device_id + global_rank_id * 8class LossCallBack(Callback):'''Monitor the loss in training.If the loss is NAN or INF, terminate training.Note:If per_print_times is 0, do not print loss.Args:per_print_times (int): Print loss every times. Default: 1.'''def __init__(self, per_print_times=1):super(LossCallBack, self).__init__()if not isinstance(per_print_times, int) or per_print_times < 0:raise ValueError("print_step must be in and >= 0.")self._per_print_times = per_print_timesdef step_end(self, run_context):cb_params = run_context.original_args()with open("./loss.log", "a+") as f:print("epoch: {}, step: {}, outputs are {}".format(cb_params.cur_epoch_num, cb_params.cur_step_num,str(cb_params.net_outputs)))def get_dataset(batch_size=1, repeat_count=1, distribute_file=''):'''get dataset'''ds = de.TFRecordDataset([cfg.data_file], cfg.schema_file, columns_list=["input_ids", "input_mask","segment_ids", "label_ids"])type_cast_op = C.TypeCast(mstype.int32)ds = ds.map(input_columns="segment_ids", operations=type_cast_op)ds = ds.map(input_columns="input_mask", operations=type_cast_op)ds = ds.map(input_columns="input_ids", operations=type_cast_op)ds = ds.map(input_columns="label_ids", operations=type_cast_op)ds = ds.repeat(repeat_count)# apply shuffle operationbuffer_size = 960ds = ds.shuffle(buffer_size=buffer_size)# apply batch operationsds = ds.batch(batch_size, drop_remainder=True)return dsdef get_squad_dataset(batch_size=1, repeat_count=1, distribute_file=''):'''get SQuAD dataset'''ds = de.TFRecordDataset([cfg.data_file], cfg.schema_file, columns_list=["input_ids", "input_mask", "segment_ids","start_positions", "end_positions","unique_ids", "is_impossible"])type_cast_op = C.TypeCast(mstype.int32)ds = ds.map(input_columns="segment_ids", operations=type_cast_op)ds = ds.map(input_columns="input_ids", operations=type_cast_op)ds = ds.map(input_columns="input_mask", operations=type_cast_op)ds = ds.map(input_columns="start_positions", operations=type_cast_op)ds = ds.map(input_columns="end_positions", operations=type_cast_op)ds = ds.repeat(repeat_count)buffer_size = 960ds = ds.shuffle(buffer_size=buffer_size)ds = ds.batch(batch_size, drop_remainder=True)return dsdef sync_dataset(data_url):import moxing as moximport sysimport timesync_lock = "/tmp/copy_sync.lock"if device_id % min(device_num, 8) == 0 and not os.path.exists(sync_lock):mox.file.copy_parallel(data_url, "dataset/")print("===finish download datasets===")try:os.mknod(sync_lock)except:passprint("===save flag===")while True:if os.path.exists(sync_lock):breaktime.sleep(1)def test_train():'''finetune function'''target = device_targetif target == "Ascend":devid = int(os.getenv('DEVICE_ID'))context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", device_id=devid)elif target == "GPU":context.set_context(mode=context.GRAPH_MODE, device_target="GPU")if bert_net_cfg.compute_type != mstype.float32:logger.warning('GPU only support fp32 temporarily, run with fp32.')bert_net_cfg.compute_type = mstype.float32else:raise Exception("Target error, GPU or Ascend is supported.")#BertCLSTrain for classification#BertNERTrain for sequence labelingif cfg.task == 'NER':if cfg.use_crf:netwithloss = BertNER(bert_net_cfg, True, num_labels=len(tag_to_index), use_crf=True,tag_to_index=tag_to_index, dropout_prob=0.1)else:netwithloss = BertNER(bert_net_cfg, True, num_labels=cfg.num_labels, dropout_prob=0.1)elif cfg.task == 'SQUAD':netwithloss = BertSquad(bert_net_cfg, True, 2, dropout_prob=0.1)else:netwithloss = BertCLS(bert_net_cfg, True, num_labels=cfg.num_labels, dropout_prob=0.1)if cfg.task == 'SQUAD':dataset = get_squad_dataset(bert_net_cfg.batch_size, cfg.epoch_num)else:dataset = get_dataset(bert_net_cfg.batch_size, cfg.epoch_num)# optimizersteps_per_epoch = dataset.get_dataset_size()print("step per epoch: ", steps_per_epoch)if cfg.optimizer == 'AdamWeightDecayDynamicLR':optimizer = AdamWeightDecayDynamicLR(netwithloss.trainable_params(),decay_steps=steps_per_epoch * cfg.epoch_num,learning_rate=cfg.AdamWeightDecayDynamicLR.learning_rate,end_learning_rate=cfg.AdamWeightDecayDynamicLR.end_learning_rate,power=cfg.AdamWeightDecayDynamicLR.power,warmup_steps=int(steps_per_epoch * cfg.epoch_num * 0.1),weight_decay=cfg.AdamWeightDecayDynamicLR.weight_decay,eps=cfg.AdamWeightDecayDynamicLR.eps)elif cfg.optimizer == 'Lamb':optimizer = Lamb(netwithloss.trainable_params(), decay_steps=steps_per_epoch * cfg.epoch_num,start_learning_rate=cfg.Lamb.start_learning_rate, end_learning_rate=cfg.Lamb.end_learning_rate,power=cfg.Lamb.power, weight_decay=cfg.Lamb.weight_decay,warmup_steps=int(steps_per_epoch * cfg.epoch_num * 0.1), decay_filter=cfg.Lamb.decay_filter)elif cfg.optimizer == 'Momentum':optimizer = Momentum(netwithloss.trainable_params(), learning_rate=cfg.Momentum.learning_rate,momentum=cfg.Momentum.momentum)else:raise Exception("Optimizer not supported.")# load checkpoint into networkckpt_config = CheckpointConfig(save_checkpoint_steps=steps_per_epoch, keep_checkpoint_max=1)ckpoint_cb = ModelCheckpoint(prefix=cfg.ckpt_prefix, directory=cfg.ckpt_dir, config=ckpt_config)print(cfg.pre_training_ckpt)param_dict = load_checkpoint('/home/ma-user/work/bert/bert_base.ckpt')
#     param_dict = load_checkpoint(cfg.pre_training_ckpt)load_param_into_net(netwithloss, param_dict)update_cell = DynamicLossScaleUpdateCell(loss_scale_value=2**32, scale_factor=2, scale_window=1000)if cfg.task == 'SQUAD':netwithgrads = BertSquadCell(netwithloss, optimizer=optimizer, scale_update_cell=update_cell)else:netwithgrads = BertFinetuneCell(netwithloss, optimizer=optimizer, scale_update_cell=update_cell)print("start to train.")model = Model(netwithgrads)print(cfg.epoch_num, dataset)model.train(cfg.epoch_num, dataset, callbacks=[LossCallBack(), ckpoint_cb])parser = argparse.ArgumentParser(description='Bert finetune')
# parser.add_argument('--device_target', type=str, default='Ascend', help='Device target')
# parser.add_argument('--data_url', type=str, default='Ascend', help='data url')
# parser.add_argument('--train_url', type=str, default='Ascend', help='train url')
# args_opt = parser.parse_args()
device_target = 'Ascend'
# predict='侃爷参选'
data_url='obs://huqi88/mindspore-camp/BERT-finetune-new/dataset/tnews/'
train_url='obs://huqi88/mindspore-camp/BERT-notebook'if __name__ == "__main__":print("start to run.", sys.argv)sync_dataset(data_url)test_train()if rank_id % device_num == 0:mox.file.copy_parallel("/home/ma-user/work/train", train_url)

执行代码之后,会进行3个epoch的训练,然后将训练得到的tnews-3_3335.ckpt。在我的这次实践中该ckpt文件存放在/home/ma-user/work/train,并且会上传到OBS。

验证

同样的我们把evaluation.py复制过来新建evaluation.ipynb,修改少许代码及evaluation_config配置,即可验证上一步finetune得到的模型。

我的evaluation.ipynb如下,依旧注意的是路径,当然还有predict入参。本次尝试判断侃爷参选,结果是娱乐新闻,还是挺准备的。

import os,sys
source_file_path = os.environ['HOME'] + '/work/' + 'bert' + '/'
os.chdir(source_file_path)
print(source_file_path)# Copyright 2020 Huawei Technologies Co., Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================"""
Bert evaluation script.
"""import os
import sys
import argparse
import numpy as np
import mindspore.common.dtype as mstype
from mindspore import context
from mindspore import log as logger
from mindspore.common.tensor import Tensor
import mindspore.dataset as de
import mindspore.dataset.transforms.c_transforms as C
from mindspore.train.model import Model
from mindspore.train.serialization import load_checkpoint, load_param_into_net
from src.evaluation_config import cfg, bert_net_cfg
from src.utils import BertNER, BertCLS, BertCLSModel
from src.CRF import postprocess
#from src.cluener_evaluation import submit
from src.finetune_config import tag_to_index
from convert_example import convert_textimport moxing as mox# 省略部分代码parser = argparse.ArgumentParser(description='Bert eval')
# parser.add_argument('--device_target', type=str, default='Ascend', help='Device target')
# parser.add_argument('--data_url', type=str, default='Ascend', help='data url')
# parser.add_argument('--train_url', type=str, default='Ascend', help='train url')
# parser.add_argument('--predict', type=str, default='', help='text to predict')
# args_opt = parser.parse_args()
target = 'Ascend'
predict='侃爷参选'
data_url='obs://huqi88/mindspore-camp/BERT-new/dataset/tnews/'if __name__ == "__main__":
#     target = args_opt.device_targetprint(target)if target == "Ascend":devid = int(os.getenv('DEVICE_ID'))context.set_context(mode=context.GRAPH_MODE, device_target="Ascend", device_id=devid)elif target == "GPU":context.set_context(mode=context.GRAPH_MODE, device_target="GPU")if bert_net_cfg.compute_type != mstype.float32:logger.warning('GPU only support fp32 temporarily, run with fp32.')bert_net_cfg.compute_type = mstype.float32else:raise Exception("Target error, GPU or Ascend is supported.")print("start to run.", sys.argv)num_labels = cfg.num_labelsif predict != '':test_predict(predict)else:sync_dataset(data_url)test_eval()

notebook运行Ascend的好处是免排队,而且调试非常方便。比如,再判断一下学AI,就到huaweicloud.ai,我记得在句在训练任务中通过设置参数传递的时候是不允许输入的。在notebook中轻松就能判断出来,不错,就是技术类的!

再来试试今日头条:袁隆平团队双季稻亩产超过3000斤,厉害了,检测到农业新闻!

本次分享就告一段落,我是Copy攻城狮胡琦,欢迎一起来参与MindSpore 21天实战营,全场景AI实战营,从小白到大牛只需21天!

MindSpore21天实战营(2):基于BERT实现中文新闻分类实战相关推荐

  1. MindSpore21天实战营丨基于MindSpore的ResNet-50蘑菇“君”的识别应用体验

    借助全新的设计理念,华为云推出了 MindSpore深度学习实战营,帮助小白更快的上手高性能深度学习框架,快速训练ResNet-50,实现你的第一个手机App开发,学会智能新闻分类.篮球检测和「猜你喜 ...

  2. 基于BERT做中文文本分类(情感分析)

    Bert: BERT是一种预训练语言表示的方法,这意味着我们在大型文本语料库(例如Wikipedia)上训练通用的"语言理解"模型,然后将该模型用于我们关心的下游NLP任务,BER ...

  3. 基于CNN中文文本分类实战

    一.前言 之前写过一篇基于循环神经网络(RNN)的情感分类文章,这次我们换种思路,采用卷积神经网络(CNN)来进行文本分类任务.倘若对CNN如何在文本上进行卷积的可以移步博主的快速入门CNN在NLP中 ...

  4. 复盘:基于attention的多任务多模态情绪情感识别,基于BERT实现文本情感分类(pytorch实战)

    复盘:基于attention机制的多任务多模态情绪情感识别(pytorch实战),基于BERT实现文本情感分类 提示:系列被面试官问的问题,我自己当时不会,所以下来自己复盘一下,认真学习和总结,以应对 ...

  5. 【项目调研+论文阅读】基于BERT的中文命名实体识别方法[J] | day6

    <基于BERT的中文命名实体识别方法>王子牛 2019-<计算机科学> 文章目录 一.相关工作 二.具体步骤 1.Bi-LSTM 2.CRF结构 三.相关实验 1.数据集 2. ...

  6. 【周末送新书】基于BERT模型的自然语言处理实战

    如果你是一名自然语言处理从业者,那你一定听说过大名鼎鼎的 BERT 模型. BERT(Bidirectional Encoder Representations From Transformers)模 ...

  7. textcnn文本词向量_基于Text-CNN模型的中文文本分类实战

    1 文本分类 文本分类是自然语言处理领域最活跃的研究方向之一,目前文本分类在工业界的应用场景非常普遍,从新闻的分类.商品评论信息的情感分类到微博信息打标签辅助推荐系统,了解文本分类技术是NLP初学者比 ...

  8. 基于 LSTM-Attention 的中文新闻文本分类

    1.摘 要 经典的 LSTM 分类模型,一种是利用 LSTM 最后时刻的输出作为高一级的表示,而另一种是将所有时刻的LSTM 输出求平均作为高一级的表示.这两种表示都存在一定的缺陷,第一种缺失了前面的 ...

  9. python中文文本分析_基于CNN的中文文本分类算法(可应用于垃圾邮件过滤、情感分析等场景)...

    基于cnn的中文文本分类算法 简介 参考IMPLEMENTING A CNN FOR TEXT CLASSIFICATION IN TENSORFLOW实现的一个简单的卷积神经网络,用于中文文本分类任 ...

  10. 我的实践:pytorch框架下基于BERT实现文本情感分类

    当前,在BERT等预训练模型的基础上进行微调已经成了NLP任务的一个定式了.为了了解BERT怎么用,在这次实践中,我实现了一个最简单的NLP任务,即文本情感分类. 文章目录 1.基于BERT进行情感分 ...

最新文章

  1. HDU1548:A strange lift(Dijkstra或BFS)
  2. 小明学习Linux运维课后习题实战A
  3. [Flex]Flex SDK 4(Gumbo)更方便的自定义样式、自定义SparkSkin(三)
  4. ASP.NET Core的身份认证框架IdentityServer4(3)-术语的解释
  5. 白鹭引擎助力《迷你世界》研发团队开发3D小游戏版
  6. Java并发编程中volatile实现过程详细解析
  7. php 模拟客户端访问,PHP通过伪造和模拟客户端COOKIE登陆来采集抓取远程网址
  8. 组态王登录服务器为空,组态王服务器与客户端配置
  9. 游戏本自动掉帧_机 · 科普帖丨如何在夏天告别游戏掉帧的问题
  10. Win2008使用无线网络
  11. 按键精灵手机助手php通讯,按键精灵手机助手教程_按键精灵手机助手怎么连接手机...
  12. 栅栏密码及其变形W型栅栏密码
  13. 无线通信行业常用名词
  14. Numpy中的向量运算
  15. 一道阿姆斯特朗回旋好题( Convex HDU - 5979)
  16. healthd log 解读
  17. Google Bard vs ChatGPT:哪一个更适合创造富有创造性的文学作品?
  18. bi java lajp 和php_新宠混血儿诞生记--Java+PHP整合
  19. 当前京东数据平台用到spark 的五种方式
  20. 《詹姆斯·高斯林Java白皮书1996自译》00:概览

热门文章

  1. 纯HTML+CSS实现3D炫酷魔方(相册)
  2. 一个简单的界面拖动切换效果类ScrollViewGroup
  3. java 穷举 排列组合_穷举排列组合列表
  4. docker、containerd、runc、shim... 容器技术名词全解析
  5. php生成带文字的二维码
  6. 如何在你的 wordpress 网站中添加搜索框
  7. 怎样注册完申请个人电子邮箱?2022邮箱号码大全速看
  8. 前端可视化大屏适配方案
  9. Flow-Guided-Feature-Aggregation-的安装配置,demo运行,以及采用少量ILSVRC2015 VID数据集在其中训练
  10. python图片后缀转换---统一转换成.jpg