Quantcast
Channel: IT瘾博客推荐
Viewing all 532 articles
Browse latest View live

那些虐哭过你的大学数学课都有什么用处?看完后恍然大悟! - CSDN博客

$
0
0

640?wx_fmt=gif

导读:高等数学有什么用?很多人都在问这个问题。其实大多数人在问这个问题的时候,心里已经预设了否定的答案。确实,对于大多数人来说,已经发展到了连数字都基本很少用了的一些高等数学分支,是过于虚无飘渺了。 但是实际上,今天我们的生活已经完全离不开数学。甚至可以这么说,没有高等数学的发展,就不会有今天的现代社会。


也许很多人会怀疑这点,那么我们就来稍微介绍一下现在高等数学的各主要学科的“用处”。初等数学就不说了,一些如离散数学、运筹学、控制论等纯粹就是为了应用而发展起来的分支也不说了,重点介绍基础方面的。


640?wx_fmt=jpeg


数学分析:主要包括微积分和级数理论。微积分是高等数学的基础,应用范围非常广,基本上涉及到函数的领域都需要微积分的知识。级数中,傅立叶级数和傅立叶变换主要应用在信号分析领域,包括滤波、数据压缩、电力系统的监控等,电子产品的制造离不开它。


实变函数(实分析):数学分析的加强版之一。主要应用于经济学等注重数据分析的领域。


复变函数(复分析):数学分析加强版之二。应用很广的一门学科,在航空力学、流体力学、固体力学、信息工程、电气工程等领域都有广泛的应用,所以工科学生都要学这门课的。


高等代数:主要包括线形代数和多项式理论。线形代数可以说是目前应用很广泛的数学分支,数据结构、程序算法、机械设计、电子电路、电子信号、自动控制、经济分析、管理科学、医学、会计等都需要用到线形代数的知识,是目前经管、理工、计算机专业学生的必修课程。


高等几何:包括空间解析几何、射影几何、球面几何等,主要应用在建筑设计、工程制图方面。


分析学、高等代数、高等几何是近代数学的三大支柱。


640?wx_fmt=jpeg


微分方程:包括常微分方程和偏微分方程,重要工具之一。流体力学、超导技术、量子力学、数理金融、材料科学、模式识别、信号(图像)处理 、工业控制、输配电、遥感测控、传染病分析、天气预报等领域都需要它。


泛函分析:主要研究无限维空间上的函数。因为比较抽象,在技术上的直接应用不多,一般应用于连续介质力学、量子物理、计算数学、控制论、最优化理论等理论。


近世代数(抽象代数):主要研究各种公理化抽象代数系统的。技术上没有应用,物理上用得比较多,尤其是其中的群论。


拓扑学:研究集合在连续变换下的不变性。在自然科学中应用较多,如物理学的液晶结构缺陷的分类、化学的分子拓扑构形、生物学的DNA的环绕和拓扑异构酶等,此外在经济学中也有很重要的应用。


泛函分析、近世代数、拓扑学是现代数学三大热门分支。


640?wx_fmt=jpeg


非欧几何:主要应用在物理上,最著名的是相对论。


数论:曾经被认为是数学家的游戏、唯一不会有什么应用价值的分支。著名的哥德巴赫猜想就是数论里的。现在随着网络加密技术的发展,数论也找到了自己用武之地——密码学。前几年破解MD5码的王小云就是数论出身。


到目前为止, 数学的所有一级分支都已经找到了应用领域,从自然科学、社会科学、工程技术到信息技术,数学的影响无处不在如果没有高等数学在二十世纪的发展,我们平时所玩的电脑、上的网络、听的mp3、用的手机都不可能存在。当然,一般的普通大众是没必要了结这些艰深抽象的东西,但是它们的存在和发展却是必需的,总要有一些人去研究这些。


数学,就是算术,小学直接面对数字,计算,1+1=2之类的东东,初中有了代数和方程,实际上就是用一个字母来代表一个数,这个数的具体值可以是未知的。到了高中,主要研究未知数的对应变化关系,即函数。到了大学,更进一步,研究函数值的变化规律,比如导数就是函数的变化率。最后泛函就是研究不同函数之间的变化关系了。


640?wx_fmt=jpeg


数学是从具体到抽象,再抽象的过程,从自然数到集合,从集合到群,从群到拓扑,从拓扑到流形。只要你有时间,都能看懂,必竟数学家也是人,人脑是肉长的。肉长的人脑能想到的东西也就这点了。


最难的还是数论,一个哥德巴赫猜想,整了三百年,没人想出来怎么证。搞数论,人脑估计不够用了。


不过,对于大多数数学家来说,研究数学的目的就是为了好玩。这种心情和宅男们对galgame的感情在本质上是没有什么不同的。所谓数学的“用处”,不过是一个副产品罢了。


本文内容来源于网络,如有版权问题请与我们取得联系。


中国移动业务支撑系统简介(BOSS、BASS、BOMC、4A及VGOP)_李晓杰的博客

$
0
0

业务支撑系统(Business Support Systems,简称BSS)主要应用于通信行业,通过该系统对用户执行相应业务操作。它采用省中心/全国中心两级系统架构,两级系统相辅相成,共同构建全网服务/全网运营的运营支撑能力。

省中心将侧重于省内业务的运营,在满足个性化、本地化的业务需求的基础上,提供标准化的接口以满足全网运营的要求,通过业务支撑系统提供全网共享、一致的业务和服务能力,实现面向客户的全业务支撑融合,包括全网业务和本地业务的融合、自有产品和合作伙伴产品的融合等。全国中心将成为全网运营的核心和枢纽,提供全网运营的核心能力,并将重点建立对全网运营的管理与监控功能。

大的业务支撑系统可分为多种业务,如BOSS、BASS、BOMC、4A及VGOP等。

移动BOSS系统

BOSS是业务运营支撑系统(Business Operation Support System)的简称,它包含客户管理(CBOSS)、产品管理(PBOSS)、资源管理、客户服务、渠道管理、计费、账务、结算、合作伙伴管理等多方面的功能。它对各种业务功能进行集中、统一的规划和整合,是一体化得、信息资源充分共享的支撑系统。

BOSS系统实现对分期账单、语音及GPRS欠费风险控制、欠费提醒等计费业务的支撑,旨在构建了一个支持多业务、离线/在线计费统一的融合计费系统,根据客户对业务的需求以及业务特点,提升在线计费能力。通过对融合计费、账务处理、账务管理、综合结算和统计分析等功能的改造,实现对分散账期业务模式的支撑;支撑新账单、详单规范,实现账单、详单在各渠道的服务及用户体验一致;加强集团客户欠费管理能力,建立精细化的欠费管理视图,强化分级信用控制,打通与各集团业务平台的停开机接口;通过两级系统的配合,实现集团客户产品一点支付,并完善集团成员代付功能。

有具体业务中我们经常见NGBOSS类似字样,其全称为Next Generation Business Operation Support System,NG即表示“下一代”。NGBOSS系统与BOSS系统略有不同,NGBOSS和BOSS建设思路完全不一样,BOSS先建省级系统,然后再逐渐完善和补充,是打补丁方式建设,虽然BOSS很大,但是周边的系统不小,外挂系统也不小,是逐渐整合的系统。NGBOSS是自顶向下重构业务支撑网,这个不仅包括BOSS、CRM客服、包括网管,甚至包括DSMP。

目前的BOSS系统是一个大而全、完全紧耦合的系统,可以说是牵一发而动全身,风险非常大,所以NGBOSS从业务上主要关注:(1)将CRM从BOSS系统中分离出来,解决系统藕合问题,降低BOSS系统整体风险;(2)在CRM中彻底解决管理流程的问题;(3)解决业务服务重用的问题。

中国移动BOSS系统采用“两级三层”的结构(两级系统、三层结构)。

BOSS的两级

一级业务支撑系统(集团):具有管理、实体和枢纽功能,为有限公司进行全网业务管理和业务运营提供支撑和保障,实现全网信息的交换和管理。

二级业务支撑系统(省级):具有管理和实体功能,为省公司进行省内业务管理和业务运营提供支撑和保障。

一级业务支撑系统与二级业务支撑系统共同支撑业务的运营与协作。

bass-boss

BOSS的三层

接入层:是BOSS系统与外部进行数据交换的平台,由接入逻辑构成。

业务层:是BOSS系统业务处理的逻辑平台,它通过对数据访问子层的调用访问业务数据,实现不同的功能模块,满足不同的业务需求。

数据层:是BOSS系统对业务数据进行统一组织、集中管理的平台,它通过数据访问子层为业务层提供规范、高效的数据服务,实现业务数据的充分共享,是整个BOSS系统的基础。

boss-3cheng

BOSS的核心“三户模型”

客户:客户是与使用移动服务有关的个人或实体,由用户和潜在用户组成。

用户:运行商所提供服务的实际消费实体,跟服务提供者有商业上的契约关系,可以理解为个或设备。

账户:是客户使用服务的一个付费实体,便于用户一张账单可以缴纳定义的多种服务的费用。

账目:是组成账单的基本单位,比如语音通话费就是一个账目,长途漫游费是一个账目,基本月租也是一个账目。

boss-3hu

规制关系:

一个客户可以是运营商的用户,也可以是其潜在的用户,每一个客户在BOSS系统中有唯一的标识,同时客户与客户之间存在从属关系。

一个客户可以包含一个或多个用户,一个用户对应于一项主体服务,订购多个主体服务则为多个用户。

一个帐户可以为多个用户对应的帐目付费,一个用户对应的所有帐目可以由多个帐户付费。

BOSS系统的目标

BOSS系统要实现“三个特征、两个能力、一个综合”。其中,“三个特征”以提供“个性化、社会化、信息化”服务为重要特征;“两个能力”指具有“满足未来业务发展需要”和“满足实时处理”;“一个综合”指提供一个综合性的业务处理平台。

移动BASS系统

BASS是经营分析系统(Business Analysis Support System)的简称。

BASS系统应用框架可分为四大区域和九大功能:

bass

BASS系统四大区域

BASS系统四大区域包括:客户管理域、市场营销与产品管理域、供应商/合作伙伴管理域、资源管理域。

客户管理域主要是以客户为分析目标,分析客户的发展、客户的业务及收益等信息,为提升客户服务水平,增强客户维系能力,提升客户价值,提供决策分析依据的专业功能域。

市场营销与产品管理域是面向市场运营、营销与服务、产品管理,服务于企业市场经营和服务部门的专业功能域。

供应商/合作伙伴管理域是建立和完善中国移动产业链管理的专业功能域。它有效管理、评价供应商/合作伙伴,协助加强供应商/合作伙伴合作能力,丰富合作方式,巩固产业链,为逐步建立牢固的、多赢的产业链提供专门服务。

资源管理域是支撑市场运营、营销管理、产品管理等市场经营活动资源,及部分资源调度功能的专业功能域。

BASS系统九大功能

功能一:客户发展及总体收益分析

是对客户发展、业务收益、市场竞争等方面提供综合性分析,向各级管理层提供全省范围内企业经营、业务发展、市场竞争等信息,为移动公司制定企业经营规划、业务发展策略提供支持,进一步提高移动公司业务管理水平和市场竞争力。

bass-1

功能二:语言业务

对基础语音业务提供专项分析,通过对客户各种呼叫行为进行分析,为语音产品的开发、市场推广计划和客户挽留计划的制定提供依据,并提供国际业务的专项分析。

bass-2

功能三:增值业务

增值业务是移动公司的重点发展业务,也是企业发展的重要驱动力之一。为适应业务发展新形势,本规范针对增值业务提供客户发展、业务分析、业务营销分析、营销渠道分析、门户运营支撑、数据业务排行分析以及SP监控与管理等应用,以满足增值业务新产品的快速高效支撑、深度运营和精确营销需求,从而实现产品、管理及商务模式的创新和新业务的运营管理,从传统的技术驱动向市场驱动转变。

bass-3

功能四:集团客户业务

集团客户是实现企业利润和可持续发展的重要保障之一。通过对集团客户的分析,加强集团客户营销和服务工作,发展集团客户,提高集团客户的忠诚度。集团客户分析可从集团客户稳定性、集团客户产品、集团客户行业应用、VPMN集团、集团客户异动、集团成员构成等方面为移动公司集团客户的发展和相关政策的制定提供有力的分析支持,以解决集团客户业务发展和运营管理的热点问题(如:离网预警、精细化营销和双跨客户信息支撑等),深化集团客户业务的管理分析功能,加强对合作伙伴的精细化管理,提升集团客户业务运营管理的信息化水平。

bass-4

功能五:客户服务与管理

随着移动通信业务和市场竞争不断发展,中国移动的客户规模和客户质量进一步地提升,对客户服务水平提出了更高要求。客户服务与管理针对客户服务的服务质量、服务内容、服务效率和服务流程提供各项分析应用,并提供外呼管理支撑,为进一步提高客户服务水平,提升客户满意度和忠诚度提供支持。

bass-5

功能六:资源管理

资源包括各类渠道资源和销售资源。通过实体渠道运营分析、电子渠道分析、新业务体验营销、终端管理、卡号资源管理以及资费营销案等分析应用,为构建高效的渠道运营管理分析体系、提升渠道精准化营销和精细化管理能力、提高资源的利用率和管理水平提供支持。

bass-6

功能七:网络分析

通过网络系统获取客户位置信息和行为数据,加强对网络承载特征和客户行为特征的分析,为优化网络资源、支持精细化营销等工作提供支持。

bass-7

功能八:财务分析

财务分析是企业运营管理的重要组成部分,其内容主要包括公司内部结算、与其他电信运营商结算、与银行结算、与代销商结算的分析,为掌握各项结算费用信息提供支持。

功能九:基本功能

基础功能是为经营分析系统前端各项应用提供分析数据和业务处理逻辑的模块,可划分为以下四大类:基础信息库、基础应用、业务应用知识库、其他部分。

bass-9

移动BOMC系统

BOMC(Business Operation Management Center)是中国移动业务支撑网运营管理系统的简称。BOMC定位于为中国移动业务支撑网的运营管理水平提供有效支撑,不但实现在业务支撑网生产运行过程中对平台部件和应用软件等进行“集中监控、集中维护、集中管理”,并且在此基础上进行业务服务管理,同时对BOSS(含客服)系统、经分系统以及BOMC系统进行4A系统改造和统一平台建设。BOMC实现的系统功能有:

监控管理中心:集中提供了平台应用监控、告警管理、预警管理、故障诊断管理、操作控制等监控功能。

运营分析中心:监控分析、告警分析、性能分析管理、系统运行日报、周报、月报管理、业务运营分析管理等。

资源管理:对存储在配置管理数据库(CMDB)的资源模型管理及资源数据管理功能。本期BOMC系统需要实现统一的CMDB和统一的资源管理平台,作为支撑四大管理中心的基础资源信息平台。

统一运营门户:作为BOMC的工作门户,为运营管理人员、业务管理人员、维护人员、监控人员、外部用户等内外部用户提供的统一工作平台。

业务管理中心:以提升业务服务的可用性、服务水平、健康度为目标,通过业务建模构建业务服务支撑网络,与底层IT平台部件和系统应用关联起来,提供一个以业务为中心的IT运营管理平台。

运维服务中心:充分考虑流程角色与实际组织的映射、流程环节与实际工作的对应、ITIL术语与各省习惯用语的翻译,在保证流程实用性的前提下借鉴ITIL3.0、ISO20000理念,保证规范的先进性。另外,服务管理平台还应该满足高可靠性、开放性、可扩展性和性能、安全方面的要求。

指标管理:目前在日常运营工作中,根据不同管理目标的要求,存在着各类指标,包括IT基础设施的KPI指标、应用的KPI指标、KQI、BAM指标、CAPES指标等多类指标,各类指标存在互相重复和定义不唯一的情况,本期规范将对以上指标进行梳理,合理地合并部分指标,统一定义,从而简化和校正现有的指标体系。

采集平台:是运营管理系统中各种数据采集的重要入口。需要支持各种数据采集功能实现和丰富的采集协议。

控制平台:控制的范围根据安全管理和当前的管理需求主要包括部分平台类设备和部分应用类对象。

系统自管理:业务支撑网运营管理系统自身管理是指对整个业务支撑网运营管理系统自身进行管理。包括自告警、自身性能监控、系统管理。

4A系统

4A系统是统一安全管理平台解决方案,指认证Authentication、账号Account、授权Authorization、审计Audit,中文名称为统一安全管理平台解决方案。即将身份认证、授权、审计和账号(即不可否认性及数据完整性)定义为网络安全的四大组成部分,从而确立了身份认证在整个网络安全系统中的地位与作用。

4A平台的管理功能包括:集中认证管理、集中账号管理、集中权限管理和集中审计管理,具体如下:

集中认证(authentication)管理:可以根据用户应用的实际需要,为用户提供不同强度的认证方式,既可以保持原有的静态口令方式,又可以提供具有双因子认证方式的高强度认证(一次性口令、数字证书、动态口令),而且还能够集成现有其它如生物特征等新型的认证方式。不仅可以实现用户认证的统一管理,并且能够为用户提供统一的认证门户,实现企业信息资源访问的单点登录。

集中帐号(account)管理:为用户提供统一集中的帐号管理,支持管理的资源包括主流的操作系统、网络设备和应用系统;不仅能够实现被管理资源帐号的创建、删除及同步等帐号管理生命周期所包含的基本功能,而且也可以通过平台进行帐号密码策略,密码强度、生存周期的设定。

集中权限(authorization)管理:可以对用户的资源访问权限进行集中控制。它既可以实现对B/S、C/S应用系统资源的访问权限控制,也可以实现对数据库、主机及网络设备的操作的权限控制,资源控制类型既包括B/S的URL、C/S的功能模块,也包括数据库的数据、记录及主机、网络设备的操作命令、IP地址及端口。

集中审计(audit)管理:将用户所有的操作日志集中记录管理和分析,不仅可以对用户行为进行监控,并且可以通过集中的审计数据进行数据挖掘,以便于事后的安全事故责任的认定。

VGOP系统

VGOP(Value-added Service General Operation Platform)是中国移动的增值业务综合运营平台的简称,它从流程、功能、数据、接口四个方面全方位推动数据业务建设和运营从孤立走向整合,实现业务系统的规范化和水平化,加强数据业务以客户为中心的竞争力,沉淀业务综合运营的“软实力”。初期实现三大核心功能:业务能力互通和调度;客户行为诊断和分析;业务质量持续监控和优化。

中国移动的增值业务综合运营平台(VGOP)由一级VGOP、省级(或称为二级)VGOP两级组成。两级VGOP互联,相互协作与配合,共同支撑增值业务的运营、管理和调度:

一级VGOP为有限公司进行全网增值业务营销、管理和运营提供支撑和保障,包括调度功能、运营功能和管理功能,同时是一类业务的管理者,调度者和运营者;

省级VGOP是二、三类自有业务的运营、管理中心,为省内增值业务管理、运营和调度提供支撑和保障。它面向本省客户提供包括一类自有业务在内的完整的运营支撑功能,包括调度功能、运营功能和管理功能。

两级VGOP都主要包括能力管理、能力控制与调度、业务开通管理、数据管理、客户信息分析、业务监控、业务稽核、业务优化、营销支撑、基础管理、管理门户等功能模块。


如何使用TensorFlow mobile部署模型到移动设备 - CSDN博客

$
0
0

截止到今年,已经有超过 20 亿活跃的安卓设备。安卓手机的迅速普及很大程度上是因为各式各样的智能 app,从地图到图片编辑器应有尽有。随着深度学习的出现,我们的手机 app 将变得更加智能。下一代由深度学习驱动的手机 app 将可以学习并为你定制功能。一个很显著的例子是「Microsoft Swiftkey」,这是一个键盘 app, 能通过学习你常用的单词和词组来帮助你快速打字。 

计算机视觉,自然语言处理,语音识别和语音合成等技术能够大大改善用户在移动应用方面的体验。幸运的是,在移动应用方面,有很多工具开发成可以简化深度学习模型的部署和管理。在这篇文章中,我将阐释如何使用 TensorFlow mobile 将 PyTorch 和 Keras 部署到移动设备。

用 TensorFlow mobile 部署模型到安卓设备分为三个步骤:

  • 将你的训练模式转换到 TensorFlow

  • 在安卓应用中添加 TensorFlow mobile 作为附加功能

  • 在你的应用中使用 TensorFlow 模式写 Java 代码执行推理。

在这篇文章中,我将介绍整个过程,最后完成一个植入图像识别功能的安卓应用。


  安装

本教程会用到 PyTorch 和 Keras 两个框架-遵循下列指导安装你想使用的机器学习框架。安装哪个由你选择。

首先,安装 TensorFlow:

pip3 install tensorflow

如果你是 PyTorch 的开发者,确保你安装的是 PyTorch 的最新版本。关于安装 PyTorch 的指导文件,请查阅我之前的文章:

https://heartbeat.fritz.ai/basics-of-image-classification-with-pytorch-2f8973c51864

如果你是 Keras 的开发者,使用以下命令安装:

pip3 install keras
pip3 install h5py

Android Studio(至少3.0 的版本)

https://developer.android.com/studio


  将 PyTorch 模式转成 Keras 模式

这部分仅适用于 PyTorch 开发者。如果你使用的是 Keras,你可以跳到 “将 Keras 模式转成 TensorFlow 模式”章节。

首先我们要做的是将我们的 PyTorch 模式参数转成 Keras 中的同等参数。

为了简化这个过程,我创建了一个脚本来自动运行转化。在此教程中,我们将使用 Squeezenet 。这是一种很小但具备合理精确度的移动架构。在这儿下载预训练模式(只有5M!)。

在转权值之前,我们需要在 PyTorch 和 Keras 中定义 Squeezenet 模型。

如下图所示,在这两种框架下定义 Squeezenet,然后将 PyTorch 权值转成 Keras。

创建文件 convert.py,包括下面的代码并运行脚本。

import torch
import torch.nn as nn
from torch.autograd import Variable
import keras.backend as K
from keras.models import *
from keras.layers import *

import torch
from torchvision.models import squeezenet1_1


class PytorchToKeras(object):
   def __init__(self,pModel,kModel):
       super(PytorchToKeras,self)
       self.__source_layers = []
       self.__target_layers = []
       self.pModel = pModel
       self.kModel = kModel

       K.set_learning_phase(0)

   def __retrieve_k_layers(self):

       for i,layer in enumerate(self.kModel.layers):
           if len(layer.weights) > 0:
               self.__target_layers.append(i)

   def __retrieve_p_layers(self,input_size):

       input = torch.randn(input_size)

       input = Variable(input.unsqueeze(0))

       hooks = []

       def add_hooks(module):

           def hook(module, input, output):
               if hasattr(module,"weight"):
                   self.__source_layers.append(module)

           if not isinstance(module, nn.ModuleList) and not isinstance(module,nn.Sequential) and module != self.pModel:
               hooks.append(module.register_forward_hook(hook))

       self.pModel.apply(add_hooks)


       self.pModel(input)
       for hook in hooks:
           hook.remove()

   def convert(self,input_size):
       self.__retrieve_k_layers()
       self.__retrieve_p_layers(input_size)

       for i,(source_layer,target_layer) in enumerate(zip(self.__source_layers,self.__target_layers)):

           weight_size = len(source_layer.weight.data.size())

           transpose_dims = []

           for i in range(weight_size):
               transpose_dims.append(weight_size - i - 1)

           self.kModel.layers[target_layer].set_weights([source_layer.weight.data.numpy().transpose(transpose_dims), source_layer.bias.data.numpy()])

   def save_model(self,output_file):
       self.kModel.save(output_file)
   def save_weights(self,output_file):
       self.kModel.save_weights(output_file)



"""
We explicitly redefine the Squeezent architecture since Keras has no predefined Squeezent
"""

