【序列召回推荐】(task1)NeuralCF(学习匹配函数)

news2025/2/25 22:09:17

note:

  • 和协同过滤矩阵分解的区别:NeuralCF 用一个多层的神经网络替代掉了原来简单的点积操作,另外user隐向量和item隐向量在进入MLP前需要concat拼接,其实就是两个矩阵在水平位置上拼接而已。这样就可以让用户和物品隐向量之间进行充分的交叉,提高模型整体的拟合能力。
  • 统计每个用户频率后,需要重置索引reset_index操作,否则索引号会乱掉;不想保留原来的index,则使用参数 drop=True,否则默认是保存原来的index。;关于pd.set_index()reset_index()可以参考如何在pandas中使用set_index( )与reset_index( )设置索引。
  • 统计用户频率可以使用df['user_count'] = df['user_id'].map(df['user_id'].value_counts()),如果是pyspark可以写成udf或者spark.sql统计。

文章目录

  • note:
  • 一、基础铺垫
    • 1.1 Paddle的安装与入门
    • 1.2 回顾协同过滤和矩阵分解
    • 1.3 推荐系统的召回算法
  • 二、NeuralCF模型
    • 2.1 定义Dataset
    • 2.2 定义NeuralCF模型
    • 2.3 train pipeline/valid pipeline
    • 2.4 指标计算
    • 2.5 训练过程可视化
  • 时间安排
  • Reference

一、基础铺垫

1.1 Paddle的安装与入门

在本地环境安装GPU版的paddle:https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/2.0/install/conda/linux-conda.html#cuda11

参考:
[1] paddle官方文档
[2] PyTorch-PaddlePaddle模型转化&API映射关系对照表

1.2 回顾协同过滤和矩阵分解

协同过滤:利用用户和物品之间的交互行为历史,构建出一个像下图左一样的共现矩阵(一般为交互评分矩阵)。在共现矩阵的基础上,利用每一行的用户向量相似性,找到相似用户,再利用相似用户喜欢的物品进行推荐。
在这里插入图片描述
矩阵分解:进一步加强了协同过滤的泛化能力,它把协同过滤中的共现矩阵分解成了用户矩阵和物品矩阵(如上图右侧),从用户矩阵中提取出用户隐向量,从物品矩阵中提取出物品隐向量,再利用它们之间的内积相似性进行推荐排序。矩阵分解的神经网路结构图如下。
在这里插入图片描述

图 2 中的输入层是由用户 ID 和物品 ID 生成的 One-hot 向量,Embedding 层是把 One-hot 向量转化成稠密的 Embedding 向量表达,这部分就是矩阵分解中的用户隐向量和物品隐向量。输出层使用了用户隐向量和物品隐向量的内积作为最终预测得分,之后通过跟目标得分对比,进行反向梯度传播,更新整个网络。

1.3 推荐系统的召回算法

召回算法一般分类:

  • 表征学习:如下图,input representation可以是无序交互特征、序列特征、多模态、图数据等。

在这里插入图片描述

  • 匹配函数的学习:如下图

在这里插入图片描述
上图摘自《Deep Learning for Matching in Search and Recommendation》李航,何向南 第五章

二、NeuralCF模型

2.1 定义Dataset

import paddle
from paddle import nn
from paddle.io import DataLoader, Dataset
import pandas as pd
import numpy as np
import copy
import os
from matplotlib import pyplot as plt
from sklearn.metrics import roc_auc_score,log_loss
from tqdm import tqdm
from collections import defaultdict
import math
import random
import warnings
warnings.filterwarnings("ignore")

#参数配置

config = {
    'train_path':'/home/aistudio/data/data173799/train_enc.csv',
    'valid_path':'/home/aistudio/data/data173799/valid_enc.csv',
    'test_path':'/home/aistudio/data/data173799/test_enc.csv',
    "debug_mode" : True,
    "epoch" : 5,
    "batch" : 20480,
    "lr" : 0.001,
}

(1)前期准备:导入paddle对应包、randomnumpy等常用包,将训练集测试集路径、训练轮次等参数封装为字典。一共有900w条数据,这里使用valid数据来完成流程,初始数据集只有user_iditem_id和时间戳timestamp三个字段。

  • 历史行为记录条数少于20的item剔除。
  • 统计每个用户频率后,需要重置索引reset_index操作,否则索引号会乱掉;不想保留原来的index,则使用参数 drop=True,否则默认是保存原来的index。;关于pd.set_index()reset_index()可以参考如何在pandas中使用set_index( )与reset_index( )设置索引。
