图像描述

  • 1、数据集获取
  • 2、文本数据处理
  • 3、图像数据处理
  • 4、训练
  • 5、全部代码
  • 6、总结

1、数据集获取

数据来自:AI challenger 2017 图像描述数据集
百度网盘: https://pan.baidu.com/s/1g1XaPKzNvOurH9M44p1qrw 提取码: bag3

这里由于原训练集太大,这里仅使用验证集ai_challenger_caption_validation_20170910.zip,解压一下

2、文本数据处理

图像中文描述比赛的数据分为两部分,一是30000张图片,二是对应的描述caption_validation_annotations_20170910.json,每个样本格式如下:

[{"url": "http://img5.cache.netease.com/photo/0005/2013-09-25/99LA1FC60B6P0005.jpg", "image_id": "3cd32bef87ed98572bac868418521852ac3f6a70.jpg", "caption": ["\u4e00\u4e2a\u53cc\u81c2\u62ac\u8d77\u7684\u8fd0\u52a8\u5458\u8dea\u5728\u7eff\u8335\u8335\u7684\u7403\u573a\u4e0a", "\u4e00\u4e2a\u62ac\u7740\u53cc\u81c2\u7684\u8fd0\u52a8\u5458\u8dea\u5728\u8db3\u7403\u573a\u4e0a", "\u4e00\u4e2a\u53cc\u624b\u63e1\u62f3\u7684\u7537\u4eba\u8dea\u5728\u7eff\u8335\u8335\u7684\u8db3\u7403\u573a\u4e0a", "\u4e00\u4e2a\u62ac\u8d77\u53cc\u624b\u7684\u7537\u4eba\u8dea\u5728\u78a7\u7eff\u7684\u7403\u573a\u4e0a", "\u4e00\u4e2a\u53cc\u624b\u63e1\u62f3\u7684\u8fd0\u52a8\u5458\u8dea\u5728\u5e73\u5766\u7684\u8fd0\u52a8\u573a\u4e0a"]}, ...

"一个双臂抬起的运动员跪在绿茵茵的球场上", "一个抬着双臂的运动员跪在足球场上", "一个双手握拳的男人跪在绿茵茵的足球场上", "一个抬起双手的男人跪在碧绿的球场上", "一个双手握拳的运动员跪在平坦的运动场上"

以上描述的特点:

  • 每一句话长短不一
  • 描述不涉及太多额外知识,尽量客观
  • 尽可能点明图片中的人物关系

这里直接下载人工描述的预处理,包括:

  • 中文jieba分词
  • word2ix,过滤低频词
  • 所有描述补齐到等长(pad_sequence)
  • 利用pack_padded_sequence进行计算加速

但是不能使用caption.pth,因为这里仅使用原来的验证集,所以用书里提供的代码自行处理一下:

# coding:utf8
import torch as t
import numpy as np
import json
import jieba
import tqdmclass Config:annotation_file = r'... your path\ai_challenger_caption_validation_20170910\caption_validation_annotations_20170910.json'unknown = '</UNKNOWN>'end = '</EOS>'padding = '</PAD>'max_words = 5000min_appear = 2save_path = r'... your path\ai_challenger_caption_validation_20170910\caption_2.pth'# START='</START>'
# MAX_LENS = 25,def process(**kwargs):opt = Config()for k, v in kwargs.items():setattr(opt, k, v)with open(opt.annotation_file) as f:data = json.load(f)# 8f00f3d0f1008e085ab660e70dffced16a8259f6.jpg -> 0id2ix = {item['image_id']: ix for ix, item in enumerate(data)}# 0-> 8f00f3d0f1008e085ab660e70dffced16a8259f6.jpgix2id = {ix: id for id, ix in (id2ix.items())}assert id2ix[ix2id[10]] == 10captions = [item['caption'] for item in data]# 分词结果cut_captions = [[list(jieba.cut(ii, cut_all=False)) for ii in item] for item in tqdm.tqdm(captions)]word_nums = {}  # '快乐'-> 10000 (次)def update(word_nums):def fun(word):word_nums[word] = word_nums.get(word, 0) + 1return Nonereturn funlambda_ = update(word_nums)_ = {lambda_(word) for sentences in cut_captions for sentence in sentences for word in sentence}# [ (10000,u'快乐'),(9999,u'开心') ...]word_nums_list = sorted([(num, word) for word, num in word_nums.items()], reverse=True)#### 以上的操作是无损,可逆的操作################################ **********以下会删除一些信息******************# 1. 丢弃词频不够的词# 2. ~~丢弃长度过长的词~~words = [word[1] for word in word_nums_list[:opt.max_words] if word[0] >= opt.min_appear]words = [opt.unknown, opt.padding, opt.end] + wordsword2ix = {word: ix for ix, word in enumerate(words)}ix2word = {ix: word for word, ix in word2ix.items()}assert word2ix[ix2word[123]] == 123ix_captions = [[[word2ix.get(word, word2ix.get(opt.unknown)) for word in sentence]for sentence in item]for item in cut_captions]readme = u"""word:词ix:indexid:图片名caption: 分词之后的描述,通过ix2word可以获得原始中文词"""results = {'caption': ix_captions,'word2ix': word2ix,'ix2word': ix2word,'ix2id': ix2id,'id2ix': id2ix,'padding': '</PAD>','end': '</EOS>','readme': readme}t.save(results, opt.save_path)print('save file in %s' % opt.save_path)def test(ix, ix2=4):results = t.load(opt.save_path)ix2word = results['ix2word']examples = results['caption'][ix][4]sentences_p = (''.join([ix2word[ii] for ii in examples]))sentences_r = data[ix]['caption'][ix2]assert sentences_p == sentences_r, 'test failed'test(1000)print('test success')if __name__ == '__main__':process()

得到了一个caption_2.pth文件,一个使用的例子:

import torchdata = torch.load(r'... your path\ai_challenger_caption_validation_20170910\caption_2.pth')
ix2word = data['ix2word']
ix2id = data['ix2id']
caption = data['caption']img_ix = 0
img_caption = caption[img_ix]print(ix2id[img_ix])
print(img_caption)sen = img_caption[0]
sen = [ix2word[_] for _ in sen]
str = ''.join(sen)
print(str)
3cd32bef87ed98572bac868418521852ac3f6a70.jpg
[[4, 178, 79, 3, 47, 159, 5, 112, 3, 20], [4, 176, 178, 3, 47, 159, 5, 64, 6], [4, 19, 361, 3, 7, 159, 5, 112, 3, 64, 6], [4, 79, 19, 3, 7, 159, 5, 124, 3, 20], [4, 19, 361, 3, 47, 159, 5, 65, 3, 26, 6]]
一个双臂抬起的运动员跪在绿茵茵的球场上

3、图像数据处理

利用ResNet在池化层的输出、全连接层的输入,这里复制并修改ResNet的源码,在倒数第二层就输出返回,修改完ResNet后,提取3万张图片的特征,代码如下:

from torchvision.models import resnet50def new_forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)# x = self.fc(x)return xmodel = resnet50(pretrained=True)
model.forward = lambda x:new_forward(model, x)
model = model.cuda()import torchvision as tv  # 一般的图像转换操作类
from PIL import Image  # pillow库,PIL读取图片
import numpy as np
import torch
from torch.utils import data
import osIMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
normalize = tv.transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD)
transforms = tv.transforms.Compose([tv.transforms.Resize(256),tv.transforms.CenterCrop(256),tv.transforms.ToTensor(),normalize
])class Dataset(data.Dataset):def __init__(self, caption_data_path):data = torch.load('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/caption_2.pth')self.ix2id = data['ix2id']self.imgs = [os.path.join(caption_data_path, self.ix2id[_]) for _ in range(len(self.ix2id))]def __getitem__(self, item):x = Image.open(self.imgs[item]).convert('RGB')x = transforms(x)  # ([3, 256, 256])return x, itemdef __len__(self):return len(self.imgs)batch_size = 32
dataset = Dataset('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/caption_validation_images_20170910')
dataloader = data.DataLoader(dataset, batch_size=batch_size, shuffle=False)results = torch.Tensor(len(dataloader.dataset), 2048).fill_(0)for ii, (imgs, indexs) in enumerate(dataloader):assert indexs[0] == batch_size * iiimgs = imgs.cuda()features = model(imgs)results[ii * batch_size:(ii + 1) * batch_size] = features.data.cpu()print(ii * batch_size)torch.save(results, '/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/results_2048.pth')

