
大数据生态圈,计算机视觉,机器学习,高端技术的爱好者,话不多说,上代码!!!
暂时未有相关通用技术能力~
阿里云技能认证
详细说明@toc本专栏持续更新中,内容还未完整的请稍安勿躁,部分内容有参考其他书籍或是网络文献,都会给出原始出处简介坝陵河大桥全长2237米,主跨长1088米,东岸引桥长940.4米,西岸引桥长200米。钢桁梁加劲桁梁宽28.0米,高10.0米,节间长度10.8米,吊索间距10.8米;桥面行车道板厚16毫米,紧急停车带板厚14毫米,检修道桥面板后8毫米。索塔上横梁高10米,宽度7.5米,壁厚0.9米;下横梁高度10米,宽度9米,壁厚1.0米;东塔塔身高185.788米,西塔塔身高201.316米。主缆矢跨比为1:10.3,横向间距28.0米,每根索股含91根钢丝,主跨主缆索股数量为208股/缆,边跨主缆的索股数量为216股/缆;全桥共设198处吊点,每处吊点共2根吊索。东锚碇锚固段长度25米,长71米,宽45.5米,高71米;西锚碇隧洞总轴长74.34米,其中前锚室长31.34米,锚塞体长40米,后锚室长3米;洞口单洞断面尺寸为(10x10.8)米,洞底单洞断面尺寸为(21x25)米,左右洞最小间距7米,隧道锚轴线倾斜度45度,底面于水平面倾角达52度。大数据AI系统解决方案与技术架构针对日益场景的机器学习系统从设计模式、解决方案,逻辑架构出发给出最终落地实现的技术架构与深入思考。技术架构----Technical architecture,architecture 一词来源于建筑,在我们计算机领域也被称为架构。一个优秀的工程师,职业生涯发展必将面临管理或者技术道路的两方向选择。技术道路的顶尖职位就是技术架构师或者带头人的角色。设计模式逻辑架构技术架构系统调试技巧idea scala 与 pycharm python 如何 动态调试云平台实践案例部署与全流程自动化 CI / CDDocker+Harbor+Jenkins+Gitlab自动化CICD构建生成系统中的机器学习技术规范RESTFul API 设计 简明指导与规范数据库设计简明指导与规范编码规范 ---- Python:PEP8编码规范 ---- Java:GOOGLE 规范参考软件项目简明代码评审流程参考文献
@toc本专栏持续更新中,内容还未完整的请稍安勿躁,部分内容有参考其他书籍或是网络文献,都会给出原始出处21 世纪必将是大数据的时代,是智能信息处理的黄金时代。BAT 公司在 2013年左右的数据量如下:2013 年百度相关技术报告称,百度数据总量接近 1000PB,网页的数量大是几千亿个,每年更新几十亿个,每天查询次数几十亿次。2013年腾讯相关技术报告称,腾讯约有8 亿用户,4亿移动用户,总存储数据量经压缩处理以后在 100PB左右,日新增 200TB 到 300TB,月增加 10%的数据量。2013年阿里巴巴相关技术报告称,总体数据量为 100PB,每天的活跃数据量已经超过 50TB,共有4亿条产品信息和2 亿多名注册用户,每天访问超过 4000 万人次。为了采集、存储和分析大数据,互联网公司尝试研发大数据技术,在众多技术方案中,开源系统 Hadoop、 Spark、Elasticsearch等 成为应用最广泛的大数据技术,由于它们的用户量巨大,已经初步成为大数据技术规范。本专栏《大数据处理实践探索》 通过记录基于Python 的大数据处理实践探索案例,力图将大数据与机器学习相结合 产生新的实践落地思路。 网络上的大数据相关博文多是基于java 或者Scala ,本专栏的目的在于 基于python 将大数据(pyspark、 Elasticsearch、sklearn ...),数据开发,与数据分析相结合。 并在实践内容上给出一定指导,最后本专栏特地针对笔试面试高频题方面给出了分享,希望能够在找工作时候帮助到大家。本专栏 于 2021年7月20日 正式更名为《大数据机器学习实践探索》,并将主要更新:基于大数据的机器学习最佳实践 中的主要内容,围绕大数据环境下的机器学习,基于spark 给大家介绍最新的大数据机器学习算法。github 地址: big_data_repo框架平台介绍篇大数据尝试从海量数据中,通过一定的分布式技术手段,挖掘出有价值的信息,最终提供给用户,进而产生实用价值和商业价值。由于数据本身的多样性以及数据分析需求的多元化,大数据技术体系非常复杂,涉及的组件和模块众多。为了便于读者从顶层框架上对大数据有一个清楚的认识,本部分尝试首先概括大数据技术框架。云平台通过我和北美工程师的合作,他们将AWS 已经当成了一种基础设置,如果你还不了解云计算,或者任意一家云平台的话,你out 了。 学习云计算或者云平台的途径最好就是通过他们的文档。国内云平台如同雨后春笋般蓬勃发展,但最值得借鉴的还是鼻祖AWS.python 与aws 交互aws ec2 配置ftp----使用vsftp安装与调试本小节主要针对开发环境搭建,集群环境搭建进行介绍基础环境搭建:在jupyter notebook 中使用 pyspark在idea 2021 上 配置本地 scala 2.12 spark 3.0.2 开发环境使用python fabric搭建RHEL 7.2大数据基础环境以及部分优化CDH集群安装&测试总结CDH 5.x 集群安装及卸载大数据搜索框架 ElasticsearchElasticsearch是一个实时的分布式搜索和分析引擎,使得人们可以在一定规模上和一定速度上实现数据检索,常用于全文本检索,结构化检索、分析以及三种的结合应用。Wikipedia、Guardian、Stack Overflow、Github都在使用Elasticsearch实现自己的相关检索工作。大数据处理实践探索 ---- 之 搜索神器elastic search《读书报告 -- Elasticsearch入门 》---- 安装以及简单使用(1)《读书报告 -- Elasticsearch入门 》---- 简单使用(2) 基于elasticsearch的搜索自动纠错大数据框架 sparkSpark最初诞生于美国加州大学伯克利分校(UC Berkeley)的AMP实验室,是一个可应用于大规模数据处理的快速、通用引擎。2013年,Spark加入Apache孵化器项目后,开始获得迅猛的发展,如今已成为Apache软件基金会最重要的三大分布式计算系统开源项目之一(即Hadoop、Spark、Storm)。Spark最初的设计目标是使数据分析更快——不仅运行速度快,也要能快速、容易地编写程序。为了使程序运行更快,Spark提供了内存计算,减少了迭代计算时的IO开销;而为了使编写程序更为容易,Spark使用简练、优雅的Scala语言编写,基于Scala提供了交互式的编程体验。虽然,Hadoop已成为大数据的事实标准,但其MapReduce分布式计算模型仍存在诸多缺陷,而Spark不仅具备Hadoop MapReduce所具有的优点,且解决了Hadoop MapReduce的缺陷。Spark正以其结构一体化、功能多元化的优势逐渐成为当今大数据领域最热门的大数据计算平台。spark 中的数据类型基础环境搭建:在jupyter notebook jupyterhub 中使用 pyspark 及scalaspark读取elasticsearch nested array基于 kubeflow k8s 中运行jupyter 提交PySpark 任务WSL 是 Windows Subsystem for Linux windows下的linux子系统,由于直接在windows 上安装pyspark 跑很多基于linux 的库的机器学习库很费劲,有什么办法是省时省力并且占用资源少的方式呢?相比虚拟机动辄8g 左右的内存占用量,wsl 当然是一个非常好的选择。使用 WSL 进行pyspark + xgboost 分类+特征重要性 简单实践数据处理篇数据接入数据接入就是对于不同的数据来源、不同的合作伙伴,完成数据采集、数据传输、数据处理、数据缓存到行业统一的数据平台的过程。统一数据接入实践分享数据清洗数据清洗, 是整个数据分析过程中不可缺少的一个环节,其结果质量直接关系到模型效果和最终结论。在实际操作中,数据清洗通常会占据分析过程的50%—80%的时间。数据清洗的目的从两个角度来讲:一、是为了解决数据质量问题二、是让数据更适合做挖掘、展示、分析数据清洗的目的,方法ETLETL,是英文Extract-Transform-Load的缩写,用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目的端的过程。浅谈pandas,pyspark 的大数据ETL实践经验大数据处理实践探索(3)---- pyspark 之大数据ETL利器EDA还有比pandas profiling 更好使的python EDA 工具吗?经典Titanic 数据集的探索性数据分析报告:https://pandas-profiling.github.io/pandas-profiling/examples/master/titanic/titanic_report.htmlpython Pandas Profiling 一行代码EDA 探索性数据分析特征工程特征工程的主要目的是放大数据的价值。有这么一句话在业界广泛流传:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已python分箱spark 特征工程 -- 分箱 Binning降维(非监督学习之PCA降维&流行学习TSNE)正则化、归一化、标准化大数据机器学习篇在大数据领域中,机器学习几乎无处不在,即便我们没有特意引用它们,它们也经常出现在大数据应用中,例如:搜索、推荐、预测和数据挖掘等。随着互联网的高速发展,数据量不断爆发式增长,数据维度越来越丰富,这也为机器学习的发展和应用提供了良好的土壤,机器学习的良好成果也反向让数据产生更大的价值,成为真正的“大数据”,两者相辅相成,相互促进,让数据越来越智能。基于大数据的机器学习最佳实践算法原理篇集群测试----intel-hadoop/HiBench流程分析----以贝叶斯算法为例SQL 优化sql 优化无处不在,核心可以概括为几点:有效使用索引根据查询计划持续优化构建高效的sql 语句PostgreSQL 内置分区功能大数据可视化一图胜千言,视觉传达的信息量带宽远大于文字。技术调研----BI工具对比及Surperset 之 docker安装与可视化kibana 小技巧实践案例篇基于大数据的数据处理大数据处理实践探索(1)---- python 与oracle数据库导入导出使用python对数据库,云平台,oracle,aws,es导入导出实战基于大数据的数据分析使用pyspark 进行kaggle比赛Give me some credit数据集的建模与分析1. 数据准备与EDA2.1 数据清洗2.2 特征工程笔试面试篇笔试面试题复习的核心要义是什么? 一言以蔽之,基础+原理。大数据基础知识问答笔试面试高频问题 ---- 基础知识笔试面试高频问题 ---- hadoop笔试面试高频问题 ---- Yarn 基础笔试面试高频问题 ---- spark 基本调优笔试面试高频问题 ---- spark 基础笔试面试高频问题 ---- hive 基础[笔试面试高频问题 ---- ElasticSearch]()其他docker环境下zookeeper集群部署(一)docker环境下zookeeper集群部署(二)参考文献Spark入门教程(Python版)
@toc本专栏内容持续更新中,部分内容在持续优化中!请稍安勿躁,部分内容有参考其他书籍或是网络文献,都会给出原始出处,如有侵权请联系删除简介在1956年的达特茅斯会议上,人工智能的概念第一次被真正提出来,其作为信息技术的一种,早在40多年前就已经实验性地参与到医疗行为中。1972年,利兹大学研发了医疗领域最早出现的人工智能系统,此系统主要用于腹部剧痛的辅助诊断及手术的相关需求。人工智能作为一种技术,对生产工具进行了升级。人们可以通过人工智能快速地对行业数据和过往知识进行汇总和梳理,从而辅助人们做决策。人工智能所起到的作用有两个方面:一是对过往知识的总结和判断,因为它的学习速度非常快,有极强的总结能力;二是基于过去的经验做决策,决策的正确性有明显提高。所以,人工智能在效率上和准确度上远远高于人的主观判断。人工智能的应用在各行各业中都有相似或者相近的优势,目前主要有两大辅助作用:一是高效率地辅助决策,二是对项目运营进行优化。例如,在业务辅助方面,人工智能在医疗领域可以辅助医生进行诊断,在金融领域可以辅助用户进行自动交易;在管理优化方面,人工智能在医疗领域可以辅助医院进行管理,在零售领域可以对库存和交易流程进行优化。健康是人类永恒的主题也是社会进步的重要标志,健康已成为新世纪人们生活的基本目标。大健康产业具有巨大的市场潜力,未来在我国经济结构向服务业转型过程中,大健康产业将成为我国国民经济支柱型产业人工智能与医疗 总览健康是人类永恒的主题也是社会进步的重要标志,健康已成为新世纪人们生活的基本目标。大健康产业具有巨大的市场潜力,未来在我国经济结构向服务业转型过程中,大健康产业将成为我国国民经济支柱型产业,本专栏将从保险、检验检测、医疗知识库等内容切入,深度还原大健康行业从无到有的场景落地实践经验总结。场景建模与系统架构大数据AI系统解决方案与技术架构针对日益场景的机器学习系统从设计模式、解决方案,逻辑架构出发给出最终落地实现的技术架构与深入思考。大数据AI系统解决方案与技术架构目录医学AI 的未来 -- 医学知识库医学知识图谱是实现智慧医疗的基石,有望带来更高效精准的医疗服务。然而,现有知识图谱构建技术在医学领域中普遍存在效率低,限制多,拓展性差等问题。针对医疗数据跨语种,专业性强,结构复杂等特点,对构建医学知识图谱的关键技术进行了自底向上的全面解析,涵盖了医学知识表示、抽取、融合和推理以及质量评估五部分内容。此外,还介绍了医学知识图谱在信息检索、知识问答、智能诊断等医疗服务中的应用现状。最后,结合当前医学知识图谱构建技术面临的重大挑战和关键问题,对其发展前景进行了展望。大数据+AI在大健康领域中最佳实践前瞻 -- 医疗知识库构建前瞻大数据+AI在大健康领域中最佳实践前瞻 -- 医疗知识库的升级版:医疗知识图谱前瞻大数据+AI在大健康领域中最佳实践前瞻 -- 使用ElasticSearch 、数据库进行医疗基础数据标准化的方法AI 业务开展的基础 -- 标注软件数据标注有许多类型,如分类、画框、注释、标记等等。常见的几种数据标注类型1.分类标注:分类标注,就是我们常见的打标签。一般是从既定的标签中选择数据对应的标签,是封闭集合。如下图,一张图就可以有很多分类/标签:成人、女、黄种人、长发等。对于文字,可以标注主语、谓语、宾语,名词动词等。2.标框标注:机器视觉中的标框标注,很容易理解,就是框选要检测的对象。如人脸识别,首先要先把人脸的位置确定下来。3.区域标注:相比于标框标注,区域标注要求更加精确。边缘可以是柔性的。如自动驾驶中的道路识别。4.描点标注:一些对于特征要求细致的应用中常常需要描点标注。人脸识别、骨骼识别等。大数据+AI在大健康领域中最佳实践前瞻 -- 高分辨(医学及遥感图像)处理过程中,可供参考使用的开源组件小结数据集大家都知道深度学习的模型训练需要大量的数据样本,在完整的机器学习流程中通常包含训练集(Training Set)、测试集(Validation Set)和验证集(Test Set)三部分。这三部分的作用分别如下:训练集:用于训练模型。验证集:用于调整和选择模型。测试集:用于评估最终的模型。那么如何构建一个标准的医疗数据集呢,以下是一个参考思路:人工智能项目中标准数据集构建思路(从元数据管理角度出发看人工智能医疗器械数据集构建思路)基于国家《人工智能医疗器械质量要求和评价 第2部分:数据集通用要求》美国的一部分医疗数据是通过HIPPA 脱密后在 https://www.hcup-us.ahrq.gov/ 网站上对研究者开放进行探索的。但是由于她给出的数据格式为asc 的不常见格式,我们需要转化成csv 后才能正常使用spark 等大数据分析组件进行分析。还好2015年,有人用python 写了一个调用SAS 解析hcup 数据的开源库,那么今天我们就一起来探索一下,如何用python 对hcup 的asc 数据进行解析并使用。使用python PyHCUP 处理 hcup 数据集的asc 格式数据电子病历从电子病历里自动挖掘这些知识就是要自动识别电子病历文本中与患者健康密切相关的各类命名实体以及实体间的关系。近年来,在电子病历文本上应用自然语言处理、信息抽取等技术服务于临床决策支持的研究倍受关。这个过程分为两个不同的阶段: 自然语言处理研究主要关注病历文本的预处理,包括句子边界识 别、词性标注、句法分析等; 信息抽取以自然语言处理研究为基础, 主要关注病历文本中各类表达医疗知识 的命名实体或医疗概念的识别和关系抽取。海量的电子病历数据堪称医疗领域的大数据, 是座知识的宝库, 蕴含了大量的医疗知识和患者的健康信 息。 电子病历数据不应只是封存在病案室里, 应得到有效利用。 如何利用电子病历数据支持生物医学研究和临床研究是医学信息学(Medical Informatics)和转化医学(Translational Medicine)的重要研究内容。 医学信息 学可简单定义为系统地处理有关药品和临床治疗的信息、数据和知识的新兴学科, 其两个重要分支, 临床信 息学(Clinical Informatics)、用户健康信息学(Consumer Health Informatics), 都与电子病历信息抽取密切相关。 杨锦锋 等: 中文电子病历命名实体和实体关系标注体系及语料库构建 3 临床信息学主要研究利用信息技术实现临床决策支持(Clinical Decision Support), 改善临床治疗效果,电子病历是其 重要的基础数 据。临床信息学的应用领域主要是基于信息技术的循证医学(Evidence-based Medicine)和电子病历系统的智能支持。 病历电子化使得大规模病历的自动分析成为可能, 由于电子病历记 录了患者的疾病和症状、治疗过程和治疗效果, 这些信息是重要的临床证据, 自动抽取这些信息能更加高效 精确地收集证据辅助决策, 促进循证医学这种数据驱动的医疗方法。电子病历已经成为和生物医学文献 同等重要的循证医学实践的源数据。尽管电子病历系统提升了医生的工作效率, 但仍然成为医生工作的负担, 尤其表现在书写病程记录上, 这也影响到了电子病历数据的质量。基于计算机辅助的病历智能生成系统 是电子病历输入的新趋势。为了促进和规范电子病历系统智能支持的实施,中国也于2010 年推出电子病历系统功能应用水平分级评价方法及标准。卓越的临床智 能支持是电子病历系统分级的主要依据, 而临床智能支持的研究与实现必须立足于已有电子病历数据和生物 医学文献的信息抽取和知识挖掘。随着医学信息学的发展和医疗信息化的普及, 患者历次就诊的电子病历可 聚集起来生成终身个人健康记录(Personal Health Record),一个典型案例。通过分析个人健 康记录, 可以抽取患者个性化的健康知识, 进而为患者个人需求、偏好建立模型并整合到医疗信息系统中, 实 现个性化医疗服务。另外, 基础医学研究和临床治疗之间 的转化医学研究,也离不开对电子病历的分析处理。以命名实体识别和实体关系抽取为主要研究内容的电子病历信息抽取研究引起了 广大研究者的重视, 该研究在英文病历上已经全面展开, 而在中文病历上的研究却刚刚起步。电子病历主要有两类, 即门诊病历和住院病历。门诊病历通常较短, 包含信息较少, 也缺乏对患者治疗 情况的跟踪, 因而电子病历信息抽取研究大多关注于住院病历, 并且只限于文本数据的挖掘。如不明确说明, 本文所指的电子病历均指住院病历。 电子病历并不是完全结构化的数据, 还包括一些自由文本(半结构或无结 构)数据, 如病程记录和出院小结等。 这种文本信息方便表达概念以及事件等, 是临床治疗过程的主要记录形 式。 结构化的数据处理起来相对容易, 因而这些自由文本是电子病历命名实体识别和实体关系抽取的主要研 究对象。当前大多数命名实体识别和实体关系抽取方法是基于统计机器学习方法, 并且在开放领域已经趋于成熟。电子病历文本具有半结构化特点和鲜明的子语言特点。由于病历文本的特殊性以及统计机器学习方法的固有局限性, 开放领域的研究成果 很难应用于病历文本之上。 因而, 展开电子病历命名实体识别和实体关系抽取研究首当其冲的就是构建标注 语料库。 如 Roberts 所指出的, 构建标注语料库有三个方面的主要原因:1) 标注体系清晰地界定了抽取任务 的目标; 2) 标注语料用于评价抽取系统的性能; 3) 标注语料用于开发抽取系统(比如训练机器学习模型)。因此, 构建高质量的标注语料库对电子病历命名实体识别和实体关系抽取至关重要, 然而中文电子病历信息抽取研究领域还没有一个标注完整、规模较大、开放共享的命名实体和实体关系标注语料库。所以本系统的开发就显得意义重大。大数据+AI在大健康领域中最佳实践前瞻 -- 基于膨胀卷积神经网络算法的电子病历命名实体识别检验检测糖尿病目前已成为我国的常见病,根据国际糖尿病联盟统计数据,2017年中国是世界第一大糖尿病患者国,全球约有4.25亿成人患糖尿病,其中中国糖尿病患者达1.14亿人,约占全球糖尿病人数的1/4居世界首位。与庞大的糖尿病人群形成鲜明对比的是,我国糖尿病治疗现状堪忧。糖尿病知晓率低,治疗率低,治疗达标率低,并发症却高。不仅血糖达标率低,体重、血压等达标率也不尽如人意:52.3%患者血糖控制不达标,58.3%患者超重,71.6%患者血压控制不达标。我国糖尿病流行的4个因素:老龄化城市化超重肥胖患病率增加中国人的遗传易感性:2型大数据+AI在大健康领域中最佳实践前瞻 -- 连续血糖监测(CGM) 初探大数据+AI在大健康领域中最佳实践前瞻 -- 连续血糖监测(CGM) 可视化实现健康管理是指对个人或人群的健康危险因素进行全面监测、分析、评估以及预测和预防的全过程。而我对健康管理的理解,其中的内涵可以用14个字表达,那就是健康的教育、调查、体检、分析、评估、干预、指导。健康体检只是这个全过程中一个重要的不可缺少的环节。健康体检的数据是评价机体状况、疾病预测、生命质量水平的基本条件之一。它只是健康管理中信息采集的重要环节,他与健康管理既有关系又有区别,可称之为“健康管理大家庭”中的一分子。大数据+AI在大健康领域中最佳实践前瞻 -- 体检数据的理解在医学数据库中的大多数初始记录不包含任何患者标识符信息。为了分析和检测这些患者记录的异常,需要关于该特定患者的先前病历信息进行匹配。没有这些信息,匹配被检测人的任务将非常具有挑战性。该系统的目的是根据提供的特征将患者ID分配给患者记录。大数据+AI在大健康领域中最佳实践前瞻 -- 检验检测中的单一实体识别前瞻与探索大数据+AI在大健康领域中最佳实践前瞻 -- 基于DBSCAN 与软聚类实现单一实体识别疾病预测疾病风险预测核心解决的问题是预测个体在未来一段时间内患某种疾病(或发生某种事件)的风险概率。疾病预测会根据某个人群定义,例如全人群、房颤人群、心梗住院人群等,针对某个预测目标,例如脑卒中、心衰、死亡等,设定特定的时间窗口,包括做出预测的时间点,和将要预测的时间窗,预测目标的发生概率。大数据+AI在大健康领域中最佳实践前瞻 -- 浅谈变分自编码器(Variational Auto Encoder)原理解析与实战大数据+AI在大健康领域中最佳实践前瞻 -- 浅谈使用变分自编码器(VAE) 进行疾病预测大数据+AI在大健康领域中最佳实践前瞻 -- 基于变分自编码器(VAE) 进行疾病预测简单实现参考文献:BP神经网络在疾病预测中的应用面向不均衡医学数据集的疾病预测模型研究人工智能在疾病预测研究中可视化分析基于Doc2Vec和BiLSTM的老年患者疾病预测研究健康管理健康医疗保险与人工智能互联时代,特别是移动互联网日渐普及之后,大数据的搜集变得更为方便和可行,大数据的应用价值受到了各行各业的关注,甚至大数据本身也成了一个专门产业。保险作为基于大数法则运营发展的商业行为,对大数据的利用有着天然的倾向性。大数据+AI在大健康领域中最佳实践前瞻 -- 基于健康保险行业的大数据AI应用与客户场景实现及其解决思路前瞻首先,行业竞争倒逼核保和理赔速度的提升,可能带来核保、核赔质量下降的负面影响。从纯理论角度和最理想化的角度来讲,核保和核赔这两个环节是可以为保险公司屏蔽所有逆选择和道德风险的。但付出的代价是用大量的人力对每个投保和理赔申请都进行大量的细致调查。这在保险公司实际运营中是不可能的。特别是在行业竞争越来越激烈的今天,为提升客户体验,保险公司的投保条件愈发宽松,核保核赔速度快,甚至免核保、免体检、快速赔付已经成为保险公司吸引客户的“标配”所在。各家公司千方百计提高服务速度,核保核赔部门往往要承受客户和销售部门的双重压力。在此情况下,虽然保险公司的保费收入有了较大增长,但是承受的风险冲击将明显增大。公司管理层对业绩增长的期待,或多或少冲淡了本该固若金汤的风控意识。其次,互联网保险的发展,客观上增加了风险控制的难度。如今,网络销售、移动互联网销售日益被保险公司所重视。各种保险销售网站,成为了保险公司新的保费增长点。甚至客户通过手机微信等软件终端,就可以轻松完成投保或理赔过程,在这种情况下,材料真实性验证难度较大,信息不对称性更为突出,机会型欺诈风险增加。异地出险的增加,也对理赔后续工作提出较高要求,容易出现保险服务流程衔接的空白。在传统保险销售过程中,销售人员与客户面对面地沟通,其实也是一种了解客户的过程。但是互联网保险的发展让这个过程消失。核保部门失去了一道天然屏障。这些都是增加了风险控制的难度。双核系统是一个人工智能驱动的核保核赔系统。旨在辅助保险公司为投保人提供更优质的保险服务。大数据+AI在大健康领域中最佳实践前瞻 -- 智能服务在保险业务中的应用探讨基于 spark + xgboost or 孤立森林的行业风控(其实异常检测,或者分类的思路都可以在不正常数据的筛检中起到一定的作用,如欺诈检测,风控等)大数据+AI在大健康领域中最佳实践前瞻 -- 基于 pyspark + xgboost 算法的 欺诈检测 DEMO实践大数据+AI在大健康领域中最佳实践前瞻 -- 大数据环境下的异常检测思路 ---- 使用IsolationForest 与Meanshift算法进行异常检测大数据+AI在大健康领域中最佳实践前瞻 -- 基于孤立森林的异常检测 ---- Anomaly: Isolation Forest based Anomaly Detection参考:利用大数据分析将保险业风险防控做到极致保险大数据在保险领域的应用AI + 大数据 助力药物研发患者招募成为当前临床试验最大难题:临床试验是指在新药在临床广泛使用前,在国家相关政策和法规允许下进行的帮助医生找出药物最有效的使用方法、适用症状和适用患者,是基础研究成果转化成为新的治疗方法的必由之路。药品临床研究离不开患者(受试者)招募,其也是药品研发的重要环节之一。然而患者招募也是当前临床研究的最大难题。在整个临床试验的过程中,目前所面临的最大的难题是怎样发现 、招募、入组和保留受试者,并保证受试者顺利的完成试验。大数据+AI在大健康领域中最佳实践前瞻 -- AI + 大数据助力药物研发过程中志愿者招募的流程参考文献相关资料医疗信息基础数据知识库hcup数据集简介与基本分析连续血糖监测(CGM) 可视化实现体检机构接入数据接口设计电子病历命名实体识别研究资料与ccks2020论文集合电子病历命名实体识别代码人工智能医疗器械数据集标准参考2019中国医疗人工智能市场研究报告ai助力药物研发
@toc前言大家好,今天开始和大家分享,我在自然语言处理(Natural Language Processing,NLP)的一些学习经验和心得体会。随着人工智能的快速发展,自然语言处理和机器学习技术的应用愈加广泛。为使大家对该领域整体概况有一个系统、明晰的认识,同时入门一些工程实践,也借CSDN为NLP的学习,开发者们搭建一个交流的平台。我希望能够通过这个专栏《自然语言处理实战入门》和广大NLP爱好者一起学习自然语言处理技术,分享学习资料,打破NLP 技术 的实战应用壁垒。由于网络上的教程等,基本都已英文NLP 作为切入点,但本人认为汉语NLP还是有着很多根本性不同的,所以本专栏的所有代码与DEMO也都是围绕着汉语自然语言处理进行构建。本专栏持续更新中,内容还未完整的请稍安勿躁,部分内容有参考其他书籍或是网络文献,都会给出原始出处配套前期视频课程 ---- 持续更新中视频讲解课程 :《自然语言处理实战入门》综论what is it?自然语言处理(Natural Language Processing,简称 NLP)是人工智能和语言学交叉领域下的分支学科。用于分析、理解和生成自然语言,以方便人和计算机设备进行交流,以及人与人之间的交流自然语言处理(NLP)技术简介自然语言处理在深度学习的支撑下取得了迅猛发展,总结的过去5年ACL文章中自然语言发展的主要工作,包括 Word embeddings、LSTM、Encode decoder、RNN、Pre-trainedmodel 等,这些技术推动了自然语言的应用,包括基于神经网络的机器翻译,预训练模型演化,阅读理解技术等。NLP未来发展方向现在自然语言处理相关专业人才属于供不应求的状态,这种状态是因为过去很长一段时间,高校NLP人才的培养是跟不上业界需求的。毕竟目前国内高校比较有积淀的自然语言处理实验室不是很多,可能也就二三十家,而对于高校来说,建立一个学科是需要时间积累的。这就导致了培育人才的速度跟不上工业界的需求。目前,工业界对于能够理论与实践相结合、学习能力强、能够推动产品落地的人才是十分渴求的。通过对企业用人端的分析,以及NLP知识框架分析,我们可以从核心能力、工作能力两个方面回答:如何成为一名 NLP 工程师?NLP 学习资源汇总NLP 前置技术莫要轻视前置基础,所有技术都不是一蹴而就的 。希望大家能够沉下心来,慢慢打磨自己的技术,首先将前置技术融汇贯通。本篇章从搭建 开发环境开始讲起,以网络爬虫作为工程化学习NLP的引入。其次通过学习通用的开源框架,夯实NLP基础知识,最后带领大家查看NLP 工程中常用的资源、语料库。开发环境python 开发环境搭建基础技术正则表达式网络爬虫爬取本人CSDN博客网络爬虫简介开源库NLP开源工具包与云服务提供商基础库 ---- NumPy基础库 ---- Pandas基础库 ---- Pandas(练习篇)基础库 ---- SciPy基础库 ---- matplotlib机器学习 ---- Gensim机器学习 ---- Scikit-learn深度学习 ---- TensorFlow深度学习 ---- Keras深度学习 ---- Pytorch 深度学习基础深度学习组件TensorFlow2.0初探seq2seq 与 attention 机制神经网络与推荐系统初步简介常用资源语料与常用资源序列标注与分词算法“数学上,序列是被排成一列的对象(或事件);这样每个元素不是在其他元素之前,就是在其他元素之后。在自然语言处理领域,语句便是序列,对其进行标注是最常见的任务之一,只要涉及对一个序列中的各个元素进行打标签的问题,都可以通过序列标注模型解决。简介中文分词原理及相关组件简介 之 ---- 汉语语言学中文分词原理及相关组件简介 之 ---- 分词领域主要分词算法、组件、服务(上)中文分词原理及相关组件简介 之 ---- 分词领域主要分词算法、组件、服务(中)中文分词原理及相关组件简介 之 ---- 分词领域主要分词算法、组件、服务(下)分词算法原理中文分词原理及相关组件简介 之 ---- 分词算法原理中文分词原理及相关组件简介 ---- 分词算法原理(CRF)中文分词原理及相关组件简介 ---- 结构化感知机中文分词原理及相关组件简介 ---- 分词算法原理(HMM)实践工业级词法分析组件百度LAC2.0停用词 知多少?评测中文分词原理及相关组件简介 之 ---- 分词效果评测最佳实践词嵌入(分布式文本表示)word2vector顾名思义,其实就是旨在把每个单词转化为词向量,其实很多方式都可以实现这个功能,最简单的当然就是one-hot了,但是面对无敌庞大的词库,直接使用one-hot来进行表示将会面临很大的内存占用和很高的计算时间,于是有了LDA、GloVe以及现在比较新的bert等,都是尝试通过使用连续的词向量模型来进行词向量转化,从而进行后续的自然语言处理任务。词嵌入原理解析文本分类文本分类用电脑对文本集(或其他实体或物件)按照一定的分类体系或标准进行自动分类标记。 它根据一个已经被标注的训练文档集合, 找到文档特征和文档类别之间的关系模型, 然后利用这种学习得到的关系模型对 新的文档进行类别判断 。文本分类从基于知识的方法逐渐转变为基于统计 和机器学习的方法。文本分类一般包括了文本的表达、 分类器的选择与训练、 分类结果的评价与反馈等过程,其中文本的表达又可细分为文本预处理、索引和统计、特征抽取等步骤。文本分类简介使用词向量与SVM 进行文本分类CNN与中文文本分类使用词向量与TestCNN 进行文本分类预训练模型 文本分类基于 Django 3.2 框架开发 机器学习在线 系统模型在线部署 -- 使用Django在线预测NLP 可视化人眼是一个高带宽的巨量视觉信号输入并行处理器,最高带宽为每秒100MB,具有很强的模式识别能力,对可视符号的感知速度比对数字或文本快多个数量级,且大量的视觉信息的处理发生在潜意识阶段.其中的一个例子是视觉突变:在一大堆灰色物体中能瞬时注意到红色的物体。由于在整个视野中的视觉处理是并行的,无论物体所占区间大小,这种突变都会发生.视觉是获取信息的最重要通道,超过50%的人脑功能用于视觉的感知,包括解码可视信息、高层次可视信息处理和思考可视符号。python可视化初步 matplotlib 相关设置文本分析基础文本内容可视化词向量可视化语义共现网络文本检索信息检索定义为对用户做出的查询进行响应并检索出最合适的信息的过程。在信息检索中,根据元数据或基于上下文的索引,进行搜索。搜索引擎 是信息检索的一个示例,对于每个用户的查询,它基于所使用的信息检索算法进行响应。信息检索算法中使用了倒排索引的索引机制。初探ElasticSearch 基本操作Kibana 基本操作信息抽取信息抽取 (Information Extraction) 是把文本中包含的信息进行结构化处理,变成表格一样的组织形式。信息抽取的主要任务是将各种各样的信息点从文档中抽取出来。然后以统一的形式集成在一起,方便后序的检索和比较。信息抽取初探关键词抽取使用深度学习进行自然语言处理深度学习是利用多层次神经网络结合机器学习 形成的,使计算机通过自动完成学习过程的一类算 法。其与以设计为主的传统机器学习相比,不仅实 现了机器学习的自动化,减少了面对不同问题时的 人工设计成本,还增强了对数据中潜在信息的提取 与分析能力。在深度学习技术成熟之前,机器学习主要使用 的算法建模是带有一层或是没有隐形节点的,如条件随机场(Conditional Random Field,CRF)、支持向 量机(Support Vector Machine, SVM)及最大嫡模型 (Maximum Entropy,ME)等。这些带有一层或是没 有隐形节点的建模在面对结构复杂的数据泛化问题精确度的需要⑵。2006年,深度学习 被用于手写文字识别的领域,并取得了很好的效果。 此后,深度学习的方法也被用于解决自然语言处理 (NLP)领域中的问题2011年到2012年,深度 神经网络(Deep Neural Network,DNN)被应用在图 像识别领域和语音识别领域,并取得了显著的成绩。 但是,由于自然语言处理领域待解决的问题的复杂 性、多样性以及对训练数据海量的要求,导致深度学 习在该领域还没有重大的突破。综论Transformer 与Attention 架构初探预训练模型初探使用深度学习进行汉语分词预训练模型的使用(BERT)预训练模型的使用(ALBERT)深度学习与中文短文本分析总结与梳理(早期文章)知识图谱2012 年,谷 歌 提 出 了 知 识 图 谱 (knowledge Graph)的概念,为世界知识和领 域知识的构建提供了一个可资借鉴的手段。知识图谱的基本组成是由头实体、尾实体和两者之间的关 系组成的三元组关系。目前,对知识图谱 的研究应用主要 包括通用知识图谱和垂直领域知识图谱。典型的通用知识图谱有 google knowledgegraph、 DBpedia、CN-DBpedia、XLore等。虽然通用知识图谱收集了大量的领域知识,但是受到概念约束,无法完整描述比较复杂 的领域知识。垂直领域知识图谱在领域知识的描述方面优于通用知识图谱,但常采用手工构建方法,因此其构建成本很高。初探医疗知识库医疗知识图谱文本生成文本生成目前主要试用的是GPT-2 模型基本上只要了解 Transformer 架构,你馬上就懂 GPT-2 了。初探聊天机器人自动问答是指利用计算机自动回答用户所提出的问题以满足用户知识需求的任务。问答系统是信息服务的一种高级形式,系统反馈给用户的不再是基于关键词匹配排序的文档列表,而是精准的自然语言答案,这和搜索引擎提供给用户模糊的反馈是不同的。在自然语言理解领域,自动问答和机器翻译、复述和文本摘要一起被认为是验证机器是否具备自然理解能力的四个任务。自动问答系统在回答用户问题时,首先要正确理解用户所提出的问题,抽取其中关键的信息,在已有的语料库或者知识库中进行检索、匹配,将获取的答案反馈给用户。这一过程涉及了包括词法句法语义分析的基础技术,以及信息检索、知识工程、文本生成等多项技术。传统的自动问答基本集中在某些限定专业领域,但是伴随着互联网的发展和大规模知识库语料库的建立,面向开放领域和开放性类型问题的自动问答越来越受到关注。根据目标数据源的不同,问答技术大致可以分为检索式问答、社区问答以及知识库问答三种。检索式问答和搜索引擎的发展紧密联系,通过检索和匹配回答问题,推理能力较弱。社区问答是 web2.0 的产物,用户生成内容是其基础,Yahoo!、Answer、百度知道等是典型代表,这些社区问答数据覆盖了大量的用户知识和用户需求。检索式问答和社区问答的核心是浅层语义分析和关键词匹配,而知识库问答则正在逐步实现知识的深层逻辑推理。纵观自动问答发展历程,基于深度学习的端到端的自动问答将是未来的重点关注,同时,多领域、多语言的自动问答,面向问答的深度推理,篇章阅读理解以及对话也会在未来得到更广阔的发展初探以 ChatterBot 为例 构建简单的聊天机器人语音识别语音识别技术就是让机器通过识别和理解过程把语音信号转变为相应的文本或命令的技术。语音识别本质上是一种模式识别的过程,未知语音的模式与已知语音的参考模式逐一进行比较,最佳匹配的参考模式被作为识别结果。语音识别的目的就是让机器赋予人的听觉特性,听懂人说什么,并作出相应的动作。目前大多数语音识别技术是基于统计模式的,从语音产生机理来看,语音识别可以分为语音层和语言层两部分。当今语音识别技术的主流算法主要有基于动态时间规整(DTW)算法、基于非参数模型的矢量量化(VQ)方法、基于参数模型的隐马尔可夫模型(HMM)的方法、基于人工神经网络(ANN)和支持向量机等语音识别方法。离线环境的中文语音识别 paddlepaddle 与 Speech_Recognition(Sphinx)实践笔试、面试面试题集面试笔试题集:机器学习基础 1-50面试笔试题集:机器学习基础 51-100参考文献《Python自然语言处理实战-核心技术与算法》《汉语自然语言处理原理与实践》《自然语言处理理论与实践》做项目一定能用到的NLP资源
最近想做一个简单的地理位置分析,比如获取一些城市公交站点对应的geohash,geohash其实是将平时常见的经纬度进行了降维,这样可以进行类似附近的餐馆等内容的分析。 1. 正逆地理编码 http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding 正/逆地理编码服务(又名Geocoding API)是一类Web API接口服务; 正向地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能; 逆向地理编码服务提供将坐标点(经纬度)转换为对应位置信息(如所在行政区划,周边地标点分布)功能。 1.1 百度地图api正逆地理编码存在偏差 百度地图坐标拾取 http://api.map.baidu.com/lbsapi/getpoint/index.html 可以直接使用的百度url:后面直接跟地址就好如上图(key不知道是谁的),可以发现百度的搜索分词权重直接把雍和宫地铁站定位到了雍和宫, http://api.map.baidu.com/geocoder?key=f247cdb592eb43ebac6ccd27f796e2d2&output=json&address= url new key: http://api.map.baidu.com/geocoder?key=xpKTc80ZnEGiy1elZCMtEepEYKj5tqQr&output=json&address= http://api.map.baidu.com/geocoder/v2/?address=&output=json&ak=xpKTc80ZnEGiy1elZCMtEepEYKj5tqQr 1.2 高德地图接口 高德地图坐标拾取 http://lbs.amap.com/console/show/picker 发送一个request请求,带上地理位置和api key 即可返回一个包含了经纬度str。 地理编码接口: # -*- coding: utf-8 -*- import requests def geocode_change_key(address,key): parameters = {'address': address, 'key': key} base = 'http://restapi.amap.com/v3/geocode/geo' response = requests.get(base, parameters) answer = response.json() return str(answer['geocodes'][0]['location']).split(',') 2. 坐标系 谷歌地图采用的是WGS84地理坐标系(中国范围除外) 谷歌中国地图、搜搜中国地图、高德地图采用的是GCJ02地理坐标系 百度采用的是BD09坐标系。 而设备一般包含GPS芯片或者北斗芯片获取的经纬度为WGS84地理坐标系。 所以我们要根据得到的经纬度的坐标类型和地图厂商类型在地图上标点,否则会出现获取的位置误差。为什么不统一用WGS84地理坐标系这就是国家地理测绘总局对于出版地图的要求,出版地图必须符合GCJ02坐标系标准,也就是国家规定不能直接使用WGS84地理坐标系。 百度坐标系说明书:http://lbsyun.baidu.com/index.php?title=coordinate 2.1 我们常说的坐标系 WGS84:为一种大地坐标系,也是目前广泛使用的GPS全球卫星定位系统使用的坐标系。 GCJ02:又称火星坐标系,是由中国国家测绘局制定的地理坐标系统,是由WGS84加密后得到的坐标系。 BD09:为百度坐标系,在GCJ02坐标系基础上再次加密。其中bd09ll表示百度经纬度坐标,bd09mc表示百度墨卡托米制坐标。 2.2 坐标转码关键代码 # -*- coding: utf-8 -*- import json import urllib import math x_pi = 3.14159265358979324 * 3000.0 / 180.0 pi = 3.1415926535897932384626 # π a = 6378245.0 # 长半轴 ee = 0.00669342162296594323 # 扁率 def gcj02_to_bd09(lng, lat): """ 火星坐标系(GCJ-02)转百度坐标系(BD-09) 谷歌、高德——>百度 :param lng:火星坐标经度 :param lat:火星坐标纬度 :return: """ z = math.sqrt(lng * lng + lat * lat) + 0.00002 * math.sin(lat * x_pi) theta = math.atan2(lat, lng) + 0.000003 * math.cos(lng * x_pi) bd_lng = z * math.cos(theta) + 0.0065 bd_lat = z * math.sin(theta) + 0.006 return [bd_lng, bd_lat] def bd09_to_gcj02(bd_lon, bd_lat): """ 百度坐标系(BD-09)转火星坐标系(GCJ-02) 百度——>谷歌、高德 :param bd_lat:百度坐标纬度 :param bd_lon:百度坐标经度 :return:转换后的坐标列表形式 """ x = bd_lon - 0.0065 y = bd_lat - 0.006 z = math.sqrt(x * x + y * y) - 0.00002 * math.sin(y * x_pi) theta = math.atan2(y, x) - 0.000003 * math.cos(x * x_pi) gg_lng = z * math.cos(theta) gg_lat = z * math.sin(theta) return [gg_lng, gg_lat] def wgs84_to_gcj02(lng, lat): """ WGS84转GCJ02(火星坐标系) :param lng:WGS84坐标系的经度 :param lat:WGS84坐标系的纬度 :return: """ if out_of_china(lng, lat): # 判断是否在国内 return lng, lat dlat = _transformlat(lng - 105.0, lat - 35.0) dlng = _transformlng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) mglat = lat + dlat mglng = lng + dlng return [mglng, mglat] def gcj02_to_wgs84(lng, lat): """ GCJ02(火星坐标系)转GPS84 :param lng:火星坐标系的经度 :param lat:火星坐标系纬度 :return: """ if out_of_china(lng, lat): return lng, lat dlat = _transformlat(lng - 105.0, lat - 35.0) dlng = _transformlng(lng - 105.0, lat - 35.0) radlat = lat / 180.0 * pi magic = math.sin(radlat) magic = 1 - ee * magic * magic sqrtmagic = math.sqrt(magic) dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * pi) dlng = (dlng * 180.0) / (a / sqrtmagic * math.cos(radlat) * pi) mglat = lat + dlat mglng = lng + dlng return [lng * 2 - mglng, lat * 2 - mglat] def bd09_to_wgs84(bd_lon, bd_lat): lon, lat = bd09_to_gcj02(bd_lon, bd_lat) return gcj02_to_wgs84(lon, lat) def wgs84_to_bd09(lon, lat): lon, lat = wgs84_to_gcj02(lon, lat) return gcj02_to_bd09(lon, lat) def _transformlat(lng, lat): ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \ 0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lat * pi) + 40.0 * math.sin(lat / 3.0 * pi)) * 2.0 / 3.0 ret += (160.0 * math.sin(lat / 12.0 * pi) + 320 * math.sin(lat * pi / 30.0)) * 2.0 / 3.0 return ret def _transformlng(lng, lat): ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \ 0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng)) ret += (20.0 * math.sin(6.0 * lng * pi) + 20.0 * math.sin(2.0 * lng * pi)) * 2.0 / 3.0 ret += (20.0 * math.sin(lng * pi) + 40.0 * math.sin(lng / 3.0 * pi)) * 2.0 / 3.0 ret += (150.0 * math.sin(lng / 12.0 * pi) + 300.0 * math.sin(lng / 30.0 * pi)) * 2.0 / 3.0 return ret def out_of_china(lng, lat): """ 判断是否在国内,不在国内不做偏移 :param lng: :param lat: :return: """ return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55) if __name__ == '__main__': lng = lat = # result1 = gcj02_to_bd09(lng, lat) # result2 = bd09_to_gcj02(lng, lat) # result3 = wgs84_to_gcj02(lng, lat) result4 = gcj02_to_wgs84(lng, lat) #result5 = bd09_to_wgs84(lng, lat) #result6 = wgs84_to_bd09(lng, lat) print (result4) 3. geohash https://www.cnblogs.com/LBSer/p/3310455.html 当geohash base32编码长度为8时,精度在19米左右,而当编码长度为9时,精度在2米左右,所以一般来说用八位就够用。 python3如何使用geohash呢,网上说使用pip install geohash后import geohash 会报错,当然同样的作者提供了geohash包的fix版geohash2,所以安装时候应该是:(改源码的方式有点太高大上,不太安全?) pip install geohash2 我很纳闷的是python中能够生成geohash 的包实在是太多了: 3.1 获取包围盒 可以看到7位geohash编码带上一个包围盒,相对于6位geohash编码准确许多 简单写了一个类,使用geohash2(作者居然没有提供),我只好复制了mzgeohash的部分代码 https://gitee.com/wangyaning/python/tree/master/geohash 可以直接这么用: if __name__=='__main__': myTestGeohash = MyGeohash() #wx4g340 print(myTestGeohash.getneighbors('wx4g340')) 输出如下: {'ne': 'wx4g343', 'n': 'wx4g342', 'w': 'wx4g2fp', 'c': 'wx4g340', 'sw': 'wx4g2cz', 'se': 'wx4g31c', 'nw': 'wx4g2fr', 'e': 'wx4g341', 's': 'wx4g31b'} 测试geohash查询接口 https://cevin.net/geohash/ 结构化数据的处理 爬好数据的后处理,入库 新学了sqlldr命令,挺快,连python代码都不用写了 sqlldr userid='username/password@serverip/instance' control=./xxx.ctl errors=99999999 rows=20000 direct=true data=xxxxxxx.txt xxx.ctl文件如下 LOAD DATA CHARACTERSET 'UTF8' INFILE * APPEND INTO TABLE TABLENAME FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"&*!' trailing nullcols ( linename , xxxxxx, xxxxxx ) 部分参考文献 简单的城市名转换成经纬度: https://www.cnblogs.com/zle1992/p/7209932.html 批量获取经纬度: https://www.cnblogs.com/reboot777/p/7124010.html 用Python计算北京地铁的两站间最短换乘路线: http://blog.csdn.net/myjiayan/article/details/45954679 使用爬虫获取获取所有的 站点名 http://blog.csdn.net/wenwu_both/article/details/70168760 高德地图地理编码服务 http://blog.csdn.net/u013250416/article/details/71178156 https://www.cnblogs.com/xautxuqiang/p/6241561.html
2017年钟声敲响的时候,人们总是习惯于性质勃勃地写下一张全年的to do list:例如读完800本书,买个大房子等等。立志之后,就陷入到忙忙碌碌的新一年中,上班扎进电脑和手机,下班一头扎进被子,直到猛一抬头发现2017年只剩几天。。。 十八般武艺 VS 亢龙有悔 矛锤弓弩铳,鞭简剑链挝,斧钺并戈戟,牌棒与枪杈 古语有云:有志者立常智,无智者常立志。随着现代社会的迅速变化,立常志变得几乎不可能,更多的情况是你的理想需要随着现实情况的变化而做适当的调整。但即便如此,我还是认为需要一个相对恒定的理想。不忘初心,是整个国家,社会对于回归纯粹生活的热切期望。 工作一年半以来,由于单位没有互联网,手机俨然成为了我躯体的又一器官。当我发现手机严重影响 我的专注度和思维能力已经 为时过晚。远离手机的时间,大脑通过冥想可以将一些看似没有太多关联的事情联系起来。所以当我和任务独处的时候我能感觉到和整个宇宙都在产生联系,但是当手机加入,就只有我和手机了。 自媒体的迅速发展,既容易造成注意力的集中,也容易造成注意力的消解,使人们对需要严肃对待的事情越来越缺乏耐心,面对铺天盖地的信息,往往需要耗费大量时间甄选有用信息 ,本来是打开手机搜索一个问题,由于顺道开了微信,刷了一圈朋友圈八卦,又顺道看了两篇公众号文章,而忘了自己原本要打开手机的目的,长此以往根本没法集中注意力,干什么都不专心,做什么都delay,当今社会犯错成本太高,我们还是需要小心翼翼,千万不要养成时不时看手机的毛病而误了大事。 两弹一星元勋科学家钱学森出身书香门第,他不但学贯中西更是精通琴棋书画,1935年,24岁的钱学森甚至还发表过一篇题为《音乐与音乐的内容》的论文。看来天龙八部中聪辩先生苏星河精通琴棋书画,却不擅长武学,因花太多时间于杂学之上,并未学会逍遥派太多高深的功夫,他有点像我现在的状态,在技术上缺乏一个focus 的点,郭靖大侠当年将一招亢龙有悔使到极致照样威震武林,当然他也可能还是天赋有限。我也天赋有限,工作生活中还是要保持专注度,远离手机,保持纯粹的追求,也许总有一天,自己也能十八般武艺样样精通,成为一代全栈程序宗师。 天下攘攘皆为利往 以下一个小节完全转载自,安晓辉《 程序视界》 很多开发者追逐 AI ,也是从这点出发,为了自己更好的未来。但实际上,趋势并不属于每个人。它往往属于那些已经为这个趋势做了很多年准备的人。 你必须知道的是,现在 AI 趋势里风生水起的专家、科学家、公司,哪个不是之前已经在相关领域做了很多年的研究? 如果你只是看到趋势就盲目扑过去,往往沦为跟风,甚至会跟丢,跟来跟去找不到自己的位置。所以,如果你决定要跟,也要了解怎样去跟。 人工智能开发的四种类别 最后,提醒一下,并不是每个程序员都要追逐人工智能软件开发这个浪潮。软件开发的方向很多,应用场景也很多,你有非常大的可选择余地——只要你能成为你所处领域的局部头部,你就会拥有很多机会。而如果缺乏成为头部的思维、能力和行动,不论去追赶什么浪潮,结果都只能是望洋兴叹。 参考这篇安大神的文章分析,我可以有一个简单的判断,从现在做起还不算晚。 http://mp.weixin.qq.com/s/TdG3ML195g55hD9a9UuxcQ (以上内容转自上述链接) 我一直追寻着你心情的足迹 张老师的小学教师生涯并不是一副歌舞升平的盛世景象,她有时候甚至问我:自己是不是适合教师这个职业。家长到处范二,练习册丢了问老师,作业布置的啥问老师,恨不得把老师当成7*24小时不间断服务的人肉机器人,问的时候还不加敬称,你你你的呼来唤去。我看了心里都很不爽,张老师还是不卑不亢的耐心回答,就评这一点小张就应该获得优秀教师勋章。 国家说要提高基层教师工资待遇,喊了很久,张老师也没涨工资。大学教授一个月1w7带两三个研究生天天给自己报销发票有几个真正推进了我国现代化进程的进步?小学幼儿园初高中老师一个月几大千一人带80多个祖国的花朵,拿着卖白菜 的钱操着卖白粉的心。谁手中掌握着祖国的未来?多劳多得按劳分配也应该给她们涨涨工资。 记得有一次和高指导聊天龙八部,发现金庸竟然在新版改了结局,知乎上有人说,更相信王语嫣和段誉仍然是一对。后来长大后便慢慢醒悟爱情的意义。明白了金老先生的深意。其实木婉清刚出场那时,已经折服了多少人的心,他和段誉本是良偶,于是王语嫣与我,从神仙姐姐,变成了岳灵珊、变成了周芷若。 和张老师的爱情长跑将近1400多天,我们已经有了明确的人生规划,在人生大事上基本达成了共识,爱情在我们两个人的生活当中产生了微妙的变化,好像慢慢的在向柴米油盐等琐碎的事情上迁移,张老师批评我说热恋还没完,怎么就要开始准备结婚了。说起来万分惭愧,是这一年来我因为工作的原因聚少离多,给她的惊喜太少了吧。 特别感谢小张老师,她是一名优秀的产品经理,经常能敏锐且毫不客气的指出我的不足,更难得的是她胃口很好,哈哈 任何事情要做好且做爽,必然是内驱的 2015年的关键词:少说多做 http://blog.csdn.net/wangyaninglm/article/details/50640972 2016年的关键词:努力,奋斗 http://blog.csdn.net/wangyaninglm/article/details/53959333 2017年中的关键词:让我们一起为梦想窒息! http://blog.csdn.net/wangyaninglm/article/details/74612482 过去的时间我基本保持 ,每半年更新一篇总结过去,展望未来的文章,2016年底我希望自己能够成为大数据全栈工程师,还顺道唱衰了一下大数据觉得大数据什么的都是使用开源的产品,一年过去发现,好家伙用开源产品也得知道怎么用数以万计的api呀,hadoop生态圈十几二十个产品真不是盖的。和圈内专家聊了两句就露馅,人家说,您这就不叫大数据研发,充其量就是使用开源组件—-而已! 所以这半年比较尴尬,甚至没有一个可以写到简历上用来show 的项目。代码没写过多少,淡是越扯越好了。 想起csdn专家群里一个专家总结最近面试人的经历,大多数是:一问三不会,开口十几k 我呢?开不了口。。。 总结总是有喜有悲的,在单位,吃了个饭,睡了个觉,出了个差,半年过去了。这些时日我总发现自己穷的很,不单单是物质层面上的,更是精神上的穷。 造成这种身心惧穷的根本原因是基本上把钱看的比什么都重要,做事情的时候我会不自觉的去衡量事情的金钱价值,而真正应该 有的态度是首先把事情做好,顺便赚钱。至少冯仑说李嘉诚是这么认为的。 工作说完了,咱们再来聊聊人生有何意义?胡适先生说,科学家是为求真理。庄子虽有“吾生也有涯,而知也无涯,殆已”的话头,但是我们还是要向上做去,得一分就是一分,一寸就是一寸,可以有阿基米德氏发现浮力时叫Eureka 的快活。有了这种精神,做人就不会失望。所以人生的意味,全靠你自己的工作;你要它圆就圆,方就方,是有意味;因为真理无穷,趣味无穷,进步快活也无穷。 记得前段时候有一篇文章说第一批九零后已经秃了,当然没有人能躲过生活,只不过现在轮到90后,罗曼罗兰曾说过,世界上只有一种真正的英雄主义,那就是在认清生活的真相后依然热爱生活。 共勉! 特别鸣谢 小张老师 胡适先生 《人人都是产品经理》 《黑客与画家》 安晓辉 《程序视界》 All Rights Reservered 注: 在大型组织内部,有一个专门的术语描述这种跟随大多数人的选择的做法,叫做“业界最佳实践”。
作者—李笑来 点评 点评此书 运用心智,获得解放。 这看似是讲了一些大道理,其实是细节决定成败,天道酬勤的道法使然 希望时间也是我的朋友 人生充满了不确定性和戏剧性,但有一件事是确定的——时间只与那些努力的人做朋友。 一切都靠积累,一切都可提前准备,越早醒悟越好。 了解心智的力量 在任何一个部门或团队里,上司做的事情全部是显性的,所有下属或者成员都可见的;而下属和成员之间往往并不相互非常清楚对方正在做什么。于是,下属们更容易“共同”地看到上司的缺点。钱钟书先生有个很有趣的描述“猴子要爬到树上,我们才看得见它的红屁股”。 俗话讲,猫有猫道,鼠有鼠道;猎头也肯定有自己的办法。一个相对比较简单的办法就是锁定二流人才。道理也比较简单。任何一个团队里的一流人才,通常很难产生流动的愿望。他们的薪水很高,甚至过高; 首先,这些人并不是对正在做的事情没有兴趣,而是没能力把目前正在做的事情做好。 最终没有人喜欢自己做不好的事情。 做好这件事情究竟对自己有没有意义?如果有,那就要努力做,直到做好为止——没有其他选择。 只要一件事儿你能做好,并且做到比谁都好,或者至少比大多数人好,你就不会对那件事情没兴趣。 说来说去,又是顺序出了问题——往往并不是有兴趣才能做好,而是做好了才有兴趣。 方法固然重要,但是比起“用功”来说,方法几乎可以忽略不计。 最重要的只不过是:重复,不间断地重复,重复一年以上。 所有学习上的成功,都只靠两件事:策略和坚持,而坚持本身就应该是最重要的策略 如何运用心智的力量在尽管还没有机会亲身体验的情况下,仅凭心智就可以像真实经历过一样深刻体会? 何谓心智 心智主要包括以下三个方面的能力:a.获得知识;b.应用知识;c.抽象推理。 人类之所以与其它动物不同,原因之一在于人类拥有比其它动物更为发达的“大脑额页”,乃至于人类天生拥有比其它动物更为发达的学习能力。透过“获取知识”、“应用知识”、以及同样需要学习才能获得的“抽象推理能力” 我们竟然可以用我们的大脑控制我们的大脑。 “即使是在极端恶劣的环境里,人们也会拥有一种最后的自由,那就是选择自己的态度的自由。” 当我突然意识到我竟然可以(也根本就应该)控制我自己的大脑的时候,我觉得我居然不用死就可以从头再来,这该是多么神奇。 当我们觉得自己痛苦的时候,总是不自觉地把自己想象成全世界最痛苦的人 如果说记忆本身是葡萄,那么回忆的过程就是发酵。 你现在知道你为什么总那么没记性了吧?因为在你遇到挫折或者面对那些你曾经的错误决定最终带来的惩罚的时候,你太痛苦了。而这样的痛苦,必然被你的大脑自动列入遗忘的序列,并在你大脑里彻底消失。 在做所有类似的必须记住大量信息的工作的时候,一定要想办法由衷地把这件事当作快乐的事情来做。 坚持不懈是什么来着?——策略加上重复。 平静接受并且正确认识自己的天性是改变天性的第一步。 精确感知时间 我崇尚公平,向往自由,渴望平等,憧憬希望。 一位朋友读完米兰昆德拉的小说《生命不能承受之轻》之后概括说,逃避责任就会带来轻松,可那恰恰就是“生命不能承受之轻”啊! 这种大师的境界,正是所谓的遥不可及,仰之弥高,望之弥艰,钻之亦不可得。知易行难啊。 基于过程的记录要比基于结果的记录只会更为详尽。 焦虑本身没有任何好处,只能带来负面影响 柳比歇夫肯定形成了一种特殊的时间感。在我们机体深处滴答滴答走着的生物表,在他身上已成为一种感觉兼知觉器官。 一度确实可能的“随心所欲”只不过是幼年时的真实,少年时幻想,成年时的苦恼,老年时的绝望。 把每天的时间开销记录下来,一方面可以培养自己的成就感,另外一方面可以避免轻易地原谅自己。每天晚上睡觉前,看着自己的本子,发现今天做了很多事儿的话,一定会很开心。 第一个良好的习惯: 每天记录你的时间开销。如果你已经养成了这个习惯,那么,第二个良好的习惯就几乎是自然而然、合情合理的了。 这个良好的习惯是: 每天制作你的时间预算。 有意识地控制自己不要去做那些没有实际意义的事情——那是在浪费时间。 判断一件事情是否真的重要的标准只有一个:是否对你的目标(无论是长期,还是短期)的实现有益。 我从来都不相信人人都能成功之类的话,我顶多相信“其实人人原本都有可能成功”。 有一句话曾令我印象深刻,说,“失败只有一种,就是半途而废。” 我还是觉得,无论变化多块,计划总应该是有的。只不过,制定计划的时候,应该考虑到变化。 所谓千里之行始于足下,我们要做的事情是把每一步都走好,踩得足够踏实。至于千里之外的终点,既然看都看不到,就不用花时间去想了,想了也没用。用各种方法保持乐观就好——乐观是靠努力和挣扎才可以获得的经验。 无论计划简单还是复杂,缺乏切实的行动就注定会失败或者失效。 获得知识的基本途径 在“试错”这个手段的基础上,另外一个“聪明”一点的,也重要得多的获取知识的方式是“观察”。 信息爆炸使得我们处于人类历史上进步最为惊人的时代,日新月异这个词已经不够——用“分新秒异”都不过分。 不夸张地讲,今天的本科教育很大程度上干脆就是忘了本。 我个人的建议是去自学一门计算机编程语言。因为,关于计算机编程语言的文档,互联网上有最广泛最全面的资源。并且,优秀的资源往往是英文文档,所以,顺带还练习了英文阅读理解能力——想想吧,肯定不仅仅是一举两得。 然而,于学生来讲,更重要的是避开另外一个陷阱——不要因为讨厌老师而拒绝学习。这是最常见的现象之一。很多学生,仅仅因为讨厌英语老师,就开始失去学习英语的兴趣。按照正态分布的规律来看,在某一个阶段里,你必然只能遇到一两位好的老师,以及许多平庸的老师,和那么一两位甚至可能令人生厌的老师——相信我,他们也恨你,如你恨他们的话,每个人在这方面都非常敏感。 “无论如何,都不要也不应该用别人的错误惩罚自己,那么做不仅不对,并且愚蠢。” 他希望的是得到一个灵丹妙药,就着一杯凉白开灌下去之后就从此与众不同、焕然一新、重新做人。 见识越少的人越喜欢用自己所有的见识作为判断依据,并且完全不顾自己见识的局限,也不知道自己的见识有局限。 当然,直到今天也不得不时时挣扎——经常需要静下来独自一人“狠斗私字一闪念”。 不要抱怨——反正抱怨没用。抱怨最浪费时间,即便抱怨得正确。举个极端的例子。如果这个社会确实不公平,你要是抱怨一下当然没什么不对的。可是,抱怨不仅要花费时间,还会引发负面情绪,使你丧失斗志。同时,仅仅抱怨本身不会改变任何事实。与其浪费时间抱怨不公平,还不如花时间做些真正能够改变点什么的努力。 人人都能成功,你是否相信 在我们生存的这个世界里,资源稀缺是客观现实,也恰恰因此,人们的主观愿望肯定不可能全部被满足。 现代西方经济学缘起于亚当·斯密(AdamSmith,1723~1790)的学说,经过大卫·李嘉图(DavidRicardo,1772~1823 年)的补充,直至约翰·梅纳德·凯恩斯(JohnMaynardKeynes,1883~1946)才算是彻底正视资源的稀缺性,认为经济学的根本目的在于研究“如何运用有限的资源发挥最大的效用” 所谓成功,就是达成预期目标。 生活无法彻底回避比较,但是,事实上无需比较就可以获得的欢乐和幸福也确实太多太多,只不过常常被人们忽略。 如果一个人是正确的,他的世界就会是正确的。 富兰克·H·奈特(Frank H. Knight[插图])有个著名的观点:“决定一个人富有的三个条件,一是出身,二是运气,三是努力,而这三者之中,努力是最微不足道的。”这是个严肃而重要、需要耗费大量时间与精力才能通过认真思考而理解并接受的道理:无论你多努力,你都很有可能完全没有机会做到富有富足——你有勇气接受这样的现实么? ……我知道我是有来历的。走在芸芸众生当中,这种感觉尤为强烈…… 接受自己与别人没什么不同,至少没什么本质性上的不同,是心智正常成长的重要前提之一。所以,我常常这样告诉自己“你并不孤独。” 电影之所以精彩,有两个原因,首先是艺术加工,其次是时间段上的压缩——把一生的故事用一个半小时讲出来,不波澜起伏,惊险刺激才怪。 崔健是这样描述的:“为了爱情,歌曲算个屁;为了生命,爱情算个屁。”大实话往往没办法动听。其实,这并不是玩世不恭或者愤世嫉俗的说法,只不过是有勇气接受现实的人对生活的平静描述。 李宗盛在《凡人歌》说,“问你何时曾看见这世界为了人们改变?”听得我心惊胆颤。 爱默森(Ralph Waldo Emerson)说,“弱者相信运气,强者只究因果。” 另外,还有特别好玩的心理学现象。如果,你相信好运气,你的生活并不会因此就会变得更好或者更差。但是,反过来,如果你就是觉得自己是个倒霉蛋,那你的生活定会因此变得更糟。所以,尽管不应该盲目乐观,但一定不能悲观地生活。 当你没有准备好的时候,对你来讲,不存在任何机会。 “量力而行”是如此高难度的行为模式 —— a.承认自己能力有限; b.不怕在别人面前露怯; c.敢于不去证明自己是“好人” ……所以说,往往只有优秀的人才拥有有效的人脉。 生活的智慧就在于,集中精力改变那些能够改变的,而把那些不能改变的暂时忽略掉。专心打造自己,把自己打造成一个优秀的人,一个有用的人,一个独立的人,比什么都重要。 当你把时间花费到一个人身上的时候,相当于在他身上倾注了你生命的一段——哪管最终的结果如何,反正,那个人那件事都成了你生命中的一部分,不管最后你喜欢还是不喜欢。每个人的时间都是有限的。所以最终,“真正的好朋友”谁都只有几个而已。 最节省时间的方法:学习 学习就是人类所有能力的延伸——可以使人们拥有更多的能力 他们因为当初不肯花费十几二十几分钟而其后一生少做了很多事情,错过了很多机会,并且,一生只有一次却都没有过好…… 合理的时间安排应该是这样的:简单的部分要迅速做完,而后把节约出来的时间投放在处理困难的部分上。 事实上,有氧运动大约20分钟之内,消耗的往往只是水分,30分钟之后才开始消耗脂肪。 反倒是资质在各方面都处于平均水平的人更不易自卑。 她的方法是在自己的语言中,把“优点”和“缺点”这两个词替换成“特点” a.人们普遍相信他们在明天会拥有比今天更多的金钱; b.人们普遍相信自己明天会有更多的时间。 万事皆可提前准备 所有的成功本质上都是一样的。先花上相当的时间和精力去锁定一个方向或者目标。确定它是现实的、可行的之后,运用心智的力量在这个方向上再投入更多的时间,再更多一点的时间。把时间当作朋友,一路前行。当时间陪伴你足够久的时候,你的耐心就能从它那里得到回报。 事实上,今天地球上所有控制巨大财富的家族都经历过“白手起家”的过程。在荒蛮的年代,“耐心”的作用可能被其他因素掩盖,但是,在比过往任何时代都高度文明的今天(尽管依然有很多地方令人失望),“耐心”已经成为最重要的、最有力量的因素。即便是古人都早就注意到“穷不过五服,富不过三代”——请注意前半句。 仔细注意就会发现,所有惊心动魄的精彩故事的主角都是充满了耐心的人,《基督山伯爵》中的爱德蒙·邓蒂斯,《肖申克的救赎》中的银行家安迪,《越狱》中的迈克尔·斯科菲尔德……这不是编故事的人胡说八道,而是,只有这样才最真实。 读苏东坡的《留侯论》中的文字,“古之所谓豪杰之士者,必有过人之节,人情有所不能忍者。匹夫见辱,拔剑而起,挺身而斗,此不足为勇也。天下有大勇者,卒然临之而不惊,无故加之而不怒,此其所挟持者甚大,而其志甚远也。” 补记 新东方动辄几百人甚至过千人的大课堂可谓全球独一无二。在大课堂上一讲就是六七年,期间学生数万,最大的经验就是:绝大多数学习上的成败与智商几乎没有任何关系,所有的失败都与且只与时间限制有关。 来自微信读书
以下观点不保证客观,仅一家之言,如存在异议,诠做笑谈。 1. 时间成本 算法工程师通常要求硕士学历,而攻读硕士的三年时间就成为了成本。互联网圈有一万小时理论,任何技术要精通,需三年的积累。如果本科毕业之后从事一个岗位三年,那么三年后已经成为行业专家了,而有些人才硕士毕业。 2. 机会成本 算法工程师的职位大多集中在一线互联网公司,创业型公司在完成一定的融资后,当产品成熟的时候才需要算法工程师,因此能提供的工作机会相比普通开发要少很多。通常一个企业没有算法工程师可以继续生存,但是没有基础开发工程师那就无法存活。而且企业里面对于基础开发工程师的需求量远大于算法工程师,二者的比率大约是十比一。 3. 生活成本 需要算法工程师的企业大多集中在一线城市,而一线城市的物价水平对于普通上班族几乎无法承受。因此,就存在这样一批人,年轻的时候在一线城市打工,等到有孩子之后,就逐渐回退到家乡城市或二线城市发展,这样的变动对于个人的影响是十分巨大的。 4. 可扩展性 算法岗位分类繁杂,NLP、图像、推荐等等,每个算法人都需要结合具体的产品方向,在一家公司某个方向上进行积累,而这些积累想要移植到其他邻域几乎是不可能的,常常需要重新开始。但普通开发则不同,在一个单位的积累都是一行一行的写出来的,一个项目一个项目的做,那都是实打实的积累,并不会因为单位业务的变化而产生大的影响。算法工程师仅仅是换一个项目组都是极大的挑战,如同换了一家公司。但这个论点的前提是普通开发不能沉迷于具体的业务而无法自拔,因此持续的学习能力就十分重要。 5. 人生更大的可能 相比普通开发,算法工程师机会更少,因此如果你喜欢折腾或者心存大志,那么普通开发将能够提供更多的可能。我们说成功是无数次的失败换取的成功,如果没有大量的机会去失败,那么成功的几率就会非常小。或许闲暇时间自己做一个网站或者app,就能很容易的上线让用户去使用,如果用户量多,那么可以独立做公司从中牟利或者树立行业威望,而做算法就只能推导数学公式,写写算法代码,就连获取模型训练数据都是一件极为困难的事情,就更谈不上从中获利了。 6. 收益率 在刚开始的几年大家都是普通上班族,从薪水上讲算法工程师略高一些,但人生要想实现财务自由,做上班族几乎是很难实现的,除非你是开复老师。要想实现跨越财务的门槛,期权、股权等是不可或缺的。一家初创公司Java开发工程师或许是前10号员工,算法工程师只能到100号左右,二者在公司能够获得的地位和收益就会存在巨大的差别。 7. 媒体的噱头 一个产品成功与否主要在于用户的需求。现今中国人的需求是什么?看看近年大火的行业和公司就知道,美团、饿了么、滴滴、ofo、摩拜。哪家公司的产品核心是算法,简简单单的送快餐、投放自行车、打车就能完爆中国,还谈什么人工智能,大家还是洗洗睡吧!而大呼人工智能的公司以算法为核心的产品哪一家成功实现落地了,至今没有。创新工场谈人工智能,它们就是个孵化器,项目能不能存活都是未知;百度大谈无人驾驶,谷歌搞了十来年都没量产,它还是放放医疗广告来的实在;天猫精灵、小米小i、苹果Siri、微软小娜和小冰,真是谁用谁知道,能够摆脱人工智障都算不错了。媒体谈这些需要吸引大众眼球;创业者谈这些需要问投资人要钱;企业谈这些要提升逼格拉升股价。 8. 深度学习 它当然是很牛的,效果提升也是显著的,但是它的出现只是促使人们在追求人工智能的道路上更前进了一步,但它并不等于人工智能。 各位,洗洗睡吧! p.s.
12 Integer to Roman 13 Roman to Integer 有可能不注意的结果: class Solution { public: /* 1、相同的数字连写,所表示的数等于这些数字相加得到的数,如:Ⅲ = 3; 2、小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数, 如:Ⅷ = 8;Ⅻ = 12; 3、小的数字,(限于Ⅰ、X 和C)在大的数字的左边,所表示的数等于大数减小数得到的数,如:Ⅳ= 4;Ⅸ= 9; 4、正常使用时,连写的数字重复不得超过三次。(表盘上的四点钟“IIII”例外) 5、在一个数的上面画一条横线,表示这个数扩大1000倍。 */ int romanToInt(string s) { int res=0; int lastValue=0; int digit; for(int i=s.size()-1;i>=0;i--){ switch(s[i]){ case 'I': digit=1; break; case 'V': digit=5; break; case 'X': digit=10; break; case 'L': digit=50; break; case 'C': digit=100; break; case 'D': digit=500; break; case 'M': digit=1000; break; } if(digit>=lastValue){ res+=digit; lastValue=digit; } else res-=digit; } return res; } }; 罗马数字是阿拉伯数字传入之前使用的一种数码。罗马数字采用七个罗马字母作数字、即Ⅰ(1)、X(10)、C(100)、M(1000)、V(5)、L(50)、D(500)。记数的方法: 1. 相同的数字连写,所表示的数等于这些数字相加得到的数,如 Ⅲ=3; 2. 小的数字在大的数字的右边,所表示的数等于这些数字相加得到的数,如 Ⅷ=8、Ⅻ=12; 3. 小的数字(限于 Ⅰ、X 和 C)在大的数字的左边,所表示的数等于大数减小数得到的数,如 Ⅳ=4、Ⅸ=9; 4. 在一个数的上面画一条横线,表示这个数增值 1,000 倍,如 5. string intToRoman(int num) { string table[4][10] = {{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}, {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}, {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}, {"", "M", "MM", "MMM"} }; string result; int count = 0; while(num > 0){ int temp = num % 10; result = table[count][temp] + result; num /= 10; count++; } return result; } The basic idea is really simple: replace every digit in num by roman numerals. For example, we have a num: 2438. 2 –> “MM” 4 –> “CD” 3 –> “XXX” 8 –> “VIII” Then the result is “MMCDXXXVIII”. M = ["", "M", "MM", "MMM"]; C = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]; X = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]; I = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]; return M[num/1000] + C[(num%1000)/100] + X[(num%100)/10] + I[num%10]; Simple C solution 16ms ● 0 L ljbschen Reputation: 10 void helper (char* ans, int* curp, int step, int one, int five, int ten) { char ch[]={'I','V','X','L','C','D','M'}; int offset=0; if (step==1000) offset=6; else if (step==100) offset=4; else if (step==10) offset=2; else offset=0; if (one==-1) ans[(*curp)++] = ch[offset]; if (ten==1) ans[(*curp)++] = ch[offset+2]; if (five==1) ans[(*curp)++] = ch[offset+1]; while (one-->0) ans[(*curp)++] = ch[offset]; } char* intToRoman(int num) { char *ans = malloc(sizeof(char)*16); int i=0, step=1000, digit=0; int one=0,five=0,ten=0; int curp=0; while (num>0) { digit=num/step; if (digit==9) {one=-1;five=0;ten=1;} else if (digit>=5) {one=digit-5;five=1;ten=0;} else if (digit==4) {one=-1;five=1;ten=0;} else if (digit==0) {one=0;five=0;ten=0;} else {one=digit;five=0;ten=0;} helper(ans,&curp,step,one,five,ten); num-=digit*step; step/=10; } ans[curp]='\0'; return ans; } 《架构大数据—-大数据技术及算法解析》 绪论 2013年被称为大数据元年,据IDC预测,到2020年全球将拥有35ZB(1ZB = 1021字节)的数据,大数据涉及国家战略、区域及企业发展、社会民生的方方面面,掌握大数据的核心概念、模式和技术,就把握了新时代的脉搏。 1.大数据技术概述 1.1大数据的概念 大数据指的是无法在规定时间内用现有的常规软件工具对其内容进行抓取、管理和处理的数据集合。 大数据技术则特指新一代的创新型的技术,能够突破常规软件的限制,是对大数据进行采集、存储和处理的技术的统称。 大数据(BigData)一词正式出现是在2011年麦肯锡全球研究院发布的《大数据:下一个创新、竞争和生产力的前沿》研究报告中。 大数据的4个根本特征: 1.Volume,数据量足够大 2.Variety,数据的种类多样 3.Velocity,数据的增长及处理速度快 4.Value,数据蕴藏价值大 1.2 大数据的行业价值 1.分析用户行为,建立数据模型,并进行预测 WalMart将尿不湿和啤酒摆放在一起的销售策略。 2.提升企业的资产管理,优化企业的业务流程 UPS通过在货车上安装传感器,优化行车路线,2011年,其驾驶员少跑了将近4828万千米的路程。 3.大数据服务智慧城市,智慧交通 智能电表,升级智能电网,由原来的数据库架构升级为HBase,使用Hive进行相关的统计分析。 4.变革公共医疗卫生,对疾病进行预测 Google 的Flurend,百度的疾病预测 5.在金融行业利用大数据进行战略决策和精准营销 6.利用大数据保障公共安全 7.利用大数据促进教育行业变革 8.大数据在改善着每个人的生活 p.s.
很久没有更新图形图像处理方面的博客了,最近在培训数据发掘方面的技术,就把学到的东西和大家分享下。 1. 压箱底的资料 还有一些平时收集 的压箱底的资料拿出来和大家分享下: IPOL —-经典计算机视觉算法的c实现 http://www.ipol.im/?utm_source=doi https://www.codecademy.com/ —-编程语言自学成才 我的python就是在这个网站自学的,基本上把python的基本数据结构,list,dist等等介绍了一遍,只要一周左右甚至更短的时间就可以基本掌握一门全新的语言 在线处理网站 https://www.processon.com/ 如果没有visio这是最好的选择! 一些大牛的博客 刘未鹏 http://mindhacks.cn/ http://mindhacks.cn/2011/11/04/how-to-interview-a-person-for-two-years/ http://mindhacks.cn/2012/08/27/modern-cpp-practices/ 写技术博客的选择 在csdn耕耘了8年有余,一直很喜欢这里,然而不知道为何身边的朋友高手就是github,stackoverflow 后面我也准备尝试一些其他的平台 知乎:https://www.zhihu.com/people/wynshiter/activities https://cn.wordpress.org/ 简书: https://pages.github.com/ 2. Transwarp 机器学习培训 转型机器学习方向的过程是痛苦的。最近在上海花了一周时间参加transwarp的数据分析师培训,这是我时隔7年之后再次踏上魔都的土地。上次来这里还是7年前来看上海世博会,不同的是此处上海之行是由北京启程。我乘坐的是最早一班复兴号列车,由于很多外国人都在新奇 的拍照,这一路风驰电掣的路过祖国的大好河山,让我也怀揣着满满的民族自豪感惊异于祖国 的发展速度。2010年那会来上海一趟多难呀,尤其要买个卧铺,真是难上加难。 非常感谢单位领导给予的宝贵培训机会,之前说实话并未有全面系统的学过机器学习内容。但最重要的还是不知道:真实,工业级,业务上究竟怎么开展机器学习与业务的结合工作。这次培训基本给了我答案。transwarp 通过 推荐其支持托拉拽的机器学习产品sophon,让我直观的感受了整套机器学习工具平台的使用过程,以及机器学习模型的建模套路。其中之前我一直不太注重的有以下两点: 1.特征工程,归一化,字符串索引 2.评价指标,roc,方差和等 机器学习的算法 到底如何衡量业务是否需要机器学习? 业务问题是否适用机器学习算法? 如何选择模型 设计开发节奏 最终产品的检验 完整的数据发掘建模流程 特征工程 特征工程是机器学习的决定性因素,是机器学习成功的关键 “数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已” 纵观Kaggle、KDD,阿里天池等国内外大大小小的比赛,每个竞赛的冠军其实 并没有用到很高深的算法,大多数都是在特征工程这个环节做出了出色的工作, 然后使用一些常见的算法,比如Linear Regression(线性回归),就能得到出色的 性能。 领域特定知识( domain specific knowledge), 最近还看到公众号上面一些好的文章,整取领悟以后分享出来 可视化托拉拽机器学习产品 KNIME https://www.knime.com/ 对于机器学习和数据科学的初学者来说,最大的挑战之一是需要同时学习太多知识,特别是如果你不知道如何编码。你需要快速地适应线性代数、统计以及其他数学概念,并学习如何编码它们,对于新用户来说,这可能会有点难以承受。 如果你没有编码的背景并且发现很难学习下去,这时你可以用一个GUI驱动的工具来学习数据科学。当你刚开始学习的时候,可以集中精力学习实际的项目。一旦适应了基本的概念,你就可以在以后慢慢学习如何编写代码。 在今天的文章中,我将介绍一个基于GUI的工具:KNIME sophon 星环还通过Transwarp Sophon来帮助数据工程师开发数据挖掘的应用。Sophon提供了可视化界面工具Midas 用于创建模型,用户只需通过拖拽数据源对象和运算符就能完成模型设计,然后将设计的模型在TDH集群上训 练或预测分析。 此外,Sophon还整合了深度学习框架Tensorflow,使用户可以通过拖拽生成各种神经网络模型,灵活调参和训练,将大数据和人工智能结合起来推动业务创新。 未完待续。。。。
看到一副图片挺有意思,放在片头 序 “傍晚小街路面上沁出微雨后的湿润,和煦的西风吹来,抬头看看天边的晚霞,嗯明天又是一个好天气。走到水果摊旁,挑了个根蒂蜷缩、敲起来声音浊响的青绿西瓜,一边满心期待着皮薄肉厚瓤甜的爽落感,一边愉快地想着,这学期狠下了工夫,基础概念弄得很清楚,算法作业也是信手拈来,这门课成绩一定差不了!” 上面的经验是靠我们人类自身完成的,计算机能帮忙么?机器学习正是这样一门学科,它致力于研究如何通过计算的手段,利用经验来改善系统自身的性能。 现在各行各业强调使用大数据手段进行数据分析,大数据的上帝视角带给我们的核心竞争力是对于个体甚至群体行为的预测,那么我们就来看看使用回归类算法对于数值型的数据如何来进行预测 什么是回归? 优点:结果易于理解,计算上不复杂。 缺点:对非线性的数据拟合不好。 适用数据类型:数值型和标称型数据。 使用算法:使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅是离散的类别标签 回归的一般方法: (1)收集数据:采用任意方法收集数据; (2)准备数据:回归需要数值型数据,标称型数据将被转换成二值型数据; (3)分析数据:绘出数据的可视化二维图,有助于对数据做出理解和分析。在采用缩减法求得新回归系数后,可以将新拟合线绘在图上进行对比; (4)训练算法:找到回归系数; (5)测试算法:使用R2(相关系数的平方)或顶测值和数据的拟合度,来分析模型的效果; 使用算法:使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测出连续型数据而不仅仅是离散型的类别标签 原理简介 普通最小二乘法(ordinary least squares) 问题:如何知道sklearn拟合公式的参数结果是多少y=ax+b怎么知道a,b? # 线性回归(Linear regression)是利用称为线性回归方程的最小二乘函数(最小化误差平方和)对一个或多个自变量和因变量之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。只有一个自变量的情况称为简单回归,大于一个自变量情况的叫做多元回归。 典型业务场景 假设一路公交,在其始发站每小时会来很多人等车,坐车人数会和很多因素相关(天气,是否节假日)。 为了方便调度人员预测下一个小时,或者当天的坐车人数,可以采用回归算法制作基于时间的预测系统。 可能要有的功能 1.出现异常增量时候的预警,异常增量,概念的定义。 2.预测值和真实值的差别 数据准备 history 表中记录了所有公交卡历史记录 建表语句,从已经采集的数据中构建,主要为两列 create table t_hour_count ( quantity varchar2(128) time_frame varchar2(128) ); 其中人的主要标识为公交卡(id),我们从公交卡的记录表history中将每小时坐车的人筛选出来,由于只要数量,所以只要group_by之后再 去重再count create table t_hour_count as select count(distinct ta.id) quantity, group_by time_frame from (select tt.*, to_char(tt.update_time, 'yyyymmddhh24') group_by from (select * from history where id in (select distinct id from t_公交卡 tc where tc.type = '公交')) tt where update_time > to_date('20170716 00:00:00', 'yyyymmdd hh24:ss:mi') and update_time < to_date('20170721 00:00:00', 'yyyymmdd hh24:ss:mi')) ta group by ta.group_by order by group_by; 参考代码 python链接oracle 的简单框架 #coding = utf-8 import cx_Oracle import time import json import os os.environ['NLS_LANG']='SIMPLIFIED CHINESE_CHINA.ZHS16GBK' #-----------------------connect oracle------------------------------- username = '**' password = '**' ip = '*.*.*.*' service_name = '*' def getConnOracle(username,password,ip,service_name): try: conn = cx_Oracle.connect(username+'/'+password+'@'+ip+'/'+service_name) # 连接数据库 return conn except Exception: print(Exception) conn = getConnOracle(username, password, ip, service_name) def getOracleSelect(conn): cursor = conn.cursor() try: sqlString = "select time_frame,quantity from t_hour_count order by time_frame" sqlresult = cursor.execute(sqlString) # 使用cursor进行各种操作 result = sqlresult.fetchall() return result except cx_Oracle.DatabaseError as msg: print(msg) finally: cursor.close() #---------------------------------- 自定义数据指标统计 计算一段时间的均值,最大,最小等指标 #---------------------------------------------------------------------------------------------- def my_average(result_list = []): sumvalue = 0 if len(result_list)==0: return 0 for i in result_list: sumvalue = i[1] + sumvalue return int(sumvalue/len(result_list)) #add 'my' to declare this function is user-defined def my_min(result_list = []): if len(result_list)==0: return 0 valuelist = [i[1] for i in result_list] return min(valuelist) def my_max(result_list = []): if len(result_list)==0: return 0 valuelist = [i[1] for i in result_list] return max(valuelist) def generateAllresult(): localtime = time.localtime() all_result = [list(i) for i in getOracleSelect(conn)] all_result_time = [[(time.strptime(i[0],"%Y%m%d%H")),i[1]] for i in all_result] all_result_time_today = [i for i in all_result_time if i[0].tm_yday > localtime.tm_yday-1] all_result_time_yesterday = [i for i in all_result_time if i[0].tm_yday < localtime.tm_yday and i[0].tm_yday > localtime.tm_yday-2] all_result_time_thedaybeforeyesterday = [i for i in all_result_time if i[0].tm_yday < localtime.tm_yday-1 and i[0].tm_yday > localtime.tm_yday-3] all_result_time_last3day = [i for i in all_result_time if i[0].tm_yday < localtime.tm_yday and i[0].tm_yday > localtime.tm_yday-4] all_result_time_last7day = [i for i in all_result_time if i[0].tm_yday < localtime.tm_yday and i[0].tm_yday > localtime.tm_yday-8] all_result_time_lastweekthisday = [i for i in all_result_time if i[0].tm_yday < localtime.tm_yday-6 and i[0].tm_yday > localtime.tm_yday-8] my_dict = {"all_result":all_result_time,"today":all_result_time_today,"yesterday":all_result_time_yesterday,"before_yesterday":all_result_time_thedaybeforeyesterday,"last3day":all_result_time_last3day,"last7day":all_result_time_last7day,"lastweekthisday":all_result_time_lastweekthisday} my_result_dict = {} for item in my_dict: #print(my_dict[item]) #print(len(my_dict[item])) my_result_dict[item] = [my_average(my_dict[item]),my_max(my_dict[item]),my_min(my_dict[item])] #print(my_result_dict) return my_result_dict Flask页面展示 还有一个3js需要下载 整个项目的目录结果如下图所示: 在windows上cmd中居然也有tree命令,使用tree /f显示如下结构: 页面html: <title xmlns="http://www.w3.org/1999/html">monitor.com</title> <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Highstock Example</title> <script type="text/javascript" src="../static/js/jquery-1.8.2.min.js"></script> <style type="text/css"> ${demo.css} </style> <script type="text/javascript"> $(function () { $.getJSON('/data?callback=?', function (data) { // Create the chart $('#container').highcharts('StockChart', { rangeSelector: { inputEnabled: $('#container').width() > 480, selected: 1 }, title: { text: '人数情况小时统计' }, series: [{ name: '人数情况小时统计', data: data, type: 'spline', tooltip: { valueDecimals: 2 } }] }); }); }); </script> </head> <p> <script src="../static/js/2.js"></script> <script src="../static/js/3.js"></script> <div id="container" style="height: 400px"></div> <script type="text/javascript"> $(function () { $.getJSON('/predict?callback=?', function (data) { }); }); </script> <p>{{"统计概况"}}</p> <table border="1"> <tr> <td>日期</td> <td> 平均值 </td> <td>最大值</td> <td>最小值</td> </tr> {%for key in mydict%} <tr> <td>{{key}}</td> <td>{{mydict[key][0]}}</td> <td>{{mydict[key][1]}}</td> <td>{{mydict[key][2]}}</td> </tr> {%endfor%} </table> <img src="../static/sample.png" width="640" height="480"> </body> </html> from flask import Flask, request, render_template app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def hello(): if request.method == "GET": today_Regression() return render_template("mon.html",mydict=generateAllresult()) else: return "post method is not define" @app.route("/data", methods=["GET"]) def getdata(): #today_Regression() ones = [[(time.strptime(i[0],"%Y%m%d%H")), i[1]] for i in getOracleSelect(conn)] ones = [[time.mktime(i[0])*1000+28800000,i[1]] for i in ones] return "%s(%s);" % (request.args.get('callback'), json.dumps(ones)) ''' ''' sklearn 回归预测 #-------------------------------------------------------------------------------------- from sklearn import linear_model import numpy as np import matplotlib.pyplot as plt import pandas as pd #dataset = pd.read_csv('CSV.csv') #x is time ,y is people like [[1],[2],[3]] def my_Regression(X_array=[[]],Y_array=[[]]): X = np.array(X_array) y = np.array(Y_array) print (type(X)) from sklearn.preprocessing import PolynomialFeatures poly_reg = PolynomialFeatures(degree = 2) X_poly = poly_reg.fit_transform(X) lin_reg_2 = linear_model.LinearRegression() lin_reg_2.fit(X_poly,y) print(lin_reg_2.intercept_) X_grid = np.arange(min(X),max(X),0.1) X_grid = X_grid.reshape((len(X_grid),1)) plt.scatter(X,y,color = 'red') plt.plot(X_grid,lin_reg_2.predict(poly_reg.fit_transform(X_grid)),color = 'blue') plt.title('predict(2-Polynomial Regression)') plt.xlabel('time') plt.ylabel('people count') plt.savefig("./static/sample.png",dpi=200) #plt.show() def today_Regression(): localtime = time.localtime() all_result = [list(i) for i in getOracleSelect(conn)] all_result_time = [[(time.strptime(i[0],"%Y%m%d%H")),i[1]] for i in all_result] all_result_time_today = [i for i in all_result_time if i[0].tm_yday > localtime.tm_yday-1] X_array = [[time.mktime(i[0])] for i in all_result_time if i[0].tm_yday > localtime.tm_yday-1] Y_array = [i[1] for i in all_result_time_today] my_Regression(X_array,Y_array) if __name__ =='__main__': app.run(host="0.0.0.0", port=55555, debug=True) 使用一天的数据绘制一个二次函数,保存到本地作为一张图片 但是sklearn怎么输出二次函数的参数呢,我一直没有找到 未完待续,将来将这个小项目共享出来 js文件下载地址: 1: http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js 2:2.js http://cdnjs.cloudflare.com/ajax/libs/highstock/2.0.4/highstock.js 3:3.js http://code.highcharts.com/modules/exporting.js 大数据框架下的回归预测 官方文档 http://spark.apache.org/docs/latest/ml-classification-regression.html#regression 中文翻译 http://www.apache.wiki/display/Spark/ML+Pipelines python接口: http://spark.apache.org/docs/latest/api/python/pyspark.ml.html#module-pyspark.ml.regression spark mllib 全面介绍: http://www.cnblogs.com/shishanyuan/p/4747761.html python实现: http://www.cnblogs.com/adienhsuan/p/5654481.html 学习笔记: http://www.cnblogs.com/charlotte77/p/5518368.html 参考文献 机器学习实战,第八章 周志华,机器学习
绪论 计算机视觉是一门通过研究使用计算机来模拟人的视觉系统的学科。“一图胜千言”,人类对于图像中的信息感知效率远超文字等其他媒介,人类获取的信息总量中更是有高达80%依靠视觉系统[1]。相对于人类高效的图像信息提取能力,计算机在图像信息的理解上仍然效率低下。 计算机视觉作为一门交叉学科,综合了生物学,心理学,数学,计算机科学等学科,从20世纪60年代至今其在科学研究领域中的大量成果已经应用于工程领域,并影响了我们每个人生活的方方面面。 双目立体视觉是计算机视觉领域的重要分支,它通过模拟人的双眼视觉系统来处理现实世界。以机器人,无人汽车导航为例,由于双目立体匹配在非接触测量中的优秀性能,视觉测量在探月工程,火星探测工程中起到了重要作用[2],如图所示的我国嫦娥探月工程的巡航车就配备了立体视觉导航系统如图1-1,来进行行进间的运动控制和路径规划[3]。 图1-1 嫦娥三号巡航车立体视觉系统[4] Figure 1-1 Stereo Cameras equipped on the lunar rover 1.1 研究背景与意义 立体匹配是一种从平面图像中恢复深度信息的技术。由于双目立体匹配系统通过模拟人眼视觉感知原理,仅需要两台数字摄像机安装在同一水平线上,经过立体矫正就可以投入使用。具有实现简单,成本低廉,并且可以在非接触条件下测量距离等优点。在机器人制导系统中可以用于导航判断、目标拾取,在工业自动化控制系统中可用于零部件安装、质量检测,环境检测,在安防监控系统中可用于人流检测,危害报警。 近年来,随着社会的科技进步,立体匹配技术的发展日新月异,随着匹配算法精度与速度的提高,其应用场景进一步扩大。在此背景下,研究立体匹配变的意义非凡。 立体匹配作为三维重建、立体导航、非接触测距等技术的关键步骤通过匹配两幅或者多幅图像来获取深度信息。并且广泛应用于,工业生产自动化、流水线控制、无人驾驶汽车(测距,导航)、安防监控、遥感图像分析、机器人智能控制等方面。虽然立体匹配应用广泛但是还有很多尚未解决的难题因此该技术成为了近年来计算机视觉领域广泛关注的难点和热点。 立体匹配作为一种工程化问题,在实施过程中有多种因素影响其精度与速度,并没有一种复杂算法可以完整的处理立体匹配的整个流程,本文所述算法主要针对立体匹配中图像像素匹配并计算视差这一核心步骤。 通常根据立体匹配算法所采用的约束,可以将其分为两大类算法[5]:第一类为基于区域约束的局部匹配算法。如采用匹配窗的代价聚合算法(平方差算法SSD,绝对差算法SAD,归一化算法NCC等);采用特征点的匹配算法;采用相位匹配的的匹配算法。这些算法的优点是算法整体的计算量小,能够快速恢复出纹理丰富区域的视差。缺点是在低纹理区域会造成误匹配[5],得到的视差图不致密,需要在后期通过插值算法来进行修正。第二类为基于全局约束的优化算法,如图割算法(Graph Cuts, GC),人工智能算法(如遗传算法,多层神经网络),置信传播算法(Belief Propagation, BP),动态规划算法(Dynamic Programming, DP)。这些算法虽然运算时间较长并且会产生一些误匹配,但是基本上能够获得所有的视差信息从而生成稠密的视差图。 1.2 国内外研究现状 国外在计算机立体视觉上的研究开展较早,Roy[7]最早将图割算法应用于立体匹配,并通过实验表明,图割算法能有效克服其他全局优化算法的缺点(如动态规划算法等生成视差图产生的横向条纹瑕疵),避免了视差在临近极线处不连续的问题。但该算法生成的视差图轮廓边缘模糊,视差层的区分度低。Geiger等[8],针对高分辨率图像立体匹配运算时间长的问题,创造性的提出了使用强约束点(纹理或特征信息较为丰富)作为支撑点,在强约束点之间通过三角剖分对视差图进行插值计算,结合OpenMP技术在通用CPU上实现了并行计算,操作简单易于搭建环境,在通用微型计算机上实现了实时立体匹配,但是匹配效果和基于全局优化的匹配算法有一定差距。 国内对于立体视觉的研究起步较晚,早期主要采用基于特征点匹配的方法,随着技术的进步,后序对立体匹配的改进工作主要集中在对全局优化算法性能和准确度的提升上。其中大部分方法采用对待匹配图像进行图像分割后,再结合能量最优化的方法进行立体匹配。如尹等[9]首先采用均值平移算法将参考图像根据颜色信息快速聚类;之后计算初始视差图;最后采用图割算法并将分割结果作为独立的一项计算能量函数最小值。此种基于图像分割的立体匹配方法的理论基础认为,分割区域块内的视差变化是平滑的。因此与其他基于图像分割的立体匹配算法相比,此类算法[9]可有效地处理大块低纹理区域,匹配精度高,更有利于估计视差图的边界。并且上述算法通过分割减少了匹配基元,使得运算速度更快,能够很好的解决的边界模糊和低纹理区域的误匹配问题。 立体匹配技术在工程领域的应用十分广泛,比如在我国嫦娥探月工程中,王等[4]改进了勇气号机遇号火星车复杂的定位技术,对嫦娥3号月面巡航器的视觉导航系统,采用SIFT(scale-invariant feature transform) 匹配、相关系数匹配、最小二乘匹配和光束法平差等多项技术融合,实现了相邻站间月面巡视器的导航定位。实验表明该系统视觉定位相对精度优于4%。 朱[10]使用双目立体视觉的方法进行工件的自动定位、识别与抓取,首先根据采集到图像的SIFT特征,从SIFT特征集合模板中匹配工件进行识别。其次,去除噪声后对图像进行二值化并获取质心坐标进行定位。最后,结合双目立体视觉标定技术,使用最小二乘法求得工件质心的世界坐标,为机器人抓取工件提供信息。 顾等[11]为实现统计实时人流,提出一种基于立体视觉的人头检测算法。该方法对双目相机采集的图像通过运动目标检测分离出运动人员所在区域,利用视差的连续性只对强纹理点进行绝对误差累积(SAD)匹配,其余点只进行视差验证,因此能够得到稠密的视差图,再由三角投影关系计算出深度图。由于双目立体成像得到的深度图中人员与场景的深度分布不同,采用深度分层的方法将存在人头信息的深度层提取出来,并通过几何形态来确定人的头部,该算法可以很好地适应复杂场景下的人头检测,并且由于采用了基于局部优化的匹配算法结合插值计算等手段所以其在精度、速度上都有很好的实时特性。 Yang等[12]提出了基于最小生成树的代价聚合方案,采用像素间的相似性作为边的权值,通过无向连通图构建最小生成树,使得局部像素点获取了全局的信息。该算法对于低纹理,无纹理,光照变化等区域的匹配效果达到甚至超越了全局优化算法的水平。针对采集的待匹配图像可能带有噪声或者复杂纹理的问题,Yang等在上述算法的基础上进行了系统化的流程设计与改进[13],利用左右交叉检验精确更新代价聚合中稳定和不稳定的点的代价,提升了算法精度。 近年来立体匹配技术的实时性需求日益高涨,随着硬件的发展,立体匹配围绕如何快速获取稠密视差图以及将匹配算法并行化进行了卓有成效的研究,通过将图像分割替换为运算效率更高的保边滤波器,Yang等[14],利用双边滤波器性质并加以改进,融合并行计算技术,达到了平均每秒60帧左右的匹配结果。Qingqing Yang等[15]针对另一种保边滤波器——导向滤波,提出了新的代价聚合方案,采用自适应窗口融合滤波进行代价聚合,在局部匹配算法中取得了不错的精度,并且该算法经过并行化处理后可以在GPU上进行运算,相对于CPU取得了平均30倍的加速[16]。 1.3本文研究内容 本文针对双目立体视觉中基于图论的立体匹配算法进行研究。主要研究以下几个方面的内容: 1.通过研究立体匹配算法的发展历史和分类准则,在对前人文献阅读的基础上根据文献的实验结果评价算法质量,从中选取一到两个有意义且实用性强的研究切入点。 2.研究图割算法及其应用,主要包括:基本思想,算法流程,能量函数的构造,以及能量最小化等相关内容。 3.根据对立体匹配应用场景的分析,本文提出一种基于交互式图像分割的立体匹配方法。针对大多数场景中只需计算特定目标视差的需求,通过有效利用网络图先后完成了图像分割和立体匹配的工作,降低了算法的空间复杂度和内存占用,并且减少了运算时间。 4.针对全局优化算法运算时间缓慢,局部优化算法结果不精确的问题。本文利用最小生成树取代局部优化算法的匹配窗,为匹配基元添加了全局特性,并通过OpenMP,SIMD等技术在通用处理器上对算法进行扩展优化,达到了实时计算的标准。 5.介绍了本文算法试验系统的软硬件配置,阐述并详细分析了实验结果,对双目立体视觉未来的研究方向,研究热点进行了预测与展望。 1.4论文结构 本文针对上述研究内容,共包含五章: 第一章:绪论,介绍了研究立体匹配的背景与意义,根据国内外文献的实验结果和原理阐述各类立体匹配方法的优缺点,总结本文的基于图论的立体匹配算法研究内容,描述文章框架。 第二章:立体匹配基础,首先介绍了双目视觉的基础理论,立体匹配的基础——视差理论。其次介绍了立体匹配算法设计中需要注意的各类约束,主要包括:极线,相容性,唯一性,连续性等约束。最后介绍了立体匹配的几种主要算法,立体匹配后处理中遮挡问题的处理。 第三章:基于交互式图像分割的立体匹配方法,提出了一种基于图割算法的立体匹配方法,其流程充分利用了网络图资源,有效降低了内存占用提高了算法运行时间。本章主要介绍了图割算法中的网络流问题,能量函数的优化问题。详细介绍了图割算法中立体匹配网络图的构造,基于图割算法的交互式图像分割与立体匹配的融合过程。介绍了本章算法的优秀效果,基于Middlebury学院提供的标准图像库对原始图割算法进行了对比实验,并给出了分析。 第四章:基于最小生成树的实时立体匹配算法,针对带有非局部特性的立体匹配方法,根据局部算法特性,将匹配窗的代价聚合方式替换为拥有全局特性的树形结构的代价聚合,介绍了在树形结构上进行代价聚合的两种方式,提出了一种可移植的通用并行化加速扩展方案,通过此方法将原有串行算法的运算速度提升为实时运算。 第五章:总结与展望,对本文工作进行了总结,展望了立体匹配算法今后的发展方向。 参考文献 马颂德,张正友. 计算机视觉—计算理论与算法基础[M].北京:科学出版社,1997. 邸凯昌. 勇气号和机遇号火星车定位方法评述[J]. 航天器工程, 2009, 18(5):1-5. 吴伟仁, 王大轶, 邢琰,等. 月球车巡视探测的双目视觉里程算法与实验研究[J]. 中国科学:信息科学, 2011(12):1415-1422. 王保丰, 周建亮, 唐歌实,等. 嫦娥三号巡视器视觉定位方法[J]. 中国科学:信息科学, 2014, 04期(04):452-460. 白明, 庄严, 王伟. 双目立体匹配算法的研究与进展[J]. 控制与决策, 2008, 23(7):721-729. 张令涛, 曲道奎, 徐方. 一种基于图割的改进立体匹配算法[J]. 机器人, 2010, 32(1):104-108. Roy S, Cox I J. A maximum-flow formulation of the n-camera stereo correspondence problem[A]// IEEE International Conference on Computer Vision[A], 1998 January 4-7, Bombay India:492-499. Geiger A, Roser M, Urtasun R. Efficient large-scale stereo matching[M]//Computer Vision–ACCV 2010. Springer Berlin Heidelberg, 2011: 25-38. 尹传历, 刘冬梅, 宋建中. 改进的基于图像分割的立体匹配算法[J]. 计算机辅助设计与图形学学报, 2008, 20(6):808-812. 朱代先. 基于双目视觉的工件定位与抓取研究[J]. 计算机测量与控制, 2015, 19(1):92-94. 顾骋, 钱惟贤, 陈钱,等. 基于双目立体视觉的快速人头检测方法[J]. 中国激光, 2014(1):150-155. Yang Q. A non-local cost aggregation method for stereo matching[C]// Proceedings / CVPR, IEEE Computer Society Conference on Computer Vision and Pattern Recognition. IEEE Computer Society Conference on Computer Vision and Pattern Recognition. 2012:1402-1409. Yang Q. Stereo Matching Using Tree Filtering[J]. Pattern Analysis & Machine Intelligence IEEE Transactions on, 2015, 37(4):834-846. Yang Q. Hardware-efficient bilateral filtering for stereo matching[J]. Pattern Analysis and Machine Intelligence, IEEE Transactions on, 2014, 36(5): 1026-1032. Yang Q, Li D, Wang L, et al. Full-Image Guided Filtering for Fast Stereo Matching[J]. IEEE Signal Processing Letters, 2013, 20(3):237-240. Yang Q, Ji P, Li D, et al. Fast stereo matching using adaptive guided filtering[J]. Image and Vision Computing, 2014, 32(3): 202-211.
作者:一人 2013年9月7号,我和母亲两个人到达了西安,我一手拉着箱子,上面放着本科时候用的旧褥子被子,母亲手提着放满衣服的提包,两个人穿梭于林立的高楼当中。母亲看护行李,我办理手续,直到下午很晚的时候,才在城西送母亲坐上了回家的大巴车。就这样,开始了我的研究生求学生活。 研一开始选择导师,我没有像很多同学那样提前联系导师,因此误打误撞的选择了老L。那年,我听说老L给院办公室叮咛说要选几个男学生,因此那一届我们三个就都是男生了,后面见到高一级同门才明白为什么,原来上面是三个师姐,呵呵哒,三个青壮年啊哈哈……。接下来面临的就是选课,当我们拿着选课单找老L时,老L扶了一下眼镜笑嘻嘻的说:选这些课就是为了拿学分,顺利拿到毕业证,上课无足轻重,重点是能够发文章。我们潜意识当中那个得意的笑啊,老师的指导牢记在心,上课一句话,就是混。 老师很忙,一学期下来就只有一两次谈话,感觉老师就是“牧师”,我们就是一群羊。结果呐,研究生一年级大半的时间荒废掉了。啊,啊,啊,啊,啊,后来啊,我的肠子都悔青了。怎么荒废的?这你还问? 一直到研一第二学期期末,有一门课《模式识别》由汪茜利教授讲授,最后两堂课对我们提交的作业进行讲授,汪老师的高一级的学姐张硕(是的,记得很清楚,学姐嘛)讲解了通过使用SVD分解进行人脸识别。我很好奇内心惊呼,这都可以,这竟然可以,下课后抱着强烈的疑惑去问她,细节部分她也没能回答上来。回到实验室,我自己就开始查资料,看博客,逐渐发现《模式识别》的内容也挺有意思的,随着了解的深入很快意识到这门学科的重要性。在这之后,我通过微博关注了大量这方面的专家,每天都会看这些专家们在讨论什么,经过长时间的积累帮助甚大,现在已经成为我获取新信息的重要渠道之一了。说来也巧,那段时间有一位博士毕业,请来了清华大学的孙茂松教授,他顺道给我们做了一场报告,具体的内容我已经不大能够回忆起了,有两点记忆深刻:第一,他谈到了图灵测试,在场的大多数学生不知道,老师很是震怒,我亦感受到很大的耻辱,颇受打击;第二点,他谈到了在学术界很火热的深度学习,然而我们学院里面没有几个老师听过这个东西。当我给老L谈起深度学习,他说这个不是已经很老的东西了吗!可见学院里面的老师已经很OUT了。就在那个时候认清了自己,认清了自己所处的环境。在此之后,我就坚定自己一定要从外面获取信息,不能一味的听取导师的意见,要将视野放在整个领域,不能只局限在学院和导师的范围。 (男同学们,看来要常问师姐问题哦……) 在接下来几个月的时间里,我每天上微博,每天刷知乎,看看大家怎么学习,都在学习什么。发现机器学习的方向很是火热,恰好和自己感兴趣的模式识别很相似,就开始了自己真正的学习生活。 上斯坦福的CS299、coursera的机器学习、李航的《统计学习方法》,《机器学习实战》等一系列,在微博上知道了LeetCode,后面也花了一两个月在上面刷编程题,在后面的面试求职过程中帮助很大。 在整个过程中有以下几点比较遗憾: - 导师说我们开设的课程无足轻重,主要是发论文。在后来看来,我最大损失就是没能好好上这些课程,这些课程是整个计算机科学的基石,没有这些课程的正规训练自己的路很难走的顺利。因此在研二的时候花了大量的时间去弥补这些缺失,但也只是挽回了很小的一部分。现在依然觉得没能学到那些知识是最大的损失。 《人工智能》《机器学习》《模式识别》《计算机视觉》《随机过程》《组合数学》《图论》《数字图像处理》《小波分析》等等,现在看看,随便的一门课学精都是几十万的工作啊。 导师的方向很偏,国内也很少人研究。老师给我定的方向已经很成熟了,我很难找到新的点,唉! 感兴趣的地方没有人指导,自己也没有很好的学习方法,因此只能获取片面的信息,学习效率低下。 荒废研一,研二补,造成在发论文和找工作上吃亏。 在后面时间随着对机器学习认识的深入,逐渐认识到这个行业将会繁荣发展,那个时候对于深度学习对图像行业的变革自己也清醒的认识到了。但是窘迫于自己的功力不足,只能先找到相符的方向之后,再说其他好的公司,因此就签约了现在这家传统国有企业。 自己一概莫有问题,哈哈 三年的时间很快就过去了,在那里认识了很多优秀有趣的同学,他们也给了我很大的支持和照顾。其他方面有时间,另开一篇吧。 在我离校要走进地铁站的那个时候,我给大师兄说:三年,有得有失,总体还是及格,还算满意吧! 特别鸣谢:大师兄的驾驶技术还是不错de,虽然去地铁站十多分钟的路程开了二十多分钟哈 选方向,明不足,结交优秀风趣的朋友,乃最大收获。 现在回来看看,研究生到底应该怎么过,对于普通的大多数: 研一要认真上课,努力认真完成各科作业,并做课程的知识拓展。 斯坦福的学生平日很忙,偶尔会通宵学习的哦,你应该知道需要怎么认真了吧。 从研一暑假开始,确立方向认真研究半年出论文。 课程给予广泛的视野,在经过几个月深入到一个领域,一篇毕业水论文应该是很容易。很多老师让学生刚开始就忽视基础【咣咣,敲黑板啊】,只关注研究领域,真的是种舍本逐末的行为,这样的结果就是一辈子只能给别人填坑,而且大多的研究成果都是垃圾,最后获得吃力不讨好的结局。 研二第二学期出外实习。 如果你打算硕士毕业就就业,那这一环节就很重要,建议选择和自己今后从事的工作方向相符的实习岗位,否则待在实验室好好学习也不错。 研三找工作。 很多人很多情况,这里就无法叙述了,只能因时因人因事而具体讨论。 最后,列举一些需要长期积累的东西: - 要有科学合理的学习方法 - 培养合理的学习习惯 - 建立独特的信息获取渠道 - 寻找自己的专业导师队伍和人生导师队伍 如果你还是觉得干货不足,那我就罗列一下微软的人才评价标准,大家细细玩味: - 迅速掌握新知识的能力 - 仅需片刻思考即可提出尖锐问题的能力 - 可以在不同领域的知识中找出它们之间的联系 - 扫视一眼即可用通俗语言解释软件代码的能力 - 关注眼前的问题,不论是否在工作中都应如此 - 非常强的集中注意力的能力 - 对自己过去的工作仍然记忆犹新 - 注重实际的思想观念、善于表达、勇于面对挑战、快速反应 满足两项以上的请举手,大家一起做朋友。
leetcode 主要是一个针对北美的coder人群找工作的代码练习网站,我在2015年初次接触这个网站的时候,总共只有200多道题目,是一个类似acm 的a题网站。这些年变化越来越大,主要是因为找工作当然是多样化的考核过程,leetcode 也逐渐与时俱进,推出了下面几个类别的联系,今天我们随便挑几个练习一下: 175. Combine Two Tables —SQL Table: Person Column Name Type PersonId int FirstName varchar LastName varchar PersonId is the primary key column for this table. Table: Address Column Name Type AddressId int PersonId int City varchar State varchar AddressId is the primary key column for this table. Write a SQL query for a report that provides the following information for each person in the Person table, regardless if there is an address for each of those people: FirstName, LastName, City, State # Write your MySQL query statement below # /** 这是个内链接 select t1.FirstName,t1.LastName ,t2.City,t2.State from (select * from Person as t1), (select * from Address as t2), where t1.PersonId=t2.PersonId */ select FirstName,LastName,City,State from Person left join Address on Person.PersonId=Address.PersonId 数据库到底有多少种链接呢? 1、内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。包括相等联接和自然联接。 内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。 2、外联接。外联接可以是左向外联接、右向外联接或完整外部联接。 在 FROM子句中指定外联接时,可以由下列几组关键字中的一组指定: 1)LEFT JOIN或LEFT OUTER JOIN 左向外联接的结果集包括 LEFT OUTER子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。 2)RIGHT JOIN 或 RIGHT OUTER JOIN 右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。 3)FULL JOIN 或 FULL OUTER JOIN 完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。 3、交叉联接 交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。 FROM 子句中的表或视图可通过内联接或完整外部联接按任意顺序指定;但是,用左或右向外联接指定表或视图时,表或视图的顺序很重要。有关使用左或右向外联接排列表的更多信息,请参见使用外联接。 多表查询分为 内、外连接 外连接分为左连接(left join 或left outer join)、右连接(right join 或者 right outer join)、和完整外部连接 (full join 或者 full outer join) 左连接(left join 或 left outer join)的结果就是left join子句中的左表的所有行,而不仅仅是链接列所匹配的行,如果左表中的某行在右表中没有匹配,则在相关联的结果行中右表的所有选择列均为空值(NULL) SQL语法 select * from table1 left join table2 on table1.条件列名 = table2.条件列名; 注释: 显示的就是table1中的所有列和能匹配的列 右连接(right join 或 right outer join )在这里不做多说这左连接很象但是是相反的,只说一下语法 select *from table1 right join table2 on table1. 条件列= table2.条件列 完全外部连接(full join 或 full outer join) 显示左右表中的所有行,当某一个表中没有匹配的行时,则另一个表的选择列表列包含空值(NULL)如果有则显示全部数据 SQL语法: select *from table1 full join table2 on table1.条件列名= table2.条件列名 内连接: 概念:内连接就是用比较运算符比较要用连接列的值的连接 内连接(join 或者inner join ) SQL语法: select *fron table1 join table2 on table1.条件列名 = table2.条件列名 返回符合匹配条件的两表列 等价于: select A* ,B* from table1 A ,table2 B where A.条件列名 =B.条件列名 select *form table1 cross join table2 where table1.条件列名 = table2.条件列名(注: Cross join 后面不能跟on 只能用where) 交叉连接(完全) 概念:没有用where子句的交叉连接将产生连接所涉及的笛卡尔积第一个表的行数乘以第二个表的行数等于笛卡尔积和结果集的大小 交叉连接: Cross join(不带条件where,如果带返回或显示的是匹配的行数) SQL语法: select *from table1 cross join table2 如果有条件(where) select * from table1 cross join table2 where table1. 条件列名= table2.条件列名 等价于 select *from table1,table2 (不带where) 193. Valid Phone Numbers — shell Given a text file file.txt that contains list of phone numbers (one per line), write a one liner bash script to print all valid phone numbers. You may assume that a valid phone number must appear in one of the following two formats: (xxx) xxx-xxxx or xxx-xxx-xxxx. (x means a digit) You may also assume each line in the text file must not contain leading or trailing white spaces. For example, assume that file.txt has the following content: 987-123-4567 123 456 7890 (123) 456-7890 Your script should output the following valid phone numbers: 987-123-4567 (123) 456-7890 三种解决方案: Using grep grep -P '^(\d{3}-|\(\d{3}\) )\d{3}-\d{4}$' file.txt Using sed: sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。 sed使用参数 [root@www ~]# sed [-nefr] [动作] 选项与参数: -n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。 -e :直接在命令列模式上进行 sed 的动作编辑; -f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作; -r :sed 的动作支持的是延伸型正规表示法的语法。(默认是基础正规表示法语法) -i :直接修改读取的文件内容,而不是输出到终端。 动作说明: [n1[,n2]]function n1, n2 :不见得会存在,一般代表『选择进行动作的行数』,举例来说,如果我的动作是需要在 10 到 20 行之间进行的,则『 10,20[动作行为] 』 function: a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~ c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行! d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚; i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); p :列印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~ s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦! sed -n -r '/^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$/p' file.txt Using awk: 简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理 使用方法 awk '{pattern + action}' {filenames} 尽管操作可能会很复杂,但语法总是这样,其中 pattern 表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。 pattern就是要表示的正则表达式,用斜杠括起来。 awk语言的最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后,才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息。 通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。 awk '/^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$/' file.txt 或者使用: grep -e '\(^[0-9]\{3\}-[0-9]\{3\}-[0-9]\{4\}$\)' -e '\(^([0-9]\{3\})[ ]\{1\}[0-9]\{3\}-\([0-9]\{4\}\)$\)' file.txt In Bash, we use \ to escape next one trailing character; ^ is used to denote the beginning of a line $ is used to denote the end of a line {M} is used to denote to match exactly M times of the previous occurence/regex (…) is used to group pattern/regex together Back to this problem: it requires us to match two patterns, for better readability, I used -e and separate the two patterns into two regexes, the first one matches this case: xxx-xxx-xxxx and the second one matches this case: (xxx) xxx-xxxx Please vote this post up if you find it helpful for your understanding! 534. Design TinyURL — system design https://segmentfault.com/a/1190000006140476 Note: For the coding companion problem, please see: Encode and Decode TinyURL. How would you design a URL shortening service that is similar to TinyURL? Background: TinyURL is a URL shortening service where you enter a URL such as https://leetcode.com/problems/design-tinyurl and it returns a short URL such as http://tinyurl.com/4e9iAk. Requirements: For instance, “http://tinyurl.com/4e9iAk” is the tiny url for the page “https://leetcode.com/problems/design-tinyurl“. The identifier (the highlighted part) can be any string with 6 alphanumeric characters containing 0-9, a-z, A-Z. Each shortened URL must be unique; that is, no two different URLs can be shortened to the same URL. Note about Questions: Below are just a small subset of questions to get you started. In real world, there could be many follow ups and questions possible and the discussion is open-ended (No one true or correct way to solve a problem). If you have more ideas or questions, please ask in Discuss and we may compile it here! Questions: 1. How many unique identifiers possible? Will you run out of unique URLs? 2. Should the identifier be increment or not? Which is easier to design? Pros and cons? 3. Mapping an identifier to an URL and its reversal - Does this problem ring a bell to you? 4. How do you store the URLs? Does a simple flat file database work? 5. What is the bottleneck of the system? Is it read-heavy or write-heavy? 6. Estimate the maximum number of URLs a single machine can store. 7. Estimate the maximum number of queries per second (QPS) for decoding a shortened URL in a single machine. 8. How would you scale the service? For example, a viral link which is shared in social media could result in a peak QPS at a moment’s notice. 9. How could you handle redundancy? i,e, if a server is down, how could you ensure the service is still operational? 10. Keep URLs forever or prune, pros/cons? How we do pruning? (Contributed by @alex_svetkin) 11. What API would you provide to a third-party developer? (Contributed by @alex_svetkin) 12. If you can enable caching, what would you cache and what’s the expiry time? (Contributed by @Humandroid) public class URLService { HashMap<String, Integer> ltos; HashMap<Integer, String> stol; static int COUNTER; String elements; URLService() { ltos = new HashMap<String, Integer>(); stol = new HashMap<Integer, String>(); COUNTER = 1; elements = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; } public String longToShort(String url) { String shorturl = base10ToBase62(COUNTER); ltos.put(url, COUNTER); stol.put(COUNTER, url); COUNTER++; return "http://tiny.url/" + shorturl; } public String shortToLong(String url) { url = url.substring("http://tiny.url/".length()); int n = base62ToBase10(url); return stol.get(n); } public int base62ToBase10(String s) { int n = 0; for (int i = 0; i < s.length(); i++) { n = n * 62 + convert(s.charAt(i)); } return n; } public int convert(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'z') { return c - 'a' + 10; } if (c >= 'A' && c <= 'Z') { return c - 'A' + 36; } return -1; } public String base10ToBase62(int n) { StringBuilder sb = new StringBuilder(); while (n != 0) { sb.insert(0, elements.charAt(n % 62)); n /= 62; } while (sb.length() != 6) { sb.insert(0, '0'); } return sb.toString(); } }
目前对中文分词精度影响最大的主要是两方面:未登录词的识别和歧义切分。 据统计:未登录词中中文姓人名在文本中一般只占2%左右,但这其中高达50%以上的人名会产生切分错误。在所有的分词错误中,与人名有关的错误占到了将近90%,这中国人名都是根据人的想法起的名字,有很大的随意性,并且数量巨大,规律也不尽相同。 1.理论简介 命名实体识别(Named Entities Recognition, NER)是自然语言处理(Natural LanguageProcessing, NLP)的一个基础任务。其目的是识别语料中人名、地名、组织机构名等命名实体。由于这些命名实体数量不断增加,通常不可能在词典中穷尽列出,且其构成方法具有各自的一些规律性,因而,通常把对这些词的识别从词汇形态处理(如汉语切分)任务中独立处理,称为命名实体识别。命名实体识别技术是信息抽取、信息检索、机器翻译、问答系统等多种自然语言处理技术必不可少的组成部分。 命名实体是命名实体识别的研究主体,一般包括3大类(实体类、时间类和数字类)和7小类(人名、地名、机构名、时间、日期、货币和百分比)命名实体。评判一个命名实体是否被正确识别包括两个方面:实体的边界是否正确;实体的类型是否标注正确。主要错误类型包括文本正确,类型可能错误;反之,文本边界错误,而其包含的主要实体词和词类标记可能正确。 命名实体识别的主要技术方法分为:基于规则和词典的方法、基于统计的方法、二者混合的方法等 1.1基于规则和词典的方法 基于规则的方法多采用语言学专家手工构造规则模板,选用特征包括统计信息、标点符号、关键字、指示词和方向词、位置词(如尾字)、中心词等方法,以模式和字符串相匹配为主要手段,这类系统大多依赖于知识库和词典的建立。基于规则和词典的方法是命名实体识别中最早使用的方法,一般而言,当提取的规则能比较精确地反映语言现象时,基于规则的方法性能要优于基于统计的方法。但是这些规则往往依赖于具体语言、领域和文本风格,编制过程耗时且难以涵盖所有的语言现象,特别容易产生错误,系统可移植性不好,对于不同的系统需要语言学专家重新书写规则。基于规则的方法的另外一个缺点是代价太大,存在系统建设周期长、移植性差而且需要建立不同领域知识库作为辅助以提高系统识别能力等问题。 1.2基于统计的方法 基于统计机器学习的方法主要包括:隐马尔可夫模型(HiddenMarkovMode,HMM)、最大熵(MaxmiumEntropy,ME)、支持向量机(Support VectorMachine,SVM)、条件随机场( ConditionalRandom Fields,CRF)等。 在这4种学习方法中,最大熵模型结构紧凑,具有较好的通用性,主要缺点是训练时间复杂性非常高,有时甚至导致训练代价难以承受,另外由于需要明确的归一化计算,导致开销比较大。而条件随机场为命名实体识别提供了一个特征灵活、全局最优的标注框架,但同时存在收敛速度慢、训练时间长的问题。一般说来,最大熵和支持向量机在正确率上要比隐马尔可夫模型高一些,但是隐马尔可夫模型在训练和识别时的速度要快一些,主要是由于在利用Viterbi算法求解命名实体类别序列的效率较高。隐马尔可夫模型更适用于一些对实时性有要求以及像信息检索这样需要处理大量文本的应用,如短文本命名实体识别。 基于统计的方法对特征选取的要求较高,需要从文本中选择对该项任务有影响的各种特征,并将这些特征加入到特征向量中。依据特定命名实体识别所面临的主要困难和所表现出的特性,考虑选择能有效反映该类实体特性的特征集合。主要做法是通过对训练语料所包含的语言信息进行统计和分析,从训练语料中挖掘出特征。有关特征可以分为具体的单词特征、上下文特征、词典及词性特征、停用词特征、核心词特征以及语义特征等。 基于统计的方法对语料库的依赖也比较大,而可以用来建设和评估命名实体识别系统的大规模通用语料库又比较少。 1.3混合方法 自然语言处理并不完全是一个随机过程,单独使用基于统计的方法使状态搜索空间非常庞大,必须借助规则知识提前进行过滤修剪处理。目前几乎没有单纯使用统计模型而不使用规则知识的命名实体识别系统,在很多情况下是使用混合方法: 3.1 统计学习方法之间或内部层叠融合。 3.2 规则、词典和机器学习方法之间的融合,其核心是融合方法技术。 在基于统计的学习方法中引入部分规则,将机器学习和人工知识结合起来。 3.3 将各类模型、算法结合起来,将前一级模型的结果作为下一级的训练数据,并用这些训练数据对模型进行训练,得到下一级模型。 这种方法在具体实现过程中需要考虑怎样高效地将两种方法结合起来,采用什么样的融合技术。由于命名实体识别在很大程度上依赖于分类技术,在分类方面可以采用的融合技术主要包括如Voting, XVoting,GradingVa,l Grading等。 2 jieba框架以及算法简介jieba介绍 jieba分词系统,主要实现三个模块, 分词 词性标注 关键词抽取 以下算法介绍,均参考jieba介绍 2.1分词 jieba基于前缀词典和动态规划方法实现分词, 2.2词性标注 jieba分词是如何对未登录词进行分词呢? 基于汉字成词能力的HMM模型识别未登录词。利用HMM模型进行分词,主要是将分词问题视为一个序列标注(sequence labeling)问题,其中,句子为观测序列,分词结果为状态序列。首先通过语料训练出HMM相关的模型,然后利用Viterbi算法进行求解,最终得到最优的状态序列,然后再根据状态序列,输出分词结果。 e.g.ICTCLAS中的HMM人名识别 1.以“王菲”为例,粗分结果是“始##始, 王, 菲, 末##末,”,很明显,粗分过程并不能识别正确的人名,因为“王菲”这个词并不存在于一元语言模型词典中。 观测序列 观测序列是我们能看到的显状态序列,这个例子里是“始##始, 王, 菲, 末##末,”。 之后通过查表,初分等以下几个过程 隐状态 初始概率 转移概率 发射概率 求解HMM 通过维特比算法找出最可能的标注序列了。最终标注结果: 始##始, 王, 菲, 末##末, 100-* 1-B 4-E 101-* 模式匹配 对于BE这个标注序列,如何知道里面是否含有人名,含有的是哪种人名呢?这需要通过模式匹配来发现,模式串有: 我们的BE匹配到了BE: 姓+单名这条规则,所以是一个单名人名,最终识别出结果: 王菲 3 单机版实现 本文基于大数据的开源组件实现了两个姓名提取脚本, 一个单机版,一个spark版本。 主要使用到了python3和jieba分词库,以及部分人工积累的停用词库。 利用hdfs清洗后的结构化数据,在hive中创建外表语句: create external table name_analysis ( name string, idcard string, src string, ) PARTITIONED BY (source string) ROW FORMAT DELIMITED FIELDS TERMINATED BY ','; 调用脚本 #!/bin/bash echo "start running the Abstract Name analysis working ......" START=$(date +%s); date=`date -d "$1" "+%Y%m%d"` echo "running date is $date" ################################################## ##你的数据外表 echo "-------------------start write content to txt-------------------" hive -f hive_temp.hql -hivevar date=$date>>$date.txt echo "-------------------content to $date.txt done-----------------------" ############################################### ###abstract name to txt file echo "------------------start abstract name------------------------------" python3 abstractNameToFile.py $date.txt echo "------------------abstract name done-------------------------------" ############################################## echo "-----------------start put file to hdfs/hive---------------------" NAME_CONTENT=name_$date.txt hdfs dfs -put $NAME_CONTENT /HDFS/name_analysis/content echo "----------------put file to hdfs/hive done ----------------------------" ################################################ END=$(date +%s); echo 'running time is: ' echo $((END - START))| awk '{print int($1/3600)":"int($1%3600/60)":"int($1%3600%60)}' python3分词脚本 # -*- coding: utf-8 -*- import jieba import jieba.posseg as pseg import datetime import sys #词性标注,nr为人名 def getFirstName(messageContent): words = pseg.cut(messageContent) for word, flag in words: if flag == 'nr'and len(word)>1:#单字姓名去掉 return word return False def getAllName(messageContent): words = pseg.cut(messageContent) names = [] for word, flag in words: print('%s,%s' %(word,flag)) if flag == 'nr':#人名词性为nr names.append(word) return names #修改停用词集合中所有词性为名词,大部分为名词 def alterWordTagToX(list): for x in list: jieba.add_word(x, tag='n') def LoadStopWord(StopWordFileName): StopWord_file = open(StopWordFileName, 'r', encoding='utf-8') StopWordList = [] for line in StopWord_file.readlines(): StopWordList.append(line.strip('\n')) set(StopWordList) StopWord_file.close() alterWordTagToX(StopWordList) def main(): #加载停用词词典文件 LoadStopWord('stopword.txt') input_file_name = sys.argv[1] output_file_name = 'name_'+ input_file_name print(input_file_name) print(output_file_name) begin = datetime.datetime.now() #单机并行分词 jieba.enable_parallel(8) input_file = open(input_file_name, 'r', encoding='utf-8') output_file = open(output_file_name, 'w') for line in input_file: temp = line.split('\t') if len(temp)!=4: continue name = getFirstName(temp[1]) if name != False: #print(name)姓名作为一行中的一个字段,其他为你需要的字段 time = str(temp[3]).strip('\n') output_file.write(temp[0] + ','+ name + ','+ '\n') else: continue end = datetime.datetime.now() print((end - begin).seconds) #单元测试代码 names = getAllName('我老公宝贝叫王宁,尊敬的王大力,CCHHKK旗舰店,尊敬的铁路客服人员李天,冯达辉') print(names) print(getFirstName('尊敬的铁路客服人员李天')) output_file.close() input_file.close() if __name__ =='__main__': main() 停用词文件举例 人名提取的结果示例 4.spark分布式版本 4.1分布式环境搭建 4.1.1 spark环境搭建 略 4.1.2 分布式环境下,分词库的安装 每个节点jieba库的安装,在一个节点配置好免密登录后可使用如下脚本进行jieba库的批量安装 for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; mkdir /opt/python;";done for((i=2;i<=xxx;i++));do scp /opt/jieba-0.38.zip root@host-${i}:/opt/python;done for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; unzip /opt/python/jieba-0.38.zip;";done for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; mv ~/jieba-0.38 /opt/python;";done for((i=2;i<=xxx;i++));do ssh host-${i} "hostname; cd /opt/python/jieba-0.38;python setup.py install";done 4.2 分布式分词要点 4.2.1 如何保障每个节点都能加载停用词: spark有两个技术可以保证: 1.全局变量Broadcast spark文档 A broadcast variable that gets reused across tasks. A broadcast variable created with SparkContext.broadcast(). Access its value through value. class pyspark.Broadcast(sc=None, value=None, pickle_registry=None, path=None) A broadcast variable created with SparkContext.broadcast(). Access its value through value. Examples: >>> from pyspark.context import SparkContext >>> sc = SparkContext('local', 'test') >>> b = sc.broadcast([1, 2, 3, 4, 5]) >>> b.value [1, 2, 3, 4, 5] >>> sc.parallelize([0, 0]).flatMap(lambda x: b.value).collect() [1, 2, 3, 4, 5, 1, 2, 3, 4, 5] >>> b.unpersist() >>> large_broadcast = sc.broadcast(range(10000)) 2.sc.addFile(path)添加可分发的文件 spark文档 addFile(path, recursive=False) Add a file to be downloaded with this Spark job on every node. The path passed can be either a local file, a file in HDFS (or other Hadoop-supported filesystems), or an HTTP, HTTPS or FTP URI. To access the file in Spark jobs, use L{SparkFiles.get(fileName) 4.2.2 使用spark-submit 提交姓名提取脚本 在命令行调用:(后面还可以根据自己的集群添加其他选项) spark-submit SparkAbstractName.py 基于python2的pyspark脚本,本来想统一成python3的但是集群是生存环境不好更改,只好用系统自带的python2了,因为jieba库是python2,3都兼容的,这一点向作者致敬。 # -*- coding: utf-8 -*- from pyspark import SparkConf,SparkContext from pyspark import SparkFiles import jieba import jieba.posseg as pseg import datetime import os import sys reload(sys) sys.setdefaultencoding('utf-8') #word tagging,nr for name def getFirstName(messageContent): words = pseg.cut(messageContent) for word, flag in words: if flag == 'nr'and len(word)>1:#delete single name return word return False #alter stopName's property to N def alterWordTagToX(list): for x in list: jieba.add_word(x, tag='n') #load local stopName def LoadStopWord(StopWordFileName): with open(StopWordFileName, 'r') as StopWord_file: StopWordList = [] for line in StopWord_file.readlines(): StopWordList.append(line.strip('\n')) set(StopWordList) alterWordTagToX(StopWordList) return StopWordList def Abstractfunc(line): LoadStopWord(SparkFiles.get('stopName.txt')) name = getFirstName(line[3]) if name != False:#对原始数据的重新排列 return [line[1],name,'',line[2],line[0]] else: return [line[1],'0','',line[2],line[0]] def main(sc): #print(LoadStopWord(SparkFiles.get("stopName.txt"))) input_file = sc.textFile('''file:///name_analysis/test.txt''') begin = datetime.datetime.now() length = input_file.map(lambda s:len(s)).reduce(lambda a,b:a+b) print(length) #加载,分割的原始数据 content_list = input_file.map(lambda x: x.split(',')) #获取我需要的列 row_content = content_list.map(lambda x:(x[8],x[9],.....)) print(row_content.map(lambda s:len(s)).reduce(lambda a,b:a+b)) #数据清洗,分词 list_content = row_content.map(lambda x:(list(x))).filter(lambda x:x[1]!='0') result_content = list_content.map(lambda line:(Abstractfunc(line))).filter(lambda x:x[1]!='0') print(list_content.map(lambda s:len(s)).reduce(lambda a,b:a+b)) #获取样例数据 test = result_content.take(10) for x in test: print (x[1]) print(type(x)) ''' jieba.enable_parallel(8) input_file = open(input_file_name, 'r', encoding='utf-8') output_file = open(output_file_name, 'w') ''' end = datetime.datetime.now() print((end - begin).seconds) #unit test ''' ...... ''' if __name__ =='__main__': conf = SparkConf().setAppName("SparkAbstractName") sc = SparkContext(conf = conf) sc.setLogLevel("WARN") path = os.path.join(os.getcwd(), '''stopName.txt''') print(os.getcwd()) print(path) sc.addFile(path) main(sc) sc.stop() 未完待续。。。 参考文献 1.http://blog.csdn.net/lalalawxt/article/details/55804384 2.http://www.cnblogs.com/yuxc/archive/2012/01/11/2319631.html 3.臧勇真. 基于统计和规则的中文人名识别研究与实现[D]. 西南交通大学, 2013. 4.jieba介绍 http://www.cnblogs.com/zhbzz2007/p/6076246.html 5.spark文档 http://spark.apache.org/docs/latest/api/python/pyspark.html 6.文本情感分析 https://www.ibm.com/developerworks/cn/cognitive/library/cc-1606-spark-seniment-analysis/index.html 7.ICTCLAS中的HMM人名识别 http://www.hankcs.com/nlp/segment/ictclas-the-hmm-name-recognition.html 8.实战HMM-Viterbi角色标注中国人名识别 http://www.hankcs.com/nlp/chinese-name-recognition-in-actual-hmm-viterbi-role-labeling.html
作者:一人 1.深度神经网络对于任何领域都是适用的 深度神经网络(Deep Neural Networks, DNN)在过去的数年已经在图像分类、语音识别、自然语言处理中取得了突破性的进展。在实践中的应用已经证明了它可以作为对于一种十分有效的技术手段应用在大数据相关领域中。深度神经网络通过众多的简单线性变换层次性的进行非线性变换对于数据中的复杂关系能够很好的进行拟合,即对数据特征进行的深层次的挖掘。因此作为一种技术手段,深度神经网络对于任何领域都是适用的。 2.推荐系统简介 推荐系统的功能是帮助用户主动的找到满足偏好的个性化物品并推荐给用户。在本质上可以当做一个个性化的搜索引擎,输入的数据为用户行为信息、偏好信息等,返回的结果为最符合查询条件的物品列表。数学化的表示: 物品列表=f(用户偏好)−−−−−−−−−−−公式(1) 我们的推荐引擎就扮演者这里的函数的角色,它主要需要完成两部分的工作: A > 针对查询条件对物品的相关性进行估计。 B > 晒选出topN个最相关的物品。 因此,推荐系统的关键就是对上面函数的一种求解。 实际应用中的物品数量很大,因此在满足业务需要的前提下,对于所有物品使用评估函数进行评估是不实际的。因此为了实现性能与效果的平衡,大多的推荐系统将以上的计算过程分为两个部分: 推荐召回 推荐排序 推荐召回指在所有物品集合中检索到符合用户兴趣的候选集,大约筛选出几百个候选的列表。排序的目的是要利用展示、点击(或转化)数据,然后加入更多的用户、物品特征,对推荐候选进行更精细的修正、打分。这种模式另一个好处是能够利用多种候选集。 因此,推荐系统需要完成两步计算:候选集生成和排序,这两阶段的估计函数分别表示为g和h,即有: f=g(h(x))−−−−−−−−−−−−−−−−−−−−−−−−−公式(2) 3.使用神经网络近似求解函数参考1 对于函数的求解大多分为以下几种途径: 确定性求解:通过对数据的规律进行建模直接求解。 确定性近似求解:通过变分推断的相关方法进行求解,EM。 随机性近似求解: 通过采样的方法对函数进行求解,蒙特卡洛方法。 非结构化求解 不管这个函数是什么样的,总会有一个神经网络能够对任何可能的输入 x网络可以得到对应的值 f(x)(或者某个足够准确的近似) 即使函数有很多输入或者多个输出,这个结果都是成立的,f=f(x1,...,xm) 。例如,这里有一个输入为 m=3 和输出为 n=2 的网络: 综上,神经网络作为一种近似化求解方法可以用来对于公式(2)两个函数g, h进行近似。 4.推荐召回 Google利用DNN来做YouTube的视频推荐其模型图如下图所示。通过对用户观看的视频,搜索的关键字做embedding,然后在串联上用户的side information等信息,作为DNN的输入,利用一个多层的DNN学习出用户的隐向量,然后在其上面加上一层softmax学习出Item的隐向量,进而即可为用户做Top-N的推荐。 Autoencoder(AE)是一个无监督学习模型(类似矩阵分解),它利用反向传播算法,让模型的输出等于输入。利用AE来预测用户对物品missing的评分值,该模型的输入为评分矩阵中的一行(User-based)或者一列(Item-based),其目标函数通过计算输入与输出的损失来优化模型,而评分矩阵中missing的评分值通过模型的输出来预测,进而为用户做推荐,其模型如下图所示。后续,Denoising Autoencoder(DAE)是在AE的基础之上,对输入的训练数据加入噪声。所以DAE必须学习去除这些噪声而获得真正的没有被噪声污染过的输入数据。因此,这就迫使编码器去学习输入数据的更加鲁棒的表达,通常DAE的泛化能力比一般的AE强。Stacked Denoising Autoencoder(SDAE)是一个多层的AE组成的神经网络,其前一层自编码器的输出作为其后一层自编码器的输入。还有Bayesian SDAE等等众多方法均同源于此。 5.推荐排序 Wide & Deep 模型,Google利用DNN和传统广义线性模型结合的方式实现对于Google Play 中的应用进行推荐。 DNN具有很好的泛化性而广义的线性模型具有很好的记忆性,通过将二者结合,在实现很好的泛化性基础上对于不相干的物品规则进行了抑制。在输入层将类别特征通过embedding和连续值进行连接形成输入的嵌入向量并通过三层的网络形成输入的隐向量,并在输入层将app相关的特征进行交叉相乘,连同隐向量输入一个逻辑输出单元中,最终输出对于特定app的评分。 6.神经网络其他应用 词向量表示,使用浅层神经网络方法进行学习。利用序列数据中蕴含的信息,将物品的表示由高维稀疏表示映射到低维密集表示。典型的模型方法有:word2vec [无监督]和GloVe[无监督] (Global Vectors for Word Representation)。 ——————————————————————————————————– 下图展示的是基于CBOW层次网络结构的word2vec,输入层是若干个词的词向量,通过映射层进行累加,输出层中黄色节点是非叶子节点代表一个类别,而叶子节点代表一个词向量,整个输出层是一个霍夫曼树。假设对于特定的上下文,特定的中间词的预测概率最大,进行训练得到词的低维密集表示。 ——————————————————————————————————– 例如:语句“直接修改此文件”,分词后有“直接”,“修改”,“此文件”。那么对于词“修改”进行训练,那么输入的上下文就是“直接”、“此文件”,我们期望“修改”的概率最大。通过使用大量样本训练后,可以在叶子节点训练得到对应词的向量表示。之后,可以计算词向量之间的相似性来代表词之间的相似性,诸如此类对进一步的分析提供方便。 ——————————————————————————————————– 7.神经网络的难点 由于神经网络用多层结构拟合复杂的非线性关系,具有庞大的参数,并且随着网络的深入进行训练愈发困难。因此对于实际中的应用具有以下难点: 需要大量的训练数据 调参不存在合理的选择方法 对于具体应用不存在标准的网络结构 8.当前数据应用深度模型面临的挑战 用户行为稀疏,因此数据中存在大量的噪音 媒体库数据可用字段较少 用户画像杂乱,用户属性信息采集不明确 总结 以前,计算资源宝贵,并且计算能力偏弱,因此为了实现智能化功能,需要研发人员将功能规则通过人为的方式间接的融入进算法当中,以此来减少计算量。但是由于用户的应用场景繁杂,因此往往存在着众多研发人员无法预估的情况。而且由于很多的近似求解方法需要得到精确地结果需要大量的计算而迫使多数应用场景无法实现和采用,因此在过去的数年间,应用层面的智能化发展停滞不前。而随着计算能力的迅速发展,利用大量计算实现智能化的功能已经成为可行策略。而深层神经网络算法以其强大的拟合能力就是适应了这种发展趋势,迅速的在图像、语音、自然语言等领域取得了巨大的成就。 个性化推荐作为众多智能场景中的一员,已经吸引了众多的研发人员投入其中,不同于图像、语音等具有丰富的特征且算法结果和真实样本不会产生互相影响,由于推荐中特征数据的繁杂,且推荐的结果影响着采集到的数据,目前推荐当中并不存在一种通用型的结构和方法。也有很多人将神经网络的方法应用在整体推荐的子领域当中已经取得了不错的效果。可以预见随着更多的人员参与进来,个性化推荐必将被神经网络的方法所侵占。 在工业中,在有限的资源投入的情况下,紧跟技术前沿的发展,将先进的方法在系统当中进行验证。或者对于行业取得稳定效果的方法进行验证并进行系统集成,产品将会获得巨大的收益。 附: Word2vec 效果【节目vec之间的相似度】: 碟中谍5:神秘国度 【 # 危机13小时,# 碟中谍4,# 死亡飞车,# 极限特工2,# 虎胆龙威5,# 星际穿越,# 丛林奇兵, # 刺客联盟, # 谍影重重2, # 非常人贩】 86版西游记 【# 西游记动画片,# 西游记之锁妖封魔塔,# 西游记之大闹天宫(3D),# 西游记之大闹天宫,# 西游记之孙悟空三打白骨精, # 嘻游记, # 西游记之大圣归来, # 西洋镜, # 电哪咤, # 孙悟空七打九尾狐 】 射雕英雄传 【# 射雕英雄传 第3集,# 射雕英雄传 李亚鹏版,# 神雕侠侣,# 神雕侠侣[粤语版],# 天龙八部, # 方世玉与胡惠乾, # 倚天屠龙记大结局, # 新神雕侠侣, # 神雕侠侣黄晓明版, # 天涯明月刀】
只有奄奄一息过,那个真正的我,他才能够诞生。 —- 题记 1.Life is about growing, learning, and being a better person. 简要回顾这半年来,工作生活的经历,总体来说往返于西安与北京之间。 2017年第一天,和很多人一起起个大早吸着雾霾,看了升旗仪式,觉得生活应该有点仪式感,才能心存敬畏。 后面花几周时间走访了BAT京东小米等各大IT公司,工作半年心中积累起来的自信感荡然无存。 过年去了趟兵马俑,龙门石窟,少林寺,感慨当我自以为真的每天追随技术最前沿,站在时代最顶端,还是要停下来回头补补课。江山留胜迹,我辈复登临。 篮球打到了全国各地,从西北大学到东单体育场,并没有太多的进攻欲望,但还是能在一早上三百五十分钟里豪取13分,打完还久久不愿离开。 《深度学习与中文短文本分析总结与梳理》有幸被清华大学数据派公众号认可并转载,欣喜若狂,文章热了48小时,然后骤然遇冷,可谁知我写了将近1月有余,这样的东西也能被消费?!不解,并陷入写博客,被消费的循环,有瘾! 3月19日凌晨,我在首都机场候机厅发了一个朋友圈,并配了如下文字: 如今我对自己故乡 像来往匆匆的过客 雷总将这幅照片作为我们公司留迹app 的封面,然而半年过去,留迹开始要消声遗迹了,因为CTO觉得app似乎并没有什么卵用。 5月5日,和张老师开车去了趟红河谷,爱上一句话: 青山清我目 流水静我耳 6月的最后一天,在同学喜宴上见识了50年茅台,路易十三,2017的上半年就这样过去了,也算不枉此生。最近看了一些关键词为追寻的电影:《冈仁波齐》,《猜火车2》 朝拜,choose life 都是在寻找,追寻,毫无意义的意义之所在。 当天晚上,我在家翻到自己19岁时候的日记本,上面写到: 世界上的忧虑,一大半是因为人们没有足够的知识来做决定而产生的。 混乱是产生忧虑的主要原因,如果一个人能够把他所有的时间偶花在以一种十分超然,客观的态度去寻找事实的话,他的忧虑就会在知识的光芒下消失的无影无踪。 事情既然如此,就不会另有它样,那还有什么好忧虑的呢? 看来那时候的自己远比现在通透多了。 我在豆瓣上看到一段电影《冈仁波齐》的影评: 朝圣、磕长头,是苦行,苦修,但大多数人的日常生活,又何尝不是苦行苦修呢。每天高峰期挤地铁,每天四五个小时的通勤,加班,耗尽全家所有积蓄、借遍亲朋好友的钱来买房,为了让孩子进入好学校,变成行贿高手,又何尝不像是在磕长头。 就像《冈仁波齐》的幕后花絮里说的:“这个世界上没有什么生活方式是完全正确的……神山圣湖并不是重点,接受平凡的自我,但不放弃理想和信仰,热爱生活,我们都在路上。” 信仰,生活,爱,可能是一件事物的三个名字,是一个事物的三个面相。其实,你我都有各自的冈仁波齐。 2.交待—-我以前没得选择,现在我想做一个好人。 几天前,坐在从西安去北京的高铁上,我这个将要27岁的大龄程序员,凝视窗外,一路上看太阳升起又落下,决定给自己一个交代。 其实我就是想写一篇充满文艺气息的年中工作生活总结,好宣泄我这大半年压抑的情感。然而我写的时候又在想,如果我自己是读者会有想要读下去的冲动么?这个文章想要说啥,它的主线是什么? 它能够充满对知识的渴求,对未来的美好希望么? 2016年我正式开始工作,当年年底总结的时候写了一篇文章,殷切希望自己能够成为可以独立解决复杂问题的软件工程师。年底至今10个月有余,在单位见识几位大牛show操作后觉的和达成目标还尚存不少差距。 这半年日子飞快,我像一发被上膛的子弹,对着一个个目标打了出去,只是有时候扣扳机的人并没有怎么瞄准。 身体的各项机能开始衰退,业余时间我选择拼命运动,打球,游泳,长跑,其实是花钱治疗拿命换钱所产生的副作用:腰肌劳损,颈椎病,痔疮,蛀牙,前列腺上的尿频尿急尿无力,现如今迎风撒一鞋。。。 似乎是每天都在紧紧追随最新的前沿技术,连买手环都要选择有久坐提醒功能的产品,以求延长自己的计算机职业生涯。 我想在工作中给自己一个交待,然而面对选择的苍白无力,比如你想要鱼丸粗面,对不起,没有鱼丸! 这让我想起那段经典对白 刘建明:我以前没得选择,现在我想做一个好人。 陈永仁:好,跟法官说,看他让不让你做好人。 刘建明:那就是要我死。 陈永仁:对不起,我是警察。 …… 从来只有事情改变人,而人是无法改变事情的。如果可以从头来过,可能我还是会选择现在的职业,不同的是从头开始就一直努力。 3.工作—-工作有什么好谈的,fuck 学长在自己博客的工作总结上写了一句话:工作有什么好谈的,fuck! 单位有位oracle大拿,知天命的年纪,走起路来却步履蹒跚,看起来70有余。据说他上世纪90年代开始从事oracle dba至今,业务上有解决不了的数据库问题只能找他。看着他的样子,我希望他的今天不要是大家的明天。 单位还有很多这样的人,被混乱的业务虐,被客户虐,被领导虐,出完差再回家被媳妇虐。 这样的人,苍老的很快。 现阶段存在很多困扰我的事情: 1.首先就是每天都在用电脑,手机,在各种渠道被迫接受信息轰炸。思维,行为碎片化严重。提笔忘字,如今你还能一口气写完八百字,题目自拟么?反正这篇文章我是来来回回写了好久。 2.并没有精通某一项编程语言,只知道似乎用别人的,开源的库可以做任何事情,缺少造轮子的勇气。 3.开始变的害怕尝试了,觉的人生的每一个机会都稍纵即逝不敢走错一步。 4.手机碎片化我们生活后,一个人最宝贵的实力就是专注。卸载qq,微信,微博,知乎,关闭朋友圈么?我们会失去更多还是得到更多 5.人变懒了,后半年希望自己勤快起来。多写代码! p.s.看来加班时间长,会脑子不好 4.读书—-抱道不曲 拥书自雄 成长,自我价值的提升,是刚刚工作的我最关注的点,1932年胡适先生在文章《大学生毕业后的几条路,以及两类堕落方式》中说:毕业后的我们容易抛弃学生时代的理想信念和人生追求,为了不忘初心,预防堕落,胡适先生给我们写了三句话: 总得时时寻一两个值得研究的问题! 总得多发展一点非职业的兴趣。 你总得有一点信心。 在你我最悲观最失望的时候,你要深信:天下没有白费的努力。成功不必在我,而功力必不唐捐。所以读万卷书,行万里路,天道酬勤而已! 有幸在单位加入了助力形成研发文化的虚拟组织,并在杨哥的带领下读到两本好书,《组织生存力》,中国版的金字塔原理《结构思考力》 组织生存力问了我们五个赤裸裸的问题。 我们的使命是什么? 我们的顾客是谁? 我们的顾客重视什么? 我们追求的成果是什么? 我们的计划是什么? 如果我们做每一个项目都能清楚的回答上述问题,我想目标明确的坚持下去一定能够出成果的。 《结构思考力》这本书主要教我们怎么构思说话,如何结构化思维。你有发现身边的很多人平时说话毫无重点么,推荐这本书给他吧。 结构思考的四个基本特点: ● 结论先行的表达,一次表达只支持一个思想,而且要在开头表达出来。先框架后细节。 ● 以上统下的论证,在任何层次上的思想都必须是下一层次思想的总结概括。 ● 归类分组的论据,每一个组的思想必须在同一个范畴之内,使之条理清晰模块化。 ● 层层递进的逻辑,每一组的思想,按照一定的逻辑顺序进行处置,更容易让对方理解和记忆。 两本书共有的闪光点,方法论: 1.专注,抓住主要矛盾 2.先仔细分析问题,再解决问题 3.冷静,思考 刚上研究生那年我看视频,俞敏洪说他大学期间读了800多本书。我也不甘示弱,在那一年开始了800本的征程,同样4年过去,我还剩700多本。 我只想说一个字,计算机专业的书看的比较慢! 5.情感—-Happiness lies in the consciousness we have of it. 每天下班回旅店的路上,小张老师会和我打个电话,(因为中国移动亲情号只能本地主叫免费,我给张老师打还算漫游。垄断在无形中阻碍了我们秀恩爱。) 张老师照例在电话中倾吐一天中生活中的精彩与不快 。我在电话这头,唯张老师马首是瞻,直到胳膊 酸痛转去另一只耳朵。请示能否歇息一会,张老师说:你一天就会说,我去上班呀,我去吃饭 呀,我去睡觉呀,我去跑步呀,要你有何用!? 可能张老师上课时候学生训的多了,潜意识里当我是她的学生之一。当然,我也是好为人师的,有时候忽然被我训一下她就会大发雷霆,还质问我,你凭什么总是站在道德的制高点上对我横加指责?扯着长脸让我安慰道歉半天。 我想,在教学相长这一点上我们是互相成就的。 张老师独自一人在古都的教育生活充满奇闻异事,因为每天都有层出不穷的各类数学题目出现,可以用来摧残祖国的花朵。然而整个社会乐此不疲,学校乐此不疲,家长也乐此不疲,张老师多数时候忧国忧民的问我咋办。我也是吃饭大学毕业,不能丢,雪糕为师,身正为饭的脸面。 我说: you save one, you save the world. 为我们点赞。 总的来说,数学考试是越来越难,如果让我当数学老师而不是程序员的话,可能我现在已经秃顶了。老张顶着压力继续奋战在数学战线上,说实话,现在学生数学差的太多,我国学校里的数学教育任重而道远啊。 985,211毕业的你能在十分钟内做出来上面的题目么?这可不是什么压轴题,填空题最后一道。 感情的事情不必多说,出差的日子里难为张老师了。我也没给家里做啥贡献,在此深表歉意。我这人不喜欢立什么flag,但是今天说到这了,我流川疯不能让爱我的人失望。 6.职业—–青春献祖国,永远跟党走 我的职业目标至今没有改变,依旧希望自己可以独当一面,成为能够独立解决复杂问题的软件工程师。工作这些时日,还在充分了解大数据生态圈,从底层的hdfs,hive,yarn等组件到集群管理的CDH,TDH,再到pyspark,oracle,甚至简单的数据分析等都有所涉猎,当然技术是始终在更新换代的,唯一不变的是永恒的解决问题之道,这就是算法。未来是AI的,是算法的! 2016年简直一晃而过,这是我半年前写的年终总结,里面依稀写了点对大数据行业的理解判断,半年过去感觉自己的技术水平并没有突飞猛进,看来革命尚未成功,同志仍需努力。 为了伟大祖国永远繁荣昌盛,努力吧! 有点乱,暂时写到这里,未完待续 此致,敬礼 ALL RIGHTS RESERVED!!! 如有巧合,纯属雷同! 已满18岁青年,请在家长陪同下观看!
昨天在北理工参加了一场由 雪晴数据网和北京理工大学大数据创新学习中心联合举办的知识图谱分享活动,聆听了一下午报告,可谓是受益匪浅。一下午时间安排的非常饱满,总共三场报告。 不得不说首都的学校就是厉害啊,楼都这么漂亮。 下面我就来分别说说重点和感想。 1.佛学知识图谱构建技术 东南大学 漆桂林教授 1.1 什么是知识? 1.2 知识图谱为搜索引擎带来的补充作用! 1.3知识图谱的几个关键技术 1.data extraction 数据从哪里来? 2.entity matching 就是说怎么知道beijing和北京是一个东西 3.type inference e.g. China is an instance of country 1.Explicit IsA Relation Detector 2.Category Attributes Generator 3.Instance Type Ranker 以上步骤中包含一些复杂算法,我个人觉的偏工程应用,具体参考ppt,在下载链接中。 1.4 data extraction实战 报告的老师基于以上内容给出了一个课堂小实战训练,让我们直观体验了一下构建知识图谱中的基础性工作,知识抽取,从非结构化数据中抽取结构化内容,这和我们大数据领域中首当其冲的数据清洗步骤是不谋而合的。 实例文本: ************************************************************************* title:大报国慈仁寺 大报国慈仁寺,俗称报国寺,位于北京市西城区,在广安门内大街路北。 经考证报国寺始建于辽代;明代塌毁,成化二年(1466年)重修,改名慈仁寺,俗称报国寺;清乾隆十九年(1754年)重修,更名为大报国慈仁寺。曾有七进院落,七层殿堂,后有毗卢阁,为当时北京南城最大庙宇。1900年因义和团在此寺设坛,被八国联军用炮轰毁。现全寺已修整一新,辟作“报国寺文化市场”,成为中国收藏活动著名的聚集地。 明清之际学者顾炎武(字亭林)在北京时曾住该寺西院。道光二十三年(1843年)改西院为顾亭林祠。如今在各种古旧书籍、钱币邮票、古玩首饰等的商摊中,祠堂已不可见,只余《顾亭林先生祠记》和《重建顾亭林先生祠记》两块碑文记载当年旧事。 目前每周四为报国寺文化市场交易日。 ************************************************************************* title:法门寺 法门寺,又称法云寺、阿育王寺,位于中国陕西省宝鸡市扶风县城北10公里处的法门镇。始建于东汉末年桓灵年间,距今约有1700多年历史,有“关中塔庙始祖”之称。法门寺因舍利而置塔,因塔而建寺,原名阿育王寺。释迦牟尼佛灭度后,遗体火化结成舍利。1980年以来,法门寺在前任方丈澄观、净一法师的住持下,相继建成大雄宝殿、玉佛殿、禅堂、祖堂、斋堂、寮房、佛学院等仿唐建筑。现任主持为中国佛教协会副会长学诚法师。 === 建寺 === 关于建寺时间,从唐代时就已无法准确确定了。有一种说法认为法门寺及真身宝塔始建于古印度孔雀王朝阿育王(前273年~前232年)时期。阿育王统一印度后为了弘扬佛法,将佛的舍利分送世界各地,兴建八万四千塔。中国有十九处,法门寺为第五处,先建塔后建寺。北周以前法门寺名为阿育王寺,寺塔名为阿育王塔。另一种说法受到了出土的汉代瓦当、砖刻的支持,认为法门寺建于东汉桓灵之世。 公元558年,北魏皇室后裔拓跋育曾扩建,并于元魏二年(494年)首次开塔瞻礼舍利。 基于给出的文本文件,进行正则表达式的提取python3脚本: #-*-coding:utf-8-*- import re def read_file(filename): with open(filename, encoding='utf-8') as fd: for line in fd: yield line if __name__== "__main__": filename = "templeArticles.txt" title = re.compile('^title:') weiyu = re.compile('位于([\\S]+)(,|。){0,1}') shijianyu = re.compile('始建于(((?!,|;|。).)+)(,|;|。)') for line in read_file(filename): # 处理文件每一行文件 if re.match(title,line): print(line[6:-1]) continue if re.findall(weiyu,line): print('位于: ' + re.findall(weiyu,line)[0][0]) continue if re.findall(shijianyu,line): print('始建于:' + re.findall(shijianyu,line)[0][0]) continue 处理结果: 1.5 不能简单使用正则的场景 无法用规则抽取的原因: 句式种类繁多,无法找到高质量且匹配多的规则。 只能界定属性值的一个边界。(如:用规则“(,|。){0,1} ([\S]+)担任主持”匹配上述5个句子,能得到“,并由其徒弟佛智法师”和“,之后交由第一世创古仁波切”,但是无法找到法师名字的前边界) 对于这种问题,需要使用多规则来进行抽取,包括但不限于机器学习深度学习等。 1.6 总结 整体给我的直观感觉是,知识图谱的构建工作是需求驱动的,它需要非常多的人工参与才能构建精确,并且能为你的搜索引擎,智能问答系统提供锦上添花的作用。 2.知识图谱应用关键技术及行业应用 这一场略微有广告嫌疑,不过报告老师提到了面向数据的互联网这个新奇的概念。并且突出了互联网本体,实体的概念。本体中突出和强调的是概念以及概念之间的关系。 2.1 本体以及什么是知识图谱 2.2 知识图谱的部分应用 2.3 时代的变化,思维的变化 2.4 大数据应用的挑战—-多源异构数据的融合 这块老师总结的非常到位,我司也面临同样的问题,知识图谱可以解决这两个问题么?我们拭目以待! 2.5 人民的名义—-关系图谱发掘 现场咨询了老师,他说是echarts结合一些其他定制技术做的效果,这块暂时没有拿到视频,是现场手机拍摄 的,大家凑活看吧。 3.中文知识图谱CN-DBpedia构建的关键技术 徐波 复旦大学知识工场实验室 徐老师这个报告真的是干货满满,他讲了非常多的技术细节,包括cn-dpedia的架构,以及我印象比较深刻的cn-dbpedia中知识更新的问题,以及采用深度学习来抽取特征的新思路。 3.1 CN-DBPEDIA系统框架 3.2 知识库实体更新 参考文献 以上三场报告ppt 资料打包下载 http://download.csdn.net/detail/wangyaninglm/9866353
作者:黄永刚 ML Phase II: 特征工程 第一阶段介绍了机器学习的一个周期,为学习系统获取训练数据,通过有趣的引导设计指标,创建一个服务框架。在有了一个完整系统之后,就进入了第一阶段。 第二阶段有很多比较容易的东西。任务就是将大量丰富的特征搞进系统中。因此,机器学习的第二阶段就是获取尽可能多的特征并将其有意的组合。第二阶段,所有的指标应该任然在提升。很多东西开始开发,很多工程师将一起花费大量的时间处理数据,以便于你能够创建出非常优秀的学习系统。 Rule #16: 不要尝试一次做出完美的模型,或者打算停止模型迭代。因此需要考虑是否当前增加的信息会延缓未来的迭代。很多团队花费一个季度在建立一个模型上,或者数年时间都在进行模型迭代。建立新模型很必要,原因如下: 1. 你不断发掘出新的特征。 2. 你可能调整了正则,改变了原来特征的处理方式。 3. 你可能调整了目标。 无论如何,多给模型一些关注总是好的额,查找一些数据并回馈给旧模型或许帮助你找到新的信息,所以在建立模型的时候,就要考虑添加或移除特征或者重新组合特征的难度如何。考虑重建原来pipeline的一个新复制版本难度大与否,验证其正确性难不难。考虑并行运行两三个副本的难度大不大。最后,暂不考虑从35个中抽取16个特征是不是就进行模型更新。这是下一个阶段干的事情。 Rule #17: 开始时,使用可直接观察或可以记录的特征作为学习特征的反面。 这个看就有争论了,但是它可以避免很多容易犯的错误。首先,首先阐述一下学习特征是什么。学习特征是从外部系统获得的特征(例如通过聚类获得的)或者通过模型自身获得(因子模型或深度学习)。这些都很有用,但是也存在很多问题,所以在第一个模型中就不要使用它。 如果使用外部系统生成特征,要认识到那个系统有它自己的目标。外部系统的目标或许是以周期性与当前的系统目标相关。如果你利用的是外部系统的一个副本,那么它很快就过时了。如果你更新了这个特征,那么它代表的意思可能已经发生了变化。 因子模型和深度模型最大的问题是它们是非凸的。因此不能保证找到近似最优解,或许每次迭代找到的都是一个不同的局部最小。这种变化就导致了很大程度上很难判定其对于你的系统影响是有意义的还是随机的噪声。如果不利用深度特征,你可得到一个较好性能的基准模型。有了这个基准之后,你就可以尝试更加复杂的方法了。 Rule #18: 从不同的场景中找到特征 通常一个机器学习系统是一个大的业务场景的很小的一部分。例如,判断一个post是不是应该上热榜,很多人在这个post上热榜之前可能对这个post,赞、转发、评论。如果提供这些信息给学习器,可能提高了所优化场景中没有数据post的权重。YouTube的 ‘自动播放下一个’ 功能就利用了来自YouTube搜索中的数据(一个视频看过之后又看了哪个视频,对此数据进行频率统计,即看了又看)你也可以使用明显的用户评分。最后,如果你使用用户行为作为标签,用户行为在不同场景中的释义将会是很好的特征。这些特征将会将其他场景信息带入这个场景中。记住这不是个性化:找到喜欢这个内容的人群,然后判断其喜欢的程度。 Rule #20: 用人能够理解的方式对已存特征进行改写和组合创建新的特征。 有很多的方法修改和组合特征。机器学习系统像TensorFlow允许你使用transformations预处理数据。两个标准方法:离散化和交叉。 离散化是将连续性特征离散,创建出以值为标签的离散特征。如年龄是连续特征。使用一个特征表示年龄小于18岁,18到35岁的为另一个特征等等。不要过多的考虑这个边界,可以参考基础的分位数。 交叉是将多个特征进行交叉组合。特征列,在TensorFlow的terminology中是一组同质的特征。(如{男、女},{美国、加拿大、墨西哥}等)。特征交叉出新特征,如{男、女}*{美国、加拿大、墨西哥}。新特征包含这样的值{男、加拿大}。如果你使用TensorFlow做特征交叉,这个特征{男、加拿大}表示男性加拿大人。使用多个基本特征形成的交叉特征将需要大量的数据来训练模型。 交叉会产生大量的特征,有可能会过拟合。例如,你在做搜索排序,有查询词的向量和文档词向量。如果采用交叉组合,将会产生大量的特征(参考Rule #21)。当处理文本问题,这里有两个变种。严格一点大多应用点乘。点乘的最简单形式是计算查询和文档共用词汇的数量。这个特征也可以被离散化。另一种变种是使用交集:只使用那些 同时出现在查询和文档中的词作为特征。 Rule #21: 从线性模型中可学到的特征权重数目大体和拥有的数据量成比例 统计学习理论中对于模型复杂度的描述是很有意思的,这些知识是基础,你需要知道。我以前说过,人们怀疑可以从一千个样本中学到任何东西,或者最多需要的样本数量不会超过一百万。这是由于这些人陷入了特定的学习方法中了。其实这个的关键在于调整你的学习方法适应于你的数据量的大小: 1. 假设你从事搜索排序系统,在文档和查询中有上百万的不同词汇,而且你又1000个标记样本,那么你可以使用文档和查询的词频-逆文频率与六个其他人工特征的点乘。1000样本,12个特征。 2. 如果有100万样本,获取文档和查询特征的交集,使用正则化和可能的特征选择。获取的数百万特征在正则化的作用下就会变得很少。1000万样本或许只会获得10万特征。 3. 如果有数十亿的样本,交叉文档词和查询字符的特征,并使用特征选择和正则化。10亿样本只会用到1000万的特征。 统计学习理论很少给出精确的边界,但是对于起点给出了很好的指引。最后使用Rule #28去决定使用哪些特征吧! Rule #22: 清除掉你不在使用的特征 不用的特征就是技术债务。如果发现没有使用的特征,并将其和其他特恒组合之后仍然不起作用,那么将其从你结构里面清楚出去。保持结构整洁以便于能够尽快的寻找到有价值的特征。 添加或保留哪些特征,要多考虑特征的覆盖率。这个特征在多少的样例里面出现?例如,考虑个人属性,如果只有8%的用户有这些特征,那么添加这些特征的效果不会很好。 同时,一些特征或许对于权重有很重要的作用。如一个特征只有1%的覆盖率,但是有这个特征的数据90%都是正样例,那这个特征就是很好的。 人工分析系统 进入机器学习第三阶段之前,学习一下:怎么看一个模型,并改进它。这是非常重要的,而且所有机器学习相关课程中不会讲述这些东西。这是优点偏艺术的成分,因此,告诉你一些反面模式帮助你少跳坑。 Rule #23 你不是典型的最终用户 很多团队容易陷入这个陷阱里面。员工应该自己看产品原型的性能是否正确。当产品明显变差时,就应该果断抛弃掉;当产品原型看起来比较合理的时候,就需要做进一步的测试。可以花钱请一些非行业内人士回答问题或者对用户做在线实验。 这样做有两个原因。第一,你太熟悉业务了。你可能看的角度和大众不同,或者会对产品存在情绪上的代入感。第二,你的时间太宝贵了。你能想象9个工程师坐在一个会议室里面讨论这些东西的代价有多大。 如果你真的需要用户的反馈,就使用调查用户体验方法。在早期的时候创造一个用户意像,并做可用性测试。用户意像是假设一个用户。例如,如果你的团队里面都是男性,那么设计一个35岁的女性意像,看它会做出什么样的选择。将你的站点(本地或者远程)给真实的人看看他们的反应,对于可用性可能会有不同的观点。 Rule #24 衡量模型之间的差异 在展示给其他人看之前,最简单或许最有用的方法就是,计算不同模型在产品系统中结果输出的差异大小。例如,对于排序问题,将两个模型使用同一个查询样例在整个系统中跑一遍,比较一下结果的加权大小。如果差异非常小,那你完全不用在跑实验就可以告诉别人它们的变化不大;如果两者差异比较大,那就要考考确认一下变化的好坏了。认真查看差异比较大的情况能够帮助你理解变化在质量上是什么样子地。然而一定要确保系统是稳定的。确认模型和自身比较的对称误差(symmetric difference)很低,理想情况为0. Rule #25: 选择模型的时候,实用效果最关键 你的模型可能做的是点击率预测。然而,最终关键的问题是你要拿这个预测值做什么。如果你用来对文档进行排序,那么最终排序的质量才是关键,而不是预测本身。如果你用来判断文档是不是垃圾内容而选择是不是将其屏蔽掉,那选择‘阈值’的准确度就比预测本身重要。大多数情况这两者应该都是一致的。当它们不一致的时候,通常获得收益也很小。因此,模型的改变使得损失函数减小,但是却降低了系统的性能,那就换个特征吧。当这种情况经常发生的时候,那就需要你再次审视一下你的模型目标是不是恰当的。 假设你得到一个模型做错的样例。在分类中,应该是假阳或者假阴(false positive or false negative, FP or FN)。在排序中,是一对样例,其中正样本的排名低于负样本。看待这个一种重要的观点是:机器学习系统知道它是错的,但若你能提供机会,它自己会修正过来。如果你给模型喂进一些特征,那么模型自己会尝试修正。 另外,如果你尝试基于一些样例创造一些特这,然而这个样例系统比不将其视作错误,那么这个特征将被忽略掉。例如,在Play应用搜索中,有人搜索了‘免费游戏’。假设靠前的结果都是相关度比较低的 gag app,那么你创建一个特征来表达gag app.如果你正在优化应用安装数量,当人们搜索‘免费游戏’的手就会安装gag app,这个特征‘gag app’并没有取得你想要的效果。 一旦你发现模型错误的样例,那你就从当前特征集合之外找找有哪些趋势。例如,如果系统看上去会将posts较长的权重降低。那么将这个长度作为特征加入里面。不要太关注你所添加特征的细节了。如果你将要将post的长度加入里面,不要尝试去猜长度怎么定义才好,你只需要扔一堆特征进去,让模型自己去找哪个有用。这个才是你获得想要结果最简单的方法。 Rule #27: 量化不理想行为 你的团队成员当遇到一些他们不愿看到且没有被已存的损失函数捕获的系统情况时,可能变得沮丧。此时,无论做什么都要将这种抱怨转变成坚实的数字。例如,如果他们认为在Play搜索中有太多的‘gag apps’,应该使用专人来识别gag apps。(在这种情况下你可以灵活的使用人工标注的数据,因为这些查询部分可能占了很大一部分流量)。如果你的问题可以衡量,那就开始将其作为特征、目标或者指标。总结一句话,“量化第一,优化第二”。 Rule #28: 短期行为相同不意味长期行为相同 假设你有一个新系统做文档审计的,之后在计算每个查询对于每个文档的点击率。你会发现它的行为和当前线上系统不论在任何测试中都是相同的。鉴于它比较简单,你直接使用它。然而你慢慢发现新上架的应用不会出现在这里。为啥呢?那是因为你的系统仅仅是基于历史查询记录来展示文档,没有办法处理新文档的情况。 唯一可以理解系统在长期表现如何的方法是,将模型上线产生记录,在来训练。这就比较扯了。 训练和上线服务之间的偏差 训练性能和服务性能之间的差异可能由一下原因引起: - 训练和服务时的在处理数据的流程上存在不一致的地方 - 训练和服务时数据发生了变化 - 模型和算法之间形成了回馈环流 我们在Google的机器学习系统中见到过这种偏差,对系统产生了负面影响。最好的解决办法就是做监控确保系统和数据的变化导致的偏差能够被注意到。 Rule #29: 确保训练和服务一致的最好方法就是保存服务中的特征集,在将其用在训练当中 即便不对每一个样本都这样做,只选择一部分也是可以的,只要能够确认服务和训练的一致性就行(参考Rule #37)。有的时候Google的团队们应用这种方法获得了惊人的结果。YouTube主页通过应用这个方法质量上获得很大提高,同时也减小了代码的复杂度。很多团队都在往这种结构上转。 Rule #30: 数据采样使用的权重可不能随意丢掉 当你有太多的数据时,倾向于使用1-12忽略13-99.这是错误的,在过去很多的团队在训练中丢弃数据导致了很多问题。虽然从来没有展示给用户的数据可以被丢弃,但是可以被其他用来做重要性衡量。重要性衡量指的是如果你对X进行30%的采样,它将给这个数据一个10/3的权重。有重要性衡量,在Rule #14中谈论的属性矫正就是可以做的。 Rule #31 如果要合并训练和服务时段的数据,这个数据可能会发生变化 假设一要合并一个包含文档特征的表格,如评论和点击数。在训练阶段和服务阶段,特征可能是不同的。模型对于相同文档的预测可能在训练和服务时不同。最简单的避免这个的方式就是记录服务时段的特征(参考Rule #32)。如果表格变化的比较缓慢,你可以每小时或每天对最近的数据做快照。注意这依然没有解决掉这个问题。 Rule #32 尽可能在训练流程和服务流程中重用代码 批处理和实时处理是不同的。实时处理中当有请求达到,就要处理。然而在批处理中,你可以将这些任务集中起来一起处理。服务阶段,你做的是实时处理,而训练做的是批处理。要重用代码还有一些其他事情要做。例如,你可以创建一个独特的系统对象,请求的结果或者合并的结果以人可读的方式存储,而且保证错误可以很容易的被测试出来。之后,一旦你将所有信息汇总起来,无论是训练阶段的还是服务阶段的,你就可以使用通用的方法对该人可读的对象和系统需要的格式之间建立转化桥梁,此时无论系统需要什么格式都可以容易的处理了。这种方法从根源上减少了训练和服务的偏差。一个推论,不要尝试在训练和服务中使用两种编程语言,否则重用代码就成了几乎不可能的事情。 Rule #33 如果模型试验使用的数据是截止5号的,那么就使用6号以后的数据对其进行测试 通常,模型评估使用的数据是训练数据之后的,这样可以很好的反映出在生产环境中系统的表现。如果模型试验使用的数据是截止5号的,那么就使用6号以后的数据对其进行测试。可以预知在心数据上的表现不会很好,但是它也不应该太差。或许由于日期的影响,你可能不能预测出平均点击率或者转化率,但是AUC(area under the curve),代表正样例的评分高于负样例的似然性,应该是比较接近的。 Rule #34: 二值分类的过滤中(垃圾邮件或感兴趣邮件),为了获得干净的数据需要做出微小的牺牲 在过滤中,标记为负的样本不会呈现给用户。假设模型可以在服务中过滤75%的负样本。你可能想获得用户的其他样例作为训练数据。例如,如果用户将邮件标记为垃圾邮件,而你没能过滤掉,你可能像从这个学习到一些东西。 但是这种方法引入了样本偏差。你可以将你需要过滤掉的1%样本作为‘held out’,并将其依旧发送给用户,这样你就可以获得更加干净的数据了。而你的过滤器至少还能过滤74%的负样本。这些‘held out’样本就可以变成你的训练数据了。 如果过滤器有95%及以上的过滤比例,这个方法看起来可行性就不高了。即便如此,如果你想评估服务质量的性能,你可以使用更小的样本比例(比方说0。1%或0.001%)。有一万个样本做估计足以获取到相当精准的结果了。 Rule #35: 排序问题中的固有偏差 当你转变算法足够激进的时候,不同的结果就会出现。这样你已经改变了算法未来会用到的数据。这种固有偏差就会出现,在设计模型的时候就应该加以考虑。这里有几个方法。这些方法更偏爱模型已经看到过的数据: 1. 对于覆盖更多查询的特征给予给高的正则系数,覆盖面小的特征则相反。该方法更偏爱那些泛化性不是很好的特征(即比较独特的特征)。这个方法可以阻止非常流行的结果泄入不相关的查询当中。注意,传统的建议是对于具有更多独特值的特征给予更高的正则系数,这里和它不一样。 2. 将特征权重限制在正数范围内。因此好的特征降会由于‘unkown’特征(0权重特征)。 3. 不要只获取文本特征。这是#1的一个拓展。例如对于任何查询,有一些app都是热门,你肯定不希望任何地方都展示它。1 Rule #36: 避免位置特征的回馈循环 内容呈现的位置很大程度像影响着用户的交互的喜好度。如果你将一个APP放在第一个的位置,那它就会获得更多的点击,你会认为这个APP本身受到更多的偏爱和点击。一种解决办法是加入位置特征。将位置特征加入模型一起训练,模型自身会学习这个权重,例如,第一位的获得较高的权重。因此你的模型对于位于第一位的样本的其他因子会给予更小的权重。之后,再服务阶段,你不会给出位置权重,或者都是默认值,那是因为对候选进行评定之后才会决定展示的次序(译注:不要因果倒置)。 将位置特征和其他模型特征分开来是很重要的,这是由于训练和测试的不对称性决定的。最好的方式是对其他特征函数和位置特征函数求和。还有不要和位置特征做特征交叉。 Rule #37: 评估训练或服务偏差 这里有些事情在很多场景中会导致偏差。而且可以将其分成几个部分: 1. 训练数据和holdout数据的性能差异通常会一直存在,也不会一直都是坏的。 2. holdout数据和‘新一期’数据之间的性能差异也会一直存在。你应该调整正则化来最大化‘新一期’数据上的性能。但是,如果holdout和‘新一期’数据上的性能差异比较大,那表明了其中一些特征对时间太过敏感了,很可能降低了模型的性能。 3. 同样,‘新一期’数据上的性能和实时数据的也存在差异。如果你对训练数据和服务阶段的同一个样本进行模型预测,它们应该给出完全相同的数据。因此,如果存在差异,很大程度上表明工程商存在错误。 reference: - http://feisky.xyz/machine-learning/resources/rules_of_ml.html - Rules of Machine Learning: Best Practices for ML Engineering 不愿将流行的app到处展示的原因是要保证所有用户想要的app都是可见的。例如,如果有人搜索‘bird watching app’,他们可能下载了‘愤怒的小鸟’,但是那不是他们真正的意图。展示这种APP或许可以提高下载率,但是却忽略了用户最终的满意度。 ↩
1.使用python fabric进行Linux基础配置 使用python,可以让任何事情高效起来,包括运维工作,fabric正式这样一套基于python2的类库,它执行本地或远程shell命令提供了操作的基本套件(正常或通过sudo)和上传/下载文件,如提示用户输入运行辅助功能,或中止执行。 用Python3开发的部署工具叫fabric3:fabric3,和fabric一样最大特点是不用登录远程服务器,在本地运行远程命令,几行Python脚本就可以轻松部署。 典型用途包括创建一个包含一个或多个函数的Python模块,然后通过fab命令行工具执行它们。下面是一个小而全的“fab file”包含一个单独的任务: from fabric.api import run def host_type(): run('uname -s') 执行方法: fab -f deploy.py go Fabric提供几个简单的API来完成所有的部署,最常用的是local()和run(),分别在本地和远程执行命令,put()可以把本地文件上传到远程,当需要在远程指定当前目录时,只需用with cd(‘/path/to/dir/’):即可。 默认情况下,当命令执行失败时,Fabric会停止执行后续命令。 有时,我们允许忽略失败的命令继续执行,比如run(‘rm /tmp/abc’)在文件不存在的时候有可能失败,这时可以用with settings(warn_only=True):执行命令,这样Fabric只会打出警告信息而不会中断执行。 Fabric是如何在远程执行命令的呢?其实Fabric所有操作都是基于SSH执行的,必要时它会提示输入口令,所以非常安全。更好的办法是在指定的部署服务器上用证书配置无密码的ssh连接。 更多请fabric参考官方文档,http://www.fabfile.org/ 1.1 基建工作基本流程 对于私有云的用户来说,服务器的基建工作比较重要,需要做一些规范化的统一操作,方便后序搭建集群时候服务器上的操作系统有统一的配置,比如CDH,TDH等的集群管理工具,都有这样的要求。 当然,如果是公有云,不用考虑这么多。私有云追赶潮流的话,直接上docker或者openstack就好。下面就是比较low的基建流程,基建工作主要流程为: 修改hostname 关闭sshd 关闭Selinux以及防火墙 重启 格式化磁盘 挂载磁盘 Check上述流程结果。 1.2 使用anaconda2包(有网环境下准备) 在有网环境下,pip instatll fabric后将anaconda打包 解压安装到/opt下 tar -xzf anaconda2.tar.gz pwd mkdir python cd python/ mv ../anaconda2* . mkdir deployment cd deployment/ vim deploy.py (copy 下一节的内容) //在当前命令行上下文,加入anaconda环境变量 ls /opt/python/anaconda2/bin/ PATH=/opt/python/anaconda2/bin;$PATH 1.3 python 代码,deploy.py #-*- coding: utf-8 -*- import sys from fabric.api import * env.user = 'root' hostList = [] hostnameprefix='hostname-' for i in range(1,n): #ip range hostList.append(hostnameprefix + str(i)) env.hosts = hostList env.password = 'Pass1234' diskList='bcdefghijklm' fstabappend=''' /dev/sdb1 /mnt/data01 xfs defaults 0 0 #....... /dev/sdc1 /mnt/data02 xfs defaults 0 0 ''' with open('/root/.ssh/authorized_keys', 'r') as f: sshkey = f.read() def gethostname(): run('hostname') # only for RHEL7.2 def changehostname(): cmd = '''num=`ifconfig| grep your_ip | awk '{print $2}' | cut -d '.' -f 4` && echo hostname-$num > /etc/hostname ''' run(cmd) def checksshd(): cmd = 'grep UseDNS /etc/ssh/sshd_config' run(cmd) # only for RHEL7.2 def disableSSHDNS(): cmd = '''echo UseDNS no >> /etc/ssh/sshd_config''' run(cmd) def getSelinux(): cmd = '''grep 'SELINUX=' /etc/selinux/config && getenforce''' run(cmd) def disableSelinux(): cmd = '''sed -i 's/SELINUX=.*[=A-Za-z]$/SELINUX=disabled/g' /etc/selinux/config''' run(cmd) def getFirewalld(): cmd = 'systemctl status firewalld' run(cmd, warn_only=True) def disableFirewalld(): cmd = 'systemctl disable firewalld && systemctl stop firewalld' run(cmd) def checkdiskparted(): for d in diskList: run('''parted /dev/sd%s print ''' % (d)) def parteddisk(): for d in diskList: run('''parted /dev/sd%s mklabel gpt ''' % (d)) run('''parted /dev/sd%s mkpart primary %s %s''' % (d, '0%', '100%')) def mkfsdisk(): for d in diskList: run('''mkfs.xfs /dev/sd%s1 ''' % (d)) def mountdisk(): run('cp /etc/fstab /root/fstab.bak') run('''mkdir /mnt/data0{1,2,3,4,5,6,7,8,9} /mnt/data1{0,1,2}''') run('echo >> /etc/fstab') run('''tee -a /etc/fstab <<EOF%sEOF''' % (fstabappend)) run('mount -a') def checkmount(): run('''df -h | grep '/mnt/data'| wc -l ''') def checknetspeed(): run(''' ethtool bond0| grep Speed ''') @runs_once def confirm_opetions(message): return prompt(message, default='N') ## !!!!!!!!!!!!!! don't to run this function @task def rebootall(lhost='default'): # if lhost in env.hosts: # env.hosts.remove(lhost) # else : # print('*******************') # print('give me the lhost , tell me: who are you?') # print('*******************') # sys.exit(-1) # reboot(use_sudo=False) run('shutdown -r +3') @task def nopasswd(): run('mkdir -p /root/.ssh && chmod 700 /root/.ssh && echo "%s" >> /root/.ssh/authorized_keys && chmod 644 /root/.ssh/authorized_keys' % (sshkey)) @task def go(): changehostname() # disableSSHDNS() # disableSelinux() # disableFirewalld() # mkfsdisk() # mountdisk() @task def check(): gethostname() checksshd() getSelinux() getFirewalld() checkdiskparted() checkmount() checknetspeed() 1.4 LINUX 基建使用的命令参考 命令进行ip与主机名字符串的拼接: num=`ifconfig| grep yourip | awk '{print $2}' | cut -d '.' -f 4` && echo gaibdv1_$num 找到Selinux状态 grep 'SELINUX=' /etc/selinux/config && getenforce 关闭Selinux sed -i 's/SELINUX=.*[=A-Za-z]$/SELINUX=disabled/g' /etc/selinux/config 格式化磁盘 for d in diskList: run('''parted /dev/sd%s mklabel gpt ''' % (d)) run('''parted /dev/sd%s mkpart primary %s %s''' % (d, '0%', '100%')) 获取网速 ethtool bond0| grep Speed 2. RHEL 7.2 部分优化 2.1 RHEL 7.x系统的闪光点与新特性 XFS文件系统 Linux Container(Docker) RHEV 3.0 红帽企业级虚拟化 RHEV-M能管理虚拟机与其磁盘镜像,安装ISO,进行高可用性设置,创建虚拟机模板等,这些都能从图形web界面完成。也可使用RHEV-M管理两种类型的hypervisor。RHEV自身带有一个独立的裸机hypervisor,基于RHEL与KVM虚拟化,作为托管的物理节点使用。另外,如果你想从RHEV管理运行在RHEL上的虚拟机,可注册RHEL服务器到RHEV-M控制台。 2.2 部分新旧命令对比 任务 旧命令 新命令 使某服务自动启动 chkconfig –level 3 httpd on systemctl enable httpd.service 使某服务不自动启动 chkconfig –level 3 httpd off systemctl disable httpd.service 检查服务状态 service httpd status systemctl status httpd.service (服务详细信息)systemctl is-active httpd.service (仅显示是否 Active) 显示所有已启动的服务 chkconfig –list systemctl list-units –type=service 启动某服务 service httpd start systemctl start httpd.service 停止某服务 service httpd stop systemctl stop httpd.service 重启某服务 service httpd restart systemctl restart httpd.service 2.3 更新的命令RHEL7.2 命令更新 一、CentOS的Services使用了systemd来代替sysvinit管理 二、修改系统运行级别 三、其他配置工具 主要是多了systemd这个软件,采用了以下新技术: 采用Socket激活式与总线激活式服务,以提高相互依赖的各服务的并行运行性能; 用cgroups代替PID来追踪进程,以此即使是两次fork之后生成的守护进程也不会脱离systemd的控制。 Systemd是一个系统管理守护进程、工具和库的集合,用于取代System V初始进程。Systemd的功能是用于集中管理和配置类UNIX系统。主要负责控制systemd系统和服务管理器。从设计构思上说,由于systemd使用了cgroup与fanotify等组件以实现其特性,所以只适用于Linux。 在Linux生态系统中,Systemd被部署到了大多数的标准Linux发行版中,只有为数不多的几个发行版尚未部署。Systemd通常是所有其它守护进程的父进程,但并非总是如此。 其他请看参考7。 2.4 优化配置 (未完待续。。。) 可能优化的地方 更新系统 yum update -y 给/etc/rc.local添加执行权限 chmod +x /etc/rc.d/rc.local 加大打开文件数的限制(open files) ulimit -n ulimit -a vi /etc/security/limits.conf 最后添加 * soft nofile 1024000 * hard nofile 1024000 hive - nofile 1024000 hive - nproc 1024000 用户进程限制 # sed -i 's#4096#65535#g' /etc/security/limits.d/20-nproc.conf #加大普通用户限制 也可以改为unlimited # egrep -v "^$|^#" /etc/security/limits.d/20-nproc.conf * soft nproc 65535 root soft nproc unlimited reboot 优化内核 cat /etc/sysctl.conf #CTCDN系统优化参数 #关闭ipv6 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 #决定检查过期多久邻居条目 net.ipv4.neigh.default.gc_stale_time=120 #使用arp_announce / arp_ignore解决ARP映射问题 net.ipv4.conf.default.arp_announce = 2 net.ipv4.conf.all.arp_announce=2 net.ipv4.conf.lo.arp_announce=2 # 避免放大攻击 net.ipv4.icmp_echo_ignore_broadcasts = 1 # 开启恶意icmp错误消息保护 net.ipv4.icmp_ignore_bogus_error_responses = 1 #关闭路由转发 net.ipv4.ip_forward = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 #开启反向路径过滤 net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 #处理无源路由的包 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 #关闭sysrq功能 kernel.sysrq = 0 #core文件名中添加pid作为扩展名 kernel.core_uses_pid = 1 # 开启SYN洪水攻击保护 net.ipv4.tcp_syncookies = 1 #修改消息队列长度 kernel.msgmnb = 65536 kernel.msgmax = 65536 #设置最大内存共享段大小bytes kernel.shmmax = 68719476736 kernel.shmall = 4294967296 #timewait的数量,默认180000 net.ipv4.tcp_max_tw_buckets = 6000 net.ipv4.tcp_sack = 1 net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_rmem = 4096 87380 4194304 net.ipv4.tcp_wmem = 4096 16384 4194304 net.core.wmem_default = 8388608 net.core.rmem_default = 8388608 net.core.rmem_max = 16777216 net.core.wmem_max = 16777216 #每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目 net.core.netdev_max_backlog = 262144 #限制仅仅是为了防止简单的DoS 攻击 net.ipv4.tcp_max_orphans = 3276800 #未收到客户端确认信息的连接请求的最大值 net.ipv4.tcp_max_syn_backlog = 262144 net.ipv4.tcp_timestamps = 0 #内核放弃建立连接之前发送SYNACK 包的数量 net.ipv4.tcp_synack_retries = 1 #内核放弃建立连接之前发送SYN 包的数量 net.ipv4.tcp_syn_retries = 1 #启用timewait 快速回收 net.ipv4.tcp_tw_recycle = 1 #开启重用。允许将TIME-WAIT sockets 重新用于新的TCP 连接 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_mem = 94500000 915000000 927000000 net.ipv4.tcp_fin_timeout = 1 #当keepalive 起用的时候,TCP 发送keepalive 消息的频度。缺省是2 小时 net.ipv4.tcp_keepalive_time = 1800 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_intvl = 15 #允许系统打开的端口范围 net.ipv4.ip_local_port_range = 1024 65000 #修改防火墙表大小,默认65536 net.netfilter.nf_conntrack_max=655350 net.netfilter.nf_conntrack_tcp_timeout_established=1200 # 确保无人能修改路由表 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.secure_redirects = 0 net.ipv4.conf.default.secure_redirects = 0 本文出自 “兰芷” 博客,请务必保留此出处http://7826443.blog.51cto.com/7816443/1775248 sysctl -p #生效 vim基本设置 vim /root/.vimrc set history=1000 autocmd InsertLeave * se cul autocmd InsertLeave * se nocul set nu set bs=2 syntax on set laststatus=2 set tabstop=4 set go= set ruler set showcmd set cmdheight=1 hi CursorLine cterm=NONE ctermbg=blue ctermfg=white guibg=blue guifg=white set hls set cursorline set ignorecase set hlsearch set incsearch set helplang=cn inoremap ( ()<ESC>i inoremap [ []<ESC>i inoremap { {}<ESC>i inoremap < <><ESC>i inoremap " ""<ESC>i inoremap ' ''<ESC>i 日志系统简介 在rhel7系统中有两个日志服务,分别是: rsyslog systemd-journal systemd-journald是一个改进型日志管理服务,可以收集来自内核、系统早期启动阶段的日志、系统守护进程在启动和运行中的标准输出和错误信息,还有syslog的日志。该日志服务仅仅把日志集中保存在单一结构的日志文件/run/log中,由于日志是经历过压缩和格式化的二进制数据,所以在查看和定位的时候很迅速。默认情况下并不会持久化保存日志,只会保留一个月的日志。另外,一些rsyslog无法收集的日志也会被journal记录到。 rsyslog作为传统的系统日志服务,把所有收集到的日志都记录到/var/log/目录下的各个日志文件中。常见的日志文件如下: /var/log/messages 绝大多数的系统日志都记录到该文件 /var/log/secure 所有跟安全和认证授权等日志都会记录到此文件 /var/log/maillog 邮件服务的日志 /var/log/cron crond计划任务的日志 /var/log/boot.log 系统启动的相关日志 linux命令大全 http://man.linuxde.net/ 参考文献 1.官方文档 http://www.fabfile.org/ 2.参考 http://wklken.me/posts/2013/03/25/python-tool-fabric.html 3.python3中使用fabric3 https://github.com/mathiasertl/fabric/ pip install Fabric3 4.关闭UseDNS加速SSH登录 经常登陆SSH的朋友可以感觉出,每次登录SSH时总是要停顿等待一下才能连接上,,这是因为OpenSSH服务器有一个DNS查找选项UseDNS默认情况下是打开的。 UseDNS 选项打开状态下,当客户端试图登录SSH服务器时,服务器端先根据客户端的IP地址进行DNS PTR反向查询出客户端的主机名,然后根据查询出的客户端主机名进行DNS正向A记录查询,验证与其原始IP地址是否一致,这是防止客户端欺骗的一种措施,但一般我们的是动态IP不会有PTR记录,打开这个选项不过是在白白浪费时间而已,不如将其关闭。 http://www.kwx.gd/CentOSApp/Centos-SSH-UseDNS.html 6.fabric使用简单例子,中文: http://www.cnblogs.com/MacoLee/p/5680672.html 另一个参考: http://blog.csdn.net/wklken/article/details/8719541/ 7.RHEL 一些改变 http://www.ha97.com/5657.html https://linux.cn/article-5926-1.html 8.优化RHEL http://hequan.blog.51cto.com/5701886/1789146/ 9.日志 http://www.linuxidc.com/Linux/2016-01/127729.htm http://blog.csdn.net/sinat_34689375/article/details/53789592 https://blog.linuxeye.cn/400.html
作者:郭少雷 搞android搞了几年也没搞出个啥牛逼app出来,眼看时下最火的app微信如此火热,实在想搞搞它,索性就想着给它加点东西进去。 以下内容纯属本人个人爱好,仅限个人学习android用途以及对android的深入了解。 首先我们得想一想加点什么东西在微信里面,这里简单做个体验,加一个推送sdk至微信最新(6.5.7)apk包中,并由服务端控制向其推送消息。以下步骤依次讲解加入流程 1.申请推送平台 这里以个推为例并下载Getui_SDK;新建一Android Studio工程,包名同微信包名保持一致(com.tencent.mm),新建一PushActivity用于获取启动个推SDK的smali代码片段。依据个推sdk创建相应DemoIntentService和DemoPushService; 2.获取资源文件 生成工程apk后使用ApkTool反编译生成好的apk后得到以下smali代码及资源文件。反编译apk文件 命令: apktool d <file.apk> <dir> 得到所有资源及代码文件。 进入smali目录获取到PushActivity.smali中启动个推代码片段: 3.反编译 使用ApkTool反编译微信apk得到微信资源及smali代码: 4.定位onCreate方法 查看微信资源AndroidManifext.xml中启动Launcher的Activity为LauncherUI,打开LauncherUI.smali文件并找到onCreate方法: 在该方法最后加入启动个推smali代码,并将PushActivity改为Launcher所在位置后保存: 5.加入个推服务 保存个推用到的资源文件到微信目录后修改微信AndroidManifest.xml加入个推服务: 6.回编apk文件 命令: apktool b <dir> 使用apk回编命令对微信资源目录进行回编后得到新的微信apk;安装启动后即可由个推服务端对客户端进行Push消息推送。
1.题目描述 Given n non-negative integers a1, a2, ..., an, where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0). Find two lines, which together with x-axis forms a container, such that the container contains the most water. Note: You may not slant the container and n is at least 2. 2.中文解释: 给定n个非负整数a1,a2,…,an,其中每个代表一个点坐标(i,ai)。 n个垂直线段例如线段的两个端点在(i,ai)和(i,0)。 找到两个线段,与x轴形成一个容器,使其包含最多的水。 备注:你不必倾倒容器。 3.超时的c++算法 当然,谁都可以想到的解法就是暴力匹配,当遇到等差数列的时候当然就超时了!!! // leetcode11.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<vector> using namespace std; int maxArea(vector<int>& height) { int max = 0; if (height.size() == 0) return 0; int length = height.size(); int area = 0; for(int i=0;i<length;i++) { for (int j = i; j < length; j++) { int high = height[j] > height[i]?height[i]:height[j]; area = high*(j - i); if (max<area) { max = area; } } } return max; } int main() { vector<int> array(10); array.push_back(1); array.push_back(1); int area = maxArea(array); return 0; } 4.正确答案 算法证明: Here is the proof. Proved by contradiction: Suppose the returned result is not the optimal solution. Then there must exist an optimal solution, say a container with a_ol and a_or (left and right respectively), such that it has a greater volume than the one we got. Since our algorithm stops only if the two pointers meet. So, we must have visited one of them but not the other. WLOG, let’s say we visited a_ol but not a_or. When a pointer stops at a_ol, it won’t move until The other pointer also points to a_ol. In this case, iteration ends. But the other pointer must have visited a_or on its way from right end to a_ol. Contradiction to our assumption that we didn’t visit a_or. The other pointer arrives at a value, say a_rr, that is greater than a_ol before it reaches a_or. In this case, we does move a_ol. But notice that the volume of a_ol and a_rr is already greater than a_ol and a_or (as it is wider and heigher), which means that a_ol and a_or is not the optimal solution – Contradiction! Both cases arrive at a contradiction. 参考答案: ///C++ int maxArea(vector<int>& height) { int water = 0; int i = 0, j = height.size() - 1; while (i < j) { int h = min(height[i], height[j]); water = max(water, (j - i) * h); while (height[i] <= h && i < j) i++; while (height[j] <= h && i < j) j--; } return water; } ///C语言参考答案 A bit shorter and perhaps faster because I can use raw int pointers, but a bit longer because I don't have min and max. int maxArea(int* heights, int n) { int water = 0, *i = heights, *j = i + n - 1; while (i < j) { int h = *i < *j ? *i : *j; int w = (j - i) * h; if (w > water) water = w; while (*i <= h && i < j) i++; while (*j <= h && i < j) j--; } return water; } python参考答案 class Solution: def maxArea(self, height): i, j = 0, len(height) - 1 water = 0 while i < j: water = max(water, (j - i) * min(height[i], height[j])) if height[i] < height[j]: i += 1 else: j -= 1 return water 我的完整工程: // leetcode11.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include<vector> using namespace std; ///超时了 int maxArea(vector<int>& height) { int max = 0; if (height.size() == 0) return 0; int length = height.size(); int area = 0; for(int i=0;i<length;i++) { for (int j = i; j < length; j++) { int high = height[j] > height[i]?height[i]:height[j]; area = high*(j - i); if (max<area) { max = area; } } } return max; } ///accept int maxArea2(vector<int>& height) { int max = 0; if (height.size() == 0) return 0; int length = height.size(); int area = 0; int l = 0; int r = length - 1; while (l < r) { int high = height[l] > height[r] ? height[r] : height[l]; max = max > (high * (r - l)) ? max : (high * (r - l)); if (height[l] < height[r]) l++; else r--; } return max; } int main() { vector<int> array(0); array.push_back(1); array.push_back(1); int area = maxArea2(array); return 0; }
我们在做数据分析,清洗的过程中,很多时候会面对各种各样的数据源,要针对不同的数据源进行清洗,入库的工作。当然python这个语言,我比较喜欢,开发效率高,基本上怎么写都能运行,而且安装配置简单,基本上有网的环境pip install全部都搞定,没网的话,把whl包copy过来一行命令也就解决了( windows下python3.5使用pip离线安装whl包)。 本篇博客就针对,在windows平台下使用python3(python2社区将要停止支持,使用3是大势所趋),读取xls,xlsx格式的数据进行清洗入库做一个小例子。 初步业务流程 整个业务的流程十分简单:两个大的步骤 1. 读取xlsx数据进行清洗 2. cx_Oracle批量入库 建表语句: create table temp_table ( importtime varchar2(128), carrier varchar2(32), ); select * from temp_table 一个例子脚本: # -*- coding: utf-8 -*- import xlrd import datetime import cx_Oracle import time from itertools import islice import os os.environ['NLS_LANG']='SIMPLIFIED CHINESE_CHINA.ZHS16GBK' LineName = ['1号线','2号线'] StationName = [] ########################链接数据库相关###################################### def getConnOracle(username,password,ip,service_name): try: conn = cx_Oracle.connect(username+'/'+password+'@'+ip+'/'+service_name) # 连接数据库 return conn except Exception: print(Exception) #######################进行数据批量插入####################### def insertOracle(conn,data,input_file_name): sheetnumber = getSheetNumber(data) cursor = conn.cursor() try: for x in range(0,sheetnumber): templist = excel_table_byindex(input_file_name,0,x) cursor.prepare('insert into temp_table(importtime ,carrier) values(:1,:2)') # 使用cursor进行各种操作,templist数值需要和表temp_table对应 cursor.executemany(None,templist) conn.commit() except cx_Oracle.DatabaseError as msg: print(msg) finally: cursor.close() conn.close() ###########################打开excel文件######################## def openXLS(path): try: data = xlrd.open_workbook(path) return data except Exception: print(Exception) def getSheetNumber(data): sheet_num = len(data.sheets()) return sheet_num #######################一些数据清洗工作######################## def getlineName(str): for x in LineName: if x in str: return x def getStationName(str): for x in StationName: if x in str: return x ##########将excel中除去表头的一个sheet读出来,返回一个list############# def excel_table_byindex(path,colnameindex = 0,by_index = 0): today = time.strftime('%Y%m%d', time.localtime(time.time())) data = openXLS(path) table = data.sheets()[by_index] nrows = table.nrows ncols = table.ncols colnames = table.row_values(colnameindex) list = [] for rownum in range(1,nrows): row = table.row_values(rownum) temp_lineName = getlineName(row[6]) temp_stationName = getStationName(row[6]) if row: app = [today, str(row[1]), str(row[2]),temp_stationName,temp_lineName] # for i in range(len(colnames)): # app[colnames[i]] = row[i] list.append(app) return list ###################一个可以从文件第二行开始读的办法############# def getAllStationName(path): StationName_file = open(path, 'r', encoding='utf-8') #count = len(StationName_file.readlines()) for line in islice(StationName_file,1,None): str_temp = line.strip('\n') if str_temp not in LineName and str_temp !='----'and str_temp!='': StationName.append(str_temp) #################################################################### def getStationNamefromexcel(path): data = openXLS(path) table = data.sheets()[0] nrows = table.nrows ncols = table.ncols colnames = table.row_values(0) list = [] for rownum in range(0,nrows): row = table.row_values(rownum)[0] if row: list.append(row) return list ################################################################# def main(): username = 'xx' password = 'xx' ip = '192.168.1.1' service_name = 'iop' #获取数据库链接 conn = getConnOracle(username,password,ip,service_name) input_file_name = (r"E:\code\python\findS\subwayBase\xx.xlsx") #output_file_name = input("Enter the output file name:") getAllStationName(r"E:\code\python\findS\subwayBase\站点.txt") begin = datetime.datetime.now() insertOracle(conn,openXLS(input_file_name),input_file_name) # x.fetchone() # c.close() # 关闭cursor # conn.close() # 关闭连接 end = datetime.datetime.now() print((end - begin).seconds) if __name__ =='__main__': main() python3 windows下使用cx_Oracle操作oracle的报错问题 报错信息如下: Traceback (most recent call last): File "E:/code/python/findS/findSubwayBase.py", line 134, in <module> main() File "E:/code/python/findS/findSubwayBase.py", line 124, in main insertOracle(conn,openXLS(input_file_name),input_file_name) File "E:/code/python/findS/findSubwayBase.py", line 32, in insertOracle cursor.executemany(None,templist) UnicodeEncodeError: 'ascii' codec can't encode characters in position 1-6: ordinal not in range(128) Process finished with exit code 1 在使用python3 的cx_Oracle操作oracle数据时候,不可避免的会遇到中文的编码问题,当然,上网一搜全是python2的,解决方案是: #在开头加上 import sys reload(sys) sys.setdefaultencoding( "utf-8" ) python3中的解决方案为:加上核心代码 import os os.environ['NLS_LANG']='SIMPLIFIED CHINESE_CHINA.ZHS16GBK' 就ok啦,其实就是设置一下客户端编码 ,参考:python编码 OS.ENVIRON详解 xlrd 操作excel demo代码: #获取一个工作表 table = data.sheets()[0] #通过索引顺序获取 table = data.sheet_by_index(0) #通过索引顺序获取 table = data.sheet_by_name(u'Sheet1')#通过名称获取 #获取整行和整列的值(数组) table.row_values(i) table.col_values(i) #获取行数和列数 nrows = table.nrows ncols = table.ncols #循环行列表数据 for i in range(nrows ): print table.row_values(i) #单元格 cell_A1 = table.cell(0,0).value cell_C4 = table.cell(2,3).value #使用行列索引 cell_A1 = table.row(0)[0].value cell_A2 = table.col(1)[0].value #简单的写入 row = 0 col = 0 # 类型 0 empty,1 string, 2 number, 3 date, 4 boolean, 5 error ctype = 1 value = '单元格的值' xf = 0 # 扩展的格式化 table.put_cell(row, col, ctype, value, xf) table.cell(0,0) #单元格的值' table.cell(0,0).value #单元格的值' 参考链接 [OS.ENVIRON详解]: http://blog.csdn.net/junweifan/article/details/7615591 [python编码]:http://www.cnblogs.com/fkissx/p/5417363.html 再次强烈推荐,精通oracle+python系列:官方文档 http://www.oracle.com/technetwork/cn/articles/dsl/mastering-oracle-python-1391323-zhs.html 离线版本下载链接: http://download.csdn.net/detail/wangyaninglm/9815726
我记得hongyang大神说,当你觉的不满意的时候,那么你的机会来就了。 《创业:我们创什么》这本书,很好的诠释了这个观点,全书围绕时下最火的创业点,各个行业的风口,进行了一一剖析。 创业你还在等什么! 《财经天下》周刊主编 他急切地想要得到相关证照,不是为了阻止后来者——商业社会无法靠证照阻止竞争,而是想要让行业各方面参与者的责任边界变得清晰,更有利于行业发展。他说,当前更实际的做法是,尽快做大规模,加强商业契约的合理性,倒逼政策法规的制定。 ----也以此缅怀,春雨医生的张锐先生 第1章 争夺“机场+”入口 肖昭恩表示,逸臣现有商业模式可简要概括为“拿地+运营”。“拿地”即从机场商业管理公司处取得航站楼特定区域的特许经营权,“运营”即根据公司商业规划,结合经营区域所在位置,确定经营业态,装修后开展经营。 他还留意到坐飞机的人中,坐在前排头等舱的人更爱看书,坐在后面经济舱的人打游戏居多。 第2章 “超市搬运工”的生意能做多大? 之后,邵又找来曾在IBM中国区就职的马来西亚籍华人杜国强。邵元元曾在接受科技网站36氪采访时透露,三名创始人当年最终凑出了3000万元初始资金,于2012年3月在北京创立了家捷送电子商务有限公司(下称家捷送)。 第3章 李志活在互联网上 2011年年初,李志拖着几大箱自己的唱片到荒郊野外,付之一炬,下决心下张专辑不做实体。 迟斌回忆,那是2011年情人节之后,李志把烧唱片经过录了下来,并选择了一首齐秦的歌——《把梦烧光》作为背景音乐,其中有这样一句歌词:“输得荒凉,死得牵强。” “不赚钱的商业是不道德的” 聚在一起还会导致一个很致命的问题,像《乌合之众》里所写:个体的习惯和行为会被群体影响,没有了自己的特色。 第4章 一起作业网:要么火,要么死 “我会一直投,直到它挣到钱或者挣不到钱。” 第5章 摩登天空:不仅是中国公司,它还会成为一家全球公司 他认为,工作以及从事的行业只是人本身的延伸。 具备创造力的想象力,会衍生出商业想象力。 第6章 做题机器 一个产品如果免费都没有人用,就不用想别的了。”互联网行业先圈地后种庄稼的模式依然被奉为准则。 技术路线上,学习宝是OCR技术,而我们追求的是STR技术,自然环境下的文字识别。 第9章 “一元洗车”杀入汽车后市场 O2O创业必须要满足三个条件:刚需、有痛点、高频次。 第11章 “开桌”试图打通O2O 最后一公里 “打车软件烧钱大战的大赢家不是滴滴和快的,而是财付通和支付宝。这个案例充分证明,O2O模式竞争离不开移动支付清算,这是构建移动商务业务闭环的必要条件,另一个必要条件是支付的场景。” 第16章 饿了么想做餐饮业“天猫” 一切都源自2008年4月深夜寝室里的那句“你们饿了么”。当时在上海交大读研一的张旭豪和室友在房间里翻找外卖单。“不饿的时候外卖单随处扔,等到真的饿的时候却找不到了。” 第17章 疯狂的煎饼 餐饮业的人他随时可以找,但要找到能给餐饮业带来新鲜感的人却并不容易。 他很懂年轻人的语境:情侣来店亲吻就获赠煎饼果子一套,光棍节有油条相赠,奥巴马连任也搞促销,效仿苹果大会召开世界煎饼果子大会,店里摆放着时髦玩偶和小木马——总之,营造各种气氛来让粉丝主动在社交网络上曝光黄太吉。 第18章 十万笑话,冷极必火 后现代文本中的标志性特征——反逻辑、不合作、跳脱等,在《十冷》中被奉为高级别招数。 第19章 “坏孩子”的新时代 从2003年国家体育总局宣布电子竞技成为我国第99个正式体育项目至今,电竞获得合法身份已经有12年 柚子在服务器内闯下了响当当的名号,但为了玩游戏,他在高一办理了休学。“父母肯定是不支持的,不过我爸对我很好,也没有很反对。”柚子说自己是“电竞选手中比较少的得到家人理解的运动员。”一次在网吧玩游戏的时候他父亲过去拍了拍他肩膀,就在朋友们都认为他要被父亲揪回家的时候,他父亲却问他吃饭了吗?还掏出200元钱让他自己去买饭。 第20章 活在视频网站 《老外屌丝超强十二人模仿》红了以后,Mike隋花掉5000块钱买了微博大号的营销。但当视频真正火起来后,Mike隋才意识到,具备病毒式传播基因的视频其实是不需要营销的,它自然会火。 第21章 离开腾讯的日子 百度的离职员工组织叫“百老汇”,阿里巴巴的叫“前橙会”,网易的叫“离易”、“新浪的叫“毕浪”,金山的叫“旧金山”等等。 2014年以来,无论是腾讯人力资源的员工回流计划,还是腾讯开放平台对腾讯系创业者的欢迎,还是投资部连续在老员工创业项目中的投资,都在表达一个强烈的信号:腾讯在乎老员工。 第32章 把私人医生请到你的手机里 他急切地想要得到相关证照,不是为了阻止后来者——商业社会无法靠证照阻止竞争,而是想要让行业各方面参与者的责任边界变得清晰,更有利于行业发展。他说,当前更实际的做法是,尽快做大规模,加强商业契约的合理性,倒逼政策法规的制定。
周鸿祎个人简介: 周鸿祎这个人比较有争议,如果不是他,中国互联网的免费文化可能还不会像今天这样,免费,共享等等概念满天飞。 周教主的核心理念是说,如果使用软件的用户足够多,那么软件成本分摊到每位用户就是近似免费的,而软件完全可以通过赞助商,广告商的出资抵消这部分成本费用。所以对于软件产业来说,用户才是最重要的,有了用户就有了一切。 下面,让我们来快速围观一下老周和他的互联网方法论。 第一章 欢迎来到互联网时代 360推出免费杀毒,既让传统杀毒厂商愤怒,又让他们不解,同时心里又有一种看不起。他们愤怒的是,免费杀毒跟他们原先的竞争方式根本就不一样:以前他们打的是价格战,你便宜,我更便宜;以前是营销战,你有八大功能,我有十大亮点。但360这一次直接以零价格闯入市场,用户一分钱都不用花。我们发布360免费杀毒的当天晚上,一个传统杀毒公司的老板半夜打电话给我,说:鸿祎啊,你这是干啥呢?是要跟整个行业为敌吗?你这不是要砸我们的饭碗吗?你这是连锅都要端走了。 结果是,360免费杀毒推出三个月之后,就成功掀翻瑞星市场份额达到第一,半年之后用户量超过1亿。在互联网面前,传统杀毒厂商都成了不幸的火鸡。 第二章 互联网里的用户至上 观察现实,无论是脸谱网(Facebook)、推特(Twitter)还是谷歌,所有伟大的互联网公司都有巨大的用户基础。他们获得巨大用户基础的前提,是给用户提供了若干有价值的服务。 第三章 颠覆式创新 颠覆式创新,就像自然界的新陈代谢一样,不断把老的、旧的公司从行业中挤出去。所以,这种颠覆式创新已经成为美国硅谷的一个象征。 破坏和颠覆,都是强调打破原有的平衡,建立新秩序。但这两个词在中文里都是贬义词,因为中国文化崇尚平衡、稳定、和谐。一说颠覆式创新,我们的潜意识就会觉得是反动的东西,就不由自主地想到阶级敌人搞破坏。我有些时候受邀给一些单位讲互联网里的颠覆式创新。讲完后,有的领导就过来跟我握手说:小周,讲得挺好的嘛,只不过以后不要讲颠覆、讲破坏,影响社会和谐。 所有的颠覆式创新都不是敲锣打鼓来的,而是隐藏在一片噪声里。 我对颠覆式创新的另一种理解,就是一定要逆向思维,反向操作。苹果的口号是“think different” 反向思维,通过逆向操作,在用户体验和商业模式上创造一种新的游戏规则。 齐白石说过一句话:“学我者生,似我者死。” 做好产品服务消费者才是根本,用谁的技术、用什么技术都是手段而已。 第四章 免费时代 痛饮狂歌空度日,飞扬跋扈为谁雄 下面这一条摘抄是重点:必考内容,送分题 你究竟拿什么免费? 这个东西会不会成为一项基础服务? 通过免费能不能得到用户? 在得到用户和免费的基础上,有没有机会做出新的增值服务? 增值服务的用户愿意付费吗? |——— 如果你能回答清楚这些问题,就是一个好的商业模式。 如果我手里有1 000万,在中国打一则广告连个响儿也没有,我还不如花1 000万做一款免费的互联网产品,给几千万用户用,这几千万用户用了我的产品,就建立了对我品牌的认知、忠诚、信任,这比广告有效得多。 我军撤出延安的时候,很多人都不理解,说革命根据地不能说丢就丢啊。毛泽东跟大家说了十六个字:地在人失,人地皆失;地失人在,人地皆得。 我主张把创新从神坛上拉下来,从一些细微点上进行持续创新,这样反而更有效。 第五章 体验为王 乔布斯有一天给谷歌高管打电话,说苹果iOS有一个谷歌地图图标,放大多少倍之后,第三行一个像素颜色不对,他认为这影响了iOS的美观。这就是对细节的一种坚持。 我一直说,好的用户体验,要像针扎一样,给用户一个刺激。 第六章 互联网方法论 消费者永远会问你一个问题:我上你的网站,装你的软件,你解决我的什么问题? 你可以把这看成一种头脑体操:如果我是道路设计师,如果我来设计医院,如果我来设计遥控器、手机,我应该怎么做?这个思考的过程,就是一个提升自己对体验的感觉的过程。 比尔·盖茨曾经说过,初出茅庐的时候你要自尊干什么? 附录 周鸿祎批注“遗失的乔布斯访谈” 毕加索曾说过,“好的艺术家懂得复制,伟大的艺术家则擅长偷窃”,而我们不羞于窃取伟大的想法。 懂得测量人类骑自行车的效率,这让兀鹫甘拜下风,称霸整个排行榜。 所以要有一点理想主义色彩,才能支持你长期坚韧不拔地把一件事干下去。 无论嬉皮还是屌丝其实反映的都是一种心灵的自由,都是挑战权威,不遵守当时的规则。我觉得这才是苹果“think different”的思想和来源。 p.s. 致敬老周和他的360,中国互联网的搅局者!
张大志著 书有历史了,在我上大学的时候,雷总就看了这本书。如果能在上学期间就接触到书中关于工作选择的内容,那么就可以早早的有的放矢,成为企业需要的人才,并且最重要的是避免被企业忽悠!。 内容主要看了职场部分,还是很有点启发的。 前言 想改变规则吗?那么首先是全面地了解它,然后把规则运用得比制定它的人还要好,之后我们才有资格来改写、重写规则。百尺竿头站脚,千层浪里翻身; 来自微软十大IT英雄的推荐 其实所有励志著作的核心内容都只有一句话:“只要努力,便可成功”。 初入职场——程序员的职场成长 这种受累不讨好的活儿是很难做得很开心的。对于我们而言,能做的只是给导师足够的尊重,尽可能表达我们的谢意。尽力学习,表现自己的能力。 永远记住,工作中没有什么事情是理所当然的。 尽早感受一下无奈和失败,以提高我们的耐力、磨炼我们解决问题的思路吧!尽早了解失败、明白失败的意义,并不是件坏事。 早日明确我们对利益的态度,有助于我们掌握自己在社会上的行事作风。 第2章 四招找到好工作 即我们想做又能做的事情。在这方面我们既有兴趣又具备做好工作的能力。 倒序——从最近的一份经历开始写;(符合阅读习惯。如果我们换过几次工作,招聘方还是希望看到我们在目前的公司或者最近一家公司的工作内容) 换工作的目的,就是为了让自己工作得更快乐,更有幸福感。 我要在的部门会成长到什么规模?我将有何种成长空间?(面试时候对面试官的提问) 面试时,时刻提醒自己: (1)我在这次面试当中,有什么需要提高或者改进的地方?(每次面试都应该让自己有所收获和提高) (2)公司是如何看待(定位)我要从事的职位的?(抓住机会进一步了解公司对该职位的期望) (3)如果我有幸通过今天的面试,大概多久可以收到复试的通知?(表明对进入公司的渴望) (4)最后,再次提醒自己的薪水底线,即“低于多少就不考虑相关工作机会了”。 第3章 初入职场第一年 是浑浑噩噩地在工作中消磨自己的生命,还是找份能让自己的能力发光的工作,选择权在我们自己手里。 有没有在相同纬度上的不同工作任务,比如:开发项目中,新加入的程序员是否有机会尝试从需求分析到代码开发,直至上线测试、集成测试的工作。 请永远记住“社会与学校有很大的差别,主要的一点是不能不高兴就走人!” 没有开放的心态,我们就只能每天不开心了。对不开心的事,除了有开放的心态之外,我想还应该用职业化的态度来对待,不喜欢的同事请保持同事关系,我们永远不会是朋友;不喜欢的公司要问一句能不能让自己在工作中有所长进,如果回答是肯定的,那么别犹豫努力做好;不高兴的时候可以休假,去找朋友聊聊天,看看书,调整一下自己的情绪。 销售的巧言令色、前台的和颜悦色里面都包含着职场生存的智慧,用心体会和学习,为自身所用。只有尊重经验、尊重阅历,才能少走弯路。 是否具有良好的企业文化、是否能为今后的履历加分、是否有不同的工作任务。 每名研发人员的试用期都应该是至少一年 想一想,工作为什么?工作狂会说,工作是为了更好的工作;正常人会说,工作是为了更好的生活。掌握工作和生活之间的平衡是每个人职业生涯中必不可少的一课 第4章 正确判断公司情况 人无压力轻飘飘是有理论依据的,科学实验证明,在适度的压力下工作,有助于我们的快速成长。 大可不必为此伤害我们工作的热情和良好的心情。 认清了公司的形势,掌握工作中不同的人对我们的影响程度,正确判断公司的情况之后,我们要做的就是努力工作,开发好每行代码、认真完成每项测试,让自己的努力尽早展示出成果 第5章 正确面对职场压力 庄子说:“吾生也有涯,而知也无涯,以有涯随无涯,殆已。”把我们的工作与生活之间人为地画一条线,清晰地把它们分开。工作和追求知识不是我们生活的全部,生活中还有很多美好的东西等着我们。原则上8小时工作时间以外是我们自己的时间。加班也有个下班的时候,下了班的时间就是我们自己的。充分地享受生活,享受属于我们自己的时光。 第6章 程序员与劳动法 工资条、收入证明和离职证明是我每个员工早晚要从HR手里拿到的三样东西。 渐入佳境——成熟並非遥不可及 保持良好的心态最重要。好的心态能让我们走得更远、更开心! 看着钱的面子完成所有本职的工作 一种态度叫做事业,即力所能及地把工作做得最好,把公司的事当成是自己的事 想事业有成,那首先要做到的就是把自己的本职工作做到最好,额外付出些努力,同时也额外得到些回报 程序员所擅长的沟通是与客户沟通真实需求、与牛人沟通学习心得、与朋友沟通成长所得 第8章 加钱、加钱、加钱 我希望试用期 1500 元/月,转正之后再谈。(作者为了证明自己实力,哈哈) 薪水是非常重要的因素,但不是工作中我们唯一需要考虑的因素,请全面评估之后作出自己的选择。有时,我们还是值得在目前位置上坚持一下的。 即使我们不擅长提要求,那么每半年总结一下自己工作中取得的成绩发给相关领导 新的工作机会薪水增加在25%~30%之间是比较正常的。低于25%的增长除非有其他更好的理由。每个工作的转换都或多或少有转换成本(路途远近、新同事关系等),增长的薪水是用来弥补这些成本的, 大家更在意的是十年里我们所做的工作、我们的进步和取得的成绩。 工资,不仅仅是我们个人价值的体现,更重要的是,它是我们生活幸福、梦想成真的魔术棒。 第9章 职场转型与跳槽 《庄子》里说:“吾生也有涯,而知也无涯,以有涯随无涯,殆已。”每个人的生命都是有限的,不要浪费在无谓的追逐中。 中高级职位,公司很少放在网上。从公司角度更相信内部推荐或者内部提拔的可靠方式。 拓展人脉,我建议你多参加商务聚会,这样成本更低。 第11章 程序员与猎头 “东家,外面风大、雨大,我有去闯的心,真不成俺还回来继续好好干” 猎头行业永远是一个晴天送伞的行业 还有一种相对间接,但可能更容易被客户接受。那就是告诉客户,我能帮你把在公司里你看不顺眼的人“铲走”(挖走),让你眼前清静。后者,就是所谓的“猎头陷阱”。 “不行春风,莫望秋雨” 第13章 创业者的话,别全信 创业之前,应该看看自己有什么?什么是自己擅长的?能为世界做什么?有什么样的社会资源可供自己在创业时使用?没有收入自己能坚持多久?自己是否只擅长技术?有没有好的合作伙伴可以在创业中互补? 薪酬始终包括两部分:为技能而支付的,为忠诚而支付的。 如果非要说创业成功有什么诀窍,那我认为确实有一点:正确的坚持 羊皮卷的实践 一门功课、一个课后作业、一次跟导师合作的项目机会,只要我们从中有所收获,都可以为视为经验;
本文最早原创首发于公众号:老王和他的IT界朋友们 原文地址: https://mp.weixin.qq.com/s?__biz=MzIyMDQwNzc5MQ==&mid=2247484146&idx=1&sn=7fd4c13f7f5ac4e708c604124f8920a9&chksm=97cd3072a0bab964cbe16dc2bfc76adc5e2dfc88c6e0b9e2ab0594a66fd45442888acb8e3a63#rd 作者:躺在海里的猫喵 最近由于课题原因,需要学习C++和OpenCV相关的知识,经常逛CSDN,然后就认识了老王,还有他IT的朋友们。细聊之后,发现你们是一群灵魂有趣,而且特别有情怀的人。毕竟这个年代,有趣的灵魂太少,竟让我有些许欣喜。 写到这里,忽然就词穷了,看来书还是要多读的,今年的100本书计划已经搁置好久了,从开年到现在只读完了7本书,还是假期在家的时候读的,推荐两本书,一本是《卡尔威特的教育》,对如何启蒙孩子很有帮助,即使我们现在大都还没有孩子,但是婚总会结的,孩子也会有的,算是未雨绸缪吧。还有一本是《巴尔扎克集:三十岁的女人》,爱情,婚姻,责任,很多东西值得去体味。人类堕落的方式各有各的不同,优秀者的共性却也极为相似,这也是为什么要读书,要终生学习的原因。 再推荐一部纪录片《Becoming Warren Buffett 》(成为沃伦巴菲特),这部纪录片向我们展示了一个事实:一个人一生如果想要获得过人的成就,注定要与读书和终生学习形影不离。这部记录片里,还原了一个真实的巴菲特,可以看到一个活生生的人,除去身上的光环,他只是一个平凡的老人而已。唯一不同的是,他比较自律,一生都在读书,保持学习。而实现终生学习的最佳途径就是阅读大量优秀的书籍。这部记录片,也说明了:成功是枯燥的,也是孤独的。然而孤独的最高境界终究是繁华,只有耐得住那份孤独,才能有资格享受那份繁华。 言归正传,不知如何形容你们,你们对代码的态度,那真是对技术的执着,不知道你们是否有信仰,大概就是那种感觉,虽然我是个没有信仰的人,用你们的话说就是技术宅,写不出满意的代码,那就不要睡觉。软件、算法是一件可以穷尽一生去琢磨的事情,需要的是匠人精神。 在我印象中,你们不仅执着,而且还有一种追求极致的态度,这也是我所缺少的,说出来不怕你们笑话,我是一个对自己要求不高的人,只要能实现功能就可以了,管它花了几秒,占用多大内存,可是你们会一遍遍的优化程序,想着怎么带来新的体验,有什么新的功能可以开发,更好的服务客户。虽然在这个过程中,有些开发者是因为危机意识,但是这种追求极致的品质是值得一提的,这一点也是值得我去借鉴和学习的。 我觉得自己还蛮有耐心的,可是相对于你们来说,真的是小巫见大巫了。刚接触代码不久,说个最简单的例子,VS和OpenCV之间需要各种配置,不同版本之间配置也有所区别,不同版本的函数库也不一样,对我来说真的是分不清的Dao Rai Mi,一不小心就各种Bug出现,编程的时候,对逻辑能力也是比较考验的,我的逻辑能力就不行,只要复杂一点,就怎么也想不明白了。Debug的过程我一直觉得是一个非常孤独的过程,也是极其需要耐心的,可能在你们眼里是一种乐趣吧。 在我眼中,这个优化的过程是特别艰难的,因为在这个过程中,需要保持空杯心态,如果始终在过去的逻辑里转悠,那么可能不会有什么新的突破,这时候就需要放空原来的已知,才能得到更多的未知。前几天看了一篇文章,说人的认知有四种状态[1],也是人的四种境界: 不知道自己不知道—以为自己什么都知道,自以为是的认知状态。 知道自己不知道—有敬畏之心,开始空杯心态,准备丰富自己的认知。 知道自己知道—抓住了事情的规律,提升了自己的认知。 不知道自己知道—永远保持空杯心态,认知的最高境界。 人与人的根本区别就在于这四种状态,更可怕的是,95%的人都处于第一种状态,甚至更多,这也就是为什么大多数人都在感叹“最怕一生碌碌无为,安慰自己平凡可贵。”有时候,你以为眼前的就是全世界,其实是因为眼界太狭隘,没有见过更大的世界而已。 所谓成长,就来自于认知,而认知的本质也是做选择。人和人一旦产生认知差别,就会做出完全不一样的选择,而这些选择,就决定了会有不同的人生轨迹。只有一个人,不断想学习,想了解,去反思;保持空杯心态,放下恐惧,不再拒绝改变,才可能真正的成长,实现跨越。《达摩流浪者》结尾有这么一句话“O ever youthful, O everweeping”,翻译过来是“永远年轻,永远热泪盈眶”,我想放在这里也很合适。 学习编程对我来说是一个全新的领域,面对新的领域有好奇,但更多的是一种焦虑感,因为要学习的东西太多,慢慢来,每天进步一点点我就很知足了。 很高兴认识你们,跟着你们入门,学习的路上不孤单。 参考: 1、《所谓成长就是认知升级》,傅盛,来源公众号Talent Guide
Sqoop是一个用来将hadoop和关系型数据库中的数据相互转移的工具,可以将一个关系型数据库(例如:mysql,oracle,等)中的数据导入到hadoop的HDFS中,也可以将HDFS的数据导入到关系型数据库中。 1.简介 首先切换到到hadoop用户:su - hadoop 温馨提示:oracle的所有表名列名都要大写!!! 下面的代码,在命令行输入的时候,可能要写成一行 比如第一条需要写成: sqoop export --table Oracle_table_name --connect jdbc:oracle:thin:@ip:1521:数据库名 --username 用户名 --password 密码 --export-dir hdfs:/user/hive/warehouse/XXX --columns COLUMN1,2,3 --input-fields-terminated-by '\001' --input-lines-terminated-by '\n' 1.导hive表到Oracle sqoop export --table Oracle_table_name --connect jdbc:oracle:thin:@ip:1521:数据库名 --username 用户名 --password 密码 --export-dir hdfs:/user/hive/warehouse/XXX --columns COLUMN1,2,3, --input-fields-terminated-by '\001' #或者其他分隔符,比如逗号等 --input-lines-terminated-by '\n' 注意:导hive表是“\001” –解释: sqoop export –table Oracle_table_name(// 数据库Oracle的表名) –connect jdbc:oracle:thin:@ip:1521:数据库名 (//数据库的地址,其中1521为端口号,默认都为1521,ibd为数据库实例名) –username用户名(//数据库用户名) –password用户名(//数据库密码) –export-dir hdfs:/user/hive/warehouse/XXX (//hdfs上Hive表的绝对路径) –columns column1, column2… (//数据库表的所有列名) –input-fields-terminated-by ‘\001’(列分隔符) –input-lines-terminated-by ‘\n’ (行分隔符) 2. 查询数据导入到Oracle的数据是否成功 sqoop eval --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username XXX --password XXX --query "select * from table_name" 3.导Oracle表到hive表 sqoop import --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username xxx --password xxx --table TABLE_NAME --columns COLUMN1,2,3... --hive-import --hive-table hive_table_name -m 1 —解释: TABLE_NAME为oracle表名(切忌:Oracle个表名在命令中必须是大写,不然会报错) hive_test为hive表名(hive中的这个表可以不用提前建,只用给表名即可,导成功后列名和Oracle表的列名相同) 4. 连接oracle数据库,列出数据库中的表 sqoop list-tables --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username xxx --password xxx 5.从数据库导出表的数据到HDFS文件(这个比较实用) sqoop import --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username xxx --password xxx --table DD --m 1 --target-dir /home/dpt 解释: DD为oracle表名(切忌:Oracle表名在命令中必须是大写,不然会报错); /home/dpt为目的目录,如果没有这个目录则会在hdfs上自动创建这个目录. 导成功后可以用命令查看: hadoop fs -text /home/dpt/part-m-00000 6.分区表的导入 通过sqoop将hive中的表导入到oracle中 sqoop export --table t_amap_all --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username xxx --password xxx --export-dir hdfs://user/hive/warehouse/ --columns 1,2,3... --input-fields-terminated-by '\t' --input-lines-terminated-by '\n'; 导入分区表需要指定到具体分区目录,不然会找不到数据,在oracle表中能指定分区这个字段! 分隔符要遵循hive表的具体分隔符 导致任务失败有可能是表名不一致,字段不一致,oracle中的字段大小不够 2.可能遇到的问题 连接oracle数据库,列出数据库中的表时 sqoop list-tables --connect jdbc:oracle:thin:@YOUR-IP-ADDRESS:1521:database-name --username xxx --password xxx 报错: 16/01/28 09:27:15 ERROR sqoop.Sqoop: Got exception running Sqoop: java.lang.RuntimeException: Could not load db driver class: oracle.jdbc.OracleDriver 则 1)发现sqoop的安装目录 /usr/lib/sqoop/lib中缺ojdbc驱动包,然后将驱动包(ojdbc6-11.2.0.1.0.jar)复制到your-ip的sqoop安装目录就可以了: scp ./ojdbc6-11.2.0.1.0.jar root@your-ip:/usr/lib/sqoop/lib 参考链接 1.下载链接: https://github.com/apache/sqoop 2.官方文档: http://sqoop.apache.org/docs/1.4.6/SqoopUserGuide.html#_introduction 3.官网: http://sqoop.apache.org/
作者:黄永刚 Practical machine learning tricks from the KDD 2011 best industry paper 原文链接:http://blog.david-andrzejewski.com/machine-learning/practical-machine-learning-tricks-from-the-kdd-2011-best-industry-paper/ 研究机器学习的论文通常倾向于提出一种新理论或算法,对于问题背景、数据表示、特征工程等往往是只言片语,然而这些东西对于读者的理解和算法的重现是非常重要的。鉴于论文目的和格式的约束,只能用有限的文字去描述更核心通常比较抽象的思想。 因此,对于在工业系统中应用论文中的方法所必须的实现细节,论文中很少进行描述。机器学习的这些方面,被称为‘平民智慧’,大多数来自同事间的讨论、博客、论坛、开源库等一手的经验之谈。 不同于以上的描述,有些会议设有专题对工业界的方法进行追踪,提出了很多能够提高机器学习在实践应用中效率的见解。我们下来要介绍的这篇文章,它来自于goolge荣获KDD 2011 最佳工业论文,关于检测广告作弊。 Detecting Adversarial Advertisements in the Wild \ D. Sculley, Matthew Otey, Michael Pohl, Bridget Spitznagel, \ John Hainsworth, Yunkai Zhou \ http://research.google.com/pubs/archive/37195.pdf 看到这个主题,第一个想法,这不就是机器学习界的“hello world”吗!随便找一本相关的书籍或者教程里面都有,对于正样本集和负样本集分别训练一个朴素贝叶斯,就OK了。很显然,这和Google的应用场景相差甚远,这篇文章阐述了现实当中的许多挑战,是google商业应用需要解决的关键问题。 这篇文章提出了很多不同的方法和技巧,我在这里只简单的描述文章中的重点,我极力鼓励对此感兴趣的读者直接去查看他们的论文1和演示文稿[^slide]。 2:https://www.eecs.tufts.edu/~dsculley/papers/adversarial-ads.pdf http://www.eecs.tufts.edu/~dsculley/papers/Detecting_Adversarial_Advertisements.pdf 1. 分类(Classification) 机器学习的核心方法是分类:这个广告展示给用户是不是OK?这里有一些源代码关于机器学习的核心方法3 方法集成 获得Netflix奖的方法、微软的Kinect及IBM Watson,这些系统都使用了集成方法,将许多不同模型产生的结果集合起来做出最终的预测。这个方法在当前众多的方法中可以称得上是最省心的方法了,如果你的目标是预测精确度,至少也应该考虑使用集成的方法。 只执行高可靠的预测 衡量预测的不确定性并适当的对系统的执行条件进行调整是非常必要的。在这个应用中,需要做出合理的决策,因此,当预测结果可靠性不高时,系统应该不执行相应的动作。 找出大量的特征,使用L1正则进行特征选择 特征表示是机器学习设计中的关键问题,它涵盖了非常广的范围:对于广告有表述的用词、表述主题、链接到的网站、链接落地页、广告商等等,会产生大量的特征,使用L1正则强制稀疏化特征集,最终得到较少的与结果关联性强的特征。 特征降维 处理高维特征这是十分实用的方法,通过降维将特征映射到低维空间。 使用排序来处理不均衡问题 这种极不均衡数据问题是典型的监督式分类中的疑难杂症,广告中大多是正常数据,只有极少量是异常数据[此类问题十分常见]。这类问题有很多的处理方法,在这里他们通过将这个问题转化为排序问题,获得了性能上的改进。恶意广告应该比正常广告获得更高的排名。 使用分类器的级联 还是不均衡问题,对于负样本中也存在不同的种类,如恶意软件刷点击、假冒伪劣商品等。这里同时采用了两阶段的分类。第一阶段判断正常或者异常?第二阶段,如果这个广告属于异常,是不是属于异常A,是不是异常B,以此类推。 2. 可扩展性、工程实现、操作 不同于为了发论文所写的实验性软件,线上的机器学习系统是有工程和商业背景。系统的可扩展性、可用性、可靠性、可维护性也十分重要。 MapReduce:预处理(map), 算法训练(reduce) 稍微有些意外,他们发现性能瓶颈是来自于从磁盘加载数据和提取特征的阶段。因此,他们使用多个map作业并行执行,用一个reduce来做随机梯度下降分类训练(Stochastic Gradient Descent, SGD) 部署监控 为了使系统始终如一的工作,需要监控一些数据,以便于一些异常出现的时候能够做进一步的研究,如: - 持有数据上的precision/recall - 输入特征的分布 - 输出值的分布 - 输出类别的分布 - 人工定期评价系统质量 丰富模型对象 在机器学习的论文中,一个预测模型经常归结于数学思想即学习到的特征权重向量。然而,在软件工程实践中,作者认为将“模型对象”拓展更广泛的范围会十分有用,例如包含特征转换、概率校准、训练超参等。 3. 人工经验 基于商业考量,提出通用的解决方法需要人类专家的参与。 有效的利用专家经验 对于界限模糊的情况或比较难分辨的情况,人工专家手工进行标注,然后采用主动学习策略识别这些高价值数据。他们为人类专家提供了可以获取信息的用户接口,以便发现新出现的异常威胁。 允许人为的编制规则 相比于全自动化的机器学习方法,有的时候人类才是知道如何做才最好。因此,他们允许专家们在适当的时候,编制一些规则进去。 人为评估 专家的判断也不能理解为事情的本质,专家提供的标签也会因人为因素产生错误,不同的专家对于各个类别理解的也不一样。为了调整这种不确定性, 请多个专家对同一事物进行判别来调整标签的可信度。如果有兴趣可以参考这里4. 最后,他们阶段性进行非专业评估以确保系统对于大众来说工作正常。用户满意是最终目标,如果进行量化衡量就完美了。 https://www.eecs.tufts.edu/~dsculley/papers/adversarial-ads.pdf [^slide]:http://www.eecs.tufts.edu/~dsculley/papers/Detecting_Adversarial_Advertisements.pdf ↩ https://www.eecs.tufts.edu/~dsculley/papers/adversarial-ads.pdf [^slide]:http://www.eecs.tufts.edu/~dsculley/papers/Detecting_Adversarial_Advertisements.pdf ↩ http://code.google.com/p/sofia-ml/ ↩ http://web.stanford.edu/~jurafsky/amt.pdf ↩
第十三章 全文检索 这一章开始介绍 全文检索 :怎样对全文字段(full-text fields)进行检索以找到相关度最高的文档。 全文检索最重要的两个方面是: 相关度(Relevance) 根据文档与查询的相关程度对结果集进行排序的能力。相关度可以使用TF/IDF、地理位置相近程度、模糊相似度或其他算法计算。 分析(Analysis) 将一段文本转换为一组唯一的、标准化了的标记(token),用以(a)创建倒排索引,(b)查询倒排索引。 注意,一旦提到相关度和分析,指的都是查询(queries)而非过滤器(filters)。 基于短语 vs. 全文 虽然所有的查询都会进行相关度计算,但不是所有的查询都有分析阶段。而且像bool或function_score这样的查询并不在文本字段执行。文本查询可以分为两大类: 1. 基于短语(Term-based)的查询: 像term或fuzzy一类的查询是低级查询,它们没有分析阶段。这些查询在单一的短语上执行。例如对单词'Foo'的term查询会在倒排索引里精确地查找'Foo'这个词,并对每个包含这个单词的文档计算TF/IDF相关度'_score'。 牢记term查询只在倒排查询里精确地查找特定短语,而不会匹配短语的其它变形,如foo或FOO。不管短语怎样被加入索引,都只匹配倒排索引里的准确值。如果你在一个设置了'not_analyzed'的字段为'["Foo", "Bar"]'建索引,或者在一个用'whitespace'解析器解析的字段为'Foo Bar'建索引,都会在倒排索引里加入两个索引'Foo'和'Bar'。 2. 全文(Full-text)检索 match和query_string这样的查询是高级查询,它们会对字段进行分析: 如果检索一个'date'或'integer'字段,它们会把查询语句作为日期或者整数格式数据。 如果检索一个准确值('not_analyzed')字符串字段,它们会把整个查询语句作为一个短语。 如果检索一个全文('analyzed')字段,查询会先用适当的解析器解析查询语句,产生需要查询的短语列表。然后对列表中的每个短语执行低级查询,合并查询结果,得到最终的文档相关度。 我们将会在后续章节讨论这一过程的细节。 我们很少需要直接使用基于短语的查询。通常我们会想要检索全文,而不是单独的短语,使用高级的全文检索会更简单(全文检索内部最终还是使用基于短语的查询)。 13.1 匹配查询 不管搜索什么内容,match查询是首先需要接触的查询。它是一个高级查询,意味着match查询知道如何更好的处理全文检索和准确值检索。 这也就是说,match查询的一个主要用途是进行全文搜索。通过一个小例子来看一下全文搜索是如何工作的。 索引一些数据 首先,我们使用bulk API来创建和索引一些文档: DELETE /my_index <1> PUT /my_index_second { "settings": { "number_of_shards": 1 }} <2> POST /my_index/my_type/_bulk { "index": { "_id": 1 }} { "title": "The quick brown fox" } { "index": { "_id": 2 }} { "title": "The quick brown fox jumps over the lazy dog" } { "index": { "_id": 3 }} { "title": "The quick brown fox jumps over the quick dog" } { "index": { "_id": 4 }} { "title": "Brown fox brown dog" } <1> 删除已经存在的索引(如果索引存在) <2> 然后,关联失效这一节解释了为什么我们创建该索引的时候只使用一个主分片。 单词查询 第一个例子解释了当使用match查询进行单词全文搜索时发生了什么: GET /my_index_second/my_type/_search { "query": { "match": { "title": "QUICK!" } } } Elasticsearch通过下面的步骤执行match查询: 检查field类型 title字段是一个字符串(analyzed),所以该查询字符串也需要被分析(analyzed) 分析查询字符串 查询词QUICK!经过标准分析器的分析后变成单词quick。因为我们只有一个查询词,因此match查询可以以一种低级别term查询的方式执行。 找到匹配的文档 term查询在倒排索引中搜索quick,并且返回包含该词的文档。在这个例子中,返回的文档是1,2,3。 为每个文档打分 term查询综合考虑词频(每篇文档title字段包含quick的次数)、逆文档频率(在全部文档中title字段包含quick的次数)、包含quick的字段长度(长度越短越相关)来计算每篇文档的相关性得分_score。(更多请见相关性介绍) 这个过程之后我们将得到以下结果(简化后): "hits": [ { "_id": "1", "_score": 0.5, <1> "_source": { "title": "The quick brown fox" } }, { "_id": "3", "_score": 0.44194174, <2> "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_id": "2", "_score": 0.3125, <2> "_source": { "title": "The quick brown fox jumps over the lazy dog" } } ] <1> 文档1最相关,因为 title 最短,意味着quick在语义中起比较大的作用。 <2> 文档3比文档2更相关,因为在文档3中quick出现了两次。 多词查询 如果一次只能查询一个关键词,全文检索将会很不方便。幸运的是,用match查询进行多词查询也很简单: GET /my_index_second/my_type/_search { "query": { "match": { "title": "BROWN DOG!" } } } 上面这个查询返回以下结果集: { "hits": [ { "_id": "4", "_score": 0.73185337, <1> "_source": { "title": "Brown fox brown dog" } }, { "_id": "2", "_score": 0.47486103, <2> "_source": { "title": "The quick brown fox jumps over the lazy dog" } }, { "_id": "3", "_score": 0.47486103, <2> "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_id": "1", "_score": 0.11914785, <3> "_source": { "title": "The quick brown fox" } } ] } <1> 文档4的相关度最高,因为包含两个”brown”和一个”dog”。 <2> 文档2和3都包含一个”brown”和一个”dog”,且’title’字段长度相同,所以相关度相等。 <3> 文档1只包含一个”brown”,不包含”dog”,所以相关度最低。 因为match查询需要查询两个关键词:"brown"和"dog",在内部会执行两个term查询并综合二者的结果得到最终的结果。match的实现方式是将两个term查询放入一个bool查询,bool查询在之前的章节已经介绍过。 重要的一点是,'title'字段包含至少一个查询关键字的文档都被认为是符合查询条件的。匹配的单词数越多,文档的相关度越高。 提高精度 匹配包含任意个数查询关键字的文档可能会得到一些看似不相关的结果,这是一种霰弹策略(shotgun approach)。然而我们可能想得到包含所有查询关键字的文档。换句话说,我们想得到的是匹配'brown AND dog'的文档,而非'brown OR dog'。 match查询接受一个'operator'参数,默认值为or。如果要求所有查询关键字都匹配,可以更改参数值为and: GET /my_index/my_type/_search { "query": { "match": { "title": { <1> "query": "BROWN DOG!", "operator": "and" } } } } <1> 为了加入'operator'参数,match查询的结构有一些不同。 这个查询会排除文档1,因为文档1只包含了一个查询关键词。 控制精度 在 all 和 any 之间的选择有点过于非黑即白。如果用户指定了5个查询关键字,而一个文档只包含了其中的4个?将'operator'设置为'and'会排除这个文档。 有时这的确是用户想要的结果。但在大多数全文检索的使用场景下,用户想得到相关的文档,排除那些不太可能相关的文档。换句话说,我们需要介于二者之间的选项。 match查询有'minimum_should_match'参数,参数值表示被视为相关的文档必须匹配的关键词个数。参数值可以设为整数,也可以设置为百分数。因为不能提前确定用户输入的查询关键词个数,使用百分数也很合理。 GET /my_index/my_type/_search { "query": { "match": { "title": { "query": "quick brown dog", "minimum_should_match": "75%" } } } } 当'minimum_should_match'被设置为百分数时,查询进行如下:在上面的例子里,'75%'会被下舍为'66.6%',也就是2个关键词。不论参数值为多少,进入结果集的文档至少应匹配一个关键词。 [提示] 'minimum_should_match'参数很灵活,根据用户输入的关键词个数,可以采用不同的匹配规则。 要全面理解match查询是怎样处理多词查询,我们需要了解怎样用bool查询合并多个查询。 13.2 组合查询 在《组合过滤》中我们讨论了怎样用布尔过滤器组合多个用and, or, and not逻辑组成的过滤子句,在查询中, 布尔查询充当着相似的作用,但是有一个重要的区别。 过滤器会做一个判断: 是否应该将文档添加到结果集? 然而查询会做更精细的判断. 他们不仅决定一个文档是否要添加到结果集,而且还要计算文档的相关性(relevant). 像过滤器一样, 布尔查询接受多个用must, must_not, and should的查询子句. 例: GET /my_index/my_type/_search { "query": { "bool": { "must": { "match": { "title": "quick" }}, "must_not": { "match": { "title": "lazy" }}, "should": [ { "match": { "title": "brown" }}, { "match": { "title": "dog" }} ] } } } 在前面的查询中,凡是满足title字段中包含quick,但是不包含lazy的文档都会在查询结果中。到目前为止,布尔查询的作用非常类似于布尔过滤的作用。 当should过滤器中有两个子句时不同的地方就体现出来了,下面例子就可以体现:一个文档不需要同时包含brown和dog,但如果同时有这两个词,这个文档的相关性就更高: { "hits": [ { "_id": "3", "_score": 0.70134366, <1> "_source": { "title": "The quick brown fox jumps over the quick dog" } }, { "_id": "1", "_score": 0.3312608, "_source": { "title": "The quick brown fox" } } ] } <1> 文档3的得分更高,是因为它同时包含了brown 和 dog。 得分计算 布尔查询通过把所有符合must 和 should的子句得分加起来,然后除以must 和 should子句的总数为每个文档计算相关性得分。 must_not子句并不影响得分;他们存在的意义是排除已经被包含的文档。 精度控制 所有的 must 子句必须匹配, 并且所有的 must_not 子句必须不匹配, 但是多少 should 子句应该匹配呢? 默认的,不需要匹配任何 should 子句,一种情况例外:如果没有must子句,就必须至少匹配一个should子句。 像我们控制match查询的精度一样,我们也可以通过minimum_should_match参数控制多少should子句需要被匹配,这个参数可以是正整数,也可以是百分比。 GET /my_index/my_type/_search { "query": { "bool": { "should": [ { "match": { "title": "brown" }}, { "match": { "title": "fox" }}, { "match": { "title": "dog" }} ], "minimum_should_match": 2 <1> } } } <1> 这也可以用百分比表示 结果集仅包含title字段中有"brown"和 "fox", "brown"和 "dog", 或 "fox" 和"dog"的文档。如果一个文档包含上述三个条件,那么它的相关性就会比其他仅包含三者中的两个条件的文档要高。 13.3 match匹配怎么当成布尔查询来使用 到现在为止,你可能已经意识到在一个布尔查询中多字段match查询仅仅包裹了已经生成的term查询。通过默认的or操作符,每个term查询都会像一个should子句一样被添加,只要有一个子句匹配就可以了。下面的两个查询是等价的: { "match": { "title": "brown fox"} } { "bool": { "should": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } } 通过and操作符,所有的term查询会像must子句一样被添加,因此所有的子句都必须匹配。下面的两个查询是等价的: { "match": { "title": { "query": "brown fox", "operator": "and" } } } { "bool": { "must": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }} ] } } 如果minimum_should_match参数被指定,match查询就直接被转换成一个bool查询,下面两个查询是等价的: { "match": { "title": { "query": "quick brown fox", "minimum_should_match": "75%" } } } { "bool": { "should": [ { "term": { "title": "brown" }}, { "term": { "title": "fox" }}, { "term": { "title": "quick" }} ], "minimum_should_match": 2 <1> } } <1>因为只有三个子句,所以 minimum_should_match参数在match查询中的值75%就下舍到了2。3个should子句中至少有两个子句匹配。 当然,我们通常写这些查询类型的时候还是使用match查询的,但是理解match查询在内部是怎么工作的可以让你在任何你需要使用的时候更加得心应手。有些情况仅仅使用一个match查询是不够的,比如给某些查询词更高的权重。这种情况我们会在下一节看个例子。 13.4 提高查询得分 当然,bool查询并不仅仅是组合多个简单的一个词的match查询。他可以组合任何其他查询,包括bool查询。bool查询通常会通过组合几个不同查询的得分为每个文档调整相关性得分。 假设想查找关于”full-text search”的文档,但是我们又想给涉及到“Elasticsearch”或者“Lucene”的文档更高的权重。我们的用意是想涉及到”Elasticsearch” 或者 “Lucene”的文档的相关性得分会比那些没有涉及到的文档的得分要高,也就是说这些文档会出现在结果集更靠前的位置。 一个简单的bool查询允许我们写出像下面一样的非常复杂的逻辑: GET /_search { "query": { "bool": { "must": { "match": { "content": { (1) "query": "full text search", "operator": "and" } } }, "should": [ (2) { "match": { "content": "Elasticsearch" }}, { "match": { "content": "Lucene" }} ] } } } content字段必须包含full,text,search这三个单词。 如果content字段也包含了“Elasticsearch”或者“Lucene”,则文档会有一个更高的得分。 匹配的should子句越多,文档的相关性就越强。到目前为止一切都很好。但是如果我们想给包含“Lucene”一词的文档比较高的得分,甚至给包含“Elasticsearch”一词更高的得分要怎么做呢? 同时可以在任何查询子句中指定一个boost值来控制相对权重,默认值为1。一个大于1的boost值可以提高查询子句的相对权重。因此我们可以像下面一样重写之前的查询: GET /_search { "query": { "bool": { "must": { "match": { (1) "content": { "query": "full text search", "operator": "and" } } }, "should": [ { "match": { "content": { "query": "Elasticsearch", "boost": 3 (2) } }}, { "match": { "content": { "query": "Lucene", "boost": 2 (3) } }} ] } } } 这些查询子句的boost值为默认值1。 这个子句是最重要的,因为他有最高的boost值。 这个子句比第一个查询子句的要重要,但是没有“Elasticsearch”子句重要。 注意: boost参数用于提高子句的相对权重(boost值大于1)或者降低子句的相对权重(boost值在0-1之间),但是提高和降低并非是线性的。换句话说,boost值为2并不能够使结果变成两倍的得分。 另外,boost值被使用了以后新的得分是标准的。每个查询类型都会有一个独有的标准算法,算法的详细内容并不在本书的范畴。简单的概括一下,一个更大的boost值可以得到一个更高的得分。 如果你自己实现了没有基于TF/IDF的得分模型,但是你想得到更多的对于提高得分过程的控制,你可以使用function_score查询来调整一个文档的boost值而不用通过标准的步骤。 13.5 分析控制 查询只能查找在倒排索引中出现的词,所以确保在文档索引的时候以及字符串查询的时候使用同一个分析器是很重要的,为了查询的词能够在倒排索引中匹配到。 尽管我们说文档中每个字段的分析器是已经定好的。但是字段可以有不同的分析器,通过给那个字段配置一个指定的分析器或者直接使用类型,索引,或节点上的默认分析器。在索引的时候,一个字段的值会被配置的或者默认的分析器分析。 举个例子,让我们添加一个字段到my_index: PUT /my_index/_mapping/my_type { "my_type": { "properties": { "english_title": { "type": "string", "analyzer": "english" } } } } 现在我们可以通过analyzeAPI分析Foxes词来比较english_title字段中的值以及title字段中的值在创建索引的时候是怎么被分析的: GET /my_index/_analyze?field=my_type.title <1> Foxes GET /my_index/_analyze?field=my_type.english_title <2> Foxes <1> 使用standard分析器的title字段将会返回词foxes。 <2> 使用english分析器的english_title字段将会返回词fox。 这意味着,如果我们为精确的词fox执行一个低级别的term查询,english_title字段会匹 配而title字段不会。 像match查询一样的高级别的查询可以知道字段的映射并且能够在被查询的字段上使用正确的分析器。我们可以在validate-query API的执行中看到这个: GET /my_index/my_type/_validate/query?explain { "query": { "bool": { "should": [ { "match": { "title": "Foxes"}}, { "match": { "english_title": "Foxes"}} ] } } } 它会返回explanation: (title:foxes english_title:fox) match查询为每个字段使用合适的分析器用来确保在那个字段上可以用正确的格式查询这个词。 默认分析器 虽然我们可以在字段级别指定一个分析器,但是如果我们在字段级别没有指定分析器的话,那要怎么决定某个字段使用什么分析器呢? 分析器可以在好几个地方设置。Elasticsearch会查找每个级别直到找到它可以使用的分析器。在创建索引的时候,Elasticsearch查找分析器的顺序如下: 在映射文件中指定字段的analyzer,或者 在文档的_analyzer字段上指定分析器,或者 在映射文件中指定类型的默认分析器analyzer 在索引映射文件中设置默认分析器default 在节点级别设置默认分析器default standard分析器 查找索引的时候,Elasticsearch查找分析器的顺序稍微有点不一样: 在查询参数中指定analyzer,或者 在映射文件中指定字段的analyzer,或者 在映射文件中指定类型的默认分析器analyzer 在索引映射文件中设置默认分析器default 在节点级别设置默认分析器default standard分析器 提示: 上面列表中用斜体字的两行突出了创建索引以及查询索引的时候Elasticsearch查找分析器的区别。_analyzer字段允许你为每个文档指定默认的分析器(比如, english, french, spanish),虽然在查询的时候指定analyzer参数,但是在一个索引中处理多种语言这并不是一个好方法,因为在多语言环境下很多问题会暴露出来。 有时候,在创建索引与查询索引的时候使用不同的分析器也是有意义的。举个例子:在创建索引的时候想要索引同义词 (比如, 出现quick的时候,我们也索引 fast, rapid, 和 speedy)。但是在查询索引的时候,我们不需要查询所有的同义词,我们只要查询用户输入的一个单词就可以了,它可以是quick, fast, rapid, 或者 speedy。 为了满足这种差异,Elasticsearch也支持index_analyzer 和 search_analyzer 参数,并且分析器被命名为default_index和default_search。 把这些额外的参数考虑进去,Elasticsearch查找所有的分析器的顺序实际上像下面的样子: 在映射文件中指定字段的index_analyzer,或者 在映射文件中指定字段的analyzer,或者 在文档的_analyzer字段上指定分析器,或者 在映射文件中指定类型的创建索引的默认分析器index_analyzer 在映射文件中指定类型的默认分析器analyzer 在索引映射文件中设置创建索引的默认分析器default_index 在索引映射文件中设置默认分析器default 在节点级别设置创建索引的默认分析器default_index 在节点级别设置默认分析器default standard分析器 以及查询索引的时候: 在查询参数中指定analyzer,或者 在映射文件中指定字段的search_analyzer,或者 在映射文件中指定字段的analyzer,或者 在映射文件中指定类型的查询索引的默认分析器analyzer 在索引映射文件中设置查询索引的默认分析器default_search 在索引映射文件中设置默认分析器default_search 在节点级别设置查询索引的默认分析器default_search 在节点级别设置默认分析器default standard 分析器 实际配置分析器 可以指定分析器的地方实在是太多了,但实际上,指定分析器很简单。 用索引配置,而不是用配置文件 第一点要记住的是,尽管开始使用Elasticsearch仅仅只是为了一个简单的目的或者是一个应用比如日志,但很可能你会发现更多的案例,结局是在同一个集群中运行着好几个不同的应用。每一个索引都需要是独立的,并且可以被独立的配置。你不要想着给一个案例设置默认值,但是不得不重写他们来适配后面的案例。 这个规则把节点级别的配置分析器方法排除在外了。另外,节点级别的分析器配置方法需要改变每个节点的配置文件并且重启每个节点,这将成为维护的噩梦。保持Elasticsearch持续运行并通过API来管理索引的设置是一个更好的方法。 保持简便性 大多数时间,你可以预先知道文档会包含哪些字段。最简单的方法是在你创建索引或者添加类型映射的时候为每一个全文检索字段设置分析器。虽然这个方法有点啰嗦,但是它可以很容易的看到哪个字段应用了哪个分析器。 通常情况下,大部分的字符串字段是确切值not_analyzed字段(索引但不分析字段)比如标签,枚举,加上少数全文检索字段会使用默认的分析器,像standard 或者 english 或者其他语言。然后你可能只有一到两个字段需要定制分析:或许title字段需要按照你查找的方式去索引来支持你的查找。(指的是你查找的字符创用什么分析器,创建索引就用什么分析器) 你可以在索引设置default分析器的地方为几乎所有全文检索字段设置成你想要的分析器,并且只要在一到两个字段指定专门的分析器。如果,在你的模型中,你每个类型都需要不同的分析器,那么在类型级别使用analyzer配置来代替。 提示: 一个普通的像日志一样的基于时间轴的工作流数据每天都得创建新的索引,忙着不断的创建索引。虽然这种工作流阻止你预先创建索引,但是你可以使用索引模板来指定新的索引的配置和映射。 13.6 关联失效 在讨论多字段检索中的更复杂的查询前,让我们顺便先解释一下为什么我们只用一个主分片来创建索引。 有时有的新手会开一个问题说通过相关性排序没有效果,并且提供了一小段复制的结果:该用户创建了一些文档,执行了一个简单的查询,结果发现相关性较低的结果排在了相关性较高的结果的前面。 为了理解为什么会出现这样的结果,我们假设用两个分片创建一个索引,以及索引10个文档,6个文档包含词 foo,这样可能会出现分片1中有3个文档包含 foo,分片2中也有三个文档包含 foo。换句话说,我们的文档做了比较好的分布式。 在相关性介绍这一节,我们描述了Elasticsearch默认的相似算法,叫做词频率/反转文档频率(TF/IDF)。词频率是一个词在我们当前查询的文档的字段中出现的次数。出现的次数越多,相关性就越大。反转文档频率指的是该索引中所有文档数与出现这个词的文件数的百分比,词出现的频率越大,IDF越小。 然而,由于性能问题,Elasticsearch不通过索引中所有的文档计算IDF。每个分片会为分片中所有的文档计算一个本地的IDF取而代之。 因为我们的文档做了很好的分布式,每个分片的IDF是相同的。现在假设5个包含foo的文档在分片1中,以及其他6各文档在分片2中。在这个场景下,词foo在第一个分片中是非常普通的(因此重要性较小),但是在另一个分片中是很稀少的(因此重要性较高)。这些区别在IDF中就会产生不正确的结果。 事实证明,这并不是一个问题。你索引越多的文档,本地IDF和全局IDF的区别就会越少。在实际工作的数据量下,本地IDF立刻能够很好的工作(With real-world volumes of data, the local IDFs soon even out,不知道这么翻译合不合适)。所以问题不是因为关联失效,而是因为数据太少。 为了测试的目的,对于这个问题,有两种方法可以奏效。第一种方法是创建一个只有一个主分片的索引,像我们介绍match查询那节一样做。如果你只有一个分片,那么本地IDF就是全局IDF。 第二种方法是在你们请求中添加?search_type=dfs_query_then_fetch。dfs就是Distributed Frequency Search,并且它会告诉Elasticsearch检查每一个分片的本地IDF为了计算整个索引的全局IDF。 第十四章 多字段搜索 只有一个简单的match子句的查询是很少的。我们经常需要在一个或者多个字段中查询相同的或者不同的 查询字符串,意味着我们需要能够组合多个查询子句以及使他们的相关性得分有意义。 或许我们在寻找列夫·托尔斯泰写的一本叫《战争与和平》的书。或许我们在Elasticsearch的文档中查找minimum should match,它可能在标题中,或者在一页的正文中。或许我们查找名为John,姓为Smith的人。 在这一章节,我们会介绍用于构建多个查询子句搜索的可能的工具,以及怎么样选择解决方案来应用到你特殊的场景。 14.1 多重查询字段 在明确的字段中的词查询是最容易处理的多字段查询。如果我们知道War and Peace是标题,Leo Tolstoy是作者,可以很容易的用match查询表达每个条件,并且用布尔查询组合起来: GET /_search { "query": { "bool": { "should": [ { "match": { "title": "War and Peace" }}, { "match": { "author": "Leo Tolstoy" }} ] } } } 布尔查询采用越多匹配越好的方法,所以每个match子句的得分会被加起来变成最后的每个文档的得分。匹配两个子句的文档的得分会比只匹配了一个文档的得分高。 当然,没有限制你只能使用match子句:布尔查询可以包装任何其他的查询类型,包含其他的布尔查询,我们可以添加一个子句来指定我们更喜欢看被哪个特殊的翻译者翻译的那版书: GET /_search { "query": { "bool": { "should": [ { "match": { "title": "War and Peace" }}, { "match": { "author": "Leo Tolstoy" }}, { "bool": { "should": [ { "match": { "translator": "Constance Garnett" }}, { "match": { "translator": "Louise Maude" }} ] }} ] } } } 为什么我们把翻译者的子句放在一个独立的布尔查询中?所有的匹配查询都是should子句,所以为什么不把翻译者的子句放在和title以及作者的同一级? 答案就在如何计算得分中。布尔查询执行每个匹配查询,把他们的得分加在一起,然后乘以匹配子句的数量,并且除以子句的总数。每个同级的子句权重是相同的。在前面的查询中,包含翻译者的布尔查询占用总得分的三分之一。如果我们把翻译者的子句放在和标题与作者同级的目录中,我们会把标题与作者的作用减少的四分之一。 优选子句 在先前的查询中我们可能不需要使每个子句都占用三分之一的权重。我们可能对标题以及作者比翻译者更感兴趣。我们需要调整查询来使得标题和作者的子句相关性更重要。 最简单的方法是使用boost参数。为了提高标题和作者字段的权重,我们给boost参数提供一个比1高的值: GET /_search { "query": { "bool": { "should": [ { "match": { <1> "title": { "query": "War and Peace", "boost": 2 }}}, { "match": { <1> "author": { "query": "Leo Tolstoy", "boost": 2 }}}, { "bool": { <2> "should": [ { "match": { "translator": "Constance Garnett" }}, { "match": { "translator": "Louise Maude" }} ] }} ] } } } 标题和作者的boost值为2。 嵌套的布尔查询的boost值为默认的1。 给boost参数一个最好的值可以通过试验和犯错来很容易的决定:设置一个boost值,执行测试查询,重复上述过程。一个合理的boost值在1-10之间,也可能15。更高的boost值影响会很小,因为分数是标准化得。 14.2 单个查询字符串 布尔查询是多重查询的支柱,它在多数情况下有用,尤其是当你能够将不同查询字符串映射到对应的单一字段时。 问题在于,用户期望把他们所有的搜索项放到一个单独字段中去查询。并且期望这个应用能够得出他们想要的正确的结果。讽刺的是,多字段查询形式是一个高级的——它给用户呈现的形式是高级的,但是执行起来却特别简单。 对于多词,多字段查询,没有一种简单的一个通用的途径。要获得最适合的结果,你必须对你的数据有足够的了解,并且知道如何使用合适的工具。 了解你的数据 当你唯一的用户输入一个单个查询字符串,你可能经常会遇到下面三个情形: * Best fields 当搜索一个代表概念的词时,例如“brown fox”,这两个词在一起比它们单独更有意义。而像‘title’,‘body’这些字段,会被认为之间存在竞争。文档在同一个字段上会有许多值,所以得分应该来自最匹配的字段。 * Most fields 一个常用的微调相关性的方法就是为同样的数据索引到多个字段,每一个都有自己的解析链 主要的字段可能包含他们的词根,同义词,和去掉音符或者重音符的词。它用来匹配尽可能多的文档。 同一个文本可能会在其他字段建立索引以提供更加精确的匹配。一个字段可能包含非词根的版本,另一个是带着重音符的原词,第三个可能使用小招牌来提供相似词的信息 这些字段作为匹配文档的增加相关性得分标记,匹配的字段越多,越好。 * Cross fields 对于一些实体对象,标识的信息可能在多个字段之间分布,每个字段组成是其一部分。如下所示。 * Person: ‘first_name’、‘last_name’ * Book:‘title’、‘author’、‘description’ * Address:‘street’、‘city’、‘country’、‘postcode’ 在这种情况下,我们想要找到在任何一个所列字段中找到尽可能多的词。我们需要把多个字段当成一个大的字段,然后在这个字段进行搜索,所有这些都是多词的,多字段的查询,但是每种都使用不同的策略 14.3 最好的字段(Best fields) 假如我们有一个网站,允许用户搜索博客信息,例如下面这两个文档: PUT /my_index/my_type/1 { "title": "Quick brown rabbits", "body": "Brown rabbits are commonly seen." } PUT /my_index/my_type/2 { "title": "Keeping pets healthy", "body": "My quick brown fox eats rabbits on a regular basis." } 用户输入‘Brown fox’,点击搜索。提前我们不知道用户的搜索选项会被宰‘title’或‘body’字段找到,但是用户很有可能在搜索相关的单词。就人眼观察,显然文档2似乎是更好的匹配,因为两个单词被搜索的单词文档2都包含。 现在运行下面的布尔查询: GET /my_index/my_type_second/_search { "query": { "bool": { "should": [ { "match": { "title": "Brown fox" }}, { "match": { "body": "Brown fox" }} ] } } } 结果如下: "hits" : [ { "_index" : "my_index_sencond", "_type" : "my_type", "_id" : "1", "_score" : 0.029836398, "_source" : { "title" : "Quick brown rabbits", "body" : "Brown rabbits are commonly seen." } }, { "_index" : "my_index_sencond", "_type" : "my_type", "_id" : "2", "_score" : 0.01989093, "_source" : { "title" : "Keeping pets healthy", "body" : "My quick brown fox eats rabbits on a regular basis." } } ] 我们发现,这个查询给出文档1的得分更高。 为了理解这是为什么,考虑布尔查询计算得分的步骤: 1.它在should子句里运行两个匹配查询 2.它将两者得分相加 3.乘以总的匹配子句个数 4.除以总的子句个数 文档1在两个字段中都包含‘brown’,因此匹配查询成功获得得分。文档2在body字段中包含‘brown’和‘fox’,但title字段却一个单词都不包含。从body得到的高分,加上从title得到的0分,乘以1/2(它会乘以匹配到文档数目/总文档数目),所以得分就低。 dis_max match查询 现在我们使用dis_max查询或者Disjunction Max Query。Disjunction意思是或(conjunction意思是and)所以Disjunction Max Query查询简单的意思就是:返回任意一个查询匹配成功的文档,并且该文档得分最高: GET /my_index/my_type_second/_search { "query": { "dis_max": { "queries": [ { "match": { "title": "Brown fox" }}, { "match": { "body": "Brown fox" }} ] } } } 这样就返回了我们期望返回的文档了。 14.4 调整Best Field 查询 当用户将搜索内容改成“quick pets”,会发生什么?两个文档中都包含了quick,但是只有文档2中包含了pets。两个文档都没有在同一个字段中全部包含两个搜索词。 一个简单的dis_max查询会挑选出最匹配的字段,并且忽略另外一个: { "query": { "dis_max": { "queries": [ { "match": { "title": "Quick pets" }}, { "match": { "body": "Quick pets" }} ] } } } 这个执行结果中,会得出两个相同得分的文档。 我们期望同时出现在title字段和body字段的文档比只在一个字段出现搜索词的文档的得分更高,但是,显示并非如此。你需要记住的是:dis_max查询只是简单地使用单个匹配得分最高的查询而已。 tie_breaker 然而,同时把其他匹配语句的分数考虑在内也是可行的。你可以通过制定tie_breaker参数来实现 { "query": { "dis_max": { "queries": [ { "match": { "title": "Quick pets" }}, { "match": { "body": "Quick pets" }} ], "tie_breaker": 0.3 } } } tie_breaker参数会使dis_max查询的行为更像dis_max和bool的结合。它会按照下面计算得分: 先获得最匹配的得分 用tie_breaker乘以每个匹配语句的得分 把它们加在一起,然后标准化 通过tie_breaker,所有的匹配语句都会计算,并且最匹配语句得分最高 tie_breaker的值可以设为0-1之间 样例格式
最近代码写的少了,而leetcode一直想做一个python,c/c++解题报告的专题,c/c++一直是我非常喜欢的,c语言编程练习的重要性体现在linux内核编程以及一些大公司算法上机的要求,python主要为了后序转型数据分析和机器学习,所以今天来做一个难度为hard 的简单正则表达式匹配。 做了很多leetcode题目,我们来总结一下套路: 首先一般是检查输入参数是否正确,然后是处理算法的特殊情况,之后就是实现逻辑,最后就是返回值。 当编程成为一种解决问题的习惯,我们就成为了一名纯粹的程序员 leetcode 10 Regular Expression Matching (简单正则表达式匹配) 题目描述 Implement regular expression matching with support for ‘.’ and ‘*’. ‘.’ Matches any single character. ‘*’ Matches zero or more of the preceding element. The matching should cover the entire input string (not partial). The function prototype should be: bool isMatch(const char *s, const char *p) Some examples: isMatch(“aa”,”a”) → false isMatch(“aa”,”aa”) → true isMatch(“aaa”,”aa”) → false isMatch(“aa”, “a*”) → true isMatch(“aa”, “.*”) → true isMatch(“ab”, “.*”) → true isMatch(“aab”, “c*a*b”) → true 题目意义及背景 It might seem deceptively easy even you know the general idea, but programming it correctly with all the details require careful thought. 在程序设计中,规划所有的细节问题都需要认真思考。 题目分析以及需要注意的问题 为什么aab可以匹配模式c*a*b呢? ● It is a regular expression.Not a wild card.So the ” * ” does not mean any string.And the cab should be split like this “c * a * b” which means N “c”,N “a” and One “b”. ’ * ’ Matches zero or more of the preceding element, so ” c* ” could match nothing. 此题不能使用贪心法 考虑情况: s = “ac”, p = “ab*c” After the first ‘a’, we see that there is no b’s to skip for “b*”. We match the last ‘c’ on both side and conclude that they both match. It seems that being greedy is good. But how about this case? s = “abbc”, p = “ab*bbc” When we see “b*” in p, we would have skip all b’s in s. They both should match, but we have no more b’s to match. Therefore, the greedy approach fails in the above case. 可能还有人说,如果碰到这种情况可以先看一下*后面的内容,但是碰见下面的情况就不好办了。 s = “abcbcd”, p = “a.*c.*d” Here, “.*” in p means repeat ‘.’ 0 or more times. Since ‘.’ can match any character, it is not clear how many times ‘.’ should be repeated. Should the ‘c’ in p matches the first or second ‘c’ in s? 所以: Unfortunately, there is no way to tell without using some kind of exhaustive search(穷举搜索). Hints: A sample diagram of a deterministic finite state automata (DFA). DFAs are useful for doing lexical analysis and pattern matching. An example is UNIX’s grep tool. Please note that this post does not attempt to descibe a solution using DFA. 什么是DFA? Solution 主要解决方案是回溯法,使用递归或者dp We need some kind of backtracking mechanism (回溯法)such that when a matching fails, we return to the last successful matching state and attempt to match more characters in s with ‘*’. This approach leads naturally to recursion. The recursion mainly breaks down elegantly to the following two cases: 主要考虑两种递归情况 1. If the next character of p is NOT ‘*’, then it must match the current character of s. Continue pattern matching with the next character of both s and p. 2. If the next character of p is ‘*’, then we do a brute force exhaustive matching of 0, 1, or more repeats of current character of p… Until we could not match any more characters. You would need to consider the base case carefully too. That would be left as an exercise to the reader. Below is the extremely concise code (Excluding comments and asserts, it’s about 10 lines of code). 解题过程如下: 1、考虑特殊情况即*s字符串或者*p字符串结束。 (1)s字符串结束,要求*p也结束或者间隔‘’ (例如p=”a*b*c……”),否则无法匹配 (2)*s字符串未结束,而*p字符串结束,则无法匹配 2、*s字符串与*p字符串均未结束 (1)(p+1)字符不为’‘,则只需比较s字符与*p字符,若相等则递归到(s+1)字符串与*(p+1)字符串的比较,否则无法匹配。 (2)(p+1)字符为’‘,则p字符可以匹配*s字符串中从0开始任意多(记为i)等于*p的字符,然后递归到(s+i+1)字符串与*(p+2)字符串的比较, 只要匹配一种情况就算完全匹配。 bool isMatch(const char *s,const char *p) { //判断参数合法,以及程序正常结束 assert( s && p); if(*p == '\0') return *s == '\0'; //next char is not '*'; must match current character if(*(p+1) != '*') { assert(*p != '*');//考虑情况isMatch('aa','a*'); return ((*p == *s) ||(*p == '.' && *s != '\0')) && isMatch(s + 1, p + 1); } //next char is '*' 继续递归匹配,不能写成*(p+1) == '*' 考虑情况isMatch('ab','.*c') while((*p == *s)|| (*p == '.' && *s != '\0')) { if (isMatch(s, p+2)) return true; s++; } //匹配下一个模式 return isMatch(s,p+2); } 此代码运行时间:18ms Further Thoughts: Some extra exercises to this problem: 1. If you think carefully, you can exploit some cases that the above code runs in exponential complexity. Could you think of some examples? How would you make the above code more efficient? 2. Try to implement partial matching instead of full matching. In addition, add ‘^’ and ‘$’ to the rule. ‘^’ matches the starting position within the string, while ‘$’ matches the ending position of the string. 3. Try to implement wildcard matching where ‘*’ means any sequence of zero or more characters. For the interested reader, real world regular expression matching (such as the grep tool) are usually implemented by applying formal language theory. To understand more about it, you may read this article. Rating: 4.8/5 (107 votes cast) Regular Expression Matching, 4.8 out of 5 based on 107 ratings leetcode的 解题报告提醒我们说: leetcode的解答报告中说的If you are stuck, recursion is your friend. // 递归版,时间复杂度O(n),空间复杂度O(1) class Solution { public: bool isMatch(const char *s, const char *p) { if (*p == '\0') return *s == '\0'; // next char is not '*', then must match current character if (*(p + 1) != '*') { if (*p == *s || (*p == '.' && *s != '\0')) return isMatch(s + 1, p + 1); else return false; } else { // next char is '*' while (*p == *s || (*p == '.' && *s != '\0')) { if (isMatch(s, p + 2)) return true; s++; } return isMatch(s, p + 2); } } }; c++解决方案: My concise recursive and DP solutions with full explanation in C++ ● Please refer to my blog post if you have any comment. Wildcard matching problem can be solved similarly. class Solution { public: bool isMatch(string s, string p) { if (p.empty()) return s.empty(); if ('*' == p[1]) // x* matches empty string or at least one character: x* -> xx* // *s is to ensure s is non-empty return (isMatch(s, p.substr(2)) || !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p)); else return !s.empty() && (s[0] == p[0] || '.' == p[0]) && isMatch(s.substr(1), p.substr(1)); } }; class Solution { public: bool isMatch(string s, string p) { /** * f[i][j]: if s[0..i-1] matches p[0..j-1] * if p[j - 1] != '*' * f[i][j] = f[i - 1][j - 1] && s[i - 1] == p[j - 1] * if p[j - 1] == '*', denote p[j - 2] with x * f[i][j] is true iff any of the following is true * 1) "x*" repeats 0 time and matches empty: f[i][j - 2] * 2) "x*" repeats >= 1 times and matches "x*x": s[i - 1] == x && f[i - 1][j] * '.' matches any single character */ int m = s.size(), n = p.size(); vector<vector<bool>> f(m + 1, vector<bool>(n + 1, false)); f[0][0] = true; for (int i = 1; i <= m; i++) f[i][0] = false; // p[0.., j - 3, j - 2, j - 1] matches empty iff p[j - 1] is '*' and p[0..j - 3] matches empty for (int j = 1; j <= n; j++) f[0][j] = j > 1 && '*' == p[j - 1] && f[0][j - 2]; for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (p[j - 1] != '*') f[i][j] = f[i - 1][j - 1] && (s[i - 1] == p[j - 1] || '.' == p[j - 1]); else // p[0] cannot be '*' so no need to check "j > 1" here f[i][j] = f[i][j - 2] || (s[i - 1] == p[j - 2] || '.' == p[j - 2]) && f[i - 1][j]; return f[m][n]; } }; The shortest AC code. 1.’.’ is easy to handle. if p has a ‘.’, it can pass any single character in s except ‘\0’. 2.” is a totally different problem. if p has a ” character, it can pass any length of first-match characters in s including ‘\0’. class Solution { public: bool matchFirst(const char *s, const char *p){ return (*p == *s || (*p == '.' && *s != '\0')); } bool isMatch(const char *s, const char *p) { if (*p == '\0') return *s == '\0'; //empty if (*(p + 1) != '*') {//without * if(!matchFirst(s,p)) return false; return isMatch(s + 1, p + 1); } else { //next: with a * if(isMatch(s, p + 2)) return true; //try the length of 0 while ( matchFirst(s,p) ) //try all possible lengths if (isMatch(++s, p + 2))return true; } } }; a shorter one (14 lines of code) with neatly format: class Solution { public: bool isMatch(const char *s, const char *p) { for( char c = *p; c != 0; ++s, c = *p ) { if( *(p+1) != '*' ) p++; else if( isMatch( s, p+2 ) ) return true; if( (*s==0) || ((c!='.') && (c!=*s)) ) return false; } return *s == 0; } }; 9-lines 16ms C++ DP Solutions with Explanations ● This problem has a typical solution using Dynamic Programming. We define the state P[i][j] to be true if s[0..i) matches p[0..j) and false otherwise. Then the state equations are: a. P[i][j] = P[i - 1][j - 1], if p[j - 1] != ‘*’ && (s[i - 1] == p[j - 1] || p[j - 1] == ‘.’); b. P[i][j] = P[i][j - 2], if p[j - 1] == ‘*’ and the pattern repeats for 0 times; c. P[i][j] = P[i - 1][j] && (s[i - 1] == p[j - 2] || p[j - 2] == ‘.’), if p[j - 1] == ‘*’ and the pattern repeats for at least 1 times. Putting these together, we will have the following code. class Solution { public: bool isMatch(string s, string p) { int m = s.length(), n = p.length(); vector<vector<bool> > dp(m + 1, vector<bool> (n + 1, false)); dp[0][0] = true; for (int i = 0; i <= m; i++) for (int j = 1; j <= n; j++) if (p[j - 1] == '*') dp[i][j] = dp[i][j - 2] || (i > 0 && (s[i - 1] == p[j - 2] || p[j - 2] == '.') && dp[i - 1][j]); else dp[i][j] = i > 0 && dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '.'); return dp[m][n]; } }; 2 years agoreply quote python解决方案 使用re库,叼炸天! import re class Solution: # @return a boolean def isMatch(self, s, p): return re.match('^' + p + '$', s) != None # debug s = Solution() print (s.isMatch("aaa", ".*")) Python DP solution in 36 ms def isMatch(self, s, p): m = len(s) n = len(p) dp = [[True] + [False] * m] for i in xrange(n): dp.append([False]*(m+1)) for i in xrange(1, n + 1): x = p[i-1] if x == '*' and i > 1: dp[i][0] = dp[i-2][0] for j in xrange(1, m+1): if x == '*': dp[i][j] = dp[i-2][j] or dp[i-1][j] or (dp[i-1][j-1] and p[i-2] == s[j-1]) or (dp[i][j-1] and p[i-2]=='.') elif x == '.' or x == s[j-1]: dp[i][j] = dp[i-1][j-1] return dp[n][m] about a year agoreply quote class Solution(object): def isMatch(self, s, p, memo={("",""):True}): if not p and s: return False if not s and p: return set(p[1::2]) == {"*"} and not (len(p) % 2) if (s,p) in memo: return memo[s,p] char, exp, prev = s[-1], p[-1], 0 if len(p) < 2 else p[-2] memo[s,p] =\ (exp == '*' and ((prev in {char, '.'} and self.isMatch(s[:-1], p, memo)) or self.isMatch(s, p[:-2], memo)))\ or\ (exp in {char, '.'} and self.isMatch(s[:-1], p[:-1], memo)) return memo[s,p] # 445 / 445 test cases passed. # Status: Accepted # Runtime: 72 ms 8ms backtracking solution C++ //regular expression matching //first solution: using recursive version class Solution { public: bool isMatch(string s, string p) { int m = s.length(), n = p.length(); return backtracking(s, m, p, n); } bool backtracking(string& s, int i, string& p, int j) { if (i == 0 && j == 0) return true; if (i != 0 && j == 0) return false; if (i == 0 && j != 0) { //in this case only p == "c*c*c*" this pattern can match null string if (p[j-1] == '*') { return backtracking(s, i, p, j-2); } return false; } //now both i and j are not null if (s[i-1] == p[j-1] || p[j-1] == '.') { return backtracking(s, i - 1, p, j - 1); } else if (p[j-1] == '*') { //two cases: determines on whether p[j-2] == s[i-1] //first p[j-2]* matches zero characters of p if (backtracking(s, i, p, j - 2)) return true; //second consider whether p[j-2] == s[i-1], if true, then s[i-1] is matched, move to backtracking(i - 1, j) if (p[j-2] == s[i-1] || p[j-2] == '.') { return backtracking(s, i - 1, p, j); } return false; } return false; } }; c语言参考解决方案: 3ms C solution using O(mn) time and O(n) space bool isMatch(char *s, char *p){ int i; int ls = strlen(s); int lp = strlen(p); bool* m = malloc((ls + 1) * sizeof(bool)); // init m[0] = true; for (i = 1; i <= ls; i++) { m[i] = false; } int ip; for (ip = 0; ip < lp; ip++) { if (ip + 1 < lp && p[ip + 1] == '*') { // do nothing } else if (p[ip] == '*') { char c = p[ip - 1]; for (i = 1; i <= ls; i++) { m[i] = m[i] || (m[i - 1] && (s[i - 1] == c || c == '.')); } } else { char c = p[ip]; for (i = ls; i > 0; i--) { m[i] = m[i - 1] && (s[i - 1] == c || c == '.'); } m[0] = false; } } bool ret = m[ls]; free(m); return ret; } 简短的代码: bool isMatch(char* s, char* p) { while (*s) { if (*p&&*(p+1)=='*') { if (!(*p==*s||*p=='.')) {p+=2;continue;} if (!isMatch(s,p+2)) {s++;continue;} else return true; } if (*p==*s||*p=='.') {s++;p++;continue;} return false; } while(*p&&*(p+1)=='*') p+=2; return !*p; } 参考文献 http://articles.leetcode.com/regular-expression-matching http://blog.csdn.net/gatieme/article/details/51049244 http://www.jianshu.com/p/85f3e5a9fcda
在Docker – 系统整洁之道 – 1中已经对Docker的一些命令和Docker镜像的使用及操作做了记录。 这次就利用docker进行一次真正的实例使用,使用docker搭建一个简单的答题系统,这个系统是当时做来给网络安全周做手机答题的系统,很简单,代码风格很差。 这篇主要记录了三种docker使用的方式。 用supervisor方式运行一个多进程的docker实例 创建一个ngnix和php运行的环境 创建一个ngnix,php,mysql集合运行的环境,使用docker-compose构建 感觉docker的东西越看越多,从刚开始的简简单单的一个docker run,到现在看到要build自己的镜像,compose,也就是以前的Fig,配置网络,还有swarm的docker集群,一点一点来吧。 先把两个附件写在这里吧 此片博客中构建php+ngnix+mysql测试环境的脚本 在测试环境中的答题网站源码 supervisor方式运行一个多进程的docker实例 Docker 容器在启动的时候开启单个进程,比如,一个 ssh 或者 apache 的 daemon 服务。但我们经常需要在一个机器上开启多个服务,这可以有很多方法,最简单的就是把多个启动命令放到一个启动脚本里面,启动的时候直接启动这个脚本,另外就是安装进程管理工具。这里使用进程管理工具 supervisor 来管理容器中的多个进程。使用 Supervisor 可以更好的控制、管理、重启我们希望运行的进程。 首先创一个文件夹叫做supervisor,目录结构为 ~/Docker tree supervisor supervisor ├── Dockerfile └── supervisord 其中文件Dockerfile文件内容为: #使用时哪个镜像 FROM ubuntu:13.04 MAINTAINER examples@docker.com RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list RUN apt-get update RUN apt-get upgrade -y #这里安装 3 个软件,还创建了 2 个 ssh 和 supervisor 服务正常运行所需要的目录。 RUN apt-get install -y --force-yes perl-base=5.14.2-6ubuntu2 RUN apt-get install -y apache2.2-common RUN apt-get install -y openssh-server apache2 supervisor RUN mkdir -p /var/run/sshd RUN mkdir -p /var/log/supervisor #添加 supervisord 的配置文件,并复制配置文件到对应目录下面。 COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf #映射了 22 和 80 端口,使用 supervisord 的可执行路径启动服务 EXPOSE 22 80 CMD ["/usr/bin/supervisord"] 文件supervisord内容为: #supervsord 配置软件本身,使用 nodaemon 参数来运行 [supervisord] nodaemon=true #配置两个服务 [program:sshd] command=/usr/sbin/sshd -D [program:apache2] command=/bin/bash -c "source /etc/apache2/envvars && exec /usr/sbin/apache2 -DFOREGROUND" 使用命令进行构建 sudo docker build -t supervisor 输出: ~/Docker/supervisor sudo docker build -t supervisord . Password: Sending build context to Docker daemon 3.584 kB Step 1 : FROM ubuntu:13.04 ---> a58cd502f927 Step 2 : MAINTAINER examples@docker.com ---> Using cache ---> 15f104cdeb77 Step 3 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list ---> Using cache ---> c6bb44d794ea Step 4 : RUN apt-get update ---> Using cache ---> adcd83eecb0d Step 5 : RUN apt-get upgrade -y ---> Using cache ---> 89e045811261 Step 6 : RUN apt-get install -y --force-yes perl-base=5.14.2-6ubuntu2 ---> Using cache ---> bcdc472cc73a Step 7 : RUN apt-get install -y apache2.2-common ---> Using cache ---> d8991f8aa3c6 Step 8 : RUN apt-get install -y openssh-server apache2 supervisor ---> Using cache ---> a713034800d6 Step 9 : RUN mkdir -p /var/run/sshd ---> Using cache ---> 3138e3644958 Step 10 : RUN mkdir -p /var/log/supervisor ---> Using cache ---> 958c08978b0c Step 11 : COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf ---> 8e9a0c97a133 Removing intermediate container d95b58057f73 Step 12 : EXPOSE 22 80 ---> Running in 9cabb0865159 ---> b4aa8b82cd57 Removing intermediate container 9cabb0865159 Step 13 : CMD /usr/bin/supervisord ---> Running in 237f71166211 ---> 569f95736129 Removing intermediate container 237f71166211 Successfully built 569f95736129 使用docker ps 一下 ~/Docker/supervisor docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c82c830770bc supervisord:latest "/usr/bin/supervisord" 32 seconds ago Up 30 seconds 0.0.0.0:32770->22/tcp, 0.0.0.0:32769->80/tcp supervisord 发现刚才build的镜像已经跑起来了,访问 http://127.0.0.1:32769,可以web服务已经跑起来了。 使用命令docker exec进入container里面看看 ~/Docker/supervisor docker exec -it c82c830770bc bash root@c82c830770bc:/# hello bash: hello: command not found root@c82c830770bc:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin selinux srv sys tmp usr var root@c82c830770bc:/# 使用passwd修改一下密码,然后在本机的命令行里进行ssh连接吧。 ngnix和php运行的环境 该方法就是直接使用docker命令进行构建一个ngnix,php结合运行的环境,没有使用docker-compose。 先用户根目录~下创建目录,并将该目录设置为Docker的共享目录。 Workspace └── tmp ├── docker │ └── nginx │ └── conf.d │ └── default.conf └── www ├── index.html └── phpinfo.php 其中default.conf文件内容,这是个nginx的配置文件 server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name; include fastcgi_params; } } index.html 里写一句 HelloW0rld,phpinfo.php里面写一个<?php phpinfo();?>。 然后在命令行下执行命令 docker pull php:5.6-fpm-alpine docker pull ngnix:1.10.2 docker run --name dream.php -d -v ~/Workspace/tmp/www:/var/www/html:ro php:5.6-fpm docker run --name dream.nginx -p 80:80 -d -v ~/Workspace/tmp/www:/usr/share/nginx/html:ro -v ~/Workspace/tmp/docker/nginx/conf.d:/etc/nginx/conf.d:ro --link dream.php:php nginx:1.10.2 好的,如果不出意外,就可以看到phpinfo的界面了。这个是没有添加mysql的测试环境,直接在目录~/Workspace/tmp/www下面放网页就可以直接使用了。 ngnix,php,mysql集合运行的环境 Supervisor给出了一种能够在container中运行多个线程的方法,但是现在还是不知道要怎么样把自己的web服务部署到container中,数据库怎么建,可以有人会说直接使用SFTP将网站直接传到container里,安装数据库,配环境,但是docker中一旦container被删除,内容就没了。像这样将所有服务放在一个容器内的模式有个形象的非官方称呼:Fat Container。与之相对的是将服务分拆到容器的模式。从Docker的设计可以看到,构建镜像的过程中可以指定唯一一个容器启动的指令,因此Docker天然适合一个容器只运行一种服务,而这也是官方更推崇的。下面就记录一下部署一个简单的php程序和数据库联动的测试环境。 整体的文件结构是这样的 我们创建一个这样的目录 Docker └── test ├── data 数据库文件夹 │ └── mysql ├── docker-compose.yml docker-compose配置文件 ├── htdocs 网站文件夹 │ ├── index.html │ └── index.php ├── log 日志文件 │ └── nginx ├── mysql mysql构建文件 │ └── Dockerfile ├── nginx nginx构建文件 │ ├── Dockerfile │ ├── conf.d │ │ └── default.conf │ └── nginx.conf └── php php构建文件 └── Dockerfile mysql 独立部署 mysql目录下的Dockerfile文件只有一行FROM mysql:5.6,也就是直接使用mysql官方镜像5.6,然后使用命令 docker build -t phpenv/mysql mysql 构建自己的镜像phpenv/mysql。 使用命令 docker run -p 3306:3306 -v ~/Docker/test/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -it phpenv/mysql 启动镜像,将容器的3306端口绑定到本机的3306端口,其中参数-v后代表使用~/Docker/test/data/mysql挂在到镜像的/var/lib/mysql,也就是替代源镜像的数据库文件目录,让数据库文件目录暴露在本机上,做到数据库内容的持久化。MYSQL_ROOT_PASSWORD为设置mysql的一个root密码。 运行结果 ~/Docker/test docker run -p 3306:3306 -v ~/Docker/test/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -it phpenv/mysql 2016-12-27 15:06:49 0 [Note] mysqld (mysqld 5.6.35) starting as process 1 ... 2016-12-27 15:06:49 1 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive 2016-12-27 15:06:49 1 [Note] Plugin 'FEDERATED' is disabled. 2016-12-27 15:06:49 1 [Note] InnoDB: Using atomics to ref count buffer pool pages 2016-12-27 15:06:49 1 [Note] InnoDB: The InnoDB memory heap is disabled 2016-12-27 15:06:49 1 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins 2016-12-27 15:06:49 1 [Note] InnoDB: Memory barrier is not used 2016-12-27 15:06:49 1 [Note] InnoDB: Compressed tables use zlib 1.2.8 2016-12-27 15:06:49 1 [Note] InnoDB: Using Linux native AIO 2016-12-27 15:06:49 1 [Note] InnoDB: Using CPU crc32 instructions 2016-12-27 15:06:49 1 [Note] InnoDB: Initializing buffer pool, size = 128.0M 2016-12-27 15:06:49 1 [Note] InnoDB: Completed initialization of buffer pool 2016-12-27 15:06:49 1 [Note] InnoDB: Highest supported file format is Barracuda. 2016-12-27 15:06:49 1 [Note] InnoDB: 128 rollback segment(s) are active. 2016-12-27 15:06:49 1 [Note] InnoDB: Waiting for purge to start 2016-12-27 15:06:49 1 [Note] InnoDB: 5.6.35 started; log sequence number 1626027 2016-12-27 15:06:49 1 [Note] Server hostname (bind-address): '*'; port: 3306 2016-12-27 15:06:49 1 [Note] IPv6 is available. 2016-12-27 15:06:49 1 [Note] - '::' resolves to '::'; 2016-12-27 15:06:49 1 [Note] Server socket created on IP: '::'. 2016-12-27 15:06:49 1 [Warning] 'proxies_priv' entry '@ root@bd69eb248839' ignored in --skip-name-resolve mode. 2016-12-27 15:06:49 1 [Note] Event Scheduler: Loaded 0 events 2016-12-27 15:06:49 1 [Note] mysqld: ready for connections. Version: '5.6.35' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) 使用DBeaver连接后 查看一下当前~/Docker/test/data/mysql数据库目录下的文件 ~/Docker/test/data/mysql ls auto.cnf ib_logfile0 ib_logfile1 ibdata1 mysql performance_schema 新建一个库docker_test后~/Docker/test/data/mysql数据库目录下的文件 ~/Docker/test/data/mysql ls auto.cnf docker_test ib_logfile0 ib_logfile1 ibdata1 mysql performance_schema 可以发现数据库已经创建好了,也如下图 为了验证数据库数据的持久型,我们先停止当前运行的container并产出它,然后从镜像启动一个新的container,如命令 ~/Docker ⮀ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 970dec0f7de9 phpenv/mysql "docker-entrypoint.sh" 30 minutes ago Up 30 minutes 0.0.0.0:3306->3306/tcp berserk_brown ~/Docker ⮀ docker stop 970dec0f7de9 970dec0f7de9 ~/Docker ⮀ docker rm 970dec0f7de9 970dec0f7de9 ~/Docker ⮀ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c82c830770bc supervisord:latest "/usr/bin/supervisord" 35 hours ago Exited (0) 32 minutes ago supervisord ~/Docker ⮀ docker run -p 3306:3306 -v ~/Docker/test/data/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -it phpenv/mysql 2016-12-27 15:38:04 0 [Note] mysqld (mysqld 5.6.35) starting as process 1 ... 2016-12-27 15:38:04 1 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive 2016-12-27 15:38:04 1 [Note] Plugin 'FEDERATED' is disabled. 2016-12-27 15:38:04 1 [Note] InnoDB: Using atomics to ref count buffer pool pages 2016-12-27 15:38:04 1 [Note] InnoDB: The InnoDB memory heap is disabled 2016-12-27 15:38:04 1 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins 2016-12-27 15:38:04 1 [Note] InnoDB: Memory barrier is not used 2016-12-27 15:38:04 1 [Note] InnoDB: Compressed tables use zlib 1.2.8 2016-12-27 15:38:04 1 [Note] InnoDB: Using Linux native AIO 2016-12-27 15:38:04 1 [Note] InnoDB: Using CPU crc32 instructions 2016-12-27 15:38:04 1 [Note] InnoDB: Initializing buffer pool, size = 128.0M 2016-12-27 15:38:04 1 [Note] InnoDB: Completed initialization of buffer pool 2016-12-27 15:38:04 1 [Note] InnoDB: Highest supported file format is Barracuda. 2016-12-27 15:38:04 1 [Note] InnoDB: 128 rollback segment(s) are active. 2016-12-27 15:38:04 1 [Note] InnoDB: Waiting for purge to start 2016-12-27 15:38:04 1 [Note] InnoDB: 5.6.35 started; log sequence number 1626037 2016-12-27 15:38:04 1 [Note] Server hostname (bind-address): '*'; port: 3306 2016-12-27 15:38:04 1 [Note] IPv6 is available. 2016-12-27 15:38:04 1 [Note] - '::' resolves to '::'; 2016-12-27 15:38:04 1 [Note] Server socket created on IP: '::'. 2016-12-27 15:38:04 1 [Warning] 'proxies_priv' entry '@ root@bd69eb248839' ignored in --skip-name-resolve mode. 2016-12-27 15:38:04 1 [Note] Event Scheduler: Loaded 0 events 2016-12-27 15:38:04 1 [Note] mysqld: ready for connections. Version: '5.6.35' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) 再次连接数据库验证,发现刚才新建的库docker_test还在,数据库文件持久型保存了。 docker-compose 中mysql配置 待完善 docker-compose 中nginx部署 nginx在构建的时候要替换两个配置文件,Dockfile FROM nginx:1.10.2 ADD nginx.conf /etc/nginx/nginx.conf ADD conf.d/* /etc/nginx/conf.d/ 挂载文件在docker-compose里进行定义。 待完善 docker-compose 中php配置 php什么也不做,只通过Dockfile FROM php:5.6-fpm 来构建 待完善 docker-compose 构建 docker-compose文件 nginx: build: ./nginx ports: - "40080:80" links: - "php" volumes: - ~/Docker/test/htdocs:/usr/share/nginx/html php: build: ./php ports: - "49000:9000" links: - "mysql" volumes: - ~/Docker/test/htdocs:/var/www/html mysql: build: ./mysql ports: - "43306:3306" volumes: - ~/Docker/test/data/mysql:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: 123456 记录一下,首先docker-compse是用来集合构建多个镜像的工具,这里我们集合了nginx,php,mysql来搭建一个php的测试环境,在文件中,有一个links参数,是用来连接其他实例,让多个实例之间可以进行通信。 这里有整合文件的下载链接,下载后,将文件放在用户根目录下,命令行执行docker-compose up,结果 ~/Docker/test ⮀ docker-compose up Building mysql Step 1 : FROM mysql:5.6 ---> e1406e1f7c42 Successfully built e1406e1f7c42 WARNING: Image for service mysql was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Building php Step 1 : FROM php:5.6-fpm 5.6-fpm: Pulling from library/php 75a822cd7888: Already exists e4d8a4e038be: Pull complete 81d4d961577a: Pull complete 54283fea14a4: Pull complete a1b82ddb6e57: Pull complete fe532c795718: Pull complete f02389f3f13e: Pull complete 5777f6cf03c5: Pull complete 24b45111f193: Pull complete Digest: sha256:022410892774f45ebd39bdb4df39a4a72e6ae5db96a31ee83e7eb25382cd2491 Status: Downloaded newer image for php:5.6-fpm ---> 55423bcf0cfc Successfully built 55423bcf0cfc WARNING: Image for service php was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Building nginx Step 1 : FROM nginx:1.10.2 ---> c2d83d8cde8d Step 2 : ADD nginx.conf /etc/nginx/nginx.conf ---> e45c0dceafb9 Removing intermediate container ca538d0f2fd1 Step 3 : ADD conf.d/* /etc/nginx/conf.d/ ---> bf0d37221331 Removing intermediate container ebaa3b27453a Successfully built bf0d37221331 WARNING: Image for service nginx was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating test_mysql_1 Creating test_php_1 Creating test_nginx_1 Attaching to test_mysql_1, test_php_1, test_nginx_1 mysql_1 | 2016-12-28 07:29:43 0 [Note] mysqld (mysqld 5.6.35) starting as process 1 ... mysql_1 | 2016-12-28 07:29:43 1 [Warning] Setting lower_case_table_names=2 because file system for /var/lib/mysql/ is case insensitive mysql_1 | 2016-12-28 07:29:43 1 [Note] Plugin 'FEDERATED' is disabled. mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Using atomics to ref count buffer pool pages mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: The InnoDB memory heap is disabled mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Memory barrier is not used mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Compressed tables use zlib 1.2.8 mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Using Linux native AIO mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Using CPU crc32 instructions mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Initializing buffer pool, size = 128.0M mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Completed initialization of buffer pool mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Highest supported file format is Barracuda. php_1 | [28-Dec-2016 07:29:43] NOTICE: fpm is running, pid 1 mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: 128 rollback segment(s) are active. php_1 | [28-Dec-2016 07:29:43] NOTICE: ready to handle connections mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: Waiting for purge to start mysql_1 | 2016-12-28 07:29:43 1 [Note] InnoDB: 5.6.35 started; log sequence number 1626263 mysql_1 | 2016-12-28 07:29:43 1 [Note] Server hostname (bind-address): '*'; port: 3306 mysql_1 | 2016-12-28 07:29:43 1 [Note] IPv6 is available. mysql_1 | 2016-12-28 07:29:43 1 [Note] - '::' resolves to '::'; mysql_1 | 2016-12-28 07:29:43 1 [Note] Server socket created on IP: '::'. mysql_1 | 2016-12-28 07:29:43 1 [Warning] 'proxies_priv' entry '@ root@bd69eb248839' ignored in --skip-name-resolve mode. mysql_1 | 2016-12-28 07:29:43 1 [Note] Event Scheduler: Loaded 0 events mysql_1 | 2016-12-28 07:29:43 1 [Note] mysqld: ready for connections. mysql_1 | Version: '5.6.35' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL) 访问一下 http://localhost:40080/index.php ,正常的话,如下图 启动一个真实的代码 下面的代码是今年网络安全周的一个手机在线答题系统,代码很挫,大牛误笑 源码在这里。 将目录直接放在~/Docker/test/htdocs下面,然后在test目录下执行docker-compose up,正常情况下,就会跑起来上面的容器,然后按照代码的README将数据库部署就可以运行了。 参考链接 Docker 从入门到实践 第一本Docker书 如何进入一个正在运行的Container Docker在PHP项目开发环境中的应用 原文链接: http://dengnanyi.com/2016/12/24/2016_12/docker-learn-3/
在上文Docker – 系统整洁之道 – 0中已经对Docker是什么,安装Docker以及怎么运行一个简单的容器有了初步了解,这篇文章介绍Docker的一些命令和Docker镜像的使用及操作。 一些Docker命令 Docker的命令按照使用一个容器的顺序进行。 docker info 查看Docker的信息 ~ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 8 Server Version: 1.12.1 Storage Driver: aufs Root Dir: /var/lib/docker/aufs ... 能查看到docker信息,说明docker是安装好的。 docker run 运行一个容器 ~ docker run -it ubuntu root@8eac2e6cf194:/# ls bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@8eac2e6cf194:/# 由于在上文中已经运行过一次该条命令,所以ubuntu的镜像已经下载到了本地,此次运行就可以使用该镜像产生一个容器,在容器启动后,上过-it获取到命令行,运行命令ls。 使用–name标志可以给容器定义一个名字,如 docker run –name this_is_first_ubunt_container -it ubuntu 就会创建一个名字叫做this_is_first_ubunt_container的ubuntu的容器。名字只能使用大小写字母,数字,下划线,原点和横线,即[a-zA-Z0-9_.-]。 关于docker run的帮助可以使用docker run --help获取。 docker ps 列出所有的容器 ~ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8eac2e6cf194 ubuntu "/bin/bash" 5 minutes ago Up 5 minutes modest_davinci 使用docker ps命令可以看到当前正在运行的容器有哪些,并给出了一些相应的属性。给命令增加参数-a就可以获取当前所有的容器,包括已经停止的,如下。 ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4d0cc9a960f1 hello-world "/hello" About a minute ago Exited (0) About a minute ago small_roentgen 8eac2e6cf194 ubuntu "/bin/bash" 4 minutes ago Up 4 minutes modest_davinci docker ps -n x,显示最后x个容器,不管容器正在运行还是停止。 docker start 重新启动已经停止的容器 docker start docker start this_is_first_ubunt_container docker attatch 附着容器 docker attach this_is_first_ubunt_container docker stop 停止一个容器 ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4d0cc9a960f1 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago small_roentgen 8eac2e6cf194 ubuntu "/bin/bash" 8 minutes ago Up 8 minutes modest_davinci ~ docker stop 8eac2e6cf194 8eac2e6cf194 ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4d0cc9a960f1 hello-world "/hello" 4 minutes ago Exited (0) 4 minutes ago small_roentgen 8eac2e6cf194 ubuntu "/bin/bash" 8 minutes ago Exited (0) 5 seconds ago modest_davinci 前几个命令整体练习 创建一个有名字的container,停止它,启动它。 ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 03248ab5d03b tomcat:latest "catalina.sh run" 2 days ago Exited (0) 2 days ago tomcat 4d0cc9a960f1 hello-world "/hello" 4 days ago Exited (0) 4 days ago ~ docker run --name this_is_first_ubunt_container -it ubuntu root@894b1f0fa739:/# whoami root root@894b1f0fa739:/# exit exit ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 894b1f0fa739 ubuntu "/bin/bash" 21 seconds ago Exited (0) 14 seconds ago this_is_first_ubunt_container 03248ab5d03b tomcat:latest "catalina.sh run" 2 days ago Exited (0) 2 days ago tomcat 4d0cc9a960f1 hello-world "/hello" 4 days ago Exited (0) 4 days ago small_roentgen ~ docker start 894b1f0fa739 894b1f0fa739 ~ docker attach 894b1f0fa739 root@894b1f0fa739:/# whoami root root@894b1f0fa739:/# exit exit ~ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 894b1f0fa739 ubuntu "/bin/bash" About a minute ago Exited (0) 8 seconds ago this_is_first_ubunt_container 03248ab5d03b tomcat:latest "catalina.sh run" 2 days ago Exited (0) 2 days ago tomcat 4d0cc9a960f1 hello-world "/hello" 4 days ago Exited (0) 4 days ago small_roentgen ~ docker start this_is_first_ubunt_container this_is_first_ubunt_container ~ docker attach this_is_first_ubunt_container root@894b1f0fa739:/# whoami root root@894b1f0fa739:/# exit exit 守护式容器 上面创建的ubuntu是交互式运行的容器(interactive container),也可以创建一个长期运行的容器–守护式容器(daemonized container)。 ~ docker run --name daemon_ubuntu -d ubuntu /bin/sh -c "while true;do echo hello world;sleep 1;done" e56ae29adaf1d27cf49e05bccda5a7214be458fecc2afb0ff7721f16af8e044c ~ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e56ae29adaf1 ubuntu "/bin/sh -c 'while tr" About a minute ago Up About a minute daemon_ubuntu docker logs 容器日志 ~ docker logs daemon_ubuntu hello world hello world hello world hello world ... ~ docker logs --tail 0 -f daemon_ubuntu hello world hello world hello world hello world hello world docker top 容器进程 ~ docker top daemon_ubuntu PID USER TIME COMMAND 2792 root 0:00 /bin/sh -c while true;do echo hello world;sleep 1;done 3264 root 0:00 sleep 1 容器内运行进程 ~ docker exec -d daemon_ubuntu touch /etc/new_config_file ~ docker exec -it daemon_ubuntu /bin/sh # ls -l /etc | grep new -rw-r--r-- 1 root root 0 Oct 13 02:21 new_config_file docker inspect 容器详细信息 ~ docker inspect daemon_ubuntu # 获取容器运行状态 ~ docker inspect --format='{{.State.Running}}' daemon_ubuntu # 查看容器IP地址 ~ docker inspect --format='{{.NetworkSettings.IPAddress}}' daemon_ubuntu 自动重启容器 docker run –restart=always –name daemon_ubuntu -d ubuntu /bin/sh -c “while true;do echo hello world;sleep 1;done” –restart=always 无论容器的退出代码为何,都自动重启。 –restart=on-failure 当容器退出代码不为0时,自动重启。 –restart=on-failure:5 重启5次。 docker rm 删除容器 运行中的Docker容器是无法删除的。 # 删除所有容器 docker rm `docker ps -a -q` Docker 镜像 docker images Linux /var/lib/docker Mac $HOME/Library/Containers/com.docker.docker/Data/com.docker.driver.amd64-linux/Docker.qcow2 docker pull ubuntu:16.10 获取ubuntu仓库中tag为16.10的镜像。 docker pull -a ubuntu 获取ubuntu仓库中所有镜像。 docker images ubuntu 列出本地所有ubuntu仓库的镜像。 docker run 的时候如果没有指定镜像的版本,则拉取最新版本进行创建。 Docker Hub 中仓库分为两种,一种是用户仓库(user repository),这种是用户创建使用的的,命名方式为username/repositoryname,意义为用户名/仓库名;一种是顶层仓库(top-repository),由docker内部人员管理。 docker search 查找Docker Hub上公用可用镜像。 获取镜像时,格式其实可以看做 用户/仓库:标签。 由于很多仓库为官网所有,所有很多都变成了 仓库:标签,如上面写的 ubuntu:16.10,ubutnu仓库的tag为16.10的镜像。 构建镜像 docker commit docker build 和 Dockerfile文件 一般来说,我们不是真正「创建」了一个镜像,而是基于一个已有的镜像,构建了一个新的镜像。 参考链接 Docker 从入门到实践 第一本Docker书 一个比较详细的命令用法
在我的小 rmbp 256G的硬盘里,实在是装不下100多个G的虚拟机了,所以想把一些东西迁移到这两年很火的Docker下,Docker以前也有过一两次,只是按着别人给的用法用的,具体的一些细节并没有深入,和git一样,这么牛掰的东西怎么能不好好学一些呢? Docker和虚拟机的区别 Docker是一种容器,虚拟机是一种管理程序虚拟机化(hypervisor virtualization,HV)。管理程序虚拟化通过中间层将一台或者多台独立的机器虚拟运行在物理硬件之上,而容器(比如Docker)则是直接运行在操作系统内核之上的用户空间。由于容器是运行在操作系统上的,所以只能运行底层和宿主机相同或者类似的操作系统,比如说在Ubuntu下可以在容器里运行Centos,却不能运行Windows。 目前Windows上的Docker可以跑Linux的Docker容器,是因为底下跑了Linux的VM,但是马上就可以支持Windows Server 2016了,如链接[Introducing the Technical Preview of Docker Engine for Windows Server 2016](http://Introducing the Technical Preview of Docker Engine for Windows Server 2016)。 容器的优点: 一次save,到处运行。 启动速度快,消耗资源少。Docker与虚拟机性能比较 容器缺点: 资源隔离方面不如虚拟机。 安全性问题,“权限隔离”做的不够好,只要有Docker的命令权限,就可以操作所有的Docker实例。 Docker的目标 提供一个简单、轻量的建模方式。 职责的逻辑分离,防止开发环境和部署环境不一致,导致出现“开发时一切正常,肯定是运维问题”的情况。 快速、高效的开发生命周期。 Docker的核心组件 Docker客户端和服务器 Docker是一个C/S架构的程序,Docker客户端需要向Docker服务器发出请求,服务器完成请求后返回信息。一个本地Docker客户端可以连接远端的Docker服务器进行操作,如下图。 Docker镜像 镜像是构建Docker世界的基石。用户基于镜像来维护自己的容器。Docker镜像是Docker容器运行时的只读模板,每一个镜像由一系列的层 (layers) 组成。Docker使用 UnionFS来将这些层联合到单独的镜像中。UnionFS允许独立文件系统中的文件和文件夹(称之为分支)被透明覆盖,形成一个单独连贯的文件系统。正因为有了这些层的存在,Docker是如此的轻量。当你改变了一个Docker镜像,比如升级到某个程序到新的版本,一个新的层会被创建。因此,不用替换整个原先的镜像或者重新建立(在使用虚拟机的时候你可能会这么做),只是一个新的层被添加或升级了。现在你不用重新发布整个镜像,只需要升级,层使得分发Docker镜像变得简单和快速。 Docker仓库(Registry) Docker使用Registry来保存用户构建的镜像,就像苹果的apple store。Registry分为私有和公有两种,Docker公司自己运营的Registry叫做Docker Hub。 Docker容器 Docker可以帮你构建和部署容器,用户只需要把自己的应用程序或服务打包放进容器即可。每一个Docker容器都是从Docker镜像创建的。Docker容器可以运行、开始、停止、移动和删除。每一个Docker容器都是独立和安全的应用平台,Docker容器是Docker的运行部分。 Docker的技术组件 Docker可以被安装在x64架构,内核3.10以上的linux系主机、win10以上windows和OS X 10.10.3且2010年以后的Mac上。在2013年Docker刚发布的时候,它是一款基于LXC的开源容器管理引擎。把LXC复杂的容器创建与使用方式简化为Docker自己的一套命令体系。 随着Docker的不断发展,它开始有了更为远大的目标,那就是反向定义容器的实现标准,将底层实现都抽象化到Libcontainer的接口。这就意味 着,底层容器的实现方式变成了一种可变的方案,无论是使用namespace、cgroups技术抑或是使用systemd等其他方案,只要实现了 Libcontainer定义的一组接口,Docker都可以运行。 安装 安装方法都很简单,值得注意的是当前Docker版本的安装需求,比如现在Linux下安装的需求就上x64架构,内核3.10以上。 Mac下安装方法,直接在官网上下载docker app,安装即可。 Linux下安装方法,Linux下最简单的安装方法就是apt和yum包管理工具进行安装了。 Windows下安装方法 还有一个比较好用的安装脚本,这个脚本只支持在lsb、debian、fedora、oracle、centos、redhat、os这几个发行版中使用。 在安装结束后,可以使用docker info命令来查看Docker是否装好了。Mac下的docker info结果: ~ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 1.12.1 Storage Driver: aufs Root Dir: /var/lib/docker/aufs Backing Filesystem: extfs Dirs: 0 Dirperm1 Supported: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: null bridge host overlay Swarm: inactive Runtimes: runc Default Runtime: runc Security Options: seccomp Kernel Version: 4.4.20-moby Operating System: Alpine Linux v3.4 OSType: linux Architecture: x86_64 CPUs: 4 Total Memory: 1.952 GiB Name: moby ID: FSZQ:ZPKN:NEUW:55GH:Q33R:7L7M:5FLN:GW6E:CLHJ:NO66:WL4K:A3L5 Docker Root Dir: /var/lib/docker Debug Mode (client): false Debug Mode (server): true File Descriptors: 34 Goroutines: 98 System Time: 2016-09-29T01:48:55.851895948Z EventsListeners: 2 Registry: https://index.docker.io/v1/ Insecure Registries: 127.0.0.0/8 Mac装好后如下图的样子,基本功能都已经在菜单上了。 同时Mac还有一个GUI界面Kitmatic,目前还是beta版,但是用起来还是很不错的。 各种各样的image看起来很好看。 使用入门 先把Docker的命令行打印出来。 ~ docker --help Usage: docker [OPTIONS] COMMAND [arg...] docker [ --help | -v | --version ] A self-sufficient runtime for containers. Options: --config=~/.docker Location of client config files -D, --debug Enable debug mode -H, --host=[] Daemon socket(s) to connect to -h, --help Print usage -l, --log-level=info Set the logging level --tls Use TLS; implied by --tlsverify --tlscacert=~/.docker/ca.pem Trust certs signed only by this CA --tlscert=~/.docker/cert.pem Path to TLS certificate file --tlskey=~/.docker/key.pem Path to TLS key file --tlsverify Use TLS and verify the remote -v, --version Print version information and quit Commands: attach Attach to a running container build Build an image from a Dockerfile commit Create a new image from a container's changes cp Copy files/folders between a container and the local filesystem create Create a new container diff Inspect changes on a container's filesystem events Get real time events from the server exec Run a command in a running container export Export a container's filesystem as a tar archive history Show the history of an image images List images import Import the contents from a tarball to create a filesystem image info Display system-wide information inspect Return low-level information on a container, image or task kill Kill one or more running containers load Load an image from a tar archive or STDIN login Log in to a Docker registry. logout Log out from a Docker registry. logs Fetch the logs of a container network Manage Docker networks node Manage Docker Swarm nodes pause Pause all processes within one or more containers port List port mappings or a specific mapping for the container ps List containers pull Pull an image or a repository from a registry push Push an image or a repository to a registry rename Rename a container restart Restart a container rm Remove one or more containers rmi Remove one or more images run Run a command in a new container save Save one or more images to a tar archive (streamed to STDOUT by default) search Search the Docker Hub for images service Manage Docker services start Start one or more stopped containers stats Display a live stream of container(s) resource usage statistics stop Stop one or more running containers swarm Manage Docker Swarm tag Tag an image into a repository top Display the running processes of a container unpause Unpause all processes within one or more containers update Update configuration of one or more containers version Show the Docker version information volume Manage Docker volumes wait Block until a container stops, then print its exit code Run 'docker COMMAND --help' for more information on a command.' 我相信能用Docker都是的大神,就不翻译了。 在安装好以后来运行一个最简单的hello world吧。 docker run hello-world 所见即所得,如图。 在运行docker run的时候,可以看到打印出了Hello from Docker!,首先docker在本地去检查了是否有一个叫做hello-world的镜像,在这里,我们刚装好的docker里必然是没有的,所以docker就去Docker Hub上找这个镜像,找到以后下载下来,run。读一下这个helloworld的输出,可以docker run -it ubuntu bash来运行一个ubuntu。来试一试 # -i 代表保持STDIN开启,-t 代表为容器分配一个tty。 docker run -it ubuntu bash 运行以后,在docker hub里下载好ubuntu镜像后,docker构造好容器启动,就可以和正常的shell一样的进行操作了。 更多内容尽在docker-learn1。 参考链接 [Introducing the Technical Preview of Docker Engine for Windows Server 2016](http://Introducing the Technical Preview of Docker Engine for Windows Server 2016) Docker与虚拟机性能比较 第一本Docker书 5分钟弄懂Docker 一次“奇幻”的Docker libcontainer代码阅读之旅 Docker背后的容器管理—Libcontainer深度解析 LXC:Linux 容器工具 阿里云Registry加速器 docker使用阿里云Docker镜像库加速 非常好的一篇Docker教程,比较全面 知乎_Docker的应用场景在哪里 一个比较详细的命令用法 原文链接:http://dengnanyi.com/2016/09/28/docker-learn-0/
《读书报告 – Elasticsearch入门 》 ' 第四章 分布式文件存储 这章的主要内容是理解数据如何在分布式系统中存储。 4.1 路由文档到分片 创建一个新文档时,它是如何确定应该存储在分片1还是分片2上的呢? 这个过程不是随机的,因为将来要检索文档。事实上,它根据一个简单的算法决定: shard = hash(routing) % number_of_primary_shards routing值是一个任意字符串,它默认是_id但也可以自定义。这个routing字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是0到number_of_primary_shards - 1,这个数字就是特定文档所在的分片。 这也解释了为什么主分片的数量只能在创建索引时定义且不能修改:如果主分片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。 所有的文档API(get、index、delete、bulk、update、mget)都接收一个routing参数,它用来自定义文档到分片的映射。自定义路由值可以确保所有相关文档——例如属于同一个人的文档——被保存在同一分片上。 4.2 主分片和复制分片的交互 假设有三个节点的集群Node 1、Node 2、Node 3。它包含一个叫做blogs的索引并拥有两个主分片。每个主分片有两个复制分片。相同的分片不会放在同一个节点上。 我们能够发送请求给集群中任意一个节点。每个节点都有能力处理任意请求。每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。下面的例子中,我们将发送所有请求给Node 1,这个节点我们将会称之为请求节点(requesting node) > 当我们发送请求,最好的做法是循环通过所有节点请求,这样可以平衡负载。 4.3 新建、索引和删除文档 新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。 下面是在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤: 客户端给Node 1发送新建、索引或删除请求。 节点使用文档的_id确定文档属于分片0。它转发请求到Node 3,分片0位于这个节点上。 Node 3在主分片上执行请求,如果成功,它转发请求到相应的位于Node 1和Node 2的复制节点上。当所有的复制节点报告成功,Node 3报告成功到请求的节点,请求的节点再报告给客户端。 客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。这时修改就生效了。 - consistency 默认主分片在尝试写入时需要规定数量(quorum)或过半的分片(可以是主节点或复制节点)可用。这是防止数据被写入到错的网络分区。规定的数量计算公式如下: int( (primary + number_of_replicas) / 2 ) + 1 consistency允许的值为one(只有一个主分片),all(所有主分片和复制分片)或者默认的quorum或过半分片。 注意number_of_replicas是在索引中的设置,用来定义复制分片的数量,而不是现在活动的复制节点的数量。如果你定义了索引有3个复制节点,那规定数量是: int( (primary + 3 replicas) / 2 ) + 1 = 3 但如果只有2个节点,那你的活动分片不够规定数量,也就不能索引或删除任何文档。 -timeout 当分片副本不足时会怎样?Elasticsearch会等待更多的分片出现。默认等待一分钟。如果需要,你可以设置timeout参数让它终止的更早:100表示100毫秒,30s表示30秒。 新索引默认有1个复制分片,这意味着为了满足quorum的要求需要两个活动的分片。当然,这个默认设置将阻止我们在单一节点集群中进行操作。为了避开这个问题,规定数量只有在number_of_replicas大于一时才生效。 4.4 检索文档 文档能够从主分片或任意一个复制分片被检索。 下面是在主分片或复制分片上检索一个文档必要的顺序步骤: 客户端给Node 1发送get请求。 节点使用文档的_id确定文档属于分片0。分片0对应的复制分片在三个节点上都有。此时,它转发请求到Node 2。 Node 2返回document给Node 1然后返回给客户端。 可能的情况是,一个被索引的文档已经存在于主分片上却还没来得及同步到复制分片上。这时复制分片会报告文档未找到,主分片会成功返回文档。一旦索引请求成功返回给用户,文档则在主分片和复制分片都是可用的。 4.5 局部更新文档 update API 结合了之前提到的读和写的模式。 下面是执行局部更新必要的顺序步骤: 客户端给Node 1发送更新请求。 它转发请求到主分片所在节点Node 3。 Node 3从主分片检索出文档,修改_source字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以retry_on_conflict设置的次数重复步骤3,都未成功则放弃。 如果Node 3成功更新文档,它同时转发文档的新版本到Node 1和Node 2上的复制节点以重建索引。当所有复制节点报告成功,Node 3返回成功给请求节点,然后返回给客户端。 update API还接受《新建、索引和删除》章节提到的routing、replication、consistency和timout参数。 基于文档的复制 当主分片转发更改给复制分片时,并不是转发更新请求,而是转发整个文档的新版本。记住这些修改转发到复制节点是异步的,它们并不能保证到达的顺序与发送相同。如果Elasticsearch转发的仅仅是修改请求,修改的顺序可能是错误的,那得到的就是个损坏的文档。 4.6 多文档模式 mget和bulk API与单独的文档类似,差别是请求节点知道每个文档所在的分片。它把多文档请求拆成每个分片的对文档请求,然后转发每个参与的节点。 一旦接收到每个节点的应答,然后整理这些响应组合为一个单独的响应,最后返回给客户端。 -mget 通过一个mget请求检索多个文档的顺序步骤: 客户端向Node 1发送mget请求。 Node 1为每个分片构建一个多条数据检索请求,然后转发到这些请求所需的主分片或复制分片上。当所有回复被接收,Node 1构建响应并返回给客户端。 routing 参数可以被docs中的每个文档设置。 -bulk 使用一个bulk执行多个create、index、delete和update请求的顺序步骤: 客户端向Node 1发送bulk请求。 Node 1为每个分片构建批量请求,然后转发到这些请求所需的主分片上。 主分片一个接一个的按序执行操作。当一个操作执行完,主分片转发新文档(或者删除部分)给对应的复制节点,然后执行下一个操作。复制节点为报告所有操作完成,节点报告给请求节点,请求节点整理响应并返回给客户端。 bulk API还可以在最上层使用replication和consistency参数,routing参数则在每个请求的元数据中使用。 -bonus “为什么bulk API需要带换行符的奇怪格式,而不是像mget API一样使用JSON数组?” 为了回答这个问题,我们需要简单的介绍一下背景: 批量中每个引用的文档属于不同的主分片,每个分片可能被分布于集群中的某个节点上。 操作(action)需要被转发到对应的分片和节点上。 如果每个单独的请求被包装到JSON数组中,那意味着我们需要: 解析JSON为数组(包括文档数据,可能非常大) 检查每个请求决定应该到哪个分片上 为每个分片创建一个请求的数组 序列化这些数组为内部传输格式 发送请求到每个分片 这可行,但需要大量的RAM来承载本质上相同的数据,还要创建更多的数据结构使得JVM花更多的时间执行垃圾回收。 取而代之的,Elasticsearch则是从网络缓冲区中一行一行的直接读取数据。它使用换行符识别和解析action/metadata行,以决定哪些分片来处理这个请求。 这些行请求直接转发到对应的分片上。这些没有冗余复制,没有多余的数据结构。整个请求过程使用最小的内存在进行。 第五章 搜索——基本的工具 Elasticsearch真正强大之处在于可以从混乱的数据中找出有意义的信息——从大数据到全面的信息。 Elasticsearch不只是存储(store)文档,也会索引(indexes)文档内容来使之可以被搜索。 每个文档里的字段都会被索引并被查询。而且在简单查询时,Elasticsearch可以使用所有的索引,以非常快的速度返回结果。 搜索(search)可以: 在类似于gender或者age这样的字段上使用结构化查询,join_date这样的字段上使用排序,就像SQL的结构化查询一样。 全文检索,可以使用所有字段来匹配关键字,然后按照关联性(relevance)排序返回结果。 或者结合以上两条。 很多搜索都是开箱即用的,为了充分挖掘Elasticsearch的潜力,你需要理解以下三个概念: | 概念 | 解释 | ——————————- | —————————————– | | 映射(Mapping) | 数据在每个字段中的解释说明 | | 分析(Analysis) | 全文是如何处理的可以被搜索的 | | 领域特定语言查询(Query DSL) | Elasticsearch使用的灵活的、强大的查询语言 | 5.1 空搜索 最基本的搜索API表单是空搜索(empty search),它没有指定任何的查询条件,只返回集群索引中的所有文档: curl -XGET ‘ibd14:9200/_search?pretty’ - hits 响应中最重要的部分是hits,它包含了total字段来表示匹配到的文档总数,hits数组还包含了匹配到的前10条数据。 hits数组中的每个结果都包含_index、_type和文档的_id字段,被加入到_source字段中这意味着在搜索结果中我们将可以直接使用全部文档。 每个节点都有一个_score字段,这是相关性得分(relevance score),它衡量了文档与查询的匹配程度。默认的,返回的结果中关联性最大的文档排在首位;这意味着,它是按照_score降序排列的。这种情况下,我们没有指定任何查询,所以所有文档的相关性是一样的,因此所有结果的_score都是取得一个中间值1 max_score指的是所有文档匹配查询中_score的最大值。 - took took告诉我们整个搜索请求花费的毫秒数。 - shards _shards节点告诉我们参与查询的分片数(total字段),有多少是成功的(successful字段),有多少的是失败的(failed字段)。通常我们不希望分片失败,不过这个有可能发生。如果我们遭受一些重大的故障导致主分片和复制分片都故障,那这个分片的数据将无法响应给搜索请求。这种情况下,Elasticsearch将报告分片failed,但仍将继续返回剩余分片上的结果。 - timeout time_out值告诉我们查询超时与否。一般的,搜索请求不会超时。如果响应速度比完整的结果更重要,你可以定义timeout参数为10或者10ms(10毫秒),或者1s(1秒) curl -XGET ‘ibd14:9200/_search?timeout=10ms&pretty’ Elasticsearch将返回在请求超时前收集到的结果。 超时不是一个断路器(circuit breaker)(译者注:关于断路器的理解请看警告)。 警告 需要注意的是timeout不会停止执行查询,它仅仅告诉你目前顺利返回结果的节点然后关闭连接。在后台,其他分片可能依旧执行查询,尽管结果已经被发送。 使用超时是因为对于你的业务需求(译者注:SLA,Service-Level Agreement服务等级协议,在此我翻译为业务需求)来说非常重要,而不是因为你想中断执行长时间运行的查询。 5.2 多索引和多类别 通过限制搜索的不同索引或类型,我们可以在集群中跨所有文档搜索。Elasticsearch转发搜索请求到集群中平行的主分片或每个分片的复制分片上,收集结果后选择顶部十个返回给我们。 通常,当然,你可能想搜索一个或几个自定的索引或类型,我们能通过定义URL中的索引或类型达到这个目的,像这样: /_search 在所有索引的所有类型中搜索 /gb/_search 在索引gb的所有类型中搜索 /gb,us/_search 在索引gb和us的所有类型中搜索 /g*,u*/_search 在以g或u开头的索引的所有类型中搜索 /gb/user/_search 在索引gb的类型user中搜索 /gb,us/user,tweet/_search 在索引gb和us的类型user和tweet中搜索 /_all/user,tweet/_search 在所有索引的类型user和tweet中搜索 search types user and tweet in all indices 搜索一个索引有5个主分片和5个索引各有一个分片**事实上是一样的**。 5.3 分页 (空)搜索语句中,执行返回的结果只有10个文档在hits数组中。如何看到其他文档? 和SQL使用LIMIT关键字返回只有一页的结果一样,Elasticsearch接受from和size参数: size: 结果数,默认10,指定每页返回的文档数 from: 跳过开始的结果数,默认0,从第几个文档开始 如果你想每页显示5个结果,页码从1到3,那请求如下: GET /_search?size=5 curl -XGET ‘ibd14:9200/_search?size=5&pretty’ GET /_search?size=5&from=5 curl -XGET ‘ibd14:9200/_search?size=5&from=5&pretty’ GET /_search?size=5&from=10 curl -XGET ‘ibd14:9200/_search?size=5&from=10&pretty’ 应该当心分页太深或者一次请求太多的结果。结果在返回前会被排序。但是记住一个搜索请求常常涉及多个分片。每个分片生成自己排好序的结果,它们接着需要集中起来排序以确保整体排序正确。 5.4 简易搜索 search API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL) 查询字符串搜索对于在命令行下运行点对点(ad hoc)查询特别有用。例如这个语句查询所有索引类型为blog并在tags字段中包含testing字符的文档: curl -XGET ‘ibd14:9200/_all/blog/_search?q=tags:testing&pretty’ 查询所有索引类型为blog并在tags字段中包含testing字符和views字段值为1的文档 curl -XGET ‘ibd14:9200/_all/blog/_search?q=tags:testing+views:1&pretty’ "+"前缀表示语句匹配条件必须被满足。类似的"-"前缀表示条件必须不被满足。所有条件如果没有+或-表示是可选的——匹配越多,相关的文档就越多。 返回包含"testing"字符的所有文档的简单搜索 > curl -XGET ‘ibd14:9200/_search?q=testing&pretty’ _all字段 当你索引一个文档,Elasticsearch把所有字符串字段值连接起来放在一个大字符串中,它被索引为一个特殊的字段_all。 _all字段对于开始一个新应用时是一个有用的特性。之后,如果你定义字段来代替_all字段,你的搜索结果将更加可控。当_all字段不再使用,你可以停用它。 更复杂的语句 下一个搜索语句: _all field * title字段包含"first"或"second" * view大于1 * _all字段包含"entry"或"pretty" curl -XGET ‘ibd14:9200/_search?q=title:(first+second)+views>1+(entry+trying)&pretty’ 从上面的例子,看到了简单(lite)查询字符串搜索惊人的强大。 然而,也看到简洁带来了隐晦和调试困难。而且它很脆弱——查询字符串中一个细小的语法错误,像-、:、/或"错位就会导致返回错误而不是结果。 第六章 映射与分析 映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型(string, number, booleans, date等)。 分析(analysis)机制用于进行全文文本(Full Text)的分词,以建立供搜索用的反向索引。 6.1 映射及分析 在已有的索引中有12个tweets,只有一个包含日期2014-09-15,但是我们看看下面查询中的total hits。 GET /_search?q=2014 # 12 个结果 GET /_search?q=2014-09-15 # 还是 12 个结果 ! GET /_search?q=date:2014-09-15 # 1 一个结果 GET /_search?q=date:2014 # 0 个结果 ! 为什么全日期的查询返回所有的tweets,而针对date字段进行年度查询却什么都不返回? 为什么我们的结果因查询_all字段(译者注:默认所有字段中进行查询)或date字段而变得不同? 让我们看看Elasticsearch在对website_weichao索引中的blog类型进行mapping(也称之为模式定义[注:此词有待重新定义(schema definition)])后是如何解读我们的文档结构: { "website_weichao" : { "mappings" : { "blog" : { "properties" : { "date" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis" }, "doc" : { "properties" : { "tags" : { "type" : "string" }, "views" : { "type" : "long" } } }, "script" : { "type" : "string" }, "tags" : { "type" : "string" }, "text" : { "type" : "string" }, "title" : { "type" : "string" }, "views" : { "type" : "long" } } } } Elasticsearch为对字段类型进行猜测,动态生成了字段和类型的映射关系。返回的信息显示了date字段被识别为date类型。_all因为是默认字段所以没有在此显示,不过我们知道它是string类型。 date类型的字段和string类型的字段的索引方式是不同的,因此导致查询结果的不同,这并不会让我们觉得惊讶。 6.2 确切值 vs 全文文本 Elasticsearch中的数据可以大致分为两种类型: 确切值 及 全文文本 确切值是确定的,正如它的名字一样。比如一个date或用户ID,也可以包含更多的字符串比如username或email地址。确切值能够被准确地查询到。 全文文本常常被称为非结构化数据,其实是一种用词不当的称谓,实际上自然语言是高度结构化的。对于全文数据的查询来说,却有些微妙。我们不会去询问这篇文档是否匹配查询要求?。我们会询问这篇文档和查询的匹配程度如何?。换句话说,对于查询条件,这篇文档的相关性有多高? 为了方便在全文文本字段中进行这些类型的查询,Elasticsearch首先对文本分析(analyzes),然后使用结果建立一个倒排索引。 6.3 倒排索引 创建倒排索引,我们首先切分每个文档的content字段为单独的单词,把所有的唯一词放入列表并排序。 使用相同的标准化规则处理查询字符串的content字段。把这一过程叫做分词。 IMPORTANT 这很重要。你只可以找到确实存在于索引中的词,所以索引文本和查询字符串都要标准化为相同的形式。 这个标记化和标准化的过程叫做分词(analysis),这个在下节中我们讨论。 6.4 分析和分析器 分析(analysis)过程: 首先,标记化一个文本块为适用于倒排索引单独的词(term) 然后标准化这些词为标准形式,提高它们的“可搜索性”或“查全率” 这个工作是分析器(analyzer)完成的。 字符过滤器 首先字符串经过字符过滤器(character filter),它们的工作是在标记化前处理字符串。字符过滤器能够去除HTML标记,或者转换"&"为"and"。 分词器 分词器(tokenizer)的作用是标记化成独立的词。一个简单的分词器(tokenizer)可以根据空格或逗号将单词分开(译者注:这个在中文中不适用)。 标记过滤 最后,每个词都通过所有标记过滤(token filters),它可以修改词(如将"Quick"转为小写),去掉词(例如停用词像"a"、"and"、"the"等等),或者增加词(例如同义词像"jump"和"leap") Elasticsearch提供很多开箱即用的字符过滤器,分词器和标记过滤器。这些可以组合来创建自定义的分析器以应对不同的需求。 内建的分析器 不过,Elasticsearch还附带了一些预装的分析器,你可以直接使用它们。下面我们列出了最重要的几个分析器,来演示这个字符串分词后的表现差异: "Set the shape to semi-transparent by calling set_trans(5)" 标准分析器 标准分析器是Elasticsearch默认使用的分析器。对于文本分析,它对于任何语言都是最佳选择(译者注:就是没啥特殊需求,对于任何一个国家的语言,这个分析器就够用了)。它根据Unicode Consortium的定义的单词边界(word boundaries)来切分文本,然后去掉大部分标点符号。最后,把所有词转为小写。产生的结果为: set, the, shape, to, semi, transparent, by, calling, set_trans, 5 简单分析器 简单分析器将非单个字母的文本切分,然后把每个词转为小写。产生的结果为: set, the, shape, to, semi, transparent, by, calling, set, trans 空格分析器 空格分析器依据空格切分文本。它不转换小写。产生结果为: Set, the, shape, to, semi-transparent, by, calling, set_trans(5) 语言分析器 特定语言分析器适用于很多语言。它们能够考虑到特定语言的特性。例如,english分析器自带一套英语停用词库——像and或the这些与语义无关的通用词。这些词被移除后,因为语法规则的存在,英语单词的主体含义依旧能被理解(译者注:stem English words这句不知道该如何翻译,查了字典,我理解的大概意思应该是将英语语句比作一株植物,去掉无用的枝叶,主干依旧存在,停用词好比枝叶,存在与否并不影响对这句话的理解。)。 english分析器将会产生以下结果: set, shape, semi, transpar, call, set_tran, 5 注意"transparent"、"calling"和"set_trans"是如何转为词干的。 当分析器被使用 当我们索引(index)一个文档,全文字段会被分析为单独的词来创建倒排索引。不过,在全文字段搜索(search)时,查询字符串经过同样的分析流程处理,以确保这些词在索引中存在。 查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。 查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。 现在可以明白为什么《映射和分析》的开头会产生那种结果: * date字段包含一个确切值:单独的一个词"2014-09-15"。 * _all字段是一个全文字段,所以分析过程将日期转为三个词:"2014"、"09"和"15"。 当我们在_all字段查询2014,它一个匹配到12条推文,因为这些推文都包含词2014: GET /_search?q=2014 # 12 results 当我们在_all字段中查询2014-09-15,首先分析查询字符串,产生匹配任一词2014、09或15的查询语句,它依旧匹配12个推文,因为它们都包含词2014。 GET /_search?q=2014-09-15 # 12 results ! 当我们在date字段中查询2014-09-15,它查询一个确切的日期,然后只找到一条推文: GET /_search?q=date:2014-09-15 # 1 result 当我们在date字段中查询2014,没有找到文档,因为没有文档包含那个确切的日期: GET /_search?q=date:2014 # 0 results ! 测试分析器 尤其当你是Elasticsearch新手时,对于如何分词以及存储到索引中理解起来比较困难。为了更好的理解如何进行,你可以使用analyze API来查看文本是如何被分析的。在查询字符串参数中指定要使用的分析器,被分析的文本做为请求体: curl -XGET ‘ibd14:9200/_analyze?analyzer=standard&text=Starting+to+get&pretty’ 结果中每个节点在代表一个词: { "tokens" : [ { "token" : "starting", "start_offset" : 0, "end_offset" : 8, "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "to", "start_offset" : 9, "end_offset" : 11, "type" : "<ALPHANUM>", "position" : 1 }, { "token" : "get", "start_offset" : 12, "end_offset" : 15, "type" : "<ALPHANUM>", "position" : 2 } ] } token是一个实际被存储在索引中的词。position指明词在原文本中是第几个出现的。start_offset和end_offset表示词在原文本中占据的位置。 analyze API 对于理解Elasticsearch索引的内在细节是个非常有用的工具。 指定分析器 当Elasticsearch在你的文档中探测到一个新的字符串字段,它将自动设置它为全文string字段并用standard分析器分析。 你不可能总是想要这样做。也许你想使用一个更适合这个数据的语言分析器。或者,你只想把字符串字段当作一个普通的字段——不做任何分析,只存储确切值,就像字符串类型的用户ID或者内部状态字段或者标签。 为了达到这种效果,我们必须通过映射(mapping)人工设置这些字段。 6.5 映射 为了能够正确的区分精确查询和全文本搜索,Elasticsearch需要知道每个字段里面都包含了什么类型。这些类型和字段的信息存储(包含)在映射(mapping)中。 索引中每个文档都有一个类型(type)。每个类型拥有自己的映射(mapping)或者模式定义(schema definition)。一个映射定义了字段类型,每个字段的数据类型,以及字段被Elasticsearch处理的方式。映射还用于设置关联到类型上的元数据。 核心简单字段类型 Elasticsearch支持以下简单字段类型: |类型 | 表示的数据类型 | |—————-|————————————| |String | string | |Whole number | byte, short, integer, long| |Floating point | float, double | |Boolean | boolean | |Date | date | 当索引一个包含新字段的文档——一个之前没有的字段——Elasticsearch将使用动态映射猜测字段类型,这类型来自于JSON的基本数据类型,使用以下规则: |JSON type | Field type | |———————————–|————————| |Boolean: true or false | "boolean" | |Whole number: 123 | "long" | |Floating point: 123.45 | "double" | |String, valid date: "2014-09-15" | "date" | |String: "foo bar" | "string" | 查看映射 我们可以使用_mapping后缀来查看Elasticsearch中的映射。在本章开始我们已经找到索引website_weichao类型blog中的映射: > GET /website_weichao/_mapping/blog 这展示给了我们字段的映射(叫做属性(properties)),这些映射是Elasticsearch在创建索引时动态生成的: { "website_weichao" : { "mappings" : { "blog" : { "properties" : { "date" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis" }, "doc" : { "properties" : { "tags" : { "type" : "string" }, "views" : { "type" : "long" } } }, "script" : { "type" : "string" }, "tags" : { "type" : "string" }, "text" : { "type" : "string" }, "title" : { "type" : "string" }, "views" : { "type" : "long" } } } } } } 小提示 错误的映射,例如把`age`字段映射为`string`类型而不是`integer`类型,会造成查询结果混乱。 要检查映射类型,而不是假设它是正确的! 自定义字段映射 虽然大多数情况下基本数据类型已经能够满足,但你也会经常需要自定义一些特殊类型(fields),特别是字符串字段类型。 自定义类型可以使你完成一下几点: 区分全文(full text)字符串字段和准确字符串字段(译者注:就是分词与不分词,全文的一般要分词,准确的就不需要分词,比如『中国』这个词。全文会分成『中』和『国』,但作为一个国家标识的时候我们是不需要分词的,所以它就应该是一个准确的字符串字段)。 使用特定语言的分析器(译者注:例如中文、英文、阿拉伯语,不同文字的断字、断词方式的差异) 优化部分匹配字段 指定自定义日期格式(译者注:这个比较好理解,例如英文的 Feb,12,2016 和 中文的 2016年2月12日) 以及更多 映射中最重要的字段参数是type。除了string类型的字段,你可能很少需要映射其他的type: { "number_of_clicks": { "type": "integer" } } string类型的字段,默认的,考虑到包含全文本,它们的值在索引前要经过分析器分析,并且在全文搜索此字段前要把查询语句做分析处理。 对于string字段,两个最重要的映射参数是index和analyer。 index index参数控制字符串以何种方式被索引。它包含以下三个值当中的一个: |值 |解释 | |————–|————————————–| |analyzed |首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。 |not_analyzed|索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。 |no |不索引这个字段。这个字段不能为搜索到。| string类型字段默认值是analyzed。如果我们想映射字段为确切值,我们需要设置它为not_analyzed: { "tag": { "type": "string", "index": "not_analyzed" } } 其他简单类型(long、double、date等等)也接受index参数,但相应的值只能是no和not_analyzed,它们的值不能被分析。 分析 对于analyzed类型的字符串字段,使用analyzer参数来指定哪一种分析器将在搜索和索引的时候使用。默认的,Elasticsearch使用standard分析器,但是你可以通过指定一个内建的分析器来更改它,例如whitespace、simple或english。 { "tweet": { "type": "string", "analyzer": "english" } } 更新映射 你可以在第一次创建索引的时候指定映射的类型。此外,你也可以晚些时候为新类型添加映射(或者为已有的类型更新映射)。 重要 你可以向已有映射中**增加**字段,但你不能**修改**它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。 我们可以更新一个映射来增加一个新字段,但是不能把已有字段的类型那个从analyzed改到not_analyzed。 为了演示两个指定的映射方法,让我们首先删除索引gb:(我直接新建了索引’newinfo_weichao’) DELETE /gb 然后创建一个新索引,指定tweet字段的分析器为english: PUT /newinfo_weichao <1> { "mappings": { "tweet" : { "properties" : { "tweet" : { "type" : "string", "analyzer": "english" }, "date" : { "type" : "date" }, "name" : { "type" : "string" }, "user_id" : { "type" : "long" } } } } } <1> 这将创建包含mappings的索引,映射在请求体中指定。 接着,在tweet的映射中增加一个新的not_analyzed类型的文本字段,叫做tag,使用_mapping后缀: PUT /gb/_mapping/tweet { "properties" : { "tag" : { "type" : "string", "index": "not_analyzed" } } } 注意到我们不再需要列出所有的已经存在的字段,因为我们没法修改他们。我们的新字段已经被合并至存在的那个映射中。 测试映射 你可以通过名字使用analyze API测试字符串字段的映射。对比这两个请求的输出: GET /newinfo_weichao1/_analyze?field=tweet&text=Black-cats <1> GET /newinfo_weichao1/_analyze?field=tag&text=Black-cats <2> <1> <2> 我们想要分析的文本被放在请求体中。 <1>执行结果 { "tokens" : [ { "token" : "black", "start_offset" : 0, "end_offset" : 5, "type" : "<ALPHANUM>", "position" : 0 }, { "token" : "cat", "start_offset" : 6, "end_offset" : 10, "type" : "<ALPHANUM>", "position" : 1 } ] } <2>执行结果 { “tokens” : [ { “token” : “Black-cats”, “start_offset” : 0, “end_offset” : 10, “type” : “word”, “position” : 0 } ] } tweet字段产生两个词,"black"和"cat",tag字段产生单独的一个词"Black-cats"。换言之,我们的映射工作正常。 6.6 复合核心字段类型 除了之前提到的简单的标量类型,JSON还有null值,数组和对象,所有这些Elasticsearch都支持: 多值字段 我们想让tag字段包含多个字段,这非常有可能发生。我们可以索引一个标签数组来代替单一字符串: { "tag": [ "search", "nosql" ]} 对于数组不需要特殊的映射。任何一个字段可以包含零个、一个或多个值,同样对于全文字段将被分析并产生多个词。 这意味着数组中所有值必须为同一类型。如果你创建一个新字段,这个字段索引了一个数组,Elasticsearch将使用第一个值的类型来确定这个新字段的类型。 当你从Elasticsearch中取回一个文档,任何一个数组的顺序和你索引它们的顺序一致。你取回的_source字段的顺序同样与索引它们的顺序相同。 数组是作为多值字段被索引的,它们没有顺序。在搜索阶段你不能指定“第一个值”或者“最后一个值”。倒不如把数组当作一个值集合(bag of values) 空字段 当然数组可以是空的。这等价于有零个值。事实上,Lucene没法存放null值,所以一个null值的字段被认为是空字段。 这四个字段将被识别为空字段而不被索引: "empty_string": "", "null_value": null, "empty_array": [], "array_with_null_value": [ null ] 多层对象 我们需要讨论的最后一个自然JSON数据类型是对象(object)——在其它语言中叫做hash、hashmap、dictionary 或者 associative array. 内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象。例如,做为在tweet文档中user_name和user_id的替代,我们可以这样写: { "tweet": "Elasticsearch is very flexible", "user": { "id": "@johnsmith", "gender": "male", "age": 26, "name": { "full": "John Smith", "first": "John", "last": "Smith" } } } 内部对象的映射 Elasticsearch 会动态的检测新对象的字段,并且映射它们为 object 类型,将每个字段加到 properties 字段下 { "gb": { "tweet": { <1> "properties": { "tweet": { "type": "string" }, "user": { <2> "type": "object", "properties": { "id": { "type": "string" }, "gender": { "type": "string" }, "age": { "type": "long" }, "name": { <3> "type": "object", "properties": { "full": { "type": "string" }, "first": { "type": "string" }, "last": { "type": "string" } } } } } } } } } <1> 根对象. <2><3> 内部对象. 对user和name字段的映射与tweet类型自己很相似。事实上,type映射只是object映射的一种特殊类型,我们将 object 称为根对象。它与其他对象一模一样,除非它有一些特殊的顶层字段,比如 _source, _all 等等。 内部对象是怎样被索引的 Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。 为了让 Elasticsearch 可以有效的索引内部对象,将文件转换为以下格式: { "tweet": [elasticsearch, flexible, very], "user.id": [@johnsmith], "user.gender": [male], "user.age": [26], "user.name.full": [john, smith], "user.name.first": [john], "user.name.last": [smith] } 内部栏位可被归类至name,例如"first"。 为了区别两个拥有相同名字的栏位,我们可以使用完整路径,例如"user.name.first" 或甚至类型名称加上路径:"tweet.user.name.first"。 注意: 在以上扁平化文件中,并没有栏位叫作user也没有栏位叫作user.name。 Lucene 只索引阶层或简单的值,而不会索引复杂的资料结构。 对象-数组 内部对象数组 最后,一个包含内部对象的数组如何索引。 我们有个数组如下所示: { "followers": [ { "age": 35, "name": "Mary White"}, { "age": 26, "name": "Alex Jones"}, { "age": 19, "name": "Lisa Smith"} ] } 此文件会如我们以上所说的被扁平化,但其结果会像如此: { "followers.age": [19, 26, 35], "followers.name": [alex, jones, lisa, smith, mary, white] } {age: 35}与{name: Mary White}之间的关联会消失,因每个多值的栏位会变成一个值集合,而非有序的阵列。 第七章 请求体查询 请求体查询(request body search)API是对简单查询语句(lite)(一种有效的命令行adhoc查询)的有效补充。因为大多数的参数以JSON格式所容纳而非查询字符串,因此请求体查询是相当重要。 7.1 空查询 空查询将会返回索引中所有的文档。 GET /_search {} <1> <1> 这是一个空查询数据。 同字符串查询一样,你可以查询一个,多个或_all索引(indices)或类型(types): GET /index_2014*/type1,type2/_search {} 使用from及size参数进行分页: GET /_search { "from": 30, "size": 10 } 7.2 结构化查询Query DSL(Query Domain Specific Language) 结构化查询是一种灵活的,多表现形式的查询语言。 Elasticsearch在一个简单的JSON接口中用结构化查询来展现Lucene绝大多数能力。它使得查询更加灵活,精准,易于阅读并且易于debug。 使用结构化查询,需要传递query参数: GET /_search { "query": YOUR_QUERY_HERE } 空查询 - {} - 在功能上等同于使用match_all查询子句,正如其名字一样,匹配所有的文档: GET /_search { "query": { "match_all": {} } } 查询子句 一个查询子句一般使用这种结构: { QUERY_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } 或指向一个指定的字段: { QUERY_NAME: { FIELD_NAME: { ARGUMENT: VALUE, ARGUMENT: VALUE,... } } } 例如,可以使用match查询子句用来找寻在tweet字段中找寻包含elasticsearch的成员: { "match": { "tweet": "elasticsearch" } } 完整的查询请求是这样: GET /_search { "query": { "match": { "tweet": "elasticsearch" } } } 合并多子句 查询子句就像是搭积木一样,可以合并简单的子句为一个复杂的查询语句,比如: 叶子子句(leaf clauses)(比如match子句)用以在将查询字符串与一个字段(或多字段)进行比较 复合子句(compound)用以合并其他的子句。例如,bool子句允许你合并其他的合法子句,must,must_not或者should,如果可能的话: { "bool": { "must": { "match": { "tweet": "elasticsearch" }}, "must_not": { "match": { "name": "mary" }}, "should": { "match": { "tweet": "full text" }} } } 复合子句能合并 任意其他查询子句,包括其他的复合子句。 这就意味着复合子句可以相互嵌套,从而实现非常复杂的逻辑。 以下实例查询在inbox中或未标记spam的邮件中找出包含"business opportunity"的星标(starred)邮件: { "bool": { "must": { "match": { "email": "business opportunity" }}, "should": [ { "match": { "starred": true }}, { "bool": { "must": { "folder": "inbox" }}, "must_not": { "spam": true }} }} ], "minimum_should_match": 1 } } 复合子句可以合并多种子句为一个单一的查询,无论是叶子子句还是其他的复合子句。 7.3 查询与过滤 前面讲到的是关于结构化查询语句,事实上有两种结构化语句可以使用: 结构化查询(Query DSL)和结构化过滤(Filter DSL)。 查询与过滤语句非常相似,但是它们由于使用目的不同而稍有差异。 一条过滤语句会询问每个文档的字段值是否包含着特定值: created 的日期范围是否在 2013 到 2014 ? status 字段中是否包含单词 “published” ? lat_lon 字段中的地理位置与目标点相距是否不超过10km ? 一条查询语句与过滤语句相似,但问法不同: 查询语句会询问每个文档的字段值与特定值的匹配程度如何? 查询语句的典型用法是为了找到文档: 查找与 full text search 这个词语最佳匹配的文档 查找包含单词 run ,但是也包含runs, running, jog 或 sprint的文档 同时包含着 quick, brown 和 fox — 单词间离得越近,该文档的相关性越高 标识着 lucene, search 或 java — 标识词越多,该文档的相关性越高 一条查询语句会计算每个文档与查询语句的相关性,会给出一个相关性评分 _score,并且按照相关性对匹配到的文档进行排序。 这种评分方式非常适用于一个没有完全配置结果的全文本搜索。 性能差异 使用过滤语句得到的结果集 – 一个简单的文档列表,快速匹配运算并存入内存是十分方便的,每个文档仅需要1个字节。这些缓存的过滤结果集与后续请求的结合使用是非常高效的。 查询语句不仅要查找相匹配的文档,还需要计算每个文档的相关性,所以一般来说查询语句要比过滤语句更耗时,并且查询结果也不可缓存。 倒排索引,使得一个只匹配少量文档的简单查询语句在百万级文档中的查询效率会与一条经过缓存的过滤语句旗鼓相当,甚至略占上风。 但是一般情况下,一条经过缓存的过滤查询要远胜一条查询语句的执行效率。 过滤语句的目的就是缩小匹配的文档结果集,所以需要仔细检查过滤条件。 什么情况下使用 原则上来说,使用查询语句做全文本搜索或其他需要进行相关性评分的时候,剩下的全部用过滤语句 7.4 最重要的查询 快速的介绍一下这些最常用到的查询过滤语句。 match_all 查询 使用match_all 可以查询到所有文档,是没有查询条件下的默认语句。 { "match_all": {} } 此查询常用于合并过滤条件。 比如说你需要检索所有的邮箱,所有的文档相关性都是相同的,所以得到的_score为1 match 查询 match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。 如果你使用 match 查询一个全文本字段,它会在真正查询之前用分析器先分析match一下查询字符: { "match": { "tweet": "About Search" } } 如果用match指定了一个确切值,在遇到数字,日期,布尔值或者not_analyzed 的字符串时,它将为你搜索你给定的值: { "match": { "age": 26 }} { "match": { "date": "2014-09-01" }} { "match": { "public": true }} { "match": { "tag": "full_text" }} 提示: 做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据。 与《简单搜索》中介绍的字符查询不同,match查询不可以用类似”+usid:2 +tweet:search”这样的语句。它只能就指定某个确切字段某个确切的值进行搜索,而你要做的就是为它指定正确的字段名以避免语法错误。 multi_match 查询 multi_match查询允许你做match查询的基础上同时搜索多个字段: { "multi_match": { "query": "full text search", "fields": [ "title", "body" ] } } range 过滤 range过滤允许我们按照指定范围查找一批数据: { "range": { "age": { "gte": 20, "lt": 30 } } } 范围操作符包含: gt :: 大于 gte:: 大于等于 lt :: 小于 lte:: 小于等于 term 过滤 term主要用于精确匹配一些值,比如数字,日期,布尔值或 not_analyzed的字符串(未经分析的文本数据类型): { "term": { "age": 26 }} { "term": { "date": "2014-09-01" }} { "term": { "public": true }} { "term": { "tag": "full_text" }} terms 过滤 terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配: { "terms": { "tag": [ "search", "full_text", "nosql" ] } } exists 和 missing 过滤 exists 和 missing 过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的IS_NULL条件 { "exists": { "field": "title" } } 这两个过滤只是针对已经查出一批数据来,但是想区分出某个字段是否存在的时候使用。 7.5 查询与过滤条件的合并 实际生产中,没有哪个请求查询是简单的,往往需要将多请求查询和单个请求查询结合起来。 bool 过滤 bool 过滤可以用来合并多个过滤条件查询结果的布尔逻辑,它包含一下操作符: must :: 多个查询条件的完全匹配,相当于 and。 must_not :: 多个查询条件的相反匹配,相当于 not。 should :: 至少有一个查询条件匹配, 相当于 or。 filter :: 依据条件筛选出满足或不满足条件的文档。 这些参数可以分别继承一个过滤条件或者一个过滤条件的数组: { "bool": { "must": { "match": { "title": "how to make millions" }}, "must_not": { "match": { "tag": "spam" }}, "should": [ { "match": { "tag": "starred" }}, { "range": { "date": { "gte": "2014-01-01" }}} ] } } 以上查询将会找到 title 字段中包含 “how to make millions”,并且 “tag” 字段没有被标为 spam。 如果有标识为 “starred” 或者发布日期为2014年之前,那么这些匹配的文档将比同类文档等级高。 提示: 如果bool 查询下没有must子句,那至少应该有一个should子句。但是 如果有must子句,那么没有should子句也可以进行查询。 bool 查询 bool 查询与 bool 过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功, 而bool 查询要计算每一个查询子句的 _score (相关性分值)。 must:: 查询指定文档一定要被包含。 must_not:: 查询指定文档一定不要被包含。 should:: 查询指定文档,有则可以为文档相关性加分。 7.6 验证查询 查询语句可以变得非常复杂,特别是与不同的分析器和字段映射相结合后,就会有些难度。 validate API 可以验证一条查询语句是否合法。 GET /gb/tweet/_validate/query { "query": { "tweet" : { "match" : "really powerful" } } } 从下面的返回信息得知,以上请求的这条语句是非法的: { "valid" : false, "_shards" : { "total" : 1, "successful" : 1, "failed" : 0 } } 理解错误信息 想知道语句非法的具体错误信息,需要加上 explain 参数: GET /gb/tweet/_validate/query?explain <1> { "query": { "tweet" : { "match" : "really powerful" } } } <1> explain 参数可以提供语句错误的更多详情。 很显然,我们把 query 语句的 match 与字段名位置弄反了: { "valid" : false, "_shards" : { ... }, "explanations" : [ { "index" : "gb", "valid" : false, "error" : "org.elasticsearch.index.query.QueryParsingException: [gb] No query registered for [tweet]" } ] } “` 理解查询语句 如果是合法语句的话,使用 explain 参数可以返回一个带有查询语句的可阅读描述,可以帮助了解查询语句在ES中是如何执行的: GET /_validate/query?explain { "query": { "match" : { "tweet" : "really powerful" } } } explanation 会为每一个索引返回一段描述,因为每个索引会有不同的映射关系和分析器: { "valid" : true, "_shards" : { ... }, "explanations" : [ { "index" : "us", "valid" : true, "explanation" : "tweet:really tweet:powerful" }, { "index" : "gb", "valid" : true, "explanation" : "tweet:really tweet:power" } ] } 从返回的 explanation 你会看到 match 是如何为查询字符串 "really powerful" 进行查询的, 首先,它被拆分成两个独立的词分别在 tweet 字段中进行查询。 而且,在索引us中这两个词为"really"和"powerful",在索引gb中被拆分成"really" 和 "power"。 这是因为我们在索引gb中使用了english分析器。 7.7 总结 详细介绍了如何在项目中使用常见的查询语句。 想要完全掌握搜索和结构化查询,还需要在工作中花费大量的时间来理解ES的工作方式。 第八章 相关性排序 默认情况下,结果集会按照相关性进行排序 – 相关性越高,排名越靠前。 这一章我们会讲述相关性是什么以及它是如何计算的。 8.1 排序方式 为了使结果可以按照相关性进行排序,我们需要一个相关性的值。在ElasticSearch的查询结果中,相关性分值会用_score字段来给出一个浮点型的数值,所以默认情况下,结果集以_score进行倒序排列。 有时,即便如此,你还是没有一个有意义的相关性分值。比如,以下语句返回所有tweets中 user_id 是否 包含值 1: GET /_search { "query" : { "filtered" : { "filter" : { "term" : { "user_id" : 1 } } } } } 过滤语句与 _score 没有关系,但是有隐含的查询条件 match_all 为所有的文档的 _score 设值为 1。也就相当于所有的文档相关性是相同的。 字段值排序 下面例子中,对结果集按照时间排序,将最新的文档排列靠前。使用 sort 参数进行排序: GET /_search { "query" : { "filtered" : { "filter" : { "term" : { "user_id" : 1 }} } }, "sort": { "date": { "order": "desc" }} } 你会发现这里有两个不同点: "hits" : { "total" : 6, "max_score" : null, <1> "hits" : [ { "_index" : "us", "_type" : "tweet", "_id" : "14", "_score" : null, <1> "_source" : { "date": "2014-09-24",
0. 绪论 Windows离线断网环境下安装Python包,配置环境,准备用来生成word模版。姑且记录一下 生产环境 : windows 7 windows10 python 3.5.2 pip 1.5.2 友情提示:出现问题时候,看日志是王道!计算机不会犯错! 机器上python2,3混用的问题,参考: http://blog.csdn.net/wangyaninglm/article/details/53312606 第二小节:不同版本python混用(官方用法)其实主要就是前面加上py -3(或)2 1.安装过程 1.下载最新pip,更新pip版本 新建packages文件夹放在目录中:c:\python35\packages py -3 –m pip install --upgrade pip 2.在可以联网的开发机器上安装好需要的包 例如: py -3 –m pip install numpy py -3 –m pip install pandas 3.打包已安装的包 在c:\python35目录下新建packages文件夹用来存储下载下来的所需安装包。 在 c:\python35\Scripts下启动cmd窗口。 pip list #查看安装的包 pip freeze >requirements.txt py -3 –m pip install --download c:\python35\packages -r requirements.txt 上述命令需要在一个联网机器上运行,不然会报错,不联网的机器似乎没法打包已经安装好的whl包,如果有方法求高手告知,报错如下(白色字体是联网后运行正常的): requirements.txt是这个样子:大概记载了每个包的版本号 cycler==0.10.0 jieba==0.38 matplotlib==1.5.3 nltk==3.2.1 numpy==1.11.2 pyparsing==2.1.10 python-dateutil==2.6.0 pytz==2016.10 scikit-learn==0.18.1 six==1.10.0 4.离线情况安装其他机器打包好的包whl 将packages文件夹和requirement.txt拷贝至离线机器上目录下, packages文件夹放在c:\Python35下,requirement.txt放在c:\Python35\Scripts下。 requirements.txt文件放在pip.exe目录下。 py -3 –m pip install --no-index --find-index=c:\python35\packages -r requirements.txt 上述命令中的–find-index 这个命令在python27中似乎是对的,python35中需要换成–find-links 正确的命令: py -3 -m pip install --no-index --find-links=c:\python35\packages -r requirements.txt 3.原理 这种whl包下载好后,放在上述位置,再修改下面文件 添加一行: 包名=版本号 来一个命令就行了!装过的pip就不装了,没装的pip会自动安装 py -3 -m pip install –no-index –find-links=c:\python35\packages -r requirements.txt 参考 https://segmentfault.com/a/1190000006027207 pip常用命令: http://me.iblogc.com/2015/01/01/pip%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/ pip documentation: https://pip.pypa.io/en/latest/
0. 绪论 断网的环境下配置python开发环境非常讨厌,本文旨在优雅暴力的解决这一问题。 生产环境 : windows 7 windows10 python 3.5.2 pip 1.5.2 友情提示:出现问题时候,看日志是王道!计算机不会犯错! 机器上python2,3混用的问题,参考: http://blog.csdn.net/wangyaninglm/article/details/53312606 第二小节:不同版本python混用(官方用法)其实主要就是前面加上py -3(或)2 Windows离线断网环境下安装Python包,配置环境,准备用来生成word模版,需要用到一些win32com的python库,但是又没有网还想用pip方式傻瓜安装,怎么办呢,百度google探索了半天。姑且记录一下 linux下此方法应该同样可行。 当然还有暴力的方法是,pip show 包名,然后python的版本一样的话直接copy目录(红线部分),import应该也是好使的。下面以numpy包为例。 1.安装过程 1.下载最新pip,更新pip版本 新建packages文件夹放在目录中:c:\python35\packages py -3 –m pip install --upgrade pip 2.在可以联网的开发机器上安装好需要的包 例如: py -3 –m pip install numpy py -3 –m pip install pandas 3.打包已安装的包 在c:\python35目录下新建packages文件夹用来存储下载下来的所需安装包。 在 c:\python35\Scripts下启动cmd窗口。 pip list #查看安装的包 pip freeze >requirements.txt py -3 –m pip install --download c:\python35\packages -r requirements.txt 上述命令需要在一个联网机器上运行,不然会报错,不联网的机器似乎没法打包已经安装好的whl包,如果有方法求高手告知,报错如下(白色字体是联网后运行正常的): requirements.txt是这个样子:大概记载了每个包的版本号 cycler==0.10.0 jieba==0.38 matplotlib==1.5.3 nltk==3.2.1 numpy==1.11.2 pyparsing==2.1.10 python-dateutil==2.6.0 pytz==2016.10 scikit-learn==0.18.1 six==1.10.0 4.离线情况安装其他机器打包好的包whl 将packages文件夹和requirement.txt拷贝至离线机器上目录下, packages文件夹放在c:\Python35下,requirement.txt放在c:\Python35\Scripts下。 requirements.txt文件放在pip.exe目录下。 py -3 –m pip install --no-index --find-index=c:\python35\packages -r requirements.txt 上述命令中的–find-index 这个命令在python27中似乎是对的,python35中需要换成–find-links 正确的命令: py -3 -m pip install --no-index --find-links=c:\python35\packages -r requirements.txt 3.原理 这种whl包下载好后,放在上述位置,再修改下面文件 添加一行: 包名=版本号 来一个命令就行了!装过的pip就不装了,没装的pip会自动安装 py -3 -m pip install –no-index –find-links=c:\python35\packages -r requirements.txt 参考 https://segmentfault.com/a/1190000006027207 pip常用命令: http://me.iblogc.com/2015/01/01/pip%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/ pip documentation: https://pip.pypa.io/en/latest/
《读书报告 – Elasticsearch入门 》 第一章 Elasticsearch入门 Elasticsearch是一个实时的分布式搜索和分析引擎,使得人们可以在一定规模上和一定速度上实现数据检索,常用于全文本检索,结构化检索、分析以及三种的结合应用。Wikipedia、Guardian、Stack Overflow、Github都在使用Elasticsearch实现自己的相关检索工作。 1.1 you konw,for search Elasticsearch是一个基于Apache Lucene的开源搜索引擎,全文本检索引擎库。它是一个高性能、可伸缩的信息搜索库,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文和德文)。 1.2 ES安装 理解Elasticsearch最简单的方式就是使用它!安装Elasticsearch之前,首先要先安装java,最好是最新版本。 然后,在Elasticsearch的官网下载安装包。 按照完成之后,进入Elasticsearch的bin目录下,启动Elasticsearch。 cd /$path/elasticsearch-2.3.2/bin ./elasticsearch -d //-d代表后台运行 新打开一个terminal,测试Elasticsearch是否正常启动。 curl ‘http://localhost:9200/?pretty’ 执行效果如下: 安装Sense 利用Sense可以实现在浏览器里和ES的交互。 安装、运行Sense: 1.在Kibana目录下,下载安装Sense ./bin/kibana plugin –install elastic/sense 2.启动Kibana ./bin/kibana 3.打开kibana,在浏览器中输入 http://localhost:5601/app/sense 1.3 与Elasticsearch的交互 与Elasticsearch的交互,使用Java语言。 Java API Elasticsearch使用两种内置的客户端模式,在其中使用java与之交互: Node client node client作为non data node加入到本地集群,换句话说,它本身不存储任何数据,但它清楚地知道数据的存储位置,能够准确地索引到数据存储的节点。 Transport client 轻量级的transport client可以传递请求到远端集群,它本身不加入到集群,但它能给集群中的每个节点发送请求。 Java clients通过9300端口,利用ES的transport协议进行通信,集群中的节点也通过9300端口通信。 1.4 面向文档 应用中的对象很少只是简单的键值列表,更多时候它拥有复杂的数据结构,比如包含日期、地理位置、另一个对象或者数组。 总有一天你会想到把这些对象存储到数据库中。将这些数据保存到由行和列组成的关系数据库中,就好像是把一个丰富,信息表现力强的对象拆散了放入一个非常大的表格中:你不得不拆散对象以适应表模式(通常一列表示一个字段),然后又不得不在查询的时候重建它们。 Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。这种理解数据的方式与以往完全不同,这也是Elasticsearch能够执行复杂的全文搜索的原因之一。 1.5 JSON Javascript对象符号(Javascript Object Notation),文档个序列化格式。 使用json来表示一个用户对象 { "email": "john@smith.com", "first_name": "John", "last_name": "Smith", "info":{ "bio": "Eco-warrior and defender of the weak", "age": 25, "interests": ["dolphins","whales"] }, "join_date":"2014/05/01" } 尽管原始的user对象很复杂,但它的结构和对象的含义已经被完整的体现在JSON中了,在Elasticsearch中将对象转化为JSON并做索引要比在表结构中做相同的事情简单的多。 1.6 索引 index 与传统关系数据库的类比: Relational DB -> Databases -> Tables -> Rows -> Columns Elasticsearch -> Indices -> Types -> Documents -> Fields 理解点: 索引(名词) 如上文所述,一个索引(index)就像是传统关系数据库中的数据库,它是相关文档存储的地方,index的复数是indices 或indexes。 索引(动词) 「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。 倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。 默认情况下,文档中所有的字段都会被索引(拥有一个倒排索引),只有这样他们才是可被搜索的。 Let’s Build an Employee Directory 构建一个Megacorp公司的职工信息,构建employee directory。在分析了需求后,具体需要实现以下要求: * 为每个员工的文档(document)建立索引,每个文档包含了相应员工的所有信息。 * 每个文档的类型为employee。 * employee类型归属于索引megacorp。 * megacorp索引存储在Elasticsearch集群中。 第一步,建索引 curl -XPUT 'ibd14:9200/megacorp/employee/1' -d' { "first_name":"John", "last_name":"Smith", "age":25, "about":"I love to go rock climbing", "interests":["sports","music"] }' curl -XPUT 'ibd14:9200/megacorp/employee/2' -d' { "first_name" : "Jane", "last_name" : "Smith", "age" : 32, "about" : "I like to collect rock albums", "interests": [ "music" ] }' curl -XPUT 'ibd14:9200/megacorp/employee/3' -d' { "first_name" : "Douglas", "last_name" : "Fir", "age" : 35, "about": "I like to build cabinets", "interests": [ "forestry" ] }' curl -XPUT 'ibd14:9200/megacorp/employee/4' -d' { "first_name": "Kobe", "last_name": "Bryant", "age": 38, "about":"I love to play basketball,football", "interests":["sports","music"] }' 其中$path:/megacorp/employee/1包含三部分信息: 名字 说明 megacorp 索引名 employee 类型名 1 这个员工的ID 1.7 搜索——检索文档 现在Elasticsearch中已经存储了一些数据,我们可以根据业务需求开始工作了。第一个需求是能够检索单个员工的信息。 只要执行HTTP GET请求并指出文档的“地址”——索引、类型和ID既可。根据这三部分信息,我们就可以返回原始JSON文档: 代码: curl -XGET 'ibd14:9200/megacorp/employee/1' (1)简单搜索 GET请求非常简单——轻松获取想要的文档。一个最简单的搜索全部员工的请求: curl -XGET ‘ibd14:9200/megacorp/employee/_search?pretty’ (2)查询字符串 curl -XGET 'ibd14:9200/megacorp/employee/_search?q=last_name:Smith&pretty' 我们在请求中依旧使用_search关键字,然后将查询语句传递给参数q=。这样就可以得到所有姓氏为Smith的结果。 (3)使用DSL语句查询 查询字符串搜索便于通过命令行完成特定(ad hoc)的搜索,但是它也有局限性。Elasticsearch提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询。DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。我们可以这样表示之前关于“Smith”的查询: 代码: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query":{ "match":{ "last_name":"Smith" } } }' 这次返回与之前查询相同的结果。你可以看到有些东西改变了,我们不再使用查询字符串(query string)做为参数,而是使用请求体代替。这个请求体使用JSON表示,其中使用了match语句(查询类型之一,具体我们以后会学到) (4)更复杂的搜索 依旧想要找到姓氏为“Smith”的员工,但是我们只想得到年龄大于30岁的员工。我们的语句将添加过滤器(filter),它使得我们高效率的执行一个结构化搜索: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query" : { "bool": { "must": [ { "match" : { "last_name" : "smith" //<2> } } ], "filter": { "range" : { "age" : { "gt" : 30 } //<1> } } } } }' <1> 这部分查询属于区间过滤器(range filter),它用于查找所有年龄大于30岁的数据——gt为”greater than”的缩写。 <2> 这部分查询与之前的match语句(query)一致。 我们添加了一个过滤器(filter)用于执行区间搜索,然后重复利用了之前的match语句。现在我们的搜索结果只显示了一个32岁且名字是“Jane Smith”的员工。 (5)全文搜索 一种更高级的搜索,全文搜索——一种传统数据库很难实现的功能。在这里我们将会搜索所有喜欢“rock climbing”的员工,代码: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query":{ "match":{ "about":"rock climbing" } } }' 我们使用了之前的match查询,从about字段中搜索”rock climbing”,我们得到了两个匹配文档。 默认情况下,Elasticsearch根据结果相关性评分来对结果集进行排序,所谓的「结果相关性评分」就是文档与查询条件的匹配程度。很显然,排名第一的John Smith的about字段明确的写到“rock climbing”。 但是为什么Jane Smith也会出现在结果里呢?原因是“rock”在她的about字段中被提及了。因为只有“rock”被提及而“climbing”没有,所以她的score要低于John。 这个例子很好的解释了Elasticsearch如何在各种文本字段中进行全文搜索,并且返回相关性最大的结果集。相关性(relevance)的概念在Elasticsearch中非常重要,而这个概念在传统关系型数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。 (6)短语搜索 目前我们可以在字段中搜索单独的一个词,这挺好的,但是有时候你想要确切的匹配若干个单词或者短语(phrases)。例如我们想要查询同时包含”rock”和”climbing”(并且是相邻的)的员工记录。 要做到这个,我们只要将match查询变更为match_phrase查询即可: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query":{ "match_phrase":{ "about":"rock climbing" } } }' (7)高亮我们的搜索 很多应用喜欢从每个搜索结果中高亮(highlight)匹配到的关键字,这样用户可以知道为什么这些文档和查询相匹配。在Elasticsearch中高亮片段是非常容易的。让我们在之前的语句上增加highlight参数。 curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query" : { "match_phrase" : { "about" : "rock climbing" } }, "highlight": { "fields" : { "about" : {} } } }' 当我们运行这个语句时,会命中与之前相同的结果,但是在返回结果中会有一个新的部分叫做highlight,这里包含了来自about字段中的文本,并且用来标识匹配到的单词。 1.8 聚合——分析 最后,我们还有一个需求需要完成:允许管理者在职员目录中进行一些分析。 Elasticsearch有一个功能叫做聚合(aggregations),它允许你在数据上生成复杂的分析统计。它很像SQL中的GROUP BY但是功能更强大。 (1)example 1.8-1 举个例子,让我们找到所有职员中最大的共同点(兴趣爱好)是什么: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "aggs":{ "all_interests":{ "terms" :{ "field":"interests" } } } }' 从查询结果中,我们可以看到两个职员对音乐有兴趣,一个喜欢林学,一个喜欢运动。这些数据并没有被预先计算好,它们是实时的从匹配查询语句的文档中动态计算生成的。 (2)example1.8-2 如果我们想知道所有姓”Smith”的人最大的共同点(兴趣爱好),我们只需要增加合适的语句既可。 curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "query":{ "match":{ "last_name":"smith" } }, "aggs":{ "all_interests":{ "terms" :{ "field":"interests" } } } }' 从执行结果看all_interests聚合已经变成只包含和查询语句相匹配的文档了。 (3)example 1.8-3 聚合也允许分级汇总。例如,让我们统计每种兴趣下职员的平均年龄: curl -XGET 'ibd14:9200/megacorp/employee/_search?pretty' -d' { "aggs":{ "all_interests":{ "terms" :{ "field":"interests" }, "aggs":{ "avg_age":{ "avg":{ "field":"age" } } } } } }' 该聚合结果比之前的聚合结果要更加丰富。我们依然得到了兴趣以及数量(指具有该兴趣的员工人数)的列表,但是现在每个兴趣额外拥有avg_age字段来显示具有该兴趣员工的平均年龄。 即使还不理解语法,但也可以大概感觉到通过这个特性可以完成相当复杂的聚合工作,可以处理任何类型的数据。 1.9 小结 这个简短的教程能够很好的描述Elasticsearch的功能。当然这只是一些皮毛,为了保持简短,还有很多的特性未提及——像推荐、定位、渗透、模糊以及部分匹配等。但这也突出了构建高级搜索功能是多么的容易。无需配置,只需要添加数据然后开始搜索! 可能有些语法有些困惑,或者在微调方面有些疑问。那么,本书的其余部分将深入这些问题的细节,让你全面了解Elasticsearch的工作过程。 1.10 分布式 在章节的开始我们提到Elasticsearch可以扩展到上百(甚至上千)的服务器来处理PB级的数据。然而我们的教程只是给出了一些使用Elasticsearch的例子,并未涉及相关机制。Elasticsearch为分布式而生,而且它的设计隐藏了分布式本身的复杂性。 Elasticsearch在分布式概念上做了很大程度上的透明化,在教程中你不需要知道任何关于分布式系统、分片、集群发现或者其他大量的分布式概念。所有的教程你即可以运行在你的笔记本上,也可以运行在拥有100个节点的集群上,其工作方式是一样的。 1 Elasticsearch致力于隐藏分布式系统的复杂性。以下这些操作都是在底层自动完成的: 将你的文档分区到不同的容器或者分片(shards)中,它们可以存在于一个或多个节点中。 将分片均匀的分配到各个节点,对索引和搜索做负载均衡。 冗余每一个分片,防止硬件故障造成的数据丢失。 将集群中任意一个节点上的请求路由到相应数据所在的节点。 无论是增加节点,还是移除节点,分片都可以做到无缝的扩展和迁移。 当你阅读本书时,你可以遇到关于Elasticsearch分布式特性的补充章节。这些章节将教给你如何扩展集群和故障转移,如何处理文档存储,如何执行分布式搜索,分片是什么以及如何工作。 这些章节不是必读的——不懂这些内部机制也可以使用Elasticsearch的。但是这些能够帮助你更深入和完整的了解Elasticsearch。你可以略读它们,然后在你需要更深入的理解时再回头翻阅。 1.11 结语 现在对Elasticsearch可以做些什么以及其易用程度有了大概的了解。Elasticsearch致力于降低学习成本和轻松配置。学习Elasticsearch最好的方式就是开始使用它:开始索引和检索吧! 当然,你越是了解Elasticsearch,你的生产力就越高。你越是详细告诉Elasticsearch你的应用的数据特点,你就越能得到准确的输出。 本书其余部分将帮助你从新手晋级到专家。每一个章节都会阐述一个要点,并且会包含专家级别的技巧。如果你只是刚起步,那么这些技巧可能暂时和你无关。Elasticsearch有合理的默认配置而且可以在没有用户干预的情况下做正确的事情。当需要提升性能时你可以随时回顾这些章节。 第二章 集群内部工作方式 这部分是关于Elasticsearch在分布式环境下,工作机制的补充章节。这个章解释了一些通用的术语,例如集群(cluster)、节点(node)和分片(shard),Elasticsearch的扩展机制,以及它如何处理硬件故障。 Elasticsearch用于构建高可用和可扩展的系统。扩展的方式包括两种: (1)纵向扩展——购买更好的服务器 (2)横向扩展——购买更多的服务器 Elasticsearch虽然能从更强大的硬件中获得更好的性能,但是纵向扩展有它的局限性。真正的扩展应该是横向的,通过增加节点来均摊负载和增加可靠性。 由于Elasticsearch天生就是分布式的:它知道如何管理节点来提供高扩展和高可用。因此底层实现并不用去关心。 2.1 空集群 当启动一个单独的节点,它还没有数据和索引,这个集群就只有一个节点,同时也充当MASTER的角色。 一个节点(node)就是一个Elasticsearch实例,而一个集群(cluster)由一个或多个节点组成,它们具有相同的cluster.name,它们协同工作,分享数据和负载。当加入新的节点或者删除一个节点时,集群就会感知到并平衡数据。 集群中一个节点会被选举为主节点(master),它将临时管理集群级别的一些变更,例如新建或删除索引、增加或移除节点等。主节点不参与文档级别的变更或搜索,这意味着在流量增长的时候,该主节点不会成为集群的瓶颈。任何节点都可以成为主节点。我们例子中的集群只有一个节点,所以它会充当主节点的角色。 用户能够与集群中的任何节点通信,包括主节点。每一个节点都知道文档存在于哪个节点上,它们可以转发请求到相应的节点上。我们访问的节点负责收集各节点返回的数据,最后一起返回给客户端。这一切都由Elasticsearch处理。 # 2.2 集群健康 在Elasticsearch集群中可以监控统计很多信息,但是只有一个是最重要的:集群健康(cluster health)。集群健康有三种状态: green,yellow或red。 通过命令可以查看集群的状态: curl -XGET ‘ibd14:9200/_cluster/health?pretty’ 从返回的信息中,status字段提供一个综合的指标来表示集群的的服务状况。三种颜色各自的含义: | green —— 所有主要分片和复制分片都可用 | yellow —— 所有主要分片可用,但不是所有复制分片都可用 | red —— 不是所有的主要分片都可用 2.3 添加索引 索引(index)——一个存储关联数据的地方。实际上,索引只是一个用来指向一个或多个分片(shards)的“逻辑命名空间(logical namespace)”。 分片是最小级别的工作单元,它只索引中所有数据的一部分。在接下来的《深入分片》一章,将详细说明分片的工作原理,但是现在我们只要知道分片就是一个Lucene实例,并且它本身就是一个完整的搜索引擎。我们的文档存储在分片中,并且在分片中被索引,但是我们的应用程序不会直接与它们通信,取而代之的是,直接与索引通信。 分片是Elasticsearch在集群中分发数据的关键。可以把分片想象成数据的容器。文档存储在分片中,然后分片分配到你集群中的节点上。当你的集群扩容或缩小,Elasticsearch将会自动在你的节点间迁移分片,以使集群保持平衡。 分片可以是主分片(primary shard)或者是复制分片(replica shard)。你索引中的每个文档属于一个单独的主分片,所以主分片的数量决定了索引最多能存储多少数据。 理论上主分片能存储的数据大小是没有限制的,限制取决于你实际的使用情况。分片的最大容量完全取决于你的使用状况:硬件存储的大小、文档的大小和复杂度、如何索引和查询你的文档,以及你期望的响应时间。 复制分片只是主分片的一个副本,它可以防止硬件故障导致的数据丢失,同时可以提供读请求,比如搜索或者从别的shard取回文档。 当索引创建完成的时候,主分片的数量就固定了,但是复制分片的数量可以随时调整。 现在在集群中唯一一个空节点上创建一个叫做blogs的索引。默认情况下,一个索引被分配5个主分片,但是为了演示的目的,我们只分配3个主分片和一个复制分片(每个主分片都有一个复制分片): curl -XPUT 'ibd14:9200/blogs' -d' { "settings":{ "number_of_shards": 3, "number_of_replicas":1 } }' 通过前面的设置三个主分片都被分配到唯一的节点Node 1上。如果我们现在检查集群健康(cluster-health),我们将见到以下信息: { "cluster_name": "elasticsearch", "status": "yellow", <1> "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 3, "active_shards": 3, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, <2> "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 50 } 其中status是yellow,是因为三个复制分片还没有被分配到节点上。 集群的健康状态yellow表示所有的主分片(primary shards)启动并且正常运行了——集群已经可以正常处理任何请求——但是复制分片(replica shards)还没有全部可用。事实上所有的三个复制分片现在都是unassigned状态——它们还未被分配给节点。在同一个节点上保存相同的数据副本是没有必要的,如果这个节点故障了,那所有的数据副本也会丢失。 2.4 增加故障转移 上一节提到了由于复制分片还没有分配到节点上,所以一旦唯一的节点Node 1挂点,数据就会丢失,有单点故障的风险。要防止单点故障,我们唯一需要做的就是启动另一个节点。 启动第二个节点 为了测试在增加第二个节点后发生了什么,你可以使用与第一个节点相同的方式启动第二个节点(《运行Elasticsearch》一章),而且命令行在同一个目录——一个节点可以启动多个Elasticsearch实例。 只要第二个节点与第一个节点有相同的cluster.name(请看./config/elasticsearch.yml文件),它就能自动发现并加入第一个节点所在的集群。如果没有,检查日志找出哪里出了问题。这可能是网络广播被禁用,或者防火墙阻止了节点通信。 当第二个节点Node 2已经加入集群,三个复制分片(replica shards)——分别对应三个主分片,也已经被分配了,这意味着在丢失任意一个节点的情况下依旧可以保证数据的完整性。 文档的索引将首先被存储在主分片中,然后并发复制到对应的复制节点上。这可以确保我们的数据在主节点和复制节点上都可以被检索。 现在查看集群的健康状况,status就变成了green。这意味着三个主分片和三个复制分片都已可用,此时集群不但功能完备,而且具有高可用性。 2.5 横向扩展 启动第三个节点,我们的集群会重新组织自己,包含3个节点的集群——分片已经被重新分配以平衡负载。 Node3包含了分别来自Node 1和Node 2的一个分片,这样每个节点就有两个分片,和之前相比少了一个,这意味着每个节点上的分片将获得更多的硬件资源(CPU、RAM、I/O)。 分片本身就是一个完整的搜索引擎,它可以使用单一节点的所有资源。我们拥有6个分片(3个主分片和三个复制分片),最多可以扩展到6个节点,每个节点上有一个分片,每个分片可以100%使用这个节点的资源。 2.6 继续扩展 如果我们要扩展到6个以上的节点,要怎么做? 主分片的数量在创建索引时已经确定。实际上,这个数量定义了能存储到索引里数据的最大数量(实际的数量取决于你的数据、硬件和应用场景)。然而,主分片或者复制分片都可以处理读请求——搜索或文档检索,所以数据的冗余越多,我们能处理的搜索吞吐量就越大。 复制分片的数量可以在运行中的集群中动态地变更,这允许我们可以根据需求扩大或者缩小规模。让我们把复制分片的数量从原来的1增加到2: curl -XPUT 'ibd14:9200/blogs' -d' { "settings":{ "number_of_replicas":2 } }' 这样blogs索引现在就有了9个分片:3个主分片和6个复制分片。这意味着我们能够扩展到9个节点,再次变成每个节点一个分片。这样使我们的搜索性能相比原始的三节点集群增加三倍。 在同样数量的节点上增加更多的复制分片并不能提高性能,因为这样做的话平均每个分片的所占有的硬件资源就减少了。 不过这些额外的复制节点使我们有更多的冗余:通过以上对节点的设置,我们能够承受两个节点故障而不丢失数据。 2.7 应对故障 前面已经提到过Elasticsearch可以应对节点失效,所以当我们杀掉第一个节点的进程(以下简称杀掉节点),集群会发生什么变化呢! 例如,杀掉Node 1的进程后,由于一个集群必须要有一个主节点才能使其功能正常,所以集群做的第一件事就是各节点选举了一个新的主节点:Node 2。 主分片1和2在我们杀掉Node 1时已经丢失,我们的索引在丢失主分片时不能正常工作。如果此时我们检查集群健康,我们将看到状态red:不是所有主节点都可用! 但是丢失的两个主分片的完整拷贝存在于其他节点上,所以新主节点做的第一件事是把这些在Node 2和Node 3上的复制分片升级为主分片,这时集群健康回到yellow状态。这个提升是瞬间完成的,就好像按了一下开关。 此时集群健康状态是yellow而不是green?我们有三个主分片,但是我们指定了每个主分片对应两个复制分片,当前却只有一个复制分片被分配,这就是集群状态无法达到green的原因。 当我们杀掉Node 2,我们的程序依然可以在没有丢失数据的情况下继续运行,因为Node 3还有每个分片的拷贝。 如果我们重启Node 1,集群将能够重新分配丢失的复制分片,如果Node 1依旧有旧分片的拷贝,它将会尝试再利用它们,它只会从主分片上复制在故障期间有数据变更的那一部分。 第三章 数据吞吐 实际生活中被组织起来、与实际个体、对象相对应的数据才有意义。面向对象编程语言用对象来表示和处理现实生活中那些有着潜在关系和复杂结构的实体。然而,使用关系型数据库去存储这些实体对象使得它们的灵活性不复存在。 对象(object)是一种语言相关,记录在内存中的的数据结构。为了在网络间发送,或者存储它,需要一些标准的格式来表示它。JSON是一种可读的以文本来表示对象的方式。它已经成为NoSQL世界中数据交换的一种事实标准。当对象被序列化为JSON,它就成为JSON文档(JSON document)了。 Elasticsearch是一个分布式的文档(document)存储引擎。它可以实时存储并检索复杂数据结构——序列化的JSON文档。换言说,一旦文档被存储在Elasticsearch中,它就可以在集群的任一节点上被检索。 在Elasticsearch中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。而且,与其它数据库不同,它可以在同一个查询中利用所有的这些反向索引,以惊人的速度返回结果。 3.1 什么是文档? 程序中大多的实体或对象能够被序列化为包含键值对的JSON对象。 键(key)——字段(field)或属性(property) 值(value)——字符串、数字、布尔类型、另一个对象、值数组或者其他特殊类型,比如表示日期的字符串或者表示地理位置的对象。 在Elasticsearch中,文档(document),特指最顶层结构或者根对象(root object)序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)。 3.2 文档元数据 一个文档不只有数据。它还包含了元数据(metadata)——关于文档的信息。三个必须的元数据节点是: |节点 | 说明 | _index | 文档存储的地方 | _type | 文档代表的对象的类 | _id | 文档的唯一标识 索引(index)类似于关系型数据库里的“数据库”——它是我们存储和索引关联数据的地方。 实际上,数据被存储和索引在分片(shards)中,索引只是一个把一个或多个分片分组在一起的逻辑空间。然而,这只是一些内部细节——我们的程序完全不用关心分片。对于我们的程序而言,文档存储在索引(index)中。 相同类型(type)的文档表示相同的“事物”,因为他们的数据结构也是相同的,类似于数据库中的“表”。 每个类型(type)都有自己的映射(mapping)或者结构定义,就像传统数据库表中的列一样。所有类型下的文档被存储在同一个索引下,但是类型的映射(mapping)会告诉Elasticsearch不同的文档如何被索引。 id仅仅是一个字符串,它与_index和_type组合时,就可以在Elasticsearch中唯一标识一个文档。当创建一个文档可以自定义_id,也可以让Elasticsearch自动生成。 3.3 索引文档 文档通过index API被索引——使数据可以被存储和搜索。 curl -XPUT 'ibd14:9200/website_weichao/blog/123' -d' { "title": "My first blog entry", "text": "Just trying this out...", "date": "2014/01/01" }' 上述代码表示:构建了一个索引叫做“website”,类型叫做“blog”,设置的ID是“123”,那么这个索引请求就会被响应、并返回结果如下。 { "_type": "blog", "_id": "123", "_version": 1, "created": true }' 当文档产生变化时,_version的值增加。构建索引时,如果id值缺省,ES会自动创建一个id。 curl -XPOST 'ibd14:9200/website_weichao/blog/' -d' { "title": "My second blog entry", "text": "Still trying this out...", "date": "2014/01/01" }' 3.4 检索文档 (1)从Elasticsearch中获取文档,我们使用同样的_index、_type、_id,但是HTTP方法改为GET: curl -XGET 'ibd14:9200/website_weichao/blog/123?pretty' (2)检索文档的一部分 curl -XGET 'ibd14:9200/website_weichao/blog/123?_source=title,text&pretty' curl -XGET 'ibd14:9200/website_weichao/blog/123?_source&pretty' 3.5 检查稳定是否存在 当只是检查文档是否存在——对内容完全不感兴趣——使用HEAD方法来代替GET。HEAD请求不会返回响应体,只有HTTP头。 curl -i -XHEAD 'ibd14:9200/website_weichao/blog/123' 如果你的文档存在,Elasticsearch将会返回200 OK状态;不存在时返回404 Not Found curl -i -XHEAD 'ibd14:9200/website_weichao/blog/124' 当然,这只表示你在查询的那一刻文档不存在,但并不表示几毫秒后依旧不存在。另一个进程在这期间可能创建新文档。 3.6 更新整个文档 文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用《索引文档》章节提到的index API 重建索引(reindex) 或者替换掉它。 (1)重新put curl -XPUT 'ibd14:9200/website_weichao/blog/123' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 在响应中,我们可以看到Elasticsearch把_version的值增加了。每重复执行一次上述代码,_version的值就会加1。这说明: 在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。 旧版本文档不会立即消失,但也不能去访问它。Elasticsearch会在继续索引更多数据时清理被删除的文档。 (2)update update API 似乎 允许你修改文档的局部,但事实上Elasticsearch遵循与之前所说完全相同的过程,这个过程如下: 从旧文档中检索JSON 修改它 删除旧文档 索引新文档 唯一的不同是update API完成这一过程只需要一个客户端请求既可,不再需要get和index请求了。 3.7 创建一个新文档 索引一个文档,如何确定是完全创建了一个新的还是覆盖了一个已经存在的呢? (1)创建已经存在的文档。 方法一: curl -XPUT 'ibd14:9200/website_weichao/blog/123?op_type=create' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 方法二: curl -XPUT 'ibd14:9200/website_weichao/blog/123/_create?pretty' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 以上两种方法,最后返回的结果都相同,status为409,错误提示:文档已经存在。 (2)创建新文档。 方法一:op_type=create curl -XPUT 'ibd14:9200/website_weichao/blog/130?op_type=create&pretty' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 方法二: curl -XPUT 'ibd14:9200/website_weichao/blog/130/_create&pretty' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 请求成功的创建了一个新文档,Elasticsearch将返回正常的元数据且响应状态码是201 Created。 # 3.8 删除文档 删除文档的语法模式与之前基本一致,只不过要使用DELETE方法 curl -i -XDELET 'ibd14:9200/website_weichao/blog/131?pretty' 如果文档被找到,Elasticsearch将返回200 OK状态码和以下响应体。没找到时返回结果如下所示: { "_index" : "website_weichao", "_type" : "blog", "_id" : "131", "found" : false } 删除一个文档也不会立即从磁盘上移除,它只是被标记成已删除。Elasticsearch将会在你之后添加更多索引的时候才会在后台进行删除内容的清理。 3.9 处理冲突 Elasticsearch是分布式的。当文档被创建、更新或删除,文档的新版本会被复制到集群的其它节点。Elasticsearch即是同步的又是异步的,意思是这些复制请求都是平行发送的,并无序(out of sequence)的到达目的地。这就需要一种方法确保老版本的文档永远不会覆盖新的版本。 我们利用_version的这一优点确保数据不会因为修改冲突而丢失。我们可以指定文档的version来做想要的更改。如果那个版本号不是现在的(修改的版本号小于当前的版本号),我们的请求就失败了。 (1)新创建文档,查看返回的状态 curl -XPUT 'ibd14:9200/website_weichao/blog/1?pretty' -d' { "title": "My first blog entry", "text": "I am starting to get the hang of this...", "date": "2016/01/01" }' 创建成功,返回200 OK。 (2)新建文档,加入版本信息 curl -i -XPUT 'ibd14:9200/website_weichao/blog/1?version=1&pretty' -d' { "title": "My third blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 创建成功,返回200 OK,同时_version自增1。 (3)新创建文档,版本信息小于当前版本,报错 409冲突 curl -i -XPUT 'ibd14:9200/website_weichao/blog/1?version=2&pretty' -d' { "title": "My third blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 返回信息:409 conflict HTTP/1.1 409 Conflict { "error" : { "root_cause" : [ { "type" : "version_conflict_engine_exception", "reason" : "[blog][1]: version conflict, current [3], provided [2]", "index" : "website_weichao", "shard" : "3" } ], "type" : "version_conflict_engine_exception", "reason" : "[blog][1]: version conflict, current [3], provided [2]", "index" : "website_weichao", "shard" : "3" }, "status" : 409 } (4)使用外部版本控制系统 新创建文档,加入外部版本控制系统 curl -i -XPUT 'ibd14:9200/website_weichao/blog/2?version=5&version_type=external' -d' { "title": "My third blog entry", "text": "I am starting to get the hang of this...", "date": "2014/01/02" }' 3.10 文档局部更新 文档更新的实质是通过检索,修改,然后重建整文档的索引方法来更新文档。 局部更新了文档的位置,内部却是像我们之前说的一样简单的使用update API处理相同的检索-修改-重建索引流程,我们也减少了其他进程可能导致冲突的修改。 (1)局部更新 curl -i -XPUT 'ibd14:9200/website_weichao/blog/1/_update?pretty' -d' { "doc": { "tags":["testing"], "views":0 } }' (2)局部更新后 curl -i -XGET ‘ibd14:9200/website_weichao/blog/1?pretty’ 返回信息: HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Content-Length: 195 { "_index" : "website_weichao", "_type" : "blog", "_id" : "1", "_version" : 5, "found" : true, "_source" : { "tags" : [ "testing" ], "views" : 0 } } (3)使用脚本局部更新 curl -i -XPOST 'ibd14:9200/website_weichao/blog/1/_update?pretty' -d' { "script": "ctx._source.views+=1" }' 在执行脚本进行局部更新之前,把elasticsearch.yml文件进行配置,输入以下配置信息: script.inline: on script.indexed: on script.file: on script.engine.groovy.inline.aggs: on script.engine.groovy.inline.update: on 保存后退出vi,然后重启ES。 (4)更新发生冲突后尝试重新更新 POST /website/pageviews/1/_update?retry_on_conflict=5 <1> { "script" : "ctx._source.views+=1", "upsert": { "views": 0 } } 3.11 检索多个文档 Elasticsearch检索多个文档依旧非常快。同时合并多个请求可以避免每个请求单独的网络开销。检索多个文档,使用multi-get或者mget API。 (1)使用mgetAPI的doc参数检索多个文档 curl -i -XGET 'ibd14:9200/_mget?pretty' -d' { "docs" : [ { "_index" : "website_weichao", "_type" : "blog", "_id" : 2 }, { "_index" : "website_weichao", "_type" : "blog", "_id" : 1, "_source": "doc" } ] }' 响应体也包含一个docs数组,每个文档还包含一个响应,它们按照请求定义的顺序排列。每个这样的响应与单独使用get request响应体相同 (2)检索同一index或同一type的多个文档 curl -i -XGET 'ibd14:9200/website_weichao/blog/_mget?pretty' -d' { "docs" : [ { "_id" : 2 }, { "_id" : 1, "_source": "doc" } ] }' (3)检索同一type下的不同id的文档 > curl -i -XGET ‘ibd14:9200/website_weichao/blog/_mget?pretty’ -d’ { “ids” : [“2”,”1”,”202”] }’ 尽管前面提到有一个文档没有被找到,但HTTP请求状态码还是200。事实上,就算所有文档都找不到,请求也还是返回200,原因是mget请求本身成功了。如果想知道每个文档是否都成功了,需要检查found标志。 3.12 更省时的批量操作 bulk API允许我们使用单一请求来实现多个文档的create、index、update或delete。这对索引类似于日志活动这样的数据流非常有用,它们可以以成百上千的数据为一个批次按序进行索引。 行为(action)必须是以下几种: | 行为 | 解释 —————————————————— | | create | 当文档不存在时创建之。详见《创建文档》 | index | 创建新文档或替换已有文档。见《索引文档》和《更新文档》 | update | 局部更新文档。见《局部更新》 | delete | 删除一个文档。见《删除文档》 在索引、创建、更新或删除时必须指定文档的_index、_type、_id这些元数据(metadata)。 *使用mget bulk批量操作不同文档 curl -i XPOST 'ibd14:9200/_bulk?pretty' -d' { "delete": { "_index": "website", "_type": "blog", "_id": "123" }} { "create": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "My first blog post" } { "index": { "_index": "website", "_type": "blog" }} { "title": "My second blog post" } { "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } { "doc" : {"title" : "My updated blog post"} } '
0.序言 项目主要使用oracle但是我不太喜欢其他编程语言,加上可能需要用python部署算法包,从oracle表中读出数据,处理完成后在放回oracle中去,所以在windows上就想到先用python试一下,自然搜到了cx_oracle(一个按照Python DB API的oracle的实现,如MySQL、PostgreSQL等,只需要安装相应的软件包即可,流程及操作接口都与cx_Oracle基本一致),下面就简单解释一下怎么用这个包进行增删改查。 1.windows 10 安装cx_Oracle注意事项 前提条件是机器本身安装好oracle client(我的机器已经安装好了),并且,oracle client版本cx_oracle版本,cx_oracle和python版本需要对应。 1.1 oracle client版本 如果windows系统没有安装oracle client 需要首先在: http://www.oracle.com/technetwork/topics/winx64soft-089540.html 下载对应的版本,我的系统是windows10 查看系统的中的oracle client版本,使用sql/plus命令: sqlplus -vSQL*Plus: 在安装好cx_oracle后 使用cx_Oracle.clientversion()查看为(11, 2, 0, 4, 0) 1.2 cx_oracle版本 cx_oracle和python版本需要对应, 我操作系统的版本是64位,所以上述软件采用的都是64位安装程序。python安装的版本是3.5.2 tnsnames.ora文件我并没有配置?不知道是不是之前系统配置过了,或者是直接移动dll可以不用配置此文件。(期待大牛指导,我还不知道这个是弄啥的) 1.3 使用python模块cx_oracle链接oracle C:\Users\123456>sqlplus -vSQl*Plus: SQL*Plus: Release 11.2.0.2.0 Production 上述oracle client 版本为11.2,所以需要在https://pypi.python.org/pypi/cx_Oracle/5.2.1 下载cx_Oracle-5.2.1-11g.win-amd64-py3.5.exe,安装运行, 注意,windows版本使用pip安装的话可能会出错,所以推荐使用上述方式安装 将: http://www.oracle.com/technetwork/database/features/instant-client/index-097480.html 下载的安装包中的: instantclient-basic-windows.x64-11.2.0.4.0 oci.dll此dll依赖下面两个dll(不然运行时候要出现:unable to acquire oracle environment handle) ocijdbc11.dll oraociei11.dll 复制到:C:\Python35\Lib\site-packages下 2. Redhat linux 6.5 下安装cx_Oracle 当然,如果oracle安装在linux 主机上,或者需要使用通用的服务器性能。所以还是需要姜python等插件部署在linux服务器上面,下面就分享一下红帽主机下使用python的插件cx_Oracle(注意大写)入库。 2.1 Linux下多个版本的python共存 Linux下默认系统自带python2.6的版本,这个版本被系统很多程序所依赖,所以不建议删除,如果使用最新的Python3那么我们知道编译安装源码包和系统默认包之间是没有任何影响的,所以可以安装python3和python2共存 2.1.1 使用版本管理工具pyenv 经常遇到这样的情况: • 系统自带的Python是2.6,自己需要Python 2.7中的某些特性; • 系统自带的Python是2.x,自己需要Python 3.x; 此时需要在系统中安装多个Python,但又不能影响系统自带的Python,即需要实现Python的多版本共存。pyenv就是这样一个Python版本管理器。可以下载安装。 2.2.2 安装时进行配置 如果你想用python3,你可以下载python源码,在配置的时候指定perfix,比如你可以安装到/usr/local/python3, (主要步骤) ./configure –prefix=/usr/local/python3 之后配置正确就可以使用/usr/local/python3/bin/python3启动python3. 一般安装步骤 RedHat下安装Python3步骤 1.下载解压。 $ tar zxvf Python-3.5.2.tgz 2.进入解压后的目录,执行安装配置 $ ./configure –prefix=/opt/python3 3.Build $ make 4.Install $ make install 5.建立软连接 安装后建立一个链接,这样我们可以用python3直接运行程序,和python2区别开来。 $ ln -s /opt/python3/bin/python3 /usr/bin/python3 之后直接在命令行输入python3就可以直接启动啦。 2.2 linux 下 cx_Oracle安装 安装Python的cx_Oracle,接下来说说如何安装它。 一、涉及软件包 1、cx_Oracle 下载地址:http://sourceforge.net/projects/cx-oracle/files/?source=navbar 我下载的是最新版的cx_Oracle-5.2.1.tar.gz 2、Oracle_client 使用cx_Oracle必须要安装Oracle_client端,或者你已经安装了Oracle数据库。 下载地址:http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html 以连接Oracle11(其实12也行,这和oracle client版本无关)为例需要下载以下rpm包: oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-jdbc-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-sqlplus-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-odbc-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-tools-11.2.0.4.0-1.x86_64.rpm 软件包都下载完后,我们开始来安装。 二、源码安装 1、Oracle_client端安装: # rpm -ivh oracle-instantclient11.2-basic-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-jdbc-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-sqlplus-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-devel-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-odbc-11.2.0.4.0-1.x86_64.rpm oracle-instantclient11.2-tools-11.2.0.4.0-1.x86_64.rpm # echo /usr/lib/oracle/11.2/client64/lib/ >> /etc/ld.so.conf # ldconfig 如果不进行ldconfig配置,在运行cx_Oracle时会报以下错误: libclntsh.so.11.1: cannot open shared object file: No such file or directory 2、设置相应用户的环境变量: 在这里需要说明下,你使用哪个帐户装cx_Oracle就需要配置哪个帐户的环境变量,以下已root帐户为例; 如果不配置环境变量、或环境变量配置不正确,在安装cx_Oracle时,会报各种错误,比如说: oci.h: No such file or directory #vi ~/.bashrc export TNS_ADMIN=”/usr/lib/oracle” export ORACLE_HOME=”/usr/lib/oracle/11.2/client64” export LD_LIBRARY_PATH=”${LD_LIBRARY_PATH}:${ORACLE_HOME}/lib” export PATH=”${PATH}:${ORACLE_HOME}” #source ~/.bashrc 3、源码安装 #tar -zxvf cx_Oracle-5.1.2.tar.gz #cd cx_Oracle-5.1.2 #python setup.py install 4、安装成功后相应检查 #python3 Python 3.5.2 (default, Aug 21 2013, 12:12:55) [GCC 4.4.4 20100726 (Red Hat 4.4.4-13)] on linux2 Type “help”, “copyright”, “credits” or “license” for more information. >>>import cx_Oracle >>> 到这没啥问题就ok了。 相关阅读: CentOS install Python 2.6.5 & cx_Oracle http://www.linuxidc.com/Linux/2011-04/34193.htm Python中cx_Oracle模块安装遇到的问题与解决方法 http://www.linuxidc.com/Linux/2011-04/34118.htm Python+cx_Oracle安装及一个简单示例(归档下热备数据文件) http://www.linuxidc.com/Linux/2010-10/29187.htm 《Python开发技术详解》.( 周伟,宗杰).[高清PDF扫描版+随书视频+代码] http://www.linuxidc.com/Linux/2013-11/92693.htm Python脚本获取Linux系统信息 http://www.linuxidc.com/Linux/2013-08/88531.htm Python 的详细介绍:请点这里 Python 的下载地址:请点这里 更多Oracle相关信息见Oracle 专题页面 http://www.linuxidc.com/topicnews.aspx?tid=12 3. cx_Oracle使用简介 使用流程: 1.导入模块cx_Oracle 2.连接数据库 3.获取cursor 4.使用cursor进行各种操作 5.关闭cursor 6.关闭连接 实例代码 import sys import cx_Oracle connection = cx_Oracle.Connection("user/pw@tns") cursor = connection.cursor() try: cursor.execute("select 1 / 0 from dual") except cx_Oracle.DatabaseError, exc: error, = exc.args print >> sys.stderr, "Oracle-Error-Code:", error.code 一次多行 大型的插入操作不需求多次的单独插入,这是因为 Python 通过 cx_Oracle.Cursor.executemany 方法完全支持一次插入多行。限制执行操作的数量极大地改善了程序性能,因此在编写存在大量插入操作的应用程序时应首先考虑这一功能。 我们首先为 Python 模块列表创建一个表,这次直接从 Python 开始。您将在以后删除该表。 create_table = “”” CREATE TABLE python_modules ( module_name VARCHAR2(50) NOT NULL, file_path VARCHAR2(300) NOT NULL ) “”” from sys import modules cursor.execute(create_table) M = [] for m_name, m_info in modules.items(): try: M.append((m_name, m_info.__file__)) except AttributeError: pass cursor.prepare(“INSERT INTO python_modules(module_name, file_path) VALUES (:1, :2)”) cursor.executemany(None, M) db.commit() r = cursor.execute(“SELECT COUNT(*) FROM python_modules”) print cursor.fetchone() cursor.execute(“DROP TABLE python_modules PURGE”) 仅向数据库发出一个执行操作,要求将 76 个模块名称全部插入。这对大型插入操作而言是一个巨大的性能提升。注意此处的两点小的不同:cursor.execute(create_tab) 不产生任何输出,这是因为它是一个 DDL 语句,而 (76,) 是一个有单个元素的字节组。不含逗号的 (76) 完全等同于整数 76。 (未完待续。。。。) 参考文档 http://cx-oracle.readthedocs.io/en/latest/ 精通oracle+python系列:(官方文档强烈推荐) http://www.oracle.com/technetwork/cn/articles/dsl/mastering-oracle-python-1391323-zhs.html 其他还未看: https://my.oschina.net/bxxfighting/blog/386578 http://www.cnblogs.com/hzhida/archive/2012/08/13/2636735.html http://blog.itpub.net/22664653/viewspace-711879/ http://www.cnblogs.com/heric/p/5804434.html http://www.cnblogs.com/linn/p/4229083.html 调用存储过程 http://blog.csdn.net/my2010sam/article/details/20724001
绪论 最近做课题,需要分析短文本的标签,在短时间内学习了自然语言处理,社会标签推荐等非常时髦的技术。我们的需求非常类似于从大量短文本中获取关键词(融合社会标签和时间属性)进行用户画像。这一切的基础就是特征词提取技术了,本文主要围绕关键词提取这个主题进行介绍(英文)。 不同版本python混用(官方用法) Python2 和python3 是一个神一般的存在,如何让他们共存呢,直到我用了pycharm我才知道为啥这么多人选择它,如下图所示配置两个目录直接可以混用了,叼炸天。 插播一个广告,想修改pycharm中python注释的颜色找了半天居然得这么搞: 当大家搜索如何在系统中混合使用python2和python3,国内网站经常会让大家把其中一个python.exe改个名字,这样区分开两个可执行文件的名字,但是这样做有一个重大的隐患,就是修改了名字的那个python对应的pip将无法使用。有时候还是需要用用命令行的,怎么办? 官方用法为: 在安装Python3(>=3.3)时,Python的安装包实际上在系统中安装了一个启动器py.exe,默认放置在文件夹C:\Windows\下面。这个启动器允许我们指定使用Python2还是Python3来运行代码(当然前提是你已经成功安装了Python2和Python3)。 如果你有一个Python文件叫 hello.py,那么你可以这样用Python2运行它 py -2 hello.py 类似的,如果你想用Python3运行它,就这样 py -3 hello.py 去掉参数 -2/-3 每次运行都要加入参数-2/-3还是比较麻烦,所以py.exe这个启动器允许你在代码中加入说明,表明这个文件应该是由python2解释运行,还是由python3解释运行。说明的方法是在代码文件的最开始加入一行 #! python2 或者 #! python3 分别表示该代码文件使用Python2或者Python3解释运行。这样,运行的时候你的命令就可以简化为 py hello.py 使用pip 当Python2和Python3同时存在于windows上时,它们对应的pip都叫pip.exe,所以不能够直接使用 pip install 命令来安装软件包。而是要使用启动器py.exe来指定pip的版本。命令如下: py -2 -m pip install XXXX -2 还是表示使用 Python2,-m pip 表示运行 pip 模块,也就是运行pip命令了。如果是为Python3安装软件,那么命令类似的变成 py -3 -m pip install XXXX #! python2 和 # coding: utf-8 哪个写在前面? 对于Python2用户还有另外一个困惑,Python2要在代码文件顶部增加一行说明,才能够在代码中使用中文。如果指明使用的Python版本也需要在文件顶部增加一行,那哪一行应该放在第一行呢? #! python2 需要放在第一行,编码说明可以放在第二行。所以文件开头应该类似于: #!python2 # coding: utf-8 有了这些技巧,Python2和Python3就可以愉快地在一起玩耍了~ Python标准:https://www.python.org/dev/peps/pep-0397/ 信息检索概述 信息检索是当前应用十分广泛的一种技术,论文检索、搜索引擎都属于信息检索的范畴。通常,人们把信息检索问题抽象为:在文档集合D上,对于由关键词w[1] … w[k]组成的查询串q,返回一个按查询q和文档d匹配度 relevance (q, d)排序的相关文档列表D。 对于这一基问题,先后出现了布尔模型、向量模型等各种经典的信息检索模型,它们从不同的角度提出了自己的一套解决方案。 布尔模型以集合的布尔运算为基础,查询效率高,但模型过于简单,无法有效地对不同文档进行排序,查询效果不佳。 向量模型把文档和查询串都视为词所构成的多维向量,而文档与查询的相关性即对应于向量间的夹角。不过,由于通常词的数量巨大,向量维度非常高,而大量的维度都是0,计算向量夹角的效果并不好。另外,庞大的计算量也使得向量模型几乎不具有在互联网搜索引擎这样海量数据集上实施的可行性。 TF-IDF原理概述 如何衡量一个特征词在文本中的代表性呢?以往就是通过词出现的频率,简单统计一下,从高到低,结果发现了一堆的地得,和英文的介词in of with等等,于是TF-IDF应运而生。 TF-IDF不但考虑了一个词出现的频率TF,也考虑了这个词在其他文档中不出现的逆频率IDF,很好的表现出了特征词的区分度,是信息检索领域中广泛使用的一种检索方法。 Tf-idf算法公式以及说明: 具体实现如下所示,公式分成两项,词频*逆词频,逆词频取log值。 注意分母中的+1,在很多文献中并没有出现,这个可能引发异常。 本人写了一份代码近期正在修改,后续传到github 上,再贴出来。文章末尾贴出了两份我认为比较好的代码,一份是面向对象的实现一份是分布式的。 tfidf源代码实现及相关博客资料: python scikit-learn计算tf-idf词语权重(scikit-learn包中提供了tfidf的矩阵实现,缺点是词数量过大可能溢出) http://www.tuicool.com/articles/U3uiiu http://www.cnblogs.com/chenbjin/p/3851165.html http://blog.csdn.net/liuxuejiang158blog/article/details/31360765?utm_source=tuicool&utm_medium=referral http://blog.csdn.net/lsldd/article/details/41520953 http://blog.csdn.net/zhb_bupt/article/details/40985831 http://www.tuicool.com/articles/feIji2 参考文献 http://www.ruanyifeng.com/blog/2013/03/tf-idf.html https://news.cnblogs.com/n/161240/ (tf-idf的概率解释) https://www.python.org/dev/peps/pep-0397/ (python不同版本共存官方文档) http://mt.sohu.com/20160416/n444499895.shtml (python版本混用中文翻译) github代码: https://github.com/mirsamantajbakhsh/TFIDF https://github.com/laertispappas/mapreduce_python (分布式版本)
上次写了CDH安装测试总结,由于那个博客篇幅略长, 但是主要集中在第二章,所以单独把CDH安装、卸载这块的内容拉出来在一篇记录一下。 一.搭建远程yum源 1.启动http服务: service httpd start 2.挂载镜像文件rhel6.6.iso到/var/www/html下的任意文件夹 mount -o loop /RHEL-6.6Server.iso /var/www/html/rhel66 3.cd 到/etc/yum.repos.d 目录下,先把已有的repo做备份,并建立以”.repo”结尾的文件,这里我建立的是rhel66.repo,内容如下: [rhel66] name=rhel66 baseurl=http://serverIP/rhel66 enabled=1 gpgcheck = 0 #gpgkey = http://yourIP/rhel65/RPM-GPG-KEY-redhat-release 4.配置完成后用命令: yum clean all 进行刷新 yum makecache 5.输入yum install httpd,打开浏览器,输入ip/rhel66 验证是否成功 二.准备CDH安装包 1.开启apache服务: service httpd start 2.将已有的cloudera安装包和文件 CDH-5.8.0-1.cdh5.8.0.p0.42-el6.parcel, CDH-5.8.0-1.cdh5.8.0.p0.42-el6.parcel.sha1, manifest.json移到/var/www/html目录下,权限均为777,用户为root。 三. 安装cloudera 将cloudera-manager-installer.bin文件修改成可执行权限,在/var/www/html里执行cloudera-manager-installer.bin文件,开始安装 登录网址: ip:7180,用户,密码为admin 填写主机名 集群存储库,使用parcel,选择更多选项,将其中https改为http,多余url删除,只保留第一个,{latest_support}删除 选择自定义存储库,将示例的网址复制粘贴,把https改为http 点击安装Oracle Java SE开发工具包(JDK),点击继续按钮 输入所有主机的root密码,确定后点击继续 自定义选择安装的服务 安装其他内容,不详细的请参考上一篇: CDH安装测试总结 四.卸载CDH CDH5.X,完全卸载步骤步骤如下: 1.关闭集群中的所有服务。 通过clouder manger 主页关闭集群。 2.卸载 [root@master ~]# /usr/share/cmf/uninstall-cloudera-manager.sh [root@slave1 ~]# service cloudera-scm-agent stop [root@slave1 ~]# service cloudera-scm-agent stop 以下都是所有要卸载的集群均要执行清除工作: [root@master ~]# umount /var/run/cloudera-scm-agent/process [root@slave1 ~]# umount /var/run/cloudera-scm-agent/process [root@slave2 ~]# umount /var/run/cloudera-scm-agent/process [root@master ~]# rm -rf /usr/share/cmf /var/lib/cloudera* /var/cache/yum/x86_64/6/cloudera* /var/log/cloudera* /var/run/cloudera* /etc/cloudera* 3.卸载安装包: [root@slave1 ~]# rpm -qa | grep cloudera [root@slave2 ~]# for f in `rpm -qa | grep cloudera ` ; do rpm -e ${f} ; done (如果有保存,在执行一遍) 4.清除安装文件 shell 脚本如下:这一行很长,请复制全 rm -rf /var/lib/hadoop-* /var/lib/impala /var/lib/solr /var/lib/zookeeper /var/lib/hue /var/lib/oozie /var/lib/pgsql /var/lib/sqoop2 /data/dfs/ /data/impala/ /data/yarn/ /dfs/ /impala/ /yarn/ /var/run/hadoop-*/ /var/run/hdfs-*/ /usr/bin/hadoop* /usr/bin/zookeeper* /usr/bin/hbase* /usr/bin/hive* /usr/bin/hdfs /usr/bin/mapred /usr/bin/yarn /usr/bin/sqoop* /usr/bin/oozie /etc/hadoop* /etc/zookeeper* /etc/hive* /etc/hue /etc/impala /etc/sqoop* /etc/oozie /etc/hbase* /etc/hcatalog //只删除hadoop系列的,不要删除其他软件的,否则其他软件的版本控制会被破坏 [root@master alternatives]# rm -rf ` find /var/lib/alternatives/* ! -name “mta” ! -name “print” ! -name “zlibrary-ui” -mtime -3` [root@master alternatives]# rm -rf /etc/alternatives/* 5.杀死相关进程 for u in hdfs mapred cloudera-scm hbase hue zookeeper oozie hive impala flume; do sudo kill $(ps -u $u -o pid=); done 6.删除parcel包分发文件和解压文件 rm -rf /opt/cloudera/parcel-cache /opt/cloudera/parcels 到此卸载完毕。
1.HiBench算法简介 Hibench 包含9个典型的hadoop负载(micro benchmarks,hdfs benchmarks,web search bench marks,machine learning benchmarks和data analytics benchmarks) 具体参考CDH集群安装&测试总结:第三节内容 micro benchmarks Sort:使用hadoop randomtextwriter生成数据,并对数据进行排序。 Wordcount:统计输入数据中每个单词的出现次数,输入数据使用hadoop randomtextwriter生成。 TeraSort:输入数据由hadoop teragen产生,通过key值进行排序。 hdfs benchmarks 增强行的dfsio:通过产生大量同时执行读写请求的任务测试hadoop机群的hdfs吞吐量 web search bench marks Nutch indexing:大规模收索引擎,这个是负载测试nutch(apache的一个开源搜索引擎)的搜索子系统,使用自动生成的web数据,web数据中的连接和单词符合zipfian分布(一个单词出现的次数与它在频率表的排名成反比) Pagerank:这个负载包含在一种在hadoop上的pagerank的算法实现,使用自动生成的web数据,web数据中的链接符合zipfian分布。(对于任意一个term其频度(frequency)的排名(rank)和frequency的乘积大致是一个常数) machine learning benchmarks Mahout bayesian classification(bayes):大规模机器学习,这个负载测试mahout(apache开源机器学习库)中的naive bayesian 训练器,输入的数据是自动生成的文档,文档中的单词符合zipfian分布。 Mahout k-means clustering(kmeans):测试mahout中的k-means聚类算法,输入的数据集由基于平均分布和高斯分布的genkmeansdataset产生。 data analytics benchmarks Hive query benchmarks(hivebench):包含执行的典型olap查询的hive查询(aggregation和join),使用自动生成的web数据,web数据的链接符合zipfian分布。 注:使用的生成数据程序在hadoop-mapreduce-examples-2.6.0 jar 包内,可以使用反编译工具查看。 2.HiBench中bayes算法流程 主要流程为conf下配置测试项,测试语言和DataSize,然后运行bin下run-all.sh完成一次测试,此流程为手动完成,可以编写脚本重复此步骤完成多次测试减少手动操作; e.g. #!/bin/bash # Time: 20160930,created by sunfei # Describe: automatic run the hibench # Functions : # search(): Find the style of application in the 99-user_defined_properties.conf,eg:tiny,small.. # exec_application_noSQL(): run the application for times,and no use hive # exec_application_SQL(): run the application for times,and use hive # save_result(): save the result of application # main_function(): the main function of running all the appliction # main(): the main function of running different kind application cpuLoad() { cpu=`grep -c 'model name' /proc/cpuinfo` load_15=`uptime | awk '{print $NF}'` average_load=`echo "scale=2;a=${load_15}/${cpu};if(length(a)==scale(a)) print 0;print a" | bc` date >> datetime-load.txt ${average_load} >> cpu-load.txt paste datetime-load.txt cpu-load.txt >> load-day.txt } search() { #config="/opt/HiBench/HiBench-master/conf/99-user_defined_properties.conf" config=/usr/HiBench-master/conf/99-user_defined_properties.conf sed -n '/hibench.scale.profile/p' ${config} >> hibench.txt var='' while read line do if [ ${line:0:13} = "hibench.scale" ];then echo -e "\033[32m match sucessfull! \033[0m" var=${line:22} fi done<"hibench.txt" if [ "$var" = "${1}" ];then echo -e "\033[31m The style of application can't same,do you want to continue? yes | no \033[0m" read -p "Input your chose :" chose if [ "${chose}" = "no" ];then exit 1 else echo -e "\033[32m The ${1} style of application will be run! \033[0m" fi fi if [ -f "hibench.txt" ];then rm -rf "hibench.txt" echo -e "\033[32m The hibench.txt has deleted! \033[0m" fi echo -e "\033[32m The application will run the "${1}" style \033[0m" sed -i "s/${var}/${1}/" ${config} } exec_application_noSQL() { var=0 for ((i=1;i<=${1};i++)) do let "var=$i%1" if [ "$var" -eq 0 ];then hadoop fs -rm -r hdfs://archive.cloudera.com:8020/user/hdfs/.Trash/* hadoop fs -rm -r hdfs://archive.cloudera.com:8020/HiBench/* fi echo -e "\033[32m **********************The current times is ********************:\033[0m" ${i} #/opt/HiBench/HiBench-master/bin/run-all.sh /usr/HiBench-master/bin/run-all.sh echo -e "\033[32m ********************** The current time is "${i}" ,and it has exec finished successfully! ********************:\033[0m" done echo -e "\033[32m *********The application has finished,please modify the configuration!***** \033[0m" } exec_application_SQL() { var=0 for ((i=1;i<=${1};i++)) do echo "drop table uservisits;drop table uservisits_aggre;drop table rankings;drop table rankings_uservisits_join;drop table uservisits_copy;exit;" | /usr/bin/hive let "var=$i%1" if [ "$var" -eq 0 ];then hadoop fs -rm -r hdfs://archive.cloudera.com:8020/user/hdfs/.Trash/* hadoop fs -rm -r hdfs://archive.cloudera.com:8020/HiBench/* fi echo -e "\033[32m **********************The current times is ********************:\033[0m" ${i} #/opt/HiBench/HiBench-master/bin/run-all.sh /usr/HiBench-master/bin/run-all.sh echo -e "\033[32m **********************The current time is "${i}" ,and it has exec finished successfully! ********************:\033[0m" done echo -e "\033[32m *********The application has finished,please modify the configuration!***** \033[0m" } save_result() { if [ -f result.txt ];then rm -rf result.txt echo -e "\033[32m The hibench.txt has deleted! \033[0m" fi #select the words in the report #filepath=/opt/HiBench/HiBench-master/report/hibench.report filepath=/usr/HiBench-master/report/hibench.report word="" var1=`date +"%m/%d/%Y-%k:%M:%S"` var2=${1} var5=".txt" var4=${var2}${var5} case ${1} in "aggregation") word="JavaSparkAggregation" ;; "join") word="JavaSparkJoin" ;; "scan") word="JavaSparkScan" ;; "kmeans") word="JavaSparkKmeans" ;; "pagerank") word="JavaSparkPagerank" ;; "sleep") word="JavaSparkSleep" ;; "sort") word="JavaSparkSort" ;; "wordcount") word="JavaSparkWordcount" ;; "bayes") word="JavaSparkBayes" ;; "terasort") word="JavaSparkTerasort" ;; *) echo -e "\033[32m The name of application is wrong,please change it! \033[0m" ;; esac while read line do echo $line | sed -n "/${word}/p" >> ${var4} done <$filepath echo -e "\033[32m The job has finished! \033[0m" } main_function() { #Input the name of application need to exec for appName in aggregation join scan pagerank sleep sort wordcount bayes terasort kmeans do #appConfig=/opt/HiBench/HiBench-master/conf/benchmarks.lst appConfig=/usr/HiBench-master/conf/benchmarks.lst echo "The name of application is :"${appName} echo ${appName} > ${appConfig} for style in tiny small large huge gigantic do search ${style} if [ "aggregation" = ${appName} ] || [ "join" = ${appName} ] || [ "scan" = ${appName} ];then exec_application_SQL ${1} else exec_application_noSQL ${1} fi done save_result ${appName} done } main() { # run the application read -p "Input the times of exec: " times if [ "${times}" -eq 0 -o "${times}" -gt 60 ];then echo -e "\033[31m The times of application can't be empty or gt 60 ! Do you want to continue ? yes | no\033[0m" read -p "Input your chose :" chose if [ "${chose}" = "no" ];then exit 1 else echo -e "\033[32m The application will be run ${times} times ! \033[0m" fi fi echo -e "\033[33m Select the style of application : \033[0m \033[31m All | Signal \033[0m" read -p "Input your chose :" style if [ "${style}" = "" ];then echo -e "\033[31m The style of application can't be empty \033[0m" exit 1 elif [ "${style}" != "All" -a "${style}" != "Signal" ];then echo -e "\033[31m The style of application is wrong,please correct! \033[0m" exit 1 else echo -e "\033[32m The style of application is ok ! \033[0m" fi if [ "All" = "${style}" ];then main_function ${times} else echo -e "\033[033m Input the name of apliaction,eg:\033[0m \033[31m aggregation | join | scan | kmeans | pagerank | sleep | sort | wordcount | bayes | terasort\033[0m" read -p "Input you chose :" application if [ "${application}" = "" ];then echo -e "\033[31m The name of application can't be empty! \033[0m" exit 1 fi echo "********************The ${application} will be exec**********************" appConfig=/usr/HiBench-master/conf/benchmarks.lst #appConfig=/opt/HiBench/HiBench-master/conf/benchmarks.lst read -p "Do you want exec all the style of application,eg:tiny,small,large,huge,gigantic? yes | no " chose if [ "${chose}" = "" ];then echo -e "\033[31m The style of application can't be empty! \033[0m" exit 1 elif [ "yes" != ${chose} ] && [ "no" != ${chose} ];then echo -e "\033[31m The style of application is wrong,please correct! \033[0m" exit 1 else echo -e "\033[32m The style of application is ok ! \033[0m" fi read -p "Input the sytle of application,eg:( tiny small large huge gigantic )!" appStyle echo "***************************The ${appStyle} style will be exec***************************" for appName in ${application} do echo ${appName} > ${appConfig} if [ "yes" = "${chose}" ];then for var in tiny small large huge gigantic do echo "******************The ${appName} will be exec!************************************" search ${var} if [ "aggregation" = ${appName} ] || [ "join" = ${appName} ] || [ "scan" = ${appName} ];then exec_application_SQL ${times} else exec_application_noSQL ${times} fi done else # read -p "Input the sytle of application,eg:( tiny small large huge gigantic )!" appStyle echo "**************************The ${appName} will be exec!************************" if [ "${appStyle}" = "" ];then echo -e "\033[31m The style of application can't be empty! \033[0m" exit 1 fi for var in ${appStyle} do search ${var} if [ "aggregation" = ${appName} ] || [ "join" = ${appName} ] || [ "scan" = ${appName} ];then exec_application_SQL ${times} else exec_application_noSQL ${times} fi done fi save_result ${appName} done fi } # the main function of application main prepare.sh->run.sh为run-all.sh的子流程; enter_bench->…->leave_bench为prepare.sh和run.sh的子流程; enter_bench…..gen_report等为workload-functions.sh中的公共函数。 流程图如下: 2.1 数据生成代码分析,接口:HiBench.DataGen 对java代码我不太熟悉,接口中我看主要用了一个switch语句 DataGen类中DataOptions options = new DataOptions(args); 如果是bayes测试的话,就调用对应的数据生成类,进行数据生成。生成的数据接口部分代码: case BAYES: { BayesData data = new BayesData(options); data.generate(); break; } BayesData实现: package HiBench; import java.io.IOException; import java.net.URISyntaxException; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.FileInputFormat; import org.apache.hadoop.mapred.FileOutputFormat; import org.apache.hadoop.mapred.JobClient; import org.apache.hadoop.mapred.JobConf; import org.apache.hadoop.mapred.MapReduceBase; import org.apache.hadoop.mapred.Mapper; import org.apache.hadoop.mapred.OutputCollector; import org.apache.hadoop.mapred.Reporter; import org.apache.hadoop.mapred.SequenceFileOutputFormat; import org.apache.hadoop.mapred.lib.NLineInputFormat; public class BayesData { private static final Log log = LogFactory.getLog(BayesData.class.getName()); private DataOptions options; private Dummy dummy; private int cgroups; BayesData(DataOptions options) { this.options = options; parseArgs(options.getRemainArgs()); } private void parseArgs(String[] args) { for (int i=0; i<args.length; i++) { if ("-class".equals(args[i])) { cgroups = Integer.parseInt(args[++i]); } else { DataOptions.printUsage("Unknown bayes data arguments -- " + args[i] + "!!!"); System.exit(-1); } } } private static class CreateBayesPages extends MapReduceBase implements Mapper<LongWritable, Text, Text, Text> { private static final Log log = LogFactory.getLog(CreateBayesPages.class.getName()); private long pages, slotpages; private int groups; private HtmlCore generator; private Random rand; public void configure(JobConf job) { try { pages = job.getLong("pages", 0); slotpages = job.getLong("slotpages", 0); groups = job.getInt("groups", 0); generator = new HtmlCore(job); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public void map(LongWritable key, Text value, OutputCollector<Text, Text> output, Reporter reporter) throws IOException { int slotId = Integer.parseInt(value.toString().trim()); long[] range = HtmlCore.getPageRange(slotId, pages, slotpages); generator.fireRandom(slotId); rand = new Random(slotId * 1000 + 101); Text k = new Text(); for (long i=range[0]; i<range[1]; i++) { String classname = "/class" + rand.nextInt(groups); k.set(classname); value.set(generator.genBayesWords()); output.collect(k, value); reporter.incrCounter(HiBench.Counters.BYTES_DATA_GENERATED, k.getLength()+value.getLength()); if (0==(i % 10000)) { log.info("still running: " + (i - range[0]) + " of " + slotpages); } } } } private void setBayesOptions(JobConf job) throws URISyntaxException { job.setLong("pages", options.getNumPages()); job.setLong("slotpages", options.getNumSlotPages()); job.setInt("groups", cgroups); Utils.shareWordZipfCore(options, job); } private void createBayesData() throws IOException, URISyntaxException { log.info("creating bayes text data ... "); JobConf job = new JobConf(); Path fout = options.getResultPath(); Utils.checkHdfsPath(fout); String jobname = "Create bayes data"; job.setJobName(jobname); Utils.shareDict(options, job); setBayesOptions(job); FileInputFormat.setInputPaths(job, dummy.getPath()); job.setInputFormat(NLineInputFormat.class); job.setJarByClass(CreateBayesPages.class); job.setMapperClass(CreateBayesPages.class); job.setNumReduceTasks(0); FileOutputFormat.setOutputPath(job, fout); job.setOutputFormat(SequenceFileOutputFormat.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); log.info("Running Job: " +jobname); log.info("Pages file " + dummy.getPath() + " as input"); log.info("Rankings file " + fout + " as output"); JobClient.runJob(job); log.info("Finished Running Job: " + jobname); } private void init() throws IOException { Utils.checkHdfsPath(options.getResultPath(), true); Utils.checkHdfsPath(options.getWorkPath(), true); dummy = new Dummy(options.getWorkPath(), options.getNumMaps()); int words = RawData.putDictToHdfs(new Path(options.getWorkPath(), HtmlCore.getDictName()), options.getNumWords()); options.setNumWords(words); Utils.serialWordZipf(options); } public void generate() throws Exception { init(); createBayesData(); close(); } private void close() throws IOException { log.info("Closing bayes data generator..."); Utils.checkHdfsPath(options.getWorkPath()); } } prepare.sh运行时输出如下,可以看到刚开始主要是读取配置文件中的内容,随后调用hadoop和jar包跑了一个任务,这个就是bayes文本分类的生成数据,按照第一节以及介绍的和官网的说明,这个文本主要使用linux中的字典:”/usr/share/dict/words”并且符合zipfian分布。 [hdfs@sf11 prepare]$ ./prepare.sh patching args= Parsing conf: /opt/HiBench/HiBench-master/conf/00-default-properties.conf Parsing conf: /opt/HiBench/HiBench-master/conf/01-default-streamingbench.conf Parsing conf: /opt/HiBench/HiBench-master/conf/10-data-scale-profile.conf Parsing conf: /opt/HiBench/HiBench-master/conf/20-samza-common.conf Parsing conf: /opt/HiBench/HiBench-master/conf/30-samza-workloads.conf Parsing conf: /opt/HiBench/HiBench-master/conf/99-user_defined_properties.conf Parsing conf: /opt/HiBench/HiBench-master/workloads/bayes/conf/00-bayes-default.conf Parsing conf: /opt/HiBench/HiBench-master/workloads/bayes/conf/10-bayes-userdefine.conf probe sleep jar: /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/share/hadoop/mapreduce2/hadoop-mapreduce-client-jobclient-tests.jar start HadoopPrepareBayes bench /opt/HiBench/HiBench-master/bin/functions/workload-functions.sh: line 120: /dev/stderr: Permission denied rm: `hdfs://archive.cloudera.com:8020/HiBench/Bayes/Input’: No such file or directory Submit MapReduce Job: /opt/cloudera/parcels/CDH/lib/hadoop/bin/hadoop –config /etc/hadoop/conf jar /opt/HiBench/HiBench-master/src/autogen/target/autogen-5.0-SNAPSHOT-jar-with-dependencies.jar HiBench.DataGen -t bayes -b hdfs://archive.cloudera.com:8020/HiBench/Bayes -n Input -m 300 -r 1600 -p 500000 -class 100 -o sequence 16/10/21 16:34:02 WARN mapreduce.JobResourceUploader: Hadoop command-line option parsing not performed. Implement the Tool interface and execute your application with ToolRunner to remedy this. 16/10/21 16:34:32 INFO HiBench.BayesData: Closing bayes data generator… finish HadoopPrepareBayes bench 部分生成数据: 在看了将近两周的HiBench代码进行测试后,终于摸清上述的运行流程,intel 的这个测试框架确实比较简介,通过配置文件和shell以及一些大数据框架自带的例子(如Hibench中的workcount测试就是直接调用hadoop或者spark自带的程序)完成了整个庞大的测试工作,下面我们针对贝叶斯文本分类算法中HiBench使用的三种语言:python,scala,java分别进行分析: 2.3 python代码分析 部分python代码: # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # """ A naive bayes program using MLlib. This example requires NumPy (http://www.numpy.org/). """ import sys from pyspark import SparkContext from pyspark.mllib.util import MLUtils from pyspark.mllib.classification import NaiveBayes from pyspark.mllib.regression import LabeledPoint from pyspark.mllib.linalg import Vectors from pyspark.storagelevel import StorageLevel from operator import add from itertools import groupby # # Adopted from spark's doc: http://spark.apache.org/docs/latest/mllib-naive-bayes.html # def parseVector(line): return np.array([float(x) for x in line.split(' ')]) if __name__ == "__main__": if len(sys.argv) != 2: print >> sys.stderr, "Usage: bayes <file>" exit(-1) sc = SparkContext(appName="PythonNaiveBayes") filename = sys.argv[1] data = sc.sequenceFile(filename, "org.apache.hadoop.io.Text", "org.apache.hadoop.io.Text") wordCount = data \ .flatMap(lambda (key, doc):doc.split(" ")) \ .map(lambda x:(x, 1)) \ .reduceByKey(add) wordSum = wordCount.map(lambda x:x[1]).reduce(lambda x,y:x+y) wordDict = wordCount.zipWithIndex() \ .map(lambda ((key, count), index): (key, (index, count*1.0 / wordSum)) ) \ .collectAsMap() sharedWordDict = sc.broadcast(wordDict) # for each document, generate vector based on word freq def doc2vector(dockey, doc): # map to word index: freq # combine freq with same word docVector = [(key, sum((z[1] for z in values))) for key, values in groupby(sorted([sharedWordDict.value[x] for x in doc.split(" ")], key=lambda x:x[0]), key=lambda x:x[0])] (indices, values) = zip(*docVector) # unzip label = float(dockey[6:]) return label, indices, values vector = data.map( lambda (dockey, doc) : doc2vector(dockey, doc)) vector.persist(StorageLevel.MEMORY_ONLY) d = vector.map( lambda (label, indices, values) : indices[-1] if indices else 0)\ .reduce(lambda a,b:max(a,b)) + 1 # print "###### Load svm file", filename #examples = MLUtils.loadLibSVMFile(sc, filename, numFeatures = numFeatures) examples = vector.map( lambda (label, indices, values) : LabeledPoint(label, Vectors.sparse(d, indices, values))) examples.cache() # FIXME: need randomSplit! training = examples.sample(False, 0.8, 2) test = examples.sample(False, 0.2, 2) numTraining = training.count() numTest = test.count() print " numTraining = %d, numTest = %d." % (numTraining, numTest) model = NaiveBayes.train(training, 1.0) model_share = sc.broadcast(model) predictionAndLabel = test.map( lambda x: (x.label, model_share.value.predict(x.features))) # prediction = model.predict(test.map( lambda x: x.features )) # predictionAndLabel = prediction.zip(test.map( lambda x:x.label )) accuracy = predictionAndLabel.filter(lambda x: x[0] == x[1]).count() * 1.0 / numTest print "Test accuracy = %s." % accuracy 2.4 scala 代码分析 run-spark-job org.apache.spark.examples.mllib.SparseNaiveBayes ${INPUT_HDFS} 显然scala 的朴素贝叶斯就是调用spark mllib库中的代码了 2.5 java 代码分析 run-spark-job com.intel.sparkbench.bayes.JavaBayes ${INPUT_HDFS} java部分比较意外的HiBench没有采用原生的代码或者jar包,而是自己写了一个 代码如下,回头慢慢分析: /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intel.sparkbench.bayes; import org.apache.spark.SparkContext; import org.apache.spark.api.java.function.FlatMapFunction; import org.apache.spark.api.java.function.Function; import org.apache.spark.api.java.function.Function2; import org.apache.spark.api.java.function.PairFunction; import org.apache.spark.broadcast.Broadcast; import org.apache.spark.mllib.classification.NaiveBayesModel; import org.apache.spark.mllib.linalg.Vectors; import org.apache.spark.rdd.RDD; import org.apache.spark.storage.StorageLevel; import scala.*; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaPairRDD; import org.apache.spark.api.java.JavaRDD; import org.apache.spark.api.java.JavaSparkContext; import org.apache.spark.mllib.regression.LabeledPoint; import org.apache.spark.mllib.util.MLUtils; import org.apache.spark.mllib.classification.NaiveBayes; import org.apache.hadoop.io.Text; import java.lang.Boolean; import java.lang.Double; import java.lang.Long; import java.util.*; import java.util.regex.Pattern; /* * Adopted from spark's doc: http://spark.apache.org/docs/latest/mllib-naive-bayes.html */ public final class JavaBayes { private static final Pattern SPACE = Pattern.compile(" "); public static void main(String[] args) throws Exception { if (args.length < 1) { System.err.println("Usage: JavaBayes <file>"); System.exit(1); } Random rand = new Random(); SparkConf sparkConf = new SparkConf().setAppName("JavaBayes"); JavaSparkContext ctx = new JavaSparkContext(sparkConf); // int numFeatures = Integer.parseInt(args[1]); // Generate vectors according to input documents JavaPairRDD<String, String> data = ctx.sequenceFile(args[0], Text.class, Text.class) .mapToPair(new PairFunction<Tuple2<Text, Text>, String, String>() { @Override public Tuple2<String, String> call(Tuple2<Text, Text> e) { return new Tuple2<String, String>(e._1().toString(), e._2().toString()); } }); JavaPairRDD<String, Long> wordCount = data .flatMap(new FlatMapFunction<Tuple2<String, String>, String>() { @Override public Iterable<String> call(Tuple2<String, String> e) { return Arrays.asList(SPACE.split(e._2())); } }) .mapToPair(new PairFunction<String, String, Long>() { @Override public Tuple2<String, Long> call(String e) { return new Tuple2<String, Long>(e, 1L); } }) .reduceByKey(new Function2<Long, Long, Long>() { @Override public Long call(Long i1, Long i2) { return i1 + i2; } }); final Long wordSum = wordCount.map(new Function<Tuple2<String, Long>, Long>(){ @Override public Long call(Tuple2<String, Long> e) { return e._2(); } }) .reduce(new Function2<Long, Long, Long>() { @Override public Long call(Long v1, Long v2) throws Exception { return v1 + v2; } }); List<Tuple2<String, Tuple2<Long, Double>>> wordDictList = wordCount.zipWithIndex() .map(new Function<Tuple2<Tuple2<String, Long>, Long>, Tuple2<String, Tuple2<Long, Double>>>() { @Override public Tuple2<String, Tuple2<Long, Double>> call(Tuple2<Tuple2<String, Long>, Long> e) throws Exception { String key = e._1()._1(); Long count = e._1()._2(); Long index = e._2(); return new Tuple2<String, Tuple2<Long, Double>>(key, new Tuple2<Long, Double>(index, count.doubleValue() / wordSum)); } }).collect(); Map<String, Tuple2<Long, Double>> wordDict = new HashMap(); for (Tuple2<String, Tuple2<Long, Double>> item : wordDictList) { wordDict.put(item._1(), item._2()); } final Broadcast<Map<String, Tuple2<Long, Double>>> sharedWordDict = ctx.broadcast(wordDict); // for each document, generate vector based on word freq JavaRDD<Tuple3<Double, Long[], Double[]>> vector = data.map(new Function<Tuple2<String, String>, Tuple3<Double, Long[], Double[]>>() { @Override public Tuple3<Double, Long[], Double[]> call(Tuple2<String, String> v1) throws Exception { String dockey = v1._1(); String doc = v1._2(); String[] keys = SPACE.split(doc); Tuple2<Long, Double>[] datas = new Tuple2[keys.length]; for (int i = 0; i < keys.length; i++) { datas[i] = sharedWordDict.getValue().get(keys[i]); } Map<Long, Double> vector = new HashMap<Long, Double>(); for (int i = 0; i < datas.length; i++) { Long indic = datas[i]._1(); Double value = datas[i]._2(); if (vector.containsKey(indic)) { vector.put(indic, value + vector.get(indic)); } else { vector.put(indic, value); } } Long[] indices = new Long[vector.size()]; Double[] values = new Double[vector.size()]; SortedSet<Long> sortedKeys = new TreeSet<Long>(vector.keySet()); int c = 0; for (Long key : sortedKeys) { indices[c] = key; values[c] = vector.get(key); c+=1; } Double label = Double.parseDouble(dockey.substring(6)); return new Tuple3<Double, Long[], Double[]>(label, indices, values); } }); vector.persist(StorageLevel.MEMORY_ONLY()); final Long d = vector .map(new Function<Tuple3<Double,Long[],Double[]>, Long>() { @Override public Long call(Tuple3<Double, Long[], Double[]> v1) throws Exception { Long[] indices = v1._2(); if (indices.length > 0) { // System.out.println("v_length:"+indices.length+" v_val:" + indices[indices.length - 1]); return indices[indices.length - 1]; } else return Long.valueOf(0); } }) .reduce(new Function2<Long, Long, Long>() { @Override public Long call(Long v1, Long v2) throws Exception { // System.out.println("v1:"+v1+" v2:"+v2); return v1 > v2 ? v1 : v2; } }) + 1; RDD<LabeledPoint> examples = vector.map(new Function<Tuple3<Double,Long[],Double[]>, LabeledPoint>() { @Override public LabeledPoint call(Tuple3<Double, Long[], Double[]> v1) throws Exception { int intIndices [] = new int[v1._2().length]; double intValues [] = new double[v1._3().length]; for (int i=0; i< v1._2().length; i++){ intIndices[i] = v1._2()[i].intValue(); intValues[i] = v1._3()[i]; } return new LabeledPoint(v1._1(), Vectors.sparse(d.intValue(), intIndices, intValues)); } }).rdd(); //RDD<LabeledPoint> examples = MLUtils.loadLibSVMFile(ctx.sc(), args[0], false, numFeatures); RDD<LabeledPoint>[] split = examples.randomSplit(new double[]{0.8, 0.2}, rand.nextLong()); JavaRDD<LabeledPoint> training = split[0].toJavaRDD(); JavaRDD<LabeledPoint> test = split[1].toJavaRDD(); final NaiveBayesModel model = NaiveBayes.train(training.rdd(), 1.0); JavaRDD<Double> prediction = test.map(new Function<LabeledPoint, Double>() { @Override public Double call(LabeledPoint p) { return model.predict(p.features()); } }); JavaPairRDD < Double, Double > predictionAndLabel = prediction.zip(test.map(new Function<LabeledPoint, Double>() { @Override public Double call(LabeledPoint p) { return p.label(); } })); double accuracy = (double) predictionAndLabel.filter( new Function<Tuple2<Double, Double>, Boolean>() { @Override public Boolean call(Tuple2<Double, Double> pl) { return pl._1().equals(pl._2()); } }).count() / test.count(); System.out.println(String.format("Test accuracy = %f", accuracy)); ctx.stop(); } } 3.运行结果 Type Date Time Input_data_size Duration(s) Throughput(bytes/s) Throughput/node JavaSparkBayes 2016-10-09 16:41:09 113387030 48.857 2320793 2320793 ScalaSparkBayes 2016-10-09 16:42:00 113387030 45.164 2510562 2510562 PythonSparkBayes 2016-10-09 16:44:03 113387030 118.521 956683 956683 bayes算法数据规模参考: #Bayes hibench.bayes.tiny.pages 25000 hibench.bayes.tiny.classes 10 hibench.bayes.tiny.ngrams 1 hibench.bayes.small.pages 30000 hibench.bayes.small.classes 100 hibench.bayes.small.ngrams 2 hibench.bayes.large.pages 100000 hibench.bayes.large.classes 100 hibench.bayes.large.ngrams 2 hibench.bayes.huge.pages 500000 hibench.bayes.huge.classes 100 hibench.bayes.huge.ngrams 2 hibench.bayes.gigantic.pages 1000000 hibench.bayes.gigantic.classes 100 hibench.bayes.gigantic.ngrams 2 hibench.bayes.bigdata.pages 20000000 hibench.bayes.bigdata.classes 20000 hibench.bayes.bigdata.ngrams 2 参考文献 https://github.com/intel-hadoop/HiBench
作者:黄永刚 mermaid简介 当撰写文档的时候,对于流程图的生成大多使用Visio等繁重的工具,没有一种轻便的工具能够画图从而简化文档的编写,就像markdown那样。 mermaid解决这个痛点,这是一个类似markdown语法的脚本语言,通过JavaScript实现图表的生成。 先来看个例子: 1.流程图(flowchart) graph LR; A-->B; A-->C; B-->D; C-->D; 生成的图表如下所示: 2. 时序图(sequence diagram) sequenceDiagram participant Alice participant Bob Alice->John:Hello John, how are you? loop Healthcheck John->John:Fight against hypochondria end Note right of John:Rational thoughts <br/>prevail... John-->Alice:Great! John->Bob: How about you? Bob-->John: Jolly good! 生成的图表如下所示: 3.甘特图(gantt diagram) gantt dateFormat YYYY-MM-DD title Adding GANTT diagram functionality to mermaid section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d future task : des3, after des2, 5d future task2 : des4, after des3, 5d section Critical tasks Completed task in the critical line :crit, done, 2014-01-06,24h Implement parser and json :crit, done, after des1, 2d Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d Add to ,mermaid :1d 生成的表如下: 下游项目 Mermaid 是由Knut Sveidqbist发起旨在轻便化的文档撰写。所有开发者:开发者列表 Gitbook-plugin Light table Confluence plugin Using mermaid via docpad Using mermaid in Jekvll Using mermaid via Octopress Mardown editor Haroopad Plugin for atom Markdown Plus LightPaper 1.2+ Vim Plugin 以上的这些都有集成mermaid或者开发相关的插件。 Graph graph LR A --> B 这是申明一个由左到右,水平向右的图。\ 可能方向有: - TB - top bottom - BT - bottom top - RL - right left - LR - left right - TD - same as TB 节点与形状 默认节点 graph LR id1 注意:’id’显示在节点内部。 文本节点 graph LR id[This is the text in the box]; 圆角节点 graph LR id(This is the text in the box); 圆节点(The form of a circle) graph LR id((This is the text in the circle)); 非对称节点(asymetric shape) graph LR id>This is the text in the box] 菱形节点(rhombus) graph LR id{This is the text in the box} 连接线 节点间的连接线有多种形状,而且可以在连接线中加入标签: 箭头形连接 graph LR; A-->B; 开放行连接 graph LR A --- B 标签连接 graph LR A -- This is the label text --- B; 箭头标签连接 A–>|text|B\ 或者\ A– text –>B graph LR A-- text -->B 虚线(dotted link,点连线) -.-> graph LR A-.->B -.-. graph LR A-.-.B 标签虚线 -.text.-> graph LR A-.text.->B 粗实线 ==> graph LR A==>B === graph LR A===B 标签粗线 =\=text\==> graph LR A==text==>B =\=text\=== graph LR A==text===B 特殊的语法 使用引号可以抑制一些特殊的字符的使用,可以避免一些不必要的麻烦。 graph LR\ d1[“This is the (text) in the box”] graph LR d1["This is the (text) in the box"] html字符的转义字符 转义字符的使用语法: 流程图定义如下: graph LR\ A[“A double quote:#quot;”] –> B[“A dec char:#9829;”] 渲染后的图如下: graph LR A["A double quote:#quot;"]-->B["A dec char:#9829;"] 子图(Subgraphs) subgraph title\ graph definition\ end 示例: graph TB subgraph one a1 --> a2 en subgraph two b2 --> b2 end subgraph three c1 --> c2 end c1 --> a2 结果: 基础fontawesome支持 如果想加入来自frontawesome的图表字体,需要像frontawesome网站上那样引用的那样。\ 详情请点击:fontawdsome 引用的语法为:++fa:#icon class name#++ graph TD B["fa:fa-twitter for peace"] B-->C[fa:fa-ban forbidden] B-->D(fa:fa-spinner); B-->E(A fa:fa-camerra-retro perhaps?); 渲染图如下: graph TD B["fa:fa-twitter for peace"] B-->C[fa:fa-ban forbidden] B-->D(fa:fa-spinner); B-->E(A fa:fa-camera-retro perhaps?); 以上reference: 1.mermaid docs 第二部分—图表(graph) 定义连接线的样式 graph LR id1(Start)-->id2(Stop) style id1 fill:#f9f,stroke:#333,stroke-width:4px; style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray:5,5; 渲染结果: graph LR id1(Start)-->id2(Stop) style id1 fill:#f9f,stroke:#333,stroke-width:4px; style id2 fill:#ccf,stroke:#f66,stroke-width:2px,stroke-dasharray:5,5; 备注:这些样式参考CSS样式。 样式类 为了方便样式的使用,可以定义类来使用样式 类的定义示例: classDef className fill:#f9f,stroke:#333,stroke-width:4px; 对节点使用样式类: class nodeId className; 同时对多个节点使用相同的样式类: class nodeId1,nodeId2 className; 可以在CSS中提前定义样式类,应用在图表的定义中。 graph LR A-->B[AAABBB]; B-->D; class A cssClass; 默认样式类:\ 当没有指定样式的时候,默认采用。 classDef default fill:#f9f,stroke:#333,stroke-width:4px; 示例: graph LR classDef default fill:#f90,stroke:#555,stroke-width:4px; id1(Start)-->id2(Stop) 结果: graph LR classDef default fill:#f90,stroke:#555,stroke-width:4px; id1(Start)-->id2(Stop) 序列图(sequence diagram)1 序列图 示例: sequenceDiagram Alice->>John: Hello John, how are you ? John-->>Alice: Great! Alice--->>John: Huang,you are better . John-->>Alice: yeah, Just not bad. sequenceDiagram Alice->>John: Hello John, how are you ? John-->>Alice: Great! Alice->>John: Hung,you are better . John-->>Alice: yeah, Just not bad. 观察上面的图,如果想让John出现在前面,如何控制,mermaid通过设定参与者(participants)的顺序控制二者的顺序。上面的图可以做如下修改: sequenceDiagram\ participant John\ participant Alice\ Alice->>John:Hello John,how are you?\ John–>>Alice:Great! sequenceDiagram participant John participant Alice Alice-xJohn:Hello John,how are you? John-->>Alice:Great! 消息的语法: 实线或者虚线的使用: [Actor][Arrow][Actor]:Message text\ Arrow的六种样式: -> –> ->> –>> -x –x 示例: sequenceDiagram Alice->John: Hello John, how are you ? John-->Alice:Great! Alice->>John: dont borther me ! John-->>Alice:Great! Alice-xJohn: wait! John--xAlice: Ok! 便签 给序列图增加便签:\ 具体规则:\ [right of | left of | over][Actor]:Text\ 示例: sequenceDiagram participant John Note left of John: Text in note 结果: 跨越两个Actor的便签: sequenceDiagram Alice->John:Hello John, how are you? Note over Alice,John:A typical interaction sequenceDiagram Alice->>John:Hello John, how are you? Note over Alice,John:A typical interaction 循环Loops 在序列图中,也可以使用循环,具体规则如下: loop Loop text ... statements... end 示例: sequenceDiagram Alice->>John: Hello! loop Reply every minute John->>Alice:Great! end 渲染结果: 选择ALT 在序列图中选择的表达。规则如下: alt Describing text ...statements... else ...statements... end 或者使用opt(推荐在没有else的情况下使用) opt Describing text ...statements... end 示例: sequenceDiagram Alice->>Bob: Hello Bob, how are you? alt is sick Bob->>Alice:not so good :( else is well Bob->>Alice:Feeling fresh like a daisy:) end opt Extra response Bob->>Alice:Thanks for asking end 渲染结果如下: 甘特图(gantt)2 甘特图是一类条形图,由Karol Adamiechi在1896年提出, 而在1910年Henry Gantt也独立的提出了此种图形表示。通常用在对项目终端元素和总结元素的开始及完成时间进行的描述。 示例: gantt dateFormat YYYY-MM-DD section S1 T1: 2014-01-01, 9d section S2 T2: 2014-01-11, 9d section S3 T3: 2014-01-02, 9d gantt dateFormat YYYY-MM-DD section S1 T1: 2014-01-01, 9d section S2 T2: 2014-01-11, 9d section S3 T3: 2014-01-02, 9d 先来看一个大的例子: gantt dateFormat YYYY-MM-DD title Adding GANTT diagram functionality to mermaid section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d section Critical tasks Completed task in the critical line :crit, done, 2014-01-06,24h Implement parser and jison :crit, done, after des1, 2d Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d Add to mermaid :1d section Documentation Describe gantt syntax :active, a1, after des1, 3d Add gantt diagram to demo page :after a1 , 20h Add another diagram to demo page :doc1, after a1 , 48h section Last section Describe gantt syntax :after doc1, 3d Add gantt diagram to demo page : 20h Add another diagram to demo page : 48h 获得的图渲染后如下: header 1 header 2 title 标题 dateFormat 日期格式 section 模块 Completed 已经完成 Active 当前正在进行 Future 后续待处理 crit 关键阶段 日期缺失 默认从上一项完成后 关于日期的格式可以参考: - string-format - Time-Formatting Demo graph TB sq[Square shape] --> ci((Circle shape)) subgraph A subgraph di{Diamond with line break} -.-> ro(Rounded) di==>ro2(Rounded square shape) end e --> od3>Really long text with linebreak<br>in an Odd shape] cyr[Cyrillic]-->cyr2((Circle shape Начало)); classDef green fill:#9f6,stroke:#333,stroke-width:2px; classDef orange fill:#f96,stroke:#333,stroke-width:4px; class sq,e green class di orange reference mermaid docs 本文原创首发于公众号:老王和他的IT界朋友们 微信扫描关注微信号:(原创投稿有惊喜!!!) 序列图的样式的定制需要在可以渲染CSS的地方才可使用,具体可以查阅参考。 ↩ 甘特图的样式的定制需要在可以渲染CSS的地方才可使用,具体可以查阅参考。 ↩
这是最好的时光 这是最坏的时光 v0.1.1.1 1.2 学校的生活二三事之大学 话说上一回,扯了一下我青涩的少年往事,大家反响不一,有叫好的,有吐槽的,有字字码过的,也有一目十行的。我的心情也是随着读者的反响跌宕起伏,总体趋势是高开低走。原本以为洗个脚是碎碎的事,到头来才知道什么是心比天高,命比纸薄。哎,可惜了我抢了n年积攒的红包,原本以为能小投资大回报,到头来却是千分散尽。亏,血亏!好了言归正传,这回说说大学里的故事。 1.2.1 大一的日子 该从何说起呢,报到那天 or 军训?算了吧,这些记忆里,并没有什么故事可讲,不写也罢。只记得报到那天,同村的学姐带我办完手续之后,告诉我:大一你就好好玩吧,享受大学生活。新的环境,新的上课形式,以及大把可自由支配的时间,确实让人兴奋了一段时间,这段时光可能是大学里最无忧的一段时光了。回想这一年,玩是玩好了,学习自然也拉下了,记得第二学期考试那段时间,刚好是10年南非世界杯。一个长相酷似外星人的大眼睛男孩让人眼前一亮,再加上ccav解说补充说“他祖母去世,小伙子化悲痛为力量”,瞬间成为了“厄祖”的粉丝。尤其是英德大战那场,比赛的画面记忆犹新。那届世界杯,他的表现让我第一次领会到什么叫灵性!当时在足球比赛与高数复习的权衡中,我拜倒在了足球的“石榴裙”下。 后来考试的时候,看见自己没有复习的内容出现在试卷上,我是欲哭无泪,可又有什么办法呢,自己酿的苦果再苦也得吞下。最后在多路“神仙”的帮助下,勉强交了卷。考试时,解答题只能写一个“解”字的煎熬,我却再也不想体会。高数最终考了61分,谢天谢地,心里默默感谢老师手下留情。 就这样,大一的日子在好奇、适应、兴奋、任性中度过,最后剩下的只有一双踢坏的足球鞋和勉强没有挂科的成绩单,提醒着我大一的日子已经逝去。后来在大二开学的一次班会上,辅导员鼓励我们总结一下自己的大一生活。于是就有了下面这些文字: 我的大一 回想大一这一年,我是感慨颇多啊!现在回头看看,仔细思量一番,竟然没有一件令自己感到欣慰或骄傲的事,心中自觉脸上有些挂不住,很是汗颜!这时,另一个自己不满了,说道:”没你这样的吧,好歹你没有挂科吧!”我听后,笑了,笑得很是勉强。 如果非要给自己的大一生活打个分的话,我给自己打50分,不及格。因为我看到自己与别人差距已经很大了,这种差距有时候自己都不敢去想。就拿实际情况来说吧,和师院的女生相比自己显得相当惭愧。每天早上看到一大批女生勤奋早读的身影,我都会惭愧的低下头,心里也不由得佩服她们,佩服她们的坚持和韧性。在她们当中,我看到了说英语出口成章的,顺利通过四六级考试的,毕业季奋斗考上研究生的……我觉得我似乎还有希望,她们是榜样。于是我自勉道:向她们学习吧,榜样的力量是无穷的。顺便说一句:师院的女生都很可爱,尤其是但你看到她们勤奋学习的情景时,这种感觉在我心中尤其强烈。 于是乎,暗下决心:英语四六级必须要过,再给自己一个考研的机会吧,无论结果如何,放手一搏,尽管这是一个以胜败论英雄的时代,只求将来不会后悔。 决心是下了,目标是定了,可这个过程注定是艰难的。而完成这个过程所需要的毅力恰恰是我所欠缺的。记得老爸以前就说过我,办事缺乏耐心和恒心,也许就是从那时开始吧,在逆反心理的作用下,我做事也叫起了真,事事苛求完美。有时甚至体现在拖地板上,不希望地上有一个脚印,什么事都想做得合情合理,达到那种圆满的境界。但是现在呢,大一刚开始的时候我太放松了,老想着还有明天呢,一直到最后自己想认认真真从头开始都很难了,于是这让我很是纠结,渐渐地很多时候我对自己说:就这样了,已经不错了,一点一点给自己台阶下,在这种“自欺欺人”的嘴脸下度过了一年,颓废了,堕落了,什么事情都想着应付,能过就过,得过且过。 心里依稀还记得高中时校园里的一句标语“时间抓起来就是黄金,抓不起来就是流水”,俗话说得好:人活在世上要不断的总结反思,这样才能有所收获。希望我的这份总结能给自己有所警醒。 共勉!!! 不知道大家看了之后会有什么感受,有人说矫情,我觉得也是,原来大一我就开始矫情了。这段文字我在班会上读过,现在想想我也是蛮有勇气的。 。。。。割。。。。。 隔壁老王催得紧,本来都想割了,告诉老王下面没有了。可是老王说,现在公众号能“打赏“了,于是七拼八凑,强行来凑个数,能否给老王创收就看缘分吧,反正我是不会打赏的。 。。。。前方高能。。。。 。。。。看不懂,别看。。。。 。。。。哈哈。。。。 老王: 公众号的风格以后不能污了,毕竟都是原创文章,前期污是噱头,后面就要靠真正的实力来hold住人气了。 佚名:老王说的对!我们这实力肯定会让公众号涨粉(负数也是有可能的@_@)的,妥妥的。 老王:十一假期过得怎么样? 佚名:嗯,挺忙的。如右图所示。 老王:我去,看来这个月你要吃土了。 佚名:是啊,你要是看不下去了,打赏的钱分我点? 老王:不让我掉粉,再说。 佚名:……我这几天有点感触,给你讲讲吧。 老王:好啊。黄色的最好! 佚名:就是这几天参加了几场婚礼,受到了好多暴击,不知道什么时候我才能找到另一半。 老王:哎呀,缘分这东西嘛,很难讲的,说不定哪天哪个姑娘眼瞎,就看上你了。 佚名:真的吗?那我这辈子赚的钱就全给她看病,无论如何也要把她的眼睛给治好! 老王:嗯,好感动。那在找对象这件事上,你有没有反思总结过? 佚名:肯定有啊!我这么爱总结反思的人。 老王:那你说说,你怎么看你自己? 佚名:我吧,优点我自己没看出来,缺点总结了一堆。比如说,宅、懒、丑、贫、矫情、任性等等。连起来说就是:社交不成功,懒还盼巅峰。人丑却颜控,家贫还追星。单身总矫情,没钱还任性。 老王:… …. 本文原创首发于微信公众号:老王和他的IT界朋友们 原文链接: http://mp.weixin.qq.com/s?__biz=MzIyMDQwNzc5MQ==&mid=2247483936&idx=1&sn=d629920cc638643b4c876fe2a0f8d742&chksm=97cd30a0a0bab9b6e33c83febd83b57c28e3400cf62a649956702cd405e5f129869939b52123#rd
0.绪论 本文打算写个三部曲。想想还是算了,毕竟工作这么忙。 王小波说过,一个人只拥有此生此世是不够的,他还应该拥有诗意的世界。 现在的年轻人喜爱电影,而这个第七门艺术作为一种生活方式,渗透到了我们每个人的日常。 作为一个IT界人士,我们能够更加娴熟的订购座位,描述CG(computer generate)的原理,像骇客帝国的neo一样看透这一切不过是数字放映机上的二进制流。好在,我们都喜欢电影,即使是网上下载的,它其中依然有一个更大的世界。 1.优秀的电影,让我们的生命在不同纬度上得以延伸。 青年时期,每个人都对生活有一种戏剧化的要求,就好像我们爱看武侠小说,公交车开的摇摇晃晃的,我偏偏什么都不扶,看到一车人都东倒西歪的,他们那里知道这是一个中二武侠迷的戏剧代入,他们那里知道我是修习了少林《易经经》内功的武学奇才:“他强由他强,清风拂山岗,他横由他横,明月照大江”,中二的事情当然不止这样,我走过的所有自动门都是我默默用原力开启的,我会胡说? 小时候最喜欢的是周星驰,感觉他的电影都很好看,不愿意放过每一个细节,守着电视机从头看到尾,模仿他的台词动作,希望自己也一样搞笑幽默。但是其实我们很多人都知道,长大了再看星爷的电影《喜剧之王》等,会逐渐发现星爷其实是把很多喜剧当作正剧来拍的。用他自己的话来说就是:“我就是不明白,我其实即英俊又浪漫多情,但为什么观众看到我在电影里谈情说爱也会狂笑?” 一脸严肃的谈情说爱? 史蒂芬周的电影在欢乐之中总是蕴含着一定的生活智慧,和人生哲理。周星星的父亲是上海人,母亲是广东人,很典型的草根阶层长大。自然会对此阶层比较熟悉,周的电影会挑选一些很有代表性的普通人,这些人物的励志经历常常带给人光明和希望。周星驰曾说:很多时候,我会见到社会上的一些不公正不公平的事情,你有没有胆量站出来主持公道?老实说我没有这个勇气,所以我只能寄情于电影。 想要电影票的姑娘(张学友的出现让我再次相信爱情!) 做饭的真谛——只要用心,人人都可以成为食神。 就像古龙的小说《三少爷的剑》一样,成功人士在达到顶峰之后往往是寂寞的,也是他心理防线最虚弱的时候。食神中的星爷经历了胜与败的双重考验,我们喜欢星爷的电影,从某种程度上也是喜欢体验超越我们自身范围之外的另一种生活途径。 电影本身是一种想象的产物,而我们通过电影经历一次想象的想象,这种双重的想象将我们从狭窄的生活中暂时解放出来,这种奇异性,正是我们喜欢电影的理由所在。 2.不懂,你的黑色幽默 最近看电影,不停的快进,我总是希望可以看到一些可以颠覆好莱坞大片模式的新影像。比如最近谍影重重5,中情局女特工用屋子里面的手机把笔记本电脑关了,还搜出来伯恩的ip留在她电脑上的cmd窗口里,我本着职业精神瞥了一眼:192.168.1.1,难道就不能专业点,放个公网ip,谁又不是没做过木马。不知道这算不算是一种幽默,下面我们来回味一下电影中的黑色幽默。 记得王小波众多亦真亦谐,一本正经不知是否胡说八道的段子中,有一个说,有段时间全国人民都爱早起甩手,认为甩手运动是一种有助于身体健康的锻炼,但后来不知道谁传起来说甩手运动是一个可怕的阴谋,因为这意味着 “全国人民都甩手不干了!”全国人民遂恐慌地终止了这一运动的流行。 这一段子非常简练精准地体现了黑色幽默的特质:黑色幽默的核心是一种荒诞,而这种荒诞通常来源于人行为中吊诡的僵硬与机械感。Henri Bergson在用哲学框架理论化喜剧和幽默的时候,提出的一个核心观点是说:人变得像机器一样,是最引人发笑的。 当然小波同志还说了,我们的生活有这么多的障碍,真他妈的有意思,这种逻辑也是黑色幽默,苦中作乐,看天土色,看地蓝色,蹲在臭烘烘的茅厕里胃口大开,对苦难生活的解构本身就是一种黑色的风格。 冯小刚《甲方乙方》—-好梦一日游就是一种解构,同样也是一种幽默。 当然,著名的几部我就不一一解构了,黑色幽默它代表了人们积极的人生态度以及笑看风雨的豁达情怀另外还有富贵不能移威武不能屈的人生品质和先天下之忧而忧后天下之乐而乐沉舟侧畔千帆过病树前头万木春停车坐爱枫林晚不以物喜不以己悲的终极追求,既然生活是这样,不如,我们一起黑色一下吧。 《snatch》 《Lock, Stock and Two Smoking Barrels》 《Trainspotting》 下面是看截图猜电影活动: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 全部猜对有奖!请在下方留言写下你的答案 下面是老王看过电影的海报墙,现在电影fm网站好像把这个功能下线了,回头有空了写一个: 参考文献: 1.https://moment.douban.com/post/102053/ 2.《影像的行板》 3.https://moment.douban.com/post/131195/?douban_rec=1 p.s. 祝大家双节快乐。 本文原创首发于微信公众号:老王和他的IT界朋友们 原文链接 http://mp.weixin.qq.com/s?__biz=MzIyMDQwNzc5MQ==&mid=2247483899&idx=1&sn=9a984964e7e0cbe6a796e31446d94335#rd
这个年头忍一时不见得风平浪静,退一步未必海阔天空。与其忍让不动,不如我行我素,反正得失寸惜之,苦乐独我尝 ———-《新龙门客栈》 据说艺术史上最穷苦潦倒的形象非梵高莫属,他活着的时候画作鲜有买家,终日处于创作的疯癫状态。同样现今中国影坛令创作成为苦行,想看几部好片子得划拉半天搜索引擎(偷笑),有时候看的云里雾里也想考究考究,看了看书,翻了翻王家卫,有下面两段和大家分享。 1.《魂断蓝桥》的陕西血统! 《魂断蓝桥》中展现的时间是1939年9月3日,英国对德宣战,伦敦发布紧急状态令,英军上校罗伊(Roy)站在滑铁卢桥上,手持一枚吉祥符,陷入了对20年前往事的回忆。这一次横跨两次世界大战的回忆,除了表现爱情的刻骨铭心,还隐含着令人唏嘘感慨的历史感:一战无情的摧毁了罗伊的爱情,并夺走了他未婚妻玛拉(Myra)的生命,仅仅时隔20年,二战再次将他送上战场,这次他将失去什么? Waterloo Bridge滑铁卢桥,作为爱情悲剧的见证者,从一开始两人的美丽邂逅,再到结尾Myra的绝望自杀,它既是起点,又是终点,成了表达主题的一个电影眼。Waterloo Bridge也因本片而更加闻名。根据《历史的人质———个体命运在战争电影中的经典呈现》描述说Waterloo Bridge又名伦敦桥,是一座跨越泰晤士河的九孔石桥,始建于1817年,时值英国的威灵顿公爵在比利时的滑铁卢战役中大胜拿破仑两周年,该桥便由此得名。在19世纪40年代,该桥一度成为自杀胜地。20世纪40年代,该桥开始重建,当时二战正酣,建桥工作大部分由英国妇女完成,所以有时又称“女士桥”。由于受到德国法西斯的轰炸,新桥在1942年建成后,在1945年才正式通车。 1912年的桥: Waterloo Bridge不在滑铁卢,它也并非“蓝色”,中文片名称其为“蓝桥” ,还有一个小故事:国内引进该片时,最初直译为《滑铁卢桥》,后来又改译为《断桥残梦》,都不理想,于是编译组在全国征集片名。而一位女士为其取名为“魂断蓝桥”,终被认可。这个片名的灵感来自几乎已经被中国人淡忘了的爱情典故——据《庄子-盗跖》记载,“尾生与女子期于梁(桥)下,女子不来,水至不去,抱梁柱而死。”《史记-苏秦列传》也记载,公元前320年,苏秦向燕王讲过同样一个“尾生抱柱”的爱情故事。相传有一个叫尾生的人,与一个美丽的姑娘相约于桥下会面。但是姑娘没来,尾生为了不失约,水涨至桥面也没有离开,终抱柱而死于桥下。据《西安府志》记载,这座桥在陕西蓝田县的蓝峪水上,称为“蓝桥”。中国古代的文学艺术家以这个故事为题材创作了很多感人的作品。因此,“魂断蓝桥”是典型的意译。读完书中的如此一番考究,原来闻名世界的佳作还与陕西颇有一番渊源。 p.s. 电影历史上三大凄美不朽的爱情影片:《魂断蓝桥》《卡萨布兰卡》《乱世佳人》,居然有两部都是费雯丽主演的。 费雯丽: 2.原来电影可以拍成这样! “也许我的电影就只是一个单纯的梦。很显然,这些梦都是有所关联的,事实上他们只共有一个主题。比如说,你所讲到的桃花,它就是一个很潜在的主题,它包含的意思很单纯,并没有人们想像的那么复杂” ——-王家卫 之前,问了一个蛮文艺的学姐她喜欢看什么样的电影,她说王家卫的基本上都看了。我想我也这么文艺,一定不能错过,所以,按着年代《旺角卡门》《阿飞正传》一路看了下去。墨镜王的电影台词写的异常精彩,觉得《重庆森林》跟《堕落天使》这两部的台词最登峰造极,《重庆森林》整部电影的台词可能就像一个都市白领的自白,金城武把跑步变成了一件私密的事情,失恋以后的梁朝伟对着整间屋子里的东西喃喃自语,看似不经意间的一句独白却给人眼前一亮似曾相识的感觉,因为可能每个人都在某时某刻不自觉的切合了电影的情节。 后面那一部呢,有一个流传以久的段子:王家卫有一次让他的演员翻译: I love you,有的演员翻译成:我爱你。王说,怎么可以讲这样的话,应该是“我已经很久没有坐过摩托车了,也很久未试过这么接近一个人,虽然我知道这条路不是很远。我知道不久我就会下车。可是,这一分钟,我觉得好暖。” 便是出自《堕落天使》的结尾。 过年从图书馆抱了一堆书专业课,几十片论文,还有几本关于电影的闲书回家,最后除了闲书看完了,其他都没怎么看。《香港制造一梦十年》这本书里面说,王家卫的情绪很古怪,这种古怪最终不可避免的影响了他的电影。儿时的记忆、少年的梦想、青年时的失落情节,几乎都被演化成似是而非的影像作一番宣泄。就好像电影里周慕云写的故事,多少带了点自己身边人物的影子。王家卫是不是借他的独白说出了自己的内心事呢? 《阿飞正传》《花样年华》《2046》又被称为苏丽珍三部曲,因为电影里面都有一位重要的线索人物叫苏丽珍,他的扮演者是女神级的人物张曼玉。这几部电影似乎还有一个游离的主线: wrong time at the wrong place with the wrong persong . 《阿飞正传》里张国荣告诉张曼玉叫她陪着自己看了整整一分钟的秒钟然后说:一九六零年四月十六号下午三点之前的一分钟你和我在一起,因为你我会记住这一分钟从现在开始我们就是一分钟的朋友,这是事实,你改变不了,因为已经过去了。我想,这个类库,完全可以供广大单身程序员在某时某刻自己要表白的情况下,大胆的调用一下嘛!!! 金城武的擦身而过: 每天你都有机会和很多人擦身而过,有些人可能会变成你的朋友或者是知己,所以我从来没有放弃任何跟人磨擦的机会。有时候搞得自己头破血流,管他呢!开心就行了。 还有一处换成了,衣服都擦破了,也没有擦出火花,我想,世间的事大砥如此了。 张国荣的: 不如,我们从头来过! 梁朝伟的挖洞讲故事: 从前有些人,心里有了秘密,而且不想被人知道他们会跑到山上找一棵树,在树上挖一个洞,然后把秘密全说进去,再用泥把洞封上。 那秘密会留在树里,没有人知道。 一口气看完这些,原来电影可以拍成这样! 后记 我们观看一部影片,从某种程度上说,就是体验超越我们自身范围之外的另一种生活途径。电影本身是一种想象的产物,而我们通过电影经历一次想象的想象,这种双重的想象将我们从狭窄的生活中暂时解放出来 ——这种奇异性,正是我们喜欢电影的理由所在!——————《影像的行板》 THE END ALL RIGHTS RESERVED THE STORY ABOVED IS PURELY FICTITIOUS AND ANY SIMILARITY IS PURELY COINCIDENTAL!!! 公众号,新增了评论,原创声明功能,我就想测试一下,所以拿出了几年前写的东东。大家是喜欢电影的吧,不过工作很忙,谁有时间去写影评呢?另外现在的电影又都这么烂。看完就忘了讲的什么,这又让我想起一句台词:不知道从什么时候开始,在什么东西上面都有个日期,秋刀鱼,肉罐头,连保鲜纸都会过期,我怀疑,在这个世界上,还有什么东西是不会过期的。 周一了,我们又都要上班。 上班,又要打开电脑。 打开电脑又要输密码。 我电脑的密码是: 爱 你 一 万 年 ! ANYWN! 本文首发于我的公众号:老王和他的IT界朋友们 原文链接: http://mp.weixin.qq.com/s?__biz=MzIyMDQwNzc5MQ==&mid=2247483859&idx=1&sn=acd8ba72d6caf96e1126e09b51881af5#rd
0.绪论 之前完全没有接触过大数据相关的东西,都是书上啊,媒体上各种吹嘘啊,我对大数据,集群啊,分布式计算等等概念真是高山仰止,充满了仰望之情,觉得这些东西是这样的: 当我搭建的过程中,发现这些东西是这样的: 对于初学者来说,我认为缺点如下: 1.需要控制,配置的东西太多,并且配置对应并不是很清晰(以后优化集群是否会有很高含金量?) 2.整个集群,我觉的从硬件到软件整体来说还是稳定性有待提高,尤其CDH 集群这块一会这个主机失去联系,一会NameNode挂,一会monitor挂,整个使用过程就是在不断的挂,看日志,挑错。基本离自动化,智能化还有很大距离。 CDH集群测试主要包括以下几个方面的内容: 1.装机(pxe),搭建服务器集群基础环境 2.安装CDH集群,调试集群的健康状况,使集群可用 3.测试集群性能,优化集群,使用测试框架(如Intel的HiBench框架)测试集群性能 1.基础建设简称基建 上一篇文章,我们已经介绍了集群安装操作系统的大杀器: pxe无人值守安装linux机器笔记 在批量安装完毕系统之后,本节主要围绕搭建CDH集群的基础建设进行介绍,基础建设简称基建,主要是为了支撑CDH集群后序工作流畅进行的一系列Linux系统的设置工作,基础建设工作没有做好,后面安装使用集群过程中会出现很多莫名奇妙的错误。基建主要包括,免密登录,时间同步,格式化硬盘,挂载目录等一些设置,下面为大家分别介绍: 1.1 建立主机分发脚本 新建一个host文件里面逐行设置为主机ip eg. 192.168.1.1 192.168.1.2 192.168.1.3 新建一个自定义脚本文件: #!/bin/sh host= `cat host` for i in $host do echo $i #将需要分发的命令复制在此处 Done 1.2 免密码登陆 配置免密码登录 1. 执行ssh-keygen命令,点击两次“回车”,生成/root/.ssh/id_rsa.pub文件;(使用脚本分发下面两条命令) 2. cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys 3. scp -r /root/.ssh $hostname:/root/ 1.3 配置主机基础环境 修改默认语言为英文 vi /etc/sysconfig/i18n LANG=”en_US.UTF-8” 修改host文件 scp /etc/hosts root@$i:/etc 关闭防火墙以及SELinux ssh $i ‘service iptables stop’ ssh $i ‘chkconfig iptables off’ ssh $i ‘service ip6tables stop’ ssh $i ‘chkconfig ip6tables off’ ssh $i ‘setenforce 0’ ssh $i ‘echo ‘service iptables stop’ >> /etc/rc.local’ ssh $i ‘echo ‘service ip6tables stop’ >> /etc/rc.local’ ssh $i ‘sed -i ‘s/SELINUX=enforcing/SELINUX=disabled/g’ /etc/selinux/config’ 同步时间 启动ntp服务,每5分钟向服务器同步一次(还需修改时间服务器上的部分配置,具体请百度) ssh $i ‘cat >>/var/spool/cron/root << EOF */5 * * * * /usr/sbin/ntpdate serverIP> /dev/null 2>&1 EOF’ ssh $i ‘echo ‘SYNC_HWCLOCK=yes’ >> /etc/sysconfig/ntpd’ ssh $i ‘hwclock -w’ 修改用户句柄限制 ssh $i ‘cat >> /etc/security/limits.conf << EOF hadoop soft nofile 65000 hadoop hard nofile 65000 hadoop soft nproc 401408 hadoop hard nproc 401408 * soft nofile 65000 * hard nofile 65000 * soft nproc 401408 * hard nproc 401408 EOF’ 建立挂载目录(根据自己的硬盘个数) ssh $i ‘mkdir /data01 /data02 /data03 /data04 /data05 /data06 /data07 /data08 /data09 ‘ 格式化硬盘(需批量执行,此处脚本有待升级) ssh $i ‘yes|parted /dev/sdb mklabel gpt parted /dev/sdb mkpart primary 0% 100% mkfs.ext4 -T largefile /dev/sdb1 修改/etc/fstab文件 ssh $i ‘cat >> /etc/fstab << EOF /dev/sdb1 /data01 ext4 defaults,noatime 0 0 挂载目录 ssh $i ‘mount /dev/sdb1 /data01 关闭swap交换分区 ssh $i ‘swapoff -a’ ssh $i ‘sysctl -w vm.swappiness=0’ ssh $i ‘echo ‘vm.swappiness=0’ >> /etc/sysctl.conf’ 关闭大内存页面 ssh $i ‘cat >> /sys/kernel/mm/transparent_hugepage/defrag << EOF never EOF ssh $i ‘cat >> /etc/rc.local << EOF echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag EOF 卸载自带的java环境,可以根据自己的java版本卸载 检查集群机器是否安装过openJDK,如果有安装过,请卸载,执行命令 : rpm -qa | grep jdk rpm -e xxx #xxx为上一步输出的rpm包名 ssh $i ‘rpm -e –nodeps java-1.6.0-openjdk-1.6.0.0-1.66.1.13.0.el6.x86_64 rpm -e –nodeps java-1.5.0-gcj-1.5.0.0-29.1.el6.x86_64 rpm -e –nodeps java-1.6.0-openjdk-devel-1.6.0.0-1.66.1.13.0.el6.x86_64 rpm -e –nodeps java-1.6.0-openjdk-javadoc-1.6.0.0-1.66.1.13.0.el6.x86_64’ 安装pscp和scala包 ssh $i ‘rpm -i /root/rpms/pssh-2.3.1-5.el6.noarch.rpm /root/rpms/scala-2.10.4.rpm’ 配置java1.8.0_66环境 scp -r /usr/java/jdk1.8.0_66 root@$i:/usr/java/ ssh $i ‘rm -rf /usr/java/lastest’ ssh $i ‘ln -s /usr/java/jdk1.8.0_66 /usr/java/lastest’ ssh $i ‘cat >> /etc/profile << EOF JAVA_HOME=/usr/java/jdk1.8.0_66 CLASS_PATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar export JAVA_HOME PATH=\$HOME/bin:\$JAVA_HOME/bin:\$PATH export PATH export CLASS_PATH EOF’ scp /etc/profile root@$i:/etc/ ssh $i ‘source /etc/profile’ done 时间同步 ssh $i ‘service ntpd stop ntpdate lcgm2 ssh $i ‘hwclock -w’ ssh $i ‘chkconfig ntpd on’ done 配置yum源,开启http服务 Yum源先mount在var/www/html/下面,在 /etc/yum.repos.d/rhel-source.repo文件修改内容 一些可能用到的命令: 建立多级目录: mkdir -p /x/xx 查看系统是否开启cloudera相关服务:chkconfig –list|grep cloudera 查看eth0网卡网络速度:ethtool eth0|grep -i speed 1.4 绑定网卡 决定集群性能很大因素是集群的网络性能呢,所以一般大数据集群都是多个网卡绑定的bond0模式,绑定shell如下 nmcli命令可能需要NetworkManager服务来支撑 ifconfig systemctl stop firewalld.service service iptables stop setenforce 0 nmcli con add type bond con-name bond0 ifname bond0 mode 0 nmcli con add type bond-slave con-name bondeno1 ifname eno1 master bond0 nmcli con add type bond-slave con-name bondeno2 ifname eno2 master bond0 nmcli con add type bond-slave con-name bondeno3 ifname eno3 master bond0 nmcli con add type bond-slave con-name bondeno4 ifname eno4 master bond0 cd /etc/sysconfig/network-scripts/ vim ifcfg-bond0 BOOTPROTO=static IPADDR=192.168.*.* PREFIX=24 GATEWAY=192.168.*.* service network restart nmcli con reload nmcli con up bondeno4 nmcli con up bondeno1 nmcli con up bondeno2 nmcli con up bondeno3 nmcli con up bond0 2.安装配置Cloudera-Manager(离线) 在线安装方式由于需要安装的安装包过大,时间可能非常长,建议大家下载安装包进行离线安装。主要安装Cloudera Manager Server 和Agent。 2.1 离线仓库安装准备 在cloudrea下载离线仓库,下载地址 下载cm5: https://archive.cloudera.com/cm5/repo-as-tarball/5.8.0/cm5.8.0-centos6.tar.gz 下载cdh5: https://archive.cloudera.com/cdh5/parcels/5.8.0/ 列表: CDH-5.8.0-1.cdh5.8.0.p0.42-el6.parcel CDH-5.8.0-1.cdh5.8.0.p0.42-el6.parcel.sha1 manifest.json 下载验证:https://archive.cloudera.com/cm5/redhat/6/x86_64/cm/5.8.0/repodata/ 下载安装脚本: http://archive.cloudera.com/cm5/installer/latest/cloudera-manager-installer.bin 2.2 主节点解压安装 cloudera manager的目录默认位置在/opt下,解压:tar xzvf cloudera-manager*.tar.gz将解压后的cm-5.*和cloudera目录放到/opt目录下(类似在windows把软件安装在D:/software)。 为Cloudera Manager 5建立数据库,可以用Mysql,或者自带的postgresql ,本文采用自带的数据库进行测试。 配置离线仓库地址: 开启apache服务:service httpd start 将下载的cloudera仓库移到/var/www/html目录下,调整目录结构: cdh5目录结构: cm5目录结构: chmod u+x cloudera-manager-installer.bin,然后./*.bin该文件相关启动脚本,就可以进入安装界面进行安装啦。 service cloudera-scm-server start (这个启动有点慢,可以关注日志变动情况 ) service cloudera-scm-agent start 其中,日志所在路径是 /var/log/cloudera-scm-server/cloudera-scm-server.log 启动server后,使用: /sbin/iptables -I INPUT -p tcp –dport 7180 -j ACCEPT ( 打开7180端口 ) 2.3 配置集群 1.根据CM引导界面,用户名admin ,密码admin。选择Cloudera Express 免费版。点击下一步到为CDH集群安装指定主机。 2.输入需要安装集群的机器IP地址,包括Cloudera Manager Server 机器。 3.选择集群的安装方式,选择使用数据包,CDH版本选择自定义,并输入yum源地址(基建中已经配置了的) (上图链接地址https可能会出错) 升级过程中遇到的问题 提示Error Cannot retrieve repository metadata [repomod.xml] for cloudera-cdh5.Please verify its path and try again (1) 检查机器的yum及cloudera的yum源配置是否正确 (2) 在Cloudera升级步骤(5)中填写的apache上cm5包地址是否正确,协议应该使用http而不是https,不然就会出现这种错误 (3) 若没有显示本地parcel包,可能是路径填写错误,可以根据配置的远程yum地址重新填写。 4.集群安装状态,可以看到每台集群的安装状态,如果正常则进入下一步。 5.选择要安装的CDH组件,我们选择安装HBase、HDFS、Hive、Spark、YARN、Zookeeper服务。点击继续(hibench测试主要需要这几个组件),角色服务分配参考如下: 6. CM会检测安装环境,可能会提示一处安装警告,比如: cloudera 建议将/proc/sys/vm/swappiness设置为0,当前设置为60, 则我们需要在集群每台机器上执行命令: echo 0> /proc/sys/vm/swappiness 王道就是有错就看日志调试。 7.选择集群机器的角色分配,对于默认的选择都可以选择在Master机器上,当然像Second NameNode可以选择在非NameNode机器上。注意Cloudera Management Service都选Master。 8.数据库配置。根据创建数据表选择所对应的服务即可。 9.集群设置。选择默认,集群开始安装,完成,访问集群serverIP:7180/cmf,ok。 2.4 集群基本优化 2.4.1 关闭Linux THG服务 检查集群中的各个主机的THG(对虚拟化等的内存资源分配是有好处的,但是对hadoop离线计算IO密集型操作是没有优势的,关闭THG可加快处理速度) 1.查看THG cat /sys/kernel/mm/redhat_transparent_hugepage/defrag 2.关闭THG echo never > /sys/kernel/mm/redhat_transparent_hugepage/defrag 2.4.2 设置linux内核参数:vm.swappiness vm.swappiness值的范围为0~100,作用是控制应用数据在物理内存和虚拟内存之间的交换,值越低,交换的越少。默认值为60。 查看集群各个主机的此参数值: cat /proc/sys/vm/swappiness 建议调整值为1: sysctl -w vm.swappiness=1 2.4.3 配置HDFS 点击HDFS -> 配置 -> 高级:hdfs-site.xml 的 HDFS 服务高级配置代码段(安全阀),加入配置使用公平队列 <property> <name>ipc.8020.callqueue.impl</name> <value>org.apache.hadoop.ipc.FairCallQueue</value> </property> 2.4.4 配置Yarn资源 关于Yarn内存分配与管理,主要涉及到了ResourceManage(集群资源调度协调)、ApplicationMatser(任务资源配置)、NodeManager(YARN节点代理配置)这几个概念,相关的优化也要紧紧围绕着这几方面来开展。 点击Yarn -> 资源管理: 设置ApplicationMaster Java最大堆栈:800M(AM内存默认1G) 容器内存yarn.nodemanager.resource.memory-mb 计算一个节点需要分配的容器内存方法: 主机内存-操作系统预留内存(12G) - Cloudera Manager Agent(1G) - HDFS DN(1G) – Yarn NM(1G) = 主机内存-15G 如果安装了hive.需减掉12G左右内存. 如果安装了hbase.还需减掉12-16G内存。 如果安装impala.还需减掉至少16G内存。 例:64G内存主机,如果安装了hbase,hive,则建议分配的容器内存大约为:25~30G 容器虚拟CPU内核yarn.nodemanager.resource.cpu-vcores 计算一个节点需要分配的容器虚拟内核方法: (主机cpu核数 – 系统预留1 – Cloudera1 – HDFS1 – Yarn NN 1) * 4 Hbase : -1 例:24核机器,为yarn分配可用cpu核数大约20核左右,按照 核数:处理任务数=1:4(比例可酌情调整),建议分配为80。由于本次集群CPU计算能力没达到官网建议的比例的要求,大约分配的比例为1:2,分配的核数为30核左右。 高级配置中:mapred-site.xml 的 MapReduce 客户端高级配置代码段(安全阀) <property> <name>mapreduce.tasktracker.outofband.heartbeat</name> <value>true</value> </property> 2.4.5 配置oozie 点击oozie –> 配置 -> 高级 : oozie-site.xml 的 Oozie Server 高级配置代码段(安全阀),增加配置: <property> <name>oozie.launcher.fs.hdfs.impl.disable.cache</name> <value>true</value> </property> <property> <name>oozie.action.max.output.data</name> <value>5000000</value> </property> 2.4.6 配置Oozie HA(用HAproxy负载均衡) Web界面操作略 error: Oozie could not be start REASON:java.lang.noSuchFieldError:EXTERNAL_PROPERTY ERROR: java.lang.noSuchFieldError:EXTERNAL_PROPERTY Org.cod… jaskson… 导致上面错误是oozie的jaskson版本低,替换成1.9.13版本即可 只替换jackson-mapper-asl和jackson-core-asl即可 替换步骤: 1. 先将192.168.188.13的两jar包拷贝到/opt/cloudera/parcels/CDH/lib/oozie下 2. find . -name “jackson*” | grep -e “^./lib” | xargs -i dirname {} | sort |uniq | xargs -i cp jackson-* {} 3. find . -name “jackson*” | grep -e “^./lib” | xargs -i dirname {} |sort | uniq | xargs -i mv {}/jackson-mapper-asl-1.8.8.jar . 4. find . -name “jackson*” | grep -e “^./lib” | xargs -i dirname {} |sort | uniq | xargs -i mv {}/jackson-core-asl-1.8.8.jar . 2.4.7 其他优化 1.DRF策略 CDH集群调优:内存、Vcores和DRF 默认配置下,CPU核数和内存是1:1G的比例来启动任务的。可通过调整参数yarn.nodemanager.resource.memory-mb进行调整 2.每个container的分配多少内存和cpu 当应用程序向resource manager 申请资源(即申请container )时, RM分配给一个container 多大的内存是按照一个最小单位进行分配的。 例如, 我们设置分配的最小单位为4GB, 则RM分配出来的container的内存一定是4G的倍数。 假设现在有一个程序向RM申请 5.1G的内存, 则RM会分配给它一个8GB的container去执行。 yarn.scheduler.minimum-allocation-mb=4096 在实际执行map reduce的job中, 一个container实际上是执行一个map 或者reduce task的jvm的进程。 那么这个jvm在执行中会不断的请求内存,假设它的物理内存或虚拟内存占用超出了container的内存设定, 则node manager 会主动的把这个进程kill 掉。 这里需要澄清一点, JVM使用的内存实际上分为虚拟内存和物理内存。 JVM中所有存在内存中的对象都是虚拟内存, 但在实际运行中只有一部分是实际加载在物理内存中的。 我们使用linux的top 可以看到 VM, RES, 前者是虚拟内存,后者可以看成近似是实际占用的物理内存。 因此在设置mapreduce的task的 jvm opts 参数时, 应将heap size 设置的比container允许的最大虚拟内存小。 这样jvm 不会因为申请过多的内存而被node manager 强制关闭。 当然设置最大heap size 如果在执行中被超过, jvm就会报 OutOfMemoryException。 同时还有一个参数,设定了RM可以分配的最大的container是多大。 假设应用程序向RM申请的资源超过了这个值, RM会直接拒绝这个请求。 yarn.scheduler.maximum-allocation-mb 3.HiBench集群性能测试 在大数据领域中,集群的性能很大程度上我认为主要是由整体的网络,数据吞吐量决定的,在使用HiBench测试时候发现,使用传统电口千兆网络的任务运行时间比光网任务运行时间要慢10s左右。HiBench的基准测试集是用来衡量一个大数据平台(基于Hadoop)性能的基准测试集,包含了文件系统的IO性能,系统的批处理吞吐,数据仓库用的OLAP分析算子,机器学习的处理能力,以及流处理系统的能力。 切换到光纤后,需要修改机器机器ip,这时候cdh居然没法启动了,百度之后,发现如果使用自带数据库postgresql,需要修改hosts表中记录的元数据信息:修改CDH集群ip 3.1 简介 hibench作为一个测试hadoop的基准测试框架,提供了对于hive:(aggregation,scan,join),排序(sort,TeraSort),大数据基本算法(wordcount,pagerank,nutchindex),机器学习算法(kmeans,bayes),集群调度(sleep),吞吐(dfsio),以及新加入5.0版本的流测试: we provide following streaming workloads for SparkStreaming, Storm . 一个完整的TeraSort测试需要按以下三步执行: 用TeraGen生成随机数据 对输入数据运行TeraSort 用TeraValidate验证排好序的输出数据 所有hibench测试基本都是这样的流程,生成数据,运行,输出结果。 3.2 配置并编译HiBench 从GitHub下载HiBench开源包,本篇会基于HiBench-5.0为例。https://github.com/intel-hadoop/HiBench。如果是基于CDH 5.5测试,建议使用HiBench-5.0,其中包含了Spark 1.5的编译包。 编译 添加JAVA_HOME 环境变量 注释掉${HIBENCH_HOME} /src/streambench/pom.xml中两行 <!-- <module>stormbench</module> --> <!-- <module>samzabench</module> --> 调用编译脚本:${HIBENCH_HOME}/bin/build-all.sh 配置 编辑 HiBench Configuration File: cd ${HIBENCH_HOME}/conf cp 99-user_defined_properties.conf.template 99-user_defined_properties.conf 编译配置文件,如下修改一些参数: hibench.hadoop.home /opt/cloudera/parcels/CDH/lib/hadoop hibench.hadoop.mapreduce.home /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce hibench.spark.home /opt/cloudera/parcels/CDH/lib/spark hibench.hdfs.master hdfs://cdh-node-11.cdhtest.com hibench.hadoop.configure.dir /etc/hadoop/conf hibench.masters.hostnames master # Resource Manager addresses hibench.slaves.hostnames hostname… # Node Manager addresses hibench.spark.master yarn-client hibench.spark.version spark1.6 spark.kryoserializer.buffer 2000m # 否则会出现大量spark.kryoserializer.buffer.mb被启用的警告 hibench.streamingbench.zookeeper.host zookeeper-hostnames hibench.streamingbench.brokerList all-hostnames hibench.streamingbench.kafka.home /opt/cloudera/parcels/KAFKA 修改benchmarks.lst文件,只运行有必要的测试集,例: #aggregation #join #kmeans #pagerank #scan #sleep sort wordcount #bayes terasort #nutchindexing dfsioe 修改language.lst文件,只运行有必要的语言 cd ${HIBENCH_HOME}/conf 在language.lst文件中,将以下两行删除 spark/java spark/python 修改load-config.py文件,确保Bench在运行时能找到唯一的包: $HiBench-Home/bin/functions/load-config.py 将hadoop-mapreduce-client-jobclient*-tests.jar改为hadoop-mapreduce-client-jobclient-tests.jar Bench在运行时有一些固化的目录和CDH不一致,需要建立目录引用 建立目录引用 mkdir -p /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/share/hadoop cd /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce/share/hadoop ln -sf /opt/cloudera/parcels/CDH/lib/hadoop-mapreduce mapreduce2 Bench会在HDFS根目录下生成文件,将HDFS的根目录权限修改为777: sudo -u hdfs hadoop fs -chmod 777 / (可选)如果在Kerberos启用的状况下,请增加以下步骤: # 设置环境变量 export HIBENCH_HOME=/root/Downloads/HiBench-master export JAVA_HOME=/usr/java/jdk1.7.0_67-cloudera export JAVA_LIBRARY_PATH=$JAVA_LIBRARY_PATH:/opt/cloudera/parcels/CDH/lib/hadoop/lib/native export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/cloudera/parcels/CDH/lib/hadoop/lib/native export SPARK_YARN_USER_ENV=”JAVA_LIBRARY_PATH=$JAVA_LIBRARY_PATH,LD_LIBRARY_PATH=$LD_LIBRARY_PATH” # 重新登录Kerberos kdestroy kinit -k -t 运行 命令行输入 ${HIBENCH_HOME}/bin/run-all.sh 3.3 HiBench基本优化配置 **优化基本原则** 在固定数据量的前提下,一般设置成让MapReduce作业在一轮Map、Reduce内结束,否则会增加MapReduce进程的调度开销。但如果输入的数据量过大,有可能会因为单个Map或者Reduce的内存消耗过大而到时严重的GC问题,这个需要在运行时对Map或者Reduce任务进程需要监测。 **YARN基本配置** – – NodeManager Container vCores数量就是系统的virtual core的数量Container Memory配置成节点上可用内存的75%到80%之间(如128GB的机器,可以设置96GB) ResourceManager Fair Scheduler调度器最小容器内存1GB 最小容器CPU 1个核最大容器内存=NodeManager Container内存的75%~80%最大容器CPU=NodeManager Container CPU的75%~80%增量内存512MB增量CPU 1个核 Gateway mapreduce.map/reduce.max.mb = 2GBmapreduce.map/reduce.java.opts = max.mb * 0.8 附录(CDH 相关目录结构功能简介) **1.相关目录** /var/log/cloudera-scm-installer : 安装日志目录。 /var/log/* : 相关日志文件(相关服务的及CM的)。 /usr/lib64/cmf/ : Agent程序代码。 /var/lib/cloudera-scm-server-db/data : 内嵌数据库目录。 /usr/bin/postgres : 内嵌数据库程序。 /etc/cloudera-scm-agent/ : agent的配置目录。 /etc/cloudera-scm-server/ : server的配置目录。 /etc/clouder-scm-server/db.properties 默认元数据库用户名密码配置 /opt/cloudera/parcels/ : Hadoop相关服务安装目录。 /opt/cloudera/parcel-repo/ : 下载的服务软件包数据,数据格式为parcels。 /opt/cloudera/parcel-cache/ : 下载的服务软件包缓存数据。 /etc/hadoop/* : 客户端配置文件目录。 **2.配置** Hadoop配置文件: 配置文件放置于/var/run/cloudera-scm-agent/process/目录下。如: /var/run/cloudera-scm-agent/process/193-hdfs-NAMENODE/core-site.xml 这些配置文件是通过Cloudera Manager启动相应服务(如HDFS)时生成的,内容从数据库中获得(即通过界面配置的参数)。 在CM界面上更改配置是不会立即反映到配置文件中,这些信息会存储于数据库中,等下次重启服务时才会生成配置文件。且每次启动时都会产生新的配置文件。 CM Server主要数据库为scm基中放置配置的数据表为configs。里面包含了服务的配置信息,每一次配置的更改会把当前页面的所有配置内容添加到数据库中,以此保存配置修改历史。 scm数据库被配置成只能从localhost访问,如果需要从外部连接此数据库,修改 vim /var/lib/cloudera-scm-server-db/data/pg_hba.conf 文件,之后重启数据库。运行数据库的用户为cloudera-scm。 查看配置内容 直接查询scm数据库的configs数据表的内容。 访问REST API: http://hostname:7180/api/v4/cm/deployment,返回JSON格式部署配置信息。 配置生成方式 CM为每个服务进程生成独立的配置目录(文件)。所有配置统一在服务端查询数据库生成(因为scm数据库只能在localhost下访问)生成配置文件,再由agent通过网络下载包含配置文件的zip包到本地解压到指定的目录。 配置修改 CM对于需要修改的配置预先定义,对于没有预先定义的配置,则通过在高级配置项中使用xml配置片段的方式进行配置。而对于/etc/hadoop/下的配置文件是客户端的配置,可以在CM通过部署客户端生成客户端配置。 数据库 Cloudera manager主要的数据库为scm,存储Cloudera manager运行所需要的信息:配置,主机,用户等。 CM结构 CM分为Server与Agent两部分及数据库(自带更改过的嵌入Postgresql)。它主要做三件事件: 管理监控集群主机。 统一管理配置。 管理维护Hadoop平台系统。 实现采用C/S结构,Agent为客户端负责执行服务端发来的命令,执行方式一般为使用python调用相应的服务shell脚本。Server端为Java REST服务,提供REST API,Web管理端通过REST API调用Server端功能,Web界面使用富客户端技术(Knockout)。 Server端主体使用Java实现。 Agent端主体使用Python, 服务的启动通过调用相应的shell脚本进行启动,如果启动失败会重复4次调用启动脚本。 Agent与Server保持心跳,使用Thrift RPC框架。 升级 在CM中可以通过界面向导升级相关服务。升级过程为三步: 1.下载服务软件包。 2.把所下载的服务软件包分发到集群中受管的机器上。 3.安装服务软件包,使用软链接的方式把服务程序目录链接到新安装的软件包目录上。 卸载 sudo /usr/share/cmf/uninstall-scm-express.sh, 然后删除/var/lib/cloudera-scm-server-db/目录,不然下次安装可能不成功。 开启postgresql远程访问 CM内嵌数据库被配置成只能从localhost访问,如果需要从外部查看数据,数据修改vim /var/lib/cloudera-scm-server-db/data/pg_hba.conf文件,之后重启数据库。运行数据库的用户为cloudera-scm。 参考文献 1.CDH官方文档 2.http://www.cloudera.com/documentation.html 3.CDH5.8官方文档 http://www.cloudera.com/documentation/enterprise/latest.html 4.http://blog.selfup.cn/1631.html#comment-403 5.https://github.com/intel-hadoop/HiBench
突然看到去年找的关于《理想工作环境》的相关资料,现在贴出来分享给大家。这个源于一个面试题, 忘了是哪家公司问我,你心中理想的工作环境是怎样的,我一时语塞,甚至从来没有考虑过这样的问题。理想的工作环境?!能有人要我就非常不错了,后来还是找了非常多的资料总结了总结。因为准备面试所以也找了不少双语的材料,各位凑合看吧。 Can you describe your ideal working environment to me? Alternative and related questions: Which of your previous working environments was the best? The meaning behind the question: As with other questions which ask you to describe your ‘ideal’ of something work-related, the interviewer is attempting to assess how closely your ideal fits with the reality of their organisation. Unless you see through this aspect of their question, you could easily reveal reasons for them to notch up black marks on your application. They’re testing your compatibility. Your answer: How much do you know about the working environment in the organisation to which you’re applying? Shallow as it may seem, this is what you need to be describing. With a bit of luck, you’ll already have been able to glean quite a bit of useful information from the interviewer during the course of your interview – information which you can now feed back to them. Example: My ideal working environment is one where there’s a good sense of team spirit. A strong work ethic is obviously important but the human side is also important. I enjoy working with people who have a decent sense of humour and who, while they might take their work very seriously, don’t necessarily take themselves overly seriously! I like people who are down to earth but who have a dynamic and progressive approach to their work. I really enjoy working as part of a highly committed and professional team. Word of warning: Avoid saying anything along the lines of the way they’ve described their organisation’s working environment as sounding like your idea of the ideal working environment. Don’t be seen to be overtly sucking up! Subtly does it. The Interview Question & Answer Book The Interview Question and Answer Book (推荐一本书) 图书—————————————————– 介绍 Take the fear out of your interview and never be stuck for the right answer to even the toughest questions with The Interview Question & Answer Book.The job market is fierce, competition has never been greater and it’s important that you can grab every opportunity for competitive advantage and stay one step ahead. Written by one of the world’s leading careers experts and bestselling author of The Interview Book, this definitive guide to questions and answers encourages every job-hunter to think on your feet and express your individuality while supplying ideal responses to interview questions so that you’re seen as the ideal candidate for the job. https://www.amazon.co.uk/gp/product/0273763717?ie=UTF8&tag=thcvce-21 理想工作环境的核心—-充分的自由 管理大师彼得·杜拉克指出,理想的工作环境要能够授权给员工,免除不必要的监督,员工知道公司对他们的期望,也很清楚公司如何评量他们的工作。 用计算机的时候,我是半躺在椅子上。思考的时候,我习惯把脚翘的比头还高,坐相就别说有多难看了。但公司要的是我写的程序,不是要我来当美姿示范的。所有的布置装潢、茶点饮料供应,以及充足的软硬件供应,都是为了能提供一个不同一般的工作环境。以促进软件人员的生产力来说,这些投资相对来说是很便宜的 Management guru Peter Drucker pointed out, the ideal working environment for employees to be able to authorize, eliminating unnecessary oversight, employees know their company’s expectations, it is very clear how companies evaluate their work. When using a computer, I was half lying on a chair. Thinking, I used Alice’s feet higher than the head, let alone sit with how ugly. But the company is that I want to write a program, not to me when Timmia demonstration. All furnished decor, refreshments supply and adequate supply of hardware and software, are generally in order to provide a different working environment. To promote staff productivity software, these investments are relatively cheap 工资比较高,工作环境宽敞充满了人文关怀以及绿色植物。高科技快节奏的IT 企业。 有一套完整的培训计划,这样我有很多机会能跟企业一同完善自我,一同成长。 当我能够在家附近找到工作,这当然是最好的,但是趁着年期,多在外地闯荡一下也是好的,能够积累很多经验,俗话说,读万卷书,行万里路,这也是宝贵的人生体验嘛 随着时间的推移,IT时代的到来告诉我们,每一个人都能成为自己的老板,如果你的创意足够,你就能开发一款流行的app应用,从运营商的分成模式中整取不菲的收益,现在的我们应该转变思维模式,从为了给别人打工,每时每刻的学习积累经验,最后在生活中成为自己的老板 Relatively high wages, working environment full of humanistic care and spacious greenery. Fast-paced high-tech IT enterprise. Has a comprehensive training program, so I have many opportunities to talk to companies with self-improvement, to grow together. When I was able to find a job close to home, which of course is the best, but taking advantage of young, mostly in the field battles what is good, can accumulate a lot of experience, saying,‘Read a thousand books, travel a thousand miles.’ this is a valuable life experience Over time, the arrival of the era of IT tells us that everyone can be your own boss, if you are creative enough, you can develop a popular app application to get into a lot of money from the operator mode income, now we should change mindset from others in order to work, the experience accumulated learning all the time, and finally become your own boss in your life. 理想的工作环境是多元的 理想的工作环境是由“理想的企业”、“理想的领导”和“理想的员工”这三个元素组合而成,这种组合是动态的,时刻都在自我平衡。 人活着就必须要工作,有很大一部分时间是在工作,所以往往很多时候,人的压力都是来自工作,回家通常会好点,在上班的时候,很多压力往往会让人很不快乐,甚至有的时候会将这些不快乐的心情带回家。在我看来,每个人潜意识里可能都在追求一个理想的工作环境,它是我们快乐生活一个基础。有了这个开始,我就问自己什么叫理想的工作环境?它怎么组成的?我认为理想的工作环境包括三个元素:一个是理想的企业,一个是理想的上司,一个是理想的员工。 首先,理想的企业是一个环境,有了一个理想的环境,我们作为一个员工进去,我们可能扮演一个基层员工,也可能扮演一个上司,很多时候往往是同时扮演两个角色,我们有上司,同时我们也有下属,所以我认为一个理想的工作环境,就是这三个元素动态的互动,最后出来的结果就是我们的工作环境。 这三个元素里面,其实包括了心态、沟通和知识。理想的企业,需要理想的企业文化,理想的沟通机制,还有一个套方法,就是怎么样让这个公司发展的很好,发展得好的公司才会创造出一个快乐的环境,如果有很多内耗的话,也会影响到员工的成长。那么同样的这三个元素也会用在上司和员工上。所以我们可以这样来想象,一个就是心态、沟通、知识,横着的就是企业、上司、员工,这样组成一个 3 x 3 的结构。 我以前讲“理想的工作环境”的时候,采取的是互动的方式,跟很多学员在互动,我问大家是否同意理想工作环境是具备了这些因素,反馈回来的结果有7点,有几点其实不重要,它只是提出一种参考,没有对错,不是一定是要7个5个6个3个,这个不重要,重要的是理想的工作环境对每个人来讲可能都不太一样,但是对很多人来讲就是有这7个方面考虑: 第一是“清晰的企业目标” 很多员工为什么在一个企业里面做得不是很顺利,因为他们对一个企业到底想做什么、它的目标都不太清晰,如果他不知道企业的目标是什么,他就很难与这个企业去配合,也很难找到他自己的价值,所以最后还是反映到他个人的身上,所以一个企业必须要有一个清晰的目标,这个目标也是给员工一个清晰的目标去追随,这个很关键。 第二是“能体现个人价值” 在这个环境里面,从员工的角度来讲,不管你是管理者还是一个员工,这个环境里应该是一个可以让你“体现自己价值的工作环境”,就是你上班不光是为薪水,而是你觉得有些理念,有些追求,是能够在这个环境里面实现的。 第三是有一个“开放的沟通渠道” 就是能够畅所欲言,能够表达自己的思路、想法,并且感觉到很安全,这样的环境是每个人都追求的,特别是我们现在的年轻人,都追求这个。 第四是“协作的同事关系” 不要看小这个,其实我们发觉很多,虽然我没有真正的数据,但是我相信,很多所谓工作的压力都是与同事关系不顺引发的,这些同事可能是平级,可能是跟上司,这种同事关系的不协调是带来工作压力的最大原因,所以很多时候,并不是工作原因给你带来很多压力,而是同事的关系不太顺给你带来很多压力。 第五是“发展与学习” ,这个工作环境能否提供一个“持续发展与学习的平台”,一个人要不断进步的话就要不断学习,不断去提高自己,所以工作环境要提供这个条件,让员工一方面工作、付出的同时,也可以在这个环境里面去学习,自己不断的去进步。 第六就是“工作环境与家庭的平衡” 就是在这个工作环境里面我还能得到一个与家庭平衡的一个生活状态,比如有些企业就是整天要你无缘无故加班,你基本上没有什么家庭生活,理想的工作环境是会考虑到员工是需要一个生活的平衡。 第七就是“持续改善的生活素质” 你如果在一个工作环境里面很多条件都很好,但是企业经营不好,经营得不好的话,你的收入每年降低,你的生活素质也难以改善。 所以基本上上来讲,理想的工作环境就是有这七方面的考虑,当然这七点也不是很完全,但是覆盖的面也已经是蛮大的了。 环境是一种人文关怀 google,facebook等的咱就不说了。我狭隘的认为环境好的公司一定也不会差到哪去: p.s. 国内大部分公司是这个吊样: 希望终有一天,我们都能有个好的办公环境! 参考文献 1.http://www.cyzone.cn/a/20130926/245740.html
最近做一些集群的测试的工作,做服务器测试最根本就是要安装系统,曾经我们用十几个光驱并行安装光驱的日子过去了,自从有了pxe一两天搭建好一个集群不是梦!当然做多了集群的搭建工作最多的感受就是,其实运维工作谁都能做,关键是效率高不高的问题,pxe装机这个东西就是能极高提升我们效率的工具,下面我来分享一下整个搭建过程。 1. 基建工作 1.关闭防火墙 a)service iptables stop b)service ip6tables stop c)chkconfig iptables off d)chkconfig ip6tables off 2.关闭SELinux a)临时关闭SELinux(重启失效): setenforce 0 b) /etc/selinux/cofig disabled 2. 配置dhcp服务 1.将/usr/share/doc/dhcp-4.1.1/dhcpd.conf.sample拷贝至/etc/dhcp/dhcpd.conf: cp /usr/share/doc/dhcp-4.1.1/dhcpd.conf.sample /etc/dhcp/dhcpd.conf 并修改dhcpd.conf的内容: dns服务可以不配置; 2.确保开机启动dhcpd服务: chkconfig dhcpd on 3.启动dhcpd服务: service dhcpd start 4.检查dhcpd服务是否已启动: ss -nul 如果发现67端口被监听,则表示成功; 3. 配置tftp服务 由于tftp是瞬时服务进程,不能进行自我管理,需要通过超级服务进程进行管理,所以安装tftp-server时,超级服务进程程序xinetd被依赖。 1.设置并确保xinetd开机启动: chkconfig xinetd on p.s. 如果没有tftp的话需要安装一下,执行yum –y search tftp查看系统是否安装tftp软件包,若没有安装,则执行yum –y install tftp tftp-service 2.启动xinetd服务: service xinetd start 查看是否开机启动 chkconfig –list xinetd 3.检测xinetd服务是否已启动: ss -nul 如果发现69端口被监听,则表示成功; 4.编辑/etc/xinetd.d/tftp文件,将”disable=yes”改为”disable=no”; 5.测试tftp服务: a)新建test.txt,放到/var/lib/tftpboot/目录下,并在服务器本机进行测试。 b)切换到根目录下,执行 tftp 192.168.1.205(服务器地址): tftp > get test.txt; tftp > quit; 如果根目录下出现test.txt,则tftp服务搭建成功,删除测试文件test.txt; 4. 配置nfs服务 1.创建nfs目录: mkdir /nfsroot 2.配置nfs服务:在/etc/exports末尾行中加入 /nfsroot *(rw,wdelay,crossmnt,insecure,root_squash,no_subtree_check,fsid=0) 并运行 exportfs –a #使配置生效; 3.启动rpcbind服务: chkconfig rpcbind on service rpcbind start 4.启动nfs服务: chkconfig nfs on service nfs start 5. 搭建本地yum源 1.挂在镜像文件rhel6.5.iso 到 /mnt: mount -o loop /opt/rhel6.5.iso /mnt 2.cd 到/etc/yum.repos.d 目录下建立以”.repo”结尾的文件,这里我建立的是rhel65.repo,内容如下: [rhel65] name=rhel65 baseurl=file:///mnt enabled=1 gpgcheck = 0 #gpgkey = http://yum.zb/rhel65/RPM-GPG-KEY-redhat-release 3.配置完成后用命令: yum clean all #进行刷新 4.常用命令: a)yum install package1 安装指定的安装包package1 b)yum groupinsall group1 安装程序组group1 c)yum update package1 更新指定程序包package1 d)yum check-update 检查可更新的程序 e)yum upgrade package1 升级指定程序包package1 f)yum groupupdate group1 升级程序组group1 g)yum info package1 显示安装包信息package1 h)yum list 显示所有已经安装和可以安装的程序包 i)yum list package1 显示指定程序包安装情况package1 j)yum remove package1 删除程序包package1 k)yum groupremove group1 删除程序组group1 6. 制作kickstart.cfg文件 1.请先搭建本地yum源; 2.执行 system-config-kickstart 弹出kickstart.cfg制作界面。 3.Basic Configuration(基础配置) 4.Installation Method 安装方式 此处选择nfs方式: NFS Serve:nfs服务器ip NFS Directory:/nfsroot 5.Boot Loader Options 默认 6.配置分区信息 要指定分区,使用sda,其中/boot大小为200M,/的大小为剩余空间 7.Network Configuration 将网卡eth0 设置为dhcp模式 8.Authentication默认 9.Firewall Configuration 关闭防火墙,关闭selinux 10.Display Configuration默认 11.Package Selection 如果没有包显示,请先搭建本地yum源,然后在选包; 12.Pre-Installation Script和Post-Installation Script均默认设置。 13.点击File–>Save–>修改文件名为centos-6.5-ks.cfg保存至/opt/ks.cfg(本人自定义目录)下; 7. 提供pxe工作环境 1.找到/usr/share/syslinux/pxelinux.0文件,并将其复制到/var/lib/tftpboot/目录下; 2.将系统镜像盘中pxe模式下专用的内核文件和initrd镜像文件复制到tftp服务器相应目录中: 3.将系统光盘镜像中的isolinux/目录下的boot.msg splash.jpg vesamenu.c32复制到/var/lib/tftpboot/目录下 4.将系统光盘镜像中的isolinux/目录下的isolinux.cfg文件拷贝至/var/lib/tftpboot/pxelinux.cfg/目录下,命名为default,用来引导客户端启动过程;并修改/var/lib/tftpboot/pxelinux/default文件,指定ks文件的加载位置; 5.将安装光盘里的images目录复制到/nfsroot目录下,并将RHEl 6.5的ISO文件复制到/nfsroot目录下 6.所需文件见文件夹。 ps:重启后的可以使用gdm命令切换桌面
2021年11月