# 训练集太大了,一共有900w条数据,这里用valid数据来完成流程
df = pd.read_csv(config['valid_path'])

# 统计每个用户的记录出现次数,作为字段user_count
df['user_count'] = df['user_id'].map(df['user_id'].value_counts())

# 筛选出用户出现记录超过20次的用户, 并且重置索引
df = df[df['user_count']>20].reset_index(drop=True)

# 将每个用户的记录组成序列,并且将所有用户组成字典
pos_dict = df.groupby('user_id')['item_id'].apply(list).to_dict()

(2)正负样本构造,NeuralCF的构造逻辑,且训练集中:每个用户的正负样本个数比为1:3,测试集中:NeuralCF论文设定每个用户负样本个数为100。如训练集中负样本的构造如下,为了防止负采样出来的item在用户的正向历史行为序列pos_dict中,如果在pos_dict则再随机选一个进行替换:

for i in range(neg_sample_per_user):
    train_user_list.append(user)
    temp_item_index = random.randint(0, item_num - 1)
    # 为了防止 负采样选出来的Item 在用户的正向历史行为序列(pos_dict)当中
    while item_list[temp_item_index] in pos_dict[user]:
        temp_item_index = random.randint(0, item_num - 1)
    train_item_list.append(item_list[temp_item_index])
    train_label_list.append(0)

完整的NCF正负样本构造代码如下:

# 负采样
ratio = 3
# 构造样本
train_user_list = []
train_item_list = []
train_label_list = []

test_user_list = []
test_item_list = []
test_label_list = []
if config['debug_mode']:
    user_list = df['user_id'].unique()[:100]
else:
    user_list = df['user_id'].unique()
    
item_list = df['item_id'].unique()
item_num = df['item_id'].nunique()

for user in tqdm(user_list):
    # 训练集正样本
    for i in range(len(pos_dict[user])-1):
        train_user_list.append(user)
        train_item_list.append(pos_dict[user][i])
        train_label_list.append(1)
        
    # 测试集正样本
    test_user_list.append(user)
    test_item_list.append(pos_dict[user][-1])
    test_label_list.append(1)
    
    # 训练集:每个用户负样本数
    user_count = len(pos_dict[user])-1 # 训练集 用户行为序列长度
    neg_sample_per_user = user_count * ratio

    for i in range(neg_sample_per_user):
        train_user_list.append(user)
        temp_item_index = random.randint(0, item_num - 1)
        # 为了防止 负采样选出来的Item 在用户的正向历史行为序列(pos_dict)当中
        while item_list[temp_item_index] in pos_dict[user]:
            temp_item_index = random.randint(0, item_num - 1)
        train_item_list.append(item_list[temp_item_index])
        train_label_list.append(0)
    
    # 测试集合:每个用户负样本数为 100(论文设定)
    for i in range(100):
        test_user_list.append(user)
        temp_item_index = random.randint(0, item_num - 1)
        # 为了防止 负采样选出来的Item 在用户的正向历史行为序列(pos_dict)当中
        while item_list[temp_item_index] in pos_dict[user]:
            temp_item_index = random.randint(0, item_num - 1)
        test_item_list.append(item_list[temp_item_index])
        test_label_list.append(0)
        
train_df = pd.DataFrame()
train_df['user_id'] = train_user_list
train_df['item_id'] = train_item_list
train_df['label'] = train_label_list

test_df = pd.DataFrame()
test_df['user_id'] = test_user_list
test_df['item_id'] = test_item_list
test_df['label'] = test_label_list

vocab_map = {
    'user_id':df['user_id'].max()+1,
    'item_id':df['item_id'].max()+1
}

构造Dataset,这里用到了paddle的库:

#Dataset构造
class BaseDataset(Dataset):
    def __init__(self,df):
        self.df = df
        self.feature_name = ['user_id','item_id']
        #数据编码
        self.enc_data()

    def enc_data(self):
        #使用enc_dict对数据进行编码
        self.enc_data = defaultdict(dict)
        for col in self.feature_name:
            self.enc_data[col] = paddle.to_tensor(np.array(self.df[col])).squeeze(-1)
            
    def __getitem__(self, index):
        data = dict()
        for col in self.feature_name:
            data[col] = self.enc_data[col][index]
        if 'label' in self.df.columns:
            data['label'] = paddle.to_tensor([self.df['label'].iloc[index]],dtype="float32").squeeze(-1)
        return data

    def __len__(self):
        return len(self.df)