这里得到了原本ResNet的1000维特征向量results.pth和修改最后线性层的2048维向量results_2048.pth(torch.Size([30000, 2048]))

4、训练

关于np.random.choice函数:

np.random.choice(5, 3)
>> array([0, 3, 4]) # random

关于pack_padded_sequence可见此

关于beam search的代码可见书的官网

最大迭代次数为5,每100代查看一次结果:

'一个 一个 的 男人 </EOS>'
'一个 穿着 的 男人 </EOS>'
'一个 穿着 球衣 的 男人 在 运动场 上 </EOS>'
'一个 穿着 球衣 的 男人 在 运动场 上 </EOS>'
'一个 穿着 球衣 的 男人 在 运动场 上 踢足球 </EOS>'
'两个 穿着 球衣 的 男人 在 球场上 踢足球 </EOS>'
'两个 穿着 运动服 的 男人 在 球场上 踢足球 </EOS>'
'一个 右手 拿 着 话筒 的 男人 在 舞台 上 表演 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 表演 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 唱歌 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 站 在 舞台 上 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 表演 节目 </EOS>'
'一个 戴着 帽子 的 女人 在 舞台 上 表演 节目 </EOS>'
'一个 戴着 帽子 的 女人 在 舞台 上 表演 节目 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 表演 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 唱歌 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 站 在 草地 上 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 在 舞台 上 表演 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 站 在 草地 上 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 站 在 道路 上 </EOS>'
...


