环信

环信

3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

9
评论

【源码下载】一款使用环信实现的开源灵魂社交APP(含服务器) 猿匹配 开源

开发讨论beyond 发表了文章 • 29711 次浏览 • 2019-07-01 10:48 • 来自相关话题

#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。






  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

























  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖npm install
6.全局安装pm2npm install pm2 -g 
7.运行 vmshell.sh
 




扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
  查看全部
#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

1.png

2.png

3.png

4.png

5.png

6.png

  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
 
7.运行 vmshell.sh
 
环信冬冬_副本.jpg

扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
 
3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

1
回复

环信web端登录报错 failed: Data frame received after close 环信_WebIM 环信

开发讨论lizg 回复了问题 • 2 人关注 • 3282 次浏览 • 2019-03-11 18:00 • 来自相关话题

1
最佳

环信登录提示404 环信web 环信

开发讨论beyond 回复了问题 • 2 人关注 • 3031 次浏览 • 2019-03-08 10:25 • 来自相关话题

2
回复

为什么不能播放,官方也不修复一下呢?????? 环信 Android

开发讨论beyond 回复了问题 • 2 人关注 • 2854 次浏览 • 2019-02-27 13:49 • 来自相关话题

1
回复

php 如何 集成环信 laravel当中 感谢 环信 laravel PHP 集成

开发讨论beyond 回复了问题 • 2 人关注 • 3385 次浏览 • 2019-02-27 13:47 • 来自相关话题

0
评论

2018,环信是如何C位出道的!(分享你和环信的故事赢千元奖励) 有奖调查 环信即时通讯云 2018 环信

开发讨论beyond 发表了文章 • 1818 次浏览 • 2019-02-01 15:25 • 来自相关话题

六年,筚路蓝缕,环信走过了一段从无至有的征程;

六年,栉风沐雨,见证了中国SaaS从0到1到1++的幸运;

六年,砥砺前行,从IM云1.0到IM云4.0,从移动客服到全媒体客服再到智能客服;

六年,峥嵘岁月,又一个全新的起点等待环信人去超越,从“心”出发;

六载春秋,陪伴是最长情的告白!感恩有你!!!



















































分享你和环信的故事赢千元奖励

欢迎在评论区分享你和环信的故事,评论区点赞前3名各送200元京东卡一张,第4-10名各送100元京东卡一张。(春节后第一个工作日2月11日公布获奖名单)

评论地址:https://mp.weixin.qq.com/s/Tij4kpquSUSeB04lkcepXQ 查看全部
六年,筚路蓝缕,环信走过了一段从无至有的征程;

六年,栉风沐雨,见证了中国SaaS从0到1到1++的幸运;

六年,砥砺前行,从IM云1.0到IM云4.0,从移动客服到全媒体客服再到智能客服;

六年,峥嵘岁月,又一个全新的起点等待环信人去超越,从“心”出发;

六载春秋,陪伴是最长情的告白!感恩有你!!!


新年广告_01.jpg


新年广告_02.jpg


新年广告_03.jpg


新年广告_05.jpg


新年广告_06.jpg


新年广告_07.jpg


新年广告_08.jpg


新年广告_09.jpg


新年广告_10.jpg


新年广告_11.jpg

分享你和环信的故事赢千元奖励

欢迎在评论区分享你和环信的故事,评论区点赞前3名各送200元京东卡一张,第4-10名各送100元京东卡一张。(春节后第一个工作日2月11日公布获奖名单)

评论地址:https://mp.weixin.qq.com/s/Tij4kpquSUSeB04lkcepXQ
0
回复

Android studio导入easeui运行编译报错 Android 环信

回复

开发讨论₁₉₉₃₀₄₀₇の陳小垚 发起了问题 • 1 人关注 • 3392 次浏览 • 2018-09-27 22:07 • 来自相关话题

1
回复

导出聊天记录报错:Array ( [error] => apiname is invalid ) 环信集成 环信

开发讨论kaibisikai 回复了问题 • 2 人关注 • 4183 次浏览 • 2018-09-25 10:37 • 来自相关话题

2
回复

群成员主动退出,但是群里面获取所有成员还是能获取到已经退出的成员 环信

开发讨论孩子气 Rc 回复了问题 • 3 人关注 • 4300 次浏览 • 2018-08-09 15:09 • 来自相关话题

4
回复

牛逼啊,这么大个环信连做到头像和昵称保存的方法都没有 环信

开发讨论陈日明 回复了问题 • 2 人关注 • 3392 次浏览 • 2018-08-08 10:49 • 来自相关话题

1
回复

OC_SDK集成参考视频 建议 环信 iOS

开发讨论beyond 回复了问题 • 2 人关注 • 3032 次浏览 • 2018-07-26 10:48 • 来自相关话题

2
回复

iOS pod集成后点击进入单聊界面崩溃 并提示libc++abi.dylib: terminating with uncaught exception of type NSException 环信 iOS

开发讨论KevinGong 回复了问题 • 2 人关注 • 4277 次浏览 • 2018-06-27 18:51 • 来自相关话题

1
回复

都添加哪些库,视频里看不清啊? 环信 iOS

开发讨论KevinGong 回复了问题 • 2 人关注 • 2702 次浏览 • 2018-06-05 11:52 • 来自相关话题

0
评论

环信Rest java sdk吐槽+你想怎么写? 环信

开发讨论环信沈冲 发表了文章 • 2379 次浏览 • 2018-05-22 00:50 • 来自相关话题

先附上java sdk以及示例代码的GitHub地址:
https://github.com/easemob/emchat-server-examples/tree/master/emchat-server-java
 
相信各位服务端的朋友都已经瞅过了,最后是否使用了这个sdk呢?如果没有使用,为什么不使用呢?你..你...你能不能告诉我为什么??尽情的喷我吧,告诉我该怎么写,我改还不行吗..
 
先行感谢各位诚恳的意见和建议,期望得到您的回复。 查看全部
先附上java sdk以及示例代码的GitHub地址:
https://github.com/easemob/emchat-server-examples/tree/master/emchat-server-java
 
相信各位服务端的朋友都已经瞅过了,最后是否使用了这个sdk呢?如果没有使用,为什么不使用呢?你..你...你能不能告诉我为什么??尽情的喷我吧,告诉我该怎么写,我改还不行吗..
 
先行感谢各位诚恳的意见和建议,期望得到您的回复。
1
回复

Could not successfully update network info during initialization这是什么原因啊? 环信 iOS

开发讨论驱马历长洲 回复了问题 • 2 人关注 • 9345 次浏览 • 2018-04-29 15:05 • 来自相关话题

0
回复

你们用的环信ios 稳定吗? 环信 环信_iOS

回复

开发讨论tianwaifeixian 发起了问题 • 1 人关注 • 2718 次浏览 • 2018-04-25 15:56 • 来自相关话题

0
评论

基于环信的仿QQ即时通讯的简单实现 QQ 环信

开发讨论beyond 发表了文章 • 2309 次浏览 • 2018-03-08 16:01 • 来自相关话题

概述
 
今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细

我的博客地址

之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。
因为没有刻意去做聊天软件,花的时间也不多,然后界面就很简单,都是一些基本知识,如果觉得功能简单,可以自行添加,我这就不多介绍了。

照例先来一波动态演示:




 
功能很简单,注册用户 —> 用户登录 —> 选择聊天对象 —> 开始聊天

使用到的知识点:
RecyclerViewCardView环信的API的简单使用

依赖的库
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0' 1、聊天页面

首先是看了郭神的《第二行代码》做了聊天界面,用的是RecyclerView

a. 消息类的封装
public class MSG {
public static final int TYPE_RECEIVED = 0;//消息的类型:接收
public static final int TYPE_SEND = 1; //消息的类型:发送
private String content;//消息的内容
private int type; //消息的类型
public MSG(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}b. RecyclerView子项的布局​
<LinearLayout
android:id="@+id/ll_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 设置点击效果为水波纹(5.0以上) -->
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="2dp">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/man" />
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>这是左边的部分,至于右边应该也就简单了。我用CardView把ImageView包裹起来,这样比较好看。效果如下:




c. RecyclerView适配器​
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {
private List<MSG> mMsgList;
public MsgAdapter(List<MSG> mMsgList) {
this.mMsgList = mMsgList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MSG msg = mMsgList.get(position);
if (msg.getType() == MSG.TYPE_RECEIVED){
//如果是收到的消息,显示左边布局,隐藏右边布局
holder.llLeft.setVisibility(View.VISIBLE);
holder.llRight.setVisibility(View.GONE);
holder.tv_Left.setText(msg.getContent());
} else if (msg.getType() == MSG.TYPE_SEND){
//如果是发送的消息,显示右边布局,隐藏左边布局
holder.llLeft.setVisibility(View.GONE);
holder.llRight.setVisibility(View.VISIBLE);
holder.tv_Right.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
LinearLayout llLeft;
LinearLayout llRight;
TextView tv_Left;
TextView tv_Right;
public MyViewHolder(View itemView) {
super(itemView);
llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);
tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);
}
}
}这部分应该也没什么问题,就是适配器的创建,我之前的文章也讲过 传送门:简单粗暴——RecyclerView

d. RecyclerView初始化

就是一些基本的初始化,我就不赘述了,讲一下添加数据的细节处理
 
btSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = etInput.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
...//环信部分的发送消息
MSG msg = new MSG(content, MSG.TYPE_SEND);
mList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mList.size() - 1);
etInput.setText("");
}
}
});至此界面已经结束了,接下来就是数据的读取

2. 环信API的简单应用

官网有详细的API介绍 环信及时通讯V3.0,我这里就简单介绍如何简单集成

a. 环信开发账号的注册

环信官网

创建应用得到Appkey后面要用




b. SDK导入

你可以直接下载然后拷贝工程的libs目录下

Android Studio可以直接添加依赖
 
将以下代码放到项目根目录的build.gradle文件里
repositories {
maven { url "https://raw.githubusercontent. ... ot%3B }
}在你的module的build.gradle里加入以下代码
android {
//use legacy for android 6.0
useLibrary 'org.apache.http.legacy'
}
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
//Optional compile for GCM (Google Cloud Messaging).
compile 'com.google.android.gms:play-services-gcm:9.4.0'
compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}如果想使用不包含音视频通话的sdk,用compile 'com.hyphenate [:hyphenate-sdk-lite:] 3.2.3'
 
c. 清单文件配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
/>
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>APP打包混淆
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**d. 初始化SDK
在自定义Application的onCreate中初始化
public class MyApplication extends Application {
private Context appContext;
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
options.setAcceptInvitationAlways(false);
appContext = this;
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
Log.e("--->", "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(getApplicationContext(), options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
}e. 注册和登陆

注册要在子线程中执行
//注册失败会抛出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法
EMClient.getInstance().login(userName,password,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
}
});f. 发送消息
//创建一条文本消息,content为消息文字内容,toChatUsername为对方用户或者群聊的id,后文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//发送消息
EMClient.getInstance().chatManager().sendMessage(message);g. 接收消息
msgListener = new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> messages) {
//收到消息
String result = messages.get(0).getBody().toString();
String msgReceived = result.substring(5, result.length() - 1);
Log.i(TAG, "onMessageReceived: " + msgReceived);
final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
runOnUiThread(new Runnable() {
@Override
public void run() {
mList.add(msg);
mAdapter.notifyDataSetChanged();
mRecyclerView.scrollToPosition(mList.size() - 1);
}
});
}
@Override
public void onCmdMessageReceived(List<EMMessage> messages) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
}
@Override
public void onMessageChanged(EMMessage message, Object change) {
//消息状态变动
}
};接收消息的监听器分别需要在OnResume()和OnDestory()方法中注册和取消注册
EMClient.getInstance().chatManager().addMessageListener(msgListener);//注册
EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消注册需要注意的是,当接收到消息,需要在主线程中更新适配器,否则会不能及时刷新出来

项目文件截图:





到此,一个简单的及时聊天Demo已经完成,功能很简单,如果需要添加额外功能的话,可以自行参考官网,官网给出的教程还是很不错的!

最后希望大家能多多支持我,需要你们的支持喜欢!! 查看全部
概述
 
今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细

我的博客地址

之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。
因为没有刻意去做聊天软件,花的时间也不多,然后界面就很简单,都是一些基本知识,如果觉得功能简单,可以自行添加,我这就不多介绍了。

照例先来一波动态演示:
4043475-d16a88926805236a.gif

 
功能很简单,注册用户 —> 用户登录 —> 选择聊天对象 —> 开始聊天

使用到的知识点:
  1. RecyclerView
  2. CardView
  3. 环信的API的简单使用


依赖的库
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0'
1、聊天页面

首先是看了郭神的《第二行代码》做了聊天界面,用的是RecyclerView

a. 消息类的封装
public class MSG {
public static final int TYPE_RECEIVED = 0;//消息的类型:接收
public static final int TYPE_SEND = 1; //消息的类型:发送
private String content;//消息的内容
private int type; //消息的类型
public MSG(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}
b. RecyclerView子项的布局​
<LinearLayout
android:id="@+id/ll_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 设置点击效果为水波纹(5.0以上) -->
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="2dp">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/man" />
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>
这是左边的部分,至于右边应该也就简单了。我用CardView把ImageView包裹起来,这样比较好看。效果如下:
4043475-76ea5370b4d09d89.png

c. RecyclerView适配器​
 public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {
private List<MSG> mMsgList;
public MsgAdapter(List<MSG> mMsgList) {
this.mMsgList = mMsgList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MSG msg = mMsgList.get(position);
if (msg.getType() == MSG.TYPE_RECEIVED){
//如果是收到的消息,显示左边布局,隐藏右边布局
holder.llLeft.setVisibility(View.VISIBLE);
holder.llRight.setVisibility(View.GONE);
holder.tv_Left.setText(msg.getContent());
} else if (msg.getType() == MSG.TYPE_SEND){
//如果是发送的消息,显示右边布局,隐藏左边布局
holder.llLeft.setVisibility(View.GONE);
holder.llRight.setVisibility(View.VISIBLE);
holder.tv_Right.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
LinearLayout llLeft;
LinearLayout llRight;
TextView tv_Left;
TextView tv_Right;
public MyViewHolder(View itemView) {
super(itemView);
llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);
tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);
}
}
}
这部分应该也没什么问题,就是适配器的创建,我之前的文章也讲过 传送门:简单粗暴——RecyclerView

d. RecyclerView初始化

就是一些基本的初始化,我就不赘述了,讲一下添加数据的细节处理
 
  btSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = etInput.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
...//环信部分的发送消息
MSG msg = new MSG(content, MSG.TYPE_SEND);
mList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mList.size() - 1);
etInput.setText("");
}
}
});
至此界面已经结束了,接下来就是数据的读取

2. 环信API的简单应用

官网有详细的API介绍 环信及时通讯V3.0,我这里就简单介绍如何简单集成

a. 环信开发账号的注册

环信官网


创建应用得到Appkey后面要用

4043475-e4dd45e05060467f.png


b. SDK导入

你可以直接下载然后拷贝工程的libs目录下

Android Studio可以直接添加依赖
 
将以下代码放到项目根目录的build.gradle文件里
repositories {
maven { url "https://raw.githubusercontent. ... ot%3B }
}
在你的module的build.gradle里加入以下代码
android {
//use legacy for android 6.0
useLibrary 'org.apache.http.legacy'
}
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
//Optional compile for GCM (Google Cloud Messaging).
compile 'com.google.android.gms:play-services-gcm:9.4.0'
compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}
如果想使用不包含音视频通话的sdk,用compile 'com.hyphenate [:hyphenate-sdk-lite:] 3.2.3'
 
c. 清单文件配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
/>
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>
APP打包混淆
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**
d. 初始化SDK
在自定义Application的onCreate中初始化
public class MyApplication extends Application {
private Context appContext;
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
options.setAcceptInvitationAlways(false);
appContext = this;
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
Log.e("--->", "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(getApplicationContext(), options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
}
e. 注册和登陆

注册要在子线程中执行
//注册失败会抛出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法
EMClient.getInstance().login(userName,password,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
}
});
f. 发送消息
//创建一条文本消息,content为消息文字内容,toChatUsername为对方用户或者群聊的id,后文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//发送消息
EMClient.getInstance().chatManager().sendMessage(message);
g. 接收消息
msgListener = new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> messages) {
//收到消息
String result = messages.get(0).getBody().toString();
String msgReceived = result.substring(5, result.length() - 1);
Log.i(TAG, "onMessageReceived: " + msgReceived);
final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
runOnUiThread(new Runnable() {
@Override
public void run() {
mList.add(msg);
mAdapter.notifyDataSetChanged();
mRecyclerView.scrollToPosition(mList.size() - 1);
}
});
}
@Override
public void onCmdMessageReceived(List<EMMessage> messages) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
}
@Override
public void onMessageChanged(EMMessage message, Object change) {
//消息状态变动
}
};
接收消息的监听器分别需要在OnResume()和OnDestory()方法中注册和取消注册
EMClient.getInstance().chatManager().addMessageListener(msgListener);//注册
EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消注册
需要注意的是,当接收到消息,需要在主线程中更新适配器,否则会不能及时刷新出来

项目文件截图:

HyTYCEDIj4uugpEvJ59.jpg

到此,一个简单的及时聊天Demo已经完成,功能很简单,如果需要添加额外功能的话,可以自行参考官网,官网给出的教程还是很不错的!

最后希望大家能多多支持我,需要你们的支持喜欢!!
0
回复

加载EaseUI封装的聊天页面的添加id找不到 Android 环信

回复

开发讨论gorky_19 发起了问题 • 1 人关注 • 2955 次浏览 • 2018-02-08 14:46 • 来自相关话题

0
回复

java.lang.IllegalArgumentException: No view found for id 0x7f07003a for fragment EaseChatFragment Android 环信

回复

开发讨论gorky_19 发起了问题 • 1 人关注 • 4000 次浏览 • 2018-02-08 11:27 • 来自相关话题

2
最佳

看视频如果出现问题可以看这里 dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate iOS 环信

开发讨论Rickie_Lambert 回复了问题 • 3 人关注 • 9924 次浏览 • 2017-11-27 15:51 • 来自相关话题

7
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

开发讨论g711ab 回复了问题 • 8 人关注 • 6441 次浏览 • 2017-11-23 15:52 • 来自相关话题

1
回复

.a文件没找到 iOS 环信

开发讨论dujiepeng 回复了问题 • 2 人关注 • 2724 次浏览 • 2017-11-06 18:24 • 来自相关话题

0
回复

环信即时通讯 Unity,为什么只有用户的name,没有对应的id呢 环信

回复

开发讨论叶孤城的橙 发起了问题 • 1 人关注 • 2949 次浏览 • 2017-10-24 12:11 • 来自相关话题

3
回复

环信离线推送消息自定义 离线 推送 环信 通知

开发讨论╰☆╮末↘ 回复了问题 • 2 人关注 • 5492 次浏览 • 2017-09-14 11:42 • 来自相关话题

条新动态, 点击查看
expawtest

expawtest 回答了问题 • 2015-05-08 15:04 • 17 个回复 不感兴趣

为什么添加好友,通知收不到啊?

赞同来自:

> forum.php?mod=redirect&goto=findpost&pid=2572&ptid=1121
所以让你检查逻辑啊,毕竟是你写的代码,demo也不会这样


不就是在MainAct中设置监听,然后后面添加好友,然后监听到吗?
逻辑不对吗... 显示全部 »
> forum.php?mod=redirect&goto=findpost&pid=2572&ptid=1121
所以让你检查逻辑啊,毕竟是你写的代码,demo也不会这样


不就是在MainAct中设置监听,然后后面添加好友,然后监听到吗?
逻辑不对吗??
lifei9241

lifei9241 回答了问题 • 2015-06-28 17:22 • 2 个回复 不感兴趣

创建群之后在修改群的类型可以吗?

赞同来自:

创建之后,群组的类型不能更改了。可以更改groupname,description,maxusers这三个属性,http://www.easemob.com/docs/rest/groups/#update
创建之后,群组的类型不能更改了。可以更改groupname,description,maxusers这三个属性,http://www.easemob.com/docs/rest/groups/#update
Half12345

Half12345 回答了问题 • 2015-06-29 16:51 • 1 个回复 不感兴趣

iOS cocoapods2.1.8一直在更新 安装不上

赞同来自:

1、pod setup
2、pod search EaseMobSDK
3、加到podfile
4、pod install
 
1、pod setup
2、pod search EaseMobSDK
3、加到podfile
4、pod install
 
见代码板块:  http://www.imgeek.org/page/code  
见代码板块:  http://www.imgeek.org/page/code  
这个方法更新本地的消息,您需要更新本地的消息的话,将消息更新之后,调用updateMessage消息的内容会被保存到本地,再次获取本地的消息获取到的就是更新后的消息
这个方法更新本地的消息,您需要更新本地的消息的话,将消息更新之后,调用updateMessage消息的内容会被保存到本地,再次获取本地的消息获取到的就是更新后的消息
ChrisWu

ChrisWu 回答了问题 • 2016-12-06 17:06 • 1 个回复 不感兴趣

集成easeui时一直报错

赞同来自:

把你的v4包的版本改高一点,它默认的是19+
 
把你的v4包的版本改高一点,它默认的是19+
 
我前几天也被这个问题绊住了一天。最后发现是环信IM的音视频SDK与百度地图静态SDK冲突,更换成环信SDK最新版V3.2.3(framework动态库)即可。或者用没有包含音视频功能的环信SDK!亲测有效!
我前几天也被这个问题绊住了一天。最后发现是环信IM的音视频SDK与百度地图静态SDK冲突,更换成环信SDK最新版V3.2.3(framework动态库)即可。或者用没有包含音视频功能的环信SDK!亲测有效!
这个 问题很可能就是 导入的环信依赖库重复了, 即导入了包含实时音视频的依赖库,又导入了不包含实时音视频的依赖库。 可以检查一下,导入的依赖库是否导入错了, 重新导入,就可以解决这个问题了。
这个 问题很可能就是 导入的环信依赖库重复了, 即导入了包含实时音视频的依赖库,又导入了不包含实时音视频的依赖库。 可以检查一下,导入的依赖库是否导入错了, 重新导入,就可以解决这个问题了。
beyond

beyond 回答了问题 • 2019-03-08 10:25 • 1 个回复 不感兴趣

环信登录提示404

赞同来自:

登陆404是因为用户不存在。
user pwd填你在这个appkey下面注册的账号和密码
登陆404是因为用户不存在。
user pwd填你在这个appkey下面注册的账号和密码
3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

9
评论

【源码下载】一款使用环信实现的开源灵魂社交APP(含服务器) 猿匹配 开源

开发讨论beyond 发表了文章 • 29711 次浏览 • 2019-07-01 10:48 • 来自相关话题

#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。






  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

























  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖npm install
6.全局安装pm2npm install pm2 -g 
7.运行 vmshell.sh
 




扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
  查看全部
#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

1.png

2.png

3.png

4.png

5.png

6.png

  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
 
7.运行 vmshell.sh
 
环信冬冬_副本.jpg

扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
 
19
回复

【有奖征集】环信集成文档建议收集,现金打赏! 环信

开发讨论◇。 回复了问题 • 21 人关注 • 11617 次浏览 • 2016-09-06 16:09 • 来自相关话题

1
评论

环信CTO马晓宇:开源重燃移动IM大连接梦想 环信

开发讨论oscar 发表了文章 • 4932 次浏览 • 2015-06-30 18:45 • 来自相关话题

    2014年,微信凭借着QQ得天独厚的优势牢牢把控着国内的市场,陌陌通过资本力量洗牌一骑绝尘奔赴美国纳斯达克上市,Facebook 拆资190亿美元收购WhatsApp,买下了一个未来。当人们都认为移动IM局面已经尘埃落定时,众多通过移动IM走向社交类的APP也相继拿到风投,加入了这场无法预知的战争。在2015年的今天,移动互联网将进入活跃度时代,随着人们对手机依赖感的提升,用户需求不断在变化,4G的迅速发展,移动IM又掀起一场入口争夺战。WhatsApp股东的知名风投机构红杉资本高管合伙人Aaref Hilaly曾表示,移动聊天正在重新定义社交网络,诸如Facebook等充斥陌生人关系的社交模式,已经显得没有价值。移动IM将迎来一个巨大的创新机会,无论是社交、电商、教育、甚至嵌入式领域,都可以创造一个大链接时代。

     "我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。"

    与记者谈起移动IM的发展以及环信未来的方向,作为环信CTO的马晓宇从移动通信领域的实践经验和感悟来表达了自己的看法。同时作为一个开源理想主义者,他和整个团队致力把环信逐步开源,也把开源运用在整个工作环境里面,让企业跟随着开源世界的脚步,坚定在技术路线上前进,让一切都变得美好起来。






     马晓宇,环信CTO,从事十七八年程序开发,从最早的IC设计,到后来电信短信网关程序以及网关软件的开发,先后入职Symbian和Nokia公司,从事中间件以及内核软件开发,在移动技术领域有深厚积累,可以说马晓宇伴随着中国移动互联网的成长一路走来。

    在环信项目的早期,马晓宇和其他项目的成员看到移动互联网这个大趋势,以及市场的需求,想基于BaaS平台上提供IM以及推送这样的服务,在Key-Value存储、用户体系、权限管理等方面做一套完整的BaaS平台,不过这个项目持续了半年就被终止。面对着这样的打击马晓宇和整个团队也并没有因此而停止整个项目的开发,从非理性的热烈追捧到泡沫破裂,这让他们更加认识到了整个市场用户的需求和开放性及整个生态圈的重要性。2013年底,马晓宇和他们的团队再次出发,他们把之前的项目全部回收,重新定位并聚焦一件事情,那就是即时通讯业务。

    在移动互联网依旧澎湃的今天,移动IM在整个市场的冲击下愈演愈烈。不过马晓宇认为移动IM领域仍然蕴藏着巨大的潜力,差异化的市场仍有很大的一部分用户需求。所以他们开始以平台化、入口化为重点,在满足用户的即时通讯需求外,还继续不断的纵向扩展。

    可以说2013年是马晓宇和整个团队最艰难的一年。微信、易信、飞信、来往、陌陌等等似乎已经占据了整个移动IM市场,但是移动端即时通讯应用的形式还远没有定型,用户也在不断适应和迭代,一切的确定都是充满不确定性。马晓宇和整个团队也是看到了这个市场,凭借着整个团队有着开源和移动技术背景,一步一步的把环信这个项目带入移动互联网的台面上,在成功的背后也隐藏着很多艰难的决定和方向的抉择。下面就同记者一起来了解马晓宇和环信。

    记者:您是基于什么考虑和整个团队一起研发环信这类移动IM产品?

    马晓宇:我们环信这个项目从2013年初开始,当时我们看到移动互联网这个大趋势和需求,最早想做一个BaaS平台(后端即服务:Backend as a Service),提供一部分的IM功能,当时也搭建了一个推送功能,加入Key-Value存储,用户体系,权限管理等功能,最终想做一套比较全的BaaS平台,大概有半年多的时间,发现这个项目有一个很大的难度。

     主要问题是BaaS对中小开发者很好,不需要有自己的后台,很容易跑起来。但是如果你的业务发展,很难满足个性化的需求定制。实际上在2013年底我们就讨论这个事情,BaaS这个平台基本解决不了用户的个性化需求,所以后来我们就回收了这个项目,然后聚焦做一件事情---也就是现在IM即时通讯。从2013年底我们就重新定位,开始专注即时通讯业务,产品真正上线是在2014年的年中,6月份正式上线。






环信后台架构图

    记者:在2012年大家开始提及到BaaS平台,2013年在移动互联网上就有越来越多的人在谈整合BaaS服务,您认为在BaaS服务上比较适合做些什么?

    马晓宇:它对中小开发团队特别有效。如果你做移动APP开发,首先要有你的用户系统,还有Key-Value数据存储。然后了解推送社交,甚至朋友圈模块,发现很容易地搭建一套这种APP。

    但是用户有什么需求就不好解决,Key-Value即使能存储不同数据,从用户权限管需求这一块就不好办。因为有些应用,比如说只能是领导的秘书可以有一个特定的权限。所以真正到企业级的移动应用,我觉得就比较难做进一步的扩展。我们做了一年这个BaaS平台。

    记者:后面有没有继续?

    马晓宇:没有,不过我们做BaaS平台的这个团队都有科研背景,我们选了Usergrid Apache这顶级的开源项目,同时我们的主要创始人也是Usergrid Committer,通过技术的演变,基于Usergrid开发环信的用户系统。

    记者:据了解你们团队很多高管都有着一些重要的开源项目背景,像Jboss和Hibernate这类顶级的开源项目中也有着丰富的经验,而环信这样的平台大部分的用户都是开发者,而您以及整个团队从某个角度来讲也算是自己产品的用户,也是直接最懂自己用户的。那么您认为最能够让开发者接受的产品最重要的是什么?

    马晓宇:对,首先我们用环信,同时我们也用其他的开发者工具,从统计到一些分析、监控、推送,我们自身觉得比较重要的还是服务。包括我们以前做开源社区,有时候也在一些兄弟公司QQ群里看看,基本上环信是唯一一个每天晚上两三点开发者能找到主要技术人员给你回复,我们在服务商还是获得了很好的评价,这也是我们现在能赢得开发者的认可。

    记者:从开源的角度看,环信把即时通信云的SDK代码开源了,这对于开发者来说是非常有利部署自己的应用,从这方面我们能够看出环信对开源的态度。请谈谈你们是如何看待开源?以及接下来的开源计划是怎样的?

    马晓宇:首先我们是逐步开源的,包括我们的后台。因为我们最终不是靠这个CODE本身,毕竟即时通讯现在都快做成免费市场了,所以我们最终还是要靠服务来赢得市场。

    还有一个,我们其实做开源的时间也比较长,利用这个机会做环信也是想做一些尝试,也一直在推动。我们是希望做环信能做到三个成功。

    第一个是商业成功,首先是产品有价值,我们做的是toD面向开发者和面向企业SAAS服务,最后我们是能有收入,能有一个合理的利润,能够商业成功。

    第二个是技术成功。我们本身的技术栈用到的不光是我们自己研发的技术,同时我们也用了大量的优秀开源软件,从中间件到数据库大多数都是来自国外的。我们在解决移动终端和服务器之间的这种消费程度,或者是跨平台部署。所以我们是希望我们的一个目标,技术成功的目标是说,我们找一个important的问题,然后做一个方案,作为我们商业产品的一部分,同时我们把它进行开源,吸引用户来使用,这也是整个团队需要长期要做的事情。

    第三个是团队成功。我们团队有一部分都是从事过开源项目背景,比较向往开源企业的文化和情怀。这个团队成功最好理解,就是说比如我们有同事家在海南,他就可以在海南工作,拿北京的工资,然后隔几个月来一起开会。

    我们也开始逐渐尝试,在过完年后有一个同事在泰国工作了一星期,然后回来跟我们分享。这样的结果我们认为还是比较靠谱的,在自己比较向往或者熟悉的地方工作效率完全不一样,思路特别清晰。所以我们是希望在商业公司也实现一些开源的企业文化。

    记者:目前市场上即时通信类的产品很多,我个人认为还都比较成熟,但是当技术发展到一定程度的时候,用户往往不会再关注到底技术哪家强,更多的是取决于产品体验以及服务态度。在这方面,您认为环信从哪些方面更能够体现出来?

    马晓宇:现在是三方面,一方面还是服务进一步深化,我觉得接入环信平台,提供IM只是第一步,我们现在专门有一个CSM(Cluster System Management 专门用于集群系统管理的中间件),帮助我们的客户成功。通过第一步的发消息到头像、位置、分级、互动、推送等等提高它的活跃度,让用户真正玩起来。所以我们专门有这么一个团队开始组建,帮助这些比较大的APP真正去分析用户指标,通过我们的数据怎么去帮助他们更多的互动,这是我们叫客户成功。

    第二步其实大家也在做了,就是做数据挖掘,挖掘用户的行为实际上对APP是非常有利的。比如,我们有一个用户是猎聘网,可以通过挖掘用户和露猎头在上面的一些行为关键词进行分析,根据这些数据去指导他的业务。

    第三步就是我们现在逐渐感到有一定用户量后,即时通讯需要保持一个长连接,所以这是有一定费用的。我认为成本优势也是一个优势,企业真正做到百万用户后,每月运维成本可能是几万。但是如果环信能经过优化,提供一半或者四分之一的价格,对企业来讲是挺大一笔费用。

    所以总结一下,第一个是我们的客户成功,第二个是数据挖掘,第三个是成本优势。目前客户成功和数据挖掘我们都启动,成本优势我们还在继续优化过程中,下一步主要是在技术上,我们要把成本给优化下来。

    记者:环信是基于PaaS平台去做的,而像这样的产品也很多,但是真正的提高核心竞争力我认为还是要加强自己的生态圈建设,在不断的收集用户的需求以及痛点并及时解决,完善和壮大自己的产品,在这方面你们是如何建设的呢?

    马晓宇:现在我们是分三层,第一层是我们所有的用户,就是环信的直接用户。

    第二层是我们有一个QQ群的形式,一个QQ群有2000人。我们有五个群,现在建了第六个群。然后在这个基础上,我们有一些基于环信的核心开发者,他们会给我们做demo,或者真正地提交了一个完全开源的APP,就是基于环信的。

    第三层是我们和一些合作伙伴做的一些探索,专门做个IM Geek开源社区,作为我们整个生态圈的核心部分。

    记者:从微信、飞信、易信这些即时通信软件到即时通信云平台,从只需要服务自己一家产品到要服务于成千上万家不同的APP产品,她们的需求也是不一样的,从系统设计和运维的角度,你们如何保证他们的稳定性以及安全性?
   
    马晓宇:不太一样,因为我们是做多租户APP平台,每个APP有不同的需求,所以我们现在是有一个基于APP一些参数可以动态的,这是一部分。

    还有一部分是因为多租户,所以我们对数据安全比较看重。我们参与运维项目主要是用户体系比较好,提供了最基本的用户隔离和安全,完全做好用户访问其他APP信息的隔离。

    另外从运维层面上,平台上运维用户数据比较关键,系统内部规定只有两人有权限,真正登陆以后是能够看到用户。

    还有一部分是大家做云端要解决的一个共享问题,也就是公有云。怎么能保证所有用户体验在操作上不影响其他的用户。最基本的接口有限流,服务器端提供调用接口,可以给你的客户发消息,可以创建群组,可以做用户管理,但这个是有限流的,不能无限制地发,超过限流就会反馈错误。

    最后是队列设计。避免有些APP大量给用户发消息,影响其他用户,所以在系统的这些队列设计上,我们也考虑到让APP尽量隔离。现在我们有高速、低速两条通道,如果发现有大量的信息时就会走到低速队列,我们尽量保持用户体验。

    记者:从近两年来看,我们都能够感觉到移动互联网都是呈现爆发式增长,还有从今年春节我们也看到了大家都比较倾向通过IM通信这种方式拜年,交流,IM通信功能也会被广泛应用在其他的APP当中,面对这种趋势,我们在这方面如何去保证高稳定性及高并发这种情况?还有应急措施有哪些?

    马晓宇:我们会有相应的措施,像您说春节期间的突发状况,我们主要技术人员和运维在春节期间都没怎么休息,就是为了保证整个系统正常运转。首先我们的架构比较好,在后面的数据库、服务器,如果有系统瓶颈的话,我们能够及时加机器。现在我们加机器用的是云,不需要到机房去搭建,基本我们现在做到在分钟级就能够配好整个架构集群。

    真正线上我们有标准的监控,通过购买第三方服务监控全国各地到我们服务器的访问情况。对比系统的指标,比如登陆整个集群时间,登陆每台服务器的时间,监控发消息的整个过程时间。然后监控数据库系统的主要的队列,当发现有队列堆积,我们会及时进一步处理。

    同时我们也监控DB,主要是DB负载,整个系统是自动化的,有问题会报警,防患于未然。然后如果是真出了紧急情况,在系统里有一些降级开关,如果有紧急情况,保证最小可用。所以现在我们的系统设计,在关键模块是有降级开关的,那么降级开关实际上是要运维,我们每个月有一次演习,比如说我们的缓存宕机,整个储存都宕机,如何做到进一步重新加载数据。还有一些没有出现过的情况,假如数据库宕机,如何切换成直接写到log文件,再把log重新导到数据库恢复。

    记者:我们都知道2月27日工信部正式向中国电信和中国联通发放了FDD制式4G牌照,这也意味着移动音频和视频的时代到来了,我认为提高这方面的技术支持给开发者更好的体验将是突破竞争门槛的重要突破点,你们将如何面对这个浪潮?

    马晓宇:我们今年上半年主要的开发方向就是将音视频产品化,现在iOS和安卓都支持实时语音,而且可以互通,但是实时视频只有Android支持,iOS系统在今年4月初我们也会出支持实时视频的release版本, 有开发者基于我们视频SDK,做一些视频交流,这也是4G带宽的优势,把之前的不可能变成为可能。

    另外除了我们基础功能之外,我们还深入研究,在一对一语音接通率进行优化。大家在Wifi下,中间要跨防火墙,所以有接通率,我们现在在改进这一块。还有在4G网络下,当网络不稳定或弱网络的情况下,我们也做了优化。通过优化对音视频掉包补偿算法,在标准算法的基础上再做进一步优化。

    现在看到了很多机会,很多APP开始集成视频功能。包括我们一个合作伙伴,基于我们多人语音开始做智能的导航设备。通过定位,基于4G加入频道通话。现在实验是在3G网,但在使用上设备绑定是4G卡,基于云端。

    记者:还是要看未来终端的发展,兼容性好的话可以往上发力。从即时通信产品的多样化来看,环信还是显得比较单一,是做“专”还是做“全”,环信在未来将有哪样的布局?

    马晓宇:首先我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。现在很多巨头都在定标准,我们目前只提供SDK,让设备之间互联。

    同时,我们从去年年底基于即时通讯提供SaaS服务,它能提供移动应用实时客服,这和以前的呼叫中心不太一样,我们围绕的客户是一些移动应用,进入APP后点击帮助就可以通过IM技术和后台的客服沟通,给你提供帮助。 查看全部
    2014年,微信凭借着QQ得天独厚的优势牢牢把控着国内的市场,陌陌通过资本力量洗牌一骑绝尘奔赴美国纳斯达克上市,Facebook 拆资190亿美元收购WhatsApp,买下了一个未来。当人们都认为移动IM局面已经尘埃落定时,众多通过移动IM走向社交类的APP也相继拿到风投,加入了这场无法预知的战争。在2015年的今天,移动互联网将进入活跃度时代,随着人们对手机依赖感的提升,用户需求不断在变化,4G的迅速发展,移动IM又掀起一场入口争夺战。WhatsApp股东的知名风投机构红杉资本高管合伙人Aaref Hilaly曾表示,移动聊天正在重新定义社交网络,诸如Facebook等充斥陌生人关系的社交模式,已经显得没有价值。移动IM将迎来一个巨大的创新机会,无论是社交、电商、教育、甚至嵌入式领域,都可以创造一个大链接时代。

     "我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。"

    与记者谈起移动IM的发展以及环信未来的方向,作为环信CTO的马晓宇从移动通信领域的实践经验和感悟来表达了自己的看法。同时作为一个开源理想主义者,他和整个团队致力把环信逐步开源,也把开源运用在整个工作环境里面,让企业跟随着开源世界的脚步,坚定在技术路线上前进,让一切都变得美好起来。

wKiom1UYNwDCJmxQAACwdgCMSuw087.jpg


     马晓宇,环信CTO,从事十七八年程序开发,从最早的IC设计,到后来电信短信网关程序以及网关软件的开发,先后入职Symbian和Nokia公司,从事中间件以及内核软件开发,在移动技术领域有深厚积累,可以说马晓宇伴随着中国移动互联网的成长一路走来。

    在环信项目的早期,马晓宇和其他项目的成员看到移动互联网这个大趋势,以及市场的需求,想基于BaaS平台上提供IM以及推送这样的服务,在Key-Value存储、用户体系、权限管理等方面做一套完整的BaaS平台,不过这个项目持续了半年就被终止。面对着这样的打击马晓宇和整个团队也并没有因此而停止整个项目的开发,从非理性的热烈追捧到泡沫破裂,这让他们更加认识到了整个市场用户的需求和开放性及整个生态圈的重要性。2013年底,马晓宇和他们的团队再次出发,他们把之前的项目全部回收,重新定位并聚焦一件事情,那就是即时通讯业务。

    在移动互联网依旧澎湃的今天,移动IM在整个市场的冲击下愈演愈烈。不过马晓宇认为移动IM领域仍然蕴藏着巨大的潜力,差异化的市场仍有很大的一部分用户需求。所以他们开始以平台化、入口化为重点,在满足用户的即时通讯需求外,还继续不断的纵向扩展。

    可以说2013年是马晓宇和整个团队最艰难的一年。微信、易信、飞信、来往、陌陌等等似乎已经占据了整个移动IM市场,但是移动端即时通讯应用的形式还远没有定型,用户也在不断适应和迭代,一切的确定都是充满不确定性。马晓宇和整个团队也是看到了这个市场,凭借着整个团队有着开源和移动技术背景,一步一步的把环信这个项目带入移动互联网的台面上,在成功的背后也隐藏着很多艰难的决定和方向的抉择。下面就同记者一起来了解马晓宇和环信。

    记者:您是基于什么考虑和整个团队一起研发环信这类移动IM产品?

    马晓宇:我们环信这个项目从2013年初开始,当时我们看到移动互联网这个大趋势和需求,最早想做一个BaaS平台(后端即服务:Backend as a Service),提供一部分的IM功能,当时也搭建了一个推送功能,加入Key-Value存储,用户体系,权限管理等功能,最终想做一套比较全的BaaS平台,大概有半年多的时间,发现这个项目有一个很大的难度。

     主要问题是BaaS对中小开发者很好,不需要有自己的后台,很容易跑起来。但是如果你的业务发展,很难满足个性化的需求定制。实际上在2013年底我们就讨论这个事情,BaaS这个平台基本解决不了用户的个性化需求,所以后来我们就回收了这个项目,然后聚焦做一件事情---也就是现在IM即时通讯。从2013年底我们就重新定位,开始专注即时通讯业务,产品真正上线是在2014年的年中,6月份正式上线。

wKioL1UYOGPjYE2ZAADuoQziNd0748.jpg


环信后台架构图

    记者:在2012年大家开始提及到BaaS平台,2013年在移动互联网上就有越来越多的人在谈整合BaaS服务,您认为在BaaS服务上比较适合做些什么?

    马晓宇:它对中小开发团队特别有效。如果你做移动APP开发,首先要有你的用户系统,还有Key-Value数据存储。然后了解推送社交,甚至朋友圈模块,发现很容易地搭建一套这种APP。

    但是用户有什么需求就不好解决,Key-Value即使能存储不同数据,从用户权限管需求这一块就不好办。因为有些应用,比如说只能是领导的秘书可以有一个特定的权限。所以真正到企业级的移动应用,我觉得就比较难做进一步的扩展。我们做了一年这个BaaS平台。

    记者:后面有没有继续?

    马晓宇:没有,不过我们做BaaS平台的这个团队都有科研背景,我们选了Usergrid Apache这顶级的开源项目,同时我们的主要创始人也是Usergrid Committer,通过技术的演变,基于Usergrid开发环信的用户系统。

    记者:据了解你们团队很多高管都有着一些重要的开源项目背景,像Jboss和Hibernate这类顶级的开源项目中也有着丰富的经验,而环信这样的平台大部分的用户都是开发者,而您以及整个团队从某个角度来讲也算是自己产品的用户,也是直接最懂自己用户的。那么您认为最能够让开发者接受的产品最重要的是什么?

    马晓宇:对,首先我们用环信,同时我们也用其他的开发者工具,从统计到一些分析、监控、推送,我们自身觉得比较重要的还是服务。包括我们以前做开源社区,有时候也在一些兄弟公司QQ群里看看,基本上环信是唯一一个每天晚上两三点开发者能找到主要技术人员给你回复,我们在服务商还是获得了很好的评价,这也是我们现在能赢得开发者的认可。

    记者:从开源的角度看,环信把即时通信云的SDK代码开源了,这对于开发者来说是非常有利部署自己的应用,从这方面我们能够看出环信对开源的态度。请谈谈你们是如何看待开源?以及接下来的开源计划是怎样的?

    马晓宇:首先我们是逐步开源的,包括我们的后台。因为我们最终不是靠这个CODE本身,毕竟即时通讯现在都快做成免费市场了,所以我们最终还是要靠服务来赢得市场。

    还有一个,我们其实做开源的时间也比较长,利用这个机会做环信也是想做一些尝试,也一直在推动。我们是希望做环信能做到三个成功。

    第一个是商业成功,首先是产品有价值,我们做的是toD面向开发者和面向企业SAAS服务,最后我们是能有收入,能有一个合理的利润,能够商业成功。

    第二个是技术成功。我们本身的技术栈用到的不光是我们自己研发的技术,同时我们也用了大量的优秀开源软件,从中间件到数据库大多数都是来自国外的。我们在解决移动终端和服务器之间的这种消费程度,或者是跨平台部署。所以我们是希望我们的一个目标,技术成功的目标是说,我们找一个important的问题,然后做一个方案,作为我们商业产品的一部分,同时我们把它进行开源,吸引用户来使用,这也是整个团队需要长期要做的事情。

    第三个是团队成功。我们团队有一部分都是从事过开源项目背景,比较向往开源企业的文化和情怀。这个团队成功最好理解,就是说比如我们有同事家在海南,他就可以在海南工作,拿北京的工资,然后隔几个月来一起开会。

    我们也开始逐渐尝试,在过完年后有一个同事在泰国工作了一星期,然后回来跟我们分享。这样的结果我们认为还是比较靠谱的,在自己比较向往或者熟悉的地方工作效率完全不一样,思路特别清晰。所以我们是希望在商业公司也实现一些开源的企业文化。

    记者:目前市场上即时通信类的产品很多,我个人认为还都比较成熟,但是当技术发展到一定程度的时候,用户往往不会再关注到底技术哪家强,更多的是取决于产品体验以及服务态度。在这方面,您认为环信从哪些方面更能够体现出来?

    马晓宇:现在是三方面,一方面还是服务进一步深化,我觉得接入环信平台,提供IM只是第一步,我们现在专门有一个CSM(Cluster System Management 专门用于集群系统管理的中间件),帮助我们的客户成功。通过第一步的发消息到头像、位置、分级、互动、推送等等提高它的活跃度,让用户真正玩起来。所以我们专门有这么一个团队开始组建,帮助这些比较大的APP真正去分析用户指标,通过我们的数据怎么去帮助他们更多的互动,这是我们叫客户成功。

    第二步其实大家也在做了,就是做数据挖掘,挖掘用户的行为实际上对APP是非常有利的。比如,我们有一个用户是猎聘网,可以通过挖掘用户和露猎头在上面的一些行为关键词进行分析,根据这些数据去指导他的业务。

    第三步就是我们现在逐渐感到有一定用户量后,即时通讯需要保持一个长连接,所以这是有一定费用的。我认为成本优势也是一个优势,企业真正做到百万用户后,每月运维成本可能是几万。但是如果环信能经过优化,提供一半或者四分之一的价格,对企业来讲是挺大一笔费用。

    所以总结一下,第一个是我们的客户成功,第二个是数据挖掘,第三个是成本优势。目前客户成功和数据挖掘我们都启动,成本优势我们还在继续优化过程中,下一步主要是在技术上,我们要把成本给优化下来。

    记者:环信是基于PaaS平台去做的,而像这样的产品也很多,但是真正的提高核心竞争力我认为还是要加强自己的生态圈建设,在不断的收集用户的需求以及痛点并及时解决,完善和壮大自己的产品,在这方面你们是如何建设的呢?

    马晓宇:现在我们是分三层,第一层是我们所有的用户,就是环信的直接用户。

    第二层是我们有一个QQ群的形式,一个QQ群有2000人。我们有五个群,现在建了第六个群。然后在这个基础上,我们有一些基于环信的核心开发者,他们会给我们做demo,或者真正地提交了一个完全开源的APP,就是基于环信的。

    第三层是我们和一些合作伙伴做的一些探索,专门做个IM Geek开源社区,作为我们整个生态圈的核心部分。

    记者:从微信、飞信、易信这些即时通信软件到即时通信云平台,从只需要服务自己一家产品到要服务于成千上万家不同的APP产品,她们的需求也是不一样的,从系统设计和运维的角度,你们如何保证他们的稳定性以及安全性?
   
    马晓宇:不太一样,因为我们是做多租户APP平台,每个APP有不同的需求,所以我们现在是有一个基于APP一些参数可以动态的,这是一部分。

    还有一部分是因为多租户,所以我们对数据安全比较看重。我们参与运维项目主要是用户体系比较好,提供了最基本的用户隔离和安全,完全做好用户访问其他APP信息的隔离。

    另外从运维层面上,平台上运维用户数据比较关键,系统内部规定只有两人有权限,真正登陆以后是能够看到用户。

    还有一部分是大家做云端要解决的一个共享问题,也就是公有云。怎么能保证所有用户体验在操作上不影响其他的用户。最基本的接口有限流,服务器端提供调用接口,可以给你的客户发消息,可以创建群组,可以做用户管理,但这个是有限流的,不能无限制地发,超过限流就会反馈错误。

    最后是队列设计。避免有些APP大量给用户发消息,影响其他用户,所以在系统的这些队列设计上,我们也考虑到让APP尽量隔离。现在我们有高速、低速两条通道,如果发现有大量的信息时就会走到低速队列,我们尽量保持用户体验。

    记者:从近两年来看,我们都能够感觉到移动互联网都是呈现爆发式增长,还有从今年春节我们也看到了大家都比较倾向通过IM通信这种方式拜年,交流,IM通信功能也会被广泛应用在其他的APP当中,面对这种趋势,我们在这方面如何去保证高稳定性及高并发这种情况?还有应急措施有哪些?

    马晓宇:我们会有相应的措施,像您说春节期间的突发状况,我们主要技术人员和运维在春节期间都没怎么休息,就是为了保证整个系统正常运转。首先我们的架构比较好,在后面的数据库、服务器,如果有系统瓶颈的话,我们能够及时加机器。现在我们加机器用的是云,不需要到机房去搭建,基本我们现在做到在分钟级就能够配好整个架构集群。

    真正线上我们有标准的监控,通过购买第三方服务监控全国各地到我们服务器的访问情况。对比系统的指标,比如登陆整个集群时间,登陆每台服务器的时间,监控发消息的整个过程时间。然后监控数据库系统的主要的队列,当发现有队列堆积,我们会及时进一步处理。

    同时我们也监控DB,主要是DB负载,整个系统是自动化的,有问题会报警,防患于未然。然后如果是真出了紧急情况,在系统里有一些降级开关,如果有紧急情况,保证最小可用。所以现在我们的系统设计,在关键模块是有降级开关的,那么降级开关实际上是要运维,我们每个月有一次演习,比如说我们的缓存宕机,整个储存都宕机,如何做到进一步重新加载数据。还有一些没有出现过的情况,假如数据库宕机,如何切换成直接写到log文件,再把log重新导到数据库恢复。

    记者:我们都知道2月27日工信部正式向中国电信和中国联通发放了FDD制式4G牌照,这也意味着移动音频和视频的时代到来了,我认为提高这方面的技术支持给开发者更好的体验将是突破竞争门槛的重要突破点,你们将如何面对这个浪潮?

    马晓宇:我们今年上半年主要的开发方向就是将音视频产品化,现在iOS和安卓都支持实时语音,而且可以互通,但是实时视频只有Android支持,iOS系统在今年4月初我们也会出支持实时视频的release版本, 有开发者基于我们视频SDK,做一些视频交流,这也是4G带宽的优势,把之前的不可能变成为可能。

    另外除了我们基础功能之外,我们还深入研究,在一对一语音接通率进行优化。大家在Wifi下,中间要跨防火墙,所以有接通率,我们现在在改进这一块。还有在4G网络下,当网络不稳定或弱网络的情况下,我们也做了优化。通过优化对音视频掉包补偿算法,在标准算法的基础上再做进一步优化。

    现在看到了很多机会,很多APP开始集成视频功能。包括我们一个合作伙伴,基于我们多人语音开始做智能的导航设备。通过定位,基于4G加入频道通话。现在实验是在3G网,但在使用上设备绑定是4G卡,基于云端。

    记者:还是要看未来终端的发展,兼容性好的话可以往上发力。从即时通信产品的多样化来看,环信还是显得比较单一,是做“专”还是做“全”,环信在未来将有哪样的布局?

    马晓宇:首先我们的方向是即时通信云,主要面向多端,除了现有的iOS、Android和Web端,我们实际还在做Linux嵌入式,会支持更多嵌入设备,加入IOT(物体组成的因特网)实现设备之间互联。现在很多巨头都在定标准,我们目前只提供SDK,让设备之间互联。

    同时,我们从去年年底基于即时通讯提供SaaS服务,它能提供移动应用实时客服,这和以前的呼叫中心不太一样,我们围绕的客户是一些移动应用,进入APP后点击帮助就可以通过IM技术和后台的客服沟通,给你提供帮助。
3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

回复

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

回复

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

9
评论

【源码下载】一款使用环信实现的开源灵魂社交APP(含服务器) 猿匹配 开源

开发讨论beyond 发表了文章 • 29711 次浏览 • 2019-07-01 10:48 • 来自相关话题

#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。






  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

























  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖npm install
6.全局安装pm2npm install pm2 -g 
7.运行 vmshell.sh
 




扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
  查看全部
#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

1.png

2.png

3.png

4.png

5.png

6.png

  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
 
7.运行 vmshell.sh
 
环信冬冬_副本.jpg

扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
 
3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

回复

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

回复

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

1
回复

环信web端登录报错 failed: Data frame received after close 环信_WebIM 环信

回复

开发讨论lizg 回复了问题 • 2 人关注 • 3282 次浏览 • 2019-03-11 18:00 • 来自相关话题

1
最佳

环信登录提示404 环信web 环信

回复

开发讨论beyond 回复了问题 • 2 人关注 • 3031 次浏览 • 2019-03-08 10:25 • 来自相关话题

2
回复

为什么不能播放,官方也不修复一下呢?????? 环信 Android

回复

开发讨论beyond 回复了问题 • 2 人关注 • 2854 次浏览 • 2019-02-27 13:49 • 来自相关话题

1
回复

php 如何 集成环信 laravel当中 感谢 环信 laravel PHP 集成

回复

开发讨论beyond 回复了问题 • 2 人关注 • 3385 次浏览 • 2019-02-27 13:47 • 来自相关话题

0
回复

Android studio导入easeui运行编译报错 Android 环信

回复

开发讨论₁₉₉₃₀₄₀₇の陳小垚 发起了问题 • 1 人关注 • 3392 次浏览 • 2018-09-27 22:07 • 来自相关话题

1
回复

导出聊天记录报错:Array ( [error] => apiname is invalid ) 环信集成 环信

回复

开发讨论kaibisikai 回复了问题 • 2 人关注 • 4183 次浏览 • 2018-09-25 10:37 • 来自相关话题

2
回复

群成员主动退出,但是群里面获取所有成员还是能获取到已经退出的成员 环信

回复

开发讨论孩子气 Rc 回复了问题 • 3 人关注 • 4300 次浏览 • 2018-08-09 15:09 • 来自相关话题

4
回复

牛逼啊,这么大个环信连做到头像和昵称保存的方法都没有 环信

回复

开发讨论陈日明 回复了问题 • 2 人关注 • 3392 次浏览 • 2018-08-08 10:49 • 来自相关话题

1
回复

OC_SDK集成参考视频 建议 环信 iOS

回复

开发讨论beyond 回复了问题 • 2 人关注 • 3032 次浏览 • 2018-07-26 10:48 • 来自相关话题

2
回复
1
回复

都添加哪些库,视频里看不清啊? 环信 iOS

回复

开发讨论KevinGong 回复了问题 • 2 人关注 • 2702 次浏览 • 2018-06-05 11:52 • 来自相关话题

1
回复

Could not successfully update network info during initialization这是什么原因啊? 环信 iOS

回复

开发讨论驱马历长洲 回复了问题 • 2 人关注 • 9345 次浏览 • 2018-04-29 15:05 • 来自相关话题

0
回复

你们用的环信ios 稳定吗? 环信 环信_iOS

回复

开发讨论tianwaifeixian 发起了问题 • 1 人关注 • 2718 次浏览 • 2018-04-25 15:56 • 来自相关话题

0
回复

加载EaseUI封装的聊天页面的添加id找不到 Android 环信

回复

开发讨论gorky_19 发起了问题 • 1 人关注 • 2955 次浏览 • 2018-02-08 14:46 • 来自相关话题

0
回复

java.lang.IllegalArgumentException: No view found for id 0x7f07003a for fragment EaseChatFragment Android 环信

回复

开发讨论gorky_19 发起了问题 • 1 人关注 • 4000 次浏览 • 2018-02-08 11:27 • 来自相关话题

2
最佳

看视频如果出现问题可以看这里 dyld: Library not loaded: @rpath/Hyphenate.framework/Hyphenate iOS 环信

回复

开发讨论Rickie_Lambert 回复了问题 • 3 人关注 • 9924 次浏览 • 2017-11-27 15:51 • 来自相关话题

7
回复

【环信官方外包一个项目】利用环信IM实现一个在线白板 环信

回复

开发讨论g711ab 回复了问题 • 8 人关注 • 6441 次浏览 • 2017-11-23 15:52 • 来自相关话题

1
回复

.a文件没找到 iOS 环信

回复

开发讨论dujiepeng 回复了问题 • 2 人关注 • 2724 次浏览 • 2017-11-06 18:24 • 来自相关话题

0
回复

环信即时通讯 Unity,为什么只有用户的name,没有对应的id呢 环信

回复

开发讨论叶孤城的橙 发起了问题 • 1 人关注 • 2949 次浏览 • 2017-10-24 12:11 • 来自相关话题

3
回复

环信离线推送消息自定义 离线 推送 环信 通知

回复

开发讨论╰☆╮末↘ 回复了问题 • 2 人关注 • 5492 次浏览 • 2017-09-14 11:42 • 来自相关话题

0
回复

收到消息的代理回调不会执行 环信 iOS

回复

开发讨论Aries 发起了问题 • 1 人关注 • 3673 次浏览 • 2017-09-12 10:28 • 来自相关话题

3
回复

环信v3,聊天室解除禁言在回调前崩溃的问题 环信 禁言

回复

开发讨论XiaoMai 回复了问题 • 2 人关注 • 3832 次浏览 • 2017-08-23 14:32 • 来自相关话题

1
回复

'EMSDKFull.h' file not found iOS 环信

回复

开发讨论wangyuzhang 回复了问题 • 2 人关注 • 2936 次浏览 • 2017-08-08 19:46 • 来自相关话题

3
回复

【招聘.北京】急需一位开发者运营小伙伴,你看我还有机会吗? imgeek 环信

回复

开发讨论美国队长 回复了问题 • 3 人关注 • 668 次浏览 • 2020-07-28 18:18 • 来自相关话题

2
回复

环信IM的开发者用户体验就靠你了 !!【内推职位招聘】环信Web/android/ios技术支持工程师 环信

回复

开发讨论beyond 回复了问题 • 5 人关注 • 1126 次浏览 • 2020-06-28 10:24 • 来自相关话题

9
评论

【源码下载】一款使用环信实现的开源灵魂社交APP(含服务器) 猿匹配 开源

开发讨论beyond 发表了文章 • 29711 次浏览 • 2019-07-01 10:48 • 来自相关话题

#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。






  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

























  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖npm install
6.全局安装pm2npm install pm2 -g 
7.运行 vmshell.sh
 




扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
  查看全部
#前言
近期,环信热心开发者-穿裤衩闯天下使用环信IM开发了一款实时聊天应用,包含简单的服务器端,现在正式开源给小伙伴们。感兴趣的同学可以一起搞一下哦,详细介绍请往下看。

猿匹配_logo_副本.png


  上代码
服务器:VMServer
客户端:VMMatch
 
 #VMMatch
猿匹配 —— 国内首个程序猿非严肃婚恋交友应用,让我们一言不合就来场匹配吧
 
#介绍#
首先说下中文名:为什么叫这个名字呢,因为这是一个程序猿(媛)之间匹配交流的应用啊其实这是一个使用环信 IM 开发的一款开源聊天项目,涵盖了时下流行的一些聊天元素,同时已将 IM 功能封装为单独库,可以直接引用,方便使用
项目还处在初期阶段,还有许多功能需要实现,有兴趣的可以一起来
项目资源均来自于互联网,如果有侵权请联系我
 
 #下载体验
猿匹配 小米商店 审核中
猿匹配 Google Play
 
  #项目截图

1.png

2.png

3.png

4.png

5.png

6.png

  
 #开发环境
项目基本属于在最新的Android开发环境下开发,使用Java8的一些新特性,比如Lambda表达式,
然后项目已经适配Android6.x以上的动态权限适配,以及7.x的文件选择,和8.x的通知提醒等;
· Mac OS 10.14.4
· Android Studio 3.3.2
  #项目模块儿
本项目包含两部分:
一部分是项目主模块app,这部分主要包含了项目的业务逻辑,比如匹配、信息修改、设置等
另一部分是封装成library的vmim,这是为了方便大家引用到自己的项目中做的一步封装,不用再去复杂的复制代码和资源等,
只需要将vmim以module导入到自己的项目中就行了,具体使用方式参见项目app模块儿;
 
  #功能与 TODO
IM部分功能
· [x] 链接监听
· [x] 登录注册
· [x] 会话功能
      。[x] 置顶
      。[x] 标为未读
      。[x] 删除与清空
      。[x] 草稿功能
· [x] 消息功能
      。[x] 下拉加载更多
      。[x] 消息复制(仅文字类消息)
      。[x] 消息删除
      。[x] 文本+Emoji消息收发
      。[x] 大表情消息收发
      。[x] 图片消息
        ~[x] 查看大图
        ~[ ] 保存图片
      。[x] 语音消息
        ~[x] 语音录制
        ~[x] 语音播放(可暂停,波形待优化)
        ~[x] 听筒和扬声器播放切换
      。[x] 语音实时通话功能
      。[x] 视频实时通话功能
      。[x] 通话过程中的娱乐消息收发
        ~[x] 骰子
        ~[x] 石头剪刀布
        ~[x] 大表情
      。[x] 昵称头像处理(通过回调实现)
App部分功能
· [x] 登录注册(包括业务逻辑和 IM 逻辑)
· [x] 匹配
      。[x] 提交匹配信息
      。[x] 拉取匹配信息
· [x] 聊天(这里直接加载 IM 模块儿)
· [x] 我的
      。[x] 个人信息展示
      。[x] 上传头像
      。[x] 设置昵称
      。[x] 设置签名
· [x] 设置
      。[x] 个人信息设置
      。[x] 通知提醒
      。[x] 聊天
      。[ ] 隐私(随业务部分一起完善)
      。[ ] 通用(随业务部分一起完善)
      。[ ] 帮助反馈(随业务部分一起完善)
      。[x] 关于
      。[x] 退出
· [ ] 社区
      。[ ] 发布
      。[ ] 评论
      。[ ] 收藏
      。[ ] 关注
发布功能
· [x] 多渠道打包
· [x] 签名配置
· [x] 开发与线上环境配置
· [x] 敏感信息保护
 
  #配置运行
1.首先复制config.default.gradle到config.gradle
2.配置下config.gradle环信appkey以及bugly统计Id
3.正式打包需要配置下签名信息,同时将签名文件放置在项目根目录
 
  #参与贡献
如果你有什么好的想法,或者好的实现,可以通过下边的步骤参与进来,让我们一起把这个项目做得更好,欢迎参与
1.Fork本仓库
2.新建feature_xxx分支 (单独创建一个实现你自己想法的分支)
3.提交代码
4.新建Pull Request
5.等待我们的Review & Merge
 
 #关联项目
服务器端由nodejs实现,地址见这里 VMServer
 
  #VMServer
是为Android开源项目VMMatch项目(中文名猿匹配)实现的服务端
 
  #简介
这个项目包含两部分
· 根目录:服务逻辑及API接口实现
· client目录:前端界面,和服务器端代码端放置在同一仓库下(暂未实现)
 
 #使用
简单介绍下运行环境及部署方法
1.安装nodejs开发时使用的是v10.16.0版本
2.需要安装mongodb并启动,开发使用版本4.0.10
3.下载项目到服务器,可以下载压缩包,或者用git clone命令
4.复制config_default.js到config.js,可根据自己需要修改配置文件
5.安装依赖
npm install

6.全局安装pm2
npm install pm2 -g
 
7.运行 vmshell.sh
 
环信冬冬_副本.jpg

扫码备注【开源项目】邀你加入环信开源社群
 
转载自https://blog.melove.net/develop-open-source-im-match-and-server/ 
 
0
评论

2018,环信是如何C位出道的!(分享你和环信的故事赢千元奖励) 有奖调查 环信即时通讯云 2018 环信

开发讨论beyond 发表了文章 • 1818 次浏览 • 2019-02-01 15:25 • 来自相关话题

六年,筚路蓝缕,环信走过了一段从无至有的征程;

六年,栉风沐雨,见证了中国SaaS从0到1到1++的幸运;

六年,砥砺前行,从IM云1.0到IM云4.0,从移动客服到全媒体客服再到智能客服;

六年,峥嵘岁月,又一个全新的起点等待环信人去超越,从“心”出发;

六载春秋,陪伴是最长情的告白!感恩有你!!!



















































分享你和环信的故事赢千元奖励

欢迎在评论区分享你和环信的故事,评论区点赞前3名各送200元京东卡一张,第4-10名各送100元京东卡一张。(春节后第一个工作日2月11日公布获奖名单)

评论地址:https://mp.weixin.qq.com/s/Tij4kpquSUSeB04lkcepXQ 查看全部
六年,筚路蓝缕,环信走过了一段从无至有的征程;

六年,栉风沐雨,见证了中国SaaS从0到1到1++的幸运;

六年,砥砺前行,从IM云1.0到IM云4.0,从移动客服到全媒体客服再到智能客服;

六年,峥嵘岁月,又一个全新的起点等待环信人去超越,从“心”出发;

六载春秋,陪伴是最长情的告白!感恩有你!!!


新年广告_01.jpg


新年广告_02.jpg


新年广告_03.jpg


新年广告_05.jpg


新年广告_06.jpg


新年广告_07.jpg


新年广告_08.jpg


新年广告_09.jpg


新年广告_10.jpg


新年广告_11.jpg

分享你和环信的故事赢千元奖励

欢迎在评论区分享你和环信的故事,评论区点赞前3名各送200元京东卡一张,第4-10名各送100元京东卡一张。(春节后第一个工作日2月11日公布获奖名单)

评论地址:https://mp.weixin.qq.com/s/Tij4kpquSUSeB04lkcepXQ
0
评论

环信Rest java sdk吐槽+你想怎么写? 环信

开发讨论环信沈冲 发表了文章 • 2379 次浏览 • 2018-05-22 00:50 • 来自相关话题

先附上java sdk以及示例代码的GitHub地址:
https://github.com/easemob/emchat-server-examples/tree/master/emchat-server-java
 
相信各位服务端的朋友都已经瞅过了,最后是否使用了这个sdk呢?如果没有使用,为什么不使用呢?你..你...你能不能告诉我为什么??尽情的喷我吧,告诉我该怎么写,我改还不行吗..
 
先行感谢各位诚恳的意见和建议,期望得到您的回复。 查看全部
先附上java sdk以及示例代码的GitHub地址:
https://github.com/easemob/emchat-server-examples/tree/master/emchat-server-java
 
相信各位服务端的朋友都已经瞅过了,最后是否使用了这个sdk呢?如果没有使用,为什么不使用呢?你..你...你能不能告诉我为什么??尽情的喷我吧,告诉我该怎么写,我改还不行吗..
 
先行感谢各位诚恳的意见和建议,期望得到您的回复。
0
评论

基于环信的仿QQ即时通讯的简单实现 QQ 环信

开发讨论beyond 发表了文章 • 2309 次浏览 • 2018-03-08 16:01 • 来自相关话题

概述
 
今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细

我的博客地址

之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。
因为没有刻意去做聊天软件,花的时间也不多,然后界面就很简单,都是一些基本知识,如果觉得功能简单,可以自行添加,我这就不多介绍了。

照例先来一波动态演示:




 
功能很简单,注册用户 —> 用户登录 —> 选择聊天对象 —> 开始聊天

使用到的知识点:
RecyclerViewCardView环信的API的简单使用

依赖的库
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0' 1、聊天页面

首先是看了郭神的《第二行代码》做了聊天界面,用的是RecyclerView

a. 消息类的封装
public class MSG {
public static final int TYPE_RECEIVED = 0;//消息的类型:接收
public static final int TYPE_SEND = 1; //消息的类型:发送
private String content;//消息的内容
private int type; //消息的类型
public MSG(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}b. RecyclerView子项的布局​
<LinearLayout
android:id="@+id/ll_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 设置点击效果为水波纹(5.0以上) -->
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="2dp">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/man" />
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>这是左边的部分,至于右边应该也就简单了。我用CardView把ImageView包裹起来,这样比较好看。效果如下:




c. RecyclerView适配器​
public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {
private List<MSG> mMsgList;
public MsgAdapter(List<MSG> mMsgList) {
this.mMsgList = mMsgList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MSG msg = mMsgList.get(position);
if (msg.getType() == MSG.TYPE_RECEIVED){
//如果是收到的消息,显示左边布局,隐藏右边布局
holder.llLeft.setVisibility(View.VISIBLE);
holder.llRight.setVisibility(View.GONE);
holder.tv_Left.setText(msg.getContent());
} else if (msg.getType() == MSG.TYPE_SEND){
//如果是发送的消息,显示右边布局,隐藏左边布局
holder.llLeft.setVisibility(View.GONE);
holder.llRight.setVisibility(View.VISIBLE);
holder.tv_Right.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
LinearLayout llLeft;
LinearLayout llRight;
TextView tv_Left;
TextView tv_Right;
public MyViewHolder(View itemView) {
super(itemView);
llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);
tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);
}
}
}这部分应该也没什么问题,就是适配器的创建,我之前的文章也讲过 传送门:简单粗暴——RecyclerView

d. RecyclerView初始化

就是一些基本的初始化,我就不赘述了,讲一下添加数据的细节处理
 
btSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = etInput.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
...//环信部分的发送消息
MSG msg = new MSG(content, MSG.TYPE_SEND);
mList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mList.size() - 1);
etInput.setText("");
}
}
});至此界面已经结束了,接下来就是数据的读取

2. 环信API的简单应用

官网有详细的API介绍 环信及时通讯V3.0,我这里就简单介绍如何简单集成

a. 环信开发账号的注册

环信官网

创建应用得到Appkey后面要用




b. SDK导入

你可以直接下载然后拷贝工程的libs目录下

Android Studio可以直接添加依赖
 
将以下代码放到项目根目录的build.gradle文件里
repositories {
maven { url "https://raw.githubusercontent. ... ot%3B }
}在你的module的build.gradle里加入以下代码
android {
//use legacy for android 6.0
useLibrary 'org.apache.http.legacy'
}
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
//Optional compile for GCM (Google Cloud Messaging).
compile 'com.google.android.gms:play-services-gcm:9.4.0'
compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}如果想使用不包含音视频通话的sdk,用compile 'com.hyphenate [:hyphenate-sdk-lite:] 3.2.3'
 
c. 清单文件配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
/>
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>APP打包混淆
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**d. 初始化SDK
在自定义Application的onCreate中初始化
public class MyApplication extends Application {
private Context appContext;
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
options.setAcceptInvitationAlways(false);
appContext = this;
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
Log.e("--->", "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(getApplicationContext(), options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
}e. 注册和登陆

注册要在子线程中执行
//注册失败会抛出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法
EMClient.getInstance().login(userName,password,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
}
});f. 发送消息
//创建一条文本消息,content为消息文字内容,toChatUsername为对方用户或者群聊的id,后文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//发送消息
EMClient.getInstance().chatManager().sendMessage(message);g. 接收消息
msgListener = new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> messages) {
//收到消息
String result = messages.get(0).getBody().toString();
String msgReceived = result.substring(5, result.length() - 1);
Log.i(TAG, "onMessageReceived: " + msgReceived);
final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
runOnUiThread(new Runnable() {
@Override
public void run() {
mList.add(msg);
mAdapter.notifyDataSetChanged();
mRecyclerView.scrollToPosition(mList.size() - 1);
}
});
}
@Override
public void onCmdMessageReceived(List<EMMessage> messages) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
}
@Override
public void onMessageChanged(EMMessage message, Object change) {
//消息状态变动
}
};接收消息的监听器分别需要在OnResume()和OnDestory()方法中注册和取消注册
EMClient.getInstance().chatManager().addMessageListener(msgListener);//注册
EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消注册需要注意的是,当接收到消息,需要在主线程中更新适配器,否则会不能及时刷新出来

项目文件截图:





到此,一个简单的及时聊天Demo已经完成,功能很简单,如果需要添加额外功能的话,可以自行参考官网,官网给出的教程还是很不错的!

最后希望大家能多多支持我,需要你们的支持喜欢!! 查看全部
概述
 
今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。详细

我的博客地址

之前一直想实现聊天的功能,但是感觉有点困难,今天看了环信的API,就利用下午的时间动手试了试,然后做了一个小Demo。
因为没有刻意去做聊天软件,花的时间也不多,然后界面就很简单,都是一些基本知识,如果觉得功能简单,可以自行添加,我这就不多介绍了。

照例先来一波动态演示:
4043475-d16a88926805236a.gif

 
功能很简单,注册用户 —> 用户登录 —> 选择聊天对象 —> 开始聊天

使用到的知识点:
  1. RecyclerView
  2. CardView
  3. 环信的API的简单使用


依赖的库
compile 'com.android.support:appcompat-v7:24.2.1'
compile 'com.android.support:cardview-v7:24.1.1'
compile 'com.android.support:recyclerview-v7:24.0.0'
1、聊天页面

首先是看了郭神的《第二行代码》做了聊天界面,用的是RecyclerView

a. 消息类的封装
public class MSG {
public static final int TYPE_RECEIVED = 0;//消息的类型:接收
public static final int TYPE_SEND = 1; //消息的类型:发送
private String content;//消息的内容
private int type; //消息的类型
public MSG(String content, int type) {
this.content = content;
this.type = type;
}
public String getContent() {
return content;
}
public int getType() {
return type;
}
}
b. RecyclerView子项的布局​
<LinearLayout
android:id="@+id/ll_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 设置点击效果为水波纹(5.0以上) -->
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:orientation="horizontal"
android:padding="2dp">
<android.support.v7.widget.CardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="false"
app:cardUseCompatPadding="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:scaleType="centerCrop"
android:src="@mipmap/man" />
</android.support.v7.widget.CardView>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/message_left"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_msg_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:textColor="#fff" />
</LinearLayout>
</LinearLayout>
这是左边的部分,至于右边应该也就简单了。我用CardView把ImageView包裹起来,这样比较好看。效果如下:
4043475-76ea5370b4d09d89.png

c. RecyclerView适配器​
 public class MsgAdapter extends RecyclerView.Adapter<MsgAdapter.MyViewHolder> {
private List<MSG> mMsgList;
public MsgAdapter(List<MSG> mMsgList) {
this.mMsgList = mMsgList;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = View.inflate(parent.getContext(), R.layout.item_msg, null);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
MSG msg = mMsgList.get(position);
if (msg.getType() == MSG.TYPE_RECEIVED){
//如果是收到的消息,显示左边布局,隐藏右边布局
holder.llLeft.setVisibility(View.VISIBLE);
holder.llRight.setVisibility(View.GONE);
holder.tv_Left.setText(msg.getContent());
} else if (msg.getType() == MSG.TYPE_SEND){
//如果是发送的消息,显示右边布局,隐藏左边布局
holder.llLeft.setVisibility(View.GONE);
holder.llRight.setVisibility(View.VISIBLE);
holder.tv_Right.setText(msg.getContent());
}
}
@Override
public int getItemCount() {
return mMsgList.size();
}
static class MyViewHolder extends RecyclerView.ViewHolder{
LinearLayout llLeft;
LinearLayout llRight;
TextView tv_Left;
TextView tv_Right;
public MyViewHolder(View itemView) {
super(itemView);
llLeft = (LinearLayout) itemView.findViewById(R.id.ll_msg_left);
llRight = (LinearLayout) itemView.findViewById(R.id.ll_msg_right);
tv_Left = (TextView) itemView.findViewById(R.id.tv_msg_left);
tv_Right = (TextView) itemView.findViewById(R.id.tv_msg_right);
}
}
}
这部分应该也没什么问题,就是适配器的创建,我之前的文章也讲过 传送门:简单粗暴——RecyclerView

d. RecyclerView初始化

就是一些基本的初始化,我就不赘述了,讲一下添加数据的细节处理
 
  btSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String content = etInput.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
...//环信部分的发送消息
MSG msg = new MSG(content, MSG.TYPE_SEND);
mList.add(msg);
//当有新消息时,刷新RecyclerView中的显示
mAdapter.notifyItemInserted(mList.size() - 1);
//将RecyclerView定位到最后一行
mRecyclerView.scrollToPosition(mList.size() - 1);
etInput.setText("");
}
}
});
至此界面已经结束了,接下来就是数据的读取

2. 环信API的简单应用

官网有详细的API介绍 环信及时通讯V3.0,我这里就简单介绍如何简单集成

a. 环信开发账号的注册

环信官网


创建应用得到Appkey后面要用

4043475-e4dd45e05060467f.png


b. SDK导入

你可以直接下载然后拷贝工程的libs目录下

Android Studio可以直接添加依赖
 
将以下代码放到项目根目录的build.gradle文件里
repositories {
maven { url "https://raw.githubusercontent. ... ot%3B }
}
在你的module的build.gradle里加入以下代码
android {
//use legacy for android 6.0
useLibrary 'org.apache.http.legacy'
}
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
//Optional compile for GCM (Google Cloud Messaging).
compile 'com.google.android.gms:play-services-gcm:9.4.0'
compile 'com.hyphenate:hyphenate-sdk:3.2.3'
}
如果想使用不包含音视频通话的sdk,用compile 'com.hyphenate [:hyphenate-sdk-lite:] 3.2.3'
 
c. 清单文件配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="Your Package"
android:versionCode="100"
android:versionName="1.0.0">
<!-- Required -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="Your Application">
<!-- 设置环信应用的AppKey -->
<meta-data android:name="EASEMOB_APPKEY" android:value="Your AppKey" />
<!-- 声明SDK所需的service SDK核心功能-->
<service android:name="com.hyphenate.chat.EMChatService" android:exported="true"/>
<service android:name="com.hyphenate.chat.EMJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"
/>
<!-- 声明SDK所需的receiver -->
<receiver android:name="com.hyphenate.chat.EMMonitorReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
<!-- 可选filter -->
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
</application>
</manifest>
APP打包混淆
-keep class com.hyphenate.** {*;}
-dontwarn com.hyphenate.**
d. 初始化SDK
在自定义Application的onCreate中初始化
public class MyApplication extends Application {
private Context appContext;
@Override
public void onCreate() {
super.onCreate();
EMOptions options = new EMOptions();
options.setAcceptInvitationAlways(false);
appContext = this;
int pid = android.os.Process.myPid();
String processAppName = getAppName(pid);
// 如果APP启用了远程的service,此application:onCreate会被调用2次
// 为了防止环信SDK被初始化2次,加此判断会保证SDK被初始化1次
// 默认的APP会在以包名为默认的process name下运行,如果查到的process name不是APP的process name就立即返回
if (processAppName == null || !processAppName.equalsIgnoreCase(appContext.getPackageName())) {
Log.e("--->", "enter the service process!");
// 则此application::onCreate 是被service 调用的,直接返回
return;
}
//初始化
EMClient.getInstance().init(getApplicationContext(), options);
//在做打包混淆时,关闭debug模式,避免消耗不必要的资源
EMClient.getInstance().setDebugMode(true);
}
private String getAppName(int pID) {
String processName = null;
ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List l = am.getRunningAppProcesses();
Iterator i = l.iterator();
PackageManager pm = this.getPackageManager();
while (i.hasNext()) {
ActivityManager.RunningAppProcessInfo info = (ActivityManager.RunningAppProcessInfo) (i.next());
try {
if (info.pid == pID) {
processName = info.processName;
return processName;
}
} catch (Exception e) {
// Log.d("Process", "Error>> :"+ e.toString());
}
}
return processName;
}
}
e. 注册和登陆

注册要在子线程中执行
//注册失败会抛出HyphenateException
EMClient.getInstance().createAccount(username, pwd);//同步方法
EMClient.getInstance().login(userName,password,new EMCallBack() {//回调
@Override
public void onSuccess() {
EMClient.getInstance().groupManager().loadAllGroups();
EMClient.getInstance().chatManager().loadAllConversations();
Log.d("main", "登录聊天服务器成功!");
}
@Override
public void onProgress(int progress, String status) {
}
@Override
public void onError(int code, String message) {
Log.d("main", "登录聊天服务器失败!");
}
});
f. 发送消息
//创建一条文本消息,content为消息文字内容,toChatUsername为对方用户或者群聊的id,后文皆是如此
EMMessage message = EMMessage.createTxtSendMessage(content, toChatUsername);
//发送消息
EMClient.getInstance().chatManager().sendMessage(message);
g. 接收消息
msgListener = new EMMessageListener() {
@Override
public void onMessageReceived(List<EMMessage> messages) {
//收到消息
String result = messages.get(0).getBody().toString();
String msgReceived = result.substring(5, result.length() - 1);
Log.i(TAG, "onMessageReceived: " + msgReceived);
final MSG msg = new MSG(msgReceived, MSG.TYPE_RECEIVED);
runOnUiThread(new Runnable() {
@Override
public void run() {
mList.add(msg);
mAdapter.notifyDataSetChanged();
mRecyclerView.scrollToPosition(mList.size() - 1);
}
});
}
@Override
public void onCmdMessageReceived(List<EMMessage> messages) {
//收到透传消息
}
@Override
public void onMessageRead(List<EMMessage> list) {
}
@Override
public void onMessageDelivered(List<EMMessage> list) {
}
@Override
public void onMessageChanged(EMMessage message, Object change) {
//消息状态变动
}
};
接收消息的监听器分别需要在OnResume()和OnDestory()方法中注册和取消注册
EMClient.getInstance().chatManager().addMessageListener(msgListener);//注册
EMClient.getInstance().chatManager().removeMessageListener(msgListener);//取消注册
需要注意的是,当接收到消息,需要在主线程中更新适配器,否则会不能及时刷新出来

项目文件截图:

HyTYCEDIj4uugpEvJ59.jpg

到此,一个简单的及时聊天Demo已经完成,功能很简单,如果需要添加额外功能的话,可以自行参考官网,官网给出的教程还是很不错的!

最后希望大家能多多支持我,需要你们的支持喜欢!!
0
评论

【移动战略说 · 第一期】智能硬件产品开发从0到1 环信 行业活动

开发讨论beyond 发表了文章 • 1623 次浏览 • 2017-08-14 19:09 • 来自相关话题

   据GSMA预期,到2020年,全球互联设备将突破270亿,移动互联设备有望达到105亿,新的市场机遇将进一步增多。自动上报家中燃气数据、远程开关气阀、精准透明计费、自动调节远程定时控制路灯等,基于新一代物联网的新型智慧产品正在走入我们的生产生活。

   开发一款智能硬件产品涉及的环节很多。本次活动,APICloud联合华强聚丰和智石科技,从样品生产、App开发和近场通讯技术在智能硬件中的应用跟大家分享智能硬件产品如何快速从0到1!
 
活动概况
【活动时间】2017年8月19日(周六),13:30-16:30
【面向人群】制造业企业、智能硬件管理层、产品负责人、技术负责人,其他相关从业者
【活动咨询/合作】请加微信:appdev1,备注819活动议程
【13:30-14:00】签到
【14:00-14:40】如何快速完成样品生产
内容概要:产品硬件开发者希望快速拿到样品,进行方案验证和调试,发现设计问题,快速进行方案修正,以便进入下一开发环节,完成开发工作。怎样解决这些困难?怎样才能让生产环节快速顺利完成样品生产?
【14:40-15:20】自主研发or外包?智能硬件App开发指南
内容概要:现在市场上智能硬件往往需要一款App配合,无论是控制设备还是查看数据。企业不但需要考虑成本,还要兼顾产品体验。本环节APICloud将会为大家介绍App开发技术如何选型;自己招团队与找外包的对比;以及项目准备、开发、测试、上线各个阶段可能遇到的问题和注意事项。
【15:20-16:00】室内精准位置物联网络搭建
【16:00-】幸运抽奖&自由交流分享嘉宾
















报名地址活动报名 查看全部
   据GSMA预期,到2020年,全球互联设备将突破270亿,移动互联设备有望达到105亿,新的市场机遇将进一步增多。自动上报家中燃气数据、远程开关气阀、精准透明计费、自动调节远程定时控制路灯等,基于新一代物联网的新型智慧产品正在走入我们的生产生活。

   开发一款智能硬件产品涉及的环节很多。本次活动,APICloud联合华强聚丰和智石科技,从样品生产、App开发和近场通讯技术在智能硬件中的应用跟大家分享智能硬件产品如何快速从0到1!
 
活动概况
【活动时间】2017年8月19日(周六),13:30-16:30
【面向人群】制造业企业、智能硬件管理层、产品负责人、技术负责人,其他相关从业者
【活动咨询/合作】请加微信:appdev1,备注819
活动议程
【13:30-14:00】签到
【14:00-14:40】如何快速完成样品生产
内容概要:产品硬件开发者希望快速拿到样品,进行方案验证和调试,发现设计问题,快速进行方案修正,以便进入下一开发环节,完成开发工作。怎样解决这些困难?怎样才能让生产环节快速顺利完成样品生产?
【14:40-15:20】自主研发or外包?智能硬件App开发指南
内容概要:现在市场上智能硬件往往需要一款App配合,无论是控制设备还是查看数据。企业不但需要考虑成本,还要兼顾产品体验。本环节APICloud将会为大家介绍App开发技术如何选型;自己招团队与找外包的对比;以及项目准备、开发、测试、上线各个阶段可能遇到的问题和注意事项。
【15:20-16:00】室内精准位置物联网络搭建
【16:00-】幸运抽奖&自由交流
分享嘉宾

30972779689807524.jpeg


30242778644313753.jpg


30852779689957552.jpeg


报名地址活动报名
0
评论

环信荣膺“2017未来独角兽企业”,做商业的连接器 独角兽 新闻资讯 环信

开发讨论新闻资讯 发表了文章 • 2253 次浏览 • 2017-07-25 11:29 • 来自相关话题

 独角兽企业往往聚集了行业里大多的资源,随着技术、人才、资金的积累,其在行业里的地位与机会将逐步放大,前进愈发顺利,发展迎风而起。近日,由中国科学院《互联网周刊》杂志评选的“2017未来独角兽企业TOP150”榜单正式揭晓,有着国际领先的企业级软件服务提供商愿景的环信凭借在即时通讯云和SaaS客服领域的行业深耕和迅猛发展,以全国榜单第130名位居垂直行业第一。




“连接人与人”,“连接人与商业”的愿景支撑垂直行业第一

   企业服务市场是一块大蛋糕,练就的是技术内功,靠的是对市场趋势的准确把握,环信以用户需求为出发点近期推出客户互动云(CEC),环信CEC基于全球领先的即时通讯云技术,通过人工智能和大数据赋能,依托多渠道接入管理、精准用户画像、智能客服机器人、客户之声、智能质检、视频客服等SaaS客服体系为包括保险、证券、金融、教育、电商等行业提供了从客户互动渠道、到客户服务、再到精准营销的全流程客户互动解决方案。

   环信的起家产品“即时通讯云”,承担环信“连接人与人”的商业愿景,为开发者提供基于移动互联网的即时通讯能力,作为全球最大的即时通讯云厂商已经广泛应用服务13万余APP客户,环信全面支持Android、iOS、Web等多种平台,在流量、电量、长连接、语音、位置、安全等能力做了极致的优化,让移动开发者摆脱繁重的移动IM通讯底层开发,极大限度地缩短产品开发周期,二十四时间内即可让App拥有移动IM能力。目前环信即时通讯云已经拓展推出了包括直播、社交大数据、红包、鉴黄、视频人脸特效、短信验证码等增值服务。

   而“环信移动客服”是即时通讯云“连接人与人”场景的一个延伸到“连接人与商业”,包括网页在线客服、社交媒体客服(微博、微信)、APP内置客服、工单和呼叫中心等多种渠道均可一键接入。基于环信业界领先的IM长连接技术保证消息必达,并通过智能客服机器人技术降低人工客服工作量。同时,基于人工智能和大数据挖掘的客户旅程透析产品”环信客户声音”能够帮助企业优化运营,提高跨渠道客服体验。

   2016年,基于开发即时通讯云和移动客服的基础上,环信对已有产品进行了再次的研发和升级,针对已有在包括电商、保险、证券、金融、教育等优势行业的积累基础上,着手开发人工智能 —— “环信智能客服机器人”。他们希望在不降低用户体验的情况下,尽可能地解决商家日益增长的客服成本和海量客服请求之间的天然矛盾。

   比如,我们在移动端通过国美在线下单后,产品遇到的任何问题,通过IM窗口和客服咨询,这个沟通通道就是环信即时通讯云在提供底层通信服务。环信移动客服依托智能客服机器人很大程度上为企业节约了人力成本,众多保险公司已经通过部署环信智能客服机器人来提高客服效率。环信客户声音提高了跨渠道的客户服务体验,帮助企业优化运营,实现了客户中心完成从成本中心向价值中心的转化,国内某标杆教育机构已经在部署完环信客服声音以后尝到了客户转化率、客单价双升的甜头。

   与此同时,环信还一直利用自己的大数据平台产品给客户提供增值服务,让客户通过即时通讯云和移动客服等产品的后台数据,分析得出自己产品的适用人群、产品体验和活跃度等。让客户能更好地改善自己的产品,更好地服务消费者。

   环信的所有更新、改变与尝试,都是在围绕着一个行业,或者说是一个目标在努力。走上行业的顶峰之后,面临坦途时就会懈怠很多,但他们似乎仍然没有放弃向更高峰的攀登。

   2016年环信作为国内唯一的SaaS厂商荣膺Gartner 2016 Cool Vendor,2017年3月环信刚获得由经纬中国领投、银泰嘉禾跟投的1.03亿元C轮融资,显示出包括国际顶级研究机构和资本市场对于环信商业模式和发展前景的认可。正是得益于包括红杉资本、经纬中国、SIG和银泰嘉禾的鼎力支持,保障了环信持续巨额的研发投入,形成了公司业务发展的正向循环。

志存高远方能有所大成

   独角兽企业往往聚集了行业里大多的资源,随着技术、人才、资金的积累,其在行业里的地位与机会将逐步放大,前进愈发顺利,发展迎风而起。优势会被放大,引领、变革行业发展的责任则愈发凸显。换句话说,独角兽企业不仅仅是行业的佼佼者,更重要的,它是行业的领跑者、推动者。

   身为未来独角兽企业,要以行业推动者自居,以创新破迷局,以诚信守正心,以担当为己任。只有致力于市场需求的满足,行业发展瓶颈的突破,才会在纷乱的市场竞争中把握准方向,守得住初心,冲出迷雾,把握未来。

独角兽是一种荣耀的名片,更意味着一种担当与责任。 查看全部
 独角兽企业往往聚集了行业里大多的资源,随着技术、人才、资金的积累,其在行业里的地位与机会将逐步放大,前进愈发顺利,发展迎风而起。近日,由中国科学院《互联网周刊》杂志评选的“2017未来独角兽企业TOP150”榜单正式揭晓,有着国际领先的企业级软件服务提供商愿景的环信凭借在即时通讯云和SaaS客服领域的行业深耕和迅猛发展,以全国榜单第130名位居垂直行业第一。
ea790d9dly1fhv495ni8gj211j0pyn6b.jpg

“连接人与人”,“连接人与商业”的愿景支撑垂直行业第一

   企业服务市场是一块大蛋糕,练就的是技术内功,靠的是对市场趋势的准确把握,环信以用户需求为出发点近期推出客户互动云(CEC),环信CEC基于全球领先的即时通讯云技术,通过人工智能和大数据赋能,依托多渠道接入管理、精准用户画像、智能客服机器人、客户之声、智能质检、视频客服等SaaS客服体系为包括保险、证券、金融、教育、电商等行业提供了从客户互动渠道、到客户服务、再到精准营销的全流程客户互动解决方案。

   环信的起家产品“即时通讯云”,承担环信“连接人与人”的商业愿景,为开发者提供基于移动互联网的即时通讯能力,作为全球最大的即时通讯云厂商已经广泛应用服务13万余APP客户,环信全面支持Android、iOS、Web等多种平台,在流量、电量、长连接、语音、位置、安全等能力做了极致的优化,让移动开发者摆脱繁重的移动IM通讯底层开发,极大限度地缩短产品开发周期,二十四时间内即可让App拥有移动IM能力。目前环信即时通讯云已经拓展推出了包括直播、社交大数据、红包、鉴黄、视频人脸特效、短信验证码等增值服务。

   而“环信移动客服”是即时通讯云“连接人与人”场景的一个延伸到“连接人与商业”,包括网页在线客服、社交媒体客服(微博、微信)、APP内置客服、工单和呼叫中心等多种渠道均可一键接入。基于环信业界领先的IM长连接技术保证消息必达,并通过智能客服机器人技术降低人工客服工作量。同时,基于人工智能和大数据挖掘的客户旅程透析产品”环信客户声音”能够帮助企业优化运营,提高跨渠道客服体验。

   2016年,基于开发即时通讯云和移动客服的基础上,环信对已有产品进行了再次的研发和升级,针对已有在包括电商、保险、证券、金融、教育等优势行业的积累基础上,着手开发人工智能 —— “环信智能客服机器人”。他们希望在不降低用户体验的情况下,尽可能地解决商家日益增长的客服成本和海量客服请求之间的天然矛盾。

   比如,我们在移动端通过国美在线下单后,产品遇到的任何问题,通过IM窗口和客服咨询,这个沟通通道就是环信即时通讯云在提供底层通信服务。环信移动客服依托智能客服机器人很大程度上为企业节约了人力成本,众多保险公司已经通过部署环信智能客服机器人来提高客服效率。环信客户声音提高了跨渠道的客户服务体验,帮助企业优化运营,实现了客户中心完成从成本中心向价值中心的转化,国内某标杆教育机构已经在部署完环信客服声音以后尝到了客户转化率、客单价双升的甜头。

   与此同时,环信还一直利用自己的大数据平台产品给客户提供增值服务,让客户通过即时通讯云和移动客服等产品的后台数据,分析得出自己产品的适用人群、产品体验和活跃度等。让客户能更好地改善自己的产品,更好地服务消费者。

   环信的所有更新、改变与尝试,都是在围绕着一个行业,或者说是一个目标在努力。走上行业的顶峰之后,面临坦途时就会懈怠很多,但他们似乎仍然没有放弃向更高峰的攀登。

   2016年环信作为国内唯一的SaaS厂商荣膺Gartner 2016 Cool Vendor,2017年3月环信刚获得由经纬中国领投、银泰嘉禾跟投的1.03亿元C轮融资,显示出包括国际顶级研究机构和资本市场对于环信商业模式和发展前景的认可。正是得益于包括红杉资本、经纬中国、SIG和银泰嘉禾的鼎力支持,保障了环信持续巨额的研发投入,形成了公司业务发展的正向循环。

志存高远方能有所大成

   独角兽企业往往聚集了行业里大多的资源,随着技术、人才、资金的积累,其在行业里的地位与机会将逐步放大,前进愈发顺利,发展迎风而起。优势会被放大,引领、变革行业发展的责任则愈发凸显。换句话说,独角兽企业不仅仅是行业的佼佼者,更重要的,它是行业的领跑者、推动者。

   身为未来独角兽企业,要以行业推动者自居,以创新破迷局,以诚信守正心,以担当为己任。只有致力于市场需求的满足,行业发展瓶颈的突破,才会在纷乱的市场竞争中把握准方向,守得住初心,冲出迷雾,把握未来。

独角兽是一种荣耀的名片,更意味着一种担当与责任。
0
评论

环信CEC亮相GMIS 2017峰会,智能客服大有可为! 新闻资讯 Gartner 环信

开发讨论新闻资讯 发表了文章 • 1944 次浏览 • 2017-06-06 11:40 • 来自相关话题

    近日,2017全球机器智能峰会(GMIS 2017)在北京圆满举行,47位重磅嘉宾带来的32场演讲、4个圆桌论坛、1场人机大战及5个主题Session轮番上演,使GMIS 2017成为聚焦人工智能及相关领域的顶级行业盛宴。说出来你可能不信,环信是唯一受邀参展GMIS 2017的智能客服公司!




   LSTM 之父&Dalle Molle 人工智能研究所副主任Jürgen Schmidhube大胆预测,在未来几年人类将创造出具有灵长类动物智能的人工智能系统。而现阶段AI在行业发挥最大生产力更可能是在垂直行业,特别是客服行业,智能客服聊天机器人已经展示给世人强大的生产力。




  也许你已经被环信诸如“全球最大、国际领先、国内市场占有率第一...”等狂轰滥炸的晕头转向了,我也很懵逼啊,我只是一个新媒体小编,对于市场第一描述词汇量的匮乏我也很绝望啊...




   环信作为智能客服企业的先行者,基于自然语言处理和机器学习技术推出了环信智能客服机器人,辅助或代替人工客服精准回答常见或高频问题,降低企业客服人力成本。目前,环信在客服领域已经服务了58541家标杆客户,积累了人工智能在客户服务行业落地的大量最佳实践。





为什么环信在AI方面有领先优势,投资人说的对!






   主会场演讲嘉宾美国通用电气GE Transportation CTO Wesly Mukai谈到机器学习目前已经应用在美国铁路运输这种非常实际的领域中,为提高效率做出了很大贡献。Wesly先生会后来到环信展台深入了解国内智能客服机器人在客服行业的应用,他认真听取了环信客服聊天机器人的实现方式(单轮会话、多轮会话、人机协作...)和应用场景以及帮助客户取得的效果和成绩,他认为中国企业在智能客服行业的探索已经走在了世界前列,同时他很看好AI在垂直领域所爆发的强大生产力。





Wesly和环信员工谈笑风生(照片由Wesly先生私人翻译帮助拍摄提供)






   Citadel 首席人工智能官邓力发表了以“无监督学习的最新进展(Recent Advances in Unsupervised Learning)”为主题的演讲。他认为,聚类方法、GAN 和变分自编码器(VAE)等传统无监督学习方法关注的重点是对输入数据的结构建模。腾讯 AI Lab 副主任俞栋在大会上洞悉了语音识别领域的前沿研究,今日头条、第四范式等嘉宾们从语音交互领域、自然语言处理及人工智能平台等领域切入,详细解构了其在人工智能时代所实践的产业创新,展示出AI技术在不同领域产生的巨大价值及未来机遇。




   当下,科技和创新进入拐点式爆发,人工智能的浪潮席卷全球。在此背景下,GMIS 2017作为国内首次汇集起全球人工智能和机器人领域顶级专家的大会,中立、权威、系统地呈现了机器智能相关技术的前沿研究,为全球人工智能领域前沿专家学者共同探讨机器智能如何从技术转化成产品和应用提供了绝佳平台,同时深切关注人工智能未来能够解决哪些具体问题,及如何帮助人类智慧生活的体验得到提升。GMIS将对行业产生积极而深远的影响,并开启人工智能发展的新起点。




   最后提前剧透:环信联合Gartner即将在国内发布客服行业首个机器人选型报告《智能客服机器人之客户服务行业最佳实践》我会到处乱说么?
 
报告抢先看
 
   市场上关于机器人的分类很多,误区也有很多。往往人们会将客服机器人等同于聊天机器人,但客服机器人其实只是聊天机器人的一种。聊天机器人主要分为两个大类:闲聊机器人与Task Oriented 机器人。Task Oriented 机器人是以任务目的为导向的机器人,又包括个人助理机器人与客服机器人。





上表对闲聊机器人、个人助理机器人、客服机器人从解决问题领域、平台系统开放性、技术方案的角度进行了详细比较。





智能客服机器人概述与分类 查看全部
    近日,2017全球机器智能峰会(GMIS 2017)在北京圆满举行,47位重磅嘉宾带来的32场演讲、4个圆桌论坛、1场人机大战及5个主题Session轮番上演,使GMIS 2017成为聚焦人工智能及相关领域的顶级行业盛宴。说出来你可能不信,环信是唯一受邀参展GMIS 2017的智能客服公司!
001.jpg

   LSTM 之父&Dalle Molle 人工智能研究所副主任Jürgen Schmidhube大胆预测,在未来几年人类将创造出具有灵长类动物智能的人工智能系统。而现阶段AI在行业发挥最大生产力更可能是在垂直行业,特别是客服行业,智能客服聊天机器人已经展示给世人强大的生产力。
002.jpg

  也许你已经被环信诸如“全球最大、国际领先、国内市场占有率第一...”等狂轰滥炸的晕头转向了,我也很懵逼啊,我只是一个新媒体小编,对于市场第一描述词汇量的匮乏我也很绝望啊...
003.jpg

   环信作为智能客服企业的先行者,基于自然语言处理和机器学习技术推出了环信智能客服机器人,辅助或代替人工客服精准回答常见或高频问题,降低企业客服人力成本。目前,环信在客服领域已经服务了58541家标杆客户,积累了人工智能在客户服务行业落地的大量最佳实践。


004.jpg

为什么环信在AI方面有领先优势,投资人说的对!



005.jpg

   主会场演讲嘉宾美国通用电气GE Transportation CTO Wesly Mukai谈到机器学习目前已经应用在美国铁路运输这种非常实际的领域中,为提高效率做出了很大贡献。Wesly先生会后来到环信展台深入了解国内智能客服机器人在客服行业的应用,他认真听取了环信客服聊天机器人的实现方式(单轮会话、多轮会话、人机协作...)和应用场景以及帮助客户取得的效果和成绩,他认为中国企业在智能客服行业的探索已经走在了世界前列,同时他很看好AI在垂直领域所爆发的强大生产力。


006.jpg

Wesly和环信员工谈笑风生(照片由Wesly先生私人翻译帮助拍摄提供)



007.jpg

   Citadel 首席人工智能官邓力发表了以“无监督学习的最新进展(Recent Advances in Unsupervised Learning)”为主题的演讲。他认为,聚类方法、GAN 和变分自编码器(VAE)等传统无监督学习方法关注的重点是对输入数据的结构建模。腾讯 AI Lab 副主任俞栋在大会上洞悉了语音识别领域的前沿研究,今日头条、第四范式等嘉宾们从语音交互领域、自然语言处理及人工智能平台等领域切入,详细解构了其在人工智能时代所实践的产业创新,展示出AI技术在不同领域产生的巨大价值及未来机遇。
008jpg.jpg

   当下,科技和创新进入拐点式爆发,人工智能的浪潮席卷全球。在此背景下,GMIS 2017作为国内首次汇集起全球人工智能和机器人领域顶级专家的大会,中立、权威、系统地呈现了机器智能相关技术的前沿研究,为全球人工智能领域前沿专家学者共同探讨机器智能如何从技术转化成产品和应用提供了绝佳平台,同时深切关注人工智能未来能够解决哪些具体问题,及如何帮助人类智慧生活的体验得到提升。GMIS将对行业产生积极而深远的影响,并开启人工智能发展的新起点。
009.jpg

   最后提前剧透:环信联合Gartner即将在国内发布客服行业首个机器人选型报告《智能客服机器人之客户服务行业最佳实践》我会到处乱说么?
 
报告抢先看
 
   市场上关于机器人的分类很多,误区也有很多。往往人们会将客服机器人等同于聊天机器人,但客服机器人其实只是聊天机器人的一种。聊天机器人主要分为两个大类:闲聊机器人与Task Oriented 机器人。Task Oriented 机器人是以任务目的为导向的机器人,又包括个人助理机器人与客服机器人。

010.jpg

上表对闲聊机器人、个人助理机器人、客服机器人从解决问题领域、平台系统开放性、技术方案的角度进行了详细比较。

011.jpg

智能客服机器人概述与分类
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP-15.微信-在其它设备登录 环信 仿微信 郭永峰

开发讨论郭永峰 发表了文章 • 3558 次浏览 • 2017-05-16 11:39 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版
【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建
【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能
【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能
【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录
【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出
 
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
15.微信-在其它设备登录 查看全部
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP- 14.微信-主动退出 仿微信 环信 郭永峰

开发讨论郭永峰 发表了文章 • 2227 次浏览 • 2017-05-16 11:35 • 来自相关话题

接上篇
【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版
【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建
【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能
【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能
【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录
 
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
 14.微信-主动退出 查看全部
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP- 13.微信-自动登录 环信 仿微信 郭永峰

开发讨论郭永峰 发表了文章 • 2248 次浏览 • 2017-05-16 11:29 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解) 
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版
【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建
【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能
【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能
 
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
13.微信-自动登录 查看全部
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP- 12.微信-登录功能 环信 仿微信 郭永峰

开发讨论郭永峰 发表了文章 • 2471 次浏览 • 2017-05-16 11:25 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版
【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建
【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能
 
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
12.微信-登录功能 查看全部
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP-11.微信-注册功能 仿微信 环信 郭永峰

开发讨论郭永峰 发表了文章 • 2359 次浏览 • 2017-05-16 11:21 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版
【视频教程+源码】基于环信IM做一个仿微信APP-10.微信-主界面搭建
 
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
11.微信-注册功能 查看全部
0
评论

【视频教程+源码】基于环信IM做一个仿微信APP-09.微信-登录界面排版 郭永峰 环信 仿微信

开发讨论郭永峰 发表了文章 • 3095 次浏览 • 2017-05-16 11:10 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
09.微信-登录界面排版 查看全部
1
评论

【视频教程+源码】基于环信IM做一个仿微信APP-08.微信-集成环信SDK 环信 仿微信 郭永峰

开发讨论郭永峰 发表了文章 • 3861 次浏览 • 2017-05-16 11:04 • 来自相关话题

接上篇

【视频教程+源码】基于环信IM做一个仿微信APP-01.即时通讯简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-02.XMPP简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-03.XMPP实现即时通信的准备工作(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-04.环信简介(了解)
【视频教程+源码】基于环信IM做一个仿微信APP-05.集成环信的前提准备(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-06.环信SDK的版本的区别(掌握)
【视频教程+源码】基于环信IM做一个仿微信APP-07.微信-项目创建及代码目录结构规范(MVC)
 
我是郭永峰,本套课程基于环信IM教大家如何做一个类似微信的APP,只用于技术交流,请勿用于任何商业用途。​
 
08.微信-集成环信SDK 查看全部
0
评论

环信入选《创业家》最具价值企业服务商推荐榜,听经纬熊飞聊怎样才是靠谱的企业级生意! 新闻资讯 环信

开发讨论新闻资讯 发表了文章 • 2374 次浏览 • 2017-04-24 10:59 • 来自相关话题

为了预见中国企业级服务市场即将诞生的独角兽,为了给《创业家》黑马用户推荐最优质的企业级服务商。创业家I黑马梳理了《冲刺期最具服务价值的企业服务商推荐榜》,环信凭借优秀的产品能力和领先的市场占有率入选榜单...企业级服务市场究竟该如何看待,记者近日接触经纬中国投资董事熊飞先生听他讲述看待行业的方式,这篇口述文章也不失为大家看待行业的一种可参考性方向。





经纬中国投资董事熊飞先生

口述 | 熊飞
采访 | 李阳林
整理 | 张一、李书娜

   首先,我认为(企业服务)行业是非常健康的。对于很多2C的需求,比较容易被大众所理解,因为大部分人都是用户;在2B领域,HR系统所提高HR的效率,一年价值是好几百万,所以创造的价值是很实在的。这是这个行业作为一个系统性的风口起来的一个机会。

   在中国,推动企业级服务领域发展的原因主要有两个:一、人力成本的持续上涨;二、中国经济进入新常态,商品市场供大于求导致对于效率的要求提高。

   先说人力成本。人力成本应该说是企业级服务发展的动力,中国企业发展已进入第一个拐点,这个拐点像六七年前一个人,每月三千块,一个电脑五千块。现在反过来,一个人每月五千块,一台电脑二千、二千五。企业主很理性,原来不用(工具)觉得贵,现在不用就是傻,人越来越贵,IT越来越便宜。

   再看经济模式转型。5年前中国经济的增长方式比较粗放,最近两三年系统性供大于求。以前的企业发展都是拉贷款扩产能,人越多体量越大。现在的市场已经变成了各行各业供大于求、毛利下降,各种各样的产品卖不出去,反过来人力成本还在涨。所以企业主都希望是不是能有一个工具,能够让100人干150人的活。这个市场客观需求在爆发。
 
判断是否健康的三个标准
    三个判断标准在上述大背景下判断一家企业级服务公司是否健康,主要看:一、客户规模的大小;二、客户续费率的高低;三、公司的月、年盈亏是否平衡。
   第一:先看供应端企业的大小。企业级服务行业的马太效应比2C要强,巨头们通常资源广阔。但只有这些还不行。在企业级领域行业核心的本质是门槛、有时间的积累。一方面包括产品相对比较复杂;另一方面它需要用户的锤炼,是在跟用户互动的过程中,根据用户反馈去优化产品,包括各种配置,销售也是一个时间积累好的售前,好的实施,好的客户成功。要懂这个领域,不是从零开始,而是需要有很强的行业背景。

   市场是后知后觉的,两年前还是企业服务元年,我们喊要中大型客户。很多人都不相信,都去做中小型客户。原来做中大型客户的公司五六千万收入,亏个两三千万,大家觉得SaaS穷途末路,觉得这个东西不挣钱,不能投;两年之后,再看,这个市场并不是那么回事。这是因为你的续约率在那儿。

   第二,看客户续费率,用户续费率低于80%,基本上就是一个不太靠谱的生意。举个例子,为什么会要求这么高?我当年在一家零售交易平台(现已成为全球最大的零售交易平台),它的用户续费率是百分之六十几,什么概率?看上去挺高的,三年之后就没啥了,65%×65%×65%,剩不了什么?第二年就剩30%多,第三年剩20%多,你今年好不容易获取很多客户,三年之后这些客户剩二十几个了,这不是一个SaaS所谓长需的生意。

   比如:某国内领先Saas公司是90%多的用户续费率,超过100%的金额续费率。其实做大客户服务就符合这么高的续费率,不是说只为了做大客户,而是说大客户的经济性很好,就好像实现梦想有各种方式,做大客户这个方式是实现续约率、实现价值的好生意。

第三,看公司能否实现单月盈亏平衡、全年盈亏平衡。

   中国Saas公司开始系统性的进入单月盈亏平衡,全年盈亏平衡。何谓单月盈亏平衡或全年盈亏平衡呢?SaaS收费模式是不同于做其它服务。原来一个三百万的单,现在第一年只能收三五十万,因为客户不太相信,只想先试试,供应端可能连三五十万都收不到,只有客户用好了才会持续买,如果不续约,那客户就流失了。理想性的说,金额续约率百分之百的时候,那么在开始之前企业就已经收到钱了。

   续约的维护成本是新销售的五分之一到十分之一。在美国有一个统计,平均你拿新销售一块钱的时候,所有的营销成本加在一起,续约的成本大概两毛钱、一毛七八,因为需求方跟供应方建立联系,需求方用得不错,客户成功,定期跟供应方沟通,帮需求方解决一些问题,收取费用。在这样的过程中,难免会增购其它服务。所以经济性就显著。 
中国企业级服务公司的未来
 
   现在是Sass B、C轮公司投资的黄金期,中晚期投资Saas黄金期。为什么是中后期?
 
   在美国,企业服务从时间上划分有三拨:七十年代第一拨,微软,现在是四千亿美金的公司;第二拨是90年代末到2000年,所谓的云计算,如今也算是几十亿到五百亿美金公司;第三拨刚刚开始,中国是三拨合为一拨,三年前没有人谈企业服务,现在所有人在谈企业服务。

   如果和美国市场对照,在中国当前这样的环境下,其实是可以出现美国的SAP、微软这样的公司,非常像十五年前2C领域。比如说携程的市值是Priceline、Expedia的总和,为什么?因为在美国爆发互联网旅游的时候,旅行社已经很强大了,所以美国的线上做不起来。但是在中国线下旅行社不强,线上也没有,所以呢,有市场发展空间,所以中国的线上旅行社就发展起来了。再比如,淘宝在中国就比eBay在美国要强大的多了,很大一部分原因是因为美国线下商业形态很成熟,发展空间没那么大。

   现在对照企业级服务也是如此。中国企业级服务市场没有SAP,没有相对成熟的企业级服务公司。所以说中国企业级服务公司发展空间很大,没有天花板。现在的北森、销售易、环信,很可能就是未来各个垂直领域的微软和SAP。当然他们最终能不能做到三五千亿或者两三千亿美金,就看它未来的凶悍生长的能力。

   未来三到五年之内,一定有估值超一百亿到二百亿人民币的公司出现,十年之内,一定有五百到一千亿人民币估值的公司出现。为什么敢这么说?中国现在最大的SaaS公司,今年确认的收入大概在三到四亿,每年还在上升。

具服务价值的企业服务商推荐榜
 
   为了预见这个市场即将诞生的独角兽,为了给黑马用户推荐最优质的企业级服务商。我们梳理了《冲刺期最具服务价值的企业服务商推荐榜》,排名不分先后,不单一的从估值角度出发,选取C轮、D轮、新三板企业样本,成熟的上市公司不进入参选范畴,由投资人及行业人士推荐,上一轮融资规模。

   我们将请这些未来的独角兽,为黑马及广大用户带来最有效的企业服务领域内的创业知识。黑马学吧将会邀请榜单里的18位成员,成立企业级理事会,位大家带来“企业级创业十八招”,敬请期待。

   本次榜单成型,感谢蓝海通讯创始人何晓阳、六度人和创始人张星亮、黑马企业级服务分会秘书长万涛、环信唐大欢、北森市场副总裁高燕、销售易市场副总裁Joyce在专业上的支持









 本文系创业&黑马原创发布,策划内容创业营,未经授权,转载必究。推荐关注i黑马微信公号(ID:iheima)。
环信成立于2013年4月,是一家国内领先的企业级软件服务提供商,于2016年荣膺“Gartner 2016 Cool Vendor”。产品包括国内上线最早规模最大的即时通讯云平台——环信即时通讯云,以及移动端最佳实践的全媒体智能云客服平台——环信移动客服。截至2016年底,环信即时通讯云共服务了130176家APP客户。环信移动客服共服务了58541家企业客户,现已覆盖包括保险、证券、银行、电商、教育、O2O等领域的众多标杆企业,包括泰康在线、中意人寿、中信证券、国美在线、优信二手车、新东方、新浪微博、链家、58到家、神州专车等典型用户。 查看全部
为了预见中国企业级服务市场即将诞生的独角兽,为了给《创业家》黑马用户推荐最优质的企业级服务商。创业家I黑马梳理了《冲刺期最具服务价值的企业服务商推荐榜》,环信凭借优秀的产品能力和领先的市场占有率入选榜单...企业级服务市场究竟该如何看待,记者近日接触经纬中国投资董事熊飞先生听他讲述看待行业的方式,这篇口述文章也不失为大家看待行业的一种可参考性方向。


001.jpg

经纬中国投资董事熊飞先生


口述 | 熊飞
采访 | 李阳林
整理 | 张一、李书娜

   首先,我认为(企业服务)行业是非常健康的。对于很多2C的需求,比较容易被大众所理解,因为大部分人都是用户;在2B领域,HR系统所提高HR的效率,一年价值是好几百万,所以创造的价值是很实在的。这是这个行业作为一个系统性的风口起来的一个机会。

   在中国,推动企业级服务领域发展的原因主要有两个:一、人力成本的持续上涨;二、中国经济进入新常态,商品市场供大于求导致对于效率的要求提高。

   先说人力成本。人力成本应该说是企业级服务发展的动力,中国企业发展已进入第一个拐点,这个拐点像六七年前一个人,每月三千块,一个电脑五千块。现在反过来,一个人每月五千块,一台电脑二千、二千五。企业主很理性,原来不用(工具)觉得贵,现在不用就是傻,人越来越贵,IT越来越便宜。

   再看经济模式转型。5年前中国经济的增长方式比较粗放,最近两三年系统性供大于求。以前的企业发展都是拉贷款扩产能,人越多体量越大。现在的市场已经变成了各行各业供大于求、毛利下降,各种各样的产品卖不出去,反过来人力成本还在涨。所以企业主都希望是不是能有一个工具,能够让100人干150人的活。这个市场客观需求在爆发。
 
判断是否健康的三个标准
    三个判断标准在上述大背景下判断一家企业级服务公司是否健康,主要看:一、客户规模的大小;二、客户续费率的高低;三、公司的月、年盈亏是否平衡。
   第一:先看供应端企业的大小。企业级服务行业的马太效应比2C要强,巨头们通常资源广阔。但只有这些还不行。在企业级领域行业核心的本质是门槛、有时间的积累。一方面包括产品相对比较复杂;另一方面它需要用户的锤炼,是在跟用户互动的过程中,根据用户反馈去优化产品,包括各种配置,销售也是一个时间积累好的售前,好的实施,好的客户成功。要懂这个领域,不是从零开始,而是需要有很强的行业背景。

   市场是后知后觉的,两年前还是企业服务元年,我们喊要中大型客户。很多人都不相信,都去做中小型客户。原来做中大型客户的公司五六千万收入,亏个两三千万,大家觉得SaaS穷途末路,觉得这个东西不挣钱,不能投;两年之后,再看,这个市场并不是那么回事。这是因为你的续约率在那儿。

   第二,看客户续费率,用户续费率低于80%,基本上就是一个不太靠谱的生意。举个例子,为什么会要求这么高?我当年在一家零售交易平台(现已成为全球最大的零售交易平台),它的用户续费率是百分之六十几,什么概率?看上去挺高的,三年之后就没啥了,65%×65%×65%,剩不了什么?第二年就剩30%多,第三年剩20%多,你今年好不容易获取很多客户,三年之后这些客户剩二十几个了,这不是一个SaaS所谓长需的生意。

   比如:某国内领先Saas公司是90%多的用户续费率,超过100%的金额续费率。其实做大客户服务就符合这么高的续费率,不是说只为了做大客户,而是说大客户的经济性很好,就好像实现梦想有各种方式,做大客户这个方式是实现续约率、实现价值的好生意。

第三,看公司能否实现单月盈亏平衡、全年盈亏平衡。

   中国Saas公司开始系统性的进入单月盈亏平衡,全年盈亏平衡。何谓单月盈亏平衡或全年盈亏平衡呢?SaaS收费模式是不同于做其它服务。原来一个三百万的单,现在第一年只能收三五十万,因为客户不太相信,只想先试试,供应端可能连三五十万都收不到,只有客户用好了才会持续买,如果不续约,那客户就流失了。理想性的说,金额续约率百分之百的时候,那么在开始之前企业就已经收到钱了。

   续约的维护成本是新销售的五分之一到十分之一。在美国有一个统计,平均你拿新销售一块钱的时候,所有的营销成本加在一起,续约的成本大概两毛钱、一毛七八,因为需求方跟供应方建立联系,需求方用得不错,客户成功,定期跟供应方沟通,帮需求方解决一些问题,收取费用。在这样的过程中,难免会增购其它服务。所以经济性就显著。 
中国企业级服务公司的未来
 
   现在是Sass B、C轮公司投资的黄金期,中晚期投资Saas黄金期。为什么是中后期?
 
   在美国,企业服务从时间上划分有三拨:七十年代第一拨,微软,现在是四千亿美金的公司;第二拨是90年代末到2000年,所谓的云计算,如今也算是几十亿到五百亿美金公司;第三拨刚刚开始,中国是三拨合为一拨,三年前没有人谈企业服务,现在所有人在谈企业服务。

   如果和美国市场对照,在中国当前这样的环境下,其实是可以出现美国的SAP、微软这样的公司,非常像十五年前2C领域。比如说携程的市值是Priceline、Expedia的总和,为什么?因为在美国爆发互联网旅游的时候,旅行社已经很强大了,所以美国的线上做不起来。但是在中国线下旅行社不强,线上也没有,所以呢,有市场发展空间,所以中国的线上旅行社就发展起来了。再比如,淘宝在中国就比eBay在美国要强大的多了,很大一部分原因是因为美国线下商业形态很成熟,发展空间没那么大。

   现在对照企业级服务也是如此。中国企业级服务市场没有SAP,没有相对成熟的企业级服务公司。所以说中国企业级服务公司发展空间很大,没有天花板。现在的北森、销售易、环信,很可能就是未来各个垂直领域的微软和SAP。当然他们最终能不能做到三五千亿或者两三千亿美金,就看它未来的凶悍生长的能力。

   未来三到五年之内,一定有估值超一百亿到二百亿人民币的公司出现,十年之内,一定有五百到一千亿人民币估值的公司出现。为什么敢这么说?中国现在最大的SaaS公司,今年确认的收入大概在三到四亿,每年还在上升。

具服务价值的企业服务商推荐榜
 
   为了预见这个市场即将诞生的独角兽,为了给黑马用户推荐最优质的企业级服务商。我们梳理了《冲刺期最具服务价值的企业服务商推荐榜》,排名不分先后,不单一的从估值角度出发,选取C轮、D轮、新三板企业样本,成熟的上市公司不进入参选范畴,由投资人及行业人士推荐,上一轮融资规模。

   我们将请这些未来的独角兽,为黑马及广大用户带来最有效的企业服务领域内的创业知识。黑马学吧将会邀请榜单里的18位成员,成立企业级理事会,位大家带来“企业级创业十八招”,敬请期待。

   本次榜单成型,感谢蓝海通讯创始人何晓阳、六度人和创始人张星亮、黑马企业级服务分会秘书长万涛、环信唐大欢、北森市场副总裁高燕、销售易市场副总裁Joyce在专业上的支持
002.jpg


003.jpg

 本文系创业&黑马原创发布,策划内容创业营,未经授权,转载必究。推荐关注i黑马微信公号(ID:iheima)。
环信成立于2013年4月,是一家国内领先的企业级软件服务提供商,于2016年荣膺“Gartner 2016 Cool Vendor”。产品包括国内上线最早规模最大的即时通讯云平台——环信即时通讯云,以及移动端最佳实践的全媒体智能云客服平台——环信移动客服。截至2016年底,环信即时通讯云共服务了130176家APP客户。环信移动客服共服务了58541家企业客户,现已覆盖包括保险、证券、银行、电商、教育、O2O等领域的众多标杆企业,包括泰康在线、中意人寿、中信证券、国美在线、优信二手车、新东方、新浪微博、链家、58到家、神州专车等典型用户。
0
评论

环信生曦:全媒体客服如何做好信息共享设计 环信 全媒体客服 生曦

开发讨论beyond 发表了文章 • 1922 次浏览 • 2017-03-22 15:47 • 来自相关话题

   经过数十年的沉淀,国内客户中心正从话音呼叫中心、网页端客服向全媒体架构的统一客服平台升级,有调研机构预计2017年全媒体客户中心将进入高速发展期。作为传统呼叫中心的继任者,全媒体客户中心将在技术上如何演进?未来形态又将如何?以环信为首的中国创新者们正在慢慢给出答案。近日,环信设计组负责人生曦撰文浅谈全媒体客服如何做好信息共享设计,和大家一起摸索全媒体客服设计中的痛点和难点。





是的,您没看错,上图当年那位最帅的青涩骚年就是现在的老司机生曦

什么是服务设计?与产品设计有哪些不同?

   服务设计是基于某个行业的服务需要,以服务流程中的参与者为核心,所涉及的服务场景、交互逻辑以及操作方式等进行的设计。因此,服务设计的衡量标准也是以参与者的服务效率和使用体验进行设定。与产品设计的不同之处在于,服务设计所面对的参与者不仅是为用户,也包含服务人员本身;服务设计不仅是通过产品交互界面(设备的操作界面),也可能会有除产品以外的服务(话术访谈等)。因此对设计师有更高的要求,需要对完成的服务场景有行业化的认识,对客户服务有深度的行业理解。

什么是全媒体服务?

   某种行业服务过程中,需要通过不同方式进行服务接入、统一分配服务资源、合理进行服务转化等一系列服务操作中,包含了多种渠道服务的支持。例如,传统的医疗服务行业,通过挂号,缴费,分诊,就医等全线下完成服务。逐渐转化为通过电话、网站、APP等多渠道进行预约、挂号等,选择进行线下就医,或者在线完成医疗咨询、诊治等,后续进行在线回访以及调查等。典型的传统转向数字化的全渠道全媒体服务模式。

全媒体服务如何实现信息共享?

   全媒体服务根据每个行业的需要,有不同的服务场景和流程定制,信息同步高效是全媒体服务的基础,设计通常会选择业务信息聚合节点来完成信息聚合。以电商为例,通常以消费者(用户)为维度进行信息聚合,客户服务人员可以通过用户聚合查看到相关的互动记录信息,进行连续不间断的服务支持;以客户服务人员本身作为信息聚合节点,查看相关服务操作过程,来管理和监控服务流程;可以通过服务信息单元进行聚合节点,查看相关涉及的消费者与客户服务人员的行为,来进行服务跟踪和追溯服务状态。当然还有更多信息聚合节点,可以根据行业服务需要进行定制和管理。通过梳理具有行业目的的信息线索,进行节点聚合,是在服务设计中实现信息共享方式最有效的办法。

如何确保和渠道服务能力畅通连贯?

   现实生活中,我们经常与遇到一些骚扰的营销电话,其中或多或少可能之前是我们之前有所涉及的服务,但是也许目前不需要的就成了骚扰电话。而有一些可能就成为我们下一次消费的开始,这是渠道营销服务的一种方式。所以营销服务的节点(信息线索)是根据客户信息聚合,如果还有其他渠道方面的节点,我们也许会收到短信或者APP等消息推送,也可能会更有效的完成此次营销服务。所以服务节点聚合的效率是保证服务与服务之间准确、连贯、有效的关键因素。例如,环信移动客服,通过对全渠道消息、语音统一接入,进行服务资源调度与分配,将信息整合为几大重要的信息聚合节点,客户资料中心、云数据计算分析、全媒体接入等等,为客户提供在线服务、管理、追踪、营销等组合业务,高效且系统化的满足行业需求定制。

新生服务如何可以融入现有服务设计中?

   这个非常有挑战性的话题,作为设计师,不断的进行设计创新,完善产品、优化服务是产品生命的保证。而技术新模式的加入也在不断改变和推翻低效的工具和实现方式。这不经意间就会落后的技术时代,是与客户一起深入行业发展,共同创新摸索产品的必经之路。现在正在追捧的VR/AR技术,以及AI等人工智能在服务领域的渗透,产品设计也在悄然发生变革。当然,对技术能力的验证不能闭门造车,环信移动客服智能机器人系统中,悄悄然无声融入的智能业务场景应答服务已经在很多大客户身上得到了应用,并迸发出了强大的生产力。在现有产品系统框架的基础上逐渐结合AI与行业服务,以此为基础进行尝试与深入,逐渐可以成长和完善为新的利器。

最后安利一个服务流程设计工具:http://servicedesigntools.org/repository 查看全部
   经过数十年的沉淀,国内客户中心正从话音呼叫中心、网页端客服向全媒体架构的统一客服平台升级,有调研机构预计2017年全媒体客户中心将进入高速发展期。作为传统呼叫中心的继任者,全媒体客户中心将在技术上如何演进?未来形态又将如何?以环信为首的中国创新者们正在慢慢给出答案。近日,环信设计组负责人生曦撰文浅谈全媒体客服如何做好信息共享设计,和大家一起摸索全媒体客服设计中的痛点和难点。


0a7df6f.jpg

是的,您没看错,上图当年那位最帅的青涩骚年就是现在的老司机生曦


什么是服务设计?与产品设计有哪些不同?

   服务设计是基于某个行业的服务需要,以服务流程中的参与者为核心,所涉及的服务场景、交互逻辑以及操作方式等进行的设计。因此,服务设计的衡量标准也是以参与者的服务效率和使用体验进行设定。与产品设计的不同之处在于,服务设计所面对的参与者不仅是为用户,也包含服务人员本身;服务设计不仅是通过产品交互界面(设备的操作界面),也可能会有除产品以外的服务(话术访谈等)。因此对设计师有更高的要求,需要对完成的服务场景有行业化的认识,对客户服务有深度的行业理解。

什么是全媒体服务?

   某种行业服务过程中,需要通过不同方式进行服务接入、统一分配服务资源、合理进行服务转化等一系列服务操作中,包含了多种渠道服务的支持。例如,传统的医疗服务行业,通过挂号,缴费,分诊,就医等全线下完成服务。逐渐转化为通过电话、网站、APP等多渠道进行预约、挂号等,选择进行线下就医,或者在线完成医疗咨询、诊治等,后续进行在线回访以及调查等。典型的传统转向数字化的全渠道全媒体服务模式。

全媒体服务如何实现信息共享?

   全媒体服务根据每个行业的需要,有不同的服务场景和流程定制,信息同步高效是全媒体服务的基础,设计通常会选择业务信息聚合节点来完成信息聚合。以电商为例,通常以消费者(用户)为维度进行信息聚合,客户服务人员可以通过用户聚合查看到相关的互动记录信息,进行连续不间断的服务支持;以客户服务人员本身作为信息聚合节点,查看相关服务操作过程,来管理和监控服务流程;可以通过服务信息单元进行聚合节点,查看相关涉及的消费者与客户服务人员的行为,来进行服务跟踪和追溯服务状态。当然还有更多信息聚合节点,可以根据行业服务需要进行定制和管理。通过梳理具有行业目的的信息线索,进行节点聚合,是在服务设计中实现信息共享方式最有效的办法。

如何确保和渠道服务能力畅通连贯?

   现实生活中,我们经常与遇到一些骚扰的营销电话,其中或多或少可能之前是我们之前有所涉及的服务,但是也许目前不需要的就成了骚扰电话。而有一些可能就成为我们下一次消费的开始,这是渠道营销服务的一种方式。所以营销服务的节点(信息线索)是根据客户信息聚合,如果还有其他渠道方面的节点,我们也许会收到短信或者APP等消息推送,也可能会更有效的完成此次营销服务。所以服务节点聚合的效率是保证服务与服务之间准确、连贯、有效的关键因素。例如,环信移动客服,通过对全渠道消息、语音统一接入,进行服务资源调度与分配,将信息整合为几大重要的信息聚合节点,客户资料中心、云数据计算分析、全媒体接入等等,为客户提供在线服务、管理、追踪、营销等组合业务,高效且系统化的满足行业需求定制。

新生服务如何可以融入现有服务设计中?

   这个非常有挑战性的话题,作为设计师,不断的进行设计创新,完善产品、优化服务是产品生命的保证。而技术新模式的加入也在不断改变和推翻低效的工具和实现方式。这不经意间就会落后的技术时代,是与客户一起深入行业发展,共同创新摸索产品的必经之路。现在正在追捧的VR/AR技术,以及AI等人工智能在服务领域的渗透,产品设计也在悄然发生变革。当然,对技术能力的验证不能闭门造车,环信移动客服智能机器人系统中,悄悄然无声融入的智能业务场景应答服务已经在很多大客户身上得到了应用,并迸发出了强大的生产力。在现有产品系统框架的基础上逐渐结合AI与行业服务,以此为基础进行尝试与深入,逐渐可以成长和完善为新的利器。

最后安利一个服务流程设计工具:http://servicedesigntools.org/repository
0
评论

环信CEO刘俊彦荣获2016年度最具技术领导力人物奖 2016年度最具技术领导力人物奖 环信 刘俊彦

开发讨论新闻资讯 发表了文章 • 1956 次浏览 • 2017-03-13 10:22 • 来自相关话题

    作为英国伦敦大学的计算机硕士,有着17年程序员生涯的环信CEO凭借打造环信即时通讯云和环信移动客服两款现象级的SaaS产品获得由<互联网周刊>颁发的"2016年度最具技术领导力人物奖"。
 
   同时近期环信也在资本市场获得了极大的追捧,3月8日获得由经纬领投的C轮103000000元人民币融资。环信创始人、CEO刘俊彦表示:“本轮融资资金将用于环信BI和AI层的产品打磨、完善生态圈建设以及提升垂直行业解决方案能力,继续在追求卓越的技术道路上深耕。














  查看全部
    作为英国伦敦大学的计算机硕士,有着17年程序员生涯的环信CEO凭借打造环信即时通讯云和环信移动客服两款现象级的SaaS产品获得由<互联网周刊>颁发的"2016年度最具技术领导力人物奖"。
 
   同时近期环信也在资本市场获得了极大的追捧,3月8日获得由经纬领投的C轮103000000元人民币融资。环信创始人、CEO刘俊彦表示:“本轮融资资金将用于环信BI和AI层的产品打磨、完善生态圈建设以及提升垂直行业解决方案能力,继续在追求卓越的技术道路上深耕。
ea790d9dly1fdgtk2bckzj20m80ermyq.jpg


ea790d9dly1fdgtk0oavij20qo0zkk0j.jpg


ea790d9dly1fdgttvdvufj20zk0qok2g.jpg

 
3
评论

环信完成C轮1.03亿元融资,深耕BI和AI层! 环信 新闻资讯

开发讨论新闻资讯 发表了文章 • 3540 次浏览 • 2017-03-08 14:01 • 来自相关话题

3月8日,企业级软件服务提供商环信今日对外宣布,完成C轮103000000元人民币融资,本轮融资由经纬领投,银泰嘉禾跟投。环信创始人、CEO刘俊彦表示:“本轮融资资金将用于环信BI和AI层的产品打磨、完善生态圈建设以及提升垂直行业解决方案能力。”




环信CEO宣布完成由经纬领投的C轮1.03亿元融资
 
1. 顶级风投持续看好,资本、客户规模、产品、大客户等核心维度均领先行业。

一直将环信视为中国企业级服务潜在“独角兽”公司的经纬中国合伙人左凌烨表示:“客服是企业服务软件的最大市场之一。随着用户体验和技术演进,对客服产品提出更多挑战,包括全渠道,实时性,移动化和AI辅助等等。环信作为该领域的绝对领先的创业公司,率先满足这些需求,推出并不断优化其一流产品。先后获得泰康,中信证券,中意人寿,国美在线等多家国内500强标杆客户,年收入保持250%以上的增长,取得骄人的市场认可和业务增长。经纬非常看好客服市场在中国的前景,并坚信环信将保持势头,成为该领域的领军企业。”

环信目前有三条产品线,包括环信即时通讯云、环信移动客服和环信人工智能。在2013年成立之初,环信推出PaaS通讯能力平台“环信即时通讯云”,用即时通讯“连接人与人”。2015年环信推出SaaS客服云产品“环信移动客服”,用即时通讯“连接人与商业”。随后,环信移动客服又拓展包括微信、微博、网页端、呼叫中心等全渠道客服接入能力,帮助企业和消费者多点接触。

移动互联时代,当用户能够在任何时间、任意地点,跨渠道、跨媒体、跨平台联系商家获得服务以后,客服请求必然激增。“我们希望能通过人工智能解决商家日益增长的客服成本和海量客服请求之间的天然矛盾,人工智能显然是绝佳的手段。”刘俊彦这样评价人工智能产品线。从最初的连接人与人,到连接人与商业,再到如今的人工智能,环信的主线关键词一直都是“连接”。

截至2016年底,环信即时通讯云共服务了130176家APP客户。环信移动客服共服务了58541家企业客户,包括泰康在线、中意人寿、中信证券、国美在线、优信二手车、新东方、新浪微博、链家、58到家、神州专车等。

2. SaaS公司四个竞争层面,深耕细作BI和AI层,深度连接人与商业。

环信CEO刘俊彦认为如果从核心竞争力的角度,可以将SaaS企业的发展划分为四个层面。分为工具层、BI层(数据层)、生态圈和AI层。SaaS产品在工具层面的竞争将越来越难以差异化。BI层(数据层)是将工作流里产生的数据和知识变成产品。一旦用户的个性化数据和知识也变成了产品的一部分,用户的迁移成本将变得更高,这时企业才开始有了自己独特的竞争壁垒。SaaS企业的终极竞争层面是AI层,这是一个大趋势。

对于产品的优势,刘俊彦表示环信已经做到了工具层面的领先,在第二个层面即BI层面,环信也推出了相应的产品,如环信客户声音。环信客户声音是基于人工智能和大数据挖掘的客户体验透析产品。Gartner联合环信发布的《下一代客户服务软件趋势》报告显示:“客户声音(VOC)是企业有关客户体验管理(CEM)战略需要考量的核心维度。全媒体客服的最佳体验不仅是多渠道的接入,更重要的是跨渠道环境下的用户体验保证。”环信认为理解客户声音是保证客户体验的最重要一环,环信客户声音运用NLP(自然语言解析),深度学习等人工智能技术,对来自多种渠道的非结构化数据源进行客服业务的特征提取,主题聚类解析,情感分析建模。从而帮助企业挖掘和分析客户服务中的热点话题,发现服务运营问题,寻找畅销或问题产品,洞察销售机会。

在第四层即AI层面,环信推出了环信智能客服机器人和环信智能质检。环信客服机器人基于机器学习技术和自然语言处理,辅助、替代人工客服回答常见、高频的问题,从而降低人力成本。环信智能质检则是基于环信在线客服积累的各个领域的海量用户对话,提取出数百个客服对话特征,并用这些特征训练得到的几十种常见通用质检模型,从而将质检从过去人工、抽样,转变为自动、全面的工作。刘俊彦认为:“软件正在吃掉世界,而AI正在吃掉软件。客户服务行业因为其劳动力密集,有海量数据等特点,是现阶段AI能够真实落地并能产生巨大价值的几个行业之一。”

环信在客户互动这一主线上,从连接消费者,到客服的效率工具,已经完成主要的布局。下一步是紧紧把握住客户服务全面转向移动端,以及对话经济(Conversation As A Commerce)的新趋势,产品继续深耕细作,深度连接人与商业,全力发展BI和AI,引导SaaS客服行业的发展进入到一个新的层面。

3. 完善生态圈,推出五大垂直行业智能客户互动解决方案,服务好大型客户。

SaaS企业的核心竞争力的第三层是生态圈,只有建立生态圈,SaaS企业才能真正筑起足够高的竞争门槛。Salesforce目前拥有上百家企业在其force.com平台上进行软件、插件的开发,已形成自己的生态,其他公司基本无法与之抗衡。比如要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

只有完善生态圈推出行业垂直解决方案才能更好的服务大客户。环信已经陆续推出了针对五大垂直行业的智能客户互动解决方案,同时大客户战略的执行也初见成效。2016年环信平均客单价已经达到6.2万左右,2017年将会持续提升,预计将进入10万到20万区间。同时,在金融、证券、银行、教育等行业均实现了大客户的重要突破,签约了众多500强行业标杆客户,标志着中国的新兴SaaS企业,开始和中科软,恒生电子这样的传统大型IT系统供应商同场竞技,获得了主流大型传统企业的认可。

4. 诗和远方是中国的Salesforce,这一代明星SaaS公司是下代中国SaaS创业者的天花板。

中国企业级软件服务市场和北美市场最大的不同就是,在北美除了Salesforce和Oracle等巨头外,大部分SaaS企业只能做巨头看不上的中小客户市场,上升空间天花板明显。中国企业级服务市场既没有本土巨头,又因为国外巨头企业服务公司进入中国有天然政策壁垒,可谓既无内忧也无外患。所以包括客服云、销售云、HR云、财务云、协同云等核心企业级服务赛道这几年都在野蛮生长,各赛道格局也已初定,成长起来了一批明星公司。

环信的诗和远方是中国的Salesforce,中国这一代各个赛道的领先SaaS企业因为没有既有的天花板,都有可能最终成为中国的Salesforce,这一批企业也将成为下一代中国SaaS创业者的天花板。 查看全部
3月8日,企业级软件服务提供商环信今日对外宣布,完成C轮103000000元人民币融资,本轮融资由经纬领投,银泰嘉禾跟投。环信创始人、CEO刘俊彦表示:“本轮融资资金将用于环信BI和AI层的产品打磨、完善生态圈建设以及提升垂直行业解决方案能力。”

QQ图片20170308135918.png

环信CEO宣布完成由经纬领投的C轮1.03亿元融资
 
1. 顶级风投持续看好,资本、客户规模、产品、大客户等核心维度均领先行业。

一直将环信视为中国企业级服务潜在“独角兽”公司的经纬中国合伙人左凌烨表示:“客服是企业服务软件的最大市场之一。随着用户体验和技术演进,对客服产品提出更多挑战,包括全渠道,实时性,移动化和AI辅助等等。环信作为该领域的绝对领先的创业公司,率先满足这些需求,推出并不断优化其一流产品。先后获得泰康,中信证券,中意人寿,国美在线等多家国内500强标杆客户,年收入保持250%以上的增长,取得骄人的市场认可和业务增长。经纬非常看好客服市场在中国的前景,并坚信环信将保持势头,成为该领域的领军企业。”

环信目前有三条产品线,包括环信即时通讯云、环信移动客服和环信人工智能。在2013年成立之初,环信推出PaaS通讯能力平台“环信即时通讯云”,用即时通讯“连接人与人”。2015年环信推出SaaS客服云产品“环信移动客服”,用即时通讯“连接人与商业”。随后,环信移动客服又拓展包括微信、微博、网页端、呼叫中心等全渠道客服接入能力,帮助企业和消费者多点接触。

移动互联时代,当用户能够在任何时间、任意地点,跨渠道、跨媒体、跨平台联系商家获得服务以后,客服请求必然激增。“我们希望能通过人工智能解决商家日益增长的客服成本和海量客服请求之间的天然矛盾,人工智能显然是绝佳的手段。”刘俊彦这样评价人工智能产品线。从最初的连接人与人,到连接人与商业,再到如今的人工智能,环信的主线关键词一直都是“连接”。

截至2016年底,环信即时通讯云共服务了130176家APP客户。环信移动客服共服务了58541家企业客户,包括泰康在线、中意人寿、中信证券、国美在线、优信二手车、新东方、新浪微博、链家、58到家、神州专车等。

2. SaaS公司四个竞争层面,深耕细作BI和AI层,深度连接人与商业。

环信CEO刘俊彦认为如果从核心竞争力的角度,可以将SaaS企业的发展划分为四个层面。分为工具层、BI层(数据层)、生态圈和AI层。SaaS产品在工具层面的竞争将越来越难以差异化。BI层(数据层)是将工作流里产生的数据和知识变成产品。一旦用户的个性化数据和知识也变成了产品的一部分,用户的迁移成本将变得更高,这时企业才开始有了自己独特的竞争壁垒。SaaS企业的终极竞争层面是AI层,这是一个大趋势。

对于产品的优势,刘俊彦表示环信已经做到了工具层面的领先,在第二个层面即BI层面,环信也推出了相应的产品,如环信客户声音。环信客户声音是基于人工智能和大数据挖掘的客户体验透析产品。Gartner联合环信发布的《下一代客户服务软件趋势》报告显示:“客户声音(VOC)是企业有关客户体验管理(CEM)战略需要考量的核心维度。全媒体客服的最佳体验不仅是多渠道的接入,更重要的是跨渠道环境下的用户体验保证。”环信认为理解客户声音是保证客户体验的最重要一环,环信客户声音运用NLP(自然语言解析),深度学习等人工智能技术,对来自多种渠道的非结构化数据源进行客服业务的特征提取,主题聚类解析,情感分析建模。从而帮助企业挖掘和分析客户服务中的热点话题,发现服务运营问题,寻找畅销或问题产品,洞察销售机会。

在第四层即AI层面,环信推出了环信智能客服机器人和环信智能质检。环信客服机器人基于机器学习技术和自然语言处理,辅助、替代人工客服回答常见、高频的问题,从而降低人力成本。环信智能质检则是基于环信在线客服积累的各个领域的海量用户对话,提取出数百个客服对话特征,并用这些特征训练得到的几十种常见通用质检模型,从而将质检从过去人工、抽样,转变为自动、全面的工作。刘俊彦认为:“软件正在吃掉世界,而AI正在吃掉软件。客户服务行业因为其劳动力密集,有海量数据等特点,是现阶段AI能够真实落地并能产生巨大价值的几个行业之一。”

环信在客户互动这一主线上,从连接消费者,到客服的效率工具,已经完成主要的布局。下一步是紧紧把握住客户服务全面转向移动端,以及对话经济(Conversation As A Commerce)的新趋势,产品继续深耕细作,深度连接人与商业,全力发展BI和AI,引导SaaS客服行业的发展进入到一个新的层面。

3. 完善生态圈,推出五大垂直行业智能客户互动解决方案,服务好大型客户。

SaaS企业的核心竞争力的第三层是生态圈,只有建立生态圈,SaaS企业才能真正筑起足够高的竞争门槛。Salesforce目前拥有上百家企业在其force.com平台上进行软件、插件的开发,已形成自己的生态,其他公司基本无法与之抗衡。比如要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

只有完善生态圈推出行业垂直解决方案才能更好的服务大客户。环信已经陆续推出了针对五大垂直行业的智能客户互动解决方案,同时大客户战略的执行也初见成效。2016年环信平均客单价已经达到6.2万左右,2017年将会持续提升,预计将进入10万到20万区间。同时,在金融、证券、银行、教育等行业均实现了大客户的重要突破,签约了众多500强行业标杆客户,标志着中国的新兴SaaS企业,开始和中科软,恒生电子这样的传统大型IT系统供应商同场竞技,获得了主流大型传统企业的认可。

4. 诗和远方是中国的Salesforce,这一代明星SaaS公司是下代中国SaaS创业者的天花板。

中国企业级软件服务市场和北美市场最大的不同就是,在北美除了Salesforce和Oracle等巨头外,大部分SaaS企业只能做巨头看不上的中小客户市场,上升空间天花板明显。中国企业级服务市场既没有本土巨头,又因为国外巨头企业服务公司进入中国有天然政策壁垒,可谓既无内忧也无外患。所以包括客服云、销售云、HR云、财务云、协同云等核心企业级服务赛道这几年都在野蛮生长,各赛道格局也已初定,成长起来了一批明星公司。

环信的诗和远方是中国的Salesforce,中国这一代各个赛道的领先SaaS企业因为没有既有的天花板,都有可能最终成为中国的Salesforce,这一批企业也将成为下一代中国SaaS创业者的天花板。
1
评论

两会专题报道:从北京到天津,他们改变了什么?看环信天津研发中心负责人赵贵斌的花样人生 两会 赵贵斌 环信

开发讨论beyond 发表了文章 • 2796 次浏览 • 2017-03-06 14:30 • 来自相关话题

    年近40岁的北京人赵贵斌,去年10月带着组建环信天津研发中心的任务来到天津于家堡,和同事们一起,让已经在北京生根的环信延伸到天津。早在来天津之前,这名清华北大双料高材生就已经属于“外溢”的人才,曾跟随妻子在宁波工作过几年。他是跟随着工作走,也是跟随着自己的心走。 如今,环信正在快速地发展,招聘到更多的人才,是他面临的最大难题。




  对于家堡这个地方,赵贵斌有着很高的评价。他和妻子居住在公寓,周围有购物中心、菜市场、高铁站,干什么都很方便。平时闲下来,赵贵斌还喜欢在河边散步、跑步。这周边人少,安静,不堵车,他说在这样的环境中,他能够很快地高效率地投入到工作,更能有许多独立思考问题的时间。 




  如今,赵贵斌的公司正在快速地发展,招聘到更多的人才,是他们面临的最大难题。赵贵斌希望,那些盲目地想要去“北漂”的年轻人,应该好好地算一笔账,说不定算明白之后就会发现,北京之外,还有更多更明智的选择。 (来自:腾讯大燕网·天津站) 查看全部

0.jpg

    年近40岁的北京人赵贵斌,去年10月带着组建环信天津研发中心的任务来到天津于家堡,和同事们一起,让已经在北京生根的环信延伸到天津。早在来天津之前,这名清华北大双料高材生就已经属于“外溢”的人才,曾跟随妻子在宁波工作过几年。他是跟随着工作走,也是跟随着自己的心走。 如今,环信正在快速地发展,招聘到更多的人才,是他面临的最大难题。
0_(1).jpg

  对于家堡这个地方,赵贵斌有着很高的评价。他和妻子居住在公寓,周围有购物中心、菜市场、高铁站,干什么都很方便。平时闲下来,赵贵斌还喜欢在河边散步、跑步。这周边人少,安静,不堵车,他说在这样的环境中,他能够很快地高效率地投入到工作,更能有许多独立思考问题的时间。 
0_(2).jpg

  如今,赵贵斌的公司正在快速地发展,招聘到更多的人才,是他们面临的最大难题。赵贵斌希望,那些盲目地想要去“北漂”的年轻人,应该好好地算一笔账,说不定算明白之后就会发现,北京之外,还有更多更明智的选择。 (来自:腾讯大燕网·天津站)
1
评论

产品同质化?SaaS制胜之道本就不在工具层! 刘俊彦 SaaS 环信

开发讨论beyond 发表了文章 • 1862 次浏览 • 2017-03-01 10:42 • 来自相关话题

    走进环信的办公室,一层看起来人数不算太多,都是市场和销售人员,另一层的工位则几乎全满,全都是技术人员。当我诧异于环信的技术团队如此庞大的时候,刘俊彦仿佛看穿了我的心思,这位CEO笑着说到:“我们相信,技术实力能让我们走得更远。”

   环信从2013年成立,从一开始的“即时通讯云”到移动客服,再到入选“Gartner 2016Cool Vendor”,环信的成绩着实令人惊诧。直到上周五,牛透社获取了来自Gartner和环信联合发布的《下一代客户服务软件趋势》报告,我们开始重新审视客服领域,重新审视带给我们诸多震撼的环信。

连接:从即时通讯云到人工智能,让连接价值更深入

   “目前的环信,其实有三条产品线,分别是:即时通讯云、移动客服、人工智能。”当我问及环信当下的产品结构时,刘俊彦给出了这样的答案。

   环信在2013年成立时,开始做即时通讯云,让企业的APP拥有像微信一样的聊天沟通能力,这个场景是“连接人和人”。

   但当产品上线后,他们很快发现,IM其实天然的还适用在第二个场景——“连接人和商业”。最典型的例子就是淘宝旺旺。旺旺作为一款聊天工具,很好地连接了消费者和企业客服,这样的场景,用户黏性会更强,在商业上的价值也更高。这个场景离企业更近,也更适合商业化,环信移动客服应运而生,其核心就是用即时通讯工具连接人和商业。随后,环信又推出了全媒体客服,让用户可以通过各种通道联系到企业。

   实际上,牛透社此前对于环信的印象就止步于此了,看起来,环信似乎就是做了这两件事情,并凭此声名大噪。但刘俊彦说,环信还有第三条产品线——环信人工智能。

   当环信在做移动客服的时候,发现他们在一开始构建的“蓝图”其实还不够完美,有个最大的问题——当你真的将工具做得很出色,让任何一个消费者在任何地点都能很容易地与企业聊天沟通的时候,所有的压力和责任就会转移到商家身上。在手机时代,每个人都能24小时联系到商家,但商家的客服人员其实是有限的,用户需求的激增难以满足,所以回过头来看,这样的客服工具并没能真的提升用户体验,反而让商家倍感压力。

   “我们希望能通过人工智能解决商家日益高涨的客服成本和用户需求不断增长的天然矛盾,人工智能显然是绝佳的手段。”刘俊彦这样评价道人工智能产品线。




环信创始人兼CEO 刘俊彦

   从一开始的IM试图连接人和人,再到连接人和商业,打造了一款完整的客服产品,到如今的人工智能,环信的主线其实并没有发生任何变化——连接。以刘俊彦的话来说,环信的基因就是连接与对话,环信希望用对话的方式将人和人、人和商业连接在一起,这是环信公司的主线或者说愿景。

引领:结合“土壤”引领创新,缔造移动客服时代

   牛透社早在去年7月份就得知了环信入选“Gartner 2016 Cool Vendor”,但仅通过过往的一些通稿并不能了解太多讯息,直到近期环信与Gartner联合发布的《下一代客户服务软件趋势》报告出炉,坐在环信的会议室里,和刘俊彦面对面交流,我们才从行业的角度,看到了一个更真实的环信。

   “Gartner每年5月的Cool Vendor评选,都是从四五十家被不同分析师所提企业中筛选出的寥寥几家上榜企业,环信到底‘酷’在哪,能成为中国唯一入选的SaaS客服企业?”我抛出了牛透社对此最想了解的问题。

   “在我看来,有两点非常关键——环信的创新和我们所扎根的‘土壤’。创新让我们始终走在行业前列,而‘土壤’则带来了更多契机。”刘俊彦这样答道,“Gartner几年前就作出预测,发生在移动端上的客户服务在未来将达到什么规模,在市场上占据多少比例,但实际的数据始终落后于预测,于是不断修正数据。从北美市场来看,不论是移动互联网的发展还是基于IM的商业文化,都还不足以支撑Gartner所预测的数据。但纵观全球,Gartner发现中国的市场做到了——不论是其预测的移动端客服的快速兴起还是客服机器人在客户服务中的大规模应用,美国都没能实现,但中国都实现了。而且Gartner发现,在中国引领世界的这一波客户服务的创新浪潮背后,主要的推动企业是一家叫环信的公司。”

   环信达到了Gartner几年前预测的行业数据,做到了全球诸多企业都没能做到的事情,听起来有些不可思议,但Gartner以其报告严谨性享誉全球,这又是毋庸置疑的。

   环信之所以能有这样的成绩,也正如刘俊彦所说:一方面是来源于环信不断的创新,这让环信始终保持着足够的敏锐度;另一方面,中国有着很独特的“土壤”,移动互联网的发展非常迅速,这是其它国家,包括美国在内都不能及的,而基于IM的商业文化、社交文化,也让环信移动客服获得了快速发展。

挑战:解析用户之难,让服务能力不止于工具

   “当下的国内企业客服部门面临三大挑战: 

   一是移动化的挑战。过去的消费者都是通过电话、网页的方式联系客服,而现在则更多地转移到了微信公众号、APP上,许多企业的IT架构难以适应移动时代。 

   二是来自服务体验的挑战。过去以电话呼叫中心为主的客服部门,通常采用录音和抽样质检方式来监控服务质量。当用户服务请求激增,且服务请求来自微信、APP、网页、电话等多个渠道,每个渠道的数据格式都不一样,都是非结构化数据,且数据量极大,企业就不能像过去一样做抽样人工质检,也就失去了对服务体验最基本的监测。

   三是客服人力成本与用户量激增的天然矛盾。当用户量越来越大,如何匹配足够的客服资源,这让许多公司头疼。”
 
   当问及国内企业客服部门所面临的挑战,刘俊彦给出了这样的答案。

   对于环信而言,专注在移动端即时通讯领域已经许久,所谓移动化的挑战,自然并不难应对。刘俊彦也表示,环信的全渠道客服,其移动端体验是颠覆性的,用户的痛点问题能得到很好的解决。

   但他显然并不认为这是多末大的优势,在他看来,所谓全渠道客服,只是一个工具,一个相对专业的团队,有足够的资金,两年时间,基本都能将工具属性的全渠道客服做得很不错。

刘俊彦将客服领域的竞争分为三个层面:工具层面、BI层面、AI层面。

工具层面

   在工具层面,诸多厂商的差距在变小,从表面来看,产品趋于同质化。

BI和AI层面

   在这两个层面,是要通过数据分析和人工智能的能力,提供更好的用户体验。

   全媒体客服的最佳体验不仅只是多渠道的接入,更重要的是用户跨渠道的体验和跟踪,在海量的数据中发现问题。而要做到这一点,企业首先需要理解客户到底体验到了什么。倾听客户声音的能力决定了他们在客户体验这个领域上的竞争力。

   Gartner报告也指出:“VOC(客户声音)是企业有关客户体验管理(CEM)战略需要考量的核心维度。CEM是个很大的话题,覆盖了企业交付給用户的客户体验的方方面面,是未来五年全球CEO所关注的排名前三的重点领域之一。”

   正是基于此,环信推出了“环信客户声音”——一款基于人工智能和大数据挖掘的客户体验透析产品。通过对多渠道的非结构化数据源进行客服业务的特征提取,发现服务运营的问题。通俗地来说,就是可以将系统中每天产生的数十万会话都转化成文本分析,主题关键词热度越高,说明用户关注度越高,加上对关键词做情感分析,了解用户对于某件事所带有的情绪。由此,企业就可以优先解决用户最关心、最影响体验的问题。

突围:纵观SaaS竞争格局,生态圈方为制胜之道

   将目光聚焦在整个SaaS软件领域,刘俊彦认为,存在四个层面的竞争:

工具层竞争

   所有SaaS软件的第一个竞争层面都是工具层,在工作流层面。也就说你做一个SaaS软件,主要是帮助企业实现它的工作流,比如客服的工作流,比如销售团队管理的工作流。目前大部分中国的SaaS企业公司都从工具开始起家。但在美国这个充分竞争的市场上,你只做第一个层面的SaaS企业,基本是没人会投资的。中国的SaaS行业还不像美国那样成熟,在这个层面还存在一些机会。而具体到客服领域来看,这一层面的竞争已经快结束了,开始进入第二层面的竞争。

数据层竞争

   在这一层面,是数据的竞争,或者说知识的竞争。工具层面拉不开差距,而工作流中沉淀了大量数据,要把这些数据变为产品。

   像环信现在做BI,竞争中我们不是比较谁的报表数量多,而是比较是否有探索式BI自定义报表的能力。想象一下,如果一个用户,在你的平台上自己生成了很多自定义的报表和BI数据,那么他就很难迁移走了。一旦用户的个性化数据和知识变成了产品的一部分,这将让用户的迁移成本变得更高,这时企业间的竞争才开始有了自己独特的壁垒。但中国目前能够做到这个层面的SaaS公司不多,因为第一层面的工具竞争还没有结束。

生态圈的竞争

   最典型的例子就是Salesforce。Salesforce的成功并不仅仅是因为其产品很出色,还在于它的生态圈很完善。Salesforce目前拥有上百家企业在其force.com平台上进行软件、插件的开发,已形成自己的生态,其他公司基本无法与之抗衡。比如美国有一家著名的生命科技公司,viva,目前市值是17亿美金,但他自己没有底层平台,他把底层平台搭建在Salesforce上并基于此开发自己使用的软件。当你要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

AI层竞争

   “SaaS企业的终极竞争层面是AI层,这是一个大趋势。环信的智能客服机器人、客户声音、环信智能质检,都是AI团队打造出来的。所有的数据、业务流程最终都以AI形式展现出来,这是最终决定所有SaaS公司生死的核心关键。”刘俊彦坚定地说到,“环信目前处于第二层到第三层的竞争阶段,对于第四层的AI也一直在努力。”

   还是以客服行业为例子。客服行业在10年前,只有呼叫中心这一种形式。呼叫中心最初是解决基本沟通的问题,让消费者能找到我。所以第一阶段是解决沟通与通信问题,即通讯设备厂商阶段,出现Avaya、中兴、华为等销售通讯设备的企业;第二阶段是如何管理客服人员,促发了一批以提供管理和效率工具软件为主的客服企业。这阶段的主要挑战是如何使人像机器一样高效标准;第三阶段是机器替代人,由于不断提高的人力成本和不断增加的客户请求之间的不可调和的矛盾,我们只能用AI来代替人。这阶段的主要挑战是如何让机器像人一样智能、灵活。

   “您认为中国SaaS市场会不会出现Salesforce这样的巨头企业?”临走前我问到。

   “其实在中国企业服务的各主要赛道已经出现巨头了,格局也相对清晰,如果这些领头羊不出现什么重大失误,相信能一直居于前列。而当大家将第三层生态圈做好之后,或许就会是中国的Salesforce。” 查看全部
    走进环信的办公室,一层看起来人数不算太多,都是市场和销售人员,另一层的工位则几乎全满,全都是技术人员。当我诧异于环信的技术团队如此庞大的时候,刘俊彦仿佛看穿了我的心思,这位CEO笑着说到:“我们相信,技术实力能让我们走得更远。”

   环信从2013年成立,从一开始的“即时通讯云”到移动客服,再到入选“Gartner 2016Cool Vendor”,环信的成绩着实令人惊诧。直到上周五,牛透社获取了来自Gartner和环信联合发布的《下一代客户服务软件趋势》报告,我们开始重新审视客服领域,重新审视带给我们诸多震撼的环信。

连接:从即时通讯云到人工智能,让连接价值更深入

   “目前的环信,其实有三条产品线,分别是:即时通讯云、移动客服、人工智能。”当我问及环信当下的产品结构时,刘俊彦给出了这样的答案。

   环信在2013年成立时,开始做即时通讯云,让企业的APP拥有像微信一样的聊天沟通能力,这个场景是“连接人和人”。

   但当产品上线后,他们很快发现,IM其实天然的还适用在第二个场景——“连接人和商业”。最典型的例子就是淘宝旺旺。旺旺作为一款聊天工具,很好地连接了消费者和企业客服,这样的场景,用户黏性会更强,在商业上的价值也更高。这个场景离企业更近,也更适合商业化,环信移动客服应运而生,其核心就是用即时通讯工具连接人和商业。随后,环信又推出了全媒体客服,让用户可以通过各种通道联系到企业。

   实际上,牛透社此前对于环信的印象就止步于此了,看起来,环信似乎就是做了这两件事情,并凭此声名大噪。但刘俊彦说,环信还有第三条产品线——环信人工智能。

   当环信在做移动客服的时候,发现他们在一开始构建的“蓝图”其实还不够完美,有个最大的问题——当你真的将工具做得很出色,让任何一个消费者在任何地点都能很容易地与企业聊天沟通的时候,所有的压力和责任就会转移到商家身上。在手机时代,每个人都能24小时联系到商家,但商家的客服人员其实是有限的,用户需求的激增难以满足,所以回过头来看,这样的客服工具并没能真的提升用户体验,反而让商家倍感压力。

   “我们希望能通过人工智能解决商家日益高涨的客服成本和用户需求不断增长的天然矛盾,人工智能显然是绝佳的手段。”刘俊彦这样评价道人工智能产品线。
1488254559811329.jpg

环信创始人兼CEO 刘俊彦

   从一开始的IM试图连接人和人,再到连接人和商业,打造了一款完整的客服产品,到如今的人工智能,环信的主线其实并没有发生任何变化——连接。以刘俊彦的话来说,环信的基因就是连接与对话,环信希望用对话的方式将人和人、人和商业连接在一起,这是环信公司的主线或者说愿景。

引领:结合“土壤”引领创新,缔造移动客服时代

   牛透社早在去年7月份就得知了环信入选“Gartner 2016 Cool Vendor”,但仅通过过往的一些通稿并不能了解太多讯息,直到近期环信与Gartner联合发布的《下一代客户服务软件趋势》报告出炉,坐在环信的会议室里,和刘俊彦面对面交流,我们才从行业的角度,看到了一个更真实的环信。

   “Gartner每年5月的Cool Vendor评选,都是从四五十家被不同分析师所提企业中筛选出的寥寥几家上榜企业,环信到底‘酷’在哪,能成为中国唯一入选的SaaS客服企业?”我抛出了牛透社对此最想了解的问题。

   “在我看来,有两点非常关键——环信的创新和我们所扎根的‘土壤’。创新让我们始终走在行业前列,而‘土壤’则带来了更多契机。”刘俊彦这样答道,“Gartner几年前就作出预测,发生在移动端上的客户服务在未来将达到什么规模,在市场上占据多少比例,但实际的数据始终落后于预测,于是不断修正数据。从北美市场来看,不论是移动互联网的发展还是基于IM的商业文化,都还不足以支撑Gartner所预测的数据。但纵观全球,Gartner发现中国的市场做到了——不论是其预测的移动端客服的快速兴起还是客服机器人在客户服务中的大规模应用,美国都没能实现,但中国都实现了。而且Gartner发现,在中国引领世界的这一波客户服务的创新浪潮背后,主要的推动企业是一家叫环信的公司。”

   环信达到了Gartner几年前预测的行业数据,做到了全球诸多企业都没能做到的事情,听起来有些不可思议,但Gartner以其报告严谨性享誉全球,这又是毋庸置疑的。

   环信之所以能有这样的成绩,也正如刘俊彦所说:一方面是来源于环信不断的创新,这让环信始终保持着足够的敏锐度;另一方面,中国有着很独特的“土壤”,移动互联网的发展非常迅速,这是其它国家,包括美国在内都不能及的,而基于IM的商业文化、社交文化,也让环信移动客服获得了快速发展。

挑战:解析用户之难,让服务能力不止于工具

   “当下的国内企业客服部门面临三大挑战: 

   一是移动化的挑战。过去的消费者都是通过电话、网页的方式联系客服,而现在则更多地转移到了微信公众号、APP上,许多企业的IT架构难以适应移动时代。 

   二是来自服务体验的挑战。过去以电话呼叫中心为主的客服部门,通常采用录音和抽样质检方式来监控服务质量。当用户服务请求激增,且服务请求来自微信、APP、网页、电话等多个渠道,每个渠道的数据格式都不一样,都是非结构化数据,且数据量极大,企业就不能像过去一样做抽样人工质检,也就失去了对服务体验最基本的监测。

   三是客服人力成本与用户量激增的天然矛盾。当用户量越来越大,如何匹配足够的客服资源,这让许多公司头疼。”
 
   当问及国内企业客服部门所面临的挑战,刘俊彦给出了这样的答案。

   对于环信而言,专注在移动端即时通讯领域已经许久,所谓移动化的挑战,自然并不难应对。刘俊彦也表示,环信的全渠道客服,其移动端体验是颠覆性的,用户的痛点问题能得到很好的解决。

   但他显然并不认为这是多末大的优势,在他看来,所谓全渠道客服,只是一个工具,一个相对专业的团队,有足够的资金,两年时间,基本都能将工具属性的全渠道客服做得很不错。

刘俊彦将客服领域的竞争分为三个层面:工具层面、BI层面、AI层面。

工具层面

   在工具层面,诸多厂商的差距在变小,从表面来看,产品趋于同质化。

BI和AI层面

   在这两个层面,是要通过数据分析和人工智能的能力,提供更好的用户体验。

   全媒体客服的最佳体验不仅只是多渠道的接入,更重要的是用户跨渠道的体验和跟踪,在海量的数据中发现问题。而要做到这一点,企业首先需要理解客户到底体验到了什么。倾听客户声音的能力决定了他们在客户体验这个领域上的竞争力。

   Gartner报告也指出:“VOC(客户声音)是企业有关客户体验管理(CEM)战略需要考量的核心维度。CEM是个很大的话题,覆盖了企业交付給用户的客户体验的方方面面,是未来五年全球CEO所关注的排名前三的重点领域之一。”

   正是基于此,环信推出了“环信客户声音”——一款基于人工智能和大数据挖掘的客户体验透析产品。通过对多渠道的非结构化数据源进行客服业务的特征提取,发现服务运营的问题。通俗地来说,就是可以将系统中每天产生的数十万会话都转化成文本分析,主题关键词热度越高,说明用户关注度越高,加上对关键词做情感分析,了解用户对于某件事所带有的情绪。由此,企业就可以优先解决用户最关心、最影响体验的问题。

突围:纵观SaaS竞争格局,生态圈方为制胜之道

   将目光聚焦在整个SaaS软件领域,刘俊彦认为,存在四个层面的竞争:

工具层竞争

   所有SaaS软件的第一个竞争层面都是工具层,在工作流层面。也就说你做一个SaaS软件,主要是帮助企业实现它的工作流,比如客服的工作流,比如销售团队管理的工作流。目前大部分中国的SaaS企业公司都从工具开始起家。但在美国这个充分竞争的市场上,你只做第一个层面的SaaS企业,基本是没人会投资的。中国的SaaS行业还不像美国那样成熟,在这个层面还存在一些机会。而具体到客服领域来看,这一层面的竞争已经快结束了,开始进入第二层面的竞争。

数据层竞争

   在这一层面,是数据的竞争,或者说知识的竞争。工具层面拉不开差距,而工作流中沉淀了大量数据,要把这些数据变为产品。

   像环信现在做BI,竞争中我们不是比较谁的报表数量多,而是比较是否有探索式BI自定义报表的能力。想象一下,如果一个用户,在你的平台上自己生成了很多自定义的报表和BI数据,那么他就很难迁移走了。一旦用户的个性化数据和知识变成了产品的一部分,这将让用户的迁移成本变得更高,这时企业间的竞争才开始有了自己独特的壁垒。但中国目前能够做到这个层面的SaaS公司不多,因为第一层面的工具竞争还没有结束。

生态圈的竞争

   最典型的例子就是Salesforce。Salesforce的成功并不仅仅是因为其产品很出色,还在于它的生态圈很完善。Salesforce目前拥有上百家企业在其force.com平台上进行软件、插件的开发,已形成自己的生态,其他公司基本无法与之抗衡。比如美国有一家著名的生命科技公司,viva,目前市值是17亿美金,但他自己没有底层平台,他把底层平台搭建在Salesforce上并基于此开发自己使用的软件。当你要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

AI层竞争

   “SaaS企业的终极竞争层面是AI层,这是一个大趋势。环信的智能客服机器人、客户声音、环信智能质检,都是AI团队打造出来的。所有的数据、业务流程最终都以AI形式展现出来,这是最终决定所有SaaS公司生死的核心关键。”刘俊彦坚定地说到,“环信目前处于第二层到第三层的竞争阶段,对于第四层的AI也一直在努力。”

   还是以客服行业为例子。客服行业在10年前,只有呼叫中心这一种形式。呼叫中心最初是解决基本沟通的问题,让消费者能找到我。所以第一阶段是解决沟通与通信问题,即通讯设备厂商阶段,出现Avaya、中兴、华为等销售通讯设备的企业;第二阶段是如何管理客服人员,促发了一批以提供管理和效率工具软件为主的客服企业。这阶段的主要挑战是如何使人像机器一样高效标准;第三阶段是机器替代人,由于不断提高的人力成本和不断增加的客户请求之间的不可调和的矛盾,我们只能用AI来代替人。这阶段的主要挑战是如何让机器像人一样智能、灵活。

   “您认为中国SaaS市场会不会出现Salesforce这样的巨头企业?”临走前我问到。

   “其实在中国企业服务的各主要赛道已经出现巨头了,格局也相对清晰,如果这些领头羊不出现什么重大失误,相信能一直居于前列。而当大家将第三层生态圈做好之后,或许就会是中国的Salesforce。”
0
评论

一家SaaS客服企业要做AI,环信打的是什么算盘? 环信移动客服 SaaS 环信 新闻资讯

开发讨论beyond 发表了文章 • 2705 次浏览 • 2017-02-28 11:30 • 来自相关话题

   从SaaStr回来后,环信以CEO刘俊彦的口吻,连续对外做了几次观点发声,关于SaaS创业的9种正确姿势、AI正在吃掉软件……其中有些观点引起了笔者的兴趣,笔者也查找梳理了这家近两年在移动客服领域风生水起的创业企业的资料。全媒体客服、客服移动化、智能化、营销化是其突出的特点,然而在这一次与刘俊彦的采访中,他更想强调的是环信在数据层面的战略优势以及对AI的认知和布局。

   笔者将环信所发出的观点和这次采访做了一些结合,以展示环信CEO刘俊彦以及环信对SaaS和自身发展的看法、布局。

从行业焦点看SaaS发展三大阶段

   “在中国,不知道是不是因为SaaS企业有准确的数学模型,可以用一大串公式表达,直接戳中了资本的甜点,反正在过去1,2年的资本寒冬里,SaaS企业已经成为了很多资本寻求低风险高质量投资标的的热门选择。”这是一篇文章中提到的一句话,实质上也确实是前两年,尤其是2015下半年、2016上半年的SaaS市场行业状况,无论是哪个领域,无论是CEO、COO等CXO,还是市场经理、销售业务员等相对基层的员工,都会时不时拽出“CAC”“LTV”等高大上的词汇。

   然而刘俊彦认为,这种现象正在逐渐削弱。他将近几年这一波SaaS的发展分为三个阶段,第一个是野蛮生长的阶段,SaaS浪潮涌起后,大波创业者入海,各自发展、野蛮生长;第二个是经典SaaS理论大行其道的阶段,CAC、LTV、续约率、客单价等等。美国SaaS企业已经发展了10多年,形成了一整套完整的理论体系,这为国内野蛮生长的市场打开了一扇经验之门,行业逐渐褪去虚热,开始转向理性阶段;然而,从去年下半年开始,很多SaaS创业公司开始发现美国经典SaaS理论并不完全适用,很多企业开始明确做大客户的思路,这就进入了SaaS发展的第三个阶段。

   中国企业服务市场和北美市场存在很大的不同:在北美,除了Salesforce和Oracle等巨头外,大部分SaaS企业只能做巨头看不上的中小客户市场;而在中国,企业服务的6个核心赛道,客服云、市场云、销售云、HR云、财务云、协同云,都没有历史巨头。这就意味着中国的这一批SaaS企业都有可能成长为各自赛道上的巨头,都有机会做大客户。针对大客户的SaaS运营体系和针对中小企业的体系是不太一样的,而大家目前讨论比较多的经典SaaS理论体系主要针对中小客户,原因也很简单:在美国,绝大部分SaaS企业都是在做中小客户,愿意出来分享的也是这部分SaaS企业,而Oracle、Salesforce这样的巨头通常是不出来分享经验的。




从核心竞争力看SaaS的四级阶梯

   如果从核心竞争力的角度,可以将SaaS企业的发展划分为四个阶段。

   第一阶段的重点在工具层面,所有的产品都是为解决工作流的问题而开发的工具。工具的核心在于技术,技术本身并不是不可突破的壁垒,那么如果仅仅局限于工具层面,就很难让竞争产生差异化。

   第二阶段的重点在数据层面,将工作流里产生的数据和知识变成产品。比如企业要做定制BI,数据源的挖掘聚合、数据清洗、数据的视图展现,这些部分往往要根据企业需求来进行定制。一旦用户的个性化数据和知识也变成了产品的一部分,用户的迁移成本将变得更高,这时企业才开始有了自己独特的竞争壁垒。

   第三阶段的重点是生态圈的建设。只有建立生态圈,SaaS企业才能真正筑起足够高的竞争门槛。比如要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

   第四阶段的重点是AI,严格来讲这是刘俊彦个人对SaaS未来技术发展方向的看法。“个人觉得SaaS的终极竞争在AI”,正如刘俊彦在谈论AI的文章中提到的,“AI正在吃掉软件,也正在深刻的影响着SaaS客服行业,在客服领域AI正逐渐发挥着重要的作用,有望成为一股颠覆性的力量从而被整个行业寄予厚望”。

AI很可能彻底颠覆SaaS客服软件

   为什么这么说?刘俊彦以SaaS客服为例子,说明了为什么AI可能会彻底颠覆现在的SaaS客服软件。简单来讲,现在市场上所有SaaS客服软件的核心功能都是把一个服务请求按特定的规则分配给客服,然后给客服提供一个好用的效率工具,并提供各种报表来考核和管理客服的绩效。进入到智能客服机器人时代后,一个机器人可以秒级处理上百万的服务请求,所以不需要分配。机器人也不需要管理和发工资,所以也不再需要各种绩效管理和报表。那么目前市场上的这些传统SaaS客服软件还有存在的意义吗?

   “当然,完全用客服机器人代替人,技术还不成熟,还需要5到10年时间,所以环信做SaaS客服软件,一二三层的能力还是要持续加强的”,刘俊彦补充到。基于这种思考和AI的发展趋势,刘俊彦将AI提到了环信的发展战略层面,并很早组建了AI团队。

进击二三四级技术力量,环信要做下一个Salesforce

   回到环信,客服移动化、全媒体客服、客服智能化、客服营销化等,是环信移动客服的特点。对于产品的优势,刘俊彦表示环信已经做到了工具层面的领先,在第二个层面即数据层面,环信也推出了相应的数据产品,如环信客户声音。

   环信客户声音是基于人工智能和大数据挖掘的客户体验透析产品。全媒体客服的最佳体验不仅是多渠道的接入,更重要的是跨渠道环境下,如何保证用户体验。环信认为,理解客户声音是保证客户体验的最重要一环。环信客户声音通过NLP(自然语言解析)、主题聚类、情感分析等技术手段,对来自多个渠道的非结构化文本数据进行挖掘和分析热点话题,发现服务运营问题,寻找畅销或者问题产品,洞察销售机会。

   在第四层即AI层面,环信推出了环信智能客服机器人和环信智能质检。

   环信客服机器人是环信基于自然语言处理和机器学习技术所推出的产品,其主要功能是辅助、替代人工客服回答常见、高频的问题,从而降低人力成本。

   环信智能质检则是基于环信在线客服积累的各个领域的海量用户对话,提取出数百个客服对话特征,并用这些特征训练得到的几十种常见通用质检模型,从而将质检从过去人工、抽样,转变为自动、全面的工作。

   令刘俊彦感到振奋的是,Gartner对于下一代客户服务软件的趋势预测和环信的实践是完全吻合的。Gartner报告指出“消费者对移动设备的偏好正在快速发展,到2019年,移动设备的使用将占到所有互联网交互的85%,如果不能改善移动客户服务,企业将遭受损失。” 。Gartner报告还指出,“VOC(客户声音)是企业有关客户体验管理(CEM)战略需要考量的核心维度。CEM是未来五年全球CEO所关注的排名前三的重点领域之一。”

   应该说,这几条预测都在环信身上得到了有效地验证。相比于北美市场,中国有着很独特的“土壤”,移动互联网的发展非常迅速,这是包括美国在内的其它国家都不能及的,而基于IM的商业文化、社交文化,也让环信移动客服获得了快速发展。




   2017年,环信的重点是加大二三四层核心竞争力的建设:将数据产品做得更好,在生态圈建设方面继续建设自身的PaaS平台,并在AI层面加大投入。

   总的来说,刘俊彦认为客服是中国企业级服务市场六大核心赛道——客服云、市场云、销售云、HR云、财务云、协同云之一,环信希望能够在这一赛道上深耕细作并筑起足够高的竞争壁垒,成为像Salesforce一样的SaaS企业巨头。 查看全部

c8ea15ce36d3d539138996423387e950342ab0ff.jpg

   从SaaStr回来后,环信以CEO刘俊彦的口吻,连续对外做了几次观点发声,关于SaaS创业的9种正确姿势、AI正在吃掉软件……其中有些观点引起了笔者的兴趣,笔者也查找梳理了这家近两年在移动客服领域风生水起的创业企业的资料。全媒体客服、客服移动化、智能化、营销化是其突出的特点,然而在这一次与刘俊彦的采访中,他更想强调的是环信在数据层面的战略优势以及对AI的认知和布局。

   笔者将环信所发出的观点和这次采访做了一些结合,以展示环信CEO刘俊彦以及环信对SaaS和自身发展的看法、布局。

从行业焦点看SaaS发展三大阶段

   “在中国,不知道是不是因为SaaS企业有准确的数学模型,可以用一大串公式表达,直接戳中了资本的甜点,反正在过去1,2年的资本寒冬里,SaaS企业已经成为了很多资本寻求低风险高质量投资标的的热门选择。”这是一篇文章中提到的一句话,实质上也确实是前两年,尤其是2015下半年、2016上半年的SaaS市场行业状况,无论是哪个领域,无论是CEO、COO等CXO,还是市场经理、销售业务员等相对基层的员工,都会时不时拽出“CAC”“LTV”等高大上的词汇。

   然而刘俊彦认为,这种现象正在逐渐削弱。他将近几年这一波SaaS的发展分为三个阶段,第一个是野蛮生长的阶段,SaaS浪潮涌起后,大波创业者入海,各自发展、野蛮生长;第二个是经典SaaS理论大行其道的阶段,CAC、LTV、续约率、客单价等等。美国SaaS企业已经发展了10多年,形成了一整套完整的理论体系,这为国内野蛮生长的市场打开了一扇经验之门,行业逐渐褪去虚热,开始转向理性阶段;然而,从去年下半年开始,很多SaaS创业公司开始发现美国经典SaaS理论并不完全适用,很多企业开始明确做大客户的思路,这就进入了SaaS发展的第三个阶段。

   中国企业服务市场和北美市场存在很大的不同:在北美,除了Salesforce和Oracle等巨头外,大部分SaaS企业只能做巨头看不上的中小客户市场;而在中国,企业服务的6个核心赛道,客服云、市场云、销售云、HR云、财务云、协同云,都没有历史巨头。这就意味着中国的这一批SaaS企业都有可能成长为各自赛道上的巨头,都有机会做大客户。针对大客户的SaaS运营体系和针对中小企业的体系是不太一样的,而大家目前讨论比较多的经典SaaS理论体系主要针对中小客户,原因也很简单:在美国,绝大部分SaaS企业都是在做中小客户,愿意出来分享的也是这部分SaaS企业,而Oracle、Salesforce这样的巨头通常是不出来分享经验的。
2934349b033b5bb56863b97f3fd3d539b700bcff.jpg

从核心竞争力看SaaS的四级阶梯

   如果从核心竞争力的角度,可以将SaaS企业的发展划分为四个阶段。

   第一阶段的重点在工具层面,所有的产品都是为解决工作流的问题而开发的工具。工具的核心在于技术,技术本身并不是不可突破的壁垒,那么如果仅仅局限于工具层面,就很难让竞争产生差异化。

   第二阶段的重点在数据层面,将工作流里产生的数据和知识变成产品。比如企业要做定制BI,数据源的挖掘聚合、数据清洗、数据的视图展现,这些部分往往要根据企业需求来进行定制。一旦用户的个性化数据和知识也变成了产品的一部分,用户的迁移成本将变得更高,这时企业才开始有了自己独特的竞争壁垒。

   第三阶段的重点是生态圈的建设。只有建立生态圈,SaaS企业才能真正筑起足够高的竞争门槛。比如要颠覆Salesforce,就不再只是颠覆掉Salesforce的产品本身,还要同时颠覆Salesforce生态中的几百家合作伙伴公司。

   第四阶段的重点是AI,严格来讲这是刘俊彦个人对SaaS未来技术发展方向的看法。“个人觉得SaaS的终极竞争在AI”,正如刘俊彦在谈论AI的文章中提到的,“AI正在吃掉软件,也正在深刻的影响着SaaS客服行业,在客服领域AI正逐渐发挥着重要的作用,有望成为一股颠覆性的力量从而被整个行业寄予厚望”。

AI很可能彻底颠覆SaaS客服软件

   为什么这么说?刘俊彦以SaaS客服为例子,说明了为什么AI可能会彻底颠覆现在的SaaS客服软件。简单来讲,现在市场上所有SaaS客服软件的核心功能都是把一个服务请求按特定的规则分配给客服,然后给客服提供一个好用的效率工具,并提供各种报表来考核和管理客服的绩效。进入到智能客服机器人时代后,一个机器人可以秒级处理上百万的服务请求,所以不需要分配。机器人也不需要管理和发工资,所以也不再需要各种绩效管理和报表。那么目前市场上的这些传统SaaS客服软件还有存在的意义吗?

   “当然,完全用客服机器人代替人,技术还不成熟,还需要5到10年时间,所以环信做SaaS客服软件,一二三层的能力还是要持续加强的”,刘俊彦补充到。基于这种思考和AI的发展趋势,刘俊彦将AI提到了环信的发展战略层面,并很早组建了AI团队。

进击二三四级技术力量,环信要做下一个Salesforce

   回到环信,客服移动化、全媒体客服、客服智能化、客服营销化等,是环信移动客服的特点。对于产品的优势,刘俊彦表示环信已经做到了工具层面的领先,在第二个层面即数据层面,环信也推出了相应的数据产品,如环信客户声音。

   环信客户声音是基于人工智能和大数据挖掘的客户体验透析产品。全媒体客服的最佳体验不仅是多渠道的接入,更重要的是跨渠道环境下,如何保证用户体验。环信认为,理解客户声音是保证客户体验的最重要一环。环信客户声音通过NLP(自然语言解析)、主题聚类、情感分析等技术手段,对来自多个渠道的非结构化文本数据进行挖掘和分析热点话题,发现服务运营问题,寻找畅销或者问题产品,洞察销售机会。

   在第四层即AI层面,环信推出了环信智能客服机器人和环信智能质检。

   环信客服机器人是环信基于自然语言处理和机器学习技术所推出的产品,其主要功能是辅助、替代人工客服回答常见、高频的问题,从而降低人力成本。

   环信智能质检则是基于环信在线客服积累的各个领域的海量用户对话,提取出数百个客服对话特征,并用这些特征训练得到的几十种常见通用质检模型,从而将质检从过去人工、抽样,转变为自动、全面的工作。

   令刘俊彦感到振奋的是,Gartner对于下一代客户服务软件的趋势预测和环信的实践是完全吻合的。Gartner报告指出“消费者对移动设备的偏好正在快速发展,到2019年,移动设备的使用将占到所有互联网交互的85%,如果不能改善移动客户服务,企业将遭受损失。” 。Gartner报告还指出,“VOC(客户声音)是企业有关客户体验管理(CEM)战略需要考量的核心维度。CEM是未来五年全球CEO所关注的排名前三的重点领域之一。”

   应该说,这几条预测都在环信身上得到了有效地验证。相比于北美市场,中国有着很独特的“土壤”,移动互联网的发展非常迅速,这是包括美国在内的其它国家都不能及的,而基于IM的商业文化、社交文化,也让环信移动客服获得了快速发展。
a8014c086e061d956164aeb972f40ad162d9ca15.jpg

   2017年,环信的重点是加大二三四层核心竞争力的建设:将数据产品做得更好,在生态圈建设方面继续建设自身的PaaS平台,并在AI层面加大投入。

   总的来说,刘俊彦认为客服是中国企业级服务市场六大核心赛道——客服云、市场云、销售云、HR云、财务云、协同云之一,环信希望能够在这一赛道上深耕细作并筑起足够高的竞争壁垒,成为像Salesforce一样的SaaS企业巨头。
0
评论

SaaStr 2017大会启示:SaaS创业最正确的十种姿势! 环信 SaaStr大会

开发讨论客服超人 发表了文章 • 1814 次浏览 • 2017-02-22 16:07 • 来自相关话题

很多人感叹:“听过很多道理,却依然过不好这一生。听过很多创业鸡汤,却仍然没升职加薪...”那是因为你们还没有找到最正确的姿势。近日,环信CEO刘俊彦亲临美国SaaStr Annual 2017大会,帮你揭秘SaaS创业最正确的十种姿势! 
   SaaS行业最迷人之处之一就是经过10多年无数公司的探索,尤其是在北美,SaaS企业已经有了一个可以被精确计算和测量的模型。我们看到SaaS行业的人不管懂还是不懂,都在谈论着CAC, LTV, LTV>3CAC,续约率,平均客单价,MRR/ARR, inbound marketing, outbound prospecting...

   在中国,不知道是不是因为SaaS企业有数学模型,可以用一大串公式表达,直接戳中了人民币资本的甜点,反正在过去1,2年的资本寒冬里,SaaS企业已经成为了很多资本寻求低风险高质量投资标的的热门选择。

   同时,对中国的创业者来说,SaaS还是一个新鲜事物。当Salesforce已经上市了6年,享有560亿美金市值的时候,中国才刚刚有第一批SaaS企业进入ARR(年度可重复销售额)亿元俱乐部。

   相信,我们这一批还稍显稚嫩但胸怀星辰大海的中国SaaS创业者们一定都想过,我现在SaaS创业的姿势到底对不对啊?如果5年后我可以重来一次,我会怎么做一个SaaS企业,我是否可以做的更好?

   很幸运,在2017 SaaStr年会上,原Marketo创始人Jon Miller讲到了他在SaaS企业二次创业时的10点改进。

   Marketo是一家做市场自动化软件的SaaS公司,于2006年创建,于2016以18亿美金的价格被Vista收购。Jon Miller随后离开了Marketo,创建了Engagio。 Engagio近来在硅谷可谓炙手可热,Engagio最牛的地方在于它从新定义了一个崭新的SaaS品类:ABM(Account based marketing)。




在Jon的演讲中,他分享了作为一个SaaS的二次创业者,他认为有10个方面在重新来过时,可以做的更好。
 
定义公司的愿景和核心价值观

   Jon创建Engagio公司时,第一天做的第一件事就是定义公司的愿景和价值观。以前在Marketo,这个事情在公司成立2年后才开始做。




   其实我很惊讶Jon把公司愿景和价值观放在第一。环顾四周,国内大部分创业公司是没有明确的公司愿景和价值观的。

   以环信自己为例子,环信从做IM云到客服云到AI,虽然一直明确的以连接为主线,IM是连接人与人,客服是连接人与商业,但真正正式确定公司的愿景是在去年:“连接人与人,连接人与商业,用卓越的技术改变每个人的生活和工作”。同时也定下了核心价值观。但Jon是对的,当我做了环信的愿景和价值观后,我发现我确实希望我能做的更早。
 
建立有凝聚力的团队
  
   我估计Jon应该是Patrick Lencioni 的粉丝,因为这页讲的完全就是“优势(The Advantage)”这本书的内容,即建立信任->掌控冲突->兑现承诺->承担责任->关注结果。这本书确实很好,尤其建议技术出身的CEO多看看,我每次坐飞机都带着,用来帮助睡眠。




 
用文化来驱动公司运行
 这包括一整套公司运转流程:

招聘
员工福利
员工入职,学习,发展
绩效管理
办公环境
企业社会责任




   我又一次被切中了痛点。我们的员工入职融入至今都还做的不够完美。招聘机器还有上升空间,想到我们可能每天都在错过优秀的人才,很忧虑。

   员工的绩效考核和薪酬激励制度还需要继续完善。几十个人的公司时不觉得这些制度流程的完善有多么重要,变成几百人的公司后,再补课就发现晚了。
 
 高效会议
 这个就不用说了。好在环信这样的工程师文化的公司还没有太多会议,程序员都讲究“code wins”
 
 融资策略
 
   Jon在这一次重新创业的时候,希望能够用较少轮次的融资,较少的稀释,拿到以前的Marketo更多轮次融资同样金额的钱。这当然很好,但这是创二代的特权。

   同时,Jon还说到,这一次创业,他会更重视财务管理,更重视盈利(少烧钱),他会希望B轮的钱一直用到公司盈利。




财务管理
 
更好的财务管理
 
竞争对手分析
 
   一定要重视竞品分析。所以Jon特意画了一个密密麻麻的密集恐惧症患者无法直视的市场分析图,以表示他真的做了很透彻的市场研究。
 
   Jon强调他创建Engagio的指导思想是要找到一个已经存在的市场,以避免去教育市场,同时还要找到一个竞争不是那么激烈的市场,以前Marketo的竞争实在太惨烈了。

   对于这点我也深表同意。环信是从即时通讯云起家,即时通讯云这个名词以前是不存在的。我自己知道,为了教育这个市场,我们花了多少钱。
 
做更大的客户
 
   Engagio的目标客单价是4万美金,目前已经做到了2.7万美金。而以前的Marketo只有5000美金的客单价.

   这点就更不用说了。记得是北森的纪伟国先生说过一句话,“国内能转做大客户的都转做大客户了,没转做大客户的,是因为暂时能力不够,想转但转不了”。 
做更多的outbound sales
 
   我一直觉得美国的inbound marketing太热了,热的不太合理。hubspot,marketo,美国有几百家这样的公司。而且SaaStr上一半的内容都是在讲inbound marketing。inbound marketing对美国2B企业的销售真的这末重要吗?其实Jon创建Engagio已经说明了其中的秘密。

   美国在中大型企业市场有Oracle,Salesforce等把持,创业公司是完全没有机会的。创业公司只能玩中小企业市场。所以才会有一大堆创业企业到处鼓吹inbound marketing。

   inbound marketing是针对中小企业为主的,而Engagio的ABM(Account based marketing),是为中大型企业准备的。在二次创业的一开始,Jon就已经想好了,这次他要做大企业。

   最后不得不感叹中国的SaaS创业者有多么幸运,中国在SaaS的各个核心赛道上,比如销售云,客服云,市场云,HR云等(财务和协同这2个赛道除外,你懂的)都没有本土巨头公司,Salesforce等海外巨头因为ICP牌照问题又进不了中国,可谓即无内忧,也无外患。这一代中国的SaaS创业者是没有天花板的,这一代中国SaaS创业者就是下一代创业者的天花板!
 
强迫症看了会沉默,处女座看了会流泪
 
为了照顾有强迫症的处女座创业者们,小编擅自加了最后这一条,凑齐了第十条。据说他们看完都默默点赞了...
 
环信成立于2013年4月,是一家国内领先的企业级软件服务提供商,于2016年荣膺“Gartner 2016 Cool Vendor”。产品包括国内上线最早规模最大的即时通讯云平台——环信即时通讯云,以及移动端最佳实践的全媒体智能云客服平台——环信移动客服。 查看全部
   很多人感叹:“听过很多道理,却依然过不好这一生。听过很多创业鸡汤,却仍然没升职加薪...”那是因为你们还没有找到最正确的姿势。近日,环信CEO刘俊彦亲临美国SaaStr Annual 2017大会,帮你揭秘SaaS创业最正确的十种姿势!
 
   SaaS行业最迷人之处之一就是经过10多年无数公司的探索,尤其是在北美,SaaS企业已经有了一个可以被精确计算和测量的模型。我们看到SaaS行业的人不管懂还是不懂,都在谈论着CAC, LTV, LTV>3CAC,续约率,平均客单价,MRR/ARR, inbound marketing, outbound prospecting...

   在中国,不知道是不是因为SaaS企业有数学模型,可以用一大串公式表达,直接戳中了人民币资本的甜点,反正在过去1,2年的资本寒冬里,SaaS企业已经成为了很多资本寻求低风险高质量投资标的的热门选择。

   同时,对中国的创业者来说,SaaS还是一个新鲜事物。当Salesforce已经上市了6年,享有560亿美金市值的时候,中国才刚刚有第一批SaaS企业进入ARR(年度可重复销售额)亿元俱乐部。

   相信,我们这一批还稍显稚嫩但胸怀星辰大海的中国SaaS创业者们一定都想过,我现在SaaS创业的姿势到底对不对啊?如果5年后我可以重来一次,我会怎么做一个SaaS企业,我是否可以做的更好?

   很幸运,在2017 SaaStr年会上,原Marketo创始人Jon Miller讲到了他在SaaS企业二次创业时的10点改进。

   Marketo是一家做市场自动化软件的SaaS公司,于2006年创建,于2016以18亿美金的价格被Vista收购。Jon Miller随后离开了Marketo,创建了Engagio。 Engagio近来在硅谷可谓炙手可热,Engagio最牛的地方在于它从新定义了一个崭新的SaaS品类:ABM(Account based marketing)。
bb791b7.jpg

在Jon的演讲中,他分享了作为一个SaaS的二次创业者,他认为有10个方面在重新来过时,可以做的更好。
 
  • 定义公司的愿景和核心价值观


   Jon创建Engagio公司时,第一天做的第一件事就是定义公司的愿景和价值观。以前在Marketo,这个事情在公司成立2年后才开始做。
6c02cfa.jpg

   其实我很惊讶Jon把公司愿景和价值观放在第一。环顾四周,国内大部分创业公司是没有明确的公司愿景和价值观的。

   以环信自己为例子,环信从做IM云到客服云到AI,虽然一直明确的以连接为主线,IM是连接人与人,客服是连接人与商业,但真正正式确定公司的愿景是在去年:“连接人与人,连接人与商业,用卓越的技术改变每个人的生活和工作”。同时也定下了核心价值观。但Jon是对的,当我做了环信的愿景和价值观后,我发现我确实希望我能做的更早。
 
  • 建立有凝聚力的团队

  
   我估计Jon应该是Patrick Lencioni 的粉丝,因为这页讲的完全就是“优势(The Advantage)”这本书的内容,即建立信任->掌控冲突->兑现承诺->承担责任->关注结果。这本书确实很好,尤其建议技术出身的CEO多看看,我每次坐飞机都带着,用来帮助睡眠。
QQ图片20170222160052.png

 
  • 用文化来驱动公司运行

 这包括一整套公司运转流程:

招聘
员工福利
员工入职,学习,发展
绩效管理
办公环境
企业社会责任
QQ图片20170222160216.png

   我又一次被切中了痛点。我们的员工入职融入至今都还做的不够完美。招聘机器还有上升空间,想到我们可能每天都在错过优秀的人才,很忧虑。

   员工的绩效考核和薪酬激励制度还需要继续完善。几十个人的公司时不觉得这些制度流程的完善有多么重要,变成几百人的公司后,再补课就发现晚了。
 
  •  高效会议

 这个就不用说了。好在环信这样的工程师文化的公司还没有太多会议,程序员都讲究“code wins”
 
  •  融资策略

 
   Jon在这一次重新创业的时候,希望能够用较少轮次的融资,较少的稀释,拿到以前的Marketo更多轮次融资同样金额的钱。这当然很好,但这是创二代的特权。

   同时,Jon还说到,这一次创业,他会更重视财务管理,更重视盈利(少烧钱),他会希望B轮的钱一直用到公司盈利。
QQ图片20170222160407.png

  • 财务管理

 
更好的财务管理
 
  • 竞争对手分析

 
   一定要重视竞品分析。所以Jon特意画了一个密密麻麻的密集恐惧症患者无法直视的市场分析图,以表示他真的做了很透彻的市场研究。
 
   Jon强调他创建Engagio的指导思想是要找到一个已经存在的市场,以避免去教育市场,同时还要找到一个竞争不是那么激烈的市场,以前Marketo的竞争实在太惨烈了。

   对于这点我也深表同意。环信是从即时通讯云起家,即时通讯云这个名词以前是不存在的。我自己知道,为了教育这个市场,我们花了多少钱。
 
  • 做更大的客户

 
   Engagio的目标客单价是4万美金,目前已经做到了2.7万美金。而以前的Marketo只有5000美金的客单价.

   这点就更不用说了。记得是北森的纪伟国先生说过一句话,“国内能转做大客户的都转做大客户了,没转做大客户的,是因为暂时能力不够,想转但转不了”。 
  • 做更多的outbound sales

 
   我一直觉得美国的inbound marketing太热了,热的不太合理。hubspot,marketo,美国有几百家这样的公司。而且SaaStr上一半的内容都是在讲inbound marketing。inbound marketing对美国2B企业的销售真的这末重要吗?其实Jon创建Engagio已经说明了其中的秘密。

   美国在中大型企业市场有Oracle,Salesforce等把持,创业公司是完全没有机会的。创业公司只能玩中小企业市场。所以才会有一大堆创业企业到处鼓吹inbound marketing。

   inbound marketing是针对中小企业为主的,而Engagio的ABM(Account based marketing),是为中大型企业准备的。在二次创业的一开始,Jon就已经想好了,这次他要做大企业。

   最后不得不感叹中国的SaaS创业者有多么幸运,中国在SaaS的各个核心赛道上,比如销售云,客服云,市场云,HR云等(财务和协同这2个赛道除外,你懂的)都没有本土巨头公司,Salesforce等海外巨头因为ICP牌照问题又进不了中国,可谓即无内忧,也无外患。这一代中国的SaaS创业者是没有天花板的,这一代中国SaaS创业者就是下一代创业者的天花板!
 
  • 强迫症看了会沉默,处女座看了会流泪

 
为了照顾有强迫症的处女座创业者们,小编擅自加了最后这一条,凑齐了第十条。据说他们看完都默默点赞了...
 
环信成立于2013年4月,是一家国内领先的企业级软件服务提供商,于2016年荣膺“Gartner 2016 Cool Vendor”。产品包括国内上线最早规模最大的即时通讯云平台——环信即时通讯云,以及移动端最佳实践的全媒体智能云客服平台——环信移动客服。
0
评论

技术选型最怕的是什么? 一乐 技术选型 架构 环信

开发讨论beyond 发表了文章 • 4052 次浏览 • 2017-02-07 16:41 • 来自相关话题

昨天聊聊架构发布了一篇关于技术选型的文章,文章作者介绍了目前流行的技术选型方式,比如有微博驱动、技术会议驱动、嗓门驱动、领导驱动.....不少读者都表示深有体会,并在评论区贴出了自己的经历。今天再推荐一篇由环信首席架构师一乐所撰写的关于技术选型的文章(旧文),希望能帮到各位。另推荐一乐的个人微信一乐来了,id是yilecoming,欢迎关注。这也许是我上半年最大的欠账,在去普吉的飞机上突发无聊,想想还了这债吧。

去年的时候,我们使用Cassandra出了一次问题,定位加修复用了一晚上。当我把经历发出来的时候,收到了下面一段话:

“一个开源产品,连官方文档都没看完大半,然后匆匆忙忙上生产环境,出了问题团团转。若是不能掌控就先不要玩,说回这Cassandra的例子,在对它不了解的情况下,仅通过Google就能解决问题,不正说明它不难掌握有大量资料可查吗,实在不行还能翻代码。”

我现在都不知道这位神仙从哪里看到的匆匆忙忙上和团团转,当时我还是忍了,因为实在太忙,口水又没那么多。当然我思考了很多,这就是你现在看到的文章。我相信它会有一些价值,毕竟有些事情有的人你不告诉他,他永远不可能知道。比如个体认知的局限,比如口无遮拦的损失,比如做事之人才会有的思考角度。

本文讲的是技术选型。

大多数技术都存在选型问题,因为技术的发展已经让一件事情可以有多种解决方案,选型问题就自然出现。前段时间也有人说过语言选型,这里举的例子是在组件、框架、服务的范畴。其中有相通之处,各位可以自行领会。
 
选型最怕什么
 
怕失败么?那肯定的。你的服务崩溃,用户愤而投诉,客户电话打到老板那里,明天你要洗干净到办公室去一趟(笑...)。而所有对失败的无法容忍,最终都会变成一句话,为什么你要选这个型?

你总要回答这个问题,所以选型一怕随意,公鸡头母鸡头,选上哪头是哪头;二怕凭感觉,某某已经在用听起来还不错。你需要真正的思考,而且尽可能的全面。我下文会详细讲解,但这还不是最怕的。

最怕的是什么?看看本文开头引用的那句话,你体会一下。

嗯,最怕的是喷子。怕任意总结,如果再加上一些诋毁,一次选型失败足以让人心碎一万次。

失败不可怕,可怕的是没有总结,因为没有总结就没有提高。而比没有总结更可怕的是乱总结。

为了方便理解,我再帮你换个角度。你天天在河边走,一次不小心湿了鞋。如果是本文说的这种人,那肯定要说:

一条公共的河,你连旱季旺季都没搞清楚,就匆匆忙忙跑过来散步,湿了鞋还到处讲。若是脚不行就别在这玩,说回这条河,湿了鞋就能爬上来,不正说明他水不深么。

这种人实在不算少见。他说的每一句话都有一点道理,但都跟事情的本质毫无关系,每一句话又都掺加了嘲讽,来体现那无处安放的莫名优越感。而所有的这些,对于解决问题和后续提高通常毫无帮助。

想想也真遗憾,人生本是如此美好,有的人却硬生生地活成了奇葩。
 
选型需要什么
 
言归正传,我认为有三点不可或缺:分析、实验和胆量。
 
 分析
 
分析主要有定性分析和定量分析。实际操作中,前者主要针对的是模型维度的估计,用来考虑一个组件是否有可能达到它宣称的目的,后者主要用来验证,用来确认它是否在真的做到了。

比如在语言选型时,你要考虑它的范型、内存模型和并发设计;数据库选型时你要考虑存储模型、支撑量级、成本开销;开源项目要考虑它的社区发展、文档完善程度;如果是库或者中间件,还要考虑他的易用性、灵活性以及可替代性,等等。

需要说明的一点是,我个人并不觉得阅读全部源码或者文档这种事情是必须的,这不局限在OS、VM层面。不仅因为这样的事情会耗费过多精力,而且受制于代码以及文档质量,就算真正阅读完毕也未必意味着完全领会。

这些都是定性的,而定性的东西就有可能存在理解偏差。一个库可以完成工作,并不代表它在高并发压力下依然表现正常;一个语言做到了自动管理内存,并不代表他能做得很好没有副作用;一件事情设计者觉得达到了目标并不代表能够满足使用者期望。因此我们还需要量化分析,也就是一直口口相传的,用数据说话。

量化分析需要你构建或使用现成的工具和数据集,对服务进行特定场景下的分析。通过提高压力、增加容量或者针对性的测试,来验证之前的定性分析是否达到预期,并分析不同技术之间的差异和表现。
 
实验
 
量化分析可以为真正的实验做一些准备和帮助,但是实验要走的明显更远。到了这一步,意味着要在真正的业务场景下进行验证,这跟量化分析中通用性场景有所不同。

在真正的业务中采用需要很多细致和琐碎的工作,除此之外,还要构建自己的测试工具集,这需要非常扎实的业务理解能力和勤奋的工作。而所有这些,你需要在开发环境做一次,在沙箱环境做一次,然后在仿真环境再做一次。

这几步经常被简化,但经验告诉我们,如果你想做一个高可用的系统,你就不应该少走任何一步。

步子大了,容易扯到蛋。 
 
胆量
 
实验做完,剩下的就是上线,但这一步有很多人跨不过去。因为就算做了再多准备,你依然不敢说百分百保证没问题。现实情况是,80%的线上问题都是升级或者上线引起的。

你需要胆量。

这不是说要硬着头皮做,人家都是艺高才胆大。所以为了让胆子大一点,你首先需要考虑降级和开关。从最悲观的角度来重新审视整个方案,如果升级出现问题怎么办,如何才能让出现的问题影响最小化。

而只要弄完了这些,也就只要再记住一句话就行:

你行你上啊!
 
对技术服务的提醒
 
得到认可
 
刚才在胆量里没说的一点。我们经常会看到,一项新技术在公司内久久难以推行,因为业务主管百般阻挠。即使排除利益纠葛,仍然会发现一种发自心底的不信任存在。而这种不信任,又往往来源于对同事工作的不认可。

这个问题原因很多,也许没有通用的解决方案,但我说一个例子。

我们最近开始使用Codis,就是@goroutine 和几个家伙之前搞过的玩意儿。虽然他们最近已经独立开搞像Google Spanner但拥有更高级特性的TiDB(就是太牛了的意思)。由于我对他们比较熟悉和认可,所以在Codis尝试方面也多出很多底气。这种信任并非完全来自于出问题之后的直接电话支持,而是真心觉得活儿好。

反过来,这对很多服务也是一个提醒,特别是云服务。也许只要你得到合作伙伴的认可,或者至少让他们觉得,自己动手不会比你做得更好,你基本也就成功了。

对于大多数理性创业公司来讲,他们还是更愿意把精力放在自己的主要业务上,不会希望所有的服务都自己做,因为这个年代,唯快不破,创业等不起一辈子。 
 
产品意识
 
回到开始那句话,“在对它不了解的情况下,仅通过google就能解决问题,不正说明它不难掌握有大量资料可查吗,实在不行还能翻代码。” 这话有些道理,然而却存在一个问题,这个问题就是:

作为一个使用者,是否有能力解决遇到的问题,与是否有意愿去遇到并解决问题,是两回事。

你有本CPU设计手册,你可以说处理器很简单,但我只想看个电影啊?给你Linux内核的源码,你可以说内核设计不难掌握,但我只想跑个游戏啊?何况他们是否因此就变得不难了,也是值得怀疑的。

这其实反映了技术人的产品意识。

很多技术人员喜欢玩酷的东西,他们愿意去探索新的领域,把不可能的变为可能。但是很多时候,他们做出来的东西却很难使用。

有的库可以增加很多参数,参数之间却有耦合,导致你在采用的时候需要写很多设置代码,而有点库却只需要一行代码;有的服务功能众多,却需要用户学习繁杂的步骤,而有的服务却可以开箱即用;有的服务功能可以实现,却会有很多不稳定甚至崩溃的情况出现,等等。

对于实现的工程师来讲,可能最大的区别在于,你是否考虑从用户的角度审视过自己的东西。即使这个服务也许只是为其他技术人员使用的。

技术人员可以,也应该,让技术人员更幸福。

最后,聊聊架构为大家送上一个关于技术选型的小漫画。其实技术并没有错,错的也许是我们.....




  查看全部

WOE262@[SP29YD2R2}GGSSQ.png
昨天聊聊架构发布了一篇关于技术选型的文章,文章作者介绍了目前流行的技术选型方式,比如有微博驱动、技术会议驱动、嗓门驱动、领导驱动.....不少读者都表示深有体会,并在评论区贴出了自己的经历。今天再推荐一篇由环信首席架构师一乐所撰写的关于技术选型的文章(旧文),希望能帮到各位。另推荐一乐的个人微信一乐来了,id是yilecoming,欢迎关注。
这也许是我上半年最大的欠账,在去普吉的飞机上突发无聊,想想还了这债吧。

去年的时候,我们使用Cassandra出了一次问题,定位加修复用了一晚上。当我把经历发出来的时候,收到了下面一段话:

“一个开源产品,连官方文档都没看完大半,然后匆匆忙忙上生产环境,出了问题团团转。若是不能掌控就先不要玩,说回这Cassandra的例子,在对它不了解的情况下,仅通过Google就能解决问题,不正说明它不难掌握有大量资料可查吗,实在不行还能翻代码。”

我现在都不知道这位神仙从哪里看到的匆匆忙忙上和团团转,当时我还是忍了,因为实在太忙,口水又没那么多。当然我思考了很多,这就是你现在看到的文章。我相信它会有一些价值,毕竟有些事情有的人你不告诉他,他永远不可能知道。比如个体认知的局限,比如口无遮拦的损失,比如做事之人才会有的思考角度。

本文讲的是技术选型。

大多数技术都存在选型问题,因为技术的发展已经让一件事情可以有多种解决方案,选型问题就自然出现。前段时间也有人说过语言选型,这里举的例子是在组件、框架、服务的范畴。其中有相通之处,各位可以自行领会。
 
选型最怕什么
 
怕失败么?那肯定的。你的服务崩溃,用户愤而投诉,客户电话打到老板那里,明天你要洗干净到办公室去一趟(笑...)。而所有对失败的无法容忍,最终都会变成一句话,为什么你要选这个型?

你总要回答这个问题,所以选型一怕随意,公鸡头母鸡头,选上哪头是哪头;二怕凭感觉,某某已经在用听起来还不错。你需要真正的思考,而且尽可能的全面。我下文会详细讲解,但这还不是最怕的。

最怕的是什么?看看本文开头引用的那句话,你体会一下。

嗯,最怕的是喷子。怕任意总结,如果再加上一些诋毁,一次选型失败足以让人心碎一万次。

失败不可怕,可怕的是没有总结,因为没有总结就没有提高。而比没有总结更可怕的是乱总结。

为了方便理解,我再帮你换个角度。你天天在河边走,一次不小心湿了鞋。如果是本文说的这种人,那肯定要说:

一条公共的河,你连旱季旺季都没搞清楚,就匆匆忙忙跑过来散步,湿了鞋还到处讲。若是脚不行就别在这玩,说回这条河,湿了鞋就能爬上来,不正说明他水不深么。

这种人实在不算少见。他说的每一句话都有一点道理,但都跟事情的本质毫无关系,每一句话又都掺加了嘲讽,来体现那无处安放的莫名优越感。而所有的这些,对于解决问题和后续提高通常毫无帮助。

想想也真遗憾,人生本是如此美好,有的人却硬生生地活成了奇葩。
 
选型需要什么
 
言归正传,我认为有三点不可或缺:分析、实验和胆量。
 
  •  分析

 
分析主要有定性分析和定量分析。实际操作中,前者主要针对的是模型维度的估计,用来考虑一个组件是否有可能达到它宣称的目的,后者主要用来验证,用来确认它是否在真的做到了。

比如在语言选型时,你要考虑它的范型、内存模型和并发设计;数据库选型时你要考虑存储模型、支撑量级、成本开销;开源项目要考虑它的社区发展、文档完善程度;如果是库或者中间件,还要考虑他的易用性、灵活性以及可替代性,等等。

需要说明的一点是,我个人并不觉得阅读全部源码或者文档这种事情是必须的,这不局限在OS、VM层面。不仅因为这样的事情会耗费过多精力,而且受制于代码以及文档质量,就算真正阅读完毕也未必意味着完全领会。

这些都是定性的,而定性的东西就有可能存在理解偏差。一个库可以完成工作,并不代表它在高并发压力下依然表现正常;一个语言做到了自动管理内存,并不代表他能做得很好没有副作用;一件事情设计者觉得达到了目标并不代表能够满足使用者期望。因此我们还需要量化分析,也就是一直口口相传的,用数据说话。

量化分析需要你构建或使用现成的工具和数据集,对服务进行特定场景下的分析。通过提高压力、增加容量或者针对性的测试,来验证之前的定性分析是否达到预期,并分析不同技术之间的差异和表现。
 
  • 实验

 
量化分析可以为真正的实验做一些准备和帮助,但是实验要走的明显更远。到了这一步,意味着要在真正的业务场景下进行验证,这跟量化分析中通用性场景有所不同。

在真正的业务中采用需要很多细致和琐碎的工作,除此之外,还要构建自己的测试工具集,这需要非常扎实的业务理解能力和勤奋的工作。而所有这些,你需要在开发环境做一次,在沙箱环境做一次,然后在仿真环境再做一次。

这几步经常被简化,但经验告诉我们,如果你想做一个高可用的系统,你就不应该少走任何一步。

步子大了,容易扯到蛋。 
 
  • 胆量

 
实验做完,剩下的就是上线,但这一步有很多人跨不过去。因为就算做了再多准备,你依然不敢说百分百保证没问题。现实情况是,80%的线上问题都是升级或者上线引起的。

你需要胆量。

这不是说要硬着头皮做,人家都是艺高才胆大。所以为了让胆子大一点,你首先需要考虑降级和开关。从最悲观的角度来重新审视整个方案,如果升级出现问题怎么办,如何才能让出现的问题影响最小化。

而只要弄完了这些,也就只要再记住一句话就行:

你行你上啊!
 
对技术服务的提醒
 
  • 得到认可

 
刚才在胆量里没说的一点。我们经常会看到,一项新技术在公司内久久难以推行,因为业务主管百般阻挠。即使排除利益纠葛,仍然会发现一种发自心底的不信任存在。而这种不信任,又往往来源于对同事工作的不认可。

这个问题原因很多,也许没有通用的解决方案,但我说一个例子。

我们最近开始使用Codis,就是@goroutine 和几个家伙之前搞过的玩意儿。虽然他们最近已经独立开搞像Google Spanner但拥有更高级特性的TiDB(就是太牛了的意思)。由于我对他们比较熟悉和认可,所以在Codis尝试方面也多出很多底气。这种信任并非完全来自于出问题之后的直接电话支持,而是真心觉得活儿好。

反过来,这对很多服务也是一个提醒,特别是云服务。也许只要你得到合作伙伴的认可,或者至少让他们觉得,自己动手不会比你做得更好,你基本也就成功了。

对于大多数理性创业公司来讲,他们还是更愿意把精力放在自己的主要业务上,不会希望所有的服务都自己做,因为这个年代,唯快不破,创业等不起一辈子。 
 
  • 产品意识

 
回到开始那句话,“在对它不了解的情况下,仅通过google就能解决问题,不正说明它不难掌握有大量资料可查吗,实在不行还能翻代码。” 这话有些道理,然而却存在一个问题,这个问题就是:

作为一个使用者,是否有能力解决遇到的问题,与是否有意愿去遇到并解决问题,是两回事。

你有本CPU设计手册,你可以说处理器很简单,但我只想看个电影啊?给你Linux内核的源码,你可以说内核设计不难掌握,但我只想跑个游戏啊?何况他们是否因此就变得不难了,也是值得怀疑的。

这其实反映了技术人的产品意识。

很多技术人员喜欢玩酷的东西,他们愿意去探索新的领域,把不可能的变为可能。但是很多时候,他们做出来的东西却很难使用。

有的库可以增加很多参数,参数之间却有耦合,导致你在采用的时候需要写很多设置代码,而有点库却只需要一行代码;有的服务功能众多,却需要用户学习繁杂的步骤,而有的服务却可以开箱即用;有的服务功能可以实现,却会有很多不稳定甚至崩溃的情况出现,等等。

对于实现的工程师来讲,可能最大的区别在于,你是否考虑从用户的角度审视过自己的东西。即使这个服务也许只是为其他技术人员使用的。

技术人员可以,也应该,让技术人员更幸福。

最后,聊聊架构为大家送上一个关于技术选型的小漫画。其实技术并没有错,错的也许是我们.....
R[Z}P6_7OS1Y}196XXK5Y.png

 
0
评论

美团热更方案ASM实践 Android 环信 热更新

开发讨论beyond 发表了文章 • 3906 次浏览 • 2017-01-06 16:57 • 来自相关话题

    美团热更新的文章已经讲了,他们用的是Instant Run的方案。
这篇文章主要讲美团热更方案中没讲到的部分,包含几个方面:
作为云服务提供厂商,需要提供给客户SDK,SDK发布后同样要考虑bug修复问题。这里讲一下作为sdk发布者的热更新方案选型,也就是为什么用美团方案&Instant Run方案。美团方案实现的大致结构最后讲一下asm插桩的过程,字节码导读,以及遇到的各种坑。
 方案选择:

  我们公司提供及时通讯服务,同时需要提供给用户方便集成的及时通讯的SDK,每次SDK发布的同时也面临SDK发布后紧急bug的修复问题。 现在市面上的热更新方案通常不适用SDK提供方使用。 以阿里的andFix和微信的tinker为例,都是直接修改并合成新的apk。这样做对于普通app没有问题,但是对于sdk提供方是不可以的,SDK发布者不能够直接修改apk,这个事情只能由app开发者来做。

tinker方案如图:




女娲方案,由大众点评Jason Ross实现并开源,他们是在classLoader过程中,將自己的修改后的patch类所在的dex, 插入到dex队列前面,这样在classloader按照类名字加载的时候会优先加载patch类。

女娲方案如图:




   女娲方案有一个条件约束,就是每个类都要插桩,插入一个类的引用,并且这个被引用类需要打包到单独的dex文件中,这样保证每个类都没有被打上CLASS_ISPREVERIFIED标志。 具体详细描述在早期的hotpatch方案 安卓App热补丁动态修复技术介绍

  作为SDK提供者,只能提供jar包给用户,无法约束用户的dex生成过程,所以女娲方案无法直接应用。 女娲方案是开源的,而且其中提供了asm插桩示例,对于后面应用美团方案有很好参考意义。

美团&&Instant Run方案

   美团方案 也就是Instant Run的方案基本思路就是在每个函数都插桩,如果一个类存在bug,需要修复,就将插桩点类的changeRedirect字段从null值变成patch类。 基本原理在美团方案中有讲述,但是美团文中没有讲最重要的一个问题,就是如何在每一个函数前面插桩,下面会详细讲一下。 Patch应用部分,这里忽略,因为是java代码,大家可以反编译Instant Run.jar,看一下大致思路,基本都能写出来。

插桩

   插桩的动作就是在每一个函数前面都插入PatchProxy.isSupport...PatchProxy.accessDisPatch这一系列代码(参看美团方案)。插桩工作直接修改class文件,因为这样不影响正常代码逻辑,只有最后打包发布的时候才进行插桩。
   插桩最常用的是asm.jar。接下来的部分需要用户先了解asm.jar的大致使用流程。了解这个过程最好是找个实例实践一下,光看介绍文章是看不懂的。

   asm有两种方式解析class文件,一种是core API, provides an event based representation of classes,类似解析xml的SAX的事件触发方式,遍历类,以及类的字段,类中的方法,在遍历的过程中会依次触发相应的函数,比如遍历类函数时,触发visitMethod(name, signature...),用户可以在这个方法中修改函数实现。 另外一种 tree API, provides an object based representation,类似解析xml中的DOM树方式。本文中,这里使用了core API方式。asm.jar有对应的manual asm4-guide.pdf,需要仔细研读,了解其用法。

使用asm.jar把java class反编译为字节码

反编译为字节码对应的命令是java -classpath "asm-all.jar" org.jetbrains.org.objectweb.asm.util.ASMifier State.class    这个地方有一个坑,官方版本asm.jar 在执行ASMifier命令的时候总是报错,后来在Android Stuidio的目录下面找一个asm-all.jar替换再执行就不出问题了。但是用asm.jar插桩的过程,仍然使用官方提供的asm.jar。
 
插入前代码:class State {
long getIndex(int val) {
return 100;
}
}ASMifier反编译后字节码如下mv = cw.visitMethod(0, "getIndex", "(I)J", null, null);
mv.visitCode();
mv.visitLdcInsn(new Long(100L));
mv.visitInsn(LRETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();插桩后代码:long getIndex(int a) {
if ($patch != null) {
if (PatchProxy.isSupport(new Object[0], this, $patch, false)) {
return ((Long) com.hyphenate.patch.PatchProxy.accessDispatch(new Object[] {a}, this, $patch, false)).longValue();
}
}
return 100;
}ASMifier反编译后代码如下:mv = cw.visitMethod(ACC_PUBLIC, "getIndex", "(I)J", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
Label l0 = new Label();
mv.visitJumpInsn(IFNULL, l0);
mv.visitInsn(ICONST_0);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "isSupport", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Z", false);
mv.visitJumpInsn(IFEQ, l0);
mv.visitIntInsn(BIPUSH, 1);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitIntInsn(BIPUSH, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
mv.visitInsn(AASTORE);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "accessDispatch", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitInsn(LRETURN);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitLdcInsn(new Long(100L));
mv.visitInsn(LRETURN);
mv.visitMaxs(4, 2);
mv.visitEnd();对于插桩程序来说,需要做的就是把差异部分插桩到代码中​
 
   需要将全部入參传递给patch方法,插入的代码因此会根据入參进行调整,同时也要处理返回值.

   可以观察上面代码,上面的例子显示了一个int型入參a,装箱变成Integer,放在一个Object[]数组中,先后调用isSupport和accessDispatch,传递给patch类的对应方法,patch返回类型是Long,然后调用longValue,拆箱变成long类型。

   对于普通的java对象,因为均派生自Object,所以对象的引用直接放在数组中;对于primitive类型(包括int, long, float....)的处理,需要先调用Integer, Boolean, Float等java对象的构造函数,将primitive类型装箱后作为object对象放在数组中。

   如果原来函数返回结果的是primitive类型,需要插桩代码将其转化为primitive类型。还要处理数组类型,和void类型。 java的primitive类型在 java virtual machine specification中有定义。
 
   这个插入过程有两个关键问题,一个是函数signature的解析,另外一个是适配这个参数变化插入代码。下面详细解释下:@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {   这个函数是asm.jar访问类函数时触发的事件,desc变量对应java jni中的signature,比如这里是'(I)J', 需要解析并转换成primitive类型,类,数组,void。这部分代码参考了android底层的源码libcore/luni/src/main/java/libcore/reflect,和sun java的SignatureParser.java,都有反映了这个遍历过程。

   关于java字节码的理解,汇编指令主要是看 Java bytecode instruction listings

   理解java字节码,需要理解jvm中的栈的结构。JVM是一个基于栈的架构。方法执行的时候(包括main方法),在栈上会分配一个新的帧,这个栈帧包含一组局部变量。这组局部变量包含了方法运行过程中用到的所有变量,包括this引用,所有的方法参数,以及其它局部定义的变量。对于类方法(也就是static方法)来说,方法参数是从第0个位置开始的,而对于实例方法来说,第0个位置上的变量是this指针。引自: Java字节码浅析

分析中间部分字节码实现,com.hyphenate.patch.PatchProxy.accessDispatch(new Object[] {a}, this, $patch, false))对应字节码如下,请对照Java bytecode instruction listings中每条指令观察对应栈帧的变化,下面注释中'[]'中表示栈帧中的内容。
mv.visitIntInsn(BIPUSH, 1); # 数字1入栈,对应new Object[1]数组长度1。 栈:[1]
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); # ANEWARRY:count(1) → arrayref, 栈:[arr_ref]
mv.visitInsn(DUP); # 栈:[arr_ref, arr_ref]
mv.visitIntInsn(BIPUSH, 0); # 栈:[arr_ref, arr_ref, 0]
mv.visitVarInsn(ILOAD, 1); # 局部变量位置1的内容入栈, 栈:[arr_ref, arr_ref, 0, a]
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); # 调用Integer.valueOf, INVOKESTATIC: [arg1, arg2, ...] → result, 栈:[arr_ref, arr_ref, 0, integerObjectOf_a]
mv.visitInsn(AASTORE); # store a reference into array: arrayref, index, value →, 栈:[arr_ref]
mv.visitVarInsn(ALOAD, 0); # this入栈,栈:[arr_ref, this]
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;"); #$patch入栈,栈:[arr_ref, this, $patch]
mv.visitInsn(ICONST_0); #false入栈, # 栈:[arr_ref, this, $patch, false]
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "accessDispatch", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Ljava/lang/Object;", false); # 调用accessDispatch, 栈包含返回结果,栈:[longObject]熟悉上面的字节码以及对应的栈帧变化,也就掌握了插桩过程。
 
坑:

   ClassVisitor.visitMethod()中access如果是ACC_SYNTHETIC或者ACC_BRIDGE,插桩后无法正常运行。ACC_SYNTHETIC表示函数由javac自动生成的,enum类型就会产生这种类型的方法,不需要插桩,直接略过。因为观察到模版类也会产生ACC_SYNTHETIC,所以插桩过程跳过了模版类。

ClassVisitor.visit()函数对应遍历到类触发的事件,access如果是ACC_INTERFACE或者ACC_ENUM,无需插桩。简单说就是接口和enum不涉及方法修改,无需插桩。

静态方法的实现和普通类成员函数略有出入,对于汇编程序来说,本地栈的第一个位置,如果是普通方法,会存储this引用,static方法没有this,这里稍微调整一下就可以实现的。

不定参数由于要求连续输入的参数类型相同,被编译器直接变成了数组,所以对本程序没有造成影响。

大小:

   插桩因为对每个函数都插桩,反编译后看实际上增加了大量代码,甚至可以说插入的代码比原有的代码还要多。但是实际上最终生成的jar包增长了大概20%多一点,并没有想的那么多,在可接受范围内。因为class所占的空间不止是代码部分,还包括类描述,字段描述,方法描述,const-pool等,代码段只占其中的不到一半。可以参考[The class File Format](link @http://docs.oracle.com/javase/ ... 4.html)

讨论

   前面代码插桩的部分和美团热更文章中保持一致,实际上还有些细节还可以调整。isSupport这个函数的参数可以调整如下if (PatchProxy.isSupport(“getIndex”, "(I)J", false)) {这样能减小插桩部分代码,而且可以区分名字相同的函数。

PatchProxy.isSupport最后一个参数表示是普通类函数还是static函数,这个是方便java应用patch的时候处理。

源码地址
https://github.com/easemob/empatch
 作者:李楠
公司:环信
关注领域:Android开发
文章署名: greenmemo 查看全部
    美团热更新的文章已经讲了,他们用的是Instant Run的方案。
这篇文章主要讲美团热更方案中没讲到的部分,包含几个方面:
  1. 作为云服务提供厂商,需要提供给客户SDK,SDK发布后同样要考虑bug修复问题。这里讲一下作为sdk发布者的热更新方案选型,也就是为什么用美团方案&Instant Run方案。
  2. 美团方案实现的大致结构
  3. 最后讲一下asm插桩的过程,字节码导读,以及遇到的各种坑。

 方案选择:

  我们公司提供及时通讯服务,同时需要提供给用户方便集成的及时通讯的SDK,每次SDK发布的同时也面临SDK发布后紧急bug的修复问题。 现在市面上的热更新方案通常不适用SDK提供方使用。 以阿里的andFix和微信的tinker为例,都是直接修改并合成新的apk。这样做对于普通app没有问题,但是对于sdk提供方是不可以的,SDK发布者不能够直接修改apk,这个事情只能由app开发者来做。

tinker方案如图:
图片1.png

女娲方案,由大众点评Jason Ross实现并开源,他们是在classLoader过程中,將自己的修改后的patch类所在的dex, 插入到dex队列前面,这样在classloader按照类名字加载的时候会优先加载patch类。

女娲方案如图:
图片2.png

   女娲方案有一个条件约束,就是每个类都要插桩,插入一个类的引用,并且这个被引用类需要打包到单独的dex文件中,这样保证每个类都没有被打上CLASS_ISPREVERIFIED标志。 具体详细描述在早期的hotpatch方案 安卓App热补丁动态修复技术介绍

  作为SDK提供者,只能提供jar包给用户,无法约束用户的dex生成过程,所以女娲方案无法直接应用。 女娲方案是开源的,而且其中提供了asm插桩示例,对于后面应用美团方案有很好参考意义。

美团&&Instant Run方案

   美团方案 也就是Instant Run的方案基本思路就是在每个函数都插桩,如果一个类存在bug,需要修复,就将插桩点类的changeRedirect字段从null值变成patch类。 基本原理在美团方案中有讲述,但是美团文中没有讲最重要的一个问题,就是如何在每一个函数前面插桩,下面会详细讲一下。 Patch应用部分,这里忽略,因为是java代码,大家可以反编译Instant Run.jar,看一下大致思路,基本都能写出来。

插桩

   插桩的动作就是在每一个函数前面都插入PatchProxy.isSupport...PatchProxy.accessDisPatch这一系列代码(参看美团方案)。插桩工作直接修改class文件,因为这样不影响正常代码逻辑,只有最后打包发布的时候才进行插桩。
   插桩最常用的是asm.jar。接下来的部分需要用户先了解asm.jar的大致使用流程。了解这个过程最好是找个实例实践一下,光看介绍文章是看不懂的。

   asm有两种方式解析class文件,一种是core API, provides an event based representation of classes,类似解析xml的SAX的事件触发方式,遍历类,以及类的字段,类中的方法,在遍历的过程中会依次触发相应的函数,比如遍历类函数时,触发visitMethod(name, signature...),用户可以在这个方法中修改函数实现。 另外一种 tree API, provides an object based representation,类似解析xml中的DOM树方式。本文中,这里使用了core API方式。asm.jar有对应的manual asm4-guide.pdf,需要仔细研读,了解其用法。

使用asm.jar把java class反编译为字节码

反编译为字节码对应的命令是
java -classpath "asm-all.jar"   org.jetbrains.org.objectweb.asm.util.ASMifier State.class 
   这个地方有一个坑,官方版本asm.jar 在执行ASMifier命令的时候总是报错,后来在Android Stuidio的目录下面找一个asm-all.jar替换再执行就不出问题了。但是用asm.jar插桩的过程,仍然使用官方提供的asm.jar。
 
插入前代码:
class State {
long getIndex(int val) {
return 100;
}
}
ASMifier反编译后字节码如下
mv = cw.visitMethod(0, "getIndex", "(I)J", null, null);
mv.visitCode();
mv.visitLdcInsn(new Long(100L));
mv.visitInsn(LRETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
插桩后代码:
long getIndex(int a) {
if ($patch != null) {
if (PatchProxy.isSupport(new Object[0], this, $patch, false)) {
return ((Long) com.hyphenate.patch.PatchProxy.accessDispatch(new Object[] {a}, this, $patch, false)).longValue();
}
}
return 100;
}
ASMifier反编译后代码如下:
mv = cw.visitMethod(ACC_PUBLIC, "getIndex", "(I)J", null, null);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
Label l0 = new Label();
mv.visitJumpInsn(IFNULL, l0);
mv.visitInsn(ICONST_0);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "isSupport", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Z", false);
mv.visitJumpInsn(IFEQ, l0);
mv.visitIntInsn(BIPUSH, 1);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitIntInsn(BIPUSH, 0);
mv.visitVarInsn(ILOAD, 1);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
mv.visitInsn(AASTORE);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;");
mv.visitInsn(ICONST_0);
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "accessDispatch", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
mv.visitInsn(LRETURN);
mv.visitLabel(l0);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitLdcInsn(new Long(100L));
mv.visitInsn(LRETURN);
mv.visitMaxs(4, 2);
mv.visitEnd();
对于插桩程序来说,需要做的就是把差异部分插桩到代码中​
 
   需要将全部入參传递给patch方法,插入的代码因此会根据入參进行调整,同时也要处理返回值.

   可以观察上面代码,上面的例子显示了一个int型入參a,装箱变成Integer,放在一个Object[]数组中,先后调用isSupport和accessDispatch,传递给patch类的对应方法,patch返回类型是Long,然后调用longValue,拆箱变成long类型。

   对于普通的java对象,因为均派生自Object,所以对象的引用直接放在数组中;对于primitive类型(包括int, long, float....)的处理,需要先调用Integer, Boolean, Float等java对象的构造函数,将primitive类型装箱后作为object对象放在数组中。

   如果原来函数返回结果的是primitive类型,需要插桩代码将其转化为primitive类型。还要处理数组类型,和void类型。 java的primitive类型在 java virtual machine specification中有定义。
 
   这个插入过程有两个关键问题,一个是函数signature的解析,另外一个是适配这个参数变化插入代码。下面详细解释下:
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
   这个函数是asm.jar访问类函数时触发的事件,desc变量对应java jni中的signature,比如这里是'(I)J', 需要解析并转换成primitive类型,类,数组,void。这部分代码参考了android底层的源码libcore/luni/src/main/java/libcore/reflect,和sun java的SignatureParser.java,都有反映了这个遍历过程。

   关于java字节码的理解,汇编指令主要是看 Java bytecode instruction listings

   理解java字节码,需要理解jvm中的栈的结构。JVM是一个基于栈的架构。方法执行的时候(包括main方法),在栈上会分配一个新的帧,这个栈帧包含一组局部变量。这组局部变量包含了方法运行过程中用到的所有变量,包括this引用,所有的方法参数,以及其它局部定义的变量。对于类方法(也就是static方法)来说,方法参数是从第0个位置开始的,而对于实例方法来说,第0个位置上的变量是this指针。引自: Java字节码浅析

分析中间部分字节码实现,
com.hyphenate.patch.PatchProxy.accessDispatch(new Object[] {a}, this, $patch, false))
对应字节码如下,请对照Java bytecode instruction listings中每条指令观察对应栈帧的变化,下面注释中'[]'中表示栈帧中的内容。
mv.visitIntInsn(BIPUSH, 1);  # 数字1入栈,对应new Object[1]数组长度1。 栈:[1]
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); # ANEWARRY:count(1) → arrayref, 栈:[arr_ref]
mv.visitInsn(DUP); # 栈:[arr_ref, arr_ref]
mv.visitIntInsn(BIPUSH, 0); # 栈:[arr_ref, arr_ref, 0]
mv.visitVarInsn(ILOAD, 1); # 局部变量位置1的内容入栈, 栈:[arr_ref, arr_ref, 0, a]
mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); # 调用Integer.valueOf, INVOKESTATIC: [arg1, arg2, ...] → result, 栈:[arr_ref, arr_ref, 0, integerObjectOf_a]
mv.visitInsn(AASTORE); # store a reference into array: arrayref, index, value →, 栈:[arr_ref]
mv.visitVarInsn(ALOAD, 0); # this入栈,栈:[arr_ref, this]
mv.visitFieldInsn(GETSTATIC, "com/hyphenate/State", "$patch", "Lcom/hyphenate/patch/PatchReDirection;"); #$patch入栈,栈:[arr_ref, this, $patch]
mv.visitInsn(ICONST_0); #false入栈, # 栈:[arr_ref, this, $patch, false]
mv.visitMethodInsn(INVOKESTATIC, "com/hyphenate/patch/PatchProxy", "accessDispatch", "([Ljava/lang/Object;Ljava/lang/Object;Lcom/hyphenate/patch/PatchReDirection;Z)Ljava/lang/Object;", false); # 调用accessDispatch, 栈包含返回结果,栈:[longObject]
熟悉上面的字节码以及对应的栈帧变化,也就掌握了插桩过程。
 
坑:

   ClassVisitor.visitMethod()中access如果是ACC_SYNTHETIC或者ACC_BRIDGE,插桩后无法正常运行。ACC_SYNTHETIC表示函数由javac自动生成的,enum类型就会产生这种类型的方法,不需要插桩,直接略过。因为观察到模版类也会产生ACC_SYNTHETIC,所以插桩过程跳过了模版类。

ClassVisitor.visit()函数对应遍历到类触发的事件,access如果是ACC_INTERFACE或者ACC_ENUM,无需插桩。简单说就是接口和enum不涉及方法修改,无需插桩。

静态方法的实现和普通类成员函数略有出入,对于汇编程序来说,本地栈的第一个位置,如果是普通方法,会存储this引用,static方法没有this,这里稍微调整一下就可以实现的。

不定参数由于要求连续输入的参数类型相同,被编译器直接变成了数组,所以对本程序没有造成影响。

大小:

   插桩因为对每个函数都插桩,反编译后看实际上增加了大量代码,甚至可以说插入的代码比原有的代码还要多。但是实际上最终生成的jar包增长了大概20%多一点,并没有想的那么多,在可接受范围内。因为class所占的空间不止是代码部分,还包括类描述,字段描述,方法描述,const-pool等,代码段只占其中的不到一半。可以参考[The class File Format](link @http://docs.oracle.com/javase/ ... 4.html)

讨论

   前面代码插桩的部分和美团热更文章中保持一致,实际上还有些细节还可以调整。isSupport这个函数的参数可以调整如下if (PatchProxy.isSupport(“getIndex”, "(I)J", false)) {这样能减小插桩部分代码,而且可以区分名字相同的函数。

PatchProxy.isSupport最后一个参数表示是普通类函数还是static函数,这个是方便java应用patch的时候处理。

源码地址
https://github.com/easemob/empatch
 作者:李楠
公司:环信
关注领域:Android开发
文章署名: greenmemo
0
评论

李理:自动梯度求解——cs231n的notes 环信 深度学习 李理

开发讨论beyond 发表了文章 • 2963 次浏览 • 2016-12-21 11:05 • 来自相关话题

本系列文章面向深度学习研发者,希望通过Image Caption Generation,一个有意思的具体任务,深入浅出地介绍深度学习的知识。本系列文章涉及到很多深度学习流行的模型,如CNN,RNN/LSTM,Attention等。本文为第5篇。
 
作者:李理 
目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。
相关文章: 
环信李理:从Image Caption Generation了解深度学习
李理:从Image Caption Generation理解深度学习(part II)
李理:从Image Caption Generation理解深度学习(part III)
李理:自动梯度求解 反向传播算法的另外一种视角
 
Optimization

这一部分内容来自:CS231n Convolutional Neural Networks for Visual Recognition

简介

我们的目标:x是一个向量,f(x)是一个函数,它的输入是一个向量(或者认为是多变量的函数,这个输入向量就是自变量),输出是一个实数值。我们需要计算的是f对每一个自变量的导数,然后把它们排成一个向量,也就是梯度。




为什么要求这个呢?前面我们也讲了,我们的神经网络的损失函数最终可以看成是权重weights和bias的函数,我们的目标就是调整这些参数,使得损失函数最小。

简单的表达式和梯度的解释

首先我们看一个很简单的函数 f(x,y)=xy,求f对x和y的偏导数很简单:




首先来看导数的定义:




函数在某个点的导数就是函数曲线在这个点的斜率,也就是f(x)随x的变化率。 
比如上面的例子,当x=4,y=−3时 f(x,y)=−12,f对x的偏导数




也就是说,如果我们固定y=4,然后给x一个很小的变化h,那么f(x,y)的变化大约是-3*h。 
因此乘法的梯度就是




同样,加法的梯度更简单:




最后一个简单函数是max函数:




这个导数是ReLU(x)=max(x,0)的导数,其实也简单,如果 x>=y,那么 max(x,y)=x,则导数是1,否则 max(x,y)=0,那么对x求导就是0。

复杂表达式的链式法则

接下来看一个稍微复杂一点的函数 f(x,y,z)=(x+y)z。我们引入一个中间变量q,f=qz,q=x+y,我们可以使用链式法则求f对x和y的导数。




对y的求导也是类似的。

下面是用python代码来求f对x和y的导数在某一个点的值。# 设置自变量的值
x = -2; y = 5; z = -4

# “前向”计算f
q = x + y # q becomes 3
f = q * z # f becomes -12

# 从“后”往前“反向”计算
# 首先是 f = q * z
dfdz = q # 因为df/dz = q, 所以f对z的梯度是 3
dfdq = z # 因为df/dq = z, 所以f对q的梯度是 -4
# 然后 q = x + y
dfdx = 1.0 * dfdq # 因为dq/dx = 1,所以使用链式法则计算dfdx=-4
dfdy = 1.0 * dfdq # 因为dq/dy = 1,所以使用链式法则计算dfdy=-4我们也可以用计算图来表示和计算:




绿色的值是feed forward的结果,而红色的值是backprop的结果。

不过我觉得cs231n课程的这个图没有上面blog的清晰,原因是虽然它标示出来了最终的梯度,但是没有标示出local gradient,我在下面会画出完整的计算过程。

反向传播算法的直觉解释

我们如果把计算图的每一个点看成一个“门”(或者一个模块),或者说一个函数。它有一个输入(向量),也有一个输出(标量)。对于一个门来说有两个计算,首先是根据输入,计算输出,这个一般很容易。还有一种计算就是求输出对每一个输入的偏导数,或者说输出对输入向量的”局部“梯度(local gradient)。一个复杂计算图(神经网络)的计算首先就是前向计算,然后反向计算,反向计算公式可能看起来很复杂,但是如果在计算图上其实就是简单的用local gradient乘以从后面传过来的gradient,然后加起来。

Sigmoid模块的例子

接下来我们看一个更复杂的例子:




这个函数是一个比较复杂的复合函数,但是构成它的基本函数是如下4个简单函数:




下面是用计算图画出这个计算过程:




这个图有4种gate,加法,乘法,指数和倒数。加法有加一个常数和两个变量相加,乘法也是一样。

上图绿色的值是前向计算的结果,而红色的值是反向计算的结果,local graident并没有标示出来,所以看起来可能有些跳跃,下面我在纸上详细的分解了其中的步骤,请读者跟着下图自己动手计算一遍。




上图就是前向计算的过程,比较简单。




第二个图是计算local gradient,对于两个输入的乘法和加法,local gradient也是两个值,local gradient的值我是放到图的节点上了。




第三个图是具体计算一个乘法的local gradient的过程,因为上图可能看不清,所以单独放大了这一步。




最后计算真正的梯度,是把local gradient乘以来自上一步的gradient。不过这个例子一个节点只有一个输出,如果有多个的话,梯度是加起来的,可以参考1.4的




上面我们看到把




分解成最基本的加法,乘法,导数和指数函数,但是我们也可以不分解这么细。之前我们也学习过了sigmoid函数,那么我们可以这样分解:




σ(x)σ(x) 的导数我们之前已经推导过一次了,这里再列一下:




因此我们可以把后面一长串的gate”压缩“成一个gate:




我们来比较一下,之前前向计算 σ(x)σ(x) 需要一次乘法,一次exp,一次加法导数;而反向计算需要分别计算这4个gate的导数。

而压缩后前向计算是一样的,但是反向计算可以”利用“前向计算的结果




这只需要一次减法和一次乘法!当然如果不能利用前向的结果,我们如果需要重新计算 σ(x)σ(x) ,那么压缩其实没有什么用处。能压缩的原因在于σ函数导数的特殊形式。而神经网络的关键问题是在训练,训练性能就取决于这些细节。如果是我们自己来实现反向传播算法,我们就需要利用这样的特性。而如果是使用工具,那么就依赖于工具的优化水平了。

下面我们用代码来实现一下:w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]

# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function

# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit上面的例子用了一个小技巧,就是所谓的staged backpropagation,说白了就是给中间的计算节点起一个名字。比如dot。为了让大家熟悉这种技巧,下面有一个例子。

Staged computation练习




我们用代码来计算这个函数对x和y的梯度在某一点的值

前向计算x = 3 # example values
y = -4

# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # 分子上的sigmoid #(1)
num = x + sigy # 分子 #(2)
sigx = 1.0 / (1 + math.exp(-x)) # 分母上的sigmoid #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # 分母 #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)反向计算# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
# done! phew需要注意的两点:1. 前向的结果都要保存下来,反向的时候要用的。2. 如果某个变量有多个出去的边,第一次是等于,第二次就是+=,因为我们要把不同出去点的梯度加起来。

下面我们来逐行分析反向计算: 
(8) f = num * invden 
local gradient




而上面传过来的梯度是1,所以 dnum=1∗invden。注意变量的命名规则, df/dnum就命名为dnum【省略了df,因为默认我们是求f对所有变量的偏导数】 
同理: dinvden=num

(7) invden = 1.0 / den

local gradient是 (−1.0/(den∗∗2)) ,然后乘以上面来的dinvden

(6) den = sigx + xpysqr

这个函数有两个变量sigx和xpysqr,所以需要计算两个local梯度,然后乘以dden 
加法的local梯度是1,所以就是(1)*dden

(5) xpysqr = xpy**2

local gradient是2*xpy,再乘以dxpysqr

(4) xpy = x + y

还是一个加法,local gradient是1,所以dx和dy都是dxpy乘1

(3) sigx = 1.0 / (1 + math.exp(-x))

这是sigmoid函数,local gradient是 (1-sigx)*sigx,再乘以dsigx。 
不过需要注意的是这是dx的第二次出现,所以是+=,表示来自不同路径反向传播过来给x的梯度值

(2) num = x + sigy

还是个很简单的加法,local gradient是1。需要注意的是dx是+=,理由同上。

(1) sigy = 1.0 / (1 + math.exp(-y))

最后是sigmoid(y)和前面(3)一样的。

请仔细阅读上面反向计算的每一步代码,确保自己理解了之后再往下阅读。

梯度的矩阵运算

前面都是对一个标量的计算,在实际实现时用矩阵运算一次计算一层的所有梯度会更加高效。因为矩阵乘以向量和向量乘以向量都可以看出矩阵乘以矩阵的特殊形式,所以下面我们介绍矩阵乘法怎么求梯度。

首先我们得定义什么叫矩阵对矩阵的梯度!

我查阅了很多资料,也没找到哪里有矩阵对矩阵的梯度的定义,如果哪位读者知道,请告诉我,谢谢!唯一比较接近的是Andrew Ng的课程cs294的背景知识介绍的slides linalg的4.1节定义了gradient of Matrix,关于矩阵对矩阵的梯度我会有一个猜测性的解释,可能会有问题。

首先介绍graident of matrix

假设 f:Rm×n→R是一个函数,输入是一个m×n的实数值矩阵,输出是一个实数。那么f对A的梯度是如下定义的:




看起来定义很复杂?其实很简单,我们把f看成一个mn个自变量的函数,因此我们可以求f对这mn个自变量的偏导数,然后把它们排列成m*n的矩阵就行了。为什么要多此一举把变量拍成矩阵把他们的偏导数也排成矩阵?想想我们之前的神经网络的weights矩阵,这是很自然的定义,同时我们需要计算loss对weights矩阵的每一个变量的偏导数,写出这样的形式计算起来比较方便。

那么什么是矩阵对矩阵的梯度呢?我们先看实际神经网络的一个计算情况。对于全连接的神经网络,我们有一个矩阵乘以向量 D=WxD=Wx 【我们这里把向量x看成矩阵】。现在我们需要计算loss对某一个 WijWij 的偏导数,根据我们之前的计算图, WijWij 有多少条出边,那么就有多少个要累加的梯度乘以local梯度。 
假设W是m×n的矩阵,x是n×p的矩阵,则D是m×p的矩阵




根据矩阵乘法的定义




我们可以计算:




请仔细理解上面这一步,如果 k≠i,则不论s是什么,Wks跟Wij不是同一个变量,所以导数就是0;如果k=i,∑sWisxsl=xjl,也就求和的下标s取j的时候有WijWij。 
因此




上面计算了loss对一个Wij的偏导数,如果把它写成矩阵形式就是:




前面我们推导出了对Wij的偏导数的计算公式,下面我们把它写成矩阵乘法的形式并验证【证明】它。




为什么可以写成这样的形式呢?




上面的推导似乎很复杂,但是我们只要能记住就行,记法也很简单——把矩阵都变成最特殊的1 1的矩阵(也就是标量,一个实数)。D=w x,这个导数很容易吧,对w求导就是local gradient x,然后乘以得到dW=dD x;同理dx=dD W。 
但是等等,刚才那个公式里还有矩阵的转置,这个怎么记?这里有一个小技巧,就是矩阵乘法的条件,两个矩阵能相乘他们的大小必须匹配,比如D=Wx,W是m n,x是n p,也就是第二个矩阵的行数等于第一个的列数。 
现在我们已经知道dW是dD”乘以“x了,dW的大小和W一样是m n,而dD和D一样是m p,而x是n p,那么为了得到一个m n的矩阵,唯一的办法就是 dD∗xT 
同理dx是n p,dD是m p,W是m*n,唯一的乘法就是 WT∗dD 
下面是用python代码来演示,numpy的dot就是矩阵乘法,可以用numpy.dot(A,B),也可以直接调用ndarray的dot函数——A.dot(B):# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)

# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)至此,本系列文章的第5部分告一段落。在接下来的文章中,作者将为大家详细讲述关于常见的深度学习框架/工具的使用方法、使用自动求导来实现多层神经网络等内容,敬请期待。 查看全部
本系列文章面向深度学习研发者,希望通过Image Caption Generation,一个有意思的具体任务,深入浅出地介绍深度学习的知识。本系列文章涉及到很多深度学习流行的模型,如CNN,RNN/LSTM,Attention等。本文为第5篇。
 
作者:李理 
目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。
相关文章: 
环信李理:从Image Caption Generation了解深度学习
李理:从Image Caption Generation理解深度学习(part II)
李理:从Image Caption Generation理解深度学习(part III)
李理:自动梯度求解 反向传播算法的另外一种视角
 
Optimization

这一部分内容来自:CS231n Convolutional Neural Networks for Visual Recognition

简介

我们的目标:x是一个向量,f(x)是一个函数,它的输入是一个向量(或者认为是多变量的函数,这个输入向量就是自变量),输出是一个实数值。我们需要计算的是f对每一个自变量的导数,然后把它们排成一个向量,也就是梯度。
001.jpg

为什么要求这个呢?前面我们也讲了,我们的神经网络的损失函数最终可以看成是权重weights和bias的函数,我们的目标就是调整这些参数,使得损失函数最小。

简单的表达式和梯度的解释

首先我们看一个很简单的函数 f(x,y)=xy,求f对x和y的偏导数很简单:
002.jpg

首先来看导数的定义:
003.jpg

函数在某个点的导数就是函数曲线在这个点的斜率,也就是f(x)随x的变化率。 
比如上面的例子,当x=4,y=−3时 f(x,y)=−12,f对x的偏导数
004.jpg

也就是说,如果我们固定y=4,然后给x一个很小的变化h,那么f(x,y)的变化大约是-3*h。 
因此乘法的梯度就是
005.jpg

同样,加法的梯度更简单:
006.jpg

最后一个简单函数是max函数:
007.jpg

这个导数是ReLU(x)=max(x,0)的导数,其实也简单,如果 x>=y,那么 max(x,y)=x,则导数是1,否则 max(x,y)=0,那么对x求导就是0。

复杂表达式的链式法则

接下来看一个稍微复杂一点的函数 f(x,y,z)=(x+y)z。我们引入一个中间变量q,f=qz,q=x+y,我们可以使用链式法则求f对x和y的导数。
008.jpg

对y的求导也是类似的。

下面是用python代码来求f对x和y的导数在某一个点的值。
# 设置自变量的值
x = -2; y = 5; z = -4

# “前向”计算f
q = x + y # q becomes 3
f = q * z # f becomes -12

# 从“后”往前“反向”计算
# 首先是 f = q * z
dfdz = q # 因为df/dz = q, 所以f对z的梯度是 3
dfdq = z # 因为df/dq = z, 所以f对q的梯度是 -4
# 然后 q = x + y
dfdx = 1.0 * dfdq # 因为dq/dx = 1,所以使用链式法则计算dfdx=-4
dfdy = 1.0 * dfdq # 因为dq/dy = 1,所以使用链式法则计算dfdy=-4
我们也可以用计算图来表示和计算:
009.jpg

绿色的值是feed forward的结果,而红色的值是backprop的结果。

不过我觉得cs231n课程的这个图没有上面blog的清晰,原因是虽然它标示出来了最终的梯度,但是没有标示出local gradient,我在下面会画出完整的计算过程。

反向传播算法的直觉解释

我们如果把计算图的每一个点看成一个“门”(或者一个模块),或者说一个函数。它有一个输入(向量),也有一个输出(标量)。对于一个门来说有两个计算,首先是根据输入,计算输出,这个一般很容易。还有一种计算就是求输出对每一个输入的偏导数,或者说输出对输入向量的”局部“梯度(local gradient)。一个复杂计算图(神经网络)的计算首先就是前向计算,然后反向计算,反向计算公式可能看起来很复杂,但是如果在计算图上其实就是简单的用local gradient乘以从后面传过来的gradient,然后加起来。

Sigmoid模块的例子

接下来我们看一个更复杂的例子:
010.jpg

这个函数是一个比较复杂的复合函数,但是构成它的基本函数是如下4个简单函数:
011.jpg

下面是用计算图画出这个计算过程:
012.jpg

这个图有4种gate,加法,乘法,指数和倒数。加法有加一个常数和两个变量相加,乘法也是一样。

上图绿色的值是前向计算的结果,而红色的值是反向计算的结果,local graident并没有标示出来,所以看起来可能有些跳跃,下面我在纸上详细的分解了其中的步骤,请读者跟着下图自己动手计算一遍。
013.jpg

上图就是前向计算的过程,比较简单。
014.jpg

第二个图是计算local gradient,对于两个输入的乘法和加法,local gradient也是两个值,local gradient的值我是放到图的节点上了。
015.jpg

第三个图是具体计算一个乘法的local gradient的过程,因为上图可能看不清,所以单独放大了这一步。
016.jpg

最后计算真正的梯度,是把local gradient乘以来自上一步的gradient。不过这个例子一个节点只有一个输出,如果有多个的话,梯度是加起来的,可以参考1.4的
017.jpg

上面我们看到把
018.jpg

分解成最基本的加法,乘法,导数和指数函数,但是我们也可以不分解这么细。之前我们也学习过了sigmoid函数,那么我们可以这样分解:
019.jpg

σ(x)σ(x) 的导数我们之前已经推导过一次了,这里再列一下:
020.jpg

因此我们可以把后面一长串的gate”压缩“成一个gate:
021.jpg

我们来比较一下,之前前向计算 σ(x)σ(x) 需要一次乘法,一次exp,一次加法导数;而反向计算需要分别计算这4个gate的导数。

而压缩后前向计算是一样的,但是反向计算可以”利用“前向计算的结果
022.jpg

这只需要一次减法和一次乘法!当然如果不能利用前向的结果,我们如果需要重新计算 σ(x)σ(x) ,那么压缩其实没有什么用处。能压缩的原因在于σ函数导数的特殊形式。而神经网络的关键问题是在训练,训练性能就取决于这些细节。如果是我们自己来实现反向传播算法,我们就需要利用这样的特性。而如果是使用工具,那么就依赖于工具的优化水平了。

下面我们用代码来实现一下:
w = [2,-3,-3] # assume some random weights and data
x = [-1, -2]

# forward pass
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid function

# backward pass through the neuron (backpropagation)
ddot = (1 - f) * f # gradient on dot variable, using the sigmoid gradient derivation
dx = [w[0] * ddot, w[1] * ddot] # backprop into x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # backprop into w
# we're done! we have the gradients on the inputs to the circuit
上面的例子用了一个小技巧,就是所谓的staged backpropagation,说白了就是给中间的计算节点起一个名字。比如dot。为了让大家熟悉这种技巧,下面有一个例子。

Staged computation练习
023.jpg

我们用代码来计算这个函数对x和y的梯度在某一点的值

前向计算
x = 3 # example values
y = -4

# forward pass
sigy = 1.0 / (1 + math.exp(-y)) # 分子上的sigmoid #(1)
num = x + sigy # 分子 #(2)
sigx = 1.0 / (1 + math.exp(-x)) # 分母上的sigmoid #(3)
xpy = x + y #(4)
xpysqr = xpy**2 #(5)
den = sigx + xpysqr # 分母 #(6)
invden = 1.0 / den #(7)
f = num * invden # done! #(8)
反向计算
# backprop f = num * invden
dnum = invden # gradient on numerator #(8)
dinvden = num #(8)
# backprop invden = 1.0 / den
dden = (-1.0 / (den**2)) * dinvden #(7)
# backprop den = sigx + xpysqr
dsigx = (1) * dden #(6)
dxpysqr = (1) * dden #(6)
# backprop xpysqr = xpy**2
dxpy = (2 * xpy) * dxpysqr #(5)
# backprop xpy = x + y
dx = (1) * dxpy #(4)
dy = (1) * dxpy #(4)
# backprop sigx = 1.0 / (1 + math.exp(-x))
dx += ((1 - sigx) * sigx) * dsigx # Notice += !! See notes below #(3)
# backprop num = x + sigy
dx += (1) * dnum #(2)
dsigy = (1) * dnum #(2)
# backprop sigy = 1.0 / (1 + math.exp(-y))
dy += ((1 - sigy) * sigy) * dsigy #(1)
# done! phew
需要注意的两点:1. 前向的结果都要保存下来,反向的时候要用的。2. 如果某个变量有多个出去的边,第一次是等于,第二次就是+=,因为我们要把不同出去点的梯度加起来。

下面我们来逐行分析反向计算: 
(8) f = num * invden 
local gradient
024.jpg

而上面传过来的梯度是1,所以 dnum=1∗invden。注意变量的命名规则, df/dnum就命名为dnum【省略了df,因为默认我们是求f对所有变量的偏导数】 
同理: dinvden=num

(7) invden = 1.0 / den

local gradient是 (−1.0/(den∗∗2)) ,然后乘以上面来的dinvden

(6) den = sigx + xpysqr

这个函数有两个变量sigx和xpysqr,所以需要计算两个local梯度,然后乘以dden 
加法的local梯度是1,所以就是(1)*dden

(5) xpysqr = xpy**2

local gradient是2*xpy,再乘以dxpysqr

(4) xpy = x + y

还是一个加法,local gradient是1,所以dx和dy都是dxpy乘1

(3) sigx = 1.0 / (1 + math.exp(-x))

这是sigmoid函数,local gradient是 (1-sigx)*sigx,再乘以dsigx。 
不过需要注意的是这是dx的第二次出现,所以是+=,表示来自不同路径反向传播过来给x的梯度值

(2) num = x + sigy

还是个很简单的加法,local gradient是1。需要注意的是dx是+=,理由同上。

(1) sigy = 1.0 / (1 + math.exp(-y))

最后是sigmoid(y)和前面(3)一样的。

请仔细阅读上面反向计算的每一步代码,确保自己理解了之后再往下阅读。

梯度的矩阵运算

前面都是对一个标量的计算,在实际实现时用矩阵运算一次计算一层的所有梯度会更加高效。因为矩阵乘以向量和向量乘以向量都可以看出矩阵乘以矩阵的特殊形式,所以下面我们介绍矩阵乘法怎么求梯度。

首先我们得定义什么叫矩阵对矩阵的梯度!

我查阅了很多资料,也没找到哪里有矩阵对矩阵的梯度的定义,如果哪位读者知道,请告诉我,谢谢!唯一比较接近的是Andrew Ng的课程cs294的背景知识介绍的slides linalg的4.1节定义了gradient of Matrix,关于矩阵对矩阵的梯度我会有一个猜测性的解释,可能会有问题。

首先介绍graident of matrix

假设 f:Rm×n→R是一个函数,输入是一个m×n的实数值矩阵,输出是一个实数。那么f对A的梯度是如下定义的:
025.jpg

看起来定义很复杂?其实很简单,我们把f看成一个mn个自变量的函数,因此我们可以求f对这mn个自变量的偏导数,然后把它们排列成m*n的矩阵就行了。为什么要多此一举把变量拍成矩阵把他们的偏导数也排成矩阵?想想我们之前的神经网络的weights矩阵,这是很自然的定义,同时我们需要计算loss对weights矩阵的每一个变量的偏导数,写出这样的形式计算起来比较方便。

那么什么是矩阵对矩阵的梯度呢?我们先看实际神经网络的一个计算情况。对于全连接的神经网络,我们有一个矩阵乘以向量 D=WxD=Wx 【我们这里把向量x看成矩阵】。现在我们需要计算loss对某一个 WijWij 的偏导数,根据我们之前的计算图, WijWij 有多少条出边,那么就有多少个要累加的梯度乘以local梯度。 
假设W是m×n的矩阵,x是n×p的矩阵,则D是m×p的矩阵
026.jpg

根据矩阵乘法的定义
027.jpg

我们可以计算:
028.jpg

请仔细理解上面这一步,如果 k≠i,则不论s是什么,Wks跟Wij不是同一个变量,所以导数就是0;如果k=i,∑sWisxsl=xjl,也就求和的下标s取j的时候有WijWij。 
因此
029.jpg

上面计算了loss对一个Wij的偏导数,如果把它写成矩阵形式就是:
031.jpg

前面我们推导出了对Wij的偏导数的计算公式,下面我们把它写成矩阵乘法的形式并验证【证明】它。
032.jpg

为什么可以写成这样的形式呢?
033.jpg

上面的推导似乎很复杂,但是我们只要能记住就行,记法也很简单——把矩阵都变成最特殊的1 1的矩阵(也就是标量,一个实数)。D=w x,这个导数很容易吧,对w求导就是local gradient x,然后乘以得到dW=dD x;同理dx=dD W。 
但是等等,刚才那个公式里还有矩阵的转置,这个怎么记?这里有一个小技巧,就是矩阵乘法的条件,两个矩阵能相乘他们的大小必须匹配,比如D=Wx,W是m n,x是n p,也就是第二个矩阵的行数等于第一个的列数。 
现在我们已经知道dW是dD”乘以“x了,dW的大小和W一样是m n,而dD和D一样是m p,而x是n p,那么为了得到一个m n的矩阵,唯一的办法就是 dD∗xT 
同理dx是n p,dD是m p,W是m*n,唯一的乘法就是 WT∗dD 
下面是用python代码来演示,numpy的dot就是矩阵乘法,可以用numpy.dot(A,B),也可以直接调用ndarray的dot函数——A.dot(B):
# forward pass
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)

# now suppose we had the gradient on D from above in the circuit
dD = np.random.randn(*D.shape) # same shape as D
dW = dD.dot(X.T) #.T gives the transpose of the matrix
dX = W.T.dot(dD)
至此,本系列文章的第5部分告一段落。在接下来的文章中,作者将为大家详细讲述关于常见的深度学习框架/工具的使用方法、使用自动求导来实现多层神经网络等内容,敬请期待。
0
评论

李理:从Image Caption Generation理解深度学习(part II) 环信 深度学习 李理

开发讨论beyond 发表了文章 • 4288 次浏览 • 2016-11-24 14:45 • 来自相关话题

书接上文:环信李理:从Image Caption Generation了解深度学习(part I
 
2. 机器学习基本概念和前馈神经网络
2.1 机器学习基本概念

   大家可能平时都写过很多程序,写程序和机器学习的思路可能有一些不同。写程序时,我们是“上帝”,我们规定计算机的每一个步骤,第一步做什么第二步做什么,我们称之为算法。我们能够控制所有的情况,如果出了任何问题,肯定都是程序员的责任。而在机器学习的时候,我们只是“老师”。我们告诉学生(计算机)输入是什么,输出是什么,然后期望它能够学到和我们类似的知识。比如我们跟小孩说这是狗,那是猫,我们没有办法像上帝那样拿着“纳米手术刀”去操作人脑神 经元的连接方式。我们只能不断的给小孩“训练数据”,然后期望他能够学会什么是猫,即使我们觉得他“学会”了识别猫,我们也没有办法知道他是“怎么”学会 的,而且同样的训练过程可能换一个人就不好使。

   机器学习和人类的学习是类似的——我们也是给它训练数据,然后期望它能学会。我们会给机器建一个模型,从数学的角度来说一个模型就是一个函数,它的输入一般是一个向量【当然可以是二维的矩阵如图片或者三维的张量比如视频】,输出可以是有限的离散的标签如“猫”,“狗”,这类问题我们称之为分类;而如果输出 是连续的值比如用这个模型来预测气温,那么我们就称之为回归。其实人类的很多科学活动和日常生活,都是在“学习”模型和“应用”模型。比如开普勒通过观测 大量天文数据“归纳”出行星的运动规律。从本质上讲,智能就是从“过去”学习,然后根据“现在”来预测可能的将来并根据自己的目标选择有利于自己行为。只不过之前,似乎只有人类能够从数据中“学习”出规律,而人工智能的目标就是让机器也有类似的学习能力。

   模型用数学来说就是一个函数,我们人脑的函数由神经元的连接构成,它可能是一个很复杂的函数,我们现在还很难彻底研究清楚。神经网络就是试图通过计算机来 模拟和借鉴人脑这个模型,除了我们这里要讲的神经网络之外,机器学习领域还有各种各样的模型,它们各有特点。但不管形式怎么变化,本质都是一个函数。一个(或者更准确的是一种)模型一般都是一种函数形式,它有一些“参数”可以改变。而学习的过程就是不断调整这些参数,使得输出(尽量)接近“正确”的答案。 但是一般情况下很难所有的数据我们都能预测正确,所以一般我们会定义一个loss function,可以理解为“错误”的程度,错的越“离谱”,loss就越大。而我们的目标就是调整参数使得loss最小。

   但是我们是在“训练”数据上调整的参数,那么它能在“测试”数据上也表现的好吗?这个就是模型的“泛化”能力了。就和人在学校学习一样,有的同学做过的一 模一样的题就会,但是考试时稍微改变一下就不会了,这就是“泛化”能力太差,学到的不是最本质的东西。所以平时会定期有一些“模拟考试”,来检验学生是不 是真的学会了,如果考得不好,那就打回去重新训练模型调整参数。这在机器学习里对应的就是validation的阶段。最后到最终的考试了,就是最终检验 的时候了,这个试卷里的题目是不能提前让人看到的,只能拿出来用一次,否则就是作弊了。对应到机器学习里就是test阶段。

   当然这里用通俗的话描述了机器学习,主要是有监督的学习。其实机器学习还有无监督的学习和强化学习。前者就是不给答案,只给数据,让人总结规律;而后者会有答案,但是答案不是现在就告诉你。我个人觉得人类社会里更多的是监督学习和强化学习。从人类社会总体来说,强化学习是获取新知识的唯一途径,也就是向自 然学习,我们做了一个决策,其好坏可能要很长一段时间才能显现出来。而学习出来的这些知识通过监督的方式,通过家庭和学校的教育教给下一代。

   另外输出除了简单的分为离散和连续,还可以是序列(时序)的,比如自然语言(文本)是一个字符串的序列 ,对于我们的Image Caption Generation就是生成一个单词序列。另外还有更复杂的输出,比如parsing,输出是一棵语法树。

2.2 多层神经网络

   前面介绍了机器学习的基本概念,接下来我们就来学习一下神经网络。现在流行的说法“深度学习”,其实大多指的就是“深度神经网络”,那么首先我们先了解一下“浅度神经网络”,也就是传统的神经网络。这里的内容主要来自http://neuralnetworksanddeeplearning.com的前两章。

2.2.1 手写数字识别问题

   我们在学习一门新的语言时会写一个hello world程序,而mnist数据的手写数字识别就是一个很好的学习机器学习(包括深度学习)的一个hello world任务。

   计算机和人类大脑似乎有很大的不同,很多人类认为复杂的工作计算机可能认为很简单,而人类认为很简单的事情计算机可能非常难处理。比如数字的计算,记忆,人类的准确度和速度都远远不如计算机。但是识别0-9的手写数字,我们觉得很轻而易举的事情,让计算机程序来处理却异常困难。经过数百万年进化的人类视觉系统在我们大脑没有意识到的时候就已经帮我们完成了数字的识别,把那些复杂的视觉处理过程深深的掩藏了起来。但当我们想自己写一个程序来识别数字的时候,这些困难才能体现出来。首先,对于计算机来说,它“看到”的不是数字,甚至不是笔画。它“看到”的只是一个二位的矩阵(数组),每个点都是一个数字。比如下图,我们“看到”的是左边的“猫”,其实计算机“看到”的是右边的像素灰度值。当然我们视觉系统的视网膜看到的也是类似的一些“数值”,只不过我们的视觉系统已经处理了这些信息并且把它识别成了“猫”(甚至和语言还做了映射)。 




   MNIST数据介绍:MNIST的每个图片经过缩放和居中等预处理之后,大小是28*28,每个点都是0-255的灰度值,下图是一些样例。总共有60,000个训练数据(0-9共10个类别,每个类别6,000个)和10,000个测试数据。一般会拿60000个中的50000个来做训练集,而剩下的10000个用来做验证集(用来选择一些超参数)。




   如果我们自己来写一个“算法”识别数字“9”,我们可能会这么定义:9在上面有个圆圈,在这个圆圈的右下部分有一个竖直的笔画。说起来很简单,如果用算法 来实现就很麻烦了:什么是圆圈?每个人画的圆圈都不同,同样竖直的笔画怎么识别,圆圈和竖直笔画连接处怎么寻找,右下是哪?大家如果有兴趣可以尝试一下用 上面的方法,其实最早做数字识别就是这样的思路。

   机器学习的思路则不同,它不需要这么细节的“指示”计算机应该怎么做。而是给计算机足够的“训练”样本,让它“看”不同的10个数字,然后让它“学”出 来。前面我们也讲了,现在的机器学习一般是一个参数化的模型。比如最简单的一个线性模型:f(w;x)=w0+ w1*x1+w2*x2。如果我们的输入有两个“特征”x1和x2,那么这个模型有3个参数w0,w1和w2,机器学习的过程就是选择“最优”的参数。对 于上面的mnist数据,输入就是28*28=784维的向量。

   如果用“原始”的输入作为“特征”,线性的模型很可能学到一些简单的特征,比如它看到1一般是分布在从上到下居中的一些位置,那么对于这些位置一旦发现有比较大的灰度值,那么就倾向于判断成1。如果一个像素点2也经常出现,但3不出现,那么它就能学到如果这个像素出现,那么这个数字是2和3的可能性就大一些。

   但是这样的“特征”可能不是“本质”的,因为我写字的时候笔稍微平移一点,那么你之前“学到”的参数就可能有问题。而更“本质”的特征是什么呢?可能还是像之前我们总结的——9在上面有个圆圈,在这个圆圈的右下部分有一个竖直的笔画。我们把识别一个数字的问题转化成圆圈和竖直笔画的问题。传统的机器学习需要方法来提取“类似”(但不完全是)基本笔画这样的“特征”,这些特征相对于像素的特征会更加“本质”。但是要“提取”这些特征需要很多的“领域”知识,比如图像处理的技术。所以使用传统的机器学习方法来解决问题,我们不但需要很多机器学习的知识,而且也需要很多“领域”的知识,同时拥有这两方面的知识是比较难的。

   而“深度学习”最近之所以火热,其中很重要的一个原因就是对于很多问题,我们只需要输入最原始的信号,比如图片的像素值,通过“多层”的网络,让底层的网络学习出“底层”的特征,比如基本的形状,而中间的层学习出抽象一点的特征,比如眼睛鼻子耳朵。而更上的层次识别出这是一个猫还是一个狗。所有这些都是机器学习出来的,所以基本不需要领域的知识。




   上面的图就说明了这一点,而且我们发现越是底层的特征就越“通用”,不管是猫鼻子还是狗眼睛,可能用到的都是一些基本的形状,因此我们可以把这些知识(特征)transfer到别的任务,也就是transfer learning,后面我们讲到CNN的时候还会提及。

2.2.2 单个神经元和多层神经网络(MLP)

   神经网络从名字来看是和人类的大脑有些关系的,而且即使到现在,很多有用的东西如CNN和Attention,都有很多借鉴神经科学研究人脑的结果的。不过这里我就不介绍这些东西了,有兴趣的读者可以找一些资料来了解。

一个神经元如下图的结构:




   它的输入是一个向量,(x1,x2,x3),输出是一个标量,一个实数。z=w0+ w1*x1 + w2*x2 + w3*x3。z是输入的加权累加,权值是w1,w2,w3,w0是bias,输出 output = f(z)。函数f一般叫做激活函数。最早流行的激活函数是Sigmoid函数,当然现在更流行Relu和它的改进版本。Sigmoid函数的公式和图形如下:









   当z=0时,sigmoid(z)=0.5 z趋于无穷大时,sigmoid(z)趋近于1,z趋于负无穷,值趋于0。为什么选择这样的激活函数呢?因为是模拟人脑的神经元。人脑的神经元也是把输入的信号做加权累加,然后看累加和是否超过一个“阈值”。如果超过,继续向下一个神经元发送信号,否则就不发送。因此人脑的神经元更像是一个阶跃函数:




   最早的感知机(Perception)其实用的就是这个激活函数。但是它有一个缺点就是0之外的所有点的导数都是0,在0点的导数是无穷大,所以很难用梯度的方法优化。而Sigmoid函数是处处可导。下面我手工推导了一下,如果大家不熟悉可以试着推导一下Sigmoid函数的导数,我们后面也会用到。




我们把许多的单个神经元按照层次组织起来就是多层的神经网络。




   比如我们的手写数字识别,输入层是784维,就是神经网络的地一层,然后中间有15个hidden(因为我们不知道它的值)神经元,然后输出层是10个神经元。中间隐层的每个神经元的输入都是784个原始像素通过上面的公式加权累加然后用sigmoid激活。而输出层的每一个神经元也是中间15个神经元的累加然后激活。上面的图就是一个3层的神经网络。

   输入一个28*28的图像,我们得到一个10维的输出,那么怎么分类呢?最直接的想法就是把认为最大的那个输出,比如输出是(10,11,12,13,14,15,16,17,18,19),那么我们认为输出是9。

   当然,更常见的做法是最后一次经过线性累加之后并不用Sigmoid函数激活,而是加一个softmax的函数,让10个输出加起来等于1,这样更像一个 概率。而我们上面的情况,虽然训练数据的输出加起来是1,但是实际给一个其它输入,输出加起来很可能不是1。不过为了与Nielsen的文章一致,我们还 是先用这种方法。

   因此,假设我们有了这些参数【总共是784*15 + 15(w0或者叫bias) + 15*10 + 10】,我们很容易通过上面的公式一个一个的计算出10维的输出。然后选择最大的那个作为我们识别的结果。问题的难点就在怎么 选择这么多参数,然后使得我们分类的错误最少。

   而我们怎么训练呢?对于一张图片,假设它是数字“1”,那么我们期望它的输出是(0,1,0,0,0,0,0,0,0,0),所以我们可以简单的用最小平方错误作为损失函数。不过你可能会有些疑问,我们关注的指标应该是分类的“正确率”(或者错误率),那么我们为什么不直接把分类的错误率作为损失函数呢?这样神经网络学习出来的参数就是最小化错误率。

   主要的原因就是错误率不是参数的连续函数。因为一个训练数据如果分类正确那么就是1,否则就是0,这样就不是一个连续的函数。比如最简单的两类线性分类器,f(x)=w0+w1*x1+w2*x2。如果f(x)>0我们分类成类别1;否则我们分类成类别2。如果当前的w0+w1*x1+w2*x2<0,我们很小的调整w0(或者w1,w2),w0+w1*x1+w2*x2仍然小于0,【事实上对于这个例子,只要是w0变小,他们的累加都是小于0的】所以f(x)的值不会变化,而w0一直增大到使累加和等于0之前都不会变化,只有大于0时突然变成1了,然后一直就是1。因此之前的错误率都是1,然后就突然是0。所以它不是个连续的函数。

   因为我们使用的优化算法一般是(随机)梯度下降的算法,在每次迭代的时候都是试图做一个微小的参数调整使得损失变小,但是不连续的函数显然也不可导,也就没法用这个算法来优化参数。

   因此我们使用了最小平方误差(MSE)损失函数。




   y(x)就是神经网络的输出,可能写成f(x)大家会习惯一点。a是目标的输出,比如当前分类是数字1,那么我们期望的输出就是(0,1,0,0,0,0,0,0,0,0)。

   首先这个损失函数是参数w的连续函数,因为y(x)就是神经网络的输出,每个神经元都是它的输入的线性加权累加,然后使用sigmoid激活函数【如果使用最早的阶跃函数就不连续了,所以后来使用了Sigmoid函数】,然后每一层的神经元都是用上一层的神经元通过这样的方式计算的(只不过每个神经元的参数也就是权重是不同的数值而已),所以这些连续函数的复合函数也是连续的。

   其次这个损失函数和我们的最终优化目标是“大致”一致的。比如C(w,b)趋于0时,它就要求y(x)趋于a,那么我们的分类也就趋于正确。当然可能存在一种极端的情况,比如有3个训练数据,第一组参数,它分类正确了2个训练数据,但是错的那1个错的很“离谱”,也就是y(x)和a差距极大;而第二组参数,他正确分类了1个训练数据,但是错的那两个都还不算太差。那么这种情况下MSE和正确率并不一致。

2.2.3 随机梯度下降(Stochastic Gradient Descent)和自动求梯度(Automatic Derivatives)

   上面说了,我们有了一个参数化的模型,训练的过程就是根据训练数据和loss function,选择“最优”的参数,使得loss“最小”,这从数学上来讲就是一个优化问题。这看起来似乎不是什么值得一提的问题,也许你还记得微积 分里的知识,极值点的各种充分必要条件,比如必要条件是导数是0,然后直接把参数解出来。但在现实生活中的函数远比教科书里学到的复杂,很多模型都无法用 解析的方式求出最优解。所以现实的方法就是求“数值”解,一般最常见的方法就是迭代的方法,根据现在的参数,我们很小幅度的调整参数,使得loss变小一 点点。然后一步一步的最终能够达到一个最优解(一般是局部最优解)。那怎么小幅调整呢?像闷头苍蝇那样随机乱试显然效率极低。因此我们要朝着一个能使函数 值变小的方向前进。而在一个点能使函数值变小的方向有无穷多个,但有一个方向是下降速度最快的,那就是梯度。因此更常见的方法就是在当前点求函数的梯度, 然后朝着梯度的方向下降。朝梯度的方向走多远呢?一般走一个比较小的值是比较安全的,这个值就是“步长”。一般刚开始随机的初始化参数,loss比较大, 所以多走一些也没关系,但是到了后面,就不能走太快,否则很容易错过最优的点。

   因为loss是所有训练数据的函数,所以求loss的梯度需要计算所有的训练数据,对于很多task来说,训练数据可能上百万,计算一次代价太大,所以一 般会“随机”的采样少部分数据,比如128个数据,求它的梯度。虽然128个点的梯度和一百万个的是不一样的,但是从概率来讲至少是一致的方向而不会是相 反的方向,所以也能使loss变小。当然这个128是可以调整的,它一般被叫做batch size,最极端的就是batch是1和一百万,那么分别就是online learning和退化到梯度下降。batch size越大,计算一次梯度的时间就越久【当然由于GPU和各种类似SSE的指令,一次计算128个可能并不比计算1个慢多少】,随机梯度和真正梯度一致 的概率就越大,走的方向就更“正确”;batch size越小,计算一次的时间就越短,但可能方向偏离最优的方向就更远,会在不是“冤枉路”。但实际的情况也很难说哪个值是最优的,一般的经验取值都是几 十到一两百的范围,另外因为计算机都是字节对齐,32,64,128这样的值也许能稍微加快矩阵运算的速度。但是实际也很多人选择10,50,100这样 的值。

   除了常见的随机梯度下降,还有不少改进的方法,如Momentum,Adagrad等等,有兴趣的可以看看 http://cs231n.github.io/neural-networks-3/#update ,里面还有个动画,比较了不同方法的收敛速度的比较。

通过上面的分析,我们把问题变成了怎么求loss对参数W的梯度。

求梯度有如下4种方法:

1.手工求解析解

比如 f(x)=x^2, df/dx=2*x。然后我们要求f(x)在x=1.5的值,代进去就2*1.5=3

2.数值解

使用极限的定义:




3.机器符号计算

让机器做符号运算,实现1的方法,但是机器如果优化的不好的话可能会有一些不必要的运算。

比如 x^2 + 2*x*y + y^2,直接对x求导数变成了 2*x + 2*y,两次乘法一次加分,但是我们可以合并一下变成2*(x+y),一次乘法一次加分。

4.自动梯度

下面我会在稍微细讲一下,所以这里暂时跳过。

这些方法的优缺点:
 
手工求解“数学”要求高,有可能水平不够求不对,但效率应该是能最优的。没任何函数,甚至没有解析导数的情况下都能使用,缺点是计算量太大,而且只是近似解【因为极限的定义】,在某些特别不“连续”的地方可能误差较大。所以实际使用是很少,只是用它来验证其它方法是否正确。机器符号计算,前面说的,依赖于这个库的好坏。

实际的框架,如TensorFlow就是自动梯度,而Theano就是符号梯度。

2.2.4 编程实战

   通过上面的介绍,我们其实就可以实现一个经典的前馈(feed forward)神经网络了,这种网络结构很简单,每一层的输入是前一层的输出。输入层没有输入,它就是原始的信号输入。而且上一层的所有神经元都会连接到下一层的所有神经元,就像我们刚才的例子,输入是784,中间层是15,那么就有785*15个连接【再加上每个中间节点有一个bias】。所以这种网络有时候也加做全连接的网络(full connected),用来和CNN这种不是全连接的网络有所区别,另外就是信号是从前往后传递,没有反馈,所以也叫前溃神经网络,这是为了和RNN这种有反馈的区别。

   当然,我们还没有讲怎么计算梯度,也就是损失函数相对于每一个参数的偏导数。在下一部分我们会详细讨论介绍,这里我们先把它当成一个黑盒的函数就好了。

1.代码
我们这里学习一下Nielsen提供的代码。代码非常简洁,只有不到100行代码。
https://github.com/mnielsen/ne ... rning

git clone https://github.com/mnielsen/ne ... g.git

2.运行

创建一个 test_network1.py,输入如下代码:import mnist_loader
import network

training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
net = network.Network([784, 30, 10])
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)保存后直接运行 Python test_network1.py。这里我们让他进行了30次迭代,最终在测试数据上的准确率大概在95%左右(当然因为随机初始化参数不同,最终的结果可能有所不同)Epoch 0: 8250 / 10000
Epoch 1: 8371 / 10000
Epoch 2: 9300 / 10000
......
Epoch 28: 9552 / 10000
Epoch 29: 9555 / 100003. 代码阅读

   Python代码很容易阅读,即使之前没有用过,稍微学习两天也就可以上手,而且大部分机器学习相关的代码不会用到太复杂的语言特性,基本就是一些数学的线性代数的运算。而Python的numpy这个库是用的最多的,后面阅读代码的时候我会把用到的函数做一些介绍,继续下面的阅读之前建议花十分钟阅读一下 http://cs231n.github.io/python-numpy-tutorial/。

3.1 mnist_loader.load_data_wrapper函数

   这个函数用来读取mnist数据,数据是放在data/mnist.pkl.gz。首先这是个gzip的压缩文件,是Pickle工具序列化到磁盘的格式。不熟悉也没有关系,反正我们知道这个函数的返回值就行了。

   这个函数返回三个对象,分别代表training_data,validation_data和test_data。

   training_data是一个50,000的list,然后其中的每一个元素是一个tuple。tuple的第一个元素是一个784维的numpy一维数组。第二个元素是10维的数组,也就是one-hot的表示方法——如果正确的答案是数字0,那么这个10维数组就是(1, 0, 0, …)。

   而validation_data是一个10,000的list,每个元素也是一个tuple。tuple的第一个元素也是784维的numpy一维数组。第二个元素是一个0-9的数字,代表正确答案是那个数字。

   test_data的格式和validation_data一样。

   为什么training_data要是这样的格式呢?因为这样的格式计算loss更方便一些。

3.2 Network类的构造函数

   我们在调用net = network.Network([784, 30, 10])时就到了init函数。为了减少篇幅,代码里的注释我都去掉了,重要的地方我会根据自己的理解说明,但是有空还是值得阅读代码里的注释。class Network(object):
def __init__(self, sizes):self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]   比如上面的参数,我们保存下来的self.num_layers=3,也就是3层的网络。每一层的神经元的个数保存到self.sizes里。接下来就是构造biases数组并随机初始化。因为输入层是没有参数的,所以是for y in sizes[1:],我们使用了numpy的random.randn生成正态分布的随机数用来作为参数的初始值。注意这里生成了2维的随机变量。回忆一下,如果我们有30个hidden unit,那么bias的个数也是30,那就生成一个30维的1维数组就行了,为什么要是30*1的二维数组呢?其实用1维也可以,不过为了和weights一致,后面代码方便,就用二维数组了。另外weights也是一样的初始化方法,不过注意randn(y,x)而不是randn(x,y)。比如对于我们输入的[784,30,10],weights分别是30*784和10*30的。当然其实weights矩阵转置一下也可以,就是计算矩阵乘法的时候也需要有一个转置。不同的文献可能有不同的记法,但是我们在实现代码的时候只需要随时注意矩阵的大小,检查矩阵乘法满足乘法的约束就行了,矩阵AB能相乘,必须满足的条件是B的列数等于A的函数就行。

   对于Nielsen的记法,矩阵的每一行就是一个神经元的784个参数,那么weights(30*784) * input(784*1)就得到30个hidden unit的加权累加。

3.3 feedforward函数

   给点输入a(784维),计算最终神经网络的输出(10维)。def feedforward(self, a):
"""Return the output of the network if ``a`` is input."""for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a)+b)
return a   代码非常简单,这里用到了np.dot,也就是矩阵向量的乘法,此外这里有一个Sigmoid函数,这个函数的输入是numpy的ndarray,输出也是同样大小的数组,不过对于每个元素都进行了sigmoid的计算。用numpy的术语就是universal function,很多文献里一般都叫elementwise的function。我觉得后面这个名字更直接。#### Miscellaneous functionsdef sigmoid(z):
"""The sigmoid function."""return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
"""Derivative of the sigmoid function."""return sigmoid(z)*(1-sigmoid(z))上面就是Sigmoid函数,另外也把sigmoid_prime,也就是Sigmoid的导数放在了一起【不记得的话看前面Sigmoid的导数的推导】。

3.4 SGD函数

这个函数是训练的入口,比如我们之前的训练代码:net.SGD(training_data, 30, 10, 3.0, test_data=test_data)


def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
if test_data: n_test = len(test_data)
n = len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)]
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta)
if test_data:
print "Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test)
else:
print "Epoch {0} complete".format(j)第一个参数就是training_data。

第二个参数就是epochs,也就是总共对训练数据迭代多少次,我们这里是30次迭代。

第三个参数是batch大小,我们这里是10,最后一个参数是eta,也就是步长,这里是3.0。除了网络结构(比如总共多少个hidden layer,每个hidder layer多少个hidden unit),另外一个非常重要的参数就是步长。前面我们也讨论过了,步长太小,收敛速度过慢,步长太大,可能不收敛。实际的情况是没有一个万能的准则,更多的是根据数据,不停的尝试合适的步长。如果发现收敛太慢,就适当调大,反之则调小。所以要训练好一个神经网络,还是有很多tricky的技巧,包括参数怎么初始化,激活函数怎么选择,比SGD更好的优化算法等等。

第四个参数test_data是可选的,如果有(我们的例子是穿了进来的),则每次epoch之后都测试一下。

代码的大致解释我用注释的形式嵌在代码里了: for j in xrange(epochs): ## 一共进行 epochs=30 轮迭代
random.shuffle(training_data) ## 训练数据随机打散
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)] ## 把50,000个训练数据分成5,000个batch,每个batch包含10个训练数据。
for mini_batch in mini_batches: ## 对于每个batch
self.update_mini_batch(mini_batch, eta) ## 使用梯度下降更新参数
if test_data: ## 如果提供了测试数据
print "Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test) ## 评价在测试数据上的准确率
else:
print "Epoch {0} complete".format(j)下面是evaluate函数:def evaluate(self, test_data):
test_results = [(np.argmax(self.feedforward(x)), y)
for (x, y) in test_data]
return sum(int(x == y) for (x, y) in test_results)   对于test_data里的每一组(x,y),y是0-9之间的正确答案。而self.feedforward(x)返回的是10维的数组,我们选择得分最高的那个值作为模型的预测结果np.argmax就是返回最大值的下标。比如x=[0.3, 0.6, 0.1, 0, ….],那么argmax(x) = 1。

   因此test_results这个列表的每一个元素是一个tuple,tuple的第一个是模型预测的数字,而第二个是正确答案。

所以最后一行返回的是模型预测正确的个数。
 
3.5 update_mini_batch函数def update_mini_batch(self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]它的输入参数是mini_batch【size=10的tuple(x,y)】和eta【3.0】。def update_mini_batch(self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases]
## 回忆一下__init__,biases是一个列表,包含两个矩阵,分别是30*1和10*1
## 我们先构造一个和self.biases一样大小的列表,用来存放累加的梯度(偏导数)
nabla_w = [np.zeros(w.shape) for w in self.weights]
## 同上, weights包含两个矩阵,大小分别是30*784和10*30
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
## 对于一个训练数据(x,y)计算loss相对于所有参数的偏导数
## 因此delta_nabla_b和self.biases, nabla_b是一样大小(shape)
## 同样delta_nabla_w和self.weights,nabla_w一样大小
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
## 把bias的梯度累加到nabla_b里
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
## 把weight的梯度累加到nable_w里
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
## 使用这个batch的梯度和eta(步长)更新参数weights
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
## 更新biases
## 这里更新参数是除了batch的大小(10),有的人实现时不除,其实没有什么区别,因为超参数eta会有所不同,如果不除,那么eta相当于是0.3(在eta那里就除了batch的大小了)。3.6 backprop函数

这个函数就是求loss相对于所有参数的偏导数,这里先不仔细讲解,等下次我们学习梯度的求解方法我们再回来讨论,这里可以先了解一下这个函数的输入和输出,把它当成一个黑盒就行,其实它的代码也很少,但是如果不知道梯度的公式,也很难明白。def backprop(self, x, y):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforwardactivation = x
activations = [x] # list to store all the activations, layer by layerzs = [] # list to store all the z vectors, layer by layerfor b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward passdelta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
for l in xrange(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)   它的输入就是一个训练样本(x,y)分别是784*1和10*1。输出就是和self.biases,self.weights一样大小的列表,然后列表中的每一个数组的大小也是一样。具体到上面的例子,输出nabla_b包含两个矩阵,大小分别是30*1和10*1;nabla_w也包含两个矩阵,大小分别是30*784和10*30。
 
未完待续
作者简介:李理,目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。
责编:周建丁(zhoujd@csdn.net) 查看全部
书接上文:环信李理:从Image Caption Generation了解深度学习(part I
 
2. 机器学习基本概念和前馈神经网络
2.1 机器学习基本概念

   大家可能平时都写过很多程序,写程序和机器学习的思路可能有一些不同。写程序时,我们是“上帝”,我们规定计算机的每一个步骤,第一步做什么第二步做什么,我们称之为算法。我们能够控制所有的情况,如果出了任何问题,肯定都是程序员的责任。而在机器学习的时候,我们只是“老师”。我们告诉学生(计算机)输入是什么,输出是什么,然后期望它能够学到和我们类似的知识。比如我们跟小孩说这是狗,那是猫,我们没有办法像上帝那样拿着“纳米手术刀”去操作人脑神 经元的连接方式。我们只能不断的给小孩“训练数据”,然后期望他能够学会什么是猫,即使我们觉得他“学会”了识别猫,我们也没有办法知道他是“怎么”学会 的,而且同样的训练过程可能换一个人就不好使。

   机器学习和人类的学习是类似的——我们也是给它训练数据,然后期望它能学会。我们会给机器建一个模型,从数学的角度来说一个模型就是一个函数,它的输入一般是一个向量【当然可以是二维的矩阵如图片或者三维的张量比如视频】,输出可以是有限的离散的标签如“猫”,“狗”,这类问题我们称之为分类;而如果输出 是连续的值比如用这个模型来预测气温,那么我们就称之为回归。其实人类的很多科学活动和日常生活,都是在“学习”模型和“应用”模型。比如开普勒通过观测 大量天文数据“归纳”出行星的运动规律。从本质上讲,智能就是从“过去”学习,然后根据“现在”来预测可能的将来并根据自己的目标选择有利于自己行为。只不过之前,似乎只有人类能够从数据中“学习”出规律,而人工智能的目标就是让机器也有类似的学习能力。

   模型用数学来说就是一个函数,我们人脑的函数由神经元的连接构成,它可能是一个很复杂的函数,我们现在还很难彻底研究清楚。神经网络就是试图通过计算机来 模拟和借鉴人脑这个模型,除了我们这里要讲的神经网络之外,机器学习领域还有各种各样的模型,它们各有特点。但不管形式怎么变化,本质都是一个函数。一个(或者更准确的是一种)模型一般都是一种函数形式,它有一些“参数”可以改变。而学习的过程就是不断调整这些参数,使得输出(尽量)接近“正确”的答案。 但是一般情况下很难所有的数据我们都能预测正确,所以一般我们会定义一个loss function,可以理解为“错误”的程度,错的越“离谱”,loss就越大。而我们的目标就是调整参数使得loss最小。

   但是我们是在“训练”数据上调整的参数,那么它能在“测试”数据上也表现的好吗?这个就是模型的“泛化”能力了。就和人在学校学习一样,有的同学做过的一 模一样的题就会,但是考试时稍微改变一下就不会了,这就是“泛化”能力太差,学到的不是最本质的东西。所以平时会定期有一些“模拟考试”,来检验学生是不 是真的学会了,如果考得不好,那就打回去重新训练模型调整参数。这在机器学习里对应的就是validation的阶段。最后到最终的考试了,就是最终检验 的时候了,这个试卷里的题目是不能提前让人看到的,只能拿出来用一次,否则就是作弊了。对应到机器学习里就是test阶段。

   当然这里用通俗的话描述了机器学习,主要是有监督的学习。其实机器学习还有无监督的学习和强化学习。前者就是不给答案,只给数据,让人总结规律;而后者会有答案,但是答案不是现在就告诉你。我个人觉得人类社会里更多的是监督学习和强化学习。从人类社会总体来说,强化学习是获取新知识的唯一途径,也就是向自 然学习,我们做了一个决策,其好坏可能要很长一段时间才能显现出来。而学习出来的这些知识通过监督的方式,通过家庭和学校的教育教给下一代。

   另外输出除了简单的分为离散和连续,还可以是序列(时序)的,比如自然语言(文本)是一个字符串的序列 ,对于我们的Image Caption Generation就是生成一个单词序列。另外还有更复杂的输出,比如parsing,输出是一棵语法树。

2.2 多层神经网络

   前面介绍了机器学习的基本概念,接下来我们就来学习一下神经网络。现在流行的说法“深度学习”,其实大多指的就是“深度神经网络”,那么首先我们先了解一下“浅度神经网络”,也就是传统的神经网络。这里的内容主要来自http://neuralnetworksanddeeplearning.com的前两章。

2.2.1 手写数字识别问题

   我们在学习一门新的语言时会写一个hello world程序,而mnist数据的手写数字识别就是一个很好的学习机器学习(包括深度学习)的一个hello world任务。

   计算机和人类大脑似乎有很大的不同,很多人类认为复杂的工作计算机可能认为很简单,而人类认为很简单的事情计算机可能非常难处理。比如数字的计算,记忆,人类的准确度和速度都远远不如计算机。但是识别0-9的手写数字,我们觉得很轻而易举的事情,让计算机程序来处理却异常困难。经过数百万年进化的人类视觉系统在我们大脑没有意识到的时候就已经帮我们完成了数字的识别,把那些复杂的视觉处理过程深深的掩藏了起来。但当我们想自己写一个程序来识别数字的时候,这些困难才能体现出来。首先,对于计算机来说,它“看到”的不是数字,甚至不是笔画。它“看到”的只是一个二位的矩阵(数组),每个点都是一个数字。比如下图,我们“看到”的是左边的“猫”,其实计算机“看到”的是右边的像素灰度值。当然我们视觉系统的视网膜看到的也是类似的一些“数值”,只不过我们的视觉系统已经处理了这些信息并且把它识别成了“猫”(甚至和语言还做了映射)。 
001.png

   MNIST数据介绍:MNIST的每个图片经过缩放和居中等预处理之后,大小是28*28,每个点都是0-255的灰度值,下图是一些样例。总共有60,000个训练数据(0-9共10个类别,每个类别6,000个)和10,000个测试数据。一般会拿60000个中的50000个来做训练集,而剩下的10000个用来做验证集(用来选择一些超参数)。
002.png

   如果我们自己来写一个“算法”识别数字“9”,我们可能会这么定义:9在上面有个圆圈,在这个圆圈的右下部分有一个竖直的笔画。说起来很简单,如果用算法 来实现就很麻烦了:什么是圆圈?每个人画的圆圈都不同,同样竖直的笔画怎么识别,圆圈和竖直笔画连接处怎么寻找,右下是哪?大家如果有兴趣可以尝试一下用 上面的方法,其实最早做数字识别就是这样的思路。

   机器学习的思路则不同,它不需要这么细节的“指示”计算机应该怎么做。而是给计算机足够的“训练”样本,让它“看”不同的10个数字,然后让它“学”出 来。前面我们也讲了,现在的机器学习一般是一个参数化的模型。比如最简单的一个线性模型:f(w;x)=w0+ w1*x1+w2*x2。如果我们的输入有两个“特征”x1和x2,那么这个模型有3个参数w0,w1和w2,机器学习的过程就是选择“最优”的参数。对 于上面的mnist数据,输入就是28*28=784维的向量。

   如果用“原始”的输入作为“特征”,线性的模型很可能学到一些简单的特征,比如它看到1一般是分布在从上到下居中的一些位置,那么对于这些位置一旦发现有比较大的灰度值,那么就倾向于判断成1。如果一个像素点2也经常出现,但3不出现,那么它就能学到如果这个像素出现,那么这个数字是2和3的可能性就大一些。

   但是这样的“特征”可能不是“本质”的,因为我写字的时候笔稍微平移一点,那么你之前“学到”的参数就可能有问题。而更“本质”的特征是什么呢?可能还是像之前我们总结的——9在上面有个圆圈,在这个圆圈的右下部分有一个竖直的笔画。我们把识别一个数字的问题转化成圆圈和竖直笔画的问题。传统的机器学习需要方法来提取“类似”(但不完全是)基本笔画这样的“特征”,这些特征相对于像素的特征会更加“本质”。但是要“提取”这些特征需要很多的“领域”知识,比如图像处理的技术。所以使用传统的机器学习方法来解决问题,我们不但需要很多机器学习的知识,而且也需要很多“领域”的知识,同时拥有这两方面的知识是比较难的。

   而“深度学习”最近之所以火热,其中很重要的一个原因就是对于很多问题,我们只需要输入最原始的信号,比如图片的像素值,通过“多层”的网络,让底层的网络学习出“底层”的特征,比如基本的形状,而中间的层学习出抽象一点的特征,比如眼睛鼻子耳朵。而更上的层次识别出这是一个猫还是一个狗。所有这些都是机器学习出来的,所以基本不需要领域的知识。
003.png

   上面的图就说明了这一点,而且我们发现越是底层的特征就越“通用”,不管是猫鼻子还是狗眼睛,可能用到的都是一些基本的形状,因此我们可以把这些知识(特征)transfer到别的任务,也就是transfer learning,后面我们讲到CNN的时候还会提及。

2.2.2 单个神经元和多层神经网络(MLP)

   神经网络从名字来看是和人类的大脑有些关系的,而且即使到现在,很多有用的东西如CNN和Attention,都有很多借鉴神经科学研究人脑的结果的。不过这里我就不介绍这些东西了,有兴趣的读者可以找一些资料来了解。

一个神经元如下图的结构:
004.png

   它的输入是一个向量,(x1,x2,x3),输出是一个标量,一个实数。z=w0+ w1*x1 + w2*x2 + w3*x3。z是输入的加权累加,权值是w1,w2,w3,w0是bias,输出 output = f(z)。函数f一般叫做激活函数。最早流行的激活函数是Sigmoid函数,当然现在更流行Relu和它的改进版本。Sigmoid函数的公式和图形如下:
005.png


006.png

   当z=0时,sigmoid(z)=0.5 z趋于无穷大时,sigmoid(z)趋近于1,z趋于负无穷,值趋于0。为什么选择这样的激活函数呢?因为是模拟人脑的神经元。人脑的神经元也是把输入的信号做加权累加,然后看累加和是否超过一个“阈值”。如果超过,继续向下一个神经元发送信号,否则就不发送。因此人脑的神经元更像是一个阶跃函数:
007.png

   最早的感知机(Perception)其实用的就是这个激活函数。但是它有一个缺点就是0之外的所有点的导数都是0,在0点的导数是无穷大,所以很难用梯度的方法优化。而Sigmoid函数是处处可导。下面我手工推导了一下,如果大家不熟悉可以试着推导一下Sigmoid函数的导数,我们后面也会用到。
008.jpg

我们把许多的单个神经元按照层次组织起来就是多层的神经网络。
009.png

   比如我们的手写数字识别,输入层是784维,就是神经网络的地一层,然后中间有15个hidden(因为我们不知道它的值)神经元,然后输出层是10个神经元。中间隐层的每个神经元的输入都是784个原始像素通过上面的公式加权累加然后用sigmoid激活。而输出层的每一个神经元也是中间15个神经元的累加然后激活。上面的图就是一个3层的神经网络。

   输入一个28*28的图像,我们得到一个10维的输出,那么怎么分类呢?最直接的想法就是把认为最大的那个输出,比如输出是(10,11,12,13,14,15,16,17,18,19),那么我们认为输出是9。

   当然,更常见的做法是最后一次经过线性累加之后并不用Sigmoid函数激活,而是加一个softmax的函数,让10个输出加起来等于1,这样更像一个 概率。而我们上面的情况,虽然训练数据的输出加起来是1,但是实际给一个其它输入,输出加起来很可能不是1。不过为了与Nielsen的文章一致,我们还 是先用这种方法。

   因此,假设我们有了这些参数【总共是784*15 + 15(w0或者叫bias) + 15*10 + 10】,我们很容易通过上面的公式一个一个的计算出10维的输出。然后选择最大的那个作为我们识别的结果。问题的难点就在怎么 选择这么多参数,然后使得我们分类的错误最少。

   而我们怎么训练呢?对于一张图片,假设它是数字“1”,那么我们期望它的输出是(0,1,0,0,0,0,0,0,0,0),所以我们可以简单的用最小平方错误作为损失函数。不过你可能会有些疑问,我们关注的指标应该是分类的“正确率”(或者错误率),那么我们为什么不直接把分类的错误率作为损失函数呢?这样神经网络学习出来的参数就是最小化错误率。

   主要的原因就是错误率不是参数的连续函数。因为一个训练数据如果分类正确那么就是1,否则就是0,这样就不是一个连续的函数。比如最简单的两类线性分类器,f(x)=w0+w1*x1+w2*x2。如果f(x)>0我们分类成类别1;否则我们分类成类别2。如果当前的w0+w1*x1+w2*x2<0,我们很小的调整w0(或者w1,w2),w0+w1*x1+w2*x2仍然小于0,【事实上对于这个例子,只要是w0变小,他们的累加都是小于0的】所以f(x)的值不会变化,而w0一直增大到使累加和等于0之前都不会变化,只有大于0时突然变成1了,然后一直就是1。因此之前的错误率都是1,然后就突然是0。所以它不是个连续的函数。

   因为我们使用的优化算法一般是(随机)梯度下降的算法,在每次迭代的时候都是试图做一个微小的参数调整使得损失变小,但是不连续的函数显然也不可导,也就没法用这个算法来优化参数。

   因此我们使用了最小平方误差(MSE)损失函数。
010.png

   y(x)就是神经网络的输出,可能写成f(x)大家会习惯一点。a是目标的输出,比如当前分类是数字1,那么我们期望的输出就是(0,1,0,0,0,0,0,0,0,0)。

   首先这个损失函数是参数w的连续函数,因为y(x)就是神经网络的输出,每个神经元都是它的输入的线性加权累加,然后使用sigmoid激活函数【如果使用最早的阶跃函数就不连续了,所以后来使用了Sigmoid函数】,然后每一层的神经元都是用上一层的神经元通过这样的方式计算的(只不过每个神经元的参数也就是权重是不同的数值而已),所以这些连续函数的复合函数也是连续的。

   其次这个损失函数和我们的最终优化目标是“大致”一致的。比如C(w,b)趋于0时,它就要求y(x)趋于a,那么我们的分类也就趋于正确。当然可能存在一种极端的情况,比如有3个训练数据,第一组参数,它分类正确了2个训练数据,但是错的那1个错的很“离谱”,也就是y(x)和a差距极大;而第二组参数,他正确分类了1个训练数据,但是错的那两个都还不算太差。那么这种情况下MSE和正确率并不一致。

2.2.3 随机梯度下降(Stochastic Gradient Descent)和自动求梯度(Automatic Derivatives)

   上面说了,我们有了一个参数化的模型,训练的过程就是根据训练数据和loss function,选择“最优”的参数,使得loss“最小”,这从数学上来讲就是一个优化问题。这看起来似乎不是什么值得一提的问题,也许你还记得微积 分里的知识,极值点的各种充分必要条件,比如必要条件是导数是0,然后直接把参数解出来。但在现实生活中的函数远比教科书里学到的复杂,很多模型都无法用 解析的方式求出最优解。所以现实的方法就是求“数值”解,一般最常见的方法就是迭代的方法,根据现在的参数,我们很小幅度的调整参数,使得loss变小一 点点。然后一步一步的最终能够达到一个最优解(一般是局部最优解)。那怎么小幅调整呢?像闷头苍蝇那样随机乱试显然效率极低。因此我们要朝着一个能使函数 值变小的方向前进。而在一个点能使函数值变小的方向有无穷多个,但有一个方向是下降速度最快的,那就是梯度。因此更常见的方法就是在当前点求函数的梯度, 然后朝着梯度的方向下降。朝梯度的方向走多远呢?一般走一个比较小的值是比较安全的,这个值就是“步长”。一般刚开始随机的初始化参数,loss比较大, 所以多走一些也没关系,但是到了后面,就不能走太快,否则很容易错过最优的点。

   因为loss是所有训练数据的函数,所以求loss的梯度需要计算所有的训练数据,对于很多task来说,训练数据可能上百万,计算一次代价太大,所以一 般会“随机”的采样少部分数据,比如128个数据,求它的梯度。虽然128个点的梯度和一百万个的是不一样的,但是从概率来讲至少是一致的方向而不会是相 反的方向,所以也能使loss变小。当然这个128是可以调整的,它一般被叫做batch size,最极端的就是batch是1和一百万,那么分别就是online learning和退化到梯度下降。batch size越大,计算一次梯度的时间就越久【当然由于GPU和各种类似SSE的指令,一次计算128个可能并不比计算1个慢多少】,随机梯度和真正梯度一致 的概率就越大,走的方向就更“正确”;batch size越小,计算一次的时间就越短,但可能方向偏离最优的方向就更远,会在不是“冤枉路”。但实际的情况也很难说哪个值是最优的,一般的经验取值都是几 十到一两百的范围,另外因为计算机都是字节对齐,32,64,128这样的值也许能稍微加快矩阵运算的速度。但是实际也很多人选择10,50,100这样 的值。

   除了常见的随机梯度下降,还有不少改进的方法,如Momentum,Adagrad等等,有兴趣的可以看看 http://cs231n.github.io/neural-networks-3/#update ,里面还有个动画,比较了不同方法的收敛速度的比较。

通过上面的分析,我们把问题变成了怎么求loss对参数W的梯度。

求梯度有如下4种方法:

1.手工求解析解

比如 f(x)=x^2, df/dx=2*x。然后我们要求f(x)在x=1.5的值,代进去就2*1.5=3

2.数值解

使用极限的定义:
011.png

3.机器符号计算

让机器做符号运算,实现1的方法,但是机器如果优化的不好的话可能会有一些不必要的运算。

比如 x^2 + 2*x*y + y^2,直接对x求导数变成了 2*x + 2*y,两次乘法一次加分,但是我们可以合并一下变成2*(x+y),一次乘法一次加分。

4.自动梯度

下面我会在稍微细讲一下,所以这里暂时跳过。

这些方法的优缺点:
 
  • 手工求解“数学”要求高,有可能水平不够求不对,但效率应该是能最优的。
  • 没任何函数,甚至没有解析导数的情况下都能使用,缺点是计算量太大,而且只是近似解【因为极限的定义】,在某些特别不“连续”的地方可能误差较大。所以实际使用是很少,只是用它来验证其它方法是否正确。
  • 机器符号计算,前面说的,依赖于这个库的好坏。


实际的框架,如TensorFlow就是自动梯度,而Theano就是符号梯度。

2.2.4 编程实战

   通过上面的介绍,我们其实就可以实现一个经典的前馈(feed forward)神经网络了,这种网络结构很简单,每一层的输入是前一层的输出。输入层没有输入,它就是原始的信号输入。而且上一层的所有神经元都会连接到下一层的所有神经元,就像我们刚才的例子,输入是784,中间层是15,那么就有785*15个连接【再加上每个中间节点有一个bias】。所以这种网络有时候也加做全连接的网络(full connected),用来和CNN这种不是全连接的网络有所区别,另外就是信号是从前往后传递,没有反馈,所以也叫前溃神经网络,这是为了和RNN这种有反馈的区别。

   当然,我们还没有讲怎么计算梯度,也就是损失函数相对于每一个参数的偏导数。在下一部分我们会详细讨论介绍,这里我们先把它当成一个黑盒的函数就好了。

1.代码
我们这里学习一下Nielsen提供的代码。代码非常简洁,只有不到100行代码。
https://github.com/mnielsen/ne ... rning

git clone https://github.com/mnielsen/ne ... g.git

2.运行

创建一个 test_network1.py,输入如下代码:
import mnist_loader
import network

training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
net = network.Network([784, 30, 10])
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
保存后直接运行 Python test_network1.py。这里我们让他进行了30次迭代,最终在测试数据上的准确率大概在95%左右(当然因为随机初始化参数不同,最终的结果可能有所不同)
Epoch 0: 8250 / 10000
Epoch 1: 8371 / 10000
Epoch 2: 9300 / 10000
......
Epoch 28: 9552 / 10000
Epoch 29: 9555 / 10000
3. 代码阅读

   Python代码很容易阅读,即使之前没有用过,稍微学习两天也就可以上手,而且大部分机器学习相关的代码不会用到太复杂的语言特性,基本就是一些数学的线性代数的运算。而Python的numpy这个库是用的最多的,后面阅读代码的时候我会把用到的函数做一些介绍,继续下面的阅读之前建议花十分钟阅读一下 http://cs231n.github.io/python-numpy-tutorial/

3.1 mnist_loader.load_data_wrapper函数

   这个函数用来读取mnist数据,数据是放在data/mnist.pkl.gz。首先这是个gzip的压缩文件,是Pickle工具序列化到磁盘的格式。不熟悉也没有关系,反正我们知道这个函数的返回值就行了。

   这个函数返回三个对象,分别代表training_data,validation_data和test_data。

   training_data是一个50,000的list,然后其中的每一个元素是一个tuple。tuple的第一个元素是一个784维的numpy一维数组。第二个元素是10维的数组,也就是one-hot的表示方法——如果正确的答案是数字0,那么这个10维数组就是(1, 0, 0, …)。

   而validation_data是一个10,000的list,每个元素也是一个tuple。tuple的第一个元素也是784维的numpy一维数组。第二个元素是一个0-9的数字,代表正确答案是那个数字。

   test_data的格式和validation_data一样。

   为什么training_data要是这样的格式呢?因为这样的格式计算loss更方便一些。

3.2 Network类的构造函数

   我们在调用net = network.Network([784, 30, 10])时就到了init函数。为了减少篇幅,代码里的注释我都去掉了,重要的地方我会根据自己的理解说明,但是有空还是值得阅读代码里的注释。
class Network(object):
def __init__(self, sizes):self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]
   比如上面的参数,我们保存下来的self.num_layers=3,也就是3层的网络。每一层的神经元的个数保存到self.sizes里。接下来就是构造biases数组并随机初始化。因为输入层是没有参数的,所以是for y in sizes[1:],我们使用了numpy的random.randn生成正态分布的随机数用来作为参数的初始值。注意这里生成了2维的随机变量。回忆一下,如果我们有30个hidden unit,那么bias的个数也是30,那就生成一个30维的1维数组就行了,为什么要是30*1的二维数组呢?其实用1维也可以,不过为了和weights一致,后面代码方便,就用二维数组了。另外weights也是一样的初始化方法,不过注意randn(y,x)而不是randn(x,y)。比如对于我们输入的[784,30,10],weights分别是30*784和10*30的。当然其实weights矩阵转置一下也可以,就是计算矩阵乘法的时候也需要有一个转置。不同的文献可能有不同的记法,但是我们在实现代码的时候只需要随时注意矩阵的大小,检查矩阵乘法满足乘法的约束就行了,矩阵AB能相乘,必须满足的条件是B的列数等于A的函数就行。

   对于Nielsen的记法,矩阵的每一行就是一个神经元的784个参数,那么weights(30*784) * input(784*1)就得到30个hidden unit的加权累加。

3.3 feedforward函数

   给点输入a(784维),计算最终神经网络的输出(10维)。
def feedforward(self, a):
"""Return the output of the network if ``a`` is input."""for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a)+b)
return a
   代码非常简单,这里用到了np.dot,也就是矩阵向量的乘法,此外这里有一个Sigmoid函数,这个函数的输入是numpy的ndarray,输出也是同样大小的数组,不过对于每个元素都进行了sigmoid的计算。用numpy的术语就是universal function,很多文献里一般都叫elementwise的function。我觉得后面这个名字更直接。
#### Miscellaneous functionsdef sigmoid(z):
"""The sigmoid function."""return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
"""Derivative of the sigmoid function."""return sigmoid(z)*(1-sigmoid(z))
上面就是Sigmoid函数,另外也把sigmoid_prime,也就是Sigmoid的导数放在了一起【不记得的话看前面Sigmoid的导数的推导】。

3.4 SGD函数

这个函数是训练的入口,比如我们之前的训练代码:
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)


def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
if test_data: n_test = len(test_data)
n = len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)]
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta)
if test_data:
print "Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test)
else:
print "Epoch {0} complete".format(j)
第一个参数就是training_data。

第二个参数就是epochs,也就是总共对训练数据迭代多少次,我们这里是30次迭代。

第三个参数是batch大小,我们这里是10,最后一个参数是eta,也就是步长,这里是3.0。除了网络结构(比如总共多少个hidden layer,每个hidder layer多少个hidden unit),另外一个非常重要的参数就是步长。前面我们也讨论过了,步长太小,收敛速度过慢,步长太大,可能不收敛。实际的情况是没有一个万能的准则,更多的是根据数据,不停的尝试合适的步长。如果发现收敛太慢,就适当调大,反之则调小。所以要训练好一个神经网络,还是有很多tricky的技巧,包括参数怎么初始化,激活函数怎么选择,比SGD更好的优化算法等等。

第四个参数test_data是可选的,如果有(我们的例子是穿了进来的),则每次epoch之后都测试一下。

代码的大致解释我用注释的形式嵌在代码里了:
  for j in xrange(epochs): ## 一共进行 epochs=30 轮迭代
random.shuffle(training_data) ## 训练数据随机打散
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)] ## 把50,000个训练数据分成5,000个batch,每个batch包含10个训练数据。
for mini_batch in mini_batches: ## 对于每个batch
self.update_mini_batch(mini_batch, eta) ## 使用梯度下降更新参数
if test_data: ## 如果提供了测试数据
print "Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test) ## 评价在测试数据上的准确率
else:
print "Epoch {0} complete".format(j)
下面是evaluate函数:
def evaluate(self, test_data):
test_results = [(np.argmax(self.feedforward(x)), y)
for (x, y) in test_data]
return sum(int(x == y) for (x, y) in test_results)
   对于test_data里的每一组(x,y),y是0-9之间的正确答案。而self.feedforward(x)返回的是10维的数组,我们选择得分最高的那个值作为模型的预测结果np.argmax就是返回最大值的下标。比如x=[0.3, 0.6, 0.1, 0, ….],那么argmax(x) = 1。

   因此test_results这个列表的每一个元素是一个tuple,tuple的第一个是模型预测的数字,而第二个是正确答案。

所以最后一行返回的是模型预测正确的个数。
 
3.5 update_mini_batch函数
def update_mini_batch(self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
它的输入参数是mini_batch【size=10的tuple(x,y)】和eta【3.0】。
def update_mini_batch(self, mini_batch, eta):
nabla_b = [np.zeros(b.shape) for b in self.biases]
## 回忆一下__init__,biases是一个列表,包含两个矩阵,分别是30*1和10*1
## 我们先构造一个和self.biases一样大小的列表,用来存放累加的梯度(偏导数)
nabla_w = [np.zeros(w.shape) for w in self.weights]
## 同上, weights包含两个矩阵,大小分别是30*784和10*30
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
## 对于一个训练数据(x,y)计算loss相对于所有参数的偏导数
## 因此delta_nabla_b和self.biases, nabla_b是一样大小(shape)
## 同样delta_nabla_w和self.weights,nabla_w一样大小
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
## 把bias的梯度累加到nabla_b里
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
## 把weight的梯度累加到nable_w里
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
## 使用这个batch的梯度和eta(步长)更新参数weights
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
## 更新biases
## 这里更新参数是除了batch的大小(10),有的人实现时不除,其实没有什么区别,因为超参数eta会有所不同,如果不除,那么eta相当于是0.3(在eta那里就除了batch的大小了)。
3.6 backprop函数

这个函数就是求loss相对于所有参数的偏导数,这里先不仔细讲解,等下次我们学习梯度的求解方法我们再回来讨论,这里可以先了解一下这个函数的输入和输出,把它当成一个黑盒就行,其实它的代码也很少,但是如果不知道梯度的公式,也很难明白。
def backprop(self, x, y):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforwardactivation = x
activations = [x] # list to store all the activations, layer by layerzs = [] # list to store all the z vectors, layer by layerfor b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward passdelta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
for l in xrange(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
   它的输入就是一个训练样本(x,y)分别是784*1和10*1。输出就是和self.biases,self.weights一样大小的列表,然后列表中的每一个数组的大小也是一样。具体到上面的例子,输出nabla_b包含两个矩阵,大小分别是30*1和10*1;nabla_w也包含两个矩阵,大小分别是30*784和10*30。
 
未完待续

作者简介:李理,目前就职于环信,即时通讯云平台和全媒体智能客服平台,在环信从事智能客服和智能机器人相关工作,致力于用深度学习来提高智能机器人的性能。
责编:周建丁(zhoujd@csdn.net