defaultdict的作用是在于,当字典里的key不存在但被查找时,返回的不是keyError而是一个默认值。dict =defaultdict( factory_function)factory_function可以是list、set、str等等,作用是当key不存在时,返回的是工厂函数的默认值,比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0。

train_dataset = BaseDataset(train_df)
test_dataset = BaseDataset(test_df)

# 看数据集类的每条样本:
train_dataset.__getitem__(777)
"""
{'user_id': Tensor(shape=[1], dtype=int64, place=Place(gpu:0), stop_gradient=True,
        [110123]),
 'item_id': Tensor(shape=[1], dtype=int64, place=Place(gpu:0), stop_gradient=True,
        [14374]),
 'label': Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
        0.)}
"""
test_dataset.__getitem__(777)

2.2 定义NeuralCF模型

把矩阵分解神经网络化之后,把它跟 Embedding+MLP 以及 Wide&Deep 模型做对比,可以看出网络中的薄弱环节:矩阵分解在 Embedding 层之上的操作是直接利用内积,得出最终结果。这会导致特征之间还没有充分交叉就直接输出结果,模型会有欠拟合的风险。NeuralCF 基于这点对矩阵分解进行了改进,结构图如下。

在这里插入图片描述

图3 NeuralCF的模型结构图 (出自论文Neural Collaborative Filtering)

区别就是 NeuralCF 用一个多层的神经网络替代掉了原来简单的点积操作,另外user隐向量和item隐向量在进入MLP前需要concat拼接,其实就是两个矩阵在水平位置上拼接而已。这样就可以让用户和物品隐向量之间进行充分的交叉,提高模型整体的拟合能力。

class NCF(paddle.nn.Layer):
    def __init__(self,
                embedding_dim = 16,
                vocab_map = None,
                loss_fun = 'nn.BCELoss()'):
        super(NCF, self).__init__()
        self.embedding_dim = embedding_dim
        self.vocab_map = vocab_map
        self.loss_fun = eval(loss_fun) # self.loss_fun  = paddle.nn.BCELoss()
        
        self.user_emb_layer = nn.Embedding(self.vocab_map['user_id'],
                                          self.embedding_dim)
        self.item_emb_layer = nn.Embedding(self.vocab_map['item_id'],
                                          self.embedding_dim)
        
        self.mlp = nn.Sequential(
            nn.Linear(2*self.embedding_dim,self.embedding_dim),
            nn.ReLU(),
            nn.BatchNorm1D(self.embedding_dim),
            nn.Linear(self.embedding_dim,1),
            nn.Sigmoid()
        )
        
    def forward(self,data):
        user_emb = self.user_emb_layer(data['user_id']) # [batch,emb]
        item_emb = self.item_emb_layer(data['item_id']) # [batch,emb]
        mlp_input = paddle.concat([user_emb, item_emb],axis=-1).squeeze(1)
        y_pred = self.mlp(mlp_input)
        if 'label' in data.keys():
            loss = self.loss_fun(y_pred.squeeze(),data['label'])
            output_dict = {'pred':y_pred,'loss':loss}
        else:
            output_dict = {'pred':y_pred}
        return output_dict

上面forward返回的字典是以predloss为key的key-value字典。可以看到上面接口和pytoch还是很像的,如paddle.nn.Embedding(vocab_size, embedding_dim)等。

2.3 train pipeline/valid pipeline

#训练模型,验证模型
def train_model(model, train_loader, optimizer, metric_list=['roc_auc_score','log_loss']):
    model.train()
    pred_list = []
    label_list = []
    pbar = tqdm(train_loader)
    for data in pbar:

        output = model(data)
        pred = output['pred']
        loss = output['loss']
		# 反向传播,更新参数,梯度清零
        loss.backward()
        optimizer.step()
        optimizer.clear_grad()

        pred_list.extend(pred.squeeze(-1).cpu().detach().numpy())
        label_list.extend(data['label'].squeeze(-1).cpu().detach().numpy())
        pbar.set_description("Loss {}".format(loss.numpy()[0]))

    res_dict = dict()
    for metric in metric_list:
        if metric =='log_loss':
            res_dict[metric] = log_loss(label_list,pred_list, eps=1e-7)
        else:
            res_dict[metric] = eval(metric)(label_list,pred_list)

    return res_dict