这里每次迭代都保存了模型,名字分别为model_0.pthmodel_4.pth

这里自己找一下照片测试一下:

'一个 穿着 球衣 的 男人 在 球场上 踢足球 </EOS>'
'一个 穿着 黑色 上衣 的 男人 和 一个 戴着 帽子 的 男人 站 在 道路 上 </EOS>'
'一个 戴着 帽子 的 男人 在 舞台 上 表演 </EOS>'
'一个 戴着 帽子 的 男人 和 一个 戴着 帽子 的 男人 走 在 道路 上 </EOS>'
'一个 右手 拿 着 话筒 的 男人 在 舞台 上 唱歌 </EOS>'

结果有点bug,修改一下最大迭代次数为100:



以及再怎么修改隐层层数、个数,结果都没有太好,猜测可能的原因在于Beam Search搜索、本身数据集很脏…

最大迭代次数为50:


5、全部代码

import torch
from torch.utils import data
import numpy as np
import tqdm
from torch.nn.utils.rnn import pack_padded_sequence
from beam_search import CaptionGenerator
from PIL import Image
import torchvision as tv
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from torchvision.models import resnet50
from torch.utils.data.dataset import random_splitdef new_forward(self, x):x = self.conv1(x)x = self.bn1(x)x = self.relu(x)x = self.maxpool(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = self.avgpool(x)x = x.view(x.size(0), -1)# x = self.fc(x)return xmodel_feature = resnet50(pretrained=True)
model_feature.forward = lambda x:new_forward(model_feature, x)
model_feature = model_feature.cuda()IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
normalize = tv.transforms.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD)
transforms = tv.transforms.Compose([tv.transforms.Resize(256),tv.transforms.CenterCrop(256),tv.transforms.ToTensor(),normalize
])class CaptionDataset(data.Dataset):def __init__(self):data = torch.load('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/caption_2.pth')# ix2word = data['ix2word']self.ix2id = data['ix2id']self.caption = data['caption']word2ix = data['word2ix']self.padding = word2ix.get(data.get('padding'))self.end = word2ix.get(data.get('end'))self.feature = torch.load('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/results_2048.pth')def __getitem__(self, item):img = self.feature[item]caption = self.caption[item]rdn_index = np.random.choice(len(caption), 1)[0]        # 5句描述随机选一句caption = caption[rdn_index]return img, torch.LongTensor(caption), itemdef __len__(self):return len(self.ix2id)def create_collate_fn(padding, eos, max_length=50):def collate_fn(img_cap):"""将多个样本拼接在一起成一个batch输入: list of data,形如[(img1, cap1, index1), (img2, cap2, index2) ....]拼接策略如下:- batch中每个样本的描述长度都是在变化的,不丢弃任何一个词\选取长度最长的句子,将所有句子pad成一样长- 长度不够的用</PAD>在结尾PAD- 没有START标识符- 如果长度刚好和词一样,那么就没有</EOS>返回:- imgs(Tensor): batch_sie*2048- cap_tensor(Tensor): batch_size*max_length (I think it is wrong!)- lengths(list of int): 长度为batch_size- index(list of int): 长度为batch_size"""img_cap.sort(key=lambda p: len(p[1]), reverse=True)imgs, caps, indexs = zip(*img_cap)imgs = torch.cat([img.unsqueeze(0) for img in imgs], 0)     # batch * 2048lengths = [min(len(c) + 1, max_length) for c in caps]batch_length = max(lengths)cap_tensor = torch.LongTensor(batch_length, len(caps)).fill_(padding)for i, c in enumerate(caps):end_cap = lengths[i] - 1if end_cap < batch_length:cap_tensor[end_cap, i] = eoscap_tensor[:end_cap, i].copy_(c[:end_cap])return (imgs, (cap_tensor, lengths), indexs)        # batch * 2048, (max_len * batch, ...), ...return collate_fnbatch_size = 32
max_epoch = 50
embedding_dim = 64
hidden_size = 64
lr = 1e-4
num_layers = 2def get_dataloader():dataset = CaptionDataset()n_train = int(len(dataset) * 0.9)split_train, split_valid = random_split(dataset=dataset, lengths=[n_train, len(dataset) - n_train])train_dataloader = data.DataLoader(split_train, batch_size=batch_size, shuffle=True, num_workers=4,collate_fn=create_collate_fn(dataset.padding, dataset.end))valid_dataloader = data.DataLoader(split_valid, batch_size=batch_size, shuffle=True, num_workers=4,collate_fn=create_collate_fn(dataset.padding, dataset.end))return train_dataloader, valid_dataloaderclass Net(torch.nn.Module):def __init__(self, word2ix, ix2word):super(Net,self).__init__()self.ix2word = ix2wordself.word2ix = word2ixself.embedding = torch.nn.Embedding(len(word2ix), embedding_dim)self.fc = torch.nn.Linear(2048, hidden_size)self.rnn = torch.nn.LSTM(embedding_dim, hidden_size, num_layers=num_layers)self.classifier = torch.nn.Linear(hidden_size, len(word2ix))def forward(self, img_feats, captions, lengths):embeddings = self.embedding(captions)       # seq_len * batch * embeddingimg_feats = self.fc(img_feats).unsqueeze(0)     # img_feats是2048维的向量,通过全连接层转为256维的向量,和词向量一样, 1 * batch * hidden_sizeembeddings = torch.cat([img_feats, embeddings], 0)      # 将img_feats看成第一个词的词向量, (1+seq_len) * batch * hidden_sizepacked_embeddings = pack_padded_sequence(embeddings, lengths)       # PackedSequence, lengths - batch中每个seq的有效长度outputs, state = self.rnn(packed_embeddings)    # seq_len * batch * (1*256), (1*2) * batch * hidden_size, lstm的输出作为特征用来分类预测下一个词的序号, 因为输入是PackedSequence,所以输出的output也是PackedSequence, PackedSequence第一个元素是Variable,第二个元素是batch_sizes,即batch中每个样本的长度*pred = self.classifier(outputs[0])return pred, statedef generate(self, img, eos_token='</EOS>', beam_size=3, max_caption_length=30, length_normalization_factor=0.0):   # 根据图片生成描述,主要是使用beam search算法以得到更好的描述cap_gen = CaptionGenerator(embedder=self.embedding,rnn=self.rnn,classifier=self.classifier,eos_id=self.word2ix[eos_token],beam_size=beam_size,max_caption_length=max_caption_length,length_normalization_factor=length_normalization_factor)if next(self.parameters()).is_cuda:img = img.cuda()img = img.unsqueeze(0)img = self.fc(img).unsqueeze(0)sentences, score = cap_gen.beam_search(img)sentences = [' '.join([self.ix2word[idx.item()] for idx in sent])for sent in sentences]return sentencesdef evaluate(dataloader):model.eval()total_loss = 0with torch.no_grad():for ii, (imgs, (captions, lengths), indexes) in enumerate(dataloader):imgs = imgs.to(device)captions = captions.to(device)input_captions = captions[:-1]target_captions = pack_padded_sequence(captions, lengths)[0]score, _ = model(imgs, input_captions, lengths)loss = criterion(score, target_captions)total_loss += loss.item()model.train()return total_lossif __name__ == '__main__':train_dataloader, valid_dataloader = get_dataloader()_data = torch.load('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/caption_2.pth')word2ix, ix2word = _data['word2ix'], _data['ix2word']# max_loss = float('inf')     # 221max_loss = 263device = torch.device('cuda')model = Net(word2ix, ix2word)optimizer = torch.optim.Adam(model.parameters(), lr=lr)criterion = torch.nn.CrossEntropyLoss()model.to(device)losses = []valid_losses = []img_path = '/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/123.jpg'raw_img = Image.open(img_path).convert('RGB')raw_img = transforms(raw_img)  # 3*256*256img_feature = model_feature(raw_img.cuda().unsqueeze(0))print(img_feature)for epoch in range(max_epoch):for ii, (imgs, (captions, lengths), indexes) in tqdm.tqdm(enumerate(train_dataloader)):optimizer.zero_grad()imgs = imgs.to(device)captions = captions.to(device)input_captions = captions[:-1]target_captions = pack_padded_sequence(captions, lengths)[0]score, _ = model(imgs, input_captions, lengths)loss = criterion(score, target_captions)loss.backward()optimizer.step()losses.append(loss.item())if (ii + 1) % 20 == 0:  # 可视化# 可视化原始图片 + 可视化人工的描述语句# raw_img = _data['ix2id'][indexes[0]]# img_path = '/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/caption_validation_images_20170910/' + raw_img# raw_img = Image.open(img_path).convert('RGB')# raw_img = tv.transforms.ToTensor()(raw_img)## raw_caption = captions.data[:, 0]# raw_caption = ''.join([_data['ix2word'][ii.item()] for ii in raw_caption])## results = model.generate(imgs.data[0])## print(img_path, raw_caption, results)### print(model.generate(img_feature.squeeze(0)))tmp = evaluate(valid_dataloader)valid_losses.append(tmp)if tmp < max_loss:max_loss = tmptorch.save(model.state_dict(),'/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/model_best.pth')print(max_loss)     # 190 111plt.figure(1)plt.plot(losses)plt.figure(2)plt.plot(valid_losses)plt.show()# model.load_state_dict(torch.load('/mnt/Data1/ysc/ai_challenger_caption_validation_20170910/model_best.pth'))# print(model.generate(img_feature.squeeze(0)))