def squeezenet_fire_module(input, input_channel_small=16, input_channel_large=64):

   channel_axis = 3

   input = Conv2D(input_channel_small, (1,1), padding="valid" )(input)
   input = Activation("relu")(input)

   input_branch_1 = Conv2D(input_channel_large, (1,1), padding="valid" )(input)
   input_branch_1 = Activation("relu")(input_branch_1)

   input_branch_2 = Conv2D(input_channel_large, (3, 3), padding="same")(input)
   input_branch_2 = Activation("relu")(input_branch_2)

   input = concatenate([input_branch_1, input_branch_2], axis=channel_axis)

   return input


def SqueezeNet(input_shape=(224,224,3)):



   image_input = Input(shape=input_shape)


   network = Conv2D(64, (3,3), strides=(2,2), padding="valid")(image_input)
   network = Activation("relu")(network)
   network = MaxPool2D( pool_size=(3,3) , strides=(2,2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
   network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
   network = MaxPool2D(pool_size=(3,3), strides=(2,2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
   network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
   network = MaxPool2D(pool_size=(3, 3), strides=(2, 2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
   network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
   network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
   network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)

   #Remove layers like Dropout and BatchNormalization, they are only needed in training
   #network = Dropout(0.5)(network)

   network = Conv2D(1000, kernel_size=(1,1), padding="valid", name="last_conv")(network)
   network = Activation("relu")(network)

   network = GlobalAvgPool2D()(network)
   network = Activation("softmax",name="output")(network)


   input_image = image_input
   model = Model(inputs=input_image, outputs=network)

   return model


keras_model = SqueezeNet()


#Lucky for us, PyTorch includes a predefined Squeezenet
pytorch_model = squeezenet1_1()

#Load the pretrained model
pytorch_model.load_state_dict(torch.load("squeezenet.pth"))

#Time to transfer weights

converter = PytorchToKeras(pytorch_model,keras_model)
converter.convert((3,224,224))

#Save the weights of the converted keras model for later use
converter.save_weights("squeezenet.h5")

上面是已经转好权值的,你所需要做的是将 Keras 模型保存为 squeezenet.h5。到这一步,我们可以抛弃 PyTorch 模型,继续下一步了。


  将 Keras 转成 TensorFlow 模式

到这一步,你已经有了 Keras 模式,无论是从 PyTorch 转化而来的还是直接用 Keras 训练而获得的。你可以在这儿下载预训练的 Keras Squeezenet 模式。下一步是将我们整个的模型架构和权值转成可运行的 TensorFlow 模型。

创建一个新文件 ConvertToTensorflow.py 并添加以下代码。

from keras.models import Model
from keras.layers import *
import os
import tensorflow as tf


def keras_to_tensorflow(keras_model, output_dir, model_name,out_prefix="output_", log_tensorboard=True):

   if os.path.exists(output_dir) == False:
       os.mkdir(output_dir)

   out_nodes = []

   for i in range(len(keras_model.outputs)):
       out_nodes.append(out_prefix + str(i + 1))
       tf.identity(keras_model.output[i], out_prefix + str(i + 1))

   sess = K.get_session()

   from tensorflow.python.framework import graph_util, graph_io

   init_graph = sess.graph.as_graph_def()

   main_graph = graph_util.convert_variables_to_constants(sess, init_graph, out_nodes)

   graph_io.write_graph(main_graph, output_dir, name=model_name, as_text=False)

   if log_tensorboard:
       from tensorflow.python.tools import import_pb_to_tensorboard

       import_pb_to_tensorboard.import_to_tensorboard(
           os.path.join(output_dir, model_name),
           output_dir)


"""
We explicitly redefine the Squeezent architecture since Keras has no predefined Squeezenet
"""

def squeezenet_fire_module(input, input_channel_small=16, input_channel_large=64):

   channel_axis = 3

   input = Conv2D(input_channel_small, (1,1), padding="valid" )(input)
   input = Activation("relu")(input)

   input_branch_1 = Conv2D(input_channel_large, (1,1), padding="valid" )(input)
   input_branch_1 = Activation("relu")(input_branch_1)

   input_branch_2 = Conv2D(input_channel_large, (3, 3), padding="same")(input)
   input_branch_2 = Activation("relu")(input_branch_2)

   input = concatenate([input_branch_1, input_branch_2], axis=channel_axis)

   return input


def SqueezeNet(input_shape=(224,224,3)):



   image_input = Input(shape=input_shape)


   network = Conv2D(64, (3,3), strides=(2,2), padding="valid")(image_input)
   network = Activation("relu")(network)
   network = MaxPool2D( pool_size=(3,3) , strides=(2,2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
   network = squeezenet_fire_module(input=network, input_channel_small=16, input_channel_large=64)
   network = MaxPool2D(pool_size=(3,3), strides=(2,2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
   network = squeezenet_fire_module(input=network, input_channel_small=32, input_channel_large=128)
   network = MaxPool2D(pool_size=(3, 3), strides=(2, 2))(network)

   network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
   network = squeezenet_fire_module(input=network, input_channel_small=48, input_channel_large=192)
   network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)
   network = squeezenet_fire_module(input=network, input_channel_small=64, input_channel_large=256)

   #Remove layers like Dropout and BatchNormalization, they are only needed in training
   #network = Dropout(0.5)(network)

   network = Conv2D(1000, kernel_size=(1,1), padding="valid", name="last_conv")(network)
   network = Activation("relu")(network)

   network = GlobalAvgPool2D()(network)
   network = Activation("softmax",name="output")(network)


   input_image = image_input
   model = Model(inputs=input_image, outputs=network)

   return model


keras_model = SqueezeNet()

keras_model.load_weights("squeezenet.h5")


output_dir = os.path.join(os.getcwd(),"checkpoint")

keras_to_tensorflow(keras_model,output_dir=output_dir,model_name="squeezenet.pb")

print("MODEL SAVED")

上面的代码将我们的 squeezenet.pb 保存到了 output_dir 中。并在同一文件夹中创建 了 TensorBoard 事件。

为了更加清晰地理解你的模型,你可以用 TensorBoard 将它可视化。

打开命令行并输入

tensorboard –logdir=output_dir_path

output_dir_path would be the path to your output_dir.

一旦 TensorBoard 成功启动,你将看到提示让你打开如下 url COMPUTER_NAME:6006

640

将 URL 地址输入到浏览器中,将显示以下界面。 

640

为了可视化你的模式,双击 IMPORT.

仔细看下该模型并记下输入和输出节点的名字(框架中的第一个和最后一个)。

如果你的命名和我之前代码一样的话,他们就应该是 input_1 和output_1  。

到这一步, 我们的模型就可以调用了。


  将 TensorFlow Mobile 添加到你的项目中

TensorFlow 有 2 个针对移动设备的库,分别是「TensorFlow Mobile」和「TensorFlow Lite.」Lite 版本设计得非常小,所有的依赖库大约只有 1M。它的模型也更优化。另外,在安卓 8 以上的设备中,还可以用神经网络 API 加速。与「TensorFlow Mobile」不同,「TensorFlow Lite.」目前还不太完善,有些层并不能实现预期的效果。此外,windows 系统还不支持编译库和将模式转成原生格式的操作。因此,在这个教程里,我坚持用 TensorFlow Mobile。

如果没有现存项目的话,使用 Android Studio,创建一个新的安卓项目。然后添加TensorFlow Mobile 依赖库到你的build.gradle 文件。

implementation ‘org.tensorflow:tensorflow-android:+’

Android studio  将提示你同步 gradle,点击 Sync Now 等待同步完成。

到这一步项目就创建完成了。


  在你的移动 App 上执行推理

在用代码执行推理前,你需要将转化的模式 (squeezenet.pb)  添加到你的应用的资源文件夹里。

在 Android Studio 中右击你的项目,鼠标移到「添加文件夹」选项,然后选择「资源文件夹」。这时会在你的 app 目录下创建一个资源文件夹。然后,拷贝你的模式到此目录下。

点击 here 下载标签类,并拷贝文件到资源目录。

现在你的项目包含了分类图像的所有工具。

添加一个新的 Java 类到你的项目的主包中,取名为 ImageUtils , 然后将以下代码拷贝到其中。

package com.specpal.mobileai;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Environment;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import org.json.*;

/**
* Utility class for manipulating images.
**/
public class ImageUtils {
   /**
    * Returns a transformation matrix from one reference frame into another.
    * Handles cropping (if maintaining aspect ratio is desired) and rotation.
    *
    * @param srcWidth Width of source frame.
    * @param srcHeight Height of source frame.
    * @param dstWidth Width of destination frame.
    * @param dstHeight Height of destination frame.
    * @param applyRotation Amount of rotation to apply from one frame to another.
    *  Must be a multiple of 90.
    * @param maintainAspectRatio If true, will ensure that scaling in x and y remains constant,
    * cropping the image if necessary.
    * @return The transformation fulfilling the desired requirements.
    */
   public static Matrix getTransformationMatrix(
           final int srcWidth,
           final int srcHeight,
           final int dstWidth,
           final int dstHeight,
           final int applyRotation,
           final boolean maintainAspectRatio) {
       final Matrix matrix = new Matrix();

       if (applyRotation != 0) {
           // Translate so center of image is at origin.
           matrix.postTranslate(-srcWidth / 2.0f, -srcHeight / 2.0f);

           // Rotate around origin.
           matrix.postRotate(applyRotation);
       }

       // Account for the already applied rotation, if any, and then determine how
       // much scaling is needed for each axis.
       final boolean transpose = (Math.abs(applyRotation) + 90) % 180 == 0;

       final int inWidth = transpose ? srcHeight : srcWidth;
       final int inHeight = transpose ? srcWidth : srcHeight;

       // Apply scaling if necessary.
       if (inWidth != dstWidth || inHeight != dstHeight) {
           final float scaleFactorX = dstWidth / (float) inWidth;
           final float scaleFactorY = dstHeight / (float) inHeight;

           if (maintainAspectRatio) {
               // Scale by minimum factor so that dst is filled completely while
               // maintaining the aspect ratio. Some image may fall off the edge.
               final float scaleFactor = Math.max(scaleFactorX, scaleFactorY);
               matrix.postScale(scaleFactor, scaleFactor);
           } else {
               // Scale exactly to fill dst from src.
               matrix.postScale(scaleFactorX, scaleFactorY);
           }
       }

       if (applyRotation != 0) {
           // Translate back from origin centered reference to destination frame.
           matrix.postTranslate(dstWidth / 2.0f, dstHeight / 2.0f);
       }

       return matrix;
   }


   public static Bitmap processBitmap(Bitmap source,int size){

       int image_height = source.getHeight();
       int image_width = source.getWidth();

       Bitmap croppedBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);

       Matrix frameToCropTransformations = getTransformationMatrix(image_width,image_height,size,size,0,false);
       Matrix cropToFrameTransformations = new Matrix();
       frameToCropTransformations.invert(cropToFrameTransformations);

       final Canvas canvas = new Canvas(croppedBitmap);
       canvas.drawBitmap(source, frameToCropTransformations, );

       return croppedBitmap;


   }

   public static float[] normalizeBitmap(Bitmap source,int size,float mean,float std){

       float[] output = new float[size * size * 3];

       int[] intValues = new int[source.getHeight() * source.getWidth()];

       source.getPixels(intValues, 0, source.getWidth(), 0, 0, source.getWidth(), source.getHeight());
       for (int i = 0; i < intValues.length; ++i) {
           final int val = intValues[i];
           output[i * 3] = (((val >> 16) & 0xFF) - mean)/std;
           output[i * 3 + 1] = (((val >> 8) & 0xFF) - mean)/std;
           output[i * 3 + 2] = ((val & 0xFF) - mean)/std;
       }

       return output;

   }

   public static Object[] argmax(float[] array){


       int best = -1;
       float best_confidence = 0.0f;

       for(int i = 0;i < array.length;i++){

           float value = array[i];

           if (value > best_confidence){

               best_confidence = value;
               best = i;
           }
       }



       return new Object[]{best,best_confidence};


   }


   public static String getLabel( InputStream jsonStream,int index){
       String label = "";
       try {

           byte[] jsonData = new byte[jsonStream.available()];
           jsonStream.read(jsonData);
           jsonStream.close();

           String jsonString = new String(jsonData,"utf-8");

           JSONObject object = new JSONObject(jsonString);

           label = object.getString(String.valueOf(index));



       }
       catch (Exception e){


       }
       return label;
   }
}

如果不理解上面的代码也没关系,这是一些未在核心 TensorFlow-Mobile 库中实现的功能。因此,在参考了一些官方样例后,我写了这些代码以方便后续工作。

在你的主活动中,创建一个 ImageView 和一个 TextView ,这将被用来显示图像和其预测结果。

在你的主活动中,你需要加载 TensorFlow-inference  库和初始化一些类变量。在你的 onCreate 方法前添加以下内容:

//Load the tensorflow inference library
   static {
       System.loadLibrary("tensorflow_inference");
   }

   //PATH TO OUR MODEL FILE AND NAMES OF THE INPUT AND OUTPUT NODES
   private String MODEL_PATH = "file:///android_asset/squeezenet.pb";
   private String INPUT_NAME = "input_1";
   private String OUTPUT_NAME = "output_1";
   private TensorFlowInferenceInterface tf;

   //ARRAY TO HOLD THE PREDICTIONS AND FLOAT VALUES TO HOLD THE IMAGE DATA
   float[] PREDICTIONS = new float[1000];
   private float[] floatValues;
   private int[] INPUT_SIZE = {224,224,3};

   ImageView imageView;
   TextView resultView;
   Snackbar progressBar;

添加一个计算预测类的函数:

//FUNCTION TO COMPUTE THE MAXIMUM PREDICTION AND ITS CONFIDENCE
   public Object[] argmax(float[] array){


       int best = -1;
       float best_confidence = 0.0f;

       for(int i = 0;i < array.length;i++){

           float value = array[i];

           if (value > best_confidence){

               best_confidence = value;
               best = i;
           }
       }

       return new Object[]{best,best_confidence};


   }

添加函数来接收 Image Bitmap 并在其上执行推理:

public void predict(final Bitmap bitmap){


       //Runs inference in background thread
       new AsyncTask(){

           @Override

           protected Integer doInBackground(Integer ...params){

               //Resize the image into 224 x 224
               Bitmap resized_image = ImageUtils.processBitmap(bitmap,224);

               //Normalize the pixels
               floatValues = ImageUtils.normalizeBitmap(resized_image,224,127.5f,1.0f);

               //Pass input into the tensorflow
               tf.feed(INPUT_NAME,floatValues,1,224,224,3);

               //compute predictions
               tf.run(new String[]{OUTPUT_NAME});

               //copy the output into the PREDICTIONS array
               tf.fetch(OUTPUT_NAME,PREDICTIONS);

               //Obtained highest prediction
               Object[] results = argmax(PREDICTIONS);


               int class_index = (Integer) results[0];
               float confidence = (Float) results[1];


               try{

                   final String conf = String.valueOf(confidence * 100).substring(0,5);

                   //Convert predicted class index into actual label name
                  final String label = ImageUtils.getLabel(getAssets().open("labels.json"),class_index);



                  //Display result on UI
                   runOnUiThread(new Runnable() {
                       @Override
                       public void run() {

                           progressBar.dismiss();
                           resultView.setText(label + " : " + conf + "%");

                       }
                   });

               }

               catch (Exception e){


               }


               return 0;
           }



       }.execute(0);

   }

以上代码在后台线程中运行预测,并将预测的类和它的评估得分写到我们之前定义的 TextView 中。

注意在主 UI 线程运行推理时可能会挂起。记住一个原则 :“永远在你的后台线程运行推理!”

本教程的重点是图像识别,为此我在资源文件夹中添加了一只小鸟的图像。在标准应用程序中,你要用代码从文件系统加载图像。

添加任何你想做预测的图像到资源文件夹中。为了方便的运行算法,在下列的代码中,我们在一个按钮上添加了一个点击监听。该监听可以加载该图像并调用预测功能。

@Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);


       Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
       setSupportActionBar(toolbar);


       //initialize tensorflow with the AssetManager and the Model
       tf = new TensorFlowInferenceInterface(getAssets(),MODEL_PATH);

       imageView = (ImageView) findViewById(R.id.imageview);
       resultView = (TextView) findViewById(R.id.results);

       progressBar = Snackbar.make(imageView,"PROCESSING IMAGE",Snackbar.LENGTH_INDEFINITE);


       final FloatingActionButton predict = (FloatingActionButton) findViewById(R.id.predict);
       predict.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {


               try{

                   //READ THE IMAGE FROM ASSETS FOLDER
                   InputStream imageStream = getAssets().open("testimage.jpg");

                   Bitmap bitmap = BitmapFactory.decodeStream(imageStream);

                   imageView.setImageBitmap(bitmap);

                   progressBar.show();

                   predict(bitmap);
               }
               catch (Exception e){

               }

           }
       });
   }

很好!所有步骤都已完成!双击检验一下,如果都没有问题。点击「Bulid APK.」按钮

APK很快就创建完成了,之后在设备上安装并运行App.

结果如下图所示:

640

为了得到更新奇的体验,你的 App 应当从安卓文件系统加载图像或用摄像头抓取图像,而不是从资源文件夹加载。


  总结

移动端的深度学习框架将最终转变我们开发和使用 app 的方式。使用上述代码,你能轻松导出你训练的 PyTorch 和 Keras 模型到 TensorFlow。运用 TensorFlow Mobile 和这篇文章中介绍的步骤,你可以将卓越的 AI 功能完美的植入到你的移动端应用中。

安卓项目的完整代码和模型转换工具在我的 GitHub 上可以找到:

https://github.com/johnolafenwa/Pytorch-Keras-ToAndroid

Oracle - Spool导出数据到TXT文件 - CSDN博客

$
0
0

【1】Spool

spool的作用可以用一句话来描述:在sqlplus中用来保存或打印查询结果。即,可以将sql查询的结果保存问文件。

spool常用的设置

set colsep',';    //域(列)输出分隔符 
set echo off;    //不显示start启动的脚本中的每个sql命令,缺省为on 
set feedback off;  //不回显本次sql命令处理的记录条数,缺省为on 
set heading off;   //不输出域(列)标题,缺省为on 
set pagesize 0;   //输出每页行数,缺省为24,为了避免分页,可设定为0。 
set termout off;   //不显示脚本中的命令的执行结果,缺省为on 
set trimout on;   //去除标准输出每行的拖尾空格,缺省为off 
set trimspool on;  //去除重定向(spool)输出每行的拖尾空格,缺省为off 
set term off;        //不在屏幕上显示
set linesize 10000; //设置行宽,根据需要设置,默认100
set wrap off;       //让它不要自动换行,当行的长度大于LINESIZE的时候,超出的部分会被截掉。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

【2】PL/SQL下使用spool脚本导出txt

spool.sql脚本如下:

SPOOL D:\测试.txt 
set echo off  --不显示脚本中正在执行的SQL语句
set feedback off --不显示sql查询或修改行数
set term off   --不在屏幕上显示
set heading off  --不显示列
set linesize 1000; //设置行宽,根据需要设置,默认100
select AAB301||','||AAE002|| ',' ||AAC001|| ',' ||AAE252|| ',' ||AAE091|| ',' ||AAE020|| ',' ||AAE022 FROM JGCA;  --需要导出的数据查询sql
SPOOL OFF
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

PL/SQL操作如下:

这里写图片描述


将查询的行记录保存到D盘下TXT文档中

H5视频直播扫盲 | 吕小鸣前端博客

$
0
0

视频直播这么火,再不学就out了。

为了紧跟潮流,本文将向大家介绍一下视频直播中的基本流程和主要的技术点,包括但不限于前端技术。

1 H5到底能不能做视频直播?


当然可以, H5火了这么久,涵盖了各个方面的技术。

对于视频录制,可以使用强大的webRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音对话或视频对话的技术,缺点是只在PC的chrome上支持较好,移动端支持不太理想。

对于视频播放,可以使用HLS(HTTP Live Streaming)协议播放直播流,ios和android都天然支持这种协议,配置简单,直接使用video标签即可。

webRTC兼容性:

video标签播放hls协议视频:

<video controls autoplay>      
<source src=” http://10.66.69.77:8080/hls/mystream.m3u8“ type=”application/vnd.apple.mpegurl” />
<p class=”warning”>Your browser does not support HTML5 video.</p>
</video>

2 到底什么是HLS协议?


当简单讲就是把整个流分成一个个小的基于HTTP的文件来下载,每次只下载一些,前面提到了用于H5播放直播视频时引入的一个.m3u8的文件,这个文件就是基于HLS协议,存放视频流元数据的文件。

每一个.m3u8文件,分别对应若干个ts文件,这些ts文件才是真正存放视频的数据,m3u8文件只是存放了一些ts文件的配置信息和相关路径,当视频播放时,.m3u8是动态改变的,video标签会解析这个文件,并找到对应的ts文件来播放,所以一般为了加快速度,.m3u8放在web服务器上,ts文件放在cdn上。

.m3u8文件,其实就是以UTF-8编码的m3u文件,这个文件本身不能播放,只是存放了播放信息的文本文件:

​#EXTM3U                     m3u文件头

#EXT-X-MEDIA-SEQUENCE 第一个TS分片的序列号

#EXT-X-TARGETDURATION 每个分片TS的最大的时长

#EXT-X-ALLOW-CACHE 是否允许cache

#EXT-X-ENDLIST m3u8文件结束符

#EXTINF 指定每个媒体段(ts)的持续时间(秒),仅对其后面的URI有效
mystream-12.ts
ts文件:

HLS的请求流程是:
1 http请求m3u8的url
2 服务端返回一个m3u8的播放列表,这个播放列表是实时跟新的,一般一次给出3段数据的url
3 客户端解析m3u8的播放列表,再按序请求每一段的url,获取ts数据流

简单流程:

3 HLS直播延时


当我们知道hls协议是将直播流分成一段一段的小段视频去下载播放的,所以假设列表里面的包含5个TS文件,每个TS文件包含5秒的视频内容,那么整体的延迟就是25秒。因为当你看到这些视频时,主播已经将视频录制好上传上去了,所以时这样产生的延迟。当然可以缩短列表的长度和单个TS文件的大小来降低延迟,极致来说可以缩减列表长度为1,并且TS的时长为1s,但是这样会造成请求次数增加,增大服务器压力,当网速慢时回造成更多的缓冲,所以苹果官方推荐的ts时长时10s,所以这样就会大改有30s的延迟。参考资料: https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StreamingMediaGuide/FrequentlyAskedQuestions/FrequentlyAskedQuestions.html

4 视频直播的整个流程是什么?


当视频直播可大致分为:

1 视频录制端:一般是电脑上的音视频输入设备或者手机端的摄像头或者麦克风,目前已移动端的手机视频为主。

2 视频播放端:可以是电脑上的播放器,手机端的native播放器,还有就是h5的video标签等,目前还是已手机端的native播放器为主。

3 视频服务器端:一般是一台nginx服务器,用来接受视频录制端提供的视频源,同时提供给视频播放端流服务。

简单流程:

 

 

5 怎样进行音视频采集?


当首先明确几个概念:

视频编码:所谓视频编码就是指通过特定的压缩技术,将某个视频格式的文件转换成另一种视频格式文件的方式,我们使用的iphone录制的视频,必须要经过编码,上传,解码,才能真正的在用户端的播放器里播放。

编解码标准:视频流传输中最为重要的编解码标准有国际电联的H.261、H.263、H.264,其中HLS协议支持H.264格式的编码。
音频编码:同视频编码类似,将原始的音频流按照一定的标准进行编码,上传,解码,同时在播放器里播放,当然音频也有许多编码标准,例如PCM编码,WMA编码,AAC编码等等,这里我们HLS协议支持的音频编码方式是AAC编码。

下面将利用ios上的摄像头,进行音视频的数据采集,主要分为以下几个步骤:

 

1 音视频的采集,ios中,利用AVCaptureSession和AVCaptureDevice可以采集到原始的音视频数据流。
2 对视频进行H264编码,对音频进行AAC编码,在ios中分别有已经封装好的编码库来实现对音视频的编码。
3 对编码后的音、视频数据进行组装封包;
4 建立RTMP连接并上推到服务端。

 

ps:由于编码库大多使用c语言编写,需要自己使用时编译,对于ios,可以使用已经编译好的编码库。

x264编码: https://github.com/kewlbear/x264-ios

faac编码: https://github.com/fflydev/faac-ios-build

ffmpeg编码: https://github.com/kewlbear/FFmpeg-iOS-build-script

关于如果想给视频增加一些特殊效果,例如增加滤镜等,一般在编码前给使用滤镜库,但是这样也会造成一些耗时,导致上传视频数据有一定延时。

简单流程:

6 前面提到的ffmpeg是什么?


和之前的x264一样,ffmpeg其实也是一套编码库,类似的还有Xvid,Xvid是基于MPEG4协议的编解码器,x264是基于H.264协议的编码器,ffmpeg集合了各种音频,视频编解码协议,通过设置参数可以完成基于MPEG4,H.264等协议的编解码,demo这里使用的是x264编码库。

7 什么是RTMP?


Real Time Messaging Protocol(简称 RTMP)是 Macromedia 开发的一套视频直播协议,现在属于 Adobe。和HLS一样都可以应用于视频直播,区别是RTMP基于flash无法在ios的浏览器里播放,但是实时性比HLS要好。所以一般使用这种协议来上传视频流,也就是视频流推送到服务器。

这里列举一下hls和rtmp对比:

8 推流


简所谓推流,就是将我们已经编码好的音视频数据发往视频流服务器中,一般常用的是使用rtmp推流,可以使用第三方库 librtmp-iOS进行推流,librtmp封装了一些核心的api供使用者调用,如果觉得麻烦,可以使用现成的ios视频推流sdk,也是基于rtmp的, https://github.com/runner365/LiveVideoCoreSDK

9 推流服务器搭建


简简单的推流服务器搭建,由于我们上传的视频流都是基于rtmp协议的,所以服务器也必须要支持rtmp才行,大概需要以下几个步骤:

1 安装一台nginx服务器。

2 安装nginx的rtmp扩展,目前使用比较多的是 https://github.com/arut/nginx-rtmp-module

3 配置nginx的conf文件:

rtmp {

server {  

    listen 1935;  #监听的端口

    chunk_size 4000;  


    application hls {  #rtmp推流请求路径
        live on;  
        hls on;  
        hls_path /usr/local/var/www/hls;  
        hls_fragment 5s;  
    }  
}

}
4 重启nginx,将rtmp的推流地址写为rtmp://ip:1935/hls/mystream,其中hls_path表示生成的.m3u8和ts文件所存放的地址,hls_fragment表示切片时长,mysteam表示一个实例,即将来要生成的文件名可以先自己随便设置一个。更多配置可以参考: https://github.com/arut/nginx-rtmp-module/wiki/

根据以上步骤基本上已经实现了一个支持rtmp的视频服务器了。

10 在html5页面进行播放直播视频?


简单来说,直接使用video标签即可播放hls协议的直播视频:

<video autoplay webkit-playsinline>      
<source src=” http://10.66.69.77:8080/hls/mystream.m3u8“ type=”application/vnd.apple.mpegurl” />
<p class=”warning”>Your browser does not support HTML5 video.</p>
</video>

需要注意的是,给video标签增加webkit-playsinline属性,这个属性是为了让video视频在ios的uiwebview里面可以不全屏播放,默认ios会全屏播放视频,需要给uiwebview设置allowsInlineMediaPlayback=YES。业界比较成熟的 videojs,可以根据不同平台选择不同的策略,例如ios使用video标签,pc使用flash等。

11 坑点总结


简根据以上步骤,笔者写了一个demo,从实现ios视频录制,采集,上传,nginx服务器下发直播流,h5页面播放直播视频者一整套流程,总结出以下几点比较坑的地方:

1 在使用AVCaptureSession进行采集视频时,需要实现AVCaptureVideoDataOutputSampleBufferDelegate协议,同时在- (void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection)connection捕获到视频流,要注意的是didOutputSampleBuffer这个方法不是didDropSampleBuffer方法,后者只会触发一次,当时开始写的是didDropSampleBuffer方法,差了半天才发现方法调用错了。

2 在使用rtmp推流时,rmtp地址要以rtmp://开头,ip地址要写实际ip地址,不要写成localhost,同时要加上端口号,因为手机端上传时是无法识别localhost的。

这里后续会补充上一些坑点,有的需要贴代码,这里先列这么多。

12 业界支持


目前, 腾讯云,百度云,阿里云都已经有了基于视频直播的解决方案,从视频录制到视频播放,推流,都有一系列的sdk可以使用,缺点就是需要收费,如果可以的话,自己实现一套也并不是难事哈。

 

demo地址: https://github.com/lvming6816077/LMVideoTest/

异地多活(异地双活)实践经验 - CSDN博客

$
0
0
异地多活(异地双活)是最近业界讨论比较多的话题,特别是前一阵子支付宝机房光纤故障和携程网数据库丢失之后,更加唤起了技术人员们对异地容灾的考虑。


而异地多活比异地容灾更高一级,因为异地容灾仅仅是一个冷备的概念,而异地多活却是指有两个或者多个可以同时对外服务的节点,任意一个点挂了,也可以迅速切换到其他节点对外服务,节点之间的数据做到准实时同步。


网上看了很多技术分享,总结了以下实践经验:


1 如果业务量不大,没必要做异地多活,因为异地多活需要的运维资源成本、开发成本都非常高;


2 注意机房间的延时问题,延时大的可能达到100ms以上,如果业务需要多次跨机房请求应用的话,延迟的问题会彻底放大;


3 跨机房的专线很大概率会出问题,要做好运维或者程序层面的容错;


4 不能依赖MySQL双写,必须有适应自身业务的跨机房消息同步方案;


5 MySQL或者其他存储的数据同步问题,在高延时和较差的网络质量的情况下,考虑如何保证同步质量;


6 核心业务和次要业务需要分而治之,异地多活的业务形式越简单越好,甚至可以只做核心业务;


7 异地多活的监控、部署、测试等流程也要跟上;


8 在业务允许的情况下,考虑用户分区,特别是游戏、邮箱业务比较容易做到;


9 控制跨机房消息体大小,越小越好;


10 考虑使用docker等容器虚拟化技术,提高动态调度能力;

Kafka MirrorMaker实践 - (a != b) ? b : a - ITeye博客

$
0
0

最近准备使用Kafka Mirrormaker做两个数据中心的数据同步,以下是一些要点:

 

  1. mirrormaker必须提供一个或多个consumer配置,一个producer配置,一个whitelist或一个blacklist(支持java正则表达式)
  2. 启动多个mirrormaker进程,单个进程启动多个consuemr streams, 可以提高吞吐量和提高性能
  3. mirrormaker部署在destination datacenter,这样如果kafka集群之间发生网络问题,也不至于从src cluster拿到了数据但发不到dest cluster导致数据丢失
  4. mirrormaker不能防止数据循环发送,即如果使用mm将数据从ClusterA的TopicA复制到ClusterB的TopicA,另一个mm将数据从ClusterB的TopicA复制到ClusterA的TopicA,那么会产生endless loop,mm的负载会急剧上升
  5. mirrormaker的producer和consumer的一些配置的目标是数据不丢失,而不是高性能,它们分别是
  • acks=all(kafka consumer默认1), 意味着数据被拷贝到dest cluster的所有replicas之后才响应
  • retries=max integer(kafka producer默认0)
  • block.on.buffer.full=true(kafka produmer默认false)
  • max.in.flight.requests.per.connection=1(kafka producer默认5), 提升该值可以获得更快的速度,同时意味着如果mirrormaker挂掉,将会丢更多的数据
  • auto.commit.enable=false(默认true)
  • abort.on.send.failure=true(mirrormaker配置)

 

    6. 其他配置:

  • linger.ms=0(kafka producer默认0), 调高linger.ms会使mirrormaker能够将更多的消息打包发送以提升效率,同时意味着消息的平均延迟上升

    7. 可以给所有需要mm的topics设置优先级,优先级高的topic将获得更低的延迟,并且能在更短的时间内重启,重启之后也能更快的追上拷贝进度

Kafka跨集群迁移方案MirrorMaker原理、使用以及性能调优实践 - CSDN博客

$
0
0

序言

Kakfa MirrorMaker是Kafka 官方提供的跨数据中心的流数据同步方案。其实现原理,其实就是通过从Source Cluster消费消息然后将消息生产到Target Cluster,即普通的消息生产和消费。用户只要通过简单的consumer配置和producer配置,然后启动Mirror,就可以实现准实时的数据同步。

1. Kafka MirrorMaker基本特性

Kafka Mirror的基本特性有:

  1. 在Target Cluster没有对应的Topic的时候,Kafka MirrorMaker会自动为我们在Target Cluster上创建一个一模一样(Topic Name、分区数量、副本数量)一模一样的topic。如果Target Cluster存在相同的Topic则不进行创建,并且,MirrorMaker运行Source Cluster和Target Cluster的Topic的分区数量和副本数量不同。
  2. 同我们使用Kafka API创建KafkaConsumer一样,Kafka MirrorMaker允许我们指定多个Topic。比如,TopicA|TopicB|TopicC。在这里,|其实是正则匹配符,MirrorMaker也兼容使用逗号进行分隔。
  3. 多线程支持。MirrorMaker会在每一个线程上创建一个Consumer对象,如果性能允许,建议多创建一些线程
  4. 多进程任意横向扩展,前提是这些进程的consumerGroup相同。无论是多进程还是多线程,都是由Kafka ConsumerGroup的设计带来的任意横向扩展性,具体的分区分派,即具体的TopicPartition会分派给Group中的哪个Topic负责,是Kafka自动完成的,Consumer无需关心。 
    我们使用Kafka MirrorMaker完成远程的AWS(Source Cluster)上的Kafka信息同步到公司的计算集群(Target Cluster)。由于我们的大数据集群只有一个统一的出口IP,外网访问我们的内网服务器必须通过nginx转发,因此为了降低复杂度,决定使用“拉”模式而不是“推”模式,即,Kafka MirrorMaker部署在我们内网集群(Target Cluster),它负责从远程的Source Cluster(AWS)的Kafka 上拉取数据,然后生产到本地的Kafka。

Kafka MirrorMaker的官方文档一直没有更新,因此新版Kafka为MirrorMaker增加的一些参数、特性等在文档上往往找不到,需要看Kafka MirrorMaker的源码。Kafka MirrorMaker的主类位于 kafka.tools.MirrorMaker,尤其是一些参数的解析逻辑和主要的执行流程,会比较有助于我们理解、调试和优化Kafka MirrorMaker。


2. 新旧Consumer API的使用问题

从Kafka 0.9版本开始引入了new consumer API。相比于普通的old consumer api,new Conumser API有以下主要改变:

  • 统一了旧版本的High-Level和Low-Level Consumer API;
  • new consumer API消除了对zookeeper的依赖,修改了ConsumerGroup的管理等等协议;
  • new Consumer API完全使用Java实现,不再依赖Scala环境;
  • 更好了安全认证只在new consumer中实现,在old consumer中没有。

Kakfa MirrorMaker同时提供了对新旧版本的Consumer API的支持。 
默认是旧版API,当添加 --new.consumer参数,MirrorMaker将使用新的Consumer API进行消息消费:

// Create consumers
      val mirrorMakerConsumers = if (!useNewConsumer) {//如果用户没有配置使用new consumer,则使用旧的consumer
        val customRebalanceListener = {
          val customRebalanceListenerClass = options.valueOf(consumerRebalanceListenerOpt)
          if (customRebalanceListenerClass != null) {
            val rebalanceListenerArgs = options.valueOf(rebalanceListenerArgsOpt)
            if (rebalanceListenerArgs != null) {
              Some(CoreUtils.createObject[ConsumerRebalanceListener](customRebalanceListenerClass, rebalanceListenerArgs))
            } else {
              Some(CoreUtils.createObject[ConsumerRebalanceListener](customRebalanceListenerClass))
            }
          } else {
            None
          }
        }

        if (customRebalanceListener.exists(!_.isInstanceOf[ConsumerRebalanceListener]))
          throw new IllegalArgumentException("The rebalance listener should be an instance of kafka.consumer.ConsumerRebalanceListener")
        createOldConsumers(//创建旧的consumer
          numStreams,
          options.valueOf(consumerConfigOpt),
          customRebalanceListener,
          Option(options.valueOf(whitelistOpt)),
          Option(options.valueOf(blacklistOpt)))
      } else {//用户指定使用new  consumer
        val customRebalanceListener = {
          val customRebalanceListenerClass = options.valueOf(consumerRebalanceListenerOpt)
          if (customRebalanceListenerClass != null) {
            val rebalanceListenerArgs = options.valueOf(rebalanceListenerArgsOpt)
            if (rebalanceListenerArgs != null) {
                Some(CoreUtils.createObject[org.apache.kafka.clients.consumer.ConsumerRebalanceListener](customRebalanceListenerClass, rebalanceListenerArgs))
            } else {
                Some(CoreUtils.createObject[org.apache.kafka.clients.consumer.ConsumerRebalanceListener](customRebalanceListenerClass))
            }
          } else {
            None
          }
        }
        if (customRebalanceListener.exists(!_.isInstanceOf[org.apache.kafka.clients.consumer.ConsumerRebalanceListener]))
          throw new IllegalArgumentException("The rebalance listener should be an instance of" +"org.apache.kafka.clients.consumer.ConsumerRebalanceListner")
        createNewConsumers(//创建new consumer
          numStreams,
          options.valueOf(consumerConfigOpt),
          customRebalanceListener,
          Option(options.valueOf(whitelistOpt)))
      }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

这是我启动Kakfa MirrorMaker 的命令:

nohup ./bin/kafka-mirror-maker.sh --new.consumer  --consumer.config config/mirror-consumer.properties  --num.streams 40 --producer.config config/mirror-producer.properties  --whitelist 'ABTestMsg|AppColdStartMsg|BackPayMsg|WebMsg|GoldOpenMsg|BoCaiMsg' &
  • 1

mirror-consumer.properties配置文件如下:

#新版consumer摈弃了对zookeeper的依赖,使用bootstrap.servers告诉consumer kafka server的位置
bootstrap.servers=ip-188-33-33-31.eu-central-1.compute.internal:9092,ip-188-33-33-32.eu-central-1.compute.internal:9092,ip-188-33-33-33.eu-central-1.compute.internal:9092

#如果使用旧版Consumer,则使用zookeeper.connect
#zookeeper.connect=ip-188-33-33-31.eu-central-1.compute.internal:2181,ip-188-33-33-32.eu-central-1.compute.internal:2181,ip-188-33-33-33.eu-central-1.compute.internal:2181
1.compute.internal:2181
#change the default 40000 to 50000
request.timeout.ms=50000

#hange default heartbeat interval from 3000 to 15000
heartbeat.interval.ms=30000

#change default session timeout from 30000 to 40000
session.timeout.ms=40000
#consumer group id
group.id=africaBetMirrorGroupTest
partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor
#restrict the max poll records from 2147483647 to 200000
max.poll.records=20000
#set receive buffer from default 64kB to 512kb
receive.buffer.bytes=524288

#set max amount of data per partition to override default 1048576
max.partition.fetch.bytes=5248576
#consumer timeout
#consumer.timeout.ms=5000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

mirror-producer.properties的配置文件如下:

bootstrap.servers=10.120.241.146:9092,10.120.241.82:9092,10.120.241.110:9092

# name of the partitioner class for partitioning events; default partition spreads data randomly
#partitioner.class=

# specifies whether the messages are sent asynchronously (async) or synchronously (sync)
producer.type=sync

# specify the compression codec for all data generated: none, gzip, snappy, lz4.
# the old config values work as well: 0, 1, 2, 3 for none, gzip, snappy, lz4, respectively
compression.codec=none
# message encoder
serializer.class=kafka.serializer.DefaultEncoder
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

同时,我使用 kafka-consumer-groups.sh循环监控消费延迟:

bin/kafka-consumer-groups.sh  --bootstrap-server ip-188-33-33-31.eu-central-1.compute.internal:9092,ip-188-33-33-32.eu-central-1.compute.internal:9092,ip-188-33-33-33.eu-central-1.compute.internal:9092 --describe --group africaBetMirrorGroupTest  --new-consumer
  • 1

当我们使用new KafkaConsumer进行消息消费,要想通过kafka-consumer-groups.sh获取整个group的offset、lag延迟信息,也必须加上–new-consumer,告知kafka-consumer-groups.sh,这个group的消费者使用的是new kafka consumer,即group中所有consumer的信息保存在了Kafka上的一个名字叫做 __consumer_offsets的特殊topic上,而不是保存在zookeeper上。我在使用kafka-consumer-groups.sh的时候就不知道还需要添加 --new-consumer,结果我启动了MirrorMaker以后,感觉消息在消费,但是就是在zookeeper的/consumer/ids/上找不到group的任何信息。后来在stack overflow上问了别人才知道。

3. 负载不均衡原因诊断以及问题解决

在我的另外一篇博客 《Kafka为Consumer分派分区:RangeAssignor和RoundRobinAssignor》中,介绍了Kafka内置的分区分派策略:RangeAssignor和RoundRobinAssignor。由于RangeAssignor是早期版本的Kafka的唯一的分区分派策略,因此,默认不配置的情况下,Kafka使用RangeAssignor进行分区分派,但是,在MirrorMaker的使用场景下,RoundRobinAssignor更有利于均匀的分区分派。甚至在 KAFKA-3831中有人建议直接将MirrorMaker的默认分区分派策略改为RoundRobinAssignor。那么,它们到底有什么区别呢?我们先来看两种策略下的分区分派结果。在我的实验场景下,有6个topic: ABTestMsg|AppColdStartMsg|BackPayMsg|WebMsg|GoldOpenMsg|BoCaiMsg,每个topic有两个分区。由于MirrorMaker所在的服务器性能良好,我设置 --num.streams 40,即单台MirrorMaker会用40个线程,创建40个独立的Consumer进行消息消费,两个MirrorMaker加起来80个线程,80个并行Consumer。由于总共只有 6 * 2=12个TopicPartition,因此最多也只有12个Consumer会被分派到分区,其余Consumer空闲。 
我们来看基于RangeAssignor分派策略,运行kafka-consumer-groups.sh观察到的分区分派的结果:

TOPIC                          PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG        CONSUMER-ID                                       HOST                           CLIENT-ID
ABTestMsg                      0          780000          820038          49938      africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-0
ABTestMsg                      1          774988          820038          55000      africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-1
AppColdStartMsg                0          774000          820039          55938      africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-0
AppColdStartMsg                1          774100          820045          56038      africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-1
BackPayMsg                     0          780000          820038          49938      africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-0
BackPayMsg                     1          774988          820038          55000      africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-1
WebMsg                         0          774000          820039          55938      africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-0
WebMsg                         1          774100          820045          56038      africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-1
GoldOpenMsg                    0          780000          820038          49938      africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-0
GoldOpenMsg                    1          774988          820038          55000      africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-1
BoCaiMsg                       0          774000          820039          55938      africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-0
BoCaiMsg                       1          774100          820045          56038      africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-1
-                              -          -               -               -          africaBetMirrorGroupTest-6-ae373364-2ae2-42b8-8a74-683557e315bf/114.113.198.126               africaBetMirrorGroupTest-6
-                              -          -               -               -          africaBetMirrorGroupTest-9-0e346b46-1a2c-46a2-a2da-d977402f5c5d/114.113.198.126               africaBetMirrorGroupTest-9
-                              -          -               -               -          africaBetMirrorGroupTest-7-f0ae9f31-33e6-4ddd-beac-236fb7cf20d5/114.113.198.126               africaBetMirrorGroupTest-7
-                              -          -               -               -          africaBetMirrorGroupTest-7-e2a9e905-57c1-40a6-a7f3-4aefd4f1a30a/114.113.198.126               africaBetMirrorGroupTest-7
-                              -          -               -               -          africaBetMirrorGroupTest-8-480a2ef5-907c-48ed-be1f-33450903ec72/114.113.198.126               africaBetMirrorGroupTest-8
-                              -          -               -               -          africaBetMirrorGroupTest-8-4206bc08-58a5-488a-b756-672fb4eee6e0/114.113.198.126               africaBetMirrorGroupTest-8
.....后续更多空闲consumer省略不显示
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

当没有在 mirror-consumer.properties 中配置分区分派策略,即使用默认的RangeAssignor的时候,我们发现,尽管我们每一个MirrorMaker有40个Consumer,整个Group中有80个Consumer,但是,一共 6 * 2 = 12个TopicPartition竟然全部聚集在2-3个Consumer上,显然,这完全浪费了并行特性,被分配到一个consumer上的多个TopicPartition只能串行消费。

因此,通过 partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor显式指定分区分派策略为RoundRobinAssignor,重启MirrorMaker,重新通过 kafka-consumer-groups.sh 命令观察分区分派和消费延迟结果:

TOPIC                          PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG        CONSUMER-ID                                       HOST                           CLIENT-ID
ABTestMsg                      0          819079          820038          959        africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-1
ABTestMsg                      1          818373          820038          1665       africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-5
AppColdStartMsg                0          818700          818907          1338       africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-20
AppColdStartMsg                1          818901          820045          1132       africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-18
BackPayMsg                     0          819032          820038          959        africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-5
BackPayMsg                     1          818343          820038          1638       africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-8
WebMsg                         0          818710          818907          1328       africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-7
WebMsg                         1          818921          820045          1134       africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-9
GoldOpenMsg                    0          819032          820038          959        africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126               africaBetMirrorGroupTest-12
GoldOpenMsg                    1          818343          820038          1638       africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126              africaBetMirrorGroupTest-14
BoCaiMsg                       0          818710          818907          1322       africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126              africaBetMirrorGroupTest-14
BoCaiMsg                       1          818921          820045          1189       africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126              africaBetMirrorGroupTest-117
-                              -          -               -               -          africaBetMirrorGroupTest-6-ae373364-2ae2-42b8-8a74-683557e315bf/114.113.198.126               africaBetMirrorGroupTest-6
-                              -          -               -               -          africaBetMirrorGroupTest-9-0e346b46-1a2c-46a2-a2da-d977402f5c5d/114.113.198.126               africaBetMirrorGroupTest-9
-                              -          -               -               -          africaBetMirrorGroupTest-7-f0ae9f31-33e6-4ddd-beac-236fb7cf20d5/114.113.198.126               africaBetMirrorGroupTest-7
-                              -          -               -               -          africaBetMirrorGroupTest-7-e2a9e905-57c1-40a6-a7f3-4aefd4f1a30a/114.113.198.126               africaBetMirrorGroupTest-7
-                              -          -               -               -          africaBetMirrorGroupTest-8-480a2ef5-907c-48ed-be1f-33450903ec72/114.113.198.126               africaBetMirrorGroupTest-8
-                              -          -               -               -          africaBetMirrorGroupTest-8-4206bc08-58a5-488a-b756-672fb4eee6e0/114.113.198.126               africaBetMirrorGroupTest-8
.....后续更多空闲consumer省略不显示
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

对比RangeAssingor,消息延迟明显减轻,而且,12个TopicPartition被均匀分配到了不同的consumer上,即单个Consumer只负责一个TopicPartition的消息消费,不同的TopicPartition之间实现了完全并行。 
之所以出现以上不同,原因在于两个分区分派方式的策略不同:

  • RangeAssingor:先对所有Consumer进行排序,然后对Topic逐个进行分区分派。用以上Topic作为例子:
对所有的Consumer进行排序,排序后的结果为Consumer-0,Consumer-1,Consumer-2 ....Consumer-79
对ABTestMsg进行分区分派:
ABTestMsg-0分配给Consumer-0
ABTestMsg-1分配各Consumer-1

对AppColdStartMsg进行分区分派:
AppColdStartMsg-0分配各Consumer-0
AppColdStartMsg-1分配各Consumer-1

#后续TopicParition的分派以此类推
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

可见,RangeAssingor 会导致多个TopicPartition被分派在少量分区上面。 
- RoundRobinAssignor:与RangeAssignor最大的区别,是不再逐个Topic进行分区分派,而是先将Group中的所有TopicPartition平铺展开,再一次性对他们进行一轮分区分派。

将Group中的所有TopicPartition展开,展开结果为:

ABTestMsg-0,ABTestMsg-1,AppColdStartMsg-0,AppColdStartMsg-1,BackPayMsg-0,BackPayMsg-1,WebMsg-0,WebMsg-1,GoldOpenMsg-0,GoldOpenMsg-1,BoCaiMsg-0,BoCaiMsg-1
  • 1

对所有的Consumer进行排序,排序后的结果为 Consumer-0Consumer-1Consumer-2 , Consumer-79

开始讲平铺的TopicPartition进行分区分派

ABTestMsg-0分配给Consumer-0
ABTestMsg-1分配给Consumer-1
AppColdStartMsg-0分配给Consumer-2
AppColdStartMsg-1分配给Consumer-3
BackPayMsg-0分配给Consumer-4
BackPayMsg-1分配给Consumer-5


#后续TopicParition的分派以此类推
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

由此可见,RoundRobinAssignor平铺式的分区分派算法是让我们的Kafka MirrorMaker能够无重叠地将TopicParition分派给Consumer的原因。

4. 本身网络带宽限制问题

网络带宽本身也会限制Kafka Mirror的吞吐量。进行压测的时候,我分别在我们的在线环境和测试环境分别运行Kafka MirrorMaker,均选择两台服务器运行MirrorMaker,但是在线环境是实体机环境,单台机器通过SCP方式拷贝Source Cluster上的大文件,平均吞吐量是600KB-1.5MB之间,但是测试环境的机器是同一个host主机上的多台虚拟机,SCP吞吐量是100KB以下。经过测试,测试环境消息积压会逐渐增多,在线环境持续积压,但是积压一直保持稳定。这种稳定积压是由于每次 poll()的间隙新产生的消息量,属于正常现象。

5. 适当配置单次poll的消息总量和单次poll()的消息大小

通过Kafka MirrorMaker运行时指定的consumer配置文件(在我的环境中为 $MIRROR_HOME/config/mirror-consumer.properties)来配置consumer。其中,通过以下配置,可以控制单次 poll()的消息体量(数量和总体大小) 
max.poll.records:单次 poll()操作最多消费的消息总量,这里说的poll是单个consumer而言的。无论过大过小,都会发生问题:

  • 如果设置得过小,则消息传输率降低,大量的头信息会占用较大的网络带宽;-
  • 如果设置得过大,则会产生一个非常难以判断原因同时又会影响整个group中所有消息的消费的重要问题:rebalance。看过kafka代码的话可以看到,每次poll()请求都会顺带向远程server发送心跳信息,远程GroupCoordinator会根据这个心跳信息判断consumer的活性。如果超过指定时间( heartbeat.interval.ms)没有收到对应Consumer的心跳,则GroupCoordinator会判定这个Server已经挂掉,因此将这个Consumer负责的partition分派给其它Consumer,即触发rebalance。rebalance操作的影响范围是整个Group,即Group中所有的Consumer全部暂停消费直到Rebalance完成。而且,TopicPartition越长,这个过程会越长。其实,一个正常消费的环境,应该是任何时候都不应该发生rebalance的(一个新的Consumer的正常加入也会引起Rebalance,这种情况除外)。虽然Kafka本身是非常稳定的,但是还是应该尽量避免rebalance的发生。在某些极端情况下触发一些bug,rebalance可能永远停不下来了。。。如果单次 max.poll.records消费太多消息,这些消息produce到Target Cluster的时间可能会较长,从而可能触发Rebalance。

6. 恶劣网络环境下增加超时时间配置

在不稳定的网络环境下,应该增加部分超时时间配置,如 request.timeout.ms或者 session.timeout.ms,一方面可以避免频繁的超时导致大量不必要的重试操作,同时,通过增加如上文所讲,通过增加 heartbeat.interval.ms时间,可以避免不必要的rebalance操作。当然,在网络环境良好的情况下,上述配置可以适当减小以增加Kafka Server对MirrorMaker出现异常情况下的更加及时的响应。


总之,Kafka MirrorMaker作为跨数据中心的Kafka数据同步方案,绝对无法允许数据丢失以及数据的传输速度低于生产速度导致数据越积累越多。因此,唯有进行充分的压测和精准的性能调优,才能综合网络环境、服务器性能,将Kafka MirrorMaker的性能发挥到最大。


Kafka系列之-Kafka监控工具KafkaOffsetMonitor配置及使用 - CSDN博客

$
0
0

  KafkaOffsetMonitor是一个可以用于监控Kafka的Topic及Consumer消费状况的工具,其配置和使用特别的方便。源项目Github地址为: https://github.com/quantifind/KafkaOffsetMonitor。 
  最简单的使用方式是从Github上下载一个最新的 KafkaOffsetMonitor-assembly-0.2.1.jar,上传到某服务器上,然后执行一句命令就可以运行起来。但是在使用过程中有可能会发现页面反应缓慢或者无法显示相应内容的情况。据说这是由于jar包中的某些js等文件需要连接到网络,或者需要翻墙导致的。网上找的一个修改版的KafkaOffsetMonitor对应jar包,可以完全在本地运行,经过测试效果不错。下载地址是: http://pan.baidu.com/s/1ntzIUPN,在此感谢一下贡献该修改版的原作者。链接失效的话,可以博客下方留言联系我。

一、KafkaOffsetMonitor的使用

  因为完全没有安装配置的过程,所以直接从KafkaOffsetMonitor的使用开始。 
  将KafkaOffsetMonitor-assembly-0.2.0.jar上传到服务器后,可以新建一个脚本用于启动该应用。脚本内容如下:

java -cp KafkaOffsetMonitor-assembly-0.2.0.jar \
    com.quantifind.kafka.offsetapp.OffsetGetterWeb \
    --zk m000:2181,m001:2181,m002:2181 \
    --port 8088 \
    --refresh 10.seconds \
    --retain 2.days
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

  各参数的作用可以参考一下Github上的描述:

  • offsetStorage valid options are ”zookeeper”, ”kafka” or ”storm”. Anything else falls back to ”zookeeper”
  • zk the ZooKeeper hosts
  • port on what port will the app be available
  • refresh how often should the app refresh and store a point in the DB
  • retain how long should points be kept in the DB
  • dbName where to store the history (default ‘offsetapp’)
  • kafkaOffsetForceFromStart only applies to ”kafka” format. Force KafkaOffsetMonitor to scan the commit messages from start (see notes below)
  • stormZKOffsetBase only applies to ”storm” format. Change the offset storage base in zookeeper, default to ”/stormconsumers” (see notes below)
  • pluginsArgs additional arguments used by extensions (see below)

      启动后,访问 m000:8088端口,可以看到如下页面: 
       这里写图片描述
      在这个页面上,可以看到当前Kafka集群中现有的Consumer Groups。

    在上图中有一个Visualizations选项卡,点击其中的Cluster Overview可以查看当前Kafka集群的Broker情况 
    这里写图片描述

      接下来将继续上一篇Kafka相关的文章 Kafka系列之-自定义Producer,在最后对Producer进行包装的基础上,分别实现一个简单的往随机Partition写messge,以及自定义Partitioner的Producer,对KafkaOffsetMonitor其他页面进行展示。

二、简单的Producer

1、新建一个Topic

  首先为本次试验新建一个Topic,命令如下:

bin/kafka-topics.sh \
    --create \
    --zookeeper m000:2181 \
    --replication-factor 3 \
    --partition 3 \
    --topic kafkamonitor-simpleproducer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、新建SimpleProducer代码

  在上一篇文章中提到的Producer封装Github代码的基础上,写了一个往kafkamonitor-simpleproducer发送message的java代码。

import com.ckm.kafka.producer.impl.KafkaProducerToolImpl;
import com.ckm.kafka.producer.inter.KafkaProducerTool;

/**
 * Created by ckm on 2016/8/30.
 */
public class SimpleProducer {
    public static void main(String[] args) {
        KafkaProducerTool kafkaProducerTool = new KafkaProducerToolImpl();
        int i = 0;
        String message = "";
        while (true) {
            message = "test-simple-producer : " + i ++;
            kafkaProducerTool.publishMessage("kafkamonitor-simpleproducer", message);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

  程序运行效果: 
   这里写图片描述

3、ConsoleConsumer消费该topic

  用kafka自带的ConsoleConsumer消费kafkamonitor-simpleproducer中的message。

bin/kafka-console-consumer.sh --zookeeper m000:2181 --from-beginning --topic kafkamonitor-simpleproducer
  • 1

  消费截图如下: 
   这里写图片描述

4、KafkaOffsetMonitor页面

(1)在Topic List选项卡中,我们可以看到刚才新建的 kafkamonitor-simpleproducer 
   这里写图片描述
(2)点开后,能看到有一个console-consumer正在消费该topic 
   这里写图片描述
(3)继续进入该Consumer,可以查看该Consumer当前的消费状况 
   这里写图片描述
  这张图片的左上角显示了当前Topic的生产速率,右上角显示了当前Consumer的消费速率。 
  图片中还有三种颜色的线条,蓝色的表示当前Topic中的Message数目,灰色的表示当前Consumer消费的offset位置,红色的表示蓝色灰色的差值,即当前Consumer滞后于Producer的message数目。 
(4)看一眼各partition中的message消费情况 
   这里写图片描述
  从上图可以看到,当前有3个Partition,每个Partition中的message数目分布很不均匀。这里可以与接下来的自定义Producer的情况进行一个对比。

三、自定义Partitioner的Producer

1、新建一个Topic

bin/kafka-topics.sh \
    --create \
    --zookeeper m000:2181 \
    --replication-factor 3 \
    --partition 3 \
    --topic kafkamonitor-partitionedproducer
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

2、Partitioner代码

  逻辑很简单,循环依次往各Partition中发送message。

import kafka.producer.Partitioner;

/**
 * Created by ckm on 2016/8/30.
 */
public class TestPartitioner implements Partitioner {
    public TestPartitioner() {
    }

    @Override
    public int partition(Object key, int numPartitions) {
        int intKey = (int) key;
        return intKey % numPartitions;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3、Producer代码

  将自定义的Partitioner设置到Producer,其他调用过程和二中类似。

import com.ckm.kafka.producer.impl.KafkaProducerToolImpl;
import com.ckm.kafka.producer.inter.KafkaProducerTool;

/**
 * Created by ckm on 2016/8/30.
 */
public class PartitionedProducer {
    public static void main(String[] args) {
        KafkaProducerTool kafkaProducerTool = new KafkaProducerToolImpl();
        kafkaProducerTool.getProducerProperties().put("partitioner.class", "TestPartitioner");
        int i = 0;
        String message = "";
        while (true) {
            message = "test-partitioner-producer : " + i;
            System.out.println(message);
            kafkaProducerTool.publishPartitionedMessage("kafkamonitor-partitionedproducer", i + "", message);
            i ++;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

  代码运行效果如下图: 
   这里写图片描述

4、ConsoleConsumer消费Message

bin/kafka-console-consumer.sh --zookeeper m000:2181 --from-beginning --topic kafkamonitor-partitionedproducer
  • 1

  消费效果如下图: 
   这里写图片描述

5、KafkaOffsetMonitor页面

  其他页面与上面的类似,这里只观察一下每个partition中的message数目与第二节中的对比。可以看到这里每个Partition中message分别是很均匀的。 
   这里写图片描述

注意事项: 
  注意这里有一个坑,默认情况下Producer往一个不存在的Topic发送message时会自动创建这个Topic。由于在这个封装中,有同时传递message和topic的情况,如果调用方法时传入的参数反了,将会在Kafka集群中自动创建Topic。在正常情况下,应该是先把Topic根据需要创建好,然后Producer往该Topic发送Message,最好把Kafka这个默认自动创建Topic的功能关掉。 
  那么,假设真的不小心创建了多余的Topic,在删除时,会出现“marked for deletion”提示,只是将该topic标记为删除,使用list命令仍然能看到。如果需要调整这两个功能的话,在server.properties中配置如下两个参数:

参数默认值作用
auto.create.topics.enabletrueEnable auto creation of topic on the server
delete.topic.enablefalseEnables delete topic. Delete topic through the admin tool will have no effect if this config is turned off

【译】调优Apache Kafka集群 - huxihx - 博客园

$
0
0

  今天带来一篇译文“调优Apache Kafka集群”,里面有一些观点并无太多新颖之处,但总结得还算详细。该文从四个不同的目标出发给出了各自不同的参数配置,值得大家一读~ 原文地址请参考:https://www.confluent.io/blog/optimizing-apache-kafka-deployment/

==========================================

  Apache Kafka是当前最好的企业级流式处理平台。把你的应用程序链接到Kafka集群,剩下的所有事情Kafka都可以帮你做了:自动帮你完成负载均衡,自动实现Zero-Copy的数据传输、消费者组成员变动时自动的rebalance以及应用状态持久化存储的自动备份以及分区leader自动的故障转移等——运维人员的梦想终于成真了!

————笔者:最近在看Apache Flink。说到streaming这部分,Flink可一点都不比Kafka streams差。至于是不是最好的流式处理平台,仁者见仁吧~~

  使用默认的Kafka参数配置你就能够从零搭建起一个Kafka集群环境用于开发及测试之用,但默认配置通常都不匹配你的生产环境,因此必须要做某种程度的调优。毕竟不同的使用场景有着不同的使用需求和性能指标。而Kafka提供的各种参数就是为了优化这些需求和指标的。Kafka提供了很多配置供用户设置以确保搭建起来的Kafka环境是能够满足需求目标的,因此详细地去调研这些参数的含义以及针对不同参数值进行测试是非常重要的。所有这些工作都应该在Kafka正式上生产环境前就做好,并且各种参数的配置要考虑未来集群规模的扩展。

   执行优化的流程如下图所示:

  1. 明确调优目标
  2. 有针对性地配置Kafka server端和clients端参数
  3. 执行性能测试,监控各个指标以确定是否满足需求以及是否有进一步调优的可能

一、确立目标 

  第一步就是要明确性能调优目标,主要从4个方面考虑:吞吐量(throughput)、延时(latency)、持久性(durability)和可用性(availability)。根据实际的使用场景来确定要达到这4个中的哪个(或哪几个)目标。有时候我们可能很难确定自己到底想要什么,那么此时可以尝试采用这样的方法:让你的团队坐下来讨论一下原本的业务使用场景然后看看主要的业务目标是什么。确立目标的原因主要有两点:

  • “鱼和熊掌不可兼得”——你没有办法最大化所有目标。这4者之间必然存在着权衡(tradeoff)。常见的tradeoff包括:吞吐量和延时权衡、持久性和可用性之间权衡。但是当我们考虑整个系统时通常都不能孤立地只考虑其中的某一个方面,而是需要全盘考量。虽然它们之间不是互斥的,但使所有目标同时达到最优几乎肯定是不可能的
  • 我们需要不断调整Kafka配置参数以实现这些目标,并确保我们对Kafka的优化是满足用户实际使用场景的需要

  下面的这些问题可以帮助你确立目标:

  • 是否期望着Kafka实现高吞吐量(TPS,即producer生产速度和consumer消费速度),比如几百万的TPS?由于Kafka自身良好的设计,生产超大数量的消息并不是什么难事。比起传统的数据库或KV存储而言,Kafka要快得多,而且使用普通的硬件就能够做到这点
  • 是否期望着Kafka实现低延时(即消息从被写入到被读取之间的时间间隔越小越好)? 低延时的一个实际应用场景就是平时的聊天程序,接收到某一条消息越快越好。其他的例子还包括交互性网站中用户期望实时看到好友动态以及物联网中的实时流处理等
  • 是否期望着Kafka实现高持久性,即被成功提交的消息永远不能丢失?比如事件驱动的微服务数据管道使用Kafka作为底层数据存储,那么就要求Kafka不能丢失事件。再比如streaming框架读取持久化存储时一定要确保关键的业务事件不能遗漏等
  • 是否期望着Kafka实现高可用?即使出现崩溃也不能出现服务的整体宕机。Kafka本身是分布式系统,天然就是能够对抗崩溃的。如果高可用是你的主要目标,配置特定的参数确保Kafka可以及时从崩溃中恢复就显得至关重要了

二、配置参数

下面我们将分别讨论这四个目标的优化以及对应的参数设置。这些参数涵盖了producer端、broker端和consumer端的不同配置。如前所述,很多配置都提现了某种程度的tradeoff,在使用时一定要弄清楚这些配置的真正含义,做到有的放矢。

producer端

  • batch.size
  • linger.ms
  • compression.type
  • acks
  • retries
  • max.in.flight.requests.per.connection
  • buffer.memory

Broker端

  • default.replication.factor
  • num.replica.fetchers
  • auto.create.topics.enable
  • min.insync.replicas
  • unclean.leader.election.enable
  • broker.rack
  • log.flush.interval.messages
  • log.flush.interval.ms
  • unclean.leader.election.enable
  • min.insync.replicas
  • num.recovery.threads.per.data.dir

Consumer端

  • fetch.min.bytes
  • auto.commit.enable
  • session.timeout.ms

1 调优吞吐量

Producer端

  • batch.size = 100000 - 200000(默认是16384,通常都太小了)
  • linger.ms = 10 - 100 (默认是0)
  • compression.type = lz4
  • acks = 1
  • retries = 0
  • buffer.memory:如果分区数很多则适当增加 (默认是32MB)

Consumer端

  • fetch.min.bytes = 10 ~ 100000 (默认是1)

2 调优延时

Producer端

  • linger.ms = 0
  • compression.type = none
  • acks = 1

Broker端

  • num.replica.fetchers:如果发生ISR频繁进出的情况或follower无法追上leader的情况则适当增加该值,但通常不要超过CPU核数+1

Consumer端

  • fetch.min.bytes = 1

3 调优持久性

Producer端

  • replication.factor = 3
  • acks = all
  • retries = 相对较大的值,比如5 ~ 10
  • max.in.flight.requests.per.connection = 1 (防止乱序)

Broker端

  • default.replication.factor = 3
  • auto.create.topics.enable = false
  • min.insync.replicas = 2,即设置为replication factor - 1
  • unclean.leader.election.enable = false
  • broker.rack: 如果有机架信息,则最好设置该值,保证数据在多个rack间的分布性以达到高持久化
  • log.flush.interval.messages和log.flush.interval.ms: 如果是特别重要的topic并且TPS本身也不高,则推荐设置成比较低的值,比如1

Consumer端

  • auto.commit.enable = false 自己控制位移

4 调优高可用

 

Broker端

  • unclean.leader.election.enable = true
  • min.insync.replicas = 1
  • num.recovery.threads.per.data.dir = log.dirs中配置的目录数

Consumer端

  • session.timeout.ms:尽可能地低

三、指标监控

1 操作系统级指标

  • 内存使用率
  • 磁盘占用率
  • CPU使用率
  • 打开的文件句柄数
  • 磁盘IO使用率
  • 带宽IO使用率

2 Kafka常规JMX监控

3 易发现瓶颈的JMX监控

 

4 clients端常用JMX监控 

 

5 broker端ISR相关的JMX监控 

==========================================

  以上就是这篇原文的简要译文。还是那句话,里面的很多参数设置都已经司空见惯了,并无太多新意。不过这篇文章从吞吐量、延时、持久化和可用性4个方面给出了不同的思考。从这一点上来说还是值得一读的。

记一次kafka数据丢失问题的排查 - CSDN博客

$
0
0
数据丢失为大事,针对数据丢失的问题我们排查结果如下。
第一:是否存在数据丢失的问题?
    存在,且已重现。

第二:是在什么地方丢失的数据,是否是YDB的问题?
    数据丢失是在导入阶段,数据并没有写入到Kafka里面,所以YDB也就不会从Kafka里面消费到缺失的数据,数据丢失与延云YDB无关。

第三:是如何发现有数据丢失?
    1.测试数据会一共创建365个分区,每个分区均是9亿数据,如果最终每个分区还是9亿(多一条少一条均不行),则数据完整。
    2.测试开始第二天,开始有丢失数据的现象,且丢失的数据越来越多。

第四:如何定位到是写入端丢失数据的,而不是YDB消费丢失数据的?
    kafka支持数据的重新回放的功能(换个消费group),我们清空了ydb的所有数据,重新用kafka回放了原先的数据。
    如果是在ydb消费端丢失数据,那么第二遍回放数据的结果,跟第一次消费的数据在条数上肯定会有区别,完全一模一样的几率很低。
    数据回放结果为:与第一次回放结果完全一样,可以确认为写入段丢失。

第五:写入kafka数据为什么会丢失?
    导入数据我们采用的为kafka给的 官方的默认示例,官方默认并没有处理网络负载很高或者磁盘很忙写入失败的情况(网上遇到同类问题的也很多)
    一旦网络中断或者磁盘负载很高导致的写入失败,并没有自动重试重发消息。
    而我们之前的测试,
    第1次测试是在共享集群环境上做的测试,由于有其他任务的影响,网络与负载很不稳定,就会导致数据丢失。
    第2次测试是在独立集群,并没有其他任务干预,但是我们导入程序与kafka不在一台机器上,而我们又没有做限速处理(每小时导入5亿条数据)
    千兆网卡的流量常态在600~800M左右,如果此时突然又索引合并,瞬间的网络跑满是很正常的,丢包也是很正常的。
    延云之前持续压了20多天,确实一条数据没有丢失,究其原因是导入程序与kafka在同一个机器上,且启用了限速。

第六:这个问题如何解决?
    官方给出的默认示例并不可靠,并没有考虑到网络繁忙的情况,并不适合生产。
    故kafka一定要配置上消息重试的机制,并且重试的时间间隔一定要长一些,默认1秒钟并不符合生产环境(网络中断时间有可能超过1秒)。
    延云认为,增加如下参数会较大幅度的减少kafka写入数据照成的数据丢失,在公司实测,目前还没遇到数据丢失的情况。
         props.put("compression.type", "gzip");
         props.put("linger.ms", "50");
         props.put("acks", "all");
         props.put("retries ", 30);
         props.put("reconnect.backoff.ms ", 20000);
         props.put("retry.backoff.ms", 20000);
    


Kafka无消息丢失配置 - huxihx - 博客园

$
0
0

Kafka到底会不会丢数据(data loss)? 通常不会,但有些情况下的确有可能会发生。下面的参数配置及Best practice列表可以较好地保证数据的持久性(当然是trade-off,牺牲了吞吐量)。笔者会在该列表之后对列表中的每一项进行讨论,有兴趣的同学可以看下后面的分析。

  1. block.on.buffer.full = true
  2. acks = all
  3. retries = MAX_VALUE
  4. max.in.flight.requests.per.connection = 1
  5. 使用KafkaProducer.send(record, callback)
  6. callback逻辑中显式关闭producer:close(0) 
  7. unclean.leader.election.enable=false
  8. replication.factor = 3 
  9. min.insync.replicas = 2
  10. replication.factor > min.insync.replicas
  11. enable.auto.commit=false
  12. 消息处理完成之后再提交位移

给出列表之后,我们从两个方面来探讨一下数据为什么会丢失:

1. Producer端

  目前比较新版本的Kafka正式替换了Scala版本的old producer,使用了由Java重写的producer。新版本的producer采用异步发送机制。KafkaProducer.send(ProducerRecord)方法仅仅是把这条消息放入一个缓存中(即RecordAccumulator,本质上使用了队列来缓存记录),同时后台的IO线程会不断扫描该缓存区,将满足条件的消息封装到某个batch中然后发送出去。显然,这个过程中就有一个数据丢失的窗口:若IO线程发送之前client端挂掉了,累积在accumulator中的数据的确有可能会丢失。

  Producer的另一个问题是消息的乱序问题。假设客户端代码依次执行下面的语句将两条消息发到相同的分区

producer.send(record1);
producer.send(record2);

如果此时由于某些原因(比如瞬时的网络抖动)导致record1没有成功发送,同时Kafka又配置了重试机制和max.in.flight.requests.per.connection大于1(默认值是5,本来就是大于1的),那么重试record1成功后,record1在分区中就在record2之后,从而造成消息的乱序。很多某些要求强顺序保证的场景是不允许出现这种情况的。

  鉴于producer的这两个问题,我们应该如何规避呢??对于消息丢失的问题,很容易想到的一个方案就是:既然异步发送有可能丢失数据, 我改成同步发送总可以吧?比如这样:

producer.send(record).get();

这样当然是可以的,但是性能会很差,不建议这样使用。因此特意总结了一份配置列表。个人认为该配置清单应该能够比较好地规避producer端数据丢失情况的发生:(特此说明一下,软件配置的很多决策都是trade-off,下面的配置也不例外:应用了这些配置,你可能会发现你的producer/consumer 吞吐量会下降,这是正常的,因为你换取了更高的数据安全性)

  • block.on.buffer.full = true  尽管该参数在0.9.0.0已经被标记为“deprecated”,但鉴于它的含义非常直观,所以这里还是显式设置它为true,使得producer将一直等待缓冲区直至其变为可用。否则如果producer生产速度过快耗尽了缓冲区,producer将抛出异常
  • acks=all  很好理解,所有follower都响应了才认为消息提交成功,即"committed"
  • retries = MAX 无限重试,直到你意识到出现了问题:)
  • max.in.flight.requests.per.connection = 1 限制客户端在单个连接上能够发送的未响应请求的个数。设置此值是1表示kafka broker在响应请求之前client不能再向同一个broker发送请求。注意:设置此参数是为了避免消息乱序
  • 使用KafkaProducer.send(record, callback)而不是send(record)方法   自定义回调逻辑处理消息发送失败
  • callback逻辑中最好显式关闭producer:close(0) 注意:设置此参数是为了避免消息乱序
  • unclean.leader.election.enable=false   关闭unclean leader选举,即不允许非ISR中的副本被选举为leader,以避免数据丢失
  • replication.factor >= 3   这个完全是个人建议了,参考了Hadoop及业界通用的三备份原则
  • min.insync.replicas > 1 消息至少要被写入到这么多副本才算成功,也是提升数据持久性的一个参数。与acks配合使用
  • 保证replication.factor > min.insync.replicas  如果两者相等,当一个副本挂掉了分区也就没法正常工作了。通常设置replication.factor = min.insync.replicas + 1即可

2. Consumer端

  consumer端丢失消息的情形比较简单:如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。由于Kafka consumer默认是自动提交位移的,所以在后台提交位移前一定要保证消息被正常处理了,因此不建议采用很重的处理逻辑,如果处理耗时很长,则建议把逻辑放到另一个线程中去做。为了避免数据丢失,现给出两点建议:

  • enable.auto.commit=false  关闭自动提交位移
  • 在消息被完整处理之后再手动提交位移

高可用Hadoop平台-Flume NG实战图解篇 - 哥不是小萝莉 - 博客园

$
0
0

1.概述

  今天补充一篇关于Flume的博客,前面在讲解高可用的Hadoop平台的时候遗漏了这篇,本篇博客为大家讲述以下内容:

  • Flume NG简述
  • 单点Flume NG搭建、运行
  • 高可用Flume NG搭建
  • Failover测试
  • 截图预览

  下面开始今天的博客介绍。

2.Flume NG简述

  Flume NG是一个分布式,高可用,可靠的系统,它能将不同的海量数据收集,移动并存储到一个数据存储系统中。轻量,配置简单,适用于各种日志收集,并支持Failover和负载均衡。并且它拥有非常丰富的组件。Flume NG采用的是三层架构:Agent层,Collector层和Store层,每一层均可水平拓展。其中Agent包含Source,Channel和Sink,三者组建了一个Agent。三者的职责如下所示:

  • Source:用来消费(收集)数据源到Channel组件中
  • Channel:中转临时存储,保存所有Source组件信息
  • Sink:从Channel中读取,读取成功后会删除Channel中的信息

  下图是Flume NG的架构图,如下所示:

  图中描述了,从外部系统(Web Server)中收集产生的日志,然后通过Flume的Agent的Source组件将数据发送到临时存储Channel组件,最后传递给Sink组件,Sink组件直接把数据存储到HDFS文件系统中。

3.单点Flume NG搭建、运行

  我们在熟悉了Flume NG的架构后,我们先搭建一个单点Flume收集信息到HDFS集群中,由于资源有限,本次直接在之前的高可用Hadoop集群上搭建Flume。

  场景如下:在NNA节点上搭建一个Flume NG,将本地日志收集到HDFS集群。

3.1基础软件

  在搭建Flume NG之前,我们需要准备必要的软件,具体下载地址如下所示:

  JDK由于之前在安装Hadoop集群时已经配置过,这里就不赘述了,若需要配置的同学,可参考《 配置高可用的Hadoop平台》。

3.2安装与配置

  • 安装

  首先,我们解压flume安装包,命令如下所示:

[hadoop@nna ~]$tar-zxvf apache-flume-1.5.2-bin.tar.gz
  • 配置

  环境变量配置内容如下所示:

export FLUME_HOME=/home/hadoop/flume-1.5.2export PATH=$PATH:$FLUME_HOME/bin

  flume-conf.properties

#agent1 name
agent1.sources=source1
agent1.sinks=sink1
agent1.channels=channel1


#Spooling Directory
#set source1
agent1.sources.source1.type=spooldir
agent1.sources.source1.spoolDir=/home/hadoop/dir/logdfs
agent1.sources.source1.channels=channel1
agent1.sources.source1.fileHeader=falseagent1.sources.source1.interceptors=i1
agent1.sources.source1.interceptors.i1.type=timestamp

#set sink1
agent1.sinks.sink1.type=hdfs
agent1.sinks.sink1.hdfs.path=/home/hdfs/flume/logdfs
agent1.sinks.sink1.hdfs.fileType=DataStream
agent1.sinks.sink1.hdfs.writeFormat=TEXT
agent1.sinks.sink1.hdfs.rollInterval=1agent1.sinks.sink1.channel=channel1
agent1.sinks.sink1.hdfs.filePrefix=%Y-%m-%d

#set channel1
agent1.channels.channel1.type=fileagent1.channels.channel1.checkpointDir=/home/hadoop/dir/logdfstmp/point
agent1.channels.channel1.dataDirs=/home/hadoop/dir/logdfstmp

  flume-env.sh

JAVA_HOME=/usr/java/jdk1.7

  注:配置中的目录若不存在,需提前创建。

3.3启动

  启动命令如下所示:

flume-ng agent -n agent1 -c conf -f flume-conf.properties -Dflume.root.logger=DEBUG,console

  注:命令中的agent1表示配置文件中的Agent的Name,如配置文件中的agent1。flume-conf.properties表示配置文件所在配置,需填写准确的配置文件路径。

3.4效果预览

  之后,成功上传后本地目的会被标记完成。如下图所示:

 4.高可用Flume NG搭建

  在完成单点的Flume NG搭建后,下面我们搭建一个高可用的Flume NG集群,架构图如下所示:

  图中,我们可以看出,Flume的存储可以支持多种,这里只列举了HDFS和Kafka(如:存储最新的一周日志,并给Storm系统提供实时日志流)。

4.1节点分配

  Flume的Agent和Collector分布如下表所示:

名称 HOST角色
Agent110.211.55.14Web Server
Agent210.211.55.15Web Server
Agent310.211.55.16 Web Server
Collector110.211.55.18AgentMstr1
Collector210.211.55.19AgentMstr2

 

  图中所示,Agent1,Agent2,Agent3数据分别流入到Collector1和Collector2,Flume NG本身提供了Failover机制,可以自动切换和恢复。在上图中,有3个产生日志服务器分布在不同的机房,要把所有的日志都收集到一个集群中存储。下面我们开发配置Flume NG集群

4.2配置

  在下面单点Flume中,基本配置都完成了,我们只需要新添加两个配置文件,它们是flume-client.properties和flume-server.properties,其配置内容如下所示:

  • flume-client.properties
#agent1 name
agent1.channels=c1
agent1.sources=r1
agent1.sinks=k1 k2

#set gruop
agent1.sinkgroups=g1 

#set channel
agent1.channels.c1.type=memory
agent1.channels.c1.capacity=1000agent1.channels.c1.transactionCapacity=100agent1.sources.r1.channels=c1
agent1.sources.r1.type=exec
agent1.sources.r1.command=tail-F /home/hadoop/dir/logdfs/test.log

agent1.sources.r1.interceptors=i1 i2
agent1.sources.r1.interceptors.i1.type=static
agent1.sources.r1.interceptors.i1.key=Type
agent1.sources.r1.interceptors.i1.value=LOGIN
agent1.sources.r1.interceptors.i2.type=timestamp

# set sink1
agent1.sinks.k1.channel=c1
agent1.sinks.k1.type=avro
agent1.sinks.k1.hostname=nna
agent1.sinks.k1.port=52020# set sink2
agent1.sinks.k2.channel=c1
agent1.sinks.k2.type=avro
agent1.sinks.k2.hostname=nns
agent1.sinks.k2.port=52020#set sink group
agent1.sinkgroups.g1.sinks=k1 k2

#set failover
agent1.sinkgroups.g1.processor.type=failover
agent1.sinkgroups.g1.processor.priority.k1=10agent1.sinkgroups.g1.processor.priority.k2=1agent1.sinkgroups.g1.processor.maxpenalty=10000

  注:指定Collector的IP和Port。

  • flume-server.properties
#set Agent name
a1.sources=r1
a1.channels=c1
a1.sinks=k1

#set channel
a1.channels.c1.type=memory
a1.channels.c1.capacity=1000a1.channels.c1.transactionCapacity=100# other node,nna to nns
a1.sources.r1.type=avro
a1.sources.r1.bind=nna
a1.sources.r1.port=52020a1.sources.r1.interceptors=i1
a1.sources.r1.interceptors.i1.type=static
a1.sources.r1.interceptors.i1.key=Collector
a1.sources.r1.interceptors.i1.value=NNA
a1.sources.r1.channels=c1

#set sink to hdfs
a1.sinks.k1.type=hdfs
a1.sinks.k1.hdfs.path=/home/hdfs/flume/logdfs
a1.sinks.k1.hdfs.fileType=DataStream
a1.sinks.k1.hdfs.writeFormat=TEXT
a1.sinks.k1.hdfs.rollInterval=1a1.sinks.k1.channel=c1
a1.sinks.k1.hdfs.filePrefix=%Y-%m-%d

  注:在另一台Collector节点上修改IP,如在NNS节点将绑定的对象有nna修改为nns。

4.3启动

  在Agent节点上启动命令如下所示:

flume-ng agent -n agent1 -c conf -f flume-client.properties -Dflume.root.logger=DEBUG,console

  注:命令中的agent1表示配置文件中的Agent的Name,如配置文件中的agent1。flume-client.properties表示配置文件所在配置,需填写准确的配置文件路径。

  在Collector节点上启动命令如下所示:

flume-ng agent -n a1 -c conf -f flume-server.properties -Dflume.root.logger=DEBUG,console

  注:命令中的a1表示配置文件中的Agent的Name,如配置文件中的a1。flume-server.properties表示配置文件所在配置,需填写准确的配置文件路径。

5.Failover测试

  下面我们来测试下Flume NG集群的高可用(故障转移)。场景如下:我们在Agent1节点上传文件,由于我们配置Collector1的权重比Collector2大,所以Collector1优先采集并上传到存储系统。然后我们kill掉Collector1,此时有Collector2负责日志的采集上传工作,之后,我们手动恢复Collector1节点的Flume服务,再次在Agent1上次文件,发现Collector1恢复优先级别的采集工作。具体截图如下所示:

  • Collector1优先上传

  • HDFS集群中上传的log内容预览

  • Collector1宕机,Collector2获取优先上传权限

  • 重启Collector1服务,Collector1重新获得优先上传的权限

6.截图预览

  下面为大家附上HDFS文件系统中的截图预览,如下图所示:

  • HDFS文件系统中的文件预览

  • 上传的文件内容预览

7.总结

  在配置高可用的Flume NG时,需要注意一些事项。在Agent中需要绑定对应的Collector1和Collector2的IP和Port,另外,在配置Collector节点时,需要修改当前Flume节点的配置文件,Bind的IP(或HostName)为当前节点的IP(或HostName),最后,在启动的时候,指定配置文件中的Agent的Name和配置文件的路径,否则会出错。

8.结束语

  这篇博客就和大家分享到这里,如果大家在研究学习的过程当中有什么问题,可以加群进行讨论或发送邮件给我,我会尽我所能为您解答,与君共勉!

【实践】Spark 协同过滤ALS之Item2Item相似度计算优化 - CSDN博客

$
0
0

最近项目在做推荐系统中 match策略中的 CF召回优化,自之前第一版自己实现的基于item的协同过滤算法 http://blog.csdn.net/dengxing1234/article/details/76122465,考虑到用户隐型评分的 稀疏性问题,所以尝试用Spark ml包(非mllib)中的ALS算法的中间产物item的隐性向量,进行进一步item到item的余弦相似度计算。

由于item的数据量较大(百万级别),涉及计算每个item与其他所有item的向量计算,尝试过以下几种方法:

  • (1)spark rdd的笛卡尔积
  • (2)spark dataframe的cross join
  • (3)避免集群shuffle,采用driver端的本地计算
  • (4)spark分布式矩阵运算IndexedRowMatrix
效果都不佳,数据量一大几乎任务没法运行,本文从两个方面优化相似度计算过程,第一从计算引擎底层考虑使用Breeze Blas计算包,第二从数据结构算法方面考虑,以下是自己的实现代码

package model

import org.apache.log4j.{Level, Logger}
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.sql.DataFrame
import scala.collection.mutable
import org.apache.spark.ml.recommendation.ALS
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import util.BlasSim


object ModelBasedCF {
  case class Rating(userId: Int, movieId: Int, rating: Double)
  /** 基于dt时间获取原始数据源
    *
    * @param spark SparkContext
    * @param hql   hql
    * @return 原始数据的dataFrame
    */
  def getResource(spark: SparkSession, hql: String) = {
    import spark.sql
    val resource = sql(hql)
    resource
  }

  /**
    * 基于item相似度矩阵为user生成topN推荐列表
    *
    * @param resource
    * @param item_sim_bd
    * @param topN
    * @return RDD[(user,List[(item,score)])]
    */
  def recommend(resource: DataFrame, item_sim_bd: Broadcast[scala.collection.Map[String, List[(String, Double)]]], topN: Int = 50) = {
    val user_item_score = resource.rdd.map(
      row => {
        val uid = row.getString(0)
        val aid = row.getString(1)
        val score = row.getDouble(2)
        ((uid, aid), score)
      }
    )
    /*
     * 提取user_item_score为((user,item2),sim * score)
     * RDD[(user,item2),sim * score]
     */
    val user_item_simscore = user_item_score.flatMap(
      f => {
        val items_sim = item_sim_bd.value.getOrElse(f._1._2, List(("0", 0.0)))
        for (w <- items_sim) yield ((f._1._1, w._1), w._2 * f._2)
      })

    /*
     * 聚合user_item_simscore为 ((user,item2),sim1 * score1 + sim2 * score2))
     * 假设user观看过两个item,评分分别为score1和score2,item2是与user观看过的两个item相似的item,相似度分别为sim1,sim2
     * RDD[(user,item2),sim1 * score1 + sim2 * score2))]
     */
    val user_item_rank = user_item_simscore.reduceByKey(_ + _, 1000)
    /*
     * 过滤用户已看过的item,并对user_item_rank基于user聚合
     * RDD[(user,CompactBuffer((item2,rank2),(item3,rank3)...))]
     */
    // val user_items_ranks = user_item_rank.subtractByKey(user_item_score).map(f => (f._1._1, (f._1._2, f._2))).groupByKey(500)
    val user_items_ranks = user_item_rank.map(f => (f._1._1, (f._1._2, f._2))).groupByKey(500)
    /*
     * 对user_items_ranks基于rank降序排序,并提取topN,其中包括用户已观看过的item
     * RDD[(user,ArrayBuffer((item2,rank2),...,(itemN,rankN)))]
     */
    val user_items_ranks_desc = user_items_ranks.map(f => {
      val item_rank_list = f._2.toList
      val item_rank_desc = item_rank_list.sortWith((x, y) => x._2 > y._2)
      (f._1, item_rank_desc.take(topN))
    })
    user_items_ranks_desc
  }

  /**
    * 计算推荐的召回率
    *
    * @param recTopN
    * @param testData
    */
  def getRecall(recTopN: RDD[(String, List[(String, Double)])], testData: DataFrame) = {
    val uid_rec = recTopN.flatMap(r => {
      val uid = r._1
      val itemList = r._2
      for (item <- itemList) yield (uid, item._1)
    })
    val uid_test = testData.rdd.map(row => {
      val uid = row.getString(0)
      val aid = row.getString(1)
      (uid, aid)
    })
    uid_rec.intersection(uid_test).count() / uid_test.count().toDouble
  }

  def main(args: Array[String]) {
    //屏蔽日志
    Logger.getLogger("org.apache.spark").setLevel(Level.WARN)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

    val spark = SparkSession
      .builder
      .appName("ModelBased CF")
      .enableHiveSupport()
      .getOrCreate()

    val trainHql = args(0)
    val testHql = args(1)

    val trainDataFrame = getResource(spark, trainHql).repartition(100).cache()
    val testDataRrame = getResource(spark, testHql).repartition(100)

    val trainRdd = trainDataFrame.rdd.map(row => {
      val uid = row.getString(0)
      val aid = row.getString(1)
      val score = row.getDouble(2)
      (uid, (aid, score))
    }).cache()

    val userIndex = trainRdd.map(x => x._1).distinct().zipWithIndex().map(x => (x._1, x._2.toInt)).cache()
    val itemIndex = trainRdd.map(x => x._2._1).distinct().zipWithIndex().map(x => (x._1, x._2.toInt)).cache()

    import spark.sqlContext.implicits._
    val trainRatings = trainRdd
      .join(userIndex, 500)
      .map(x => (x._2._1._1, (x._2._2, x._2._1._2)))
      .join(itemIndex, 500)
      .map(x => Rating(x._2._1._1.toInt, x._2._2.toInt, x._2._1._2.toDouble)).toDF()


    val rank = 200

    val als = new ALS()
      .setMaxIter(10)
      .setRank(rank)
      .setNumBlocks(100)
      .setUserCol("userId")
      .setItemCol("movieId")
      .setRatingCol("rating")
      .setImplicitPrefs(true)

    val model = als.fit(trainRatings)
    val itemFeature = model.itemFactors.rdd.map(
      row => {
        val item = row.getInt(0)
        val vec = row.get(1).asInstanceOf[mutable.WrappedArray[Float]]
        (item, vec)
      }
    ).sortByKey().collect()



    val numItems = itemIndex.count().toInt
    val itemVectors = itemFeature.flatMap(x => x._2)
    val itemIndex_tmp = itemIndex.collectAsMap()

    val blasSim = new BlasSim(numItems, rank, itemVectors, itemIndex_tmp)
    val itemString = itemIndex.map(x => (x._2, x._1)).repartition(100)

    val item_sim_rdd = itemString.map(x => {
      (x._2, blasSim.getCosinSimilarity((itemFeature(x._1)._2.toVector), 50, None).toList)
    })


    // 广播相似度矩阵
    val item_sim_map = item_sim_rdd.collectAsMap()
    val item_sim_bd: Broadcast[scala.collection.Map[String, List[(String, Double)]]] = spark.sparkContext.broadcast(item_sim_map)
    // 为用户生成推荐列表
    val recTopN = recommend(trainDataFrame, item_sim_bd, 50)
    // 计算召回率
    println(getRecall(recTopN, testDataRrame))


    spark.stop()
  }
}

package util

import com.github.fommil.netlib.BLAS.{getInstance => blas}
import java.io.Serializable
import java.util.{PriorityQueue => JPriorityQueue}
import scala.collection.JavaConverters._
import scala.collection.Map
import scala.collection.generic.Growable


class BlasSim(val numItems: Int, val vectorSize: Int, val itemVectors: Array[Float], val itemIndex: Map[java.lang.String, Int])extends Serializable{

  val itemList = {
    val (wl, _) = itemIndex.toSeq.sortBy(_._2).unzip
    wl.toArray
  }

  val wordVecNorms: Array[Double] = {
    val wordVecNorms = new Array[Double](numItems)
    var i = 0
    while (i < numItems) {
      val vec = itemVectors.slice(i * vectorSize, i * vectorSize + vectorSize)
      wordVecNorms(i) = blas.snrm2(vectorSize, vec, 1)
      i += 1
    }
    wordVecNorms
  }

  def getCosinSimilarity(vector:Vector[Float], num: Int, wordOpt: Option[String]): Array[(String, Double)] = {
    require(num > 0, "Number of similar words should > 0")
    val fVector = vector.toArray.map(_.toFloat)
    val cosineVec = Array.fill[Float](numItems)(0)
    val alpha: Float = 1
    val beta: Float = 0
    // 归一化输入向量
    val vecNorm = blas.snrm2(vectorSize, fVector, 1)
    if (vecNorm != 0.0f) {
      blas.sscal(vectorSize, 1 / vecNorm, fVector, 0, 1)
    }
    blas.sgemv("T", vectorSize, numItems, alpha, itemVectors, vectorSize, fVector, 1, beta, cosineVec, 1)
    val cosVec = cosineVec.map(_.toDouble)
    var ind = 0
    while (ind < numItems) {
      val norm = wordVecNorms(ind)
      if (norm == 0.0) {
        cosVec(ind) = 0.0
      } else {
        cosVec(ind) /= norm
      }
      ind += 1
    }
    val pq = new BoundedPriorityQueue[(String, Double)](num + 1)(Ordering.by(_._2))
    for (i <- cosVec.indices) {
      pq += Tuple2(itemList(i), cosVec(i))
    }

    val scored = pq.toSeq.sortBy(-_._2)

    val filtered = wordOpt match {
      case Some(w) => scored.filter(tup => w != tup._1)
      case None => scored
    }
    filtered.take(num).toArray
  }
}

class BoundedPriorityQueue[A](maxSize: Int)(implicit ord: Ordering[A])
  extends Iterable[A] with Growable[A] with Serializable {

  private val underlying = new JPriorityQueue[A](maxSize, ord)

  override def iterator: Iterator[A] = underlying.iterator.asScala

  override def size: Int = underlying.size

  override def ++=(xs: TraversableOnce[A]): this.type = {
    xs.foreach {
      this += _
    }
    this
  }

  override def +=(elem: A): this.type = {
    if (size < maxSize) {
      underlying.offer(elem)
    } else {
      maybeReplaceLowest(elem)
    }
    this
  }

  override def +=(elem1: A, elem2: A, elems: A*): this.type = {
    this += elem1 += elem2 ++= elems
  }

  override def clear() {
    underlying.clear()
  }

  private def maybeReplaceLowest(a: A): Boolean = {
    val head = underlying.peek()
    if (head != null && ord.gt(a, head)) {
      underlying.poll()
      underlying.offer(a)
    } else {
      false
    }
  }
}



word2vec词向量训练及中文文本相似度计算 - CSDN博客

$
0
0
本文是讲述如何使用word2vec的基础教程,文章比较基础,希望对你有所帮助!
官网C语言下载地址: http://word2vec.googlecode.com/svn/trunk/
官网Python下载地址: http://radimrehurek.com/gensim/models/word2vec.html


1.简单介绍


参考:《Word2vec的核心架构及其应用 · 熊富林,邓怡豪,唐晓晟 · 北邮2015年》
          《Word2vec的工作原理及应用探究 · 周练 · 西安电子科技大学2014年》
          《Word2vec对中文词进行聚类的研究 · 郑文超,徐鹏 · 北京邮电大学2013年》

PS:第一部分主要是给大家引入基础内容作铺垫,这类文章很多,希望大家自己去学习更多更好的基础内容,这篇博客主要是介绍Word2Vec对中文文本的用法。

(1) 统计语言模型
统计语言模型的一般形式是给定已知的一组词,求解下一个词的条件概率。形式如下:


统计语言模型的一般形式直观、准确,n元模型中假设在不改变词语在上下文中的顺序前提下,距离相近的词语关系越近,距离较远的关联度越远,当距离足够远时,词语之间则没有关联度。

但该模型没有完全利用语料的信息:
1) 没有考虑距离更远的词语与当前词的关系,即超出范围n的词被忽略了,而这两者很可能有关系的。
例如,“华盛顿是美国的首都”是当前语句,隔了大于n个词的地方又出现了“北京是中国的首都”,在n元模型中“华盛顿”和“北京”是没有关系的,然而这两个句子却隐含了语法及语义关系,即”华盛顿“和“北京”都是名词,并且分别是美国和中国的首都。

2) 忽略了词语之间的相似性,即上述模型无法考虑词语的语法关系。
例如,语料中的“鱼在水中游”应该能够帮助我们产生“马在草原上跑”这样的句子,因为两个句子中“鱼”和“马”、“水”和“草原”、“游”和“跑”、“中”和“上”具有相同的语法特性。
而在神经网络概率语言模型中,这两种信息将充分利用到。

(2) 神经网络概率语言模型
神经网络概率语言模型是一种新兴的自然语言处理算法,该模型通过学习训练语料获取词向量和概率密度函数,词向量是多维实数向量,向量中包含了自然语言中的语义和语法关系,词向量之间余弦距离的大小代表了词语之间关系的远近,词向量的加减运算则是计算机在"遣词造句"。

神经网络概率语言模型经历了很长的发展阶段,由Bengio等人2003年提出的神经网络语言模型NNLM(Neural network language model)最为知名,以后的发展工作都参照此模型进行。历经十余年的研究,神经网络概率语言模型有了很大发展。
如今在架构方面有比NNLM更简单的CBOW模型、Skip-gram模型;其次在训练方面,出现了Hierarchical Softmax算法、负采样算法(Negative Sampling),以及为了减小频繁词对结果准确性和训练速度的影响而引入的欠采样(Subsumpling)技术。


上图是基于三层神经网络的自然语言估计模型NNLM(Neural Network Language Model)。NNLM可以计算某一个上下文的下一个词为wi的概率,即(wi=i|context),词向量是其训练的副产物。NNLM根据语料库C生成对应的词汇表V。

神将网络知识可以参考我的前文博客: 神经网络和机器学习基础入门分享
NNLM推荐Rachel-Zhang大神文章: word2vec——高效word特征求取
近年来,神经网络概率语言模型发展迅速,Word2vec是最新技术理论的合集。
Word2vec是Google公司在2013年开放的一款用于训练词向量的软件工具。所以,在讲述word2vec之前,先给大家介绍词向量的概念。

(3) 词向量
参考:licstar大神的NLP文章  Deep Learning in NLP (一)词向量和语言模型
正如作者所说:Deep Learning 算法已经在图像和音频领域取得了惊人的成果,但是在 NLP 领域中尚未见到如此激动人心的结果。有一种说法是,语言(词、句子、篇章等)属于人类认知过程中产生的高层认知抽象实体,而语音和图像属于较为底层的原始输入信号,所以后两者更适合做deep learning来学习特征。
但是将词用“词向量”的方式表示可谓是将 Deep Learning 算法引入 NLP 领域的一个核心技术。自然语言理解问题转化为机器学习问题的第一步都是通过一种方法把这些符号数学化。

词向量具有良好的语义特性,是表示词语特征的常用方式。词向量的每一维的值代表一个具有一定的语义和语法上解释的特征。故可以将词向量的每一维称为一个词语特征。词向量用Distributed Representation表示,一种低维实数向量。
例如,NLP中最直观、最常用的词表示方法是One-hot Representation。每个词用一个很长的向量表示,向量的维度表示词表大小,绝大多数是0,只有一个维度是1,代表当前词。
“话筒”表示为 [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 …] 即从0开始话筒记为3。
但这种One-hot Representation采用稀疏矩阵的方式表示词,在解决某些任务时会造成维数灾难,而使用低维的词向量就很好的解决了该问题。同时从实践上看,高维的特征如果要套用Deep Learning,其复杂度几乎是难以接受的,因此低维的词向量在这里也饱受追捧。
Distributed Representation低维实数向量,如:[0.792, −0.177, −0.107, 0.109, −0.542, …]。它让相似或相关的词在距离上更加接近。

总之,Distributed Representation是一个稠密、低维的实数限量,它的每一维表示词语的一个潜在特征,该特征捕获了有用的句法和语义特征。其特点是将词语的不同句法和语义特征分布到它的每一个维度上去表示。
推荐我前面的基础文章: Python简单实现基于VSM的余弦相似度计算

(4) Word2vec
参考:Word2vec的核心架构及其应用 · 熊富林,邓怡豪,唐晓晟 · 北邮2015年
Word2vec是Google公司在2013年开放的一款用于训练词向量的软件工具。它根据给定的语料库,通过优化后的训练模型快速有效的将一个词语表达成向量形式,其核心架构包括CBOW和Skip-gram。

在开始之前,引入模型复杂度,定义如下:
                                                      O = E * T * Q
其中,E表示训练的次数,T表示训练语料中词的个数,Q因模型而异。E值不是我们关心的内容,T与训练语料有关,其值越大模型就越准确,Q在下面讲述具体模型是讨论。

NNLM模型是神经网络概率语言模型的基础模型。在NNLM模型中,从隐含层到输出层的计算时主要影响训练效率的地方,CBOW和Skip-gram模型考虑去掉隐含层。实践证明新训练的词向量的精确度可能不如NNLM模型(具有隐含层),但可以通过增加训练语料的方法来完善。
Word2vec包含两种训练模型,分别是CBOW和Skip_gram(输入层、发射层、输出层),如下图所示:


CBOW模型:
理解为上下文决定当前词出现的概率。在CBOW模型中,上下文所有的词对当前词出现概率的影响的权重是一样的,因此叫CBOW(continuous bag-of-words model)模型。如在袋子中取词,取出数量足够的词就可以了,至于取出的先后顺序是无关紧要的。

Skip-gram模型:
Skip-gram模型是一个简单实用的模型。为什么会提出该问题呢?
在NLP中,语料的选取是一个相当重要的问题。
首先,语料必须充分。一方面词典的词量要足够大,另一方面尽可能地包含反映词语之间关系的句子,如“鱼在水中游”这种句式在语料中尽可能地多,模型才能学习到该句中的语义和语法关系,这和人类学习自然语言是一个道理,重复次数多了,也就会模型了。
其次,语料必须准确。所选取的语料能够正确反映该语言的语义和语法关系。如中文的《人民日报》比较准确。但更多时候不是语料选取引发准确性问题,而是处理的方法。
由于窗口大小的限制,这会导致超出窗口的词语与当前词之间的关系不能正确地反映到模型中,如果单纯扩大窗口大小会增加训练的复杂度。Skip-gram模型的提出很好解决了这些问题。

Skip-gram表示“跳过某些符号”。例如句子“中国足球踢得真是太烂了”有4个3元词组,分别是“中国足球踢得”、“足球踢得真是”、“踢得真是太烂”、“真是太烂了”,句子的本意都是“中国足球太烂”,可是上面4个3元组并不能反映出这个信息。
此时,使用Skip-gram模型允许某些词被跳过,因此可组成“中国足球太烂”这个3元词组。如果允许跳过2个词,即2-Skip-gram,那么上句话组成的3元词组为:


由上表可知:一方面Skip-gram反映了句子的真实意思,在新组成的这18个3元词组中,有8个词组能够正确反映例句中的真实意思;另一方面,扩大了语料,3元词组由原来的4个扩展到了18个。
语料的扩展能够提高训练的准确度,获得的词向量更能反映真实的文本含义。


2.下载源码


下载地址: http://word2vec.googlecode.com/svn/trunk/
使用SVN Checkout源代码,如下图所示。





3.中文语料


PS:最后附有word2vec源码、三大百科语料、腾讯新闻语料和分词python代码。
中文语料可以参考我的文章,通过Python下载百度百科、互动百科、维基百科的内容。
         [python] lantern访问中文维基百科及selenium爬取维基百科语料
         [Python爬虫] Selenium获取百度百科旅游景点的InfoBox消息盒

下载结果如下图所示,共300个国家,百度百科、互动百科、维基百科各自100个,对应的编号都是0001.txt~0100.txt,每个txt中包含一个实体(国家)的信息。


然后再使用Jieba分词工具对齐进行中文分词和文档合并。
#encoding=utf-8
import sys
import re
import codecs
import os
import shutil
import jieba
import jieba.analyse

#导入自定义词典
jieba.load_userdict("dict_all.txt")

#Read file and cut
def read_file_cut():    
    #create path
    pathBaidu = "BaiduSpiderCountry\\"
    resName = "Result_Country.txt"
    if os.path.exists(resName):
        os.remove(resName)
    result = codecs.open(resName, 'w', 'utf-8')

    num = 1
    while num<=100:  #5A 200 其它100
        name = "%04d" % num 
        fileName = pathBaidu + str(name) + ".txt"
        source = open(fileName, 'r')
        line = source.readline()
        while line!="":
            line = line.rstrip('\n')
            #line = unicode(line, "utf-8")
            seglist = jieba.cut(line,cut_all=False)  #精确模式
            output = ' '.join(list(seglist))         #空格拼接
            #print output
            result.write(output + ' ')               #空格取代换行'\r\n'
            line = source.readline()
        else:
            print 'End file: ' + str(num)
            result.write('\r\n')  
            source.close()
        num = num + 1
    else:
        print 'End Baidu'
        result.close()

#Run function
if __name__ == '__main__':
    read_file_cut()

上面只显示了对百度百科100个国家进行分词的代码,但核心代码一样。同时,如果需要对停用词过滤或标点符号过滤可以自定义实现。
分词详见:  [python] 使用Jieba工具中文分词及文本聚类概念
分词合并后的结果为Result_Country.txt,相当于600行,每行对应一个分词后的国家。




4.运行源码


强烈推荐三篇大神介绍word2vec处理中文语料的文章,其中Felven好像是师兄。
        Windows下使用Word2vec继续词向量训练 - 一只鸟的天空
         利用word2vec对关键词进行聚类 - Felven
         http://www.52nlp.cn/中英文维基百科语料上的word2vec实验
         word2vec 词向量工具 - 百度文库

因为word2vec需要linux环境,所有首先在windows下安装linux环境模拟器,推荐cygwin。然后把语料Result_Country.txt放入word2vec目录下,修改demo-word.sh文件,该文件默认情况下使用自带的text8数据进行训练,如果训练数据不存在,则会进行下载,因为需要使用自己的数据进行训练,故注释掉下载代码。

demo-word.sh文件修改如下:
make
#if [ ! -e text8 ]; then
#  wget http://mattmahoney.net/dc/text8.zip -O text8.gz
#  gzip -d text8.gz -f
#fi
time ./word2vec -train Result_Country.txt -output vectors.bin -cbow 1 -size 200 -window 8 -negative 25 -hs 0 -sample 1e-4 -threads 20 -binary 1 -iter 15
./distance vectors.bin

下图参数源自文章: Windows下使用Word2vec继续词向量训练 - 一只鸟的天空


运行命令sh demo-word.sh,等待训练完成。模型训练完成之后,得到了vectors.bin这个词向量文件,可以直接运用。




5.结果展示


通过训练得到的词向量我们可以进行相应的自然语言处理工作,比如求相似词、关键词聚类等。其中word2vec中提供了distance求词的cosine相似度,并排序。也可以在训练时,设置-classes参数来指定聚类的簇个数,使用kmeans进行聚类。
cd C:/Users/dell/Desktop/word2vec
sh demo-word.sh
./distance vectors.bin

输入阿富汗:喀布尔(首都)、坎大哈(主要城市)、吉尔吉斯斯坦、伊拉克等。

输入国歌:


输入首都:

输入GDP:


最后希望文章对你有所帮助,主要是使用的方法。同时更多应用需要你自己去研究学习。
word2vec源码、语料下载地址:
        http://download.csdn.net/detail/eastmount/9434889
(By:Eastmount 2016-02-18 深夜1点   http://blog.csdn.net/eastmount/ )


Latent Semantic Analysis(LSA) - CSDN博客

$
0
0

背景:什么是LSA?

Latent Semantic Analysis(LSA)中文翻译为潜语义分析,也被叫做Latent Semantic Indexing ( LSI )。意思是指通过分析一堆(不止一个)文档去发现这些文档中潜在的意思和概念,什么叫潜在的意思?我第一次看到这个解释,直接懵逼。其实就是发现文档的中心主题吧?假设每个词仅表示一个概念,并且每个概念仅仅被一个词所描述,LSA将非常简单(从词到概念存在一个简单的映射关系)。

根据常识我们知道两个很常见的语言现象:1. 存在不同的词表示同一个意思(同义词,吃饭 & 就餐);2. 一个词表示多个意思(一词多义, 苹果【水果】 & 苹果 【公司】)。这种二义性(多义性)都会混淆概念以至于有时就算是人也很难理解。


LSA工作原理

潜语义分析(Latent Semantic Analysis)来源于信息检索(IR)中的一个问题:如何从搜索query中找到相关的文档。有的人说可以用关键词匹配文档,匹配上的就放在搜索结果前面。这明显是有问题滴,一是背景里我们讲过一词多义和同义词问题,二则10亿+的文档采用关键词匹配,实在不现实。

其实呀,在搜索中我们实际想要去比较的不是词,而是隐藏在词之后的意义和概念,用人话说:query搜索的结果文档隐含的概念要和query的概念尽可能的相近。LSA分析试图去解决这个问题,它把词和文档都映射到一个”概念”空间并在这个空间内进行比较(注:也就是一种降维技术)。

当文档的作者写作的时候,对于词语有着非常宽泛的选择。不同的作者对于词语的选择有着不同的偏好,这样会导致概念的混淆。这种对于词语的随机选择在 词-概念 的关系中引入了噪音。LSA滤除了这样的一些噪音,并且还能够从全部的文档中找到最小的概念集合(为什么是最小?)。

为了让这个难题更好解决,LSA引入一些重要的简化:

  • 文档被用BOW( bags of words : 词袋 )表示,因此词在文档中出现的位置并不重要,只有一个词的出现次数。
  • 概念被表示成经常出现在一起的一些词的某种模式。有点想KNN的思想,文档中概念A相关的词出现次数最多,就把这个文档映射到概念A。例如:4k屏幕、8核CPU 、4G RAM ……经常出现在手机广告文档中。
  • 词被认为只有一个意思。但是这可以使得问题变得更加容易。(这个简化会有怎样的缺陷呢?)

LSA能彻底解决文档映射到概念这个问题吗?

NO! NO !NO! LSA只能很好的应对,并不能解决。一方面是因为文档长度,语法结构,用词规范,各种问题的限制 ,一句话,NLP 太复杂了;另一方面是LSA本身的问题。


LSA实现步骤

文档预处理

对文档分词,词根还原(en),去停用词,保留索引词(index word),删除特殊符号等。

索引词可以是满足下面条件的词:

  • 不是那种特别常见的词,例如 “and”, ”the” 这种停用词(stop word)。这种词没有包含进来是因为他们本身不存在什么意义。
  • 在2个或者2个以上文档里出现。

例子:在amazon.com上搜索”investing”(投资) 并且取top 10搜索结果的书名。下面就是那9个标题,索引词(在2个或2个以上标题出现过的非停用词)被下划线标注:

  1. The Neatest Little Guideto Stock Market Investing
  2. InvestingFor Dummies, 4th Edition
  3. The Little Bookof Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock MarketReturns
  4. The Little Bookof Value Investing
  5. ValueInvesting: From Graham to Buffett and Beyond
  6. Rich Dad’s Guideto Investing: What the Rich Invest in, That the Poor and the Middle Class Do Not!
  7. Investing in Real Estate, 5th Edition
  8. Stock InvestingFor Dummies
  9. Rich Dad’sAdvisors: The ABC’s of Real Estate Investing: The Secrets of Finding Hidden Profits Most Investors Miss

在这个例子里面应用了LSA,在XY轴的图中画出词和标题的位置(只有2维),并且识别出标题的聚类。蓝色圆圈表示9个标题,红色方块表示11个索引词。我们不但能够画出标题的聚类,并且由于索引词可以被画在标题一起,我们还可以给这些聚类打标签。例如,蓝色的聚类,包含了T7和T9,是关于real estate(房地产)的,绿色的聚类,包含了标题T2,T4,T5和T8,是讲value investing(价值投资)的,最后是红色的聚类,包含了标题T1和T3,是讲stock market(股票市场)的。标题T6是孤立点(outlier)。


这里写图片描述

创建词到文档的矩阵

在这个矩阵里,每一个索引词占据了一行,每一个文档占据一列。每一个单元(cell)包含了这个词出现在那个文档中的次数。例如,词”book”出现在T3中一次,出现在T4中一次,而” investing”在所有标题中都出现了一次。一般来说,在LSA中的矩阵会非常大而且会非常稀疏(大部分的单元都是0)。这是因为每个标题或者文档一般只包含所有词汇的一小部分。更复杂的LSA算法会利用这种稀疏性去改善空间和时间复杂度。



这里写图片描述

代码实现

在这篇文章中,我们用python代码去实现LSA的所有步骤。我们将介绍所有的代码。Python代码可以在这里被下到(见上)。需要安装NumPy 和 SciPy这两个库。

NumPy是python的数值计算类,用到了zeros(初始化矩阵),scipy.linalg这个线性代数的库中,我们引入了svd函数也就是做奇异值分解 , LSA的核心。string是python自带的库,我们用到string.punctuation,包含文档中的特殊符号。

In [1]:importstring
In [2]:printstring.punctuation
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

导入Python 包

# -*- coding: utf-8 -*-"""
Created on Fri Jun 17 15:42:40 2016

@author: zang
"""fromnumpyimportzerosfromscipy.linalgimportsvdimportstring

定义LSA类

classLSA(object):def__init__(self, stopwords, ignorechars):self.stopwords = stopwords
    self.ignorechars = ignorechars
    self.wdict = {}
    self.dcount =0

定义一个LSA类,wdict字典用来记录词的个数,dcount用来记录文档号。

解析文档

defparseDoc(self, doc):words = doc.split()forwinwords:
       w = w.lower().translate(None, self.ingorechars).strip()# 去除特殊字符ifw =="":continueelifwinself.stopwords:continueelifwinself.wdict:
         self.wdict[w].append(self.dcount)else:
         self.wdict[w] = [self.dcount]
    self.dcount +=1

在LSA类里添加这个函数。这个函数就是把文档分词,并滤除停用词和标点,剩下的词会把其出现的文档号填入到wdict中去,例如,词book出现在标题3和4中,则我们有self.wdict[‘book’] = [3, 4]。相当于建了一下倒排。

建立索引词文档矩阵

defbuildMwd(self):self.keys = [kforkinself.wdict.keys()iflen(self.wdict[k]) >1]   
    self.keys.sort()   
    self.Mwd = zeros([len(self.keys), self.dcount])fori, kinenumerate(self.keys):fordinself.wdict[k]:  
        self.Mwd[i,d] +=1

所有的文档被解析之后,所有出现的词(也就是词典的keys)被取出并且排序。建立一个矩阵,其行数是词的个数,列数是文档个数。最后,所有的词和文档对所对应的矩阵单元的值被统计出来。

打印索引词文档矩阵

defprintMwd(self):printself.Mwd

打印出索引词文档矩阵。

测试LSA类

if__name__ =="__main__":
    docs =\
    ["The Neatest Little Guide to Stock Market Investing","Investing For Dummies, 4th Edition","The Little Book of Common Sense Investing: The Only Way to Guarantee Your Fair Share of Stock Market Returns","The Little Book of Value Investing","Value Investing: From Graham to Buffett and Beyond","Rich Dad's Guide to Investing: What the Rich Invest in, That the Poor and the Middle Class Do Not!","Investing in Real Estate, 5th Edition","Stock Investing For Dummies","Rich Dad's Advisors: The ABC's of Real Estate Investing: The Secrets of Finding Hidden Profits Most Investors Miss"]

    stopwords = ['and','edition','for','in','little','of','the','to'] 
    ignorechars = string.punctuation
    lsaDemo = LSA(stopwords,ignorechars)fordindocs:
        lsaDemo.parseDoc(d)
    lsaDemo.buildMwd()
    lsaDemo.printMwd()

在刚才的测试数据中验证程序逻辑,并查看最终生成的矩阵:

In [33] : runfile('G:/Python/nlp/LSA.py', wdir='G:/Python/nlp')
[[0.0.1.1.0.0.0.0.0.]
 [0.0.0.0.0.1.0.0.1.]
 [0.1.0.0.0.0.0.1.0.]
 [0.0.0.0.0.0.1.0.1.]
 [1.0.0.0.0.1.0.0.0.]
 [1.1.1.1.1.1.1.1.1.]
 [1.0.1.0.0.0.0.0.0.]
 [0.0.0.0.0.0.1.0.1.]
 [0.0.0.0.0.2.0.0.1.]
 [1.0.1.0.0.0.0.1.0.]
 [0.0.0.1.1.0.0.0.0.]]

用TF-IDF替代简单计数

在复杂的LSA系统中,为了重要的词占据更重的权重,原始矩阵中的计数往往会被修改。例如,一个词仅在5%的文档中应该比那些出现在90%文档中的词占据更重的权重。最常用的权重计算方法就是TF-IDF(词频-逆文档频率)。基于这种方法,我们把每个单元的数值进行修改:

TF-IDF定义如下:

TF−IDF=Ni,jN∗,j×log(DDi)

  • Ni,j= 某个词i出现在文档j的次数(矩阵单元中的原始值)
  • N∗,j= 在文档j中所有词的个数(就是列j上所有数值的和)
  • D= 文档个数(也就是矩阵的列数)
  • Di= 包含词i的文档个数(也就是矩阵第i行非0列的个数)

在这个公式里,在某个文档中密集出现的词通过Ni,jN∗,j被加强,那些仅在少数文档中出现的词通过log(DDi)也被加强。

增加TF-IDF到这个LSA类中,我们需要加入以下两行代码。

frommathimportlogfromnumpyimportasarray, sum

增加下面这个TFIDF方法到我们的LSA类中。WordsPerDoc 就是矩阵每列的和,也就是每篇文档的词语总数。DocsPerWord 利用asarray方法创建一个0、1数组(也就是大于0的数值会被归一到1),然后每一行会被加起来,从而计算出每个词出现在了多少文档中。最后,我们对每一个矩阵单元计算TFIDF公式

defTFIDF(self):WordsPerDoc = sum(self.Mwd, axis=0)         
    DocsPerWord = sum(asarray(self.Mwd >0,'i'), axis=1)  
    rows, cols = self.Mwd.shapeforiinrange(rows):forjinrange(cols):  
            self.Mwd[i,j] = (self.Mwd[i,j] /WordsPerDoc[j]) * log(float(cols) / DocsPerWord[i])

使用奇异值分解

建立完词文档矩阵以后,用奇异值分解(SVD)分析这个矩阵。SVD非常有用的原因是,它能够找到我们矩阵的一个降维表示,他强化了其中较强的关系并且扔掉了噪音(这个算法也常被用来做图像压缩)。换句话说,它可以用尽可能少的信息尽量完善的去重建整个矩阵。为了做到这点,它会扔掉无用的噪音,强化本身较强的模式和趋势。利用SVD的技巧就是去找到用多少维度(概念)去估计这个矩阵。太少的维度会导致重要的模式被扔掉,反之维度太多会引入一些噪音。
这里SVD算法介绍的很少,使用的是python有一个简单好用的类库scipy。如下述代码所示,我们在LSA类中增加了一行代码,这行代码把矩阵分解为另外三个矩阵。矩阵U告诉我们每个词在我们的“概念”空间中的坐标,矩阵Vt告诉我们每个文档在我们的“概念”空间中的坐标,奇异值矩阵S告诉我们如何选择维度数量的线索。

defcalcSVD(self):self.U, self.S, self.Vt = svd(self.Mwd)

为了去选择一个合适的维度数量,我们可以做一个奇异值平方的直方图。它描绘了每个奇异值对于估算矩阵的重要度。下图是我们这个例子的直方图。(每个奇异值的平方代表了重要程度,下图应该是归一化后的结果)。

对于大规模的文档,维度选择在100到500这个范围。在我们的例子中,因为我们打算用图表来展示最后的结果,我们使用3个维度,扔掉第一个维度,用第二和第三个维度来画图(为什么扔掉第一个维度?)。



这里写图片描述

代码:

defplotSingularValuesBar(self):y_value = (self.S*self.S)/sum(self.S*self.S)
    x_value = range(len(y_value))
    plt.bar(x_value, y_value, alpha =1, color ='g', align="center")
    plt.autoscale()
    plt.xlabel("Singular Values")
    plt.ylabel("Importance")
    plt.title("The importance of Each Singular Value")
    plt.show()

我们扔掉维度1的原因非常有意思。对于文档来说,第一个维度和文档长度相关。对于词来说,它和这个词在所有文档中出现的次数有关(为什么?)。如果我们已经对齐了矩阵(centering matrix),通过每列减去每列的平均值,那么我们将会使用维度1(为什么?)。类似的,像高尔夫分数。我们不会想要知道实际的分数,我们想要知道减去标准分之后的分数。这个分数告诉我们这个选手打到小鸟球、老鹰球(猜)等等。

我们没有对齐矩阵的原因是,对齐后稀疏矩阵会变成稠密矩阵,而这将大大增加内存和计算量。更有效的方法是并不对齐矩阵并且扔掉维度1。

下面是我们矩阵经过SVD之后3个维度的完整结果。每个词都有三个数字与其关联,一个代表了一维。上面讨论过,第一个数字趋向于和该词出现的所有次数有关,并且不如第二维和第三维更有信息量。类似的,每个标题都有三个数字与其关联,一个代表一维。同样的,我们对第一维不感兴趣,因为它趋向于和每个标题词的数量有关系。

U:
[[-0.152835560.26603445-0.04450319]
 [-0.23746367-0.378262820.08595889]
 [-0.130265380.17428415-0.06901432]
 [-0.18440432-0.1939483-0.44568964]
 [-0.2161232-0.087272480.46011902]
 [-0.740096540.21114703-0.21075317]
 [-0.176875850.297911610.28320277]
 [-0.18440432-0.1939483-0.44568964]
 [-0.3630785-0.588541280.34119818]
 [-0.25019360.415577160.28435272]
 [-0.122936170.14317803-0.23449128]]
S:
[[3.909418040.0.]
 [0.2.609118810.]
 [0.0.1.99682784]]
Vt:
[[-0.35383506-0.22263209-0.33764656-0.25985153-0.22075733-0.49108087-0.28364968-0.28662975-0.43726389]
 [0.320937220.147724660.456349580.237765910.13580258-0.54864149-0.067743010.3070034-0.43829115]
 [0.40910955-0.140105970.15639762-0.24526283-0.222975890.50966892-0.551941650.00229626-0.33802383]]

用颜色聚类

我们可以把数字转换为颜色。例如,下图表示了文档矩阵3个维度的颜色分布。除了蓝色表示负值,红色表示正值,它包含了和矩阵同样的信息。例如,文档9在所有三个维度上正数值都较大,那么它在3个维度上都会很红。



这里写图片描述

代码(这里用到了seaborn包绘制热力图):

defplotSingularHeatmap(self):labels = ["T1","T2","T3","T4","T5","T6","T7","T8","T9"]
    rows = ["Dim1","Dim2","Dim3"]
    self.Vtdf_norm = pd.DataFrame(self.Vt2*(-1))
    self.Vtdf_norm.columns = labels
    self.Vtdf_norm.index = rows
    sns.set(font_scale=1.2)
    ax = sns.heatmap(self.Vtdf_norm, cmap=plt.cm.bwr, linewidths=.1,square=2)
    ax.xaxis.tick_top()
    plt.xlabel("Book Title")
    plt.ylabel("Dimensions")

按值聚类

去掉维度1,让我们用xy轴坐标图来画出第二维和第三维。第二维作为X、第三维作为Y,并且把每个词和标题都画上去。比较下这个图和刚才聚类的表格会非常有意思。
在下图中,词表示为红色方形,标题表示为蓝色圆圈。例如,词“book”有坐标值(0.15, -0.27,0.04)。这里我们忽略第一维度0.15 把点画在(x = -0.27, y =0.04)。标题也是一样。

这里写图片描述

这个技术的一个有点是词和标题都在一张图上。不仅我们可以区分标题的聚类,而且我们可以把聚类中的词给这个聚类打上标签。例如左下的聚类中有标题1和标题3都是关于股票市场投资(stock market investing)的。Stock和market可以方便的定位在这个聚类中,让描述这个聚类变得容易。其它也类似。


LSA的优势、劣势以及应用

LSA有着许多优良的品质使得其应用广泛:

  • 第一,文档和词被映射到了同一个“概念空间”。在这个空间中,我们可以把聚类文档,聚类词,最重要的是可以知道不同类型的聚类如何联系在一起的,这样我们可以通过词来寻找文档,反之亦然。
  • 第二,概念空间比较原始矩阵来说维度大大减少。不仅如此,这种维度数量是刻意为之的,因为他们包含了大部分的信息和最少的噪音。这使得新产生的概念空间对于运行之后的算法非常理想,例如尝试不同的聚类算法。
  • 最后LSA天生是全局算法,它从所有的文档和所有的词中找到一些东西,而这是一些局部算法不能完成的。它也能和一些更局部的算法(最近邻算法nearest neighbors)所结合发挥更大的作用。

当选择使用LSA时也有一些限制需要被考量。其中的一些是:

  • LSA假设Gaussiandistribution and Frobenius norm,这些假设不一定适合所有问题。例如,文章中的词符合Poissondistribution而不是Gaussian distribution。
  • LSA不能够有效解决多义性(一个词有多个意思)。它假设同样的词有同样的概念,这就解决不了例如bank这种词需要根据语境才能确定其具体含义的。
  • LSA严重依赖于SVD,而SVD计算复杂度非常高并且对于新出现的文档很难去做更新。然而,近期出现了一种可以更新SVD的非常有效的算法。

不考虑这些限制,LSA被广泛应用在发现和组织搜索结果,把文章聚类,过滤作弊,语音识别,专利搜索,自动文章评价等应用之上。


Github地址

本文的代码已经同步到Github上, 点这里, 有兴趣的同学欢迎批评指正。


Ref : 参考资料

在此列出各位前辈大神的劳动成功,我都是偷他们的,谢谢。
- Latent Semantic Analysis(Tutorial)
- An Introduction to Latent Semantic Analysis
- Latent Semantic Indexing (LSI) A Fast Track Tutorial
- yihucha166博客
- zhikaizhang博客

Python提取数字图片特征向量 | kTWO-个人博客

$
0
0

u=2579025859,808111110&fm=26&gp=0.jpg

引言

在机器学习中有一种学习叫做手写数字识别,其主要功能就是让机器识别出图片中的数字,其步骤主要包括:图片特征提取、将特征值点阵转化为特征向量、进行模型训练。第一步便是提取图片中的特征提取。数据的预处理关系着后面模型的构建情况,所以,数据的处理也是机器学习中非常重要的一部分。下面我就说一下如何提取图片中的特征向量。

图片灰度化

blob.png => blob.png

当我们拿到一种图片的时候,这张图片可能是多种颜色集合在一起的,而我们为了方便处理这张图片,我们首先会将这张图片灰度化(左图灰度化之前,右图灰度化之后)。如果该图片已经是黑白两色的就可以省略此步骤。

1
2
3
4
5
6
7
8
9
10
11
from PIL import Image
import numpy as np

#打开一张图片
img = Image.open("image/77.jpg")
#图片灰度化
img = img.convert("L")
#显示图片
img.show()
#将图片转换为数组形式,元素为其像素的亮度值
print np.asarray(img)

在图片灰度化之前这张图片的数组值应该是一个三维的,灰度化之后将变为二维数组。数组行列数就是图片的像素宽度和高度。

打印的数组形式如下:

blob.png

图片的二值化

图片的二值化就是将上面的数组化为0和1的形式,转化之前我们要设定一个阈值,大于这个阈值的像素点我们将其设置为1,小于这个阈值的像素点我们将其设置为0。下面我找了一张数字的图片,这张图片已经灰度化过了。我们就直接将它二值化。图片如下:

blob.png

图片的像素是32x32的。如果不是要化为此值,这一步我们叫做尺寸归一化。

1
2
3
4
5
6
7
8
#打开一张图片
img = Image.open("numImage/3.jpg")
#将图片化为32*21的
img = img.resize((32, 32))
#二值化
#将图片转换为数组形式,元素为其像素的亮度值
img_array = np.asarray(img)
print img_array

解释一下上面的代码,resize方法里的参数是一个元组,元素分别是宽和高;point函数是用来二值化图片的,其参数是一个lambda函数,函数体就是判断其元素值是否大于120,这里的120就是上面提到的阈值。

二值化后的数组:

blob.png

在数组中我们可以大似的看到,数字1大似组成了一个3的形状。

获取网格特征数字统计图

在图片二值化之后,我们通常需要获取到网格统计图,这里我们的图片尺寸是32*32的,所以我们将其化为8*8的点阵图,步骤如下:

1、将二值化后的点阵水平平均划线分成8份,竖直平均划线分成8份。

2、分别统计每一份中像素点为1的个数。

3、将每一个份统计值组合在一起,构成8*8的点阵统计图。

下面我写了个函数来将32*32的数组转化成8*8的网格特征数字统计图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#将二值化后的数组转化成网格特征统计图
def get_features(array):
    #拿到数组的高度和宽度
    h, w = array.shape
    data = []
    for x in range(0, w/4):
        offset_y = x * 4
        temp = []
        for y in range(0,h/4):
            offset_x = y * 4
            #统计每个区域的1的值
            temp.append(sum(sum(array[0+offset_y:4+offset_y,0+offset_x:4+offset_x])))
        data.append(temp)
    return np.asarray(data)

转化之后我们的到的数组点阵是这样的:

blob.png

将二维的统计图转化为一维的特征向量

这一步就比较简单了,只需要将矩阵全部放到一行即可,直接使用np的reshape()方法即可:

1
2
features_vector =features_array.reshape(features_array.shape[0]*features_array.shape[1])
print features_vector

输出结果:

blob.png

有些同学可能要问,为什么要将二维的点阵转化成一维的特征向量? 这是因为在机器学习中,数据集的格式就是这样的,数据集的一个样例就是一个特征向量,对个样例组成一个训练集。转化为以为的特征向量是便于我们的使用。

全部代码(省略灰度化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from PIL import Image
import numpy as np


#将二值化后的数组转化成网格特征统计图
def get_features(array):
    #拿到数组的高度和宽度
    h, w = array.shape
    data = []
    for x in range(0, w/4):
        offset_y = x * 4
        temp = []
        for y in range(0,h/4):
            offset_x = y * 4
            #统计每个区域的1的值
            temp.append(sum(sum(array[0+offset_y:4+offset_y,0+offset_x:4+offset_x])))
        data.append(temp)
    return np.asarray(data)
    
#打开一张图片
img = Image.open("numImage/3.jpg")
#将图片化为32*32的
img = img.resize((32, 32))

#二值化
img = img.point(lambda x:1 if x > 120 else 0)
#将图片转换为数组形式,元素为其像素的亮度值
img_array = np.asarray(img)
print img_array
#得到网格特征统计图
features_array = get_features(img_array)
print features_array
features_vector =features_array.reshape(features_array.shape[0]*features_array.shape[1])
print features_vector

最后送上手写数字训练集图片链接: 点击下载

Spark-mllib 文本特征提取算法 - CSDN博客

$
0
0

Spark MLlib 提供三种文本特征提取方法,分别为TF-IDF、Word2Vec以及CountVectorizer,

其原理与调用代码整理如下:

TF-IDF

算法介绍:

词频-逆向文件频率(TF-IDF)是一种在文本挖掘中广泛使用的特征向量化方法,它可以体现一个文档中词语在语料库中的重要程度。

词语由t表示,文档由d表示,语料库由D表示。词频TF(t,,d)是词语t在文档d中出现的次数。文件频率DF(t,D)是包含词语的文档的个数。如果我们只使用词频来衡量重要性,很容易过度强调在文档中经常出现而并没有包含太多与文档有关的信息的词语,比如“a”,“the”以及“of”。如果一个词语经常出现在语料库中,它意味着它并没有携带特定的文档的特殊信息。逆向文档频率数值化衡量词语提供多少信息:

\[IDF(t,D) = \log \frac{{\left| D \right| + 1}}{{DF(t,D) + 1}}\]

其中,|D|是语料库中的文档总数。由于采用了对数,如果一个词出现在所有的文件,其IDF值变为0。

\[TFIDF(t,d,D) = TF(t,d) \bullet IDF(t,D)\]

调用:

在下面的代码段中,我们以一组句子开始。首先使用分解器Tokenizer把句子划分为单个词语。对每一个句子(词袋),我们使用HashingTF将句子转换为特征向量,最后使用IDF重新调整特征向量。这种转换通常可以提高使用文本特征的性能。然后,我们的特征向量可以在算法学习中。

Scala:

import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}

val sentenceData = spark.createDataFrame(Seq(
  (0, "Hi I heard about Spark"),
  (0, "I wish Java could use case classes"),
  (1, "Logistic regression models are neat")
)).toDF("label", "sentence")

val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val wordsData = tokenizer.transform(sentenceData)
val hashingTF = new HashingTF()
  .setInputCol("words").setOutputCol("rawFeatures").setNumFeatures(20)
val featurizedData = hashingTF.transform(wordsData)
// alternatively, CountVectorizer can also be used to get term frequency vectors

val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
val idfModel = idf.fit(featurizedData)
val rescaledData = idfModel.transform(featurizedData)
rescaledData.select("features", "label").take(3).foreach(println)

Word2Vec

算法介绍:

Word2vec是一个Estimator,它采用一系列代表文档的词语来训练word2vecmodel。该模型将每个词语映射到一个固定大小的向量。word2vecmodel使用文档中每个词语的平均数来将文档转换为向量,然后这个向量可以作为预测的特征,来计算文档相似度计算等等。

在下面的代码段中,我们首先用一组文档,其中每一个文档代表一个词语序列。对于每一个文档,我们将其转换为一个特征向量。此特征向量可以被传递到一个学习算法。

调用:

Scala:

import org.apache.spark.ml.feature.Word2Vec

// Input data: Each row is a bag of words from a sentence or document.
val documentDF = spark.createDataFrame(Seq(
  "Hi I heard about Spark".split(" "),"I wish Java could use case classes".split(" "),"Logistic regression models are neat".split(" ")
).map(Tuple1.apply)).toDF("text")

// Learn a mapping from words to Vectors.
val word2Vec = new Word2Vec()
  .setInputCol("text")
  .setOutputCol("result")
  .setVectorSize(3)
  .setMinCount(0)
val model = word2Vec.fit(documentDF)
val result = model.transform(documentDF)
result.select("result").take(3).foreach(println)

Countvectorizer

算法介绍:

Countvectorizer和Countvectorizermodel旨在通过计数来将一个文档转换为向量。当不存在先验字典时,Countvectorizer可作为Estimator来提取词汇,并生成一个Countvectorizermodel。该模型产生文档关于词语的稀疏表示,其表示可以传递给其他算法如LDA。

在fitting过程中,countvectorizer将根据语料库中的词频排序选出前vocabsize个词。一个可选的参数minDF也影响fitting过程中,它指定词汇表中的词语在文档中最少出现的次数。另一个可选的二值参数控制输出向量,如果设置为真那么所有非零的计数为1。这对于二值型离散概率模型非常有用。

示例:

假设我们有如下的DataFrame包含id和texts两列:

id | texts

----|----------

0 |Array("a", "b", "c")

1 |Array("a", "b", "b", "c","a")

文本中的每一行都是一个文档类型的数组(字符串)。调用的CountVectorizer产生词汇(a,b,c)的CountVectorizerModel,转换后的输出向量如下:

id | texts | vector

----|---------------------------------|---------------

0 |Array("a", "b", "c") | (3,[0,1,2],[1.0,1.0,1.0])

1 |Array("a", "b", "b", "c","a") |(3,[0,1,2],[2.0,2.0,1.0])

每个向量代表文档的词汇表中每个词语出现的次数。

调用:

Scala:

import org.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel}

val df = spark.createDataFrame(Seq(
  (0, Array("a", "b", "c")),
  (1, Array("a", "b", "b", "c", "a"))
)).toDF("id", "words")

// fit a CountVectorizerModel from the corpus
val cvModel: CountVectorizerModel = new CountVectorizer()
  .setInputCol("words")
  .setOutputCol("features")
  .setVocabSize(3)
  .setMinDF(2)
  .fit(df)

// alternatively, define CountVectorizerModel with a-priori vocabulary
val cvm = new CountVectorizerModel(Array("a", "b", "c"))
  .setInputCol("words")
  .setOutputCol("features")

cvModel.transform(df).select("features").show()

手机淘宝推荐中的排序学习-博客-云栖社区-阿里云

$
0
0


周梁:淘宝推荐机器学习技术专家,中国科学院自动化研究所机器学习博士,主要研究工作方向是机器学习、大规模并行算法优化。先后从事过广告CTR预估,MPI机器学习平台搭建,手淘个性化推荐等多方面工作。

排序学习是推荐、搜索、广告的核心问题。在手机淘宝的推荐场景中,受制于展示空间的限制,排序学习显得尤为重要。在淘宝,如何从十亿的商品中,挑选出用户 今天喜欢的商品,也是个巨大的挑战。 本次我们分享排序学习在手机淘宝中的应用,其中包括:解决了哪些问题,遇到了哪些挑战,以及做了哪些改进。

 

手淘推荐介绍

 

图1手淘推荐业务全覆盖

用户提升体验,千人千面;商家提供流量,提升转换;平台引导行为,流量分配。

图2手淘推荐系统

Match:基于内容,行为的推荐。场景,社交,人群,个人的长期兴趣,短期行为。

图3排序学习的原因

排序学习分类:PointWise:

PairWise:

ListWise:直接优化整个集合序列,不再做Transform,优化目标NDCG.

业务实例

 图4店铺内推荐业务

业务:只可以推荐同店铺商品,可以是相似搭配。目标:CTR.方法:PointWise。

 图5模型

样本构造:

模型目标:预测<user,item> ctr,并按照ctr排序。

手机埋点的困难:曝光,点击收集,Native 版本,H5 版本。

正负样本处理:

1.      点击/曝光PV

2.      (点击 + 折算成交)/曝光PV

3.      (点击 + 折算的成交)/(有效点击以上PV截断)

4.      (点击 + 折算的成交)/(泊松采样的虚拟PV)

特征设计

ID类特征,User、Item 、Context基本特征,移动特定场景相关特征:设备ID  VS 用户ID;城市区域特征;手机型号特征,PC & Mobile 特征融合。

每个特征权重反映该特征在数据中的统计意义,方便进行特征组合和模型debug,比较方便引入在线学习。

特征工程

 

  图6年龄匹配

图7年龄匹配

特征组合,交叉特征,例如年龄匹配。

个性化模型,特征交叉

–       User:U1={张三,男,年龄35},U2={李四,男,年龄29}

–       Item:I1={鼠标},I2={枕头}

–       训练集:U1点了I1,没点I2

–       预测:U2对I1、I2的喜好

–       特征归并,{张三,男,年龄35,鼠标},无泛化能力

–       特征交叉:{张三,男,年龄35,鼠标,男_鼠标}

–       对常见问题的解决方法

–       性别匹配:user性别与item性别交叉

–       年龄匹配:user年龄与item年龄交叉

–       购买力匹配:user购买力与item购买力交叉

–       用户类目偏好:user id与item类目id交叉

–       Position bias:训练时引入pos id为特征

–       多Matchtype融合:引入Matchtype id为特征

–       人群属性偏好:人群特征同item id做交叉

实时用户特征

 用户的Session 特征 怎么办?:用户当前时刻看了多少本类目商品;用户是否已经在别的场景下看过了本商品;用户是否已经购买本类目同款商品。

在线学习:离线特征提取,在线模型学习(FTRL)


图8在线学习

行业市场业务

图9

业务:个性化行业模块排序,个性化图文排序,最大化点击。

目标:行业流量的均衡。

方法:优化auc,Pairwise-ranknet。

PairWise思考:只考虑了两篇文档的相对顺序,对于不同的查询相关文档集的数量差异很大,投入产出比看,pairwise最佳。

业务场景Position因素:前两个图的面积明显占优,统计数据显示CTR明显占优。

流量均衡考虑。

图10 BPR模型

BPR:Bayesian Personalized Ranking。

构造pair样本是关键:

1.      Click > Skip Above

2.      Last Click > Skip Above

3.      Click > Earlier Click

4.      Click > No-Click Next

 图11女装瀑布流

业务:瀑布流个性化,多目标优化。目标:CTR,CVR,客单价。方法:优化NDCG,listwise-lambdamart。

        

        图12多目标融合

优化NDCG

DCG (Discounted Cumulative Gain)

NDCG(Normalized Version)

 图13

左图pairwise错误相比右图小(13 VS 11);希望出现红色的梯度方向和强度;直接优化NDCG。

 

LambdaRank 不再从Cost Function出发推导梯度,反而直接计算梯度来优化NDCG等一类的IR指标。

Mart(Multiple Additive Regression Tree)  与 Lambda 结合 , 得到 LambdaMart。

 

特征表示:

  1.   连续特征表示,便于Mart训练以及特征选择、组合
  2. User,Item,Context的各个维度反馈特征
  3. User Session 维度特征
  4. 各种子目标模型的Score
  5. LBS特征反馈

 


图14样本构造

  多目标构成ListWise,输入Lambdamart,按照等权重构造梯度权重。

等权重构造梯度权重的问题:样本有偏;训练较慢。

改进策略:按人工加权方式修正梯度强度;针对多种不同等级pair构造中,每个List只挑选最大违反的同类型pair做当前轮训练。

 

  图15计划&展望

日志:手机日志收集,终端较多,多App间协作。

特征:家庭用户特征同账户问题,地域特征,PC、Mobile 特征对齐。

目标:业务目标多,LTR有较大的应用空间。

实时:在线模型更新,用户行为特征挖掘。

 

 

 

 

 

金融数据分析与挖掘具体实现方法 -1 - 汪凡 - 博客园

$
0
0

有人让我写一下关于数据挖掘在金融方面的应用,再加上现在金融对数据方面的要求不断提高,准备用两篇随笔来做个入门总结。

首先,在看这篇随笔以前稍微补充一点金融方面的知识,因为我不是金融专业的,以下补充知识来自互联网与个人整理,欢迎批评指正并补充说明。

1 先来了解一下什么是金融市场呢?

通常狭义的金融市场特指有价证券(股票、债券)发行和流通的场所。

股票、债券是用来资本流通的金融产品,广义上的金融市场还包含货币市场,其中代表性的是期货市场等。

其实不管是资本市场,还是货币市场,都是可以提供给投资者投资的地方,其中货币市场以期货为代表的适合短期投资,而以股票代表的证券市场适合长期投资!

常见机构:银行、投资银行(证券公司)、保险公司、基金等

有价证券:是虚拟资本的一种形式,它本身没价值,但有价格。

2 狭义的金融市场划分

2.1 按交易程序划分

  • 发行市场:又称为处理新发行证券的金融市场,筹集资金的公司、政府或公共部门通过发行新的股票和债券来进行融资。

  • 流通市场:又称二级市场,是指已经发行的证券进行转让、交易的市场。

我们通过一张图来理解一下:

关于投资的几个类别,一般我们将天使、VC、PE三个部分统称为私募(Private Equity),指的是没有在证券交易所公开上市交易的资产。

1、公司规模

天使投资主要投资早期创业公司;

VC投资中期高速发展型创业公司;

PE介入即将上市或被兼并收购的成熟企业。

2、资金规模

天使投资:500万以下 VC投资:千万 PE:千万级别以上

2.2 参与主体

  • 证券交易所
    • 买卖股票、公司债等有价证券的市场

证券公司:

  • 可以承销发行、自营买卖或自营兼代理买卖证券

2.3 关于金融产品的区别

 

注:政府、公司等发行股票、债券目的为了进行融资、发展等

3 什么是股票?

股票,是股份公司签发的证明股东所持股份的凭证,代表了股东对股份公司净资产的所有权。

  • 特点:每股股票都代表股东对企业拥有单位的所有权,所拥有的份额取决于持有的股票数量总占比。本身没有价格,代表一种价值

3.1 股票按照股东权利的分类

  • 按股东权利分类,股票可分为普通股、优先股等

普通股

普通股是指在公司的经营管理和盈利及财产的分配上享有普通权利的股份,代表满足所有债权偿付要求及优先股东的收益权与求偿权要求后对企业盈利和剩余财产的索取权。普通股构成公司资本的基础,是股票的一种基本形式。现上海和深圳证券交易所上进行交易的股票都是普通股。

普通股股东

(1)公司决策参与权。普通股股东有权参与股东大会,并有建议权、表决权和选举权,也可以委托他人代表其行使其股东权利。

(2)利润分配权。普通股股东有权从公司利润分配中得到股息。普通股的股息是不固定的,由公司赢利状况及其分配政策决定。普通股股东必须在优先股股东取得固定股息之后才有权享受股息分配权。

(3)优先认股权。如果公司需要扩张而增发普通股股票时,现有普通股股东有权按其持股比例,以低于市价的某一特定价格优先购买一定数量的新发行股票,从而保持其对企业所有权的原有比例。

(4)剩余资产分配权。当公司破产或清算时,若公司的资产在偿还欠债后还有剩余,其剩余部分按先优先股股东、后普通股股东的顺序进行分配。

优先股

优先股相对于普通股。优先股在利润分红及剩余财产分配的权利方面优先于普通股。

(1)优先分配权。在公司分配利润时,拥有优先股票的股东比持有普通股票的股东,分配在先,但是享受固定金额的股利,即优先股的股利是相对固定的。

(2)优先求偿权。若公司清算,分配剩余财产时,优先股在普通股之前分配。注:当公司决定连续几年不分配股利时,优先股股东可以进入股东大会来表达他们的意见,保护他们自己的权利。

3.2 股票按照上市地区的分类

  • 根据上市地区可以分为,我国上市公司的股票有A股(在上海和深圳上市)、B股(上海和深圳上市,其中上海B股以美元结算,深圳B股以港元结算),H股(香港交易所上市,在大陆运作的公司)。并且还有一些N股和S股等的划分。这一区分主要依据股票的上市地点和所面对的投资者而定

3.3 股票按照股票业绩的分类

  • 根据业绩也分为:ST股、垃圾股、蓝筹股 等
    • 蓝筹股:股票市场上,那些在其所属行业内占有重要支配性地位、业绩优良,成交活跃、红利优厚的大公司。

在进行股票投资的时候,我们会使用价值投资方式。选择公司前景好、业绩好的一些公司

知道了股票的基本分类,接下来我们去看看股票具体的一些机制。

3.4 人民币普通股票

A股

  • A股即人民币普通股票。它是由我国境内的公司发行,供境内机构、组织或个人(不含港、澳、台投资者)以人民币认购和交易的普通股股票。
    • 特点: A股不是实物股票,以无纸化电子记帐,实行T+1交易制度

T+1制度

自1995年1月1日起,为了保证股票市场的稳定,防止过度投机,股市实行“T+1”交易制度,当日买进的股票,要到下一个交易日才能卖出。同时,对资金仍然实行“T+0”,即当日回笼的资金马上可以使用。

T+1是一种股票交易制度,即当日买进的股票,要到下一个交易日才能卖出。

“T+1"中"T"指的是交易登记日,"T+1"指的是交易登记日的第二天。

3.5 股票的代码、开户、价格形成

我们可以看到这样的符号:

3.5.1 股票代码

股票代码用数字表示股票的不同含义。股票代码除了区分各种股票,也有其潜在的意义,比如600*是上交所上市的股票代码,6006是最早上市的股票,一个公司的股票代码跟车牌号差不多,能够显示出这个公司的实力以及知名度

1、沪市A股票买卖的代码是以600、601或603打头(在上海证券交易所上市的全是主板)

2、深市A股票买卖的代码是以000打头,其中中小板代码以002打头,创业板股票代码以300打头

问题:那么经常说的股票价格是什么?怎么形成的?

3.5.2 股票价格

股票价格(Stock Price)又叫股票行市,是指股票在证券市场上买卖的价格。股票在流通市场上的价格,才是完全意义上的股票的市场价格

股票初始发行价格=市盈率还原值×40%+股息还原率×20%+每股净值×20%+预计当年股息与一年期存款利率还原值×20%,影响股票价格的因素有很多,如企业因素、盈利情况、净资产、市场、行业等

3.5.3 股票的交易时间和过程

  • 股票交易时间
    • 休息日:周六、周日和上证所公告的休市日不交易。(一般为五一、十一国庆节、春节、元旦、清明节、端午节、中秋节等国家法定节假日)
  • 股票交易过程

(一)、集合竞价阶段:9:15 — 9:25

1、9:15 — 9:19可以申报和撤单;9:20 — 9:25 可以申报,不可以撤单。

2、深圳交易所14:57 — 15:00实行集合竞价,可以申报,不可以撤单。

(二)、连续竞价阶段

1、上海交易所:9:30 — 11:30;13:00 — 15:00

2、深圳交易所:9:30 — 11:30;13:00 — 14:56:59

(三)成交原则

价格优先,时间优先

3.5.4 交易费用

1、印花税:1‰(卖的时候才收取,此为国家税收,全国统一)。
2、过户费:深圳交易所无此项费用,上海交易所收费标准(按成交金额的0.02‰人民币 [2]  )。
3、交易佣金:最高收费为3‰,最低收费5元。各家劵商收费不一,开户前可咨询清楚。


例子:
假设你买入10000股,每股票价格为10元,以此为例,对其买入成本计算:
买入股票所用金额:10元/股×10000股=100000元;
过户费:0.02‰×100000=2元(沪市股票计算,深市为0);
交易佣金:100000×3‰=300元(按最高标准计算,正常情况下都小于这个值);
买入总成本:100000元+300元+2元=100302元(买入10000股,每股10元,所需总资金)
多少每股卖出才不赔钱?
可按如下公式计算:(买入总成本+卖出过户费)÷(1-印花税率-交易佣金率)÷股票数量=(100310元+10元)÷(1-0.001-0.003)÷10000=10.07228916元
=10.07元(四舍五入)。
若以10.08每股卖出价格计算:
股票金额:10.08元/股×10000股=100800元;
印花税:100800元×1‰=100.8元;
过户费:0.002%×100800元≈2元;
交易佣金:100800元×3‰=302.4元;
卖出后收入:100800元-100.8元-2元-302.4元=100394.8元;
最终实际盈利为:卖出后收入-买入总成本=100394.8-100302=92.8元;

3.6 股票的层次划分

即使在国内对于A股来说,目前总共有3000多只股票。并且数字可能随着时间会不断改变,如何更好的管理这些上市公司?实现怎样的制度去区分公司的规模大小?

3.6.1 中国股票市场的层次划分

  • 主板:市场占有率高、规模较大、基础较好、高收益、低风险的大型优秀企业。
  • 中小板:主要服务于即将或已进入成熟期、盈利能力强、但规模较主板小的中小企业。
  • 创业板:是以自主创新企业及其他成长型创业企业为服务对象,主要为“两高”、“六新”企业,即高科技、高成长性、新经济、新服务、新农业、新能源、新材料、新商业模式企业。
  • 新三板:主要为创新型、创业型、成长型中小微企业发展服务。

在选择购买股票的时候,有时候会根据划分依据去选择特定指数、行业、板块下的股票!!!

3.6.2 股票的不同性质划分

概念股概念股是与业绩股相对而言的。业绩股需要有良好的业绩支撑。概念股则是依靠某一种题材比如资产重组概念,三通概念等支撑价格。          

行业:

指数:

4 股票数据

4.1 交易数据

股票在流通市场上的价格,才是完全意义上的股票的市场价格,一般称为股票市价或股票行市。股票市价表现为开盘价、收盘价、最高价、最低价等形式。其中收盘价最重要,是分析股市行情时采用的基本数据。 

4.2 股票K线图

K线图这种图表源处于日本德川幕府时代,被当时日本米市的商人用来记录米市的行情与价格波动,后因其细腻独到的标画方式而被引入到股市及期货市场。

4.2.1 K线图基本形态

4.2.2 K线图的计算周期

不同的时间间隔看待股票数据变化,会有不一样的发现!

K线的计算周期可将其分为日K线,周K线,月K线,年K线

很多网站提供了日线、周K线、月K线等周期数据,但是最原始的只有日K线的数据。我们需要自己去生成计算不同频率的数据

4.3 案例:股票K线数据重采样

股票方面的基础知识差不多了,接下来我们做个将日k线图转换成周k线图的案例吧!

  • DataFrame.resample(rule, how=None, axis=0, fill_method=None, closed=None,kind=None,)
    • 频率转换和时间序列重采样,对象必须具有类似日期时间的索引(DatetimeIndex,PeriodIndex或TimedeltaIndex)

  • 日K周K对比:

那么日线、周线、月线等怎么切换标准??

周K线是指以周一的开盘价,周五的收盘价,全周最高价和全周最低价来画的K线图

大部分周线的指标是这个日线指标在这一周最后一个交易日的值。比如周线的’close’应该等于这一周最后一天日线数据的‘close’,但是有的指标是例外,比如周线的’high’应该等于这一周所有日线‘high’中的最大值

接下来我们还是使用之前stock_day当中的某个股票的行情数据

  • 将索引转换成DatetimeIndex类型
  • 对不同指标进行重采样
stock_day = pd.read_csv("./data/stock_day/stock_day.csv")
stock_day = stock_day.sort_index()
# 对每日交易数据进行重采样 (频率转换)
stock_day.index


# 1、必须将时间索引类型编程Pandas默认的类型
stock_day.index = pd.to_datetime(stock_day.index)

# 2、进行频率转换日K---周K,首先让所有指标都为最后一天的价格
period_week_data = stock_day.resample('W').last()

# 分别对于开盘、收盘、最高价、最低价进行处理
period_week_data['open'] = stock_day['open'].resample('W').first()
# 处理最高价和最低价
period_week_data['high'] = stock_day['high'].resample('W').max()
# 最低价
period_week_data['low'] = stock_day['low'].resample('W').min()
# 成交量 这一周的每天成交量的和
period_week_data['volume'] = stock_day['volume'].resample('W').sum()
  • 对于其中存在的缺失值

period_week_data.dropna(axis=0)

  

我们可以将计算出来的周K和原先的日K画图显示出来

  • 画出K线图显示

金融数据绘制需要使用mpl_finance框架, 通过pip 安装即可

from mpl_finance import candlestick_ochl
import matplotlib.pyplot as plt

# 先画日K线
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(20, 8), dpi=80)
# 准备数据, array数组
stock_day['index'] = [i for i in range(stock_day.shape[0])]
day_k = stock_day[['index', 'open', 'close', 'high', 'low']]
candlestick_ochl(axes, day_k.values, width=0.2, colorup='r', colordown='g')
plt.show()


# 周K线图数据显示出来
period_week_data['index'] = [i for i in range(period_week_data.shape[0])]
week_k = period_week_data[['index', 'open', 'close', 'high', 'low']]
candlestick_ochl(axes, week_k.values, width=0.2, colorup='r', colordown='g')

plt.show()

4.4 什么是除权数据以及复权操作?

 上市公司会时不时的发生现金分红、送股等一系列股本变动,这会造成股价的非正常变化,导致我们不能直接通过股价来计算股票的涨跌幅。这种数据我们也称之为除权数据。

所以我们要对这种数据做处理,也称之为复权数据。怎么进行复权呢?

简单的一种方式:
原始数据:
1号:100  2号:50 3号:53 4号:51
复权后:
100 / 50 = 2 比例
1号:100  2号:100 3号:106 4号:102

4.5 基本面数据

4.5.1 基本面数据的用处

主要用于基本面分析,主要侧重于从股票的基本面因素,如企业经营能力,财务状况,行业背景等对公司进行研究与分析,试图从公司角度找出股票的“内在价值”,从而与股票市场价值进行比较,挑选出最具投资价值的股票。

量化主要就属于这样的一个分析方式

5 股票时间序列数据处理

5.1 什么是时间序列?

时间序列是一组按照时间发生先后顺序进行排列的数据点序列。通常一组时间序列的时间间隔为一恒定值(如1秒,5分钟,12小时,7天,1年),因此时间序列可以作为离散时间数据进行分析处理。

例如:某监控系统的折线图表,显示了请求次数和响应时间随时间的变化趋势

 

 

5.2 Pandas的时间类型

  • pd.to_datetime():转换成pandas的时间类型 Timestamp('2018-03-02 00:00:00')
# pd将时间数据转换成pandas时间类型
# 1、填入时间的字符串,格式有几种, "2018-01-01" ,”01/02/2018“
pd.to_datetime("01/02/2017")

5.3 Pandas的时间序列类型

  • 1、转换时间序列类型
# 传入时间的列表
pd.to_datetime(["2017-01-01", "2017-02-01", "2017-03-01"])

# 或者
date = [datetime(2018, 3, 1), datetime(2018, 3, 2), datetime(2018, 3, 3), datetime(2018, 3, 4), datetime(2018, 3, 5)]
date = pd.to_datetime(date)

# 如果其中有空值
date = [datetime(2018, 3, 1), datetime(2018, 3, 2), np.nan, datetime(2018, 3, 4), datetime(2018, 3, 5)]
date = pd.to_datetime(date)
# 结果会变成NaT类型
DatetimeIndex(['2018-03-01', '2018-03-02', 'NaT', '2018-03-04', '2018-03-05'], dtype='datetime64[ns]', freq=None)
  • 2、Pandas的时间序列类型:DatetimeIndex
# DateTimeIndex
pd.to_datetime(date)
DatetimeIndex(['2018-03-01', '2018-03-02', '2018-03-03', '2018-03-04','2018-03-05'],
              dtype='datetime64[ns]', freq=None)

pd.to_datetime(date).values

array(['2018-03-01T00:00:00.000000000', '2018-03-02T00:00:00.000000000','2018-03-03T00:00:00.000000000', '2018-03-04T00:00:00.000000000','2018-03-05T00:00:00.000000000'], dtype='datetime64[ns]')

  

我们也可以通过DatetimeIndex来转换

  • 3、通过pd.DatetimeIndex进行转换
pd.DatetimeIndex(date)

知道了时间序列类型,所以我们可以用这个当做索引,获取数据

5.4 Pandas的基础时间序列结构

# 最基础的pandas的时间序列结构,以时间为索引的,Series序列结构
# 以时间为索引的DataFrame结构
series_date = pd.Series(3.0, index=date)

pd.to_datetime(series_date)
pd.DatetimeIndex(series_date)

pandas时间序列series的index必须是DatetimeIndex

  • DatetimeIndex的属性
    • year,month,weekday,day,hour….
time.year
time.month
time.weekday

5.5 Pandas生成指定频率的时间序列

  • pandas.date_range(start=None, end=None, periods=None, freq='D', tz=None, normalize=False, name=None, closed=None, **kwargs)
    • Returna fixed frequency DatetimeIndex, with day (calendar) as the default frequency
    • start:开始时间
    • end:结束时间
    • periods:产生多长的序列
    • freq:频率 D,H,Q等
    • tz:时区

# 生成指定的时间序列
# 1、生成2017-01-02~2017-12-30,生成频率为1天, 不跳过周六周日
pd.date_range("2017-01-02", "2017-12-30", freq="D")

# 2、生成2017-01-02~2017-12-30,生成频率为1天, 跳过周六周日, 能够用在金融的数据,日线的数据
pd.date_range("2017-01-02", "2017-12-30", freq="B")

# 3、只知道开始时间日期,我也知道总共天数多少,生成序列, 从"2016-01-01", 共504天,跳过周末
pd.date_range("2016-01-01", periods=504, freq="B")

# 4、生成按照小时排列的时间序列数据
pd.date_range("2017-01-02", "2017-12-30", freq='H')

# 5、按照3H去进行生成
pd.date_range("2017-01-02", "2017-12-30", freq='3H')

# 6、按照1H30分钟去进行生成时间序列
pd.date_range("2017-01-02", "2017-12-30", freq='1H30min')

# 7、按照每月最后一天
pd.date_range("2017-01-02", "2017-12-30", freq='BM')

# 8、按照每个月的第几个星期几
pd.date_range("2017-01-02", "2017-12-30", freq='WOM-3FRI')

5.6什么是时间序列分析

对于时间序列类型,有特有的分析方法。同样股票本身也是一种时间序列类型,我们就以股票的数据来进行时间序列的分析

时间序列分析( time series analysis)方法,强调的是通过对一个区域进行一定时间段内的连续观察计算,提取相关特征,并分析其变化过程。

时间序列分析主要有确定性变化分析

  • 确定性变化分析:移动平均法, 移动方差和标准差、移动相关系数

5.7 移动平均法

5.7.1 移动窗口

主要用在时间序列的数组变换, 不同作用的函数将它们统称为移动窗口函数

5.7.2 移动平均线

那么会有各种观察窗口的方法,其中最常用的就是移动平均法

  • 移动平均线(Moving Average)简称均线, 将某一段时间的收盘价之和除以该周期

5.7.3 移动平均线的分类

  • 移动平均线依计算周期分为短期(5天)、中期(20天)和长期(60天、120天),移动平均线没有固定的界限
  • 移动平均线依据算法分为算数、加权法和指数移动平均线

不同的移动平均线方法不一样

1 简单移动平均线

简单移动平均线(SMA),又称“算数移动平均线”,是指特定期间的收盘价进行平均化比如说,5日的均线SMA=(C1+ C2 + C3 + C4 + C5) / 5

 

例子:

  • 案例:对股票数据进行移动平均计算

拿到股票数据,画出K线图

# 拿到股票K线数据
stock_day = pd.read_csv("./data/stock_day/stock_day.csv")
stock_day = stock_day.sort_index()
stock_day["index"] = [i for i in range(stock_day.shape[0])]
arr = stock_day[['index', 'open', 'close', 'high', 'low']]
values = arr.values[:200]
# 画出K线图
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(20, 8), dpi=80)
candlestick_ochl(axes, values, width=0.2, colorup='r', colordown='g')

2 计算移动平均线

  • pandas.rolling_mean(arg, window, min_periods=None, freq=None, center=False, how=None, **kwargs) Moving mean.

    Parameters:

    • arg : Series, DataFrame
    • window : 计算周期

 

# 直接对每天的收盘价进行求平均值, 简单移动平局线(SMA)
# 分别加上短期、中期、长期局均线
pd.rolling_mean(stock_day["close"][:200], window=5).plot()
pd.rolling_mean(stock_day["close"][:200], window=10).plot()
pd.rolling_mean(stock_day["close"][:200], window=20).plot()
pd.rolling_mean(stock_day["close"][:200], window=30).plot()
pd.rolling_mean(stock_day["close"][:200], window=60).plot()
pd.rolling_mean(stock_day["close"][:200], window=120).plot()

3 加权移动平均线 (WMA)

加权移动平均线 (WMA)将过去某特定时间内的价格取其平均值,它的比重以平均线的长度设定,愈近期的收市价,对市况影响愈重要。

正因加权移动平均线强调将愈近期的价格比重提升,故此当市况倒退时,加权移动平均线比起其它平均线更容易预测价格波动。但是我们还是不会轻易使用加权,应为他的比重过大!!!!

4 指数平滑移动平均线(EWMA)

是因应移动平均线被视为落后指标的缺失而发展出来的,为解决一旦价格已脱离均线差值扩大,而平均线未能立即反应,EWMA可以减少类似缺点。

  

pd.ewma(com=None, span=one)

  • 指数平均线
  • span:时间间隔
# 画出指数平滑移动平均线
pd.ewma(stock_day['close'][:200], span=10).plot()
pd.ewma(stock_day['close'][:200], span=30).plot()
pd.ewma(stock_day['close'][:200], span=60).plot()

 

5.8 移动方差和标准差

  • 方差和标准差:反应某一时期的序列的稳定性

# 求出指定窗口大小的收盘价标准差和方差
pd.rolling_var(stock_day['close'][:200], window=10).plot()
pd.rolling_std(stock_day['close'][:200], window=10).plot()

5.9 各项指标数据两两关联散点图

  • pd.scatter_matrix(frame, figsize=None)
    • frame:DataFrame
frame = data[['open','volume', 'ma20', 'p_change', 'turnover']]
pd.scatter_matrix(frame, figsize=(20, 8))

 

从中我们可以简单看到成交量(volume)和换手率(turnover)有非常明显的线性关系,因为换手率的定义就是:成交量除以发行总股数。

通过一些图或者相关性分析可以找到强相关的一些指标,在机器学习、量化方向中会详细介绍

相关系数:后面会介绍,目前我们只需知道他是反应两个序列之间的关系即可

5.10 案例:移动平均线数据本地保存

ma_list = [5, 20 ,60]
for ma in ma_list:
    data['MA' + str(ma)] = pd.rolling_mean(data.close, ma)
for ma in ma_list:
    data['EMA' + str(ma)] = pd.ewma(data.close, span=ma)

data.to_csv("EWMA.csv")

5.11 移动平均线的作用

移动平均线经常会作为技术分析的基础理论,从中衍生出各种技术指标策略。后面将会介绍简单的基于均线的策略。

Viewing all 532 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>