def valid_model(model, valid_loader, metric_list=['roc_auc_score','log_loss']):
    model.eval()
    pred_list = []
    label_list = []

    for data in (valid_loader):

        output = model(data)
        pred = output['pred']

        pred_list.extend(pred.squeeze(-1).cpu().detach().numpy())
        label_list.extend(data['label'].squeeze(-1).cpu().detach().numpy())

    res_dict = dict()
    for metric in metric_list:
        if metric =='log_loss':
            res_dict[metric] = log_loss(label_list,pred_list, eps=1e-7)
        else:
            res_dict[metric] = eval(metric)(label_list,pred_list)

    return res_dict

def test_model(model, test_loader):
    model.eval()
    pred_list = []

    for data in tqdm(test_loader):

        output = model(data)
        pred = output['pred']
        pred_list.extend(pred.squeeze().cpu().detach().numpy())

    return np.array(pred_list)

#dataloader
train_loader = DataLoader(train_dataset,batch_size=config['batch'],shuffle=True,num_workers=0)
test_loader = DataLoader(test_dataset,batch_size=config['batch'],shuffle=False,num_workers=0)


model = NCF(embedding_dim=64,vocab_map=vocab_map)
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=config['lr'])
train_metric_list = []

#模型训练流程
for i in range(config['epoch']):
    #模型训练
    train_metirc = train_model(model,train_loader,optimizer=optimizer)
    train_metric_list.append(train_metirc)

    print("Train Metric:")
    print(train_metirc)

2.4 指标计算

常用的指标有ndcg、mrr、recall、precision和hit_rate,这里用后两个作为召回指标。

y_pre = test_model(model,test_loader)
test_df['y_pre'] = y_pre
test_df['ranking'] = test_df.groupby(['user_id'])['y_pre'].rank(method='first', ascending=False)
test_df = test_df.sort_values(by=['user_id','ranking'],ascending=True)
test_df

上面代码对用户进行groupby分组,对每个用户的y_pred预测概率值进行排序,这里注意pd可以直接使用rank进行排序。得到新的字段ranking排名,再使用召回常用指标hitrate和ndcg。

# 计算指标
def hitrate(test_df,k=20):
    user_num = test_df['user_id'].nunique()
    test_gd_df = test_df[test_df['ranking']<=k].reset_index(drop=True)
    return test_gd_df['label'].sum() / user_num


def ndcg(test_df,k=20):
    '''
    idcg@k 一定为1
    dcg@k 1/log_2(ranking+1) -> log(2)/log(ranking+1)
    '''
    user_num = test_df['user_id'].nunique()
    test_gd_df = test_df[test_df['ranking']<=k].reset_index(drop=True)
    
    test_gd_df = test_gd_df[test_gd_df['label']==1].reset_index(drop=True)
    test_gd_df['ndcg'] = math.log(2) / np.log(test_gd_df['ranking']+1)
    return test_gd_df['ndcg'].sum() / user_num

hitrate(test_df,k=5) # 0.16
ndcg(test_df,k=5)    # 0.13148712314377456

2.5 训练过程可视化

def plot_metric(metric_dict_list, metric_name):
    epoch_list = [x for x in range(1,1+len(metric_dict_list))]
    metric_list = [metric_dict_list[i][metric_name] for i in range(len(metric_dict_list))]
    plt.figure(dpi=100)
    plt.plot(epoch_list,metric_list)
    plt.xlabel('Epoch')
    plt.ylabel(metric_name)
    plt.title('Train Metric')
    plt.show()

plot_metric(train_metric_list,'log_loss')

在这里插入图片描述

plot_metric(train_metric_list,'log_loss')  

在这里插入图片描述

时间安排

任务信息截止时间完成情况
11月14日周一正式开始
Task01:Paddle开发深度学习模型快速入门11月14、15、16日周三完成
Task02:传统序列召回实践:GRU4Rec11月17、18、19日周六
Task03:GNN在召回中的应用:SR-GNN11月20、21、22日周二
Task04:多兴趣召回实践:MIND11月23、24、25、26日周六
Task05:多兴趣召回实践:Comirec-DR11月27、28日周一
Task06:多兴趣召回实践:Comirec-SA11月29日周二