6、总结

最后效果不是很好,好像每次关于图像的处理效果我都不是很理想…

【PyTorch】13 Image Caption:让神经网络看图讲故事相关推荐

  1. 【PyTorch实战】图像描述——让神经网络看图讲故事

    图像描述--让神经网络看图讲故事 1. 图像描述介绍 2. 数据 2.1 数据介绍 2.2 图像数据处理 2.3 数据加载 3. 模型与训练 3. 实验结果 参考资料 Image Caption: 图 ...

  2. 图网络究竟在研究什么?从15篇研究综述看图神经网络GNN的最新研究进展

    近年来,由于图结构的强大表现力,用机器学习方法分析图的研究越来越受到重视.图神经网络(GNN)是一类基于深度学习的处理图域信息的方法. 到目前,相关研究的已经非常多了,不过我们回过头来看思考和回顾一下 ...

  3. 看图猜口袋妖怪属性,这个神经网络可能比你强!(教程)

    本文来自AI新媒体量子位(QbitAI) △ Who's that Pokémon? 还记得去年异常火爆,然而最终也没能入华的Pokémon Go么?我们今天要讲的,就和<口袋妖怪>有关. ...

  4. 超详细!“看图说话”(Image Caption)项目实战

    超详细!基于pytorch的"看图说话"(Image Caption)项目实战 0.简介 1.运行环境 1.1 我的环境 1.2 建立环境 2.理论介绍 3.运行项目 3.1 项目 ...

  5. 对Image caption的一些理解(看图说话)

    1. 背景 ​ 在计算机视觉中,图像分类和目标检测任务是比较成熟的领域,已经应用到实际的产品领域.而"看图说话"要实现的功能是,给定一张图像,计算机能告诉我们图片的内容,显然,这会 ...

  6. 教你用PyTorch实现“看图说话”(附代码、学习资源)

    作者:FAIZAN SHAIKH 翻译:和中华 校对:白静 本文共2200字,建议阅读10分钟. 本文用浅显易懂的方式解释了什么是"看图说话"(Image Captioning), ...

  7. 30行代码就可以实现看图识字!python使用tensorflow.keras搭建简单神经网络

    文章目录 搭建过程 1. 引入必需的库 2. 引入数据集 3. 搭建神经网络层 4. 编译神经网络模型 5. 训练模型 效果测试 大概几个月前,神经网络.人工智能等概念在我心里仍高不可攀,直到自己亲身 ...

  8. Neurons字幕组 | 2分钟带你看懂李飞飞论文:神经网络是怎样给一幅图增加文字描述,实现“看图说话”的?(附论文下载)

    Neurons字幕组出品 翻译|智博校对|龙牧雪 时间轴|虫2后期| Halo 项目管理|大力 Neurons字幕组 第四期作品震撼来袭! Neurons字幕组源自英文单词Neuron,一个个独立的神 ...

  9. Image caption——图像理解——看图说话综述(2015-2018)

    本文章没有太多公式,仅仅讲述个人理解,主要怎对入门人士.文中定有许多错误,希望大家能相互交流. Image caption顾名思义,即可让算法根据输入的一幅图自动生成对应的描述性文字.有点类似于看图说 ...

  10. 看图说话:从图片到文字

    文章目录 模型整体结构 比较翻译模型和看图说话 编码器模型 解码器模型 训练和评估 了解数据集 了解pack_padded_sequence函数和pad_packed_sequence函数 对梯度进行 ...

最新文章

  1. 使用Apache TVM将机器学习编译为WASM和WebGPU
  2. 【LeetCode从零单排】No.169 Majority Element(hashmap用法)
  3. jQuery---操作类名
  4. 美联储降息首日:资本市场反向操作 道指狂泻800点
  5. windows纯手工安装php和Apache以及连接mysql
  6. CSU 8月月赛 Decimal 小数化分数
  7. Pazera Free MP4 To MP3 Converter 1.6 中文64位+32位便携版,免费的视频转换器
  8. mysql dump gtid_GTID环境下mysqldump set-gtid-purged取值
  9. 黎曼ζ(2)的导数:ζ'(2)=-1
  10. python读取plt文件吗_如何读取连续的.plt文件并存储它们
  11. k8s 配置 Secret 集成Harbor
  12. 【C语言】把一个结构体指针转换为另一个结构体指针
  13. 医保局:医保政策性利好消息!
  14. C#实现生产者与消费者关系
  15. 云服务ftp服务器搭建_如何在阿里云服务器搭建FTP服务器,在本地电脑连接并操作...
  16. 酷狗服务器显示失败怎么回事,酷狗音乐如何分享音乐失败怎么办 ?酷狗音乐分享音乐失败如何解决?...
  17. GitHub热榜:来膜拜这个流弊的AI框架!
  18. 教你如何解决网络所面临的安全问题?
  19. 函数柯理化是什么,手动实现一个柯理化函数
  20. INI文件编程,WINAPI函数WritePrivateProfileString,GetPrivateProfileString

热门文章

  1. ACM基础题——小刘认亲
  2. 医院时钟系统(网络授时设备)设计方案
  3. ActionBar隐藏app图标
  4. 包误差率(PER)与BER相关
  5. css 固定定位失效问题 position: fixed
  6. Unity3D案例太空射击(Space Shooter)流程介绍与代码分析(中)
  7. 华硕(ASUS)X554LP笔记本重装win7后网卡和USB驱动问题的解决
  8. OpenCV—Python 导向滤波
  9. 32位系统支持多大内存 Windows32位/64位系统最大支持内存详解
  10. 汉语拼音字母n和l、in和ing的发音有什么区别?