Reference

[1] https://oljacoephk.feishu.cn/docx/He0GdxFr5o9hVwx7Vzjc3M4JnKd
[2] task1:Paddle开发深度学习模型快速入门
[3] 深入理解推荐系统:召回
[4] NCF作者何向南个人主页:https://hexiangnan.github.io/
[5] 推荐场景中召回模型的演化过程. 京东大佬
[6] https://www.paddlepaddle.org.cn/install/quick?docurl=/documentation/docs/zh/2.0/install/conda/linux-conda.html#cuda11
[7] Centos7安装opencv-python缺少共享库(libSM.so.6, libXrender.so.1, libXext.so.6)的解决办法
[8] https://github.com/PaddlePaddle/Paddle/issues/25609

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

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

相关文章

5 分钟教你搭建「视频动作分类」系统

写在前面 在之前的文章中&#xff0c;我们已经搭建过「以文搜图」、「以图搜图」等搜索服务&#xff0c;而今天这篇文章&#xff0c;将要教会你如何搭建一个「视频动作分类」的 AI 系统&#xff01; 例如&#xff0c;我们只需放上一张“婴儿吃胡萝卜”的视频&#xff0c;这个…

代码随想录57——动态规划:647回文子串、516最长回文子序列

文章目录1.647回文子串1.1.题目1.2.解答2.516最长回文子序列2.1.题目2.2.解答1.647回文子串 参考&#xff1a;代码随想录&#xff0c;647回文子串&#xff1b;力扣题目链接 1.1.题目 1.2.解答 动规五部曲&#xff1a; 1.确定dp数组&#xff08;dp table&#xff09;以及下标…

如何优雅的使用 IDEA Debug 进行调试

如何优雅的使用 IDEA Debug 进行调试 Debug 是我们在开发过程中经常会使用到的一种排查问题的手段&#xff0c;我们用它来定位分析异常的出现&#xff0c;以及程序在运行中参数的变化。 IDEA 本身具有很强的调试功能&#xff0c;掌握 IDEA 的一些 Debug 技巧&#xff0c;对我们…

【JS】原生js实现矩形框的绘制/拖动/拉伸

1、要点及功能描述 通过js监听mouse事件来实现矩形框的绘制&#xff0c;再通过区分点击的是边角还是其他位置来实现矩形框的拉伸和拖动&#xff0c;并且在拖动和拉伸时&#xff0c;都做了边界限制&#xff0c;当拉伸或拖动 到边界时&#xff0c;就不能继续拉伸拖动了。当然在相…

7个实用有效的shopify运营策略,跨境电商卖家必知

关键词&#xff1a;shopify运营、跨境电商卖家 您的Shopify 在线商店是使用当今最好的平台之一构建的。2022 年第二季度&#xff0c;Shopify 在美国电子商务平台中占据最大市场份额&#xff0c;约占美国所有在线业务的 32%。 这也意味着电子商务品牌之间的竞争比以往任何时候都…

【图像融合】基于matlab DSIFT多聚焦图像融合【含Matlab源码 2224期】

⛄一、SIFT配准简介 1 算法概述 在实时系统中&#xff0c;算法的输入为相机数据流&#xff0c;当前输入的图像与上一张相似度很高时应不参与融合&#xff0c;由于在体视显微镜下序列图像存在较大程度的偏移&#xff0c;所以融合前还需要进行图像配准&#xff0c;配准完成后再进…

安杰思提交注册:预计2022年度收入不低于3.5亿元,同比增长15%

11月16日&#xff0c;杭州安杰思医学科技股份有限公司&#xff08;下称“安杰思”&#xff09;在上海证券交易所科创板提交招股书&#xff08;注册稿&#xff09;。据贝多财经了解&#xff0c;安杰思于2022年6月24日在科创板递交上市申请材料&#xff0c;2022年11月7日获得上市…

面试--线程池的执行流程和拒绝策略有哪些?

一. 执行流程 聊到线程池就一定会聊到线程池的执行流程, 也就是当有一个任务进入线程池之后, 线程池是如何执行的? 想要真正的了解线程池的执行流程&#xff0c;就得先从线程池的执行方法 execute() 说起, execute() 实现源码如下: public void execute(Runnable command)…

2.10.2版本的青龙升级2.10.13及2.11.3版本的教程

重要提醒&#xff1a; 这个教程仅限使用我下面这个命令搭建的青龙面板使用 docker run -dit \--name QL \--hostname QL \--restart always \-p 5700:5700 \-v $PWD/QL/config:/ql/config \-v $PWD/QL/log:/ql/log \-v $PWD/QL/db:/ql/db \-v $PWD/QL/scripts:/ql/scripts \-…

【消息队列笔记】chp3-如何确保消息不丢失

一、检测消息是否丢失 我们要保证消息的可靠交付&#xff0c;首先就要知道消息是否丢失了。如何做到这一点呢&#xff1f; 对于IT基础设施比较完善的公司&#xff0c;可以使用分布式链路追踪系统来追踪每一条消息。如果没有这样的系统&#xff0c;可以使用消息的有序性来验证…

圆角矩形不是圆:圆角的画法和二阶连续性

本文中的所有重要图片都会给出基于Matplotlib的Python绘制代码以供参考 引言 如果在百度搜索圆角矩形的画法&#xff0c;那么多数结果都会告诉你&#xff0c;就是把一个普通矩形的拐角换成相切的 14\frac{1}{4}41​ 圆弧&#xff0c;就像 引文1 和 引文2 说的那样。然而&#x…

网络规划设计与综合布线技术详解

一、网络工程概述 1、计算机网络及其组成 计算机网络是现代通信技术与计算机技术相结合的产物。 随着计算机网络本身的发展,人们认为:计算机网络是把地理位置不同、功能独立自治的计算机系统及数据设备通过通信设备和线路连接起来,在功能完善的网络软件运行支持下,以实现…

springboot+vue实现excel的导出

首先是springboot对数据的处理 依赖的导入 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency>RequestMapping("/exportExcel") public R exportR…

【SpringBoot项目】SpringBoot项目-瑞吉外卖【day01】

文章目录前言软件开发整体介绍软件开发流程瑞吉外卖项目介绍项目介绍产品原型展示技术选型功能架构角色开发环境搭建数据库环境搭建maven项目搭建设置静态资源映射后台登录需求分析代码开发功能测试后台退出需求分析代码开发功能测试&#x1f315;博客x主页&#xff1a;己不由心…

JVS低代码如何实现复杂物料编码?

日常业务过程中&#xff0c;存在大量的编码&#xff0c;例如订单的流水号&#xff0c;复杂的物料编码&#xff0c;学生证号等等场景&#xff0c;那么通过JVS如何去实现各种编码&#xff1f; 为了让使用者使用尽量简单&#xff0c;我们编码分为简单配置的编码和复杂配置的编码。…

[附源码]java毕业设计家校通信息管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

docker stats查询容器状态显示异常有模线

docker stats 命令用来显示容器使用的系统资源。 默认情况下&#xff0c;stats 命令会每隔 1 秒钟刷新一次输出的内容直到你按下 ctrl c。 输出详情介绍&#xff1a; CONTAINER ID 与 NAME: 容器 ID 与名称。 CPU % 与 MEM %: 容器使用的 CPU 和内存的百分比。 MEM USAGE…

Java基础之《netty(1)—netty介绍》

一、介绍 1、netty是由JBOSS提供的一个java开源框架&#xff0c;现为github上的独立项目。 2、netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;可以快速开发高性能、高可靠的网络IO程序。 3、netty主要针对在TCP协议下&#xff0c;面向clients端的高并发应用&…

【python3】4.文件管理

2022.11.16 本学习内容总结于莫烦python:4.文件管理 https://mofanpy.com/tutorials/python-basic/interactive-python/read-write-file4 文件管理 4.1 读写文件 均是用特殊字符open 4.1.1 创建文件 f open("new_file.txt", "w") # 创建并打开 f.wr…

进入数字化供应链高潮期,与IBM咨询共创无边界竞争力

供应链领域的国际专家马丁克里斯托弗在30年前就提出“未来的竞争不再是企业和企业之间的竞争&#xff0c;而是供应链之间的竞争。”近几年来&#xff0c;基于工业4.0技术的供应链4.0开始进入业界的视野&#xff0c;2020年开始的疫情让全球供应链结束了长期稳定状态而进入VUCA&a…